// 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). 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 describeValue(o *Oracle, qpos *QueryPos, path []ast.Node) (*describeValueResult, error) { 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: // Is this reachable? return nil, fmt.Errorf("unexpected AST for expr: %T", n) } typ := qpos.info.TypeOf(expr) constVal := qpos.info.ValueOf(expr) // From this point on, we cannot fail with an error. // Failure to run the pointer analysis will be reported later. // // Our disposition to pointer analysis may be one of the following: // - ok: ssa.Value was const or func. // - error: no ssa.Value for expr (e.g. trivially dead code) // - ok: ssa.Value is non-pointerlike // - error: no Pointer for ssa.Value (e.g. analytically unreachable) // - ok: Pointer has empty points-to set // - ok: Pointer has non-empty points-to set // ptaErr is non-nil only in the "error:" cases. var ptaErr error var ptrs []pointerResult // Only run pointer analysis on pointerlike expression types. if pointer.CanPoint(typ) { // Determine the ssa.Value for the expression. var value ssa.Value if obj != nil { // def/ref of func/var/const object value, ptaErr = ssaValueForIdent(o.prog, qpos.info, obj, path) } else { // any other expression if qpos.info.ValueOf(path[0].(ast.Expr)) == nil { // non-constant? value, ptaErr = ssaValueForExpr(o.prog, qpos.info, path) } } if value != nil { // TODO(adonovan): IsIdentical may be too strict; // perhaps we need is-assignable or even // has-same-underlying-representation? indirect := types.IsIdentical(types.NewPointer(typ), value.Type()) ptrs, ptaErr = describePointer(o, value, indirect) } } return &describeValueResult{ qpos: qpos, expr: expr, typ: typ, constVal: constVal, obj: obj, ptaErr: ptaErr, ptrs: ptrs, }, nil }
func doOneInput(input, filename string) bool { impctx := &importer.Config{Build: &build.Default} imp := importer.New(impctx) // Parsing. f, err := parser.ParseFile(imp.Fset, filename, input, 0) if err != nil { // TODO(adonovan): err is a scanner error list; // display all errors not just first? fmt.Println(err) return false } // Create single-file main package and import its dependencies. info := imp.CreatePackage("main", f) // SSA creation + building. prog := ssa.NewProgram(imp.Fset, ssa.SanityCheckFunctions) if err := prog.CreatePackages(imp); err != nil { fmt.Println(err) return false } prog.BuildAll() mainpkg := prog.Package(info.Pkg) ptrmain := mainpkg // main package for the pointer analysis if mainpkg.Func("main") == nil { // No main function; assume it's a test. ptrmain = prog.CreateTestMainPackage(mainpkg) } ok := true lineMapping := make(map[string]string) // maps "file:line" to @line tag // Parse expectations in this input. var exps []*expectation re := regexp.MustCompile("// *@([a-z]*) *(.*)$") lines := strings.Split(input, "\n") for linenum, line := range lines { linenum++ // make it 1-based if matches := re.FindAllStringSubmatch(line, -1); matches != nil { match := matches[0] kind, rest := match[1], match[2] e := &expectation{kind: kind, filename: filename, linenum: linenum} if kind == "line" { if rest == "" { ok = false e.errorf("@%s expectation requires identifier", kind) } else { lineMapping[fmt.Sprintf("%s:%d", filename, linenum)] = rest } continue } if e.needsProbe() && !strings.Contains(line, "print(") { ok = false e.errorf("@%s expectation must follow call to print(x)", kind) continue } switch kind { case "pointsto": e.args = split(rest, "|") case "types": for _, typstr := range split(rest, "|") { var t types.Type = types.Typ[types.Invalid] // means "..." if typstr != "..." { texpr, err := parser.ParseExpr(typstr) if err != nil { ok = false // Don't print err since its location is bad. e.errorf("'%s' is not a valid type", typstr) continue } mainFileScope := mainpkg.Object.Scope().Child(0) t, _, err = types.EvalNode(imp.Fset, texpr, mainpkg.Object, mainFileScope) if err != nil { ok = false // Don't print err since its location is bad. e.errorf("'%s' is not a valid type: %s", typstr, err) continue } } e.types = append(e.types, t) } case "calls": e.args = split(rest, "->") // TODO(adonovan): eagerly reject the // expectation if fn doesn't denote // existing function, rather than fail // the expectation after analysis. if len(e.args) != 2 { ok = false e.errorf("@calls expectation wants 'caller -> callee' arguments") continue } case "warning": lit, err := strconv.Unquote(strings.TrimSpace(rest)) if err != nil { ok = false e.errorf("couldn't parse @warning operand: %s", err.Error()) continue } e.args = append(e.args, lit) default: ok = false e.errorf("unknown expectation kind: %s", e) continue } exps = append(exps, e) } } var log bytes.Buffer // Run the analysis. config := &pointer.Config{ Reflection: true, BuildCallGraph: true, QueryPrintCalls: true, Mains: []*ssa.Package{ptrmain}, Log: &log, } // Print the log is there was an error or a panic. complete := false defer func() { if !complete || !ok { log.WriteTo(os.Stderr) } }() result := pointer.Analyze(config) // Check the expectations. for _, e := range exps { var call *ssa.CallCommon var ptr pointer.Pointer var tProbe types.Type if e.needsProbe() { if call, ptr = findProbe(prog, result.PrintCalls, e); call == nil { ok = false e.errorf("unreachable print() statement has expectation %s", e) continue } tProbe = call.Args[0].Type() if !pointer.CanPoint(tProbe) { ok = false e.errorf("expectation on non-pointerlike operand: %s", tProbe) continue } } switch e.kind { case "pointsto": if !checkPointsToExpectation(e, ptr, lineMapping, prog) { ok = false } case "types": if !checkTypesExpectation(e, ptr, tProbe) { ok = false } case "calls": if !checkCallsExpectation(prog, e, result.CallGraph) { ok = false } case "warning": if !checkWarningExpectation(prog, e, result.Warnings) { ok = false } } } complete = true // ok = false // debugging: uncomment to always see log return ok }