assertiontree

package
v0.0.0-...-06f1eed Latest Latest
Warning

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

Go to latest
Published: Apr 24, 2024 License: Apache-2.0 Imports: 21 Imported by: 3

Documentation

Overview

Package assertiontree contains the node definitions for the assertion tree, as well as the main backpropagation algorithm.

Index

Constants

View Source
const BuiltinAppend = "append"

BuiltinAppend is used to check the builtin append method for slice

View Source
const BuiltinNew = "new"

BuiltinNew is used to check the builtin `new` function

Variables

This section is empty.

Functions

func AsTrustedFuncAction

func AsTrustedFuncAction(expr ast.Expr, p *analysis.Pass) (any, bool)

AsTrustedFuncAction checks a function call AST node to see if it is one of the trusted functions, and if it is then runs the corresponding action and returns that as the output along with a bool indicating success or failure. For example, a binary expression `x != nil` is returned for trusted function `assert.NotNil(t, x)`, while a `TrustedFuncNonnil` producer is returned for `errors.New(s)`

func BackpropAcrossFunc

func BackpropAcrossFunc(ctx context.Context, pass *analysis.Pass, decl *ast.FuncDecl,
	functionContext FunctionContext, graph *cfg.CFG) ([]annotation.FullTrigger, error)

BackpropAcrossFunc is the main driver of the backpropagation, it takes a function declaration with accompanying CFG, and back-propagates a tree of assertions across it to generate, at entry to the function, the set of assertions that must hold to avoid possible nil flow errors.

func CheckGuardOnFullTrigger

func CheckGuardOnFullTrigger(trigger annotation.FullTrigger) annotation.FullTrigger

CheckGuardOnFullTrigger gives guarding its intended semantics: if a full trigger would be created with a guarded producer but not a guarded consumer, then the production as written in the trigger is ignored and replaced with an always-nilable-producing instance of annotation.GuardMissing

func FilterTriggersForErrorReturn

func FilterTriggersForErrorReturn(
	triggers []annotation.FullTrigger,
	computeProducerNilability func(p *annotation.ProduceTrigger) ProducerNilability,
) (filteredTriggers []annotation.FullTrigger, deletedTriggers map[annotation.FullTrigger]bool)

FilterTriggersForErrorReturn analyzes return expression triggers of error returning functions to filter out redundant triggers based on the error contract that were earlier conservatively added in `handleErrorReturns`. The function operates in two steps: (1) infer nilability status of the error return expression based on the producers for its corresponding trigger, and (2) remove redundant triggers based on the inferred nilability of error return, and appropriately update consumers of the remaining triggers

FilterTriggersForErrorReturn takes two arguments: (1) set of triggers that need to be filtered. These are function-level triggers for intra-procedural analysis, and package-level for inter-procedural analysis (2) a computeProducerNilability that defines how to compute the nilability status of a given producer. Particularly, we are interested in knowing the nilability of the producer of the error return consumer (`UseAsErrorRetWithNilabilityUnknown`). This argument is needed since the computation differs from intra-procedural to inter-procedural analysis, as well as between different modes of inference.

FilterTriggersForErrorReturn produces two outputs: (1) final set of triggers that is filtered and refined by replacing consumers; (2) raw set of deleted triggers (nil if there are no deleted triggers).

func GetDeclaringPath

func GetDeclaringPath(pass *analysis.Pass, start, end token.Pos) ([]ast.Node, bool)

GetDeclaringPath finds the path of nested AST nodes beginning with the passed interval `[start, end]`

Types

type AssertionNode

