// ParseQueryPos parses the source query position pos and returns the // AST node of the loaded program lprog that it identifies. // If needExact, it must identify a single AST subtree; // this is appropriate for queries that allow fairly arbitrary syntax, // e.g. "describe". // func parseQueryPos(lprog *loader.Program, pos string, needExact bool) (*queryPos, error) { filename, startOffset, endOffset, err := parsePos(pos) if err != nil { return nil, err } // Find the named file among those in the loaded program. var file *token.File lprog.Fset.Iterate(func(f *token.File) bool { if sameFile(filename, f.Name()) { file = f return false // done } return true // continue }) if file == nil { return nil, fmt.Errorf("file %s not found in loaded program", filename) } start, end, err := fileOffsetToPos(file, startOffset, endOffset) if err != nil { return nil, err } info, path, exact := lprog.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{lprog.Fset, start, end, path, exact, info}, nil }
// 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) PrintPlain(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) for _, pos := range r.sameids { printf(pos, "%s", r.object) } }
// 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, fields, and methods (for an expression or type expression) // func describe(q *Query) error { lconf := loader.Config{Build: q.Build} allowErrors(&lconf) if _, err := importQueryPackage(q.Pos, &lconf); err != nil { return err } // Load/parse/type-check the program. lprog, err := lconf.Load() if err != nil { return err } qpos, err := parseQueryPos(lprog, q.Pos, true) // (need exact pos) if err != nil { return err } if false { // debugging fprintf(os.Stderr, lprog.Fset, qpos.path[0], "you selected: %s %s", astutil.NodeDescription(qpos.path[0]), pathToString(qpos.path)) } var qr QueryResult path, action := findInterestingNode(qpos.info, qpos.path) switch action { case actionExpr: qr, err = describeValue(qpos, path) case actionType: qr, err = describeType(qpos, path) case actionPackage: qr, err = describePackage(qpos, path) case actionStmt: qr, err = describeStmt(qpos, path) case actionUnknown: qr = &describeUnknownResult{path[0]} default: panic(action) // unreachable } if err != nil { return err } q.Output(lprog.Fset, qr) return 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 }
func (r *describeValueResult) PrintPlain(printf printfFunc) { var prefix, suffix string if r.constVal != nil { suffix = fmt.Sprintf(" of value %s", r.constVal) } // Describe the expression. if r.obj != nil { 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 " } } case *types.Alias: prefix = tokenOf(obj.Orig()) + " " } 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)) } } printMethods(printf, r.expr, r.methods) printFields(printf, r.expr, r.fields) }
// ParseQueryPos parses the source query position pos and returns the // AST node of the loaded program lprog that it identifies. // If needExact, it must identify a single AST subtree; // this is appropriate for queries that allow fairly arbitrary syntax, // e.g. "describe". // func parseQueryPos(lprog *loader.Program, posFlag string, needExact bool) (*queryPos, error) { filename, startOffset, endOffset, err := parsePosFlag(posFlag) if err != nil { return nil, err } start, end, err := findQueryPos(lprog.Fset, filename, startOffset, endOffset) if err != nil { return nil, err } info, path, exact := lprog.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{lprog.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) JSON(fset *token.FileSet) []byte { var value, objpos string if r.constVal != nil { value = r.constVal.String() } if r.obj != nil { objpos = fset.Position(r.obj.Pos()).String() } return toJSON(&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 *whatResult) JSON(fset *token.FileSet) []byte { 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, }) } var sameids []string for _, pos := range r.sameids { sameids = append(sameids, fset.Position(pos).String()) } return toJSON(&serial.What{ Modes: r.modes, SrcDir: r.srcdir, ImportPath: r.importPath, Enclosing: enclosing, Object: r.object, SameIDs: sameids, }) }
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)) }
// 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(q *Query) error { lconf := loader.Config{Build: q.Build} if err := setPTAScope(&lconf, q.Scope); err != nil { return err } // Load/parse/type-check the program. lprog, err := lconf.Load() if err != nil { return err } q.Fset = lprog.Fset qpos, err := parseQueryPos(lprog, q.Pos, true) // needs exact pos if err != nil { return err } prog := ssautil.CreateProgram(lprog, ssa.GlobalDebug) ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection) if err != nil { return err } path, action := findInterestingNode(qpos.info, qpos.path) if action != actionExpr { return 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 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 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 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 if obj != nil { // def/ref of func/var object value, isAddr, err = ssaValueForIdent(prog, qpos.info, obj, path) } else { value, isAddr, err = ssaValueForExpr(prog, qpos.info, path) } if err != nil { return err // e.g. trivially dead code } // Defer SSA construction till after errors are reported. prog.Build() // Run the pointer analysis. ptrs, err := runPTA(ptaConfig, value, isAddr) if err != nil { return err // e.g. analytically unreachable } q.result = &pointstoResult{ qpos: qpos, typ: typ, ptrs: ptrs, } return nil }
func (r *describeUnknownResult) JSON(fset *token.FileSet) []byte { return toJSON(&serial.Describe{ Desc: astutil.NodeDescription(r.node), Pos: fset.Position(r.node.Pos()).String(), }) }
// 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(q *Query) error { lconf := loader.Config{Build: q.Build} if err := setPTAScope(&lconf, q.Scope); err != nil { return err } // Load/parse/type-check the program. lprog, err := lconf.Load() if err != nil { return err } q.Fset = lprog.Fset qpos, err := parseQueryPos(lprog, q.Pos, true) // needs exact pos if err != nil { return err } prog := ssautil.CreateProgram(lprog, ssa.GlobalDebug) ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection) if err != nil { return err } path, action := findInterestingNode(qpos.info, qpos.path) if action != actionExpr { return 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 fmt.Errorf("multiple value specification") case *ast.Ident: obj = qpos.info.ObjectOf(n) expr = n case ast.Expr: expr = n default: return fmt.Errorf("unexpected AST for expr: %T", n) } typ := qpos.info.TypeOf(expr) if !types.Identical(typ, builtinErrorType) { return fmt.Errorf("selection is not an expression of type 'error'") } // Determine the ssa.Value for the expression. var value ssa.Value if obj != nil { // def/ref of func/var object value, _, err = ssaValueForIdent(prog, qpos.info, obj, path) } else { value, _, err = ssaValueForExpr(prog, qpos.info, path) } if err != nil { return err // e.g. trivially dead code } // Defer SSA construction till after errors are reported. prog.Build() globals := findVisibleErrs(prog, qpos) constants := findVisibleConsts(prog, qpos) res := &whicherrsResult{ qpos: qpos, errpos: expr.Pos(), } // TODO(adonovan): the following code is heavily duplicated // w.r.t. "pointsto". Refactor? // 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(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 } } } ptaConfig.AddQuery(value) for _, v := range globals { ptaConfig.AddQuery(v) } ptares := ptrAnalysis(ptaConfig) 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)) q.result = res return nil }