immcheck

package module
v0.0.0-...-bf4f929 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 21, 2022 License: CC0-1.0 Imports: 11 Imported by: 0

README

immcheck - Runtime immutability checks library

Go Reference Build Linters Go Report Card Coverage Status

Example:

m := map[string]string{
    "k1": "v1",
}
m["k2"] = "v2"
// returns function that you can call to verify that m didn't change
checkFunction := immcheck.EnsureImmutability(&m)
checkFunction() // no mutations are fine

func() {
    defer func() {
        mutationPanic := recover()
        fmt.Println(mutationPanic)
    }()

    // you can also use defer for such checks
    // now when we mutate m, we will get panic at the end of the function
    defer immcheck.EnsureImmutability(&m)()

    // it is also possible to set a finalizer that can check
    // if object remained immutable from this point till garbage collection
    immcheck.CheckImmutabilityOnFinalization(&m)
    
    // this function works only with `-race` or `-tags immcheck` build flags
    defer immcheck.RaceEnsureImmutability(&m)()

    // this function works only with `-race` or `-tags immcheck` build flags as well
    immcheck.RaceCheckImmutabilityOnFinalization(&m)

    delete(m, "k1")
}()
Brief description of how it works internally and how it affects the performance of your program

The library uses reflection to walk the tree of all reachable pointers, starting from the pointer you provided, and stores checksums of every encountered value into a map[uint32]uint32. When it is time to check immutability, it walks the same structure, collects the same map of checksums, and verifies that new map is equal to the previous one. From the performance standpoint of view, the library does a lot of tricks and optimizations to make the overhead as low as possible. For example:

  • it uses memory pooling for these maps of checksums and some internal buffers
  • it avoids allocations everywhere where possible, though some reflection API calls require allocations (with 1.18 we will be able to get rid of those that remain right now)
  • it treats slices of pointerless structures as just one contiguous value, so it hashes such slices efficiently and uses only one item in the checksums map to store its hash

In general, performance overhead will depend on what kind of structures you're declaring as immutable and how deeply nested they are. For most applications, the overhead should be non-noticeable or at least bearable. If performance is a concern though: you can use RaceEnsureImmutability methods that will have 0 overhead in normal builds and will perform checks only when race detector is enabled or if you build your program with -tags immcheck build flag

Documentation

Index

Constants

View Source
const (
	MutationDetectedError     mutationDetectionError = "mutation of immutable value detected"
	InvalidSnapshotStateError mutationDetectionError = "invalid snapshot state"
	UnsupportedTypeError      mutationDetectionError = "unsupported type for immutability check"
)
View Source
const (
	// SkipOriginCapturing forces immcheck to not capture caller information to report snapshot origin.
	// This option gives a tiny bit more performance.
	SkipOriginCapturing immutabilityCheckFlag = 1 << iota
	// AllowInherentlyUnsafeTypes forces immcheck to allow reflect.UnsafePointer, reflect.Func and reflect.Chan
	// inside target value.
	AllowInherentlyUnsafeTypes
	// SkipPanicOnDetectedMutation forces immcheck to not panic in
	// immcheck.EnsureImmutability and immcheck.CheckImmutabilityOnFinalization methods when mutation is detected.
	SkipPanicOnDetectedMutation
	// SkipLoggingOnMutation forces immcheck to not log details of found mutation
	// in immcheck.EnsureImmutability and immcheck.CheckImmutabilityOnFinalization methods.
	SkipLoggingOnMutation
)
View Source
const ImmcheckRaceEnabled = false

ImmcheckRaceEnabled can be used in test to verify if mutability should be detected or not.

Variables

This section is empty.

Functions

func CheckImmutabilityOnFinalization

func CheckImmutabilityOnFinalization(v interface{})

CheckImmutabilityOnFinalization captures checksum of v and sets finalizer on v to check if it was mutated during its lifetime. If mutation is detected finalizer will log details and panic which will stop the process. If you don't want to exit on detected mutation use immcheck.CheckImmutabilityOnFinalizationWithOptions and override default flags.

func CheckImmutabilityOnFinalizationWithOptions

func CheckImmutabilityOnFinalizationWithOptions(v interface{}, options Options)

