Example #1
0
// 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
}
Example #2
0
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
}
Example #4
0
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,
	})
}