Beispiel #1
0
// 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
	}
}
Beispiel #2
0
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)
}
Beispiel #3
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---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
}
Beispiel #4
0
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,
	}
}
Beispiel #5
0
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
}
Beispiel #6
0
// ParseQueryPos parses the source query position pos.
// If needExact, it must identify a single AST subtree;
// this is appropriate for queries that allow fairly arbitrary syntax,
// e.g. "describe".
//
func ParseQueryPos(iprog *loader.Program, posFlag string, needExact bool) (*QueryPos, error) {
	filename, startOffset, endOffset, err := parsePosFlag(posFlag)
	if err != nil {
		return nil, err
	}
	start, end, err := findQueryPos(iprog.Fset, filename, startOffset, endOffset)
	if err != nil {
		return nil, err
	}
	info, path, exact := iprog.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{iprog.Fset, start, end, path, exact, info}, nil
}
Beispiel #7
0
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,
		},
	}
}
Beispiel #8
0
func (r *describeValueResult) display(printf printfFunc) {
	var prefix, suffix string
	if r.constVal != nil {
		suffix = fmt.Sprintf(" of constant value %s", r.constVal)
	}
	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 "
			}
		}
	}

	// Describe the expression.
	if r.obj != nil {
		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))
		}
	}
}
Beispiel #9
0
// 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(o *Oracle, qpos *QueryPos) (queryResult, error) {
	path, action := findInterestingNode(qpos.info, qpos.path)
	if action != actionExpr {
		return nil, 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 nil, fmt.Errorf("multiple value specification")
	case *ast.Ident:
		obj = qpos.info.ObjectOf(n)
		expr = n
	case ast.Expr:
		expr = n
	default:
		return nil, fmt.Errorf("unexpected AST for expr: %T", n)
	}

	typ := qpos.info.TypeOf(expr)
	if !types.Identical(typ, builtinErrorType) {
		return nil, fmt.Errorf("selection is not an expression of type 'error'")
	}
	// Determine the ssa.Value for the expression.
	var value ssa.Value
	var err error
	if obj != nil {
		// def/ref of func/var object
		value, _, err = ssaValueForIdent(o.prog, qpos.info, obj, path)
	} else {
		value, _, err = ssaValueForExpr(o.prog, qpos.info, path)
	}
	if err != nil {
		return nil, err // e.g. trivially dead code
	}
	buildSSA(o)

	globals := findVisibleErrs(o.prog, qpos)
	constants := findVisibleConsts(o.prog, qpos)

	res := &whicherrsResult{
		qpos:   qpos,
		errpos: expr.Pos(),
	}

	// 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(o.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
			}
		}
	}

	o.ptaConfig.AddQuery(value)
	for _, v := range globals {
		o.ptaConfig.AddQuery(v)
	}

	ptares := ptrAnalysis(o)
	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))
	return res, nil
}
Beispiel #10
0
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(),
	}
}
Beispiel #11
0
func (r *describeUnknownResult) display(printf printfFunc) {
	// Nothing much to say about misc syntax.
	printf(r.node, "%s", astutil.NodeDescription(r.node))
}