go/src/cmd/compile/internal/walk/expr.go
Khaled Yakdan 2b0e457b42 cmd/compile: intercept string compares in libFuzzer mode
IR string compares as well as calls to string comparison functions such
as `strings.EqualFold` are intercepted and the corresponding libFuzzer
callbacks are invoked with the corresponding arguments. As a result, the
compared strings will be added to libFuzzer’s table of recent compares,
which feeds future mutations performed by the fuzzer and thus allow it
to reach into branches guarded by string comparisons.

The list of methods to intercept is maintained in
`cmd/compile/internal/walk/expr.go` and can easily be extended to cover
more standard library functions in the future.

Change-Id: I5c8b89499c4e19459406795dea923bf777779c51
GitHub-Last-Rev: 6b8529b555
GitHub-Pull-Request: golang/go#51319
Reviewed-on: https://go-review.googlesource.com/c/go/+/387335
Reviewed-by: Keith Randall <khr@golang.org>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@google.com>
Run-TryBot: Keith Randall <khr@golang.org>
2022-05-20 22:30:37 +00:00

1042 lines
26 KiB
Go

// Copyright 2009 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 walk
import (
"fmt"
"go/constant"
"internal/buildcfg"
"strings"
"cmd/compile/internal/base"
"cmd/compile/internal/ir"
"cmd/compile/internal/reflectdata"
"cmd/compile/internal/staticdata"
"cmd/compile/internal/typecheck"
"cmd/compile/internal/types"
"cmd/internal/obj"
)
// The result of walkExpr MUST be assigned back to n, e.g.
//
// n.Left = walkExpr(n.Left, init)
func walkExpr(n ir.Node, init *ir.Nodes) ir.Node {
if n == nil {
return n
}
if n, ok := n.(ir.InitNode); ok && init == n.PtrInit() {
// not okay to use n->ninit when walking n,
// because we might replace n with some other node
// and would lose the init list.
base.Fatalf("walkExpr init == &n->ninit")
}
if len(n.Init()) != 0 {
walkStmtList(n.Init())
init.Append(ir.TakeInit(n)...)
}
lno := ir.SetPos(n)
if base.Flag.LowerW > 1 {
ir.Dump("before walk expr", n)
}
if n.Typecheck() != 1 {
base.Fatalf("missed typecheck: %+v", n)
}
if n.Type().IsUntyped() {
base.Fatalf("expression has untyped type: %+v", n)
}
n = walkExpr1(n, init)
// Eagerly compute sizes of all expressions for the back end.
if typ := n.Type(); typ != nil && typ.Kind() != types.TBLANK && !typ.IsFuncArgStruct() {
types.CheckSize(typ)
}
if n, ok := n.(*ir.Name); ok && n.Heapaddr != nil {
types.CheckSize(n.Heapaddr.Type())
}
if ir.IsConst(n, constant.String) {
// Emit string symbol now to avoid emitting
// any concurrently during the backend.
_ = staticdata.StringSym(n.Pos(), constant.StringVal(n.Val()))
}
if base.Flag.LowerW != 0 && n != nil {
ir.Dump("after walk expr", n)
}
base.Pos = lno
return n
}
func walkExpr1(n ir.Node, init *ir.Nodes) ir.Node {
switch n.Op() {
default:
ir.Dump("walk", n)
base.Fatalf("walkExpr: switch 1 unknown op %+v", n.Op())
panic("unreachable")
case ir.OGETG, ir.OGETCALLERPC, ir.OGETCALLERSP:
return n
case ir.OTYPE, ir.ONAME, ir.OLITERAL, ir.ONIL, ir.OLINKSYMOFFSET:
// TODO(mdempsky): Just return n; see discussion on CL 38655.
// Perhaps refactor to use Node.mayBeShared for these instead.
// If these return early, make sure to still call
// StringSym for constant strings.
return n
case ir.OMETHEXPR:
// TODO(mdempsky): Do this right after type checking.
n := n.(*ir.SelectorExpr)
return n.FuncName()
case ir.ONOT, ir.ONEG, ir.OPLUS, ir.OBITNOT, ir.OREAL, ir.OIMAG, ir.OSPTR, ir.OITAB, ir.OIDATA:
n := n.(*ir.UnaryExpr)
n.X = walkExpr(n.X, init)
return n
case ir.ODOTMETH, ir.ODOTINTER:
n := n.(*ir.SelectorExpr)
n.X = walkExpr(n.X, init)
return n
case ir.OADDR:
n := n.(*ir.AddrExpr)
n.X = walkExpr(n.X, init)
return n
case ir.ODEREF:
n := n.(*ir.StarExpr)
n.X = walkExpr(n.X, init)
return n
case ir.OEFACE, ir.OAND, ir.OANDNOT, ir.OSUB, ir.OMUL, ir.OADD, ir.OOR, ir.OXOR, ir.OLSH, ir.ORSH,
ir.OUNSAFEADD:
n := n.(*ir.BinaryExpr)
n.X = walkExpr(n.X, init)
n.Y = walkExpr(n.Y, init)
return n
case ir.OUNSAFESLICE:
n := n.(*ir.BinaryExpr)
return walkUnsafeSlice(n, init)
case ir.ODOT, ir.ODOTPTR:
n := n.(*ir.SelectorExpr)
return walkDot(n, init)
case ir.ODOTTYPE, ir.ODOTTYPE2:
n := n.(*ir.TypeAssertExpr)
return walkDotType(n, init)
case ir.ODYNAMICDOTTYPE, ir.ODYNAMICDOTTYPE2:
n := n.(*ir.DynamicTypeAssertExpr)
return walkDynamicDotType(n, init)
case ir.OLEN, ir.OCAP:
n := n.(*ir.UnaryExpr)
return walkLenCap(n, init)
case ir.OCOMPLEX:
n := n.(*ir.BinaryExpr)
n.X = walkExpr(n.X, init)
n.Y = walkExpr(n.Y, init)
return n
case ir.OEQ, ir.ONE, ir.OLT, ir.OLE, ir.OGT, ir.OGE:
n := n.(*ir.BinaryExpr)
return walkCompare(n, init)
case ir.OANDAND, ir.OOROR:
n := n.(*ir.LogicalExpr)
return walkLogical(n, init)
case ir.OPRINT, ir.OPRINTN:
return walkPrint(n.(*ir.CallExpr), init)
case ir.OPANIC:
n := n.(*ir.UnaryExpr)
return mkcall("gopanic", nil, init, n.X)
case ir.ORECOVERFP:
return walkRecoverFP(n.(*ir.CallExpr), init)
case ir.OCFUNC:
return n
case ir.OCALLINTER, ir.OCALLFUNC:
n := n.(*ir.CallExpr)
return walkCall(n, init)
case ir.OAS, ir.OASOP:
return walkAssign(init, n)
case ir.OAS2:
n := n.(*ir.AssignListStmt)
return walkAssignList(init, n)
// a,b,... = fn()
case ir.OAS2FUNC:
n := n.(*ir.AssignListStmt)
return walkAssignFunc(init, n)
// x, y = <-c
// order.stmt made sure x is addressable or blank.
case ir.OAS2RECV:
n := n.(*ir.AssignListStmt)
return walkAssignRecv(init, n)
// a,b = m[i]
case ir.OAS2MAPR:
n := n.(*ir.AssignListStmt)
return walkAssignMapRead(init, n)
case ir.ODELETE:
n := n.(*ir.CallExpr)
return walkDelete(init, n)
case ir.OAS2DOTTYPE:
n := n.(*ir.AssignListStmt)
return walkAssignDotType(n, init)
case ir.OCONVIFACE:
n := n.(*ir.ConvExpr)
return walkConvInterface(n, init)
case ir.OCONVIDATA:
n := n.(*ir.ConvExpr)
return walkConvIData(n, init)
case ir.OCONV, ir.OCONVNOP:
n := n.(*ir.ConvExpr)
return walkConv(n, init)
case ir.OSLICE2ARRPTR:
n := n.(*ir.ConvExpr)
n.X = walkExpr(n.X, init)
return n
case ir.ODIV, ir.OMOD:
n := n.(*ir.BinaryExpr)
return walkDivMod(n, init)
case ir.OINDEX:
n := n.(*ir.IndexExpr)
return walkIndex(n, init)
case ir.OINDEXMAP:
n := n.(*ir.IndexExpr)
return walkIndexMap(n, init)
case ir.ORECV:
base.Fatalf("walkExpr ORECV") // should see inside OAS only
panic("unreachable")
case ir.OSLICEHEADER:
n := n.(*ir.SliceHeaderExpr)
return walkSliceHeader(n, init)
case ir.OSLICE, ir.OSLICEARR, ir.OSLICESTR, ir.OSLICE3, ir.OSLICE3ARR:
n := n.(*ir.SliceExpr)
return walkSlice(n, init)
case ir.ONEW:
n := n.(*ir.UnaryExpr)
return walkNew(n, init)
case ir.OADDSTR:
return walkAddString(n.(*ir.AddStringExpr), init)
case ir.OAPPEND:
// order should make sure we only see OAS(node, OAPPEND), which we handle above.
base.Fatalf("append outside assignment")
panic("unreachable")
case ir.OCOPY:
return walkCopy(n.(*ir.BinaryExpr), init, base.Flag.Cfg.Instrumenting && !base.Flag.CompilingRuntime)
case ir.OCLOSE:
n := n.(*ir.UnaryExpr)
return walkClose(n, init)
case ir.OMAKECHAN:
n := n.(*ir.MakeExpr)
return walkMakeChan(n, init)
case ir.OMAKEMAP:
n := n.(*ir.MakeExpr)
return walkMakeMap(n, init)
case ir.OMAKESLICE:
n := n.(*ir.MakeExpr)
return walkMakeSlice(n, init)
case ir.OMAKESLICECOPY:
n := n.(*ir.MakeExpr)
return walkMakeSliceCopy(n, init)
case ir.ORUNESTR:
n := n.(*ir.ConvExpr)
return walkRuneToString(n, init)
case ir.OBYTES2STR, ir.ORUNES2STR:
n := n.(*ir.ConvExpr)
return walkBytesRunesToString(n, init)
case ir.OBYTES2STRTMP:
n := n.(*ir.ConvExpr)
return walkBytesToStringTemp(n, init)
case ir.OSTR2BYTES:
n := n.(*ir.ConvExpr)
return walkStringToBytes(n, init)
case ir.OSTR2BYTESTMP:
n := n.(*ir.ConvExpr)
return walkStringToBytesTemp(n, init)
case ir.OSTR2RUNES:
n := n.(*ir.ConvExpr)
return walkStringToRunes(n, init)
case ir.OARRAYLIT, ir.OSLICELIT, ir.OMAPLIT, ir.OSTRUCTLIT, ir.OPTRLIT:
return walkCompLit(n, init)
case ir.OSEND:
n := n.(*ir.SendStmt)
return walkSend(n, init)
case ir.OCLOSURE:
return walkClosure(n.(*ir.ClosureExpr), init)
case ir.OMETHVALUE:
return walkMethodValue(n.(*ir.SelectorExpr), init)
}
// No return! Each case must return (or panic),
// to avoid confusion about what gets returned
// in the presence of type assertions.
}
// walk the whole tree of the body of an
// expression or simple statement.
// the types expressions are calculated.
// compile-time constants are evaluated.
// complex side effects like statements are appended to init
func walkExprList(s []ir.Node, init *ir.Nodes) {
for i := range s {
s[i] = walkExpr(s[i], init)
}
}
func walkExprListCheap(s []ir.Node, init *ir.Nodes) {
for i, n := range s {
s[i] = cheapExpr(n, init)
s[i] = walkExpr(s[i], init)
}
}
func walkExprListSafe(s []ir.Node, init *ir.Nodes) {
for i, n := range s {
s[i] = safeExpr(n, init)
s[i] = walkExpr(s[i], init)
}
}
// return side-effect free and cheap n, appending side effects to init.
// result may not be assignable.
func cheapExpr(n ir.Node, init *ir.Nodes) ir.Node {
switch n.Op() {
case ir.ONAME, ir.OLITERAL, ir.ONIL:
return n
}
return copyExpr(n, n.Type(), init)
}
// return side effect-free n, appending side effects to init.
// result is assignable if n is.
func safeExpr(n ir.Node, init *ir.Nodes) ir.Node {
if n == nil {
return nil
}
if len(n.Init()) != 0 {
walkStmtList(n.Init())
init.Append(ir.TakeInit(n)...)
}
switch n.Op() {
case ir.ONAME, ir.OLITERAL, ir.ONIL, ir.OLINKSYMOFFSET:
return n
case ir.OLEN, ir.OCAP:
n := n.(*ir.UnaryExpr)
l := safeExpr(n.X, init)
if l == n.X {
return n
}
a := ir.Copy(n).(*ir.UnaryExpr)
a.X = l
return walkExpr(typecheck.Expr(a), init)
case ir.ODOT, ir.ODOTPTR:
n := n.(*ir.SelectorExpr)
l := safeExpr(n.X, init)
if l == n.X {
return n
}
a := ir.Copy(n).(*ir.SelectorExpr)
a.X = l
return walkExpr(typecheck.Expr(a), init)
case ir.ODEREF:
n := n.(*ir.StarExpr)
l := safeExpr(n.X, init)
if l == n.X {
return n
}
a := ir.Copy(n).(*ir.StarExpr)
a.X = l
return walkExpr(typecheck.Expr(a), init)
case ir.OINDEX, ir.OINDEXMAP:
n := n.(*ir.IndexExpr)
l := safeExpr(n.X, init)
r := safeExpr(n.Index, init)
if l == n.X && r == n.Index {
return n
}
a := ir.Copy(n).(*ir.IndexExpr)
a.X = l
a.Index = r
return walkExpr(typecheck.Expr(a), init)
case ir.OSTRUCTLIT, ir.OARRAYLIT, ir.OSLICELIT:
n := n.(*ir.CompLitExpr)
if isStaticCompositeLiteral(n) {
return n
}
}
// make a copy; must not be used as an lvalue
if ir.IsAddressable(n) {
base.Fatalf("missing lvalue case in safeExpr: %v", n)
}
return cheapExpr(n, init)
}
func copyExpr(n ir.Node, t *types.Type, init *ir.Nodes) ir.Node {
l := typecheck.Temp(t)
appendWalkStmt(init, ir.NewAssignStmt(base.Pos, l, n))
return l
}
func walkAddString(n *ir.AddStringExpr, init *ir.Nodes) ir.Node {
c := len(n.List)
if c < 2 {
base.Fatalf("walkAddString count %d too small", c)
}
buf := typecheck.NodNil()
if n.Esc() == ir.EscNone {
sz := int64(0)
for _, n1 := range n.List {
if n1.Op() == ir.OLITERAL {
sz += int64(len(ir.StringVal(n1)))
}
}
// Don't allocate the buffer if the result won't fit.
if sz < tmpstringbufsize {
// Create temporary buffer for result string on stack.
buf = stackBufAddr(tmpstringbufsize, types.Types[types.TUINT8])
}
}
// build list of string arguments
args := []ir.Node{buf}
for _, n2 := range n.List {
args = append(args, typecheck.Conv(n2, types.Types[types.TSTRING]))
}
var fn string
if c <= 5 {
// small numbers of strings use direct runtime helpers.
// note: order.expr knows this cutoff too.
fn = fmt.Sprintf("concatstring%d", c)
} else {
// large numbers of strings are passed to the runtime as a slice.
fn = "concatstrings"
t := types.NewSlice(types.Types[types.TSTRING])
// args[1:] to skip buf arg
slice := ir.NewCompLitExpr(base.Pos, ir.OCOMPLIT, t, args[1:])
slice.Prealloc = n.Prealloc
args = []ir.Node{buf, slice}
slice.SetEsc(ir.EscNone)
}
cat := typecheck.LookupRuntime(fn)
r := ir.NewCallExpr(base.Pos, ir.OCALL, cat, nil)
r.Args = args
r1 := typecheck.Expr(r)
r1 = walkExpr(r1, init)
r1.SetType(n.Type())
return r1
}
type hookInfo struct {
paramType types.Kind
argsNum int
runtimeFunc string
}
var hooks = map[string]hookInfo{
"strings.EqualFold": {paramType: types.TSTRING, argsNum: 2, runtimeFunc: "libfuzzerHookEqualFold"},
}
// walkCall walks an OCALLFUNC or OCALLINTER node.
func walkCall(n *ir.CallExpr, init *ir.Nodes) ir.Node {
if n.Op() == ir.OCALLMETH {
base.FatalfAt(n.Pos(), "OCALLMETH missed by typecheck")
}
if n.Op() == ir.OCALLINTER || n.X.Op() == ir.OMETHEXPR {
// We expect both interface call reflect.Type.Method and concrete
// call reflect.(*rtype).Method.
usemethod(n)
}
if n.Op() == ir.OCALLINTER {
reflectdata.MarkUsedIfaceMethod(n)
}
if n.Op() == ir.OCALLFUNC && n.X.Op() == ir.OCLOSURE {
directClosureCall(n)
}
if isFuncPCIntrinsic(n) {
// For internal/abi.FuncPCABIxxx(fn), if fn is a defined function, rewrite
// it to the address of the function of the ABI fn is defined.
name := n.X.(*ir.Name).Sym().Name
arg := n.Args[0]
var wantABI obj.ABI
switch name {
case "FuncPCABI0":
wantABI = obj.ABI0
case "FuncPCABIInternal":
wantABI = obj.ABIInternal
}
if isIfaceOfFunc(arg) {
fn := arg.(*ir.ConvExpr).X.(*ir.Name)
abi := fn.Func.ABI
if abi != wantABI {
base.ErrorfAt(n.Pos(), "internal/abi.%s expects an %v function, %s is defined as %v", name, wantABI, fn.Sym().Name, abi)
}
var e ir.Node = ir.NewLinksymExpr(n.Pos(), fn.Sym().LinksymABI(abi), types.Types[types.TUINTPTR])
e = ir.NewAddrExpr(n.Pos(), e)
e.SetType(types.Types[types.TUINTPTR].PtrTo())
e = ir.NewConvExpr(n.Pos(), ir.OCONVNOP, n.Type(), e)
return e
}
// fn is not a defined function. It must be ABIInternal.
// Read the address from func value, i.e. *(*uintptr)(idata(fn)).
if wantABI != obj.ABIInternal {
base.ErrorfAt(n.Pos(), "internal/abi.%s does not accept func expression, which is ABIInternal", name)
}
arg = walkExpr(arg, init)
var e ir.Node = ir.NewUnaryExpr(n.Pos(), ir.OIDATA, arg)
e.SetType(n.Type().PtrTo())
e = ir.NewStarExpr(n.Pos(), e)
e.SetType(n.Type())
return e
}
walkCall1(n, init)
return n
}
func walkCall1(n *ir.CallExpr, init *ir.Nodes) {
if n.Walked() {
return // already walked
}
n.SetWalked(true)
if n.Op() == ir.OCALLMETH {
base.FatalfAt(n.Pos(), "OCALLMETH missed by typecheck")
}
args := n.Args
params := n.X.Type().Params()
n.X = walkExpr(n.X, init)
walkExprList(args, init)
for i, arg := range args {
// Validate argument and parameter types match.
param := params.Field(i)
if !types.Identical(arg.Type(), param.Type) {
base.FatalfAt(n.Pos(), "assigning %L to parameter %v (type %v)", arg, param.Sym, param.Type)
}
// For any argument whose evaluation might require a function call,
// store that argument into a temporary variable,
// to prevent that calls from clobbering arguments already on the stack.
if mayCall(arg) {
// assignment of arg to Temp
tmp := typecheck.Temp(param.Type)
init.Append(convas(typecheck.Stmt(ir.NewAssignStmt(base.Pos, tmp, arg)).(*ir.AssignStmt), init))
// replace arg with temp
args[i] = tmp
}
}
n.Args = args
funSym := n.X.Sym()
if base.Debug.Libfuzzer != 0 && funSym != nil {
if hook, found := hooks[funSym.Pkg.Path+"."+funSym.Name]; found {
if len(args) != hook.argsNum {
panic(fmt.Sprintf("%s.%s expects %d arguments, but received %d", funSym.Pkg.Path, funSym.Name, hook.argsNum, len(args)))
}
var hookArgs []ir.Node
for _, arg := range args {
hookArgs = append(hookArgs, tracecmpArg(arg, types.Types[hook.paramType], init))
}
hookArgs = append(hookArgs, fakePC(n))
init.Append(mkcall(hook.runtimeFunc, nil, init, hookArgs...))
}
}
}
// walkDivMod walks an ODIV or OMOD node.
func walkDivMod(n *ir.BinaryExpr, init *ir.Nodes) ir.Node {
n.X = walkExpr(n.X, init)
n.Y = walkExpr(n.Y, init)
// rewrite complex div into function call.
et := n.X.Type().Kind()
if types.IsComplex[et] && n.Op() == ir.ODIV {
t := n.Type()
call := mkcall("complex128div", types.Types[types.TCOMPLEX128], init, typecheck.Conv(n.X, types.Types[types.TCOMPLEX128]), typecheck.Conv(n.Y, types.Types[types.TCOMPLEX128]))
return typecheck.Conv(call, t)
}
// Nothing to do for float divisions.
if types.IsFloat[et] {
return n
}
// rewrite 64-bit div and mod on 32-bit architectures.
// TODO: Remove this code once we can introduce
// runtime calls late in SSA processing.
if types.RegSize < 8 && (et == types.TINT64 || et == types.TUINT64) {
if n.Y.Op() == ir.OLITERAL {
// Leave div/mod by constant powers of 2 or small 16-bit constants.
// The SSA backend will handle those.
switch et {
case types.TINT64:
c := ir.Int64Val(n.Y)
if c < 0 {
c = -c
}
if c != 0 && c&(c-1) == 0 {
return n
}
case types.TUINT64:
c := ir.Uint64Val(n.Y)
if c < 1<<16 {
return n
}
if c != 0 && c&(c-1) == 0 {
return n
}
}
}
var fn string
if et == types.TINT64 {
fn = "int64"
} else {
fn = "uint64"
}
if n.Op() == ir.ODIV {
fn += "div"
} else {
fn += "mod"
}
return mkcall(fn, n.Type(), init, typecheck.Conv(n.X, types.Types[et]), typecheck.Conv(n.Y, types.Types[et]))
}
return n
}
// walkDot walks an ODOT or ODOTPTR node.
func walkDot(n *ir.SelectorExpr, init *ir.Nodes) ir.Node {
usefield(n)
n.X = walkExpr(n.X, init)
return n
}
// walkDotType walks an ODOTTYPE or ODOTTYPE2 node.
func walkDotType(n *ir.TypeAssertExpr, init *ir.Nodes) ir.Node {
n.X = walkExpr(n.X, init)
// Set up interface type addresses for back end.
if !n.Type().IsInterface() && !n.X.Type().IsEmptyInterface() {
n.ITab = reflectdata.ITabAddr(n.Type(), n.X.Type())
}
return n
}
// walkDynamicdotType walks an ODYNAMICDOTTYPE or ODYNAMICDOTTYPE2 node.
func walkDynamicDotType(n *ir.DynamicTypeAssertExpr, init *ir.Nodes) ir.Node {
n.X = walkExpr(n.X, init)
n.RType = walkExpr(n.RType, init)
n.ITab = walkExpr(n.ITab, init)
return n
}
// walkIndex walks an OINDEX node.
func walkIndex(n *ir.IndexExpr, init *ir.Nodes) ir.Node {
n.X = walkExpr(n.X, init)
// save the original node for bounds checking elision.
// If it was a ODIV/OMOD walk might rewrite it.
r := n.Index
n.Index = walkExpr(n.Index, init)
// if range of type cannot exceed static array bound,
// disable bounds check.
if n.Bounded() {
return n
}
t := n.X.Type()
if t != nil && t.IsPtr() {
t = t.Elem()
}
if t.IsArray() {
n.SetBounded(bounded(r, t.NumElem()))
if base.Flag.LowerM != 0 && n.Bounded() && !ir.IsConst(n.Index, constant.Int) {
base.Warn("index bounds check elided")
}
if ir.IsSmallIntConst(n.Index) && !n.Bounded() {
base.Errorf("index out of bounds")
}
} else if ir.IsConst(n.X, constant.String) {
n.SetBounded(bounded(r, int64(len(ir.StringVal(n.X)))))
if base.Flag.LowerM != 0 && n.Bounded() && !ir.IsConst(n.Index, constant.Int) {
base.Warn("index bounds check elided")
}
if ir.IsSmallIntConst(n.Index) && !n.Bounded() {
base.Errorf("index out of bounds")
}
}
if ir.IsConst(n.Index, constant.Int) {
if v := n.Index.Val(); constant.Sign(v) < 0 || ir.ConstOverflow(v, types.Types[types.TINT]) {
base.Errorf("index out of bounds")
}
}
return n
}
// mapKeyArg returns an expression for key that is suitable to be passed
// as the key argument for runtime map* functions.
// n is is the map indexing or delete Node (to provide Pos).
func mapKeyArg(fast int, n, key ir.Node, assigned bool) ir.Node {
if fast == mapslow {
// standard version takes key by reference.
// orderState.expr made sure key is addressable.
return typecheck.NodAddr(key)
}
if assigned {
// mapassign does distinguish pointer vs. integer key.
return key
}
// mapaccess and mapdelete don't distinguish pointer vs. integer key.
switch fast {
case mapfast32ptr:
return ir.NewConvExpr(n.Pos(), ir.OCONVNOP, types.Types[types.TUINT32], key)
case mapfast64ptr:
return ir.NewConvExpr(n.Pos(), ir.OCONVNOP, types.Types[types.TUINT64], key)
default:
// fast version takes key by value.
return key
}
}
// walkIndexMap walks an OINDEXMAP node.
// It replaces m[k] with *map{access1,assign}(maptype, m, &k)
func walkIndexMap(n *ir.IndexExpr, init *ir.Nodes) ir.Node {
n.X = walkExpr(n.X, init)
n.Index = walkExpr(n.Index, init)
map_ := n.X
t := map_.Type()
fast := mapfast(t)
key := mapKeyArg(fast, n, n.Index, n.Assigned)
args := []ir.Node{reflectdata.TypePtr(t), map_, key}
var mapFn ir.Node
switch {
case n.Assigned:
mapFn = mapfn(mapassign[fast], t, false)
case t.Elem().Size() > zeroValSize:
args = append(args, reflectdata.ZeroAddr(t.Elem().Size()))
mapFn = mapfn("mapaccess1_fat", t, true)
default:
mapFn = mapfn(mapaccess1[fast], t, false)
}
call := mkcall1(mapFn, nil, init, args...)
call.SetType(types.NewPtr(t.Elem()))
call.MarkNonNil() // mapaccess1* and mapassign always return non-nil pointers.
star := ir.NewStarExpr(base.Pos, call)
star.SetType(t.Elem())
star.SetTypecheck(1)
return star
}
// walkLogical walks an OANDAND or OOROR node.
func walkLogical(n *ir.LogicalExpr, init *ir.Nodes) ir.Node {
n.X = walkExpr(n.X, init)
// cannot put side effects from n.Right on init,
// because they cannot run before n.Left is checked.
// save elsewhere and store on the eventual n.Right.
var ll ir.Nodes
n.Y = walkExpr(n.Y, &ll)
n.Y = ir.InitExpr(ll, n.Y)
return n
}
// walkSend walks an OSEND node.
func walkSend(n *ir.SendStmt, init *ir.Nodes) ir.Node {
n1 := n.Value
n1 = typecheck.AssignConv(n1, n.Chan.Type().Elem(), "chan send")
n1 = walkExpr(n1, init)
n1 = typecheck.NodAddr(n1)
return mkcall1(chanfn("chansend1", 2, n.Chan.Type()), nil, init, n.Chan, n1)
}
// walkSlice walks an OSLICE, OSLICEARR, OSLICESTR, OSLICE3, or OSLICE3ARR node.
func walkSlice(n *ir.SliceExpr, init *ir.Nodes) ir.Node {
n.X = walkExpr(n.X, init)
n.Low = walkExpr(n.Low, init)
if n.Low != nil && ir.IsZero(n.Low) {
// Reduce x[0:j] to x[:j] and x[0:j:k] to x[:j:k].
n.Low = nil
}
n.High = walkExpr(n.High, init)
n.Max = walkExpr(n.Max, init)
if n.Op().IsSlice3() {
if n.Max != nil && n.Max.Op() == ir.OCAP && ir.SameSafeExpr(n.X, n.Max.(*ir.UnaryExpr).X) {
// Reduce x[i:j:cap(x)] to x[i:j].
if n.Op() == ir.OSLICE3 {
n.SetOp(ir.OSLICE)
} else {
n.SetOp(ir.OSLICEARR)
}
return reduceSlice(n)
}
return n
}
return reduceSlice(n)
}
// walkSliceHeader walks an OSLICEHEADER node.
func walkSliceHeader(n *ir.SliceHeaderExpr, init *ir.Nodes) ir.Node {
n.Ptr = walkExpr(n.Ptr, init)
n.Len = walkExpr(n.Len, init)
n.Cap = walkExpr(n.Cap, init)
return n
}
// TODO(josharian): combine this with its caller and simplify
func reduceSlice(n *ir.SliceExpr) ir.Node {
if n.High != nil && n.High.Op() == ir.OLEN && ir.SameSafeExpr(n.X, n.High.(*ir.UnaryExpr).X) {
// Reduce x[i:len(x)] to x[i:].
n.High = nil
}
if (n.Op() == ir.OSLICE || n.Op() == ir.OSLICESTR) && n.Low == nil && n.High == nil {
// Reduce x[:] to x.
if base.Debug.Slice > 0 {
base.Warn("slice: omit slice operation")
}
return n.X
}
return n
}
// return 1 if integer n must be in range [0, max), 0 otherwise
func bounded(n ir.Node, max int64) bool {
if n.Type() == nil || !n.Type().IsInteger() {
return false
}
sign := n.Type().IsSigned()
bits := int32(8 * n.Type().Size())
if ir.IsSmallIntConst(n) {
v := ir.Int64Val(n)
return 0 <= v && v < max
}
switch n.Op() {
case ir.OAND, ir.OANDNOT:
n := n.(*ir.BinaryExpr)
v := int64(-1)
switch {
case ir.IsSmallIntConst(n.X):
v = ir.Int64Val(n.X)
case ir.IsSmallIntConst(n.Y):
v = ir.Int64Val(n.Y)
if n.Op() == ir.OANDNOT {
v = ^v
if !sign {
v &= 1<<uint(bits) - 1
}
}
}
if 0 <= v && v < max {
return true
}
case ir.OMOD:
n := n.(*ir.BinaryExpr)
if !sign && ir.IsSmallIntConst(n.Y) {
v := ir.Int64Val(n.Y)
if 0 <= v && v <= max {
return true
}
}
case ir.ODIV:
n := n.(*ir.BinaryExpr)
if !sign && ir.IsSmallIntConst(n.Y) {
v := ir.Int64Val(n.Y)
for bits > 0 && v >= 2 {
bits--
v >>= 1
}
}
case ir.ORSH:
n := n.(*ir.BinaryExpr)
if !sign && ir.IsSmallIntConst(n.Y) {
v := ir.Int64Val(n.Y)
if v > int64(bits) {
return true
}
bits -= int32(v)
}
}
if !sign && bits <= 62 && 1<<uint(bits) <= max {
return true
}
return false
}
// usemethod checks calls for uses of reflect.Type.{Method,MethodByName}.
func usemethod(n *ir.CallExpr) {
// Don't mark reflect.(*rtype).Method, etc. themselves in the reflect package.
// Those functions may be alive via the itab, which should not cause all methods
// alive. We only want to mark their callers.
if base.Ctxt.Pkgpath == "reflect" {
switch ir.CurFunc.Nname.Sym().Name { // TODO: is there a better way than hardcoding the names?
case "(*rtype).Method", "(*rtype).MethodByName", "(*interfaceType).Method", "(*interfaceType).MethodByName":
return
}
}
dot, ok := n.X.(*ir.SelectorExpr)
if !ok {
return
}
// Looking for either direct method calls and interface method calls of:
// reflect.Type.Method - func(int) reflect.Method
// reflect.Type.MethodByName - func(string) (reflect.Method, bool)
var pKind types.Kind
switch dot.Sel.Name {
case "Method":
pKind = types.TINT
case "MethodByName":
pKind = types.TSTRING
default:
return
}
t := dot.Selection.Type
if t.NumParams() != 1 || t.Params().Field(0).Type.Kind() != pKind {
return
}
switch t.NumResults() {
case 1:
// ok
case 2:
if t.Results().Field(1).Type.Kind() != types.TBOOL {
return
}
default:
return
}
// Check that first result type is "reflect.Method". Note that we have to check sym name and sym package
// separately, as we can't check for exact string "reflect.Method" reliably (e.g., see #19028 and #38515).
if s := t.Results().Field(0).Type.Sym(); s != nil && s.Name == "Method" && types.IsReflectPkg(s.Pkg) {
ir.CurFunc.SetReflectMethod(true)
// The LSym is initialized at this point. We need to set the attribute on the LSym.
ir.CurFunc.LSym.Set(obj.AttrReflectMethod, true)
}
}
func usefield(n *ir.SelectorExpr) {
if !buildcfg.Experiment.FieldTrack {
return
}
switch n.Op() {
default:
base.Fatalf("usefield %v", n.Op())
case ir.ODOT, ir.ODOTPTR:
break
}
field := n.Selection
if field == nil {
base.Fatalf("usefield %v %v without paramfld", n.X.Type(), n.Sel)
}
if field.Sym != n.Sel {
base.Fatalf("field inconsistency: %v != %v", field.Sym, n.Sel)
}
if !strings.Contains(field.Note, "go:\"track\"") {
return
}
outer := n.X.Type()
if outer.IsPtr() {
outer = outer.Elem()
}
if outer.Sym() == nil {
base.Errorf("tracked field must be in named struct type")
}
sym := reflectdata.TrackSym(outer, field)
if ir.CurFunc.FieldTrack == nil {
ir.CurFunc.FieldTrack = make(map[*obj.LSym]struct{})
}
ir.CurFunc.FieldTrack[sym] = struct{}{}
}