Exemplo n.º 1
0
// 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
}
Exemplo n.º 2
0
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
}
Exemplo n.º 3
0
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
}