package pointer import ( "errors" "fmt" "go/ast" "go/parser" "go/token" "go/types" "strconv" ) // An extendedQuery represents a sequence of destructuring operations // applied to an ssa.Value (denoted by "x"). type extendedQuery struct { ops []interface{} ptr *Pointer } // indexValue returns the value of an integer literal used as an // index. func indexValue(expr ast.Expr) (int, error) { lit, ok := expr.(*ast.BasicLit) if !ok { return 0, fmt.Errorf("non-integer index (%T)", expr) } if lit.Kind != token.INT { return 0, fmt.Errorf("non-integer index %s", lit.Value) } return strconv.Atoi(lit.Value) } // parseExtendedQuery parses and validates a destructuring Go // expression and returns the sequence of destructuring operations. // See parseDestructuringExpr for details. func parseExtendedQuery(typ types.Type, query string) ([]interface{}, types.Type, error) { expr, err := parser.ParseExpr(query) if err != nil { return nil, nil, err } ops, typ, err := destructuringOps(typ, expr) if err != nil { return nil, nil, err } if len(ops) == 0 { return nil, nil, errors.New("invalid query: must not be empty") } if ops[0] != "x" { return nil, nil, fmt.Errorf("invalid query: query operand must be named x") } if !CanPoint(typ) { return nil, nil, fmt.Errorf("query does not describe a pointer-like value: %s", typ) } return ops, typ, nil } // destructuringOps parses a Go expression consisting only of an // identifier "x", field selections, indexing, channel receives, load // operations and parens---for example: "<-(*x[i])[key]"--- and // returns the sequence of destructuring operations on x. func destructuringOps(typ types.Type, expr ast.Expr) ([]interface{}, types.Type, error) { switch expr := expr.(type) { case *ast.SelectorExpr: out, typ, err := destructuringOps(typ, expr.X) if err != nil { return nil, nil, err } var structT *types.Struct switch typ := typ.(type) { case *types.Pointer: var ok bool structT, ok = typ.Elem().Underlying().(*types.Struct) if !ok { return nil, nil, fmt.Errorf("cannot access field %s of pointer to type %s", expr.Sel.Name, typ.Elem()) } out = append(out, "load") case *types.Struct: structT = typ default: return nil, nil, fmt.Errorf("cannot access field %s of type %s", expr.Sel.Name, typ) } for i := 0; i < structT.NumFields(); i++ { field := structT.Field(i) if field.Name() == expr.Sel.Name { out = append(out, "field", i) return out, field.Type().Underlying(), nil } } // TODO(dh): supporting embedding would need something like // types.LookupFieldOrMethod, but without taking package // boundaries into account, because we may want to access // unexported fields. If we were only interested in one level // of unexported name, we could determine the appropriate // package and run LookupFieldOrMethod with that. However, a // single query may want to cross multiple package boundaries, // and at this point it's not really worth the complexity. return nil, nil, fmt.Errorf("no field %s in %s (embedded fields must be resolved manually)", expr.Sel.Name, structT) case *ast.Ident: return []interface{}{expr.Name}, typ, nil case *ast.BasicLit: return []interface{}{expr.Value}, nil, nil case *ast.IndexExpr: out, typ, err := destructuringOps(typ, expr.X) if err != nil { return nil, nil, err } switch typ := typ.(type) { case *types.Array: out = append(out, "arrayelem") return out, typ.Elem().Underlying(), nil case *types.Slice: out = append(out, "sliceelem") return out, typ.Elem().Underlying(), nil case *types.Map: out = append(out, "mapelem") return out, typ.Elem().Underlying(), nil case *types.Tuple: out = append(out, "index") idx, err := indexValue(expr.Index) if err != nil { return nil, nil, err } out = append(out, idx) if idx >= typ.Len() || idx < 0 { return nil, nil, fmt.Errorf("tuple index %d out of bounds", idx) } return out, typ.At(idx).Type().Underlying(), nil default: return nil, nil, fmt.Errorf("cannot index type %s", typ) } case *ast.UnaryExpr: if expr.Op != token.ARROW { return nil, nil, fmt.Errorf("unsupported unary operator %s", expr.Op) } out, typ, err := destructuringOps(typ, expr.X) if err != nil { return nil, nil, err } ch, ok := typ.(*types.Chan) if !ok { return nil, nil, fmt.Errorf("cannot receive from value of type %s", typ) } out = append(out, "recv") return out, ch.Elem().Underlying(), err case *ast.ParenExpr: return destructuringOps(typ, expr.X) case *ast.StarExpr: out, typ, err := destructuringOps(typ, expr.X) if err != nil { return nil, nil, err } ptr, ok := typ.(*types.Pointer) if !ok { return nil, nil, fmt.Errorf("cannot dereference type %s", typ) } out = append(out, "load") return out, ptr.Elem().Underlying(), err default: return nil, nil, fmt.Errorf("unsupported expression %T", expr) } } func (a *analysis) evalExtendedQuery(t types.Type, id nodeid, ops []interface{}) (types.Type, nodeid) { pid := id // TODO(dh): we're allocating intermediary nodes each time // evalExtendedQuery is called. We should probably only generate // them once per (v, ops) pair. for i := 1; i < len(ops); i++ { var nid nodeid switch ops[i] { case "recv": t = t.(*types.Chan).Elem().Underlying() nid = a.addNodes(t, "query.extended") a.load(nid, pid, 0, a.sizeof(t)) case "field": i++ // fetch field index tt := t.(*types.Struct) idx := ops[i].(int) offset := a.offsetOf(t, idx) t = tt.Field(idx).Type().Underlying() nid = a.addNodes(t, "query.extended") a.copy(nid, pid+nodeid(offset), a.sizeof(t)) case "arrayelem": t = t.(*types.Array).Elem().Underlying() nid = a.addNodes(t, "query.extended") a.copy(nid, 1+pid, a.sizeof(t)) case "sliceelem": t = t.(*types.Slice).Elem().Underlying() nid = a.addNodes(t, "query.extended") a.load(nid, pid, 1, a.sizeof(t)) case "mapelem": tt := t.(*types.Map) t = tt.Elem() ksize := a.sizeof(tt.Key()) vsize := a.sizeof(tt.Elem()) nid = a.addNodes(t, "query.extended") a.load(nid, pid, ksize, vsize) case "index": i++ // fetch index tt := t.(*types.Tuple) idx := ops[i].(int) t = tt.At(idx).Type().Underlying() nid = a.addNodes(t, "query.extended") a.copy(nid, pid+nodeid(idx), a.sizeof(t)) case "load": t = t.(*types.Pointer).Elem().Underlying() nid = a.addNodes(t, "query.extended") a.load(nid, pid, 0, a.sizeof(t)) default: // shouldn't happen panic(fmt.Sprintf("unknown op %q", ops[i])) } pid = nid } return t, pid }