| 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 |
| } |