// Comapres xv to yv using operator op // Both xv and yv must be loaded and have a compatible type (as determined by negotiateType) func compareOp(op token.Token, xv *Variable, yv *Variable) (bool, error) { switch xv.Kind { case reflect.Bool: fallthrough case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: fallthrough case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: fallthrough case reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128: return constantCompare(op, xv.Value, yv.Value) case reflect.String: if int64(len(constant.StringVal(xv.Value))) != xv.Len || int64(len(constant.StringVal(yv.Value))) != yv.Len { return false, fmt.Errorf("string too long for comparison") } return constantCompare(op, xv.Value, yv.Value) } if op != token.EQL && op != token.NEQ { return false, fmt.Errorf("operator %s not defined on %s", op.String(), xv.Kind.String()) } var eql bool var err error switch xv.Kind { case reflect.Ptr: eql = xv.Children[0].Addr == yv.Children[0].Addr case reflect.Array: if int64(len(xv.Children)) != xv.Len || int64(len(yv.Children)) != yv.Len { return false, fmt.Errorf("array too long for comparison") } eql, err = equalChildren(xv, yv, true) case reflect.Struct: if len(xv.Children) != len(yv.Children) { return false, nil } if int64(len(xv.Children)) != xv.Len || int64(len(yv.Children)) != yv.Len { return false, fmt.Errorf("sturcture too deep for comparison") } eql, err = equalChildren(xv, yv, false) case reflect.Slice, reflect.Map, reflect.Func, reflect.Chan: if xv != nilVariable && yv != nilVariable { return false, fmt.Errorf("can not compare %s variables", xv.Kind.String()) } eql = xv.base == yv.base default: return false, fmt.Errorf("unimplemented comparison of %s variables", xv.Kind.String()) } if op == token.NEQ { return !eql, err } return eql, err }
// goVal returns the Go value for val, or nil. func goVal(val constant.Value) interface{} { // val should exist, but be conservative and check if val == nil { return nil } // Match implementation restriction of other compilers. // gc only checks duplicates for integer, floating-point // and string values, so only create Go values for these // types. switch val.Kind() { case constant.Int: if x, ok := constant.Int64Val(val); ok { return x } if x, ok := constant.Uint64Val(val); ok { return x } case constant.Float: if x, ok := constant.Float64Val(val); ok { return x } case constant.String: return constant.StringVal(val) } return nil }
func (p *exporter) value(x constant.Value) { if trace { p.tracef("value { ") defer p.tracef("} ") } switch kind := x.Kind(); kind { case constant.Bool: tag := falseTag if constant.BoolVal(x) { tag = trueTag } p.int(tag) case constant.Int: if i, ok := constant.Int64Val(x); ok { p.int(int64Tag) p.int64(i) return } p.int(floatTag) p.float(x) case constant.Float: p.int(fractionTag) p.fraction(x) case constant.Complex: p.int(complexTag) p.fraction(constant.Real(x)) p.fraction(constant.Imag(x)) case constant.String: p.int(stringTag) p.string(constant.StringVal(x)) default: panic(fmt.Sprintf("unexpected value kind %d", kind)) } }
func (dbp *Process) getGoInformation() (ver GoVersion, isextld bool, err error) { vv, err := dbp.EvalPackageVariable("runtime.buildVersion") if err != nil { err = fmt.Errorf("Could not determine version number: %v\n", err) return } if vv.Unreadable != nil { err = fmt.Errorf("Unreadable version number: %v\n", vv.Unreadable) return } ver, ok := parseVersionString(constant.StringVal(vv.Value)) if !ok { err = fmt.Errorf("Could not parse version number: %v\n", vv.Value) return } rdr := dbp.DwarfReader() rdr.Seek(0) for entry, err := rdr.NextCompileUnit(); entry != nil; entry, err = rdr.NextCompileUnit() { if err != nil { return ver, isextld, err } if prod, ok := entry.Val(dwarf.AttrProducer).(string); ok && (strings.HasPrefix(prod, "GNU AS")) { isextld = true break } } return }
func (gvar *Variable) parseG() (*G, error) { mem := gvar.mem dbp := gvar.dbp gaddr := uint64(gvar.Addr) _, deref := gvar.RealType.(*dwarf.PtrType) initialInstructions := make([]byte, dbp.arch.PtrSize()+1) initialInstructions[0] = op.DW_OP_addr binary.LittleEndian.PutUint64(initialInstructions[1:], gaddr) if deref { gaddrbytes, err := mem.readMemory(uintptr(gaddr), dbp.arch.PtrSize()) if err != nil { return nil, fmt.Errorf("error derefing *G %s", err) } initialInstructions = append([]byte{op.DW_OP_addr}, gaddrbytes...) gaddr = binary.LittleEndian.Uint64(gaddrbytes) if gaddr == 0 { id := 0 if thread, ok := mem.(*Thread); ok { id = thread.ID } return nil, NoGError{tid: id} } } if gaddr == 0 { return nil, NoGError{} } gvar.loadValue() if gvar.Unreadable != nil { return nil, gvar.Unreadable } schedVar := gvar.toFieldNamed("sched") pc, _ := constant.Int64Val(schedVar.toFieldNamed("pc").Value) sp, _ := constant.Int64Val(schedVar.toFieldNamed("sp").Value) id, _ := constant.Int64Val(gvar.toFieldNamed("goid").Value) gopc, _ := constant.Int64Val(gvar.toFieldNamed("gopc").Value) waitReason := constant.StringVal(gvar.toFieldNamed("waitreason").Value) d := gvar.toFieldNamed("_defer") deferPC := int64(0) fnvar := d.toFieldNamed("fn") if fnvar != nil { fnvalvar := fnvar.toFieldNamed("fn") deferPC, _ = constant.Int64Val(fnvalvar.Value) } status, _ := constant.Int64Val(gvar.toFieldNamed("atomicstatus").Value) f, l, fn := gvar.dbp.goSymTable.PCToLine(uint64(pc)) g := &G{ ID: int(id), GoPC: uint64(gopc), PC: uint64(pc), SP: uint64(sp), WaitReason: waitReason, DeferPC: uint64(deferPC), Status: uint64(status), CurrentLoc: Location{PC: uint64(pc), File: f, Line: l, Fn: fn}, dbp: gvar.dbp, } return g, nil }
// checkPrintf checks a call to a formatted print routine such as Printf. // call.Args[formatIndex] is (well, should be) the format argument. func (f *File) checkPrintf(call *ast.CallExpr, name string, formatIndex int) { if formatIndex >= len(call.Args) { f.Bad(call.Pos(), "too few arguments in call to", name) return } lit := f.pkg.types[call.Args[formatIndex]].Value if lit == nil { if *verbose { f.Warn(call.Pos(), "can't check non-constant format in call to", name) } return } if lit.Kind() != constant.String { f.Badf(call.Pos(), "constant %v not a string in call to %s", lit, name) return } format := constant.StringVal(lit) firstArg := formatIndex + 1 // Arguments are immediately after format string. if !strings.Contains(format, "%") { if len(call.Args) > firstArg { f.Badf(call.Pos(), "no formatting directive in %s call", name) } return } // Hard part: check formats against args. argNum := firstArg indexed := false for i, w := 0, 0; i < len(format); i += w { w = 1 if format[i] == '%' { state := f.parsePrintfVerb(call, name, format[i:], firstArg, argNum) if state == nil { return } w = len(state.format) if state.indexed { indexed = true } if !f.okPrintfArg(call, state) { // One error per format is enough. return } if len(state.argNums) > 0 { // Continue with the next sequential argument. argNum = state.argNums[len(state.argNums)-1] + 1 } } } // Dotdotdot is hard. if call.Ellipsis.IsValid() && argNum >= len(call.Args)-1 { return } // If the arguments were direct indexed, we assume the programmer knows what's up. // Otherwise, there should be no leftover arguments. if !indexed && argNum != len(call.Args) { expect := argNum - firstArg numArgs := len(call.Args) - firstArg f.Badf(call.Pos(), "wrong number of args for format in %s call: %d needed but %d args", name, expect, numArgs) } }
// stringConstantArg returns call's string constant argument at the index idx. // // ("", false) is returned if call's argument at the index idx isn't a string // constant. func stringConstantArg(f *File, call *ast.CallExpr, idx int) (string, bool) { if idx >= len(call.Args) { return "", false } arg := call.Args[idx] lit := f.pkg.types[arg].Value if lit != nil && lit.Kind() == constant.String { return constant.StringVal(lit), true } return "", false }
// constValue returns the value of the constant with the // dynamic type tag appropriate for c.Type(). func constValue(c *ssa.Const) value { if c.IsNil() { return zero(c.Type()) // typed nil } if t, ok := c.Type().Underlying().(*types.Basic); ok { // TODO(adonovan): eliminate untyped constants from SSA form. switch t.Kind() { case types.Bool, types.UntypedBool: return exact.BoolVal(c.Value) case types.Int, types.UntypedInt: // Assume sizeof(int) is same on host and target. return int(c.Int64()) case types.Int8: return int8(c.Int64()) case types.Int16: return int16(c.Int64()) case types.Int32, types.UntypedRune: return int32(c.Int64()) case types.Int64: return c.Int64() case types.Uint: // Assume sizeof(uint) is same on host and target. return uint(c.Uint64()) case types.Uint8: return uint8(c.Uint64()) case types.Uint16: return uint16(c.Uint64()) case types.Uint32: return uint32(c.Uint64()) case types.Uint64: return c.Uint64() case types.Uintptr: // Assume sizeof(uintptr) is same on host and target. return uintptr(c.Uint64()) case types.Float32: return float32(c.Float64()) case types.Float64, types.UntypedFloat: return c.Float64() case types.Complex64: return complex64(c.Complex128()) case types.Complex128, types.UntypedComplex: return c.Complex128() case types.String, types.UntypedString: if c.Value.Kind() == exact.String { return exact.StringVal(c.Value) } return string(rune(c.Int64())) } } panic(fmt.Sprintf("constValue: %s", c)) }
func (c *funcContext) identifierConstant(expr ast.Expr) (string, bool) { val := c.p.Types[expr].Value if val == nil { return "", false } s := constant.StringVal(val) if len(s) == 0 { return "", false } for i, c := range s { if !((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (i > 0 && c >= '0' && c <= '9') || c == '_' || c == '$') { return "", false } } return s, true }
func (c *Const) RelString(from *types.Package) string { var s string if c.Value == nil { s = "nil" } else if c.Value.Kind() == exact.String { s = exact.StringVal(c.Value) const max = 20 // TODO(adonovan): don't cut a rune in half. if len(s) > max { s = s[:max-3] + "..." // abbreviate } s = strconv.Quote(s) } else { s = c.Value.String() } return s + ":" + relType(c.Type(), from) }
func newConstant(val constant.Value, mem memoryReadWriter) *Variable { v := &Variable{Value: val, mem: mem, loaded: true} switch val.Kind() { case constant.Int: v.Kind = reflect.Int case constant.Float: v.Kind = reflect.Float64 case constant.Bool: v.Kind = reflect.Bool case constant.Complex: v.Kind = reflect.Complex128 case constant.String: v.Kind = reflect.String v.Len = int64(len(constant.StringVal(val))) } return v }
func (p *exporter) value(x constant.Value) { if trace { p.tracef("= ") } switch x.Kind() { case constant.Bool: tag := falseTag if constant.BoolVal(x) { tag = trueTag } p.tag(tag) case constant.Int: if v, exact := constant.Int64Val(x); exact { // common case: x fits into an int64 - use compact encoding p.tag(int64Tag) p.int64(v) return } // uncommon case: large x - use float encoding // (powers of 2 will be encoded efficiently with exponent) p.tag(floatTag) p.float(constant.ToFloat(x)) case constant.Float: p.tag(floatTag) p.float(x) case constant.Complex: p.tag(complexTag) p.float(constant.Real(x)) p.float(constant.Imag(x)) case constant.String: p.tag(stringTag) p.string(constant.StringVal(x)) case constant.Unknown: // package contains type errors p.tag(unknownTag) default: log.Fatalf("gcimporter: unexpected value %v (%T)", x, x) } }
func ConvertVar(v *proc.Variable) *Variable { r := Variable{ Addr: v.Addr, Name: v.Name, Kind: v.Kind, Len: v.Len, Cap: v.Cap, } if v.DwarfType != nil { r.Type = v.DwarfType.String() } if v.RealType != nil { r.RealType = v.RealType.String() } if v.Unreadable != nil { r.Unreadable = v.Unreadable.Error() } if v.Value != nil { switch v.Kind { case reflect.Float32: f, _ := constant.Float64Val(v.Value) r.Value = strconv.FormatFloat(f, 'f', -1, 32) case reflect.Float64: f, _ := constant.Float64Val(v.Value) r.Value = strconv.FormatFloat(f, 'f', -1, 64) case reflect.String, reflect.Func: r.Value = constant.StringVal(v.Value) default: r.Value = v.Value.String() } } r.Children = make([]Variable, len(v.Children)) for i := range v.Children { r.Children[i] = *ConvertVar(&v.Children[i]) } return &r }
func (c *converter) convertConstantValue(v goconstant.Value) constant.Value { if v == nil { return nil } if v, ok := c.converted[v]; ok { return v.(constant.Value) } var ret constant.Value switch v.Kind() { case goconstant.Bool: ret = constant.MakeBool(goconstant.BoolVal(v)) case goconstant.String: ret = constant.MakeString(goconstant.StringVal(v)) case goconstant.Int: ret = constant.MakeFromLiteral(v.String(), token.INT, 0) case goconstant.Float: ret = constant.MakeFromLiteral(v.String(), token.FLOAT, 0) case goconstant.Complex: ret = constant.MakeFromLiteral(v.String(), token.IMAG, 0) } c.converted[v] = ret return ret }
// stringVal returns the (unquoted) string value and true if // tv is a string constant; otherwise it returns "" and false. func stringVal(tv types.TypeAndValue) (string, bool) { if tv.IsValue() && tv.Value != nil && tv.Value.Kind() == constant.String { return constant.StringVal(tv.Value), true } return "", false }
// ConvertVar converts from proc.Variable to api.Variable. func ConvertVar(v *proc.Variable) *Variable { r := Variable{ Addr: v.Addr, OnlyAddr: v.OnlyAddr, Name: v.Name, Kind: v.Kind, Len: v.Len, Cap: v.Cap, } r.Type = prettyTypeName(v.DwarfType) r.RealType = prettyTypeName(v.RealType) if v.Unreadable != nil { r.Unreadable = v.Unreadable.Error() } if v.Value != nil { switch v.Kind { case reflect.Float32: f, _ := constant.Float64Val(v.Value) r.Value = strconv.FormatFloat(f, 'f', -1, 32) case reflect.Float64: f, _ := constant.Float64Val(v.Value) r.Value = strconv.FormatFloat(f, 'f', -1, 64) case reflect.String, reflect.Func: r.Value = constant.StringVal(v.Value) default: r.Value = v.Value.String() } } switch v.Kind { case reflect.Complex64: r.Children = make([]Variable, 2) r.Len = 2 real, _ := constant.Float64Val(constant.Real(v.Value)) imag, _ := constant.Float64Val(constant.Imag(v.Value)) r.Children[0].Name = "real" r.Children[0].Kind = reflect.Float32 r.Children[0].Value = strconv.FormatFloat(real, 'f', -1, 32) r.Children[1].Name = "imaginary" r.Children[1].Kind = reflect.Float32 r.Children[1].Value = strconv.FormatFloat(imag, 'f', -1, 32) case reflect.Complex128: r.Children = make([]Variable, 2) r.Len = 2 real, _ := constant.Float64Val(constant.Real(v.Value)) imag, _ := constant.Float64Val(constant.Imag(v.Value)) r.Children[0].Name = "real" r.Children[0].Kind = reflect.Float64 r.Children[0].Value = strconv.FormatFloat(real, 'f', -1, 64) r.Children[1].Name = "imaginary" r.Children[1].Kind = reflect.Float64 r.Children[1].Value = strconv.FormatFloat(imag, 'f', -1, 64) default: r.Children = make([]Variable, len(v.Children)) for i := range v.Children { r.Children[i] = *ConvertVar(&v.Children[i]) } } return &r }
// builtin type-checks a call to the built-in specified by id and // returns true if the call is valid, with *x holding the result; // but x.expr is not set. If the call is invalid, the result is // false, and *x is undefined. // func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ bool) { // append is the only built-in that permits the use of ... for the last argument bin := predeclaredFuncs[id] if call.Ellipsis.IsValid() && id != _Append { check.invalidOp(call.Ellipsis, "invalid use of ... with built-in %s", bin.name) check.use(call.Args...) return } // For len(x) and cap(x) we need to know if x contains any function calls or // receive operations. Save/restore current setting and set hasCallOrRecv to // false for the evaluation of x so that we can check it afterwards. // Note: We must do this _before_ calling unpack because unpack evaluates the // first argument before we even call arg(x, 0)! if id == _Len || id == _Cap { defer func(b bool) { check.hasCallOrRecv = b }(check.hasCallOrRecv) check.hasCallOrRecv = false } // determine actual arguments var arg getter nargs := len(call.Args) switch id { default: // make argument getter arg, nargs, _ = unpack(func(x *operand, i int) { check.multiExpr(x, call.Args[i]) }, nargs, false) if arg == nil { return } // evaluate first argument, if present if nargs > 0 { arg(x, 0) if x.mode == invalid { return } } case _Make, _New, _Offsetof, _Trace: // arguments require special handling } // check argument count { msg := "" if nargs < bin.nargs { msg = "not enough" } else if !bin.variadic && nargs > bin.nargs { msg = "too many" } if msg != "" { check.invalidOp(call.Rparen, "%s arguments for %s (expected %d, found %d)", msg, call, bin.nargs, nargs) return } } switch id { case _Append: // append(s S, x ...T) S, where T is the element type of S // spec: "The variadic function append appends zero or more values x to s of type // S, which must be a slice type, and returns the resulting slice, also of type S. // The values x are passed to a parameter of type ...T where T is the element type // of S and the respective parameter passing rules apply." S := x.typ var T Type if s, _ := S.Underlying().(*Slice); s != nil { T = s.elem } else { check.invalidArg(x.pos(), "%s is not a slice", x) return } // remember arguments that have been evaluated already alist := []operand{*x} // spec: "As a special case, append also accepts a first argument assignable // to type []byte with a second argument of string type followed by ... . // This form appends the bytes of the string. if nargs == 2 && call.Ellipsis.IsValid() && x.assignableTo(check.conf, NewSlice(universeByte), nil) { arg(x, 1) if x.mode == invalid { return } if isString(x.typ) { if check.Types != nil { sig := makeSig(S, S, x.typ) sig.variadic = true check.recordBuiltinType(call.Fun, sig) } x.mode = value x.typ = S break } alist = append(alist, *x) // fallthrough } // check general case by creating custom signature sig := makeSig(S, S, NewSlice(T)) // []T required for variadic signature sig.variadic = true check.arguments(x, call, sig, func(x *operand, i int) { // only evaluate arguments that have not been evaluated before if i < len(alist) { *x = alist[i] return } arg(x, i) }, nargs) // ok to continue even if check.arguments reported errors x.mode = value x.typ = S if check.Types != nil { check.recordBuiltinType(call.Fun, sig) } case _Cap, _Len: // cap(x) // len(x) mode := invalid var typ Type var val constant.Value switch typ = implicitArrayDeref(x.typ.Underlying()); t := typ.(type) { case *Basic: if isString(t) && id == _Len { if x.mode == constant_ { mode = constant_ val = constant.MakeInt64(int64(len(constant.StringVal(x.val)))) } else { mode = value } } case *Array: mode = value // spec: "The expressions len(s) and cap(s) are constants // if the type of s is an array or pointer to an array and // the expression s does not contain channel receives or // function calls; in this case s is not evaluated." if !check.hasCallOrRecv { mode = constant_ val = constant.MakeInt64(t.len) } case *Slice, *Chan: mode = value case *Map: if id == _Len { mode = value } } if mode == invalid { check.invalidArg(x.pos(), "%s for %s", x, bin.name) return } x.mode = mode x.typ = Typ[Int] x.val = val if check.Types != nil && mode != constant_ { check.recordBuiltinType(call.Fun, makeSig(x.typ, typ)) } case _Close: // close(c) c, _ := x.typ.Underlying().(*Chan) if c == nil { check.invalidArg(x.pos(), "%s is not a channel", x) return } if c.dir == RecvOnly { check.invalidArg(x.pos(), "%s must not be a receive-only channel", x) return } x.mode = novalue if check.Types != nil { check.recordBuiltinType(call.Fun, makeSig(nil, c)) } case _Complex: // complex(x, y floatT) complexT var y operand arg(&y, 1) if y.mode == invalid { return } // convert or check untyped arguments d := 0 if isUntyped(x.typ) { d |= 1 } if isUntyped(y.typ) { d |= 2 } switch d { case 0: // x and y are typed => nothing to do case 1: // only x is untyped => convert to type of y check.convertUntyped(x, y.typ) case 2: // only y is untyped => convert to type of x check.convertUntyped(&y, x.typ) case 3: // x and y are untyped => // 1) if both are constants, convert them to untyped // floating-point numbers if possible, // 2) if one of them is not constant (possible because // it contains a shift that is yet untyped), convert // both of them to float64 since they must have the // same type to succeed (this will result in an error // because shifts of floats are not permitted) if x.mode == constant_ && y.mode == constant_ { toFloat := func(x *operand) { if isNumeric(x.typ) && constant.Sign(constant.Imag(x.val)) == 0 { x.typ = Typ[UntypedFloat] } } toFloat(x) toFloat(&y) } else { check.convertUntyped(x, Typ[Float64]) check.convertUntyped(&y, Typ[Float64]) // x and y should be invalid now, but be conservative // and check below } } if x.mode == invalid || y.mode == invalid { return } // both argument types must be identical if !Identical(x.typ, y.typ) { check.invalidArg(x.pos(), "mismatched types %s and %s", x.typ, y.typ) return } // the argument types must be of floating-point type if !isFloat(x.typ) { check.invalidArg(x.pos(), "arguments have type %s, expected floating-point", x.typ) return } // if both arguments are constants, the result is a constant if x.mode == constant_ && y.mode == constant_ { x.val = constant.BinaryOp(constant.ToFloat(x.val), token.ADD, constant.MakeImag(constant.ToFloat(y.val))) } else { x.mode = value } // determine result type var res BasicKind switch x.typ.Underlying().(*Basic).kind { case Float32: res = Complex64 case Float64: res = Complex128 case UntypedFloat: res = UntypedComplex default: unreachable() } resTyp := Typ[res] if check.Types != nil && x.mode != constant_ { check.recordBuiltinType(call.Fun, makeSig(resTyp, x.typ, x.typ)) } x.typ = resTyp case _Copy: // copy(x, y []T) int var dst Type if t, _ := x.typ.Underlying().(*Slice); t != nil { dst = t.elem } var y operand arg(&y, 1) if y.mode == invalid { return } var src Type switch t := y.typ.Underlying().(type) { case *Basic: if isString(y.typ) { src = universeByte } case *Slice: src = t.elem } if dst == nil || src == nil { check.invalidArg(x.pos(), "copy expects slice arguments; found %s and %s", x, &y) return } if !Identical(dst, src) { check.invalidArg(x.pos(), "arguments to copy %s and %s have different element types %s and %s", x, &y, dst, src) return } if check.Types != nil { check.recordBuiltinType(call.Fun, makeSig(Typ[Int], x.typ, y.typ)) } x.mode = value x.typ = Typ[Int] case _Delete: // delete(m, k) m, _ := x.typ.Underlying().(*Map) if m == nil { check.invalidArg(x.pos(), "%s is not a map", x) return } arg(x, 1) // k if x.mode == invalid { return } if !x.assignableTo(check.conf, m.key, nil) { check.invalidArg(x.pos(), "%s is not assignable to %s", x, m.key) return } x.mode = novalue if check.Types != nil { check.recordBuiltinType(call.Fun, makeSig(nil, m, m.key)) } case _Imag, _Real: // imag(complexT) floatT // real(complexT) floatT // convert or check untyped argument if isUntyped(x.typ) { if x.mode == constant_ { // an untyped constant number can alway be considered // as a complex constant if isNumeric(x.typ) { x.typ = Typ[UntypedComplex] } } else { // an untyped non-constant argument may appear if // it contains a (yet untyped non-constant) shift // expression: convert it to complex128 which will // result in an error (shift of complex value) check.convertUntyped(x, Typ[Complex128]) // x should be invalid now, but be conservative and check if x.mode == invalid { return } } } // the argument must be of complex type if !isComplex(x.typ) { check.invalidArg(x.pos(), "argument has type %s, expected complex type", x.typ) return } // if the argument is a constant, the result is a constant if x.mode == constant_ { if id == _Real { x.val = constant.Real(x.val) } else { x.val = constant.Imag(x.val) } } else { x.mode = value } // determine result type var res BasicKind switch x.typ.Underlying().(*Basic).kind { case Complex64: res = Float32 case Complex128: res = Float64 case UntypedComplex: res = UntypedFloat default: unreachable() } resTyp := Typ[res] if check.Types != nil && x.mode != constant_ { check.recordBuiltinType(call.Fun, makeSig(resTyp, x.typ)) } x.typ = resTyp case _Make: // make(T, n) // make(T, n, m) // (no argument evaluated yet) arg0 := call.Args[0] T := check.typ(arg0) if T == Typ[Invalid] { return } var min int // minimum number of arguments switch T.Underlying().(type) { case *Slice: min = 2 case *Map, *Chan: min = 1 default: check.invalidArg(arg0.Pos(), "cannot make %s; type must be slice, map, or channel", arg0) return } if nargs < min || min+1 < nargs { check.errorf(call.Pos(), "%s expects %d or %d arguments; found %d", call, min, min+1, nargs) return } var sizes []int64 // constant integer arguments, if any for _, arg := range call.Args[1:] { if s, ok := check.index(arg, -1); ok && s >= 0 { sizes = append(sizes, s) } } if len(sizes) == 2 && sizes[0] > sizes[1] { check.invalidArg(call.Args[1].Pos(), "length and capacity swapped") // safe to continue } x.mode = value x.typ = T if check.Types != nil { params := [...]Type{T, Typ[Int], Typ[Int]} check.recordBuiltinType(call.Fun, makeSig(x.typ, params[:1+len(sizes)]...)) } case _New: // new(T) // (no argument evaluated yet) T := check.typ(call.Args[0]) if T == Typ[Invalid] { return } x.mode = value x.typ = &Pointer{base: T} if check.Types != nil { check.recordBuiltinType(call.Fun, makeSig(x.typ, T)) } case _Panic: // panic(x) T := new(Interface) check.assignment(x, T, "argument to panic") if x.mode == invalid { return } x.mode = novalue if check.Types != nil { check.recordBuiltinType(call.Fun, makeSig(nil, T)) } case _Print, _Println: // print(x, y, ...) // println(x, y, ...) var params []Type if nargs > 0 { params = make([]Type, nargs) for i := 0; i < nargs; i++ { if i > 0 { arg(x, i) // first argument already evaluated } check.assignment(x, nil, "argument to "+predeclaredFuncs[id].name) if x.mode == invalid { // TODO(gri) "use" all arguments? return } params[i] = x.typ } } x.mode = novalue if check.Types != nil { check.recordBuiltinType(call.Fun, makeSig(nil, params...)) } case _Recover: // recover() interface{} x.mode = value x.typ = new(Interface) if check.Types != nil { check.recordBuiltinType(call.Fun, makeSig(x.typ)) } case _Alignof: // unsafe.Alignof(x T) uintptr check.assignment(x, nil, "argument to unsafe.Alignof") if x.mode == invalid { return } x.mode = constant_ x.val = constant.MakeInt64(check.conf.alignof(x.typ)) x.typ = Typ[Uintptr] // result is constant - no need to record signature case _Offsetof: // unsafe.Offsetof(x T) uintptr, where x must be a selector // (no argument evaluated yet) arg0 := call.Args[0] selx, _ := unparen(arg0).(*ast.SelectorExpr) if selx == nil { check.invalidArg(arg0.Pos(), "%s is not a selector expression", arg0) check.use(arg0) return } check.expr(x, selx.X) if x.mode == invalid { return } base := derefStructPtr(x.typ) sel := selx.Sel.Name obj, index, indirect := LookupFieldOrMethod(base, false, check.pkg, sel) switch obj.(type) { case nil: check.invalidArg(x.pos(), "%s has no single field %s", base, sel) return case *Func: // TODO(gri) Using derefStructPtr may result in methods being found // that don't actually exist. An error either way, but the error // message is confusing. See: https://play.golang.org/p/al75v23kUy , // but go/types reports: "invalid argument: x.m is a method value". check.invalidArg(arg0.Pos(), "%s is a method value", arg0) return } if indirect { check.invalidArg(x.pos(), "field %s is embedded via a pointer in %s", sel, base) return } // TODO(gri) Should we pass x.typ instead of base (and indirect report if derefStructPtr indirected)? check.recordSelection(selx, FieldVal, base, obj, index, false) offs := check.conf.offsetof(base, index) x.mode = constant_ x.val = constant.MakeInt64(offs) x.typ = Typ[Uintptr] // result is constant - no need to record signature case _Sizeof: // unsafe.Sizeof(x T) uintptr check.assignment(x, nil, "argument to unsafe.Sizeof") if x.mode == invalid { return } x.mode = constant_ x.val = constant.MakeInt64(check.conf.sizeof(x.typ)) x.typ = Typ[Uintptr] // result is constant - no need to record signature case _Assert: // assert(pred) causes a typechecker error if pred is false. // The result of assert is the value of pred if there is no error. // Note: assert is only available in self-test mode. if x.mode != constant_ || !isBoolean(x.typ) { check.invalidArg(x.pos(), "%s is not a boolean constant", x) return } if x.val.Kind() != constant.Bool { check.errorf(x.pos(), "internal error: value of %s should be a boolean constant", x) return } if !constant.BoolVal(x.val) { check.errorf(call.Pos(), "%s failed", call) // compile-time assertion failure - safe to continue } // result is constant - no need to record signature case _Trace: // trace(x, y, z, ...) dumps the positions, expressions, and // values of its arguments. The result of trace is the value // of the first argument. // Note: trace is only available in self-test mode. // (no argument evaluated yet) if nargs == 0 { check.dump("%s: trace() without arguments", call.Pos()) x.mode = novalue break } var t operand x1 := x for _, arg := range call.Args { check.rawExpr(x1, arg, nil) // permit trace for types, e.g.: new(trace(T)) check.dump("%s: %s", x1.pos(), x1) x1 = &t // use incoming x only for first argument } // trace is only available in test mode - no need to record signature default: unreachable() } return true }
func TestVariableEvaluation(t *testing.T) { testcases := []struct { name string st reflect.Kind value interface{} length, cap int64 childrenlen int }{ {"a1", reflect.String, "foofoofoofoofoofoo", 18, 0, 0}, {"a11", reflect.Array, nil, 3, -1, 3}, {"a12", reflect.Slice, nil, 2, 2, 2}, {"a13", reflect.Slice, nil, 3, 3, 3}, {"a2", reflect.Int, int64(6), 0, 0, 0}, {"a3", reflect.Float64, float64(7.23), 0, 0, 0}, {"a4", reflect.Array, nil, 2, -1, 2}, {"a5", reflect.Slice, nil, 5, 5, 5}, {"a6", reflect.Struct, nil, 2, 0, 2}, {"a7", reflect.Ptr, nil, 1, 0, 1}, {"a8", reflect.Struct, nil, 2, 0, 2}, {"a9", reflect.Ptr, nil, 1, 0, 1}, {"baz", reflect.String, "bazburzum", 9, 0, 0}, {"neg", reflect.Int, int64(-1), 0, 0, 0}, {"f32", reflect.Float32, float64(float32(1.2)), 0, 0, 0}, {"c64", reflect.Complex64, complex128(complex64(1 + 2i)), 0, 0, 0}, {"c128", reflect.Complex128, complex128(2 + 3i), 0, 0, 0}, {"a6.Baz", reflect.Int, int64(8), 0, 0, 0}, {"a7.Baz", reflect.Int, int64(5), 0, 0, 0}, {"a8.Baz", reflect.String, "feh", 3, 0, 0}, {"a8", reflect.Struct, nil, 2, 0, 2}, {"i32", reflect.Array, nil, 2, -1, 2}, {"b1", reflect.Bool, true, 0, 0, 0}, {"b2", reflect.Bool, false, 0, 0, 0}, {"f", reflect.Func, "main.barfoo", 0, 0, 0}, {"ba", reflect.Slice, nil, 200, 200, 64}, } withTestProcess("testvariables", t, func(p *Process, fixture protest.Fixture) { assertNoError(p.Continue(), t, "Continue() returned an error") for _, tc := range testcases { v, err := evalVariable(p, tc.name) assertNoError(err, t, fmt.Sprintf("EvalVariable(%s)", tc.name)) if v.Kind != tc.st { t.Fatalf("%s simple type: expected: %s got: %s", tc.name, tc.st, v.Kind.String()) } if v.Value == nil && tc.value != nil { t.Fatalf("%s value: expected: %v got: %v", tc.name, tc.value, v.Value) } else { switch v.Kind { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: x, _ := constant.Int64Val(v.Value) if y, ok := tc.value.(int64); !ok || x != y { t.Fatalf("%s value: expected: %v got: %v", tc.name, tc.value, v.Value) } case reflect.Float32, reflect.Float64: x, _ := constant.Float64Val(v.Value) if y, ok := tc.value.(float64); !ok || x != y { t.Fatalf("%s value: expected: %v got: %v", tc.name, tc.value, v.Value) } case reflect.Complex64, reflect.Complex128: xr, _ := constant.Float64Val(constant.Real(v.Value)) xi, _ := constant.Float64Val(constant.Imag(v.Value)) if y, ok := tc.value.(complex128); !ok || complex(xr, xi) != y { t.Fatalf("%s value: expected: %v got: %v", tc.name, tc.value, v.Value) } case reflect.String: if y, ok := tc.value.(string); !ok || constant.StringVal(v.Value) != y { t.Fatalf("%s value: expected: %v got: %v", tc.name, tc.value, v.Value) } } } if v.Len != tc.length { t.Fatalf("%s len: expected: %d got: %d", tc.name, tc.length, v.Len) } if v.Cap != tc.cap { t.Fatalf("%s cap: expected: %d got: %d", tc.name, tc.cap, v.Cap) } if len(v.Children) != tc.childrenlen { t.Fatalf("%s children len: expected %d got: %d", tc.name, tc.childrenlen, len(v.Children)) } } }) }
func Test(t *testing.T) { switch runtime.GOOS { case "windows": t.Skipf("skipping test on %q (no /usr/bin/diff)", runtime.GOOS) } conf := loader.Config{ Fset: token.NewFileSet(), ParserMode: parser.ParseComments, } // Each entry is a single-file package. // (Multi-file packages aren't interesting for this test.) // Order matters: each non-template package is processed using // the preceding template package. for _, filename := range []string{ "testdata/A.template", "testdata/A1.go", "testdata/A2.go", "testdata/B.template", "testdata/B1.go", "testdata/C.template", "testdata/C1.go", "testdata/D.template", "testdata/D1.go", "testdata/E.template", "testdata/E1.go", "testdata/F.template", "testdata/F1.go", "testdata/G.template", "testdata/G1.go", "testdata/H.template", "testdata/H1.go", "testdata/bad_type.template", "testdata/no_before.template", "testdata/no_after_return.template", "testdata/type_mismatch.template", "testdata/expr_type_mismatch.template", } { pkgname := strings.TrimSuffix(filepath.Base(filename), ".go") conf.CreateFromFilenames(pkgname, filename) } iprog, err := conf.Load() if err != nil { t.Fatal(err) } var xform *eg.Transformer for _, info := range iprog.Created { file := info.Files[0] filename := iprog.Fset.File(file.Pos()).Name() // foo.go if strings.HasSuffix(filename, "template") { // a new template shouldFail, _ := info.Pkg.Scope().Lookup("shouldFail").(*types.Const) xform, err = eg.NewTransformer(iprog.Fset, info.Pkg, file, &info.Info, *verboseFlag) if err != nil { if shouldFail == nil { t.Errorf("NewTransformer(%s): %s", filename, err) } else if want := exact.StringVal(shouldFail.Val()); !strings.Contains(err.Error(), want) { t.Errorf("NewTransformer(%s): got error %q, want error %q", filename, err, want) } } else if shouldFail != nil { t.Errorf("NewTransformer(%s) succeeded unexpectedly; want error %q", filename, shouldFail.Val()) } continue } if xform == nil { t.Errorf("%s: no previous template", filename) continue } // apply previous template to this package n := xform.Transform(&info.Info, info.Pkg, file) if n == 0 { t.Errorf("%s: no matches", filename) continue } got := filename + "t" // foo.got golden := filename + "lden" // foo.golden // Write actual output to foo.got. if err := eg.WriteAST(iprog.Fset, got, file); err != nil { t.Error(err) } defer os.Remove(got) // Compare foo.got with foo.golden. var cmd *exec.Cmd switch runtime.GOOS { case "plan9": cmd = exec.Command("/bin/diff", "-c", golden, got) default: cmd = exec.Command("/usr/bin/diff", "-u", golden, got) } buf := new(bytes.Buffer) cmd.Stdout = buf cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { t.Errorf("eg tests for %s failed: %s.\n%s\n", filename, err, buf) if *updateFlag { t.Logf("Updating %s...", golden) if err := exec.Command("/bin/cp", got, golden).Run(); err != nil { t.Errorf("Update failed: %s", err) } } } } }
func (c *funcContext) translateExpr(expr ast.Expr) *expression { exprType := c.p.TypeOf(expr) if value := c.p.Types[expr].Value; value != nil { basic := exprType.Underlying().(*types.Basic) switch { case isBoolean(basic): return c.formatExpr("%s", strconv.FormatBool(constant.BoolVal(value))) case isInteger(basic): if is64Bit(basic) { if basic.Kind() == types.Int64 { d, ok := constant.Int64Val(constant.ToInt(value)) if !ok { panic("could not get exact uint") } return c.formatExpr("new %s(%s, %s)", c.typeName(exprType), strconv.FormatInt(d>>32, 10), strconv.FormatUint(uint64(d)&(1<<32-1), 10)) } d, ok := constant.Uint64Val(constant.ToInt(value)) if !ok { panic("could not get exact uint") } return c.formatExpr("new %s(%s, %s)", c.typeName(exprType), strconv.FormatUint(d>>32, 10), strconv.FormatUint(d&(1<<32-1), 10)) } d, ok := constant.Int64Val(constant.ToInt(value)) if !ok { panic("could not get exact int") } return c.formatExpr("%s", strconv.FormatInt(d, 10)) case isFloat(basic): f, _ := constant.Float64Val(value) return c.formatExpr("%s", strconv.FormatFloat(f, 'g', -1, 64)) case isComplex(basic): r, _ := constant.Float64Val(constant.Real(value)) i, _ := constant.Float64Val(constant.Imag(value)) if basic.Kind() == types.UntypedComplex { exprType = types.Typ[types.Complex128] } return c.formatExpr("new %s(%s, %s)", c.typeName(exprType), strconv.FormatFloat(r, 'g', -1, 64), strconv.FormatFloat(i, 'g', -1, 64)) case isString(basic): return c.formatExpr("%s", encodeString(constant.StringVal(value))) default: panic("Unhandled constant type: " + basic.String()) } } var obj types.Object switch e := expr.(type) { case *ast.SelectorExpr: obj = c.p.Uses[e.Sel] case *ast.Ident: obj = c.p.Defs[e] if obj == nil { obj = c.p.Uses[e] } } if obj != nil && typesutil.IsJsPackage(obj.Pkg()) { switch obj.Name() { case "Global": return c.formatExpr("$global") case "Module": return c.formatExpr("$module") case "Undefined": return c.formatExpr("undefined") } } switch e := expr.(type) { case *ast.CompositeLit: if ptrType, isPointer := exprType.(*types.Pointer); isPointer { exprType = ptrType.Elem() } collectIndexedElements := func(elementType types.Type) []string { var elements []string i := 0 zero := c.translateExpr(c.zeroValue(elementType)).String() for _, element := range e.Elts { if kve, isKve := element.(*ast.KeyValueExpr); isKve { key, ok := constant.Int64Val(constant.ToInt(c.p.Types[kve.Key].Value)) if !ok { panic("could not get exact int") } i = int(key) element = kve.Value } for len(elements) <= i { elements = append(elements, zero) } elements[i] = c.translateImplicitConversionWithCloning(element, elementType).String() i++ } return elements } switch t := exprType.Underlying().(type) { case *types.Array: elements := collectIndexedElements(t.Elem()) if len(elements) == 0 { return c.formatExpr("%s.zero()", c.typeName(t)) } zero := c.translateExpr(c.zeroValue(t.Elem())).String() for len(elements) < int(t.Len()) { elements = append(elements, zero) } return c.formatExpr(`$toNativeArray(%s, [%s])`, typeKind(t.Elem()), strings.Join(elements, ", ")) case *types.Slice: return c.formatExpr("new %s([%s])", c.typeName(exprType), strings.Join(collectIndexedElements(t.Elem()), ", ")) case *types.Map: entries := make([]string, len(e.Elts)) for i, element := range e.Elts { kve := element.(*ast.KeyValueExpr) entries[i] = fmt.Sprintf("{ k: %s, v: %s }", c.translateImplicitConversionWithCloning(kve.Key, t.Key()), c.translateImplicitConversionWithCloning(kve.Value, t.Elem())) } return c.formatExpr("$makeMap(%s.keyFor, [%s])", c.typeName(t.Key()), strings.Join(entries, ", ")) case *types.Struct: elements := make([]string, t.NumFields()) isKeyValue := true if len(e.Elts) != 0 { _, isKeyValue = e.Elts[0].(*ast.KeyValueExpr) } if !isKeyValue { for i, element := range e.Elts { elements[i] = c.translateImplicitConversionWithCloning(element, t.Field(i).Type()).String() } } if isKeyValue { for i := range elements { elements[i] = c.translateExpr(c.zeroValue(t.Field(i).Type())).String() } for _, element := range e.Elts { kve := element.(*ast.KeyValueExpr) for j := range elements { if kve.Key.(*ast.Ident).Name == t.Field(j).Name() { elements[j] = c.translateImplicitConversionWithCloning(kve.Value, t.Field(j).Type()).String() break } } } } return c.formatExpr("new %s.ptr(%s)", c.typeName(exprType), strings.Join(elements, ", ")) default: panic(fmt.Sprintf("Unhandled CompositeLit type: %T\n", t)) } case *ast.FuncLit: _, fun := translateFunction(e.Type, nil, e.Body, c, exprType.(*types.Signature), c.p.FuncLitInfos[e], "") if len(c.p.escapingVars) != 0 { names := make([]string, 0, len(c.p.escapingVars)) for obj := range c.p.escapingVars { names = append(names, c.p.objectNames[obj]) } sort.Strings(names) list := strings.Join(names, ", ") return c.formatExpr("(function(%s) { return %s; })(%s)", list, fun, list) } return c.formatExpr("(%s)", fun) case *ast.UnaryExpr: t := c.p.TypeOf(e.X) switch e.Op { case token.AND: if typesutil.IsJsObject(exprType) { return c.formatExpr("%e.object", e.X) } switch t.Underlying().(type) { case *types.Struct, *types.Array: return c.translateExpr(e.X) } switch x := astutil.RemoveParens(e.X).(type) { case *ast.CompositeLit: return c.formatExpr("$newDataPointer(%e, %s)", x, c.typeName(c.p.TypeOf(e))) case *ast.Ident: obj := c.p.Uses[x].(*types.Var) if c.p.escapingVars[obj] { return c.formatExpr("(%1s.$ptr || (%1s.$ptr = new %2s(function() { return this.$target[0]; }, function($v) { this.$target[0] = $v; }, %1s)))", c.p.objectNames[obj], c.typeName(exprType)) } return c.formatExpr(`(%1s || (%1s = new %2s(function() { return %3s; }, function($v) { %4s })))`, c.varPtrName(obj), c.typeName(exprType), c.objectName(obj), c.translateAssign(x, c.newIdent("$v", exprType), false)) case *ast.SelectorExpr: sel, ok := c.p.SelectionOf(x) if !ok { // qualified identifier obj := c.p.Uses[x.Sel].(*types.Var) return c.formatExpr(`(%1s || (%1s = new %2s(function() { return %3s; }, function($v) { %4s })))`, c.varPtrName(obj), c.typeName(exprType), c.objectName(obj), c.translateAssign(x, c.newIdent("$v", exprType), false)) } newSel := &ast.SelectorExpr{X: c.newIdent("this.$target", c.p.TypeOf(x.X)), Sel: x.Sel} c.setType(newSel, exprType) c.p.additionalSelections[newSel] = sel return c.formatExpr("(%1e.$ptr_%2s || (%1e.$ptr_%2s = new %3s(function() { return %4e; }, function($v) { %5s }, %1e)))", x.X, x.Sel.Name, c.typeName(exprType), newSel, c.translateAssign(newSel, c.newIdent("$v", exprType), false)) case *ast.IndexExpr: if _, ok := c.p.TypeOf(x.X).Underlying().(*types.Slice); ok { return c.formatExpr("$indexPtr(%1e.$array, %1e.$offset + %2e, %3s)", x.X, x.Index, c.typeName(exprType)) } return c.formatExpr("$indexPtr(%e, %e, %s)", x.X, x.Index, c.typeName(exprType)) case *ast.StarExpr: return c.translateExpr(x.X) default: panic(fmt.Sprintf("Unhandled: %T\n", x)) } case token.ARROW: call := &ast.CallExpr{ Fun: c.newIdent("$recv", types.NewSignature(nil, types.NewTuple(types.NewVar(0, nil, "", t)), types.NewTuple(types.NewVar(0, nil, "", exprType), types.NewVar(0, nil, "", types.Typ[types.Bool])), false)), Args: []ast.Expr{e.X}, } c.Blocking[call] = true if _, isTuple := exprType.(*types.Tuple); isTuple { return c.formatExpr("%e", call) } return c.formatExpr("%e[0]", call) } basic := t.Underlying().(*types.Basic) switch e.Op { case token.ADD: return c.translateExpr(e.X) case token.SUB: switch { case is64Bit(basic): return c.formatExpr("new %1s(-%2h, -%2l)", c.typeName(t), e.X) case isComplex(basic): return c.formatExpr("new %1s(-%2r, -%2i)", c.typeName(t), e.X) case isUnsigned(basic): return c.fixNumber(c.formatExpr("-%e", e.X), basic) default: return c.formatExpr("-%e", e.X) } case token.XOR: if is64Bit(basic) { return c.formatExpr("new %1s(~%2h, ~%2l >>> 0)", c.typeName(t), e.X) } return c.fixNumber(c.formatExpr("~%e", e.X), basic) case token.NOT: return c.formatExpr("!%e", e.X) default: panic(e.Op) } case *ast.BinaryExpr: if e.Op == token.NEQ { return c.formatExpr("!(%s)", c.translateExpr(&ast.BinaryExpr{ X: e.X, Op: token.EQL, Y: e.Y, })) } t := c.p.TypeOf(e.X) t2 := c.p.TypeOf(e.Y) _, isInterface := t2.Underlying().(*types.Interface) if isInterface || types.Identical(t, types.Typ[types.UntypedNil]) { t = t2 } if basic, isBasic := t.Underlying().(*types.Basic); isBasic && isNumeric(basic) { if is64Bit(basic) { switch e.Op { case token.MUL: return c.formatExpr("$mul64(%e, %e)", e.X, e.Y) case token.QUO: return c.formatExpr("$div64(%e, %e, false)", e.X, e.Y) case token.REM: return c.formatExpr("$div64(%e, %e, true)", e.X, e.Y) case token.SHL: return c.formatExpr("$shiftLeft64(%e, %f)", e.X, e.Y) case token.SHR: return c.formatExpr("$shiftRight%s(%e, %f)", toJavaScriptType(basic), e.X, e.Y) case token.EQL: return c.formatExpr("(%1h === %2h && %1l === %2l)", e.X, e.Y) case token.LSS: return c.formatExpr("(%1h < %2h || (%1h === %2h && %1l < %2l))", e.X, e.Y) case token.LEQ: return c.formatExpr("(%1h < %2h || (%1h === %2h && %1l <= %2l))", e.X, e.Y) case token.GTR: return c.formatExpr("(%1h > %2h || (%1h === %2h && %1l > %2l))", e.X, e.Y) case token.GEQ: return c.formatExpr("(%1h > %2h || (%1h === %2h && %1l >= %2l))", e.X, e.Y) case token.ADD, token.SUB: return c.formatExpr("new %3s(%1h %4t %2h, %1l %4t %2l)", e.X, e.Y, c.typeName(t), e.Op) case token.AND, token.OR, token.XOR: return c.formatExpr("new %3s(%1h %4t %2h, (%1l %4t %2l) >>> 0)", e.X, e.Y, c.typeName(t), e.Op) case token.AND_NOT: return c.formatExpr("new %3s(%1h & ~%2h, (%1l & ~%2l) >>> 0)", e.X, e.Y, c.typeName(t)) default: panic(e.Op) } } if isComplex(basic) { switch e.Op { case token.EQL: return c.formatExpr("(%1r === %2r && %1i === %2i)", e.X, e.Y) case token.ADD, token.SUB: return c.formatExpr("new %3s(%1r %4t %2r, %1i %4t %2i)", e.X, e.Y, c.typeName(t), e.Op) case token.MUL: return c.formatExpr("new %3s(%1r * %2r - %1i * %2i, %1r * %2i + %1i * %2r)", e.X, e.Y, c.typeName(t)) case token.QUO: return c.formatExpr("$divComplex(%e, %e)", e.X, e.Y) default: panic(e.Op) } } switch e.Op { case token.EQL: return c.formatParenExpr("%e === %e", e.X, e.Y) case token.LSS, token.LEQ, token.GTR, token.GEQ: return c.formatExpr("%e %t %e", e.X, e.Op, e.Y) case token.ADD, token.SUB: return c.fixNumber(c.formatExpr("%e %t %e", e.X, e.Op, e.Y), basic) case token.MUL: switch basic.Kind() { case types.Int32, types.Int: return c.formatParenExpr("$imul(%e, %e)", e.X, e.Y) case types.Uint32, types.Uintptr: return c.formatParenExpr("$imul(%e, %e) >>> 0", e.X, e.Y) } return c.fixNumber(c.formatExpr("%e * %e", e.X, e.Y), basic) case token.QUO: if isInteger(basic) { // cut off decimals shift := ">>" if isUnsigned(basic) { shift = ">>>" } return c.formatExpr(`(%1s = %2e / %3e, (%1s === %1s && %1s !== 1/0 && %1s !== -1/0) ? %1s %4s 0 : $throwRuntimeError("integer divide by zero"))`, c.newVariable("_q"), e.X, e.Y, shift) } if basic.Kind() == types.Float32 { return c.fixNumber(c.formatExpr("%e / %e", e.X, e.Y), basic) } return c.formatExpr("%e / %e", e.X, e.Y) case token.REM: return c.formatExpr(`(%1s = %2e %% %3e, %1s === %1s ? %1s : $throwRuntimeError("integer divide by zero"))`, c.newVariable("_r"), e.X, e.Y) case token.SHL, token.SHR: op := e.Op.String() if e.Op == token.SHR && isUnsigned(basic) { op = ">>>" } if v := c.p.Types[e.Y].Value; v != nil { i, _ := constant.Uint64Val(constant.ToInt(v)) if i >= 32 { return c.formatExpr("0") } return c.fixNumber(c.formatExpr("%e %s %s", e.X, op, strconv.FormatUint(i, 10)), basic) } if e.Op == token.SHR && !isUnsigned(basic) { return c.fixNumber(c.formatParenExpr("%e >> $min(%f, 31)", e.X, e.Y), basic) } y := c.newVariable("y") return c.fixNumber(c.formatExpr("(%s = %f, %s < 32 ? (%e %s %s) : 0)", y, e.Y, y, e.X, op, y), basic) case token.AND, token.OR: if isUnsigned(basic) { return c.formatParenExpr("(%e %t %e) >>> 0", e.X, e.Op, e.Y) } return c.formatParenExpr("%e %t %e", e.X, e.Op, e.Y) case token.AND_NOT: return c.fixNumber(c.formatParenExpr("%e & ~%e", e.X, e.Y), basic) case token.XOR: return c.fixNumber(c.formatParenExpr("%e ^ %e", e.X, e.Y), basic) default: panic(e.Op) } } switch e.Op { case token.ADD, token.LSS, token.LEQ, token.GTR, token.GEQ: return c.formatExpr("%e %t %e", e.X, e.Op, e.Y) case token.LAND: if c.Blocking[e.Y] { skipCase := c.caseCounter c.caseCounter++ resultVar := c.newVariable("_v") c.Printf("if (!(%s)) { %s = false; $s = %d; continue s; }", c.translateExpr(e.X), resultVar, skipCase) c.Printf("%s = %s; case %d:", resultVar, c.translateExpr(e.Y), skipCase) return c.formatExpr("%s", resultVar) } return c.formatExpr("%e && %e", e.X, e.Y) case token.LOR: if c.Blocking[e.Y] { skipCase := c.caseCounter c.caseCounter++ resultVar := c.newVariable("_v") c.Printf("if (%s) { %s = true; $s = %d; continue s; }", c.translateExpr(e.X), resultVar, skipCase) c.Printf("%s = %s; case %d:", resultVar, c.translateExpr(e.Y), skipCase) return c.formatExpr("%s", resultVar) } return c.formatExpr("%e || %e", e.X, e.Y) case token.EQL: switch u := t.Underlying().(type) { case *types.Array, *types.Struct: return c.formatExpr("$equal(%e, %e, %s)", e.X, e.Y, c.typeName(t)) case *types.Interface: return c.formatExpr("$interfaceIsEqual(%s, %s)", c.translateImplicitConversion(e.X, t), c.translateImplicitConversion(e.Y, t)) case *types.Pointer: if _, ok := u.Elem().Underlying().(*types.Array); ok { return c.formatExpr("$equal(%s, %s, %s)", c.translateImplicitConversion(e.X, t), c.translateImplicitConversion(e.Y, t), c.typeName(u.Elem())) } case *types.Basic: if isBoolean(u) { if b, ok := analysis.BoolValue(e.X, c.p.Info.Info); ok && b { return c.translateExpr(e.Y) } if b, ok := analysis.BoolValue(e.Y, c.p.Info.Info); ok && b { return c.translateExpr(e.X) } } } return c.formatExpr("%s === %s", c.translateImplicitConversion(e.X, t), c.translateImplicitConversion(e.Y, t)) default: panic(e.Op) } case *ast.ParenExpr: return c.formatParenExpr("%e", e.X) case *ast.IndexExpr: switch t := c.p.TypeOf(e.X).Underlying().(type) { case *types.Array, *types.Pointer: pattern := rangeCheck("%1e[%2f]", c.p.Types[e.Index].Value != nil, true) if _, ok := t.(*types.Pointer); ok { // check pointer for nix (attribute getter causes a panic) pattern = `(%1e.nilCheck, ` + pattern + `)` } return c.formatExpr(pattern, e.X, e.Index) case *types.Slice: return c.formatExpr(rangeCheck("%1e.$array[%1e.$offset + %2f]", c.p.Types[e.Index].Value != nil, false), e.X, e.Index) case *types.Map: if typesutil.IsJsObject(c.p.TypeOf(e.Index)) { c.p.errList = append(c.p.errList, types.Error{Fset: c.p.fileSet, Pos: e.Index.Pos(), Msg: "cannot use js.Object as map key"}) } key := fmt.Sprintf("%s.keyFor(%s)", c.typeName(t.Key()), c.translateImplicitConversion(e.Index, t.Key())) if _, isTuple := exprType.(*types.Tuple); isTuple { return c.formatExpr(`(%1s = %2e[%3s], %1s !== undefined ? [%1s.v, true] : [%4e, false])`, c.newVariable("_entry"), e.X, key, c.zeroValue(t.Elem())) } return c.formatExpr(`(%1s = %2e[%3s], %1s !== undefined ? %1s.v : %4e)`, c.newVariable("_entry"), e.X, key, c.zeroValue(t.Elem())) case *types.Basic: return c.formatExpr("%e.charCodeAt(%f)", e.X, e.Index) default: panic(fmt.Sprintf("Unhandled IndexExpr: %T\n", t)) } case *ast.SliceExpr: if b, isBasic := c.p.TypeOf(e.X).Underlying().(*types.Basic); isBasic && isString(b) { switch { case e.Low == nil && e.High == nil: return c.translateExpr(e.X) case e.Low == nil: return c.formatExpr("%e.substring(0, %f)", e.X, e.High) case e.High == nil: return c.formatExpr("%e.substring(%f)", e.X, e.Low) default: return c.formatExpr("%e.substring(%f, %f)", e.X, e.Low, e.High) } } slice := c.translateConversionToSlice(e.X, exprType) switch { case e.Low == nil && e.High == nil: return c.formatExpr("%s", slice) case e.Low == nil: if e.Max != nil { return c.formatExpr("$subslice(%s, 0, %f, %f)", slice, e.High, e.Max) } return c.formatExpr("$subslice(%s, 0, %f)", slice, e.High) case e.High == nil: return c.formatExpr("$subslice(%s, %f)", slice, e.Low) default: if e.Max != nil { return c.formatExpr("$subslice(%s, %f, %f, %f)", slice, e.Low, e.High, e.Max) } return c.formatExpr("$subslice(%s, %f, %f)", slice, e.Low, e.High) } case *ast.SelectorExpr: sel, ok := c.p.SelectionOf(e) if !ok { // qualified identifier return c.formatExpr("%s", c.objectName(obj)) } switch sel.Kind() { case types.FieldVal: fields, jsTag := c.translateSelection(sel, e.Pos()) if jsTag != "" { if _, ok := sel.Type().(*types.Signature); ok { return c.formatExpr("$internalize(%1e.%2s.%3s, %4s, %1e.%2s)", e.X, strings.Join(fields, "."), jsTag, c.typeName(sel.Type())) } return c.internalize(c.formatExpr("%e.%s.%s", e.X, strings.Join(fields, "."), jsTag), sel.Type()) } return c.formatExpr("%e.%s", e.X, strings.Join(fields, ".")) case types.MethodVal: return c.formatExpr(`$methodVal(%s, "%s")`, c.makeReceiver(e), sel.Obj().(*types.Func).Name()) case types.MethodExpr: if !sel.Obj().Exported() { c.p.dependencies[sel.Obj()] = true } if _, ok := sel.Recv().Underlying().(*types.Interface); ok { return c.formatExpr(`$ifaceMethodExpr("%s")`, sel.Obj().(*types.Func).Name()) } return c.formatExpr(`$methodExpr(%s, "%s")`, c.typeName(sel.Recv()), sel.Obj().(*types.Func).Name()) default: panic(fmt.Sprintf("unexpected sel.Kind(): %T", sel.Kind())) } case *ast.CallExpr: plainFun := astutil.RemoveParens(e.Fun) if astutil.IsTypeExpr(plainFun, c.p.Info.Info) { return c.formatExpr("%s", c.translateConversion(e.Args[0], c.p.TypeOf(plainFun))) } sig := c.p.TypeOf(plainFun).Underlying().(*types.Signature) switch f := plainFun.(type) { case *ast.Ident: obj := c.p.Uses[f] if o, ok := obj.(*types.Builtin); ok { return c.translateBuiltin(o.Name(), sig, e.Args, e.Ellipsis.IsValid()) } if typesutil.IsJsPackage(obj.Pkg()) && obj.Name() == "InternalObject" { return c.translateExpr(e.Args[0]) } return c.translateCall(e, sig, c.translateExpr(f)) case *ast.SelectorExpr: sel, ok := c.p.SelectionOf(f) if !ok { // qualified identifier obj := c.p.Uses[f.Sel] if typesutil.IsJsPackage(obj.Pkg()) { switch obj.Name() { case "Debugger": return c.formatExpr("debugger") case "InternalObject": return c.translateExpr(e.Args[0]) } } return c.translateCall(e, sig, c.translateExpr(f)) } externalizeExpr := func(e ast.Expr) string { t := c.p.TypeOf(e) if types.Identical(t, types.Typ[types.UntypedNil]) { return "null" } return c.externalize(c.translateExpr(e).String(), t) } externalizeArgs := func(args []ast.Expr) string { s := make([]string, len(args)) for i, arg := range args { s[i] = externalizeExpr(arg) } return strings.Join(s, ", ") } switch sel.Kind() { case types.MethodVal: recv := c.makeReceiver(f) declaredFuncRecv := sel.Obj().(*types.Func).Type().(*types.Signature).Recv().Type() if typesutil.IsJsObject(declaredFuncRecv) { globalRef := func(id string) string { if recv.String() == "$global" && id[0] == '$' && len(id) > 1 { return id } return recv.String() + "." + id } switch sel.Obj().Name() { case "Get": if id, ok := c.identifierConstant(e.Args[0]); ok { return c.formatExpr("%s", globalRef(id)) } return c.formatExpr("%s[$externalize(%e, $String)]", recv, e.Args[0]) case "Set": if id, ok := c.identifierConstant(e.Args[0]); ok { return c.formatExpr("%s = %s", globalRef(id), externalizeExpr(e.Args[1])) } return c.formatExpr("%s[$externalize(%e, $String)] = %s", recv, e.Args[0], externalizeExpr(e.Args[1])) case "Delete": return c.formatExpr("delete %s[$externalize(%e, $String)]", recv, e.Args[0]) case "Length": return c.formatExpr("$parseInt(%s.length)", recv) case "Index": return c.formatExpr("%s[%e]", recv, e.Args[0]) case "SetIndex": return c.formatExpr("%s[%e] = %s", recv, e.Args[0], externalizeExpr(e.Args[1])) case "Call": if id, ok := c.identifierConstant(e.Args[0]); ok { if e.Ellipsis.IsValid() { objVar := c.newVariable("obj") return c.formatExpr("(%s = %s, %s.%s.apply(%s, %s))", objVar, recv, objVar, id, objVar, externalizeExpr(e.Args[1])) } return c.formatExpr("%s(%s)", globalRef(id), externalizeArgs(e.Args[1:])) } if e.Ellipsis.IsValid() { objVar := c.newVariable("obj") return c.formatExpr("(%s = %s, %s[$externalize(%e, $String)].apply(%s, %s))", objVar, recv, objVar, e.Args[0], objVar, externalizeExpr(e.Args[1])) } return c.formatExpr("%s[$externalize(%e, $String)](%s)", recv, e.Args[0], externalizeArgs(e.Args[1:])) case "Invoke": if e.Ellipsis.IsValid() { return c.formatExpr("%s.apply(undefined, %s)", recv, externalizeExpr(e.Args[0])) } return c.formatExpr("%s(%s)", recv, externalizeArgs(e.Args)) case "New": if e.Ellipsis.IsValid() { return c.formatExpr("new ($global.Function.prototype.bind.apply(%s, [undefined].concat(%s)))", recv, externalizeExpr(e.Args[0])) } return c.formatExpr("new (%s)(%s)", recv, externalizeArgs(e.Args)) case "Bool": return c.internalize(recv, types.Typ[types.Bool]) case "String": return c.internalize(recv, types.Typ[types.String]) case "Int": return c.internalize(recv, types.Typ[types.Int]) case "Int64": return c.internalize(recv, types.Typ[types.Int64]) case "Uint64": return c.internalize(recv, types.Typ[types.Uint64]) case "Float": return c.internalize(recv, types.Typ[types.Float64]) case "Interface": return c.internalize(recv, types.NewInterface(nil, nil)) case "Unsafe": return recv default: panic("Invalid js package object: " + sel.Obj().Name()) } } methodName := sel.Obj().Name() if reservedKeywords[methodName] { methodName += "$" } return c.translateCall(e, sig, c.formatExpr("%s.%s", recv, methodName)) case types.FieldVal: fields, jsTag := c.translateSelection(sel, f.Pos()) if jsTag != "" { call := c.formatExpr("%e.%s.%s(%s)", f.X, strings.Join(fields, "."), jsTag, externalizeArgs(e.Args)) switch sig.Results().Len() { case 0: return call case 1: return c.internalize(call, sig.Results().At(0).Type()) default: c.p.errList = append(c.p.errList, types.Error{Fset: c.p.fileSet, Pos: f.Pos(), Msg: "field with js tag can not have func type with multiple results"}) } } return c.translateCall(e, sig, c.formatExpr("%e.%s", f.X, strings.Join(fields, "."))) case types.MethodExpr: return c.translateCall(e, sig, c.translateExpr(f)) default: panic(fmt.Sprintf("unexpected sel.Kind(): %T", sel.Kind())) } default: return c.translateCall(e, sig, c.translateExpr(plainFun)) } case *ast.StarExpr: if typesutil.IsJsObject(c.p.TypeOf(e.X)) { return c.formatExpr("new $jsObjectPtr(%e)", e.X) } if c1, isCall := e.X.(*ast.CallExpr); isCall && len(c1.Args) == 1 { if c2, isCall := c1.Args[0].(*ast.CallExpr); isCall && len(c2.Args) == 1 && types.Identical(c.p.TypeOf(c2.Fun), types.Typ[types.UnsafePointer]) { if unary, isUnary := c2.Args[0].(*ast.UnaryExpr); isUnary && unary.Op == token.AND { return c.translateExpr(unary.X) // unsafe conversion } } } switch exprType.Underlying().(type) { case *types.Struct, *types.Array: return c.translateExpr(e.X) } return c.formatExpr("%e.$get()", e.X) case *ast.TypeAssertExpr: if e.Type == nil { return c.translateExpr(e.X) } t := c.p.TypeOf(e.Type) if _, isTuple := exprType.(*types.Tuple); isTuple { return c.formatExpr("$assertType(%e, %s, true)", e.X, c.typeName(t)) } return c.formatExpr("$assertType(%e, %s)", e.X, c.typeName(t)) case *ast.Ident: if e.Name == "_" { panic("Tried to translate underscore identifier.") } switch o := obj.(type) { case *types.Var, *types.Const: return c.formatExpr("%s", c.objectName(o)) case *types.Func: return c.formatExpr("%s", c.objectName(o)) case *types.TypeName: return c.formatExpr("%s", c.typeName(o.Type())) case *types.Nil: if typesutil.IsJsObject(exprType) { return c.formatExpr("null") } switch t := exprType.Underlying().(type) { case *types.Basic: if t.Kind() != types.UnsafePointer { panic("unexpected basic type") } return c.formatExpr("0") case *types.Slice, *types.Pointer: return c.formatExpr("%s.nil", c.typeName(exprType)) case *types.Chan: return c.formatExpr("$chanNil") case *types.Map: return c.formatExpr("false") case *types.Interface: return c.formatExpr("$ifaceNil") case *types.Signature: return c.formatExpr("$throwNilPointerError") default: panic(fmt.Sprintf("unexpected type: %T", t)) } default: panic(fmt.Sprintf("Unhandled object: %T\n", o)) } case *this: if isWrapped(c.p.TypeOf(e)) { return c.formatExpr("this.$val") } return c.formatExpr("this") case nil: return c.formatExpr("") default: panic(fmt.Sprintf("Unhandled expression: %T\n", e)) } }
func (v *Variable) loadInterface(recurseLevel int, loadData bool) { var typestring, data *Variable isnil := false v.mem = cacheMemory(v.mem, v.Addr, int(v.RealType.Size())) for _, f := range v.RealType.(*dwarf.StructType).Field { switch f.Name { case "tab": // for runtime.iface tab, _ := v.toField(f) _type, err := tab.structMember("_type") if err != nil { _, isnil = err.(*IsNilErr) if !isnil { v.Unreadable = fmt.Errorf("invalid interface type: %v", err) return } } else { typestring, err = _type.structMember("_string") if err != nil { v.Unreadable = fmt.Errorf("invalid interface type: %v", err) return } typestring = typestring.maybeDereference() } case "_type": // for runtime.eface var err error _type, _ := v.toField(f) typestring, err = _type.structMember("_string") if err != nil { _, isnil = err.(*IsNilErr) if !isnil { v.Unreadable = fmt.Errorf("invalid interface type: %v", err) return } } else { typestring = typestring.maybeDereference() } case "data": data, _ = v.toField(f) } } if isnil { // interface to nil data = data.maybeDereference() v.Children = []Variable{*data} v.Children[0].loadValueInternal(recurseLevel) return } if typestring == nil || data == nil || typestring.Addr == 0 || typestring.Kind != reflect.String { v.Unreadable = fmt.Errorf("invalid interface type") return } typestring.loadValue() if typestring.Unreadable != nil { v.Unreadable = fmt.Errorf("invalid interface type: %v", typestring.Unreadable) return } t, err := parser.ParseExpr(constant.StringVal(typestring.Value)) if err != nil { v.Unreadable = fmt.Errorf("invalid interface type, unparsable data type: %v", err) return } typ, err := v.dbp.findTypeExpr(t) if err != nil { v.Unreadable = fmt.Errorf("interface type \"%s\" not found for 0x%x: %v", constant.StringVal(typestring.Value), data.Addr, err) return } realtyp := resolveTypedef(typ) if _, isptr := realtyp.(*dwarf.PtrType); !isptr { // interface to non-pointer types are pointers even if the type says otherwise typ = v.dbp.pointerTo(typ) } data = data.newVariable("data", data.Addr, typ) v.Children = []Variable{*data} if loadData { v.Children[0].loadValueInternal(recurseLevel) } return }
// exprInternal contains the core of type checking of expressions. // Must only be called by rawExpr. // func (check *Checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind { // make sure x has a valid state in case of bailout // (was issue 5770) x.mode = invalid x.typ = Typ[Invalid] switch e := e.(type) { case *ast.BadExpr: goto Error // error was reported before case *ast.Ident: check.ident(x, e, nil, nil) case *ast.Ellipsis: // ellipses are handled explicitly where they are legal // (array composite literals and parameter lists) check.error(e.Pos(), "invalid use of '...'") goto Error case *ast.BasicLit: x.setConst(e.Kind, e.Value) if x.mode == invalid { check.invalidAST(e.Pos(), "invalid literal %v", e.Value) goto Error } case *ast.FuncLit: if sig, ok := check.typ(e.Type).(*Signature); ok { // Anonymous functions are considered part of the // init expression/func declaration which contains // them: use existing package-level declaration info. check.funcBody(check.decl, "", sig, e.Body) x.mode = value x.typ = sig } else { check.invalidAST(e.Pos(), "invalid function literal %s", e) goto Error } case *ast.CompositeLit: typ := hint openArray := false if e.Type != nil { // [...]T array types may only appear with composite literals. // Check for them here so we don't have to handle ... in general. typ = nil if atyp, _ := e.Type.(*ast.ArrayType); atyp != nil && atyp.Len != nil { if ellip, _ := atyp.Len.(*ast.Ellipsis); ellip != nil && ellip.Elt == nil { // We have an "open" [...]T array type. // Create a new ArrayType with unknown length (-1) // and finish setting it up after analyzing the literal. typ = &Array{len: -1, elem: check.typ(atyp.Elt)} openArray = true } } if typ == nil { typ = check.typ(e.Type) } } if typ == nil { // TODO(gri) provide better error messages depending on context check.error(e.Pos(), "missing type in composite literal") goto Error } switch typ, _ := deref(typ); utyp := typ.Underlying().(type) { case *Struct: if len(e.Elts) == 0 { break } fields := utyp.fields if _, ok := e.Elts[0].(*ast.KeyValueExpr); ok { // all elements must have keys visited := make([]bool, len(fields)) for _, e := range e.Elts { kv, _ := e.(*ast.KeyValueExpr) if kv == nil { check.error(e.Pos(), "mixture of field:value and value elements in struct literal") continue } key, _ := kv.Key.(*ast.Ident) if key == nil { check.errorf(kv.Pos(), "invalid field name %s in struct literal", kv.Key) continue } i := fieldIndex(utyp.fields, check.pkg, key.Name) if i < 0 { check.errorf(kv.Pos(), "unknown field %s in struct literal", key.Name) continue } fld := fields[i] check.recordUse(key, fld) // 0 <= i < len(fields) if visited[i] { check.errorf(kv.Pos(), "duplicate field name %s in struct literal", key.Name) continue } visited[i] = true check.expr(x, kv.Value) etyp := fld.typ check.assignment(x, etyp, "struct literal") } } else { // no element must have a key for i, e := range e.Elts { if kv, _ := e.(*ast.KeyValueExpr); kv != nil { check.error(kv.Pos(), "mixture of field:value and value elements in struct literal") continue } check.expr(x, e) if i >= len(fields) { check.error(x.pos(), "too many values in struct literal") break // cannot continue } // i < len(fields) fld := fields[i] if !fld.Exported() && fld.pkg != check.pkg { check.errorf(x.pos(), "implicit assignment to unexported field %s in %s literal", fld.name, typ) continue } etyp := fld.typ check.assignment(x, etyp, "struct literal") } if len(e.Elts) < len(fields) { check.error(e.Rbrace, "too few values in struct literal") // ok to continue } } case *Array: n := check.indexedElts(e.Elts, utyp.elem, utyp.len) // if we have an "open" [...]T array, set the length now that we know it if openArray { utyp.len = n } case *Slice: check.indexedElts(e.Elts, utyp.elem, -1) case *Map: visited := make(map[interface{}][]Type, len(e.Elts)) for _, e := range e.Elts { kv, _ := e.(*ast.KeyValueExpr) if kv == nil { check.error(e.Pos(), "missing key in map literal") continue } check.exprWithHint(x, kv.Key, utyp.key) check.assignment(x, utyp.key, "map literal") if x.mode == invalid { continue } if x.mode == constant_ { duplicate := false // if the key is of interface type, the type is also significant when checking for duplicates if _, ok := utyp.key.Underlying().(*Interface); ok { for _, vtyp := range visited[x.val] { if Identical(vtyp, x.typ) { duplicate = true break } } visited[x.val] = append(visited[x.val], x.typ) } else { _, duplicate = visited[x.val] visited[x.val] = nil } if duplicate { check.errorf(x.pos(), "duplicate key %s in map literal", x.val) continue } } check.exprWithHint(x, kv.Value, utyp.elem) check.assignment(x, utyp.elem, "map literal") } default: // if utyp is invalid, an error was reported before if utyp != Typ[Invalid] { check.errorf(e.Pos(), "invalid composite literal type %s", typ) goto Error } } x.mode = value x.typ = typ case *ast.ParenExpr: kind := check.rawExpr(x, e.X, nil) x.expr = e return kind case *ast.SelectorExpr: check.selector(x, e) case *ast.IndexExpr: check.expr(x, e.X) if x.mode == invalid { goto Error } valid := false length := int64(-1) // valid if >= 0 switch typ := x.typ.Underlying().(type) { case *Basic: if isString(typ) { valid = true if x.mode == constant_ { length = int64(len(constant.StringVal(x.val))) } // an indexed string always yields a byte value // (not a constant) even if the string and the // index are constant x.mode = value x.typ = universeByte // use 'byte' name } case *Array: valid = true length = typ.len if x.mode != variable { x.mode = value } x.typ = typ.elem case *Pointer: if typ, _ := typ.base.Underlying().(*Array); typ != nil { valid = true length = typ.len x.mode = variable x.typ = typ.elem } case *Slice: valid = true x.mode = variable x.typ = typ.elem case *Map: var key operand check.expr(&key, e.Index) check.assignment(&key, typ.key, "map index") if x.mode == invalid { goto Error } x.mode = mapindex x.typ = typ.elem x.expr = e return expression } if !valid { check.invalidOp(x.pos(), "cannot index %s", x) goto Error } if e.Index == nil { check.invalidAST(e.Pos(), "missing index for %s", x) goto Error } check.index(e.Index, length) // ok to continue case *ast.SliceExpr: check.expr(x, e.X) if x.mode == invalid { goto Error } valid := false length := int64(-1) // valid if >= 0 switch typ := x.typ.Underlying().(type) { case *Basic: if isString(typ) { if e.Slice3 { check.invalidOp(x.pos(), "3-index slice of string") goto Error } valid = true if x.mode == constant_ { length = int64(len(constant.StringVal(x.val))) } // spec: "For untyped string operands the result // is a non-constant value of type string." if typ.kind == UntypedString { x.typ = Typ[String] } } case *Array: valid = true length = typ.len if x.mode != variable { check.invalidOp(x.pos(), "cannot slice %s (value not addressable)", x) goto Error } x.typ = &Slice{elem: typ.elem} case *Pointer: if typ, _ := typ.base.Underlying().(*Array); typ != nil { valid = true length = typ.len x.typ = &Slice{elem: typ.elem} } case *Slice: valid = true // x.typ doesn't change } if !valid { check.invalidOp(x.pos(), "cannot slice %s", x) goto Error } x.mode = value // spec: "Only the first index may be omitted; it defaults to 0." if e.Slice3 && (e.High == nil || e.Max == nil) { check.error(e.Rbrack, "2nd and 3rd index required in 3-index slice") goto Error } // check indices var ind [3]int64 for i, expr := range []ast.Expr{e.Low, e.High, e.Max} { x := int64(-1) switch { case expr != nil: // The "capacity" is only known statically for strings, arrays, // and pointers to arrays, and it is the same as the length for // those types. max := int64(-1) if length >= 0 { max = length + 1 } if t, ok := check.index(expr, max); ok && t >= 0 { x = t } case i == 0: // default is 0 for the first index x = 0 case length >= 0: // default is length (== capacity) otherwise x = length } ind[i] = x } // constant indices must be in range // (check.index already checks that existing indices >= 0) L: for i, x := range ind[:len(ind)-1] { if x > 0 { for _, y := range ind[i+1:] { if y >= 0 && x > y { check.errorf(e.Rbrack, "invalid slice indices: %d > %d", x, y) break L // only report one error, ok to continue } } } } case *ast.TypeAssertExpr: check.expr(x, e.X) if x.mode == invalid { goto Error } xtyp, _ := x.typ.Underlying().(*Interface) if xtyp == nil { check.invalidOp(x.pos(), "%s is not an interface", x) goto Error } // x.(type) expressions are handled explicitly in type switches if e.Type == nil { check.invalidAST(e.Pos(), "use of .(type) outside type switch") goto Error } T := check.typ(e.Type) if T == Typ[Invalid] { goto Error } check.typeAssertion(x.pos(), x, xtyp, T) x.mode = commaok x.typ = T case *ast.CallExpr: return check.call(x, e) case *ast.StarExpr: check.exprOrType(x, e.X) switch x.mode { case invalid: goto Error case typexpr: x.typ = &Pointer{base: x.typ} default: if typ, ok := x.typ.Underlying().(*Pointer); ok { x.mode = variable x.typ = typ.base } else { check.invalidOp(x.pos(), "cannot indirect %s", x) goto Error } } case *ast.UnaryExpr: check.expr(x, e.X) if x.mode == invalid { goto Error } check.unary(x, e, e.Op) if x.mode == invalid { goto Error } if e.Op == token.ARROW { x.expr = e return statement // receive operations may appear in statement context } case *ast.BinaryExpr: check.binary(x, e, e.X, e.Y, e.Op) if x.mode == invalid { goto Error } case *ast.KeyValueExpr: // key:value expressions are handled in composite literals check.invalidAST(e.Pos(), "no key:value expected") goto Error case *ast.ArrayType, *ast.StructType, *ast.FuncType, *ast.InterfaceType, *ast.MapType, *ast.ChanType: x.mode = typexpr x.typ = check.typ(e) // Note: rawExpr (caller of exprInternal) will call check.recordTypeAndValue // even though check.typ has already called it. This is fine as both // times the same expression and type are recorded. It is also not a // performance issue because we only reach here for composite literal // types, which are comparatively rare. default: panic(fmt.Sprintf("%s: unknown expression type %T", check.fset.Position(e.Pos()), e)) } // everything went well x.expr = e return expression Error: x.mode = invalid x.expr = e return statement // avoid follow-up errors }
// genDecl processes one declaration clause. func (f *File) genDecl(node ast.Node) bool { decl, ok := node.(*ast.GenDecl) if !ok || decl.Tok != token.CONST { // We only care about const declarations. return true } // The name of the type of the constants we are declaring. // Can change if this is a multi-element declaration. typ := "" // Loop over the elements of the declaration. Each element is a ValueSpec: // a list of names possibly followed by a type, possibly followed by values. // If the type and value are both missing, we carry down the type (and value, // but the "go/types" package takes care of that). for _, spec := range decl.Specs { vspec := spec.(*ast.ValueSpec) // Guaranteed to succeed as this is CONST. if vspec.Type == nil && len(vspec.Values) > 0 { // "X = 1". With no type but a value, the constant is untyped. // Skip this vspec and reset the remembered type. typ = "" continue } if vspec.Type != nil { // "X T". We have a type. Remember it. ident, ok := vspec.Type.(*ast.Ident) if !ok { continue } typ = ident.Name } if typ != f.typeName { // This is not the type we're looking for. continue } // We now have a list of names (from one line of source code) all being // declared with the desired type. // Grab their names and actual values and store them in f.values. for _, name := range vspec.Names { if name.Name == "_" { continue } // This dance lets the type checker find the values for us. It's a // bit tricky: look up the object declared by the name, find its // types.Const, and extract its value. obj, ok := f.pkg.defs[name] var v Value if ok { info := obj.Type().Underlying().(*types.Basic).Info() if info&types.IsString == 0 { log.Fatalf("can't handle non-integer constant type %s", typ) } value := obj.(*types.Const).Val() // Guaranteed to succeed as this is CONST. if value.Kind() != exact.String { log.Fatalf("can't happen: constant is not a string %s", name) } sVal := exact.StringVal(value) v = Value{ name: name.Name, value: sVal, str: value.String(), } } else { v = Value{ name: name.Name, value: "", str: "", } } f.values = append(f.values, v) } } return false }
// Run runs program analysis and computes the resulting markup, // populating *result in a thread-safe manner, first with type // information then later with pointer analysis information if // enabled by the pta flag. // func Run(pta bool, result *Result) { conf := loader.Config{ AllowErrors: true, } // Silence the default error handler. // Don't print all errors; we'll report just // one per errant package later. conf.TypeChecker.Error = func(e error) {} var roots, args []string // roots[i] ends with os.PathSeparator // Enumerate packages in $GOROOT. root := filepath.Join(runtime.GOROOT(), "src") + string(os.PathSeparator) roots = append(roots, root) args = allPackages(root) log.Printf("GOROOT=%s: %s\n", root, args) // Enumerate packages in $GOPATH. for i, dir := range filepath.SplitList(build.Default.GOPATH) { root := filepath.Join(dir, "src") + string(os.PathSeparator) roots = append(roots, root) pkgs := allPackages(root) log.Printf("GOPATH[%d]=%s: %s\n", i, root, pkgs) args = append(args, pkgs...) } // Uncomment to make startup quicker during debugging. //args = []string{"golang.org/x/tools/cmd/godoc"} //args = []string{"fmt"} if _, err := conf.FromArgs(args, true); err != nil { // TODO(adonovan): degrade gracefully, not fail totally. // (The crippling case is a parse error in an external test file.) result.setStatusf("Analysis failed: %s.", err) // import error return } result.setStatusf("Loading and type-checking packages...") iprog, err := conf.Load() if iprog != nil { // Report only the first error of each package. for _, info := range iprog.AllPackages { for _, err := range info.Errors { fmt.Fprintln(os.Stderr, err) break } } log.Printf("Loaded %d packages.", len(iprog.AllPackages)) } if err != nil { result.setStatusf("Loading failed: %s.\n", err) return } // Create SSA-form program representation. // Only the transitively error-free packages are used. prog := ssautil.CreateProgram(iprog, ssa.GlobalDebug) // Compute the set of main packages, including testmain. allPackages := prog.AllPackages() var mainPkgs []*ssa.Package if testmain := prog.CreateTestMainPackage(allPackages...); testmain != nil { mainPkgs = append(mainPkgs, testmain) if p := testmain.Const("packages"); p != nil { log.Printf("Tested packages: %v", exact.StringVal(p.Value.Value)) } } for _, pkg := range allPackages { if pkg.Pkg.Name() == "main" && pkg.Func("main") != nil { mainPkgs = append(mainPkgs, pkg) } } log.Print("Transitively error-free main packages: ", mainPkgs) // Build SSA code for bodies of all functions in the whole program. result.setStatusf("Constructing SSA form...") prog.Build() log.Print("SSA construction complete") a := analysis{ result: result, prog: prog, pcgs: make(map[*ssa.Package]*packageCallGraph), } // Build a mapping from openable filenames to godoc file URLs, // i.e. "/src/" plus path relative to GOROOT/src or GOPATH[i]/src. a.path2url = make(map[string]string) for _, info := range iprog.AllPackages { nextfile: for _, f := range info.Files { if f.Pos() == 0 { continue // e.g. files generated by cgo } abs := iprog.Fset.File(f.Pos()).Name() // Find the root to which this file belongs. for _, root := range roots { rel := strings.TrimPrefix(abs, root) if len(rel) < len(abs) { a.path2url[abs] = "/src/" + filepath.ToSlash(rel) continue nextfile } } log.Printf("Can't locate file %s (package %q) beneath any root", abs, info.Pkg.Path()) } } // Add links for scanner, parser, type-checker errors. // TODO(adonovan): fix: these links can overlap with // identifier markup, causing the renderer to emit some // characters twice. errors := make(map[token.Position][]string) for _, info := range iprog.AllPackages { for _, err := range info.Errors { switch err := err.(type) { case types.Error: posn := a.prog.Fset.Position(err.Pos) errors[posn] = append(errors[posn], err.Msg) case scanner.ErrorList: for _, e := range err { errors[e.Pos] = append(errors[e.Pos], e.Msg) } default: log.Printf("Package %q has error (%T) without position: %v\n", info.Pkg.Path(), err, err) } } } for posn, errs := range errors { fi, offset := a.fileAndOffsetPosn(posn) fi.addLink(errorLink{ start: offset, msg: strings.Join(errs, "\n"), }) } // ---------- type-based analyses ---------- // Compute the all-pairs IMPLEMENTS relation. // Collect all named types, even local types // (which can have methods via promotion) // and the built-in "error". errorType := types.Universe.Lookup("error").Type().(*types.Named) a.allNamed = append(a.allNamed, errorType) for _, info := range iprog.AllPackages { for _, obj := range info.Defs { if obj, ok := obj.(*types.TypeName); ok { a.allNamed = append(a.allNamed, obj.Type().(*types.Named)) } } } log.Print("Computing implements relation...") facts := computeImplements(&a.prog.MethodSets, a.allNamed) // Add the type-based analysis results. log.Print("Extracting type info...") for _, info := range iprog.AllPackages { a.doTypeInfo(info, facts) } a.visitInstrs(pta) result.setStatusf("Type analysis complete.") if pta { a.pointer(mainPkgs) } }
func (v *Variable) loadInterface(recurseLevel int, loadData bool, cfg LoadConfig) { var _type, str, typestring, data *Variable var typename string var err error isnil := false // An interface variable is implemented either by a runtime.iface // struct or a runtime.eface struct. The difference being that empty // interfaces (i.e. "interface {}") are represented by runtime.eface // and non-empty interfaces by runtime.iface. // // For both runtime.ifaces and runtime.efaces the data is stored in v.data // // The concrete type however is stored in v.tab._type for non-empty // interfaces and in v._type for empty interfaces. // // For nil empty interface variables _type will be nil, for nil // non-empty interface variables tab will be nil // // In either case the _type field is a pointer to a runtime._type struct. // // Before go1.7 _type used to have a field named 'string' containing // the name of the type. Since go1.7 the field has been replaced by a // str field that contains an offset in the module data, the concrete // type must be calculated using the str address along with the value // of v.tab._type (v._type for empty interfaces). // // The following code works for both runtime.iface and runtime.eface // and sets the go17 flag when the 'string' field can not be found // but the str field was found go17 := false v.mem = cacheMemory(v.mem, v.Addr, int(v.RealType.Size())) ityp := resolveTypedef(&v.RealType.(*dwarf.InterfaceType).TypedefType).(*dwarf.StructType) for _, f := range ityp.Field { switch f.Name { case "tab": // for runtime.iface tab, _ := v.toField(f) tab = tab.maybeDereference() isnil = tab.Addr == 0 if !isnil { _type, err = tab.structMember("_type") if err != nil { v.Unreadable = fmt.Errorf("invalid interface type: %v", err) return } typestring, err = _type.structMember("_string") if err == nil { typestring = typestring.maybeDereference() } else { go17 = true str, err = _type.structMember("str") if err != nil { v.Unreadable = fmt.Errorf("invalid interface type: %v", err) return } } } case "_type": // for runtime.eface _type, _ = v.toField(f) _type = _type.maybeDereference() isnil = _type.Addr == 0 if !isnil { typestring, err = _type.structMember("_string") if err == nil { typestring = typestring.maybeDereference() } else { go17 = true str, err = _type.structMember("str") if err != nil { v.Unreadable = fmt.Errorf("invalid interface type: %v", err) return } } } case "data": data, _ = v.toField(f) } } if isnil { // interface to nil data = data.maybeDereference() v.Children = []Variable{*data} if loadData { v.Children[0].loadValueInternal(recurseLevel, cfg) } return } if data == nil { v.Unreadable = fmt.Errorf("invalid interface type") return } if go17 { // No 'string' field use 'str' and 'runtime.firstmoduledata' to // find out what the concrete type is typeAddr := _type.maybeDereference().Addr strOff, err := str.asInt() if err != nil { v.Unreadable = fmt.Errorf("invalid interface type: %v", err) return } res, err := v.dbp.resolveNameOff(typeAddr, uintptr(strOff)) if err != nil { v.Unreadable = fmt.Errorf("could not resolve concrete type (data: %#x): %v", data.Addr, err) return } // For a description of how memory is organized for type names read // the comment to 'type name struct' in $GOROOT/src/reflect/type.go typdata, err := v.dbp.CurrentThread.readMemory(res, 3+v.dbp.arch.PtrSize()) if err != nil { v.Unreadable = fmt.Errorf("could not read concrete type (data: %#v): %v", data.Addr, err) } nl := int(typdata[1]<<8 | typdata[2]) rawstr, err := v.dbp.CurrentThread.readMemory(res+3, nl) typename = string(rawstr) } else { if typestring == nil || typestring.Addr == 0 || typestring.Kind != reflect.String { v.Unreadable = fmt.Errorf("invalid interface type") return } typestring.loadValue(LoadConfig{false, 0, 512, 0, 0}) if typestring.Unreadable != nil { v.Unreadable = fmt.Errorf("invalid interface type: %v", typestring.Unreadable) return } typename = constant.StringVal(typestring.Value) } t, err := parser.ParseExpr(typename) if err != nil { v.Unreadable = fmt.Errorf("invalid interface type, unparsable data type: %v", err) return } typ, err := v.dbp.findTypeExpr(t) if err != nil { v.Unreadable = fmt.Errorf("interface type \"%s\" not found for 0x%x: %v", typename, data.Addr, err) return } realtyp := resolveTypedef(typ) if _, isptr := realtyp.(*dwarf.PtrType); !isptr { // interface to non-pointer types are pointers even if the type says otherwise typ = v.dbp.pointerTo(typ) } data = data.newVariable("data", data.Addr, typ) v.Children = []Variable{*data} if loadData { v.Children[0].loadValueInternal(recurseLevel, cfg) } else { v.Children[0].OnlyAddr = true } return }
func runExtract(cmd *Command, args []string) error { if len(args) == 0 { args = []string{"."} } conf := loader.Config{ Build: &build.Default, ParserMode: parser.ParseComments, } // Use the initial packages from the command line. args, err := conf.FromArgs(args, false) if err != nil { return err } // Load, parse and type-check the whole program. iprog, err := conf.Load() if err != nil { return err } // print returns Go syntax for the specified node. print := func(n ast.Node) string { var buf bytes.Buffer format.Node(&buf, conf.Fset, n) return buf.String() } var translations []Translation for _, info := range iprog.InitialPackages() { for _, f := range info.Files { // Associate comments with nodes. cmap := ast.NewCommentMap(iprog.Fset, f, f.Comments) getComment := func(n ast.Node) string { cs := cmap.Filter(n).Comments() if len(cs) > 0 { return strings.TrimSpace(cs[0].Text()) } return "" } // Find function calls. ast.Inspect(f, func(n ast.Node) bool { call, ok := n.(*ast.CallExpr) if !ok { return true } // Skip calls of functions other than // (*message.Printer).{Sp,Fp,P}rintf. sel, ok := call.Fun.(*ast.SelectorExpr) if !ok { return true } meth := info.Selections[sel] if meth == nil || meth.Kind() != types.MethodVal { return true } // TODO: remove cheap hack and check if the type either // implements some interface or is specifically of type // "golang.org/x/text/message".Printer. m, ok := extractFuncs[path.Base(meth.Recv().String())] if !ok { return true } // argn is the index of the format string. argn, ok := m[meth.Obj().Name()] if !ok || argn >= len(call.Args) { return true } // Skip calls with non-constant format string. fmtstr := info.Types[call.Args[argn]].Value if fmtstr == nil || fmtstr.Kind() != constant.String { return true } posn := conf.Fset.Position(call.Lparen) filepos := fmt.Sprintf("%s:%d:%d", filepath.Base(posn.Filename), posn.Line, posn.Column) // TODO: identify the type of the format argument. If it is not // a string, multiple keys may be defined. var key []string // TODO: replace substitutions (%v) with a translator friendly // notation. For instance: // "%d files remaining" -> "{numFiles} files remaining", or // "%d files remaining" -> "{arg1} files remaining" // Alternatively, this could be done at a later stage. msg := constant.StringVal(fmtstr) // Construct a Translation unit. c := Translation{ Key: key, Position: filepath.Join(info.Pkg.Path(), filepos), Original: Text{Msg: msg}, ExtractedComment: getComment(call.Args[0]), // TODO(fix): this doesn't get the before comment. // Comment: getComment(call), } for i, arg := range call.Args[argn+1:] { var val string if v := info.Types[arg].Value; v != nil { val = v.ExactString() } posn := conf.Fset.Position(arg.Pos()) filepos := fmt.Sprintf("%s:%d:%d", filepath.Base(posn.Filename), posn.Line, posn.Column) c.Args = append(c.Args, Argument{ ID: i + 1, Type: info.Types[arg].Type.String(), UnderlyingType: info.Types[arg].Type.Underlying().String(), Expr: print(arg), Value: val, Comment: getComment(arg), Position: filepath.Join(info.Pkg.Path(), filepos), // TODO report whether it implements // interfaces plural.Interface, // gender.Interface. }) } translations = append(translations, c) return true }) } } data, err := json.MarshalIndent(translations, "", " ") if err != nil { return err } for _, tag := range getLangs() { // TODO: merge with existing files, don't overwrite. os.MkdirAll(*dir, 0744) file := filepath.Join(*dir, fmt.Sprintf("gotext_%v.out.json", tag)) if err := ioutil.WriteFile(file, data, 0744); err != nil { return fmt.Errorf("could not create file: %v", err) } } return nil }