type AssertionNode interface {
	Parent() AssertionNode
	Children() []AssertionNode
	ConsumeTriggers() []*annotation.ConsumeTrigger

	SetParent(AssertionNode)
	SetChildren([]AssertionNode)
	SetConsumeTriggers([]*annotation.ConsumeTrigger)

	// DefaultTrigger determines the ProducingAnnotationTrigger that produces this
	// value as a last resort - called when a tracked value is determined to only be
	// producible only by read of its default. An example case of calling this method
	// is that a lingering ConsumeTrigger resides at x.f in the tree when x is assigned
	// into by a non-trackable expression. Then that ConsumeTrigger will be matched with
	// the result of this method as a ProduceTrigger, which in particular will be an
	// annotation.FldRead. See implementations for full range of cases.
	// It is called from two places. First, at the process entry it is used to match all
	// the unmatched consumers. Second, is for consuming all the nodes in subtree of the
	// node if the node gets matched.
	DefaultTrigger() annotation.ProducingAnnotationTrigger

	// BuildExpr takes an expression, and builds a new one by wrapping it in a new AST expression
	// corresponding to this node
	// nilable(param 1)
	BuildExpr(ast.Expr) ast.Expr

	// Root returns the RootAssertionNode at the root of the tree this assertion node is part of,
	// if it is part of such a tree - otherwise returns nil
	// nilable(result 0)
	Root() *RootAssertionNode

	// Size returns an integer representing the number of objects in the tree
	// rooted at this AssertionNode - its use case is to determine whether
	// an AssertionNode has grown after a merge
	Size() int

	// MinimalString returns a minimal string representation of this assertion node
	// This is primarily for use when printing as part of a trackable expression chain,
	// such as f.g()[i].x
	MinimalString() string
}

An AssertionNode is the root of a tree of assertions, so it contains parent and child pointers, as well as a set of "ConsumeTriggers" - these are half assertions representing a point at which nil may be erroneously consumed and which annotation should be checked to see if that consumption is in fact erroneous. Their position in the tree gives the expression that they are asserting should possibly be non-nil TODO: make more efficient by having children and triggers be a keyed map instead of a slice

func CopyNode

func CopyNode(node AssertionNode) AssertionNode

CopyNode computes a deep code of an AssertionNode precondition: node is not nil

type ChannelOkRecv

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

A ChannelOkRecv is a RichCheckEffect for the `ok` in `v, ok := <-chan` assignment. To match such an assignment, both the `v` and the `ok` must be identifiers, and to have the intended effect, an `if ok { }` must be encountered before an assignment to either `v` or `ok`.

Possible future extensions to the robustness of this effect would be to track the flow of `v` and `ok` instead of just giving up when flow (i.e. assignment) occurs, and to expand the allowed language of `v` and `ok` from identifiers to trackable expressions.

type ChannelOkRecvRefl

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

A ChannelOkRecvRefl indicates that a channel receive was encountered with a `v, ok := <-chan` assignment, and now if `ok` is checked it should produce non-nil for `chan` because it cannot be nil if `ok` is true.

type FuncErrRet

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

A FuncErrRet is a RichCheckEffect for the `err` in `r0, r1, r2, ..., err := f()`, where the function `f` has a final result of type `error` - and until this is checked all other results are assumed nilable

For proper invalidation, each stored return of a function is treated as a separate effect

type FuncOkReturn

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

A FuncOkReturn is a RichCheckEffect for the `ok` in `r0, r1, r2, ..., ok := f()`, where the function `f` has a final result of type `bool` - and until this is checked all other results are assumed nilable. For proper invalidation, each stored return of a function is treated as a separate effect

type FunctionConfig

type FunctionConfig struct {
	// EnableStructInitCheck is a flag to enable tracking struct initializations.
	EnableStructInitCheck bool
	// EnableAnonymousFunc is a flag to enable checking anonymous functions.
	EnableAnonymousFunc bool
}

FunctionConfig is meant to hold all the user set configuration for analyzing a function

type FunctionContext

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

FunctionContext holds the context of the function during backpropagation. The state should include function declaration, map objects that are created at initialization, and configurations that are passed through function analyzer.

func NewFunctionContext

