// isUntypedConst reports whether expr is an untyped constant, // and indicates what its default type is. // scope may be nil. func (f *file) isUntypedConst(expr ast.Expr, scope *types.Scope) (defType string, ok bool) { typ := f.pkg.typeOf(expr) if typ == nil || scope == nil { return "", false } // Re-evaluate expr outside of its context to see if it's untyped. // (An expr evaluated within, for example, an assignment context will get the type of the LHS.) typ, _, err := types.EvalNode(f.fset, expr, f.pkg.typesPkg, scope) if err != nil { return "", false } if b, ok := typ.(*types.Basic); ok { if dt, ok := basicTypeKinds[b.Kind()]; ok { return dt, true } } return "", false }
func doOneInput(input, filename string) bool { conf := loader.Config{SourceImports: true} // Parsing. f, err := conf.ParseFile(filename, input) if err != nil { fmt.Println(err) return false } // Create single-file main package and import its dependencies. conf.CreateFromFiles("main", f) iprog, err := conf.Load() if err != nil { fmt.Println(err) return false } mainPkgInfo := iprog.Created[0].Pkg // SSA creation + building. prog := ssa.Create(iprog, ssa.SanityCheckFunctions) prog.BuildAll() mainpkg := prog.Package(mainPkgInfo) 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) } // Find all calls to the built-in print(x). Analytically, // print is a no-op, but it's a convenient hook for testing // the PTS of an expression, so our tests use it. probes := make(map[*ssa.CallCommon]bool) for fn := range ssautil.AllFunctions(prog) { if fn.Pkg == mainpkg { for _, b := range fn.Blocks { for _, instr := range b.Instrs { if instr, ok := instr.(ssa.CallInstruction); ok { if b, ok := instr.Common().Value.(*ssa.Builtin); ok && b.Name() == "print" { probes[instr.Common()] = true } } } } } } 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(prog.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 fmt.Fprintf(&log, "Input: %s\n", filename) // Run the analysis. config := &pointer.Config{ Reflection: true, BuildCallGraph: true, Mains: []*ssa.Package{ptrmain}, Log: &log, } for probe := range probes { v := probe.Args[0] if pointer.CanPoint(v.Type()) { config.AddQuery(v) } } // Print the log is there was an error or a panic. complete := false defer func() { if !complete || !ok { log.WriteTo(os.Stderr) } }() result, err := pointer.Analyze(config) if err != nil { panic(err) // internal error in pointer analysis } // Check the expectations. for _, e := range exps { var call *ssa.CallCommon var pts pointer.PointsToSet var tProbe types.Type if e.needsProbe() { if call, pts = findProbe(prog, probes, result.Queries, 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, pts, lineMapping, prog) { ok = false } case "types": if !checkTypesExpectation(e, pts, 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 }
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 probes []probe var log bytes.Buffer // Run the analysis. config := &pointer.Config{ Reflection: true, BuildCallGraph: true, Mains: []*ssa.Package{ptrmain}, Log: &log, Print: func(site *ssa.CallCommon, p pointer.Pointer) { probes = append(probes, probe{site, p}) }, } // 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 pr *probe if e.needsProbe() { if pr = findProbe(prog, probes, e); pr == nil { ok = false e.errorf("unreachable print() statement has expectation %s", e) continue } if pr.arg0 == nil { ok = false e.errorf("expectation on non-pointerlike operand: %s", pr.instr.Args[0].Type()) continue } } switch e.kind { case "pointsto": if !checkPointsToExpectation(e, pr, lineMapping, prog) { ok = false } case "types": if !checkTypesExpectation(e, pr) { 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 }
func processPackage(path string, fSet *token.FileSet, pkg *ast.Package) { fmt.Printf("Processing package: %s\n", pkg.Name) var files []*ast.File for _, f := range pkg.Files { files = append(files, f) } // Find all the types var nodes []*ast.GenDecl ast.Inspect(pkg, func(node ast.Node) bool { if d, ok := node.(*ast.GenDecl); ok && d.Tok == token.TYPE { nodes = append(nodes, d) return false } return true }) cfg := &types.Config{} info := &types.Info{ Types: map[ast.Expr]types.TypeAndValue{}, } typePackage, err := cfg.Check(path, fSet, files, info) if err != nil { log.Fatalln(err.Error()) } var resources []Resource for _, node := range nodes { spec := node.Specs[0].(*ast.TypeSpec) resource := new(Resource) resource.Name = spec.Name.String() resource.Doc = node.Doc.Text() resource.Methods = make(map[string]*Method) // Get the receiver type t, _, err := types.EvalNode(fSet, spec.Name, typePackage, typePackage.Scope()) if err != nil { log.Fatalln(err.Error()) } targetType := t.(*types.Named) // Find all the methods for targetType var methods []*ast.FuncDecl ast.Inspect(pkg, func(node ast.Node) bool { if d, ok := node.(*ast.FuncDecl); ok { if d.Recv == nil { return true } t, _, err := types.EvalNode(fSet, d.Recv.List[0].Type, typePackage, typePackage.Scope()) if err != nil { log.Fatalln(err.Error()) } if types.Identical(t, targetType) { methods = append(methods, d) return false } return true } return true }) for _, method := range methods { analyzeMethod(fSet, typePackage, info, method, resource) } resources = append(resources, *resource) } out := os.Stdout if len(os.Args) == 3 { out, err = os.Create(os.Args[2]) if err != nil { fmt.Println(err.Error()) return } } json.NewEncoder(out).Encode(map[string]interface{}{ "Resources": resources, }) }