// 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/types" ) // expr models evaluating an expression n and flowing the result into // hole k. func (e *escape) expr(k hole, n ir.Node) { if n == nil { return } e.stmts(n.Init()) e.exprSkipInit(k, n) } func (e *escape) exprSkipInit(k hole, n ir.Node) { if n == nil { return } lno := ir.SetPos(n) defer func() { base.Pos = lno }() if k.derefs >= 0 && !n.Type().IsUntyped() && !n.Type().HasPointers() { k.dst = &e.blankLoc } switch n.Op() { default: base.Fatalf("unexpected expr: %s %v", n.Op().String(), n) case ir.OLITERAL, ir.ONIL, ir.OGETG, ir.OGETCALLERSP, ir.OTYPE, ir.OMETHEXPR, ir.OLINKSYMOFFSET: // nop case ir.ONAME: n := n.(*ir.Name) if n.Class == ir.PFUNC || n.Class == ir.PEXTERN { return } e.flow(k, e.oldLoc(n)) case ir.OPLUS, ir.ONEG, ir.OBITNOT, ir.ONOT: n := n.(*ir.UnaryExpr) e.discard(n.X) case ir.OADD, ir.OSUB, ir.OOR, ir.OXOR, ir.OMUL, ir.ODIV, ir.OMOD, ir.OLSH, ir.ORSH, ir.OAND, ir.OANDNOT, ir.OEQ, ir.ONE, ir.OLT, ir.OLE, ir.OGT, ir.OGE: n := n.(*ir.BinaryExpr) e.discard(n.X) e.discard(n.Y) case ir.OANDAND, ir.OOROR: n := n.(*ir.LogicalExpr) e.discard(n.X) e.discard(n.Y) case ir.OADDR: n := n.(*ir.AddrExpr) e.expr(k.addr(n, "address-of"), n.X) // "address-of" case ir.ODEREF: n := n.(*ir.StarExpr) e.expr(k.deref(n, "indirection"), n.X) // "indirection" case ir.ODOT, ir.ODOTMETH, ir.ODOTINTER: n := n.(*ir.SelectorExpr) e.expr(k.note(n, "dot"), n.X) case ir.ODOTPTR: n := n.(*ir.SelectorExpr) e.expr(k.deref(n, "dot of pointer"), n.X) // "dot of pointer" case ir.ODOTTYPE, ir.ODOTTYPE2: n := n.(*ir.TypeAssertExpr) e.expr(k.dotType(n.Type(), n, "dot"), n.X) case ir.ODYNAMICDOTTYPE, ir.ODYNAMICDOTTYPE2: n := n.(*ir.DynamicTypeAssertExpr) e.expr(k.dotType(n.Type(), n, "dot"), n.X) // n.T doesn't need to be tracked; it always points to read-only storage. case ir.OINDEX: n := n.(*ir.IndexExpr) if n.X.Type().IsArray() { e.expr(k.note(n, "fixed-array-index-of"), n.X) } else { // TODO(mdempsky): Fix why reason text. e.expr(k.deref(n, "dot of pointer"), n.X) } e.discard(n.Index) case ir.OINDEXMAP: n := n.(*ir.IndexExpr) e.discard(n.X) e.discard(n.Index) case ir.OSLICE, ir.OSLICEARR, ir.OSLICE3, ir.OSLICE3ARR, ir.OSLICESTR: n := n.(*ir.SliceExpr) e.expr(k.note(n, "slice"), n.X) e.discard(n.Low) e.discard(n.High) e.discard(n.Max) case ir.OCONV, ir.OCONVNOP: n := n.(*ir.ConvExpr) if (ir.ShouldCheckPtr(e.curfn, 2) || ir.ShouldAsanCheckPtr(e.curfn)) && n.Type().IsUnsafePtr() && n.X.Type().IsPtr() { // When -d=checkptr=2 or -asan is enabled, // treat conversions to unsafe.Pointer as an // escaping operation. This allows better // runtime instrumentation, since we can more // easily detect object boundaries on the heap // than the stack. e.assignHeap(n.X, "conversion to unsafe.Pointer", n) } else if n.Type().IsUnsafePtr() && n.X.Type().IsUintptr() { e.unsafeValue(k, n.X) } else { e.expr(k, n.X) } case ir.OCONVIFACE: n := n.(*ir.ConvExpr) if !n.X.Type().IsInterface() && !types.IsDirectIface(n.X.Type()) { k = e.spill(k, n) } e.expr(k.note(n, "interface-converted"), n.X) case ir.OMAKEFACE: n := n.(*ir.BinaryExpr) // Note: n.X is not needed because it can never point to memory that might escape. e.expr(k, n.Y) case ir.OITAB, ir.OIDATA, ir.OSPTR: n := n.(*ir.UnaryExpr) e.expr(k, n.X) case ir.OSLICE2ARR: // Converting a slice to array is effectively a deref. n := n.(*ir.ConvExpr) e.expr(k.deref(n, "slice-to-array"), n.X) case ir.OSLICE2ARRPTR: // the slice pointer flows directly to the result n := n.(*ir.ConvExpr) e.expr(k, n.X) case ir.ORECV: n := n.(*ir.UnaryExpr) e.discard(n.X) case ir.OCALLMETH, ir.OCALLFUNC, ir.OCALLINTER, ir.OINLCALL, ir.OLEN, ir.OCAP, ir.OMIN, ir.OMAX, ir.OCOMPLEX, ir.OREAL, ir.OIMAG, ir.OAPPEND, ir.OCOPY, ir.ORECOVERFP, ir.OUNSAFEADD, ir.OUNSAFESLICE, ir.OUNSAFESTRING, ir.OUNSAFESTRINGDATA, ir.OUNSAFESLICEDATA: e.call([]hole{k}, n) case ir.ONEW: n := n.(*ir.UnaryExpr) e.spill(k, n) case ir.OMAKESLICE: n := n.(*ir.MakeExpr) e.spill(k, n) e.discard(n.Len) e.discard(n.Cap) case ir.OMAKECHAN: n := n.(*ir.MakeExpr) e.discard(n.Len) case ir.OMAKEMAP: n := n.(*ir.MakeExpr) e.spill(k, n) e.discard(n.Len) case ir.OMETHVALUE: // Flow the receiver argument to both the closure and // to the receiver parameter. n := n.(*ir.SelectorExpr) closureK := e.spill(k, n) m := n.Selection // We don't know how the method value will be called // later, so conservatively assume the result // parameters all flow to the heap. // // TODO(mdempsky): Change ks into a callback, so that // we don't have to create this slice? var ks []hole for i := m.Type.NumResults(); i > 0; i-- { ks = append(ks, e.heapHole()) } name, _ := m.Nname.(*ir.Name) paramK := e.tagHole(ks, name, m.Type.Recv()) e.expr(e.teeHole(paramK, closureK), n.X) case ir.OPTRLIT: n := n.(*ir.AddrExpr) e.expr(e.spill(k, n), n.X) case ir.OARRAYLIT: n := n.(*ir.CompLitExpr) for _, elt := range n.List { if elt.Op() == ir.OKEY { elt = elt.(*ir.KeyExpr).Value } e.expr(k.note(n, "array literal element"), elt) } case ir.OSLICELIT: n := n.(*ir.CompLitExpr) k = e.spill(k, n) for _, elt := range n.List { if elt.Op() == ir.OKEY { elt = elt.(*ir.KeyExpr).Value } e.expr(k.note(n, "slice-literal-element"), elt) } case ir.OSTRUCTLIT: n := n.(*ir.CompLitExpr) for _, elt := range n.List { e.expr(k.note(n, "struct literal element"), elt.(*ir.StructKeyExpr).Value) } case ir.OMAPLIT: n := n.(*ir.CompLitExpr) e.spill(k, n) // Map keys and values are always stored in the heap. for _, elt := range n.List { elt := elt.(*ir.KeyExpr) e.assignHeap(elt.Key, "map literal key", n) e.assignHeap(elt.Value, "map literal value", n) } case ir.OCLOSURE: n := n.(*ir.ClosureExpr) k = e.spill(k, n) e.closures = append(e.closures, closure{k, n}) if fn := n.Func; fn.IsClosure() { for _, cv := range fn.ClosureVars { if loc := e.oldLoc(cv); !loc.captured { loc.captured = true // Ignore reassignments to the variable in straightline code // preceding the first capture by a closure. if loc.loopDepth == e.loopDepth { loc.reassigned = false } } } for _, n := range fn.Dcl { // Add locations for local variables of the // closure, if needed, in case we're not including // the closure func in the batch for escape // analysis (happens for escape analysis called // from reflectdata.methodWrapper) if n.Op() == ir.ONAME && n.Opt == nil { e.with(fn).newLoc(n, true) } } e.walkFunc(fn) } case ir.ORUNES2STR, ir.OBYTES2STR, ir.OSTR2RUNES, ir.OSTR2BYTES, ir.ORUNESTR: n := n.(*ir.ConvExpr) e.spill(k, n) e.discard(n.X) case ir.OADDSTR: n := n.(*ir.AddStringExpr) e.spill(k, n) // Arguments of OADDSTR never escape; // runtime.concatstrings makes sure of that. e.discards(n.List) case ir.ODYNAMICTYPE: // Nothing to do - argument is a *runtime._type (+ maybe a *runtime.itab) pointing to static data section } } // unsafeValue evaluates a uintptr-typed arithmetic expression looking // for conversions from an unsafe.Pointer. func (e *escape) unsafeValue(k hole, n ir.Node) { if n.Type().Kind() != types.TUINTPTR { base.Fatalf("unexpected type %v for %v", n.Type(), n) } if k.addrtaken { base.Fatalf("unexpected addrtaken") } e.stmts(n.Init()) switch n.Op() { case ir.OCONV, ir.OCONVNOP: n := n.(*ir.ConvExpr) if n.X.Type().IsUnsafePtr() { e.expr(k, n.X) } else { e.discard(n.X) } case ir.ODOTPTR: n := n.(*ir.SelectorExpr) if ir.IsReflectHeaderDataField(n) { e.expr(k.deref(n, "reflect.Header.Data"), n.X) } else { e.discard(n.X) } case ir.OPLUS, ir.ONEG, ir.OBITNOT: n := n.(*ir.UnaryExpr) e.unsafeValue(k, n.X) case ir.OADD, ir.OSUB, ir.OOR, ir.OXOR, ir.OMUL, ir.ODIV, ir.OMOD, ir.OAND, ir.OANDNOT: n := n.(*ir.BinaryExpr) e.unsafeValue(k, n.X) e.unsafeValue(k, n.Y) case ir.OLSH, ir.ORSH: n := n.(*ir.BinaryExpr) e.unsafeValue(k, n.X) // RHS need not be uintptr-typed (#32959) and can't meaningfully // flow pointers anyway. e.discard(n.Y) default: e.exprSkipInit(e.discardHole(), n) } } // discard evaluates an expression n for side-effects, but discards // its value. func (e *escape) discard(n ir.Node) { e.expr(e.discardHole(), n) } func (e *escape) discards(l ir.Nodes) { for _, n := range l { e.discard(n) } } // spill allocates a new location associated with expression n, flows // its address to k, and returns a hole that flows values to it. It's // intended for use with most expressions that allocate storage. func (e *escape) spill(k hole, n ir.Node) hole { loc := e.newLoc(n, false) e.flow(k.addr(n, "spill"), loc) return loc.asHole() }