func NewFunctionContext(
	pass *analysis.Pass,
	decl *ast.FuncDecl,
	funcLit *ast.FuncLit,
	functionConfig FunctionConfig,
	funcLitMap map[*ast.FuncLit]*anonymousfunc.FuncLitInfo,
	pkgFakeIdentMap map[*ast.Ident]types.Object,
	funcContracts functioncontracts.Map,
) FunctionContext

NewFunctionContext returns a new FunctionContext and initializes all the maps

func (*FunctionContext) AddFakeIdent

func (fc *FunctionContext) AddFakeIdent(ident *ast.Ident, obj types.Object)

AddFakeIdent adds fake ident to fakeIdentMap

type GuardMatchBehavior

type GuardMatchBehavior = int

GuardMatchBehavior as a type represents the set of possible effects of obtaining a guard match.

const (
	// ContinueTracking is a GuardMatchBehavior indicating that the field
	// GuardMatched should be set to true and the ConsumeTrigger that was matched should
	// otherwise be left in the assertion tree to flow through the function
	ContinueTracking GuardMatchBehavior = iota

	// ProduceAsNonnil is a GuardMatchBehavior indicating that the ConsumeTrigger
	// that was matched should be treated as nonnil-produced at this point, using the
	// trigger OkReadReflCheck
	ProduceAsNonnil
)

type MapOkRead

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

A MapOkRead is a RichCheckEffect for the `ok` in `v, ok := m[k]` assignment. To match such an assignment, both the `v` and the `ok` must be identifiers, and to have the intended effect, an `if ok { }` must be encountered before an assignment to either `v` or `ok`.

Possible future extensions to the robustness of this effect would be to track the flow of `v` and `ok` instead of just giving up when flow (i.e. assignment) occurs, and to expand the allowed language of `v` and `ok` from identifiers to trackable expressions.

type MapOkReadRefl

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

A MapOkReadRefl indicates that a map was read in a `v, ok := m[k]` assignment, and now if `ok` is checked it should produce non-nil for `m` because it cannot be nil if `ok` is true.

type ProducerNilability

type ProducerNilability uint8

ProducerNilability is a type to denote the nilability status of the producer.

const (
	// ProducerNilabilityUnknown is the default value when a producer's nilability is not guaranteed to be nil or nonnil --> TriggerIfNilable and TriggerifDeepNilable
	ProducerNilabilityUnknown ProducerNilability = iota
	// ProducerIsNil is when the producer is guranteed to produce a nil value --> ProduceTriggerTautology
	ProducerIsNil
	// ProducerIsNonNil is when the producer is guranteed to produce a non-nil value --> ProduceTriggerNever
	ProducerIsNonNil
)

type RichCheckEffect

type RichCheckEffect interface {
	// contains filtered or unexported methods
}

A RichCheckEffect is the fact that a certain check is associated with an effect that can be triggered by a conditional, for example the `ok` in `v, ok := m[k]`

the functions `effectIfTrue` and `effectIfFalse` are analogous to the respective returns from `AddNilCheck` - functions that are marked as preprocessing at the beginning of successor blocks to a conditional that matches the trigger. In this case, an expression in a conditional matching the trigger is determined by the interface function `isTriggeredBy`. There are certain statements that, if encountered between the establishment of the RichCheckEffect and the trigger, invalidate its effect. For example, for the `ok` in `v, ok := m[k]`, an assignment to either `v` or `ok` invalidates the effect. Whether an expression invalidates this effect is determined by the interface function `isInvalidatedBy`.

func NodeTriggersFuncErrRet

func NodeTriggersFuncErrRet(rootNode *RootAssertionNode, nonceGenerator *util.GuardNonceGenerator, node ast.Node) ([]RichCheckEffect, bool)

NodeTriggersFuncErrRet is a case of a node creating a rich check effect. it matches on calls to functions with error-returning types

func NodeTriggersOkRead

func NodeTriggersOkRead(rootNode *RootAssertionNode, nonceGenerator *util.GuardNonceGenerator, node ast.Node) ([]RichCheckEffect, bool)

