mirror of
https://github.com/golang/go.git
synced 2025-05-05 15:43:04 +00:00
x/tools/...: fork and tag !1.5 all files that use go/types et al
This change will ensure that the tree continues to work with go1.4.1. All files continue to depend on golang.org/x/tools/go/types, but in a follow-up change, I will switch the primary files to depend on the standard go/types package. Another (smaller) set of files will be forked and tagged, this time !1.6, due to API differences between the two packages. All tests pass using 1.4.1, 1.5, and ~1.6 (tip). Change-Id: Ifd75a6330e120957d646be91693daaba1ce0e8c9 Reviewed-on: https://go-review.googlesource.com/18333 Reviewed-by: Robert Griesemer <gri@golang.org>
This commit is contained in:
parent
8f90b5e560
commit
2477c0d578
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
// The bundle command concatenates the source files of a package,
|
||||
// renaming package-level names by adding a prefix and renaming
|
||||
// identifiers as needed to preserve referential integrity.
|
||||
|
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
// ssadump: a tool for displaying and interpreting the SSA form of Go programs.
|
||||
package main // import "golang.org/x/tools/cmd/ssadump"
|
||||
|
||||
|
188
cmd/ssadump/main14.go
Normal file
188
cmd/ssadump/main14.go
Normal file
@ -0,0 +1,188 @@
|
||||
// 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.
|
||||
|
||||
// +build !go1.5
|
||||
|
||||
// ssadump: a tool for displaying and interpreting the SSA form of Go programs.
|
||||
package main // import "golang.org/x/tools/cmd/ssadump"
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/build"
|
||||
"os"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
|
||||
"golang.org/x/tools/go/buildutil"
|
||||
"golang.org/x/tools/go/loader"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/tools/go/ssa/interp"
|
||||
"golang.org/x/tools/go/ssa/ssautil"
|
||||
"golang.org/x/tools/go/types"
|
||||
)
|
||||
|
||||
var (
|
||||
modeFlag = ssa.BuilderModeFlag(flag.CommandLine, "build", 0)
|
||||
|
||||
testFlag = flag.Bool("test", false, "Loads test code (*_test.go) for imported packages.")
|
||||
|
||||
runFlag = flag.Bool("run", false, "Invokes the SSA interpreter on the program.")
|
||||
|
||||
interpFlag = flag.String("interp", "", `Options controlling the SSA test interpreter.
|
||||
The value is a sequence of zero or more more of these letters:
|
||||
R disable [R]ecover() from panic; show interpreter crash instead.
|
||||
T [T]race execution of the program. Best for single-threaded programs!
|
||||
`)
|
||||
)
|
||||
|
||||
const usage = `SSA builder and interpreter.
|
||||
Usage: ssadump [<flag> ...] <args> ...
|
||||
Use -help flag to display options.
|
||||
|
||||
Examples:
|
||||
% ssadump -build=F hello.go # dump SSA form of a single package
|
||||
% ssadump -run -interp=T hello.go # interpret a program, with tracing
|
||||
% ssadump -run -test unicode -- -test.v # interpret the unicode package's tests, verbosely
|
||||
` + loader.FromArgsUsage +
|
||||
`
|
||||
When -run is specified, ssadump will run the program.
|
||||
The entry point depends on the -test flag:
|
||||
if clear, it runs the first package named main.
|
||||
if set, it runs the tests of each package.
|
||||
`
|
||||
|
||||
var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")
|
||||
|
||||
func init() {
|
||||
flag.Var((*buildutil.TagsFlag)(&build.Default.BuildTags), "tags", buildutil.TagsFlagDoc)
|
||||
|
||||
// If $GOMAXPROCS isn't set, use the full capacity of the machine.
|
||||
// For small machines, use at least 4 threads.
|
||||
if os.Getenv("GOMAXPROCS") == "" {
|
||||
n := runtime.NumCPU()
|
||||
if n < 4 {
|
||||
n = 4
|
||||
}
|
||||
runtime.GOMAXPROCS(n)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
if err := doMain(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "ssadump: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func doMain() error {
|
||||
flag.Parse()
|
||||
args := flag.Args()
|
||||
|
||||
conf := loader.Config{Build: &build.Default}
|
||||
|
||||
// Choose types.Sizes from conf.Build.
|
||||
var wordSize int64 = 8
|
||||
switch conf.Build.GOARCH {
|
||||
case "386", "arm":
|
||||
wordSize = 4
|
||||
}
|
||||
conf.TypeChecker.Sizes = &types.StdSizes{
|
||||
MaxAlign: 8,
|
||||
WordSize: wordSize,
|
||||
}
|
||||
|
||||
var interpMode interp.Mode
|
||||
for _, c := range *interpFlag {
|
||||
switch c {
|
||||
case 'T':
|
||||
interpMode |= interp.EnableTracing
|
||||
case 'R':
|
||||
interpMode |= interp.DisableRecover
|
||||
default:
|
||||
return fmt.Errorf("unknown -interp option: '%c'", c)
|
||||
}
|
||||
}
|
||||
|
||||
if len(args) == 0 {
|
||||
fmt.Fprint(os.Stderr, usage)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Profiling support.
|
||||
if *cpuprofile != "" {
|
||||
f, err := os.Create(*cpuprofile)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
pprof.StartCPUProfile(f)
|
||||
defer pprof.StopCPUProfile()
|
||||
}
|
||||
|
||||
// Use the initial packages from the command line.
|
||||
args, err := conf.FromArgs(args, *testFlag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// The interpreter needs the runtime package.
|
||||
if *runFlag {
|
||||
conf.Import("runtime")
|
||||
}
|
||||
|
||||
// Load, parse and type-check the whole program.
|
||||
iprog, err := conf.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create and build SSA-form program representation.
|
||||
prog := ssautil.CreateProgram(iprog, *modeFlag)
|
||||
|
||||
// Build and display only the initial packages
|
||||
// (and synthetic wrappers), unless -run is specified.
|
||||
for _, info := range iprog.InitialPackages() {
|
||||
prog.Package(info.Pkg).Build()
|
||||
}
|
||||
|
||||
// Run the interpreter.
|
||||
if *runFlag {
|
||||
prog.Build()
|
||||
|
||||
var main *ssa.Package
|
||||
pkgs := prog.AllPackages()
|
||||
if *testFlag {
|
||||
// If -test, run all packages' tests.
|
||||
if len(pkgs) > 0 {
|
||||
main = prog.CreateTestMainPackage(pkgs...)
|
||||
}
|
||||
if main == nil {
|
||||
return fmt.Errorf("no tests")
|
||||
}
|
||||
} else {
|
||||
// Otherwise, run main.main.
|
||||
for _, pkg := range pkgs {
|
||||
if pkg.Pkg.Name() == "main" {
|
||||
main = pkg
|
||||
if main.Func("main") == nil {
|
||||
return fmt.Errorf("no func main() in main package")
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if main == nil {
|
||||
return fmt.Errorf("no main package")
|
||||
}
|
||||
}
|
||||
|
||||
if runtime.GOARCH != build.Default.GOARCH {
|
||||
return fmt.Errorf("cross-interpretation is not supported (target has GOARCH %s, interpreter has %s)",
|
||||
build.Default.GOARCH, runtime.GOARCH)
|
||||
}
|
||||
|
||||
interp.Interpret(main, interpMode, conf.TypeChecker.Sizes, main.Pkg.Path(), args)
|
||||
}
|
||||
return nil
|
||||
}
|
@ -1,3 +1,9 @@
|
||||
// Copyright 2014 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.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
// Package cha computes the call graph of a Go program using the Class
|
||||
// Hierarchy Analysis (CHA) algorithm.
|
||||
//
|
||||
|
126
go/callgraph/cha/cha14.go
Normal file
126
go/callgraph/cha/cha14.go
Normal file
@ -0,0 +1,126 @@
|
||||
// Copyright 2014 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.
|
||||
|
||||
// +build !go1.5
|
||||
|
||||
// Package cha computes the call graph of a Go program using the Class
|
||||
// Hierarchy Analysis (CHA) algorithm.
|
||||
//
|
||||
// CHA was first described in "Optimization of Object-Oriented Programs
|
||||
// Using Static Class Hierarchy Analysis", Jeffrey Dean, David Grove,
|
||||
// and Craig Chambers, ECOOP'95.
|
||||
//
|
||||
// CHA is related to RTA (see go/callgraph/rta); the difference is that
|
||||
// CHA conservatively computes the entire "implements" relation between
|
||||
// interfaces and concrete types ahead of time, whereas RTA uses dynamic
|
||||
// programming to construct it on the fly as it encounters new functions
|
||||
// reachable from main. CHA may thus include spurious call edges for
|
||||
// types that haven't been instantiated yet, or types that are never
|
||||
// instantiated.
|
||||
//
|
||||
// Since CHA conservatively assumes that all functions are address-taken
|
||||
// and all concrete types are put into interfaces, it is sound to run on
|
||||
// partial programs, such as libraries without a main or test function.
|
||||
//
|
||||
package cha // import "golang.org/x/tools/go/callgraph/cha"
|
||||
|
||||
import (
|
||||
"golang.org/x/tools/go/callgraph"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/tools/go/ssa/ssautil"
|
||||
"golang.org/x/tools/go/types"
|
||||
"golang.org/x/tools/go/types/typeutil"
|
||||
)
|
||||
|
||||
// CallGraph computes the call graph of the specified program using the
|
||||
// Class Hierarchy Analysis algorithm.
|
||||
//
|
||||
func CallGraph(prog *ssa.Program) *callgraph.Graph {
|
||||
cg := callgraph.New(nil) // TODO(adonovan) eliminate concept of rooted callgraph
|
||||
|
||||
allFuncs := ssautil.AllFunctions(prog)
|
||||
|
||||
// funcsBySig contains all functions, keyed by signature. It is
|
||||
// the effective set of address-taken functions used to resolve
|
||||
// a dynamic call of a particular signature.
|
||||
var funcsBySig typeutil.Map // value is []*ssa.Function
|
||||
|
||||
// methodsByName contains all methods,
|
||||
// grouped by name for efficient lookup.
|
||||
methodsByName := make(map[string][]*ssa.Function)
|
||||
|
||||
// methodsMemo records, for every abstract method call call I.f on
|
||||
// interface type I, the set of concrete methods C.f of all
|
||||
// types C that satisfy interface I.
|
||||
methodsMemo := make(map[*types.Func][]*ssa.Function)
|
||||
lookupMethods := func(m *types.Func) []*ssa.Function {
|
||||
methods, ok := methodsMemo[m]
|
||||
if !ok {
|
||||
I := m.Type().(*types.Signature).Recv().Type().Underlying().(*types.Interface)
|
||||
for _, f := range methodsByName[m.Name()] {
|
||||
C := f.Signature.Recv().Type() // named or *named
|
||||
if types.Implements(C, I) {
|
||||
methods = append(methods, f)
|
||||
}
|
||||
}
|
||||
methodsMemo[m] = methods
|
||||
}
|
||||
return methods
|
||||
}
|
||||
|
||||
for f := range allFuncs {
|
||||
if f.Signature.Recv() == nil {
|
||||
// Package initializers can never be address-taken.
|
||||
if f.Name() == "init" && f.Synthetic == "package initializer" {
|
||||
continue
|
||||
}
|
||||
funcs, _ := funcsBySig.At(f.Signature).([]*ssa.Function)
|
||||
funcs = append(funcs, f)
|
||||
funcsBySig.Set(f.Signature, funcs)
|
||||
} else {
|
||||
methodsByName[f.Name()] = append(methodsByName[f.Name()], f)
|
||||
}
|
||||
}
|
||||
|
||||
addEdge := func(fnode *callgraph.Node, site ssa.CallInstruction, g *ssa.Function) {
|
||||
gnode := cg.CreateNode(g)
|
||||
callgraph.AddEdge(fnode, site, gnode)
|
||||
}
|
||||
|
||||
addEdges := func(fnode *callgraph.Node, site ssa.CallInstruction, callees []*ssa.Function) {
|
||||
// Because every call to a highly polymorphic and
|
||||
// frequently used abstract method such as
|
||||
// (io.Writer).Write is assumed to call every concrete
|
||||
// Write method in the program, the call graph can
|
||||
// contain a lot of duplication.
|
||||
//
|
||||
// TODO(adonovan): opt: consider factoring the callgraph
|
||||
// API so that the Callers component of each edge is a
|
||||
// slice of nodes, not a singleton.
|
||||
for _, g := range callees {
|
||||
addEdge(fnode, site, g)
|
||||
}
|
||||
}
|
||||
|
||||
for f := range allFuncs {
|
||||
fnode := cg.CreateNode(f)
|
||||
for _, b := range f.Blocks {
|
||||
for _, instr := range b.Instrs {
|
||||
if site, ok := instr.(ssa.CallInstruction); ok {
|
||||
call := site.Common()
|
||||
if call.IsInvoke() {
|
||||
addEdges(fnode, site, lookupMethods(call.Method))
|
||||
} else if g := call.StaticCallee(); g != nil {
|
||||
addEdge(fnode, site, g)
|
||||
} else if _, ok := call.Value.(*ssa.Builtin); !ok {
|
||||
callees, _ := funcsBySig.At(call.Signature()).([]*ssa.Function)
|
||||
addEdges(fnode, site, callees)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cg
|
||||
}
|
112
go/callgraph/cha/cha14_test.go
Normal file
112
go/callgraph/cha/cha14_test.go
Normal file
@ -0,0 +1,112 @@
|
||||
// Copyright 2014 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.
|
||||
|
||||
// +build !go1.5
|
||||
|
||||
// No testdata on Android.
|
||||
|
||||
// +build !android
|
||||
|
||||
package cha_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"io/ioutil"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/go/callgraph"
|
||||
"golang.org/x/tools/go/callgraph/cha"
|
||||
"golang.org/x/tools/go/loader"
|
||||
"golang.org/x/tools/go/ssa/ssautil"
|
||||
"golang.org/x/tools/go/types"
|
||||
)
|
||||
|
||||
var inputs = []string{
|
||||
"testdata/func.go",
|
||||
"testdata/iface.go",
|
||||
"testdata/recv.go",
|
||||
}
|
||||
|
||||
func expectation(f *ast.File) (string, token.Pos) {
|
||||
for _, c := range f.Comments {
|
||||
text := strings.TrimSpace(c.Text())
|
||||
if t := strings.TrimPrefix(text, "WANT:\n"); t != text {
|
||||
return t, c.Pos()
|
||||
}
|
||||
}
|
||||
return "", token.NoPos
|
||||
}
|
||||
|
||||
// TestCHA runs CHA on each file in inputs, prints the dynamic edges of
|
||||
// the call graph, and compares it with the golden results embedded in
|
||||
// the WANT comment at the end of the file.
|
||||
//
|
||||
func TestCHA(t *testing.T) {
|
||||
for _, filename := range inputs {
|
||||
content, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
t.Errorf("couldn't read file '%s': %s", filename, err)
|
||||
continue
|
||||
}
|
||||
|
||||
conf := loader.Config{
|
||||
ParserMode: parser.ParseComments,
|
||||
}
|
||||
f, err := conf.ParseFile(filename, content)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
want, pos := expectation(f)
|
||||
if pos == token.NoPos {
|
||||
t.Errorf("No WANT: comment in %s", filename)
|
||||
continue
|
||||
}
|
||||
|
||||
conf.CreateFromFiles("main", f)
|
||||
iprog, err := conf.Load()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
prog := ssautil.CreateProgram(iprog, 0)
|
||||
mainPkg := prog.Package(iprog.Created[0].Pkg)
|
||||
prog.Build()
|
||||
|
||||
cg := cha.CallGraph(prog)
|
||||
|
||||
if got := printGraph(cg, mainPkg.Pkg); got != want {
|
||||
t.Errorf("%s: got:\n%s\nwant:\n%s",
|
||||
prog.Fset.Position(pos), got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func printGraph(cg *callgraph.Graph, from *types.Package) string {
|
||||
var edges []string
|
||||
callgraph.GraphVisitEdges(cg, func(e *callgraph.Edge) error {
|
||||
if strings.Contains(e.Description(), "dynamic") {
|
||||
edges = append(edges, fmt.Sprintf("%s --> %s",
|
||||
e.Caller.Func.RelString(from),
|
||||
e.Callee.Func.RelString(from)))
|
||||
}
|
||||
return nil
|
||||
})
|
||||
sort.Strings(edges)
|
||||
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString("Dynamic calls\n")
|
||||
for _, edge := range edges {
|
||||
fmt.Fprintf(&buf, " %s\n", edge)
|
||||
}
|
||||
return strings.TrimSpace(buf.String())
|
||||
}
|
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
// No testdata on Android.
|
||||
|
||||
// +build !android
|
||||
|
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
// This package provides Rapid Type Analysis (RTA) for Go, a fast
|
||||
// algorithm for call graph construction and discovery of reachable code
|
||||
// (and hence dead code) and runtime types. The algorithm was first
|
||||
|
461
go/callgraph/rta/rta14.go
Normal file
461
go/callgraph/rta/rta14.go
Normal file
@ -0,0 +1,461 @@
|
||||
// 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.
|
||||
|
||||
// +build !go1.5
|
||||
|
||||
// This package provides Rapid Type Analysis (RTA) for Go, a fast
|
||||
// algorithm for call graph construction and discovery of reachable code
|
||||
// (and hence dead code) and runtime types. The algorithm was first
|
||||
// described in:
|
||||
//
|
||||
// David F. Bacon and Peter F. Sweeney. 1996.
|
||||
// Fast static analysis of C++ virtual function calls. (OOPSLA '96)
|
||||
// http://doi.acm.org/10.1145/236337.236371
|
||||
//
|
||||
// The algorithm uses dynamic programming to tabulate the cross-product
|
||||
// of the set of known "address taken" functions with the set of known
|
||||
// dynamic calls of the same type. As each new address-taken function
|
||||
// is discovered, call graph edges are added from each known callsite,
|
||||
// and as each new call site is discovered, call graph edges are added
|
||||
// from it to each known address-taken function.
|
||||
//
|
||||
// A similar approach is used for dynamic calls via interfaces: it
|
||||
// tabulates the cross-product of the set of known "runtime types",
|
||||
// i.e. types that may appear in an interface value, or be derived from
|
||||
// one via reflection, with the set of known "invoke"-mode dynamic
|
||||
// calls. As each new "runtime type" is discovered, call edges are
|
||||
// added from the known call sites, and as each new call site is
|
||||
// discovered, call graph edges are added to each compatible
|
||||
// method.
|
||||
//
|
||||
// In addition, we must consider all exported methods of any runtime type
|
||||
// as reachable, since they may be called via reflection.
|
||||
//
|
||||
// Each time a newly added call edge causes a new function to become
|
||||
// reachable, the code of that function is analyzed for more call sites,
|
||||
// address-taken functions, and runtime types. The process continues
|
||||
// until a fixed point is achieved.
|
||||
//
|
||||
// The resulting call graph is less precise than one produced by pointer
|
||||
// analysis, but the algorithm is much faster. For example, running the
|
||||
// cmd/callgraph tool on its own source takes ~2.1s for RTA and ~5.4s
|
||||
// for points-to analysis.
|
||||
//
|
||||
package rta // import "golang.org/x/tools/go/callgraph/rta"
|
||||
|
||||
// TODO(adonovan): test it by connecting it to the interpreter and
|
||||
// replacing all "unreachable" functions by a special intrinsic, and
|
||||
// ensure that that intrinsic is never called.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/tools/go/callgraph"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/tools/go/types"
|
||||
"golang.org/x/tools/go/types/typeutil"
|
||||
)
|
||||
|
||||
// A Result holds the results of Rapid Type Analysis, which includes the
|
||||
// set of reachable functions/methods, runtime types, and the call graph.
|
||||
//
|
||||
type Result struct {
|
||||
// CallGraph is the discovered callgraph.
|
||||
// It does not include edges for calls made via reflection.
|
||||
CallGraph *callgraph.Graph
|
||||
|
||||
// Reachable contains the set of reachable functions and methods.
|
||||
// This includes exported methods of runtime types, since
|
||||
// they may be accessed via reflection.
|
||||
// The value indicates whether the function is address-taken.
|
||||
//
|
||||
// (We wrap the bool in a struct to avoid inadvertent use of
|
||||
// "if Reachable[f] {" to test for set membership.)
|
||||
Reachable map[*ssa.Function]struct{ AddrTaken bool }
|
||||
|
||||
// RuntimeTypes contains the set of types that are needed at
|
||||
// runtime, for interfaces or reflection.
|
||||
//
|
||||
// The value indicates whether the type is inaccessible to reflection.
|
||||
// Consider:
|
||||
// type A struct{B}
|
||||
// fmt.Println(new(A))
|
||||
// Types *A, A and B are accessible to reflection, but the unnamed
|
||||
// type struct{B} is not.
|
||||
RuntimeTypes typeutil.Map
|
||||
}
|
||||
|
||||
// Working state of the RTA algorithm.
|
||||
type rta struct {
|
||||
result *Result
|
||||
|
||||
prog *ssa.Program
|
||||
|
||||
worklist []*ssa.Function // list of functions to visit
|
||||
|
||||
// addrTakenFuncsBySig contains all address-taken *Functions, grouped by signature.
|
||||
// Keys are *types.Signature, values are map[*ssa.Function]bool sets.
|
||||
addrTakenFuncsBySig typeutil.Map
|
||||
|
||||
// dynCallSites contains all dynamic "call"-mode call sites, grouped by signature.
|
||||
// Keys are *types.Signature, values are unordered []ssa.CallInstruction.
|
||||
dynCallSites typeutil.Map
|
||||
|
||||
// invokeSites contains all "invoke"-mode call sites, grouped by interface.
|
||||
// Keys are *types.Interface (never *types.Named),
|
||||
// Values are unordered []ssa.CallInstruction sets.
|
||||
invokeSites typeutil.Map
|
||||
|
||||
// The following two maps together define the subset of the
|
||||
// m:n "implements" relation needed by the algorithm.
|
||||
|
||||
// concreteTypes maps each concrete type to the set of interfaces that it implements.
|
||||
// Keys are types.Type, values are unordered []*types.Interface.
|
||||
// Only concrete types used as MakeInterface operands are included.
|
||||
concreteTypes typeutil.Map
|
||||
|
||||
// interfaceTypes maps each interface type to
|
||||
// the set of concrete types that implement it.
|
||||
// Keys are *types.Interface, values are unordered []types.Type.
|
||||
// Only interfaces used in "invoke"-mode CallInstructions are included.
|
||||
interfaceTypes typeutil.Map
|
||||
}
|
||||
|
||||
// addReachable marks a function as potentially callable at run-time,
|
||||
// and ensures that it gets processed.
|
||||
func (r *rta) addReachable(f *ssa.Function, addrTaken bool) {
|
||||
reachable := r.result.Reachable
|
||||
n := len(reachable)
|
||||
v := reachable[f]
|
||||
if addrTaken {
|
||||
v.AddrTaken = true
|
||||
}
|
||||
reachable[f] = v
|
||||
if len(reachable) > n {
|
||||
// First time seeing f. Add it to the worklist.
|
||||
r.worklist = append(r.worklist, f)
|
||||
}
|
||||
}
|
||||
|
||||
// addEdge adds the specified call graph edge, and marks it reachable.
|
||||
// addrTaken indicates whether to mark the callee as "address-taken".
|
||||
func (r *rta) addEdge(site ssa.CallInstruction, callee *ssa.Function, addrTaken bool) {
|
||||
r.addReachable(callee, addrTaken)
|
||||
|
||||
if g := r.result.CallGraph; g != nil {
|
||||
if site.Parent() == nil {
|
||||
panic(site)
|
||||
}
|
||||
from := g.CreateNode(site.Parent())
|
||||
to := g.CreateNode(callee)
|
||||
callgraph.AddEdge(from, site, to)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- addrTakenFuncs × dynCallSites ----------
|
||||
|
||||
// visitAddrTakenFunc is called each time we encounter an address-taken function f.
|
||||
func (r *rta) visitAddrTakenFunc(f *ssa.Function) {
|
||||
// Create two-level map (Signature -> Function -> bool).
|
||||
S := f.Signature
|
||||
funcs, _ := r.addrTakenFuncsBySig.At(S).(map[*ssa.Function]bool)
|
||||
if funcs == nil {
|
||||
funcs = make(map[*ssa.Function]bool)
|
||||
r.addrTakenFuncsBySig.Set(S, funcs)
|
||||
}
|
||||
if !funcs[f] {
|
||||
// First time seeing f.
|
||||
funcs[f] = true
|
||||
|
||||
// If we've seen any dyncalls of this type, mark it reachable,
|
||||
// and add call graph edges.
|
||||
sites, _ := r.dynCallSites.At(S).([]ssa.CallInstruction)
|
||||
for _, site := range sites {
|
||||
r.addEdge(site, f, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// visitDynCall is called each time we encounter a dynamic "call"-mode call.
|
||||
func (r *rta) visitDynCall(site ssa.CallInstruction) {
|
||||
S := site.Common().Signature()
|
||||
|
||||
// Record the call site.
|
||||
sites, _ := r.dynCallSites.At(S).([]ssa.CallInstruction)
|
||||
r.dynCallSites.Set(S, append(sites, site))
|
||||
|
||||
// For each function of signature S that we know is address-taken,
|
||||
// mark it reachable. We'll add the callgraph edges later.
|
||||
funcs, _ := r.addrTakenFuncsBySig.At(S).(map[*ssa.Function]bool)
|
||||
for g := range funcs {
|
||||
r.addEdge(site, g, true)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- concrete types × invoke sites ----------
|
||||
|
||||
// addInvokeEdge is called for each new pair (site, C) in the matrix.
|
||||
func (r *rta) addInvokeEdge(site ssa.CallInstruction, C types.Type) {
|
||||
// Ascertain the concrete method of C to be called.
|
||||
imethod := site.Common().Method
|
||||
cmethod := r.prog.MethodValue(r.prog.MethodSets.MethodSet(C).Lookup(imethod.Pkg(), imethod.Name()))
|
||||
r.addEdge(site, cmethod, true)
|
||||
}
|
||||
|
||||
// visitInvoke is called each time the algorithm encounters an "invoke"-mode call.
|
||||
func (r *rta) visitInvoke(site ssa.CallInstruction) {
|
||||
I := site.Common().Value.Type().Underlying().(*types.Interface)
|
||||
|
||||
// Record the invoke site.
|
||||
sites, _ := r.invokeSites.At(I).([]ssa.CallInstruction)
|
||||
r.invokeSites.Set(I, append(sites, site))
|
||||
|
||||
// Add callgraph edge for each existing
|
||||
// address-taken concrete type implementing I.
|
||||
for _, C := range r.implementations(I) {
|
||||
r.addInvokeEdge(site, C)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- main algorithm ----------
|
||||
|
||||
// visitFunc processes function f.
|
||||
func (r *rta) visitFunc(f *ssa.Function) {
|
||||
var space [32]*ssa.Value // preallocate space for common case
|
||||
|
||||
for _, b := range f.Blocks {
|
||||
for _, instr := range b.Instrs {
|
||||
rands := instr.Operands(space[:0])
|
||||
|
||||
switch instr := instr.(type) {
|
||||
case ssa.CallInstruction:
|
||||
call := instr.Common()
|
||||
if call.IsInvoke() {
|
||||
r.visitInvoke(instr)
|
||||
} else if g := call.StaticCallee(); g != nil {
|
||||
r.addEdge(instr, g, false)
|
||||
} else if _, ok := call.Value.(*ssa.Builtin); !ok {
|
||||
r.visitDynCall(instr)
|
||||
}
|
||||
|
||||
// Ignore the call-position operand when
|
||||
// looking for address-taken Functions.
|
||||
// Hack: assume this is rands[0].
|
||||
rands = rands[1:]
|
||||
|
||||
case *ssa.MakeInterface:
|
||||
r.addRuntimeType(instr.X.Type(), false)
|
||||
}
|
||||
|
||||
// Process all address-taken functions.
|
||||
for _, op := range rands {
|
||||
if g, ok := (*op).(*ssa.Function); ok {
|
||||
r.visitAddrTakenFunc(g)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Analyze performs Rapid Type Analysis, starting at the specified root
|
||||
// functions. It returns nil if no roots were specified.
|
||||
//
|
||||
// If buildCallGraph is true, Result.CallGraph will contain a call
|
||||
// graph; otherwise, only the other fields (reachable functions) are
|
||||
// populated.
|
||||
//
|
||||
func Analyze(roots []*ssa.Function, buildCallGraph bool) *Result {
|
||||
if len(roots) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
r := &rta{
|
||||
result: &Result{Reachable: make(map[*ssa.Function]struct{ AddrTaken bool })},
|
||||
prog: roots[0].Prog,
|
||||
}
|
||||
|
||||
if buildCallGraph {
|
||||
// TODO(adonovan): change callgraph API to eliminate the
|
||||
// notion of a distinguished root node. Some callgraphs
|
||||
// have many roots, or none.
|
||||
r.result.CallGraph = callgraph.New(roots[0])
|
||||
}
|
||||
|
||||
hasher := typeutil.MakeHasher()
|
||||
r.result.RuntimeTypes.SetHasher(hasher)
|
||||
r.addrTakenFuncsBySig.SetHasher(hasher)
|
||||
r.dynCallSites.SetHasher(hasher)
|
||||
r.invokeSites.SetHasher(hasher)
|
||||
r.concreteTypes.SetHasher(hasher)
|
||||
r.interfaceTypes.SetHasher(hasher)
|
||||
|
||||
// Visit functions, processing their instructions, and adding
|
||||
// new functions to the worklist, until a fixed point is
|
||||
// reached.
|
||||
var shadow []*ssa.Function // for efficiency, we double-buffer the worklist
|
||||
r.worklist = append(r.worklist, roots...)
|
||||
for len(r.worklist) > 0 {
|
||||
shadow, r.worklist = r.worklist, shadow[:0]
|
||||
for _, f := range shadow {
|
||||
r.visitFunc(f)
|
||||
}
|
||||
}
|
||||
return r.result
|
||||
}
|
||||
|
||||
// interfaces(C) returns all currently known interfaces implemented by C.
|
||||
func (r *rta) interfaces(C types.Type) []*types.Interface {
|
||||
// Ascertain set of interfaces C implements
|
||||
// and update 'implements' relation.
|
||||
var ifaces []*types.Interface
|
||||
r.interfaceTypes.Iterate(func(I types.Type, concs interface{}) {
|
||||
if I := I.(*types.Interface); types.Implements(C, I) {
|
||||
concs, _ := concs.([]types.Type)
|
||||
r.interfaceTypes.Set(I, append(concs, C))
|
||||
ifaces = append(ifaces, I)
|
||||
}
|
||||
})
|
||||
r.concreteTypes.Set(C, ifaces)
|
||||
return ifaces
|
||||
}
|
||||
|
||||
// implementations(I) returns all currently known concrete types that implement I.
|
||||
func (r *rta) implementations(I *types.Interface) []types.Type {
|
||||
var concs []types.Type
|
||||
if v := r.interfaceTypes.At(I); v != nil {
|
||||
concs = v.([]types.Type)
|
||||
} else {
|
||||
// First time seeing this interface.
|
||||
// Update the 'implements' relation.
|
||||
r.concreteTypes.Iterate(func(C types.Type, ifaces interface{}) {
|
||||
if types.Implements(C, I) {
|
||||
ifaces, _ := ifaces.([]*types.Interface)
|
||||
r.concreteTypes.Set(C, append(ifaces, I))
|
||||
concs = append(concs, C)
|
||||
}
|
||||
})
|
||||
r.interfaceTypes.Set(I, concs)
|
||||
}
|
||||
return concs
|
||||
}
|
||||
|
||||
// addRuntimeType is called for each concrete type that can be the
|
||||
// dynamic type of some interface or reflect.Value.
|
||||
// Adapted from needMethods in go/ssa/builder.go
|
||||
//
|
||||
func (r *rta) addRuntimeType(T types.Type, skip bool) {
|
||||
if prev, ok := r.result.RuntimeTypes.At(T).(bool); ok {
|
||||
if skip && !prev {
|
||||
r.result.RuntimeTypes.Set(T, skip)
|
||||
}
|
||||
return
|
||||
}
|
||||
r.result.RuntimeTypes.Set(T, skip)
|
||||
|
||||
mset := r.prog.MethodSets.MethodSet(T)
|
||||
|
||||
if _, ok := T.Underlying().(*types.Interface); !ok {
|
||||
// T is a new concrete type.
|
||||
for i, n := 0, mset.Len(); i < n; i++ {
|
||||
sel := mset.At(i)
|
||||
m := sel.Obj()
|
||||
|
||||
if m.Exported() {
|
||||
// Exported methods are always potentially callable via reflection.
|
||||
r.addReachable(r.prog.MethodValue(sel), true)
|
||||
}
|
||||
}
|
||||
|
||||
// Add callgraph edge for each existing dynamic
|
||||
// "invoke"-mode call via that interface.
|
||||
for _, I := range r.interfaces(T) {
|
||||
sites, _ := r.invokeSites.At(I).([]ssa.CallInstruction)
|
||||
for _, site := range sites {
|
||||
r.addInvokeEdge(site, T)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Precondition: T is not a method signature (*Signature with Recv()!=nil).
|
||||
// Recursive case: skip => don't call makeMethods(T).
|
||||
// Each package maintains its own set of types it has visited.
|
||||
|
||||
var n *types.Named
|
||||
switch T := T.(type) {
|
||||
case *types.Named:
|
||||
n = T
|
||||
case *types.Pointer:
|
||||
n, _ = T.Elem().(*types.Named)
|
||||
}
|
||||
if n != nil {
|
||||
owner := n.Obj().Pkg()
|
||||
if owner == nil {
|
||||
return // built-in error type
|
||||
}
|
||||
}
|
||||
|
||||
// Recursion over signatures of each exported method.
|
||||
for i := 0; i < mset.Len(); i++ {
|
||||
if mset.At(i).Obj().Exported() {
|
||||
sig := mset.At(i).Type().(*types.Signature)
|
||||
r.addRuntimeType(sig.Params(), true) // skip the Tuple itself
|
||||
r.addRuntimeType(sig.Results(), true) // skip the Tuple itself
|
||||
}
|
||||
}
|
||||
|
||||
switch t := T.(type) {
|
||||
case *types.Basic:
|
||||
// nop
|
||||
|
||||
case *types.Interface:
|
||||
// nop---handled by recursion over method set.
|
||||
|
||||
case *types.Pointer:
|
||||
r.addRuntimeType(t.Elem(), false)
|
||||
|
||||
case *types.Slice:
|
||||
r.addRuntimeType(t.Elem(), false)
|
||||
|
||||
case *types.Chan:
|
||||
r.addRuntimeType(t.Elem(), false)
|
||||
|
||||
case *types.Map:
|
||||
r.addRuntimeType(t.Key(), false)
|
||||
r.addRuntimeType(t.Elem(), false)
|
||||
|
||||
case *types.Signature:
|
||||
if t.Recv() != nil {
|
||||
panic(fmt.Sprintf("Signature %s has Recv %s", t, t.Recv()))
|
||||
}
|
||||
r.addRuntimeType(t.Params(), true) // skip the Tuple itself
|
||||
r.addRuntimeType(t.Results(), true) // skip the Tuple itself
|
||||
|
||||
case *types.Named:
|
||||
// A pointer-to-named type can be derived from a named
|
||||
// type via reflection. It may have methods too.
|
||||
r.addRuntimeType(types.NewPointer(T), false)
|
||||
|
||||
// Consider 'type T struct{S}' where S has methods.
|
||||
// Reflection provides no way to get from T to struct{S},
|
||||
// only to S, so the method set of struct{S} is unwanted,
|
||||
// so set 'skip' flag during recursion.
|
||||
r.addRuntimeType(t.Underlying(), true)
|
||||
|
||||
case *types.Array:
|
||||
r.addRuntimeType(t.Elem(), false)
|
||||
|
||||
case *types.Struct:
|
||||
for i, n := 0, t.NumFields(); i < n; i++ {
|
||||
r.addRuntimeType(t.Field(i).Type(), false)
|
||||
}
|
||||
|
||||
case *types.Tuple:
|
||||
for i, n := 0, t.Len(); i < n; i++ {
|
||||
r.addRuntimeType(t.At(i).Type(), false)
|
||||
}
|
||||
|
||||
default:
|
||||
panic(T)
|
||||
}
|
||||
}
|
141
go/callgraph/rta/rta14_test.go
Normal file
141
go/callgraph/rta/rta14_test.go
Normal file
@ -0,0 +1,141 @@
|
||||
// Copyright 2014 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.
|
||||
|
||||
// +build !go1.5
|
||||
|
||||
// No testdata on Android.
|
||||
|
||||
// +build !android
|
||||
|
||||
package rta_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"io/ioutil"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/go/callgraph"
|
||||
"golang.org/x/tools/go/callgraph/rta"
|
||||
"golang.org/x/tools/go/loader"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/tools/go/ssa/ssautil"
|
||||
"golang.org/x/tools/go/types"
|
||||
)
|
||||
|
||||
var inputs = []string{
|
||||
"testdata/func.go",
|
||||
"testdata/rtype.go",
|
||||
"testdata/iface.go",
|
||||
}
|
||||
|
||||
func expectation(f *ast.File) (string, token.Pos) {
|
||||
for _, c := range f.Comments {
|
||||
text := strings.TrimSpace(c.Text())
|
||||
if t := strings.TrimPrefix(text, "WANT:\n"); t != text {
|
||||
return t, c.Pos()
|
||||
}
|
||||
}
|
||||
return "", token.NoPos
|
||||
}
|
||||
|
||||
// TestRTA runs RTA on each file in inputs, prints the results, and
|
||||
// compares it with the golden results embedded in the WANT comment at
|
||||
// the end of the file.
|
||||
//
|
||||
// The results string consists of two parts: the set of dynamic call
|
||||
// edges, "f --> g", one per line, and the set of reachable functions,
|
||||
// one per line. Each set is sorted.
|
||||
//
|
||||
func TestRTA(t *testing.T) {
|
||||
for _, filename := range inputs {
|
||||
content, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
t.Errorf("couldn't read file '%s': %s", filename, err)
|
||||
continue
|
||||
}
|
||||
|
||||
conf := loader.Config{
|
||||
ParserMode: parser.ParseComments,
|
||||
}
|
||||
f, err := conf.ParseFile(filename, content)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
want, pos := expectation(f)
|
||||
if pos == token.NoPos {
|
||||
t.Errorf("No WANT: comment in %s", filename)
|
||||
continue
|
||||
}
|
||||
|
||||
conf.CreateFromFiles("main", f)
|
||||
iprog, err := conf.Load()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
prog := ssautil.CreateProgram(iprog, 0)
|
||||
mainPkg := prog.Package(iprog.Created[0].Pkg)
|
||||
prog.Build()
|
||||
|
||||
res := rta.Analyze([]*ssa.Function{
|
||||
mainPkg.Func("main"),
|
||||
mainPkg.Func("init"),
|
||||
}, true)
|
||||
|
||||
if got := printResult(res, mainPkg.Pkg); got != want {
|
||||
t.Errorf("%s: got:\n%s\nwant:\n%s",
|
||||
prog.Fset.Position(pos), got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func printResult(res *rta.Result, from *types.Package) string {
|
||||
var buf bytes.Buffer
|
||||
|
||||
writeSorted := func(ss []string) {
|
||||
sort.Strings(ss)
|
||||
for _, s := range ss {
|
||||
fmt.Fprintf(&buf, " %s\n", s)
|
||||
}
|
||||
}
|
||||
|
||||
buf.WriteString("Dynamic calls\n")
|
||||
var edges []string
|
||||
callgraph.GraphVisitEdges(res.CallGraph, func(e *callgraph.Edge) error {
|
||||
if strings.Contains(e.Description(), "dynamic") {
|
||||
edges = append(edges, fmt.Sprintf("%s --> %s",
|
||||
e.Caller.Func.RelString(from),
|
||||
e.Callee.Func.RelString(from)))
|
||||
}
|
||||
return nil
|
||||
})
|
||||
writeSorted(edges)
|
||||
|
||||
buf.WriteString("Reachable functions\n")
|
||||
var reachable []string
|
||||
for f := range res.Reachable {
|
||||
reachable = append(reachable, f.RelString(from))
|
||||
}
|
||||
writeSorted(reachable)
|
||||
|
||||
buf.WriteString("Reflect types\n")
|
||||
var rtypes []string
|
||||
res.RuntimeTypes.Iterate(func(key types.Type, value interface{}) {
|
||||
if value == false { // accessible to reflection
|
||||
rtypes = append(rtypes, types.TypeString(key, types.RelativeTo(from)))
|
||||
}
|
||||
})
|
||||
writeSorted(rtypes)
|
||||
|
||||
return strings.TrimSpace(buf.String())
|
||||
}
|
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
// No testdata on Android.
|
||||
|
||||
// +build !android
|
||||
|
@ -1,3 +1,9 @@
|
||||
// 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.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
package loader
|
||||
|
||||
// This file handles cgo preprocessing of files containing `import "C"`.
|
||||
|
205
go/loader/cgo14.go
Normal file
205
go/loader/cgo14.go
Normal file
@ -0,0 +1,205 @@
|
||||
// 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.
|
||||
|
||||
// +build !go1.5
|
||||
|
||||
package loader
|
||||
|
||||
// This file handles cgo preprocessing of files containing `import "C"`.
|
||||
//
|
||||
// DESIGN
|
||||
//
|
||||
// The approach taken is to run the cgo processor on the package's
|
||||
// CgoFiles and parse the output, faking the filenames of the
|
||||
// resulting ASTs so that the synthetic file containing the C types is
|
||||
// called "C" (e.g. "~/go/src/net/C") and the preprocessed files
|
||||
// have their original names (e.g. "~/go/src/net/cgo_unix.go"),
|
||||
// not the names of the actual temporary files.
|
||||
//
|
||||
// The advantage of this approach is its fidelity to 'go build'. The
|
||||
// downside is that the token.Position.Offset for each AST node is
|
||||
// incorrect, being an offset within the temporary file. Line numbers
|
||||
// should still be correct because of the //line comments.
|
||||
//
|
||||
// The logic of this file is mostly plundered from the 'go build'
|
||||
// tool, which also invokes the cgo preprocessor.
|
||||
//
|
||||
//
|
||||
// REJECTED ALTERNATIVE
|
||||
//
|
||||
// An alternative approach that we explored is to extend go/types'
|
||||
// Importer mechanism to provide the identity of the importing package
|
||||
// so that each time `import "C"` appears it resolves to a different
|
||||
// synthetic package containing just the objects needed in that case.
|
||||
// The loader would invoke cgo but parse only the cgo_types.go file
|
||||
// defining the package-level objects, discarding the other files
|
||||
// resulting from preprocessing.
|
||||
//
|
||||
// The benefit of this approach would have been that source-level
|
||||
// syntax information would correspond exactly to the original cgo
|
||||
// file, with no preprocessing involved, making source tools like
|
||||
// godoc, oracle, and eg happy. However, the approach was rejected
|
||||
// due to the additional complexity it would impose on go/types. (It
|
||||
// made for a beautiful demo, though.)
|
||||
//
|
||||
// cgo files, despite their *.go extension, are not legal Go source
|
||||
// files per the specification since they may refer to unexported
|
||||
// members of package "C" such as C.int. Also, a function such as
|
||||
// C.getpwent has in effect two types, one matching its C type and one
|
||||
// which additionally returns (errno C.int). The cgo preprocessor
|
||||
// uses name mangling to distinguish these two functions in the
|
||||
// processed code, but go/types would need to duplicate this logic in
|
||||
// its handling of function calls, analogous to the treatment of map
|
||||
// lookups in which y=m[k] and y,ok=m[k] are both legal.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// processCgoFiles invokes the cgo preprocessor on bp.CgoFiles, parses
|
||||
// the output and returns the resulting ASTs.
|
||||
//
|
||||
func processCgoFiles(bp *build.Package, fset *token.FileSet, DisplayPath func(path string) string, mode parser.Mode) ([]*ast.File, error) {
|
||||
tmpdir, err := ioutil.TempDir("", strings.Replace(bp.ImportPath, "/", "_", -1)+"_C")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
pkgdir := bp.Dir
|
||||
if DisplayPath != nil {
|
||||
pkgdir = DisplayPath(pkgdir)
|
||||
}
|
||||
|
||||
cgoFiles, cgoDisplayFiles, err := runCgo(bp, pkgdir, tmpdir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var files []*ast.File
|
||||
for i := range cgoFiles {
|
||||
rd, err := os.Open(cgoFiles[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
display := filepath.Join(bp.Dir, cgoDisplayFiles[i])
|
||||
f, err := parser.ParseFile(fset, display, rd, mode)
|
||||
rd.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
files = append(files, f)
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
|
||||
var cgoRe = regexp.MustCompile(`[/\\:]`)
|
||||
|
||||
// runCgo invokes the cgo preprocessor on bp.CgoFiles and returns two
|
||||
// lists of files: the resulting processed files (in temporary
|
||||
// directory tmpdir) and the corresponding names of the unprocessed files.
|
||||
//
|
||||
// runCgo is adapted from (*builder).cgo in
|
||||
// $GOROOT/src/cmd/go/build.go, but these features are unsupported:
|
||||
// pkg-config, Objective C, CGOPKGPATH, CGO_FLAGS.
|
||||
//
|
||||
func runCgo(bp *build.Package, pkgdir, tmpdir string) (files, displayFiles []string, err error) {
|
||||
cgoCPPFLAGS, _, _, _ := cflags(bp, true)
|
||||
_, cgoexeCFLAGS, _, _ := cflags(bp, false)
|
||||
|
||||
if len(bp.CgoPkgConfig) > 0 {
|
||||
return nil, nil, fmt.Errorf("cgo pkg-config not supported")
|
||||
}
|
||||
|
||||
// Allows including _cgo_export.h from .[ch] files in the package.
|
||||
cgoCPPFLAGS = append(cgoCPPFLAGS, "-I", tmpdir)
|
||||
|
||||
// _cgo_gotypes.go (displayed "C") contains the type definitions.
|
||||
files = append(files, filepath.Join(tmpdir, "_cgo_gotypes.go"))
|
||||
displayFiles = append(displayFiles, "C")
|
||||
for _, fn := range bp.CgoFiles {
|
||||
// "foo.cgo1.go" (displayed "foo.go") is the processed Go source.
|
||||
f := cgoRe.ReplaceAllString(fn[:len(fn)-len("go")], "_")
|
||||
files = append(files, filepath.Join(tmpdir, f+"cgo1.go"))
|
||||
displayFiles = append(displayFiles, fn)
|
||||
}
|
||||
|
||||
var cgoflags []string
|
||||
if bp.Goroot && bp.ImportPath == "runtime/cgo" {
|
||||
cgoflags = append(cgoflags, "-import_runtime_cgo=false")
|
||||
}
|
||||
if bp.Goroot && bp.ImportPath == "runtime/race" || bp.ImportPath == "runtime/cgo" {
|
||||
cgoflags = append(cgoflags, "-import_syscall=false")
|
||||
}
|
||||
|
||||
args := stringList(
|
||||
"go", "tool", "cgo", "-objdir", tmpdir, cgoflags, "--",
|
||||
cgoCPPFLAGS, cgoexeCFLAGS, bp.CgoFiles,
|
||||
)
|
||||
if false {
|
||||
log.Printf("Running cgo for package %q: %s (dir=%s)", bp.ImportPath, args, pkgdir)
|
||||
}
|
||||
cmd := exec.Command(args[0], args[1:]...)
|
||||
cmd.Dir = pkgdir
|
||||
cmd.Stdout = os.Stderr
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
return nil, nil, fmt.Errorf("cgo failed: %s: %s", args, err)
|
||||
}
|
||||
|
||||
return files, displayFiles, nil
|
||||
}
|
||||
|
||||
// -- unmodified from 'go build' ---------------------------------------
|
||||
|
||||
// Return the flags to use when invoking the C or C++ compilers, or cgo.
|
||||
func cflags(p *build.Package, def bool) (cppflags, cflags, cxxflags, ldflags []string) {
|
||||
var defaults string
|
||||
if def {
|
||||
defaults = "-g -O2"
|
||||
}
|
||||
|
||||
cppflags = stringList(envList("CGO_CPPFLAGS", ""), p.CgoCPPFLAGS)
|
||||
cflags = stringList(envList("CGO_CFLAGS", defaults), p.CgoCFLAGS)
|
||||
cxxflags = stringList(envList("CGO_CXXFLAGS", defaults), p.CgoCXXFLAGS)
|
||||
ldflags = stringList(envList("CGO_LDFLAGS", defaults), p.CgoLDFLAGS)
|
||||
return
|
||||
}
|
||||
|
||||
// envList returns the value of the given environment variable broken
|
||||
// into fields, using the default value when the variable is empty.
|
||||
func envList(key, def string) []string {
|
||||
v := os.Getenv(key)
|
||||
if v == "" {
|
||||
v = def
|
||||
}
|
||||
return strings.Fields(v)
|
||||
}
|
||||
|
||||
// stringList's arguments should be a sequence of string or []string values.
|
||||
// stringList flattens them into a single []string.
|
||||
func stringList(args ...interface{}) []string {
|
||||
var x []string
|
||||
for _, arg := range args {
|
||||
switch arg := arg.(type) {
|
||||
case []string:
|
||||
x = append(x, arg...)
|
||||
case string:
|
||||
x = append(x, arg)
|
||||
default:
|
||||
panic("stringList: invalid argument")
|
||||
}
|
||||
}
|
||||
return x
|
||||
}
|
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
package loader
|
||||
|
||||
// See doc.go for package documentation and implementation notes.
|
||||
|
1018
go/loader/loader14.go
Normal file
1018
go/loader/loader14.go
Normal file
File diff suppressed because it is too large
Load Diff
676
go/loader/loader14_test.go
Normal file
676
go/loader/loader14_test.go
Normal file
@ -0,0 +1,676 @@
|
||||
// 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.
|
||||
|
||||
// +build !go1.5
|
||||
|
||||
// No testdata on Android.
|
||||
|
||||
// +build !android
|
||||
|
||||
package loader_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/build"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/go/buildutil"
|
||||
"golang.org/x/tools/go/loader"
|
||||
)
|
||||
|
||||
// TestFromArgs checks that conf.FromArgs populates conf correctly.
|
||||
// It does no I/O.
|
||||
func TestFromArgs(t *testing.T) {
|
||||
type result struct {
|
||||
Err string
|
||||
Rest []string
|
||||
ImportPkgs map[string]bool
|
||||
CreatePkgs []loader.PkgSpec
|
||||
}
|
||||
for _, test := range []struct {
|
||||
args []string
|
||||
tests bool
|
||||
want result
|
||||
}{
|
||||
// Mix of existing and non-existent packages.
|
||||
{
|
||||
args: []string{"nosuchpkg", "errors"},
|
||||
want: result{
|
||||
ImportPkgs: map[string]bool{"errors": false, "nosuchpkg": false},
|
||||
},
|
||||
},
|
||||
// Same, with -test flag.
|
||||
{
|
||||
args: []string{"nosuchpkg", "errors"},
|
||||
tests: true,
|
||||
want: result{
|
||||
ImportPkgs: map[string]bool{"errors": true, "nosuchpkg": true},
|
||||
},
|
||||
},
|
||||
// Surplus arguments.
|
||||
{
|
||||
args: []string{"fmt", "errors", "--", "surplus"},
|
||||
want: result{
|
||||
Rest: []string{"surplus"},
|
||||
ImportPkgs: map[string]bool{"errors": false, "fmt": false},
|
||||
},
|
||||
},
|
||||
// Ad hoc package specified as *.go files.
|
||||
{
|
||||
args: []string{"foo.go", "bar.go"},
|
||||
want: result{CreatePkgs: []loader.PkgSpec{{
|
||||
Filenames: []string{"foo.go", "bar.go"},
|
||||
}}},
|
||||
},
|
||||
// Mixture of *.go and import paths.
|
||||
{
|
||||
args: []string{"foo.go", "fmt"},
|
||||
want: result{
|
||||
Err: "named files must be .go files: fmt",
|
||||
},
|
||||
},
|
||||
} {
|
||||
var conf loader.Config
|
||||
rest, err := conf.FromArgs(test.args, test.tests)
|
||||
got := result{
|
||||
Rest: rest,
|
||||
ImportPkgs: conf.ImportPkgs,
|
||||
CreatePkgs: conf.CreatePkgs,
|
||||
}
|
||||
if err != nil {
|
||||
got.Err = err.Error()
|
||||
}
|
||||
if !reflect.DeepEqual(got, test.want) {
|
||||
t.Errorf("FromArgs(%q) = %+v, want %+v", test.args, got, test.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoad_NoInitialPackages(t *testing.T) {
|
||||
var conf loader.Config
|
||||
|
||||
const wantErr = "no initial packages were loaded"
|
||||
|
||||
prog, err := conf.Load()
|
||||
if err == nil {
|
||||
t.Errorf("Load succeeded unexpectedly, want %q", wantErr)
|
||||
} else if err.Error() != wantErr {
|
||||
t.Errorf("Load failed with wrong error %q, want %q", err, wantErr)
|
||||
}
|
||||
if prog != nil {
|
||||
t.Errorf("Load unexpectedly returned a Program")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoad_MissingInitialPackage(t *testing.T) {
|
||||
var conf loader.Config
|
||||
conf.Import("nosuchpkg")
|
||||
conf.Import("errors")
|
||||
|
||||
const wantErr = "couldn't load packages due to errors: nosuchpkg"
|
||||
|
||||
prog, err := conf.Load()
|
||||
if err == nil {
|
||||
t.Errorf("Load succeeded unexpectedly, want %q", wantErr)
|
||||
} else if err.Error() != wantErr {
|
||||
t.Errorf("Load failed with wrong error %q, want %q", err, wantErr)
|
||||
}
|
||||
if prog != nil {
|
||||
t.Errorf("Load unexpectedly returned a Program")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoad_MissingInitialPackage_AllowErrors(t *testing.T) {
|
||||
var conf loader.Config
|
||||
conf.AllowErrors = true
|
||||
conf.Import("nosuchpkg")
|
||||
conf.ImportWithTests("errors")
|
||||
|
||||
prog, err := conf.Load()
|
||||
if err != nil {
|
||||
t.Errorf("Load failed unexpectedly: %v", err)
|
||||
}
|
||||
if prog == nil {
|
||||
t.Fatalf("Load returned a nil Program")
|
||||
}
|
||||
if got, want := created(prog), "errors_test"; got != want {
|
||||
t.Errorf("Created = %s, want %s", got, want)
|
||||
}
|
||||
if got, want := imported(prog), "errors"; got != want {
|
||||
t.Errorf("Imported = %s, want %s", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateUnnamedPackage(t *testing.T) {
|
||||
var conf loader.Config
|
||||
conf.CreateFromFilenames("")
|
||||
prog, err := conf.Load()
|
||||
if err != nil {
|
||||
t.Fatalf("Load failed: %v", err)
|
||||
}
|
||||
if got, want := fmt.Sprint(prog.InitialPackages()), "[(unnamed)]"; got != want {
|
||||
t.Errorf("InitialPackages = %s, want %s", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoad_MissingFileInCreatedPackage(t *testing.T) {
|
||||
var conf loader.Config
|
||||
conf.CreateFromFilenames("", "missing.go")
|
||||
|
||||
const wantErr = "couldn't load packages due to errors: (unnamed)"
|
||||
|
||||
prog, err := conf.Load()
|
||||
if prog != nil {
|
||||
t.Errorf("Load unexpectedly returned a Program")
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatalf("Load succeeded unexpectedly, want %q", wantErr)
|
||||
}
|
||||
if err.Error() != wantErr {
|
||||
t.Fatalf("Load failed with wrong error %q, want %q", err, wantErr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoad_MissingFileInCreatedPackage_AllowErrors(t *testing.T) {
|
||||
conf := loader.Config{AllowErrors: true}
|
||||
conf.CreateFromFilenames("", "missing.go")
|
||||
|
||||
prog, err := conf.Load()
|
||||
if err != nil {
|
||||
t.Errorf("Load failed: %v", err)
|
||||
}
|
||||
if got, want := fmt.Sprint(prog.InitialPackages()), "[(unnamed)]"; got != want {
|
||||
t.Fatalf("InitialPackages = %s, want %s", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoad_ParseError(t *testing.T) {
|
||||
var conf loader.Config
|
||||
conf.CreateFromFilenames("badpkg", "testdata/badpkgdecl.go")
|
||||
|
||||
const wantErr = "couldn't load packages due to errors: badpkg"
|
||||
|
||||
prog, err := conf.Load()
|
||||
if prog != nil {
|
||||
t.Errorf("Load unexpectedly returned a Program")
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatalf("Load succeeded unexpectedly, want %q", wantErr)
|
||||
}
|
||||
if err.Error() != wantErr {
|
||||
t.Fatalf("Load failed with wrong error %q, want %q", err, wantErr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoad_ParseError_AllowErrors(t *testing.T) {
|
||||
var conf loader.Config
|
||||
conf.AllowErrors = true
|
||||
conf.CreateFromFilenames("badpkg", "testdata/badpkgdecl.go")
|
||||
|
||||
prog, err := conf.Load()
|
||||
if err != nil {
|
||||
t.Errorf("Load failed unexpectedly: %v", err)
|
||||
}
|
||||
if prog == nil {
|
||||
t.Fatalf("Load returned a nil Program")
|
||||
}
|
||||
if got, want := created(prog), "badpkg"; got != want {
|
||||
t.Errorf("Created = %s, want %s", got, want)
|
||||
}
|
||||
|
||||
badpkg := prog.Created[0]
|
||||
if len(badpkg.Files) != 1 {
|
||||
t.Errorf("badpkg has %d files, want 1", len(badpkg.Files))
|
||||
}
|
||||
wantErr := filepath.Join("testdata", "badpkgdecl.go") + ":1:34: expected 'package', found 'EOF'"
|
||||
if !hasError(badpkg.Errors, wantErr) {
|
||||
t.Errorf("badpkg.Errors = %v, want %s", badpkg.Errors, wantErr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoad_FromSource_Success(t *testing.T) {
|
||||
var conf loader.Config
|
||||
conf.CreateFromFilenames("P", "testdata/a.go", "testdata/b.go")
|
||||
|
||||
prog, err := conf.Load()
|
||||
if err != nil {
|
||||
t.Errorf("Load failed unexpectedly: %v", err)
|
||||
}
|
||||
if prog == nil {
|
||||
t.Fatalf("Load returned a nil Program")
|
||||
}
|
||||
if got, want := created(prog), "P"; got != want {
|
||||
t.Errorf("Created = %s, want %s", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoad_FromImports_Success(t *testing.T) {
|
||||
var conf loader.Config
|
||||
conf.ImportWithTests("fmt")
|
||||
conf.ImportWithTests("errors")
|
||||
|
||||
prog, err := conf.Load()
|
||||
if err != nil {
|
||||
t.Errorf("Load failed unexpectedly: %v", err)
|
||||
}
|
||||
if prog == nil {
|
||||
t.Fatalf("Load returned a nil Program")
|
||||
}
|
||||
if got, want := created(prog), "errors_test fmt_test"; got != want {
|
||||
t.Errorf("Created = %q, want %s", got, want)
|
||||
}
|
||||
if got, want := imported(prog), "errors fmt"; got != want {
|
||||
t.Errorf("Imported = %s, want %s", got, want)
|
||||
}
|
||||
// Check set of transitive packages.
|
||||
// There are >30 and the set may grow over time, so only check a few.
|
||||
want := map[string]bool{
|
||||
"strings": true,
|
||||
"time": true,
|
||||
"runtime": true,
|
||||
"testing": true,
|
||||
"unicode": true,
|
||||
}
|
||||
for _, path := range all(prog) {
|
||||
delete(want, path)
|
||||
}
|
||||
if len(want) > 0 {
|
||||
t.Errorf("AllPackages is missing these keys: %q", keys(want))
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoad_MissingIndirectImport(t *testing.T) {
|
||||
pkgs := map[string]string{
|
||||
"a": `package a; import _ "b"`,
|
||||
"b": `package b; import _ "c"`,
|
||||
}
|
||||
conf := loader.Config{Build: fakeContext(pkgs)}
|
||||
conf.Import("a")
|
||||
|
||||
const wantErr = "couldn't load packages due to errors: b"
|
||||
|
||||
prog, err := conf.Load()
|
||||
if err == nil {
|
||||
t.Errorf("Load succeeded unexpectedly, want %q", wantErr)
|
||||
} else if err.Error() != wantErr {
|
||||
t.Errorf("Load failed with wrong error %q, want %q", err, wantErr)
|
||||
}
|
||||
if prog != nil {
|
||||
t.Errorf("Load unexpectedly returned a Program")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoad_BadDependency_AllowErrors(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
descr string
|
||||
pkgs map[string]string
|
||||
wantPkgs string
|
||||
}{
|
||||
|
||||
{
|
||||
descr: "missing dependency",
|
||||
pkgs: map[string]string{
|
||||
"a": `package a; import _ "b"`,
|
||||
"b": `package b; import _ "c"`,
|
||||
},
|
||||
wantPkgs: "a b",
|
||||
},
|
||||
{
|
||||
descr: "bad package decl in dependency",
|
||||
pkgs: map[string]string{
|
||||
"a": `package a; import _ "b"`,
|
||||
"b": `package b; import _ "c"`,
|
||||
"c": `package`,
|
||||
},
|
||||
wantPkgs: "a b",
|
||||
},
|
||||
{
|
||||
descr: "parse error in dependency",
|
||||
pkgs: map[string]string{
|
||||
"a": `package a; import _ "b"`,
|
||||
"b": `package b; import _ "c"`,
|
||||
"c": `package c; var x = `,
|
||||
},
|
||||
wantPkgs: "a b c",
|
||||
},
|
||||
} {
|
||||
conf := loader.Config{
|
||||
AllowErrors: true,
|
||||
Build: fakeContext(test.pkgs),
|
||||
}
|
||||
conf.Import("a")
|
||||
|
||||
prog, err := conf.Load()
|
||||
if err != nil {
|
||||
t.Errorf("%s: Load failed unexpectedly: %v", test.descr, err)
|
||||
}
|
||||
if prog == nil {
|
||||
t.Fatalf("%s: Load returned a nil Program", test.descr)
|
||||
}
|
||||
|
||||
if got, want := imported(prog), "a"; got != want {
|
||||
t.Errorf("%s: Imported = %s, want %s", test.descr, got, want)
|
||||
}
|
||||
if got := all(prog); strings.Join(got, " ") != test.wantPkgs {
|
||||
t.Errorf("%s: AllPackages = %s, want %s", test.descr, got, test.wantPkgs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCwd(t *testing.T) {
|
||||
ctxt := fakeContext(map[string]string{"one/two/three": `package three`})
|
||||
for _, test := range []struct {
|
||||
cwd, arg, want string
|
||||
}{
|
||||
{cwd: "/go/src/one", arg: "./two/three", want: "one/two/three"},
|
||||
{cwd: "/go/src/one", arg: "../one/two/three", want: "one/two/three"},
|
||||
{cwd: "/go/src/one", arg: "one/two/three", want: "one/two/three"},
|
||||
{cwd: "/go/src/one/two/three", arg: ".", want: "one/two/three"},
|
||||
{cwd: "/go/src/one", arg: "two/three", want: ""},
|
||||
} {
|
||||
conf := loader.Config{
|
||||
Cwd: test.cwd,
|
||||
Build: ctxt,
|
||||
}
|
||||
conf.Import(test.arg)
|
||||
|
||||
var got string
|
||||
prog, err := conf.Load()
|
||||
if prog != nil {
|
||||
got = imported(prog)
|
||||
}
|
||||
if got != test.want {
|
||||
t.Errorf("Load(%s) from %s: Imported = %s, want %s",
|
||||
test.arg, test.cwd, got, test.want)
|
||||
if err != nil {
|
||||
t.Errorf("Load failed: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(adonovan): more Load tests:
|
||||
//
|
||||
// failures:
|
||||
// - to parse package decl of *_test.go files
|
||||
// - to parse package decl of external *_test.go files
|
||||
// - to parse whole of *_test.go files
|
||||
// - to parse whole of external *_test.go files
|
||||
// - to open a *.go file during import scanning
|
||||
// - to import from binary
|
||||
|
||||
// features:
|
||||
// - InitialPackages
|
||||
// - PackageCreated hook
|
||||
// - TypeCheckFuncBodies hook
|
||||
|
||||
func TestTransitivelyErrorFreeFlag(t *testing.T) {
|
||||
// Create an minimal custom build.Context
|
||||
// that fakes the following packages:
|
||||
//
|
||||
// a --> b --> c! c has an error
|
||||
// \ d and e are transitively error-free.
|
||||
// e --> d
|
||||
//
|
||||
// Each package [a-e] consists of one file, x.go.
|
||||
pkgs := map[string]string{
|
||||
"a": `package a; import (_ "b"; _ "e")`,
|
||||
"b": `package b; import _ "c"`,
|
||||
"c": `package c; func f() { _ = int(false) }`, // type error within function body
|
||||
"d": `package d;`,
|
||||
"e": `package e; import _ "d"`,
|
||||
}
|
||||
conf := loader.Config{
|
||||
AllowErrors: true,
|
||||
Build: fakeContext(pkgs),
|
||||
}
|
||||
conf.Import("a")
|
||||
|
||||
prog, err := conf.Load()
|
||||
if err != nil {
|
||||
t.Errorf("Load failed: %s", err)
|
||||
}
|
||||
if prog == nil {
|
||||
t.Fatalf("Load returned nil *Program")
|
||||
}
|
||||
|
||||
for pkg, info := range prog.AllPackages {
|
||||
var wantErr, wantTEF bool
|
||||
switch pkg.Path() {
|
||||
case "a", "b":
|
||||
case "c":
|
||||
wantErr = true
|
||||
case "d", "e":
|
||||
wantTEF = true
|
||||
default:
|
||||
t.Errorf("unexpected package: %q", pkg.Path())
|
||||
continue
|
||||
}
|
||||
|
||||
if (info.Errors != nil) != wantErr {
|
||||
if wantErr {
|
||||
t.Errorf("Package %q.Error = nil, want error", pkg.Path())
|
||||
} else {
|
||||
t.Errorf("Package %q has unexpected Errors: %v",
|
||||
pkg.Path(), info.Errors)
|
||||
}
|
||||
}
|
||||
|
||||
if info.TransitivelyErrorFree != wantTEF {
|
||||
t.Errorf("Package %q.TransitivelyErrorFree=%t, want %t",
|
||||
pkg.Path(), info.TransitivelyErrorFree, wantTEF)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test that syntax (scan/parse), type, and loader errors are recorded
|
||||
// (in PackageInfo.Errors) and reported (via Config.TypeChecker.Error).
|
||||
func TestErrorReporting(t *testing.T) {
|
||||
pkgs := map[string]string{
|
||||
"a": `package a; import (_ "b"; _ "c"); var x int = false`,
|
||||
"b": `package b; 'syntax error!`,
|
||||
}
|
||||
conf := loader.Config{
|
||||
AllowErrors: true,
|
||||
Build: fakeContext(pkgs),
|
||||
}
|
||||
var mu sync.Mutex
|
||||
var allErrors []error
|
||||
conf.TypeChecker.Error = func(err error) {
|
||||
mu.Lock()
|
||||
allErrors = append(allErrors, err)
|
||||
mu.Unlock()
|
||||
}
|
||||
conf.Import("a")
|
||||
|
||||
prog, err := conf.Load()
|
||||
if err != nil {
|
||||
t.Errorf("Load failed: %s", err)
|
||||
}
|
||||
if prog == nil {
|
||||
t.Fatalf("Load returned nil *Program")
|
||||
}
|
||||
|
||||
// TODO(adonovan): test keys of ImportMap.
|
||||
|
||||
// Check errors recorded in each PackageInfo.
|
||||
for pkg, info := range prog.AllPackages {
|
||||
switch pkg.Path() {
|
||||
case "a":
|
||||
if !hasError(info.Errors, "cannot convert false") {
|
||||
t.Errorf("a.Errors = %v, want bool conversion (type) error", info.Errors)
|
||||
}
|
||||
if !hasError(info.Errors, "could not import c") {
|
||||
t.Errorf("a.Errors = %v, want import (loader) error", info.Errors)
|
||||
}
|
||||
case "b":
|
||||
if !hasError(info.Errors, "rune literal not terminated") {
|
||||
t.Errorf("b.Errors = %v, want unterminated literal (syntax) error", info.Errors)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check errors reported via error handler.
|
||||
if !hasError(allErrors, "cannot convert false") ||
|
||||
!hasError(allErrors, "rune literal not terminated") ||
|
||||
!hasError(allErrors, "could not import c") {
|
||||
t.Errorf("allErrors = %v, want syntax, type and loader errors", allErrors)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCycles(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
descr string
|
||||
ctxt *build.Context
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
"self-cycle",
|
||||
fakeContext(map[string]string{
|
||||
"main": `package main; import _ "selfcycle"`,
|
||||
"selfcycle": `package selfcycle; import _ "selfcycle"`,
|
||||
}),
|
||||
`import cycle: selfcycle -> selfcycle`,
|
||||
},
|
||||
{
|
||||
"three-package cycle",
|
||||
fakeContext(map[string]string{
|
||||
"main": `package main; import _ "a"`,
|
||||
"a": `package a; import _ "b"`,
|
||||
"b": `package b; import _ "c"`,
|
||||
"c": `package c; import _ "a"`,
|
||||
}),
|
||||
`import cycle: c -> a -> b -> c`,
|
||||
},
|
||||
{
|
||||
"self-cycle in dependency of test file",
|
||||
buildutil.FakeContext(map[string]map[string]string{
|
||||
"main": {
|
||||
"main.go": `package main`,
|
||||
"main_test.go": `package main; import _ "a"`,
|
||||
},
|
||||
"a": {
|
||||
"a.go": `package a; import _ "a"`,
|
||||
},
|
||||
}),
|
||||
`import cycle: a -> a`,
|
||||
},
|
||||
// TODO(adonovan): fix: these fail
|
||||
// {
|
||||
// "two-package cycle in dependency of test file",
|
||||
// buildutil.FakeContext(map[string]map[string]string{
|
||||
// "main": {
|
||||
// "main.go": `package main`,
|
||||
// "main_test.go": `package main; import _ "a"`,
|
||||
// },
|
||||
// "a": {
|
||||
// "a.go": `package a; import _ "main"`,
|
||||
// },
|
||||
// }),
|
||||
// `import cycle: main -> a -> main`,
|
||||
// },
|
||||
// {
|
||||
// "self-cycle in augmented package",
|
||||
// buildutil.FakeContext(map[string]map[string]string{
|
||||
// "main": {
|
||||
// "main.go": `package main`,
|
||||
// "main_test.go": `package main; import _ "main"`,
|
||||
// },
|
||||
// }),
|
||||
// `import cycle: main -> main`,
|
||||
// },
|
||||
} {
|
||||
conf := loader.Config{
|
||||
AllowErrors: true,
|
||||
Build: test.ctxt,
|
||||
}
|
||||
var mu sync.Mutex
|
||||
var allErrors []error
|
||||
conf.TypeChecker.Error = func(err error) {
|
||||
mu.Lock()
|
||||
allErrors = append(allErrors, err)
|
||||
mu.Unlock()
|
||||
}
|
||||
conf.ImportWithTests("main")
|
||||
|
||||
prog, err := conf.Load()
|
||||
if err != nil {
|
||||
t.Errorf("%s: Load failed: %s", test.descr, err)
|
||||
}
|
||||
if prog == nil {
|
||||
t.Fatalf("%s: Load returned nil *Program", test.descr)
|
||||
}
|
||||
|
||||
if !hasError(allErrors, test.wantErr) {
|
||||
t.Errorf("%s: Load() errors = %q, want %q",
|
||||
test.descr, allErrors, test.wantErr)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(adonovan):
|
||||
// - Test that in a legal test cycle, none of the symbols
|
||||
// defined by augmentation are visible via import.
|
||||
}
|
||||
|
||||
// ---- utilities ----
|
||||
|
||||
// Simplifying wrapper around buildutil.FakeContext for single-file packages.
|
||||
func fakeContext(pkgs map[string]string) *build.Context {
|
||||
pkgs2 := make(map[string]map[string]string)
|
||||
for path, content := range pkgs {
|
||||
pkgs2[path] = map[string]string{"x.go": content}
|
||||
}
|
||||
return buildutil.FakeContext(pkgs2)
|
||||
}
|
||||
|
||||
func hasError(errors []error, substr string) bool {
|
||||
for _, err := range errors {
|
||||
if strings.Contains(err.Error(), substr) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func keys(m map[string]bool) (keys []string) {
|
||||
for key := range m {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
return
|
||||
}
|
||||
|
||||
// Returns all loaded packages.
|
||||
func all(prog *loader.Program) []string {
|
||||
var pkgs []string
|
||||
for _, info := range prog.AllPackages {
|
||||
pkgs = append(pkgs, info.Pkg.Path())
|
||||
}
|
||||
sort.Strings(pkgs)
|
||||
return pkgs
|
||||
}
|
||||
|
||||
// Returns initially imported packages, as a string.
|
||||
func imported(prog *loader.Program) string {
|
||||
var pkgs []string
|
||||
for _, info := range prog.Imported {
|
||||
pkgs = append(pkgs, info.Pkg.Path())
|
||||
}
|
||||
sort.Strings(pkgs)
|
||||
return strings.Join(pkgs, " ")
|
||||
}
|
||||
|
||||
// Returns initially created packages, as a string.
|
||||
func created(prog *loader.Program) string {
|
||||
var pkgs []string
|
||||
for _, info := range prog.Created {
|
||||
pkgs = append(pkgs, info.Pkg.Path())
|
||||
}
|
||||
return strings.Join(pkgs, " ")
|
||||
}
|
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
// No testdata on Android.
|
||||
|
||||
// +build !android
|
||||
@ -432,6 +434,11 @@ func TestLoad_vendor(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestVendorCwd(t *testing.T) {
|
||||
if buildutil.AllowVendor == 0 {
|
||||
// Vendoring requires Go 1.6.
|
||||
// TODO(adonovan): delete in due course.
|
||||
t.Skip()
|
||||
}
|
||||
// Test the interaction of cwd and vendor directories.
|
||||
ctxt := fakeContext(map[string]string{
|
||||
"net": ``, // mkdir net
|
||||
|
197
go/loader/stdlib14_test.go
Normal file
197
go/loader/stdlib14_test.go
Normal file
@ -0,0 +1,197 @@
|
||||
// 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.
|
||||
|
||||
// +build !go1.5
|
||||
|
||||
package loader_test
|
||||
|
||||
// This file enumerates all packages beneath $GOROOT, loads them, plus
|
||||
// their external tests if any, runs the type checker on them, and
|
||||
// prints some summary information.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/token"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/tools/go/buildutil"
|
||||
"golang.org/x/tools/go/loader"
|
||||
"golang.org/x/tools/go/types"
|
||||
)
|
||||
|
||||
func TestStdlib(t *testing.T) {
|
||||
if runtime.GOOS == "android" {
|
||||
t.Skipf("incomplete std lib on %s", runtime.GOOS)
|
||||
}
|
||||
|
||||
runtime.GC()
|
||||
t0 := time.Now()
|
||||
var memstats runtime.MemStats
|
||||
runtime.ReadMemStats(&memstats)
|
||||
alloc := memstats.Alloc
|
||||
|
||||
// Load, parse and type-check the program.
|
||||
ctxt := build.Default // copy
|
||||
ctxt.GOPATH = "" // disable GOPATH
|
||||
conf := loader.Config{Build: &ctxt}
|
||||
for _, path := range buildutil.AllPackages(conf.Build) {
|
||||
conf.ImportWithTests(path)
|
||||
}
|
||||
|
||||
prog, err := conf.Load()
|
||||
if err != nil {
|
||||
t.Fatalf("Load failed: %v", err)
|
||||
}
|
||||
|
||||
t1 := time.Now()
|
||||
runtime.GC()
|
||||
runtime.ReadMemStats(&memstats)
|
||||
|
||||
numPkgs := len(prog.AllPackages)
|
||||
if want := 205; numPkgs < want {
|
||||
t.Errorf("Loaded only %d packages, want at least %d", numPkgs, want)
|
||||
}
|
||||
|
||||
// Dump package members.
|
||||
if false {
|
||||
for pkg := range prog.AllPackages {
|
||||
fmt.Printf("Package %s:\n", pkg.Path())
|
||||
scope := pkg.Scope()
|
||||
qualifier := types.RelativeTo(pkg)
|
||||
for _, name := range scope.Names() {
|
||||
if ast.IsExported(name) {
|
||||
fmt.Printf("\t%s\n", types.ObjectString(scope.Lookup(name), qualifier))
|
||||
}
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
|
||||
// Check that Test functions for io/ioutil, regexp and
|
||||
// compress/bzip2 are all simultaneously present.
|
||||
// (The apparent cycle formed when augmenting all three of
|
||||
// these packages by their tests was the original motivation
|
||||
// for reporting b/7114.)
|
||||
//
|
||||
// compress/bzip2.TestBitReader in bzip2_test.go imports io/ioutil
|
||||
// io/ioutil.TestTempFile in tempfile_test.go imports regexp
|
||||
// regexp.TestRE2Search in exec_test.go imports compress/bzip2
|
||||
for _, test := range []struct{ pkg, fn string }{
|
||||
{"io/ioutil", "TestTempFile"},
|
||||
{"regexp", "TestRE2Search"},
|
||||
{"compress/bzip2", "TestBitReader"},
|
||||
} {
|
||||
info := prog.Imported[test.pkg]
|
||||
if info == nil {
|
||||
t.Errorf("failed to load package %q", test.pkg)
|
||||
continue
|
||||
}
|
||||
obj, _ := info.Pkg.Scope().Lookup(test.fn).(*types.Func)
|
||||
if obj == nil {
|
||||
t.Errorf("package %q has no func %q", test.pkg, test.fn)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Dump some statistics.
|
||||
|
||||
// determine line count
|
||||
var lineCount int
|
||||
prog.Fset.Iterate(func(f *token.File) bool {
|
||||
lineCount += f.LineCount()
|
||||
return true
|
||||
})
|
||||
|
||||
t.Log("GOMAXPROCS: ", runtime.GOMAXPROCS(0))
|
||||
t.Log("#Source lines: ", lineCount)
|
||||
t.Log("Load/parse/typecheck: ", t1.Sub(t0))
|
||||
t.Log("#MB: ", int64(memstats.Alloc-alloc)/1000000)
|
||||
}
|
||||
|
||||
func TestCgoOption(t *testing.T) {
|
||||
switch runtime.GOOS {
|
||||
// On these systems, the net and os/user packages don't use cgo
|
||||
// or the std library is incomplete (Android).
|
||||
case "android", "plan9", "solaris", "windows":
|
||||
t.Skipf("no cgo or incomplete std lib on %s", runtime.GOOS)
|
||||
}
|
||||
// In nocgo builds (e.g. linux-amd64-nocgo),
|
||||
// there is no "runtime/cgo" package,
|
||||
// so cgo-generated Go files will have a failing import.
|
||||
if !build.Default.CgoEnabled {
|
||||
return
|
||||
}
|
||||
// Test that we can load cgo-using packages with
|
||||
// CGO_ENABLED=[01], which causes go/build to select pure
|
||||
// Go/native implementations, respectively, based on build
|
||||
// tags.
|
||||
//
|
||||
// Each entry specifies a package-level object and the generic
|
||||
// file expected to define it when cgo is disabled.
|
||||
// When cgo is enabled, the exact file is not specified (since
|
||||
// it varies by platform), but must differ from the generic one.
|
||||
//
|
||||
// The test also loads the actual file to verify that the
|
||||
// object is indeed defined at that location.
|
||||
for _, test := range []struct {
|
||||
pkg, name, genericFile string
|
||||
}{
|
||||
{"net", "cgoLookupHost", "cgo_stub.go"},
|
||||
{"os/user", "lookupId", "lookup_stubs.go"},
|
||||
} {
|
||||
ctxt := build.Default
|
||||
for _, ctxt.CgoEnabled = range []bool{false, true} {
|
||||
conf := loader.Config{Build: &ctxt}
|
||||
conf.Import(test.pkg)
|
||||
prog, err := conf.Load()
|
||||
if err != nil {
|
||||
t.Errorf("Load failed: %v", err)
|
||||
continue
|
||||
}
|
||||
info := prog.Imported[test.pkg]
|
||||
if info == nil {
|
||||
t.Errorf("package %s not found", test.pkg)
|
||||
continue
|
||||
}
|
||||
obj := info.Pkg.Scope().Lookup(test.name)
|
||||
if obj == nil {
|
||||
t.Errorf("no object %s.%s", test.pkg, test.name)
|
||||
continue
|
||||
}
|
||||
posn := prog.Fset.Position(obj.Pos())
|
||||
t.Logf("%s: %s (CgoEnabled=%t)", posn, obj, ctxt.CgoEnabled)
|
||||
|
||||
gotFile := filepath.Base(posn.Filename)
|
||||
filesMatch := gotFile == test.genericFile
|
||||
|
||||
if ctxt.CgoEnabled && filesMatch {
|
||||
t.Errorf("CGO_ENABLED=1: %s found in %s, want native file",
|
||||
obj, gotFile)
|
||||
} else if !ctxt.CgoEnabled && !filesMatch {
|
||||
t.Errorf("CGO_ENABLED=0: %s found in %s, want %s",
|
||||
obj, gotFile, test.genericFile)
|
||||
}
|
||||
|
||||
// Load the file and check the object is declared at the right place.
|
||||
b, err := ioutil.ReadFile(posn.Filename)
|
||||
if err != nil {
|
||||
t.Errorf("can't read %s: %s", posn.Filename, err)
|
||||
continue
|
||||
}
|
||||
line := string(bytes.Split(b, []byte("\n"))[posn.Line-1])
|
||||
ident := line[posn.Column-1:]
|
||||
if !strings.HasPrefix(ident, test.name) {
|
||||
t.Errorf("%s: %s not declared here (looking at %q)", posn, obj, ident)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
package loader_test
|
||||
|
||||
// This file enumerates all packages beneath $GOROOT, loads them, plus
|
||||
|
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
package pointer
|
||||
|
||||
// This file defines the main datatypes and Analyze function of the pointer analysis.
|
||||
|
449
go/pointer/analysis14.go
Normal file
449
go/pointer/analysis14.go
Normal file
@ -0,0 +1,449 @@
|
||||
// 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.
|
||||
|
||||
// +build !go1.5
|
||||
|
||||
package pointer
|
||||
|
||||
// This file defines the main datatypes and Analyze function of the pointer analysis.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/token"
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"sort"
|
||||
|
||||
"golang.org/x/tools/go/callgraph"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/tools/go/types"
|
||||
"golang.org/x/tools/go/types/typeutil"
|
||||
)
|
||||
|
||||
const (
|
||||
// optimization options; enable all when committing
|
||||
optRenumber = true // enable renumbering optimization (makes logs hard to read)
|
||||
optHVN = true // enable pointer equivalence via Hash-Value Numbering
|
||||
|
||||
// debugging options; disable all when committing
|
||||
debugHVN = false // enable assertions in HVN
|
||||
debugHVNVerbose = false // enable extra HVN logging
|
||||
debugHVNCrossCheck = false // run solver with/without HVN and compare (caveats below)
|
||||
debugTimers = false // show running time of each phase
|
||||
)
|
||||
|
||||
// object.flags bitmask values.
|
||||
const (
|
||||
otTagged = 1 << iota // type-tagged object
|
||||
otIndirect // type-tagged object with indirect payload
|
||||
otFunction // function object
|
||||
)
|
||||
|
||||
// An object represents a contiguous block of memory to which some
|
||||
// (generalized) pointer may point.
|
||||
//
|
||||
// (Note: most variables called 'obj' are not *objects but nodeids
|
||||
// such that a.nodes[obj].obj != nil.)
|
||||
//
|
||||
type object struct {
|
||||
// flags is a bitset of the node type (ot*) flags defined above.
|
||||
flags uint32
|
||||
|
||||
// Number of following nodes belonging to the same "object"
|
||||
// allocation. Zero for all other nodes.
|
||||
size uint32
|
||||
|
||||
// data describes this object; it has one of these types:
|
||||
//
|
||||
// ssa.Value for an object allocated by an SSA operation.
|
||||
// types.Type for an rtype instance object or *rtype-tagged object.
|
||||
// string for an instrinsic object, e.g. the array behind os.Args.
|
||||
// nil for an object allocated by an instrinsic.
|
||||
// (cgn provides the identity of the intrinsic.)
|
||||
data interface{}
|
||||
|
||||
// The call-graph node (=context) in which this object was allocated.
|
||||
// May be nil for global objects: Global, Const, some Functions.
|
||||
cgn *cgnode
|
||||
}
|
||||
|
||||
// nodeid denotes a node.
|
||||
// It is an index within analysis.nodes.
|
||||
// We use small integers, not *node pointers, for many reasons:
|
||||
// - they are smaller on 64-bit systems.
|
||||
// - sets of them can be represented compactly in bitvectors or BDDs.
|
||||
// - order matters; a field offset can be computed by simple addition.
|
||||
type nodeid uint32
|
||||
|
||||
// A node is an equivalence class of memory locations.
|
||||
// Nodes may be pointers, pointed-to locations, neither, or both.
|
||||
//
|
||||
// Nodes that are pointed-to locations ("labels") have an enclosing
|
||||
// object (see analysis.enclosingObject).
|
||||
//
|
||||
type node struct {
|
||||
// If non-nil, this node is the start of an object
|
||||
// (addressable memory location).
|
||||
// The following obj.size nodes implicitly belong to the object;
|
||||
// they locate their object by scanning back.
|
||||
obj *object
|
||||
|
||||
// The type of the field denoted by this node. Non-aggregate,
|
||||
// unless this is an tagged.T node (i.e. the thing
|
||||
// pointed to by an interface) in which case typ is that type.
|
||||
typ types.Type
|
||||
|
||||
// subelement indicates which directly embedded subelement of
|
||||
// an object of aggregate type (struct, tuple, array) this is.
|
||||
subelement *fieldInfo // e.g. ".a.b[*].c"
|
||||
|
||||
// Solver state for the canonical node of this pointer-
|
||||
// equivalence class. Each node is created with its own state
|
||||
// but they become shared after HVN.
|
||||
solve *solverState
|
||||
}
|
||||
|
||||
// An analysis instance holds the state of a single pointer analysis problem.
|
||||
type analysis struct {
|
||||
config *Config // the client's control/observer interface
|
||||
prog *ssa.Program // the program being analyzed
|
||||
log io.Writer // log stream; nil to disable
|
||||
panicNode nodeid // sink for panic, source for recover
|
||||
nodes []*node // indexed by nodeid
|
||||
flattenMemo map[types.Type][]*fieldInfo // memoization of flatten()
|
||||
trackTypes map[types.Type]bool // memoization of shouldTrack()
|
||||
constraints []constraint // set of constraints
|
||||
cgnodes []*cgnode // all cgnodes
|
||||
genq []*cgnode // queue of functions to generate constraints for
|
||||
intrinsics map[*ssa.Function]intrinsic // non-nil values are summaries for intrinsic fns
|
||||
globalval map[ssa.Value]nodeid // node for each global ssa.Value
|
||||
globalobj map[ssa.Value]nodeid // maps v to sole member of pts(v), if singleton
|
||||
localval map[ssa.Value]nodeid // node for each local ssa.Value
|
||||
localobj map[ssa.Value]nodeid // maps v to sole member of pts(v), if singleton
|
||||
atFuncs map[*ssa.Function]bool // address-taken functions (for presolver)
|
||||
mapValues []nodeid // values of makemap objects (indirect in HVN)
|
||||
work nodeset // solver's worklist
|
||||
result *Result // results of the analysis
|
||||
track track // pointerlike types whose aliasing we track
|
||||
deltaSpace []int // working space for iterating over PTS deltas
|
||||
|
||||
// Reflection & intrinsics:
|
||||
hasher typeutil.Hasher // cache of type hashes
|
||||
reflectValueObj types.Object // type symbol for reflect.Value (if present)
|
||||
reflectValueCall *ssa.Function // (reflect.Value).Call
|
||||
reflectRtypeObj types.Object // *types.TypeName for reflect.rtype (if present)
|
||||
reflectRtypePtr *types.Pointer // *reflect.rtype
|
||||
reflectType *types.Named // reflect.Type
|
||||
rtypes typeutil.Map // nodeid of canonical *rtype-tagged object for type T
|
||||
reflectZeros typeutil.Map // nodeid of canonical T-tagged object for zero value
|
||||
runtimeSetFinalizer *ssa.Function // runtime.SetFinalizer
|
||||
}
|
||||
|
||||
// enclosingObj returns the first node of the addressable memory
|
||||
// object that encloses node id. Panic ensues if that node does not
|
||||
// belong to any object.
|
||||
func (a *analysis) enclosingObj(id nodeid) nodeid {
|
||||
// Find previous node with obj != nil.
|
||||
for i := id; i >= 0; i-- {
|
||||
n := a.nodes[i]
|
||||
if obj := n.obj; obj != nil {
|
||||
if i+nodeid(obj.size) <= id {
|
||||
break // out of bounds
|
||||
}
|
||||
return i
|
||||
}
|
||||
}
|
||||
panic("node has no enclosing object")
|
||||
}
|
||||
|
||||
// labelFor returns the Label for node id.
|
||||
// Panic ensues if that node is not addressable.
|
||||
func (a *analysis) labelFor(id nodeid) *Label {
|
||||
return &Label{
|
||||
obj: a.nodes[a.enclosingObj(id)].obj,
|
||||
subelement: a.nodes[id].subelement,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *analysis) warnf(pos token.Pos, format string, args ...interface{}) {
|
||||
msg := fmt.Sprintf(format, args...)
|
||||
if a.log != nil {
|
||||
fmt.Fprintf(a.log, "%s: warning: %s\n", a.prog.Fset.Position(pos), msg)
|
||||
}
|
||||
a.result.Warnings = append(a.result.Warnings, Warning{pos, msg})
|
||||
}
|
||||
|
||||
// computeTrackBits sets a.track to the necessary 'track' bits for the pointer queries.
|
||||
func (a *analysis) computeTrackBits() {
|
||||
var queryTypes []types.Type
|
||||
for v := range a.config.Queries {
|
||||
queryTypes = append(queryTypes, v.Type())
|
||||
}
|
||||
for v := range a.config.IndirectQueries {
|
||||
queryTypes = append(queryTypes, mustDeref(v.Type()))
|
||||
}
|
||||
for _, t := range queryTypes {
|
||||
switch t.Underlying().(type) {
|
||||
case *types.Chan:
|
||||
a.track |= trackChan
|
||||
case *types.Map:
|
||||
a.track |= trackMap
|
||||
case *types.Pointer:
|
||||
a.track |= trackPtr
|
||||
case *types.Slice:
|
||||
a.track |= trackSlice
|
||||
case *types.Interface:
|
||||
a.track = trackAll
|
||||
return
|
||||
}
|
||||
if rVObj := a.reflectValueObj; rVObj != nil && types.Identical(t, rVObj.Type()) {
|
||||
a.track = trackAll
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Analyze runs the pointer analysis with the scope and options
|
||||
// specified by config, and returns the (synthetic) root of the callgraph.
|
||||
//
|
||||
// Pointer analysis of a transitively closed well-typed program should
|
||||
// always succeed. An error can occur only due to an internal bug.
|
||||
//
|
||||
func Analyze(config *Config) (result *Result, err error) {
|
||||
if config.Mains == nil {
|
||||
return nil, fmt.Errorf("no main/test packages to analyze (check $GOROOT/$GOPATH)")
|
||||
}
|
||||
defer func() {
|
||||
if p := recover(); p != nil {
|
||||
err = fmt.Errorf("internal error in pointer analysis: %v (please report this bug)", p)
|
||||
fmt.Fprintln(os.Stderr, "Internal panic in pointer analysis:")
|
||||
debug.PrintStack()
|
||||
}
|
||||
}()
|
||||
|
||||
a := &analysis{
|
||||
config: config,
|
||||
log: config.Log,
|
||||
prog: config.prog(),
|
||||
globalval: make(map[ssa.Value]nodeid),
|
||||
globalobj: make(map[ssa.Value]nodeid),
|
||||
flattenMemo: make(map[types.Type][]*fieldInfo),
|
||||
trackTypes: make(map[types.Type]bool),
|
||||
atFuncs: make(map[*ssa.Function]bool),
|
||||
hasher: typeutil.MakeHasher(),
|
||||
intrinsics: make(map[*ssa.Function]intrinsic),
|
||||
result: &Result{
|
||||
Queries: make(map[ssa.Value]Pointer),
|
||||
IndirectQueries: make(map[ssa.Value]Pointer),
|
||||
},
|
||||
deltaSpace: make([]int, 0, 100),
|
||||
}
|
||||
|
||||
if false {
|
||||
a.log = os.Stderr // for debugging crashes; extremely verbose
|
||||
}
|
||||
|
||||
if a.log != nil {
|
||||
fmt.Fprintln(a.log, "==== Starting analysis")
|
||||
}
|
||||
|
||||
// Pointer analysis requires a complete program for soundness.
|
||||
// Check to prevent accidental misconfiguration.
|
||||
for _, pkg := range a.prog.AllPackages() {
|
||||
// (This only checks that the package scope is complete,
|
||||
// not that func bodies exist, but it's a good signal.)
|
||||
if !pkg.Pkg.Complete() {
|
||||
return nil, fmt.Errorf(`pointer analysis requires a complete program yet package %q was incomplete`, pkg.Pkg.Path())
|
||||
}
|
||||
}
|
||||
|
||||
if reflect := a.prog.ImportedPackage("reflect"); reflect != nil {
|
||||
rV := reflect.Pkg.Scope().Lookup("Value")
|
||||
a.reflectValueObj = rV
|
||||
a.reflectValueCall = a.prog.LookupMethod(rV.Type(), nil, "Call")
|
||||
a.reflectType = reflect.Pkg.Scope().Lookup("Type").Type().(*types.Named)
|
||||
a.reflectRtypeObj = reflect.Pkg.Scope().Lookup("rtype")
|
||||
a.reflectRtypePtr = types.NewPointer(a.reflectRtypeObj.Type())
|
||||
|
||||
// Override flattening of reflect.Value, treating it like a basic type.
|
||||
tReflectValue := a.reflectValueObj.Type()
|
||||
a.flattenMemo[tReflectValue] = []*fieldInfo{{typ: tReflectValue}}
|
||||
|
||||
// Override shouldTrack of reflect.Value and *reflect.rtype.
|
||||
// Always track pointers of these types.
|
||||
a.trackTypes[tReflectValue] = true
|
||||
a.trackTypes[a.reflectRtypePtr] = true
|
||||
|
||||
a.rtypes.SetHasher(a.hasher)
|
||||
a.reflectZeros.SetHasher(a.hasher)
|
||||
}
|
||||
if runtime := a.prog.ImportedPackage("runtime"); runtime != nil {
|
||||
a.runtimeSetFinalizer = runtime.Func("SetFinalizer")
|
||||
}
|
||||
a.computeTrackBits()
|
||||
|
||||
a.generate()
|
||||
a.showCounts()
|
||||
|
||||
if optRenumber {
|
||||
a.renumber()
|
||||
}
|
||||
|
||||
N := len(a.nodes) // excludes solver-created nodes
|
||||
|
||||
if optHVN {
|
||||
if debugHVNCrossCheck {
|
||||
// Cross-check: run the solver once without
|
||||
// optimization, once with, and compare the
|
||||
// solutions.
|
||||
savedConstraints := a.constraints
|
||||
|
||||
a.solve()
|
||||
a.dumpSolution("A.pts", N)
|
||||
|
||||
// Restore.
|
||||
a.constraints = savedConstraints
|
||||
for _, n := range a.nodes {
|
||||
n.solve = new(solverState)
|
||||
}
|
||||
a.nodes = a.nodes[:N]
|
||||
|
||||
// rtypes is effectively part of the solver state.
|
||||
a.rtypes = typeutil.Map{}
|
||||
a.rtypes.SetHasher(a.hasher)
|
||||
}
|
||||
|
||||
a.hvn()
|
||||
}
|
||||
|
||||
if debugHVNCrossCheck {
|
||||
runtime.GC()
|
||||
runtime.GC()
|
||||
}
|
||||
|
||||
a.solve()
|
||||
|
||||
// Compare solutions.
|
||||
if optHVN && debugHVNCrossCheck {
|
||||
a.dumpSolution("B.pts", N)
|
||||
|
||||
if !diff("A.pts", "B.pts") {
|
||||
return nil, fmt.Errorf("internal error: optimization changed solution")
|
||||
}
|
||||
}
|
||||
|
||||
// Create callgraph.Nodes in deterministic order.
|
||||
if cg := a.result.CallGraph; cg != nil {
|
||||
for _, caller := range a.cgnodes {
|
||||
cg.CreateNode(caller.fn)
|
||||
}
|
||||
}
|
||||
|
||||
// Add dynamic edges to call graph.
|
||||
var space [100]int
|
||||
for _, caller := range a.cgnodes {
|
||||
for _, site := range caller.sites {
|
||||
for _, callee := range a.nodes[site.targets].solve.pts.AppendTo(space[:0]) {
|
||||
a.callEdge(caller, site, nodeid(callee))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return a.result, nil
|
||||
}
|
||||
|
||||
// callEdge is called for each edge in the callgraph.
|
||||
// calleeid is the callee's object node (has otFunction flag).
|
||||
//
|
||||
func (a *analysis) callEdge(caller *cgnode, site *callsite, calleeid nodeid) {
|
||||
obj := a.nodes[calleeid].obj
|
||||
if obj.flags&otFunction == 0 {
|
||||
panic(fmt.Sprintf("callEdge %s -> n%d: not a function object", site, calleeid))
|
||||
}
|
||||
callee := obj.cgn
|
||||
|
||||
if cg := a.result.CallGraph; cg != nil {
|
||||
// TODO(adonovan): opt: I would expect duplicate edges
|
||||
// (to wrappers) to arise due to the elimination of
|
||||
// context information, but I haven't observed any.
|
||||
// Understand this better.
|
||||
callgraph.AddEdge(cg.CreateNode(caller.fn), site.instr, cg.CreateNode(callee.fn))
|
||||
}
|
||||
|
||||
if a.log != nil {
|
||||
fmt.Fprintf(a.log, "\tcall edge %s -> %s\n", site, callee)
|
||||
}
|
||||
|
||||
// Warn about calls to non-intrinsic external functions.
|
||||
// TODO(adonovan): de-dup these messages.
|
||||
if fn := callee.fn; fn.Blocks == nil && a.findIntrinsic(fn) == nil {
|
||||
a.warnf(site.pos(), "unsound call to unknown intrinsic: %s", fn)
|
||||
a.warnf(fn.Pos(), " (declared here)")
|
||||
}
|
||||
}
|
||||
|
||||
// dumpSolution writes the PTS solution to the specified file.
|
||||
//
|
||||
// It only dumps the nodes that existed before solving. The order in
|
||||
// which solver-created nodes are created depends on pre-solver
|
||||
// optimization, so we can't include them in the cross-check.
|
||||
//
|
||||
func (a *analysis) dumpSolution(filename string, N int) {
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for id, n := range a.nodes[:N] {
|
||||
if _, err := fmt.Fprintf(f, "pts(n%d) = {", id); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var sep string
|
||||
for _, l := range n.solve.pts.AppendTo(a.deltaSpace) {
|
||||
if l >= N {
|
||||
break
|
||||
}
|
||||
fmt.Fprintf(f, "%s%d", sep, l)
|
||||
sep = " "
|
||||
}
|
||||
fmt.Fprintf(f, "} : %s\n", n.typ)
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// showCounts logs the size of the constraint system. A typical
|
||||
// optimized distribution is 65% copy, 13% load, 11% addr, 5%
|
||||
// offsetAddr, 4% store, 2% others.
|
||||
//
|
||||
func (a *analysis) showCounts() {
|
||||
if a.log != nil {
|
||||
counts := make(map[reflect.Type]int)
|
||||
for _, c := range a.constraints {
|
||||
counts[reflect.TypeOf(c)]++
|
||||
}
|
||||
fmt.Fprintf(a.log, "# constraints:\t%d\n", len(a.constraints))
|
||||
var lines []string
|
||||
for t, n := range counts {
|
||||
line := fmt.Sprintf("%7d (%2d%%)\t%s", n, 100*n/len(a.constraints), t)
|
||||
lines = append(lines, line)
|
||||
}
|
||||
sort.Sort(sort.Reverse(sort.StringSlice(lines)))
|
||||
for _, line := range lines {
|
||||
fmt.Fprintf(a.log, "\t%s\n", line)
|
||||
}
|
||||
|
||||
fmt.Fprintf(a.log, "# nodes:\t%d\n", len(a.nodes))
|
||||
|
||||
// Show number of pointer equivalence classes.
|
||||
m := make(map[*solverState]bool)
|
||||
for _, n := range a.nodes {
|
||||
m[n.solve] = true
|
||||
}
|
||||
fmt.Fprintf(a.log, "# ptsets:\t%d\n", len(m))
|
||||
}
|
||||
}
|
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
package pointer
|
||||
|
||||
import (
|
||||
|
247
go/pointer/api14.go
Normal file
247
go/pointer/api14.go
Normal file
@ -0,0 +1,247 @@
|
||||
// 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.
|
||||
|
||||
// +build !go1.5
|
||||
|
||||
package pointer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/token"
|
||||
"io"
|
||||
|
||||
"golang.org/x/tools/container/intsets"
|
||||
"golang.org/x/tools/go/callgraph"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/tools/go/types/typeutil"
|
||||
)
|
||||
|
||||
// A Config formulates a pointer analysis problem for Analyze().
|
||||
type Config struct {
|
||||
// Mains contains the set of 'main' packages to analyze
|
||||
// Clients must provide the analysis with at least one
|
||||
// package defining a main() function.
|
||||
//
|
||||
// Non-main packages in the ssa.Program that are not
|
||||
// dependencies of any main package may still affect the
|
||||
// analysis result, because they contribute runtime types and
|
||||
// thus methods.
|
||||
// TODO(adonovan): investigate whether this is desirable.
|
||||
Mains []*ssa.Package
|
||||
|
||||
// Reflection determines whether to handle reflection
|
||||
// operators soundly, which is currently rather slow since it
|
||||
// causes constraint to be generated during solving
|
||||
// proportional to the number of constraint variables, which
|
||||
// has not yet been reduced by presolver optimisation.
|
||||
Reflection bool
|
||||
|
||||
// BuildCallGraph determines whether to construct a callgraph.
|
||||
// If enabled, the graph will be available in Result.CallGraph.
|
||||
BuildCallGraph bool
|
||||
|
||||
// The client populates Queries[v] or IndirectQueries[v]
|
||||
// for each ssa.Value v of interest, to request that the
|
||||
// points-to sets pts(v) or pts(*v) be computed. If the
|
||||
// client needs both points-to sets, v may appear in both
|
||||
// maps.
|
||||
//
|
||||
// (IndirectQueries is typically used for Values corresponding
|
||||
// to source-level lvalues, e.g. an *ssa.Global.)
|
||||
//
|
||||
// The analysis populates the corresponding
|
||||
// Result.{Indirect,}Queries map when it creates the pointer
|
||||
// variable for v or *v. Upon completion the client can
|
||||
// inspect that map for the results.
|
||||
//
|
||||
// TODO(adonovan): this API doesn't scale well for batch tools
|
||||
// that want to dump the entire solution. Perhaps optionally
|
||||
// populate a map[*ssa.DebugRef]Pointer in the Result, one
|
||||
// entry per source expression.
|
||||
//
|
||||
Queries map[ssa.Value]struct{}
|
||||
IndirectQueries map[ssa.Value]struct{}
|
||||
|
||||
// If Log is non-nil, log messages are written to it.
|
||||
// Logging is extremely verbose.
|
||||
Log io.Writer
|
||||
}
|
||||
|
||||
type track uint32
|
||||
|
||||
const (
|
||||
trackChan track = 1 << iota // track 'chan' references
|
||||
trackMap // track 'map' references
|
||||
trackPtr // track regular pointers
|
||||
trackSlice // track slice references
|
||||
|
||||
trackAll = ^track(0)
|
||||
)
|
||||
|
||||
// AddQuery adds v to Config.Queries.
|
||||
// Precondition: CanPoint(v.Type()).
|
||||
// TODO(adonovan): consider returning a new Pointer for this query,
|
||||
// which will be initialized during analysis. That avoids the needs
|
||||
// for the corresponding ssa.Value-keyed maps in Config and Result.
|
||||
func (c *Config) AddQuery(v ssa.Value) {
|
||||
if !CanPoint(v.Type()) {
|
||||
panic(fmt.Sprintf("%s is not a pointer-like value: %s", v, v.Type()))
|
||||
}
|
||||
if c.Queries == nil {
|
||||
c.Queries = make(map[ssa.Value]struct{})
|
||||
}
|
||||
c.Queries[v] = struct{}{}
|
||||
}
|
||||
|
||||
// AddQuery adds v to Config.IndirectQueries.
|
||||
// Precondition: CanPoint(v.Type().Underlying().(*types.Pointer).Elem()).
|
||||
func (c *Config) AddIndirectQuery(v ssa.Value) {
|
||||
if c.IndirectQueries == nil {
|
||||
c.IndirectQueries = make(map[ssa.Value]struct{})
|
||||
}
|
||||
if !CanPoint(mustDeref(v.Type())) {
|
||||
panic(fmt.Sprintf("%s is not the address of a pointer-like value: %s", v, v.Type()))
|
||||
}
|
||||
c.IndirectQueries[v] = struct{}{}
|
||||
}
|
||||
|
||||
func (c *Config) prog() *ssa.Program {
|
||||
for _, main := range c.Mains {
|
||||
return main.Prog
|
||||
}
|
||||
panic("empty scope")
|
||||
}
|
||||
|
||||
type Warning struct {
|
||||
Pos token.Pos
|
||||
Message string
|
||||
}
|
||||
|
||||
// A Result contains the results of a pointer analysis.
|
||||
//
|
||||
// See Config for how to request the various Result components.
|
||||
//
|
||||
type Result struct {
|
||||
CallGraph *callgraph.Graph // discovered call graph
|
||||
Queries map[ssa.Value]Pointer // pts(v) for each v in Config.Queries.
|
||||
IndirectQueries map[ssa.Value]Pointer // pts(*v) for each v in Config.IndirectQueries.
|
||||
Warnings []Warning // warnings of unsoundness
|
||||
}
|
||||
|
||||
// A Pointer is an equivalence class of pointer-like values.
|
||||
//
|
||||
// A Pointer doesn't have a unique type because pointers of distinct
|
||||
// types may alias the same object.
|
||||
//
|
||||
type Pointer struct {
|
||||
a *analysis
|
||||
n nodeid
|
||||
}
|
||||
|
||||
// A PointsToSet is a set of labels (locations or allocations).
|
||||
type PointsToSet struct {
|
||||
a *analysis // may be nil if pts is nil
|
||||
pts *nodeset
|
||||
}
|
||||
|
||||
func (s PointsToSet) String() string {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteByte('[')
|
||||
if s.pts != nil {
|
||||
var space [50]int
|
||||
for i, l := range s.pts.AppendTo(space[:0]) {
|
||||
if i > 0 {
|
||||
buf.WriteString(", ")
|
||||
}
|
||||
buf.WriteString(s.a.labelFor(nodeid(l)).String())
|
||||
}
|
||||
}
|
||||
buf.WriteByte(']')
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// PointsTo returns the set of labels that this points-to set
|
||||
// contains.
|
||||
func (s PointsToSet) Labels() []*Label {
|
||||
var labels []*Label
|
||||
if s.pts != nil {
|
||||
var space [50]int
|
||||
for _, l := range s.pts.AppendTo(space[:0]) {
|
||||
labels = append(labels, s.a.labelFor(nodeid(l)))
|
||||
}
|
||||
}
|
||||
return labels
|
||||
}
|
||||
|
||||
// If this PointsToSet came from a Pointer of interface kind
|
||||
// or a reflect.Value, DynamicTypes returns the set of dynamic
|
||||
// types that it may contain. (For an interface, they will
|
||||
// always be concrete types.)
|
||||
//
|
||||
// The result is a mapping whose keys are the dynamic types to which
|
||||
// it may point. For each pointer-like key type, the corresponding
|
||||
// map value is the PointsToSet for pointers of that type.
|
||||
//
|
||||
// The result is empty unless CanHaveDynamicTypes(T).
|
||||
//
|
||||
func (s PointsToSet) DynamicTypes() *typeutil.Map {
|
||||
var tmap typeutil.Map
|
||||
tmap.SetHasher(s.a.hasher)
|
||||
if s.pts != nil {
|
||||
var space [50]int
|
||||
for _, x := range s.pts.AppendTo(space[:0]) {
|
||||
ifaceObjId := nodeid(x)
|
||||
if !s.a.isTaggedObject(ifaceObjId) {
|
||||
continue // !CanHaveDynamicTypes(tDyn)
|
||||
}
|
||||
tDyn, v, indirect := s.a.taggedValue(ifaceObjId)
|
||||
if indirect {
|
||||
panic("indirect tagged object") // implement later
|
||||
}
|
||||
pts, ok := tmap.At(tDyn).(PointsToSet)
|
||||
if !ok {
|
||||
pts = PointsToSet{s.a, new(nodeset)}
|
||||
tmap.Set(tDyn, pts)
|
||||
}
|
||||
pts.pts.addAll(&s.a.nodes[v].solve.pts)
|
||||
}
|
||||
}
|
||||
return &tmap
|
||||
}
|
||||
|
||||
// Intersects reports whether this points-to set and the
|
||||
// argument points-to set contain common members.
|
||||
func (x PointsToSet) Intersects(y PointsToSet) bool {
|
||||
if x.pts == nil || y.pts == nil {
|
||||
return false
|
||||
}
|
||||
// This takes Θ(|x|+|y|) time.
|
||||
var z intsets.Sparse
|
||||
z.Intersection(&x.pts.Sparse, &y.pts.Sparse)
|
||||
return !z.IsEmpty()
|
||||
}
|
||||
|
||||
func (p Pointer) String() string {
|
||||
return fmt.Sprintf("n%d", p.n)
|
||||
}
|
||||
|
||||
// PointsTo returns the points-to set of this pointer.
|
||||
func (p Pointer) PointsTo() PointsToSet {
|
||||
if p.n == 0 {
|
||||
return PointsToSet{}
|
||||
}
|
||||
return PointsToSet{p.a, &p.a.nodes[p.n].solve.pts}
|
||||
}
|
||||
|
||||
// MayAlias reports whether the receiver pointer may alias
|
||||
// the argument pointer.
|
||||
func (p Pointer) MayAlias(q Pointer) bool {
|
||||
return p.PointsTo().Intersects(q.PointsTo())
|
||||
}
|
||||
|
||||
// DynamicTypes returns p.PointsTo().DynamicTypes().
|
||||
func (p Pointer) DynamicTypes() *typeutil.Map {
|
||||
return p.PointsTo().DynamicTypes()
|
||||
}
|
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
package pointer
|
||||
|
||||
import (
|
||||
|
153
go/pointer/constraint14.go
Normal file
153
go/pointer/constraint14.go
Normal file
@ -0,0 +1,153 @@
|
||||
// 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.
|
||||
|
||||
// +build !go1.5
|
||||
|
||||
package pointer
|
||||
|
||||
import (
|
||||
"golang.org/x/tools/go/types"
|
||||
)
|
||||
|
||||
type constraint interface {
|
||||
// For a complex constraint, returns the nodeid of the pointer
|
||||
// to which it is attached. For addr and copy, returns dst.
|
||||
ptr() nodeid
|
||||
|
||||
// renumber replaces each nodeid n in the constraint by mapping[n].
|
||||
renumber(mapping []nodeid)
|
||||
|
||||
// presolve is a hook for constraint-specific behaviour during
|
||||
// pre-solver optimization. Typical implementations mark as
|
||||
// indirect the set of nodes to which the solver will add copy
|
||||
// edges or PTS labels.
|
||||
presolve(h *hvn)
|
||||
|
||||
// solve is called for complex constraints when the pts for
|
||||
// the node to which they are attached has changed.
|
||||
solve(a *analysis, delta *nodeset)
|
||||
|
||||
String() string
|
||||
}
|
||||
|
||||
// dst = &src
|
||||
// pts(dst) ⊇ {src}
|
||||
// A base constraint used to initialize the solver's pt sets
|
||||
type addrConstraint struct {
|
||||
dst nodeid // (ptr)
|
||||
src nodeid
|
||||
}
|
||||
|
||||
func (c *addrConstraint) ptr() nodeid { return c.dst }
|
||||
func (c *addrConstraint) renumber(mapping []nodeid) {
|
||||
c.dst = mapping[c.dst]
|
||||
c.src = mapping[c.src]
|
||||
}
|
||||
|
||||
// dst = src
|
||||
// A simple constraint represented directly as a copyTo graph edge.
|
||||
type copyConstraint struct {
|
||||
dst nodeid // (ptr)
|
||||
src nodeid
|
||||
}
|
||||
|
||||
func (c *copyConstraint) ptr() nodeid { return c.dst }
|
||||
func (c *copyConstraint) renumber(mapping []nodeid) {
|
||||
c.dst = mapping[c.dst]
|
||||
c.src = mapping[c.src]
|
||||
}
|
||||
|
||||
// dst = src[offset]
|
||||
// A complex constraint attached to src (the pointer)
|
||||
type loadConstraint struct {
|
||||
offset uint32
|
||||
dst nodeid
|
||||
src nodeid // (ptr)
|
||||
}
|
||||
|
||||
func (c *loadConstraint) ptr() nodeid { return c.src }
|
||||
func (c *loadConstraint) renumber(mapping []nodeid) {
|
||||
c.dst = mapping[c.dst]
|
||||
c.src = mapping[c.src]
|
||||
}
|
||||
|
||||
// dst[offset] = src
|
||||
// A complex constraint attached to dst (the pointer)
|
||||
type storeConstraint struct {
|
||||
offset uint32
|
||||
dst nodeid // (ptr)
|
||||
src nodeid
|
||||
}
|
||||
|
||||
func (c *storeConstraint) ptr() nodeid { return c.dst }
|
||||
func (c *storeConstraint) renumber(mapping []nodeid) {
|
||||
c.dst = mapping[c.dst]
|
||||
c.src = mapping[c.src]
|
||||
}
|
||||
|
||||
// dst = &src.f or dst = &src[0]
|
||||
// A complex constraint attached to dst (the pointer)
|
||||
type offsetAddrConstraint struct {
|
||||
offset uint32
|
||||
dst nodeid
|
||||
src nodeid // (ptr)
|
||||
}
|
||||
|
||||
func (c *offsetAddrConstraint) ptr() nodeid { return c.src }
|
||||
func (c *offsetAddrConstraint) renumber(mapping []nodeid) {
|
||||
c.dst = mapping[c.dst]
|
||||
c.src = mapping[c.src]
|
||||
}
|
||||
|
||||
// dst = src.(typ) where typ is an interface
|
||||
// A complex constraint attached to src (the interface).
|
||||
// No representation change: pts(dst) and pts(src) contains tagged objects.
|
||||
type typeFilterConstraint struct {
|
||||
typ types.Type // an interface type
|
||||
dst nodeid
|
||||
src nodeid // (ptr)
|
||||
}
|
||||
|
||||
func (c *typeFilterConstraint) ptr() nodeid { return c.src }
|
||||
func (c *typeFilterConstraint) renumber(mapping []nodeid) {
|
||||
c.dst = mapping[c.dst]
|
||||
c.src = mapping[c.src]
|
||||
}
|
||||
|
||||
// dst = src.(typ) where typ is a concrete type
|
||||
// A complex constraint attached to src (the interface).
|
||||
//
|
||||
// If exact, only tagged objects identical to typ are untagged.
|
||||
// If !exact, tagged objects assignable to typ are untagged too.
|
||||
// The latter is needed for various reflect operators, e.g. Send.
|
||||
//
|
||||
// This entails a representation change:
|
||||
// pts(src) contains tagged objects,
|
||||
// pts(dst) contains their payloads.
|
||||
type untagConstraint struct {
|
||||
typ types.Type // a concrete type
|
||||
dst nodeid
|
||||
src nodeid // (ptr)
|
||||
exact bool
|
||||
}
|
||||
|
||||
func (c *untagConstraint) ptr() nodeid { return c.src }
|
||||
func (c *untagConstraint) renumber(mapping []nodeid) {
|
||||
c.dst = mapping[c.dst]
|
||||
c.src = mapping[c.src]
|
||||
}
|
||||
|
||||
// src.method(params...)
|
||||
// A complex constraint attached to iface.
|
||||
type invokeConstraint struct {
|
||||
method *types.Func // the abstract method
|
||||
iface nodeid // (ptr) the interface
|
||||
params nodeid // the start of the identity/params/results block
|
||||
}
|
||||
|
||||
func (c *invokeConstraint) ptr() nodeid { return c.iface }
|
||||
func (c *invokeConstraint) renumber(mapping []nodeid) {
|
||||
c.iface = mapping[c.iface]
|
||||
c.params = mapping[c.params]
|
||||
}
|
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
/*
|
||||
|
||||
Package pointer implements Andersen's analysis, an inclusion-based
|
||||
|
612
go/pointer/doc14.go
Normal file
612
go/pointer/doc14.go
Normal file
@ -0,0 +1,612 @@
|
||||
// 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.
|
||||
|
||||
// +build !go1.5
|
||||
|
||||
/*
|
||||
|
||||
Package pointer implements Andersen's analysis, an inclusion-based
|
||||
pointer analysis algorithm first described in (Andersen, 1994).
|
||||
|
||||
A pointer analysis relates every pointer expression in a whole program
|
||||
to the set of memory locations to which it might point. This
|
||||
information can be used to construct a call graph of the program that
|
||||
precisely represents the destinations of dynamic function and method
|
||||
calls. It can also be used to determine, for example, which pairs of
|
||||
channel operations operate on the same channel.
|
||||
|
||||
The package allows the client to request a set of expressions of
|
||||
interest for which the points-to information will be returned once the
|
||||
analysis is complete. In addition, the client may request that a
|
||||
callgraph is constructed. The example program in example_test.go
|
||||
demonstrates both of these features. Clients should not request more
|
||||
information than they need since it may increase the cost of the
|
||||
analysis significantly.
|
||||
|
||||
|
||||
CLASSIFICATION
|
||||
|
||||
Our algorithm is INCLUSION-BASED: the points-to sets for x and y will
|
||||
be related by pts(y) ⊇ pts(x) if the program contains the statement
|
||||
y = x.
|
||||
|
||||
It is FLOW-INSENSITIVE: it ignores all control flow constructs and the
|
||||
order of statements in a program. It is therefore a "MAY ALIAS"
|
||||
analysis: its facts are of the form "P may/may not point to L",
|
||||
not "P must point to L".
|
||||
|
||||
It is FIELD-SENSITIVE: it builds separate points-to sets for distinct
|
||||
fields, such as x and y in struct { x, y *int }.
|
||||
|
||||
It is mostly CONTEXT-INSENSITIVE: most functions are analyzed once,
|
||||
so values can flow in at one call to the function and return out at
|
||||
another. Only some smaller functions are analyzed with consideration
|
||||
of their calling context.
|
||||
|
||||
It has a CONTEXT-SENSITIVE HEAP: objects are named by both allocation
|
||||
site and context, so the objects returned by two distinct calls to f:
|
||||
func f() *T { return new(T) }
|
||||
are distinguished up to the limits of the calling context.
|
||||
|
||||
It is a WHOLE PROGRAM analysis: it requires SSA-form IR for the
|
||||
complete Go program and summaries for native code.
|
||||
|
||||
See the (Hind, PASTE'01) survey paper for an explanation of these terms.
|
||||
|
||||
|
||||
SOUNDNESS
|
||||
|
||||
The analysis is fully sound when invoked on pure Go programs that do not
|
||||
use reflection or unsafe.Pointer conversions. In other words, if there
|
||||
is any possible execution of the program in which pointer P may point to
|
||||
object O, the analysis will report that fact.
|
||||
|
||||
|
||||
REFLECTION
|
||||
|
||||
By default, the "reflect" library is ignored by the analysis, as if all
|
||||
its functions were no-ops, but if the client enables the Reflection flag,
|
||||
the analysis will make a reasonable attempt to model the effects of
|
||||
calls into this library. However, this comes at a significant
|
||||
performance cost, and not all features of that library are yet
|
||||
implemented. In addition, some simplifying approximations must be made
|
||||
to ensure that the analysis terminates; for example, reflection can be
|
||||
used to construct an infinite set of types and values of those types,
|
||||
but the analysis arbitrarily bounds the depth of such types.
|
||||
|
||||
Most but not all reflection operations are supported.
|
||||
In particular, addressable reflect.Values are not yet implemented, so
|
||||
operations such as (reflect.Value).Set have no analytic effect.
|
||||
|
||||
|
||||
UNSAFE POINTER CONVERSIONS
|
||||
|
||||
The pointer analysis makes no attempt to understand aliasing between the
|
||||
operand x and result y of an unsafe.Pointer conversion:
|
||||
y = (*T)(unsafe.Pointer(x))
|
||||
It is as if the conversion allocated an entirely new object:
|
||||
y = new(T)
|
||||
|
||||
|
||||
NATIVE CODE
|
||||
|
||||
The analysis cannot model the aliasing effects of functions written in
|
||||
languages other than Go, such as runtime intrinsics in C or assembly, or
|
||||
code accessed via cgo. The result is as if such functions are no-ops.
|
||||
However, various important intrinsics are understood by the analysis,
|
||||
along with built-ins such as append.
|
||||
|
||||
The analysis currently provides no way for users to specify the aliasing
|
||||
effects of native code.
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
IMPLEMENTATION
|
||||
|
||||
The remaining documentation is intended for package maintainers and
|
||||
pointer analysis specialists. Maintainers should have a solid
|
||||
understanding of the referenced papers (especially those by H&L and PKH)
|
||||
before making making significant changes.
|
||||
|
||||
The implementation is similar to that described in (Pearce et al,
|
||||
PASTE'04). Unlike many algorithms which interleave constraint
|
||||
generation and solving, constructing the callgraph as they go, this
|
||||
implementation for the most part observes a phase ordering (generation
|
||||
before solving), with only simple (copy) constraints being generated
|
||||
during solving. (The exception is reflection, which creates various
|
||||
constraints during solving as new types flow to reflect.Value
|
||||
operations.) This improves the traction of presolver optimisations,
|
||||
but imposes certain restrictions, e.g. potential context sensitivity
|
||||
is limited since all variants must be created a priori.
|
||||
|
||||
|
||||
TERMINOLOGY
|
||||
|
||||
A type is said to be "pointer-like" if it is a reference to an object.
|
||||
Pointer-like types include pointers and also interfaces, maps, channels,
|
||||
functions and slices.
|
||||
|
||||
We occasionally use C's x->f notation to distinguish the case where x
|
||||
is a struct pointer from x.f where is a struct value.
|
||||
|
||||
Pointer analysis literature (and our comments) often uses the notation
|
||||
dst=*src+offset to mean something different than what it means in Go.
|
||||
It means: for each node index p in pts(src), the node index p+offset is
|
||||
in pts(dst). Similarly *dst+offset=src is used for store constraints
|
||||
and dst=src+offset for offset-address constraints.
|
||||
|
||||
|
||||
NODES
|
||||
|
||||
Nodes are the key datastructure of the analysis, and have a dual role:
|
||||
they represent both constraint variables (equivalence classes of
|
||||
pointers) and members of points-to sets (things that can be pointed
|
||||
at, i.e. "labels").
|
||||
|
||||
Nodes are naturally numbered. The numbering enables compact
|
||||
representations of sets of nodes such as bitvectors (or BDDs); and the
|
||||
ordering enables a very cheap way to group related nodes together. For
|
||||
example, passing n parameters consists of generating n parallel
|
||||
constraints from caller+i to callee+i for 0<=i<n.
|
||||
|
||||
The zero nodeid means "not a pointer". For simplicity, we generate flow
|
||||
constraints even for non-pointer types such as int. The pointer
|
||||
equivalence (PE) presolver optimization detects which variables cannot
|
||||
point to anything; this includes not only all variables of non-pointer
|
||||
types (such as int) but also variables of pointer-like types if they are
|
||||
always nil, or are parameters to a function that is never called.
|
||||
|
||||
Each node represents a scalar part of a value or object.
|
||||
Aggregate types (structs, tuples, arrays) are recursively flattened
|
||||
out into a sequential list of scalar component types, and all the
|
||||
elements of an array are represented by a single node. (The
|
||||
flattening of a basic type is a list containing a single node.)
|
||||
|
||||
Nodes are connected into a graph with various kinds of labelled edges:
|
||||
simple edges (or copy constraints) represent value flow. Complex
|
||||
edges (load, store, etc) trigger the creation of new simple edges
|
||||
during the solving phase.
|
||||
|
||||
|
||||
OBJECTS
|
||||
|
||||
Conceptually, an "object" is a contiguous sequence of nodes denoting
|
||||
an addressable location: something that a pointer can point to. The
|
||||
first node of an object has a non-nil obj field containing information
|
||||
about the allocation: its size, context, and ssa.Value.
|
||||
|
||||
Objects include:
|
||||
- functions and globals;
|
||||
- variable allocations in the stack frame or heap;
|
||||
- maps, channels and slices created by calls to make();
|
||||
- allocations to construct an interface;
|
||||
- allocations caused by conversions, e.g. []byte(str).
|
||||
- arrays allocated by calls to append();
|
||||
|
||||
Many objects have no Go types. For example, the func, map and chan type
|
||||
kinds in Go are all varieties of pointers, but their respective objects
|
||||
are actual functions (executable code), maps (hash tables), and channels
|
||||
(synchronized queues). Given the way we model interfaces, they too are
|
||||
pointers to "tagged" objects with no Go type. And an *ssa.Global denotes
|
||||
the address of a global variable, but the object for a Global is the
|
||||
actual data. So, the types of an ssa.Value that creates an object is
|
||||
"off by one indirection": a pointer to the object.
|
||||
|
||||
The individual nodes of an object are sometimes referred to as "labels".
|
||||
|
||||
For uniformity, all objects have a non-zero number of fields, even those
|
||||
of the empty type struct{}. (All arrays are treated as if of length 1,
|
||||
so there are no empty arrays. The empty tuple is never address-taken,
|
||||
so is never an object.)
|
||||
|
||||
|
||||
TAGGED OBJECTS
|
||||
|
||||
An tagged object has the following layout:
|
||||
|
||||
T -- obj.flags ⊇ {otTagged}
|
||||
v
|
||||
...
|
||||
|
||||
The T node's typ field is the dynamic type of the "payload": the value
|
||||
v which follows, flattened out. The T node's obj has the otTagged
|
||||
flag.
|
||||
|
||||
Tagged objects are needed when generalizing across types: interfaces,
|
||||
reflect.Values, reflect.Types. Each of these three types is modelled
|
||||
as a pointer that exclusively points to tagged objects.
|
||||
|
||||
Tagged objects may be indirect (obj.flags ⊇ {otIndirect}) meaning that
|
||||
the value v is not of type T but *T; this is used only for
|
||||
reflect.Values that represent lvalues. (These are not implemented yet.)
|
||||
|
||||
|
||||
ANALYSIS ABSTRACTION OF EACH TYPE
|
||||
|
||||
Variables of the following "scalar" types may be represented by a
|
||||
single node: basic types, pointers, channels, maps, slices, 'func'
|
||||
pointers, interfaces.
|
||||
|
||||
Pointers
|
||||
Nothing to say here, oddly.
|
||||
|
||||
Basic types (bool, string, numbers, unsafe.Pointer)
|
||||
Currently all fields in the flattening of a type, including
|
||||
non-pointer basic types such as int, are represented in objects and
|
||||
values. Though non-pointer nodes within values are uninteresting,
|
||||
non-pointer nodes in objects may be useful (if address-taken)
|
||||
because they permit the analysis to deduce, in this example,
|
||||
|
||||
var s struct{ ...; x int; ... }
|
||||
p := &s.x
|
||||
|
||||
that p points to s.x. If we ignored such object fields, we could only
|
||||
say that p points somewhere within s.
|
||||
|
||||
All other basic types are ignored. Expressions of these types have
|
||||
zero nodeid, and fields of these types within aggregate other types
|
||||
are omitted.
|
||||
|
||||
unsafe.Pointers are not modelled as pointers, so a conversion of an
|
||||
unsafe.Pointer to *T is (unsoundly) treated equivalent to new(T).
|
||||
|
||||
Channels
|
||||
An expression of type 'chan T' is a kind of pointer that points
|
||||
exclusively to channel objects, i.e. objects created by MakeChan (or
|
||||
reflection).
|
||||
|
||||
'chan T' is treated like *T.
|
||||
*ssa.MakeChan is treated as equivalent to new(T).
|
||||
*ssa.Send and receive (*ssa.UnOp(ARROW)) and are equivalent to store
|
||||
and load.
|
||||
|
||||
Maps
|
||||
An expression of type 'map[K]V' is a kind of pointer that points
|
||||
exclusively to map objects, i.e. objects created by MakeMap (or
|
||||
reflection).
|
||||
|
||||
map K[V] is treated like *M where M = struct{k K; v V}.
|
||||
*ssa.MakeMap is equivalent to new(M).
|
||||
*ssa.MapUpdate is equivalent to *y=x where *y and x have type M.
|
||||
*ssa.Lookup is equivalent to y=x.v where x has type *M.
|
||||
|
||||
Slices
|
||||
A slice []T, which dynamically resembles a struct{array *T, len, cap int},
|
||||
is treated as if it were just a *T pointer; the len and cap fields are
|
||||
ignored.
|
||||
|
||||
*ssa.MakeSlice is treated like new([1]T): an allocation of a
|
||||
singleton array.
|
||||
*ssa.Index on a slice is equivalent to a load.
|
||||
*ssa.IndexAddr on a slice returns the address of the sole element of the
|
||||
slice, i.e. the same address.
|
||||
*ssa.Slice is treated as a simple copy.
|
||||
|
||||
Functions
|
||||
An expression of type 'func...' is a kind of pointer that points
|
||||
exclusively to function objects.
|
||||
|
||||
A function object has the following layout:
|
||||
|
||||
identity -- typ:*types.Signature; obj.flags ⊇ {otFunction}
|
||||
params_0 -- (the receiver, if a method)
|
||||
...
|
||||
params_n-1
|
||||
results_0
|
||||
...
|
||||
results_m-1
|
||||
|
||||
There may be multiple function objects for the same *ssa.Function
|
||||
due to context-sensitive treatment of some functions.
|
||||
|
||||
The first node is the function's identity node.
|
||||
Associated with every callsite is a special "targets" variable,
|
||||
whose pts() contains the identity node of each function to which
|
||||
the call may dispatch. Identity words are not otherwise used during
|
||||
the analysis, but we construct the call graph from the pts()
|
||||
solution for such nodes.
|
||||
|
||||
The following block of contiguous nodes represents the flattened-out
|
||||
types of the parameters ("P-block") and results ("R-block") of the
|
||||
function object.
|
||||
|
||||
The treatment of free variables of closures (*ssa.FreeVar) is like
|
||||
that of global variables; it is not context-sensitive.
|
||||
*ssa.MakeClosure instructions create copy edges to Captures.
|
||||
|
||||
A Go value of type 'func' (i.e. a pointer to one or more functions)
|
||||
is a pointer whose pts() contains function objects. The valueNode()
|
||||
for an *ssa.Function returns a singleton for that function.
|
||||
|
||||
Interfaces
|
||||
An expression of type 'interface{...}' is a kind of pointer that
|
||||
points exclusively to tagged objects. All tagged objects pointed to
|
||||
by an interface are direct (the otIndirect flag is clear) and
|
||||
concrete (the tag type T is not itself an interface type). The
|
||||
associated ssa.Value for an interface's tagged objects may be an
|
||||
*ssa.MakeInterface instruction, or nil if the tagged object was
|
||||
created by an instrinsic (e.g. reflection).
|
||||
|
||||
Constructing an interface value causes generation of constraints for
|
||||
all of the concrete type's methods; we can't tell a priori which
|
||||
ones may be called.
|
||||
|
||||
TypeAssert y = x.(T) is implemented by a dynamic constraint
|
||||
triggered by each tagged object O added to pts(x): a typeFilter
|
||||
constraint if T is an interface type, or an untag constraint if T is
|
||||
a concrete type. A typeFilter tests whether O.typ implements T; if
|
||||
so, O is added to pts(y). An untagFilter tests whether O.typ is
|
||||
assignable to T,and if so, a copy edge O.v -> y is added.
|
||||
|
||||
ChangeInterface is a simple copy because the representation of
|
||||
tagged objects is independent of the interface type (in contrast
|
||||
to the "method tables" approach used by the gc runtime).
|
||||
|
||||
y := Invoke x.m(...) is implemented by allocating contiguous P/R
|
||||
blocks for the callsite and adding a dynamic rule triggered by each
|
||||
tagged object added to pts(x). The rule adds param/results copy
|
||||
edges to/from each discovered concrete method.
|
||||
|
||||
(Q. Why do we model an interface as a pointer to a pair of type and
|
||||
value, rather than as a pair of a pointer to type and a pointer to
|
||||
value?
|
||||
A. Control-flow joins would merge interfaces ({T1}, {V1}) and ({T2},
|
||||
{V2}) to make ({T1,T2}, {V1,V2}), leading to the infeasible and
|
||||
type-unsafe combination (T1,V2). Treating the value and its concrete
|
||||
type as inseparable makes the analysis type-safe.)
|
||||
|
||||
reflect.Value
|
||||
A reflect.Value is modelled very similar to an interface{}, i.e. as
|
||||
a pointer exclusively to tagged objects, but with two generalizations.
|
||||
|
||||
1) a reflect.Value that represents an lvalue points to an indirect
|
||||
(obj.flags ⊇ {otIndirect}) tagged object, which has a similar
|
||||
layout to an tagged object except that the value is a pointer to
|
||||
the dynamic type. Indirect tagged objects preserve the correct
|
||||
aliasing so that mutations made by (reflect.Value).Set can be
|
||||
observed.
|
||||
|
||||
Indirect objects only arise when an lvalue is derived from an
|
||||
rvalue by indirection, e.g. the following code:
|
||||
|
||||
type S struct { X T }
|
||||
var s S
|
||||
var i interface{} = &s // i points to a *S-tagged object (from MakeInterface)
|
||||
v1 := reflect.ValueOf(i) // v1 points to same *S-tagged object as i
|
||||
v2 := v1.Elem() // v2 points to an indirect S-tagged object, pointing to s
|
||||
v3 := v2.FieldByName("X") // v3 points to an indirect int-tagged object, pointing to s.X
|
||||
v3.Set(y) // pts(s.X) ⊇ pts(y)
|
||||
|
||||
Whether indirect or not, the concrete type of the tagged object
|
||||
corresponds to the user-visible dynamic type, and the existence
|
||||
of a pointer is an implementation detail.
|
||||
|
||||
(NB: indirect tagged objects are not yet implemented)
|
||||
|
||||
2) The dynamic type tag of a tagged object pointed to by a
|
||||
reflect.Value may be an interface type; it need not be concrete.
|
||||
|
||||
This arises in code such as this:
|
||||
tEface := reflect.TypeOf(new(interface{}).Elem() // interface{}
|
||||
eface := reflect.Zero(tEface)
|
||||
pts(eface) is a singleton containing an interface{}-tagged
|
||||
object. That tagged object's payload is an interface{} value,
|
||||
i.e. the pts of the payload contains only concrete-tagged
|
||||
objects, although in this example it's the zero interface{} value,
|
||||
so its pts is empty.
|
||||
|
||||
reflect.Type
|
||||
Just as in the real "reflect" library, we represent a reflect.Type
|
||||
as an interface whose sole implementation is the concrete type,
|
||||
*reflect.rtype. (This choice is forced on us by go/types: clients
|
||||
cannot fabricate types with arbitrary method sets.)
|
||||
|
||||
rtype instances are canonical: there is at most one per dynamic
|
||||
type. (rtypes are in fact large structs but since identity is all
|
||||
that matters, we represent them by a single node.)
|
||||
|
||||
The payload of each *rtype-tagged object is an *rtype pointer that
|
||||
points to exactly one such canonical rtype object. We exploit this
|
||||
by setting the node.typ of the payload to the dynamic type, not
|
||||
'*rtype'. This saves us an indirection in each resolution rule. As
|
||||
an optimisation, *rtype-tagged objects are canonicalized too.
|
||||
|
||||
|
||||
Aggregate types:
|
||||
|
||||
Aggregate types are treated as if all directly contained
|
||||
aggregates are recursively flattened out.
|
||||
|
||||
Structs
|
||||
*ssa.Field y = x.f creates a simple edge to y from x's node at f's offset.
|
||||
|
||||
*ssa.FieldAddr y = &x->f requires a dynamic closure rule to create
|
||||
simple edges for each struct discovered in pts(x).
|
||||
|
||||
The nodes of a struct consist of a special 'identity' node (whose
|
||||
type is that of the struct itself), followed by the nodes for all
|
||||
the struct's fields, recursively flattened out. A pointer to the
|
||||
struct is a pointer to its identity node. That node allows us to
|
||||
distinguish a pointer to a struct from a pointer to its first field.
|
||||
|
||||
Field offsets are logical field offsets (plus one for the identity
|
||||
node), so the sizes of the fields can be ignored by the analysis.
|
||||
|
||||
(The identity node is non-traditional but enables the distiction
|
||||
described above, which is valuable for code comprehension tools.
|
||||
Typical pointer analyses for C, whose purpose is compiler
|
||||
optimization, must soundly model unsafe.Pointer (void*) conversions,
|
||||
and this requires fidelity to the actual memory layout using physical
|
||||
field offsets.)
|
||||
|
||||
*ssa.Field y = x.f creates a simple edge to y from x's node at f's offset.
|
||||
|
||||
*ssa.FieldAddr y = &x->f requires a dynamic closure rule to create
|
||||
simple edges for each struct discovered in pts(x).
|
||||
|
||||
Arrays
|
||||
We model an array by an identity node (whose type is that of the
|
||||
array itself) followed by a node representing all the elements of
|
||||
the array; the analysis does not distinguish elements with different
|
||||
indices. Effectively, an array is treated like struct{elem T}, a
|
||||
load y=x[i] like y=x.elem, and a store x[i]=y like x.elem=y; the
|
||||
index i is ignored.
|
||||
|
||||
A pointer to an array is pointer to its identity node. (A slice is
|
||||
also a pointer to an array's identity node.) The identity node
|
||||
allows us to distinguish a pointer to an array from a pointer to one
|
||||
of its elements, but it is rather costly because it introduces more
|
||||
offset constraints into the system. Furthermore, sound treatment of
|
||||
unsafe.Pointer would require us to dispense with this node.
|
||||
|
||||
Arrays may be allocated by Alloc, by make([]T), by calls to append,
|
||||
and via reflection.
|
||||
|
||||
Tuples (T, ...)
|
||||
Tuples are treated like structs with naturally numbered fields.
|
||||
*ssa.Extract is analogous to *ssa.Field.
|
||||
|
||||
However, tuples have no identity field since by construction, they
|
||||
cannot be address-taken.
|
||||
|
||||
|
||||
FUNCTION CALLS
|
||||
|
||||
There are three kinds of function call:
|
||||
(1) static "call"-mode calls of functions.
|
||||
(2) dynamic "call"-mode calls of functions.
|
||||
(3) dynamic "invoke"-mode calls of interface methods.
|
||||
Cases 1 and 2 apply equally to methods and standalone functions.
|
||||
|
||||
Static calls.
|
||||
A static call consists three steps:
|
||||
- finding the function object of the callee;
|
||||
- creating copy edges from the actual parameter value nodes to the
|
||||
P-block in the function object (this includes the receiver if
|
||||
the callee is a method);
|
||||
- creating copy edges from the R-block in the function object to
|
||||
the value nodes for the result of the call.
|
||||
|
||||
A static function call is little more than two struct value copies
|
||||
between the P/R blocks of caller and callee:
|
||||
|
||||
callee.P = caller.P
|
||||
caller.R = callee.R
|
||||
|
||||
Context sensitivity
|
||||
|
||||
Static calls (alone) may be treated context sensitively,
|
||||
i.e. each callsite may cause a distinct re-analysis of the
|
||||
callee, improving precision. Our current context-sensitivity
|
||||
policy treats all intrinsics and getter/setter methods in this
|
||||
manner since such functions are small and seem like an obvious
|
||||
source of spurious confluences, though this has not yet been
|
||||
evaluated.
|
||||
|
||||
Dynamic function calls
|
||||
|
||||
Dynamic calls work in a similar manner except that the creation of
|
||||
copy edges occurs dynamically, in a similar fashion to a pair of
|
||||
struct copies in which the callee is indirect:
|
||||
|
||||
callee->P = caller.P
|
||||
caller.R = callee->R
|
||||
|
||||
(Recall that the function object's P- and R-blocks are contiguous.)
|
||||
|
||||
Interface method invocation
|
||||
|
||||
For invoke-mode calls, we create a params/results block for the
|
||||
callsite and attach a dynamic closure rule to the interface. For
|
||||
each new tagged object that flows to the interface, we look up
|
||||
the concrete method, find its function object, and connect its P/R
|
||||
blocks to the callsite's P/R blocks, adding copy edges to the graph
|
||||
during solving.
|
||||
|
||||
Recording call targets
|
||||
|
||||
The analysis notifies its clients of each callsite it encounters,
|
||||
passing a CallSite interface. Among other things, the CallSite
|
||||
contains a synthetic constraint variable ("targets") whose
|
||||
points-to solution includes the set of all function objects to
|
||||
which the call may dispatch.
|
||||
|
||||
It is via this mechanism that the callgraph is made available.
|
||||
Clients may also elect to be notified of callgraph edges directly;
|
||||
internally this just iterates all "targets" variables' pts(·)s.
|
||||
|
||||
|
||||
PRESOLVER
|
||||
|
||||
We implement Hash-Value Numbering (HVN), a pre-solver constraint
|
||||
optimization described in Hardekopf & Lin, SAS'07. This is documented
|
||||
in more detail in hvn.go. We intend to add its cousins HR and HU in
|
||||
future.
|
||||
|
||||
|
||||
SOLVER
|
||||
|
||||
The solver is currently a naive Andersen-style implementation; it does
|
||||
not perform online cycle detection, though we plan to add solver
|
||||
optimisations such as Hybrid- and Lazy- Cycle Detection from (Hardekopf
|
||||
& Lin, PLDI'07).
|
||||
|
||||
It uses difference propagation (Pearce et al, SQC'04) to avoid
|
||||
redundant re-triggering of closure rules for values already seen.
|
||||
|
||||
Points-to sets are represented using sparse bit vectors (similar to
|
||||
those used in LLVM and gcc), which are more space- and time-efficient
|
||||
than sets based on Go's built-in map type or dense bit vectors.
|
||||
|
||||
Nodes are permuted prior to solving so that object nodes (which may
|
||||
appear in points-to sets) are lower numbered than non-object (var)
|
||||
nodes. This improves the density of the set over which the PTSs
|
||||
range, and thus the efficiency of the representation.
|
||||
|
||||
Partly thanks to avoiding map iteration, the execution of the solver is
|
||||
100% deterministic, a great help during debugging.
|
||||
|
||||
|
||||
FURTHER READING
|
||||
|
||||
Andersen, L. O. 1994. Program analysis and specialization for the C
|
||||
programming language. Ph.D. dissertation. DIKU, University of
|
||||
Copenhagen.
|
||||
|
||||
David J. Pearce, Paul H. J. Kelly, and Chris Hankin. 2004. Efficient
|
||||
field-sensitive pointer analysis for C. In Proceedings of the 5th ACM
|
||||
SIGPLAN-SIGSOFT workshop on Program analysis for software tools and
|
||||
engineering (PASTE '04). ACM, New York, NY, USA, 37-42.
|
||||
http://doi.acm.org/10.1145/996821.996835
|
||||
|
||||
David J. Pearce, Paul H. J. Kelly, and Chris Hankin. 2004. Online
|
||||
Cycle Detection and Difference Propagation: Applications to Pointer
|
||||
Analysis. Software Quality Control 12, 4 (December 2004), 311-337.
|
||||
http://dx.doi.org/10.1023/B:SQJO.0000039791.93071.a2
|
||||
|
||||
David Grove and Craig Chambers. 2001. A framework for call graph
|
||||
construction algorithms. ACM Trans. Program. Lang. Syst. 23, 6
|
||||
(November 2001), 685-746.
|
||||
http://doi.acm.org/10.1145/506315.506316
|
||||
|
||||
Ben Hardekopf and Calvin Lin. 2007. The ant and the grasshopper: fast
|
||||
and accurate pointer analysis for millions of lines of code. In
|
||||
Proceedings of the 2007 ACM SIGPLAN conference on Programming language
|
||||
design and implementation (PLDI '07). ACM, New York, NY, USA, 290-299.
|
||||
http://doi.acm.org/10.1145/1250734.1250767
|
||||
|
||||
Ben Hardekopf and Calvin Lin. 2007. Exploiting pointer and location
|
||||
equivalence to optimize pointer analysis. In Proceedings of the 14th
|
||||
international conference on Static Analysis (SAS'07), Hanne Riis
|
||||
Nielson and Gilberto Filé (Eds.). Springer-Verlag, Berlin, Heidelberg,
|
||||
265-280.
|
||||
|
||||
Atanas Rountev and Satish Chandra. 2000. Off-line variable substitution
|
||||
for scaling points-to analysis. In Proceedings of the ACM SIGPLAN 2000
|
||||
conference on Programming language design and implementation (PLDI '00).
|
||||
ACM, New York, NY, USA, 47-56. DOI=10.1145/349299.349310
|
||||
http://doi.acm.org/10.1145/349299.349310
|
||||
|
||||
*/
|
||||
package pointer // import "golang.org/x/tools/go/pointer"
|
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
package pointer
|
||||
|
||||
// This file defines the constraint generation phase.
|
||||
|
1315
go/pointer/gen14.go
Normal file
1315
go/pointer/gen14.go
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,3 +1,9 @@
|
||||
// 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.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
package pointer
|
||||
|
||||
// This file implements Hash-Value Numbering (HVN), a pre-solver
|
||||
|
975
go/pointer/hvn14.go
Normal file
975
go/pointer/hvn14.go
Normal file
@ -0,0 +1,975 @@
|
||||
// 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.
|
||||
|
||||
// +build !go1.5
|
||||
|
||||
package pointer
|
||||
|
||||
// This file implements Hash-Value Numbering (HVN), a pre-solver
|
||||
// constraint optimization described in Hardekopf & Lin, SAS'07 (see
|
||||
// doc.go) that analyses the graph topology to determine which sets of
|
||||
// variables are "pointer equivalent" (PE), i.e. must have identical
|
||||
// points-to sets in the solution.
|
||||
//
|
||||
// A separate ("offline") graph is constructed. Its nodes are those of
|
||||
// the main-graph, plus an additional node *X for each pointer node X.
|
||||
// With this graph we can reason about the unknown points-to set of
|
||||
// dereferenced pointers. (We do not generalize this to represent
|
||||
// unknown fields x->f, perhaps because such fields would be numerous,
|
||||
// though it might be worth an experiment.)
|
||||
//
|
||||
// Nodes whose points-to relations are not entirely captured by the
|
||||
// graph are marked as "indirect": the *X nodes, the parameters of
|
||||
// address-taken functions (which includes all functions in method
|
||||
// sets), or nodes updated by the solver rules for reflection, etc.
|
||||
//
|
||||
// All addr (y=&x) nodes are initially assigned a pointer-equivalence
|
||||
// (PE) label equal to x's nodeid in the main graph. (These are the
|
||||
// only PE labels that are less than len(a.nodes).)
|
||||
//
|
||||
// All offsetAddr (y=&x.f) constraints are initially assigned a PE
|
||||
// label; such labels are memoized, keyed by (x, f), so that equivalent
|
||||
// nodes y as assigned the same label.
|
||||
//
|
||||
// Then we process each strongly connected component (SCC) of the graph
|
||||
// in topological order, assigning it a PE label based on the set P of
|
||||
// PE labels that flow to it from its immediate dependencies.
|
||||
//
|
||||
// If any node in P is "indirect", the entire SCC is assigned a fresh PE
|
||||
// label. Otherwise:
|
||||
//
|
||||
// |P|=0 if P is empty, all nodes in the SCC are non-pointers (e.g.
|
||||
// uninitialized variables, or formal params of dead functions)
|
||||
// and the SCC is assigned the PE label of zero.
|
||||
//
|
||||
// |P|=1 if P is a singleton, the SCC is assigned the same label as the
|
||||
// sole element of P.
|
||||
//
|
||||
// |P|>1 if P contains multiple labels, a unique label representing P is
|
||||
// invented and recorded in an hash table, so that other
|
||||
// equivalent SCCs may also be assigned this label, akin to
|
||||
// conventional hash-value numbering in a compiler.
|
||||
//
|
||||
// Finally, a renumbering is computed such that each node is replaced by
|
||||
// the lowest-numbered node with the same PE label. All constraints are
|
||||
// renumbered, and any resulting duplicates are eliminated.
|
||||
//
|
||||
// The only nodes that are not renumbered are the objects x in addr
|
||||
// (y=&x) constraints, since the ids of these nodes (and fields derived
|
||||
// from them via offsetAddr rules) are the elements of all points-to
|
||||
// sets, so they must remain as they are if we want the same solution.
|
||||
//
|
||||
// The solverStates (node.solve) for nodes in the same equivalence class
|
||||
// are linked together so that all nodes in the class have the same
|
||||
// solution. This avoids the need to renumber nodeids buried in
|
||||
// Queries, cgnodes, etc (like (*analysis).renumber() does) since only
|
||||
// the solution is needed.
|
||||
//
|
||||
// The result of HVN is that the number of distinct nodes and
|
||||
// constraints is reduced, but the solution is identical (almost---see
|
||||
// CROSS-CHECK below). In particular, both linear and cyclic chains of
|
||||
// copies are each replaced by a single node.
|
||||
//
|
||||
// Nodes and constraints created "online" (e.g. while solving reflection
|
||||
// constraints) are not subject to this optimization.
|
||||
//
|
||||
// PERFORMANCE
|
||||
//
|
||||
// In two benchmarks (oracle and godoc), HVN eliminates about two thirds
|
||||
// of nodes, the majority accounted for by non-pointers: nodes of
|
||||
// non-pointer type, pointers that remain nil, formal parameters of dead
|
||||
// functions, nodes of untracked types, etc. It also reduces the number
|
||||
// of constraints, also by about two thirds, and the solving time by
|
||||
// 30--42%, although we must pay about 15% for the running time of HVN
|
||||
// itself. The benefit is greater for larger applications.
|
||||
//
|
||||
// There are many possible optimizations to improve the performance:
|
||||
// * Use fewer than 1:1 onodes to main graph nodes: many of the onodes
|
||||
// we create are not needed.
|
||||
// * HU (HVN with Union---see paper): coalesce "union" peLabels when
|
||||
// their expanded-out sets are equal.
|
||||
// * HR (HVN with deReference---see paper): this will require that we
|
||||
// apply HVN until fixed point, which may need more bookkeeping of the
|
||||
// correspondance of main nodes to onodes.
|
||||
// * Location Equivalence (see paper): have points-to sets contain not
|
||||
// locations but location-equivalence class labels, each representing
|
||||
// a set of locations.
|
||||
// * HVN with field-sensitive ref: model each of the fields of a
|
||||
// pointer-to-struct.
|
||||
//
|
||||
// CROSS-CHECK
|
||||
//
|
||||
// To verify the soundness of the optimization, when the
|
||||
// debugHVNCrossCheck option is enabled, we run the solver twice, once
|
||||
// before and once after running HVN, dumping the solution to disk, and
|
||||
// then we compare the results. If they are not identical, the analysis
|
||||
// panics.
|
||||
//
|
||||
// The solution dumped to disk includes only the N*N submatrix of the
|
||||
// complete solution where N is the number of nodes after generation.
|
||||
// In other words, we ignore pointer variables and objects created by
|
||||
// the solver itself, since their numbering depends on the solver order,
|
||||
// which is affected by the optimization. In any case, that's the only
|
||||
// part the client cares about.
|
||||
//
|
||||
// The cross-check is too strict and may fail spuriously. Although the
|
||||
// H&L paper describing HVN states that the solutions obtained should be
|
||||
// identical, this is not the case in practice because HVN can collapse
|
||||
// cycles involving *p even when pts(p)={}. Consider this example
|
||||
// distilled from testdata/hello.go:
|
||||
//
|
||||
// var x T
|
||||
// func f(p **T) {
|
||||
// t0 = *p
|
||||
// ...
|
||||
// t1 = φ(t0, &x)
|
||||
// *p = t1
|
||||
// }
|
||||
//
|
||||
// If f is dead code, we get:
|
||||
// unoptimized: pts(p)={} pts(t0)={} pts(t1)={&x}
|
||||
// optimized: pts(p)={} pts(t0)=pts(t1)=pts(*p)={&x}
|
||||
//
|
||||
// It's hard to argue that this is a bug: the result is sound and the
|
||||
// loss of precision is inconsequential---f is dead code, after all.
|
||||
// But unfortunately it limits the usefulness of the cross-check since
|
||||
// failures must be carefully analyzed. Ben Hardekopf suggests (in
|
||||
// personal correspondence) some approaches to mitigating it:
|
||||
//
|
||||
// If there is a node with an HVN points-to set that is a superset
|
||||
// of the NORM points-to set, then either it's a bug or it's a
|
||||
// result of this issue. If it's a result of this issue, then in
|
||||
// the offline constraint graph there should be a REF node inside
|
||||
// some cycle that reaches this node, and in the NORM solution the
|
||||
// pointer being dereferenced by that REF node should be the empty
|
||||
// set. If that isn't true then this is a bug. If it is true, then
|
||||
// you can further check that in the NORM solution the "extra"
|
||||
// points-to info in the HVN solution does in fact come from that
|
||||
// purported cycle (if it doesn't, then this is still a bug). If
|
||||
// you're doing the further check then you'll need to do it for
|
||||
// each "extra" points-to element in the HVN points-to set.
|
||||
//
|
||||
// There are probably ways to optimize these checks by taking
|
||||
// advantage of graph properties. For example, extraneous points-to
|
||||
// info will flow through the graph and end up in many
|
||||
// nodes. Rather than checking every node with extra info, you
|
||||
// could probably work out the "origin point" of the extra info and
|
||||
// just check there. Note that the check in the first bullet is
|
||||
// looking for soundness bugs, while the check in the second bullet
|
||||
// is looking for precision bugs; depending on your needs, you may
|
||||
// care more about one than the other.
|
||||
//
|
||||
// which we should evaluate. The cross-check is nonetheless invaluable
|
||||
// for all but one of the programs in the pointer_test suite.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
|
||||
"golang.org/x/tools/container/intsets"
|
||||
"golang.org/x/tools/go/types"
|
||||
)
|
||||
|
||||
// A peLabel is a pointer-equivalence label: two nodes with the same
|
||||
// peLabel have identical points-to solutions.
|
||||
//
|
||||
// The numbers are allocated consecutively like so:
|
||||
// 0 not a pointer
|
||||
// 1..N-1 addrConstraints (equals the constraint's .src field, hence sparse)
|
||||
// ... offsetAddr constraints
|
||||
// ... SCCs (with indirect nodes or multiple inputs)
|
||||
//
|
||||
// Each PE label denotes a set of pointers containing a single addr, a
|
||||
// single offsetAddr, or some set of other PE labels.
|
||||
//
|
||||
type peLabel int
|
||||
|
||||
type hvn struct {
|
||||
a *analysis
|
||||
N int // len(a.nodes) immediately after constraint generation
|
||||
log io.Writer // (optional) log of HVN lemmas
|
||||
onodes []*onode // nodes of the offline graph
|
||||
label peLabel // the next available PE label
|
||||
hvnLabel map[string]peLabel // hash-value numbering (PE label) for each set of onodeids
|
||||
stack []onodeid // DFS stack
|
||||
index int32 // next onode.index, from Tarjan's SCC algorithm
|
||||
|
||||
// For each distinct offsetAddrConstraint (src, offset) pair,
|
||||
// offsetAddrLabels records a unique PE label >= N.
|
||||
offsetAddrLabels map[offsetAddr]peLabel
|
||||
}
|
||||
|
||||
// The index of an node in the offline graph.
|
||||
// (Currently the first N align with the main nodes,
|
||||
// but this may change with HRU.)
|
||||
type onodeid uint32
|
||||
|
||||
// An onode is a node in the offline constraint graph.
|
||||
// (Where ambiguous, members of analysis.nodes are referred to as
|
||||
// "main graph" nodes.)
|
||||
//
|
||||
// Edges in the offline constraint graph (edges and implicit) point to
|
||||
// the source, i.e. against the flow of values: they are dependencies.
|
||||
// Implicit edges are used for SCC computation, but not for gathering
|
||||
// incoming labels.
|
||||
//
|
||||
type onode struct {
|
||||
rep onodeid // index of representative of SCC in offline constraint graph
|
||||
|
||||
edges intsets.Sparse // constraint edges X-->Y (this onode is X)
|
||||
implicit intsets.Sparse // implicit edges *X-->*Y (this onode is X)
|
||||
peLabels intsets.Sparse // set of peLabels are pointer-equivalent to this one
|
||||
indirect bool // node has points-to relations not represented in graph
|
||||
|
||||
// Tarjan's SCC algorithm
|
||||
index, lowlink int32 // Tarjan numbering
|
||||
scc int32 // -ve => on stack; 0 => unvisited; +ve => node is root of a found SCC
|
||||
}
|
||||
|
||||
type offsetAddr struct {
|
||||
ptr nodeid
|
||||
offset uint32
|
||||
}
|
||||
|
||||
// nextLabel issues the next unused pointer-equivalence label.
|
||||
func (h *hvn) nextLabel() peLabel {
|
||||
h.label++
|
||||
return h.label
|
||||
}
|
||||
|
||||
// ref(X) returns the index of the onode for *X.
|
||||
func (h *hvn) ref(id onodeid) onodeid {
|
||||
return id + onodeid(len(h.a.nodes))
|
||||
}
|
||||
|
||||
// hvn computes pointer-equivalence labels (peLabels) using the Hash-based
|
||||
// Value Numbering (HVN) algorithm described in Hardekopf & Lin, SAS'07.
|
||||
//
|
||||
func (a *analysis) hvn() {
|
||||
start("HVN")
|
||||
|
||||
if a.log != nil {
|
||||
fmt.Fprintf(a.log, "\n\n==== Pointer equivalence optimization\n\n")
|
||||
}
|
||||
|
||||
h := hvn{
|
||||
a: a,
|
||||
N: len(a.nodes),
|
||||
log: a.log,
|
||||
hvnLabel: make(map[string]peLabel),
|
||||
offsetAddrLabels: make(map[offsetAddr]peLabel),
|
||||
}
|
||||
|
||||
if h.log != nil {
|
||||
fmt.Fprintf(h.log, "\nCreating offline graph nodes...\n")
|
||||
}
|
||||
|
||||
// Create offline nodes. The first N nodes correspond to main
|
||||
// graph nodes; the next N are their corresponding ref() nodes.
|
||||
h.onodes = make([]*onode, 2*h.N)
|
||||
for id := range a.nodes {
|
||||
id := onodeid(id)
|
||||
h.onodes[id] = &onode{}
|
||||
h.onodes[h.ref(id)] = &onode{indirect: true}
|
||||
}
|
||||
|
||||
// Each node initially represents just itself.
|
||||
for id, o := range h.onodes {
|
||||
o.rep = onodeid(id)
|
||||
}
|
||||
|
||||
h.markIndirectNodes()
|
||||
|
||||
// Reserve the first N PE labels for addrConstraints.
|
||||
h.label = peLabel(h.N)
|
||||
|
||||
// Add offline constraint edges.
|
||||
if h.log != nil {
|
||||
fmt.Fprintf(h.log, "\nAdding offline graph edges...\n")
|
||||
}
|
||||
for _, c := range a.constraints {
|
||||
if debugHVNVerbose && h.log != nil {
|
||||
fmt.Fprintf(h.log, "; %s\n", c)
|
||||
}
|
||||
c.presolve(&h)
|
||||
}
|
||||
|
||||
// Find and collapse SCCs.
|
||||
if h.log != nil {
|
||||
fmt.Fprintf(h.log, "\nFinding SCCs...\n")
|
||||
}
|
||||
h.index = 1
|
||||
for id, o := range h.onodes {
|
||||
if id > 0 && o.index == 0 {
|
||||
// Start depth-first search at each unvisited node.
|
||||
h.visit(onodeid(id))
|
||||
}
|
||||
}
|
||||
|
||||
// Dump the solution
|
||||
// (NB: somewhat redundant with logging from simplify().)
|
||||
if debugHVNVerbose && h.log != nil {
|
||||
fmt.Fprintf(h.log, "\nPointer equivalences:\n")
|
||||
for id, o := range h.onodes {
|
||||
if id == 0 {
|
||||
continue
|
||||
}
|
||||
if id == int(h.N) {
|
||||
fmt.Fprintf(h.log, "---\n")
|
||||
}
|
||||
fmt.Fprintf(h.log, "o%d\t", id)
|
||||
if o.rep != onodeid(id) {
|
||||
fmt.Fprintf(h.log, "rep=o%d", o.rep)
|
||||
} else {
|
||||
fmt.Fprintf(h.log, "p%d", o.peLabels.Min())
|
||||
if o.indirect {
|
||||
fmt.Fprint(h.log, " indirect")
|
||||
}
|
||||
}
|
||||
fmt.Fprintln(h.log)
|
||||
}
|
||||
}
|
||||
|
||||
// Simplify the main constraint graph
|
||||
h.simplify()
|
||||
|
||||
a.showCounts()
|
||||
|
||||
stop("HVN")
|
||||
}
|
||||
|
||||
// ---- constraint-specific rules ----
|
||||
|
||||
// dst := &src
|
||||
func (c *addrConstraint) presolve(h *hvn) {
|
||||
// Each object (src) is an initial PE label.
|
||||
label := peLabel(c.src) // label < N
|
||||
if debugHVNVerbose && h.log != nil {
|
||||
// duplicate log messages are possible
|
||||
fmt.Fprintf(h.log, "\tcreate p%d: {&n%d}\n", label, c.src)
|
||||
}
|
||||
odst := onodeid(c.dst)
|
||||
osrc := onodeid(c.src)
|
||||
|
||||
// Assign dst this label.
|
||||
h.onodes[odst].peLabels.Insert(int(label))
|
||||
if debugHVNVerbose && h.log != nil {
|
||||
fmt.Fprintf(h.log, "\to%d has p%d\n", odst, label)
|
||||
}
|
||||
|
||||
h.addImplicitEdge(h.ref(odst), osrc) // *dst ~~> src.
|
||||
}
|
||||
|
||||
// dst = src
|
||||
func (c *copyConstraint) presolve(h *hvn) {
|
||||
odst := onodeid(c.dst)
|
||||
osrc := onodeid(c.src)
|
||||
h.addEdge(odst, osrc) // dst --> src
|
||||
h.addImplicitEdge(h.ref(odst), h.ref(osrc)) // *dst ~~> *src
|
||||
}
|
||||
|
||||
// dst = *src + offset
|
||||
func (c *loadConstraint) presolve(h *hvn) {
|
||||
odst := onodeid(c.dst)
|
||||
osrc := onodeid(c.src)
|
||||
if c.offset == 0 {
|
||||
h.addEdge(odst, h.ref(osrc)) // dst --> *src
|
||||
} else {
|
||||
// We don't interpret load-with-offset, e.g. results
|
||||
// of map value lookup, R-block of dynamic call, slice
|
||||
// copy/append, reflection.
|
||||
h.markIndirect(odst, "load with offset")
|
||||
}
|
||||
}
|
||||
|
||||
// *dst + offset = src
|
||||
func (c *storeConstraint) presolve(h *hvn) {
|
||||
odst := onodeid(c.dst)
|
||||
osrc := onodeid(c.src)
|
||||
if c.offset == 0 {
|
||||
h.onodes[h.ref(odst)].edges.Insert(int(osrc)) // *dst --> src
|
||||
if debugHVNVerbose && h.log != nil {
|
||||
fmt.Fprintf(h.log, "\to%d --> o%d\n", h.ref(odst), osrc)
|
||||
}
|
||||
} else {
|
||||
// We don't interpret store-with-offset.
|
||||
// See discussion of soundness at markIndirectNodes.
|
||||
}
|
||||
}
|
||||
|
||||
// dst = &src.offset
|
||||
func (c *offsetAddrConstraint) presolve(h *hvn) {
|
||||
// Give each distinct (addr, offset) pair a fresh PE label.
|
||||
// The cache performs CSE, effectively.
|
||||
key := offsetAddr{c.src, c.offset}
|
||||
label, ok := h.offsetAddrLabels[key]
|
||||
if !ok {
|
||||
label = h.nextLabel()
|
||||
h.offsetAddrLabels[key] = label
|
||||
if debugHVNVerbose && h.log != nil {
|
||||
fmt.Fprintf(h.log, "\tcreate p%d: {&n%d.#%d}\n",
|
||||
label, c.src, c.offset)
|
||||
}
|
||||
}
|
||||
|
||||
// Assign dst this label.
|
||||
h.onodes[c.dst].peLabels.Insert(int(label))
|
||||
if debugHVNVerbose && h.log != nil {
|
||||
fmt.Fprintf(h.log, "\to%d has p%d\n", c.dst, label)
|
||||
}
|
||||
}
|
||||
|
||||
// dst = src.(typ) where typ is an interface
|
||||
func (c *typeFilterConstraint) presolve(h *hvn) {
|
||||
h.markIndirect(onodeid(c.dst), "typeFilter result")
|
||||
}
|
||||
|
||||
// dst = src.(typ) where typ is concrete
|
||||
func (c *untagConstraint) presolve(h *hvn) {
|
||||
odst := onodeid(c.dst)
|
||||
for end := odst + onodeid(h.a.sizeof(c.typ)); odst < end; odst++ {
|
||||
h.markIndirect(odst, "untag result")
|
||||
}
|
||||
}
|
||||
|
||||
// dst = src.method(c.params...)
|
||||
func (c *invokeConstraint) presolve(h *hvn) {
|
||||
// All methods are address-taken functions, so
|
||||
// their formal P-blocks were already marked indirect.
|
||||
|
||||
// Mark the caller's targets node as indirect.
|
||||
sig := c.method.Type().(*types.Signature)
|
||||
id := c.params
|
||||
h.markIndirect(onodeid(c.params), "invoke targets node")
|
||||
id++
|
||||
|
||||
id += nodeid(h.a.sizeof(sig.Params()))
|
||||
|
||||
// Mark the caller's R-block as indirect.
|
||||
end := id + nodeid(h.a.sizeof(sig.Results()))
|
||||
for id < end {
|
||||
h.markIndirect(onodeid(id), "invoke R-block")
|
||||
id++
|
||||
}
|
||||
}
|
||||
|
||||
// markIndirectNodes marks as indirect nodes whose points-to relations
|
||||
// are not entirely captured by the offline graph, including:
|
||||
//
|
||||
// (a) All address-taken nodes (including the following nodes within
|
||||
// the same object). This is described in the paper.
|
||||
//
|
||||
// The most subtle cause of indirect nodes is the generation of
|
||||
// store-with-offset constraints since the offline graph doesn't
|
||||
// represent them. A global audit of constraint generation reveals the
|
||||
// following uses of store-with-offset:
|
||||
//
|
||||
// (b) genDynamicCall, for P-blocks of dynamically called functions,
|
||||
// to which dynamic copy edges will be added to them during
|
||||
// solving: from storeConstraint for standalone functions,
|
||||
// and from invokeConstraint for methods.
|
||||
// All such P-blocks must be marked indirect.
|
||||
// (c) MakeUpdate, to update the value part of a map object.
|
||||
// All MakeMap objects's value parts must be marked indirect.
|
||||
// (d) copyElems, to update the destination array.
|
||||
// All array elements must be marked indirect.
|
||||
//
|
||||
// Not all indirect marking happens here. ref() nodes are marked
|
||||
// indirect at construction, and each constraint's presolve() method may
|
||||
// mark additional nodes.
|
||||
//
|
||||
func (h *hvn) markIndirectNodes() {
|
||||
// (a) all address-taken nodes, plus all nodes following them
|
||||
// within the same object, since these may be indirectly
|
||||
// stored or address-taken.
|
||||
for _, c := range h.a.constraints {
|
||||
if c, ok := c.(*addrConstraint); ok {
|
||||
start := h.a.enclosingObj(c.src)
|
||||
end := start + nodeid(h.a.nodes[start].obj.size)
|
||||
for id := c.src; id < end; id++ {
|
||||
h.markIndirect(onodeid(id), "A-T object")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// (b) P-blocks of all address-taken functions.
|
||||
for id := 0; id < h.N; id++ {
|
||||
obj := h.a.nodes[id].obj
|
||||
|
||||
// TODO(adonovan): opt: if obj.cgn.fn is a method and
|
||||
// obj.cgn is not its shared contour, this is an
|
||||
// "inlined" static method call. We needn't consider it
|
||||
// address-taken since no invokeConstraint will affect it.
|
||||
|
||||
if obj != nil && obj.flags&otFunction != 0 && h.a.atFuncs[obj.cgn.fn] {
|
||||
// address-taken function
|
||||
if debugHVNVerbose && h.log != nil {
|
||||
fmt.Fprintf(h.log, "n%d is address-taken: %s\n", id, obj.cgn.fn)
|
||||
}
|
||||
h.markIndirect(onodeid(id), "A-T func identity")
|
||||
id++
|
||||
sig := obj.cgn.fn.Signature
|
||||
psize := h.a.sizeof(sig.Params())
|
||||
if sig.Recv() != nil {
|
||||
psize += h.a.sizeof(sig.Recv().Type())
|
||||
}
|
||||
for end := id + int(psize); id < end; id++ {
|
||||
h.markIndirect(onodeid(id), "A-T func P-block")
|
||||
}
|
||||
id--
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// (c) all map objects' value fields.
|
||||
for _, id := range h.a.mapValues {
|
||||
h.markIndirect(onodeid(id), "makemap.value")
|
||||
}
|
||||
|
||||
// (d) all array element objects.
|
||||
// TODO(adonovan): opt: can we do better?
|
||||
for id := 0; id < h.N; id++ {
|
||||
// Identity node for an object of array type?
|
||||
if tArray, ok := h.a.nodes[id].typ.(*types.Array); ok {
|
||||
// Mark the array element nodes indirect.
|
||||
// (Skip past the identity field.)
|
||||
for _ = range h.a.flatten(tArray.Elem()) {
|
||||
id++
|
||||
h.markIndirect(onodeid(id), "array elem")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *hvn) markIndirect(oid onodeid, comment string) {
|
||||
h.onodes[oid].indirect = true
|
||||
if debugHVNVerbose && h.log != nil {
|
||||
fmt.Fprintf(h.log, "\to%d is indirect: %s\n", oid, comment)
|
||||
}
|
||||
}
|
||||
|
||||
// Adds an edge dst-->src.
|
||||
// Note the unusual convention: edges are dependency (contraflow) edges.
|
||||
func (h *hvn) addEdge(odst, osrc onodeid) {
|
||||
h.onodes[odst].edges.Insert(int(osrc))
|
||||
if debugHVNVerbose && h.log != nil {
|
||||
fmt.Fprintf(h.log, "\to%d --> o%d\n", odst, osrc)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *hvn) addImplicitEdge(odst, osrc onodeid) {
|
||||
h.onodes[odst].implicit.Insert(int(osrc))
|
||||
if debugHVNVerbose && h.log != nil {
|
||||
fmt.Fprintf(h.log, "\to%d ~~> o%d\n", odst, osrc)
|
||||
}
|
||||
}
|
||||
|
||||
// visit implements the depth-first search of Tarjan's SCC algorithm.
|
||||
// Precondition: x is canonical.
|
||||
func (h *hvn) visit(x onodeid) {
|
||||
h.checkCanonical(x)
|
||||
xo := h.onodes[x]
|
||||
xo.index = h.index
|
||||
xo.lowlink = h.index
|
||||
h.index++
|
||||
|
||||
h.stack = append(h.stack, x) // push
|
||||
assert(xo.scc == 0, "node revisited")
|
||||
xo.scc = -1
|
||||
|
||||
var deps []int
|
||||
deps = xo.edges.AppendTo(deps)
|
||||
deps = xo.implicit.AppendTo(deps)
|
||||
|
||||
for _, y := range deps {
|
||||
// Loop invariant: x is canonical.
|
||||
|
||||
y := h.find(onodeid(y))
|
||||
|
||||
if x == y {
|
||||
continue // nodes already coalesced
|
||||
}
|
||||
|
||||
xo := h.onodes[x]
|
||||
yo := h.onodes[y]
|
||||
|
||||
switch {
|
||||
case yo.scc > 0:
|
||||
// y is already a collapsed SCC
|
||||
|
||||
case yo.scc < 0:
|
||||
// y is on the stack, and thus in the current SCC.
|
||||
if yo.index < xo.lowlink {
|
||||
xo.lowlink = yo.index
|
||||
}
|
||||
|
||||
default:
|
||||
// y is unvisited; visit it now.
|
||||
h.visit(y)
|
||||
// Note: x and y are now non-canonical.
|
||||
|
||||
x = h.find(onodeid(x))
|
||||
|
||||
if yo.lowlink < xo.lowlink {
|
||||
xo.lowlink = yo.lowlink
|
||||
}
|
||||
}
|
||||
}
|
||||
h.checkCanonical(x)
|
||||
|
||||
// Is x the root of an SCC?
|
||||
if xo.lowlink == xo.index {
|
||||
// Coalesce all nodes in the SCC.
|
||||
if debugHVNVerbose && h.log != nil {
|
||||
fmt.Fprintf(h.log, "scc o%d\n", x)
|
||||
}
|
||||
for {
|
||||
// Pop y from stack.
|
||||
i := len(h.stack) - 1
|
||||
y := h.stack[i]
|
||||
h.stack = h.stack[:i]
|
||||
|
||||
h.checkCanonical(x)
|
||||
xo := h.onodes[x]
|
||||
h.checkCanonical(y)
|
||||
yo := h.onodes[y]
|
||||
|
||||
if xo == yo {
|
||||
// SCC is complete.
|
||||
xo.scc = 1
|
||||
h.labelSCC(x)
|
||||
break
|
||||
}
|
||||
h.coalesce(x, y)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Precondition: x is canonical.
|
||||
func (h *hvn) labelSCC(x onodeid) {
|
||||
h.checkCanonical(x)
|
||||
xo := h.onodes[x]
|
||||
xpe := &xo.peLabels
|
||||
|
||||
// All indirect nodes get new labels.
|
||||
if xo.indirect {
|
||||
label := h.nextLabel()
|
||||
if debugHVNVerbose && h.log != nil {
|
||||
fmt.Fprintf(h.log, "\tcreate p%d: indirect SCC\n", label)
|
||||
fmt.Fprintf(h.log, "\to%d has p%d\n", x, label)
|
||||
}
|
||||
|
||||
// Remove pre-labeling, in case a direct pre-labeled node was
|
||||
// merged with an indirect one.
|
||||
xpe.Clear()
|
||||
xpe.Insert(int(label))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Invariant: all peLabels sets are non-empty.
|
||||
// Those that are logically empty contain zero as their sole element.
|
||||
// No other sets contains zero.
|
||||
|
||||
// Find all labels coming in to the coalesced SCC node.
|
||||
for _, y := range xo.edges.AppendTo(nil) {
|
||||
y := h.find(onodeid(y))
|
||||
if y == x {
|
||||
continue // already coalesced
|
||||
}
|
||||
ype := &h.onodes[y].peLabels
|
||||
if debugHVNVerbose && h.log != nil {
|
||||
fmt.Fprintf(h.log, "\tedge from o%d = %s\n", y, ype)
|
||||
}
|
||||
|
||||
if ype.IsEmpty() {
|
||||
if debugHVNVerbose && h.log != nil {
|
||||
fmt.Fprintf(h.log, "\tnode has no PE label\n")
|
||||
}
|
||||
}
|
||||
assert(!ype.IsEmpty(), "incoming node has no PE label")
|
||||
|
||||
if ype.Has(0) {
|
||||
// {0} represents a non-pointer.
|
||||
assert(ype.Len() == 1, "PE set contains {0, ...}")
|
||||
} else {
|
||||
xpe.UnionWith(ype)
|
||||
}
|
||||
}
|
||||
|
||||
switch xpe.Len() {
|
||||
case 0:
|
||||
// SCC has no incoming non-zero PE labels: it is a non-pointer.
|
||||
xpe.Insert(0)
|
||||
|
||||
case 1:
|
||||
// already a singleton
|
||||
|
||||
default:
|
||||
// SCC has multiple incoming non-zero PE labels.
|
||||
// Find the canonical label representing this set.
|
||||
// We use String() as a fingerprint consistent with Equals().
|
||||
key := xpe.String()
|
||||
label, ok := h.hvnLabel[key]
|
||||
if !ok {
|
||||
label = h.nextLabel()
|
||||
if debugHVNVerbose && h.log != nil {
|
||||
fmt.Fprintf(h.log, "\tcreate p%d: union %s\n", label, xpe.String())
|
||||
}
|
||||
h.hvnLabel[key] = label
|
||||
}
|
||||
xpe.Clear()
|
||||
xpe.Insert(int(label))
|
||||
}
|
||||
|
||||
if debugHVNVerbose && h.log != nil {
|
||||
fmt.Fprintf(h.log, "\to%d has p%d\n", x, xpe.Min())
|
||||
}
|
||||
}
|
||||
|
||||
// coalesce combines two nodes in the offline constraint graph.
|
||||
// Precondition: x and y are canonical.
|
||||
func (h *hvn) coalesce(x, y onodeid) {
|
||||
xo := h.onodes[x]
|
||||
yo := h.onodes[y]
|
||||
|
||||
// x becomes y's canonical representative.
|
||||
yo.rep = x
|
||||
|
||||
if debugHVNVerbose && h.log != nil {
|
||||
fmt.Fprintf(h.log, "\tcoalesce o%d into o%d\n", y, x)
|
||||
}
|
||||
|
||||
// x accumulates y's edges.
|
||||
xo.edges.UnionWith(&yo.edges)
|
||||
yo.edges.Clear()
|
||||
|
||||
// x accumulates y's implicit edges.
|
||||
xo.implicit.UnionWith(&yo.implicit)
|
||||
yo.implicit.Clear()
|
||||
|
||||
// x accumulates y's pointer-equivalence labels.
|
||||
xo.peLabels.UnionWith(&yo.peLabels)
|
||||
yo.peLabels.Clear()
|
||||
|
||||
// x accumulates y's indirect flag.
|
||||
if yo.indirect {
|
||||
xo.indirect = true
|
||||
}
|
||||
}
|
||||
|
||||
// simplify computes a degenerate renumbering of nodeids from the PE
|
||||
// labels assigned by the hvn, and uses it to simplify the main
|
||||
// constraint graph, eliminating non-pointer nodes and duplicate
|
||||
// constraints.
|
||||
//
|
||||
func (h *hvn) simplify() {
|
||||
// canon maps each peLabel to its canonical main node.
|
||||
canon := make([]nodeid, h.label)
|
||||
for i := range canon {
|
||||
canon[i] = nodeid(h.N) // indicates "unset"
|
||||
}
|
||||
|
||||
// mapping maps each main node index to the index of the canonical node.
|
||||
mapping := make([]nodeid, len(h.a.nodes))
|
||||
|
||||
for id := range h.a.nodes {
|
||||
id := nodeid(id)
|
||||
if id == 0 {
|
||||
canon[0] = 0
|
||||
mapping[0] = 0
|
||||
continue
|
||||
}
|
||||
oid := h.find(onodeid(id))
|
||||
peLabels := &h.onodes[oid].peLabels
|
||||
assert(peLabels.Len() == 1, "PE class is not a singleton")
|
||||
label := peLabel(peLabels.Min())
|
||||
|
||||
canonId := canon[label]
|
||||
if canonId == nodeid(h.N) {
|
||||
// id becomes the representative of the PE label.
|
||||
canonId = id
|
||||
canon[label] = canonId
|
||||
|
||||
if h.a.log != nil {
|
||||
fmt.Fprintf(h.a.log, "\tpts(n%d) is canonical : \t(%s)\n",
|
||||
id, h.a.nodes[id].typ)
|
||||
}
|
||||
|
||||
} else {
|
||||
// Link the solver states for the two nodes.
|
||||
assert(h.a.nodes[canonId].solve != nil, "missing solver state")
|
||||
h.a.nodes[id].solve = h.a.nodes[canonId].solve
|
||||
|
||||
if h.a.log != nil {
|
||||
// TODO(adonovan): debug: reorganize the log so it prints
|
||||
// one line:
|
||||
// pe y = x1, ..., xn
|
||||
// for each canonical y. Requires allocation.
|
||||
fmt.Fprintf(h.a.log, "\tpts(n%d) = pts(n%d) : %s\n",
|
||||
id, canonId, h.a.nodes[id].typ)
|
||||
}
|
||||
}
|
||||
|
||||
mapping[id] = canonId
|
||||
}
|
||||
|
||||
// Renumber the constraints, eliminate duplicates, and eliminate
|
||||
// any containing non-pointers (n0).
|
||||
addrs := make(map[addrConstraint]bool)
|
||||
copys := make(map[copyConstraint]bool)
|
||||
loads := make(map[loadConstraint]bool)
|
||||
stores := make(map[storeConstraint]bool)
|
||||
offsetAddrs := make(map[offsetAddrConstraint]bool)
|
||||
untags := make(map[untagConstraint]bool)
|
||||
typeFilters := make(map[typeFilterConstraint]bool)
|
||||
invokes := make(map[invokeConstraint]bool)
|
||||
|
||||
nbefore := len(h.a.constraints)
|
||||
cc := h.a.constraints[:0] // in-situ compaction
|
||||
for _, c := range h.a.constraints {
|
||||
// Renumber.
|
||||
switch c := c.(type) {
|
||||
case *addrConstraint:
|
||||
// Don't renumber c.src since it is the label of
|
||||
// an addressable object and will appear in PT sets.
|
||||
c.dst = mapping[c.dst]
|
||||
default:
|
||||
c.renumber(mapping)
|
||||
}
|
||||
|
||||
if c.ptr() == 0 {
|
||||
continue // skip: constraint attached to non-pointer
|
||||
}
|
||||
|
||||
var dup bool
|
||||
switch c := c.(type) {
|
||||
case *addrConstraint:
|
||||
_, dup = addrs[*c]
|
||||
addrs[*c] = true
|
||||
|
||||
case *copyConstraint:
|
||||
if c.src == c.dst {
|
||||
continue // skip degenerate copies
|
||||
}
|
||||
if c.src == 0 {
|
||||
continue // skip copy from non-pointer
|
||||
}
|
||||
_, dup = copys[*c]
|
||||
copys[*c] = true
|
||||
|
||||
case *loadConstraint:
|
||||
if c.src == 0 {
|
||||
continue // skip load from non-pointer
|
||||
}
|
||||
_, dup = loads[*c]
|
||||
loads[*c] = true
|
||||
|
||||
case *storeConstraint:
|
||||
if c.src == 0 {
|
||||
continue // skip store from non-pointer
|
||||
}
|
||||
_, dup = stores[*c]
|
||||
stores[*c] = true
|
||||
|
||||
case *offsetAddrConstraint:
|
||||
if c.src == 0 {
|
||||
continue // skip offset from non-pointer
|
||||
}
|
||||
_, dup = offsetAddrs[*c]
|
||||
offsetAddrs[*c] = true
|
||||
|
||||
case *untagConstraint:
|
||||
if c.src == 0 {
|
||||
continue // skip untag of non-pointer
|
||||
}
|
||||
_, dup = untags[*c]
|
||||
untags[*c] = true
|
||||
|
||||
case *typeFilterConstraint:
|
||||
if c.src == 0 {
|
||||
continue // skip filter of non-pointer
|
||||
}
|
||||
_, dup = typeFilters[*c]
|
||||
typeFilters[*c] = true
|
||||
|
||||
case *invokeConstraint:
|
||||
if c.params == 0 {
|
||||
panic("non-pointer invoke.params")
|
||||
}
|
||||
if c.iface == 0 {
|
||||
continue // skip invoke on non-pointer
|
||||
}
|
||||
_, dup = invokes[*c]
|
||||
invokes[*c] = true
|
||||
|
||||
default:
|
||||
// We don't bother de-duping advanced constraints
|
||||
// (e.g. reflection) since they are uncommon.
|
||||
|
||||
// Eliminate constraints containing non-pointer nodeids.
|
||||
//
|
||||
// We use reflection to find the fields to avoid
|
||||
// adding yet another method to constraint.
|
||||
//
|
||||
// TODO(adonovan): experiment with a constraint
|
||||
// method that returns a slice of pointers to
|
||||
// nodeids fields to enable uniform iteration;
|
||||
// the renumber() method could be removed and
|
||||
// implemented using the new one.
|
||||
//
|
||||
// TODO(adonovan): opt: this is unsound since
|
||||
// some constraints still have an effect if one
|
||||
// of the operands is zero: rVCall, rVMapIndex,
|
||||
// rvSetMapIndex. Handle them specially.
|
||||
rtNodeid := reflect.TypeOf(nodeid(0))
|
||||
x := reflect.ValueOf(c).Elem()
|
||||
for i, nf := 0, x.NumField(); i < nf; i++ {
|
||||
f := x.Field(i)
|
||||
if f.Type() == rtNodeid {
|
||||
if f.Uint() == 0 {
|
||||
dup = true // skip it
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if dup {
|
||||
continue // skip duplicates
|
||||
}
|
||||
|
||||
cc = append(cc, c)
|
||||
}
|
||||
h.a.constraints = cc
|
||||
|
||||
if h.log != nil {
|
||||
fmt.Fprintf(h.log, "#constraints: was %d, now %d\n", nbefore, len(h.a.constraints))
|
||||
}
|
||||
}
|
||||
|
||||
// find returns the canonical onodeid for x.
|
||||
// (The onodes form a disjoint set forest.)
|
||||
func (h *hvn) find(x onodeid) onodeid {
|
||||
// TODO(adonovan): opt: this is a CPU hotspot. Try "union by rank".
|
||||
xo := h.onodes[x]
|
||||
rep := xo.rep
|
||||
if rep != x {
|
||||
rep = h.find(rep) // simple path compression
|
||||
xo.rep = rep
|
||||
}
|
||||
return rep
|
||||
}
|
||||
|
||||
func (h *hvn) checkCanonical(x onodeid) {
|
||||
if debugHVN {
|
||||
assert(x == h.find(x), "not canonical")
|
||||
}
|
||||
}
|
||||
|
||||
func assert(p bool, msg string) {
|
||||
if debugHVN && !p {
|
||||
panic("assertion failed: " + msg)
|
||||
}
|
||||
}
|
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
package pointer
|
||||
|
||||
// This package defines the treatment of intrinsics, i.e. library
|
||||
|
382
go/pointer/intrinsics14.go
Normal file
382
go/pointer/intrinsics14.go
Normal file
@ -0,0 +1,382 @@
|
||||
// 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.
|
||||
|
||||
// +build !go1.5
|
||||
|
||||
package pointer
|
||||
|
||||
// This package defines the treatment of intrinsics, i.e. library
|
||||
// functions requiring special analytical treatment.
|
||||
//
|
||||
// Most of these are C or assembly functions, but even some Go
|
||||
// functions require may special treatment if the analysis completely
|
||||
// replaces the implementation of an API such as reflection.
|
||||
|
||||
// TODO(adonovan): support a means of writing analytic summaries in
|
||||
// the target code, so that users can summarise the effects of their
|
||||
// own C functions using a snippet of Go.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/tools/go/types"
|
||||
)
|
||||
|
||||
// Instances of 'intrinsic' generate analysis constraints for calls to
|
||||
// intrinsic functions.
|
||||
// Implementations may exploit information from the calling site
|
||||
// via cgn.callersite; for shared contours this is nil.
|
||||
type intrinsic func(a *analysis, cgn *cgnode)
|
||||
|
||||
// Initialized in explicit init() to defeat (spurious) initialization
|
||||
// cycle error.
|
||||
var intrinsicsByName = make(map[string]intrinsic)
|
||||
|
||||
func init() {
|
||||
// Key strings are from Function.String().
|
||||
// That little dot ۰ is an Arabic zero numeral (U+06F0),
|
||||
// categories [Nd].
|
||||
for name, fn := range map[string]intrinsic{
|
||||
// Other packages.
|
||||
"bytes.Equal": ext۰NoEffect,
|
||||
"bytes.IndexByte": ext۰NoEffect,
|
||||
"crypto/aes.decryptBlockAsm": ext۰NoEffect,
|
||||
"crypto/aes.encryptBlockAsm": ext۰NoEffect,
|
||||
"crypto/aes.expandKeyAsm": ext۰NoEffect,
|
||||
"crypto/aes.hasAsm": ext۰NoEffect,
|
||||
"crypto/md5.block": ext۰NoEffect,
|
||||
"crypto/rc4.xorKeyStream": ext۰NoEffect,
|
||||
"crypto/sha1.block": ext۰NoEffect,
|
||||
"crypto/sha256.block": ext۰NoEffect,
|
||||
"hash/crc32.castagnoliSSE42": ext۰NoEffect,
|
||||
"hash/crc32.haveSSE42": ext۰NoEffect,
|
||||
"math.Abs": ext۰NoEffect,
|
||||
"math.Acos": ext۰NoEffect,
|
||||
"math.Asin": ext۰NoEffect,
|
||||
"math.Atan": ext۰NoEffect,
|
||||
"math.Atan2": ext۰NoEffect,
|
||||
"math.Ceil": ext۰NoEffect,
|
||||
"math.Cos": ext۰NoEffect,
|
||||
"math.Dim": ext۰NoEffect,
|
||||
"math.Exp": ext۰NoEffect,
|
||||
"math.Exp2": ext۰NoEffect,
|
||||
"math.Expm1": ext۰NoEffect,
|
||||
"math.Float32bits": ext۰NoEffect,
|
||||
"math.Float32frombits": ext۰NoEffect,
|
||||
"math.Float64bits": ext۰NoEffect,
|
||||
"math.Float64frombits": ext۰NoEffect,
|
||||
"math.Floor": ext۰NoEffect,
|
||||
"math.Frexp": ext۰NoEffect,
|
||||
"math.Hypot": ext۰NoEffect,
|
||||
"math.Ldexp": ext۰NoEffect,
|
||||
"math.Log": ext۰NoEffect,
|
||||
"math.Log10": ext۰NoEffect,
|
||||
"math.Log1p": ext۰NoEffect,
|
||||
"math.Log2": ext۰NoEffect,
|
||||
"math.Max": ext۰NoEffect,
|
||||
"math.Min": ext۰NoEffect,
|
||||
"math.Mod": ext۰NoEffect,
|
||||
"math.Modf": ext۰NoEffect,
|
||||
"math.Remainder": ext۰NoEffect,
|
||||
"math.Sin": ext۰NoEffect,
|
||||
"math.Sincos": ext۰NoEffect,
|
||||
"math.Sqrt": ext۰NoEffect,
|
||||
"math.Tan": ext۰NoEffect,
|
||||
"math.Trunc": ext۰NoEffect,
|
||||
"math/big.addMulVVW": ext۰NoEffect,
|
||||
"math/big.addVV": ext۰NoEffect,
|
||||
"math/big.addVW": ext۰NoEffect,
|
||||
"math/big.bitLen": ext۰NoEffect,
|
||||
"math/big.divWVW": ext۰NoEffect,
|
||||
"math/big.divWW": ext۰NoEffect,
|
||||
"math/big.mulAddVWW": ext۰NoEffect,
|
||||
"math/big.mulWW": ext۰NoEffect,
|
||||
"math/big.shlVU": ext۰NoEffect,
|
||||
"math/big.shrVU": ext۰NoEffect,
|
||||
"math/big.subVV": ext۰NoEffect,
|
||||
"math/big.subVW": ext۰NoEffect,
|
||||
"net.runtime_Semacquire": ext۰NoEffect,
|
||||
"net.runtime_Semrelease": ext۰NoEffect,
|
||||
"net.runtime_pollClose": ext۰NoEffect,
|
||||
"net.runtime_pollOpen": ext۰NoEffect,
|
||||
"net.runtime_pollReset": ext۰NoEffect,
|
||||
"net.runtime_pollServerInit": ext۰NoEffect,
|
||||
"net.runtime_pollSetDeadline": ext۰NoEffect,
|
||||
"net.runtime_pollUnblock": ext۰NoEffect,
|
||||
"net.runtime_pollWait": ext۰NoEffect,
|
||||
"net.runtime_pollWaitCanceled": ext۰NoEffect,
|
||||
"os.epipecheck": ext۰NoEffect,
|
||||
"runtime.BlockProfile": ext۰NoEffect,
|
||||
"runtime.Breakpoint": ext۰NoEffect,
|
||||
"runtime.CPUProfile": ext۰NoEffect, // good enough
|
||||
"runtime.Caller": ext۰NoEffect,
|
||||
"runtime.Callers": ext۰NoEffect, // good enough
|
||||
"runtime.FuncForPC": ext۰NoEffect,
|
||||
"runtime.GC": ext۰NoEffect,
|
||||
"runtime.GOMAXPROCS": ext۰NoEffect,
|
||||
"runtime.Goexit": ext۰NoEffect,
|
||||
"runtime.GoroutineProfile": ext۰NoEffect,
|
||||
"runtime.Gosched": ext۰NoEffect,
|
||||
"runtime.MemProfile": ext۰NoEffect,
|
||||
"runtime.NumCPU": ext۰NoEffect,
|
||||
"runtime.NumGoroutine": ext۰NoEffect,
|
||||
"runtime.ReadMemStats": ext۰NoEffect,
|
||||
"runtime.SetBlockProfileRate": ext۰NoEffect,
|
||||
"runtime.SetCPUProfileRate": ext۰NoEffect,
|
||||
"runtime.SetFinalizer": ext۰runtime۰SetFinalizer,
|
||||
"runtime.Stack": ext۰NoEffect,
|
||||
"runtime.ThreadCreateProfile": ext۰NoEffect,
|
||||
"runtime.cstringToGo": ext۰NoEffect,
|
||||
"runtime.funcentry_go": ext۰NoEffect,
|
||||
"runtime.funcline_go": ext۰NoEffect,
|
||||
"runtime.funcname_go": ext۰NoEffect,
|
||||
"runtime.getgoroot": ext۰NoEffect,
|
||||
"runtime/pprof.runtime_cyclesPerSecond": ext۰NoEffect,
|
||||
"strings.IndexByte": ext۰NoEffect,
|
||||
"sync.runtime_Semacquire": ext۰NoEffect,
|
||||
"sync.runtime_Semrelease": ext۰NoEffect,
|
||||
"sync.runtime_Syncsemacquire": ext۰NoEffect,
|
||||
"sync.runtime_Syncsemcheck": ext۰NoEffect,
|
||||
"sync.runtime_Syncsemrelease": ext۰NoEffect,
|
||||
"sync.runtime_procPin": ext۰NoEffect,
|
||||
"sync.runtime_procUnpin": ext۰NoEffect,
|
||||
"sync.runtime_registerPool": ext۰NoEffect,
|
||||
"sync/atomic.AddInt32": ext۰NoEffect,
|
||||
"sync/atomic.AddInt64": ext۰NoEffect,
|
||||
"sync/atomic.AddUint32": ext۰NoEffect,
|
||||
"sync/atomic.AddUint64": ext۰NoEffect,
|
||||
"sync/atomic.AddUintptr": ext۰NoEffect,
|
||||
"sync/atomic.CompareAndSwapInt32": ext۰NoEffect,
|
||||
"sync/atomic.CompareAndSwapUint32": ext۰NoEffect,
|
||||
"sync/atomic.CompareAndSwapUint64": ext۰NoEffect,
|
||||
"sync/atomic.CompareAndSwapUintptr": ext۰NoEffect,
|
||||
"sync/atomic.LoadInt32": ext۰NoEffect,
|
||||
"sync/atomic.LoadInt64": ext۰NoEffect,
|
||||
"sync/atomic.LoadPointer": ext۰NoEffect, // ignore unsafe.Pointers
|
||||
"sync/atomic.LoadUint32": ext۰NoEffect,
|
||||
"sync/atomic.LoadUint64": ext۰NoEffect,
|
||||
"sync/atomic.LoadUintptr": ext۰NoEffect,
|
||||
"sync/atomic.StoreInt32": ext۰NoEffect,
|
||||
"sync/atomic.StorePointer": ext۰NoEffect, // ignore unsafe.Pointers
|
||||
"sync/atomic.StoreUint32": ext۰NoEffect,
|
||||
"sync/atomic.StoreUintptr": ext۰NoEffect,
|
||||
"syscall.Close": ext۰NoEffect,
|
||||
"syscall.Exit": ext۰NoEffect,
|
||||
"syscall.Getpid": ext۰NoEffect,
|
||||
"syscall.Getwd": ext۰NoEffect,
|
||||
"syscall.Kill": ext۰NoEffect,
|
||||
"syscall.RawSyscall": ext۰NoEffect,
|
||||
"syscall.RawSyscall6": ext۰NoEffect,
|
||||
"syscall.Syscall": ext۰NoEffect,
|
||||
"syscall.Syscall6": ext۰NoEffect,
|
||||
"syscall.runtime_AfterFork": ext۰NoEffect,
|
||||
"syscall.runtime_BeforeFork": ext۰NoEffect,
|
||||
"syscall.setenv_c": ext۰NoEffect,
|
||||
"time.Sleep": ext۰NoEffect,
|
||||
"time.now": ext۰NoEffect,
|
||||
"time.startTimer": ext۰time۰startTimer,
|
||||
"time.stopTimer": ext۰NoEffect,
|
||||
} {
|
||||
intrinsicsByName[name] = fn
|
||||
}
|
||||
}
|
||||
|
||||
// findIntrinsic returns the constraint generation function for an
|
||||
// intrinsic function fn, or nil if the function should be handled normally.
|
||||
//
|
||||
func (a *analysis) findIntrinsic(fn *ssa.Function) intrinsic {
|
||||
// Consult the *Function-keyed cache.
|
||||
// A cached nil indicates a normal non-intrinsic function.
|
||||
impl, ok := a.intrinsics[fn]
|
||||
if !ok {
|
||||
impl = intrinsicsByName[fn.String()] // may be nil
|
||||
|
||||
if a.isReflect(fn) {
|
||||
if !a.config.Reflection {
|
||||
impl = ext۰NoEffect // reflection disabled
|
||||
} else if impl == nil {
|
||||
// Ensure all "reflect" code is treated intrinsically.
|
||||
impl = ext۰NotYetImplemented
|
||||
}
|
||||
}
|
||||
|
||||
a.intrinsics[fn] = impl
|
||||
}
|
||||
return impl
|
||||
}
|
||||
|
||||
// isReflect reports whether fn belongs to the "reflect" package.
|
||||
func (a *analysis) isReflect(fn *ssa.Function) bool {
|
||||
if a.reflectValueObj == nil {
|
||||
return false // "reflect" package not loaded
|
||||
}
|
||||
reflectPackage := a.reflectValueObj.Pkg()
|
||||
if fn.Pkg != nil && fn.Pkg.Pkg == reflectPackage {
|
||||
return true
|
||||
}
|
||||
// Synthetic wrappers have a nil Pkg, so they slip through the
|
||||
// previous check. Check the receiver package.
|
||||
// TODO(adonovan): should synthetic wrappers have a non-nil Pkg?
|
||||
if recv := fn.Signature.Recv(); recv != nil {
|
||||
if named, ok := deref(recv.Type()).(*types.Named); ok {
|
||||
if named.Obj().Pkg() == reflectPackage {
|
||||
return true // e.g. wrapper of (reflect.Value).f
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// A trivial intrinsic suitable for any function that does not:
|
||||
// 1) induce aliases between its arguments or any global variables;
|
||||
// 2) call any functions; or
|
||||
// 3) create any labels.
|
||||
//
|
||||
// Many intrinsics (such as CompareAndSwapInt32) have a fourth kind of
|
||||
// effect: loading or storing through a pointer. Though these could
|
||||
// be significant, we deliberately ignore them because they are
|
||||
// generally not worth the effort.
|
||||
//
|
||||
// We sometimes violate condition #3 if the function creates only
|
||||
// non-function labels, as the control-flow graph is still sound.
|
||||
//
|
||||
func ext۰NoEffect(a *analysis, cgn *cgnode) {}
|
||||
|
||||
func ext۰NotYetImplemented(a *analysis, cgn *cgnode) {
|
||||
fn := cgn.fn
|
||||
a.warnf(fn.Pos(), "unsound: intrinsic treatment of %s not yet implemented", fn)
|
||||
}
|
||||
|
||||
// ---------- func runtime.SetFinalizer(x, f interface{}) ----------
|
||||
|
||||
// runtime.SetFinalizer(x, f)
|
||||
type runtimeSetFinalizerConstraint struct {
|
||||
targets nodeid // (indirect)
|
||||
f nodeid // (ptr)
|
||||
x nodeid
|
||||
}
|
||||
|
||||
func (c *runtimeSetFinalizerConstraint) ptr() nodeid { return c.f }
|
||||
func (c *runtimeSetFinalizerConstraint) presolve(h *hvn) {
|
||||
h.markIndirect(onodeid(c.targets), "SetFinalizer.targets")
|
||||
}
|
||||
func (c *runtimeSetFinalizerConstraint) renumber(mapping []nodeid) {
|
||||
c.targets = mapping[c.targets]
|
||||
c.f = mapping[c.f]
|
||||
c.x = mapping[c.x]
|
||||
}
|
||||
|
||||
func (c *runtimeSetFinalizerConstraint) String() string {
|
||||
return fmt.Sprintf("runtime.SetFinalizer(n%d, n%d)", c.x, c.f)
|
||||
}
|
||||
|
||||
func (c *runtimeSetFinalizerConstraint) solve(a *analysis, delta *nodeset) {
|
||||
for _, fObj := range delta.AppendTo(a.deltaSpace) {
|
||||
tDyn, f, indirect := a.taggedValue(nodeid(fObj))
|
||||
if indirect {
|
||||
// TODO(adonovan): we'll need to implement this
|
||||
// when we start creating indirect tagged objects.
|
||||
panic("indirect tagged object")
|
||||
}
|
||||
|
||||
tSig, ok := tDyn.Underlying().(*types.Signature)
|
||||
if !ok {
|
||||
continue // not a function
|
||||
}
|
||||
if tSig.Recv() != nil {
|
||||
panic(tSig)
|
||||
}
|
||||
if tSig.Params().Len() != 1 {
|
||||
continue // not a unary function
|
||||
}
|
||||
|
||||
// Extract x to tmp.
|
||||
tx := tSig.Params().At(0).Type()
|
||||
tmp := a.addNodes(tx, "SetFinalizer.tmp")
|
||||
a.typeAssert(tx, tmp, c.x, false)
|
||||
|
||||
// Call f(tmp).
|
||||
a.store(f, tmp, 1, a.sizeof(tx))
|
||||
|
||||
// Add dynamic call target.
|
||||
if a.onlineCopy(c.targets, f) {
|
||||
a.addWork(c.targets)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ext۰runtime۰SetFinalizer(a *analysis, cgn *cgnode) {
|
||||
// This is the shared contour, used for dynamic calls.
|
||||
targets := a.addOneNode(tInvalid, "SetFinalizer.targets", nil)
|
||||
cgn.sites = append(cgn.sites, &callsite{targets: targets})
|
||||
params := a.funcParams(cgn.obj)
|
||||
a.addConstraint(&runtimeSetFinalizerConstraint{
|
||||
targets: targets,
|
||||
x: params,
|
||||
f: params + 1,
|
||||
})
|
||||
}
|
||||
|
||||
// ---------- func time.startTimer(t *runtimeTimer) ----------
|
||||
|
||||
// time.StartTimer(t)
|
||||
type timeStartTimerConstraint struct {
|
||||
targets nodeid // (indirect)
|
||||
t nodeid // (ptr)
|
||||
}
|
||||
|
||||
func (c *timeStartTimerConstraint) ptr() nodeid { return c.t }
|
||||
func (c *timeStartTimerConstraint) presolve(h *hvn) {
|
||||
h.markIndirect(onodeid(c.targets), "StartTimer.targets")
|
||||
}
|
||||
func (c *timeStartTimerConstraint) renumber(mapping []nodeid) {
|
||||
c.targets = mapping[c.targets]
|
||||
c.t = mapping[c.t]
|
||||
}
|
||||
|
||||
func (c *timeStartTimerConstraint) String() string {
|
||||
return fmt.Sprintf("time.startTimer(n%d)", c.t)
|
||||
}
|
||||
|
||||
func (c *timeStartTimerConstraint) solve(a *analysis, delta *nodeset) {
|
||||
for _, tObj := range delta.AppendTo(a.deltaSpace) {
|
||||
t := nodeid(tObj)
|
||||
|
||||
// We model startTimer as if it was defined thus:
|
||||
// func startTimer(t *runtimeTimer) { t.f(t.arg) }
|
||||
|
||||
// We hard-code the field offsets of time.runtimeTimer:
|
||||
// type runtimeTimer struct {
|
||||
// 0 __identity__
|
||||
// 1 i int32
|
||||
// 2 when int64
|
||||
// 3 period int64
|
||||
// 4 f func(int64, interface{})
|
||||
// 5 arg interface{}
|
||||
// }
|
||||
f := t + 4
|
||||
arg := t + 5
|
||||
|
||||
// store t.arg to t.f.params[0]
|
||||
// (offset 1 => skip identity)
|
||||
a.store(f, arg, 1, 1)
|
||||
|
||||
// Add dynamic call target.
|
||||
if a.onlineCopy(c.targets, f) {
|
||||
a.addWork(c.targets)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ext۰time۰startTimer(a *analysis, cgn *cgnode) {
|
||||
// This is the shared contour, used for dynamic calls.
|
||||
targets := a.addOneNode(tInvalid, "startTimer.targets", nil)
|
||||
cgn.sites = append(cgn.sites, &callsite{targets: targets})
|
||||
params := a.funcParams(cgn.obj)
|
||||
a.addConstraint(&timeStartTimerConstraint{
|
||||
targets: targets,
|
||||
t: params,
|
||||
})
|
||||
}
|
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
package pointer
|
||||
|
||||
import (
|
||||
|
154
go/pointer/labels14.go
Normal file
154
go/pointer/labels14.go
Normal file
@ -0,0 +1,154 @@
|
||||
// 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.
|
||||
|
||||
// +build !go1.5
|
||||
|
||||
package pointer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/token"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/tools/go/types"
|
||||
)
|
||||
|
||||
// A Label is an entity that may be pointed to by a pointer, map,
|
||||
// channel, 'func', slice or interface.
|
||||
//
|
||||
// Labels include:
|
||||
// - functions
|
||||
// - globals
|
||||
// - tagged objects, representing interfaces and reflect.Values
|
||||
// - arrays created by conversions (e.g. []byte("foo"), []byte(s))
|
||||
// - stack- and heap-allocated variables (including composite literals)
|
||||
// - channels, maps and arrays created by make()
|
||||
// - intrinsic or reflective operations that allocate (e.g. append, reflect.New)
|
||||
// - intrinsic objects, e.g. the initial array behind os.Args.
|
||||
// - and their subelements, e.g. "alloc.y[*].z"
|
||||
//
|
||||
// Labels are so varied that they defy good generalizations;
|
||||
// some have no value, no callgraph node, or no position.
|
||||
// Many objects have types that are inexpressible in Go:
|
||||
// maps, channels, functions, tagged objects.
|
||||
//
|
||||
// At most one of Value() or ReflectType() may return non-nil.
|
||||
//
|
||||
type Label struct {
|
||||
obj *object // the addressable memory location containing this label
|
||||
subelement *fieldInfo // subelement path within obj, e.g. ".a.b[*].c"
|
||||
}
|
||||
|
||||
// Value returns the ssa.Value that allocated this label's object, if any.
|
||||
func (l Label) Value() ssa.Value {
|
||||
val, _ := l.obj.data.(ssa.Value)
|
||||
return val
|
||||
}
|
||||
|
||||
// ReflectType returns the type represented by this label if it is an
|
||||
// reflect.rtype instance object or *reflect.rtype-tagged object.
|
||||
//
|
||||
func (l Label) ReflectType() types.Type {
|
||||
rtype, _ := l.obj.data.(types.Type)
|
||||
return rtype
|
||||
}
|
||||
|
||||
// Path returns the path to the subelement of the object containing
|
||||
// this label. For example, ".x[*].y".
|
||||
//
|
||||
func (l Label) Path() string {
|
||||
return l.subelement.path()
|
||||
}
|
||||
|
||||
// Pos returns the position of this label, if known, zero otherwise.
|
||||
func (l Label) Pos() token.Pos {
|
||||
switch data := l.obj.data.(type) {
|
||||
case ssa.Value:
|
||||
return data.Pos()
|
||||
case types.Type:
|
||||
if nt, ok := deref(data).(*types.Named); ok {
|
||||
return nt.Obj().Pos()
|
||||
}
|
||||
}
|
||||
if cgn := l.obj.cgn; cgn != nil {
|
||||
return cgn.fn.Pos()
|
||||
}
|
||||
return token.NoPos
|
||||
}
|
||||
|
||||
// String returns the printed form of this label.
|
||||
//
|
||||
// Examples: Object type:
|
||||
// x (a variable)
|
||||
// (sync.Mutex).Lock (a function)
|
||||
// convert (array created by conversion)
|
||||
// makemap (map allocated via make)
|
||||
// makechan (channel allocated via make)
|
||||
// makeinterface (tagged object allocated by makeinterface)
|
||||
// <alloc in reflect.Zero> (allocation in instrinsic)
|
||||
// sync.Mutex (a reflect.rtype instance)
|
||||
// <command-line arguments> (an intrinsic object)
|
||||
//
|
||||
// Labels within compound objects have subelement paths:
|
||||
// x.y[*].z (a struct variable, x)
|
||||
// append.y[*].z (array allocated by append)
|
||||
// makeslice.y[*].z (array allocated via make)
|
||||
//
|
||||
// TODO(adonovan): expose func LabelString(*types.Package, Label).
|
||||
//
|
||||
func (l Label) String() string {
|
||||
var s string
|
||||
switch v := l.obj.data.(type) {
|
||||
case types.Type:
|
||||
return v.String()
|
||||
|
||||
case string:
|
||||
s = v // an intrinsic object (e.g. os.Args[*])
|
||||
|
||||
case nil:
|
||||
if l.obj.cgn != nil {
|
||||
// allocation by intrinsic or reflective operation
|
||||
s = fmt.Sprintf("<alloc in %s>", l.obj.cgn.fn)
|
||||
} else {
|
||||
s = "<unknown>" // should be unreachable
|
||||
}
|
||||
|
||||
case *ssa.Function:
|
||||
s = v.String()
|
||||
|
||||
case *ssa.Global:
|
||||
s = v.String()
|
||||
|
||||
case *ssa.Const:
|
||||
s = v.Name()
|
||||
|
||||
case *ssa.Alloc:
|
||||
s = v.Comment
|
||||
if s == "" {
|
||||
s = "alloc"
|
||||
}
|
||||
|
||||
case *ssa.Call:
|
||||
// Currently only calls to append can allocate objects.
|
||||
if v.Call.Value.(*ssa.Builtin).Object().Name() != "append" {
|
||||
panic("unhandled *ssa.Call label: " + v.Name())
|
||||
}
|
||||
s = "append"
|
||||
|
||||
case *ssa.MakeMap, *ssa.MakeChan, *ssa.MakeSlice, *ssa.Convert:
|
||||
s = strings.ToLower(strings.TrimPrefix(fmt.Sprintf("%T", v), "*ssa."))
|
||||
|
||||
case *ssa.MakeInterface:
|
||||
// MakeInterface is usually implicit in Go source (so
|
||||
// Pos()==0), and tagged objects may be allocated
|
||||
// synthetically (so no *MakeInterface data).
|
||||
s = "makeinterface:" + v.X.Type().String()
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("unhandled object data type: %T", v))
|
||||
}
|
||||
|
||||
return s + l.subelement.path()
|
||||
}
|
578
go/pointer/pointer14_test.go
Normal file
578
go/pointer/pointer14_test.go
Normal file
@ -0,0 +1,578 @@
|
||||
// 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.
|
||||
|
||||
// +build !go1.5
|
||||
|
||||
// No testdata on Android.
|
||||
|
||||
// +build !android
|
||||
|
||||
package pointer_test
|
||||
|
||||
// This test uses 'expectation' comments embedded within testdata/*.go
|
||||
// files to specify the expected pointer analysis behaviour.
|
||||
// See below for grammar.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/token"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/go/callgraph"
|
||||
"golang.org/x/tools/go/loader"
|
||||
"golang.org/x/tools/go/pointer"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/tools/go/ssa/ssautil"
|
||||
"golang.org/x/tools/go/types"
|
||||
"golang.org/x/tools/go/types/typeutil"
|
||||
)
|
||||
|
||||
var inputs = []string{
|
||||
"testdata/a_test.go",
|
||||
"testdata/another.go",
|
||||
"testdata/arrayreflect.go",
|
||||
"testdata/arrays.go",
|
||||
"testdata/channels.go",
|
||||
"testdata/chanreflect.go",
|
||||
"testdata/context.go",
|
||||
"testdata/conv.go",
|
||||
"testdata/finalizer.go",
|
||||
"testdata/flow.go",
|
||||
"testdata/fmtexcerpt.go",
|
||||
"testdata/func.go",
|
||||
"testdata/funcreflect.go",
|
||||
"testdata/hello.go", // NB: causes spurious failure of HVN cross-check
|
||||
"testdata/interfaces.go",
|
||||
"testdata/issue9002.go",
|
||||
"testdata/mapreflect.go",
|
||||
"testdata/maps.go",
|
||||
"testdata/panic.go",
|
||||
"testdata/recur.go",
|
||||
"testdata/reflect.go",
|
||||
"testdata/rtti.go",
|
||||
"testdata/structreflect.go",
|
||||
"testdata/structs.go",
|
||||
"testdata/timer.go",
|
||||
}
|
||||
|
||||
// Expectation grammar:
|
||||
//
|
||||
// @calls f -> g
|
||||
//
|
||||
// A 'calls' expectation asserts that edge (f, g) appears in the
|
||||
// callgraph. f and g are notated as per Function.String(), which
|
||||
// may contain spaces (e.g. promoted method in anon struct).
|
||||
//
|
||||
// @pointsto a | b | c
|
||||
//
|
||||
// A 'pointsto' expectation asserts that the points-to set of its
|
||||
// operand contains exactly the set of labels {a,b,c} notated as per
|
||||
// labelString.
|
||||
//
|
||||
// A 'pointsto' expectation must appear on the same line as a
|
||||
// print(x) statement; the expectation's operand is x.
|
||||
//
|
||||
// If one of the strings is "...", the expectation asserts that the
|
||||
// points-to set at least the other labels.
|
||||
//
|
||||
// We use '|' because label names may contain spaces, e.g. methods
|
||||
// of anonymous structs.
|
||||
//
|
||||
// From a theoretical perspective, concrete types in interfaces are
|
||||
// labels too, but they are represented differently and so have a
|
||||
// different expectation, @types, below.
|
||||
//
|
||||
// @types t | u | v
|
||||
//
|
||||
// A 'types' expectation asserts that the set of possible dynamic
|
||||
// types of its interface operand is exactly {t,u,v}, notated per
|
||||
// go/types.Type.String(). In other words, it asserts that the type
|
||||
// component of the interface may point to that set of concrete type
|
||||
// literals. It also works for reflect.Value, though the types
|
||||
// needn't be concrete in that case.
|
||||
//
|
||||
// A 'types' expectation must appear on the same line as a
|
||||
// print(x) statement; the expectation's operand is x.
|
||||
//
|
||||
// If one of the strings is "...", the expectation asserts that the
|
||||
// interface's type may point to at least the other types.
|
||||
//
|
||||
// We use '|' because type names may contain spaces.
|
||||
//
|
||||
// @warning "regexp"
|
||||
//
|
||||
// A 'warning' expectation asserts that the analysis issues a
|
||||
// warning that matches the regular expression within the string
|
||||
// literal.
|
||||
//
|
||||
// @line id
|
||||
//
|
||||
// A line directive associates the name "id" with the current
|
||||
// file:line. The string form of labels will use this id instead of
|
||||
// a file:line, making @pointsto expectations more robust against
|
||||
// perturbations in the source file.
|
||||
// (NB, anon functions still include line numbers.)
|
||||
//
|
||||
type expectation struct {
|
||||
kind string // "pointsto" | "types" | "calls" | "warning"
|
||||
filename string
|
||||
linenum int // source line number, 1-based
|
||||
args []string
|
||||
types []types.Type // for types
|
||||
}
|
||||
|
||||
func (e *expectation) String() string {
|
||||
return fmt.Sprintf("@%s[%s]", e.kind, strings.Join(e.args, " | "))
|
||||
}
|
||||
|
||||
func (e *expectation) errorf(format string, args ...interface{}) {
|
||||
fmt.Printf("%s:%d: ", e.filename, e.linenum)
|
||||
fmt.Printf(format, args...)
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
func (e *expectation) needsProbe() bool {
|
||||
return e.kind == "pointsto" || e.kind == "types"
|
||||
}
|
||||
|
||||
// Find probe (call to print(x)) of same source file/line as expectation.
|
||||
func findProbe(prog *ssa.Program, probes map[*ssa.CallCommon]bool, queries map[ssa.Value]pointer.Pointer, e *expectation) (site *ssa.CallCommon, pts pointer.PointsToSet) {
|
||||
for call := range probes {
|
||||
pos := prog.Fset.Position(call.Pos())
|
||||
if pos.Line == e.linenum && pos.Filename == e.filename {
|
||||
// TODO(adonovan): send this to test log (display only on failure).
|
||||
// fmt.Printf("%s:%d: info: found probe for %s: %s\n",
|
||||
// e.filename, e.linenum, e, p.arg0) // debugging
|
||||
return call, queries[call.Args[0]].PointsTo()
|
||||
}
|
||||
}
|
||||
return // e.g. analysis didn't reach this call
|
||||
}
|
||||
|
||||
func doOneInput(input, filename string) bool {
|
||||
var conf loader.Config
|
||||
|
||||
// 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 := ssautil.CreateProgram(iprog, ssa.SanityCheckFunctions)
|
||||
prog.Build()
|
||||
|
||||
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 {
|
||||
call := instr.Common()
|
||||
if b, ok := call.Value.(*ssa.Builtin); ok && b.Name() == "print" && len(call.Args) == 1 {
|
||||
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 != "..." {
|
||||
tv, err := types.Eval(prog.Fset, mainpkg.Pkg, f.Pos(), typstr)
|
||||
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
|
||||
}
|
||||
t = tv.Type
|
||||
}
|
||||
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 labelString(l *pointer.Label, lineMapping map[string]string, prog *ssa.Program) string {
|
||||
// Functions and Globals need no pos suffix,
|
||||
// nor do allocations in intrinsic operations
|
||||
// (for which we'll print the function name).
|
||||
switch l.Value().(type) {
|
||||
case nil, *ssa.Function, *ssa.Global:
|
||||
return l.String()
|
||||
}
|
||||
|
||||
str := l.String()
|
||||
if pos := l.Pos(); pos != token.NoPos {
|
||||
// Append the position, using a @line tag instead of a line number, if defined.
|
||||
posn := prog.Fset.Position(pos)
|
||||
s := fmt.Sprintf("%s:%d", posn.Filename, posn.Line)
|
||||
if tag, ok := lineMapping[s]; ok {
|
||||
return fmt.Sprintf("%s@%s:%d", str, tag, posn.Column)
|
||||
}
|
||||
str = fmt.Sprintf("%s@%s", str, posn)
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
func checkPointsToExpectation(e *expectation, pts pointer.PointsToSet, lineMapping map[string]string, prog *ssa.Program) bool {
|
||||
expected := make(map[string]int)
|
||||
surplus := make(map[string]int)
|
||||
exact := true
|
||||
for _, g := range e.args {
|
||||
if g == "..." {
|
||||
exact = false
|
||||
continue
|
||||
}
|
||||
expected[g]++
|
||||
}
|
||||
// Find the set of labels that the probe's
|
||||
// argument (x in print(x)) may point to.
|
||||
for _, label := range pts.Labels() {
|
||||
name := labelString(label, lineMapping, prog)
|
||||
if expected[name] > 0 {
|
||||
expected[name]--
|
||||
} else if exact {
|
||||
surplus[name]++
|
||||
}
|
||||
}
|
||||
// Report multiset difference:
|
||||
ok := true
|
||||
for _, count := range expected {
|
||||
if count > 0 {
|
||||
ok = false
|
||||
e.errorf("value does not alias these expected labels: %s", join(expected))
|
||||
break
|
||||
}
|
||||
}
|
||||
for _, count := range surplus {
|
||||
if count > 0 {
|
||||
ok = false
|
||||
e.errorf("value may additionally alias these labels: %s", join(surplus))
|
||||
break
|
||||
}
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
func checkTypesExpectation(e *expectation, pts pointer.PointsToSet, typ types.Type) bool {
|
||||
var expected typeutil.Map
|
||||
var surplus typeutil.Map
|
||||
exact := true
|
||||
for _, g := range e.types {
|
||||
if g == types.Typ[types.Invalid] {
|
||||
exact = false
|
||||
continue
|
||||
}
|
||||
expected.Set(g, struct{}{})
|
||||
}
|
||||
|
||||
if !pointer.CanHaveDynamicTypes(typ) {
|
||||
e.errorf("@types expectation requires an interface- or reflect.Value-typed operand, got %s", typ)
|
||||
return false
|
||||
}
|
||||
|
||||
// Find the set of types that the probe's
|
||||
// argument (x in print(x)) may contain.
|
||||
for _, T := range pts.DynamicTypes().Keys() {
|
||||
if expected.At(T) != nil {
|
||||
expected.Delete(T)
|
||||
} else if exact {
|
||||
surplus.Set(T, struct{}{})
|
||||
}
|
||||
}
|
||||
// Report set difference:
|
||||
ok := true
|
||||
if expected.Len() > 0 {
|
||||
ok = false
|
||||
e.errorf("interface cannot contain these types: %s", expected.KeysString())
|
||||
}
|
||||
if surplus.Len() > 0 {
|
||||
ok = false
|
||||
e.errorf("interface may additionally contain these types: %s", surplus.KeysString())
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
var errOK = errors.New("OK")
|
||||
|
||||
func checkCallsExpectation(prog *ssa.Program, e *expectation, cg *callgraph.Graph) bool {
|
||||
found := make(map[string]int)
|
||||
err := callgraph.GraphVisitEdges(cg, func(edge *callgraph.Edge) error {
|
||||
// Name-based matching is inefficient but it allows us to
|
||||
// match functions whose names that would not appear in an
|
||||
// index ("<root>") or which are not unique ("func@1.2").
|
||||
if edge.Caller.Func.String() == e.args[0] {
|
||||
calleeStr := edge.Callee.Func.String()
|
||||
if calleeStr == e.args[1] {
|
||||
return errOK // expectation satisified; stop the search
|
||||
}
|
||||
found[calleeStr]++
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err == errOK {
|
||||
return true
|
||||
}
|
||||
if len(found) == 0 {
|
||||
e.errorf("didn't find any calls from %s", e.args[0])
|
||||
}
|
||||
e.errorf("found no call from %s to %s, but only to %s",
|
||||
e.args[0], e.args[1], join(found))
|
||||
return false
|
||||
}
|
||||
|
||||
func checkWarningExpectation(prog *ssa.Program, e *expectation, warnings []pointer.Warning) bool {
|
||||
// TODO(adonovan): check the position part of the warning too?
|
||||
re, err := regexp.Compile(e.args[0])
|
||||
if err != nil {
|
||||
e.errorf("invalid regular expression in @warning expectation: %s", err.Error())
|
||||
return false
|
||||
}
|
||||
|
||||
if len(warnings) == 0 {
|
||||
e.errorf("@warning %s expectation, but no warnings", strconv.Quote(e.args[0]))
|
||||
return false
|
||||
}
|
||||
|
||||
for _, w := range warnings {
|
||||
if re.MatchString(w.Message) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
e.errorf("@warning %s expectation not satised; found these warnings though:", strconv.Quote(e.args[0]))
|
||||
for _, w := range warnings {
|
||||
fmt.Printf("%s: warning: %s\n", prog.Fset.Position(w.Pos), w.Message)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func TestInput(t *testing.T) {
|
||||
ok := true
|
||||
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Errorf("os.Getwd: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 'go test' does a chdir so that relative paths in
|
||||
// diagnostics no longer make sense relative to the invoking
|
||||
// shell's cwd. We print a special marker so that Emacs can
|
||||
// make sense of them.
|
||||
fmt.Fprintf(os.Stderr, "Entering directory `%s'\n", wd)
|
||||
|
||||
for _, filename := range inputs {
|
||||
content, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
t.Errorf("couldn't read file '%s': %s", filename, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !doOneInput(string(content), filename) {
|
||||
ok = false
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
// join joins the elements of multiset with " | "s.
|
||||
func join(set map[string]int) string {
|
||||
var buf bytes.Buffer
|
||||
sep := ""
|
||||
for name, count := range set {
|
||||
for i := 0; i < count; i++ {
|
||||
buf.WriteString(sep)
|
||||
sep = " | "
|
||||
buf.WriteString(name)
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// split returns the list of sep-delimited non-empty strings in s.
|
||||
func split(s, sep string) (r []string) {
|
||||
for _, elem := range strings.Split(s, sep) {
|
||||
elem = strings.TrimSpace(elem)
|
||||
if elem != "" {
|
||||
r = append(r, elem)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
// No testdata on Android.
|
||||
|
||||
// +build !android
|
||||
|
@ -1,3 +1,9 @@
|
||||
// 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.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
package pointer
|
||||
|
||||
// This file implements the generation and resolution rules for
|
||||
|
1977
go/pointer/reflect14.go
Normal file
1977
go/pointer/reflect14.go
Normal file
File diff suppressed because it is too large
Load Diff
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
package pointer
|
||||
|
||||
// This file defines a naive Andersen-style solver for the inclusion
|
||||
|
373
go/pointer/solve14.go
Normal file
373
go/pointer/solve14.go
Normal file
@ -0,0 +1,373 @@
|
||||
// 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.
|
||||
|
||||
// +build !go1.5
|
||||
|
||||
package pointer
|
||||
|
||||
// This file defines a naive Andersen-style solver for the inclusion
|
||||
// constraint system.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/tools/go/types"
|
||||
)
|
||||
|
||||
type solverState struct {
|
||||
complex []constraint // complex constraints attached to this node
|
||||
copyTo nodeset // simple copy constraint edges
|
||||
pts nodeset // points-to set of this node
|
||||
prevPTS nodeset // pts(n) in previous iteration (for difference propagation)
|
||||
}
|
||||
|
||||
func (a *analysis) solve() {
|
||||
start("Solving")
|
||||
if a.log != nil {
|
||||
fmt.Fprintf(a.log, "\n\n==== Solving constraints\n\n")
|
||||
}
|
||||
|
||||
// Solver main loop.
|
||||
var delta nodeset
|
||||
for {
|
||||
// Add new constraints to the graph:
|
||||
// static constraints from SSA on round 1,
|
||||
// dynamic constraints from reflection thereafter.
|
||||
a.processNewConstraints()
|
||||
|
||||
var x int
|
||||
if !a.work.TakeMin(&x) {
|
||||
break // empty
|
||||
}
|
||||
id := nodeid(x)
|
||||
if a.log != nil {
|
||||
fmt.Fprintf(a.log, "\tnode n%d\n", id)
|
||||
}
|
||||
|
||||
n := a.nodes[id]
|
||||
|
||||
// Difference propagation.
|
||||
delta.Difference(&n.solve.pts.Sparse, &n.solve.prevPTS.Sparse)
|
||||
if delta.IsEmpty() {
|
||||
continue
|
||||
}
|
||||
if a.log != nil {
|
||||
fmt.Fprintf(a.log, "\t\tpts(n%d : %s) = %s + %s\n",
|
||||
id, n.typ, &delta, &n.solve.prevPTS)
|
||||
}
|
||||
n.solve.prevPTS.Copy(&n.solve.pts.Sparse)
|
||||
|
||||
// Apply all resolution rules attached to n.
|
||||
a.solveConstraints(n, &delta)
|
||||
|
||||
if a.log != nil {
|
||||
fmt.Fprintf(a.log, "\t\tpts(n%d) = %s\n", id, &n.solve.pts)
|
||||
}
|
||||
}
|
||||
|
||||
if !a.nodes[0].solve.pts.IsEmpty() {
|
||||
panic(fmt.Sprintf("pts(0) is nonempty: %s", &a.nodes[0].solve.pts))
|
||||
}
|
||||
|
||||
// Release working state (but keep final PTS).
|
||||
for _, n := range a.nodes {
|
||||
n.solve.complex = nil
|
||||
n.solve.copyTo.Clear()
|
||||
n.solve.prevPTS.Clear()
|
||||
}
|
||||
|
||||
if a.log != nil {
|
||||
fmt.Fprintf(a.log, "Solver done\n")
|
||||
|
||||
// Dump solution.
|
||||
for i, n := range a.nodes {
|
||||
if !n.solve.pts.IsEmpty() {
|
||||
fmt.Fprintf(a.log, "pts(n%d) = %s : %s\n", i, &n.solve.pts, n.typ)
|
||||
}
|
||||
}
|
||||
}
|
||||
stop("Solving")
|
||||
}
|
||||
|
||||
// processNewConstraints takes the new constraints from a.constraints
|
||||
// and adds them to the graph, ensuring
|
||||
// that new constraints are applied to pre-existing labels and
|
||||
// that pre-existing constraints are applied to new labels.
|
||||
//
|
||||
func (a *analysis) processNewConstraints() {
|
||||
// Take the slice of new constraints.
|
||||
// (May grow during call to solveConstraints.)
|
||||
constraints := a.constraints
|
||||
a.constraints = nil
|
||||
|
||||
// Initialize points-to sets from addr-of (base) constraints.
|
||||
for _, c := range constraints {
|
||||
if c, ok := c.(*addrConstraint); ok {
|
||||
dst := a.nodes[c.dst]
|
||||
dst.solve.pts.add(c.src)
|
||||
|
||||
// Populate the worklist with nodes that point to
|
||||
// something initially (due to addrConstraints) and
|
||||
// have other constraints attached.
|
||||
// (A no-op in round 1.)
|
||||
if !dst.solve.copyTo.IsEmpty() || len(dst.solve.complex) > 0 {
|
||||
a.addWork(c.dst)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Attach simple (copy) and complex constraints to nodes.
|
||||
var stale nodeset
|
||||
for _, c := range constraints {
|
||||
var id nodeid
|
||||
switch c := c.(type) {
|
||||
case *addrConstraint:
|
||||
// base constraints handled in previous loop
|
||||
continue
|
||||
case *copyConstraint:
|
||||
// simple (copy) constraint
|
||||
id = c.src
|
||||
a.nodes[id].solve.copyTo.add(c.dst)
|
||||
default:
|
||||
// complex constraint
|
||||
id = c.ptr()
|
||||
solve := a.nodes[id].solve
|
||||
solve.complex = append(solve.complex, c)
|
||||
}
|
||||
|
||||
if n := a.nodes[id]; !n.solve.pts.IsEmpty() {
|
||||
if !n.solve.prevPTS.IsEmpty() {
|
||||
stale.add(id)
|
||||
}
|
||||
a.addWork(id)
|
||||
}
|
||||
}
|
||||
// Apply new constraints to pre-existing PTS labels.
|
||||
var space [50]int
|
||||
for _, id := range stale.AppendTo(space[:0]) {
|
||||
n := a.nodes[nodeid(id)]
|
||||
a.solveConstraints(n, &n.solve.prevPTS)
|
||||
}
|
||||
}
|
||||
|
||||
// solveConstraints applies each resolution rule attached to node n to
|
||||
// the set of labels delta. It may generate new constraints in
|
||||
// a.constraints.
|
||||
//
|
||||
func (a *analysis) solveConstraints(n *node, delta *nodeset) {
|
||||
if delta.IsEmpty() {
|
||||
return
|
||||
}
|
||||
|
||||
// Process complex constraints dependent on n.
|
||||
for _, c := range n.solve.complex {
|
||||
if a.log != nil {
|
||||
fmt.Fprintf(a.log, "\t\tconstraint %s\n", c)
|
||||
}
|
||||
c.solve(a, delta)
|
||||
}
|
||||
|
||||
// Process copy constraints.
|
||||
var copySeen nodeset
|
||||
for _, x := range n.solve.copyTo.AppendTo(a.deltaSpace) {
|
||||
mid := nodeid(x)
|
||||
if copySeen.add(mid) {
|
||||
if a.nodes[mid].solve.pts.addAll(delta) {
|
||||
a.addWork(mid)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// addLabel adds label to the points-to set of ptr and reports whether the set grew.
|
||||
func (a *analysis) addLabel(ptr, label nodeid) bool {
|
||||
b := a.nodes[ptr].solve.pts.add(label)
|
||||
if b && a.log != nil {
|
||||
fmt.Fprintf(a.log, "\t\tpts(n%d) += n%d\n", ptr, label)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (a *analysis) addWork(id nodeid) {
|
||||
a.work.Insert(int(id))
|
||||
if a.log != nil {
|
||||
fmt.Fprintf(a.log, "\t\twork: n%d\n", id)
|
||||
}
|
||||
}
|
||||
|
||||
// onlineCopy adds a copy edge. It is called online, i.e. during
|
||||
// solving, so it adds edges and pts members directly rather than by
|
||||
// instantiating a 'constraint'.
|
||||
//
|
||||
// The size of the copy is implicitly 1.
|
||||
// It returns true if pts(dst) changed.
|
||||
//
|
||||
func (a *analysis) onlineCopy(dst, src nodeid) bool {
|
||||
if dst != src {
|
||||
if nsrc := a.nodes[src]; nsrc.solve.copyTo.add(dst) {
|
||||
if a.log != nil {
|
||||
fmt.Fprintf(a.log, "\t\t\tdynamic copy n%d <- n%d\n", dst, src)
|
||||
}
|
||||
// TODO(adonovan): most calls to onlineCopy
|
||||
// are followed by addWork, possibly batched
|
||||
// via a 'changed' flag; see if there's a
|
||||
// noticeable penalty to calling addWork here.
|
||||
return a.nodes[dst].solve.pts.addAll(&nsrc.solve.pts)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Returns sizeof.
|
||||
// Implicitly adds nodes to worklist.
|
||||
//
|
||||
// TODO(adonovan): now that we support a.copy() during solving, we
|
||||
// could eliminate onlineCopyN, but it's much slower. Investigate.
|
||||
//
|
||||
func (a *analysis) onlineCopyN(dst, src nodeid, sizeof uint32) uint32 {
|
||||
for i := uint32(0); i < sizeof; i++ {
|
||||
if a.onlineCopy(dst, src) {
|
||||
a.addWork(dst)
|
||||
}
|
||||
src++
|
||||
dst++
|
||||
}
|
||||
return sizeof
|
||||
}
|
||||
|
||||
func (c *loadConstraint) solve(a *analysis, delta *nodeset) {
|
||||
var changed bool
|
||||
for _, x := range delta.AppendTo(a.deltaSpace) {
|
||||
k := nodeid(x)
|
||||
koff := k + nodeid(c.offset)
|
||||
if a.onlineCopy(c.dst, koff) {
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
if changed {
|
||||
a.addWork(c.dst)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *storeConstraint) solve(a *analysis, delta *nodeset) {
|
||||
for _, x := range delta.AppendTo(a.deltaSpace) {
|
||||
k := nodeid(x)
|
||||
koff := k + nodeid(c.offset)
|
||||
if a.onlineCopy(koff, c.src) {
|
||||
a.addWork(koff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *offsetAddrConstraint) solve(a *analysis, delta *nodeset) {
|
||||
dst := a.nodes[c.dst]
|
||||
for _, x := range delta.AppendTo(a.deltaSpace) {
|
||||
k := nodeid(x)
|
||||
if dst.solve.pts.add(k + nodeid(c.offset)) {
|
||||
a.addWork(c.dst)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *typeFilterConstraint) solve(a *analysis, delta *nodeset) {
|
||||
for _, x := range delta.AppendTo(a.deltaSpace) {
|
||||
ifaceObj := nodeid(x)
|
||||
tDyn, _, indirect := a.taggedValue(ifaceObj)
|
||||
if indirect {
|
||||
// TODO(adonovan): we'll need to implement this
|
||||
// when we start creating indirect tagged objects.
|
||||
panic("indirect tagged object")
|
||||
}
|
||||
|
||||
if types.AssignableTo(tDyn, c.typ) {
|
||||
if a.addLabel(c.dst, ifaceObj) {
|
||||
a.addWork(c.dst)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *untagConstraint) solve(a *analysis, delta *nodeset) {
|
||||
predicate := types.AssignableTo
|
||||
if c.exact {
|
||||
predicate = types.Identical
|
||||
}
|
||||
for _, x := range delta.AppendTo(a.deltaSpace) {
|
||||
ifaceObj := nodeid(x)
|
||||
tDyn, v, indirect := a.taggedValue(ifaceObj)
|
||||
if indirect {
|
||||
// TODO(adonovan): we'll need to implement this
|
||||
// when we start creating indirect tagged objects.
|
||||
panic("indirect tagged object")
|
||||
}
|
||||
|
||||
if predicate(tDyn, c.typ) {
|
||||
// Copy payload sans tag to dst.
|
||||
//
|
||||
// TODO(adonovan): opt: if tDyn is
|
||||
// nonpointerlike we can skip this entire
|
||||
// constraint, perhaps. We only care about
|
||||
// pointers among the fields.
|
||||
a.onlineCopyN(c.dst, v, a.sizeof(tDyn))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *invokeConstraint) solve(a *analysis, delta *nodeset) {
|
||||
for _, x := range delta.AppendTo(a.deltaSpace) {
|
||||
ifaceObj := nodeid(x)
|
||||
tDyn, v, indirect := a.taggedValue(ifaceObj)
|
||||
if indirect {
|
||||
// TODO(adonovan): we may need to implement this if
|
||||
// we ever apply invokeConstraints to reflect.Value PTSs,
|
||||
// e.g. for (reflect.Value).Call.
|
||||
panic("indirect tagged object")
|
||||
}
|
||||
|
||||
// Look up the concrete method.
|
||||
fn := a.prog.LookupMethod(tDyn, c.method.Pkg(), c.method.Name())
|
||||
if fn == nil {
|
||||
panic(fmt.Sprintf("n%d: no ssa.Function for %s", c.iface, c.method))
|
||||
}
|
||||
sig := fn.Signature
|
||||
|
||||
fnObj := a.globalobj[fn] // dynamic calls use shared contour
|
||||
if fnObj == 0 {
|
||||
// a.objectNode(fn) was not called during gen phase.
|
||||
panic(fmt.Sprintf("a.globalobj[%s]==nil", fn))
|
||||
}
|
||||
|
||||
// Make callsite's fn variable point to identity of
|
||||
// concrete method. (There's no need to add it to
|
||||
// worklist since it never has attached constraints.)
|
||||
a.addLabel(c.params, fnObj)
|
||||
|
||||
// Extract value and connect to method's receiver.
|
||||
// Copy payload to method's receiver param (arg0).
|
||||
arg0 := a.funcParams(fnObj)
|
||||
recvSize := a.sizeof(sig.Recv().Type())
|
||||
a.onlineCopyN(arg0, v, recvSize)
|
||||
|
||||
src := c.params + 1 // skip past identity
|
||||
dst := arg0 + nodeid(recvSize)
|
||||
|
||||
// Copy caller's argument block to method formal parameters.
|
||||
paramsSize := a.sizeof(sig.Params())
|
||||
a.onlineCopyN(dst, src, paramsSize)
|
||||
src += nodeid(paramsSize)
|
||||
dst += nodeid(paramsSize)
|
||||
|
||||
// Copy method results to caller's result block.
|
||||
resultsSize := a.sizeof(sig.Results())
|
||||
a.onlineCopyN(src, dst, resultsSize)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *addrConstraint) solve(a *analysis, delta *nodeset) {
|
||||
panic("addr is not a complex constraint")
|
||||
}
|
||||
|
||||
func (c *copyConstraint) solve(a *analysis, delta *nodeset) {
|
||||
panic("copy is not a complex constraint")
|
||||
}
|
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
package pointer
|
||||
|
||||
import (
|
||||
|
316
go/pointer/util14.go
Normal file
316
go/pointer/util14.go
Normal file
@ -0,0 +1,316 @@
|
||||
// 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.
|
||||
|
||||
// +build !go1.5
|
||||
|
||||
package pointer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"golang.org/x/tools/container/intsets"
|
||||
"golang.org/x/tools/go/types"
|
||||
)
|
||||
|
||||
// CanPoint reports whether the type T is pointerlike,
|
||||
// for the purposes of this analysis.
|
||||
func CanPoint(T types.Type) bool {
|
||||
switch T := T.(type) {
|
||||
case *types.Named:
|
||||
if obj := T.Obj(); obj.Name() == "Value" && obj.Pkg().Path() == "reflect" {
|
||||
return true // treat reflect.Value like interface{}
|
||||
}
|
||||
return CanPoint(T.Underlying())
|
||||
|
||||
case *types.Pointer, *types.Interface, *types.Map, *types.Chan, *types.Signature, *types.Slice:
|
||||
return true
|
||||
}
|
||||
|
||||
return false // array struct tuple builtin basic
|
||||
}
|
||||
|
||||
// CanHaveDynamicTypes reports whether the type T can "hold" dynamic types,
|
||||
// i.e. is an interface (incl. reflect.Type) or a reflect.Value.
|
||||
//
|
||||
func CanHaveDynamicTypes(T types.Type) bool {
|
||||
switch T := T.(type) {
|
||||
case *types.Named:
|
||||
if obj := T.Obj(); obj.Name() == "Value" && obj.Pkg().Path() == "reflect" {
|
||||
return true // reflect.Value
|
||||
}
|
||||
return CanHaveDynamicTypes(T.Underlying())
|
||||
case *types.Interface:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isInterface(T types.Type) bool { return types.IsInterface(T) }
|
||||
|
||||
// mustDeref returns the element type of its argument, which must be a
|
||||
// pointer; panic ensues otherwise.
|
||||
func mustDeref(typ types.Type) types.Type {
|
||||
return typ.Underlying().(*types.Pointer).Elem()
|
||||
}
|
||||
|
||||
// deref returns a pointer's element type; otherwise it returns typ.
|
||||
func deref(typ types.Type) types.Type {
|
||||
if p, ok := typ.Underlying().(*types.Pointer); ok {
|
||||
return p.Elem()
|
||||
}
|
||||
return typ
|
||||
}
|
||||
|
||||
// A fieldInfo describes one subelement (node) of the flattening-out
|
||||
// of a type T: the subelement's type and its path from the root of T.
|
||||
//
|
||||
// For example, for this type:
|
||||
// type line struct{ points []struct{x, y int} }
|
||||
// flatten() of the inner struct yields the following []fieldInfo:
|
||||
// struct{ x, y int } ""
|
||||
// int ".x"
|
||||
// int ".y"
|
||||
// and flatten(line) yields:
|
||||
// struct{ points []struct{x, y int} } ""
|
||||
// struct{ x, y int } ".points[*]"
|
||||
// int ".points[*].x
|
||||
// int ".points[*].y"
|
||||
//
|
||||
type fieldInfo struct {
|
||||
typ types.Type
|
||||
|
||||
// op and tail describe the path to the element (e.g. ".a#2.b[*].c").
|
||||
op interface{} // *Array: true; *Tuple: int; *Struct: *types.Var; *Named: nil
|
||||
tail *fieldInfo
|
||||
}
|
||||
|
||||
// path returns a user-friendly string describing the subelement path.
|
||||
//
|
||||
func (fi *fieldInfo) path() string {
|
||||
var buf bytes.Buffer
|
||||
for p := fi; p != nil; p = p.tail {
|
||||
switch op := p.op.(type) {
|
||||
case bool:
|
||||
fmt.Fprintf(&buf, "[*]")
|
||||
case int:
|
||||
fmt.Fprintf(&buf, "#%d", op)
|
||||
case *types.Var:
|
||||
fmt.Fprintf(&buf, ".%s", op.Name())
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// flatten returns a list of directly contained fields in the preorder
|
||||
// traversal of the type tree of t. The resulting elements are all
|
||||
// scalars (basic types or pointerlike types), except for struct/array
|
||||
// "identity" nodes, whose type is that of the aggregate.
|
||||
//
|
||||
// reflect.Value is considered pointerlike, similar to interface{}.
|
||||
//
|
||||
// Callers must not mutate the result.
|
||||
//
|
||||
func (a *analysis) flatten(t types.Type) []*fieldInfo {
|
||||
fl, ok := a.flattenMemo[t]
|
||||
if !ok {
|
||||
switch t := t.(type) {
|
||||
case *types.Named:
|
||||
u := t.Underlying()
|
||||
if isInterface(u) {
|
||||
// Debuggability hack: don't remove
|
||||
// the named type from interfaces as
|
||||
// they're very verbose.
|
||||
fl = append(fl, &fieldInfo{typ: t})
|
||||
} else {
|
||||
fl = a.flatten(u)
|
||||
}
|
||||
|
||||
case *types.Basic,
|
||||
*types.Signature,
|
||||
*types.Chan,
|
||||
*types.Map,
|
||||
*types.Interface,
|
||||
*types.Slice,
|
||||
*types.Pointer:
|
||||
fl = append(fl, &fieldInfo{typ: t})
|
||||
|
||||
case *types.Array:
|
||||
fl = append(fl, &fieldInfo{typ: t}) // identity node
|
||||
for _, fi := range a.flatten(t.Elem()) {
|
||||
fl = append(fl, &fieldInfo{typ: fi.typ, op: true, tail: fi})
|
||||
}
|
||||
|
||||
case *types.Struct:
|
||||
fl = append(fl, &fieldInfo{typ: t}) // identity node
|
||||
for i, n := 0, t.NumFields(); i < n; i++ {
|
||||
f := t.Field(i)
|
||||
for _, fi := range a.flatten(f.Type()) {
|
||||
fl = append(fl, &fieldInfo{typ: fi.typ, op: f, tail: fi})
|
||||
}
|
||||
}
|
||||
|
||||
case *types.Tuple:
|
||||
// No identity node: tuples are never address-taken.
|
||||
n := t.Len()
|
||||
if n == 1 {
|
||||
// Don't add a fieldInfo link for singletons,
|
||||
// e.g. in params/results.
|
||||
fl = append(fl, a.flatten(t.At(0).Type())...)
|
||||
} else {
|
||||
for i := 0; i < n; i++ {
|
||||
f := t.At(i)
|
||||
for _, fi := range a.flatten(f.Type()) {
|
||||
fl = append(fl, &fieldInfo{typ: fi.typ, op: i, tail: fi})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
panic(t)
|
||||
}
|
||||
|
||||
a.flattenMemo[t] = fl
|
||||
}
|
||||
|
||||
return fl
|
||||
}
|
||||
|
||||
// sizeof returns the number of pointerlike abstractions (nodes) in the type t.
|
||||
func (a *analysis) sizeof(t types.Type) uint32 {
|
||||
return uint32(len(a.flatten(t)))
|
||||
}
|
||||
|
||||
// shouldTrack reports whether object type T contains (recursively)
|
||||
// any fields whose addresses should be tracked.
|
||||
func (a *analysis) shouldTrack(T types.Type) bool {
|
||||
if a.track == trackAll {
|
||||
return true // fast path
|
||||
}
|
||||
track, ok := a.trackTypes[T]
|
||||
if !ok {
|
||||
a.trackTypes[T] = true // break cycles conservatively
|
||||
// NB: reflect.Value, reflect.Type are pre-populated to true.
|
||||
for _, fi := range a.flatten(T) {
|
||||
switch ft := fi.typ.Underlying().(type) {
|
||||
case *types.Interface, *types.Signature:
|
||||
track = true // needed for callgraph
|
||||
case *types.Basic:
|
||||
// no-op
|
||||
case *types.Chan:
|
||||
track = a.track&trackChan != 0 || a.shouldTrack(ft.Elem())
|
||||
case *types.Map:
|
||||
track = a.track&trackMap != 0 || a.shouldTrack(ft.Key()) || a.shouldTrack(ft.Elem())
|
||||
case *types.Slice:
|
||||
track = a.track&trackSlice != 0 || a.shouldTrack(ft.Elem())
|
||||
case *types.Pointer:
|
||||
track = a.track&trackPtr != 0 || a.shouldTrack(ft.Elem())
|
||||
case *types.Array, *types.Struct:
|
||||
// No need to look at field types since they will follow (flattened).
|
||||
default:
|
||||
// Includes *types.Tuple, which are never address-taken.
|
||||
panic(ft)
|
||||
}
|
||||
if track {
|
||||
break
|
||||
}
|
||||
}
|
||||
a.trackTypes[T] = track
|
||||
if !track && a.log != nil {
|
||||
fmt.Fprintf(a.log, "\ttype not tracked: %s\n", T)
|
||||
}
|
||||
}
|
||||
return track
|
||||
}
|
||||
|
||||
// offsetOf returns the (abstract) offset of field index within struct
|
||||
// or tuple typ.
|
||||
func (a *analysis) offsetOf(typ types.Type, index int) uint32 {
|
||||
var offset uint32
|
||||
switch t := typ.Underlying().(type) {
|
||||
case *types.Tuple:
|
||||
for i := 0; i < index; i++ {
|
||||
offset += a.sizeof(t.At(i).Type())
|
||||
}
|
||||
case *types.Struct:
|
||||
offset++ // the node for the struct itself
|
||||
for i := 0; i < index; i++ {
|
||||
offset += a.sizeof(t.Field(i).Type())
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("offsetOf(%s : %T)", typ, typ))
|
||||
}
|
||||
return offset
|
||||
}
|
||||
|
||||
// sliceToArray returns the type representing the arrays to which
|
||||
// slice type slice points.
|
||||
func sliceToArray(slice types.Type) *types.Array {
|
||||
return types.NewArray(slice.Underlying().(*types.Slice).Elem(), 1)
|
||||
}
|
||||
|
||||
// Node set -------------------------------------------------------------------
|
||||
|
||||
type nodeset struct {
|
||||
intsets.Sparse
|
||||
}
|
||||
|
||||
func (ns *nodeset) String() string {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteRune('{')
|
||||
var space [50]int
|
||||
for i, n := range ns.AppendTo(space[:0]) {
|
||||
if i > 0 {
|
||||
buf.WriteString(", ")
|
||||
}
|
||||
buf.WriteRune('n')
|
||||
fmt.Fprintf(&buf, "%d", n)
|
||||
}
|
||||
buf.WriteRune('}')
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func (ns *nodeset) add(n nodeid) bool {
|
||||
return ns.Sparse.Insert(int(n))
|
||||
}
|
||||
|
||||
func (x *nodeset) addAll(y *nodeset) bool {
|
||||
return x.UnionWith(&y.Sparse)
|
||||
}
|
||||
|
||||
// Profiling & debugging -------------------------------------------------------
|
||||
|
||||
var timers = make(map[string]time.Time)
|
||||
|
||||
func start(name string) {
|
||||
if debugTimers {
|
||||
timers[name] = time.Now()
|
||||
log.Printf("%s...\n", name)
|
||||
}
|
||||
}
|
||||
|
||||
func stop(name string) {
|
||||
if debugTimers {
|
||||
log.Printf("%s took %s\n", name, time.Since(timers[name]))
|
||||
}
|
||||
}
|
||||
|
||||
// diff runs the command "diff a b" and reports its success.
|
||||
func diff(a, b string) bool {
|
||||
var cmd *exec.Cmd
|
||||
switch runtime.GOOS {
|
||||
case "plan9":
|
||||
cmd = exec.Command("/bin/diff", "-c", a, b)
|
||||
default:
|
||||
cmd = exec.Command("/usr/bin/diff", "-u", a, b)
|
||||
}
|
||||
cmd.Stdout = os.Stderr
|
||||
cmd.Stderr = os.Stderr
|
||||
return cmd.Run() == nil
|
||||
}
|
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
package ssa
|
||||
|
||||
// This file implements the BUILD phase of SSA construction.
|
||||
|
2386
go/ssa/builder14.go
Normal file
2386
go/ssa/builder14.go
Normal file
File diff suppressed because it is too large
Load Diff
421
go/ssa/builder14_test.go
Normal file
421
go/ssa/builder14_test.go
Normal file
@ -0,0 +1,421 @@
|
||||
// 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.
|
||||
|
||||
// +build !go1.5
|
||||
|
||||
package ssa_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/go/loader"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/tools/go/ssa/ssautil"
|
||||
"golang.org/x/tools/go/types"
|
||||
|
||||
_ "golang.org/x/tools/go/gcimporter"
|
||||
)
|
||||
|
||||
func isEmpty(f *ssa.Function) bool { return f.Blocks == nil }
|
||||
|
||||
// Tests that programs partially loaded from gc object files contain
|
||||
// functions with no code for the external portions, but are otherwise ok.
|
||||
func TestBuildPackage(t *testing.T) {
|
||||
input := `
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var t testing.T
|
||||
t.Parallel() // static call to external declared method
|
||||
t.Fail() // static call to promoted external declared method
|
||||
testing.Short() // static call to external package-level function
|
||||
|
||||
var w io.Writer = new(bytes.Buffer)
|
||||
w.Write(nil) // interface invoke of external declared method
|
||||
}
|
||||
`
|
||||
|
||||
// Parse the file.
|
||||
fset := token.NewFileSet()
|
||||
f, err := parser.ParseFile(fset, "input.go", input, 0)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Build an SSA program from the parsed file.
|
||||
// Load its dependencies from gc binary export data.
|
||||
mainPkg, _, err := ssautil.BuildPackage(new(types.Config), fset,
|
||||
types.NewPackage("main", ""), []*ast.File{f}, ssa.SanityCheckFunctions)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
// The main package, its direct and indirect dependencies are loaded.
|
||||
deps := []string{
|
||||
// directly imported dependencies:
|
||||
"bytes", "io", "testing",
|
||||
// indirect dependencies (partial list):
|
||||
"errors", "fmt", "os", "runtime",
|
||||
}
|
||||
|
||||
prog := mainPkg.Prog
|
||||
all := prog.AllPackages()
|
||||
if len(all) <= len(deps) {
|
||||
t.Errorf("unexpected set of loaded packages: %q", all)
|
||||
}
|
||||
for _, path := range deps {
|
||||
pkg := prog.ImportedPackage(path)
|
||||
if pkg == nil {
|
||||
t.Errorf("package not loaded: %q", path)
|
||||
continue
|
||||
}
|
||||
|
||||
// External packages should have no function bodies (except for wrappers).
|
||||
isExt := pkg != mainPkg
|
||||
|
||||
// init()
|
||||
if isExt && !isEmpty(pkg.Func("init")) {
|
||||
t.Errorf("external package %s has non-empty init", pkg)
|
||||
} else if !isExt && isEmpty(pkg.Func("init")) {
|
||||
t.Errorf("main package %s has empty init", pkg)
|
||||
}
|
||||
|
||||
for _, mem := range pkg.Members {
|
||||
switch mem := mem.(type) {
|
||||
case *ssa.Function:
|
||||
// Functions at package level.
|
||||
if isExt && !isEmpty(mem) {
|
||||
t.Errorf("external function %s is non-empty", mem)
|
||||
} else if !isExt && isEmpty(mem) {
|
||||
t.Errorf("function %s is empty", mem)
|
||||
}
|
||||
|
||||
case *ssa.Type:
|
||||
// Methods of named types T.
|
||||
// (In this test, all exported methods belong to *T not T.)
|
||||
if !isExt {
|
||||
t.Fatalf("unexpected name type in main package: %s", mem)
|
||||
}
|
||||
mset := prog.MethodSets.MethodSet(types.NewPointer(mem.Type()))
|
||||
for i, n := 0, mset.Len(); i < n; i++ {
|
||||
m := prog.MethodValue(mset.At(i))
|
||||
// For external types, only synthetic wrappers have code.
|
||||
expExt := !strings.Contains(m.Synthetic, "wrapper")
|
||||
if expExt && !isEmpty(m) {
|
||||
t.Errorf("external method %s is non-empty: %s",
|
||||
m, m.Synthetic)
|
||||
} else if !expExt && isEmpty(m) {
|
||||
t.Errorf("method function %s is empty: %s",
|
||||
m, m.Synthetic)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
expectedCallee := []string{
|
||||
"(*testing.T).Parallel",
|
||||
"(*testing.common).Fail",
|
||||
"testing.Short",
|
||||
"N/A",
|
||||
}
|
||||
callNum := 0
|
||||
for _, b := range mainPkg.Func("main").Blocks {
|
||||
for _, instr := range b.Instrs {
|
||||
switch instr := instr.(type) {
|
||||
case ssa.CallInstruction:
|
||||
call := instr.Common()
|
||||
if want := expectedCallee[callNum]; want != "N/A" {
|
||||
got := call.StaticCallee().String()
|
||||
if want != got {
|
||||
t.Errorf("call #%d from main.main: got callee %s, want %s",
|
||||
callNum, got, want)
|
||||
}
|
||||
}
|
||||
callNum++
|
||||
}
|
||||
}
|
||||
}
|
||||
if callNum != 4 {
|
||||
t.Errorf("in main.main: got %d calls, want %d", callNum, 4)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRuntimeTypes tests that (*Program).RuntimeTypes() includes all necessary types.
|
||||
func TestRuntimeTypes(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
want []string
|
||||
}{
|
||||
// An exported package-level type is needed.
|
||||
{`package A; type T struct{}; func (T) f() {}`,
|
||||
[]string{"*p.T", "p.T"},
|
||||
},
|
||||
// An unexported package-level type is not needed.
|
||||
{`package B; type t struct{}; func (t) f() {}`,
|
||||
nil,
|
||||
},
|
||||
// Subcomponents of type of exported package-level var are needed.
|
||||
{`package C; import "bytes"; var V struct {*bytes.Buffer}`,
|
||||
[]string{"*bytes.Buffer", "*struct{*bytes.Buffer}", "struct{*bytes.Buffer}"},
|
||||
},
|
||||
// Subcomponents of type of unexported package-level var are not needed.
|
||||
{`package D; import "bytes"; var v struct {*bytes.Buffer}`,
|
||||
nil,
|
||||
},
|
||||
// Subcomponents of type of exported package-level function are needed.
|
||||
{`package E; import "bytes"; func F(struct {*bytes.Buffer}) {}`,
|
||||
[]string{"*bytes.Buffer", "struct{*bytes.Buffer}"},
|
||||
},
|
||||
// Subcomponents of type of unexported package-level function are not needed.
|
||||
{`package F; import "bytes"; func f(struct {*bytes.Buffer}) {}`,
|
||||
nil,
|
||||
},
|
||||
// Subcomponents of type of exported method of uninstantiated unexported type are not needed.
|
||||
{`package G; import "bytes"; type x struct{}; func (x) G(struct {*bytes.Buffer}) {}; var v x`,
|
||||
nil,
|
||||
},
|
||||
// ...unless used by MakeInterface.
|
||||
{`package G2; import "bytes"; type x struct{}; func (x) G(struct {*bytes.Buffer}) {}; var v interface{} = x{}`,
|
||||
[]string{"*bytes.Buffer", "*p.x", "p.x", "struct{*bytes.Buffer}"},
|
||||
},
|
||||
// Subcomponents of type of unexported method are not needed.
|
||||
{`package I; import "bytes"; type X struct{}; func (X) G(struct {*bytes.Buffer}) {}`,
|
||||
[]string{"*bytes.Buffer", "*p.X", "p.X", "struct{*bytes.Buffer}"},
|
||||
},
|
||||
// Local types aren't needed.
|
||||
{`package J; import "bytes"; func f() { type T struct {*bytes.Buffer}; var t T; _ = t }`,
|
||||
nil,
|
||||
},
|
||||
// ...unless used by MakeInterface.
|
||||
{`package K; import "bytes"; func f() { type T struct {*bytes.Buffer}; _ = interface{}(T{}) }`,
|
||||
[]string{"*bytes.Buffer", "*p.T", "p.T"},
|
||||
},
|
||||
// Types used as operand of MakeInterface are needed.
|
||||
{`package L; import "bytes"; func f() { _ = interface{}(struct{*bytes.Buffer}{}) }`,
|
||||
[]string{"*bytes.Buffer", "struct{*bytes.Buffer}"},
|
||||
},
|
||||
// MakeInterface is optimized away when storing to a blank.
|
||||
{`package M; import "bytes"; var _ interface{} = struct{*bytes.Buffer}{}`,
|
||||
nil,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
// Parse the file.
|
||||
fset := token.NewFileSet()
|
||||
f, err := parser.ParseFile(fset, "input.go", test.input, 0)
|
||||
if err != nil {
|
||||
t.Errorf("test %q: %s", test.input[:15], err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Create a single-file main package.
|
||||
// Load dependencies from gc binary export data.
|
||||
ssapkg, _, err := ssautil.BuildPackage(new(types.Config), fset,
|
||||
types.NewPackage("p", ""), []*ast.File{f}, ssa.SanityCheckFunctions)
|
||||
if err != nil {
|
||||
t.Errorf("test %q: %s", test.input[:15], err)
|
||||
continue
|
||||
}
|
||||
|
||||
var typstrs []string
|
||||
for _, T := range ssapkg.Prog.RuntimeTypes() {
|
||||
typstrs = append(typstrs, T.String())
|
||||
}
|
||||
sort.Strings(typstrs)
|
||||
|
||||
if !reflect.DeepEqual(typstrs, test.want) {
|
||||
t.Errorf("test 'package %s': got %q, want %q",
|
||||
f.Name.Name, typstrs, test.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestInit tests that synthesized init functions are correctly formed.
|
||||
// Bare init functions omit calls to dependent init functions and the use of
|
||||
// an init guard. They are useful in cases where the client uses a different
|
||||
// calling convention for init functions, or cases where it is easier for a
|
||||
// client to analyze bare init functions. Both of these aspects are used by
|
||||
// the llgo compiler for simpler integration with gccgo's runtime library,
|
||||
// and to simplify the analysis whereby it deduces which stores to globals
|
||||
// can be lowered to global initializers.
|
||||
func TestInit(t *testing.T) {
|
||||
tests := []struct {
|
||||
mode ssa.BuilderMode
|
||||
input, want string
|
||||
}{
|
||||
{0, `package A; import _ "errors"; var i int = 42`,
|
||||
`# Name: A.init
|
||||
# Package: A
|
||||
# Synthetic: package initializer
|
||||
func init():
|
||||
0: entry P:0 S:2
|
||||
t0 = *init$guard bool
|
||||
if t0 goto 2 else 1
|
||||
1: init.start P:1 S:1
|
||||
*init$guard = true:bool
|
||||
t1 = errors.init() ()
|
||||
*i = 42:int
|
||||
jump 2
|
||||
2: init.done P:2 S:0
|
||||
return
|
||||
|
||||
`},
|
||||
{ssa.BareInits, `package B; import _ "errors"; var i int = 42`,
|
||||
`# Name: B.init
|
||||
# Package: B
|
||||
# Synthetic: package initializer
|
||||
func init():
|
||||
0: entry P:0 S:0
|
||||
*i = 42:int
|
||||
return
|
||||
|
||||
`},
|
||||
}
|
||||
for _, test := range tests {
|
||||
// Create a single-file main package.
|
||||
var conf loader.Config
|
||||
f, err := conf.ParseFile("<input>", test.input)
|
||||
if err != nil {
|
||||
t.Errorf("test %q: %s", test.input[:15], err)
|
||||
continue
|
||||
}
|
||||
conf.CreateFromFiles(f.Name.Name, f)
|
||||
|
||||
lprog, err := conf.Load()
|
||||
if err != nil {
|
||||
t.Errorf("test 'package %s': Load: %s", f.Name.Name, err)
|
||||
continue
|
||||
}
|
||||
prog := ssautil.CreateProgram(lprog, test.mode)
|
||||
mainPkg := prog.Package(lprog.Created[0].Pkg)
|
||||
prog.Build()
|
||||
initFunc := mainPkg.Func("init")
|
||||
if initFunc == nil {
|
||||
t.Errorf("test 'package %s': no init function", f.Name.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
var initbuf bytes.Buffer
|
||||
_, err = initFunc.WriteTo(&initbuf)
|
||||
if err != nil {
|
||||
t.Errorf("test 'package %s': WriteTo: %s", f.Name.Name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if initbuf.String() != test.want {
|
||||
t.Errorf("test 'package %s': got %s, want %s", f.Name.Name, initbuf.String(), test.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestSyntheticFuncs checks that the expected synthetic functions are
|
||||
// created, reachable, and not duplicated.
|
||||
func TestSyntheticFuncs(t *testing.T) {
|
||||
const input = `package P
|
||||
type T int
|
||||
func (T) f() int
|
||||
func (*T) g() int
|
||||
var (
|
||||
// thunks
|
||||
a = T.f
|
||||
b = T.f
|
||||
c = (struct{T}).f
|
||||
d = (struct{T}).f
|
||||
e = (*T).g
|
||||
f = (*T).g
|
||||
g = (struct{*T}).g
|
||||
h = (struct{*T}).g
|
||||
|
||||
// bounds
|
||||
i = T(0).f
|
||||
j = T(0).f
|
||||
k = new(T).g
|
||||
l = new(T).g
|
||||
|
||||
// wrappers
|
||||
m interface{} = struct{T}{}
|
||||
n interface{} = struct{T}{}
|
||||
o interface{} = struct{*T}{}
|
||||
p interface{} = struct{*T}{}
|
||||
q interface{} = new(struct{T})
|
||||
r interface{} = new(struct{T})
|
||||
s interface{} = new(struct{*T})
|
||||
t interface{} = new(struct{*T})
|
||||
)
|
||||
`
|
||||
// Parse
|
||||
var conf loader.Config
|
||||
f, err := conf.ParseFile("<input>", input)
|
||||
if err != nil {
|
||||
t.Fatalf("parse: %v", err)
|
||||
}
|
||||
conf.CreateFromFiles(f.Name.Name, f)
|
||||
|
||||
// Load
|
||||
lprog, err := conf.Load()
|
||||
if err != nil {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
|
||||
// Create and build SSA
|
||||
prog := ssautil.CreateProgram(lprog, 0)
|
||||
prog.Build()
|
||||
|
||||
// Enumerate reachable synthetic functions
|
||||
want := map[string]string{
|
||||
"(*P.T).g$bound": "bound method wrapper for func (*P.T).g() int",
|
||||
"(P.T).f$bound": "bound method wrapper for func (P.T).f() int",
|
||||
|
||||
"(*P.T).g$thunk": "thunk for func (*P.T).g() int",
|
||||
"(P.T).f$thunk": "thunk for func (P.T).f() int",
|
||||
"(struct{*P.T}).g$thunk": "thunk for func (*P.T).g() int",
|
||||
"(struct{P.T}).f$thunk": "thunk for func (P.T).f() int",
|
||||
|
||||
"(*P.T).f": "wrapper for func (P.T).f() int",
|
||||
"(*struct{*P.T}).f": "wrapper for func (P.T).f() int",
|
||||
"(*struct{*P.T}).g": "wrapper for func (*P.T).g() int",
|
||||
"(*struct{P.T}).f": "wrapper for func (P.T).f() int",
|
||||
"(*struct{P.T}).g": "wrapper for func (*P.T).g() int",
|
||||
"(struct{*P.T}).f": "wrapper for func (P.T).f() int",
|
||||
"(struct{*P.T}).g": "wrapper for func (*P.T).g() int",
|
||||
"(struct{P.T}).f": "wrapper for func (P.T).f() int",
|
||||
|
||||
"P.init": "package initializer",
|
||||
}
|
||||
for fn := range ssautil.AllFunctions(prog) {
|
||||
if fn.Synthetic == "" {
|
||||
continue
|
||||
}
|
||||
name := fn.String()
|
||||
wantDescr, ok := want[name]
|
||||
if !ok {
|
||||
t.Errorf("got unexpected/duplicate func: %q: %q", name, fn.Synthetic)
|
||||
continue
|
||||
}
|
||||
delete(want, name)
|
||||
|
||||
if wantDescr != fn.Synthetic {
|
||||
t.Errorf("(%s).Synthetic = %q, want %q", name, fn.Synthetic, wantDescr)
|
||||
}
|
||||
}
|
||||
for fn, descr := range want {
|
||||
t.Errorf("want func: %q: %q", fn, descr)
|
||||
}
|
||||
}
|
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
package ssa_test
|
||||
|
||||
import (
|
||||
|
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
package ssa
|
||||
|
||||
// This file defines the Const SSA value type.
|
||||
|
170
go/ssa/const14.go
Normal file
170
go/ssa/const14.go
Normal file
@ -0,0 +1,170 @@
|
||||
// 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.
|
||||
|
||||
// +build !go1.5
|
||||
|
||||
package ssa
|
||||
|
||||
// This file defines the Const SSA value type.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/token"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/tools/go/exact"
|
||||
"golang.org/x/tools/go/types"
|
||||
)
|
||||
|
||||
// NewConst returns a new constant of the specified value and type.
|
||||
// val must be valid according to the specification of Const.Value.
|
||||
//
|
||||
func NewConst(val exact.Value, typ types.Type) *Const {
|
||||
return &Const{typ, val}
|
||||
}
|
||||
|
||||
// intConst returns an 'int' constant that evaluates to i.
|
||||
// (i is an int64 in case the host is narrower than the target.)
|
||||
func intConst(i int64) *Const {
|
||||
return NewConst(exact.MakeInt64(i), tInt)
|
||||
}
|
||||
|
||||
// nilConst returns a nil constant of the specified type, which may
|
||||
// be any reference type, including interfaces.
|
||||
//
|
||||
func nilConst(typ types.Type) *Const {
|
||||
return NewConst(nil, typ)
|
||||
}
|
||||
|
||||
// stringConst returns a 'string' constant that evaluates to s.
|
||||
func stringConst(s string) *Const {
|
||||
return NewConst(exact.MakeString(s), tString)
|
||||
}
|
||||
|
||||
// zeroConst returns a new "zero" constant of the specified type,
|
||||
// which must not be an array or struct type: the zero values of
|
||||
// aggregates are well-defined but cannot be represented by Const.
|
||||
//
|
||||
func zeroConst(t types.Type) *Const {
|
||||
switch t := t.(type) {
|
||||
case *types.Basic:
|
||||
switch {
|
||||
case t.Info()&types.IsBoolean != 0:
|
||||
return NewConst(exact.MakeBool(false), t)
|
||||
case t.Info()&types.IsNumeric != 0:
|
||||
return NewConst(exact.MakeInt64(0), t)
|
||||
case t.Info()&types.IsString != 0:
|
||||
return NewConst(exact.MakeString(""), t)
|
||||
case t.Kind() == types.UnsafePointer:
|
||||
fallthrough
|
||||
case t.Kind() == types.UntypedNil:
|
||||
return nilConst(t)
|
||||
default:
|
||||
panic(fmt.Sprint("zeroConst for unexpected type:", t))
|
||||
}
|
||||
case *types.Pointer, *types.Slice, *types.Interface, *types.Chan, *types.Map, *types.Signature:
|
||||
return nilConst(t)
|
||||
case *types.Named:
|
||||
return NewConst(zeroConst(t.Underlying()).Value, t)
|
||||
case *types.Array, *types.Struct, *types.Tuple:
|
||||
panic(fmt.Sprint("zeroConst applied to aggregate:", t))
|
||||
}
|
||||
panic(fmt.Sprint("zeroConst: unexpected ", t))
|
||||
}
|
||||
|
||||
func (c *Const) RelString(from *types.Package) string {
|
||||
var s string
|
||||
if c.Value == nil {
|
||||
s = "nil"
|
||||
} else if c.Value.Kind() == exact.String {
|
||||
s = exact.StringVal(c.Value)
|
||||
const max = 20
|
||||
// TODO(adonovan): don't cut a rune in half.
|
||||
if len(s) > max {
|
||||
s = s[:max-3] + "..." // abbreviate
|
||||
}
|
||||
s = strconv.Quote(s)
|
||||
} else {
|
||||
s = c.Value.String()
|
||||
}
|
||||
return s + ":" + relType(c.Type(), from)
|
||||
}
|
||||
|
||||
func (c *Const) Name() string {
|
||||
return c.RelString(nil)
|
||||
}
|
||||
|
||||
func (c *Const) String() string {
|
||||
return c.Name()
|
||||
}
|
||||
|
||||
func (c *Const) Type() types.Type {
|
||||
return c.typ
|
||||
}
|
||||
|
||||
func (c *Const) Referrers() *[]Instruction {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Const) Parent() *Function { return nil }
|
||||
|
||||
func (c *Const) Pos() token.Pos {
|
||||
return token.NoPos
|
||||
}
|
||||
|
||||
// IsNil returns true if this constant represents a typed or untyped nil value.
|
||||
func (c *Const) IsNil() bool {
|
||||
return c.Value == nil
|
||||
}
|
||||
|
||||
// Int64 returns the numeric value of this constant truncated to fit
|
||||
// a signed 64-bit integer.
|
||||
//
|
||||
func (c *Const) Int64() int64 {
|
||||
switch x := c.Value; x.Kind() {
|
||||
case exact.Int:
|
||||
if i, ok := exact.Int64Val(x); ok {
|
||||
return i
|
||||
}
|
||||
return 0
|
||||
case exact.Float:
|
||||
f, _ := exact.Float64Val(x)
|
||||
return int64(f)
|
||||
}
|
||||
panic(fmt.Sprintf("unexpected constant value: %T", c.Value))
|
||||
}
|
||||
|
||||
// Uint64 returns the numeric value of this constant truncated to fit
|
||||
// an unsigned 64-bit integer.
|
||||
//
|
||||
func (c *Const) Uint64() uint64 {
|
||||
switch x := c.Value; x.Kind() {
|
||||
case exact.Int:
|
||||
if u, ok := exact.Uint64Val(x); ok {
|
||||
return u
|
||||
}
|
||||
return 0
|
||||
case exact.Float:
|
||||
f, _ := exact.Float64Val(x)
|
||||
return uint64(f)
|
||||
}
|
||||
panic(fmt.Sprintf("unexpected constant value: %T", c.Value))
|
||||
}
|
||||
|
||||
// Float64 returns the numeric value of this constant truncated to fit
|
||||
// a float64.
|
||||
//
|
||||
func (c *Const) Float64() float64 {
|
||||
f, _ := exact.Float64Val(c.Value)
|
||||
return f
|
||||
}
|
||||
|
||||
// Complex128 returns the complex value of this constant truncated to
|
||||
// fit a complex128.
|
||||
//
|
||||
func (c *Const) Complex128() complex128 {
|
||||
re, _ := exact.Float64Val(exact.Real(c.Value))
|
||||
im, _ := exact.Float64Val(exact.Imag(c.Value))
|
||||
return complex(re, im)
|
||||
}
|
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
package ssa
|
||||
|
||||
// This file implements the CREATE phase of SSA construction.
|
||||
|
259
go/ssa/create14.go
Normal file
259
go/ssa/create14.go
Normal file
@ -0,0 +1,259 @@
|
||||
// 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.
|
||||
|
||||
// +build !go1.5
|
||||
|
||||
package ssa
|
||||
|
||||
// This file implements the CREATE phase of SSA construction.
|
||||
// See builder.go for explanation.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/tools/go/types"
|
||||
"golang.org/x/tools/go/types/typeutil"
|
||||
)
|
||||
|
||||
// NewProgram returns a new SSA Program.
|
||||
//
|
||||
// mode controls diagnostics and checking during SSA construction.
|
||||
//
|
||||
func NewProgram(fset *token.FileSet, mode BuilderMode) *Program {
|
||||
prog := &Program{
|
||||
Fset: fset,
|
||||
imported: make(map[string]*Package),
|
||||
packages: make(map[*types.Package]*Package),
|
||||
thunks: make(map[selectionKey]*Function),
|
||||
bounds: make(map[*types.Func]*Function),
|
||||
mode: mode,
|
||||
}
|
||||
|
||||
h := typeutil.MakeHasher() // protected by methodsMu, in effect
|
||||
prog.methodSets.SetHasher(h)
|
||||
prog.canon.SetHasher(h)
|
||||
|
||||
return prog
|
||||
}
|
||||
|
||||
// memberFromObject populates package pkg with a member for the
|
||||
// typechecker object obj.
|
||||
//
|
||||
// For objects from Go source code, syntax is the associated syntax
|
||||
// tree (for funcs and vars only); it will be used during the build
|
||||
// phase.
|
||||
//
|
||||
func memberFromObject(pkg *Package, obj types.Object, syntax ast.Node) {
|
||||
name := obj.Name()
|
||||
switch obj := obj.(type) {
|
||||
case *types.TypeName:
|
||||
pkg.Members[name] = &Type{
|
||||
object: obj,
|
||||
pkg: pkg,
|
||||
}
|
||||
|
||||
case *types.Const:
|
||||
c := &NamedConst{
|
||||
object: obj,
|
||||
Value: NewConst(obj.Val(), obj.Type()),
|
||||
pkg: pkg,
|
||||
}
|
||||
pkg.values[obj] = c.Value
|
||||
pkg.Members[name] = c
|
||||
|
||||
case *types.Var:
|
||||
g := &Global{
|
||||
Pkg: pkg,
|
||||
name: name,
|
||||
object: obj,
|
||||
typ: types.NewPointer(obj.Type()), // address
|
||||
pos: obj.Pos(),
|
||||
}
|
||||
pkg.values[obj] = g
|
||||
pkg.Members[name] = g
|
||||
|
||||
case *types.Func:
|
||||
sig := obj.Type().(*types.Signature)
|
||||
if sig.Recv() == nil && name == "init" {
|
||||
pkg.ninit++
|
||||
name = fmt.Sprintf("init#%d", pkg.ninit)
|
||||
}
|
||||
fn := &Function{
|
||||
name: name,
|
||||
object: obj,
|
||||
Signature: sig,
|
||||
syntax: syntax,
|
||||
pos: obj.Pos(),
|
||||
Pkg: pkg,
|
||||
Prog: pkg.Prog,
|
||||
}
|
||||
if syntax == nil {
|
||||
fn.Synthetic = "loaded from gc object file"
|
||||
}
|
||||
|
||||
pkg.values[obj] = fn
|
||||
if sig.Recv() == nil {
|
||||
pkg.Members[name] = fn // package-level function
|
||||
}
|
||||
|
||||
default: // (incl. *types.Package)
|
||||
panic("unexpected Object type: " + obj.String())
|
||||
}
|
||||
}
|
||||
|
||||
// membersFromDecl populates package pkg with members for each
|
||||
// typechecker object (var, func, const or type) associated with the
|
||||
// specified decl.
|
||||
//
|
||||
func membersFromDecl(pkg *Package, decl ast.Decl) {
|
||||
switch decl := decl.(type) {
|
||||
case *ast.GenDecl: // import, const, type or var
|
||||
switch decl.Tok {
|
||||
case token.CONST:
|
||||
for _, spec := range decl.Specs {
|
||||
for _, id := range spec.(*ast.ValueSpec).Names {
|
||||
if !isBlankIdent(id) {
|
||||
memberFromObject(pkg, pkg.info.Defs[id], nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case token.VAR:
|
||||
for _, spec := range decl.Specs {
|
||||
for _, id := range spec.(*ast.ValueSpec).Names {
|
||||
if !isBlankIdent(id) {
|
||||
memberFromObject(pkg, pkg.info.Defs[id], spec)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case token.TYPE:
|
||||
for _, spec := range decl.Specs {
|
||||
id := spec.(*ast.TypeSpec).Name
|
||||
if !isBlankIdent(id) {
|
||||
memberFromObject(pkg, pkg.info.Defs[id], nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case *ast.FuncDecl:
|
||||
id := decl.Name
|
||||
if !isBlankIdent(id) {
|
||||
memberFromObject(pkg, pkg.info.Defs[id], decl)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CreatePackage constructs and returns an SSA Package from the
|
||||
// specified type-checked, error-free file ASTs, and populates its
|
||||
// Members mapping.
|
||||
//
|
||||
// importable determines whether this package should be returned by a
|
||||
// subsequent call to ImportedPackage(pkg.Path()).
|
||||
//
|
||||
// The real work of building SSA form for each function is not done
|
||||
// until a subsequent call to Package.Build().
|
||||
//
|
||||
func (prog *Program) CreatePackage(pkg *types.Package, files []*ast.File, info *types.Info, importable bool) *Package {
|
||||
p := &Package{
|
||||
Prog: prog,
|
||||
Members: make(map[string]Member),
|
||||
values: make(map[types.Object]Value),
|
||||
Pkg: pkg,
|
||||
info: info, // transient (CREATE and BUILD phases)
|
||||
files: files, // transient (CREATE and BUILD phases)
|
||||
}
|
||||
|
||||
// Add init() function.
|
||||
p.init = &Function{
|
||||
name: "init",
|
||||
Signature: new(types.Signature),
|
||||
Synthetic: "package initializer",
|
||||
Pkg: p,
|
||||
Prog: prog,
|
||||
}
|
||||
p.Members[p.init.name] = p.init
|
||||
|
||||
// CREATE phase.
|
||||
// Allocate all package members: vars, funcs, consts and types.
|
||||
if len(files) > 0 {
|
||||
// Go source package.
|
||||
for _, file := range files {
|
||||
for _, decl := range file.Decls {
|
||||
membersFromDecl(p, decl)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// GC-compiled binary package.
|
||||
// No code.
|
||||
// No position information.
|
||||
scope := p.Pkg.Scope()
|
||||
for _, name := range scope.Names() {
|
||||
obj := scope.Lookup(name)
|
||||
memberFromObject(p, obj, nil)
|
||||
if obj, ok := obj.(*types.TypeName); ok {
|
||||
named := obj.Type().(*types.Named)
|
||||
for i, n := 0, named.NumMethods(); i < n; i++ {
|
||||
memberFromObject(p, named.Method(i), nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if prog.mode&BareInits == 0 {
|
||||
// Add initializer guard variable.
|
||||
initguard := &Global{
|
||||
Pkg: p,
|
||||
name: "init$guard",
|
||||
typ: types.NewPointer(tBool),
|
||||
}
|
||||
p.Members[initguard.Name()] = initguard
|
||||
}
|
||||
|
||||
if prog.mode&GlobalDebug != 0 {
|
||||
p.SetDebugMode(true)
|
||||
}
|
||||
|
||||
if prog.mode&PrintPackages != 0 {
|
||||
printMu.Lock()
|
||||
p.WriteTo(os.Stdout)
|
||||
printMu.Unlock()
|
||||
}
|
||||
|
||||
if importable {
|
||||
prog.imported[p.Pkg.Path()] = p
|
||||
}
|
||||
prog.packages[p.Pkg] = p
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
// printMu serializes printing of Packages/Functions to stdout.
|
||||
var printMu sync.Mutex
|
||||
|
||||
// AllPackages returns a new slice containing all packages in the
|
||||
// program prog in unspecified order.
|
||||
//
|
||||
func (prog *Program) AllPackages() []*Package {
|
||||
pkgs := make([]*Package, 0, len(prog.packages))
|
||||
for _, pkg := range prog.packages {
|
||||
pkgs = append(pkgs, pkg)
|
||||
}
|
||||
return pkgs
|
||||
}
|
||||
|
||||
// ImportedPackage returns the importable SSA Package whose import
|
||||
// path is path, or nil if no such SSA package has been created.
|
||||
//
|
||||
// Not all packages are importable. For example, no import
|
||||
// declaration can resolve to the x_test package created by 'go test'
|
||||
// or the ad-hoc main package created 'go build foo.go'.
|
||||
//
|
||||
func (prog *Program) ImportedPackage(path string) *Package {
|
||||
return prog.imported[path]
|
||||
}
|
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
package ssa
|
||||
|
||||
// Helpers for emitting SSA instructions.
|
||||
|
471
go/ssa/emit14.go
Normal file
471
go/ssa/emit14.go
Normal file
@ -0,0 +1,471 @@
|
||||
// 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.
|
||||
|
||||
// +build !go1.5
|
||||
|
||||
package ssa
|
||||
|
||||
// Helpers for emitting SSA instructions.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
|
||||
"golang.org/x/tools/go/types"
|
||||
)
|
||||
|
||||
// emitNew emits to f a new (heap Alloc) instruction allocating an
|
||||
// object of type typ. pos is the optional source location.
|
||||
//
|
||||
func emitNew(f *Function, typ types.Type, pos token.Pos) *Alloc {
|
||||
v := &Alloc{Heap: true}
|
||||
v.setType(types.NewPointer(typ))
|
||||
v.setPos(pos)
|
||||
f.emit(v)
|
||||
return v
|
||||
}
|
||||
|
||||
// emitLoad emits to f an instruction to load the address addr into a
|
||||
// new temporary, and returns the value so defined.
|
||||
//
|
||||
func emitLoad(f *Function, addr Value) *UnOp {
|
||||
v := &UnOp{Op: token.MUL, X: addr}
|
||||
v.setType(deref(addr.Type()))
|
||||
f.emit(v)
|
||||
return v
|
||||
}
|
||||
|
||||
// emitDebugRef emits to f a DebugRef pseudo-instruction associating
|
||||
// expression e with value v.
|
||||
//
|
||||
func emitDebugRef(f *Function, e ast.Expr, v Value, isAddr bool) {
|
||||
if !f.debugInfo() {
|
||||
return // debugging not enabled
|
||||
}
|
||||
if v == nil || e == nil {
|
||||
panic("nil")
|
||||
}
|
||||
var obj types.Object
|
||||
e = unparen(e)
|
||||
if id, ok := e.(*ast.Ident); ok {
|
||||
if isBlankIdent(id) {
|
||||
return
|
||||
}
|
||||
obj = f.Pkg.objectOf(id)
|
||||
switch obj.(type) {
|
||||
case *types.Nil, *types.Const, *types.Builtin:
|
||||
return
|
||||
}
|
||||
}
|
||||
f.emit(&DebugRef{
|
||||
X: v,
|
||||
Expr: e,
|
||||
IsAddr: isAddr,
|
||||
object: obj,
|
||||
})
|
||||
}
|
||||
|
||||
// emitArith emits to f code to compute the binary operation op(x, y)
|
||||
// where op is an eager shift, logical or arithmetic operation.
|
||||
// (Use emitCompare() for comparisons and Builder.logicalBinop() for
|
||||
// non-eager operations.)
|
||||
//
|
||||
func emitArith(f *Function, op token.Token, x, y Value, t types.Type, pos token.Pos) Value {
|
||||
switch op {
|
||||
case token.SHL, token.SHR:
|
||||
x = emitConv(f, x, t)
|
||||
// y may be signed or an 'untyped' constant.
|
||||
// TODO(adonovan): whence signed values?
|
||||
if b, ok := y.Type().Underlying().(*types.Basic); ok && b.Info()&types.IsUnsigned == 0 {
|
||||
y = emitConv(f, y, types.Typ[types.Uint64])
|
||||
}
|
||||
|
||||
case token.ADD, token.SUB, token.MUL, token.QUO, token.REM, token.AND, token.OR, token.XOR, token.AND_NOT:
|
||||
x = emitConv(f, x, t)
|
||||
y = emitConv(f, y, t)
|
||||
|
||||
default:
|
||||
panic("illegal op in emitArith: " + op.String())
|
||||
|
||||
}
|
||||
v := &BinOp{
|
||||
Op: op,
|
||||
X: x,
|
||||
Y: y,
|
||||
}
|
||||
v.setPos(pos)
|
||||
v.setType(t)
|
||||
return f.emit(v)
|
||||
}
|
||||
|
||||
// emitCompare emits to f code compute the boolean result of
|
||||
// comparison comparison 'x op y'.
|
||||
//
|
||||
func emitCompare(f *Function, op token.Token, x, y Value, pos token.Pos) Value {
|
||||
xt := x.Type().Underlying()
|
||||
yt := y.Type().Underlying()
|
||||
|
||||
// Special case to optimise a tagless SwitchStmt so that
|
||||
// these are equivalent
|
||||
// switch { case e: ...}
|
||||
// switch true { case e: ... }
|
||||
// if e==true { ... }
|
||||
// even in the case when e's type is an interface.
|
||||
// TODO(adonovan): opt: generalise to x==true, false!=y, etc.
|
||||
if x == vTrue && op == token.EQL {
|
||||
if yt, ok := yt.(*types.Basic); ok && yt.Info()&types.IsBoolean != 0 {
|
||||
return y
|
||||
}
|
||||
}
|
||||
|
||||
if types.Identical(xt, yt) {
|
||||
// no conversion necessary
|
||||
} else if _, ok := xt.(*types.Interface); ok {
|
||||
y = emitConv(f, y, x.Type())
|
||||
} else if _, ok := yt.(*types.Interface); ok {
|
||||
x = emitConv(f, x, y.Type())
|
||||
} else if _, ok := x.(*Const); ok {
|
||||
x = emitConv(f, x, y.Type())
|
||||
} else if _, ok := y.(*Const); ok {
|
||||
y = emitConv(f, y, x.Type())
|
||||
} else {
|
||||
// other cases, e.g. channels. No-op.
|
||||
}
|
||||
|
||||
v := &BinOp{
|
||||
Op: op,
|
||||
X: x,
|
||||
Y: y,
|
||||
}
|
||||
v.setPos(pos)
|
||||
v.setType(tBool)
|
||||
return f.emit(v)
|
||||
}
|
||||
|
||||
// isValuePreserving returns true if a conversion from ut_src to
|
||||
// ut_dst is value-preserving, i.e. just a change of type.
|
||||
// Precondition: neither argument is a named type.
|
||||
//
|
||||
func isValuePreserving(ut_src, ut_dst types.Type) bool {
|
||||
// Identical underlying types?
|
||||
if types.Identical(ut_dst, ut_src) {
|
||||
return true
|
||||
}
|
||||
|
||||
switch ut_dst.(type) {
|
||||
case *types.Chan:
|
||||
// Conversion between channel types?
|
||||
_, ok := ut_src.(*types.Chan)
|
||||
return ok
|
||||
|
||||
case *types.Pointer:
|
||||
// Conversion between pointers with identical base types?
|
||||
_, ok := ut_src.(*types.Pointer)
|
||||
return ok
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// emitConv emits to f code to convert Value val to exactly type typ,
|
||||
// and returns the converted value. Implicit conversions are required
|
||||
// by language assignability rules in assignments, parameter passing,
|
||||
// etc. Conversions cannot fail dynamically.
|
||||
//
|
||||
func emitConv(f *Function, val Value, typ types.Type) Value {
|
||||
t_src := val.Type()
|
||||
|
||||
// Identical types? Conversion is a no-op.
|
||||
if types.Identical(t_src, typ) {
|
||||
return val
|
||||
}
|
||||
|
||||
ut_dst := typ.Underlying()
|
||||
ut_src := t_src.Underlying()
|
||||
|
||||
// Just a change of type, but not value or representation?
|
||||
if isValuePreserving(ut_src, ut_dst) {
|
||||
c := &ChangeType{X: val}
|
||||
c.setType(typ)
|
||||
return f.emit(c)
|
||||
}
|
||||
|
||||
// Conversion to, or construction of a value of, an interface type?
|
||||
if _, ok := ut_dst.(*types.Interface); ok {
|
||||
// Assignment from one interface type to another?
|
||||
if _, ok := ut_src.(*types.Interface); ok {
|
||||
c := &ChangeInterface{X: val}
|
||||
c.setType(typ)
|
||||
return f.emit(c)
|
||||
}
|
||||
|
||||
// Untyped nil constant? Return interface-typed nil constant.
|
||||
if ut_src == tUntypedNil {
|
||||
return nilConst(typ)
|
||||
}
|
||||
|
||||
// Convert (non-nil) "untyped" literals to their default type.
|
||||
if t, ok := ut_src.(*types.Basic); ok && t.Info()&types.IsUntyped != 0 {
|
||||
val = emitConv(f, val, DefaultType(ut_src))
|
||||
}
|
||||
|
||||
f.Pkg.Prog.needMethodsOf(val.Type())
|
||||
mi := &MakeInterface{X: val}
|
||||
mi.setType(typ)
|
||||
return f.emit(mi)
|
||||
}
|
||||
|
||||
// Conversion of a compile-time constant value?
|
||||
if c, ok := val.(*Const); ok {
|
||||
if _, ok := ut_dst.(*types.Basic); ok || c.IsNil() {
|
||||
// Conversion of a compile-time constant to
|
||||
// another constant type results in a new
|
||||
// constant of the destination type and
|
||||
// (initially) the same abstract value.
|
||||
// We don't truncate the value yet.
|
||||
return NewConst(c.Value, typ)
|
||||
}
|
||||
|
||||
// We're converting from constant to non-constant type,
|
||||
// e.g. string -> []byte/[]rune.
|
||||
}
|
||||
|
||||
// A representation-changing conversion?
|
||||
// At least one of {ut_src,ut_dst} must be *Basic.
|
||||
// (The other may be []byte or []rune.)
|
||||
_, ok1 := ut_src.(*types.Basic)
|
||||
_, ok2 := ut_dst.(*types.Basic)
|
||||
if ok1 || ok2 {
|
||||
c := &Convert{X: val}
|
||||
c.setType(typ)
|
||||
return f.emit(c)
|
||||
}
|
||||
|
||||
panic(fmt.Sprintf("in %s: cannot convert %s (%s) to %s", f, val, val.Type(), typ))
|
||||
}
|
||||
|
||||
// emitStore emits to f an instruction to store value val at location
|
||||
// addr, applying implicit conversions as required by assignability rules.
|
||||
//
|
||||
func emitStore(f *Function, addr, val Value, pos token.Pos) *Store {
|
||||
s := &Store{
|
||||
Addr: addr,
|
||||
Val: emitConv(f, val, deref(addr.Type())),
|
||||
pos: pos,
|
||||
}
|
||||
f.emit(s)
|
||||
return s
|
||||
}
|
||||
|
||||
// emitJump emits to f a jump to target, and updates the control-flow graph.
|
||||
// Postcondition: f.currentBlock is nil.
|
||||
//
|
||||
func emitJump(f *Function, target *BasicBlock) {
|
||||
b := f.currentBlock
|
||||
b.emit(new(Jump))
|
||||
addEdge(b, target)
|
||||
f.currentBlock = nil
|
||||
}
|
||||
|
||||
// emitIf emits to f a conditional jump to tblock or fblock based on
|
||||
// cond, and updates the control-flow graph.
|
||||
// Postcondition: f.currentBlock is nil.
|
||||
//
|
||||
func emitIf(f *Function, cond Value, tblock, fblock *BasicBlock) {
|
||||
b := f.currentBlock
|
||||
b.emit(&If{Cond: cond})
|
||||
addEdge(b, tblock)
|
||||
addEdge(b, fblock)
|
||||
f.currentBlock = nil
|
||||
}
|
||||
|
||||
// emitExtract emits to f an instruction to extract the index'th
|
||||
// component of tuple. It returns the extracted value.
|
||||
//
|
||||
func emitExtract(f *Function, tuple Value, index int) Value {
|
||||
e := &Extract{Tuple: tuple, Index: index}
|
||||
e.setType(tuple.Type().(*types.Tuple).At(index).Type())
|
||||
return f.emit(e)
|
||||
}
|
||||
|
||||
// emitTypeAssert emits to f a type assertion value := x.(t) and
|
||||
// returns the value. x.Type() must be an interface.
|
||||
//
|
||||
func emitTypeAssert(f *Function, x Value, t types.Type, pos token.Pos) Value {
|
||||
a := &TypeAssert{X: x, AssertedType: t}
|
||||
a.setPos(pos)
|
||||
a.setType(t)
|
||||
return f.emit(a)
|
||||
}
|
||||
|
||||
// emitTypeTest emits to f a type test value,ok := x.(t) and returns
|
||||
// a (value, ok) tuple. x.Type() must be an interface.
|
||||
//
|
||||
func emitTypeTest(f *Function, x Value, t types.Type, pos token.Pos) Value {
|
||||
a := &TypeAssert{
|
||||
X: x,
|
||||
AssertedType: t,
|
||||
CommaOk: true,
|
||||
}
|
||||
a.setPos(pos)
|
||||
a.setType(types.NewTuple(
|
||||
newVar("value", t),
|
||||
varOk,
|
||||
))
|
||||
return f.emit(a)
|
||||
}
|
||||
|
||||
// emitTailCall emits to f a function call in tail position. The
|
||||
// caller is responsible for all fields of 'call' except its type.
|
||||
// Intended for wrapper methods.
|
||||
// Precondition: f does/will not use deferred procedure calls.
|
||||
// Postcondition: f.currentBlock is nil.
|
||||
//
|
||||
func emitTailCall(f *Function, call *Call) {
|
||||
tresults := f.Signature.Results()
|
||||
nr := tresults.Len()
|
||||
if nr == 1 {
|
||||
call.typ = tresults.At(0).Type()
|
||||
} else {
|
||||
call.typ = tresults
|
||||
}
|
||||
tuple := f.emit(call)
|
||||
var ret Return
|
||||
switch nr {
|
||||
case 0:
|
||||
// no-op
|
||||
case 1:
|
||||
ret.Results = []Value{tuple}
|
||||
default:
|
||||
for i := 0; i < nr; i++ {
|
||||
v := emitExtract(f, tuple, i)
|
||||
// TODO(adonovan): in principle, this is required:
|
||||
// v = emitConv(f, o.Type, f.Signature.Results[i].Type)
|
||||
// but in practice emitTailCall is only used when
|
||||
// the types exactly match.
|
||||
ret.Results = append(ret.Results, v)
|
||||
}
|
||||
}
|
||||
f.emit(&ret)
|
||||
f.currentBlock = nil
|
||||
}
|
||||
|
||||
// emitImplicitSelections emits to f code to apply the sequence of
|
||||
// implicit field selections specified by indices to base value v, and
|
||||
// returns the selected value.
|
||||
//
|
||||
// If v is the address of a struct, the result will be the address of
|
||||
// a field; if it is the value of a struct, the result will be the
|
||||
// value of a field.
|
||||
//
|
||||
func emitImplicitSelections(f *Function, v Value, indices []int) Value {
|
||||
for _, index := range indices {
|
||||
fld := deref(v.Type()).Underlying().(*types.Struct).Field(index)
|
||||
|
||||
if isPointer(v.Type()) {
|
||||
instr := &FieldAddr{
|
||||
X: v,
|
||||
Field: index,
|
||||
}
|
||||
instr.setType(types.NewPointer(fld.Type()))
|
||||
v = f.emit(instr)
|
||||
// Load the field's value iff indirectly embedded.
|
||||
if isPointer(fld.Type()) {
|
||||
v = emitLoad(f, v)
|
||||
}
|
||||
} else {
|
||||
instr := &Field{
|
||||
X: v,
|
||||
Field: index,
|
||||
}
|
||||
instr.setType(fld.Type())
|
||||
v = f.emit(instr)
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// emitFieldSelection emits to f code to select the index'th field of v.
|
||||
//
|
||||
// If wantAddr, the input must be a pointer-to-struct and the result
|
||||
// will be the field's address; otherwise the result will be the
|
||||
// field's value.
|
||||
// Ident id is used for position and debug info.
|
||||
//
|
||||
func emitFieldSelection(f *Function, v Value, index int, wantAddr bool, id *ast.Ident) Value {
|
||||
fld := deref(v.Type()).Underlying().(*types.Struct).Field(index)
|
||||
if isPointer(v.Type()) {
|
||||
instr := &FieldAddr{
|
||||
X: v,
|
||||
Field: index,
|
||||
}
|
||||
instr.setPos(id.Pos())
|
||||
instr.setType(types.NewPointer(fld.Type()))
|
||||
v = f.emit(instr)
|
||||
// Load the field's value iff we don't want its address.
|
||||
if !wantAddr {
|
||||
v = emitLoad(f, v)
|
||||
}
|
||||
} else {
|
||||
instr := &Field{
|
||||
X: v,
|
||||
Field: index,
|
||||
}
|
||||
instr.setPos(id.Pos())
|
||||
instr.setType(fld.Type())
|
||||
v = f.emit(instr)
|
||||
}
|
||||
emitDebugRef(f, id, v, wantAddr)
|
||||
return v
|
||||
}
|
||||
|
||||
// zeroValue emits to f code to produce a zero value of type t,
|
||||
// and returns it.
|
||||
//
|
||||
func zeroValue(f *Function, t types.Type) Value {
|
||||
switch t.Underlying().(type) {
|
||||
case *types.Struct, *types.Array:
|
||||
return emitLoad(f, f.addLocal(t, token.NoPos))
|
||||
default:
|
||||
return zeroConst(t)
|
||||
}
|
||||
}
|
||||
|
||||
// createRecoverBlock emits to f a block of code to return after a
|
||||
// recovered panic, and sets f.Recover to it.
|
||||
//
|
||||
// If f's result parameters are named, the code loads and returns
|
||||
// their current values, otherwise it returns the zero values of their
|
||||
// type.
|
||||
//
|
||||
// Idempotent.
|
||||
//
|
||||
func createRecoverBlock(f *Function) {
|
||||
if f.Recover != nil {
|
||||
return // already created
|
||||
}
|
||||
saved := f.currentBlock
|
||||
|
||||
f.Recover = f.newBasicBlock("recover")
|
||||
f.currentBlock = f.Recover
|
||||
|
||||
var results []Value
|
||||
if f.namedResults != nil {
|
||||
// Reload NRPs to form value tuple.
|
||||
for _, r := range f.namedResults {
|
||||
results = append(results, emitLoad(f, r))
|
||||
}
|
||||
} else {
|
||||
R := f.Signature.Results()
|
||||
for i, n := 0, R.Len(); i < n; i++ {
|
||||
T := R.At(i).Type()
|
||||
|
||||
// Return zero value of each result type.
|
||||
results = append(results, zeroValue(f, T))
|
||||
}
|
||||
}
|
||||
f.emit(&Return{Results: results})
|
||||
|
||||
f.currentBlock = saved
|
||||
}
|
140
go/ssa/example14_test.go
Normal file
140
go/ssa/example14_test.go
Normal file
@ -0,0 +1,140 @@
|
||||
// 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.
|
||||
|
||||
// +build !go1.5
|
||||
|
||||
package ssa_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
|
||||
"golang.org/x/tools/go/loader"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/tools/go/ssa/ssautil"
|
||||
"golang.org/x/tools/go/types"
|
||||
)
|
||||
|
||||
const hello = `
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
const message = "Hello, World!"
|
||||
|
||||
func main() {
|
||||
fmt.Println(message)
|
||||
}
|
||||
`
|
||||
|
||||
// This program demonstrates how to run the SSA builder on a single
|
||||
// package of one or more already-parsed files. Its dependencies are
|
||||
// loaded from compiler export data. This is what you'd typically use
|
||||
// for a compiler; it does not depend on golang.org/x/tools/go/loader.
|
||||
//
|
||||
// It shows the printed representation of packages, functions, and
|
||||
// instructions. Within the function listing, the name of each
|
||||
// BasicBlock such as ".0.entry" is printed left-aligned, followed by
|
||||
// the block's Instructions.
|
||||
//
|
||||
// For each instruction that defines an SSA virtual register
|
||||
// (i.e. implements Value), the type of that value is shown in the
|
||||
// right column.
|
||||
//
|
||||
// Build and run the ssadump.go program if you want a standalone tool
|
||||
// with similar functionality. It is located at
|
||||
// golang.org/x/tools/cmd/ssadump.
|
||||
//
|
||||
func ExampleBuildPackage() {
|
||||
// Parse the source files.
|
||||
fset := token.NewFileSet()
|
||||
f, err := parser.ParseFile(fset, "hello.go", hello, parser.ParseComments)
|
||||
if err != nil {
|
||||
fmt.Print(err) // parse error
|
||||
return
|
||||
}
|
||||
files := []*ast.File{f}
|
||||
|
||||
// Create the type-checker's package.
|
||||
pkg := types.NewPackage("hello", "")
|
||||
|
||||
// Type-check the package, load dependencies.
|
||||
// Create and build the SSA program.
|
||||
hello, _, err := ssautil.BuildPackage(
|
||||
new(types.Config), fset, pkg, files, ssa.SanityCheckFunctions)
|
||||
if err != nil {
|
||||
fmt.Print(err) // type error in some package
|
||||
return
|
||||
}
|
||||
|
||||
// Print out the package.
|
||||
hello.WriteTo(os.Stdout)
|
||||
|
||||
// Print out the package-level functions.
|
||||
hello.Func("init").WriteTo(os.Stdout)
|
||||
hello.Func("main").WriteTo(os.Stdout)
|
||||
|
||||
// Output:
|
||||
//
|
||||
// package hello:
|
||||
// func init func()
|
||||
// var init$guard bool
|
||||
// func main func()
|
||||
// const message message = "Hello, World!":untyped string
|
||||
//
|
||||
// # Name: hello.init
|
||||
// # Package: hello
|
||||
// # Synthetic: package initializer
|
||||
// func init():
|
||||
// 0: entry P:0 S:2
|
||||
// t0 = *init$guard bool
|
||||
// if t0 goto 2 else 1
|
||||
// 1: init.start P:1 S:1
|
||||
// *init$guard = true:bool
|
||||
// t1 = fmt.init() ()
|
||||
// jump 2
|
||||
// 2: init.done P:2 S:0
|
||||
// return
|
||||
//
|
||||
// # Name: hello.main
|
||||
// # Package: hello
|
||||
// # Location: hello.go:8:6
|
||||
// func main():
|
||||
// 0: entry P:0 S:0
|
||||
// t0 = new [1]interface{} (varargs) *[1]interface{}
|
||||
// t1 = &t0[0:int] *interface{}
|
||||
// t2 = make interface{} <- string ("Hello, World!":string) interface{}
|
||||
// *t1 = t2
|
||||
// t3 = slice t0[:] []interface{}
|
||||
// t4 = fmt.Println(t3...) (n int, err error)
|
||||
// return
|
||||
}
|
||||
|
||||
// This program shows how to load a main package (cmd/cover) and all its
|
||||
// dependencies from source, using the loader, and then build SSA code
|
||||
// for the entire program. This is what you'd typically use for a
|
||||
// whole-program analysis.
|
||||
//
|
||||
func ExampleLoadProgram() {
|
||||
// Load cmd/cover and its dependencies.
|
||||
var conf loader.Config
|
||||
conf.Import("cmd/cover")
|
||||
lprog, err := conf.Load()
|
||||
if err != nil {
|
||||
fmt.Print(err) // type error in some package
|
||||
return
|
||||
}
|
||||
|
||||
// Create SSA-form program representation.
|
||||
prog := ssautil.CreateProgram(lprog, ssa.SanityCheckFunctions)
|
||||
|
||||
// Build SSA code for the entire cmd/cover program.
|
||||
prog.Build()
|
||||
|
||||
// Output:
|
||||
}
|
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
package ssa_test
|
||||
|
||||
import (
|
||||
|
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
package ssa
|
||||
|
||||
// This file implements the Function and BasicBlock types.
|
||||
|
692
go/ssa/func14.go
Normal file
692
go/ssa/func14.go
Normal file
@ -0,0 +1,692 @@
|
||||
// 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.
|
||||
|
||||
// +build !go1.5
|
||||
|
||||
package ssa
|
||||
|
||||
// This file implements the Function and BasicBlock types.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/types"
|
||||
)
|
||||
|
||||
// addEdge adds a control-flow graph edge from from to to.
|
||||
func addEdge(from, to *BasicBlock) {
|
||||
from.Succs = append(from.Succs, to)
|
||||
to.Preds = append(to.Preds, from)
|
||||
}
|
||||
|
||||
// Parent returns the function that contains block b.
|
||||
func (b *BasicBlock) Parent() *Function { return b.parent }
|
||||
|
||||
// String returns a human-readable label of this block.
|
||||
// It is not guaranteed unique within the function.
|
||||
//
|
||||
func (b *BasicBlock) String() string {
|
||||
return fmt.Sprintf("%d", b.Index)
|
||||
}
|
||||
|
||||
// emit appends an instruction to the current basic block.
|
||||
// If the instruction defines a Value, it is returned.
|
||||
//
|
||||
func (b *BasicBlock) emit(i Instruction) Value {
|
||||
i.setBlock(b)
|
||||
b.Instrs = append(b.Instrs, i)
|
||||
v, _ := i.(Value)
|
||||
return v
|
||||
}
|
||||
|
||||
// predIndex returns the i such that b.Preds[i] == c or panics if
|
||||
// there is none.
|
||||
func (b *BasicBlock) predIndex(c *BasicBlock) int {
|
||||
for i, pred := range b.Preds {
|
||||
if pred == c {
|
||||
return i
|
||||
}
|
||||
}
|
||||
panic(fmt.Sprintf("no edge %s -> %s", c, b))
|
||||
}
|
||||
|
||||
// hasPhi returns true if b.Instrs contains φ-nodes.
|
||||
func (b *BasicBlock) hasPhi() bool {
|
||||
_, ok := b.Instrs[0].(*Phi)
|
||||
return ok
|
||||
}
|
||||
|
||||
// phis returns the prefix of b.Instrs containing all the block's φ-nodes.
|
||||
func (b *BasicBlock) phis() []Instruction {
|
||||
for i, instr := range b.Instrs {
|
||||
if _, ok := instr.(*Phi); !ok {
|
||||
return b.Instrs[:i]
|
||||
}
|
||||
}
|
||||
return nil // unreachable in well-formed blocks
|
||||
}
|
||||
|
||||
// replacePred replaces all occurrences of p in b's predecessor list with q.
|
||||
// Ordinarily there should be at most one.
|
||||
//
|
||||
func (b *BasicBlock) replacePred(p, q *BasicBlock) {
|
||||
for i, pred := range b.Preds {
|
||||
if pred == p {
|
||||
b.Preds[i] = q
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// replaceSucc replaces all occurrences of p in b's successor list with q.
|
||||
// Ordinarily there should be at most one.
|
||||
//
|
||||
func (b *BasicBlock) replaceSucc(p, q *BasicBlock) {
|
||||
for i, succ := range b.Succs {
|
||||
if succ == p {
|
||||
b.Succs[i] = q
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// removePred removes all occurrences of p in b's
|
||||
// predecessor list and φ-nodes.
|
||||
// Ordinarily there should be at most one.
|
||||
//
|
||||
func (b *BasicBlock) removePred(p *BasicBlock) {
|
||||
phis := b.phis()
|
||||
|
||||
// We must preserve edge order for φ-nodes.
|
||||
j := 0
|
||||
for i, pred := range b.Preds {
|
||||
if pred != p {
|
||||
b.Preds[j] = b.Preds[i]
|
||||
// Strike out φ-edge too.
|
||||
for _, instr := range phis {
|
||||
phi := instr.(*Phi)
|
||||
phi.Edges[j] = phi.Edges[i]
|
||||
}
|
||||
j++
|
||||
}
|
||||
}
|
||||
// Nil out b.Preds[j:] and φ-edges[j:] to aid GC.
|
||||
for i := j; i < len(b.Preds); i++ {
|
||||
b.Preds[i] = nil
|
||||
for _, instr := range phis {
|
||||
instr.(*Phi).Edges[i] = nil
|
||||
}
|
||||
}
|
||||
b.Preds = b.Preds[:j]
|
||||
for _, instr := range phis {
|
||||
phi := instr.(*Phi)
|
||||
phi.Edges = phi.Edges[:j]
|
||||
}
|
||||
}
|
||||
|
||||
// Destinations associated with unlabelled for/switch/select stmts.
|
||||
// We push/pop one of these as we enter/leave each construct and for
|
||||
// each BranchStmt we scan for the innermost target of the right type.
|
||||
//
|
||||
type targets struct {
|
||||
tail *targets // rest of stack
|
||||
_break *BasicBlock
|
||||
_continue *BasicBlock
|
||||
_fallthrough *BasicBlock
|
||||
}
|
||||
|
||||
// Destinations associated with a labelled block.
|
||||
// We populate these as labels are encountered in forward gotos or
|
||||
// labelled statements.
|
||||
//
|
||||
type lblock struct {
|
||||
_goto *BasicBlock
|
||||
_break *BasicBlock
|
||||
_continue *BasicBlock
|
||||
}
|
||||
|
||||
// labelledBlock returns the branch target associated with the
|
||||
// specified label, creating it if needed.
|
||||
//
|
||||
func (f *Function) labelledBlock(label *ast.Ident) *lblock {
|
||||
lb := f.lblocks[label.Obj]
|
||||
if lb == nil {
|
||||
lb = &lblock{_goto: f.newBasicBlock(label.Name)}
|
||||
if f.lblocks == nil {
|
||||
f.lblocks = make(map[*ast.Object]*lblock)
|
||||
}
|
||||
f.lblocks[label.Obj] = lb
|
||||
}
|
||||
return lb
|
||||
}
|
||||
|
||||
// addParam adds a (non-escaping) parameter to f.Params of the
|
||||
// specified name, type and source position.
|
||||
//
|
||||
func (f *Function) addParam(name string, typ types.Type, pos token.Pos) *Parameter {
|
||||
v := &Parameter{
|
||||
name: name,
|
||||
typ: typ,
|
||||
pos: pos,
|
||||
parent: f,
|
||||
}
|
||||
f.Params = append(f.Params, v)
|
||||
return v
|
||||
}
|
||||
|
||||
func (f *Function) addParamObj(obj types.Object) *Parameter {
|
||||
name := obj.Name()
|
||||
if name == "" {
|
||||
name = fmt.Sprintf("arg%d", len(f.Params))
|
||||
}
|
||||
param := f.addParam(name, obj.Type(), obj.Pos())
|
||||
param.object = obj
|
||||
return param
|
||||
}
|
||||
|
||||
// addSpilledParam declares a parameter that is pre-spilled to the
|
||||
// stack; the function body will load/store the spilled location.
|
||||
// Subsequent lifting will eliminate spills where possible.
|
||||
//
|
||||
func (f *Function) addSpilledParam(obj types.Object) {
|
||||
param := f.addParamObj(obj)
|
||||
spill := &Alloc{Comment: obj.Name()}
|
||||
spill.setType(types.NewPointer(obj.Type()))
|
||||
spill.setPos(obj.Pos())
|
||||
f.objects[obj] = spill
|
||||
f.Locals = append(f.Locals, spill)
|
||||
f.emit(spill)
|
||||
f.emit(&Store{Addr: spill, Val: param})
|
||||
}
|
||||
|
||||
// startBody initializes the function prior to generating SSA code for its body.
|
||||
// Precondition: f.Type() already set.
|
||||
//
|
||||
func (f *Function) startBody() {
|
||||
f.currentBlock = f.newBasicBlock("entry")
|
||||
f.objects = make(map[types.Object]Value) // needed for some synthetics, e.g. init
|
||||
}
|
||||
|
||||
// createSyntacticParams populates f.Params and generates code (spills
|
||||
// and named result locals) for all the parameters declared in the
|
||||
// syntax. In addition it populates the f.objects mapping.
|
||||
//
|
||||
// Preconditions:
|
||||
// f.startBody() was called.
|
||||
// Postcondition:
|
||||
// len(f.Params) == len(f.Signature.Params) + (f.Signature.Recv() ? 1 : 0)
|
||||
//
|
||||
func (f *Function) createSyntacticParams(recv *ast.FieldList, functype *ast.FuncType) {
|
||||
// Receiver (at most one inner iteration).
|
||||
if recv != nil {
|
||||
for _, field := range recv.List {
|
||||
for _, n := range field.Names {
|
||||
f.addSpilledParam(f.Pkg.info.Defs[n])
|
||||
}
|
||||
// Anonymous receiver? No need to spill.
|
||||
if field.Names == nil {
|
||||
f.addParamObj(f.Signature.Recv())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parameters.
|
||||
if functype.Params != nil {
|
||||
n := len(f.Params) // 1 if has recv, 0 otherwise
|
||||
for _, field := range functype.Params.List {
|
||||
for _, n := range field.Names {
|
||||
f.addSpilledParam(f.Pkg.info.Defs[n])
|
||||
}
|
||||
// Anonymous parameter? No need to spill.
|
||||
if field.Names == nil {
|
||||
f.addParamObj(f.Signature.Params().At(len(f.Params) - n))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Named results.
|
||||
if functype.Results != nil {
|
||||
for _, field := range functype.Results.List {
|
||||
// Implicit "var" decl of locals for named results.
|
||||
for _, n := range field.Names {
|
||||
f.namedResults = append(f.namedResults, f.addLocalForIdent(n))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// numberRegisters assigns numbers to all SSA registers
|
||||
// (value-defining Instructions) in f, to aid debugging.
|
||||
// (Non-Instruction Values are named at construction.)
|
||||
//
|
||||
func numberRegisters(f *Function) {
|
||||
v := 0
|
||||
for _, b := range f.Blocks {
|
||||
for _, instr := range b.Instrs {
|
||||
switch instr.(type) {
|
||||
case Value:
|
||||
instr.(interface {
|
||||
setNum(int)
|
||||
}).setNum(v)
|
||||
v++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// buildReferrers populates the def/use information in all non-nil
|
||||
// Value.Referrers slice.
|
||||
// Precondition: all such slices are initially empty.
|
||||
func buildReferrers(f *Function) {
|
||||
var rands []*Value
|
||||
for _, b := range f.Blocks {
|
||||
for _, instr := range b.Instrs {
|
||||
rands = instr.Operands(rands[:0]) // recycle storage
|
||||
for _, rand := range rands {
|
||||
if r := *rand; r != nil {
|
||||
if ref := r.Referrers(); ref != nil {
|
||||
*ref = append(*ref, instr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// finishBody() finalizes the function after SSA code generation of its body.
|
||||
func (f *Function) finishBody() {
|
||||
f.objects = nil
|
||||
f.currentBlock = nil
|
||||
f.lblocks = nil
|
||||
|
||||
// Don't pin the AST in memory (except in debug mode).
|
||||
if n := f.syntax; n != nil && !f.debugInfo() {
|
||||
f.syntax = extentNode{n.Pos(), n.End()}
|
||||
}
|
||||
|
||||
// Remove from f.Locals any Allocs that escape to the heap.
|
||||
j := 0
|
||||
for _, l := range f.Locals {
|
||||
if !l.Heap {
|
||||
f.Locals[j] = l
|
||||
j++
|
||||
}
|
||||
}
|
||||
// Nil out f.Locals[j:] to aid GC.
|
||||
for i := j; i < len(f.Locals); i++ {
|
||||
f.Locals[i] = nil
|
||||
}
|
||||
f.Locals = f.Locals[:j]
|
||||
|
||||
optimizeBlocks(f)
|
||||
|
||||
buildReferrers(f)
|
||||
|
||||
buildDomTree(f)
|
||||
|
||||
if f.Prog.mode&NaiveForm == 0 {
|
||||
// For debugging pre-state of lifting pass:
|
||||
// numberRegisters(f)
|
||||
// f.WriteTo(os.Stderr)
|
||||
lift(f)
|
||||
}
|
||||
|
||||
f.namedResults = nil // (used by lifting)
|
||||
|
||||
numberRegisters(f)
|
||||
|
||||
if f.Prog.mode&PrintFunctions != 0 {
|
||||
printMu.Lock()
|
||||
f.WriteTo(os.Stdout)
|
||||
printMu.Unlock()
|
||||
}
|
||||
|
||||
if f.Prog.mode&SanityCheckFunctions != 0 {
|
||||
mustSanityCheck(f, nil)
|
||||
}
|
||||
}
|
||||
|
||||
// removeNilBlocks eliminates nils from f.Blocks and updates each
|
||||
// BasicBlock.Index. Use this after any pass that may delete blocks.
|
||||
//
|
||||
func (f *Function) removeNilBlocks() {
|
||||
j := 0
|
||||
for _, b := range f.Blocks {
|
||||
if b != nil {
|
||||
b.Index = j
|
||||
f.Blocks[j] = b
|
||||
j++
|
||||
}
|
||||
}
|
||||
// Nil out f.Blocks[j:] to aid GC.
|
||||
for i := j; i < len(f.Blocks); i++ {
|
||||
f.Blocks[i] = nil
|
||||
}
|
||||
f.Blocks = f.Blocks[:j]
|
||||
}
|
||||
|
||||
// SetDebugMode sets the debug mode for package pkg. If true, all its
|
||||
// functions will include full debug info. This greatly increases the
|
||||
// size of the instruction stream, and causes Functions to depend upon
|
||||
// the ASTs, potentially keeping them live in memory for longer.
|
||||
//
|
||||
func (pkg *Package) SetDebugMode(debug bool) {
|
||||
// TODO(adonovan): do we want ast.File granularity?
|
||||
pkg.debug = debug
|
||||
}
|
||||
|
||||
// debugInfo reports whether debug info is wanted for this function.
|
||||
func (f *Function) debugInfo() bool {
|
||||
return f.Pkg != nil && f.Pkg.debug
|
||||
}
|
||||
|
||||
// addNamedLocal creates a local variable, adds it to function f and
|
||||
// returns it. Its name and type are taken from obj. Subsequent
|
||||
// calls to f.lookup(obj) will return the same local.
|
||||
//
|
||||
func (f *Function) addNamedLocal(obj types.Object) *Alloc {
|
||||
l := f.addLocal(obj.Type(), obj.Pos())
|
||||
l.Comment = obj.Name()
|
||||
f.objects[obj] = l
|
||||
return l
|
||||
}
|
||||
|
||||
func (f *Function) addLocalForIdent(id *ast.Ident) *Alloc {
|
||||
return f.addNamedLocal(f.Pkg.info.Defs[id])
|
||||
}
|
||||
|
||||
// addLocal creates an anonymous local variable of type typ, adds it
|
||||
// to function f and returns it. pos is the optional source location.
|
||||
//
|
||||
func (f *Function) addLocal(typ types.Type, pos token.Pos) *Alloc {
|
||||
v := &Alloc{}
|
||||
v.setType(types.NewPointer(typ))
|
||||
v.setPos(pos)
|
||||
f.Locals = append(f.Locals, v)
|
||||
f.emit(v)
|
||||
return v
|
||||
}
|
||||
|
||||
// lookup returns the address of the named variable identified by obj
|
||||
// that is local to function f or one of its enclosing functions.
|
||||
// If escaping, the reference comes from a potentially escaping pointer
|
||||
// expression and the referent must be heap-allocated.
|
||||
//
|
||||
func (f *Function) lookup(obj types.Object, escaping bool) Value {
|
||||
if v, ok := f.objects[obj]; ok {
|
||||
if alloc, ok := v.(*Alloc); ok && escaping {
|
||||
alloc.Heap = true
|
||||
}
|
||||
return v // function-local var (address)
|
||||
}
|
||||
|
||||
// Definition must be in an enclosing function;
|
||||
// plumb it through intervening closures.
|
||||
if f.parent == nil {
|
||||
panic("no ssa.Value for " + obj.String())
|
||||
}
|
||||
outer := f.parent.lookup(obj, true) // escaping
|
||||
v := &FreeVar{
|
||||
name: obj.Name(),
|
||||
typ: outer.Type(),
|
||||
pos: outer.Pos(),
|
||||
outer: outer,
|
||||
parent: f,
|
||||
}
|
||||
f.objects[obj] = v
|
||||
f.FreeVars = append(f.FreeVars, v)
|
||||
return v
|
||||
}
|
||||
|
||||
// emit emits the specified instruction to function f.
|
||||
func (f *Function) emit(instr Instruction) Value {
|
||||
return f.currentBlock.emit(instr)
|
||||
}
|
||||
|
||||
// RelString returns the full name of this function, qualified by
|
||||
// package name, receiver type, etc.
|
||||
//
|
||||
// The specific formatting rules are not guaranteed and may change.
|
||||
//
|
||||
// Examples:
|
||||
// "math.IsNaN" // a package-level function
|
||||
// "(*bytes.Buffer).Bytes" // a declared method or a wrapper
|
||||
// "(*bytes.Buffer).Bytes$thunk" // thunk (func wrapping method; receiver is param 0)
|
||||
// "(*bytes.Buffer).Bytes$bound" // bound (func wrapping method; receiver supplied by closure)
|
||||
// "main.main$1" // an anonymous function in main
|
||||
// "main.init#1" // a declared init function
|
||||
// "main.init" // the synthesized package initializer
|
||||
//
|
||||
// When these functions are referred to from within the same package
|
||||
// (i.e. from == f.Pkg.Object), they are rendered without the package path.
|
||||
// For example: "IsNaN", "(*Buffer).Bytes", etc.
|
||||
//
|
||||
// All non-synthetic functions have distinct package-qualified names.
|
||||
// (But two methods may have the same name "(T).f" if one is a synthetic
|
||||
// wrapper promoting a non-exported method "f" from another package; in
|
||||
// that case, the strings are equal but the identifiers "f" are distinct.)
|
||||
//
|
||||
func (f *Function) RelString(from *types.Package) string {
|
||||
// Anonymous?
|
||||
if f.parent != nil {
|
||||
// An anonymous function's Name() looks like "parentName$1",
|
||||
// but its String() should include the type/package/etc.
|
||||
parent := f.parent.RelString(from)
|
||||
for i, anon := range f.parent.AnonFuncs {
|
||||
if anon == f {
|
||||
return fmt.Sprintf("%s$%d", parent, 1+i)
|
||||
}
|
||||
}
|
||||
|
||||
return f.name // should never happen
|
||||
}
|
||||
|
||||
// Method (declared or wrapper)?
|
||||
if recv := f.Signature.Recv(); recv != nil {
|
||||
return f.relMethod(from, recv.Type())
|
||||
}
|
||||
|
||||
// Thunk?
|
||||
if f.method != nil {
|
||||
return f.relMethod(from, f.method.Recv())
|
||||
}
|
||||
|
||||
// Bound?
|
||||
if len(f.FreeVars) == 1 && strings.HasSuffix(f.name, "$bound") {
|
||||
return f.relMethod(from, f.FreeVars[0].Type())
|
||||
}
|
||||
|
||||
// Package-level function?
|
||||
// Prefix with package name for cross-package references only.
|
||||
if p := f.pkg(); p != nil && p != from {
|
||||
return fmt.Sprintf("%s.%s", p.Path(), f.name)
|
||||
}
|
||||
|
||||
// Unknown.
|
||||
return f.name
|
||||
}
|
||||
|
||||
func (f *Function) relMethod(from *types.Package, recv types.Type) string {
|
||||
return fmt.Sprintf("(%s).%s", relType(recv, from), f.name)
|
||||
}
|
||||
|
||||
// writeSignature writes to buf the signature sig in declaration syntax.
|
||||
func writeSignature(buf *bytes.Buffer, from *types.Package, name string, sig *types.Signature, params []*Parameter) {
|
||||
buf.WriteString("func ")
|
||||
if recv := sig.Recv(); recv != nil {
|
||||
buf.WriteString("(")
|
||||
if n := params[0].Name(); n != "" {
|
||||
buf.WriteString(n)
|
||||
buf.WriteString(" ")
|
||||
}
|
||||
types.WriteType(buf, params[0].Type(), types.RelativeTo(from))
|
||||
buf.WriteString(") ")
|
||||
}
|
||||
buf.WriteString(name)
|
||||
types.WriteSignature(buf, sig, types.RelativeTo(from))
|
||||
}
|
||||
|
||||
func (f *Function) pkg() *types.Package {
|
||||
if f.Pkg != nil {
|
||||
return f.Pkg.Pkg
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ io.WriterTo = (*Function)(nil) // *Function implements io.Writer
|
||||
|
||||
func (f *Function) WriteTo(w io.Writer) (int64, error) {
|
||||
var buf bytes.Buffer
|
||||
WriteFunction(&buf, f)
|
||||
n, err := w.Write(buf.Bytes())
|
||||
return int64(n), err
|
||||
}
|
||||
|
||||
// WriteFunction writes to buf a human-readable "disassembly" of f.
|
||||
func WriteFunction(buf *bytes.Buffer, f *Function) {
|
||||
fmt.Fprintf(buf, "# Name: %s\n", f.String())
|
||||
if f.Pkg != nil {
|
||||
fmt.Fprintf(buf, "# Package: %s\n", f.Pkg.Pkg.Path())
|
||||
}
|
||||
if syn := f.Synthetic; syn != "" {
|
||||
fmt.Fprintln(buf, "# Synthetic:", syn)
|
||||
}
|
||||
if pos := f.Pos(); pos.IsValid() {
|
||||
fmt.Fprintf(buf, "# Location: %s\n", f.Prog.Fset.Position(pos))
|
||||
}
|
||||
|
||||
if f.parent != nil {
|
||||
fmt.Fprintf(buf, "# Parent: %s\n", f.parent.Name())
|
||||
}
|
||||
|
||||
if f.Recover != nil {
|
||||
fmt.Fprintf(buf, "# Recover: %s\n", f.Recover)
|
||||
}
|
||||
|
||||
from := f.pkg()
|
||||
|
||||
if f.FreeVars != nil {
|
||||
buf.WriteString("# Free variables:\n")
|
||||
for i, fv := range f.FreeVars {
|
||||
fmt.Fprintf(buf, "# % 3d:\t%s %s\n", i, fv.Name(), relType(fv.Type(), from))
|
||||
}
|
||||
}
|
||||
|
||||
if len(f.Locals) > 0 {
|
||||
buf.WriteString("# Locals:\n")
|
||||
for i, l := range f.Locals {
|
||||
fmt.Fprintf(buf, "# % 3d:\t%s %s\n", i, l.Name(), relType(deref(l.Type()), from))
|
||||
}
|
||||
}
|
||||
writeSignature(buf, from, f.Name(), f.Signature, f.Params)
|
||||
buf.WriteString(":\n")
|
||||
|
||||
if f.Blocks == nil {
|
||||
buf.WriteString("\t(external)\n")
|
||||
}
|
||||
|
||||
// NB. column calculations are confused by non-ASCII
|
||||
// characters and assume 8-space tabs.
|
||||
const punchcard = 80 // for old time's sake.
|
||||
const tabwidth = 8
|
||||
for _, b := range f.Blocks {
|
||||
if b == nil {
|
||||
// Corrupt CFG.
|
||||
fmt.Fprintf(buf, ".nil:\n")
|
||||
continue
|
||||
}
|
||||
n, _ := fmt.Fprintf(buf, "%d:", b.Index)
|
||||
bmsg := fmt.Sprintf("%s P:%d S:%d", b.Comment, len(b.Preds), len(b.Succs))
|
||||
fmt.Fprintf(buf, "%*s%s\n", punchcard-1-n-len(bmsg), "", bmsg)
|
||||
|
||||
if false { // CFG debugging
|
||||
fmt.Fprintf(buf, "\t# CFG: %s --> %s --> %s\n", b.Preds, b, b.Succs)
|
||||
}
|
||||
for _, instr := range b.Instrs {
|
||||
buf.WriteString("\t")
|
||||
switch v := instr.(type) {
|
||||
case Value:
|
||||
l := punchcard - tabwidth
|
||||
// Left-align the instruction.
|
||||
if name := v.Name(); name != "" {
|
||||
n, _ := fmt.Fprintf(buf, "%s = ", name)
|
||||
l -= n
|
||||
}
|
||||
n, _ := buf.WriteString(instr.String())
|
||||
l -= n
|
||||
// Right-align the type if there's space.
|
||||
if t := v.Type(); t != nil {
|
||||
buf.WriteByte(' ')
|
||||
ts := relType(t, from)
|
||||
l -= len(ts) + len(" ") // (spaces before and after type)
|
||||
if l > 0 {
|
||||
fmt.Fprintf(buf, "%*s", l, "")
|
||||
}
|
||||
buf.WriteString(ts)
|
||||
}
|
||||
case nil:
|
||||
// Be robust against bad transforms.
|
||||
buf.WriteString("<deleted>")
|
||||
default:
|
||||
buf.WriteString(instr.String())
|
||||
}
|
||||
buf.WriteString("\n")
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(buf, "\n")
|
||||
}
|
||||
|
||||
// newBasicBlock adds to f a new basic block and returns it. It does
|
||||
// not automatically become the current block for subsequent calls to emit.
|
||||
// comment is an optional string for more readable debugging output.
|
||||
//
|
||||
func (f *Function) newBasicBlock(comment string) *BasicBlock {
|
||||
b := &BasicBlock{
|
||||
Index: len(f.Blocks),
|
||||
Comment: comment,
|
||||
parent: f,
|
||||
}
|
||||
b.Succs = b.succs2[:0]
|
||||
f.Blocks = append(f.Blocks, b)
|
||||
return b
|
||||
}
|
||||
|
||||
// NewFunction returns a new synthetic Function instance belonging to
|
||||
// prog, with its name and signature fields set as specified.
|
||||
//
|
||||
// The caller is responsible for initializing the remaining fields of
|
||||
// the function object, e.g. Pkg, Params, Blocks.
|
||||
//
|
||||
// It is practically impossible for clients to construct well-formed
|
||||
// SSA functions/packages/programs directly, so we assume this is the
|
||||
// job of the Builder alone. NewFunction exists to provide clients a
|
||||
// little flexibility. For example, analysis tools may wish to
|
||||
// construct fake Functions for the root of the callgraph, a fake
|
||||
// "reflect" package, etc.
|
||||
//
|
||||
// TODO(adonovan): think harder about the API here.
|
||||
//
|
||||
func (prog *Program) NewFunction(name string, sig *types.Signature, provenance string) *Function {
|
||||
return &Function{Prog: prog, name: name, Signature: sig, Synthetic: provenance}
|
||||
}
|
||||
|
||||
type extentNode [2]token.Pos
|
||||
|
||||
func (n extentNode) Pos() token.Pos { return n[0] }
|
||||
func (n extentNode) End() token.Pos { return n[1] }
|
||||
|
||||
// Syntax returns an ast.Node whose Pos/End methods provide the
|
||||
// lexical extent of the function if it was defined by Go source code
|
||||
// (f.Synthetic==""), or nil otherwise.
|
||||
//
|
||||
// If f was built with debug information (see Package.SetDebugRef),
|
||||
// the result is the *ast.FuncDecl or *ast.FuncLit that declared the
|
||||
// function. Otherwise, it is an opaque Node providing only position
|
||||
// information; this avoids pinning the AST in memory.
|
||||
//
|
||||
func (f *Function) Syntax() ast.Node { return f.syntax }
|
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
package interp
|
||||
|
||||
// Emulated functions that we cannot interpret because they are
|
||||
|
526
go/ssa/interp/external14.go
Normal file
526
go/ssa/interp/external14.go
Normal file
@ -0,0 +1,526 @@
|
||||
// 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.
|
||||
|
||||
// +build !go1.5
|
||||
|
||||
package interp
|
||||
|
||||
// Emulated functions that we cannot interpret because they are
|
||||
// external or because they use "unsafe" or "reflect" operations.
|
||||
|
||||
import (
|
||||
"math"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/tools/go/types"
|
||||
)
|
||||
|
||||
type externalFn func(fr *frame, args []value) value
|
||||
|
||||
// TODO(adonovan): fix: reflect.Value abstracts an lvalue or an
|
||||
// rvalue; Set() causes mutations that can be observed via aliases.
|
||||
// We have not captured that correctly here.
|
||||
|
||||
// Key strings are from Function.String().
|
||||
var externals map[string]externalFn
|
||||
|
||||
func init() {
|
||||
// That little dot ۰ is an Arabic zero numeral (U+06F0), categories [Nd].
|
||||
externals = map[string]externalFn{
|
||||
"(*sync.Pool).Get": ext۰sync۰Pool۰Get,
|
||||
"(*sync.Pool).Put": ext۰sync۰Pool۰Put,
|
||||
"(reflect.Value).Bool": ext۰reflect۰Value۰Bool,
|
||||
"(reflect.Value).CanAddr": ext۰reflect۰Value۰CanAddr,
|
||||
"(reflect.Value).CanInterface": ext۰reflect۰Value۰CanInterface,
|
||||
"(reflect.Value).Elem": ext۰reflect۰Value۰Elem,
|
||||
"(reflect.Value).Field": ext۰reflect۰Value۰Field,
|
||||
"(reflect.Value).Float": ext۰reflect۰Value۰Float,
|
||||
"(reflect.Value).Index": ext۰reflect۰Value۰Index,
|
||||
"(reflect.Value).Int": ext۰reflect۰Value۰Int,
|
||||
"(reflect.Value).Interface": ext۰reflect۰Value۰Interface,
|
||||
"(reflect.Value).IsNil": ext۰reflect۰Value۰IsNil,
|
||||
"(reflect.Value).IsValid": ext۰reflect۰Value۰IsValid,
|
||||
"(reflect.Value).Kind": ext۰reflect۰Value۰Kind,
|
||||
"(reflect.Value).Len": ext۰reflect۰Value۰Len,
|
||||
"(reflect.Value).MapIndex": ext۰reflect۰Value۰MapIndex,
|
||||
"(reflect.Value).MapKeys": ext۰reflect۰Value۰MapKeys,
|
||||
"(reflect.Value).NumField": ext۰reflect۰Value۰NumField,
|
||||
"(reflect.Value).NumMethod": ext۰reflect۰Value۰NumMethod,
|
||||
"(reflect.Value).Pointer": ext۰reflect۰Value۰Pointer,
|
||||
"(reflect.Value).Set": ext۰reflect۰Value۰Set,
|
||||
"(reflect.Value).String": ext۰reflect۰Value۰String,
|
||||
"(reflect.Value).Type": ext۰reflect۰Value۰Type,
|
||||
"(reflect.Value).Uint": ext۰reflect۰Value۰Uint,
|
||||
"(reflect.error).Error": ext۰reflect۰error۰Error,
|
||||
"(reflect.rtype).Bits": ext۰reflect۰rtype۰Bits,
|
||||
"(reflect.rtype).Elem": ext۰reflect۰rtype۰Elem,
|
||||
"(reflect.rtype).Field": ext۰reflect۰rtype۰Field,
|
||||
"(reflect.rtype).In": ext۰reflect۰rtype۰In,
|
||||
"(reflect.rtype).Kind": ext۰reflect۰rtype۰Kind,
|
||||
"(reflect.rtype).NumField": ext۰reflect۰rtype۰NumField,
|
||||
"(reflect.rtype).NumIn": ext۰reflect۰rtype۰NumIn,
|
||||
"(reflect.rtype).NumMethod": ext۰reflect۰rtype۰NumMethod,
|
||||
"(reflect.rtype).NumOut": ext۰reflect۰rtype۰NumOut,
|
||||
"(reflect.rtype).Out": ext۰reflect۰rtype۰Out,
|
||||
"(reflect.rtype).Size": ext۰reflect۰rtype۰Size,
|
||||
"(reflect.rtype).String": ext۰reflect۰rtype۰String,
|
||||
"bytes.Equal": ext۰bytes۰Equal,
|
||||
"bytes.IndexByte": ext۰bytes۰IndexByte,
|
||||
"hash/crc32.haveSSE42": ext۰crc32۰haveSSE42,
|
||||
"math.Abs": ext۰math۰Abs,
|
||||
"math.Exp": ext۰math۰Exp,
|
||||
"math.Float32bits": ext۰math۰Float32bits,
|
||||
"math.Float32frombits": ext۰math۰Float32frombits,
|
||||
"math.Float64bits": ext۰math۰Float64bits,
|
||||
"math.Float64frombits": ext۰math۰Float64frombits,
|
||||
"math.Ldexp": ext۰math۰Ldexp,
|
||||
"math.Log": ext۰math۰Log,
|
||||
"math.Min": ext۰math۰Min,
|
||||
"math.hasSSE4": ext۰math۰hasSSE4,
|
||||
"os.Pipe": ext۰os۰Pipe,
|
||||
"os.runtime_args": ext۰os۰runtime_args,
|
||||
"os.runtime_beforeExit": ext۰os۰runtime_beforeExit,
|
||||
"reflect.New": ext۰reflect۰New,
|
||||
"reflect.SliceOf": ext۰reflect۰SliceOf,
|
||||
"reflect.TypeOf": ext۰reflect۰TypeOf,
|
||||
"reflect.ValueOf": ext۰reflect۰ValueOf,
|
||||
"reflect.Zero": ext۰reflect۰Zero,
|
||||
"reflect.init": ext۰reflect۰Init,
|
||||
"reflect.valueInterface": ext۰reflect۰valueInterface,
|
||||
"runtime.Breakpoint": ext۰runtime۰Breakpoint,
|
||||
"runtime.Caller": ext۰runtime۰Caller,
|
||||
"runtime.Callers": ext۰runtime۰Callers,
|
||||
"runtime.FuncForPC": ext۰runtime۰FuncForPC,
|
||||
"runtime.GC": ext۰runtime۰GC,
|
||||
"runtime.GOMAXPROCS": ext۰runtime۰GOMAXPROCS,
|
||||
"runtime.Goexit": ext۰runtime۰Goexit,
|
||||
"runtime.Gosched": ext۰runtime۰Gosched,
|
||||
"runtime.init": ext۰runtime۰init,
|
||||
"runtime.NumCPU": ext۰runtime۰NumCPU,
|
||||
"runtime.ReadMemStats": ext۰runtime۰ReadMemStats,
|
||||
"runtime.SetFinalizer": ext۰runtime۰SetFinalizer,
|
||||
"(*runtime.Func).Entry": ext۰runtime۰Func۰Entry,
|
||||
"(*runtime.Func).FileLine": ext۰runtime۰Func۰FileLine,
|
||||
"(*runtime.Func).Name": ext۰runtime۰Func۰Name,
|
||||
"runtime.environ": ext۰runtime۰environ,
|
||||
"runtime.getgoroot": ext۰runtime۰getgoroot,
|
||||
"strings.Index": ext۰strings۰Index,
|
||||
"strings.IndexByte": ext۰strings۰IndexByte,
|
||||
"sync.runtime_Semacquire": ext۰sync۰runtime_Semacquire,
|
||||
"sync.runtime_Semrelease": ext۰sync۰runtime_Semrelease,
|
||||
"sync.runtime_Syncsemcheck": ext۰sync۰runtime_Syncsemcheck,
|
||||
"sync.runtime_registerPoolCleanup": ext۰sync۰runtime_registerPoolCleanup,
|
||||
"sync/atomic.AddInt32": ext۰atomic۰AddInt32,
|
||||
"sync/atomic.AddUint32": ext۰atomic۰AddUint32,
|
||||
"sync/atomic.AddUint64": ext۰atomic۰AddUint64,
|
||||
"sync/atomic.CompareAndSwapInt32": ext۰atomic۰CompareAndSwapInt32,
|
||||
"sync/atomic.LoadInt32": ext۰atomic۰LoadInt32,
|
||||
"sync/atomic.LoadUint32": ext۰atomic۰LoadUint32,
|
||||
"sync/atomic.StoreInt32": ext۰atomic۰StoreInt32,
|
||||
"sync/atomic.StoreUint32": ext۰atomic۰StoreUint32,
|
||||
"syscall.Close": ext۰syscall۰Close,
|
||||
"syscall.Exit": ext۰syscall۰Exit,
|
||||
"syscall.Fstat": ext۰syscall۰Fstat,
|
||||
"syscall.Getpid": ext۰syscall۰Getpid,
|
||||
"syscall.Getwd": ext۰syscall۰Getwd,
|
||||
"syscall.Kill": ext۰syscall۰Kill,
|
||||
"syscall.Lstat": ext۰syscall۰Lstat,
|
||||
"syscall.Open": ext۰syscall۰Open,
|
||||
"syscall.ParseDirent": ext۰syscall۰ParseDirent,
|
||||
"syscall.RawSyscall": ext۰syscall۰RawSyscall,
|
||||
"syscall.Read": ext۰syscall۰Read,
|
||||
"syscall.ReadDirent": ext۰syscall۰ReadDirent,
|
||||
"syscall.Stat": ext۰syscall۰Stat,
|
||||
"syscall.Write": ext۰syscall۰Write,
|
||||
"syscall.runtime_envs": ext۰runtime۰environ,
|
||||
"testing.runExample": ext۰testing۰runExample,
|
||||
"time.Sleep": ext۰time۰Sleep,
|
||||
"time.now": ext۰time۰now,
|
||||
}
|
||||
}
|
||||
|
||||
// wrapError returns an interpreted 'error' interface value for err.
|
||||
func wrapError(err error) value {
|
||||
if err == nil {
|
||||
return iface{}
|
||||
}
|
||||
return iface{t: errorType, v: err.Error()}
|
||||
}
|
||||
|
||||
func ext۰sync۰Pool۰Get(fr *frame, args []value) value {
|
||||
Pool := fr.i.prog.ImportedPackage("sync").Type("Pool").Object()
|
||||
_, newIndex, _ := types.LookupFieldOrMethod(Pool.Type(), false, Pool.Pkg(), "New")
|
||||
|
||||
if New := (*args[0].(*value)).(structure)[newIndex[0]]; New != nil {
|
||||
return call(fr.i, fr, 0, New, nil)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ext۰sync۰Pool۰Put(fr *frame, args []value) value {
|
||||
return nil
|
||||
}
|
||||
|
||||
func ext۰bytes۰Equal(fr *frame, args []value) value {
|
||||
// func Equal(a, b []byte) bool
|
||||
a := args[0].([]value)
|
||||
b := args[1].([]value)
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i := range a {
|
||||
if a[i] != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func ext۰bytes۰IndexByte(fr *frame, args []value) value {
|
||||
// func IndexByte(s []byte, c byte) int
|
||||
s := args[0].([]value)
|
||||
c := args[1].(byte)
|
||||
for i, b := range s {
|
||||
if b.(byte) == c {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func ext۰crc32۰haveSSE42(fr *frame, args []value) value {
|
||||
return false
|
||||
}
|
||||
|
||||
func ext۰math۰Float64frombits(fr *frame, args []value) value {
|
||||
return math.Float64frombits(args[0].(uint64))
|
||||
}
|
||||
|
||||
func ext۰math۰Float64bits(fr *frame, args []value) value {
|
||||
return math.Float64bits(args[0].(float64))
|
||||
}
|
||||
|
||||
func ext۰math۰Float32frombits(fr *frame, args []value) value {
|
||||
return math.Float32frombits(args[0].(uint32))
|
||||
}
|
||||
|
||||
func ext۰math۰Abs(fr *frame, args []value) value {
|
||||
return math.Abs(args[0].(float64))
|
||||
}
|
||||
|
||||
func ext۰math۰Exp(fr *frame, args []value) value {
|
||||
return math.Exp(args[0].(float64))
|
||||
}
|
||||
|
||||
func ext۰math۰Float32bits(fr *frame, args []value) value {
|
||||
return math.Float32bits(args[0].(float32))
|
||||
}
|
||||
|
||||
func ext۰math۰Min(fr *frame, args []value) value {
|
||||
return math.Min(args[0].(float64), args[1].(float64))
|
||||
}
|
||||
|
||||
func ext۰math۰hasSSE4(fr *frame, args []value) value {
|
||||
return false
|
||||
}
|
||||
|
||||
func ext۰math۰Ldexp(fr *frame, args []value) value {
|
||||
return math.Ldexp(args[0].(float64), args[1].(int))
|
||||
}
|
||||
|
||||
func ext۰math۰Log(fr *frame, args []value) value {
|
||||
return math.Log(args[0].(float64))
|
||||
}
|
||||
|
||||
func ext۰os۰runtime_args(fr *frame, args []value) value {
|
||||
return fr.i.osArgs
|
||||
}
|
||||
|
||||
func ext۰os۰runtime_beforeExit(fr *frame, args []value) value {
|
||||
return nil
|
||||
}
|
||||
|
||||
func ext۰runtime۰Breakpoint(fr *frame, args []value) value {
|
||||
runtime.Breakpoint()
|
||||
return nil
|
||||
}
|
||||
|
||||
func ext۰runtime۰Caller(fr *frame, args []value) value {
|
||||
// func Caller(skip int) (pc uintptr, file string, line int, ok bool)
|
||||
skip := 1 + args[0].(int)
|
||||
for i := 0; i < skip; i++ {
|
||||
if fr != nil {
|
||||
fr = fr.caller
|
||||
}
|
||||
}
|
||||
var pc uintptr
|
||||
var file string
|
||||
var line int
|
||||
var ok bool
|
||||
if fr != nil {
|
||||
fn := fr.fn
|
||||
// TODO(adonovan): use pc/posn of current instruction, not start of fn.
|
||||
// (Required to interpret the log package's tests.)
|
||||
pc = uintptr(unsafe.Pointer(fn))
|
||||
posn := fn.Prog.Fset.Position(fn.Pos())
|
||||
file = posn.Filename
|
||||
line = posn.Line
|
||||
ok = true
|
||||
}
|
||||
return tuple{pc, file, line, ok}
|
||||
}
|
||||
|
||||
func ext۰runtime۰Callers(fr *frame, args []value) value {
|
||||
// Callers(skip int, pc []uintptr) int
|
||||
skip := args[0].(int)
|
||||
pc := args[1].([]value)
|
||||
for i := 0; i < skip; i++ {
|
||||
if fr != nil {
|
||||
fr = fr.caller
|
||||
}
|
||||
}
|
||||
i := 0
|
||||
for fr != nil {
|
||||
pc[i] = uintptr(unsafe.Pointer(fr.fn))
|
||||
i++
|
||||
fr = fr.caller
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func ext۰runtime۰FuncForPC(fr *frame, args []value) value {
|
||||
// FuncForPC(pc uintptr) *Func
|
||||
pc := args[0].(uintptr)
|
||||
var fn *ssa.Function
|
||||
if pc != 0 {
|
||||
fn = (*ssa.Function)(unsafe.Pointer(pc)) // indeed unsafe!
|
||||
}
|
||||
var Func value
|
||||
Func = structure{fn} // a runtime.Func
|
||||
return &Func
|
||||
}
|
||||
|
||||
func ext۰runtime۰environ(fr *frame, args []value) value {
|
||||
// This function also implements syscall.runtime_envs.
|
||||
return environ
|
||||
}
|
||||
|
||||
func ext۰runtime۰getgoroot(fr *frame, args []value) value {
|
||||
return os.Getenv("GOROOT")
|
||||
}
|
||||
|
||||
func ext۰strings۰IndexByte(fr *frame, args []value) value {
|
||||
// func IndexByte(s string, c byte) int
|
||||
s := args[0].(string)
|
||||
c := args[1].(byte)
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] == c {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func ext۰strings۰Index(fr *frame, args []value) value {
|
||||
// Call compiled version to avoid tricky asm dependency.
|
||||
return strings.Index(args[0].(string), args[1].(string))
|
||||
}
|
||||
|
||||
func ext۰sync۰runtime_Syncsemcheck(fr *frame, args []value) value {
|
||||
// TODO(adonovan): fix: implement.
|
||||
return nil
|
||||
}
|
||||
|
||||
func ext۰sync۰runtime_registerPoolCleanup(fr *frame, args []value) value {
|
||||
return nil
|
||||
}
|
||||
|
||||
func ext۰sync۰runtime_Semacquire(fr *frame, args []value) value {
|
||||
// TODO(adonovan): fix: implement.
|
||||
return nil
|
||||
}
|
||||
|
||||
func ext۰sync۰runtime_Semrelease(fr *frame, args []value) value {
|
||||
// TODO(adonovan): fix: implement.
|
||||
return nil
|
||||
}
|
||||
|
||||
func ext۰runtime۰GOMAXPROCS(fr *frame, args []value) value {
|
||||
// Ignore args[0]; don't let the interpreted program
|
||||
// set the interpreter's GOMAXPROCS!
|
||||
return runtime.GOMAXPROCS(0)
|
||||
}
|
||||
|
||||
func ext۰runtime۰Goexit(fr *frame, args []value) value {
|
||||
// TODO(adonovan): don't kill the interpreter's main goroutine.
|
||||
runtime.Goexit()
|
||||
return nil
|
||||
}
|
||||
|
||||
func ext۰runtime۰GC(fr *frame, args []value) value {
|
||||
runtime.GC()
|
||||
return nil
|
||||
}
|
||||
|
||||
func ext۰runtime۰Gosched(fr *frame, args []value) value {
|
||||
runtime.Gosched()
|
||||
return nil
|
||||
}
|
||||
|
||||
func ext۰runtime۰init(fr *frame, args []value) value {
|
||||
return nil
|
||||
}
|
||||
|
||||
func ext۰runtime۰NumCPU(fr *frame, args []value) value {
|
||||
return runtime.NumCPU()
|
||||
}
|
||||
|
||||
func ext۰runtime۰ReadMemStats(fr *frame, args []value) value {
|
||||
// TODO(adonovan): populate args[0].(Struct)
|
||||
return nil
|
||||
}
|
||||
|
||||
func ext۰atomic۰LoadUint32(fr *frame, args []value) value {
|
||||
// TODO(adonovan): fix: not atomic!
|
||||
return (*args[0].(*value)).(uint32)
|
||||
}
|
||||
|
||||
func ext۰atomic۰StoreUint32(fr *frame, args []value) value {
|
||||
// TODO(adonovan): fix: not atomic!
|
||||
*args[0].(*value) = args[1].(uint32)
|
||||
return nil
|
||||
}
|
||||
|
||||
func ext۰atomic۰LoadInt32(fr *frame, args []value) value {
|
||||
// TODO(adonovan): fix: not atomic!
|
||||
return (*args[0].(*value)).(int32)
|
||||
}
|
||||
|
||||
func ext۰atomic۰StoreInt32(fr *frame, args []value) value {
|
||||
// TODO(adonovan): fix: not atomic!
|
||||
*args[0].(*value) = args[1].(int32)
|
||||
return nil
|
||||
}
|
||||
|
||||
func ext۰atomic۰CompareAndSwapInt32(fr *frame, args []value) value {
|
||||
// TODO(adonovan): fix: not atomic!
|
||||
p := args[0].(*value)
|
||||
if (*p).(int32) == args[1].(int32) {
|
||||
*p = args[2].(int32)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func ext۰atomic۰AddInt32(fr *frame, args []value) value {
|
||||
// TODO(adonovan): fix: not atomic!
|
||||
p := args[0].(*value)
|
||||
newv := (*p).(int32) + args[1].(int32)
|
||||
*p = newv
|
||||
return newv
|
||||
}
|
||||
|
||||
func ext۰atomic۰AddUint32(fr *frame, args []value) value {
|
||||
// TODO(adonovan): fix: not atomic!
|
||||
p := args[0].(*value)
|
||||
newv := (*p).(uint32) + args[1].(uint32)
|
||||
*p = newv
|
||||
return newv
|
||||
}
|
||||
|
||||
func ext۰atomic۰AddUint64(fr *frame, args []value) value {
|
||||
// TODO(adonovan): fix: not atomic!
|
||||
p := args[0].(*value)
|
||||
newv := (*p).(uint64) + args[1].(uint64)
|
||||
*p = newv
|
||||
return newv
|
||||
}
|
||||
|
||||
func ext۰runtime۰SetFinalizer(fr *frame, args []value) value {
|
||||
return nil // ignore
|
||||
}
|
||||
|
||||
// Pretend: type runtime.Func struct { entry *ssa.Function }
|
||||
|
||||
func ext۰runtime۰Func۰FileLine(fr *frame, args []value) value {
|
||||
// func (*runtime.Func) FileLine(uintptr) (string, int)
|
||||
f, _ := (*args[0].(*value)).(structure)[0].(*ssa.Function)
|
||||
pc := args[1].(uintptr)
|
||||
_ = pc
|
||||
if f != nil {
|
||||
// TODO(adonovan): use position of current instruction, not fn.
|
||||
posn := f.Prog.Fset.Position(f.Pos())
|
||||
return tuple{posn.Filename, posn.Line}
|
||||
}
|
||||
return tuple{"", 0}
|
||||
}
|
||||
|
||||
func ext۰runtime۰Func۰Name(fr *frame, args []value) value {
|
||||
// func (*runtime.Func) Name() string
|
||||
f, _ := (*args[0].(*value)).(structure)[0].(*ssa.Function)
|
||||
if f != nil {
|
||||
return f.String()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func ext۰runtime۰Func۰Entry(fr *frame, args []value) value {
|
||||
// func (*runtime.Func) Entry() uintptr
|
||||
f, _ := (*args[0].(*value)).(structure)[0].(*ssa.Function)
|
||||
return uintptr(unsafe.Pointer(f))
|
||||
}
|
||||
|
||||
// This is a workaround for a bug in go/ssa/testmain.go: it creates
|
||||
// InternalExamples even for Example functions with no Output comment.
|
||||
// TODO(adonovan): fix (and redesign) testmain.go after Go 1.6.
|
||||
func ext۰testing۰runExample(fr *frame, args []value) value {
|
||||
// This is a stripped down runExample that simply calls the function.
|
||||
// It does not capture and compare output nor recover from panic.
|
||||
//
|
||||
// func runExample(eg testing.InternalExample) bool {
|
||||
// eg.F()
|
||||
// return true
|
||||
// }
|
||||
F := args[0].(structure)[1]
|
||||
call(fr.i, fr, 0, F, nil)
|
||||
return true
|
||||
}
|
||||
|
||||
func ext۰time۰now(fr *frame, args []value) value {
|
||||
nano := time.Now().UnixNano()
|
||||
return tuple{int64(nano / 1e9), int32(nano % 1e9)}
|
||||
}
|
||||
|
||||
func ext۰time۰Sleep(fr *frame, args []value) value {
|
||||
time.Sleep(time.Duration(args[0].(int64)))
|
||||
return nil
|
||||
}
|
||||
|
||||
func ext۰syscall۰Exit(fr *frame, args []value) value {
|
||||
panic(exitPanic(args[0].(int)))
|
||||
}
|
||||
|
||||
func ext۰syscall۰Getwd(fr *frame, args []value) value {
|
||||
s, err := syscall.Getwd()
|
||||
return tuple{s, wrapError(err)}
|
||||
}
|
||||
|
||||
func ext۰syscall۰Getpid(fr *frame, args []value) value {
|
||||
return syscall.Getpid()
|
||||
}
|
||||
|
||||
func valueToBytes(v value) []byte {
|
||||
in := v.([]value)
|
||||
b := make([]byte, len(in))
|
||||
for i := range in {
|
||||
b[i] = in[i].(byte)
|
||||
}
|
||||
return b
|
||||
}
|
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
// Package ssa/interp defines an interpreter for the SSA
|
||||
// representation of Go programs.
|
||||
//
|
||||
|
752
go/ssa/interp/interp14.go
Normal file
752
go/ssa/interp/interp14.go
Normal file
@ -0,0 +1,752 @@
|
||||
// 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.
|
||||
|
||||
// +build !go1.5
|
||||
|
||||
// Package ssa/interp defines an interpreter for the SSA
|
||||
// representation of Go programs.
|
||||
//
|
||||
// This interpreter is provided as an adjunct for testing the SSA
|
||||
// construction algorithm. Its purpose is to provide a minimal
|
||||
// metacircular implementation of the dynamic semantics of each SSA
|
||||
// instruction. It is not, and will never be, a production-quality Go
|
||||
// interpreter.
|
||||
//
|
||||
// The following is a partial list of Go features that are currently
|
||||
// unsupported or incomplete in the interpreter.
|
||||
//
|
||||
// * Unsafe operations, including all uses of unsafe.Pointer, are
|
||||
// impossible to support given the "boxed" value representation we
|
||||
// have chosen.
|
||||
//
|
||||
// * The reflect package is only partially implemented.
|
||||
//
|
||||
// * "sync/atomic" operations are not currently atomic due to the
|
||||
// "boxed" value representation: it is not possible to read, modify
|
||||
// and write an interface value atomically. As a consequence, Mutexes
|
||||
// are currently broken. TODO(adonovan): provide a metacircular
|
||||
// implementation of Mutex avoiding the broken atomic primitives.
|
||||
//
|
||||
// * recover is only partially implemented. Also, the interpreter
|
||||
// makes no attempt to distinguish target panics from interpreter
|
||||
// crashes.
|
||||
//
|
||||
// * map iteration is asymptotically inefficient.
|
||||
//
|
||||
// * the sizes of the int, uint and uintptr types in the target
|
||||
// program are assumed to be the same as those of the interpreter
|
||||
// itself.
|
||||
//
|
||||
// * all values occupy space, even those of types defined by the spec
|
||||
// to have zero size, e.g. struct{}. This can cause asymptotic
|
||||
// performance degradation.
|
||||
//
|
||||
// * os.Exit is implemented using panic, causing deferred functions to
|
||||
// run.
|
||||
package interp // import "golang.org/x/tools/go/ssa/interp"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/token"
|
||||
"os"
|
||||
"reflect"
|
||||
"runtime"
|
||||
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/tools/go/types"
|
||||
)
|
||||
|
||||
type continuation int
|
||||
|
||||
const (
|
||||
kNext continuation = iota
|
||||
kReturn
|
||||
kJump
|
||||
)
|
||||
|
||||
// Mode is a bitmask of options affecting the interpreter.
|
||||
type Mode uint
|
||||
|
||||
const (
|
||||
DisableRecover Mode = 1 << iota // Disable recover() in target programs; show interpreter crash instead.
|
||||
EnableTracing // Print a trace of all instructions as they are interpreted.
|
||||
)
|
||||
|
||||
type methodSet map[string]*ssa.Function
|
||||
|
||||
// State shared between all interpreted goroutines.
|
||||
type interpreter struct {
|
||||
osArgs []value // the value of os.Args
|
||||
prog *ssa.Program // the SSA program
|
||||
globals map[ssa.Value]*value // addresses of global variables (immutable)
|
||||
mode Mode // interpreter options
|
||||
reflectPackage *ssa.Package // the fake reflect package
|
||||
errorMethods methodSet // the method set of reflect.error, which implements the error interface.
|
||||
rtypeMethods methodSet // the method set of rtype, which implements the reflect.Type interface.
|
||||
runtimeErrorString types.Type // the runtime.errorString type
|
||||
sizes types.Sizes // the effective type-sizing function
|
||||
}
|
||||
|
||||
type deferred struct {
|
||||
fn value
|
||||
args []value
|
||||
instr *ssa.Defer
|
||||
tail *deferred
|
||||
}
|
||||
|
||||
type frame struct {
|
||||
i *interpreter
|
||||
caller *frame
|
||||
fn *ssa.Function
|
||||
block, prevBlock *ssa.BasicBlock
|
||||
env map[ssa.Value]value // dynamic values of SSA variables
|
||||
locals []value
|
||||
defers *deferred
|
||||
result value
|
||||
panicking bool
|
||||
panic interface{}
|
||||
}
|
||||
|
||||
func (fr *frame) get(key ssa.Value) value {
|
||||
switch key := key.(type) {
|
||||
case nil:
|
||||
// Hack; simplifies handling of optional attributes
|
||||
// such as ssa.Slice.{Low,High}.
|
||||
return nil
|
||||
case *ssa.Function, *ssa.Builtin:
|
||||
return key
|
||||
case *ssa.Const:
|
||||
return constValue(key)
|
||||
case *ssa.Global:
|
||||
if r, ok := fr.i.globals[key]; ok {
|
||||
return r
|
||||
}
|
||||
}
|
||||
if r, ok := fr.env[key]; ok {
|
||||
return r
|
||||
}
|
||||
panic(fmt.Sprintf("get: no value for %T: %v", key, key.Name()))
|
||||
}
|
||||
|
||||
// runDefer runs a deferred call d.
|
||||
// It always returns normally, but may set or clear fr.panic.
|
||||
//
|
||||
func (fr *frame) runDefer(d *deferred) {
|
||||
if fr.i.mode&EnableTracing != 0 {
|
||||
fmt.Fprintf(os.Stderr, "%s: invoking deferred function call\n",
|
||||
fr.i.prog.Fset.Position(d.instr.Pos()))
|
||||
}
|
||||
var ok bool
|
||||
defer func() {
|
||||
if !ok {
|
||||
// Deferred call created a new state of panic.
|
||||
fr.panicking = true
|
||||
fr.panic = recover()
|
||||
}
|
||||
}()
|
||||
call(fr.i, fr, d.instr.Pos(), d.fn, d.args)
|
||||
ok = true
|
||||
}
|
||||
|
||||
// runDefers executes fr's deferred function calls in LIFO order.
|
||||
//
|
||||
// On entry, fr.panicking indicates a state of panic; if
|
||||
// true, fr.panic contains the panic value.
|
||||
//
|
||||
// On completion, if a deferred call started a panic, or if no
|
||||
// deferred call recovered from a previous state of panic, then
|
||||
// runDefers itself panics after the last deferred call has run.
|
||||
//
|
||||
// If there was no initial state of panic, or it was recovered from,
|
||||
// runDefers returns normally.
|
||||
//
|
||||
func (fr *frame) runDefers() {
|
||||
for d := fr.defers; d != nil; d = d.tail {
|
||||
fr.runDefer(d)
|
||||
}
|
||||
fr.defers = nil
|
||||
if fr.panicking {
|
||||
panic(fr.panic) // new panic, or still panicking
|
||||
}
|
||||
}
|
||||
|
||||
// lookupMethod returns the method set for type typ, which may be one
|
||||
// of the interpreter's fake types.
|
||||
func lookupMethod(i *interpreter, typ types.Type, meth *types.Func) *ssa.Function {
|
||||
switch typ {
|
||||
case rtypeType:
|
||||
return i.rtypeMethods[meth.Id()]
|
||||
case errorType:
|
||||
return i.errorMethods[meth.Id()]
|
||||
}
|
||||
return i.prog.LookupMethod(typ, meth.Pkg(), meth.Name())
|
||||
}
|
||||
|
||||
// visitInstr interprets a single ssa.Instruction within the activation
|
||||
// record frame. It returns a continuation value indicating where to
|
||||
// read the next instruction from.
|
||||
func visitInstr(fr *frame, instr ssa.Instruction) continuation {
|
||||
switch instr := instr.(type) {
|
||||
case *ssa.DebugRef:
|
||||
// no-op
|
||||
|
||||
case *ssa.UnOp:
|
||||
fr.env[instr] = unop(instr, fr.get(instr.X))
|
||||
|
||||
case *ssa.BinOp:
|
||||
fr.env[instr] = binop(instr.Op, instr.X.Type(), fr.get(instr.X), fr.get(instr.Y))
|
||||
|
||||
case *ssa.Call:
|
||||
fn, args := prepareCall(fr, &instr.Call)
|
||||
fr.env[instr] = call(fr.i, fr, instr.Pos(), fn, args)
|
||||
|
||||
case *ssa.ChangeInterface:
|
||||
fr.env[instr] = fr.get(instr.X)
|
||||
|
||||
case *ssa.ChangeType:
|
||||
fr.env[instr] = fr.get(instr.X) // (can't fail)
|
||||
|
||||
case *ssa.Convert:
|
||||
fr.env[instr] = conv(instr.Type(), instr.X.Type(), fr.get(instr.X))
|
||||
|
||||
case *ssa.MakeInterface:
|
||||
fr.env[instr] = iface{t: instr.X.Type(), v: fr.get(instr.X)}
|
||||
|
||||
case *ssa.Extract:
|
||||
fr.env[instr] = fr.get(instr.Tuple).(tuple)[instr.Index]
|
||||
|
||||
case *ssa.Slice:
|
||||
fr.env[instr] = slice(fr.get(instr.X), fr.get(instr.Low), fr.get(instr.High), fr.get(instr.Max))
|
||||
|
||||
case *ssa.Return:
|
||||
switch len(instr.Results) {
|
||||
case 0:
|
||||
case 1:
|
||||
fr.result = fr.get(instr.Results[0])
|
||||
default:
|
||||
var res []value
|
||||
for _, r := range instr.Results {
|
||||
res = append(res, fr.get(r))
|
||||
}
|
||||
fr.result = tuple(res)
|
||||
}
|
||||
fr.block = nil
|
||||
return kReturn
|
||||
|
||||
case *ssa.RunDefers:
|
||||
fr.runDefers()
|
||||
|
||||
case *ssa.Panic:
|
||||
panic(targetPanic{fr.get(instr.X)})
|
||||
|
||||
case *ssa.Send:
|
||||
fr.get(instr.Chan).(chan value) <- fr.get(instr.X)
|
||||
|
||||
case *ssa.Store:
|
||||
store(deref(instr.Addr.Type()), fr.get(instr.Addr).(*value), fr.get(instr.Val))
|
||||
|
||||
case *ssa.If:
|
||||
succ := 1
|
||||
if fr.get(instr.Cond).(bool) {
|
||||
succ = 0
|
||||
}
|
||||
fr.prevBlock, fr.block = fr.block, fr.block.Succs[succ]
|
||||
return kJump
|
||||
|
||||
case *ssa.Jump:
|
||||
fr.prevBlock, fr.block = fr.block, fr.block.Succs[0]
|
||||
return kJump
|
||||
|
||||
case *ssa.Defer:
|
||||
fn, args := prepareCall(fr, &instr.Call)
|
||||
fr.defers = &deferred{
|
||||
fn: fn,
|
||||
args: args,
|
||||
instr: instr,
|
||||
tail: fr.defers,
|
||||
}
|
||||
|
||||
case *ssa.Go:
|
||||
fn, args := prepareCall(fr, &instr.Call)
|
||||
go call(fr.i, nil, instr.Pos(), fn, args)
|
||||
|
||||
case *ssa.MakeChan:
|
||||
fr.env[instr] = make(chan value, asInt(fr.get(instr.Size)))
|
||||
|
||||
case *ssa.Alloc:
|
||||
var addr *value
|
||||
if instr.Heap {
|
||||
// new
|
||||
addr = new(value)
|
||||
fr.env[instr] = addr
|
||||
} else {
|
||||
// local
|
||||
addr = fr.env[instr].(*value)
|
||||
}
|
||||
*addr = zero(deref(instr.Type()))
|
||||
|
||||
case *ssa.MakeSlice:
|
||||
slice := make([]value, asInt(fr.get(instr.Cap)))
|
||||
tElt := instr.Type().Underlying().(*types.Slice).Elem()
|
||||
for i := range slice {
|
||||
slice[i] = zero(tElt)
|
||||
}
|
||||
fr.env[instr] = slice[:asInt(fr.get(instr.Len))]
|
||||
|
||||
case *ssa.MakeMap:
|
||||
reserve := 0
|
||||
if instr.Reserve != nil {
|
||||
reserve = asInt(fr.get(instr.Reserve))
|
||||
}
|
||||
fr.env[instr] = makeMap(instr.Type().Underlying().(*types.Map).Key(), reserve)
|
||||
|
||||
case *ssa.Range:
|
||||
fr.env[instr] = rangeIter(fr.get(instr.X), instr.X.Type())
|
||||
|
||||
case *ssa.Next:
|
||||
fr.env[instr] = fr.get(instr.Iter).(iter).next()
|
||||
|
||||
case *ssa.FieldAddr:
|
||||
x := fr.get(instr.X)
|
||||
// FIXME wrong! &global.f must not change if we do *global = zero!
|
||||
fr.env[instr] = &(*x.(*value)).(structure)[instr.Field]
|
||||
|
||||
case *ssa.Field:
|
||||
fr.env[instr] = fr.get(instr.X).(structure)[instr.Field]
|
||||
|
||||
case *ssa.IndexAddr:
|
||||
x := fr.get(instr.X)
|
||||
idx := fr.get(instr.Index)
|
||||
switch x := x.(type) {
|
||||
case []value:
|
||||
fr.env[instr] = &x[asInt(idx)]
|
||||
case *value: // *array
|
||||
fr.env[instr] = &(*x).(array)[asInt(idx)]
|
||||
default:
|
||||
panic(fmt.Sprintf("unexpected x type in IndexAddr: %T", x))
|
||||
}
|
||||
|
||||
case *ssa.Index:
|
||||
fr.env[instr] = fr.get(instr.X).(array)[asInt(fr.get(instr.Index))]
|
||||
|
||||
case *ssa.Lookup:
|
||||
fr.env[instr] = lookup(instr, fr.get(instr.X), fr.get(instr.Index))
|
||||
|
||||
case *ssa.MapUpdate:
|
||||
m := fr.get(instr.Map)
|
||||
key := fr.get(instr.Key)
|
||||
v := fr.get(instr.Value)
|
||||
switch m := m.(type) {
|
||||
case map[value]value:
|
||||
m[key] = v
|
||||
case *hashmap:
|
||||
m.insert(key.(hashable), v)
|
||||
default:
|
||||
panic(fmt.Sprintf("illegal map type: %T", m))
|
||||
}
|
||||
|
||||
case *ssa.TypeAssert:
|
||||
fr.env[instr] = typeAssert(fr.i, instr, fr.get(instr.X).(iface))
|
||||
|
||||
case *ssa.MakeClosure:
|
||||
var bindings []value
|
||||
for _, binding := range instr.Bindings {
|
||||
bindings = append(bindings, fr.get(binding))
|
||||
}
|
||||
fr.env[instr] = &closure{instr.Fn.(*ssa.Function), bindings}
|
||||
|
||||
case *ssa.Phi:
|
||||
for i, pred := range instr.Block().Preds {
|
||||
if fr.prevBlock == pred {
|
||||
fr.env[instr] = fr.get(instr.Edges[i])
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
case *ssa.Select:
|
||||
var cases []reflect.SelectCase
|
||||
if !instr.Blocking {
|
||||
cases = append(cases, reflect.SelectCase{
|
||||
Dir: reflect.SelectDefault,
|
||||
})
|
||||
}
|
||||
for _, state := range instr.States {
|
||||
var dir reflect.SelectDir
|
||||
if state.Dir == types.RecvOnly {
|
||||
dir = reflect.SelectRecv
|
||||
} else {
|
||||
dir = reflect.SelectSend
|
||||
}
|
||||
var send reflect.Value
|
||||
if state.Send != nil {
|
||||
send = reflect.ValueOf(fr.get(state.Send))
|
||||
}
|
||||
cases = append(cases, reflect.SelectCase{
|
||||
Dir: dir,
|
||||
Chan: reflect.ValueOf(fr.get(state.Chan)),
|
||||
Send: send,
|
||||
})
|
||||
}
|
||||
chosen, recv, recvOk := reflect.Select(cases)
|
||||
if !instr.Blocking {
|
||||
chosen-- // default case should have index -1.
|
||||
}
|
||||
r := tuple{chosen, recvOk}
|
||||
for i, st := range instr.States {
|
||||
if st.Dir == types.RecvOnly {
|
||||
var v value
|
||||
if i == chosen && recvOk {
|
||||
// No need to copy since send makes an unaliased copy.
|
||||
v = recv.Interface().(value)
|
||||
} else {
|
||||
v = zero(st.Chan.Type().Underlying().(*types.Chan).Elem())
|
||||
}
|
||||
r = append(r, v)
|
||||
}
|
||||
}
|
||||
fr.env[instr] = r
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("unexpected instruction: %T", instr))
|
||||
}
|
||||
|
||||
// if val, ok := instr.(ssa.Value); ok {
|
||||
// fmt.Println(toString(fr.env[val])) // debugging
|
||||
// }
|
||||
|
||||
return kNext
|
||||
}
|
||||
|
||||
// prepareCall determines the function value and argument values for a
|
||||
// function call in a Call, Go or Defer instruction, performing
|
||||
// interface method lookup if needed.
|
||||
//
|
||||
func prepareCall(fr *frame, call *ssa.CallCommon) (fn value, args []value) {
|
||||
v := fr.get(call.Value)
|
||||
if call.Method == nil {
|
||||
// Function call.
|
||||
fn = v
|
||||
} else {
|
||||
// Interface method invocation.
|
||||
recv := v.(iface)
|
||||
if recv.t == nil {
|
||||
panic("method invoked on nil interface")
|
||||
}
|
||||
if f := lookupMethod(fr.i, recv.t, call.Method); f == nil {
|
||||
// Unreachable in well-typed programs.
|
||||
panic(fmt.Sprintf("method set for dynamic type %v does not contain %s", recv.t, call.Method))
|
||||
} else {
|
||||
fn = f
|
||||
}
|
||||
args = append(args, recv.v)
|
||||
}
|
||||
for _, arg := range call.Args {
|
||||
args = append(args, fr.get(arg))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// call interprets a call to a function (function, builtin or closure)
|
||||
// fn with arguments args, returning its result.
|
||||
// callpos is the position of the callsite.
|
||||
//
|
||||
func call(i *interpreter, caller *frame, callpos token.Pos, fn value, args []value) value {
|
||||
switch fn := fn.(type) {
|
||||
case *ssa.Function:
|
||||
if fn == nil {
|
||||
panic("call of nil function") // nil of func type
|
||||
}
|
||||
return callSSA(i, caller, callpos, fn, args, nil)
|
||||
case *closure:
|
||||
return callSSA(i, caller, callpos, fn.Fn, args, fn.Env)
|
||||
case *ssa.Builtin:
|
||||
return callBuiltin(caller, callpos, fn, args)
|
||||
}
|
||||
panic(fmt.Sprintf("cannot call %T", fn))
|
||||
}
|
||||
|
||||
func loc(fset *token.FileSet, pos token.Pos) string {
|
||||
if pos == token.NoPos {
|
||||
return ""
|
||||
}
|
||||
return " at " + fset.Position(pos).String()
|
||||
}
|
||||
|
||||
// callSSA interprets a call to function fn with arguments args,
|
||||
// and lexical environment env, returning its result.
|
||||
// callpos is the position of the callsite.
|
||||
//
|
||||
func callSSA(i *interpreter, caller *frame, callpos token.Pos, fn *ssa.Function, args []value, env []value) value {
|
||||
if i.mode&EnableTracing != 0 {
|
||||
fset := fn.Prog.Fset
|
||||
// TODO(adonovan): fix: loc() lies for external functions.
|
||||
fmt.Fprintf(os.Stderr, "Entering %s%s.\n", fn, loc(fset, fn.Pos()))
|
||||
suffix := ""
|
||||
if caller != nil {
|
||||
suffix = ", resuming " + caller.fn.String() + loc(fset, callpos)
|
||||
}
|
||||
defer fmt.Fprintf(os.Stderr, "Leaving %s%s.\n", fn, suffix)
|
||||
}
|
||||
fr := &frame{
|
||||
i: i,
|
||||
caller: caller, // for panic/recover
|
||||
fn: fn,
|
||||
}
|
||||
if fn.Parent() == nil {
|
||||
name := fn.String()
|
||||
if ext := externals[name]; ext != nil {
|
||||
if i.mode&EnableTracing != 0 {
|
||||
fmt.Fprintln(os.Stderr, "\t(external)")
|
||||
}
|
||||
return ext(fr, args)
|
||||
}
|
||||
if fn.Blocks == nil {
|
||||
panic("no code for function: " + name)
|
||||
}
|
||||
}
|
||||
fr.env = make(map[ssa.Value]value)
|
||||
fr.block = fn.Blocks[0]
|
||||
fr.locals = make([]value, len(fn.Locals))
|
||||
for i, l := range fn.Locals {
|
||||
fr.locals[i] = zero(deref(l.Type()))
|
||||
fr.env[l] = &fr.locals[i]
|
||||
}
|
||||
for i, p := range fn.Params {
|
||||
fr.env[p] = args[i]
|
||||
}
|
||||
for i, fv := range fn.FreeVars {
|
||||
fr.env[fv] = env[i]
|
||||
}
|
||||
for fr.block != nil {
|
||||
runFrame(fr)
|
||||
}
|
||||
// Destroy the locals to avoid accidental use after return.
|
||||
for i := range fn.Locals {
|
||||
fr.locals[i] = bad{}
|
||||
}
|
||||
return fr.result
|
||||
}
|
||||
|
||||
// runFrame executes SSA instructions starting at fr.block and
|
||||
// continuing until a return, a panic, or a recovered panic.
|
||||
//
|
||||
// After a panic, runFrame panics.
|
||||
//
|
||||
// After a normal return, fr.result contains the result of the call
|
||||
// and fr.block is nil.
|
||||
//
|
||||
// A recovered panic in a function without named return parameters
|
||||
// (NRPs) becomes a normal return of the zero value of the function's
|
||||
// result type.
|
||||
//
|
||||
// After a recovered panic in a function with NRPs, fr.result is
|
||||
// undefined and fr.block contains the block at which to resume
|
||||
// control.
|
||||
//
|
||||
func runFrame(fr *frame) {
|
||||
defer func() {
|
||||
if fr.block == nil {
|
||||
return // normal return
|
||||
}
|
||||
if fr.i.mode&DisableRecover != 0 {
|
||||
return // let interpreter crash
|
||||
}
|
||||
fr.panicking = true
|
||||
fr.panic = recover()
|
||||
if fr.i.mode&EnableTracing != 0 {
|
||||
fmt.Fprintf(os.Stderr, "Panicking: %T %v.\n", fr.panic, fr.panic)
|
||||
}
|
||||
fr.runDefers()
|
||||
fr.block = fr.fn.Recover
|
||||
}()
|
||||
|
||||
for {
|
||||
if fr.i.mode&EnableTracing != 0 {
|
||||
fmt.Fprintf(os.Stderr, ".%s:\n", fr.block)
|
||||
}
|
||||
block:
|
||||
for _, instr := range fr.block.Instrs {
|
||||
if fr.i.mode&EnableTracing != 0 {
|
||||
if v, ok := instr.(ssa.Value); ok {
|
||||
fmt.Fprintln(os.Stderr, "\t", v.Name(), "=", instr)
|
||||
} else {
|
||||
fmt.Fprintln(os.Stderr, "\t", instr)
|
||||
}
|
||||
}
|
||||
switch visitInstr(fr, instr) {
|
||||
case kReturn:
|
||||
return
|
||||
case kNext:
|
||||
// no-op
|
||||
case kJump:
|
||||
break block
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// doRecover implements the recover() built-in.
|
||||
func doRecover(caller *frame) value {
|
||||
// recover() must be exactly one level beneath the deferred
|
||||
// function (two levels beneath the panicking function) to
|
||||
// have any effect. Thus we ignore both "defer recover()" and
|
||||
// "defer f() -> g() -> recover()".
|
||||
if caller.i.mode&DisableRecover == 0 &&
|
||||
caller != nil && !caller.panicking &&
|
||||
caller.caller != nil && caller.caller.panicking {
|
||||
caller.caller.panicking = false
|
||||
p := caller.caller.panic
|
||||
caller.caller.panic = nil
|
||||
switch p := p.(type) {
|
||||
case targetPanic:
|
||||
// The target program explicitly called panic().
|
||||
return p.v
|
||||
case runtime.Error:
|
||||
// The interpreter encountered a runtime error.
|
||||
return iface{caller.i.runtimeErrorString, p.Error()}
|
||||
case string:
|
||||
// The interpreter explicitly called panic().
|
||||
return iface{caller.i.runtimeErrorString, p}
|
||||
default:
|
||||
panic(fmt.Sprintf("unexpected panic type %T in target call to recover()", p))
|
||||
}
|
||||
}
|
||||
return iface{}
|
||||
}
|
||||
|
||||
// setGlobal sets the value of a system-initialized global variable.
|
||||
func setGlobal(i *interpreter, pkg *ssa.Package, name string, v value) {
|
||||
if g, ok := i.globals[pkg.Var(name)]; ok {
|
||||
*g = v
|
||||
return
|
||||
}
|
||||
panic("no global variable: " + pkg.Pkg.Path() + "." + name)
|
||||
}
|
||||
|
||||
var environ []value
|
||||
|
||||
func init() {
|
||||
for _, s := range os.Environ() {
|
||||
environ = append(environ, s)
|
||||
}
|
||||
environ = append(environ, "GOSSAINTERP=1")
|
||||
environ = append(environ, "GOARCH="+runtime.GOARCH)
|
||||
}
|
||||
|
||||
// deleteBodies delete the bodies of all standalone functions except the
|
||||
// specified ones. A missing intrinsic leads to a clear runtime error.
|
||||
func deleteBodies(pkg *ssa.Package, except ...string) {
|
||||
keep := make(map[string]bool)
|
||||
for _, e := range except {
|
||||
keep[e] = true
|
||||
}
|
||||
for _, mem := range pkg.Members {
|
||||
if fn, ok := mem.(*ssa.Function); ok && !keep[fn.Name()] {
|
||||
fn.Blocks = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Interpret interprets the Go program whose main package is mainpkg.
|
||||
// mode specifies various interpreter options. filename and args are
|
||||
// the initial values of os.Args for the target program. sizes is the
|
||||
// effective type-sizing function for this program.
|
||||
//
|
||||
// Interpret returns the exit code of the program: 2 for panic (like
|
||||
// gc does), or the argument to os.Exit for normal termination.
|
||||
//
|
||||
// The SSA program must include the "runtime" package.
|
||||
//
|
||||
func Interpret(mainpkg *ssa.Package, mode Mode, sizes types.Sizes, filename string, args []string) (exitCode int) {
|
||||
i := &interpreter{
|
||||
prog: mainpkg.Prog,
|
||||
globals: make(map[ssa.Value]*value),
|
||||
mode: mode,
|
||||
sizes: sizes,
|
||||
}
|
||||
runtimePkg := i.prog.ImportedPackage("runtime")
|
||||
if runtimePkg == nil {
|
||||
panic("ssa.Program doesn't include runtime package")
|
||||
}
|
||||
i.runtimeErrorString = runtimePkg.Type("errorString").Object().Type()
|
||||
|
||||
initReflect(i)
|
||||
|
||||
i.osArgs = append(i.osArgs, filename)
|
||||
for _, arg := range args {
|
||||
i.osArgs = append(i.osArgs, arg)
|
||||
}
|
||||
|
||||
for _, pkg := range i.prog.AllPackages() {
|
||||
// Initialize global storage.
|
||||
for _, m := range pkg.Members {
|
||||
switch v := m.(type) {
|
||||
case *ssa.Global:
|
||||
cell := zero(deref(v.Type()))
|
||||
i.globals[v] = &cell
|
||||
}
|
||||
}
|
||||
|
||||
// Ad-hoc initialization for magic system variables.
|
||||
switch pkg.Pkg.Path() {
|
||||
case "syscall":
|
||||
setGlobal(i, pkg, "envs", environ)
|
||||
|
||||
case "reflect":
|
||||
deleteBodies(pkg, "DeepEqual", "deepValueEqual")
|
||||
|
||||
case "runtime":
|
||||
sz := sizes.Sizeof(pkg.Pkg.Scope().Lookup("MemStats").Type())
|
||||
setGlobal(i, pkg, "sizeof_C_MStats", uintptr(sz))
|
||||
deleteBodies(pkg, "GOROOT", "gogetenv")
|
||||
}
|
||||
}
|
||||
|
||||
// Top-level error handler.
|
||||
exitCode = 2
|
||||
defer func() {
|
||||
if exitCode != 2 || i.mode&DisableRecover != 0 {
|
||||
return
|
||||
}
|
||||
switch p := recover().(type) {
|
||||
case exitPanic:
|
||||
exitCode = int(p)
|
||||
return
|
||||
case targetPanic:
|
||||
fmt.Fprintln(os.Stderr, "panic:", toString(p.v))
|
||||
case runtime.Error:
|
||||
fmt.Fprintln(os.Stderr, "panic:", p.Error())
|
||||
case string:
|
||||
fmt.Fprintln(os.Stderr, "panic:", p)
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "panic: unexpected type: %T: %v\n", p, p)
|
||||
}
|
||||
|
||||
// TODO(adonovan): dump panicking interpreter goroutine?
|
||||
// buf := make([]byte, 0x10000)
|
||||
// runtime.Stack(buf, false)
|
||||
// fmt.Fprintln(os.Stderr, string(buf))
|
||||
// (Or dump panicking target goroutine?)
|
||||
}()
|
||||
|
||||
// Run!
|
||||
call(i, nil, token.NoPos, mainpkg.Func("init"), nil)
|
||||
if mainFn := mainpkg.Func("main"); mainFn != nil {
|
||||
call(i, nil, token.NoPos, mainFn, nil)
|
||||
exitCode = 0
|
||||
} else {
|
||||
fmt.Fprintln(os.Stderr, "No main function.")
|
||||
exitCode = 1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// deref returns a pointer's element type; otherwise it returns typ.
|
||||
// TODO(adonovan): Import from ssa?
|
||||
func deref(typ types.Type) types.Type {
|
||||
if p, ok := typ.Underlying().(*types.Pointer); ok {
|
||||
return p.Elem()
|
||||
}
|
||||
return typ
|
||||
}
|
367
go/ssa/interp/interp14_test.go
Normal file
367
go/ssa/interp/interp14_test.go
Normal file
@ -0,0 +1,367 @@
|
||||
// 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.
|
||||
|
||||
// +build !go1.5
|
||||
|
||||
// +build !android,!windows,!plan9
|
||||
|
||||
package interp_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/build"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/tools/go/loader"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/tools/go/ssa/interp"
|
||||
"golang.org/x/tools/go/ssa/ssautil"
|
||||
"golang.org/x/tools/go/types"
|
||||
)
|
||||
|
||||
// Each line contains a space-separated list of $GOROOT/test/
|
||||
// filenames comprising the main package of a program.
|
||||
// They are ordered quickest-first, roughly.
|
||||
//
|
||||
// TODO(adonovan): integrate into the $GOROOT/test driver scripts,
|
||||
// golden file checking, etc.
|
||||
var gorootTestTests = []string{
|
||||
"235.go",
|
||||
"alias1.go",
|
||||
"chancap.go",
|
||||
"func5.go",
|
||||
"func6.go",
|
||||
"func7.go",
|
||||
"func8.go",
|
||||
"helloworld.go",
|
||||
"varinit.go",
|
||||
"escape3.go",
|
||||
"initcomma.go",
|
||||
"cmp.go",
|
||||
"compos.go",
|
||||
"turing.go",
|
||||
"indirect.go",
|
||||
// "complit.go", // tests go1.5 features
|
||||
"for.go",
|
||||
"struct0.go",
|
||||
"intcvt.go",
|
||||
"printbig.go",
|
||||
"deferprint.go",
|
||||
"escape.go",
|
||||
"range.go",
|
||||
"const4.go",
|
||||
"float_lit.go",
|
||||
"bigalg.go",
|
||||
"decl.go",
|
||||
"if.go",
|
||||
"named.go",
|
||||
"bigmap.go",
|
||||
"func.go",
|
||||
"reorder2.go",
|
||||
"closure.go",
|
||||
"gc.go",
|
||||
"simassign.go",
|
||||
"iota.go",
|
||||
"nilptr2.go",
|
||||
"goprint.go", // doesn't actually assert anything (cmpout)
|
||||
"utf.go",
|
||||
"method.go",
|
||||
"char_lit.go",
|
||||
"env.go",
|
||||
"int_lit.go",
|
||||
"string_lit.go",
|
||||
"defer.go",
|
||||
"typeswitch.go",
|
||||
"stringrange.go",
|
||||
"reorder.go",
|
||||
"method3.go",
|
||||
"literal.go",
|
||||
"nul1.go", // doesn't actually assert anything (errorcheckoutput)
|
||||
"zerodivide.go",
|
||||
"convert.go",
|
||||
"convT2X.go",
|
||||
"switch.go",
|
||||
"initialize.go",
|
||||
"ddd.go",
|
||||
"blank.go", // partly disabled
|
||||
"map.go",
|
||||
"closedchan.go",
|
||||
"divide.go",
|
||||
"rename.go",
|
||||
"const3.go",
|
||||
"nil.go",
|
||||
"recover.go", // reflection parts disabled
|
||||
"recover1.go",
|
||||
"recover2.go",
|
||||
"recover3.go",
|
||||
"typeswitch1.go",
|
||||
"floatcmp.go",
|
||||
"crlf.go", // doesn't actually assert anything (runoutput)
|
||||
// Slow tests follow.
|
||||
"bom.go", // ~1.7s
|
||||
"gc1.go", // ~1.7s
|
||||
"cmplxdivide.go cmplxdivide1.go", // ~2.4s
|
||||
|
||||
// Working, but not worth enabling:
|
||||
// "append.go", // works, but slow (15s).
|
||||
// "gc2.go", // works, but slow, and cheats on the memory check.
|
||||
// "sigchld.go", // works, but only on POSIX.
|
||||
// "peano.go", // works only up to n=9, and slow even then.
|
||||
// "stack.go", // works, but too slow (~30s) by default.
|
||||
// "solitaire.go", // works, but too slow (~30s).
|
||||
// "const.go", // works but for but one bug: constant folder doesn't consider representations.
|
||||
// "init1.go", // too slow (80s) and not that interesting. Cheats on ReadMemStats check too.
|
||||
// "rotate.go rotate0.go", // emits source for a test
|
||||
// "rotate.go rotate1.go", // emits source for a test
|
||||
// "rotate.go rotate2.go", // emits source for a test
|
||||
// "rotate.go rotate3.go", // emits source for a test
|
||||
// "64bit.go", // emits source for a test
|
||||
// "run.go", // test driver, not a test.
|
||||
|
||||
// Broken. TODO(adonovan): fix.
|
||||
// copy.go // very slow; but with N=4 quickly crashes, slice index out of range.
|
||||
// nilptr.go // interp: V > uintptr not implemented. Slow test, lots of mem
|
||||
// args.go // works, but requires specific os.Args from the driver.
|
||||
// index.go // a template, not a real test.
|
||||
// mallocfin.go // SetFinalizer not implemented.
|
||||
|
||||
// TODO(adonovan): add tests from $GOROOT/test/* subtrees:
|
||||
// bench chan bugs fixedbugs interface ken.
|
||||
}
|
||||
|
||||
// These are files in go.tools/go/ssa/interp/testdata/.
|
||||
var testdataTests = []string{
|
||||
"boundmeth.go",
|
||||
// "complit.go", // requires go1.5
|
||||
"coverage.go",
|
||||
"defer.go",
|
||||
"fieldprom.go",
|
||||
"ifaceconv.go",
|
||||
"ifaceprom.go",
|
||||
"initorder.go",
|
||||
"methprom.go",
|
||||
"mrvchain.go",
|
||||
"range.go",
|
||||
"recover.go",
|
||||
"reflect.go",
|
||||
"static.go",
|
||||
"callstack.go",
|
||||
}
|
||||
|
||||
// These are files and packages in $GOROOT/src/.
|
||||
var gorootSrcTests = []string{
|
||||
"encoding/ascii85",
|
||||
"encoding/hex",
|
||||
// "encoding/pem", // TODO(adonovan): implement (reflect.Value).SetString
|
||||
// "testing", // TODO(adonovan): implement runtime.Goexit correctly
|
||||
// "hash/crc32", // TODO(adonovan): implement hash/crc32.haveCLMUL
|
||||
// "log", // TODO(adonovan): implement runtime.Callers correctly
|
||||
|
||||
// Too slow:
|
||||
// "container/ring",
|
||||
// "hash/adler32",
|
||||
|
||||
"unicode/utf8",
|
||||
"path",
|
||||
"flag",
|
||||
"encoding/csv",
|
||||
"text/scanner",
|
||||
"unicode",
|
||||
}
|
||||
|
||||
type successPredicate func(exitcode int, output string) error
|
||||
|
||||
func run(t *testing.T, dir, input string, success successPredicate) bool {
|
||||
fmt.Printf("Input: %s\n", input)
|
||||
|
||||
start := time.Now()
|
||||
|
||||
var inputs []string
|
||||
for _, i := range strings.Split(input, " ") {
|
||||
if strings.HasSuffix(i, ".go") {
|
||||
i = dir + i
|
||||
}
|
||||
inputs = append(inputs, i)
|
||||
}
|
||||
|
||||
var conf loader.Config
|
||||
if _, err := conf.FromArgs(inputs, true); err != nil {
|
||||
t.Errorf("FromArgs(%s) failed: %s", inputs, err)
|
||||
return false
|
||||
}
|
||||
|
||||
conf.Import("runtime")
|
||||
|
||||
// Print a helpful hint if we don't make it to the end.
|
||||
var hint string
|
||||
defer func() {
|
||||
if hint != "" {
|
||||
fmt.Println("FAIL")
|
||||
fmt.Println(hint)
|
||||
} else {
|
||||
fmt.Println("PASS")
|
||||
}
|
||||
|
||||
interp.CapturedOutput = nil
|
||||
}()
|
||||
|
||||
hint = fmt.Sprintf("To dump SSA representation, run:\n%% go build golang.org/x/tools/cmd/ssadump && ./ssadump -test -build=CFP %s\n", input)
|
||||
|
||||
iprog, err := conf.Load()
|
||||
if err != nil {
|
||||
t.Errorf("conf.Load(%s) failed: %s", inputs, err)
|
||||
return false
|
||||
}
|
||||
|
||||
prog := ssautil.CreateProgram(iprog, ssa.SanityCheckFunctions)
|
||||
prog.Build()
|
||||
|
||||
var mainPkg *ssa.Package
|
||||
var initialPkgs []*ssa.Package
|
||||
for _, info := range iprog.InitialPackages() {
|
||||
if info.Pkg.Path() == "runtime" {
|
||||
continue // not an initial package
|
||||
}
|
||||
p := prog.Package(info.Pkg)
|
||||
initialPkgs = append(initialPkgs, p)
|
||||
if mainPkg == nil && p.Func("main") != nil {
|
||||
mainPkg = p
|
||||
}
|
||||
}
|
||||
if mainPkg == nil {
|
||||
testmainPkg := prog.CreateTestMainPackage(initialPkgs...)
|
||||
if testmainPkg == nil {
|
||||
t.Errorf("CreateTestMainPackage(%s) returned nil", mainPkg)
|
||||
return false
|
||||
}
|
||||
if testmainPkg.Func("main") == nil {
|
||||
t.Errorf("synthetic testmain package has no main")
|
||||
return false
|
||||
}
|
||||
mainPkg = testmainPkg
|
||||
}
|
||||
|
||||
var out bytes.Buffer
|
||||
interp.CapturedOutput = &out
|
||||
|
||||
hint = fmt.Sprintf("To trace execution, run:\n%% go build golang.org/x/tools/cmd/ssadump && ./ssadump -build=C -run --interp=T %s\n", input)
|
||||
exitCode := interp.Interpret(mainPkg, 0, &types.StdSizes{8, 8}, inputs[0], []string{})
|
||||
|
||||
// The definition of success varies with each file.
|
||||
if err := success(exitCode, out.String()); err != nil {
|
||||
t.Errorf("interp.Interpret(%s) failed: %s", inputs, err)
|
||||
return false
|
||||
}
|
||||
|
||||
hint = "" // call off the hounds
|
||||
|
||||
if false {
|
||||
fmt.Println(input, time.Since(start)) // test profiling
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
const slash = string(os.PathSeparator)
|
||||
|
||||
func printFailures(failures []string) {
|
||||
if failures != nil {
|
||||
fmt.Println("The following tests failed:")
|
||||
for _, f := range failures {
|
||||
fmt.Printf("\t%s\n", f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func success(exitcode int, output string) error {
|
||||
if exitcode != 0 {
|
||||
return fmt.Errorf("exit code was %d", exitcode)
|
||||
}
|
||||
if strings.Contains(output, "BUG") {
|
||||
return fmt.Errorf("exited zero but output contained 'BUG'")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TestTestdataFiles runs the interpreter on testdata/*.go.
|
||||
func TestTestdataFiles(t *testing.T) {
|
||||
var failures []string
|
||||
start := time.Now()
|
||||
for _, input := range testdataTests {
|
||||
if testing.Short() && time.Since(start) > 30*time.Second {
|
||||
printFailures(failures)
|
||||
t.Skipf("timeout - aborting test")
|
||||
}
|
||||
if !run(t, "testdata"+slash, input, success) {
|
||||
failures = append(failures, input)
|
||||
}
|
||||
}
|
||||
printFailures(failures)
|
||||
}
|
||||
|
||||
// TestGorootTest runs the interpreter on $GOROOT/test/*.go.
|
||||
func TestGorootTest(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip() // too slow (~30s)
|
||||
}
|
||||
|
||||
var failures []string
|
||||
|
||||
for _, input := range gorootTestTests {
|
||||
if !run(t, filepath.Join(build.Default.GOROOT, "test")+slash, input, success) {
|
||||
failures = append(failures, input)
|
||||
}
|
||||
}
|
||||
for _, input := range gorootSrcTests {
|
||||
if !run(t, filepath.Join(build.Default.GOROOT, "src")+slash, input, success) {
|
||||
failures = append(failures, input)
|
||||
}
|
||||
}
|
||||
printFailures(failures)
|
||||
}
|
||||
|
||||
// TestTestmainPackage runs the interpreter on a synthetic "testmain" package.
|
||||
func TestTestmainPackage(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip() // too slow on some platforms
|
||||
}
|
||||
|
||||
success := func(exitcode int, output string) error {
|
||||
if exitcode == 0 {
|
||||
return fmt.Errorf("unexpected success")
|
||||
}
|
||||
if !strings.Contains(output, "FAIL: TestFoo") {
|
||||
return fmt.Errorf("missing failure log for TestFoo")
|
||||
}
|
||||
if !strings.Contains(output, "FAIL: TestBar") {
|
||||
return fmt.Errorf("missing failure log for TestBar")
|
||||
}
|
||||
// TODO(adonovan): test benchmarks too
|
||||
return nil
|
||||
}
|
||||
run(t, "testdata"+slash, "a_test.go", success)
|
||||
}
|
||||
|
||||
// CreateTestMainPackage should return nil if there were no tests.
|
||||
func TestNullTestmainPackage(t *testing.T) {
|
||||
var conf loader.Config
|
||||
conf.CreateFromFilenames("", "testdata/b_test.go")
|
||||
iprog, err := conf.Load()
|
||||
if err != nil {
|
||||
t.Fatalf("CreatePackages failed: %s", err)
|
||||
}
|
||||
prog := ssautil.CreateProgram(iprog, ssa.SanityCheckFunctions)
|
||||
mainPkg := prog.Package(iprog.Created[0].Pkg)
|
||||
if mainPkg.Func("main") != nil {
|
||||
t.Fatalf("unexpected main function")
|
||||
}
|
||||
if prog.CreateTestMainPackage(mainPkg) != nil {
|
||||
t.Fatalf("CreateTestMainPackage returned non-nil")
|
||||
}
|
||||
}
|
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
// +build !android,!windows,!plan9
|
||||
|
||||
package interp_test
|
||||
|
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
package interp
|
||||
|
||||
// Custom hashtable atop map.
|
||||
|
115
go/ssa/interp/map14.go
Normal file
115
go/ssa/interp/map14.go
Normal file
@ -0,0 +1,115 @@
|
||||
// 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.
|
||||
|
||||
// +build !go1.5
|
||||
|
||||
package interp
|
||||
|
||||
// Custom hashtable atop map.
|
||||
// For use when the key's equivalence relation is not consistent with ==.
|
||||
|
||||
// The Go specification doesn't address the atomicity of map operations.
|
||||
// The FAQ states that an implementation is permitted to crash on
|
||||
// concurrent map access.
|
||||
|
||||
import (
|
||||
"golang.org/x/tools/go/types"
|
||||
)
|
||||
|
||||
type hashable interface {
|
||||
hash(t types.Type) int
|
||||
eq(t types.Type, x interface{}) bool
|
||||
}
|
||||
|
||||
type entry struct {
|
||||
key hashable
|
||||
value value
|
||||
next *entry
|
||||
}
|
||||
|
||||
// A hashtable atop the built-in map. Since each bucket contains
|
||||
// exactly one hash value, there's no need to perform hash-equality
|
||||
// tests when walking the linked list. Rehashing is done by the
|
||||
// underlying map.
|
||||
type hashmap struct {
|
||||
keyType types.Type
|
||||
table map[int]*entry
|
||||
length int // number of entries in map
|
||||
}
|
||||
|
||||
// makeMap returns an empty initialized map of key type kt,
|
||||
// preallocating space for reserve elements.
|
||||
func makeMap(kt types.Type, reserve int) value {
|
||||
if usesBuiltinMap(kt) {
|
||||
return make(map[value]value, reserve)
|
||||
}
|
||||
return &hashmap{keyType: kt, table: make(map[int]*entry, reserve)}
|
||||
}
|
||||
|
||||
// delete removes the association for key k, if any.
|
||||
func (m *hashmap) delete(k hashable) {
|
||||
if m != nil {
|
||||
hash := k.hash(m.keyType)
|
||||
head := m.table[hash]
|
||||
if head != nil {
|
||||
if k.eq(m.keyType, head.key) {
|
||||
m.table[hash] = head.next
|
||||
m.length--
|
||||
return
|
||||
}
|
||||
prev := head
|
||||
for e := head.next; e != nil; e = e.next {
|
||||
if k.eq(m.keyType, e.key) {
|
||||
prev.next = e.next
|
||||
m.length--
|
||||
return
|
||||
}
|
||||
prev = e
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// lookup returns the value associated with key k, if present, or
|
||||
// value(nil) otherwise.
|
||||
func (m *hashmap) lookup(k hashable) value {
|
||||
if m != nil {
|
||||
hash := k.hash(m.keyType)
|
||||
for e := m.table[hash]; e != nil; e = e.next {
|
||||
if k.eq(m.keyType, e.key) {
|
||||
return e.value
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// insert updates the map to associate key k with value v. If there
|
||||
// was already an association for an eq() (though not necessarily ==)
|
||||
// k, the previous key remains in the map and its associated value is
|
||||
// updated.
|
||||
func (m *hashmap) insert(k hashable, v value) {
|
||||
hash := k.hash(m.keyType)
|
||||
head := m.table[hash]
|
||||
for e := head; e != nil; e = e.next {
|
||||
if k.eq(m.keyType, e.key) {
|
||||
e.value = v
|
||||
return
|
||||
}
|
||||
}
|
||||
m.table[hash] = &entry{
|
||||
key: k,
|
||||
value: v,
|
||||
next: head,
|
||||
}
|
||||
m.length++
|
||||
}
|
||||
|
||||
// len returns the number of key/value associations in the map.
|
||||
func (m *hashmap) len() int {
|
||||
if m != nil {
|
||||
return m.length
|
||||
}
|
||||
return 0
|
||||
}
|
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
package interp
|
||||
|
||||
import (
|
||||
|
1396
go/ssa/interp/ops14.go
Normal file
1396
go/ssa/interp/ops14.go
Normal file
File diff suppressed because it is too large
Load Diff
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
package interp
|
||||
|
||||
// Emulated "reflect" package.
|
||||
|
576
go/ssa/interp/reflect14.go
Normal file
576
go/ssa/interp/reflect14.go
Normal file
@ -0,0 +1,576 @@
|
||||
// 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.
|
||||
|
||||
// +build !go1.5
|
||||
|
||||
package interp
|
||||
|
||||
// Emulated "reflect" package.
|
||||
//
|
||||
// We completely replace the built-in "reflect" package.
|
||||
// The only thing clients can depend upon are that reflect.Type is an
|
||||
// interface and reflect.Value is an (opaque) struct.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/token"
|
||||
"reflect"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/tools/go/types"
|
||||
)
|
||||
|
||||
type opaqueType struct {
|
||||
types.Type
|
||||
name string
|
||||
}
|
||||
|
||||
func (t *opaqueType) String() string { return t.name }
|
||||
|
||||
// A bogus "reflect" type-checker package. Shared across interpreters.
|
||||
var reflectTypesPackage = types.NewPackage("reflect", "reflect")
|
||||
|
||||
// rtype is the concrete type the interpreter uses to implement the
|
||||
// reflect.Type interface.
|
||||
//
|
||||
// type rtype <opaque>
|
||||
var rtypeType = makeNamedType("rtype", &opaqueType{nil, "rtype"})
|
||||
|
||||
// error is an (interpreted) named type whose underlying type is string.
|
||||
// The interpreter uses it for all implementations of the built-in error
|
||||
// interface that it creates.
|
||||
// We put it in the "reflect" package for expedience.
|
||||
//
|
||||
// type error string
|
||||
var errorType = makeNamedType("error", &opaqueType{nil, "error"})
|
||||
|
||||
func makeNamedType(name string, underlying types.Type) *types.Named {
|
||||
obj := types.NewTypeName(token.NoPos, reflectTypesPackage, name, nil)
|
||||
return types.NewNamed(obj, underlying, nil)
|
||||
}
|
||||
|
||||
func makeReflectValue(t types.Type, v value) value {
|
||||
return structure{rtype{t}, v}
|
||||
}
|
||||
|
||||
// Given a reflect.Value, returns its rtype.
|
||||
func rV2T(v value) rtype {
|
||||
return v.(structure)[0].(rtype)
|
||||
}
|
||||
|
||||
// Given a reflect.Value, returns the underlying interpreter value.
|
||||
func rV2V(v value) value {
|
||||
return v.(structure)[1]
|
||||
}
|
||||
|
||||
// makeReflectType boxes up an rtype in a reflect.Type interface.
|
||||
func makeReflectType(rt rtype) value {
|
||||
return iface{rtypeType, rt}
|
||||
}
|
||||
|
||||
func ext۰reflect۰Init(fr *frame, args []value) value {
|
||||
// Signature: func()
|
||||
return nil
|
||||
}
|
||||
|
||||
func ext۰reflect۰rtype۰Bits(fr *frame, args []value) value {
|
||||
// Signature: func (t reflect.rtype) int
|
||||
rt := args[0].(rtype).t
|
||||
basic, ok := rt.Underlying().(*types.Basic)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("reflect.Type.Bits(%T): non-basic type", rt))
|
||||
}
|
||||
return int(fr.i.sizes.Sizeof(basic)) * 8
|
||||
}
|
||||
|
||||
func ext۰reflect۰rtype۰Elem(fr *frame, args []value) value {
|
||||
// Signature: func (t reflect.rtype) reflect.Type
|
||||
return makeReflectType(rtype{args[0].(rtype).t.Underlying().(interface {
|
||||
Elem() types.Type
|
||||
}).Elem()})
|
||||
}
|
||||
|
||||
func ext۰reflect۰rtype۰Field(fr *frame, args []value) value {
|
||||
// Signature: func (t reflect.rtype, i int) reflect.StructField
|
||||
st := args[0].(rtype).t.Underlying().(*types.Struct)
|
||||
i := args[1].(int)
|
||||
f := st.Field(i)
|
||||
return structure{
|
||||
f.Name(),
|
||||
f.Pkg().Path(),
|
||||
makeReflectType(rtype{f.Type()}),
|
||||
st.Tag(i),
|
||||
0, // TODO(adonovan): offset
|
||||
[]value{}, // TODO(adonovan): indices
|
||||
f.Anonymous(),
|
||||
}
|
||||
}
|
||||
|
||||
func ext۰reflect۰rtype۰In(fr *frame, args []value) value {
|
||||
// Signature: func (t reflect.rtype, i int) int
|
||||
i := args[1].(int)
|
||||
return makeReflectType(rtype{args[0].(rtype).t.(*types.Signature).Params().At(i).Type()})
|
||||
}
|
||||
|
||||
func ext۰reflect۰rtype۰Kind(fr *frame, args []value) value {
|
||||
// Signature: func (t reflect.rtype) uint
|
||||
return uint(reflectKind(args[0].(rtype).t))
|
||||
}
|
||||
|
||||
func ext۰reflect۰rtype۰NumField(fr *frame, args []value) value {
|
||||
// Signature: func (t reflect.rtype) int
|
||||
return args[0].(rtype).t.Underlying().(*types.Struct).NumFields()
|
||||
}
|
||||
|
||||
func ext۰reflect۰rtype۰NumIn(fr *frame, args []value) value {
|
||||
// Signature: func (t reflect.rtype) int
|
||||
return args[0].(rtype).t.(*types.Signature).Params().Len()
|
||||
}
|
||||
|
||||
func ext۰reflect۰rtype۰NumMethod(fr *frame, args []value) value {
|
||||
// Signature: func (t reflect.rtype) int
|
||||
return fr.i.prog.MethodSets.MethodSet(args[0].(rtype).t).Len()
|
||||
}
|
||||
|
||||
func ext۰reflect۰rtype۰NumOut(fr *frame, args []value) value {
|
||||
// Signature: func (t reflect.rtype) int
|
||||
return args[0].(rtype).t.(*types.Signature).Results().Len()
|
||||
}
|
||||
|
||||
func ext۰reflect۰rtype۰Out(fr *frame, args []value) value {
|
||||
// Signature: func (t reflect.rtype, i int) int
|
||||
i := args[1].(int)
|
||||
return makeReflectType(rtype{args[0].(rtype).t.(*types.Signature).Results().At(i).Type()})
|
||||
}
|
||||
|
||||
func ext۰reflect۰rtype۰Size(fr *frame, args []value) value {
|
||||
// Signature: func (t reflect.rtype) uintptr
|
||||
return uintptr(fr.i.sizes.Sizeof(args[0].(rtype).t))
|
||||
}
|
||||
|
||||
func ext۰reflect۰rtype۰String(fr *frame, args []value) value {
|
||||
// Signature: func (t reflect.rtype) string
|
||||
return args[0].(rtype).t.String()
|
||||
}
|
||||
|
||||
func ext۰reflect۰New(fr *frame, args []value) value {
|
||||
// Signature: func (t reflect.Type) reflect.Value
|
||||
t := args[0].(iface).v.(rtype).t
|
||||
alloc := zero(t)
|
||||
return makeReflectValue(types.NewPointer(t), &alloc)
|
||||
}
|
||||
|
||||
func ext۰reflect۰SliceOf(fr *frame, args []value) value {
|
||||
// Signature: func (t reflect.rtype) Type
|
||||
return makeReflectType(rtype{types.NewSlice(args[0].(iface).v.(rtype).t)})
|
||||
}
|
||||
|
||||
func ext۰reflect۰TypeOf(fr *frame, args []value) value {
|
||||
// Signature: func (t reflect.rtype) Type
|
||||
return makeReflectType(rtype{args[0].(iface).t})
|
||||
}
|
||||
|
||||
func ext۰reflect۰ValueOf(fr *frame, args []value) value {
|
||||
// Signature: func (interface{}) reflect.Value
|
||||
itf := args[0].(iface)
|
||||
return makeReflectValue(itf.t, itf.v)
|
||||
}
|
||||
|
||||
func ext۰reflect۰Zero(fr *frame, args []value) value {
|
||||
// Signature: func (t reflect.Type) reflect.Value
|
||||
t := args[0].(iface).v.(rtype).t
|
||||
return makeReflectValue(t, zero(t))
|
||||
}
|
||||
|
||||
func reflectKind(t types.Type) reflect.Kind {
|
||||
switch t := t.(type) {
|
||||
case *types.Named:
|
||||
return reflectKind(t.Underlying())
|
||||
case *types.Basic:
|
||||
switch t.Kind() {
|
||||
case types.Bool:
|
||||
return reflect.Bool
|
||||
case types.Int:
|
||||
return reflect.Int
|
||||
case types.Int8:
|
||||
return reflect.Int8
|
||||
case types.Int16:
|
||||
return reflect.Int16
|
||||
case types.Int32:
|
||||
return reflect.Int32
|
||||
case types.Int64:
|
||||
return reflect.Int64
|
||||
case types.Uint:
|
||||
return reflect.Uint
|
||||
case types.Uint8:
|
||||
return reflect.Uint8
|
||||
case types.Uint16:
|
||||
return reflect.Uint16
|
||||
case types.Uint32:
|
||||
return reflect.Uint32
|
||||
case types.Uint64:
|
||||
return reflect.Uint64
|
||||
case types.Uintptr:
|
||||
return reflect.Uintptr
|
||||
case types.Float32:
|
||||
return reflect.Float32
|
||||
case types.Float64:
|
||||
return reflect.Float64
|
||||
case types.Complex64:
|
||||
return reflect.Complex64
|
||||
case types.Complex128:
|
||||
return reflect.Complex128
|
||||
case types.String:
|
||||
return reflect.String
|
||||
case types.UnsafePointer:
|
||||
return reflect.UnsafePointer
|
||||
}
|
||||
case *types.Array:
|
||||
return reflect.Array
|
||||
case *types.Chan:
|
||||
return reflect.Chan
|
||||
case *types.Signature:
|
||||
return reflect.Func
|
||||
case *types.Interface:
|
||||
return reflect.Interface
|
||||
case *types.Map:
|
||||
return reflect.Map
|
||||
case *types.Pointer:
|
||||
return reflect.Ptr
|
||||
case *types.Slice:
|
||||
return reflect.Slice
|
||||
case *types.Struct:
|
||||
return reflect.Struct
|
||||
}
|
||||
panic(fmt.Sprint("unexpected type: ", t))
|
||||
}
|
||||
|
||||
func ext۰reflect۰Value۰Kind(fr *frame, args []value) value {
|
||||
// Signature: func (reflect.Value) uint
|
||||
return uint(reflectKind(rV2T(args[0]).t))
|
||||
}
|
||||
|
||||
func ext۰reflect۰Value۰String(fr *frame, args []value) value {
|
||||
// Signature: func (reflect.Value) string
|
||||
return toString(rV2V(args[0]))
|
||||
}
|
||||
|
||||
func ext۰reflect۰Value۰Type(fr *frame, args []value) value {
|
||||
// Signature: func (reflect.Value) reflect.Type
|
||||
return makeReflectType(rV2T(args[0]))
|
||||
}
|
||||
|
||||
func ext۰reflect۰Value۰Uint(fr *frame, args []value) value {
|
||||
// Signature: func (reflect.Value) uint64
|
||||
switch v := rV2V(args[0]).(type) {
|
||||
case uint:
|
||||
return uint64(v)
|
||||
case uint8:
|
||||
return uint64(v)
|
||||
case uint16:
|
||||
return uint64(v)
|
||||
case uint32:
|
||||
return uint64(v)
|
||||
case uint64:
|
||||
return uint64(v)
|
||||
case uintptr:
|
||||
return uint64(v)
|
||||
}
|
||||
panic("reflect.Value.Uint")
|
||||
}
|
||||
|
||||
func ext۰reflect۰Value۰Len(fr *frame, args []value) value {
|
||||
// Signature: func (reflect.Value) int
|
||||
switch v := rV2V(args[0]).(type) {
|
||||
case string:
|
||||
return len(v)
|
||||
case array:
|
||||
return len(v)
|
||||
case chan value:
|
||||
return cap(v)
|
||||
case []value:
|
||||
return len(v)
|
||||
case *hashmap:
|
||||
return v.len()
|
||||
case map[value]value:
|
||||
return len(v)
|
||||
default:
|
||||
panic(fmt.Sprintf("reflect.(Value).Len(%v)", v))
|
||||
}
|
||||
}
|
||||
|
||||
func ext۰reflect۰Value۰MapIndex(fr *frame, args []value) value {
|
||||
// Signature: func (reflect.Value) Value
|
||||
tValue := rV2T(args[0]).t.Underlying().(*types.Map).Key()
|
||||
k := rV2V(args[1])
|
||||
switch m := rV2V(args[0]).(type) {
|
||||
case map[value]value:
|
||||
if v, ok := m[k]; ok {
|
||||
return makeReflectValue(tValue, v)
|
||||
}
|
||||
|
||||
case *hashmap:
|
||||
if v := m.lookup(k.(hashable)); v != nil {
|
||||
return makeReflectValue(tValue, v)
|
||||
}
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("(reflect.Value).MapIndex(%T, %T)", m, k))
|
||||
}
|
||||
return makeReflectValue(nil, nil)
|
||||
}
|
||||
|
||||
func ext۰reflect۰Value۰MapKeys(fr *frame, args []value) value {
|
||||
// Signature: func (reflect.Value) []Value
|
||||
var keys []value
|
||||
tKey := rV2T(args[0]).t.Underlying().(*types.Map).Key()
|
||||
switch v := rV2V(args[0]).(type) {
|
||||
case map[value]value:
|
||||
for k := range v {
|
||||
keys = append(keys, makeReflectValue(tKey, k))
|
||||
}
|
||||
|
||||
case *hashmap:
|
||||
for _, e := range v.table {
|
||||
for ; e != nil; e = e.next {
|
||||
keys = append(keys, makeReflectValue(tKey, e.key))
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("(reflect.Value).MapKeys(%T)", v))
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
func ext۰reflect۰Value۰NumField(fr *frame, args []value) value {
|
||||
// Signature: func (reflect.Value) int
|
||||
return len(rV2V(args[0]).(structure))
|
||||
}
|
||||
|
||||
func ext۰reflect۰Value۰NumMethod(fr *frame, args []value) value {
|
||||
// Signature: func (reflect.Value) int
|
||||
return fr.i.prog.MethodSets.MethodSet(rV2T(args[0]).t).Len()
|
||||
}
|
||||
|
||||
func ext۰reflect۰Value۰Pointer(fr *frame, args []value) value {
|
||||
// Signature: func (v reflect.Value) uintptr
|
||||
switch v := rV2V(args[0]).(type) {
|
||||
case *value:
|
||||
return uintptr(unsafe.Pointer(v))
|
||||
case chan value:
|
||||
return reflect.ValueOf(v).Pointer()
|
||||
case []value:
|
||||
return reflect.ValueOf(v).Pointer()
|
||||
case *hashmap:
|
||||
return reflect.ValueOf(v.table).Pointer()
|
||||
case map[value]value:
|
||||
return reflect.ValueOf(v).Pointer()
|
||||
case *ssa.Function:
|
||||
return uintptr(unsafe.Pointer(v))
|
||||
case *closure:
|
||||
return uintptr(unsafe.Pointer(v))
|
||||
default:
|
||||
panic(fmt.Sprintf("reflect.(Value).Pointer(%T)", v))
|
||||
}
|
||||
}
|
||||
|
||||
func ext۰reflect۰Value۰Index(fr *frame, args []value) value {
|
||||
// Signature: func (v reflect.Value, i int) Value
|
||||
i := args[1].(int)
|
||||
t := rV2T(args[0]).t.Underlying()
|
||||
switch v := rV2V(args[0]).(type) {
|
||||
case array:
|
||||
return makeReflectValue(t.(*types.Array).Elem(), v[i])
|
||||
case []value:
|
||||
return makeReflectValue(t.(*types.Slice).Elem(), v[i])
|
||||
default:
|
||||
panic(fmt.Sprintf("reflect.(Value).Index(%T)", v))
|
||||
}
|
||||
}
|
||||
|
||||
func ext۰reflect۰Value۰Bool(fr *frame, args []value) value {
|
||||
// Signature: func (reflect.Value) bool
|
||||
return rV2V(args[0]).(bool)
|
||||
}
|
||||
|
||||
func ext۰reflect۰Value۰CanAddr(fr *frame, args []value) value {
|
||||
// Signature: func (v reflect.Value) bool
|
||||
// Always false for our representation.
|
||||
return false
|
||||
}
|
||||
|
||||
func ext۰reflect۰Value۰CanInterface(fr *frame, args []value) value {
|
||||
// Signature: func (v reflect.Value) bool
|
||||
// Always true for our representation.
|
||||
return true
|
||||
}
|
||||
|
||||
func ext۰reflect۰Value۰Elem(fr *frame, args []value) value {
|
||||
// Signature: func (v reflect.Value) reflect.Value
|
||||
switch x := rV2V(args[0]).(type) {
|
||||
case iface:
|
||||
return makeReflectValue(x.t, x.v)
|
||||
case *value:
|
||||
return makeReflectValue(rV2T(args[0]).t.Underlying().(*types.Pointer).Elem(), *x)
|
||||
default:
|
||||
panic(fmt.Sprintf("reflect.(Value).Elem(%T)", x))
|
||||
}
|
||||
}
|
||||
|
||||
func ext۰reflect۰Value۰Field(fr *frame, args []value) value {
|
||||
// Signature: func (v reflect.Value, i int) reflect.Value
|
||||
v := args[0]
|
||||
i := args[1].(int)
|
||||
return makeReflectValue(rV2T(v).t.Underlying().(*types.Struct).Field(i).Type(), rV2V(v).(structure)[i])
|
||||
}
|
||||
|
||||
func ext۰reflect۰Value۰Float(fr *frame, args []value) value {
|
||||
// Signature: func (reflect.Value) float64
|
||||
switch v := rV2V(args[0]).(type) {
|
||||
case float32:
|
||||
return float64(v)
|
||||
case float64:
|
||||
return float64(v)
|
||||
}
|
||||
panic("reflect.Value.Float")
|
||||
}
|
||||
|
||||
func ext۰reflect۰Value۰Interface(fr *frame, args []value) value {
|
||||
// Signature: func (v reflect.Value) interface{}
|
||||
return ext۰reflect۰valueInterface(fr, args)
|
||||
}
|
||||
|
||||
func ext۰reflect۰Value۰Int(fr *frame, args []value) value {
|
||||
// Signature: func (reflect.Value) int64
|
||||
switch x := rV2V(args[0]).(type) {
|
||||
case int:
|
||||
return int64(x)
|
||||
case int8:
|
||||
return int64(x)
|
||||
case int16:
|
||||
return int64(x)
|
||||
case int32:
|
||||
return int64(x)
|
||||
case int64:
|
||||
return x
|
||||
default:
|
||||
panic(fmt.Sprintf("reflect.(Value).Int(%T)", x))
|
||||
}
|
||||
}
|
||||
|
||||
func ext۰reflect۰Value۰IsNil(fr *frame, args []value) value {
|
||||
// Signature: func (reflect.Value) bool
|
||||
switch x := rV2V(args[0]).(type) {
|
||||
case *value:
|
||||
return x == nil
|
||||
case chan value:
|
||||
return x == nil
|
||||
case map[value]value:
|
||||
return x == nil
|
||||
case *hashmap:
|
||||
return x == nil
|
||||
case iface:
|
||||
return x.t == nil
|
||||
case []value:
|
||||
return x == nil
|
||||
case *ssa.Function:
|
||||
return x == nil
|
||||
case *ssa.Builtin:
|
||||
return x == nil
|
||||
case *closure:
|
||||
return x == nil
|
||||
default:
|
||||
panic(fmt.Sprintf("reflect.(Value).IsNil(%T)", x))
|
||||
}
|
||||
}
|
||||
|
||||
func ext۰reflect۰Value۰IsValid(fr *frame, args []value) value {
|
||||
// Signature: func (reflect.Value) bool
|
||||
return rV2V(args[0]) != nil
|
||||
}
|
||||
|
||||
func ext۰reflect۰Value۰Set(fr *frame, args []value) value {
|
||||
// TODO(adonovan): implement.
|
||||
return nil
|
||||
}
|
||||
|
||||
func ext۰reflect۰valueInterface(fr *frame, args []value) value {
|
||||
// Signature: func (v reflect.Value, safe bool) interface{}
|
||||
v := args[0].(structure)
|
||||
return iface{rV2T(v).t, rV2V(v)}
|
||||
}
|
||||
|
||||
func ext۰reflect۰error۰Error(fr *frame, args []value) value {
|
||||
return args[0]
|
||||
}
|
||||
|
||||
// newMethod creates a new method of the specified name, package and receiver type.
|
||||
func newMethod(pkg *ssa.Package, recvType types.Type, name string) *ssa.Function {
|
||||
// TODO(adonovan): fix: hack: currently the only part of Signature
|
||||
// that is needed is the "pointerness" of Recv.Type, and for
|
||||
// now, we'll set it to always be false since we're only
|
||||
// concerned with rtype. Encapsulate this better.
|
||||
sig := types.NewSignature(types.NewVar(token.NoPos, nil, "recv", recvType), nil, nil, false)
|
||||
fn := pkg.Prog.NewFunction(name, sig, "fake reflect method")
|
||||
fn.Pkg = pkg
|
||||
return fn
|
||||
}
|
||||
|
||||
func initReflect(i *interpreter) {
|
||||
i.reflectPackage = &ssa.Package{
|
||||
Prog: i.prog,
|
||||
Pkg: reflectTypesPackage,
|
||||
Members: make(map[string]ssa.Member),
|
||||
}
|
||||
|
||||
// Clobber the type-checker's notion of reflect.Value's
|
||||
// underlying type so that it more closely matches the fake one
|
||||
// (at least in the number of fields---we lie about the type of
|
||||
// the rtype field).
|
||||
//
|
||||
// We must ensure that calls to (ssa.Value).Type() return the
|
||||
// fake type so that correct "shape" is used when allocating
|
||||
// variables, making zero values, loading, and storing.
|
||||
//
|
||||
// TODO(adonovan): obviously this is a hack. We need a cleaner
|
||||
// way to fake the reflect package (almost---DeepEqual is fine).
|
||||
// One approach would be not to even load its source code, but
|
||||
// provide fake source files. This would guarantee that no bad
|
||||
// information leaks into other packages.
|
||||
if r := i.prog.ImportedPackage("reflect"); r != nil {
|
||||
rV := r.Pkg.Scope().Lookup("Value").Type().(*types.Named)
|
||||
|
||||
// delete bodies of the old methods
|
||||
mset := i.prog.MethodSets.MethodSet(rV)
|
||||
for j := 0; j < mset.Len(); j++ {
|
||||
i.prog.MethodValue(mset.At(j)).Blocks = nil
|
||||
}
|
||||
|
||||
tEface := types.NewInterface(nil, nil).Complete()
|
||||
rV.SetUnderlying(types.NewStruct([]*types.Var{
|
||||
types.NewField(token.NoPos, r.Pkg, "t", tEface, false), // a lie
|
||||
types.NewField(token.NoPos, r.Pkg, "v", tEface, false),
|
||||
}, nil))
|
||||
}
|
||||
|
||||
i.rtypeMethods = methodSet{
|
||||
"Bits": newMethod(i.reflectPackage, rtypeType, "Bits"),
|
||||
"Elem": newMethod(i.reflectPackage, rtypeType, "Elem"),
|
||||
"Field": newMethod(i.reflectPackage, rtypeType, "Field"),
|
||||
"In": newMethod(i.reflectPackage, rtypeType, "In"),
|
||||
"Kind": newMethod(i.reflectPackage, rtypeType, "Kind"),
|
||||
"NumField": newMethod(i.reflectPackage, rtypeType, "NumField"),
|
||||
"NumIn": newMethod(i.reflectPackage, rtypeType, "NumIn"),
|
||||
"NumMethod": newMethod(i.reflectPackage, rtypeType, "NumMethod"),
|
||||
"NumOut": newMethod(i.reflectPackage, rtypeType, "NumOut"),
|
||||
"Out": newMethod(i.reflectPackage, rtypeType, "Out"),
|
||||
"Size": newMethod(i.reflectPackage, rtypeType, "Size"),
|
||||
"String": newMethod(i.reflectPackage, rtypeType, "String"),
|
||||
}
|
||||
i.errorMethods = methodSet{
|
||||
"Error": newMethod(i.reflectPackage, errorType, "Error"),
|
||||
}
|
||||
}
|
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
package interp
|
||||
|
||||
// Values
|
||||
|
499
go/ssa/interp/value14.go
Normal file
499
go/ssa/interp/value14.go
Normal file
@ -0,0 +1,499 @@
|
||||
// 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.
|
||||
|
||||
// +build !go1.5
|
||||
|
||||
package interp
|
||||
|
||||
// Values
|
||||
//
|
||||
// All interpreter values are "boxed" in the empty interface, value.
|
||||
// The range of possible dynamic types within value are:
|
||||
//
|
||||
// - bool
|
||||
// - numbers (all built-in int/float/complex types are distinguished)
|
||||
// - string
|
||||
// - map[value]value --- maps for which usesBuiltinMap(keyType)
|
||||
// *hashmap --- maps for which !usesBuiltinMap(keyType)
|
||||
// - chan value
|
||||
// - []value --- slices
|
||||
// - iface --- interfaces.
|
||||
// - structure --- structs. Fields are ordered and accessed by numeric indices.
|
||||
// - array --- arrays.
|
||||
// - *value --- pointers. Careful: *value is a distinct type from *array etc.
|
||||
// - *ssa.Function \
|
||||
// *ssa.Builtin } --- functions. A nil 'func' is always of type *ssa.Function.
|
||||
// *closure /
|
||||
// - tuple --- as returned by Return, Next, "value,ok" modes, etc.
|
||||
// - iter --- iterators from 'range' over map or string.
|
||||
// - bad --- a poison pill for locals that have gone out of scope.
|
||||
// - rtype -- the interpreter's concrete implementation of reflect.Type
|
||||
//
|
||||
// Note that nil is not on this list.
|
||||
//
|
||||
// Pay close attention to whether or not the dynamic type is a pointer.
|
||||
// The compiler cannot help you since value is an empty interface.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/tools/go/types"
|
||||
"golang.org/x/tools/go/types/typeutil"
|
||||
)
|
||||
|
||||
type value interface{}
|
||||
|
||||
type tuple []value
|
||||
|
||||
type array []value
|
||||
|
||||
type iface struct {
|
||||
t types.Type // never an "untyped" type
|
||||
v value
|
||||
}
|
||||
|
||||
type structure []value
|
||||
|
||||
// For map, array, *array, slice, string or channel.
|
||||
type iter interface {
|
||||
// next returns a Tuple (key, value, ok).
|
||||
// key and value are unaliased, e.g. copies of the sequence element.
|
||||
next() tuple
|
||||
}
|
||||
|
||||
type closure struct {
|
||||
Fn *ssa.Function
|
||||
Env []value
|
||||
}
|
||||
|
||||
type bad struct{}
|
||||
|
||||
type rtype struct {
|
||||
t types.Type
|
||||
}
|
||||
|
||||
// Hash functions and equivalence relation:
|
||||
|
||||
// hashString computes the FNV hash of s.
|
||||
func hashString(s string) int {
|
||||
var h uint32
|
||||
for i := 0; i < len(s); i++ {
|
||||
h ^= uint32(s[i])
|
||||
h *= 16777619
|
||||
}
|
||||
return int(h)
|
||||
}
|
||||
|
||||
var (
|
||||
mu sync.Mutex
|
||||
hasher = typeutil.MakeHasher()
|
||||
)
|
||||
|
||||
// hashType returns a hash for t such that
|
||||
// types.Identical(x, y) => hashType(x) == hashType(y).
|
||||
func hashType(t types.Type) int {
|
||||
mu.Lock()
|
||||
h := int(hasher.Hash(t))
|
||||
mu.Unlock()
|
||||
return h
|
||||
}
|
||||
|
||||
// usesBuiltinMap returns true if the built-in hash function and
|
||||
// equivalence relation for type t are consistent with those of the
|
||||
// interpreter's representation of type t. Such types are: all basic
|
||||
// types (bool, numbers, string), pointers and channels.
|
||||
//
|
||||
// usesBuiltinMap returns false for types that require a custom map
|
||||
// implementation: interfaces, arrays and structs.
|
||||
//
|
||||
// Panic ensues if t is an invalid map key type: function, map or slice.
|
||||
func usesBuiltinMap(t types.Type) bool {
|
||||
switch t := t.(type) {
|
||||
case *types.Basic, *types.Chan, *types.Pointer:
|
||||
return true
|
||||
case *types.Named:
|
||||
return usesBuiltinMap(t.Underlying())
|
||||
case *types.Interface, *types.Array, *types.Struct:
|
||||
return false
|
||||
}
|
||||
panic(fmt.Sprintf("invalid map key type: %T", t))
|
||||
}
|
||||
|
||||
func (x array) eq(t types.Type, _y interface{}) bool {
|
||||
y := _y.(array)
|
||||
tElt := t.Underlying().(*types.Array).Elem()
|
||||
for i, xi := range x {
|
||||
if !equals(tElt, xi, y[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (x array) hash(t types.Type) int {
|
||||
h := 0
|
||||
tElt := t.Underlying().(*types.Array).Elem()
|
||||
for _, xi := range x {
|
||||
h += hash(tElt, xi)
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
func (x structure) eq(t types.Type, _y interface{}) bool {
|
||||
y := _y.(structure)
|
||||
tStruct := t.Underlying().(*types.Struct)
|
||||
for i, n := 0, tStruct.NumFields(); i < n; i++ {
|
||||
if f := tStruct.Field(i); !f.Anonymous() {
|
||||
if !equals(f.Type(), x[i], y[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (x structure) hash(t types.Type) int {
|
||||
tStruct := t.Underlying().(*types.Struct)
|
||||
h := 0
|
||||
for i, n := 0, tStruct.NumFields(); i < n; i++ {
|
||||
if f := tStruct.Field(i); !f.Anonymous() {
|
||||
h += hash(f.Type(), x[i])
|
||||
}
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
// nil-tolerant variant of types.Identical.
|
||||
func sameType(x, y types.Type) bool {
|
||||
if x == nil {
|
||||
return y == nil
|
||||
}
|
||||
return y != nil && types.Identical(x, y)
|
||||
}
|
||||
|
||||
func (x iface) eq(t types.Type, _y interface{}) bool {
|
||||
y := _y.(iface)
|
||||
return sameType(x.t, y.t) && (x.t == nil || equals(x.t, x.v, y.v))
|
||||
}
|
||||
|
||||
func (x iface) hash(_ types.Type) int {
|
||||
return hashType(x.t)*8581 + hash(x.t, x.v)
|
||||
}
|
||||
|
||||
func (x rtype) hash(_ types.Type) int {
|
||||
return hashType(x.t)
|
||||
}
|
||||
|
||||
func (x rtype) eq(_ types.Type, y interface{}) bool {
|
||||
return types.Identical(x.t, y.(rtype).t)
|
||||
}
|
||||
|
||||
// equals returns true iff x and y are equal according to Go's
|
||||
// linguistic equivalence relation for type t.
|
||||
// In a well-typed program, the dynamic types of x and y are
|
||||
// guaranteed equal.
|
||||
func equals(t types.Type, x, y value) bool {
|
||||
switch x := x.(type) {
|
||||
case bool:
|
||||
return x == y.(bool)
|
||||
case int:
|
||||
return x == y.(int)
|
||||
case int8:
|
||||
return x == y.(int8)
|
||||
case int16:
|
||||
return x == y.(int16)
|
||||
case int32:
|
||||
return x == y.(int32)
|
||||
case int64:
|
||||
return x == y.(int64)
|
||||
case uint:
|
||||
return x == y.(uint)
|
||||
case uint8:
|
||||
return x == y.(uint8)
|
||||
case uint16:
|
||||
return x == y.(uint16)
|
||||
case uint32:
|
||||
return x == y.(uint32)
|
||||
case uint64:
|
||||
return x == y.(uint64)
|
||||
case uintptr:
|
||||
return x == y.(uintptr)
|
||||
case float32:
|
||||
return x == y.(float32)
|
||||
case float64:
|
||||
return x == y.(float64)
|
||||
case complex64:
|
||||
return x == y.(complex64)
|
||||
case complex128:
|
||||
return x == y.(complex128)
|
||||
case string:
|
||||
return x == y.(string)
|
||||
case *value:
|
||||
return x == y.(*value)
|
||||
case chan value:
|
||||
return x == y.(chan value)
|
||||
case structure:
|
||||
return x.eq(t, y)
|
||||
case array:
|
||||
return x.eq(t, y)
|
||||
case iface:
|
||||
return x.eq(t, y)
|
||||
case rtype:
|
||||
return x.eq(t, y)
|
||||
}
|
||||
|
||||
// Since map, func and slice don't support comparison, this
|
||||
// case is only reachable if one of x or y is literally nil
|
||||
// (handled in eqnil) or via interface{} values.
|
||||
panic(fmt.Sprintf("comparing uncomparable type %s", t))
|
||||
}
|
||||
|
||||
// Returns an integer hash of x such that equals(x, y) => hash(x) == hash(y).
|
||||
func hash(t types.Type, x value) int {
|
||||
switch x := x.(type) {
|
||||
case bool:
|
||||
if x {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
case int:
|
||||
return x
|
||||
case int8:
|
||||
return int(x)
|
||||
case int16:
|
||||
return int(x)
|
||||
case int32:
|
||||
return int(x)
|
||||
case int64:
|
||||
return int(x)
|
||||
case uint:
|
||||
return int(x)
|
||||
case uint8:
|
||||
return int(x)
|
||||
case uint16:
|
||||
return int(x)
|
||||
case uint32:
|
||||
return int(x)
|
||||
case uint64:
|
||||
return int(x)
|
||||
case uintptr:
|
||||
return int(x)
|
||||
case float32:
|
||||
return int(x)
|
||||
case float64:
|
||||
return int(x)
|
||||
case complex64:
|
||||
return int(real(x))
|
||||
case complex128:
|
||||
return int(real(x))
|
||||
case string:
|
||||
return hashString(x)
|
||||
case *value:
|
||||
return int(uintptr(unsafe.Pointer(x)))
|
||||
case chan value:
|
||||
return int(uintptr(reflect.ValueOf(x).Pointer()))
|
||||
case structure:
|
||||
return x.hash(t)
|
||||
case array:
|
||||
return x.hash(t)
|
||||
case iface:
|
||||
return x.hash(t)
|
||||
case rtype:
|
||||
return x.hash(t)
|
||||
}
|
||||
panic(fmt.Sprintf("%T is unhashable", x))
|
||||
}
|
||||
|
||||
// reflect.Value struct values don't have a fixed shape, since the
|
||||
// payload can be a scalar or an aggregate depending on the instance.
|
||||
// So store (and load) can't simply use recursion over the shape of the
|
||||
// rhs value, or the lhs, to copy the value; we need the static type
|
||||
// information. (We can't make reflect.Value a new basic data type
|
||||
// because its "structness" is exposed to Go programs.)
|
||||
|
||||
// load returns the value of type T in *addr.
|
||||
func load(T types.Type, addr *value) value {
|
||||
switch T := T.Underlying().(type) {
|
||||
case *types.Struct:
|
||||
v := (*addr).(structure)
|
||||
a := make(structure, len(v))
|
||||
for i := range a {
|
||||
a[i] = load(T.Field(i).Type(), &v[i])
|
||||
}
|
||||
return a
|
||||
case *types.Array:
|
||||
v := (*addr).(array)
|
||||
a := make(array, len(v))
|
||||
for i := range a {
|
||||
a[i] = load(T.Elem(), &v[i])
|
||||
}
|
||||
return a
|
||||
default:
|
||||
return *addr
|
||||
}
|
||||
}
|
||||
|
||||
// store stores value v of type T into *addr.
|
||||
func store(T types.Type, addr *value, v value) {
|
||||
switch T := T.Underlying().(type) {
|
||||
case *types.Struct:
|
||||
lhs := (*addr).(structure)
|
||||
rhs := v.(structure)
|
||||
for i := range lhs {
|
||||
store(T.Field(i).Type(), &lhs[i], rhs[i])
|
||||
}
|
||||
case *types.Array:
|
||||
lhs := (*addr).(array)
|
||||
rhs := v.(array)
|
||||
for i := range lhs {
|
||||
store(T.Elem(), &lhs[i], rhs[i])
|
||||
}
|
||||
default:
|
||||
*addr = v
|
||||
}
|
||||
}
|
||||
|
||||
// Prints in the style of built-in println.
|
||||
// (More or less; in gc println is actually a compiler intrinsic and
|
||||
// can distinguish println(1) from println(interface{}(1)).)
|
||||
func writeValue(buf *bytes.Buffer, v value) {
|
||||
switch v := v.(type) {
|
||||
case nil, bool, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr, float32, float64, complex64, complex128, string:
|
||||
fmt.Fprintf(buf, "%v", v)
|
||||
|
||||
case map[value]value:
|
||||
buf.WriteString("map[")
|
||||
sep := ""
|
||||
for k, e := range v {
|
||||
buf.WriteString(sep)
|
||||
sep = " "
|
||||
writeValue(buf, k)
|
||||
buf.WriteString(":")
|
||||
writeValue(buf, e)
|
||||
}
|
||||
buf.WriteString("]")
|
||||
|
||||
case *hashmap:
|
||||
buf.WriteString("map[")
|
||||
sep := " "
|
||||
for _, e := range v.table {
|
||||
for e != nil {
|
||||
buf.WriteString(sep)
|
||||
sep = " "
|
||||
writeValue(buf, e.key)
|
||||
buf.WriteString(":")
|
||||
writeValue(buf, e.value)
|
||||
e = e.next
|
||||
}
|
||||
}
|
||||
buf.WriteString("]")
|
||||
|
||||
case chan value:
|
||||
fmt.Fprintf(buf, "%v", v) // (an address)
|
||||
|
||||
case *value:
|
||||
if v == nil {
|
||||
buf.WriteString("<nil>")
|
||||
} else {
|
||||
fmt.Fprintf(buf, "%p", v)
|
||||
}
|
||||
|
||||
case iface:
|
||||
fmt.Fprintf(buf, "(%s, ", v.t)
|
||||
writeValue(buf, v.v)
|
||||
buf.WriteString(")")
|
||||
|
||||
case structure:
|
||||
buf.WriteString("{")
|
||||
for i, e := range v {
|
||||
if i > 0 {
|
||||
buf.WriteString(" ")
|
||||
}
|
||||
writeValue(buf, e)
|
||||
}
|
||||
buf.WriteString("}")
|
||||
|
||||
case array:
|
||||
buf.WriteString("[")
|
||||
for i, e := range v {
|
||||
if i > 0 {
|
||||
buf.WriteString(" ")
|
||||
}
|
||||
writeValue(buf, e)
|
||||
}
|
||||
buf.WriteString("]")
|
||||
|
||||
case []value:
|
||||
buf.WriteString("[")
|
||||
for i, e := range v {
|
||||
if i > 0 {
|
||||
buf.WriteString(" ")
|
||||
}
|
||||
writeValue(buf, e)
|
||||
}
|
||||
buf.WriteString("]")
|
||||
|
||||
case *ssa.Function, *ssa.Builtin, *closure:
|
||||
fmt.Fprintf(buf, "%p", v) // (an address)
|
||||
|
||||
case rtype:
|
||||
buf.WriteString(v.t.String())
|
||||
|
||||
case tuple:
|
||||
// Unreachable in well-formed Go programs
|
||||
buf.WriteString("(")
|
||||
for i, e := range v {
|
||||
if i > 0 {
|
||||
buf.WriteString(", ")
|
||||
}
|
||||
writeValue(buf, e)
|
||||
}
|
||||
buf.WriteString(")")
|
||||
|
||||
default:
|
||||
fmt.Fprintf(buf, "<%T>", v)
|
||||
}
|
||||
}
|
||||
|
||||
// Implements printing of Go values in the style of built-in println.
|
||||
func toString(v value) string {
|
||||
var b bytes.Buffer
|
||||
writeValue(&b, v)
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Iterators
|
||||
|
||||
type stringIter struct {
|
||||
*strings.Reader
|
||||
i int
|
||||
}
|
||||
|
||||
func (it *stringIter) next() tuple {
|
||||
okv := make(tuple, 3)
|
||||
ch, n, err := it.ReadRune()
|
||||
ok := err != io.EOF
|
||||
okv[0] = ok
|
||||
if ok {
|
||||
okv[1] = it.i
|
||||
okv[2] = ch
|
||||
}
|
||||
it.i += n
|
||||
return okv
|
||||
}
|
||||
|
||||
type mapIter chan [2]value
|
||||
|
||||
func (it mapIter) next() tuple {
|
||||
kv, ok := <-it
|
||||
return tuple{ok, kv[0], kv[1]}
|
||||
}
|
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
package ssa
|
||||
|
||||
// This file defines the lifting pass which tries to "lift" Alloc
|
||||
|
601
go/ssa/lift14.go
Normal file
601
go/ssa/lift14.go
Normal file
@ -0,0 +1,601 @@
|
||||
// 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.
|
||||
|
||||
// +build !go1.5
|
||||
|
||||
package ssa
|
||||
|
||||
// This file defines the lifting pass which tries to "lift" Alloc
|
||||
// cells (new/local variables) into SSA registers, replacing loads
|
||||
// with the dominating stored value, eliminating loads and stores, and
|
||||
// inserting φ-nodes as needed.
|
||||
|
||||
// Cited papers and resources:
|
||||
//
|
||||
// Ron Cytron et al. 1991. Efficiently computing SSA form...
|
||||
// http://doi.acm.org/10.1145/115372.115320
|
||||
//
|
||||
// Cooper, Harvey, Kennedy. 2001. A Simple, Fast Dominance Algorithm.
|
||||
// Software Practice and Experience 2001, 4:1-10.
|
||||
// http://www.hipersoft.rice.edu/grads/publications/dom14.pdf
|
||||
//
|
||||
// Daniel Berlin, llvmdev mailing list, 2012.
|
||||
// http://lists.cs.uiuc.edu/pipermail/llvmdev/2012-January/046638.html
|
||||
// (Be sure to expand the whole thread.)
|
||||
|
||||
// TODO(adonovan): opt: there are many optimizations worth evaluating, and
|
||||
// the conventional wisdom for SSA construction is that a simple
|
||||
// algorithm well engineered often beats those of better asymptotic
|
||||
// complexity on all but the most egregious inputs.
|
||||
//
|
||||
// Danny Berlin suggests that the Cooper et al. algorithm for
|
||||
// computing the dominance frontier is superior to Cytron et al.
|
||||
// Furthermore he recommends that rather than computing the DF for the
|
||||
// whole function then renaming all alloc cells, it may be cheaper to
|
||||
// compute the DF for each alloc cell separately and throw it away.
|
||||
//
|
||||
// Consider exploiting liveness information to avoid creating dead
|
||||
// φ-nodes which we then immediately remove.
|
||||
//
|
||||
// Integrate lifting with scalar replacement of aggregates (SRA) since
|
||||
// the two are synergistic.
|
||||
//
|
||||
// Also see many other "TODO: opt" suggestions in the code.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/token"
|
||||
"math/big"
|
||||
"os"
|
||||
|
||||
"golang.org/x/tools/go/types"
|
||||
)
|
||||
|
||||
// If true, perform sanity checking and show diagnostic information at
|
||||
// each step of lifting. Very verbose.
|
||||
const debugLifting = false
|
||||
|
||||
// domFrontier maps each block to the set of blocks in its dominance
|
||||
// frontier. The outer slice is conceptually a map keyed by
|
||||
// Block.Index. The inner slice is conceptually a set, possibly
|
||||
// containing duplicates.
|
||||
//
|
||||
// TODO(adonovan): opt: measure impact of dups; consider a packed bit
|
||||
// representation, e.g. big.Int, and bitwise parallel operations for
|
||||
// the union step in the Children loop.
|
||||
//
|
||||
// domFrontier's methods mutate the slice's elements but not its
|
||||
// length, so their receivers needn't be pointers.
|
||||
//
|
||||
type domFrontier [][]*BasicBlock
|
||||
|
||||
func (df domFrontier) add(u, v *BasicBlock) {
|
||||
p := &df[u.Index]
|
||||
*p = append(*p, v)
|
||||
}
|
||||
|
||||
// build builds the dominance frontier df for the dominator (sub)tree
|
||||
// rooted at u, using the Cytron et al. algorithm.
|
||||
//
|
||||
// TODO(adonovan): opt: consider Berlin approach, computing pruned SSA
|
||||
// by pruning the entire IDF computation, rather than merely pruning
|
||||
// the DF -> IDF step.
|
||||
func (df domFrontier) build(u *BasicBlock) {
|
||||
// Encounter each node u in postorder of dom tree.
|
||||
for _, child := range u.dom.children {
|
||||
df.build(child)
|
||||
}
|
||||
for _, vb := range u.Succs {
|
||||
if v := vb.dom; v.idom != u {
|
||||
df.add(u, vb)
|
||||
}
|
||||
}
|
||||
for _, w := range u.dom.children {
|
||||
for _, vb := range df[w.Index] {
|
||||
// TODO(adonovan): opt: use word-parallel bitwise union.
|
||||
if v := vb.dom; v.idom != u {
|
||||
df.add(u, vb)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func buildDomFrontier(fn *Function) domFrontier {
|
||||
df := make(domFrontier, len(fn.Blocks))
|
||||
df.build(fn.Blocks[0])
|
||||
if fn.Recover != nil {
|
||||
df.build(fn.Recover)
|
||||
}
|
||||
return df
|
||||
}
|
||||
|
||||
func removeInstr(refs []Instruction, instr Instruction) []Instruction {
|
||||
i := 0
|
||||
for _, ref := range refs {
|
||||
if ref == instr {
|
||||
continue
|
||||
}
|
||||
refs[i] = ref
|
||||
i++
|
||||
}
|
||||
for j := i; j != len(refs); j++ {
|
||||
refs[j] = nil // aid GC
|
||||
}
|
||||
return refs[:i]
|
||||
}
|
||||
|
||||
// lift attempts to replace local and new Allocs accessed only with
|
||||
// load/store by SSA registers, inserting φ-nodes where necessary.
|
||||
// The result is a program in classical pruned SSA form.
|
||||
//
|
||||
// Preconditions:
|
||||
// - fn has no dead blocks (blockopt has run).
|
||||
// - Def/use info (Operands and Referrers) is up-to-date.
|
||||
// - The dominator tree is up-to-date.
|
||||
//
|
||||
func lift(fn *Function) {
|
||||
// TODO(adonovan): opt: lots of little optimizations may be
|
||||
// worthwhile here, especially if they cause us to avoid
|
||||
// buildDomFrontier. For example:
|
||||
//
|
||||
// - Alloc never loaded? Eliminate.
|
||||
// - Alloc never stored? Replace all loads with a zero constant.
|
||||
// - Alloc stored once? Replace loads with dominating store;
|
||||
// don't forget that an Alloc is itself an effective store
|
||||
// of zero.
|
||||
// - Alloc used only within a single block?
|
||||
// Use degenerate algorithm avoiding φ-nodes.
|
||||
// - Consider synergy with scalar replacement of aggregates (SRA).
|
||||
// e.g. *(&x.f) where x is an Alloc.
|
||||
// Perhaps we'd get better results if we generated this as x.f
|
||||
// i.e. Field(x, .f) instead of Load(FieldIndex(x, .f)).
|
||||
// Unclear.
|
||||
//
|
||||
// But we will start with the simplest correct code.
|
||||
df := buildDomFrontier(fn)
|
||||
|
||||
if debugLifting {
|
||||
title := false
|
||||
for i, blocks := range df {
|
||||
if blocks != nil {
|
||||
if !title {
|
||||
fmt.Fprintf(os.Stderr, "Dominance frontier of %s:\n", fn)
|
||||
title = true
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "\t%s: %s\n", fn.Blocks[i], blocks)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
newPhis := make(newPhiMap)
|
||||
|
||||
// During this pass we will replace some BasicBlock.Instrs
|
||||
// (allocs, loads and stores) with nil, keeping a count in
|
||||
// BasicBlock.gaps. At the end we will reset Instrs to the
|
||||
// concatenation of all non-dead newPhis and non-nil Instrs
|
||||
// for the block, reusing the original array if space permits.
|
||||
|
||||
// While we're here, we also eliminate 'rundefers'
|
||||
// instructions in functions that contain no 'defer'
|
||||
// instructions.
|
||||
usesDefer := false
|
||||
|
||||
// Determine which allocs we can lift and number them densely.
|
||||
// The renaming phase uses this numbering for compact maps.
|
||||
numAllocs := 0
|
||||
for _, b := range fn.Blocks {
|
||||
b.gaps = 0
|
||||
b.rundefers = 0
|
||||
for _, instr := range b.Instrs {
|
||||
switch instr := instr.(type) {
|
||||
case *Alloc:
|
||||
index := -1
|
||||
if liftAlloc(df, instr, newPhis) {
|
||||
index = numAllocs
|
||||
numAllocs++
|
||||
}
|
||||
instr.index = index
|
||||
case *Defer:
|
||||
usesDefer = true
|
||||
case *RunDefers:
|
||||
b.rundefers++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// renaming maps an alloc (keyed by index) to its replacement
|
||||
// value. Initially the renaming contains nil, signifying the
|
||||
// zero constant of the appropriate type; we construct the
|
||||
// Const lazily at most once on each path through the domtree.
|
||||
// TODO(adonovan): opt: cache per-function not per subtree.
|
||||
renaming := make([]Value, numAllocs)
|
||||
|
||||
// Renaming.
|
||||
rename(fn.Blocks[0], renaming, newPhis)
|
||||
|
||||
// Eliminate dead new phis, then prepend the live ones to each block.
|
||||
for _, b := range fn.Blocks {
|
||||
|
||||
// Compress the newPhis slice to eliminate unused phis.
|
||||
// TODO(adonovan): opt: compute liveness to avoid
|
||||
// placing phis in blocks for which the alloc cell is
|
||||
// not live.
|
||||
nps := newPhis[b]
|
||||
j := 0
|
||||
for _, np := range nps {
|
||||
if !phiIsLive(np.phi) {
|
||||
// discard it, first removing it from referrers
|
||||
for _, newval := range np.phi.Edges {
|
||||
if refs := newval.Referrers(); refs != nil {
|
||||
*refs = removeInstr(*refs, np.phi)
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
nps[j] = np
|
||||
j++
|
||||
}
|
||||
nps = nps[:j]
|
||||
|
||||
rundefersToKill := b.rundefers
|
||||
if usesDefer {
|
||||
rundefersToKill = 0
|
||||
}
|
||||
|
||||
if j+b.gaps+rundefersToKill == 0 {
|
||||
continue // fast path: no new phis or gaps
|
||||
}
|
||||
|
||||
// Compact nps + non-nil Instrs into a new slice.
|
||||
// TODO(adonovan): opt: compact in situ if there is
|
||||
// sufficient space or slack in the slice.
|
||||
dst := make([]Instruction, len(b.Instrs)+j-b.gaps-rundefersToKill)
|
||||
for i, np := range nps {
|
||||
dst[i] = np.phi
|
||||
}
|
||||
for _, instr := range b.Instrs {
|
||||
if instr == nil {
|
||||
continue
|
||||
}
|
||||
if !usesDefer {
|
||||
if _, ok := instr.(*RunDefers); ok {
|
||||
continue
|
||||
}
|
||||
}
|
||||
dst[j] = instr
|
||||
j++
|
||||
}
|
||||
for i, np := range nps {
|
||||
dst[i] = np.phi
|
||||
}
|
||||
b.Instrs = dst
|
||||
}
|
||||
|
||||
// Remove any fn.Locals that were lifted.
|
||||
j := 0
|
||||
for _, l := range fn.Locals {
|
||||
if l.index < 0 {
|
||||
fn.Locals[j] = l
|
||||
j++
|
||||
}
|
||||
}
|
||||
// Nil out fn.Locals[j:] to aid GC.
|
||||
for i := j; i < len(fn.Locals); i++ {
|
||||
fn.Locals[i] = nil
|
||||
}
|
||||
fn.Locals = fn.Locals[:j]
|
||||
}
|
||||
|
||||
func phiIsLive(phi *Phi) bool {
|
||||
for _, instr := range *phi.Referrers() {
|
||||
if instr == phi {
|
||||
continue // self-refs don't count
|
||||
}
|
||||
if _, ok := instr.(*DebugRef); ok {
|
||||
continue // debug refs don't count
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type blockSet struct{ big.Int } // (inherit methods from Int)
|
||||
|
||||
// add adds b to the set and returns true if the set changed.
|
||||
func (s *blockSet) add(b *BasicBlock) bool {
|
||||
i := b.Index
|
||||
if s.Bit(i) != 0 {
|
||||
return false
|
||||
}
|
||||
s.SetBit(&s.Int, i, 1)
|
||||
return true
|
||||
}
|
||||
|
||||
// take removes an arbitrary element from a set s and
|
||||
// returns its index, or returns -1 if empty.
|
||||
func (s *blockSet) take() int {
|
||||
l := s.BitLen()
|
||||
for i := 0; i < l; i++ {
|
||||
if s.Bit(i) == 1 {
|
||||
s.SetBit(&s.Int, i, 0)
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// newPhi is a pair of a newly introduced φ-node and the lifted Alloc
|
||||
// it replaces.
|
||||
type newPhi struct {
|
||||
phi *Phi
|
||||
alloc *Alloc
|
||||
}
|
||||
|
||||
// newPhiMap records for each basic block, the set of newPhis that
|
||||
// must be prepended to the block.
|
||||
type newPhiMap map[*BasicBlock][]newPhi
|
||||
|
||||
// liftAlloc determines whether alloc can be lifted into registers,
|
||||
// and if so, it populates newPhis with all the φ-nodes it may require
|
||||
// and returns true.
|
||||
//
|
||||
func liftAlloc(df domFrontier, alloc *Alloc, newPhis newPhiMap) bool {
|
||||
// Don't lift aggregates into registers, because we don't have
|
||||
// a way to express their zero-constants.
|
||||
switch deref(alloc.Type()).Underlying().(type) {
|
||||
case *types.Array, *types.Struct:
|
||||
return false
|
||||
}
|
||||
|
||||
// Don't lift named return values in functions that defer
|
||||
// calls that may recover from panic.
|
||||
if fn := alloc.Parent(); fn.Recover != nil {
|
||||
for _, nr := range fn.namedResults {
|
||||
if nr == alloc {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compute defblocks, the set of blocks containing a
|
||||
// definition of the alloc cell.
|
||||
var defblocks blockSet
|
||||
for _, instr := range *alloc.Referrers() {
|
||||
// Bail out if we discover the alloc is not liftable;
|
||||
// the only operations permitted to use the alloc are
|
||||
// loads/stores into the cell, and DebugRef.
|
||||
switch instr := instr.(type) {
|
||||
case *Store:
|
||||
if instr.Val == alloc {
|
||||
return false // address used as value
|
||||
}
|
||||
if instr.Addr != alloc {
|
||||
panic("Alloc.Referrers is inconsistent")
|
||||
}
|
||||
defblocks.add(instr.Block())
|
||||
case *UnOp:
|
||||
if instr.Op != token.MUL {
|
||||
return false // not a load
|
||||
}
|
||||
if instr.X != alloc {
|
||||
panic("Alloc.Referrers is inconsistent")
|
||||
}
|
||||
case *DebugRef:
|
||||
// ok
|
||||
default:
|
||||
return false // some other instruction
|
||||
}
|
||||
}
|
||||
// The Alloc itself counts as a (zero) definition of the cell.
|
||||
defblocks.add(alloc.Block())
|
||||
|
||||
if debugLifting {
|
||||
fmt.Fprintln(os.Stderr, "\tlifting ", alloc, alloc.Name())
|
||||
}
|
||||
|
||||
fn := alloc.Parent()
|
||||
|
||||
// Φ-insertion.
|
||||
//
|
||||
// What follows is the body of the main loop of the insert-φ
|
||||
// function described by Cytron et al, but instead of using
|
||||
// counter tricks, we just reset the 'hasAlready' and 'work'
|
||||
// sets each iteration. These are bitmaps so it's pretty cheap.
|
||||
//
|
||||
// TODO(adonovan): opt: recycle slice storage for W,
|
||||
// hasAlready, defBlocks across liftAlloc calls.
|
||||
var hasAlready blockSet
|
||||
|
||||
// Initialize W and work to defblocks.
|
||||
var work blockSet = defblocks // blocks seen
|
||||
var W blockSet // blocks to do
|
||||
W.Set(&defblocks.Int)
|
||||
|
||||
// Traverse iterated dominance frontier, inserting φ-nodes.
|
||||
for i := W.take(); i != -1; i = W.take() {
|
||||
u := fn.Blocks[i]
|
||||
for _, v := range df[u.Index] {
|
||||
if hasAlready.add(v) {
|
||||
// Create φ-node.
|
||||
// It will be prepended to v.Instrs later, if needed.
|
||||
phi := &Phi{
|
||||
Edges: make([]Value, len(v.Preds)),
|
||||
Comment: alloc.Comment,
|
||||
}
|
||||
phi.pos = alloc.Pos()
|
||||
phi.setType(deref(alloc.Type()))
|
||||
phi.block = v
|
||||
if debugLifting {
|
||||
fmt.Fprintf(os.Stderr, "\tplace %s = %s at block %s\n", phi.Name(), phi, v)
|
||||
}
|
||||
newPhis[v] = append(newPhis[v], newPhi{phi, alloc})
|
||||
|
||||
if work.add(v) {
|
||||
W.add(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// replaceAll replaces all intraprocedural uses of x with y,
|
||||
// updating x.Referrers and y.Referrers.
|
||||
// Precondition: x.Referrers() != nil, i.e. x must be local to some function.
|
||||
//
|
||||
func replaceAll(x, y Value) {
|
||||
var rands []*Value
|
||||
pxrefs := x.Referrers()
|
||||
pyrefs := y.Referrers()
|
||||
for _, instr := range *pxrefs {
|
||||
rands = instr.Operands(rands[:0]) // recycle storage
|
||||
for _, rand := range rands {
|
||||
if *rand != nil {
|
||||
if *rand == x {
|
||||
*rand = y
|
||||
}
|
||||
}
|
||||
}
|
||||
if pyrefs != nil {
|
||||
*pyrefs = append(*pyrefs, instr) // dups ok
|
||||
}
|
||||
}
|
||||
*pxrefs = nil // x is now unreferenced
|
||||
}
|
||||
|
||||
// renamed returns the value to which alloc is being renamed,
|
||||
// constructing it lazily if it's the implicit zero initialization.
|
||||
//
|
||||
func renamed(renaming []Value, alloc *Alloc) Value {
|
||||
v := renaming[alloc.index]
|
||||
if v == nil {
|
||||
v = zeroConst(deref(alloc.Type()))
|
||||
renaming[alloc.index] = v
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// rename implements the (Cytron et al) SSA renaming algorithm, a
|
||||
// preorder traversal of the dominator tree replacing all loads of
|
||||
// Alloc cells with the value stored to that cell by the dominating
|
||||
// store instruction. For lifting, we need only consider loads,
|
||||
// stores and φ-nodes.
|
||||
//
|
||||
// renaming is a map from *Alloc (keyed by index number) to its
|
||||
// dominating stored value; newPhis[x] is the set of new φ-nodes to be
|
||||
// prepended to block x.
|
||||
//
|
||||
func rename(u *BasicBlock, renaming []Value, newPhis newPhiMap) {
|
||||
// Each φ-node becomes the new name for its associated Alloc.
|
||||
for _, np := range newPhis[u] {
|
||||
phi := np.phi
|
||||
alloc := np.alloc
|
||||
renaming[alloc.index] = phi
|
||||
}
|
||||
|
||||
// Rename loads and stores of allocs.
|
||||
for i, instr := range u.Instrs {
|
||||
switch instr := instr.(type) {
|
||||
case *Alloc:
|
||||
if instr.index >= 0 { // store of zero to Alloc cell
|
||||
// Replace dominated loads by the zero value.
|
||||
renaming[instr.index] = nil
|
||||
if debugLifting {
|
||||
fmt.Fprintf(os.Stderr, "\tkill alloc %s\n", instr)
|
||||
}
|
||||
// Delete the Alloc.
|
||||
u.Instrs[i] = nil
|
||||
u.gaps++
|
||||
}
|
||||
|
||||
case *Store:
|
||||
if alloc, ok := instr.Addr.(*Alloc); ok && alloc.index >= 0 { // store to Alloc cell
|
||||
// Replace dominated loads by the stored value.
|
||||
renaming[alloc.index] = instr.Val
|
||||
if debugLifting {
|
||||
fmt.Fprintf(os.Stderr, "\tkill store %s; new value: %s\n",
|
||||
instr, instr.Val.Name())
|
||||
}
|
||||
// Remove the store from the referrer list of the stored value.
|
||||
if refs := instr.Val.Referrers(); refs != nil {
|
||||
*refs = removeInstr(*refs, instr)
|
||||
}
|
||||
// Delete the Store.
|
||||
u.Instrs[i] = nil
|
||||
u.gaps++
|
||||
}
|
||||
|
||||
case *UnOp:
|
||||
if instr.Op == token.MUL {
|
||||
if alloc, ok := instr.X.(*Alloc); ok && alloc.index >= 0 { // load of Alloc cell
|
||||
newval := renamed(renaming, alloc)
|
||||
if debugLifting {
|
||||
fmt.Fprintf(os.Stderr, "\tupdate load %s = %s with %s\n",
|
||||
instr.Name(), instr, newval.Name())
|
||||
}
|
||||
// Replace all references to
|
||||
// the loaded value by the
|
||||
// dominating stored value.
|
||||
replaceAll(instr, newval)
|
||||
// Delete the Load.
|
||||
u.Instrs[i] = nil
|
||||
u.gaps++
|
||||
}
|
||||
}
|
||||
|
||||
case *DebugRef:
|
||||
if alloc, ok := instr.X.(*Alloc); ok && alloc.index >= 0 { // ref of Alloc cell
|
||||
if instr.IsAddr {
|
||||
instr.X = renamed(renaming, alloc)
|
||||
instr.IsAddr = false
|
||||
|
||||
// Add DebugRef to instr.X's referrers.
|
||||
if refs := instr.X.Referrers(); refs != nil {
|
||||
*refs = append(*refs, instr)
|
||||
}
|
||||
} else {
|
||||
// A source expression denotes the address
|
||||
// of an Alloc that was optimized away.
|
||||
instr.X = nil
|
||||
|
||||
// Delete the DebugRef.
|
||||
u.Instrs[i] = nil
|
||||
u.gaps++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For each φ-node in a CFG successor, rename the edge.
|
||||
for _, v := range u.Succs {
|
||||
phis := newPhis[v]
|
||||
if len(phis) == 0 {
|
||||
continue
|
||||
}
|
||||
i := v.predIndex(u)
|
||||
for _, np := range phis {
|
||||
phi := np.phi
|
||||
alloc := np.alloc
|
||||
newval := renamed(renaming, alloc)
|
||||
if debugLifting {
|
||||
fmt.Fprintf(os.Stderr, "\tsetphi %s edge %s -> %s (#%d) (alloc=%s) := %s\n",
|
||||
phi.Name(), u, v, i, alloc.Name(), newval.Name())
|
||||
}
|
||||
phi.Edges[i] = newval
|
||||
if prefs := newval.Referrers(); prefs != nil {
|
||||
*prefs = append(*prefs, phi)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Continue depth-first recursion over domtree, pushing a
|
||||
// fresh copy of the renaming map for each subtree.
|
||||
for _, v := range u.dom.children {
|
||||
// TODO(adonovan): opt: avoid copy on final iteration; use destructive update.
|
||||
r := make([]Value, len(renaming))
|
||||
copy(r, renaming)
|
||||
rename(v, r, newPhis)
|
||||
}
|
||||
}
|
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
package ssa
|
||||
|
||||
// lvalues are the union of addressable expressions and map-index
|
||||
|
123
go/ssa/lvalue14.go
Normal file
123
go/ssa/lvalue14.go
Normal file
@ -0,0 +1,123 @@
|
||||
// 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.
|
||||
|
||||
// +build !go1.5
|
||||
|
||||
package ssa
|
||||
|
||||
// lvalues are the union of addressable expressions and map-index
|
||||
// expressions.
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
|
||||
"golang.org/x/tools/go/types"
|
||||
)
|
||||
|
||||
// An lvalue represents an assignable location that may appear on the
|
||||
// left-hand side of an assignment. This is a generalization of a
|
||||
// pointer to permit updates to elements of maps.
|
||||
//
|
||||
type lvalue interface {
|
||||
store(fn *Function, v Value) // stores v into the location
|
||||
load(fn *Function) Value // loads the contents of the location
|
||||
address(fn *Function) Value // address of the location
|
||||
typ() types.Type // returns the type of the location
|
||||
}
|
||||
|
||||
// An address is an lvalue represented by a true pointer.
|
||||
type address struct {
|
||||
addr Value
|
||||
pos token.Pos // source position
|
||||
expr ast.Expr // source syntax of the value (not address) [debug mode]
|
||||
}
|
||||
|
||||
func (a *address) load(fn *Function) Value {
|
||||
load := emitLoad(fn, a.addr)
|
||||
load.pos = a.pos
|
||||
return load
|
||||
}
|
||||
|
||||
func (a *address) store(fn *Function, v Value) {
|
||||
store := emitStore(fn, a.addr, v, a.pos)
|
||||
if a.expr != nil {
|
||||
// store.Val is v, converted for assignability.
|
||||
emitDebugRef(fn, a.expr, store.Val, false)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *address) address(fn *Function) Value {
|
||||
if a.expr != nil {
|
||||
emitDebugRef(fn, a.expr, a.addr, true)
|
||||
}
|
||||
return a.addr
|
||||
}
|
||||
|
||||
func (a *address) typ() types.Type {
|
||||
return deref(a.addr.Type())
|
||||
}
|
||||
|
||||
// An element is an lvalue represented by m[k], the location of an
|
||||
// element of a map or string. These locations are not addressable
|
||||
// since pointers cannot be formed from them, but they do support
|
||||
// load(), and in the case of maps, store().
|
||||
//
|
||||
type element struct {
|
||||
m, k Value // map or string
|
||||
t types.Type // map element type or string byte type
|
||||
pos token.Pos // source position of colon ({k:v}) or lbrack (m[k]=v)
|
||||
}
|
||||
|
||||
func (e *element) load(fn *Function) Value {
|
||||
l := &Lookup{
|
||||
X: e.m,
|
||||
Index: e.k,
|
||||
}
|
||||
l.setPos(e.pos)
|
||||
l.setType(e.t)
|
||||
return fn.emit(l)
|
||||
}
|
||||
|
||||
func (e *element) store(fn *Function, v Value) {
|
||||
up := &MapUpdate{
|
||||
Map: e.m,
|
||||
Key: e.k,
|
||||
Value: emitConv(fn, v, e.t),
|
||||
}
|
||||
up.pos = e.pos
|
||||
fn.emit(up)
|
||||
}
|
||||
|
||||
func (e *element) address(fn *Function) Value {
|
||||
panic("map/string elements are not addressable")
|
||||
}
|
||||
|
||||
func (e *element) typ() types.Type {
|
||||
return e.t
|
||||
}
|
||||
|
||||
// A blank is a dummy variable whose name is "_".
|
||||
// It is not reified: loads are illegal and stores are ignored.
|
||||
//
|
||||
type blank struct{}
|
||||
|
||||
func (bl blank) load(fn *Function) Value {
|
||||
panic("blank.load is illegal")
|
||||
}
|
||||
|
||||
func (bl blank) store(fn *Function, v Value) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
func (bl blank) address(fn *Function) Value {
|
||||
panic("blank var is not addressable")
|
||||
}
|
||||
|
||||
func (bl blank) typ() types.Type {
|
||||
// This should be the type of the blank Ident; the typechecker
|
||||
// doesn't provide this yet, but fortunately, we don't need it
|
||||
// yet either.
|
||||
panic("blank.typ is unimplemented")
|
||||
}
|
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
package ssa
|
||||
|
||||
// This file defines utilities for population of method sets.
|
||||
|
242
go/ssa/methods14.go
Normal file
242
go/ssa/methods14.go
Normal file
@ -0,0 +1,242 @@
|
||||
// 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.
|
||||
|
||||
// +build !go1.5
|
||||
|
||||
package ssa
|
||||
|
||||
// This file defines utilities for population of method sets.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/tools/go/types"
|
||||
)
|
||||
|
||||
// MethodValue returns the Function implementing method sel, building
|
||||
// wrapper methods on demand. It returns nil if sel denotes an
|
||||
// abstract (interface) method.
|
||||
//
|
||||
// Precondition: sel.Kind() == MethodVal.
|
||||
//
|
||||
// Thread-safe.
|
||||
//
|
||||
// EXCLUSIVE_LOCKS_ACQUIRED(prog.methodsMu)
|
||||
//
|
||||
func (prog *Program) MethodValue(sel *types.Selection) *Function {
|
||||
if sel.Kind() != types.MethodVal {
|
||||
panic(fmt.Sprintf("Method(%s) kind != MethodVal", sel))
|
||||
}
|
||||
T := sel.Recv()
|
||||
if isInterface(T) {
|
||||
return nil // abstract method
|
||||
}
|
||||
if prog.mode&LogSource != 0 {
|
||||
defer logStack("Method %s %v", T, sel)()
|
||||
}
|
||||
|
||||
prog.methodsMu.Lock()
|
||||
defer prog.methodsMu.Unlock()
|
||||
|
||||
return prog.addMethod(prog.createMethodSet(T), sel)
|
||||
}
|
||||
|
||||
// LookupMethod returns the implementation of the method of type T
|
||||
// identified by (pkg, name). It returns nil if the method exists but
|
||||
// is abstract, and panics if T has no such method.
|
||||
//
|
||||
func (prog *Program) LookupMethod(T types.Type, pkg *types.Package, name string) *Function {
|
||||
sel := prog.MethodSets.MethodSet(T).Lookup(pkg, name)
|
||||
if sel == nil {
|
||||
panic(fmt.Sprintf("%s has no method %s", T, types.Id(pkg, name)))
|
||||
}
|
||||
return prog.MethodValue(sel)
|
||||
}
|
||||
|
||||
// methodSet contains the (concrete) methods of a non-interface type.
|
||||
type methodSet struct {
|
||||
mapping map[string]*Function // populated lazily
|
||||
complete bool // mapping contains all methods
|
||||
}
|
||||
|
||||
// Precondition: !isInterface(T).
|
||||
// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu)
|
||||
func (prog *Program) createMethodSet(T types.Type) *methodSet {
|
||||
mset, ok := prog.methodSets.At(T).(*methodSet)
|
||||
if !ok {
|
||||
mset = &methodSet{mapping: make(map[string]*Function)}
|
||||
prog.methodSets.Set(T, mset)
|
||||
}
|
||||
return mset
|
||||
}
|
||||
|
||||
// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu)
|
||||
func (prog *Program) addMethod(mset *methodSet, sel *types.Selection) *Function {
|
||||
if sel.Kind() == types.MethodExpr {
|
||||
panic(sel)
|
||||
}
|
||||
id := sel.Obj().Id()
|
||||
fn := mset.mapping[id]
|
||||
if fn == nil {
|
||||
obj := sel.Obj().(*types.Func)
|
||||
|
||||
needsPromotion := len(sel.Index()) > 1
|
||||
needsIndirection := !isPointer(recvType(obj)) && isPointer(sel.Recv())
|
||||
if needsPromotion || needsIndirection {
|
||||
fn = makeWrapper(prog, sel)
|
||||
} else {
|
||||
fn = prog.declaredFunc(obj)
|
||||
}
|
||||
if fn.Signature.Recv() == nil {
|
||||
panic(fn) // missing receiver
|
||||
}
|
||||
mset.mapping[id] = fn
|
||||
}
|
||||
return fn
|
||||
}
|
||||
|
||||
// RuntimeTypes returns a new unordered slice containing all
|
||||
// concrete types in the program for which a complete (non-empty)
|
||||
// method set is required at run-time.
|
||||
//
|
||||
// Thread-safe.
|
||||
//
|
||||
// EXCLUSIVE_LOCKS_ACQUIRED(prog.methodsMu)
|
||||
//
|
||||
func (prog *Program) RuntimeTypes() []types.Type {
|
||||
prog.methodsMu.Lock()
|
||||
defer prog.methodsMu.Unlock()
|
||||
|
||||
var res []types.Type
|
||||
prog.methodSets.Iterate(func(T types.Type, v interface{}) {
|
||||
if v.(*methodSet).complete {
|
||||
res = append(res, T)
|
||||
}
|
||||
})
|
||||
return res
|
||||
}
|
||||
|
||||
// declaredFunc returns the concrete function/method denoted by obj.
|
||||
// Panic ensues if there is none.
|
||||
//
|
||||
func (prog *Program) declaredFunc(obj *types.Func) *Function {
|
||||
if v := prog.packageLevelValue(obj); v != nil {
|
||||
return v.(*Function)
|
||||
}
|
||||
panic("no concrete method: " + obj.String())
|
||||
}
|
||||
|
||||
// needMethodsOf ensures that runtime type information (including the
|
||||
// complete method set) is available for the specified type T and all
|
||||
// its subcomponents.
|
||||
//
|
||||
// needMethodsOf must be called for at least every type that is an
|
||||
// operand of some MakeInterface instruction, and for the type of
|
||||
// every exported package member.
|
||||
//
|
||||
// Precondition: T is not a method signature (*Signature with Recv()!=nil).
|
||||
//
|
||||
// Thread-safe. (Called via emitConv from multiple builder goroutines.)
|
||||
//
|
||||
// TODO(adonovan): make this faster. It accounts for 20% of SSA build time.
|
||||
//
|
||||
// EXCLUSIVE_LOCKS_ACQUIRED(prog.methodsMu)
|
||||
//
|
||||
func (prog *Program) needMethodsOf(T types.Type) {
|
||||
prog.methodsMu.Lock()
|
||||
prog.needMethods(T, false)
|
||||
prog.methodsMu.Unlock()
|
||||
}
|
||||
|
||||
// Precondition: T is not a method signature (*Signature with Recv()!=nil).
|
||||
// Recursive case: skip => don't create methods for T.
|
||||
//
|
||||
// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu)
|
||||
//
|
||||
func (prog *Program) needMethods(T types.Type, skip bool) {
|
||||
// Each package maintains its own set of types it has visited.
|
||||
if prevSkip, ok := prog.runtimeTypes.At(T).(bool); ok {
|
||||
// needMethods(T) was previously called
|
||||
if !prevSkip || skip {
|
||||
return // already seen, with same or false 'skip' value
|
||||
}
|
||||
}
|
||||
prog.runtimeTypes.Set(T, skip)
|
||||
|
||||
tmset := prog.MethodSets.MethodSet(T)
|
||||
|
||||
if !skip && !isInterface(T) && tmset.Len() > 0 {
|
||||
// Create methods of T.
|
||||
mset := prog.createMethodSet(T)
|
||||
if !mset.complete {
|
||||
mset.complete = true
|
||||
n := tmset.Len()
|
||||
for i := 0; i < n; i++ {
|
||||
prog.addMethod(mset, tmset.At(i))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Recursion over signatures of each method.
|
||||
for i := 0; i < tmset.Len(); i++ {
|
||||
sig := tmset.At(i).Type().(*types.Signature)
|
||||
prog.needMethods(sig.Params(), false)
|
||||
prog.needMethods(sig.Results(), false)
|
||||
}
|
||||
|
||||
switch t := T.(type) {
|
||||
case *types.Basic:
|
||||
// nop
|
||||
|
||||
case *types.Interface:
|
||||
// nop---handled by recursion over method set.
|
||||
|
||||
case *types.Pointer:
|
||||
prog.needMethods(t.Elem(), false)
|
||||
|
||||
case *types.Slice:
|
||||
prog.needMethods(t.Elem(), false)
|
||||
|
||||
case *types.Chan:
|
||||
prog.needMethods(t.Elem(), false)
|
||||
|
||||
case *types.Map:
|
||||
prog.needMethods(t.Key(), false)
|
||||
prog.needMethods(t.Elem(), false)
|
||||
|
||||
case *types.Signature:
|
||||
if t.Recv() != nil {
|
||||
panic(fmt.Sprintf("Signature %s has Recv %s", t, t.Recv()))
|
||||
}
|
||||
prog.needMethods(t.Params(), false)
|
||||
prog.needMethods(t.Results(), false)
|
||||
|
||||
case *types.Named:
|
||||
// A pointer-to-named type can be derived from a named
|
||||
// type via reflection. It may have methods too.
|
||||
prog.needMethods(types.NewPointer(T), false)
|
||||
|
||||
// Consider 'type T struct{S}' where S has methods.
|
||||
// Reflection provides no way to get from T to struct{S},
|
||||
// only to S, so the method set of struct{S} is unwanted,
|
||||
// so set 'skip' flag during recursion.
|
||||
prog.needMethods(t.Underlying(), true)
|
||||
|
||||
case *types.Array:
|
||||
prog.needMethods(t.Elem(), false)
|
||||
|
||||
case *types.Struct:
|
||||
for i, n := 0, t.NumFields(); i < n; i++ {
|
||||
prog.needMethods(t.Field(i).Type(), false)
|
||||
}
|
||||
|
||||
case *types.Tuple:
|
||||
for i, n := 0, t.Len(); i < n; i++ {
|
||||
prog.needMethods(t.At(i).Type(), false)
|
||||
}
|
||||
|
||||
default:
|
||||
panic(T)
|
||||
}
|
||||
}
|
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
package ssa
|
||||
|
||||
// This file implements the String() methods for all Value and
|
||||
|
429
go/ssa/print14.go
Normal file
429
go/ssa/print14.go
Normal file
@ -0,0 +1,429 @@
|
||||
// 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.
|
||||
|
||||
// +build !go1.5
|
||||
|
||||
package ssa
|
||||
|
||||
// This file implements the String() methods for all Value and
|
||||
// Instruction types.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"sort"
|
||||
|
||||
"golang.org/x/tools/go/types"
|
||||
"golang.org/x/tools/go/types/typeutil"
|
||||
)
|
||||
|
||||
// relName returns the name of v relative to i.
|
||||
// In most cases, this is identical to v.Name(), but references to
|
||||
// Functions (including methods) and Globals use RelString and
|
||||
// all types are displayed with relType, so that only cross-package
|
||||
// references are package-qualified.
|
||||
//
|
||||
func relName(v Value, i Instruction) string {
|
||||
var from *types.Package
|
||||
if i != nil {
|
||||
from = i.Parent().pkg()
|
||||
}
|
||||
switch v := v.(type) {
|
||||
case Member: // *Function or *Global
|
||||
return v.RelString(from)
|
||||
case *Const:
|
||||
return v.RelString(from)
|
||||
}
|
||||
return v.Name()
|
||||
}
|
||||
|
||||
func relType(t types.Type, from *types.Package) string {
|
||||
return types.TypeString(t, types.RelativeTo(from))
|
||||
}
|
||||
|
||||
func relString(m Member, from *types.Package) string {
|
||||
// NB: not all globals have an Object (e.g. init$guard),
|
||||
// so use Package().Object not Object.Package().
|
||||
if pkg := m.Package().Pkg; pkg != nil && pkg != from {
|
||||
return fmt.Sprintf("%s.%s", pkg.Path(), m.Name())
|
||||
}
|
||||
return m.Name()
|
||||
}
|
||||
|
||||
// Value.String()
|
||||
//
|
||||
// This method is provided only for debugging.
|
||||
// It never appears in disassembly, which uses Value.Name().
|
||||
|
||||
func (v *Parameter) String() string {
|
||||
from := v.Parent().pkg()
|
||||
return fmt.Sprintf("parameter %s : %s", v.Name(), relType(v.Type(), from))
|
||||
}
|
||||
|
||||
func (v *FreeVar) String() string {
|
||||
from := v.Parent().pkg()
|
||||
return fmt.Sprintf("freevar %s : %s", v.Name(), relType(v.Type(), from))
|
||||
}
|
||||
|
||||
func (v *Builtin) String() string {
|
||||
return fmt.Sprintf("builtin %s", v.Name())
|
||||
}
|
||||
|
||||
// Instruction.String()
|
||||
|
||||
func (v *Alloc) String() string {
|
||||
op := "local"
|
||||
if v.Heap {
|
||||
op = "new"
|
||||
}
|
||||
from := v.Parent().pkg()
|
||||
return fmt.Sprintf("%s %s (%s)", op, relType(deref(v.Type()), from), v.Comment)
|
||||
}
|
||||
|
||||
func (v *Phi) String() string {
|
||||
var b bytes.Buffer
|
||||
b.WriteString("phi [")
|
||||
for i, edge := range v.Edges {
|
||||
if i > 0 {
|
||||
b.WriteString(", ")
|
||||
}
|
||||
// Be robust against malformed CFG.
|
||||
block := -1
|
||||
if v.block != nil && i < len(v.block.Preds) {
|
||||
block = v.block.Preds[i].Index
|
||||
}
|
||||
fmt.Fprintf(&b, "%d: ", block)
|
||||
edgeVal := "<nil>" // be robust
|
||||
if edge != nil {
|
||||
edgeVal = relName(edge, v)
|
||||
}
|
||||
b.WriteString(edgeVal)
|
||||
}
|
||||
b.WriteString("]")
|
||||
if v.Comment != "" {
|
||||
b.WriteString(" #")
|
||||
b.WriteString(v.Comment)
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func printCall(v *CallCommon, prefix string, instr Instruction) string {
|
||||
var b bytes.Buffer
|
||||
b.WriteString(prefix)
|
||||
if !v.IsInvoke() {
|
||||
b.WriteString(relName(v.Value, instr))
|
||||
} else {
|
||||
fmt.Fprintf(&b, "invoke %s.%s", relName(v.Value, instr), v.Method.Name())
|
||||
}
|
||||
b.WriteString("(")
|
||||
for i, arg := range v.Args {
|
||||
if i > 0 {
|
||||
b.WriteString(", ")
|
||||
}
|
||||
b.WriteString(relName(arg, instr))
|
||||
}
|
||||
if v.Signature().Variadic() {
|
||||
b.WriteString("...")
|
||||
}
|
||||
b.WriteString(")")
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (c *CallCommon) String() string {
|
||||
return printCall(c, "", nil)
|
||||
}
|
||||
|
||||
func (v *Call) String() string {
|
||||
return printCall(&v.Call, "", v)
|
||||
}
|
||||
|
||||
func (v *BinOp) String() string {
|
||||
return fmt.Sprintf("%s %s %s", relName(v.X, v), v.Op.String(), relName(v.Y, v))
|
||||
}
|
||||
|
||||
func (v *UnOp) String() string {
|
||||
return fmt.Sprintf("%s%s%s", v.Op, relName(v.X, v), commaOk(v.CommaOk))
|
||||
}
|
||||
|
||||
func printConv(prefix string, v, x Value) string {
|
||||
from := v.Parent().pkg()
|
||||
return fmt.Sprintf("%s %s <- %s (%s)",
|
||||
prefix,
|
||||
relType(v.Type(), from),
|
||||
relType(x.Type(), from),
|
||||
relName(x, v.(Instruction)))
|
||||
}
|
||||
|
||||
func (v *ChangeType) String() string { return printConv("changetype", v, v.X) }
|
||||
func (v *Convert) String() string { return printConv("convert", v, v.X) }
|
||||
func (v *ChangeInterface) String() string { return printConv("change interface", v, v.X) }
|
||||
func (v *MakeInterface) String() string { return printConv("make", v, v.X) }
|
||||
|
||||
func (v *MakeClosure) String() string {
|
||||
var b bytes.Buffer
|
||||
fmt.Fprintf(&b, "make closure %s", relName(v.Fn, v))
|
||||
if v.Bindings != nil {
|
||||
b.WriteString(" [")
|
||||
for i, c := range v.Bindings {
|
||||
if i > 0 {
|
||||
b.WriteString(", ")
|
||||
}
|
||||
b.WriteString(relName(c, v))
|
||||
}
|
||||
b.WriteString("]")
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (v *MakeSlice) String() string {
|
||||
from := v.Parent().pkg()
|
||||
return fmt.Sprintf("make %s %s %s",
|
||||
relType(v.Type(), from),
|
||||
relName(v.Len, v),
|
||||
relName(v.Cap, v))
|
||||
}
|
||||
|
||||
func (v *Slice) String() string {
|
||||
var b bytes.Buffer
|
||||
b.WriteString("slice ")
|
||||
b.WriteString(relName(v.X, v))
|
||||
b.WriteString("[")
|
||||
if v.Low != nil {
|
||||
b.WriteString(relName(v.Low, v))
|
||||
}
|
||||
b.WriteString(":")
|
||||
if v.High != nil {
|
||||
b.WriteString(relName(v.High, v))
|
||||
}
|
||||
if v.Max != nil {
|
||||
b.WriteString(":")
|
||||
b.WriteString(relName(v.Max, v))
|
||||
}
|
||||
b.WriteString("]")
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (v *MakeMap) String() string {
|
||||
res := ""
|
||||
if v.Reserve != nil {
|
||||
res = relName(v.Reserve, v)
|
||||
}
|
||||
from := v.Parent().pkg()
|
||||
return fmt.Sprintf("make %s %s", relType(v.Type(), from), res)
|
||||
}
|
||||
|
||||
func (v *MakeChan) String() string {
|
||||
from := v.Parent().pkg()
|
||||
return fmt.Sprintf("make %s %s", relType(v.Type(), from), relName(v.Size, v))
|
||||
}
|
||||
|
||||
func (v *FieldAddr) String() string {
|
||||
st := deref(v.X.Type()).Underlying().(*types.Struct)
|
||||
// Be robust against a bad index.
|
||||
name := "?"
|
||||
if 0 <= v.Field && v.Field < st.NumFields() {
|
||||
name = st.Field(v.Field).Name()
|
||||
}
|
||||
return fmt.Sprintf("&%s.%s [#%d]", relName(v.X, v), name, v.Field)
|
||||
}
|
||||
|
||||
func (v *Field) String() string {
|
||||
st := v.X.Type().Underlying().(*types.Struct)
|
||||
// Be robust against a bad index.
|
||||
name := "?"
|
||||
if 0 <= v.Field && v.Field < st.NumFields() {
|
||||
name = st.Field(v.Field).Name()
|
||||
}
|
||||
return fmt.Sprintf("%s.%s [#%d]", relName(v.X, v), name, v.Field)
|
||||
}
|
||||
|
||||
func (v *IndexAddr) String() string {
|
||||
return fmt.Sprintf("&%s[%s]", relName(v.X, v), relName(v.Index, v))
|
||||
}
|
||||
|
||||
func (v *Index) String() string {
|
||||
return fmt.Sprintf("%s[%s]", relName(v.X, v), relName(v.Index, v))
|
||||
}
|
||||
|
||||
func (v *Lookup) String() string {
|
||||
return fmt.Sprintf("%s[%s]%s", relName(v.X, v), relName(v.Index, v), commaOk(v.CommaOk))
|
||||
}
|
||||
|
||||
func (v *Range) String() string {
|
||||
return "range " + relName(v.X, v)
|
||||
}
|
||||
|
||||
func (v *Next) String() string {
|
||||
return "next " + relName(v.Iter, v)
|
||||
}
|
||||
|
||||
func (v *TypeAssert) String() string {
|
||||
from := v.Parent().pkg()
|
||||
return fmt.Sprintf("typeassert%s %s.(%s)", commaOk(v.CommaOk), relName(v.X, v), relType(v.AssertedType, from))
|
||||
}
|
||||
|
||||
func (v *Extract) String() string {
|
||||
return fmt.Sprintf("extract %s #%d", relName(v.Tuple, v), v.Index)
|
||||
}
|
||||
|
||||
func (s *Jump) String() string {
|
||||
// Be robust against malformed CFG.
|
||||
block := -1
|
||||
if s.block != nil && len(s.block.Succs) == 1 {
|
||||
block = s.block.Succs[0].Index
|
||||
}
|
||||
return fmt.Sprintf("jump %d", block)
|
||||
}
|
||||
|
||||
func (s *If) String() string {
|
||||
// Be robust against malformed CFG.
|
||||
tblock, fblock := -1, -1
|
||||
if s.block != nil && len(s.block.Succs) == 2 {
|
||||
tblock = s.block.Succs[0].Index
|
||||
fblock = s.block.Succs[1].Index
|
||||
}
|
||||
return fmt.Sprintf("if %s goto %d else %d", relName(s.Cond, s), tblock, fblock)
|
||||
}
|
||||
|
||||
func (s *Go) String() string {
|
||||
return printCall(&s.Call, "go ", s)
|
||||
}
|
||||
|
||||
func (s *Panic) String() string {
|
||||
return "panic " + relName(s.X, s)
|
||||
}
|
||||
|
||||
func (s *Return) String() string {
|
||||
var b bytes.Buffer
|
||||
b.WriteString("return")
|
||||
for i, r := range s.Results {
|
||||
if i == 0 {
|
||||
b.WriteString(" ")
|
||||
} else {
|
||||
b.WriteString(", ")
|
||||
}
|
||||
b.WriteString(relName(r, s))
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (*RunDefers) String() string {
|
||||
return "rundefers"
|
||||
}
|
||||
|
||||
func (s *Send) String() string {
|
||||
return fmt.Sprintf("send %s <- %s", relName(s.Chan, s), relName(s.X, s))
|
||||
}
|
||||
|
||||
func (s *Defer) String() string {
|
||||
return printCall(&s.Call, "defer ", s)
|
||||
}
|
||||
|
||||
func (s *Select) String() string {
|
||||
var b bytes.Buffer
|
||||
for i, st := range s.States {
|
||||
if i > 0 {
|
||||
b.WriteString(", ")
|
||||
}
|
||||
if st.Dir == types.RecvOnly {
|
||||
b.WriteString("<-")
|
||||
b.WriteString(relName(st.Chan, s))
|
||||
} else {
|
||||
b.WriteString(relName(st.Chan, s))
|
||||
b.WriteString("<-")
|
||||
b.WriteString(relName(st.Send, s))
|
||||
}
|
||||
}
|
||||
non := ""
|
||||
if !s.Blocking {
|
||||
non = "non"
|
||||
}
|
||||
return fmt.Sprintf("select %sblocking [%s]", non, b.String())
|
||||
}
|
||||
|
||||
func (s *Store) String() string {
|
||||
return fmt.Sprintf("*%s = %s", relName(s.Addr, s), relName(s.Val, s))
|
||||
}
|
||||
|
||||
func (s *MapUpdate) String() string {
|
||||
return fmt.Sprintf("%s[%s] = %s", relName(s.Map, s), relName(s.Key, s), relName(s.Value, s))
|
||||
}
|
||||
|
||||
func (s *DebugRef) String() string {
|
||||
p := s.Parent().Prog.Fset.Position(s.Pos())
|
||||
var descr interface{}
|
||||
if s.object != nil {
|
||||
descr = s.object // e.g. "var x int"
|
||||
} else {
|
||||
descr = reflect.TypeOf(s.Expr) // e.g. "*ast.CallExpr"
|
||||
}
|
||||
var addr string
|
||||
if s.IsAddr {
|
||||
addr = "address of "
|
||||
}
|
||||
return fmt.Sprintf("; %s%s @ %d:%d is %s", addr, descr, p.Line, p.Column, s.X.Name())
|
||||
}
|
||||
|
||||
func (p *Package) String() string {
|
||||
return "package " + p.Pkg.Path()
|
||||
}
|
||||
|
||||
var _ io.WriterTo = (*Package)(nil) // *Package implements io.Writer
|
||||
|
||||
func (p *Package) WriteTo(w io.Writer) (int64, error) {
|
||||
var buf bytes.Buffer
|
||||
WritePackage(&buf, p)
|
||||
n, err := w.Write(buf.Bytes())
|
||||
return int64(n), err
|
||||
}
|
||||
|
||||
// WritePackage writes to buf a human-readable summary of p.
|
||||
func WritePackage(buf *bytes.Buffer, p *Package) {
|
||||
fmt.Fprintf(buf, "%s:\n", p)
|
||||
|
||||
var names []string
|
||||
maxname := 0
|
||||
for name := range p.Members {
|
||||
if l := len(name); l > maxname {
|
||||
maxname = l
|
||||
}
|
||||
names = append(names, name)
|
||||
}
|
||||
|
||||
from := p.Pkg
|
||||
sort.Strings(names)
|
||||
for _, name := range names {
|
||||
switch mem := p.Members[name].(type) {
|
||||
case *NamedConst:
|
||||
fmt.Fprintf(buf, " const %-*s %s = %s\n",
|
||||
maxname, name, mem.Name(), mem.Value.RelString(from))
|
||||
|
||||
case *Function:
|
||||
fmt.Fprintf(buf, " func %-*s %s\n",
|
||||
maxname, name, relType(mem.Type(), from))
|
||||
|
||||
case *Type:
|
||||
fmt.Fprintf(buf, " type %-*s %s\n",
|
||||
maxname, name, relType(mem.Type().Underlying(), from))
|
||||
for _, meth := range typeutil.IntuitiveMethodSet(mem.Type(), &p.Prog.MethodSets) {
|
||||
fmt.Fprintf(buf, " %s\n", types.SelectionString(meth, types.RelativeTo(from)))
|
||||
}
|
||||
|
||||
case *Global:
|
||||
fmt.Fprintf(buf, " var %-*s %s\n",
|
||||
maxname, name, relType(mem.Type().(*types.Pointer).Elem(), from))
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintf(buf, "\n")
|
||||
}
|
||||
|
||||
func commaOk(x bool) string {
|
||||
if x {
|
||||
return ",ok"
|
||||
}
|
||||
return ""
|
||||
}
|
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
package ssa
|
||||
|
||||
// An optional pass for sanity-checking invariants of the SSA representation.
|
||||
|
522
go/ssa/sanity14.go
Normal file
522
go/ssa/sanity14.go
Normal file
@ -0,0 +1,522 @@
|
||||
// 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.
|
||||
|
||||
// +build !go1.5
|
||||
|
||||
package ssa
|
||||
|
||||
// An optional pass for sanity-checking invariants of the SSA representation.
|
||||
// Currently it checks CFG invariants but little at the instruction level.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/types"
|
||||
)
|
||||
|
||||
type sanity struct {
|
||||
reporter io.Writer
|
||||
fn *Function
|
||||
block *BasicBlock
|
||||
instrs map[Instruction]struct{}
|
||||
insane bool
|
||||
}
|
||||
|
||||
// sanityCheck performs integrity checking of the SSA representation
|
||||
// of the function fn and returns true if it was valid. Diagnostics
|
||||
// are written to reporter if non-nil, os.Stderr otherwise. Some
|
||||
// diagnostics are only warnings and do not imply a negative result.
|
||||
//
|
||||
// Sanity-checking is intended to facilitate the debugging of code
|
||||
// transformation passes.
|
||||
//
|
||||
func sanityCheck(fn *Function, reporter io.Writer) bool {
|
||||
if reporter == nil {
|
||||
reporter = os.Stderr
|
||||
}
|
||||
return (&sanity{reporter: reporter}).checkFunction(fn)
|
||||
}
|
||||
|
||||
// mustSanityCheck is like sanityCheck but panics instead of returning
|
||||
// a negative result.
|
||||
//
|
||||
func mustSanityCheck(fn *Function, reporter io.Writer) {
|
||||
if !sanityCheck(fn, reporter) {
|
||||
fn.WriteTo(os.Stderr)
|
||||
panic("SanityCheck failed")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sanity) diagnostic(prefix, format string, args ...interface{}) {
|
||||
fmt.Fprintf(s.reporter, "%s: function %s", prefix, s.fn)
|
||||
if s.block != nil {
|
||||
fmt.Fprintf(s.reporter, ", block %s", s.block)
|
||||
}
|
||||
io.WriteString(s.reporter, ": ")
|
||||
fmt.Fprintf(s.reporter, format, args...)
|
||||
io.WriteString(s.reporter, "\n")
|
||||
}
|
||||
|
||||
func (s *sanity) errorf(format string, args ...interface{}) {
|
||||
s.insane = true
|
||||
s.diagnostic("Error", format, args...)
|
||||
}
|
||||
|
||||
func (s *sanity) warnf(format string, args ...interface{}) {
|
||||
s.diagnostic("Warning", format, args...)
|
||||
}
|
||||
|
||||
// findDuplicate returns an arbitrary basic block that appeared more
|
||||
// than once in blocks, or nil if all were unique.
|
||||
func findDuplicate(blocks []*BasicBlock) *BasicBlock {
|
||||
if len(blocks) < 2 {
|
||||
return nil
|
||||
}
|
||||
if blocks[0] == blocks[1] {
|
||||
return blocks[0]
|
||||
}
|
||||
// Slow path:
|
||||
m := make(map[*BasicBlock]bool)
|
||||
for _, b := range blocks {
|
||||
if m[b] {
|
||||
return b
|
||||
}
|
||||
m[b] = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *sanity) checkInstr(idx int, instr Instruction) {
|
||||
switch instr := instr.(type) {
|
||||
case *If, *Jump, *Return, *Panic:
|
||||
s.errorf("control flow instruction not at end of block")
|
||||
case *Phi:
|
||||
if idx == 0 {
|
||||
// It suffices to apply this check to just the first phi node.
|
||||
if dup := findDuplicate(s.block.Preds); dup != nil {
|
||||
s.errorf("phi node in block with duplicate predecessor %s", dup)
|
||||
}
|
||||
} else {
|
||||
prev := s.block.Instrs[idx-1]
|
||||
if _, ok := prev.(*Phi); !ok {
|
||||
s.errorf("Phi instruction follows a non-Phi: %T", prev)
|
||||
}
|
||||
}
|
||||
if ne, np := len(instr.Edges), len(s.block.Preds); ne != np {
|
||||
s.errorf("phi node has %d edges but %d predecessors", ne, np)
|
||||
|
||||
} else {
|
||||
for i, e := range instr.Edges {
|
||||
if e == nil {
|
||||
s.errorf("phi node '%s' has no value for edge #%d from %s", instr.Comment, i, s.block.Preds[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case *Alloc:
|
||||
if !instr.Heap {
|
||||
found := false
|
||||
for _, l := range s.fn.Locals {
|
||||
if l == instr {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
s.errorf("local alloc %s = %s does not appear in Function.Locals", instr.Name(), instr)
|
||||
}
|
||||
}
|
||||
|
||||
case *BinOp:
|
||||
case *Call:
|
||||
case *ChangeInterface:
|
||||
case *ChangeType:
|
||||
case *Convert:
|
||||
if _, ok := instr.X.Type().Underlying().(*types.Basic); !ok {
|
||||
if _, ok := instr.Type().Underlying().(*types.Basic); !ok {
|
||||
s.errorf("convert %s -> %s: at least one type must be basic", instr.X.Type(), instr.Type())
|
||||
}
|
||||
}
|
||||
|
||||
case *Defer:
|
||||
case *Extract:
|
||||
case *Field:
|
||||
case *FieldAddr:
|
||||
case *Go:
|
||||
case *Index:
|
||||
case *IndexAddr:
|
||||
case *Lookup:
|
||||
case *MakeChan:
|
||||
case *MakeClosure:
|
||||
numFree := len(instr.Fn.(*Function).FreeVars)
|
||||
numBind := len(instr.Bindings)
|
||||
if numFree != numBind {
|
||||
s.errorf("MakeClosure has %d Bindings for function %s with %d free vars",
|
||||
numBind, instr.Fn, numFree)
|
||||
|
||||
}
|
||||
if recv := instr.Type().(*types.Signature).Recv(); recv != nil {
|
||||
s.errorf("MakeClosure's type includes receiver %s", recv.Type())
|
||||
}
|
||||
|
||||
case *MakeInterface:
|
||||
case *MakeMap:
|
||||
case *MakeSlice:
|
||||
case *MapUpdate:
|
||||
case *Next:
|
||||
case *Range:
|
||||
case *RunDefers:
|
||||
case *Select:
|
||||
case *Send:
|
||||
case *Slice:
|
||||
case *Store:
|
||||
case *TypeAssert:
|
||||
case *UnOp:
|
||||
case *DebugRef:
|
||||
// TODO(adonovan): implement checks.
|
||||
default:
|
||||
panic(fmt.Sprintf("Unknown instruction type: %T", instr))
|
||||
}
|
||||
|
||||
if call, ok := instr.(CallInstruction); ok {
|
||||
if call.Common().Signature() == nil {
|
||||
s.errorf("nil signature: %s", call)
|
||||
}
|
||||
}
|
||||
|
||||
// Check that value-defining instructions have valid types
|
||||
// and a valid referrer list.
|
||||
if v, ok := instr.(Value); ok {
|
||||
t := v.Type()
|
||||
if t == nil {
|
||||
s.errorf("no type: %s = %s", v.Name(), v)
|
||||
} else if t == tRangeIter {
|
||||
// not a proper type; ignore.
|
||||
} else if b, ok := t.Underlying().(*types.Basic); ok && b.Info()&types.IsUntyped != 0 {
|
||||
s.errorf("instruction has 'untyped' result: %s = %s : %s", v.Name(), v, t)
|
||||
}
|
||||
s.checkReferrerList(v)
|
||||
}
|
||||
|
||||
// Untyped constants are legal as instruction Operands(),
|
||||
// for example:
|
||||
// _ = "foo"[0]
|
||||
// or:
|
||||
// if wordsize==64 {...}
|
||||
|
||||
// All other non-Instruction Values can be found via their
|
||||
// enclosing Function or Package.
|
||||
}
|
||||
|
||||
func (s *sanity) checkFinalInstr(idx int, instr Instruction) {
|
||||
switch instr := instr.(type) {
|
||||
case *If:
|
||||
if nsuccs := len(s.block.Succs); nsuccs != 2 {
|
||||
s.errorf("If-terminated block has %d successors; expected 2", nsuccs)
|
||||
return
|
||||
}
|
||||
if s.block.Succs[0] == s.block.Succs[1] {
|
||||
s.errorf("If-instruction has same True, False target blocks: %s", s.block.Succs[0])
|
||||
return
|
||||
}
|
||||
|
||||
case *Jump:
|
||||
if nsuccs := len(s.block.Succs); nsuccs != 1 {
|
||||
s.errorf("Jump-terminated block has %d successors; expected 1", nsuccs)
|
||||
return
|
||||
}
|
||||
|
||||
case *Return:
|
||||
if nsuccs := len(s.block.Succs); nsuccs != 0 {
|
||||
s.errorf("Return-terminated block has %d successors; expected none", nsuccs)
|
||||
return
|
||||
}
|
||||
if na, nf := len(instr.Results), s.fn.Signature.Results().Len(); nf != na {
|
||||
s.errorf("%d-ary return in %d-ary function", na, nf)
|
||||
}
|
||||
|
||||
case *Panic:
|
||||
if nsuccs := len(s.block.Succs); nsuccs != 0 {
|
||||
s.errorf("Panic-terminated block has %d successors; expected none", nsuccs)
|
||||
return
|
||||
}
|
||||
|
||||
default:
|
||||
s.errorf("non-control flow instruction at end of block")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sanity) checkBlock(b *BasicBlock, index int) {
|
||||
s.block = b
|
||||
|
||||
if b.Index != index {
|
||||
s.errorf("block has incorrect Index %d", b.Index)
|
||||
}
|
||||
if b.parent != s.fn {
|
||||
s.errorf("block has incorrect parent %s", b.parent)
|
||||
}
|
||||
|
||||
// Check all blocks are reachable.
|
||||
// (The entry block is always implicitly reachable,
|
||||
// as is the Recover block, if any.)
|
||||
if (index > 0 && b != b.parent.Recover) && len(b.Preds) == 0 {
|
||||
s.warnf("unreachable block")
|
||||
if b.Instrs == nil {
|
||||
// Since this block is about to be pruned,
|
||||
// tolerating transient problems in it
|
||||
// simplifies other optimizations.
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Check predecessor and successor relations are dual,
|
||||
// and that all blocks in CFG belong to same function.
|
||||
for _, a := range b.Preds {
|
||||
found := false
|
||||
for _, bb := range a.Succs {
|
||||
if bb == b {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
s.errorf("expected successor edge in predecessor %s; found only: %s", a, a.Succs)
|
||||
}
|
||||
if a.parent != s.fn {
|
||||
s.errorf("predecessor %s belongs to different function %s", a, a.parent)
|
||||
}
|
||||
}
|
||||
for _, c := range b.Succs {
|
||||
found := false
|
||||
for _, bb := range c.Preds {
|
||||
if bb == b {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
s.errorf("expected predecessor edge in successor %s; found only: %s", c, c.Preds)
|
||||
}
|
||||
if c.parent != s.fn {
|
||||
s.errorf("successor %s belongs to different function %s", c, c.parent)
|
||||
}
|
||||
}
|
||||
|
||||
// Check each instruction is sane.
|
||||
n := len(b.Instrs)
|
||||
if n == 0 {
|
||||
s.errorf("basic block contains no instructions")
|
||||
}
|
||||
var rands [10]*Value // reuse storage
|
||||
for j, instr := range b.Instrs {
|
||||
if instr == nil {
|
||||
s.errorf("nil instruction at index %d", j)
|
||||
continue
|
||||
}
|
||||
if b2 := instr.Block(); b2 == nil {
|
||||
s.errorf("nil Block() for instruction at index %d", j)
|
||||
continue
|
||||
} else if b2 != b {
|
||||
s.errorf("wrong Block() (%s) for instruction at index %d ", b2, j)
|
||||
continue
|
||||
}
|
||||
if j < n-1 {
|
||||
s.checkInstr(j, instr)
|
||||
} else {
|
||||
s.checkFinalInstr(j, instr)
|
||||
}
|
||||
|
||||
// Check Instruction.Operands.
|
||||
operands:
|
||||
for i, op := range instr.Operands(rands[:0]) {
|
||||
if op == nil {
|
||||
s.errorf("nil operand pointer %d of %s", i, instr)
|
||||
continue
|
||||
}
|
||||
val := *op
|
||||
if val == nil {
|
||||
continue // a nil operand is ok
|
||||
}
|
||||
|
||||
// Check that "untyped" types only appear on constant operands.
|
||||
if _, ok := (*op).(*Const); !ok {
|
||||
if basic, ok := (*op).Type().(*types.Basic); ok {
|
||||
if basic.Info()&types.IsUntyped != 0 {
|
||||
s.errorf("operand #%d of %s is untyped: %s", i, instr, basic)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check that Operands that are also Instructions belong to same function.
|
||||
// TODO(adonovan): also check their block dominates block b.
|
||||
if val, ok := val.(Instruction); ok {
|
||||
if val.Parent() != s.fn {
|
||||
s.errorf("operand %d of %s is an instruction (%s) from function %s", i, instr, val, val.Parent())
|
||||
}
|
||||
}
|
||||
|
||||
// Check that each function-local operand of
|
||||
// instr refers back to instr. (NB: quadratic)
|
||||
switch val := val.(type) {
|
||||
case *Const, *Global, *Builtin:
|
||||
continue // not local
|
||||
case *Function:
|
||||
if val.parent == nil {
|
||||
continue // only anon functions are local
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(adonovan): check val.Parent() != nil <=> val.Referrers() is defined.
|
||||
|
||||
if refs := val.Referrers(); refs != nil {
|
||||
for _, ref := range *refs {
|
||||
if ref == instr {
|
||||
continue operands
|
||||
}
|
||||
}
|
||||
s.errorf("operand %d of %s (%s) does not refer to us", i, instr, val)
|
||||
} else {
|
||||
s.errorf("operand %d of %s (%s) has no referrers", i, instr, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sanity) checkReferrerList(v Value) {
|
||||
refs := v.Referrers()
|
||||
if refs == nil {
|
||||
s.errorf("%s has missing referrer list", v.Name())
|
||||
return
|
||||
}
|
||||
for i, ref := range *refs {
|
||||
if _, ok := s.instrs[ref]; !ok {
|
||||
s.errorf("%s.Referrers()[%d] = %s is not an instruction belonging to this function", v.Name(), i, ref)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sanity) checkFunction(fn *Function) bool {
|
||||
// TODO(adonovan): check Function invariants:
|
||||
// - check params match signature
|
||||
// - check transient fields are nil
|
||||
// - warn if any fn.Locals do not appear among block instructions.
|
||||
s.fn = fn
|
||||
if fn.Prog == nil {
|
||||
s.errorf("nil Prog")
|
||||
}
|
||||
|
||||
fn.String() // must not crash
|
||||
fn.RelString(fn.pkg()) // must not crash
|
||||
|
||||
// All functions have a package, except delegates (which are
|
||||
// shared across packages, or duplicated as weak symbols in a
|
||||
// separate-compilation model), and error.Error.
|
||||
if fn.Pkg == nil {
|
||||
if strings.HasPrefix(fn.Synthetic, "wrapper ") ||
|
||||
strings.HasPrefix(fn.Synthetic, "bound ") ||
|
||||
strings.HasPrefix(fn.Synthetic, "thunk ") ||
|
||||
strings.HasSuffix(fn.name, "Error") {
|
||||
// ok
|
||||
} else {
|
||||
s.errorf("nil Pkg")
|
||||
}
|
||||
}
|
||||
if src, syn := fn.Synthetic == "", fn.Syntax() != nil; src != syn {
|
||||
s.errorf("got fromSource=%t, hasSyntax=%t; want same values", src, syn)
|
||||
}
|
||||
for i, l := range fn.Locals {
|
||||
if l.Parent() != fn {
|
||||
s.errorf("Local %s at index %d has wrong parent", l.Name(), i)
|
||||
}
|
||||
if l.Heap {
|
||||
s.errorf("Local %s at index %d has Heap flag set", l.Name(), i)
|
||||
}
|
||||
}
|
||||
// Build the set of valid referrers.
|
||||
s.instrs = make(map[Instruction]struct{})
|
||||
for _, b := range fn.Blocks {
|
||||
for _, instr := range b.Instrs {
|
||||
s.instrs[instr] = struct{}{}
|
||||
}
|
||||
}
|
||||
for i, p := range fn.Params {
|
||||
if p.Parent() != fn {
|
||||
s.errorf("Param %s at index %d has wrong parent", p.Name(), i)
|
||||
}
|
||||
s.checkReferrerList(p)
|
||||
}
|
||||
for i, fv := range fn.FreeVars {
|
||||
if fv.Parent() != fn {
|
||||
s.errorf("FreeVar %s at index %d has wrong parent", fv.Name(), i)
|
||||
}
|
||||
s.checkReferrerList(fv)
|
||||
}
|
||||
|
||||
if fn.Blocks != nil && len(fn.Blocks) == 0 {
|
||||
// Function _had_ blocks (so it's not external) but
|
||||
// they were "optimized" away, even the entry block.
|
||||
s.errorf("Blocks slice is non-nil but empty")
|
||||
}
|
||||
for i, b := range fn.Blocks {
|
||||
if b == nil {
|
||||
s.warnf("nil *BasicBlock at f.Blocks[%d]", i)
|
||||
continue
|
||||
}
|
||||
s.checkBlock(b, i)
|
||||
}
|
||||
if fn.Recover != nil && fn.Blocks[fn.Recover.Index] != fn.Recover {
|
||||
s.errorf("Recover block is not in Blocks slice")
|
||||
}
|
||||
|
||||
s.block = nil
|
||||
for i, anon := range fn.AnonFuncs {
|
||||
if anon.Parent() != fn {
|
||||
s.errorf("AnonFuncs[%d]=%s but %s.Parent()=%s", i, anon, anon, anon.Parent())
|
||||
}
|
||||
}
|
||||
s.fn = nil
|
||||
return !s.insane
|
||||
}
|
||||
|
||||
// sanityCheckPackage checks invariants of packages upon creation.
|
||||
// It does not require that the package is built.
|
||||
// Unlike sanityCheck (for functions), it just panics at the first error.
|
||||
func sanityCheckPackage(pkg *Package) {
|
||||
if pkg.Pkg == nil {
|
||||
panic(fmt.Sprintf("Package %s has no Object", pkg))
|
||||
}
|
||||
pkg.String() // must not crash
|
||||
|
||||
for name, mem := range pkg.Members {
|
||||
if name != mem.Name() {
|
||||
panic(fmt.Sprintf("%s: %T.Name() = %s, want %s",
|
||||
pkg.Pkg.Path(), mem, mem.Name(), name))
|
||||
}
|
||||
obj := mem.Object()
|
||||
if obj == nil {
|
||||
// This check is sound because fields
|
||||
// {Global,Function}.object have type
|
||||
// types.Object. (If they were declared as
|
||||
// *types.{Var,Func}, we'd have a non-empty
|
||||
// interface containing a nil pointer.)
|
||||
|
||||
continue // not all members have typechecker objects
|
||||
}
|
||||
if obj.Name() != name {
|
||||
if obj.Name() == "init" && strings.HasPrefix(mem.Name(), "init#") {
|
||||
// Ok. The name of a declared init function varies between
|
||||
// its types.Func ("init") and its ssa.Function ("init#%d").
|
||||
} else {
|
||||
panic(fmt.Sprintf("%s: %T.Object().Name() = %s, want %s",
|
||||
pkg.Pkg.Path(), mem, obj.Name(), name))
|
||||
}
|
||||
}
|
||||
if obj.Pos() != mem.Pos() {
|
||||
panic(fmt.Sprintf("%s Pos=%d obj.Pos=%d", mem, mem.Pos(), obj.Pos()))
|
||||
}
|
||||
}
|
||||
}
|
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
package ssa
|
||||
|
||||
// This file defines utilities for working with source positions
|
||||
|
296
go/ssa/source14.go
Normal file
296
go/ssa/source14.go
Normal file
@ -0,0 +1,296 @@
|
||||
// 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.
|
||||
|
||||
// +build !go1.5
|
||||
|
||||
package ssa
|
||||
|
||||
// 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"
|
||||
|
||||
"golang.org/x/tools/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 SSA 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
|
||||
}
|
||||
}
|
||||
// SSA 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 SSA code for pkg has been built, so it can be
|
||||
// used to quickly reject check inputs that will cause
|
||||
// EnclosingFunction to fail, prior to SSA 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:
|
||||
if decl.Recv == nil && decl.Name.Name == "init" {
|
||||
// Explicit init() function.
|
||||
for _, b := range pkg.init.Blocks {
|
||||
for _, instr := range b.Instrs {
|
||||
if instr, ok := instr.(*Call); ok {
|
||||
if callee, ok := instr.Call.Value.(*Function); ok && callee.Pkg == pkg && callee.Pos() == decl.Name.NamePos {
|
||||
return callee
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Hack: return non-nil when SSA is not yet
|
||||
// built so that HasEnclosingFunction works.
|
||||
return pkg.init
|
||||
}
|
||||
// Declared function/method.
|
||||
return findNamedFunc(pkg, decl.Name.NamePos)
|
||||
}
|
||||
}
|
||||
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 {
|
||||
// Look at all package members and method sets of named types.
|
||||
// Not very efficient.
|
||||
for _, mem := range pkg.Members {
|
||||
switch mem := mem.(type) {
|
||||
case *Function:
|
||||
if mem.Pos() == pos {
|
||||
return mem
|
||||
}
|
||||
case *Type:
|
||||
mset := pkg.Prog.MethodSets.MethodSet(types.NewPointer(mem.Type()))
|
||||
for i, n := 0, mset.Len(); i < n; i++ {
|
||||
// Don't call Program.Method: avoid creating wrappers.
|
||||
obj := mset.At(i).Obj().(*types.Func)
|
||||
if obj.Pos() == pos {
|
||||
return pkg.values[obj].(*Function)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValueForExpr returns the SSA 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 ssa.Value given a source position, use
|
||||
// importer.PathEnclosingInterval to locate the ast.Node, then
|
||||
// EnclosingFunction to locate the Function, then ValueForExpr to find
|
||||
// the ssa.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 SSA Package corresponding to the specified
|
||||
// type-checker package object.
|
||||
// It returns nil if no such SSA 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 SSA 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 SSA 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 SSA 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 SSA 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; SSA 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
|
||||
}
|
395
go/ssa/source14_test.go
Normal file
395
go/ssa/source14_test.go
Normal file
@ -0,0 +1,395 @@
|
||||
// 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.
|
||||
|
||||
// +build !go1.5
|
||||
|
||||
package ssa_test
|
||||
|
||||
// This file defines tests of source-level debugging utilities.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"os"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/go/ast/astutil"
|
||||
"golang.org/x/tools/go/exact"
|
||||
"golang.org/x/tools/go/loader"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/tools/go/ssa/ssautil"
|
||||
"golang.org/x/tools/go/types"
|
||||
)
|
||||
|
||||
func TestObjValueLookup(t *testing.T) {
|
||||
if runtime.GOOS == "android" {
|
||||
t.Skipf("no testdata directory on %s", runtime.GOOS)
|
||||
}
|
||||
|
||||
conf := loader.Config{ParserMode: parser.ParseComments}
|
||||
f, err := conf.ParseFile("testdata/objlookup.go", nil)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
conf.CreateFromFiles("main", f)
|
||||
|
||||
// Maps each var Ident (represented "name:linenum") to the
|
||||
// kind of ssa.Value we expect (represented "Constant", "&Alloc").
|
||||
expectations := make(map[string]string)
|
||||
|
||||
// Find all annotations of form x::BinOp, &y::Alloc, etc.
|
||||
re := regexp.MustCompile(`(\b|&)?(\w*)::(\w*)\b`)
|
||||
for _, c := range f.Comments {
|
||||
text := c.Text()
|
||||
pos := conf.Fset.Position(c.Pos())
|
||||
for _, m := range re.FindAllStringSubmatch(text, -1) {
|
||||
key := fmt.Sprintf("%s:%d", m[2], pos.Line)
|
||||
value := m[1] + m[3]
|
||||
expectations[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
iprog, err := conf.Load()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
prog := ssautil.CreateProgram(iprog, 0 /*|ssa.PrintFunctions*/)
|
||||
mainInfo := iprog.Created[0]
|
||||
mainPkg := prog.Package(mainInfo.Pkg)
|
||||
mainPkg.SetDebugMode(true)
|
||||
mainPkg.Build()
|
||||
|
||||
var varIds []*ast.Ident
|
||||
var varObjs []*types.Var
|
||||
for id, obj := range mainInfo.Defs {
|
||||
// Check invariants for func and const objects.
|
||||
switch obj := obj.(type) {
|
||||
case *types.Func:
|
||||
checkFuncValue(t, prog, obj)
|
||||
|
||||
case *types.Const:
|
||||
checkConstValue(t, prog, obj)
|
||||
|
||||
case *types.Var:
|
||||
if id.Name == "_" {
|
||||
continue
|
||||
}
|
||||
varIds = append(varIds, id)
|
||||
varObjs = append(varObjs, obj)
|
||||
}
|
||||
}
|
||||
for id, obj := range mainInfo.Uses {
|
||||
if obj, ok := obj.(*types.Var); ok {
|
||||
varIds = append(varIds, id)
|
||||
varObjs = append(varObjs, obj)
|
||||
}
|
||||
}
|
||||
|
||||
// Check invariants for var objects.
|
||||
// The result varies based on the specific Ident.
|
||||
for i, id := range varIds {
|
||||
obj := varObjs[i]
|
||||
ref, _ := astutil.PathEnclosingInterval(f, id.Pos(), id.Pos())
|
||||
pos := prog.Fset.Position(id.Pos())
|
||||
exp := expectations[fmt.Sprintf("%s:%d", id.Name, pos.Line)]
|
||||
if exp == "" {
|
||||
t.Errorf("%s: no expectation for var ident %s ", pos, id.Name)
|
||||
continue
|
||||
}
|
||||
wantAddr := false
|
||||
if exp[0] == '&' {
|
||||
wantAddr = true
|
||||
exp = exp[1:]
|
||||
}
|
||||
checkVarValue(t, prog, mainPkg, ref, obj, exp, wantAddr)
|
||||
}
|
||||
}
|
||||
|
||||
func checkFuncValue(t *testing.T, prog *ssa.Program, obj *types.Func) {
|
||||
fn := prog.FuncValue(obj)
|
||||
// fmt.Printf("FuncValue(%s) = %s\n", obj, fn) // debugging
|
||||
if fn == nil {
|
||||
if obj.Name() != "interfaceMethod" {
|
||||
t.Errorf("FuncValue(%s) == nil", obj)
|
||||
}
|
||||
return
|
||||
}
|
||||
if fnobj := fn.Object(); fnobj != obj {
|
||||
t.Errorf("FuncValue(%s).Object() == %s; value was %s",
|
||||
obj, fnobj, fn.Name())
|
||||
return
|
||||
}
|
||||
if !types.Identical(fn.Type(), obj.Type()) {
|
||||
t.Errorf("FuncValue(%s).Type() == %s", obj, fn.Type())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func checkConstValue(t *testing.T, prog *ssa.Program, obj *types.Const) {
|
||||
c := prog.ConstValue(obj)
|
||||
// fmt.Printf("ConstValue(%s) = %s\n", obj, c) // debugging
|
||||
if c == nil {
|
||||
t.Errorf("ConstValue(%s) == nil", obj)
|
||||
return
|
||||
}
|
||||
if !types.Identical(c.Type(), obj.Type()) {
|
||||
t.Errorf("ConstValue(%s).Type() == %s", obj, c.Type())
|
||||
return
|
||||
}
|
||||
if obj.Name() != "nil" {
|
||||
if !exact.Compare(c.Value, token.EQL, obj.Val()) {
|
||||
t.Errorf("ConstValue(%s).Value (%s) != %s",
|
||||
obj, c.Value, obj.Val())
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func checkVarValue(t *testing.T, prog *ssa.Program, pkg *ssa.Package, ref []ast.Node, obj *types.Var, expKind string, wantAddr bool) {
|
||||
// The prefix of all assertions messages.
|
||||
prefix := fmt.Sprintf("VarValue(%s @ L%d)",
|
||||
obj, prog.Fset.Position(ref[0].Pos()).Line)
|
||||
|
||||
v, gotAddr := prog.VarValue(obj, pkg, ref)
|
||||
|
||||
// Kind is the concrete type of the ssa Value.
|
||||
gotKind := "nil"
|
||||
if v != nil {
|
||||
gotKind = fmt.Sprintf("%T", v)[len("*ssa."):]
|
||||
}
|
||||
|
||||
// fmt.Printf("%s = %v (kind %q; expect %q) wantAddr=%t gotAddr=%t\n", prefix, v, gotKind, expKind, wantAddr, gotAddr) // debugging
|
||||
|
||||
// Check the kinds match.
|
||||
// "nil" indicates expected failure (e.g. optimized away).
|
||||
if expKind != gotKind {
|
||||
t.Errorf("%s concrete type == %s, want %s", prefix, gotKind, expKind)
|
||||
}
|
||||
|
||||
// Check the types match.
|
||||
// If wantAddr, the expected type is the object's address.
|
||||
if v != nil {
|
||||
expType := obj.Type()
|
||||
if wantAddr {
|
||||
expType = types.NewPointer(expType)
|
||||
if !gotAddr {
|
||||
t.Errorf("%s: got value, want address", prefix)
|
||||
}
|
||||
} else if gotAddr {
|
||||
t.Errorf("%s: got address, want value", prefix)
|
||||
}
|
||||
if !types.Identical(v.Type(), expType) {
|
||||
t.Errorf("%s.Type() == %s, want %s", prefix, v.Type(), expType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that, in debug mode, we can determine the ssa.Value
|
||||
// corresponding to every ast.Expr.
|
||||
func TestValueForExpr(t *testing.T) {
|
||||
if runtime.GOOS == "android" {
|
||||
t.Skipf("no testdata dir on %s", runtime.GOOS)
|
||||
}
|
||||
|
||||
conf := loader.Config{ParserMode: parser.ParseComments}
|
||||
f, err := conf.ParseFile("testdata/valueforexpr.go", nil)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
conf.CreateFromFiles("main", f)
|
||||
|
||||
iprog, err := conf.Load()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
mainInfo := iprog.Created[0]
|
||||
|
||||
prog := ssautil.CreateProgram(iprog, 0)
|
||||
mainPkg := prog.Package(mainInfo.Pkg)
|
||||
mainPkg.SetDebugMode(true)
|
||||
mainPkg.Build()
|
||||
|
||||
if false {
|
||||
// debugging
|
||||
for _, mem := range mainPkg.Members {
|
||||
if fn, ok := mem.(*ssa.Function); ok {
|
||||
fn.WriteTo(os.Stderr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find the actual AST node for each canonical position.
|
||||
parenExprByPos := make(map[token.Pos]*ast.ParenExpr)
|
||||
ast.Inspect(f, func(n ast.Node) bool {
|
||||
if n != nil {
|
||||
if e, ok := n.(*ast.ParenExpr); ok {
|
||||
parenExprByPos[e.Pos()] = e
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
// Find all annotations of form /*@kind*/.
|
||||
for _, c := range f.Comments {
|
||||
text := strings.TrimSpace(c.Text())
|
||||
if text == "" || text[0] != '@' {
|
||||
continue
|
||||
}
|
||||
text = text[1:]
|
||||
pos := c.End() + 1
|
||||
position := prog.Fset.Position(pos)
|
||||
var e ast.Expr
|
||||
if target := parenExprByPos[pos]; target == nil {
|
||||
t.Errorf("%s: annotation doesn't precede ParenExpr: %q", position, text)
|
||||
continue
|
||||
} else {
|
||||
e = target.X
|
||||
}
|
||||
|
||||
path, _ := astutil.PathEnclosingInterval(f, pos, pos)
|
||||
if path == nil {
|
||||
t.Errorf("%s: can't find AST path from root to comment: %s", position, text)
|
||||
continue
|
||||
}
|
||||
|
||||
fn := ssa.EnclosingFunction(mainPkg, path)
|
||||
if fn == nil {
|
||||
t.Errorf("%s: can't find enclosing function", position)
|
||||
continue
|
||||
}
|
||||
|
||||
v, gotAddr := fn.ValueForExpr(e) // (may be nil)
|
||||
got := strings.TrimPrefix(fmt.Sprintf("%T", v), "*ssa.")
|
||||
if want := text; got != want {
|
||||
t.Errorf("%s: got value %q, want %q", position, got, want)
|
||||
}
|
||||
if v != nil {
|
||||
T := v.Type()
|
||||
if gotAddr {
|
||||
T = T.Underlying().(*types.Pointer).Elem() // deref
|
||||
}
|
||||
if !types.Identical(T, mainInfo.TypeOf(e)) {
|
||||
t.Errorf("%s: got type %s, want %s", position, mainInfo.TypeOf(e), T)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// findInterval parses input and returns the [start, end) positions of
|
||||
// the first occurrence of substr in input. f==nil indicates failure;
|
||||
// an error has already been reported in that case.
|
||||
//
|
||||
func findInterval(t *testing.T, fset *token.FileSet, input, substr string) (f *ast.File, start, end token.Pos) {
|
||||
f, err := parser.ParseFile(fset, "<input>", input, 0)
|
||||
if err != nil {
|
||||
t.Errorf("parse error: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
i := strings.Index(input, substr)
|
||||
if i < 0 {
|
||||
t.Errorf("%q is not a substring of input", substr)
|
||||
f = nil
|
||||
return
|
||||
}
|
||||
|
||||
filePos := fset.File(f.Package)
|
||||
return f, filePos.Pos(i), filePos.Pos(i + len(substr))
|
||||
}
|
||||
|
||||
func TestEnclosingFunction(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string // the input file
|
||||
substr string // first occurrence of this string denotes interval
|
||||
fn string // name of expected containing function
|
||||
}{
|
||||
// We use distinctive numbers as syntactic landmarks.
|
||||
|
||||
// Ordinary function:
|
||||
{`package main
|
||||
func f() { println(1003) }`,
|
||||
"100", "main.f"},
|
||||
// Methods:
|
||||
{`package main
|
||||
type T int
|
||||
func (t T) f() { println(200) }`,
|
||||
"200", "(main.T).f"},
|
||||
// Function literal:
|
||||
{`package main
|
||||
func f() { println(func() { print(300) }) }`,
|
||||
"300", "main.f$1"},
|
||||
// Doubly nested
|
||||
{`package main
|
||||
func f() { println(func() { print(func() { print(350) })})}`,
|
||||
"350", "main.f$1$1"},
|
||||
// Implicit init for package-level var initializer.
|
||||
{"package main; var a = 400", "400", "main.init"},
|
||||
// No code for constants:
|
||||
{"package main; const a = 500", "500", "(none)"},
|
||||
// Explicit init()
|
||||
{"package main; func init() { println(600) }", "600", "main.init#1"},
|
||||
// Multiple explicit init functions:
|
||||
{`package main
|
||||
func init() { println("foo") }
|
||||
func init() { println(800) }`,
|
||||
"800", "main.init#2"},
|
||||
// init() containing FuncLit.
|
||||
{`package main
|
||||
func init() { println(func(){print(900)}) }`,
|
||||
"900", "main.init#1$1"},
|
||||
}
|
||||
for _, test := range tests {
|
||||
conf := loader.Config{Fset: token.NewFileSet()}
|
||||
f, start, end := findInterval(t, conf.Fset, test.input, test.substr)
|
||||
if f == nil {
|
||||
continue
|
||||
}
|
||||
path, exact := astutil.PathEnclosingInterval(f, start, end)
|
||||
if !exact {
|
||||
t.Errorf("EnclosingFunction(%q) not exact", test.substr)
|
||||
continue
|
||||
}
|
||||
|
||||
conf.CreateFromFiles("main", f)
|
||||
|
||||
iprog, err := conf.Load()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
continue
|
||||
}
|
||||
prog := ssautil.CreateProgram(iprog, 0)
|
||||
pkg := prog.Package(iprog.Created[0].Pkg)
|
||||
pkg.Build()
|
||||
|
||||
name := "(none)"
|
||||
fn := ssa.EnclosingFunction(pkg, path)
|
||||
if fn != nil {
|
||||
name = fn.String()
|
||||
}
|
||||
|
||||
if name != test.fn {
|
||||
t.Errorf("EnclosingFunction(%q in %q) got %s, want %s",
|
||||
test.substr, test.input, name, test.fn)
|
||||
continue
|
||||
}
|
||||
|
||||
// While we're here: test HasEnclosingFunction.
|
||||
if has := ssa.HasEnclosingFunction(pkg, path); has != (fn != nil) {
|
||||
t.Errorf("HasEnclosingFunction(%q in %q) got %v, want %v",
|
||||
test.substr, test.input, has, fn != nil)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
package ssa_test
|
||||
|
||||
// This file defines tests of source-level debugging utilities.
|
||||
|
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
package ssa
|
||||
|
||||
// This package defines a high-level intermediate representation for
|
||||
|
1702
go/ssa/ssa14.go
Normal file
1702
go/ssa/ssa14.go
Normal file
File diff suppressed because it is too large
Load Diff
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
package ssautil
|
||||
|
||||
// This file defines utility functions for constructing programs in SSA form.
|
||||
|
97
go/ssa/ssautil/load14.go
Normal file
97
go/ssa/ssautil/load14.go
Normal file
@ -0,0 +1,97 @@
|
||||
// Copyright 2015 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.
|
||||
|
||||
// +build !go1.5
|
||||
|
||||
package ssautil
|
||||
|
||||
// This file defines utility functions for constructing programs in SSA form.
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
|
||||
"golang.org/x/tools/go/loader"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/tools/go/types"
|
||||
)
|
||||
|
||||
// CreateProgram returns a new program in SSA form, given a program
|
||||
// loaded from source. An SSA package is created for each transitively
|
||||
// error-free package of lprog.
|
||||
//
|
||||
// Code for bodies of functions is not built until BuildAll() is called
|
||||
// on the result.
|
||||
//
|
||||
// mode controls diagnostics and checking during SSA construction.
|
||||
//
|
||||
func CreateProgram(lprog *loader.Program, mode ssa.BuilderMode) *ssa.Program {
|
||||
prog := ssa.NewProgram(lprog.Fset, mode)
|
||||
|
||||
for _, info := range lprog.AllPackages {
|
||||
if info.TransitivelyErrorFree {
|
||||
prog.CreatePackage(info.Pkg, info.Files, &info.Info, info.Importable)
|
||||
}
|
||||
}
|
||||
|
||||
return prog
|
||||
}
|
||||
|
||||
// BuildPackage builds an SSA program with IR for a single package.
|
||||
//
|
||||
// It populates pkg by type-checking the specified file ASTs. All
|
||||
// dependencies are loaded using the importer specified by tc, which
|
||||
// typically loads compiler export data; SSA code cannot be built for
|
||||
// those packages. BuildPackage then constructs an ssa.Program with all
|
||||
// dependency packages created, and builds and returns the SSA package
|
||||
// corresponding to pkg.
|
||||
//
|
||||
// The caller must have set pkg.Path() to the import path.
|
||||
//
|
||||
// The operation fails if there were any type-checking or import errors.
|
||||
//
|
||||
// See ../ssa/example_test.go for an example.
|
||||
//
|
||||
func BuildPackage(tc *types.Config, fset *token.FileSet, pkg *types.Package, files []*ast.File, mode ssa.BuilderMode) (*ssa.Package, *types.Info, error) {
|
||||
if fset == nil {
|
||||
panic("no token.FileSet")
|
||||
}
|
||||
if pkg.Path() == "" {
|
||||
panic("package has no import path")
|
||||
}
|
||||
|
||||
info := &types.Info{
|
||||
Types: make(map[ast.Expr]types.TypeAndValue),
|
||||
Defs: make(map[*ast.Ident]types.Object),
|
||||
Uses: make(map[*ast.Ident]types.Object),
|
||||
Implicits: make(map[ast.Node]types.Object),
|
||||
Scopes: make(map[ast.Node]*types.Scope),
|
||||
Selections: make(map[*ast.SelectorExpr]*types.Selection),
|
||||
}
|
||||
if err := types.NewChecker(tc, fset, pkg, info).Files(files); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
prog := ssa.NewProgram(fset, mode)
|
||||
|
||||
// Create SSA packages for all imports.
|
||||
// Order is not significant.
|
||||
created := make(map[*types.Package]bool)
|
||||
var createAll func(pkgs []*types.Package)
|
||||
createAll = func(pkgs []*types.Package) {
|
||||
for _, p := range pkgs {
|
||||
if !created[p] {
|
||||
created[p] = true
|
||||
prog.CreatePackage(p, nil, nil, true)
|
||||
createAll(p.Imports())
|
||||
}
|
||||
}
|
||||
}
|
||||
createAll(pkg.Imports())
|
||||
|
||||
// Create and build the primary package.
|
||||
ssapkg := prog.CreatePackage(pkg, files, info, false)
|
||||
ssapkg.Build()
|
||||
return ssapkg, info, nil
|
||||
}
|
67
go/ssa/ssautil/load14_test.go
Normal file
67
go/ssa/ssautil/load14_test.go
Normal file
@ -0,0 +1,67 @@
|
||||
// Copyright 2015 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.
|
||||
|
||||
// +build !go1.5
|
||||
|
||||
package ssautil_test
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/go/ssa/ssautil"
|
||||
"golang.org/x/tools/go/types"
|
||||
|
||||
_ "golang.org/x/tools/go/gcimporter"
|
||||
)
|
||||
|
||||
const hello = `package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
fmt.Println("Hello, world")
|
||||
}
|
||||
`
|
||||
|
||||
func TestBuildPackage(t *testing.T) {
|
||||
// There is a more substantial test of BuildPackage and the
|
||||
// SSA program it builds in ../ssa/builder_test.go.
|
||||
|
||||
fset := token.NewFileSet()
|
||||
f, err := parser.ParseFile(fset, "hello.go", hello, 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
pkg := types.NewPackage("hello", "")
|
||||
ssapkg, _, err := ssautil.BuildPackage(new(types.Config), fset, pkg, []*ast.File{f}, 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if pkg.Name() != "main" {
|
||||
t.Errorf("pkg.Name() = %s, want main", pkg.Name())
|
||||
}
|
||||
if ssapkg.Func("main") == nil {
|
||||
ssapkg.WriteTo(os.Stderr)
|
||||
t.Errorf("ssapkg has no main function")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildPackage_MissingImport(t *testing.T) {
|
||||
fset := token.NewFileSet()
|
||||
f, err := parser.ParseFile(fset, "bad.go", `package bad; import "missing"`, 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
pkg := types.NewPackage("bad", "")
|
||||
ssapkg, _, err := ssautil.BuildPackage(new(types.Config), fset, pkg, []*ast.File{f}, 0)
|
||||
if err == nil || ssapkg != nil {
|
||||
t.Fatal("BuildPackage succeeded unexpectedly")
|
||||
}
|
||||
}
|
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
package ssautil_test
|
||||
|
||||
import (
|
||||
|
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
package ssautil
|
||||
|
||||
// This file implements discovery of switch and type-switch constructs
|
||||
|
236
go/ssa/ssautil/switch14.go
Normal file
236
go/ssa/ssautil/switch14.go
Normal file
@ -0,0 +1,236 @@
|
||||
// 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.
|
||||
|
||||
// +build !go1.5
|
||||
|
||||
package ssautil
|
||||
|
||||
// This file implements discovery of switch and type-switch constructs
|
||||
// from low-level control flow.
|
||||
//
|
||||
// Many techniques exist for compiling a high-level switch with
|
||||
// constant cases to efficient machine code. The optimal choice will
|
||||
// depend on the data type, the specific case values, the code in the
|
||||
// body of each case, and the hardware.
|
||||
// Some examples:
|
||||
// - a lookup table (for a switch that maps constants to constants)
|
||||
// - a computed goto
|
||||
// - a binary tree
|
||||
// - a perfect hash
|
||||
// - a two-level switch (to partition constant strings by their first byte).
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/token"
|
||||
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/tools/go/types"
|
||||
)
|
||||
|
||||
// A ConstCase represents a single constant comparison.
|
||||
// It is part of a Switch.
|
||||
type ConstCase struct {
|
||||
Block *ssa.BasicBlock // block performing the comparison
|
||||
Body *ssa.BasicBlock // body of the case
|
||||
Value *ssa.Const // case comparand
|
||||
}
|
||||
|
||||
// A TypeCase represents a single type assertion.
|
||||
// It is part of a Switch.
|
||||
type TypeCase struct {
|
||||
Block *ssa.BasicBlock // block performing the type assert
|
||||
Body *ssa.BasicBlock // body of the case
|
||||
Type types.Type // case type
|
||||
Binding ssa.Value // value bound by this case
|
||||
}
|
||||
|
||||
// A Switch is a logical high-level control flow operation
|
||||
// (a multiway branch) discovered by analysis of a CFG containing
|
||||
// only if/else chains. It is not part of the ssa.Instruction set.
|
||||
//
|
||||
// One of ConstCases and TypeCases has length >= 2;
|
||||
// the other is nil.
|
||||
//
|
||||
// In a value switch, the list of cases may contain duplicate constants.
|
||||
// A type switch may contain duplicate types, or types assignable
|
||||
// to an interface type also in the list.
|
||||
// TODO(adonovan): eliminate such duplicates.
|
||||
//
|
||||
type Switch struct {
|
||||
Start *ssa.BasicBlock // block containing start of if/else chain
|
||||
X ssa.Value // the switch operand
|
||||
ConstCases []ConstCase // ordered list of constant comparisons
|
||||
TypeCases []TypeCase // ordered list of type assertions
|
||||
Default *ssa.BasicBlock // successor if all comparisons fail
|
||||
}
|
||||
|
||||
func (sw *Switch) String() string {
|
||||
// We represent each block by the String() of its
|
||||
// first Instruction, e.g. "print(42:int)".
|
||||
var buf bytes.Buffer
|
||||
if sw.ConstCases != nil {
|
||||
fmt.Fprintf(&buf, "switch %s {\n", sw.X.Name())
|
||||
for _, c := range sw.ConstCases {
|
||||
fmt.Fprintf(&buf, "case %s: %s\n", c.Value, c.Body.Instrs[0])
|
||||
}
|
||||
} else {
|
||||
fmt.Fprintf(&buf, "switch %s.(type) {\n", sw.X.Name())
|
||||
for _, c := range sw.TypeCases {
|
||||
fmt.Fprintf(&buf, "case %s %s: %s\n",
|
||||
c.Binding.Name(), c.Type, c.Body.Instrs[0])
|
||||
}
|
||||
}
|
||||
if sw.Default != nil {
|
||||
fmt.Fprintf(&buf, "default: %s\n", sw.Default.Instrs[0])
|
||||
}
|
||||
fmt.Fprintf(&buf, "}")
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// Switches examines the control-flow graph of fn and returns the
|
||||
// set of inferred value and type switches. A value switch tests an
|
||||
// ssa.Value for equality against two or more compile-time constant
|
||||
// values. Switches involving link-time constants (addresses) are
|
||||
// ignored. A type switch type-asserts an ssa.Value against two or
|
||||
// more types.
|
||||
//
|
||||
// The switches are returned in dominance order.
|
||||
//
|
||||
// The resulting switches do not necessarily correspond to uses of the
|
||||
// 'switch' keyword in the source: for example, a single source-level
|
||||
// switch statement with non-constant cases may result in zero, one or
|
||||
// many Switches, one per plural sequence of constant cases.
|
||||
// Switches may even be inferred from if/else- or goto-based control flow.
|
||||
// (In general, the control flow constructs of the source program
|
||||
// cannot be faithfully reproduced from the SSA representation.)
|
||||
//
|
||||
func Switches(fn *ssa.Function) []Switch {
|
||||
// Traverse the CFG in dominance order, so we don't
|
||||
// enter an if/else-chain in the middle.
|
||||
var switches []Switch
|
||||
seen := make(map[*ssa.BasicBlock]bool) // TODO(adonovan): opt: use ssa.blockSet
|
||||
for _, b := range fn.DomPreorder() {
|
||||
if x, k := isComparisonBlock(b); x != nil {
|
||||
// Block b starts a switch.
|
||||
sw := Switch{Start: b, X: x}
|
||||
valueSwitch(&sw, k, seen)
|
||||
if len(sw.ConstCases) > 1 {
|
||||
switches = append(switches, sw)
|
||||
}
|
||||
}
|
||||
|
||||
if y, x, T := isTypeAssertBlock(b); y != nil {
|
||||
// Block b starts a type switch.
|
||||
sw := Switch{Start: b, X: x}
|
||||
typeSwitch(&sw, y, T, seen)
|
||||
if len(sw.TypeCases) > 1 {
|
||||
switches = append(switches, sw)
|
||||
}
|
||||
}
|
||||
}
|
||||
return switches
|
||||
}
|
||||
|
||||
func valueSwitch(sw *Switch, k *ssa.Const, seen map[*ssa.BasicBlock]bool) {
|
||||
b := sw.Start
|
||||
x := sw.X
|
||||
for x == sw.X {
|
||||
if seen[b] {
|
||||
break
|
||||
}
|
||||
seen[b] = true
|
||||
|
||||
sw.ConstCases = append(sw.ConstCases, ConstCase{
|
||||
Block: b,
|
||||
Body: b.Succs[0],
|
||||
Value: k,
|
||||
})
|
||||
b = b.Succs[1]
|
||||
if len(b.Instrs) > 2 {
|
||||
// Block b contains not just 'if x == k',
|
||||
// so it may have side effects that
|
||||
// make it unsafe to elide.
|
||||
break
|
||||
}
|
||||
if len(b.Preds) != 1 {
|
||||
// Block b has multiple predecessors,
|
||||
// so it cannot be treated as a case.
|
||||
break
|
||||
}
|
||||
x, k = isComparisonBlock(b)
|
||||
}
|
||||
sw.Default = b
|
||||
}
|
||||
|
||||
func typeSwitch(sw *Switch, y ssa.Value, T types.Type, seen map[*ssa.BasicBlock]bool) {
|
||||
b := sw.Start
|
||||
x := sw.X
|
||||
for x == sw.X {
|
||||
if seen[b] {
|
||||
break
|
||||
}
|
||||
seen[b] = true
|
||||
|
||||
sw.TypeCases = append(sw.TypeCases, TypeCase{
|
||||
Block: b,
|
||||
Body: b.Succs[0],
|
||||
Type: T,
|
||||
Binding: y,
|
||||
})
|
||||
b = b.Succs[1]
|
||||
if len(b.Instrs) > 4 {
|
||||
// Block b contains not just
|
||||
// {TypeAssert; Extract #0; Extract #1; If}
|
||||
// so it may have side effects that
|
||||
// make it unsafe to elide.
|
||||
break
|
||||
}
|
||||
if len(b.Preds) != 1 {
|
||||
// Block b has multiple predecessors,
|
||||
// so it cannot be treated as a case.
|
||||
break
|
||||
}
|
||||
y, x, T = isTypeAssertBlock(b)
|
||||
}
|
||||
sw.Default = b
|
||||
}
|
||||
|
||||
// isComparisonBlock returns the operands (v, k) if a block ends with
|
||||
// a comparison v==k, where k is a compile-time constant.
|
||||
//
|
||||
func isComparisonBlock(b *ssa.BasicBlock) (v ssa.Value, k *ssa.Const) {
|
||||
if n := len(b.Instrs); n >= 2 {
|
||||
if i, ok := b.Instrs[n-1].(*ssa.If); ok {
|
||||
if binop, ok := i.Cond.(*ssa.BinOp); ok && binop.Block() == b && binop.Op == token.EQL {
|
||||
if k, ok := binop.Y.(*ssa.Const); ok {
|
||||
return binop.X, k
|
||||
}
|
||||
if k, ok := binop.X.(*ssa.Const); ok {
|
||||
return binop.Y, k
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// isTypeAssertBlock returns the operands (y, x, T) if a block ends with
|
||||
// a type assertion "if y, ok := x.(T); ok {".
|
||||
//
|
||||
func isTypeAssertBlock(b *ssa.BasicBlock) (y, x ssa.Value, T types.Type) {
|
||||
if n := len(b.Instrs); n >= 4 {
|
||||
if i, ok := b.Instrs[n-1].(*ssa.If); ok {
|
||||
if ext1, ok := i.Cond.(*ssa.Extract); ok && ext1.Block() == b && ext1.Index == 1 {
|
||||
if ta, ok := ext1.Tuple.(*ssa.TypeAssert); ok && ta.Block() == b {
|
||||
// hack: relies upon instruction ordering.
|
||||
if ext0, ok := b.Instrs[n-3].(*ssa.Extract); ok {
|
||||
return ext0, ta.X, ta.AssertedType
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
package ssa
|
||||
|
||||
// CreateTestMainPackage synthesizes a main package that runs all the
|
||||
|
304
go/ssa/testmain14.go
Normal file
304
go/ssa/testmain14.go
Normal file
@ -0,0 +1,304 @@
|
||||
// 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.
|
||||
|
||||
// +build !go1.5
|
||||
|
||||
package ssa
|
||||
|
||||
// CreateTestMainPackage synthesizes a main package that runs all the
|
||||
// tests of the supplied packages.
|
||||
// It is closely coupled to $GOROOT/src/cmd/go/test.go and $GOROOT/src/testing.
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/exact"
|
||||
"golang.org/x/tools/go/types"
|
||||
)
|
||||
|
||||
// FindTests returns the list of packages that define at least one Test,
|
||||
// Example or Benchmark function (as defined by "go test"), and the
|
||||
// lists of all such functions.
|
||||
//
|
||||
func FindTests(pkgs []*Package) (testpkgs []*Package, tests, benchmarks, examples []*Function) {
|
||||
if len(pkgs) == 0 {
|
||||
return
|
||||
}
|
||||
prog := pkgs[0].Prog
|
||||
|
||||
// The first two of these may be nil: if the program doesn't import "testing",
|
||||
// it can't contain any tests, but it may yet contain Examples.
|
||||
var testSig *types.Signature // func(*testing.T)
|
||||
var benchmarkSig *types.Signature // func(*testing.B)
|
||||
var exampleSig = types.NewSignature(nil, nil, nil, false) // func()
|
||||
|
||||
// Obtain the types from the parameters of testing.Main().
|
||||
if testingPkg := prog.ImportedPackage("testing"); testingPkg != nil {
|
||||
params := testingPkg.Func("Main").Signature.Params()
|
||||
testSig = funcField(params.At(1).Type())
|
||||
benchmarkSig = funcField(params.At(2).Type())
|
||||
}
|
||||
|
||||
seen := make(map[*Package]bool)
|
||||
for _, pkg := range pkgs {
|
||||
if pkg.Prog != prog {
|
||||
panic("wrong Program")
|
||||
}
|
||||
|
||||
// TODO(adonovan): use a stable order, e.g. lexical.
|
||||
for _, mem := range pkg.Members {
|
||||
if f, ok := mem.(*Function); ok &&
|
||||
ast.IsExported(f.Name()) &&
|
||||
strings.HasSuffix(prog.Fset.Position(f.Pos()).Filename, "_test.go") {
|
||||
|
||||
switch {
|
||||
case testSig != nil && isTestSig(f, "Test", testSig):
|
||||
tests = append(tests, f)
|
||||
case benchmarkSig != nil && isTestSig(f, "Benchmark", benchmarkSig):
|
||||
benchmarks = append(benchmarks, f)
|
||||
case isTestSig(f, "Example", exampleSig):
|
||||
examples = append(examples, f)
|
||||
default:
|
||||
continue
|
||||
}
|
||||
|
||||
if !seen[pkg] {
|
||||
seen[pkg] = true
|
||||
testpkgs = append(testpkgs, pkg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Like isTest, but checks the signature too.
|
||||
func isTestSig(f *Function, prefix string, sig *types.Signature) bool {
|
||||
return isTest(f.Name(), prefix) && types.Identical(f.Signature, sig)
|
||||
}
|
||||
|
||||
// If non-nil, testMainStartBodyHook is called immediately after
|
||||
// startBody for main.init and main.main, making it easy for users to
|
||||
// add custom imports and initialization steps for proprietary build
|
||||
// systems that don't exactly follow 'go test' conventions.
|
||||
var testMainStartBodyHook func(*Function)
|
||||
|
||||
// CreateTestMainPackage creates and returns a synthetic "main"
|
||||
// package that runs all the tests of the supplied packages, similar
|
||||
// to the one that would be created by the 'go test' tool.
|
||||
//
|
||||
// It returns nil if the program contains no tests.
|
||||
//
|
||||
func (prog *Program) CreateTestMainPackage(pkgs ...*Package) *Package {
|
||||
pkgs, tests, benchmarks, examples := FindTests(pkgs)
|
||||
if len(pkgs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
testmain := &Package{
|
||||
Prog: prog,
|
||||
Members: make(map[string]Member),
|
||||
values: make(map[types.Object]Value),
|
||||
Pkg: types.NewPackage("test$main", "main"),
|
||||
}
|
||||
|
||||
// Build package's init function.
|
||||
init := &Function{
|
||||
name: "init",
|
||||
Signature: new(types.Signature),
|
||||
Synthetic: "package initializer",
|
||||
Pkg: testmain,
|
||||
Prog: prog,
|
||||
}
|
||||
init.startBody()
|
||||
|
||||
if testMainStartBodyHook != nil {
|
||||
testMainStartBodyHook(init)
|
||||
}
|
||||
|
||||
// Initialize packages to test.
|
||||
var pkgpaths []string
|
||||
for _, pkg := range pkgs {
|
||||
var v Call
|
||||
v.Call.Value = pkg.init
|
||||
v.setType(types.NewTuple())
|
||||
init.emit(&v)
|
||||
|
||||
pkgpaths = append(pkgpaths, pkg.Pkg.Path())
|
||||
}
|
||||
sort.Strings(pkgpaths)
|
||||
init.emit(new(Return))
|
||||
init.finishBody()
|
||||
testmain.init = init
|
||||
testmain.Pkg.MarkComplete()
|
||||
testmain.Members[init.name] = init
|
||||
|
||||
// For debugging convenience, define an unexported const
|
||||
// that enumerates the packages.
|
||||
packagesConst := types.NewConst(token.NoPos, testmain.Pkg, "packages", tString,
|
||||
exact.MakeString(strings.Join(pkgpaths, " ")))
|
||||
memberFromObject(testmain, packagesConst, nil)
|
||||
|
||||
// Create main *types.Func and *ssa.Function
|
||||
mainFunc := types.NewFunc(token.NoPos, testmain.Pkg, "main", new(types.Signature))
|
||||
memberFromObject(testmain, mainFunc, nil)
|
||||
main := testmain.Func("main")
|
||||
main.Synthetic = "test main function"
|
||||
|
||||
main.startBody()
|
||||
|
||||
if testMainStartBodyHook != nil {
|
||||
testMainStartBodyHook(main)
|
||||
}
|
||||
|
||||
if testingPkg := prog.ImportedPackage("testing"); testingPkg != nil {
|
||||
testingMain := testingPkg.Func("Main")
|
||||
testingMainParams := testingMain.Signature.Params()
|
||||
|
||||
// The generated code is as if compiled from this:
|
||||
//
|
||||
// func main() {
|
||||
// match := func(_, _ string) (bool, error) { return true, nil }
|
||||
// tests := []testing.InternalTest{{"TestFoo", TestFoo}, ...}
|
||||
// benchmarks := []testing.InternalBenchmark{...}
|
||||
// examples := []testing.InternalExample{...}
|
||||
// testing.Main(match, tests, benchmarks, examples)
|
||||
// }
|
||||
|
||||
matcher := &Function{
|
||||
name: "matcher",
|
||||
Signature: testingMainParams.At(0).Type().(*types.Signature),
|
||||
Synthetic: "test matcher predicate",
|
||||
parent: main,
|
||||
Pkg: testmain,
|
||||
Prog: prog,
|
||||
}
|
||||
main.AnonFuncs = append(main.AnonFuncs, matcher)
|
||||
matcher.startBody()
|
||||
matcher.emit(&Return{Results: []Value{vTrue, nilConst(types.Universe.Lookup("error").Type())}})
|
||||
matcher.finishBody()
|
||||
|
||||
// Emit call: testing.Main(matcher, tests, benchmarks, examples).
|
||||
var c Call
|
||||
c.Call.Value = testingMain
|
||||
c.Call.Args = []Value{
|
||||
matcher,
|
||||
testMainSlice(main, tests, testingMainParams.At(1).Type()),
|
||||
testMainSlice(main, benchmarks, testingMainParams.At(2).Type()),
|
||||
testMainSlice(main, examples, testingMainParams.At(3).Type()),
|
||||
}
|
||||
emitTailCall(main, &c)
|
||||
} else {
|
||||
// The program does not import "testing", but FindTests
|
||||
// returned non-nil, which must mean there were Examples
|
||||
// but no Tests or Benchmarks.
|
||||
// We'll simply call them from testmain.main; this will
|
||||
// ensure they don't panic, but will not check any
|
||||
// "Output:" comments.
|
||||
for _, eg := range examples {
|
||||
var c Call
|
||||
c.Call.Value = eg
|
||||
c.setType(types.NewTuple())
|
||||
main.emit(&c)
|
||||
}
|
||||
main.emit(&Return{})
|
||||
main.currentBlock = nil
|
||||
}
|
||||
|
||||
main.finishBody()
|
||||
|
||||
testmain.Members["main"] = main
|
||||
|
||||
if prog.mode&PrintPackages != 0 {
|
||||
printMu.Lock()
|
||||
testmain.WriteTo(os.Stdout)
|
||||
printMu.Unlock()
|
||||
}
|
||||
|
||||
if prog.mode&SanityCheckFunctions != 0 {
|
||||
sanityCheckPackage(testmain)
|
||||
}
|
||||
|
||||
prog.packages[testmain.Pkg] = testmain
|
||||
|
||||
return testmain
|
||||
}
|
||||
|
||||
// testMainSlice emits to fn code to construct a slice of type slice
|
||||
// (one of []testing.Internal{Test,Benchmark,Example}) for all
|
||||
// functions in testfuncs. It returns the slice value.
|
||||
//
|
||||
func testMainSlice(fn *Function, testfuncs []*Function, slice types.Type) Value {
|
||||
if testfuncs == nil {
|
||||
return nilConst(slice)
|
||||
}
|
||||
|
||||
tElem := slice.(*types.Slice).Elem()
|
||||
tPtrString := types.NewPointer(tString)
|
||||
tPtrElem := types.NewPointer(tElem)
|
||||
tPtrFunc := types.NewPointer(funcField(slice))
|
||||
|
||||
// TODO(adonovan): fix: populate the
|
||||
// testing.InternalExample.Output field correctly so that tests
|
||||
// work correctly under the interpreter. This requires that we
|
||||
// do this step using ASTs, not *ssa.Functions---quite a
|
||||
// redesign. See also the fake runExample in go/ssa/interp.
|
||||
|
||||
// Emit: array = new [n]testing.InternalTest
|
||||
tArray := types.NewArray(tElem, int64(len(testfuncs)))
|
||||
array := emitNew(fn, tArray, token.NoPos)
|
||||
array.Comment = "test main"
|
||||
for i, testfunc := range testfuncs {
|
||||
// Emit: pitem = &array[i]
|
||||
ia := &IndexAddr{X: array, Index: intConst(int64(i))}
|
||||
ia.setType(tPtrElem)
|
||||
pitem := fn.emit(ia)
|
||||
|
||||
// Emit: pname = &pitem.Name
|
||||
fa := &FieldAddr{X: pitem, Field: 0} // .Name
|
||||
fa.setType(tPtrString)
|
||||
pname := fn.emit(fa)
|
||||
|
||||
// Emit: *pname = "testfunc"
|
||||
emitStore(fn, pname, stringConst(testfunc.Name()), token.NoPos)
|
||||
|
||||
// Emit: pfunc = &pitem.F
|
||||
fa = &FieldAddr{X: pitem, Field: 1} // .F
|
||||
fa.setType(tPtrFunc)
|
||||
pfunc := fn.emit(fa)
|
||||
|
||||
// Emit: *pfunc = testfunc
|
||||
emitStore(fn, pfunc, testfunc, token.NoPos)
|
||||
}
|
||||
|
||||
// Emit: slice array[:]
|
||||
sl := &Slice{X: array}
|
||||
sl.setType(slice)
|
||||
return fn.emit(sl)
|
||||
}
|
||||
|
||||
// Given the type of one of the three slice parameters of testing.Main,
|
||||
// returns the function type.
|
||||
func funcField(slice types.Type) *types.Signature {
|
||||
return slice.(*types.Slice).Elem().Underlying().(*types.Struct).Field(1).Type().(*types.Signature)
|
||||
}
|
||||
|
||||
// Plundered from $GOROOT/src/cmd/go/test.go
|
||||
|
||||
// isTest tells whether name looks like a test (or benchmark, according to prefix).
|
||||
// It is a Test (say) if there is a character after Test that is not a lower-case letter.
|
||||
// We don't want TesticularCancer.
|
||||
func isTest(name, prefix string) bool {
|
||||
if !strings.HasPrefix(name, prefix) {
|
||||
return false
|
||||
}
|
||||
if len(name) == len(prefix) { // "Test" is ok
|
||||
return true
|
||||
}
|
||||
return ast.IsExported(name[len(prefix):])
|
||||
}
|
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
package ssa
|
||||
|
||||
// This file defines a number of miscellaneous utility functions.
|
||||
|
121
go/ssa/util14.go
Normal file
121
go/ssa/util14.go
Normal file
@ -0,0 +1,121 @@
|
||||
// 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.
|
||||
|
||||
// +build !go1.5
|
||||
|
||||
package ssa
|
||||
|
||||
// This file defines a number of miscellaneous utility functions.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"golang.org/x/tools/go/ast/astutil"
|
||||
"golang.org/x/tools/go/types"
|
||||
)
|
||||
|
||||
//// AST utilities
|
||||
|
||||
func unparen(e ast.Expr) ast.Expr { return astutil.Unparen(e) }
|
||||
|
||||
// isBlankIdent returns true iff e is an Ident with name "_".
|
||||
// They have no associated types.Object, and thus no type.
|
||||
//
|
||||
func isBlankIdent(e ast.Expr) bool {
|
||||
id, ok := e.(*ast.Ident)
|
||||
return ok && id.Name == "_"
|
||||
}
|
||||
|
||||
//// Type utilities. Some of these belong in go/types.
|
||||
|
||||
// isPointer returns true for types whose underlying type is a pointer.
|
||||
func isPointer(typ types.Type) bool {
|
||||
_, ok := typ.Underlying().(*types.Pointer)
|
||||
return ok
|
||||
}
|
||||
|
||||
func isInterface(T types.Type) bool { return types.IsInterface(T) }
|
||||
|
||||
// deref returns a pointer's element type; otherwise it returns typ.
|
||||
func deref(typ types.Type) types.Type {
|
||||
if p, ok := typ.Underlying().(*types.Pointer); ok {
|
||||
return p.Elem()
|
||||
}
|
||||
return typ
|
||||
}
|
||||
|
||||
// recvType returns the receiver type of method obj.
|
||||
func recvType(obj *types.Func) types.Type {
|
||||
return obj.Type().(*types.Signature).Recv().Type()
|
||||
}
|
||||
|
||||
// DefaultType returns the default "typed" type for an "untyped" type;
|
||||
// it returns the incoming type for all other types. The default type
|
||||
// for untyped nil is untyped nil.
|
||||
//
|
||||
// Exported to ssa/interp.
|
||||
//
|
||||
// TODO(gri): this is a copy of go/types.defaultType; export that function.
|
||||
//
|
||||
func DefaultType(typ types.Type) types.Type {
|
||||
if t, ok := typ.(*types.Basic); ok {
|
||||
k := t.Kind()
|
||||
switch k {
|
||||
case types.UntypedBool:
|
||||
k = types.Bool
|
||||
case types.UntypedInt:
|
||||
k = types.Int
|
||||
case types.UntypedRune:
|
||||
k = types.Rune
|
||||
case types.UntypedFloat:
|
||||
k = types.Float64
|
||||
case types.UntypedComplex:
|
||||
k = types.Complex128
|
||||
case types.UntypedString:
|
||||
k = types.String
|
||||
}
|
||||
typ = types.Typ[k]
|
||||
}
|
||||
return typ
|
||||
}
|
||||
|
||||
// logStack prints the formatted "start" message to stderr and
|
||||
// returns a closure that prints the corresponding "end" message.
|
||||
// Call using 'defer logStack(...)()' to show builder stack on panic.
|
||||
// Don't forget trailing parens!
|
||||
//
|
||||
func logStack(format string, args ...interface{}) func() {
|
||||
msg := fmt.Sprintf(format, args...)
|
||||
io.WriteString(os.Stderr, msg)
|
||||
io.WriteString(os.Stderr, "\n")
|
||||
return func() {
|
||||
io.WriteString(os.Stderr, msg)
|
||||
io.WriteString(os.Stderr, " end\n")
|
||||
}
|
||||
}
|
||||
|
||||
// newVar creates a 'var' for use in a types.Tuple.
|
||||
func newVar(name string, typ types.Type) *types.Var {
|
||||
return types.NewParam(token.NoPos, nil, name, typ)
|
||||
}
|
||||
|
||||
// anonVar creates an anonymous 'var' for use in a types.Tuple.
|
||||
func anonVar(typ types.Type) *types.Var {
|
||||
return newVar("", typ)
|
||||
}
|
||||
|
||||
var lenResults = types.NewTuple(anonVar(tInt))
|
||||
|
||||
// makeLen returns the len builtin specialized to type func(T)int.
|
||||
func makeLen(T types.Type) *Builtin {
|
||||
lenParams := types.NewTuple(anonVar(T))
|
||||
return &Builtin{
|
||||
name: "len",
|
||||
sig: types.NewSignature(nil, lenParams, lenResults, false),
|
||||
}
|
||||
}
|
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
package ssa
|
||||
|
||||
// This file defines synthesis of Functions that delegate to declared
|
||||
|
296
go/ssa/wrappers14.go
Normal file
296
go/ssa/wrappers14.go
Normal file
@ -0,0 +1,296 @@
|
||||
// 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.
|
||||
|
||||
// +build !go1.5
|
||||
|
||||
package ssa
|
||||
|
||||
// This file defines synthesis of Functions that delegate to declared
|
||||
// methods; they come in three kinds:
|
||||
//
|
||||
// (1) wrappers: methods that wrap declared methods, performing
|
||||
// implicit pointer indirections and embedded field selections.
|
||||
//
|
||||
// (2) thunks: funcs that wrap declared methods. Like wrappers,
|
||||
// thunks perform indirections and field selections. The thunk's
|
||||
// first parameter is used as the receiver for the method call.
|
||||
//
|
||||
// (3) bounds: funcs that wrap declared methods. The bound's sole
|
||||
// free variable, supplied by a closure, is used as the receiver
|
||||
// for the method call. No indirections or field selections are
|
||||
// performed since they can be done before the call.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/tools/go/types"
|
||||
)
|
||||
|
||||
// -- wrappers -----------------------------------------------------------
|
||||
|
||||
// makeWrapper returns a synthetic method that delegates to the
|
||||
// declared method denoted by meth.Obj(), first performing any
|
||||
// necessary pointer indirections or field selections implied by meth.
|
||||
//
|
||||
// The resulting method's receiver type is meth.Recv().
|
||||
//
|
||||
// This function is versatile but quite subtle! Consider the
|
||||
// following axes of variation when making changes:
|
||||
// - optional receiver indirection
|
||||
// - optional implicit field selections
|
||||
// - meth.Obj() may denote a concrete or an interface method
|
||||
// - the result may be a thunk or a wrapper.
|
||||
//
|
||||
// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu)
|
||||
//
|
||||
func makeWrapper(prog *Program, sel *types.Selection) *Function {
|
||||
obj := sel.Obj().(*types.Func) // the declared function
|
||||
sig := sel.Type().(*types.Signature) // type of this wrapper
|
||||
|
||||
var recv *types.Var // wrapper's receiver or thunk's params[0]
|
||||
name := obj.Name()
|
||||
var description string
|
||||
var start int // first regular param
|
||||
if sel.Kind() == types.MethodExpr {
|
||||
name += "$thunk"
|
||||
description = "thunk"
|
||||
recv = sig.Params().At(0)
|
||||
start = 1
|
||||
} else {
|
||||
description = "wrapper"
|
||||
recv = sig.Recv()
|
||||
}
|
||||
|
||||
description = fmt.Sprintf("%s for %s", description, sel.Obj())
|
||||
if prog.mode&LogSource != 0 {
|
||||
defer logStack("make %s to (%s)", description, recv.Type())()
|
||||
}
|
||||
fn := &Function{
|
||||
name: name,
|
||||
method: sel,
|
||||
object: obj,
|
||||
Signature: sig,
|
||||
Synthetic: description,
|
||||
Prog: prog,
|
||||
pos: obj.Pos(),
|
||||
}
|
||||
fn.startBody()
|
||||
fn.addSpilledParam(recv)
|
||||
createParams(fn, start)
|
||||
|
||||
indices := sel.Index()
|
||||
|
||||
var v Value = fn.Locals[0] // spilled receiver
|
||||
if isPointer(sel.Recv()) {
|
||||
v = emitLoad(fn, v)
|
||||
|
||||
// For simple indirection wrappers, perform an informative nil-check:
|
||||
// "value method (T).f called using nil *T pointer"
|
||||
if len(indices) == 1 && !isPointer(recvType(obj)) {
|
||||
var c Call
|
||||
c.Call.Value = &Builtin{
|
||||
name: "ssa:wrapnilchk",
|
||||
sig: types.NewSignature(nil,
|
||||
types.NewTuple(anonVar(sel.Recv()), anonVar(tString), anonVar(tString)),
|
||||
types.NewTuple(anonVar(sel.Recv())), false),
|
||||
}
|
||||
c.Call.Args = []Value{
|
||||
v,
|
||||
stringConst(deref(sel.Recv()).String()),
|
||||
stringConst(sel.Obj().Name()),
|
||||
}
|
||||
c.setType(v.Type())
|
||||
v = fn.emit(&c)
|
||||
}
|
||||
}
|
||||
|
||||
// Invariant: v is a pointer, either
|
||||
// value of *A receiver param, or
|
||||
// address of A spilled receiver.
|
||||
|
||||
// We use pointer arithmetic (FieldAddr possibly followed by
|
||||
// Load) in preference to value extraction (Field possibly
|
||||
// preceded by Load).
|
||||
|
||||
v = emitImplicitSelections(fn, v, indices[:len(indices)-1])
|
||||
|
||||
// Invariant: v is a pointer, either
|
||||
// value of implicit *C field, or
|
||||
// address of implicit C field.
|
||||
|
||||
var c Call
|
||||
if r := recvType(obj); !isInterface(r) { // concrete method
|
||||
if !isPointer(r) {
|
||||
v = emitLoad(fn, v)
|
||||
}
|
||||
c.Call.Value = prog.declaredFunc(obj)
|
||||
c.Call.Args = append(c.Call.Args, v)
|
||||
} else {
|
||||
c.Call.Method = obj
|
||||
c.Call.Value = emitLoad(fn, v)
|
||||
}
|
||||
for _, arg := range fn.Params[1:] {
|
||||
c.Call.Args = append(c.Call.Args, arg)
|
||||
}
|
||||
emitTailCall(fn, &c)
|
||||
fn.finishBody()
|
||||
return fn
|
||||
}
|
||||
|
||||
// createParams creates parameters for wrapper method fn based on its
|
||||
// Signature.Params, which do not include the receiver.
|
||||
// start is the index of the first regular parameter to use.
|
||||
//
|
||||
func createParams(fn *Function, start int) {
|
||||
var last *Parameter
|
||||
tparams := fn.Signature.Params()
|
||||
for i, n := start, tparams.Len(); i < n; i++ {
|
||||
last = fn.addParamObj(tparams.At(i))
|
||||
}
|
||||
if fn.Signature.Variadic() {
|
||||
last.typ = types.NewSlice(last.typ)
|
||||
}
|
||||
}
|
||||
|
||||
// -- bounds -----------------------------------------------------------
|
||||
|
||||
// makeBound returns a bound method wrapper (or "bound"), a synthetic
|
||||
// function that delegates to a concrete or interface method denoted
|
||||
// by obj. The resulting function has no receiver, but has one free
|
||||
// variable which will be used as the method's receiver in the
|
||||
// tail-call.
|
||||
//
|
||||
// Use MakeClosure with such a wrapper to construct a bound method
|
||||
// closure. e.g.:
|
||||
//
|
||||
// type T int or: type T interface { meth() }
|
||||
// func (t T) meth()
|
||||
// var t T
|
||||
// f := t.meth
|
||||
// f() // calls t.meth()
|
||||
//
|
||||
// f is a closure of a synthetic wrapper defined as if by:
|
||||
//
|
||||
// f := func() { return t.meth() }
|
||||
//
|
||||
// Unlike makeWrapper, makeBound need perform no indirection or field
|
||||
// selections because that can be done before the closure is
|
||||
// constructed.
|
||||
//
|
||||
// EXCLUSIVE_LOCKS_ACQUIRED(meth.Prog.methodsMu)
|
||||
//
|
||||
func makeBound(prog *Program, obj *types.Func) *Function {
|
||||
prog.methodsMu.Lock()
|
||||
defer prog.methodsMu.Unlock()
|
||||
fn, ok := prog.bounds[obj]
|
||||
if !ok {
|
||||
description := fmt.Sprintf("bound method wrapper for %s", obj)
|
||||
if prog.mode&LogSource != 0 {
|
||||
defer logStack("%s", description)()
|
||||
}
|
||||
fn = &Function{
|
||||
name: obj.Name() + "$bound",
|
||||
object: obj,
|
||||
Signature: changeRecv(obj.Type().(*types.Signature), nil), // drop receiver
|
||||
Synthetic: description,
|
||||
Prog: prog,
|
||||
pos: obj.Pos(),
|
||||
}
|
||||
|
||||
fv := &FreeVar{name: "recv", typ: recvType(obj), parent: fn}
|
||||
fn.FreeVars = []*FreeVar{fv}
|
||||
fn.startBody()
|
||||
createParams(fn, 0)
|
||||
var c Call
|
||||
|
||||
if !isInterface(recvType(obj)) { // concrete
|
||||
c.Call.Value = prog.declaredFunc(obj)
|
||||
c.Call.Args = []Value{fv}
|
||||
} else {
|
||||
c.Call.Value = fv
|
||||
c.Call.Method = obj
|
||||
}
|
||||
for _, arg := range fn.Params {
|
||||
c.Call.Args = append(c.Call.Args, arg)
|
||||
}
|
||||
emitTailCall(fn, &c)
|
||||
fn.finishBody()
|
||||
|
||||
prog.bounds[obj] = fn
|
||||
}
|
||||
return fn
|
||||
}
|
||||
|
||||
// -- thunks -----------------------------------------------------------
|
||||
|
||||
// makeThunk returns a thunk, a synthetic function that delegates to a
|
||||
// concrete or interface method denoted by sel.Obj(). The resulting
|
||||
// function has no receiver, but has an additional (first) regular
|
||||
// parameter.
|
||||
//
|
||||
// Precondition: sel.Kind() == types.MethodExpr.
|
||||
//
|
||||
// type T int or: type T interface { meth() }
|
||||
// func (t T) meth()
|
||||
// f := T.meth
|
||||
// var t T
|
||||
// f(t) // calls t.meth()
|
||||
//
|
||||
// f is a synthetic wrapper defined as if by:
|
||||
//
|
||||
// f := func(t T) { return t.meth() }
|
||||
//
|
||||
// TODO(adonovan): opt: currently the stub is created even when used
|
||||
// directly in a function call: C.f(i, 0). This is less efficient
|
||||
// than inlining the stub.
|
||||
//
|
||||
// EXCLUSIVE_LOCKS_ACQUIRED(meth.Prog.methodsMu)
|
||||
//
|
||||
func makeThunk(prog *Program, sel *types.Selection) *Function {
|
||||
if sel.Kind() != types.MethodExpr {
|
||||
panic(sel)
|
||||
}
|
||||
|
||||
key := selectionKey{
|
||||
kind: sel.Kind(),
|
||||
recv: sel.Recv(),
|
||||
obj: sel.Obj(),
|
||||
index: fmt.Sprint(sel.Index()),
|
||||
indirect: sel.Indirect(),
|
||||
}
|
||||
|
||||
prog.methodsMu.Lock()
|
||||
defer prog.methodsMu.Unlock()
|
||||
|
||||
// Canonicalize key.recv to avoid constructing duplicate thunks.
|
||||
canonRecv, ok := prog.canon.At(key.recv).(types.Type)
|
||||
if !ok {
|
||||
canonRecv = key.recv
|
||||
prog.canon.Set(key.recv, canonRecv)
|
||||
}
|
||||
key.recv = canonRecv
|
||||
|
||||
fn, ok := prog.thunks[key]
|
||||
if !ok {
|
||||
fn = makeWrapper(prog, sel)
|
||||
if fn.Signature.Recv() != nil {
|
||||
panic(fn) // unexpected receiver
|
||||
}
|
||||
prog.thunks[key] = fn
|
||||
}
|
||||
return fn
|
||||
}
|
||||
|
||||
func changeRecv(s *types.Signature, recv *types.Var) *types.Signature {
|
||||
return types.NewSignature(recv, s.Params(), s.Results(), s.Variadic())
|
||||
}
|
||||
|
||||
// selectionKey is like types.Selection but a usable map key.
|
||||
type selectionKey struct {
|
||||
kind types.SelectionKind
|
||||
recv types.Type // canonicalized via Program.canon
|
||||
obj types.Object
|
||||
index string
|
||||
indirect bool
|
||||
}
|
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
// Package analysis performs type and pointer analysis
|
||||
// and generates mark-up for the Go source view.
|
||||
//
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user