golang.org/x/tools@v0.21.0/internal/refactor/inline/calleefx.go (about)

     1  // Copyright 2023 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package inline
     6  
     7  // This file defines the analysis of callee effects.
     8  
     9  import (
    10  	"go/ast"
    11  	"go/token"
    12  	"go/types"
    13  )
    14  
    15  const (
    16  	rinf = -1 //  R∞: arbitrary read from memory
    17  	winf = -2 //  W∞: arbitrary write to memory (or unknown control)
    18  )
    19  
    20  // calleefx returns a list of parameter indices indicating the order
    21  // in which parameters are first referenced during evaluation of the
    22  // callee, relative both to each other and to other effects of the
    23  // callee (if any), such as arbitrary reads (rinf) and arbitrary
    24  // effects (winf), including unknown control flow. Each parameter
    25  // that is referenced appears once in the list.
    26  //
    27  // For example, the effects list of this function:
    28  //
    29  //	func f(x, y, z int) int {
    30  //	    return y + x + g() + z
    31  //	}
    32  //
    33  // is [1 0 -2 2], indicating reads of y and x, followed by the unknown
    34  // effects of the g() call. and finally the read of parameter z. This
    35  // information is used during inlining to ascertain when it is safe
    36  // for parameter references to be replaced by their corresponding
    37  // argument expressions. Such substitutions are permitted only when
    38  // they do not cause "write" operations (those with effects) to
    39  // commute with "read" operations (those that have no effect but are
    40  // not pure). Impure operations may be reordered with other impure
    41  // operations, and pure operations may be reordered arbitrarily.
    42  //
    43  // The analysis ignores the effects of runtime panics, on the
    44  // assumption that well-behaved programs shouldn't encounter them.
    45  func calleefx(info *types.Info, body *ast.BlockStmt, paramInfos map[*types.Var]*paramInfo) []int {
    46  	// This traversal analyzes the callee's statements (in syntax
    47  	// form, though one could do better with SSA) to compute the
    48  	// sequence of events of the following kinds:
    49  	//
    50  	// 1  read of a parameter variable.
    51  	// 2. reads from other memory.
    52  	// 3. writes to memory
    53  
    54  	var effects []int // indices of parameters, or rinf/winf (-ve)
    55  	seen := make(map[int]bool)
    56  	effect := func(i int) {
    57  		if !seen[i] {
    58  			seen[i] = true
    59  			effects = append(effects, i)
    60  		}
    61  	}
    62  
    63  	// unknown is called for statements of unknown effects (or control).
    64  	unknown := func() {
    65  		effect(winf)
    66  
    67  		// Ensure that all remaining parameters are "seen"
    68  		// after we go into the unknown (unless they are
    69  		// unreferenced by the function body). This lets us
    70  		// not bother implementing the complete traversal into
    71  		// control structures.
    72  		//
    73  		// TODO(adonovan): add them in a deterministic order.
    74  		// (This is not a bug but determinism is good.)
    75  		for _, pinfo := range paramInfos {
    76  			if !pinfo.IsResult && len(pinfo.Refs) > 0 {
    77  				effect(pinfo.Index)
    78  			}
    79  		}
    80  	}
    81  
    82  	var visitExpr func(n ast.Expr)
    83  	var visitStmt func(n ast.Stmt) bool
    84  	visitExpr = func(n ast.Expr) {
    85  		switch n := n.(type) {
    86  		case *ast.Ident:
    87  			if v, ok := info.Uses[n].(*types.Var); ok && !v.IsField() {
    88  				// Use of global?
    89  				if v.Parent() == v.Pkg().Scope() {
    90  					effect(rinf) // read global var
    91  				}
    92  
    93  				// Use of parameter?
    94  				if pinfo, ok := paramInfos[v]; ok && !pinfo.IsResult {
    95  					effect(pinfo.Index) // read parameter var
    96  				}
    97  
    98  				// Use of local variables is ok.
    99  			}
   100  
   101  		case *ast.BasicLit:
   102  			// no effect
   103  
   104  		case *ast.FuncLit:
   105  			// A func literal has no read or write effect
   106  			// until called, and (most) function calls are
   107  			// considered to have arbitrary effects.
   108  			// So, no effect.
   109  
   110  		case *ast.CompositeLit:
   111  			for _, elt := range n.Elts {
   112  				visitExpr(elt) // note: visits KeyValueExpr
   113  			}
   114  
   115  		case *ast.ParenExpr:
   116  			visitExpr(n.X)
   117  
   118  		case *ast.SelectorExpr:
   119  			if seln, ok := info.Selections[n]; ok {
   120  				visitExpr(n.X)
   121  
   122  				// See types.SelectionKind for background.
   123  				switch seln.Kind() {
   124  				case types.MethodExpr:
   125  					// A method expression T.f acts like a
   126  					// reference to a func decl,
   127  					// so it doesn't read x until called.
   128  
   129  				case types.MethodVal, types.FieldVal:
   130  					// A field or method value selection x.f
   131  					// reads x if the selection indirects a pointer.
   132  
   133  					if indirectSelection(seln) {
   134  						effect(rinf)
   135  					}
   136  				}
   137  			} else {
   138  				// qualified identifier: treat like unqualified
   139  				visitExpr(n.Sel)
   140  			}
   141  
   142  		case *ast.IndexExpr:
   143  			if tv := info.Types[n.Index]; tv.IsType() {
   144  				// no effect (G[T] instantiation)
   145  			} else {
   146  				visitExpr(n.X)
   147  				visitExpr(n.Index)
   148  				switch tv.Type.Underlying().(type) {
   149  				case *types.Slice, *types.Pointer: // []T, *[n]T (not string, [n]T)
   150  					effect(rinf) // indirect read of slice/array element
   151  				}
   152  			}
   153  
   154  		case *ast.IndexListExpr:
   155  			// no effect (M[K,V] instantiation)
   156  
   157  		case *ast.SliceExpr:
   158  			visitExpr(n.X)
   159  			visitExpr(n.Low)
   160  			visitExpr(n.High)
   161  			visitExpr(n.Max)
   162  
   163  		case *ast.TypeAssertExpr:
   164  			visitExpr(n.X)
   165  
   166  		case *ast.CallExpr:
   167  			if info.Types[n.Fun].IsType() {
   168  				// conversion T(x)
   169  				visitExpr(n.Args[0])
   170  			} else {
   171  				// call f(args)
   172  				visitExpr(n.Fun)
   173  				for i, arg := range n.Args {
   174  					if i == 0 && info.Types[arg].IsType() {
   175  						continue // new(T), make(T, n)
   176  					}
   177  					visitExpr(arg)
   178  				}
   179  
   180  				// The pure built-ins have no effects beyond
   181  				// those of their operands (not even memory reads).
   182  				// All other calls have unknown effects.
   183  				if !callsPureBuiltin(info, n) {
   184  					unknown() // arbitrary effects
   185  				}
   186  			}
   187  
   188  		case *ast.StarExpr:
   189  			visitExpr(n.X)
   190  			effect(rinf) // *ptr load or store depends on state of heap
   191  
   192  		case *ast.UnaryExpr: // + - ! ^ & ~ <-
   193  			visitExpr(n.X)
   194  			if n.Op == token.ARROW {
   195  				unknown() // effect: channel receive
   196  			}
   197  
   198  		case *ast.BinaryExpr:
   199  			visitExpr(n.X)
   200  			visitExpr(n.Y)
   201  
   202  		case *ast.KeyValueExpr:
   203  			visitExpr(n.Key) // may be a struct field
   204  			visitExpr(n.Value)
   205  
   206  		case *ast.BadExpr:
   207  			// no effect
   208  
   209  		case nil:
   210  			// optional subtree
   211  
   212  		default:
   213  			// type syntax: unreachable given traversal
   214  			panic(n)
   215  		}
   216  	}
   217  
   218  	// visitStmt's result indicates the continuation:
   219  	// false for return, true for the next statement.
   220  	//
   221  	// We could treat return as an unknown, but this way
   222  	// yields definite effects for simple sequences like
   223  	// {S1; S2; return}, so unreferenced parameters are
   224  	// not spuriously added to the effects list, and thus
   225  	// not spuriously disqualified from elimination.
   226  	visitStmt = func(n ast.Stmt) bool {
   227  		switch n := n.(type) {
   228  		case *ast.DeclStmt:
   229  			decl := n.Decl.(*ast.GenDecl)
   230  			for _, spec := range decl.Specs {
   231  				switch spec := spec.(type) {
   232  				case *ast.ValueSpec:
   233  					for _, v := range spec.Values {
   234  						visitExpr(v)
   235  					}
   236  
   237  				case *ast.TypeSpec:
   238  					// no effect
   239  				}
   240  			}
   241  
   242  		case *ast.LabeledStmt:
   243  			return visitStmt(n.Stmt)
   244  
   245  		case *ast.ExprStmt:
   246  			visitExpr(n.X)
   247  
   248  		case *ast.SendStmt:
   249  			visitExpr(n.Chan)
   250  			visitExpr(n.Value)
   251  			unknown() // effect: channel send
   252  
   253  		case *ast.IncDecStmt:
   254  			visitExpr(n.X)
   255  			unknown() // effect: variable increment
   256  
   257  		case *ast.AssignStmt:
   258  			for _, lhs := range n.Lhs {
   259  				visitExpr(lhs)
   260  			}
   261  			for _, rhs := range n.Rhs {
   262  				visitExpr(rhs)
   263  			}
   264  			for _, lhs := range n.Lhs {
   265  				id, _ := lhs.(*ast.Ident)
   266  				if id != nil && id.Name == "_" {
   267  					continue // blank assign has no effect
   268  				}
   269  				if n.Tok == token.DEFINE && id != nil && info.Defs[id] != nil {
   270  					continue // new var declared by := has no effect
   271  				}
   272  				unknown() // assignment to existing var
   273  				break
   274  			}
   275  
   276  		case *ast.GoStmt:
   277  			visitExpr(n.Call.Fun)
   278  			for _, arg := range n.Call.Args {
   279  				visitExpr(arg)
   280  			}
   281  			unknown() // effect: create goroutine
   282  
   283  		case *ast.DeferStmt:
   284  			visitExpr(n.Call.Fun)
   285  			for _, arg := range n.Call.Args {
   286  				visitExpr(arg)
   287  			}
   288  			unknown() // effect: push defer
   289  
   290  		case *ast.ReturnStmt:
   291  			for _, res := range n.Results {
   292  				visitExpr(res)
   293  			}
   294  			return false
   295  
   296  		case *ast.BlockStmt:
   297  			for _, stmt := range n.List {
   298  				if !visitStmt(stmt) {
   299  					return false
   300  				}
   301  			}
   302  
   303  		case *ast.BranchStmt:
   304  			unknown() // control flow
   305  
   306  		case *ast.IfStmt:
   307  			visitStmt(n.Init)
   308  			visitExpr(n.Cond)
   309  			unknown() // control flow
   310  
   311  		case *ast.SwitchStmt:
   312  			visitStmt(n.Init)
   313  			visitExpr(n.Tag)
   314  			unknown() // control flow
   315  
   316  		case *ast.TypeSwitchStmt:
   317  			visitStmt(n.Init)
   318  			visitStmt(n.Assign)
   319  			unknown() // control flow
   320  
   321  		case *ast.SelectStmt:
   322  			unknown() // control flow
   323  
   324  		case *ast.ForStmt:
   325  			visitStmt(n.Init)
   326  			visitExpr(n.Cond)
   327  			unknown() // control flow
   328  
   329  		case *ast.RangeStmt:
   330  			visitExpr(n.X)
   331  			unknown() // control flow
   332  
   333  		case *ast.EmptyStmt, *ast.BadStmt:
   334  			// no effect
   335  
   336  		case nil:
   337  			// optional subtree
   338  
   339  		default:
   340  			panic(n)
   341  		}
   342  		return true
   343  	}
   344  	visitStmt(body)
   345  
   346  	return effects
   347  }