NodeTriggersOkRead is a case of a node creating a rich bool effect for map reads, channel receives, and user-defined functions in the "ok" form. Specifically, it matches on `AssignStmt`s of the form - `v, ok := mp[k]` - `v, ok := <-ch` - `r0, r1, r2, ..., ok := f()`

func RichCheckFromNode

func RichCheckFromNode(rootNode *RootAssertionNode, nonceGenerator *util.GuardNonceGenerator, node ast.Node) ([]RichCheckEffect, bool)

RichCheckFromNode analyzes the passed `ast.Node` to see if it generates a rich check effect. If it does, that effect is returned along with the boolean true If it does not, then `nil, false` is returned.

type RichCheckNoop

type RichCheckNoop struct{}

A RichCheckNoop is a placeholder instance of RichCheckEffect that functions as a total noop. It is used to allow in place modification of collections of RichCheckEffects.

type RootAssertionNode

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

RootAssertionNode is the object that will be directly handled by the propagation algorithm, their only children should be VarAssertionNodes and FuncAssertionNodes

the triggers field keeps track of productions and consumptions that have been directly matched its consumeTriggers field should be kept empty

//nilable(funcObj)

func (*RootAssertionNode) AddComputation

func (r *RootAssertionNode) AddComputation(expr ast.Expr)

AddComputation takes the knowledge that the expression expr has to be computed to generate any necessary assertions to ensure that the access is safe. This will take the form of nested calls to AddConsumption

basic semantics: any ast node with an ast.Expr field recurs into that field

func (*RootAssertionNode) AddConsumption

func (r *RootAssertionNode) AddConsumption(consumer *annotation.ConsumeTrigger)

AddConsumption takes the knowledge that consumer.expr will be consumed at a site characterized by the trigger consumer.annotation, and incorporate it into the assertion tree self

func (*RootAssertionNode) AddGuardMatch

func (r *RootAssertionNode) AddGuardMatch(expr ast.Expr, behavior GuardMatchBehavior)

AddGuardMatch takes an expression, and sees if that expression is mapped to a nonce indicating a RichCheckEffect that has been propagated from the concrete site of a check to the earlier site whose nilability semantics depend on that check. If it is mapped to a nonce, it sees if that expression is also present in the assertion tree with a consume trigger guarded by that nonce. This indicates that the flow we were looking for - for example, from `v` in `v, ok := m[k]` to `if ok {needsNonnil(v)}` - exists. The function takes a `GuardMatchBehavior` indicating what to do if the guard is found, for now, either continue tracking its expression or produce it as nonnil.

To elaborate further, here is a complete rundown of the guarding mechanism.

During preprocessing (preprocess_blocks.go) some statements are identified as producing a `RichCheckEffect` - a contract indicating that certain conditionals later in the program should have an effect on the semantics of that earlier statement. As an example, if `v, ok := m[k]` is encountered, then regardless of the deep nilability of `m`, `v` will be nilable. However, if `ok` is checked later in the program, it will be exactly as nilable as `m` is deeply nilable. This non-local reliance is propagated in the form of a RichCheckEffect that takes a GuardNonce uniquely generated corresponding to the AST node `v` at that site, and indicates that any time the expression `ok` is checked to be true, `v` should have that GuardNonce added to the set `Guards` of all of its `ConsumeTrigger`s. This indicates that those consumptions occur in a context "guarded" by that check. These `Guards` sets are intersected at control flow points (see `MergeConsumeTriggerSlices`), to ensure that the presence of a guard on a consumer really does indicate that it only occurs in a context in which the appropriate check has been made.

