// describe describes the syntax node denoted by the query position, // including: // - its syntactic category // - the definition of its referent (for identifiers) [now redundant] // - its type and method set (for an expression or type expression) // func describe(o *Oracle, qpos *QueryPos) (queryResult, error) { if false { // debugging fprintf(os.Stderr, o.fset, qpos.path[0], "you selected: %s %s", astutil.NodeDescription(qpos.path[0]), pathToString(qpos.path)) } path, action := findInterestingNode(qpos.info, qpos.path) switch action { case actionExpr: return describeValue(o, qpos, path) case actionType: return describeType(o, qpos, path) case actionPackage: return describePackage(o, qpos, path) case actionStmt: return describeStmt(o, qpos, path) case actionUnknown: return &describeUnknownResult{path[0]}, nil default: panic(action) // unreachable } }
func (r *whatResult) display(printf printfFunc) { for _, n := range r.path { printf(n, "%s", astutil.NodeDescription(n)) } printf(nil, "modes: %s", r.modes) printf(nil, "srcdir: %s", r.srcdir) printf(nil, "import path: %s", r.importPath) }
// pointsto runs the pointer analysis on the selected expression, // and reports its points-to set (for a pointer-like expression) // or its dynamic types (for an interface, reflect.Value, or // reflect.Type expression) and their points-to sets. // // All printed sets are sorted to ensure determinism. // func pointsto(o *Oracle, qpos *QueryPos) (queryResult, error) { path, action := findInterestingNode(qpos.info, qpos.path) if action != actionExpr { return nil, fmt.Errorf("pointer analysis wants an expression; got %s", astutil.NodeDescription(qpos.path[0])) } var expr ast.Expr var obj types.Object switch n := path[0].(type) { case *ast.ValueSpec: // ambiguous ValueSpec containing multiple names return nil, fmt.Errorf("multiple value specification") case *ast.Ident: obj = qpos.info.ObjectOf(n) expr = n case ast.Expr: expr = n default: // TODO(adonovan): is this reachable? return nil, fmt.Errorf("unexpected AST for expr: %T", n) } // Reject non-pointerlike types (includes all constants---except nil). // TODO(adonovan): reject nil too. typ := qpos.info.TypeOf(expr) if !pointer.CanPoint(typ) { return nil, fmt.Errorf("pointer analysis wants an expression of reference type; got %s", typ) } // Determine the ssa.Value for the expression. var value ssa.Value var isAddr bool var err error if obj != nil { // def/ref of func/var object value, isAddr, err = ssaValueForIdent(o.prog, qpos.info, obj, path) } else { value, isAddr, err = ssaValueForExpr(o.prog, qpos.info, path) } if err != nil { return nil, err // e.g. trivially dead code } // Run the pointer analysis. ptrs, err := runPTA(o, value, isAddr) if err != nil { return nil, err // e.g. analytically unreachable } return &pointstoResult{ qpos: qpos, typ: typ, ptrs: ptrs, }, nil }
func (r *whatResult) toSerial(res *serial.Result, fset *token.FileSet) { var enclosing []serial.SyntaxNode for _, n := range r.path { enclosing = append(enclosing, serial.SyntaxNode{ Description: astutil.NodeDescription(n), Start: fset.Position(n.Pos()).Offset, End: fset.Position(n.End()).Offset, }) } res.What = &serial.What{ Modes: r.modes, SrcDir: r.srcdir, ImportPath: r.importPath, Enclosing: enclosing, } }
func describeStmt(o *Oracle, qpos *QueryPos, path []ast.Node) (*describeStmtResult, error) { var description string switch n := path[0].(type) { case *ast.Ident: if qpos.info.Defs[n] != nil { description = "labelled statement" } else { description = "reference to labelled statement" } default: // Nothing much to say about statements. description = astutil.NodeDescription(n) } return &describeStmtResult{o.fset, path[0], description}, nil }
// ParseQueryPos parses the source query position pos. // If needExact, it must identify a single AST subtree; // this is appropriate for queries that allow fairly arbitrary syntax, // e.g. "describe". // func ParseQueryPos(iprog *loader.Program, posFlag string, needExact bool) (*QueryPos, error) { filename, startOffset, endOffset, err := parsePosFlag(posFlag) if err != nil { return nil, err } start, end, err := findQueryPos(iprog.Fset, filename, startOffset, endOffset) if err != nil { return nil, err } info, path, exact := iprog.PathEnclosingInterval(start, end) if path == nil { return nil, fmt.Errorf("no syntax here") } if needExact && !exact { return nil, fmt.Errorf("ambiguous selection within %s", astutil.NodeDescription(path[0])) } return &QueryPos{iprog.Fset, start, end, path, exact, info}, nil }
func (r *describeValueResult) toSerial(res *serial.Result, fset *token.FileSet) { var value, objpos string if r.constVal != nil { value = r.constVal.String() } if r.obj != nil { objpos = fset.Position(r.obj.Pos()).String() } res.Describe = &serial.Describe{ Desc: astutil.NodeDescription(r.expr), Pos: fset.Position(r.expr.Pos()).String(), Detail: "value", Value: &serial.DescribeValue{ Type: r.qpos.TypeString(r.typ), Value: value, ObjPos: objpos, }, } }
func (r *describeValueResult) display(printf printfFunc) { var prefix, suffix string if r.constVal != nil { suffix = fmt.Sprintf(" of constant value %s", r.constVal) } switch obj := r.obj.(type) { case *types.Func: if recv := obj.Type().(*types.Signature).Recv(); recv != nil { if _, ok := recv.Type().Underlying().(*types.Interface); ok { prefix = "interface method " } else { prefix = "method " } } } // Describe the expression. if r.obj != nil { if r.obj.Pos() == r.expr.Pos() { // defining ident printf(r.expr, "definition of %s%s%s", prefix, r.qpos.ObjectString(r.obj), suffix) } else { // referring ident printf(r.expr, "reference to %s%s%s", prefix, r.qpos.ObjectString(r.obj), suffix) if def := r.obj.Pos(); def != token.NoPos { printf(def, "defined here") } } } else { desc := astutil.NodeDescription(r.expr) if suffix != "" { // constant expression printf(r.expr, "%s%s", desc, suffix) } else { // non-constant expression printf(r.expr, "%s of type %s", desc, r.qpos.TypeString(r.typ)) } } }
// whicherrs takes an position to an error and tries to find all types, constants // and global value which a given error can point to and which can be checked from the // scope where the error lives. // In short, it returns a list of things that can be checked against in order to handle // an error properly. // // TODO(dmorsing): figure out if fields in errors like *os.PathError.Err // can be queried recursively somehow. func whicherrs(o *Oracle, qpos *QueryPos) (queryResult, error) { path, action := findInterestingNode(qpos.info, qpos.path) if action != actionExpr { return nil, fmt.Errorf("whicherrs wants an expression; got %s", astutil.NodeDescription(qpos.path[0])) } var expr ast.Expr var obj types.Object switch n := path[0].(type) { case *ast.ValueSpec: // ambiguous ValueSpec containing multiple names return nil, fmt.Errorf("multiple value specification") case *ast.Ident: obj = qpos.info.ObjectOf(n) expr = n case ast.Expr: expr = n default: return nil, fmt.Errorf("unexpected AST for expr: %T", n) } typ := qpos.info.TypeOf(expr) if !types.Identical(typ, builtinErrorType) { return nil, fmt.Errorf("selection is not an expression of type 'error'") } // Determine the ssa.Value for the expression. var value ssa.Value var err error if obj != nil { // def/ref of func/var object value, _, err = ssaValueForIdent(o.prog, qpos.info, obj, path) } else { value, _, err = ssaValueForExpr(o.prog, qpos.info, path) } if err != nil { return nil, err // e.g. trivially dead code } buildSSA(o) globals := findVisibleErrs(o.prog, qpos) constants := findVisibleConsts(o.prog, qpos) res := &whicherrsResult{ qpos: qpos, errpos: expr.Pos(), } // Find the instruction which initialized the // global error. If more than one instruction has stored to the global // remove the global from the set of values that we want to query. allFuncs := ssautil.AllFunctions(o.prog) for fn := range allFuncs { for _, b := range fn.Blocks { for _, instr := range b.Instrs { store, ok := instr.(*ssa.Store) if !ok { continue } gval, ok := store.Addr.(*ssa.Global) if !ok { continue } gbl, ok := globals[gval] if !ok { continue } // we already found a store to this global // The normal error define is just one store in the init // so we just remove this global from the set we want to query if gbl != nil { delete(globals, gval) } globals[gval] = store.Val } } } o.ptaConfig.AddQuery(value) for _, v := range globals { o.ptaConfig.AddQuery(v) } ptares := ptrAnalysis(o) valueptr := ptares.Queries[value] for g, v := range globals { ptr, ok := ptares.Queries[v] if !ok { continue } if !ptr.MayAlias(valueptr) { continue } res.globals = append(res.globals, g) } pts := valueptr.PointsTo() dedup := make(map[*ssa.NamedConst]bool) for _, label := range pts.Labels() { // These values are either MakeInterfaces or reflect // generated interfaces. For the purposes of this // analysis, we don't care about reflect generated ones makeiface, ok := label.Value().(*ssa.MakeInterface) if !ok { continue } constval, ok := makeiface.X.(*ssa.Const) if !ok { continue } c := constants[*constval] if c != nil && !dedup[c] { dedup[c] = true res.consts = append(res.consts, c) } } concs := pts.DynamicTypes() concs.Iterate(func(conc types.Type, _ interface{}) { // go/types is a bit annoying here. // We want to find all the types that we can // typeswitch or assert to. This means finding out // if the type pointed to can be seen by us. // // For the purposes of this analysis, the type is always // either a Named type or a pointer to one. // There are cases where error can be implemented // by unnamed types, but in that case, we can't assert to // it, so we don't care about it for this analysis. var name *types.TypeName switch t := conc.(type) { case *types.Pointer: named, ok := t.Elem().(*types.Named) if !ok { return } name = named.Obj() case *types.Named: name = t.Obj() default: return } if !isAccessibleFrom(name, qpos.info.Pkg) { return } res.types = append(res.types, &errorType{conc, name}) }) sort.Sort(membersByPosAndString(res.globals)) sort.Sort(membersByPosAndString(res.consts)) sort.Sort(sorterrorType(res.types)) return res, nil }
func (r *describeUnknownResult) toSerial(res *serial.Result, fset *token.FileSet) { res.Describe = &serial.Describe{ Desc: astutil.NodeDescription(r.node), Pos: fset.Position(r.node.Pos()).String(), } }
func (r *describeUnknownResult) display(printf printfFunc) { // Nothing much to say about misc syntax. printf(r.node, "%s", astutil.NodeDescription(r.node)) }