backpropAcrossAssignment handles backpropagation for assignments, including special cases such as type switches and range statements. Moreover, it also handles special contracts such as map reads, channel reads, and type assertions by adding appropriate guards to them. Specifically, there are three
(rootNode *RootAssertionNode, lhs, rhs []ast.Expr)
| 275 | // Phase 3. mark all LHS and RHS as computed. |
| 276 | // nonnil(lhs, rhs) |
| 277 | func backpropAcrossAssignment(rootNode *RootAssertionNode, lhs, rhs []ast.Expr) error { |
| 278 | // For phase 1 and 2, we will first handle a few special assignments (with early return), then |
| 279 | // if none of the special cases are hit, which means it is a normal assignment, we further |
| 280 | // delegate the process to other functions depending on if it is many-to-one or one-to-one |
| 281 | // assignment for better code clarity. |
| 282 | // In all cases, we should do phase 3 before returning, so here we defer a function for phase 3. |
| 283 | defer func() { |
| 284 | // Phase 3 |
| 285 | // Now that we've back-propagated across the assignment itself, make sure we can compute |
| 286 | // all of the lhs and rhs. |
| 287 | for _, rhsVal := range rhs { |
| 288 | rootNode.AddComputation(rhsVal) |
| 289 | } |
| 290 | for _, lhsVal := range lhs { |
| 291 | rootNode.AddComputation(lhsVal) |
| 292 | } |
| 293 | }() |
| 294 | |
| 295 | // Phase 1 and 2 |
| 296 | // First handle a few special cases, e.g., type switches, type assertions, range statements, |
| 297 | // and some cases for "ok" contracts, all of which will have a rhs with length 1. |
| 298 | if len(rhs) == 1 { |
| 299 | // Here we first strip the parentheses of the rhs to reveal the underlying nodes. |
| 300 | rhsNode := ast.Unparen(rhs[0]) |
| 301 | |
| 302 | // Type switch `x := y.(type)`, which needs special handling because TypesInfo.Defs |
| 303 | // can't find an object for the lhs. |
| 304 | // Note that the key distinction between a "type switch" and a "type assertion" in the |
| 305 | // AST is whether the `Type` field of the AST node is nil. |
| 306 | if r, ok := rhsNode.(*ast.TypeAssertExpr); ok && r.Type == nil { |
| 307 | // lhs must have one element, which is *ast.Ident. |
| 308 | if len(lhs) != 1 { |
| 309 | return errors.New("lhs must have one element for type switches") |
| 310 | } |
| 311 | lhsIdent := lhs[0].(*ast.Ident) |
| 312 | return backpropAcrossTypeSwitch(rootNode, lhsIdent, r.X) |
| 313 | } |
| 314 | |
| 315 | if r, ok := rhsNode.(*ast.UnaryExpr); ok { |
| 316 | switch r.Op { |
| 317 | case token.RANGE: |
| 318 | // Range statement of the form `for x := range y`, which is not overly complex to |
| 319 | // handle but does involve distinct semantics. |
| 320 | return backpropAcrossRange(rootNode, lhs, r.X) |
| 321 | case token.AND: |
| 322 | if !rootNode.functionContext.functionConfig.EnableStructInitCheck { |
| 323 | // This is the case of creating a pointer and assigning it to a variable, e.g., `x := &y`, |
| 324 | // where y is a non-pointer type (e.g., y := S{}). |
| 325 | // Here, the pointer is always nonnil, so we can just add a ProduceTriggerNever. |
| 326 | if rootNode.Pass().ExprBarsNilness(r.X) { |
| 327 | if len(lhs) == 1 && !asthelper.IsEmptyExpr(lhs[0]) { |
| 328 | rootNode.AddProduction(&annotation.ProduceTrigger{ |
| 329 | Annotation: &annotation.ProduceTriggerNever{}, |
| 330 | Expr: lhs[0], |
| 331 | }) |
| 332 | } |
| 333 | } |
| 334 | } |
no test coverage detected