This intersecting guard propagation then ensures that by the time any `ConsumeTrigger`s reach the statement that was dependent on the associated nonce, they will contain the information of whether they are properly guarded by that nonce. For example, in the below code snippet, line 1 will associate a nonce with `v`, to be applied when `ok` is checked. That contract will be propagated to the check on line 3 by a RichCheckEffect, so when backpropagation occurs across that positive branch of the check, it will see that `v` has two ConsumeTriggers, one generated by line 4 and one generated by line 7, and apply the nonce guard to both. However, on unifying the two branches, it will see that the ConsumeTrigger generated on line 7 is present on both sides, so it will intersect the Guards sets on each side and erase the nonce. Two ConsumeTriggers will then reach line 1, one from line 4 and one from line 7, but only the one from line 4 will have the appropriate nonce in its Guards set.

```

1 v, ok := m[k]
2
3 if ok {
4    consume(v)
5 }
6
7 consume(v)

```

The role of this function, AddGuardMatch, is to look at an expression, take all the ConsumeTriggers for that expression in the current assertion tree, and set GuardMatched to true for them if they have the appropriate nonce in their Guards set. In the above example, this function would be called when backpropagating across line 1 with `v` for expr. The appropriate nonce would be found, and this function would see that it is present for 4's ConsumeTrigger but not 7's. Thus 4's would get GuardMatched set to true and 7's would not. If both of these ConsumeTriggers flowed to the beginning of the program, then they would get matched with a default ProduceTrigger as a deep read of `m`, which `checkGuardOnFullTrigger` would invalidate unless paired with a ConsumeTrigger with GuardMatched = true. GuardMatched for a ConsumeTrigger takes a conjunction over all paths from that production site to that ConsumeTrigger, so it is true iff the trigger has had every guard in its Guards set required every time it has passed through a contract-generating statement on any path.

This description characterizes the `ContinueTracking` behavior. A simpler alternative, `ProduceAsNonnil`, indicates that if the appropriate nonce is found in a ConsumeTrigger's Guards set, the ConsumeTrigger should be matched immediately with a ProduceTrigger indicating nonnil production. This behavior is appropriate, for example, for the map itself in a read `v, ok := m[k]` - where consumptions of `m` guarded by a check `ok == true` are guaranteed to be produced as nonnil

func (*RootAssertionNode) AddNewTriggers

func (r *RootAssertionNode) AddNewTriggers(newTrigger ...annotation.FullTrigger)

AddNewTriggers adds the given new triggers to the existing set of triggers of this node

func (*RootAssertionNode) AddProduction

func (r *RootAssertionNode) AddProduction(producer *annotation.ProduceTrigger, deeperProducer ...*annotation.ProduceTrigger)

AddProduction takes the knowledge that producer.expr will have a value produced by the trigger producer.annotation, and incorporates it into the assertion tree rootNode

func (*RootAssertionNode) BuildExpr

func (r *RootAssertionNode) BuildExpr(_ ast.Expr) ast.Expr

BuildExpr is not well defined for root nodes

func (*RootAssertionNode) Children

func (n *RootAssertionNode) Children() []AssertionNode

func (*RootAssertionNode) ConsumeTriggers

func (n *RootAssertionNode) ConsumeTriggers() []*annotation.ConsumeTrigger

func (*RootAssertionNode) DefaultTrigger

DefaultTrigger is not well defined for root nodes

func (*RootAssertionNode) Equal

func (r *RootAssertionNode) Equal(a, b TrackableExpr) bool

Equal returns true iff a is the same path as b nilable(a, b)

func (*RootAssertionNode) FuncDecl

func (r *RootAssertionNode) FuncDecl() *ast.FuncDecl

FuncDecl returns the underlying function declaration of this node

func (*RootAssertionNode) FuncNameIdent

func (r *RootAssertionNode) FuncNameIdent() *ast.Ident

FuncNameIdent returns the function name identifier node

func (*RootAssertionNode) FuncObj

func (r *RootAssertionNode) FuncObj() *types.Func

FuncObj returns the underlying function declaration of this node as a types.Func

func (*RootAssertionNode) GetDeclaringIdent

func (r *RootAssertionNode) GetDeclaringIdent(obj types.Object) *ast.Ident

GetDeclaringIdent finds the identifier that serves as the declaration of the passed object

func (*RootAssertionNode) GetNonce

func (r *RootAssertionNode) GetNonce(expr ast.Expr) (util.GuardNonce, bool)

GetNonce returns the nonce associated with the passed expression, if one exists. the boolean return indicates whether a nonce was found

func (*RootAssertionNode) GetTriggers

func (r *RootAssertionNode) GetTriggers() []annotation.FullTrigger

GetTriggers returns the full triggers accumulated at this root node

func (*RootAssertionNode) HasContract

func (r *RootAssertionNode) HasContract(funcObj *types.Func) bool

HasContract returns if the given function has any contracts.

func (*RootAssertionNode) IsPrefix

func (r *RootAssertionNode) IsPrefix(a, b TrackableExpr) bool

IsPrefix returns true iff a is a prefix of b

func (*RootAssertionNode) IsStrictPrefix

func (r *RootAssertionNode) IsStrictPrefix(a, b TrackableExpr) bool

IsStrictPrefix returns true iff a is a prefix of b and a does not equal b

func (*RootAssertionNode) LandAtPath

func (r *RootAssertionNode) LandAtPath(path TrackableExpr, node AssertionNode)

LandAtPath takes a `path` of assertion nodes, and another target `node`, and places that target into the assertion tree rooted at `rootNode` at the location specified by `path`. It fails only if `path` is nil.

This is used as the second half of an assignment between trackable expressions. For information on why this is done, and an example of how to complete an entire assignment, see `LiftFromPath`'s documentation.

func (*RootAssertionNode) LiftFromPath

func (r *RootAssertionNode) LiftFromPath(path TrackableExpr) (AssertionNode, bool)

LiftFromPath takes a `path` of assertion nodes, and searches for it in the assertion tree rooted at `rootNode`. If found, it removes that tree and returns its root as `node`, with `ok` = true. If not found, it returns `node`, `ok` = nil, false

This is used as the first half of an assignment between trackable expressions. The two halves are kept separate to allow them to be separated into two parallel phases in the case of multiple assignments, but for illustrative purposes, here is how a self-contained single assignment method would look:

```

func (rootNode *RootAssertionNode) AddAssignment(dstpath, srcpath TrackableExpr) {
	node, ok := rootNode.LiftFromPath(dstpath)
	if ok {
		rootNode.LandAtPath(srcpath, node)
	}
}

```

func (*RootAssertionNode) LocationOf

func (r *RootAssertionNode) LocationOf(expr ast.Expr) token.Position

LocationOf returns the location of the given expression.

func (*RootAssertionNode) MinimalString

func (r *RootAssertionNode) MinimalString() string

MinimalString for a RootAssertionNode returns a minimal string representation of that root node

func (*RootAssertionNode) ObjectOf

func (r *RootAssertionNode) ObjectOf(ident *ast.Ident) types.Object

ObjectOf is the same as types.Info.ObjectOf, but if an identifier cannot be looked up (e.g., it is an artificial identifier we created to aid the analysis), we look up the internal backup map instead. ObjectOf returns nil if and only if both attempts fail.

func (*RootAssertionNode) Parent

func (n *RootAssertionNode) Parent() AssertionNode

func (*RootAssertionNode) ParseExprAsProducer

func (r *RootAssertionNode) ParseExprAsProducer(expr ast.Expr, doNotTrack bool) (
	shallowSeq TrackableExpr, producers []producer.ParsedProducer)

ParseExprAsProducer takes an expression, and determines whether it is `trackable` - i.e. if it is a linear sequence of variable reads, field reads, indexes by `stable` expressions, and function calls with `stable` arguments. An expression is `stable` if our static analysis assume that multiple syntactic occurrences of it will always yield the same value - i.e. they are assumed to be constant.

This function and the cases in which it returns a sequence of nodes serve as our internal definition of `trackable`, and similarly the function isStable below serves as our internal definition of `stable`.