CheckImmutabilityOnFinalizationWithOptions captures checksum of v and sets finalizer on v to check if it was mutated during its lifetime. If mutation is detected finalizer will log details and panic which will stop the process. If you don't want to exit on detected mutation override default flags.

func EnsureImmutability

func EnsureImmutability(v interface{}) func()

EnsureImmutability captures checksum of v and returns function that can be called to verify that v was not mutated. Returned function can be called multiple times. If mutation is detected returned function will panic.

func EnsureImmutabilityWithOptions

func EnsureImmutabilityWithOptions(v interface{}, options Options) func()

EnsureImmutabilityWithOptions captures checksum of v according to settings specified in options and returns function that can be called to verify that v was not mutated. Returned function can be called multiple times. If mutation is detected returned function will panic.

func RaceCheckImmutabilityOnFinalization

func RaceCheckImmutabilityOnFinalization(v interface{})

RaceCheckImmutabilityOnFinalization same as immcheck.CheckImmutabilityOnFinalization but works only under `race` or `immcheck` build flags.

func RaceCheckImmutabilityOnFinalizationWithOptions

func RaceCheckImmutabilityOnFinalizationWithOptions(v interface{}, options Options)

RaceCheckImmutabilityOnFinalizationWithOptions same as immcheck.CheckImmutabilityOnFinalizationWithOptions but works only under `race` or `immcheck` build flags.

func RaceEnsureImmutability

func RaceEnsureImmutability(v interface{}) func()

RaceEnsureImmutability same as immcheck.EnsureImmutability but works only under `race` or `immcheck` build flags.

func RaceEnsureImmutabilityWithOptions

func RaceEnsureImmutabilityWithOptions(v interface{}, options Options) func()

RaceEnsureImmutabilityWithOptions same as immcheck.EnsureImmutabilityWithOptions but works only under `race` or `immcheck` build flags.

Types

type Options

type Options struct {
	// Specifies logger output stream. Can be nil. immcheck uses os.Stderr by default.
	LogWriter io.Writer
	// Bitmask of ImmutabilityCheckFlags.
	// You can specify it like that: SkipOriginCapturing | SkipLoggingOnMutation | AllowInherentlyUnsafeTypes
	Flags immutabilityCheckFlag
}

Options configures immutability check.

type ValueSnapshot

type ValueSnapshot struct {
	// contains filtered or unexported fields
}

ValueSnapshot is a re-usable object of snapshot value that works similar to bytes.Buffer. You can create new ValueSnapshot object using immcheck.NewValueSnapshot method. Capture snapshots into it using immcheck.CaptureSnapshot or immcheck.CaptureSnapshotWithOptions. Then you can compare snapshots using ValueSnapshot.CheckImmutabilityAgainst method. Then you can re-use snapshots by calling ValueSnapshot.Reset. This approach can help you to avoid extra allocations.

func CaptureSnapshot

func CaptureSnapshot(v interface{}, dst *ValueSnapshot) *ValueSnapshot

CaptureSnapshot creates lightweight checksum representation of v and stores if into dst. Returns modified dst object.

func CaptureSnapshotWithOptions

func CaptureSnapshotWithOptions(v interface{}, dst *ValueSnapshot, options Options) *ValueSnapshot

CaptureSnapshotWithOptions creates lightweight checksum according to settings specified in options, representation of v and stores if into dst. Returns modified dst object.

func NewValueSnapshot

func NewValueSnapshot() *ValueSnapshot

NewValueSnapshot creates new re-usable object of snapshot object.

func (*ValueSnapshot) CheckImmutabilityAgainst

func (v *ValueSnapshot) CheckImmutabilityAgainst(otherSnapshot *ValueSnapshot) error

CheckImmutabilityAgainst verifies that otherSnapshot is exactly the same as this one. Returns immcheck.MutationDetectedError if snapshots are different.

func (*ValueSnapshot) Reset

func (v *ValueSnapshot) Reset()

Reset clear internal state of ValueSnapshot, so it can be re-used.

func (*ValueSnapshot) String

func (v *ValueSnapshot) String() string

String provides string representation of ValueSnapshot.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL