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.
( ctx context.Context, pass *analysishelper.EnhancedPass, decl *ast.FuncDecl, functionContext FunctionContext, graph *cfg.CFG, )
| 899 | // with accompanying CFG, and back-propagates a tree of assertions across it to generate, at entry |
| 900 | // to the function, the set of assertions that must hold to avoid possible nil flow errors. |
| 901 | func BackpropAcrossFunc( |
| 902 | ctx context.Context, |
| 903 | pass *analysishelper.EnhancedPass, |
| 904 | decl *ast.FuncDecl, |
| 905 | functionContext FunctionContext, |
| 906 | graph *cfg.CFG, |
| 907 | ) ([]annotation.FullTrigger, int, int, error) { |
| 908 | // We transform the CFG to have it reflect the implicit control flow that happens |
| 909 | // inside short-circuiting boolean expressions. |
| 910 | preprocessor := preprocess.New(pass) |
| 911 | graph = preprocessor.CFG(graph, functionContext.funcDecl) |
| 912 | |
| 913 | // Generate rick check effects. |
| 914 | richCheckBlocks, exprNonceMap := genInitialRichCheckEffects(graph, functionContext) |
| 915 | richCheckBlocks = propagateRichChecks(graph, richCheckBlocks) |
| 916 | blocks, preprocessing := blocksAndPreprocessingFromCFG(pass, graph, richCheckBlocks) |
| 917 | |
| 918 | // The assertion nodes for each block and an array of bools to indicate whether each block is |
| 919 | // updated in this round or not. |
| 920 | // DANGER: anytime a pointer is copied from currAssertions to nextAssertions, it MUST be |
| 921 | // treated as immutable - if any modification (e.g. merging or backprop) is going to occur you |
| 922 | // must perform a deep copy with CopyNode |
| 923 | updatedLastRound, updatedThisRound := make([]bool, len(blocks)), make([]bool, len(blocks)) |
| 924 | currAssertions, nextAssertions := make([]*RootAssertionNode, len(blocks)), make([]*RootAssertionNode, len(blocks)) |
| 925 | |
| 926 | // The assertion nodes for the entry block, we will use it as an indication for stabilization. |
| 927 | // We consider the backpropagation stable if # of stable rounds > # of live blocks + tolerance. |
| 928 | var currRootAssertionNode, nextRootAssertionNode *RootAssertionNode |
| 929 | roundCount, stableRoundCount := 0, 0 |
| 930 | postOrder := computePostOrder(blocks) |
| 931 | |
| 932 | // Initialize the process by creating the assertion nodes for the return block. |
| 933 | retBlock := len(blocks) - 1 |
| 934 | currAssertions[retBlock] = newRootAssertionNode(exprNonceMap, functionContext) |
| 935 | updatedLastRound[retBlock] = true |
| 936 | |
| 937 | for slices.Contains(updatedLastRound, true) { |
| 938 | roundCount++ |
| 939 | |
| 940 | select { |
| 941 | case <-ctx.Done(): |
| 942 | return nil, roundCount, stableRoundCount, fmt.Errorf("backprop early stop due to context: %w", ctx.Err()) |
| 943 | default: |
| 944 | } |
| 945 | |
| 946 | for _, i := range postOrder { |
| 947 | block := blocks[i] |
| 948 | |
| 949 | // There is no need to process non-live blocks; their assertions will simply be nil. |
| 950 | if !block.Live { |
| 951 | continue |
| 952 | } |
| 953 | |
| 954 | if len(block.Succs) > 2 { |
| 955 | return nil, roundCount, stableRoundCount, errors.New("assumptions about CFG shape violated - a block has >2 successors") |
| 956 | } |
| 957 | |
| 958 | // No need to re-process the assertion node for the current block if it does not have |