Of its two return values, shallowSeq and producer, only one will be non-nil

In the case that expr is trackable, shallowSeq will be non-nil, and contain the AssertionNodes without pointers between them that characterize the give expression.

In the case that expr is not trackable, shallowSeq will be nil. If expr is known to be non-nil (e.g. a non-nil constant) then producer will be nil too, but otherwise it will be a slice of produceTriggers encapsulating the conditions under which expr could be nil. The slice will have length 1 for every expr except multiply returning functions, for which it will have length equal to the number of returns of that function.

The function also takes a flag doNotTrack which, if set to true, always treats the expr as non-trackable and gives its producer trigger or nil if it's not a nilable expression.

ParseExprAsProducer will panic if passed the empty expression `_`

nilable(shallowSeq) nilable(producers)

TODO: split this up into smaller functions with more granular documentation

func (*RootAssertionNode) Pass

func (r *RootAssertionNode) Pass() *analysis.Pass

Pass the overarching analysis pass

func (*RootAssertionNode) ProcessEntry

func (r *RootAssertionNode) ProcessEntry()

ProcessEntry is called when an assertion tree is known to have reached the entry to its function It takes any remaining assertions (consumeTriggers) and conclusively resolves them (see for len(self.Children()) > 0) condition by: - producing all parameters to the function from their appropriate annotations (paramAnnotationKey) - producing all non-parameter variables as definitely nil (noVarAssign) - producing all remaining function assertions according to their annotation (retAnnotationKey)

func (*RootAssertionNode) Root

Root for a RootAssertionNode is the identity function

func (*RootAssertionNode) SetChildren

func (n *RootAssertionNode) SetChildren(nodes []AssertionNode)

func (*RootAssertionNode) SetConsumeTriggers

func (n *RootAssertionNode) SetConsumeTriggers(triggers []*annotation.ConsumeTrigger)

func (*RootAssertionNode) SetParent

func (n *RootAssertionNode) SetParent(other AssertionNode)

func (*RootAssertionNode) Size

func (r *RootAssertionNode) Size() int

Size for a RootAssertionNode also includes the full triggers

type RootFunc

type RootFunc = func(*RootAssertionNode)

RootFunc is a function type taking a RootAssertionNode pointer as a parameter

func AddNilCheck

func AddNilCheck(pass *analysis.Pass, expr ast.Expr) (trueCheck, falseCheck RootFunc, isNoop bool)

AddNilCheck takes the knowledge that an expression `expr` was evaluated as part of a conditional and incorporates it into the assertion tree by producing non-nil or nil at expr, if expr is trackable

this function does not have to handle boolean operators or short circuiting because that is done as a restructuring pass on the CFG itself (see restructureBlocks)

Notably, it is "curried" - the expression and branch identifier are passed first, followed by the assertion node being modified. This is so that the processing for it can be done at most once

It returns two functions, the first: `trueCheck`, can be called on a *RootAssertionNode to incorporate the knowledge that `expr` was evaluated to true, and the second: `falseCheck` can be called on a *RootAssertionNode to incorporate the knowledge that `expr` was evaluated to false

For better performance by the caller, it also returns a boolean flag `isNoop` indicating whether the returned function is a no-op

type SelectorExprMap

type SelectorExprMap map[ast.Expr]map[*types.Var]*ast.SelectorExpr

SelectorExprMap is used to cache artificially created ast selector expressions

type TrackableExpr

type TrackableExpr []AssertionNode

TrackableExpr represents an expression that we track - i.e. observe non-local nilability properties of. If `e = nil; e.f` throws an error regardless of annotations, then `e` is trackable, for example. This notion exactly aligns with lists of `AssertionNode`s

func (TrackableExpr) MinimalString

func (t TrackableExpr) MinimalString() string

MinimalString for a TrackableExpr returns a sequence of minimal string representations of its contained nodes

Jump to

Keyboard shortcuts

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