// Copyright 2023 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 inline // This file defines the callee side of the "fallible constant" analysis. import ( "fmt" "go/ast" "go/constant" "go/format" "go/token" "go/types" "strconv" "strings" "golang.org/x/tools/go/types/typeutil" "golang.org/x/tools/internal/aliases" "golang.org/x/tools/internal/typeparams" ) // falconResult is the result of the analysis of the callee. type falconResult struct { Types []falconType // types for falcon constraint environment Constraints []string // constraints (Go expressions) on values of fallible constants } // A falconType specifies the name and underlying type of a synthetic // defined type for use in falcon constraints. // // Unique types from callee code are bijectively mapped onto falcon // types so that constraints are independent of callee type // information but preserve type equivalence classes. // // Fresh names are deliberately obscure to avoid shadowing even if a // callee parameter has a nanme like "int" or "any". type falconType struct { Name string Kind types.BasicKind // string/number/bool } // falcon identifies "fallible constant" expressions, which are // expressions that may fail to compile if one or more of their // operands is changed from non-constant to constant. // // Consider: // // func sub(s string, i, j int) string { return s[i:j] } // // If parameters are replaced by constants, the compiler is // required to perform these additional checks: // // - if i is constant, 0 <= i. // - if s and i are constant, i <= len(s). // - ditto for j. // - if i and j are constant, i <= j. // // s[i:j] is thus a "fallible constant" expression dependent on {s, i, // j}. Each falcon creates a set of conditional constraints across one // or more parameter variables. // // - When inlining a call such as sub("abc", -1, 2), the parameter i // cannot be eliminated by substitution as its argument value is // negative. // // - When inlining sub("", 2, 1), all three parameters cannot be // simultaneously eliminated by substitution without violating i // <= len(s) and j <= len(s), but the parameters i and j could be // safely eliminated without s. // // Parameters that cannot be eliminated must remain non-constant, // either in the form of a binding declaration: // // { var i int = -1; return "abc"[i:2] } // // or a parameter of a literalization: // // func (i int) string { return "abc"[i:2] }(-1) // // These example expressions are obviously doomed to fail at run // time, but in realistic cases such expressions are dominated by // appropriate conditions that make them reachable only when safe: // // if 0 <= i && i <= j && j <= len(s) { _ = s[i:j] } // // (In principle a more sophisticated inliner could entirely eliminate // such unreachable blocks based on the condition being always-false // for the given parameter substitution, but this is tricky to do safely // because the type-checker considers only a single configuration. // Consider: if runtime.GOOS == "linux" { ... }.) // // We believe this is an exhaustive list of "fallible constant" operations: // // - switch z { case x: case y } // duplicate case values // - s[i], s[i:j], s[i:j:k] // index out of bounds (0 <= i <= j <= k <= len(s)) // - T{x: 0} // index out of bounds, duplicate index // - x/y, x%y, x/=y, x%=y // integer division by zero; minint/-1 overflow // - x+y, x-y, x*y // arithmetic overflow // - x< 1 { var elts []ast.Expr for _, elem := range elems { elts = append(elts, &ast.KeyValueExpr{ Key: elem, Value: makeIntLit(0), }) } st.emit(&ast.CompositeLit{ Type: typ, Elts: elts, }) } } // -- traversal -- // The traversal functions scan the callee body for expressions that // are not constant but would become constant if the parameter vars // were redeclared as constants, and emits for each one a constraint // (a Go expression) with the property that it will not type-check // (using types.CheckExpr) if the particular argument values are // unsuitable. // // These constraints are checked by Inline with the actual // constant argument values. Violations cause it to reject // parameters as candidates for substitution. func (st *falconState) stmt(s ast.Stmt) { ast.Inspect(s, func(n ast.Node) bool { switch n := n.(type) { case ast.Expr: _ = st.expr(n) return false // skip usual traversal case *ast.AssignStmt: switch n.Tok { case token.QUO_ASSIGN, token.REM_ASSIGN: // x /= y // Possible "integer division by zero" // Emit constraint: 1/y. _ = st.expr(n.Lhs[0]) kY := st.expr(n.Rhs[0]) if kY, ok := kY.(ast.Expr); ok { op := token.QUO if n.Tok == token.REM_ASSIGN { op = token.REM } st.emit(&ast.BinaryExpr{ Op: op, X: makeIntLit(1), Y: kY, }) } return false // skip usual traversal } case *ast.SwitchStmt: if n.Init != nil { st.stmt(n.Init) } tBool := types.Type(types.Typ[types.Bool]) tagType := tBool // default: true if n.Tag != nil { st.expr(n.Tag) tagType = st.info.TypeOf(n.Tag) } // Possible "duplicate case value". // Emit constraint map[T]int{v1: 0, ..., vN:0} // to ensure all maybe-constant case values are unique // (unless switch tag is boolean, which is relaxed). var unique []ast.Expr for _, clause := range n.Body.List { clause := clause.(*ast.CaseClause) for _, caseval := range clause.List { if k := st.expr(caseval); k != nil { unique = append(unique, st.toExpr(k)) } } for _, stmt := range clause.Body { st.stmt(stmt) } } if unique != nil && !types.Identical(tagType.Underlying(), tBool) { tname := st.any if !types.IsInterface(tagType) { tname = st.typename(tagType) } t := &ast.MapType{ Key: makeIdent(tname), Value: makeIdent(st.int), } st.emitUnique(t, unique) } } return true }) } // fieldTypes visits the .Type of each field in the list. func (st *falconState) fieldTypes(fields *ast.FieldList) { if fields != nil { for _, field := range fields.List { _ = st.expr(field.Type) } } } // expr visits the expression (or type) and returns a // non-nil result if the expression is constant or would // become constant if all suitable function parameters were // redeclared as constants. // // If the expression is constant, st.expr returns its type // and value (types.TypeAndValue). If the expression would // become constant, st.expr returns an ast.Expr tree whose // leaves are literals and parameter references, and whose // interior nodes are operations that may become constant, // such as -x, x+y, f(x), and T(x). We call these would-be // constant expressions "fallible constants", since they may // fail to type-check for some values of x, i, and j. (We // refer to the non-nil cases collectively as "maybe // constant", and the nil case as "definitely non-constant".) // // As a side effect, st.expr emits constraints for each // fallible constant expression; this is its main purpose. // // Consequently, st.expr must visit the entire subtree so // that all necessary constraints are emitted. It may not // short-circuit the traversal when it encounters a constant // subexpression as constants may contain arbitrary other // syntax that may impose constraints. Consider (as always) // this contrived but legal example of a type parameter (!) // that contains statement syntax: // // func f[T [unsafe.Sizeof(func() { stmts })]int]() // // There is no need to emit constraints for (e.g.) s[i] when s // and i are already constants, because we know the expression // is sound, but it is sometimes easier to emit these // redundant constraints than to avoid them. func (st *falconState) expr(e ast.Expr) (res any) { // = types.TypeAndValue | ast.Expr tv := st.info.Types[e] if tv.Value != nil { // A constant value overrides any other result. defer func() { res = tv }() } switch e := e.(type) { case *ast.Ident: if v, ok := st.info.Uses[e].(*types.Var); ok { if _, ok := st.params[v]; ok && isBasic(v.Type(), types.IsConstType) { return e // reference to constable parameter } } // (References to *types.Const are handled by the defer.) case *ast.BasicLit: // constant case *ast.ParenExpr: return st.expr(e.X) case *ast.FuncLit: _ = st.expr(e.Type) st.stmt(e.Body) // definitely non-constant case *ast.CompositeLit: // T{k: v, ...}, where T ∈ {array,*array,slice,map}, // imposes a constraint that all constant k are // distinct and, for arrays [n]T, within range 0-n. // // Types matter, not just values. For example, // an interface-keyed map may contain keys // that are numerically equal so long as they // are of distinct types. For example: // // type myint int // map[any]bool{1: true, 1: true} // error: duplicate key // map[any]bool{1: true, int16(1): true} // ok // map[any]bool{1: true, myint(1): true} // ok // // This can be asserted by emitting a // constraint of the form T{k1: 0, ..., kN: 0}. if e.Type != nil { _ = st.expr(e.Type) } t := aliases.Unalias(typeparams.Deref(tv.Type)) var uniques []ast.Expr for _, elt := range e.Elts { if kv, ok := elt.(*ast.KeyValueExpr); ok { if !is[*types.Struct](t) { if k := st.expr(kv.Key); k != nil { uniques = append(uniques, st.toExpr(k)) } } _ = st.expr(kv.Value) } else { _ = st.expr(elt) } } if uniques != nil { // Inv: not a struct. // The type T in constraint T{...} depends on the CompLit: // - for a basic-keyed map, use map[K]int; // - for an interface-keyed map, use map[any]int; // - for a slice, use []int; // - for an array or *array, use [n]int. // The last two entail progressively stronger index checks. var ct ast.Expr // type syntax for constraint switch t := t.(type) { case *types.Map: if types.IsInterface(t.Key()) { ct = &ast.MapType{ Key: makeIdent(st.any), Value: makeIdent(st.int), } } else { ct = &ast.MapType{ Key: makeIdent(st.typename(t.Key())), Value: makeIdent(st.int), } } case *types.Array: // or *array ct = &ast.ArrayType{ Len: makeIntLit(t.Len()), Elt: makeIdent(st.int), } default: panic(t) } st.emitUnique(ct, uniques) } // definitely non-constant case *ast.SelectorExpr: _ = st.expr(e.X) _ = st.expr(e.Sel) // The defer is sufficient to handle // qualified identifiers (pkg.Const). // All other cases are definitely non-constant. case *ast.IndexExpr: if tv.IsType() { // type C[T] _ = st.expr(e.X) _ = st.expr(e.Index) } else { // term x[i] // // Constraints (if x is slice/string/array/*array, not map): // - i >= 0 // if i is a fallible constant // - i < len(x) // if x is array/*array and // i is a fallible constant; // or if s is a string and both i, // s are maybe-constants, // but not both are constants. kX := st.expr(e.X) kI := st.expr(e.Index) if kI != nil && !is[*types.Map](st.info.TypeOf(e.X).Underlying()) { if kI, ok := kI.(ast.Expr); ok { st.emitNonNegative(kI) } // Emit constraint to check indices against known length. // TODO(adonovan): factor with SliceExpr logic. var x ast.Expr if kX != nil { // string x = st.toExpr(kX) } else if arr, ok := typeparams.CoreType(typeparams.Deref(st.info.TypeOf(e.X))).(*types.Array); ok { // array, *array x = &ast.CompositeLit{ Type: &ast.ArrayType{ Len: makeIntLit(arr.Len()), Elt: makeIdent(st.int), }, } } if x != nil { st.emit(&ast.IndexExpr{ X: x, Index: st.toExpr(kI), }) } } } // definitely non-constant case *ast.SliceExpr: // x[low:high:max] // // Emit non-negative constraints for each index, // plus low <= high <= max <= len(x) // for each pair that are maybe-constant // but not definitely constant. kX := st.expr(e.X) var kLow, kHigh, kMax any if e.Low != nil { kLow = st.expr(e.Low) if kLow != nil { if kLow, ok := kLow.(ast.Expr); ok { st.emitNonNegative(kLow) } } } if e.High != nil { kHigh = st.expr(e.High) if kHigh != nil { if kHigh, ok := kHigh.(ast.Expr); ok { st.emitNonNegative(kHigh) } if kLow != nil { st.emitMonotonic(st.toExpr(kLow), st.toExpr(kHigh)) } } } if e.Max != nil { kMax = st.expr(e.Max) if kMax != nil { if kMax, ok := kMax.(ast.Expr); ok { st.emitNonNegative(kMax) } if kHigh != nil { st.emitMonotonic(st.toExpr(kHigh), st.toExpr(kMax)) } } } // Emit constraint to check indices against known length. var x ast.Expr if kX != nil { // string x = st.toExpr(kX) } else if arr, ok := typeparams.CoreType(typeparams.Deref(st.info.TypeOf(e.X))).(*types.Array); ok { // array, *array x = &ast.CompositeLit{ Type: &ast.ArrayType{ Len: makeIntLit(arr.Len()), Elt: makeIdent(st.int), }, } } if x != nil { // Avoid slice[::max] if kHigh is nonconstant (nil). high, max := st.toExpr(kHigh), st.toExpr(kMax) if high == nil { high = max // => slice[:max:max] } st.emit(&ast.SliceExpr{ X: x, Low: st.toExpr(kLow), High: high, Max: max, }) } // definitely non-constant case *ast.TypeAssertExpr: _ = st.expr(e.X) if e.Type != nil { _ = st.expr(e.Type) } case *ast.CallExpr: _ = st.expr(e.Fun) if tv, ok := st.info.Types[e.Fun]; ok && tv.IsType() { // conversion T(x) // // Possible "value out of range". kX := st.expr(e.Args[0]) if kX != nil && isBasic(tv.Type, types.IsConstType) { conv := convert(makeIdent(st.typename(tv.Type)), st.toExpr(kX)) if is[ast.Expr](kX) { st.emit(conv) } return conv } return nil // definitely non-constant } // call f(x) all := true // all args are possibly-constant kArgs := make([]ast.Expr, len(e.Args)) for i, arg := range e.Args { if kArg := st.expr(arg); kArg != nil { kArgs[i] = st.toExpr(kArg) } else { all = false } } // Calls to built-ins with fallibly constant arguments // may become constant. All other calls are either // constant or non-constant if id, ok := e.Fun.(*ast.Ident); ok && all && tv.Value == nil { if builtin, ok := st.info.Uses[id].(*types.Builtin); ok { switch builtin.Name() { case "len", "imag", "real", "complex", "min", "max": return &ast.CallExpr{ Fun: id, Args: kArgs, Ellipsis: e.Ellipsis, } } } } case *ast.StarExpr: // *T, *ptr _ = st.expr(e.X) case *ast.UnaryExpr: // + - ! ^ & <- ~ // // Possible "negation of minint". // Emit constraint: -x kX := st.expr(e.X) if kX != nil && !is[types.TypeAndValue](kX) { if e.Op == token.SUB { st.emit(&ast.UnaryExpr{ Op: e.Op, X: st.toExpr(kX), }) } return &ast.UnaryExpr{ Op: e.Op, X: st.toExpr(kX), } } case *ast.BinaryExpr: kX := st.expr(e.X) kY := st.expr(e.Y) switch e.Op { case token.QUO, token.REM: // x/y, x%y // // Possible "integer division by zero" or // "minint / -1" overflow. // Emit constraint: x/y or 1/y if kY != nil { if kX == nil { kX = makeIntLit(1) } st.emit(&ast.BinaryExpr{ Op: e.Op, X: st.toExpr(kX), Y: st.toExpr(kY), }) } case token.ADD, token.SUB, token.MUL: // x+y, x-y, x*y // // Possible "arithmetic overflow". // Emit constraint: x+y if kX != nil && kY != nil { st.emit(&ast.BinaryExpr{ Op: e.Op, X: st.toExpr(kX), Y: st.toExpr(kY), }) } case token.SHL, token.SHR: // x << y, x >> y // // Possible "constant shift too large". // Either operand may be too large individually, // and they may be too large together. // Emit constraint: // x << y (if both maybe-constant) // x << 0 (if y is non-constant) // 1 << y (if x is non-constant) if kX != nil || kY != nil { x := st.toExpr(kX) if x == nil { x = makeIntLit(1) } y := st.toExpr(kY) if y == nil { y = makeIntLit(0) } st.emit(&ast.BinaryExpr{ Op: e.Op, X: x, Y: y, }) } case token.LSS, token.GTR, token.EQL, token.NEQ, token.LEQ, token.GEQ: // < > == != <= <= // // A "x cmp y" expression with constant operands x, y is // itself constant, but I can't see how a constant bool // could be fallible: the compiler doesn't reject duplicate // boolean cases in a switch, presumably because boolean // switches are less like n-way branches and more like // sequential if-else chains with possibly overlapping // conditions; and there is (sadly) no way to convert a // boolean constant to an int constant. } if kX != nil && kY != nil { return &ast.BinaryExpr{ Op: e.Op, X: st.toExpr(kX), Y: st.toExpr(kY), } } // types // // We need to visit types (and even type parameters) // in order to reach all the places where things could go wrong: // // const ( // s = "" // i = 0 // ) // type C[T [unsafe.Sizeof(func() { _ = s[i] })]int] bool case *ast.IndexListExpr: _ = st.expr(e.X) for _, expr := range e.Indices { _ = st.expr(expr) } case *ast.Ellipsis: if e.Elt != nil { _ = st.expr(e.Elt) } case *ast.ArrayType: if e.Len != nil { _ = st.expr(e.Len) } _ = st.expr(e.Elt) case *ast.StructType: st.fieldTypes(e.Fields) case *ast.FuncType: st.fieldTypes(e.TypeParams) st.fieldTypes(e.Params) st.fieldTypes(e.Results) case *ast.InterfaceType: st.fieldTypes(e.Methods) case *ast.MapType: _ = st.expr(e.Key) _ = st.expr(e.Value) case *ast.ChanType: _ = st.expr(e.Value) } return } // toExpr converts the result of visitExpr to a falcon expression. // (We don't do this in visitExpr as we first need to discriminate // constants from maybe-constants.) func (st *falconState) toExpr(x any) ast.Expr { switch x := x.(type) { case nil: return nil case types.TypeAndValue: lit := makeLiteral(x.Value) if !isBasic(x.Type, types.IsUntyped) { // convert to "typed" type lit = &ast.CallExpr{ Fun: makeIdent(st.typename(x.Type)), Args: []ast.Expr{lit}, } } return lit case ast.Expr: return x default: panic(x) } } func makeLiteral(v constant.Value) ast.Expr { switch v.Kind() { case constant.Bool: // Rather than refer to the true or false built-ins, // which could be shadowed by poorly chosen parameter // names, we use 0 == 0 for true and 0 != 0 for false. op := token.EQL if !constant.BoolVal(v) { op = token.NEQ } return &ast.BinaryExpr{ Op: op, X: makeIntLit(0), Y: makeIntLit(0), } case constant.String: return &ast.BasicLit{ Kind: token.STRING, Value: v.ExactString(), } case constant.Int: return &ast.BasicLit{ Kind: token.INT, Value: v.ExactString(), } case constant.Float: return &ast.BasicLit{ Kind: token.FLOAT, Value: v.ExactString(), } case constant.Complex: // The components could be float or int. y := makeLiteral(constant.Imag(v)) y.(*ast.BasicLit).Value += "i" // ugh if re := constant.Real(v); !consteq(re, kZeroInt) { // complex: x + yi y = &ast.BinaryExpr{ Op: token.ADD, X: makeLiteral(re), Y: y, } } return y default: panic(v.Kind()) } } func makeIntLit(x int64) *ast.BasicLit { return &ast.BasicLit{ Kind: token.INT, Value: strconv.FormatInt(x, 10), } } func isBasic(t types.Type, info types.BasicInfo) bool { basic, ok := t.Underlying().(*types.Basic) return ok && basic.Info()&info != 0 }