// Copyright 2013 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package ir // This file defines utilities for working with source positions // or source-level named entities ("objects"). // TODO(adonovan): test that {Value,Instruction}.Pos() positions match // the originating syntax, as specified. import ( "go/ast" "go/token" "go/types" ) // EnclosingFunction returns the function that contains the syntax // node denoted by path. // // Syntax associated with package-level variable specifications is // enclosed by the package's init() function. // // Returns nil if not found; reasons might include: // - the node is not enclosed by any function. // - the node is within an anonymous function (FuncLit) and // its IR function has not been created yet // (pkg.Build() has not yet been called). // func EnclosingFunction(pkg *Package, path []ast.Node) *Function { // Start with package-level function... fn := findEnclosingPackageLevelFunction(pkg, path) if fn == nil { return nil // not in any function } // ...then walk down the nested anonymous functions. n := len(path) outer: for i := range path { if lit, ok := path[n-1-i].(*ast.FuncLit); ok { for _, anon := range fn.AnonFuncs { if anon.Pos() == lit.Type.Func { fn = anon continue outer } } // IR function not found: // - package not yet built, or maybe // - builder skipped FuncLit in dead block // (in principle; but currently the Builder // generates even dead FuncLits). return nil } } return fn } // HasEnclosingFunction returns true if the AST node denoted by path // is contained within the declaration of some function or // package-level variable. // // Unlike EnclosingFunction, the behaviour of this function does not // depend on whether IR code for pkg has been built, so it can be // used to quickly reject check inputs that will cause // EnclosingFunction to fail, prior to IR building. // func HasEnclosingFunction(pkg *Package, path []ast.Node) bool { return findEnclosingPackageLevelFunction(pkg, path) != nil } // findEnclosingPackageLevelFunction returns the Function // corresponding to the package-level function enclosing path. // func findEnclosingPackageLevelFunction(pkg *Package, path []ast.Node) *Function { if n := len(path); n >= 2 { // [... {Gen,Func}Decl File] switch decl := path[n-2].(type) { case *ast.GenDecl: if decl.Tok == token.VAR && n >= 3 { // Package-level 'var' initializer. return pkg.init } case *ast.FuncDecl: // Declared function/method. fn := findNamedFunc(pkg, decl.Pos()) if fn == nil && decl.Recv == nil && decl.Name.Name == "init" { // Hack: return non-nil when IR is not yet // built so that HasEnclosingFunction works. return pkg.init } return fn } } return nil // not in any function } // findNamedFunc returns the named function whose FuncDecl.Ident is at // position pos. // func findNamedFunc(pkg *Package, pos token.Pos) *Function { for _, fn := range pkg.Functions { if fn.Pos() == pos { return fn } } return nil } // ValueForExpr returns the IR Value that corresponds to non-constant // expression e. // // It returns nil if no value was found, e.g. // - the expression is not lexically contained within f; // - f was not built with debug information; or // - e is a constant expression. (For efficiency, no debug // information is stored for constants. Use // go/types.Info.Types[e].Value instead.) // - e is a reference to nil or a built-in function. // - the value was optimised away. // // If e is an addressable expression used in an lvalue context, // value is the address denoted by e, and isAddr is true. // // The types of e (or &e, if isAddr) and the result are equal // (modulo "untyped" bools resulting from comparisons). // // (Tip: to find the ir.Value given a source position, use // astutil.PathEnclosingInterval to locate the ast.Node, then // EnclosingFunction to locate the Function, then ValueForExpr to find // the ir.Value.) // func (f *Function) ValueForExpr(e ast.Expr) (value Value, isAddr bool) { if f.debugInfo() { // (opt) e = unparen(e) for _, b := range f.Blocks { for _, instr := range b.Instrs { if ref, ok := instr.(*DebugRef); ok { if ref.Expr == e { return ref.X, ref.IsAddr } } } } } return } // --- Lookup functions for source-level named entities (types.Objects) --- // Package returns the IR Package corresponding to the specified // type-checker package object. // It returns nil if no such IR package has been created. // func (prog *Program) Package(obj *types.Package) *Package { return prog.packages[obj] } // packageLevelValue returns the package-level value corresponding to // the specified named object, which may be a package-level const // (*Const), var (*Global) or func (*Function) of some package in // prog. It returns nil if the object is not found. // func (prog *Program) packageLevelValue(obj types.Object) Value { if pkg, ok := prog.packages[obj.Pkg()]; ok { return pkg.values[obj] } return nil } // FuncValue returns the concrete Function denoted by the source-level // named function obj, or nil if obj denotes an interface method. // // TODO(adonovan): check the invariant that obj.Type() matches the // result's Signature, both in the params/results and in the receiver. // func (prog *Program) FuncValue(obj *types.Func) *Function { fn, _ := prog.packageLevelValue(obj).(*Function) return fn } // ConstValue returns the IR Value denoted by the source-level named // constant obj. // func (prog *Program) ConstValue(obj *types.Const) *Const { // TODO(adonovan): opt: share (don't reallocate) // Consts for const objects and constant ast.Exprs. // Universal constant? {true,false,nil} if obj.Parent() == types.Universe { return NewConst(obj.Val(), obj.Type()) } // Package-level named constant? if v := prog.packageLevelValue(obj); v != nil { return v.(*Const) } return NewConst(obj.Val(), obj.Type()) } // VarValue returns the IR Value that corresponds to a specific // identifier denoting the source-level named variable obj. // // VarValue returns nil if a local variable was not found, perhaps // because its package was not built, the debug information was not // requested during IR construction, or the value was optimized away. // // ref is the path to an ast.Ident (e.g. from PathEnclosingInterval), // and that ident must resolve to obj. // // pkg is the package enclosing the reference. (A reference to a var // always occurs within a function, so we need to know where to find it.) // // If the identifier is a field selector and its base expression is // non-addressable, then VarValue returns the value of that field. // For example: // func f() struct {x int} // f().x // VarValue(x) returns a *Field instruction of type int // // All other identifiers denote addressable locations (variables). // For them, VarValue may return either the variable's address or its // value, even when the expression is evaluated only for its value; the // situation is reported by isAddr, the second component of the result. // // If !isAddr, the returned value is the one associated with the // specific identifier. For example, // var x int // VarValue(x) returns Const 0 here // x = 1 // VarValue(x) returns Const 1 here // // It is not specified whether the value or the address is returned in // any particular case, as it may depend upon optimizations performed // during IR code generation, such as registerization, constant // folding, avoidance of materialization of subexpressions, etc. // func (prog *Program) VarValue(obj *types.Var, pkg *Package, ref []ast.Node) (value Value, isAddr bool) { // All references to a var are local to some function, possibly init. fn := EnclosingFunction(pkg, ref) if fn == nil { return // e.g. def of struct field; IR not built? } id := ref[0].(*ast.Ident) // Defining ident of a parameter? if id.Pos() == obj.Pos() { for _, param := range fn.Params { if param.Object() == obj { return param, false } } } // Other ident? for _, b := range fn.Blocks { for _, instr := range b.Instrs { if dr, ok := instr.(*DebugRef); ok { if dr.Pos() == id.Pos() { return dr.X, dr.IsAddr } } } } // Defining ident of package-level var? if v := prog.packageLevelValue(obj); v != nil { return v.(*Global), true } return // e.g. debug info not requested, or var optimized away }