Source file src/cmd/compile/internal/escape/call.go

     1  // Copyright 2018 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 escape
     6  
     7  import (
     8  	"cmd/compile/internal/base"
     9  	"cmd/compile/internal/ir"
    10  	"cmd/compile/internal/typecheck"
    11  	"cmd/compile/internal/types"
    12  	"cmd/internal/src"
    13  	"strings"
    14  )
    15  
    16  // call evaluates a call expressions, including builtin calls. ks
    17  // should contain the holes representing where the function callee's
    18  // results flows.
    19  func (e *escape) call(ks []hole, call ir.Node) {
    20  	argument := func(k hole, arg ir.Node) {
    21  		// TODO(mdempsky): Should be "call argument".
    22  		e.expr(k.note(call, "call parameter"), arg)
    23  	}
    24  
    25  	switch call.Op() {
    26  	default:
    27  		ir.Dump("esc", call)
    28  		base.Fatalf("unexpected call op: %v", call.Op())
    29  
    30  	case ir.OCALLFUNC, ir.OCALLINTER:
    31  		call := call.(*ir.CallExpr)
    32  		typecheck.AssertFixedCall(call)
    33  
    34  		// Pick out the function callee, if statically known.
    35  		//
    36  		// TODO(mdempsky): Change fn from *ir.Name to *ir.Func, but some
    37  		// functions (e.g., runtime builtins, method wrappers, generated
    38  		// eq/hash functions) don't have it set. Investigate whether
    39  		// that's a concern.
    40  		var fn *ir.Name
    41  		switch call.Op() {
    42  		case ir.OCALLFUNC:
    43  			// TODO(thepudds): use an ir.ReassignOracle here.
    44  			v := ir.StaticValue(call.Fun)
    45  			fn = ir.StaticCalleeName(v)
    46  		}
    47  
    48  		// argumentParam handles escape analysis of assigning a call
    49  		// argument to its corresponding parameter.
    50  		argumentParam := func(param *types.Field, arg ir.Node) {
    51  			e.rewriteArgument(arg, call, fn)
    52  			argument(e.tagHole(ks, fn, param), arg)
    53  		}
    54  
    55  		if call.IsCompilerVarLive {
    56  			// Don't escape compiler-inserted KeepAlive.
    57  			argumentParam = func(param *types.Field, arg ir.Node) {
    58  				argument(e.discardHole(), arg)
    59  			}
    60  		}
    61  
    62  		fntype := call.Fun.Type()
    63  		if fn != nil {
    64  			fntype = fn.Type()
    65  		}
    66  
    67  		if ks != nil && fn != nil && e.inMutualBatch(fn) {
    68  			for i, result := range fn.Type().Results() {
    69  				e.expr(ks[i], result.Nname.(*ir.Name))
    70  			}
    71  		}
    72  
    73  		var recvArg ir.Node
    74  		if call.Op() == ir.OCALLFUNC {
    75  			// Evaluate callee function expression.
    76  			calleeK := e.discardHole()
    77  			if fn == nil { // unknown callee
    78  				for _, k := range ks {
    79  					if k.dst != &e.blankLoc {
    80  						// The results flow somewhere, but we don't statically
    81  						// know the callee function. If a closure flows here, we
    82  						// need to conservatively assume its results might flow to
    83  						// the heap.
    84  						calleeK = e.calleeHole().note(call, "callee operand")
    85  						break
    86  					}
    87  				}
    88  			}
    89  			e.expr(calleeK, call.Fun)
    90  		} else {
    91  			recvArg = call.Fun.(*ir.SelectorExpr).X
    92  		}
    93  
    94  		// internal/abi.EscapeNonString forces its argument to be on
    95  		// the heap, if it contains a non-string pointer.
    96  		// This is used in hash/maphash.Comparable, where we cannot
    97  		// hash pointers to local variables, as the address of the
    98  		// local variable might change on stack growth.
    99  		// Strings are okay as the hash depends on only the content,
   100  		// not the pointer.
   101  		// This is also used in unique.clone, to model the data flow
   102  		// edge on the value with strings excluded, because strings
   103  		// are cloned (by content).
   104  		// The actual call we match is
   105  		//   internal/abi.EscapeNonString[go.shape.T](dict, go.shape.T)
   106  		if fn != nil && fn.Sym().Pkg.Path == "internal/abi" && strings.HasPrefix(fn.Sym().Name, "EscapeNonString[") {
   107  			ps := fntype.Params()
   108  			if len(ps) == 2 && ps[1].Type.IsShape() {
   109  				if !hasNonStringPointers(ps[1].Type) {
   110  					argumentParam = func(param *types.Field, arg ir.Node) {
   111  						argument(e.discardHole(), arg)
   112  					}
   113  				} else {
   114  					argumentParam = func(param *types.Field, arg ir.Node) {
   115  						argument(e.heapHole(), arg)
   116  					}
   117  				}
   118  			}
   119  		}
   120  
   121  		args := call.Args
   122  		if recvParam := fntype.Recv(); recvParam != nil {
   123  			if recvArg == nil {
   124  				// Function call using method expression. Receiver argument is
   125  				// at the front of the regular arguments list.
   126  				recvArg, args = args[0], args[1:]
   127  			}
   128  
   129  			argumentParam(recvParam, recvArg)
   130  		}
   131  
   132  		for i, param := range fntype.Params() {
   133  			argumentParam(param, args[i])
   134  		}
   135  
   136  	case ir.OINLCALL:
   137  		call := call.(*ir.InlinedCallExpr)
   138  		e.stmts(call.Body)
   139  		for i, result := range call.ReturnVars {
   140  			k := e.discardHole()
   141  			if ks != nil {
   142  				k = ks[i]
   143  			}
   144  			e.expr(k, result)
   145  		}
   146  
   147  	case ir.OAPPEND:
   148  		call := call.(*ir.CallExpr)
   149  		args := call.Args
   150  
   151  		// Appendee slice may flow directly to the result, if
   152  		// it has enough capacity. Alternatively, a new heap
   153  		// slice might be allocated, and all slice elements
   154  		// might flow to heap.
   155  		appendeeK := e.teeHole(ks[0], e.mutatorHole())
   156  		if args[0].Type().Elem().HasPointers() {
   157  			appendeeK = e.teeHole(appendeeK, e.heapHole().deref(call, "appendee slice"))
   158  		}
   159  		argument(appendeeK, args[0])
   160  
   161  		if call.IsDDD {
   162  			appendedK := e.discardHole()
   163  			if args[1].Type().IsSlice() && args[1].Type().Elem().HasPointers() {
   164  				appendedK = e.heapHole().deref(call, "appended slice...")
   165  			}
   166  			argument(appendedK, args[1])
   167  		} else {
   168  			for i := 1; i < len(args); i++ {
   169  				argument(e.heapHole(), args[i])
   170  			}
   171  		}
   172  		e.discard(call.RType)
   173  
   174  		// Model the new backing store that might be allocated by append.
   175  		// Its address flows to the result.
   176  		// Users of escape analysis can look at the escape information for OAPPEND
   177  		// and use that to decide where to allocate the backing store.
   178  		backingStore := e.spill(ks[0], call)
   179  		// As we have a boolean to prevent reuse, we can treat these allocations as outside any loops.
   180  		backingStore.dst.loopDepth = 0
   181  
   182  	case ir.OCOPY:
   183  		call := call.(*ir.BinaryExpr)
   184  		argument(e.mutatorHole(), call.X)
   185  
   186  		copiedK := e.discardHole()
   187  		if call.Y.Type().IsSlice() && call.Y.Type().Elem().HasPointers() {
   188  			copiedK = e.heapHole().deref(call, "copied slice")
   189  		}
   190  		argument(copiedK, call.Y)
   191  		e.discard(call.RType)
   192  
   193  	case ir.OPANIC:
   194  		call := call.(*ir.UnaryExpr)
   195  		argument(e.heapHole(), call.X)
   196  
   197  	case ir.OCOMPLEX:
   198  		call := call.(*ir.BinaryExpr)
   199  		e.discard(call.X)
   200  		e.discard(call.Y)
   201  
   202  	case ir.ODELETE, ir.OPRINT, ir.OPRINTLN, ir.ORECOVER:
   203  		call := call.(*ir.CallExpr)
   204  		for _, arg := range call.Args {
   205  			e.discard(arg)
   206  		}
   207  		e.discard(call.RType)
   208  
   209  	case ir.OMIN, ir.OMAX:
   210  		call := call.(*ir.CallExpr)
   211  		for _, arg := range call.Args {
   212  			argument(ks[0], arg)
   213  		}
   214  		e.discard(call.RType)
   215  
   216  	case ir.OLEN, ir.OCAP, ir.OREAL, ir.OIMAG, ir.OCLOSE:
   217  		call := call.(*ir.UnaryExpr)
   218  		e.discard(call.X)
   219  
   220  	case ir.OCLEAR:
   221  		call := call.(*ir.UnaryExpr)
   222  		argument(e.mutatorHole(), call.X)
   223  
   224  	case ir.OUNSAFESTRINGDATA, ir.OUNSAFESLICEDATA:
   225  		call := call.(*ir.UnaryExpr)
   226  		argument(ks[0], call.X)
   227  
   228  	case ir.OUNSAFEADD, ir.OUNSAFESLICE, ir.OUNSAFESTRING:
   229  		call := call.(*ir.BinaryExpr)
   230  		argument(ks[0], call.X)
   231  		e.discard(call.Y)
   232  		e.discard(call.RType)
   233  	}
   234  }
   235  
   236  // goDeferStmt analyzes a "go" or "defer" statement.
   237  func (e *escape) goDeferStmt(n *ir.GoDeferStmt) {
   238  	k := e.heapHole()
   239  	if n.Op() == ir.ODEFER && e.loopDepth == 1 && n.DeferAt == nil {
   240  		// Top-level defer arguments don't escape to the heap,
   241  		// but they do need to last until they're invoked.
   242  		k = e.later(e.discardHole())
   243  
   244  		// force stack allocation of defer record, unless
   245  		// open-coded defers are used (see ssa.go)
   246  		n.SetEsc(ir.EscNever)
   247  	}
   248  
   249  	// If the function is already a zero argument/result function call,
   250  	// just escape analyze it normally.
   251  	//
   252  	// Note that the runtime is aware of this optimization for
   253  	// "go" statements that start in reflect.makeFuncStub or
   254  	// reflect.methodValueCall.
   255  
   256  	call, ok := n.Call.(*ir.CallExpr)
   257  	if !ok || call.Op() != ir.OCALLFUNC {
   258  		base.FatalfAt(n.Pos(), "expected function call: %v", n.Call)
   259  	}
   260  	if sig := call.Fun.Type(); sig.NumParams()+sig.NumResults() != 0 {
   261  		base.FatalfAt(n.Pos(), "expected signature without parameters or results: %v", sig)
   262  	}
   263  
   264  	if clo, ok := call.Fun.(*ir.ClosureExpr); ok && n.Op() == ir.OGO {
   265  		clo.IsGoWrap = true
   266  	}
   267  
   268  	e.expr(k, call.Fun)
   269  }
   270  
   271  // rewriteArgument rewrites the argument arg of the given call expression.
   272  // fn is the static callee function, if known.
   273  func (e *escape) rewriteArgument(arg ir.Node, call *ir.CallExpr, fn *ir.Name) {
   274  	if fn == nil || fn.Func == nil {
   275  		return
   276  	}
   277  	pragma := fn.Func.Pragma
   278  	if pragma&(ir.UintptrKeepAlive|ir.UintptrEscapes) == 0 {
   279  		return
   280  	}
   281  
   282  	// unsafeUintptr rewrites "uintptr(ptr)" arguments to syscall-like
   283  	// functions, so that ptr is kept alive and/or escaped as
   284  	// appropriate. unsafeUintptr also reports whether it modified arg0.
   285  	unsafeUintptr := func(arg ir.Node) {
   286  		// If the argument is really a pointer being converted to uintptr,
   287  		// arrange for the pointer to be kept alive until the call
   288  		// returns, by copying it into a temp and marking that temp still
   289  		// alive when we pop the temp stack.
   290  		conv, ok := arg.(*ir.ConvExpr)
   291  		if !ok || conv.Op() != ir.OCONVNOP {
   292  			return // not a conversion
   293  		}
   294  		if !conv.X.Type().IsUnsafePtr() || !conv.Type().IsUintptr() {
   295  			return // not an unsafe.Pointer->uintptr conversion
   296  		}
   297  
   298  		// Create and declare a new pointer-typed temp variable.
   299  		//
   300  		// TODO(mdempsky): This potentially violates the Go spec's order
   301  		// of evaluations, by evaluating arg.X before any other
   302  		// operands.
   303  		tmp := e.copyExpr(conv.Pos(), conv.X, call.PtrInit())
   304  		conv.X = tmp
   305  
   306  		k := e.mutatorHole()
   307  		if pragma&ir.UintptrEscapes != 0 {
   308  			k = e.heapHole().note(conv, "//go:uintptrescapes")
   309  		}
   310  		e.flow(k, e.oldLoc(tmp))
   311  
   312  		if pragma&ir.UintptrKeepAlive != 0 {
   313  			tmp.SetAddrtaken(true) // ensure SSA keeps the tmp variable
   314  			call.KeepAlive = append(call.KeepAlive, tmp)
   315  		}
   316  	}
   317  
   318  	// For variadic functions, the compiler has already rewritten:
   319  	//
   320  	//     f(a, b, c)
   321  	//
   322  	// to:
   323  	//
   324  	//     f([]T{a, b, c}...)
   325  	//
   326  	// So we need to look into slice elements to handle uintptr(ptr)
   327  	// arguments to variadic syscall-like functions correctly.
   328  	if arg.Op() == ir.OSLICELIT {
   329  		list := arg.(*ir.CompLitExpr).List
   330  		for _, el := range list {
   331  			if el.Op() == ir.OKEY {
   332  				el = el.(*ir.KeyExpr).Value
   333  			}
   334  			unsafeUintptr(el)
   335  		}
   336  	} else {
   337  		unsafeUintptr(arg)
   338  	}
   339  }
   340  
   341  // copyExpr creates and returns a new temporary variable within fn;
   342  // appends statements to init to declare and initialize it to expr;
   343  // and escape analyzes the data flow.
   344  func (e *escape) copyExpr(pos src.XPos, expr ir.Node, init *ir.Nodes) *ir.Name {
   345  	if ir.HasUniquePos(expr) {
   346  		pos = expr.Pos()
   347  	}
   348  
   349  	tmp := typecheck.TempAt(pos, e.curfn, expr.Type())
   350  
   351  	stmts := []ir.Node{
   352  		ir.NewDecl(pos, ir.ODCL, tmp),
   353  		ir.NewAssignStmt(pos, tmp, expr),
   354  	}
   355  	typecheck.Stmts(stmts)
   356  	init.Append(stmts...)
   357  
   358  	e.newLoc(tmp, true)
   359  	e.stmts(stmts)
   360  
   361  	return tmp
   362  }
   363  
   364  // tagHole returns a hole for evaluating an argument passed to param.
   365  // ks should contain the holes representing where the function
   366  // callee's results flows. fn is the statically-known callee function,
   367  // if any.
   368  func (e *escape) tagHole(ks []hole, fn *ir.Name, param *types.Field) hole {
   369  	// If this is a dynamic call, we can't rely on param.Note.
   370  	if fn == nil {
   371  		return e.heapHole()
   372  	}
   373  
   374  	if e.inMutualBatch(fn) {
   375  		if param.Nname == nil {
   376  			return e.discardHole()
   377  		}
   378  		return e.addr(param.Nname.(*ir.Name))
   379  	}
   380  
   381  	// Call to previously tagged function.
   382  
   383  	var tagKs []hole
   384  	esc := parseLeaks(param.Note)
   385  
   386  	if x := esc.Heap(); x >= 0 {
   387  		tagKs = append(tagKs, e.heapHole().shift(x))
   388  	}
   389  	if x := esc.Mutator(); x >= 0 {
   390  		tagKs = append(tagKs, e.mutatorHole().shift(x))
   391  	}
   392  	if x := esc.Callee(); x >= 0 {
   393  		tagKs = append(tagKs, e.calleeHole().shift(x))
   394  	}
   395  
   396  	if ks != nil {
   397  		for i := 0; i < numEscResults; i++ {
   398  			if x := esc.Result(i); x >= 0 {
   399  				tagKs = append(tagKs, ks[i].shift(x))
   400  			}
   401  		}
   402  	}
   403  
   404  	return e.teeHole(tagKs...)
   405  }
   406  
   407  func hasNonStringPointers(t *types.Type) bool {
   408  	if !t.HasPointers() {
   409  		return false
   410  	}
   411  	switch t.Kind() {
   412  	case types.TSTRING:
   413  		return false
   414  	case types.TSTRUCT:
   415  		for _, f := range t.Fields() {
   416  			if hasNonStringPointers(f.Type) {
   417  				return true
   418  			}
   419  		}
   420  		return false
   421  	case types.TARRAY:
   422  		return hasNonStringPointers(t.Elem())
   423  	}
   424  	return true
   425  }
   426  

View as plain text