// Copyright 2018 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package escape import ( "cmd/compile/internal/base" "cmd/compile/internal/ir" "cmd/compile/internal/typecheck" "cmd/compile/internal/types" "cmd/internal/src" ) // call evaluates a call expressions, including builtin calls. ks // should contain the holes representing where the function callee's // results flows. func (e *escape) call(ks []hole, call ir.Node) { argument := func(k hole, arg ir.Node) { // TODO(mdempsky): Should be "call argument". e.expr(k.note(call, "call parameter"), arg) } switch call.Op() { default: ir.Dump("esc", call) base.Fatalf("unexpected call op: %v", call.Op()) case ir.OCALLFUNC, ir.OCALLINTER: call := call.(*ir.CallExpr) typecheck.AssertFixedCall(call) // Pick out the function callee, if statically known. // // TODO(mdempsky): Change fn from *ir.Name to *ir.Func, but some // functions (e.g., runtime builtins, method wrappers, generated // eq/hash functions) don't have it set. Investigate whether // that's a concern. var fn *ir.Name switch call.Op() { case ir.OCALLFUNC: v := ir.StaticValue(call.Fun) fn = ir.StaticCalleeName(v) } fntype := call.Fun.Type() if fn != nil { fntype = fn.Type() } if ks != nil && fn != nil && e.inMutualBatch(fn) { for i, result := range fn.Type().Results() { e.expr(ks[i], result.Nname.(*ir.Name)) } } var recvArg ir.Node if call.Op() == ir.OCALLFUNC { // Evaluate callee function expression. calleeK := e.discardHole() if fn == nil { // unknown callee for _, k := range ks { if k.dst != &e.blankLoc { // The results flow somewhere, but we don't statically // know the callee function. If a closure flows here, we // need to conservatively assume its results might flow to // the heap. calleeK = e.calleeHole().note(call, "callee operand") break } } } e.expr(calleeK, call.Fun) } else { recvArg = call.Fun.(*ir.SelectorExpr).X } // argumentParam handles escape analysis of assigning a call // argument to its corresponding parameter. argumentParam := func(param *types.Field, arg ir.Node) { e.rewriteArgument(arg, call, fn) argument(e.tagHole(ks, fn, param), arg) } args := call.Args if recvParam := fntype.Recv(); recvParam != nil { if recvArg == nil { // Function call using method expression. Receiver argument is // at the front of the regular arguments list. recvArg, args = args[0], args[1:] } argumentParam(recvParam, recvArg) } for i, param := range fntype.Params() { argumentParam(param, args[i]) } case ir.OINLCALL: call := call.(*ir.InlinedCallExpr) e.stmts(call.Body) for i, result := range call.ReturnVars { k := e.discardHole() if ks != nil { k = ks[i] } e.expr(k, result) } case ir.OAPPEND: call := call.(*ir.CallExpr) args := call.Args // Appendee slice may flow directly to the result, if // it has enough capacity. Alternatively, a new heap // slice might be allocated, and all slice elements // might flow to heap. appendeeK := e.teeHole(ks[0], e.mutatorHole()) if args[0].Type().Elem().HasPointers() { appendeeK = e.teeHole(appendeeK, e.heapHole().deref(call, "appendee slice")) } argument(appendeeK, args[0]) if call.IsDDD { appendedK := e.discardHole() if args[1].Type().IsSlice() && args[1].Type().Elem().HasPointers() { appendedK = e.heapHole().deref(call, "appended slice...") } argument(appendedK, args[1]) } else { for i := 1; i < len(args); i++ { argument(e.heapHole(), args[i]) } } e.discard(call.RType) case ir.OCOPY: call := call.(*ir.BinaryExpr) argument(e.mutatorHole(), call.X) copiedK := e.discardHole() if call.Y.Type().IsSlice() && call.Y.Type().Elem().HasPointers() { copiedK = e.heapHole().deref(call, "copied slice") } argument(copiedK, call.Y) e.discard(call.RType) case ir.OPANIC: call := call.(*ir.UnaryExpr) argument(e.heapHole(), call.X) case ir.OCOMPLEX: call := call.(*ir.BinaryExpr) e.discard(call.X) e.discard(call.Y) case ir.ODELETE, ir.OPRINT, ir.OPRINTLN, ir.ORECOVERFP: call := call.(*ir.CallExpr) for _, arg := range call.Args { e.discard(arg) } e.discard(call.RType) case ir.OMIN, ir.OMAX: call := call.(*ir.CallExpr) for _, arg := range call.Args { argument(ks[0], arg) } e.discard(call.RType) case ir.OLEN, ir.OCAP, ir.OREAL, ir.OIMAG, ir.OCLOSE: call := call.(*ir.UnaryExpr) e.discard(call.X) case ir.OCLEAR: call := call.(*ir.UnaryExpr) argument(e.mutatorHole(), call.X) case ir.OUNSAFESTRINGDATA, ir.OUNSAFESLICEDATA: call := call.(*ir.UnaryExpr) argument(ks[0], call.X) case ir.OUNSAFEADD, ir.OUNSAFESLICE, ir.OUNSAFESTRING: call := call.(*ir.BinaryExpr) argument(ks[0], call.X) e.discard(call.Y) e.discard(call.RType) } } // goDeferStmt analyzes a "go" or "defer" statement. func (e *escape) goDeferStmt(n *ir.GoDeferStmt) { k := e.heapHole() if n.Op() == ir.ODEFER && e.loopDepth == 1 && n.DeferAt == nil { // Top-level defer arguments don't escape to the heap, // but they do need to last until they're invoked. k = e.later(e.discardHole()) // force stack allocation of defer record, unless // open-coded defers are used (see ssa.go) n.SetEsc(ir.EscNever) } // If the function is already a zero argument/result function call, // just escape analyze it normally. // // Note that the runtime is aware of this optimization for // "go" statements that start in reflect.makeFuncStub or // reflect.methodValueCall. call, ok := n.Call.(*ir.CallExpr) if !ok || call.Op() != ir.OCALLFUNC { base.FatalfAt(n.Pos(), "expected function call: %v", n.Call) } if sig := call.Fun.Type(); sig.NumParams()+sig.NumResults() != 0 { base.FatalfAt(n.Pos(), "expected signature without parameters or results: %v", sig) } if clo, ok := call.Fun.(*ir.ClosureExpr); ok && n.Op() == ir.OGO { clo.IsGoWrap = true } e.expr(k, call.Fun) } // rewriteArgument rewrites the argument arg of the given call expression. // fn is the static callee function, if known. func (e *escape) rewriteArgument(arg ir.Node, call *ir.CallExpr, fn *ir.Name) { if fn == nil || fn.Func == nil { return } pragma := fn.Func.Pragma if pragma&(ir.UintptrKeepAlive|ir.UintptrEscapes) == 0 { return } // unsafeUintptr rewrites "uintptr(ptr)" arguments to syscall-like // functions, so that ptr is kept alive and/or escaped as // appropriate. unsafeUintptr also reports whether it modified arg0. unsafeUintptr := func(arg ir.Node) { // If the argument is really a pointer being converted to uintptr, // arrange for the pointer to be kept alive until the call // returns, by copying it into a temp and marking that temp still // alive when we pop the temp stack. conv, ok := arg.(*ir.ConvExpr) if !ok || conv.Op() != ir.OCONVNOP { return // not a conversion } if !conv.X.Type().IsUnsafePtr() || !conv.Type().IsUintptr() { return // not an unsafe.Pointer->uintptr conversion } // Create and declare a new pointer-typed temp variable. // // TODO(mdempsky): This potentially violates the Go spec's order // of evaluations, by evaluating arg.X before any other // operands. tmp := e.copyExpr(conv.Pos(), conv.X, call.PtrInit()) conv.X = tmp k := e.mutatorHole() if pragma&ir.UintptrEscapes != 0 { k = e.heapHole().note(conv, "//go:uintptrescapes") } e.flow(k, e.oldLoc(tmp)) if pragma&ir.UintptrKeepAlive != 0 { tmp.SetAddrtaken(true) // ensure SSA keeps the tmp variable call.KeepAlive = append(call.KeepAlive, tmp) } } // For variadic functions, the compiler has already rewritten: // // f(a, b, c) // // to: // // f([]T{a, b, c}...) // // So we need to look into slice elements to handle uintptr(ptr) // arguments to variadic syscall-like functions correctly. if arg.Op() == ir.OSLICELIT { list := arg.(*ir.CompLitExpr).List for _, el := range list { if el.Op() == ir.OKEY { el = el.(*ir.KeyExpr).Value } unsafeUintptr(el) } } else { unsafeUintptr(arg) } } // copyExpr creates and returns a new temporary variable within fn; // appends statements to init to declare and initialize it to expr; // and escape analyzes the data flow. func (e *escape) copyExpr(pos src.XPos, expr ir.Node, init *ir.Nodes) *ir.Name { if ir.HasUniquePos(expr) { pos = expr.Pos() } tmp := typecheck.TempAt(pos, e.curfn, expr.Type()) stmts := []ir.Node{ ir.NewDecl(pos, ir.ODCL, tmp), ir.NewAssignStmt(pos, tmp, expr), } typecheck.Stmts(stmts) init.Append(stmts...) e.newLoc(tmp, true) e.stmts(stmts) return tmp } // tagHole returns a hole for evaluating an argument passed to param. // ks should contain the holes representing where the function // callee's results flows. fn is the statically-known callee function, // if any. func (e *escape) tagHole(ks []hole, fn *ir.Name, param *types.Field) hole { // If this is a dynamic call, we can't rely on param.Note. if fn == nil { return e.heapHole() } if e.inMutualBatch(fn) { if param.Nname == nil { return e.discardHole() } return e.addr(param.Nname.(*ir.Name)) } // Call to previously tagged function. var tagKs []hole esc := parseLeaks(param.Note) if x := esc.Heap(); x >= 0 { tagKs = append(tagKs, e.heapHole().shift(x)) } if x := esc.Mutator(); x >= 0 { tagKs = append(tagKs, e.mutatorHole().shift(x)) } if x := esc.Callee(); x >= 0 { tagKs = append(tagKs, e.calleeHole().shift(x)) } if ks != nil { for i := 0; i < numEscResults; i++ { if x := esc.Result(i); x >= 0 { tagKs = append(tagKs, ks[i].shift(x)) } } } return e.teeHole(tagKs...) }