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

View as plain text