go.tools/pointer: inclusion-based pointer analysis for Go.
Suggested reading order: - doc.go - api.go, analysis.go, callgraph.go, labels.go - print.go, util.go - gen.go - solve.go - pointer_test.go, testdata/* - intrinsics.go (none are implemented yet) R=dannyb, gri, crawshaw, 0xjnml CC=golang-dev https://golang.org/cl/10618043
This commit is contained in:
parent
c8a6890a12
commit
6643abb26c
|
|
@ -0,0 +1,31 @@
|
|||
-*- text -*-
|
||||
|
||||
Pointer analysis to-do list
|
||||
===========================
|
||||
|
||||
CONSTRAINT GENERATION:
|
||||
- reflection intrinsics
|
||||
- unsafe.Pointer conversions. Three options:
|
||||
1) unsoundly (but type-safely) treat p=unsafe.Pointer(x) conversions as
|
||||
allocations, losing aliases. This is what's currently implemented.
|
||||
2) unsoundly (but type-safely) treat p=unsafe.Pointer(x) and T(p)
|
||||
conversions as interface boxing and unboxing operations.
|
||||
This may preserve some aliasing relations at little cost.
|
||||
3) soundly track physical field offsets. (Summarise dannyb's email here.)
|
||||
A downside is that we can't keep the identity field of struct
|
||||
allocations that identifies the object.
|
||||
|
||||
PRESOLVER OPTIMISATIONS
|
||||
- use HVN, HRU, LE, PE, HCD, LCD.
|
||||
|
||||
SOLVER:
|
||||
- use BDDs and/or sparse bitvectors for ptsets
|
||||
- use sparse bitvectors for graph edges
|
||||
- use BDD-based relational composition for e.g. offset computations.
|
||||
- experiment with different worklist algorithms:
|
||||
priority queue (solver visit-time order)
|
||||
red-black tree (node id order)
|
||||
double-ended queue (insertion order)
|
||||
fast doubly-linked list (See Zhanh et al PLDI'13)
|
||||
(insertion order with fast membership test)
|
||||
dannyb recommends sparse bitmap.
|
||||
|
|
@ -0,0 +1,255 @@
|
|||
package pointer
|
||||
|
||||
// This file defines the entry points into the pointer analysis.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/token"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"code.google.com/p/go.tools/go/types"
|
||||
"code.google.com/p/go.tools/ssa"
|
||||
)
|
||||
|
||||
// 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
|
||||
|
||||
// node.flags bitmask values.
|
||||
const (
|
||||
ntObject = 1 << iota // start of an object (addressable memory location)
|
||||
ntInterface // conctype node of interface object (=> ntObject)
|
||||
ntFunction // identity node of function object (=> ntObject)
|
||||
)
|
||||
|
||||
// A node is an equivalence class of memory locations.
|
||||
// Nodes may be pointers, pointed-to locations, neither, or both.
|
||||
type node struct {
|
||||
// flags is a bitset of the node type (nt*) flags defined above.
|
||||
flags uint32
|
||||
|
||||
// Number of following words belonging to the same "object" allocation.
|
||||
// (Set by endObject.) Zero for all other nodes.
|
||||
size uint32
|
||||
|
||||
// The type of the field denoted by this node. Non-aggregate,
|
||||
// unless this is an iface.conctype node (i.e. the thing
|
||||
// pointed to by an interface) in which case typ is that type.
|
||||
typ types.Type
|
||||
|
||||
// data holds additional attributes of this node, depending on
|
||||
// its flags.
|
||||
//
|
||||
// If ntObject is set, data is the ssa.Value of the
|
||||
// instruction that allocated this memory, or nil if it was
|
||||
// implicit.
|
||||
//
|
||||
// Special cases:
|
||||
// - If ntInterface is also set, data will be a *ssa.MakeInterface.
|
||||
// - If ntFunction is also set, this node is the first word of a
|
||||
// function block, and data is a *cgnode (not an ssa.Value)
|
||||
// representing this function.
|
||||
data interface{}
|
||||
|
||||
// subelement indicates which directly embedded subelement of
|
||||
// an object of aggregate type (struct, tuple, array) this is.
|
||||
subelement *fieldInfo // e.g. ".a.b[*].c"
|
||||
|
||||
// Points-to sets.
|
||||
pts nodeset // points-to set of this node
|
||||
prevPts nodeset // pts(n) in previous iteration (for difference propagation)
|
||||
|
||||
// Graph edges
|
||||
copyTo nodeset // simple copy constraint edges
|
||||
|
||||
// Complex constraints attached to this node (x).
|
||||
// - *loadConstraint y=*x
|
||||
// - *offsetAddrConstraint y=&x.f or y=&x[0]
|
||||
// - *storeConstraint *x=z
|
||||
// - *typeAssertConstraint y=x.(T)
|
||||
// - *invokeConstraint y=x.f(params...)
|
||||
complex constraintset
|
||||
}
|
||||
|
||||
type constraint interface {
|
||||
String() string
|
||||
|
||||
// Called by solver to prepare a constraint, e.g. to
|
||||
// - initialize a points-to set (addrConstraint).
|
||||
// - attach it to a pointer node (complex constraints).
|
||||
init(a *analysis)
|
||||
|
||||
// solve is called for complex constraints when the pts for
|
||||
// the node to which they are attached has changed.
|
||||
solve(a *analysis, n *node, delta nodeset)
|
||||
}
|
||||
|
||||
// dst = &src
|
||||
// pts(dst) ⊇ {src}
|
||||
// A base constraint used to initialize the solver's pt sets
|
||||
type addrConstraint struct {
|
||||
dst nodeid
|
||||
src nodeid
|
||||
}
|
||||
|
||||
// dst = src
|
||||
// A simple constraint represented directly as a copyTo graph edge.
|
||||
type copyConstraint struct {
|
||||
dst nodeid
|
||||
src nodeid
|
||||
}
|
||||
|
||||
// dst = src[offset]
|
||||
// A complex constraint attached to src (the pointer)
|
||||
type loadConstraint struct {
|
||||
offset uint32
|
||||
dst nodeid
|
||||
src nodeid
|
||||
}
|
||||
|
||||
// dst[offset] = src
|
||||
// A complex constraint attached to dst (the pointer)
|
||||
type storeConstraint struct {
|
||||
offset uint32
|
||||
dst nodeid
|
||||
src nodeid
|
||||
}
|
||||
|
||||
// dst = &src.f or dst = &src[0]
|
||||
// A complex constraint attached to dst (the pointer)
|
||||
type offsetAddrConstraint struct {
|
||||
offset uint32
|
||||
dst nodeid
|
||||
src nodeid
|
||||
}
|
||||
|
||||
// dst = src.(typ)
|
||||
// A complex constraint attached to src (the interface).
|
||||
type typeAssertConstraint struct {
|
||||
typ types.Type
|
||||
dst nodeid
|
||||
src nodeid
|
||||
}
|
||||
|
||||
// src.method(params...)
|
||||
// A complex constraint attached to iface.
|
||||
type invokeConstraint struct {
|
||||
method *types.Func // the abstract method
|
||||
iface nodeid // the interface
|
||||
params nodeid // the first parameter in the params/results block
|
||||
}
|
||||
|
||||
// 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()
|
||||
constraints []constraint // set of constraints
|
||||
callsites []*callsite // all callsites
|
||||
genq []*cgnode // queue of functions to generate constraints for
|
||||
intrinsics map[*ssa.Function]intrinsic // non-nil values are summaries for intrinsic fns
|
||||
reflectValueObj types.Object // type symbol for reflect.Value (if present)
|
||||
reflectRtypeObj types.Object // type symbol for reflect.rtype (if present)
|
||||
reflectRtype *types.Pointer // *reflect.rtype
|
||||
funcObj map[*ssa.Function]nodeid // default function object for each func
|
||||
probes map[*ssa.CallCommon]nodeid // maps call to print() to argument variable
|
||||
valNode map[ssa.Value]nodeid // node for each ssa.Value
|
||||
work worklist // solver's worklist
|
||||
}
|
||||
|
||||
func (a *analysis) warnf(pos token.Pos, format string, args ...interface{}) {
|
||||
if Warn := a.config.Warn; Warn != nil {
|
||||
Warn(pos, format, args...)
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "%s: warning: ", a.prog.Fset.Position(pos))
|
||||
fmt.Fprintf(os.Stderr, format, args...)
|
||||
fmt.Fprintln(os.Stderr)
|
||||
}
|
||||
}
|
||||
|
||||
// Analyze runs the pointer analysis with the scope and options
|
||||
// specified by config, and returns the (synthetic) root of the callgraph.
|
||||
//
|
||||
func Analyze(config *Config) CallGraphNode {
|
||||
a := &analysis{
|
||||
config: config,
|
||||
log: config.Log,
|
||||
prog: config.prog(),
|
||||
valNode: make(map[ssa.Value]nodeid),
|
||||
flattenMemo: make(map[types.Type][]*fieldInfo),
|
||||
intrinsics: make(map[*ssa.Function]intrinsic),
|
||||
funcObj: make(map[*ssa.Function]nodeid),
|
||||
probes: make(map[*ssa.CallCommon]nodeid),
|
||||
work: makeMapWorklist(),
|
||||
}
|
||||
|
||||
if reflect := a.prog.PackagesByPath["reflect"]; reflect != nil {
|
||||
a.reflectValueObj = reflect.Object.Scope().Lookup("Value")
|
||||
a.reflectRtypeObj = reflect.Object.Scope().Lookup("rtype")
|
||||
a.reflectRtype = types.NewPointer(a.reflectRtypeObj.Type())
|
||||
}
|
||||
|
||||
if false {
|
||||
a.log = os.Stderr // for debugging crashes; extremely verbose
|
||||
}
|
||||
|
||||
if a.log != nil {
|
||||
fmt.Fprintln(a.log, "======== NEW ANALYSIS ========")
|
||||
}
|
||||
|
||||
root := a.generate()
|
||||
|
||||
// ---------- Presolver ----------
|
||||
|
||||
// TODO(adonovan): opt: presolver optimisations.
|
||||
|
||||
// ---------- Solver ----------
|
||||
|
||||
a.solve()
|
||||
|
||||
if a.log != nil {
|
||||
// Dump solution.
|
||||
for i, n := range a.nodes {
|
||||
if n.pts != nil {
|
||||
fmt.Fprintf(a.log, "pts(n%d) = %s : %s\n", i, n.pts, n.typ)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Notify the client of the callsites if they're interested.
|
||||
if CallSite := a.config.CallSite; CallSite != nil {
|
||||
for _, site := range a.callsites {
|
||||
CallSite(site)
|
||||
}
|
||||
}
|
||||
|
||||
Call := a.config.Call
|
||||
for _, site := range a.callsites {
|
||||
for nid := range a.nodes[site.targets].pts {
|
||||
cgn := a.nodes[nid].data.(*cgnode)
|
||||
|
||||
// Notify the client of the call graph, if
|
||||
// they're interested.
|
||||
if Call != nil {
|
||||
Call(site, site.caller, cgn)
|
||||
}
|
||||
|
||||
// Warn about calls to non-intrinsic external functions.
|
||||
|
||||
if fn := cgn.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)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return root
|
||||
}
|
||||
|
|
@ -0,0 +1,232 @@
|
|||
package pointer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/token"
|
||||
"io"
|
||||
|
||||
"code.google.com/p/go.tools/go/types/typemap"
|
||||
"code.google.com/p/go.tools/ssa"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
// -------- Scope of the analysis --------
|
||||
|
||||
// Clients must provide the analysis with at least one package defining a main() function.
|
||||
Mains []*ssa.Package // set of 'main' packages to analyze
|
||||
root *ssa.Function // synthetic analysis root
|
||||
|
||||
// -------- Optional callbacks invoked by the analysis --------
|
||||
|
||||
// Call is invoked for each discovered call-graph edge. The
|
||||
// call-graph is a multigraph over CallGraphNodes with edges
|
||||
// labelled by the CallSite that gives rise to the edge.
|
||||
// Clients that wish to construct a call graph may provide
|
||||
// CallGraph.AddEdge here.
|
||||
//
|
||||
// The callgraph may be context-sensitive, i.e. it may
|
||||
// distinguish separate calls to the same function depending
|
||||
// on the context.
|
||||
//
|
||||
Call func(site CallSite, caller, callee CallGraphNode)
|
||||
|
||||
// CallSite is invoked for each call-site encountered in the
|
||||
// program.
|
||||
//
|
||||
// The callgraph may be context-sensitive, i.e. it may
|
||||
// distinguish separate calls to the same function depending
|
||||
// on the context.
|
||||
//
|
||||
CallSite func(site CallSite)
|
||||
|
||||
// Warn is invoked for each warning encountered by the analysis,
|
||||
// e.g. unknown external function, unsound use of unsafe.Pointer.
|
||||
// pos may be zero if the position is not known.
|
||||
Warn func(pos token.Pos, format string, args ...interface{})
|
||||
|
||||
// Print is invoked during the analysis for each discovered
|
||||
// call to the built-in print(x).
|
||||
//
|
||||
// Pointer p may be saved until the analysis is complete, at
|
||||
// which point its methods provide access to the analysis
|
||||
// (The result of callings its methods within the Print
|
||||
// callback is undefined.) p is nil if x is non-pointerlike.
|
||||
//
|
||||
// TODO(adonovan): this was a stop-gap measure for identifing
|
||||
// arbitrary expressions of interest in the tests. Now that
|
||||
// ssa.ValueForExpr exists, we should use that instead.
|
||||
//
|
||||
Print func(site *ssa.CallCommon, p Pointer)
|
||||
|
||||
// The client populates QueryValues with {v, nil} for each
|
||||
// ssa.Value v of interest. The pointer analysis will
|
||||
// populate the corresponding map value when it creates the
|
||||
// pointer variable for v. Upon completion the client can
|
||||
// inspect the map for the results.
|
||||
//
|
||||
// If a Value belongs to a function that the analysis treats
|
||||
// context-sensitively, the corresponding slice may have
|
||||
// multiple Pointers, one per distinct context.
|
||||
// Use PointsToCombined to merge them.
|
||||
//
|
||||
// TODO(adonovan): separate the keys set (input) from the
|
||||
// key/value associations (result) and perhaps return the
|
||||
// latter from Analyze().
|
||||
//
|
||||
QueryValues map[ssa.Value][]Pointer
|
||||
|
||||
// -------- Other configuration options --------
|
||||
|
||||
// If Log is non-nil, a log messages are written to it.
|
||||
// Logging is extremely verbose.
|
||||
Log io.Writer
|
||||
}
|
||||
|
||||
func (c *Config) prog() *ssa.Program {
|
||||
for _, main := range c.Mains {
|
||||
return main.Prog
|
||||
}
|
||||
panic("empty scope")
|
||||
}
|
||||
|
||||
// A Pointer is an equivalence class of pointerlike values.
|
||||
//
|
||||
// TODO(adonovan): add a method
|
||||
// Context() CallGraphNode
|
||||
// for pointers corresponding to local variables,
|
||||
//
|
||||
type Pointer interface {
|
||||
// PointsTo returns the points-to set of this pointer.
|
||||
PointsTo() PointsToSet
|
||||
|
||||
// MayAlias reports whether the receiver pointer may alias
|
||||
// the argument pointer.
|
||||
MayAlias(Pointer) bool
|
||||
|
||||
String() string
|
||||
}
|
||||
|
||||
// A PointsToSet is a set of labels (locations or allocations).
|
||||
//
|
||||
type PointsToSet interface {
|
||||
// PointsTo returns the set of labels that this points-to set
|
||||
// contains.
|
||||
Labels() []*Label
|
||||
|
||||
// Intersects reports whether this points-to set and the
|
||||
// argument points-to set contain common members.
|
||||
Intersects(PointsToSet) bool
|
||||
|
||||
// If this PointsToSet came from a Pointer of interface kind,
|
||||
// ConcreteTypes returns the set of concrete types the
|
||||
// interface may contain.
|
||||
//
|
||||
// The result is a mapping whose keys are the concrete types
|
||||
// to which this interface may point. For each pointer-like
|
||||
// key type, the corresponding map value is a set of pointer
|
||||
// abstractions of that concrete type, represented as a
|
||||
// []Pointer slice. Use PointsToCombined to merge them.
|
||||
ConcreteTypes() *typemap.M
|
||||
}
|
||||
|
||||
// Union returns the set containing all the elements of each set in sets.
|
||||
func Union(sets ...PointsToSet) PointsToSet {
|
||||
var union ptset
|
||||
for _, set := range sets {
|
||||
set := set.(ptset)
|
||||
union.a = set.a
|
||||
union.pts.addAll(set.pts)
|
||||
}
|
||||
return union
|
||||
}
|
||||
|
||||
// PointsToCombined returns the combined points-to set of all the
|
||||
// specified pointers.
|
||||
func PointsToCombined(ptrs []Pointer) PointsToSet {
|
||||
var ptsets []PointsToSet
|
||||
for _, ptr := range ptrs {
|
||||
ptsets = append(ptsets, ptr.PointsTo())
|
||||
}
|
||||
return Union(ptsets...)
|
||||
}
|
||||
|
||||
// ---- PointsToSet public interface
|
||||
|
||||
type ptset struct {
|
||||
a *analysis // may be nil if pts is nil
|
||||
pts nodeset
|
||||
}
|
||||
|
||||
func (s ptset) Labels() []*Label {
|
||||
var labels []*Label
|
||||
for l := range s.pts {
|
||||
// Scan back to the previous object start.
|
||||
for i := l; i >= 0; i-- {
|
||||
n := s.a.nodes[i]
|
||||
if n.flags&ntObject != 0 {
|
||||
// TODO(adonovan): do bounds-check against n.size.
|
||||
var v ssa.Value
|
||||
if n.flags&ntFunction != 0 {
|
||||
v = n.data.(*cgnode).fn
|
||||
} else {
|
||||
v = n.data.(ssa.Value)
|
||||
// TODO(adonovan): what if v is nil?
|
||||
}
|
||||
labels = append(labels, &Label{
|
||||
Value: v,
|
||||
subelement: s.a.nodes[l].subelement,
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return labels
|
||||
}
|
||||
|
||||
func (s ptset) ConcreteTypes() *typemap.M {
|
||||
var tmap typemap.M // default hasher // TODO(adonovan): opt: memoize per analysis
|
||||
for ifaceObjId := range s.pts {
|
||||
if s.a.nodes[ifaceObjId].flags&ntInterface == 0 {
|
||||
// ConcreteTypes called on non-interface PT set.
|
||||
continue // shouldn't happen
|
||||
}
|
||||
v, tconc := s.a.interfaceValue(ifaceObjId)
|
||||
prev, _ := tmap.At(tconc).([]Pointer)
|
||||
tmap.Set(tconc, append(prev, ptr{s.a, v}))
|
||||
}
|
||||
return &tmap
|
||||
}
|
||||
|
||||
func (x ptset) Intersects(y_ PointsToSet) bool {
|
||||
y := y_.(ptset)
|
||||
for l := range x.pts {
|
||||
if _, ok := y.pts[l]; ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ---- Pointer public interface
|
||||
|
||||
// ptr adapts a node to the Pointer interface.
|
||||
type ptr struct {
|
||||
a *analysis
|
||||
n nodeid // non-zero
|
||||
}
|
||||
|
||||
func (p ptr) String() string {
|
||||
return fmt.Sprintf("n%d", p.n)
|
||||
}
|
||||
|
||||
func (p ptr) PointsTo() PointsToSet {
|
||||
return ptset{p.a, p.a.nodes[p.n].pts}
|
||||
}
|
||||
|
||||
func (p ptr) MayAlias(q Pointer) bool {
|
||||
return p.PointsTo().Intersects(q.PointsTo())
|
||||
}
|
||||
|
||||
func (p ptr) ConcreteTypes() *typemap.M {
|
||||
return p.PointsTo().ConcreteTypes()
|
||||
}
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
package pointer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/token"
|
||||
|
||||
"code.google.com/p/go.tools/ssa"
|
||||
)
|
||||
|
||||
// TODO(adonovan): move the CallGraph, CallGraphNode, CallSite types
|
||||
// into a separate package 'callgraph', and make them pure interfaces
|
||||
// capable of supporting several implementations (context-sensitive
|
||||
// and insensitive PTA, RTA, etc).
|
||||
|
||||
// ---------- CallGraphNode ----------
|
||||
|
||||
// A CallGraphNode is a context-sensitive representation of a node in
|
||||
// the callgraph. In other words, there may be multiple nodes
|
||||
// representing a single *Function, depending on the contexts in which
|
||||
// it is called. The identity of the node is therefore important.
|
||||
//
|
||||
type CallGraphNode interface {
|
||||
Func() *ssa.Function // the function this node represents
|
||||
String() string // diagnostic description of this callgraph node
|
||||
}
|
||||
|
||||
type cgnode struct {
|
||||
fn *ssa.Function
|
||||
obj nodeid // start of this contour's object block
|
||||
}
|
||||
|
||||
func (n *cgnode) Func() *ssa.Function {
|
||||
return n.fn
|
||||
}
|
||||
|
||||
func (n *cgnode) String() string {
|
||||
return fmt.Sprintf("cg%d:%s", n.obj, n.fn)
|
||||
}
|
||||
|
||||
// ---------- CallSite ----------
|
||||
|
||||
// A CallSite is a context-sensitive representation of a function call
|
||||
// site in the program.
|
||||
//
|
||||
type CallSite interface {
|
||||
Caller() CallGraphNode // the enclosing context of this call
|
||||
Pos() token.Pos // source position; token.NoPos for synthetic calls
|
||||
Description() string // UI description of call kind; see (*ssa.CallCommon).Description
|
||||
String() string // diagnostic description of this callsite
|
||||
}
|
||||
|
||||
// A callsite represents a single function or method callsite within a
|
||||
// function. callsites never represent calls to built-ins; they are
|
||||
// handled as intrinsics.
|
||||
//
|
||||
type callsite struct {
|
||||
caller *cgnode // the origin of the call
|
||||
targets nodeid // pts(targets) contains identities of all called functions.
|
||||
instr ssa.CallInstruction // optional call instruction; provides IsInvoke, position, etc.
|
||||
pos token.Pos // position, if instr == nil, i.e. synthetic callsites.
|
||||
}
|
||||
|
||||
// Caller returns the node in the callgraph from which this call originated.
|
||||
func (c *callsite) Caller() CallGraphNode {
|
||||
return c.caller
|
||||
}
|
||||
|
||||
// Description returns a description of this kind of call, in the
|
||||
// manner of ssa.CallCommon.Description().
|
||||
//
|
||||
func (c *callsite) Description() string {
|
||||
if c.instr != nil {
|
||||
return c.instr.Common().Description()
|
||||
}
|
||||
return "synthetic function call"
|
||||
}
|
||||
|
||||
// Pos returns the source position of this callsite, or token.NoPos if implicit.
|
||||
func (c *callsite) Pos() token.Pos {
|
||||
if c.instr != nil {
|
||||
return c.instr.Pos()
|
||||
}
|
||||
return c.pos
|
||||
}
|
||||
|
||||
func (c *callsite) String() string {
|
||||
// TODO(adonovan): provide more info, e.g. target of static
|
||||
// call, arguments, location.
|
||||
return c.Description()
|
||||
}
|
||||
|
||||
// ---------- CallGraph ----------
|
||||
|
||||
// CallGraph is a forward directed graph of functions labelled by an
|
||||
// arbitrary site within the caller.
|
||||
//
|
||||
// CallGraph.AddEdge may be used as the Context.Call callback for
|
||||
// clients that wish to construct a call graph.
|
||||
//
|
||||
// TODO(adonovan): this is just a starting point. Add options to
|
||||
// control whether we record no callsite, an arbitrary callsite, or
|
||||
// all callsites for a given graph edge. Also, this could live in
|
||||
// another package since it's just a client utility.
|
||||
//
|
||||
type CallGraph map[CallGraphNode]map[CallGraphNode]CallSite
|
||||
|
||||
func (cg CallGraph) AddEdge(site CallSite, caller, callee CallGraphNode) {
|
||||
callees := cg[caller]
|
||||
if callees == nil {
|
||||
callees = make(map[CallGraphNode]CallSite)
|
||||
cg[caller] = callees
|
||||
}
|
||||
callees[callee] = site // save an arbitrary site
|
||||
}
|
||||
|
|
@ -0,0 +1,391 @@
|
|||
/*
|
||||
|
||||
Package pointer implements Andersen's analysis, an inclusion-based
|
||||
pointer analysis algorithm first described in (Andersen, 1994).
|
||||
|
||||
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 has a strict phase ordering: generation before solving.
|
||||
Only simple (copy) constraints may be generated during solving. 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.
|
||||
|
||||
We intend to add various presolving optimisations such as Pointer and
|
||||
Location Equivalence from (Hardekopf & Lin, SAS '07) and solver
|
||||
optimisatisions such as Hybrid- and Lazy- Cycle Detection from
|
||||
(Hardekopf & Lin, PLDI'07),
|
||||
|
||||
|
||||
TERMINOLOGY
|
||||
|
||||
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.
|
||||
|
||||
|
||||
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". Currently it's only used for
|
||||
struct{} or (). We generate all flow constraints, even for non-pointer
|
||||
types, with the expectations that (a) presolver optimisations will
|
||||
quickly collapse all the non-pointer ones and (b) we may get more
|
||||
precise results by treating uintptr as a possible pointer.
|
||||
|
||||
Each node represents a scalar (word-sized) 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
|
||||
|
||||
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 the ntObject flag, and its size indicates the extent of the
|
||||
object.
|
||||
|
||||
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 literals and conversions,
|
||||
e.g. []byte("foo"), []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 the respective
|
||||
objects are actual functions, maps, and channels. Given the way we
|
||||
model interfaces, they too are pointers to interface 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, types of objects
|
||||
are usually "off by one indirection".
|
||||
|
||||
The individual nodes of an object are sometimes referred to as
|
||||
"labels".
|
||||
|
||||
Objects containing no nodes (i.e. just empty structs; tuples may be
|
||||
values but never objects in Go) are padded with an invalid-type node
|
||||
to have at least one node so that there is something to point to.
|
||||
(TODO(adonovan): I think this is unnecessary now that we have identity
|
||||
nodes; check.)
|
||||
|
||||
|
||||
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.
|
||||
|
||||
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.Pointer conversions are not yet modelled as pointer
|
||||
conversions. Consequently uintptr is always a number and uintptr
|
||||
nodes do not point to any object.
|
||||
|
||||
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; flags ⊇ {ntFunction}; data:*cgnode
|
||||
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; its .data is the
|
||||
callgraph node (*cgnode) this object represents.
|
||||
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.
|
||||
|
||||
The following block of nodes represent the flattened-out types of
|
||||
the parameters and results of the function object, and are
|
||||
collectively known as its "P/R block".
|
||||
|
||||
The treatment of free variables of closures (*ssa.Capture) 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 interface objects.
|
||||
|
||||
An interface object has the following layout:
|
||||
|
||||
conctype -- flags ⊇ {ntInterface}; data:*ssa.MakeInterface?
|
||||
value
|
||||
...
|
||||
|
||||
The conctype node's typ field is the concrete type of the interface
|
||||
value which follows, flattened out. It has the ntInterface flag.
|
||||
Its associated data is the originating MakeInterface instruction, if
|
||||
any.
|
||||
|
||||
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 filter triggered by
|
||||
each interface object E added to pts(x). If T is an interface that
|
||||
E.conctype implements, pts(y) gets E. If T is a concrete type then
|
||||
edge pts(y) <- E.value is added.
|
||||
|
||||
ChangeInterface is a simple copy because the representation of
|
||||
interface 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 a contiguous P/R
|
||||
block for the callsite and adding a dynamic rule triggered by each
|
||||
interface object E 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.)
|
||||
|
||||
|
||||
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 currently the logical field offsets (plus one for
|
||||
the identity node), so the sizes of the fields can be ignored by the
|
||||
analysis.
|
||||
|
||||
Sound treatment of unsafe.Pointer conversions (not yet implemented)
|
||||
would require us to model memory layout using physical field offsets
|
||||
to ascertain which object field(s) might be aliased by a given
|
||||
FieldAddr of a different base pointer type. It would also require
|
||||
us to dispense with the identity node.
|
||||
|
||||
*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
|
||||
params block in the function object (this includes the receiver
|
||||
if the callee is a method);
|
||||
- creating copy edges from the results block in the function
|
||||
object to the value nodes for the result of the call.
|
||||
|
||||
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:
|
||||
|
||||
*fn->params = callargs
|
||||
callresult = *fn->results
|
||||
|
||||
(Recall that the function object's params and results 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 interface object that flows to the interface, we look up
|
||||
the concrete method, find its function object, and connect its P/R
|
||||
block to the callsite's P/R block.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
SOLVER
|
||||
|
||||
The solver is currently a very naive Andersen-style implementation,
|
||||
although it does use difference propagation (Pearce et al, SQC'04).
|
||||
There is much to do.
|
||||
|
||||
|
||||
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.
|
||||
|
||||
*/
|
||||
package pointer
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,276 @@
|
|||
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 (
|
||||
"code.google.com/p/go.tools/ssa"
|
||||
)
|
||||
|
||||
// Instances of 'intrinsic' generate analysis constraints for calls to
|
||||
// intrinsic functions.
|
||||
type intrinsic func(a *analysis, cgn *cgnode)
|
||||
|
||||
// Initialized in explicit init() to defeat (spurious) initialization
|
||||
// cycle error.
|
||||
var intrinsicsByName map[string]intrinsic
|
||||
|
||||
func init() {
|
||||
// Key strings are from Function.String().
|
||||
// That little dot ۰ is an Arabic zero numeral (U+06F0),
|
||||
// categories [Nd].
|
||||
intrinsicsByName = map[string]intrinsic{
|
||||
// reflect.Value methods.
|
||||
// "(reflect.Value).Addr": ext۰reflect۰Value۰Addr,
|
||||
"(reflect.Value).Bool": ext۰NoEffect,
|
||||
// "(reflect.Value).Bytes": ext۰reflect۰Value۰Bytes,
|
||||
// "(reflect.Value).Call": ext۰reflect۰Value۰Call,
|
||||
// "(reflect.Value).CallSlice": ext۰reflect۰Value۰CallSlice,
|
||||
"(reflect.Value).CanAddr": ext۰NoEffect,
|
||||
"(reflect.Value).CanInterface": ext۰NoEffect,
|
||||
"(reflect.Value).CanSet": ext۰NoEffect,
|
||||
"(reflect.Value).Cap": ext۰NoEffect,
|
||||
"(reflect.Value).Close": ext۰NoEffect,
|
||||
"(reflect.Value).Complex": ext۰NoEffect,
|
||||
// "(reflect.Value).Convert": ext۰reflect۰Value۰Convert,
|
||||
// "(reflect.Value).Elem": ext۰reflect۰Value۰Elem,
|
||||
// "(reflect.Value).Field": ext۰reflect۰Value۰Field,
|
||||
// "(reflect.Value).FieldByIndex": ext۰reflect۰Value۰FieldByIndex,
|
||||
// "(reflect.Value).FieldByName": ext۰reflect۰Value۰FieldByName,
|
||||
// "(reflect.Value).FieldByNameFunc": ext۰reflect۰Value۰FieldByNameFunc,
|
||||
"(reflect.Value).Float": ext۰NoEffect,
|
||||
// "(reflect.Value).Index": ext۰reflect۰Value۰Index,
|
||||
"(reflect.Value).Int": ext۰NoEffect,
|
||||
// "(reflect.Value).Interface": ext۰reflect۰Value۰Interface,
|
||||
"(reflect.Value).InterfaceData": ext۰NoEffect,
|
||||
"(reflect.Value).IsNil": ext۰NoEffect,
|
||||
"(reflect.Value).IsValid": ext۰NoEffect,
|
||||
"(reflect.Value).Kind": ext۰NoEffect,
|
||||
"(reflect.Value).Len": ext۰NoEffect,
|
||||
// "(reflect.Value).MapIndex": ext۰reflect۰Value۰MapIndex,
|
||||
// "(reflect.Value).MapKeys": ext۰reflect۰Value۰MapKeys,
|
||||
// "(reflect.Value).Method": ext۰reflect۰Value۰Method,
|
||||
// "(reflect.Value).MethodByName": ext۰reflect۰Value۰MethodByName,
|
||||
"(reflect.Value).NumField": ext۰NoEffect,
|
||||
"(reflect.Value).NumMethod": ext۰NoEffect,
|
||||
"(reflect.Value).OverflowComplex": ext۰NoEffect,
|
||||
"(reflect.Value).OverflowFloat": ext۰NoEffect,
|
||||
"(reflect.Value).OverflowInt": ext۰NoEffect,
|
||||
"(reflect.Value).OverflowUint": ext۰NoEffect,
|
||||
"(reflect.Value).Pointer": ext۰NoEffect,
|
||||
// "(reflect.Value).Set": ext۰reflect۰Value۰Set,
|
||||
"(reflect.Value).SetBool": ext۰NoEffect,
|
||||
// "(reflect.Value).SetBytes": ext۰reflect۰Value۰SetBytes,
|
||||
"(reflect.Value).SetComplex": ext۰NoEffect,
|
||||
"(reflect.Value).SetFloat": ext۰NoEffect,
|
||||
"(reflect.Value).SetInt": ext۰NoEffect,
|
||||
"(reflect.Value).SetLen": ext۰NoEffect,
|
||||
// "(reflect.Value).SetMapIndex": ext۰reflect۰Value۰SetMapIndex,
|
||||
// "(reflect.Value).SetPointer": ext۰reflect۰Value۰SetPointer,
|
||||
"(reflect.Value).SetString": ext۰NoEffect,
|
||||
"(reflect.Value).SetUint": ext۰NoEffect,
|
||||
// "(reflect.Value).Slice": ext۰reflect۰Value۰Slice,
|
||||
"(reflect.Value).String": ext۰NoEffect,
|
||||
"(reflect.Value).Type": ext۰NoEffect,
|
||||
"(reflect.Value).Uint": ext۰NoEffect,
|
||||
"(reflect.Value).UnsafeAddr": ext۰NoEffect,
|
||||
|
||||
// Standalone reflect.* functions.
|
||||
"reflect.Append": ext۰NotYetImplemented,
|
||||
"reflect.AppendSlice": ext۰NotYetImplemented,
|
||||
"reflect.Copy": ext۰NotYetImplemented,
|
||||
// "reflect.ChanOf": ext۰reflect۰ChanOf,
|
||||
"reflect.DeepEqual": ext۰NoEffect,
|
||||
// "reflect.Indirect": ext۰reflect۰Indirect,
|
||||
// "reflect.MakeChan": ext۰reflect۰MakeChan,
|
||||
"reflect.MakeFunc": ext۰NotYetImplemented,
|
||||
"reflect.MakeMap": ext۰NotYetImplemented,
|
||||
"reflect.MakeSlice": ext۰NotYetImplemented,
|
||||
"reflect.MapOf": ext۰NotYetImplemented,
|
||||
// "reflect.New": ext۰reflect۰New,
|
||||
// "reflect.NewAt": ext۰reflect۰NewAt,
|
||||
"reflect.PtrTo": ext۰NotYetImplemented,
|
||||
"reflect.Select": ext۰NotYetImplemented,
|
||||
"reflect.SliceOf": ext۰NotYetImplemented,
|
||||
// "reflect.TypeOf": ext۰reflect۰TypeOf,
|
||||
// "reflect.ValueOf": ext۰reflect۰ValueOf,
|
||||
// "reflect.Zero": ext۰reflect۰Zero,
|
||||
"reflect.init": ext۰NoEffect,
|
||||
|
||||
// *reflect.rtype methods
|
||||
"(*reflect.rtype).Align": ext۰NoEffect,
|
||||
"(*reflect.rtype).AssignableTo": ext۰NoEffect,
|
||||
"(*reflect.rtype).Bits": ext۰NoEffect,
|
||||
"(*reflect.rtype).ChanDir": ext۰NoEffect,
|
||||
"(*reflect.rtype).ConvertibleTo": ext۰NoEffect,
|
||||
// "(*reflect.rtype).Elem": ext۰reflect۰rtype۰Elem,
|
||||
"(*reflect.rtype).Field": ext۰NotYetImplemented,
|
||||
"(*reflect.rtype).FieldAlign": ext۰NoEffect,
|
||||
// "(*reflect.rtype).FieldByIndex": ext۰reflect۰rtype۰FieldByIndex,
|
||||
// "(*reflect.rtype).FieldByName": ext۰reflect۰rtype۰FieldByName,
|
||||
// "(*reflect.rtype).FieldByNameFunc": ext۰reflect۰rtype۰FieldByNameFunc,
|
||||
"(*reflect.rtype).Implements": ext۰NoEffect,
|
||||
// "(*reflect.rtype).In": ext۰reflect۰rtype۰In,
|
||||
"(*reflect.rtype).IsVariadic": ext۰NoEffect,
|
||||
// "(*reflect.rtype).Key": ext۰reflect۰rtype۰Key,
|
||||
"(*reflect.rtype).Kind": ext۰NoEffect,
|
||||
"(*reflect.rtype).Len": ext۰NoEffect,
|
||||
"(*reflect.rtype).Method": ext۰NotYetImplemented,
|
||||
"(*reflect.rtype).MethodByName": ext۰NotYetImplemented,
|
||||
"(*reflect.rtype).Name": ext۰NoEffect,
|
||||
"(*reflect.rtype).NumField": ext۰NoEffect,
|
||||
"(*reflect.rtype).NumIn": ext۰NoEffect,
|
||||
"(*reflect.rtype).NumMethod": ext۰NoEffect,
|
||||
"(*reflect.rtype).NumOut": ext۰NoEffect,
|
||||
// "(*reflect.rtype).Out": ext۰reflect۰rtype۰Out,
|
||||
"(*reflect.rtype).PkgPath": ext۰NoEffect,
|
||||
"(*reflect.rtype).Size": ext۰NoEffect,
|
||||
"(*reflect.rtype).String": ext۰NoEffect,
|
||||
|
||||
// Other packages.
|
||||
"bytes.Equal": ext۰NoEffect,
|
||||
"bytes.IndexByte": 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,
|
||||
"os.epipecheck": ext۰NoEffect,
|
||||
"runtime.BlockProfile": ext۰NoEffect,
|
||||
"runtime.Breakpoint": ext۰NoEffect,
|
||||
"runtime.CPUProfile": ext۰NotYetImplemented,
|
||||
"runtime.Caller": ext۰NoEffect,
|
||||
"runtime.FuncForPC": ext۰NotYetImplemented,
|
||||
"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۰NotYetImplemented,
|
||||
"runtime.Stack": ext۰NoEffect,
|
||||
"runtime.ThreadCreateProfile": 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_Syncsemcheck": ext۰NoEffect,
|
||||
"sync/atomic.AddInt32": ext۰NoEffect,
|
||||
"sync/atomic.CompareAndSwapInt32": ext۰NoEffect,
|
||||
"sync/atomic.LoadInt32": ext۰NoEffect,
|
||||
"sync/atomic.LoadUint32": ext۰NoEffect,
|
||||
"sync/atomic.StoreInt32": ext۰NoEffect,
|
||||
"sync/atomic.StoreUint32": 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.Syscall": ext۰NoEffect,
|
||||
"syscall.Syscall6": ext۰NoEffect,
|
||||
"time.Sleep": ext۰NoEffect,
|
||||
"time.now": ext۰NoEffect,
|
||||
"time.startTimer": ext۰NoEffect,
|
||||
"time.stopTimer": ext۰NoEffect,
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
// Ensure all "reflect" code is treated intrinsically.
|
||||
if impl == nil && fn.Pkg != nil && a.reflectValueObj != nil && a.reflectValueObj.Pkg() == fn.Pkg.Object {
|
||||
impl = ext۰NotYetImplemented
|
||||
}
|
||||
|
||||
a.intrinsics[fn] = impl
|
||||
}
|
||||
return impl
|
||||
}
|
||||
|
||||
// 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.
|
||||
//
|
||||
// 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) {
|
||||
// TODO(adonovan): enable this warning when we've implemented
|
||||
// enough that it's not unbearably annoying.
|
||||
// a.warnf(fn.Pos(), "unsound: intrinsic treatment of %s not yet implemented", fn)
|
||||
}
|
||||
|
||||
// We'll model reflect.Value as an interface{} containing pointers.
|
||||
// We must use pointers since some reflect.Values (those derived by
|
||||
// Field, Elem, etc) are abstractions of lvalues, not rvalues, and
|
||||
// mutations via Set are reflected in the underlying value. (We could
|
||||
// represent it as a union of lvalue and rvalue but that's more work.)
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
package pointer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/token"
|
||||
"strings"
|
||||
|
||||
"code.google.com/p/go.tools/ssa"
|
||||
)
|
||||
|
||||
// A Label is an abstract location or an instruction that allocates memory.
|
||||
// A points-to set is (conceptually) a set of labels.
|
||||
//
|
||||
// (This is basically a pair of a Value that allocates an object and a
|
||||
// subelement indicator within that object.)
|
||||
//
|
||||
// TODO(adonovan): local labels should include their context (CallGraphNode).
|
||||
//
|
||||
type Label struct {
|
||||
Value ssa.Value
|
||||
subelement *fieldInfo // e.g. ".a.b[*].c"
|
||||
}
|
||||
|
||||
func (l *Label) Pos() token.Pos {
|
||||
if l.Value != nil {
|
||||
return l.Value.Pos()
|
||||
}
|
||||
return token.NoPos
|
||||
}
|
||||
|
||||
func (l *Label) String() string {
|
||||
var s string
|
||||
switch v := l.Value.(type) {
|
||||
case *ssa.Function, *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 interfaces objects may be allocated
|
||||
// synthetically (so no *MakeInterface data).
|
||||
s = "makeinterface:" + v.X.Type().String()
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("unhandled Label.Value type: %T", v))
|
||||
}
|
||||
|
||||
return s + l.subelement.path()
|
||||
}
|
||||
|
|
@ -0,0 +1,567 @@
|
|||
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"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"code.google.com/p/go.tools/go/types"
|
||||
"code.google.com/p/go.tools/go/types/typemap"
|
||||
"code.google.com/p/go.tools/importer"
|
||||
"code.google.com/p/go.tools/pointer"
|
||||
"code.google.com/p/go.tools/ssa"
|
||||
)
|
||||
|
||||
var inputs = []string{
|
||||
// Currently debugging:
|
||||
// "testdata/tmp.go",
|
||||
|
||||
// Working:
|
||||
"testdata/another.go",
|
||||
"testdata/arrays.go",
|
||||
"testdata/channels.go",
|
||||
"testdata/context.go",
|
||||
"testdata/conv.go",
|
||||
"testdata/flow.go",
|
||||
"testdata/fmtexcerpt.go",
|
||||
"testdata/func.go",
|
||||
"testdata/hello.go",
|
||||
"testdata/interfaces.go",
|
||||
"testdata/maps.go",
|
||||
"testdata/panic.go",
|
||||
"testdata/recur.go",
|
||||
"testdata/structs.go",
|
||||
"testdata/a_test.go",
|
||||
|
||||
// TODO(adonovan): get these tests (of reflection) passing.
|
||||
// (The tests are mostly sound since they were used for a
|
||||
// previous implementation.)
|
||||
// "testdata/funcreflect.go",
|
||||
// "testdata/arrayreflect.go",
|
||||
// "testdata/chanreflect.go",
|
||||
// "testdata/finalizer.go",
|
||||
// "testdata/reflect.go",
|
||||
// "testdata/mapreflect.go",
|
||||
// "testdata/structreflect.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, @concrete, below.
|
||||
//
|
||||
// @concrete t | u | v
|
||||
//
|
||||
// A 'concrete' 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.
|
||||
//
|
||||
// A 'concrete' 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 concrete 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" | "concrete" | "calls" | "warning"
|
||||
filename string
|
||||
linenum int // source line number, 1-based
|
||||
args []string
|
||||
types []types.Type // for concrete
|
||||
}
|
||||
|
||||
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 == "concrete"
|
||||
}
|
||||
|
||||
// A record of a call to the built-in print() function. Used for testing.
|
||||
type probe struct {
|
||||
instr *ssa.CallCommon
|
||||
arg0 pointer.Pointer // first argument to print
|
||||
}
|
||||
|
||||
// Find probe (call to print(x)) of same source
|
||||
// file/line as expectation.
|
||||
func findProbe(prog *ssa.Program, probes []probe, e *expectation) *probe {
|
||||
for _, p := range probes {
|
||||
pos := prog.Fset.Position(p.instr.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 &p
|
||||
}
|
||||
}
|
||||
return nil // e.g. analysis didn't reach this call
|
||||
}
|
||||
|
||||
func doOneInput(input, filename string) bool {
|
||||
impctx := &importer.Config{Loader: importer.MakeGoBuildLoader(nil)}
|
||||
imp := importer.New(impctx)
|
||||
|
||||
// Parsing.
|
||||
f, err := parser.ParseFile(imp.Fset, filename, input, parser.DeclarationErrors)
|
||||
if err != nil {
|
||||
// TODO(adonovan): err is a scanner error list;
|
||||
// display all errors not just first?
|
||||
fmt.Println(err.Error())
|
||||
return false
|
||||
}
|
||||
|
||||
// Type checking.
|
||||
info := imp.CreateSourcePackage("main", []*ast.File{f})
|
||||
if info.Err != nil {
|
||||
fmt.Println(info.Err.Error())
|
||||
return false
|
||||
}
|
||||
|
||||
// SSA creation + building.
|
||||
prog := ssa.NewProgram(imp.Fset, ssa.SanityCheckFunctions)
|
||||
for _, info := range imp.Packages {
|
||||
prog.CreatePackage(info)
|
||||
}
|
||||
prog.BuildAll()
|
||||
|
||||
mainpkg := prog.Package(info.Pkg)
|
||||
ptrmain := mainpkg // main package for the pointer analysis
|
||||
if mainpkg.Func("main") == nil {
|
||||
// No main function; assume it's a test.
|
||||
ptrmain = prog.CreateTestMainPackage([]*ssa.Package{mainpkg})
|
||||
fmt.Printf("%s: synthesized testmain package for test.\n", imp.Fset.Position(f.Package))
|
||||
}
|
||||
|
||||
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 "concrete":
|
||||
for _, typstr := range split(rest, "|") {
|
||||
var t types.Type = types.Typ[types.Invalid] // means "..."
|
||||
if typstr != "..." {
|
||||
texpr, err := parser.ParseExpr(typstr)
|
||||
if err != nil {
|
||||
ok = false
|
||||
// Don't print err since its location is bad.
|
||||
e.errorf("'%s' is not a valid type", typstr)
|
||||
continue
|
||||
}
|
||||
t, _, err = types.EvalNode(imp.Fset, texpr, mainpkg.Object, mainpkg.Object.Scope())
|
||||
if err != nil {
|
||||
ok = false
|
||||
// TODO Don't print err since its location is bad.
|
||||
e.errorf("'%s' is not a valid type: %s", typstr, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
e.types = append(e.types, t)
|
||||
}
|
||||
|
||||
case "calls":
|
||||
e.args = split(rest, "->")
|
||||
// TODO(adonovan): eagerly reject the
|
||||
// expectation if fn doesn't denote
|
||||
// existing function, rather than fail
|
||||
// the expectation after analysis.
|
||||
if len(e.args) != 2 {
|
||||
ok = false
|
||||
e.errorf("@calls expectation wants 'caller -> callee' arguments")
|
||||
continue
|
||||
}
|
||||
|
||||
case "warning":
|
||||
lit, err := strconv.Unquote(strings.TrimSpace(rest))
|
||||
if err != nil {
|
||||
ok = false
|
||||
e.errorf("couldn't parse @warning operand: %s", err.Error())
|
||||
continue
|
||||
}
|
||||
e.args = append(e.args, lit)
|
||||
|
||||
default:
|
||||
ok = false
|
||||
e.errorf("unknown expectation kind: %s", e)
|
||||
continue
|
||||
}
|
||||
exps = append(exps, e)
|
||||
}
|
||||
}
|
||||
|
||||
var probes []probe
|
||||
var warnings []string
|
||||
var log bytes.Buffer
|
||||
|
||||
callgraph := make(pointer.CallGraph)
|
||||
|
||||
// Run the analysis.
|
||||
config := &pointer.Config{
|
||||
Mains: []*ssa.Package{ptrmain},
|
||||
Log: &log,
|
||||
Print: func(site *ssa.CallCommon, p pointer.Pointer) {
|
||||
probes = append(probes, probe{site, p})
|
||||
},
|
||||
Call: callgraph.AddEdge,
|
||||
Warn: func(pos token.Pos, format string, args ...interface{}) {
|
||||
msg := fmt.Sprintf(format, args...)
|
||||
fmt.Printf("%s: warning: %s\n", prog.Fset.Position(pos), msg)
|
||||
warnings = append(warnings, msg)
|
||||
},
|
||||
}
|
||||
pointer.Analyze(config)
|
||||
|
||||
// Print the log is there was an error or a panic.
|
||||
complete := false
|
||||
defer func() {
|
||||
if !complete || !ok {
|
||||
log.WriteTo(os.Stderr)
|
||||
}
|
||||
}()
|
||||
|
||||
// Check the expectations.
|
||||
for _, e := range exps {
|
||||
var pr *probe
|
||||
if e.needsProbe() {
|
||||
if pr = findProbe(prog, probes, e); pr == nil {
|
||||
ok = false
|
||||
e.errorf("unreachable print() statement has expectation %s", e)
|
||||
continue
|
||||
}
|
||||
if pr.arg0 == nil {
|
||||
ok = false
|
||||
e.errorf("expectation on non-pointerlike operand: %s", pr.instr.Args[0].Type())
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
switch e.kind {
|
||||
case "pointsto":
|
||||
if !checkPointsToExpectation(e, pr, lineMapping, prog) {
|
||||
ok = false
|
||||
}
|
||||
|
||||
case "concrete":
|
||||
if !checkConcreteExpectation(e, pr) {
|
||||
ok = false
|
||||
}
|
||||
|
||||
case "calls":
|
||||
if !checkCallsExpectation(prog, e, callgraph) {
|
||||
ok = false
|
||||
}
|
||||
|
||||
case "warning":
|
||||
if !checkWarningExpectation(prog, e, 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.
|
||||
switch l.Value.(type) {
|
||||
case *ssa.Function, *ssa.Global:
|
||||
return l.String()
|
||||
}
|
||||
|
||||
str := l.String()
|
||||
if pos := l.Value.Pos(); pos != 0 {
|
||||
// Append the position, using a @line tag instead of a line number, if defined.
|
||||
posn := prog.Fset.Position(l.Value.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, pr *probe, lineMapping map[string]string, prog *ssa.Program) bool {
|
||||
expected := make(map[string]struct{})
|
||||
surplus := make(map[string]struct{})
|
||||
exact := true
|
||||
for _, g := range e.args {
|
||||
if g == "..." {
|
||||
exact = false
|
||||
continue
|
||||
}
|
||||
expected[g] = struct{}{}
|
||||
}
|
||||
// Find the set of labels that the probe's
|
||||
// argument (x in print(x)) may point to.
|
||||
for _, label := range pr.arg0.PointsTo().Labels() {
|
||||
name := labelString(label, lineMapping, prog)
|
||||
if _, ok := expected[name]; ok {
|
||||
delete(expected, name)
|
||||
} else if exact {
|
||||
surplus[name] = struct{}{}
|
||||
}
|
||||
}
|
||||
// Report set difference:
|
||||
ok := true
|
||||
if len(expected) > 0 {
|
||||
ok = false
|
||||
e.errorf("value does not alias these expected labels: %s", join(expected))
|
||||
}
|
||||
if len(surplus) > 0 {
|
||||
ok = false
|
||||
e.errorf("value may additionally alias these labels: %s", join(surplus))
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
// underlying returns the underlying type of typ. Copied from go/types.
|
||||
func underlyingType(typ types.Type) types.Type {
|
||||
if typ, ok := typ.(*types.Named); ok {
|
||||
return typ.Underlying() // underlying types are never NamedTypes
|
||||
}
|
||||
if typ == nil {
|
||||
panic("underlying(nil)")
|
||||
}
|
||||
return typ
|
||||
}
|
||||
|
||||
func checkConcreteExpectation(e *expectation, pr *probe) bool {
|
||||
var expected typemap.M
|
||||
var surplus typemap.M
|
||||
exact := true
|
||||
for _, g := range e.types {
|
||||
if g == types.Typ[types.Invalid] {
|
||||
exact = false
|
||||
continue
|
||||
}
|
||||
expected.Set(g, struct{}{})
|
||||
}
|
||||
|
||||
switch t := underlyingType(pr.instr.Args[0].Type()).(type) {
|
||||
case *types.Interface:
|
||||
// ok
|
||||
default:
|
||||
e.errorf("@concrete expectation requires an interface-typed operand, got %s", t)
|
||||
return false
|
||||
}
|
||||
|
||||
// Find the set of concrete types that the probe's
|
||||
// argument (x in print(x)) may contain.
|
||||
for _, conc := range pr.arg0.PointsTo().ConcreteTypes().Keys() {
|
||||
if expected.At(conc) != nil {
|
||||
expected.Delete(conc)
|
||||
} else if exact {
|
||||
surplus.Set(conc, struct{}{})
|
||||
}
|
||||
}
|
||||
// Report set difference:
|
||||
ok := true
|
||||
if expected.Len() > 0 {
|
||||
ok = false
|
||||
e.errorf("interface cannot contain these concrete types: %s", expected.KeysString())
|
||||
}
|
||||
if surplus.Len() > 0 {
|
||||
ok = false
|
||||
e.errorf("interface may additionally contain these concrete types: %s", surplus.KeysString())
|
||||
}
|
||||
return ok
|
||||
return false
|
||||
}
|
||||
|
||||
func checkCallsExpectation(prog *ssa.Program, e *expectation, callgraph pointer.CallGraph) bool {
|
||||
// TODO(adonovan): this is inefficient and not robust against
|
||||
// typos. Better to convert strings to *Functions during
|
||||
// expectation parsing (somehow).
|
||||
for caller, callees := range callgraph {
|
||||
if caller.Func().String() == e.args[0] {
|
||||
found := make(map[string]struct{})
|
||||
for callee := range callees {
|
||||
s := callee.Func().String()
|
||||
found[s] = struct{}{}
|
||||
if s == e.args[1] {
|
||||
return true // expectation satisfied
|
||||
}
|
||||
}
|
||||
e.errorf("found no call from %s to %s, but only to %s",
|
||||
e.args[0], e.args[1], join(found))
|
||||
return false
|
||||
}
|
||||
}
|
||||
e.errorf("didn't find any calls from %s", e.args[0])
|
||||
return false
|
||||
}
|
||||
|
||||
func checkWarningExpectation(prog *ssa.Program, e *expectation, warnings []string) 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 _, warning := range warnings {
|
||||
if re.MatchString(warning) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
e.errorf("@warning %s expectation not satised; found these warnings though:", strconv.Quote(e.args[0]))
|
||||
for _, warning := range warnings {
|
||||
fmt.Println("\t", warning)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func TestInput(t *testing.T) {
|
||||
ok := true
|
||||
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Errorf("os.Getwd: %s", err.Error())
|
||||
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.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
if !doOneInput(string(content), filename) {
|
||||
ok = false
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
// join joins the elements of set with " | "s.
|
||||
func join(set map[string]struct{}) string {
|
||||
var buf bytes.Buffer
|
||||
sep := ""
|
||||
for name := range set {
|
||||
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
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package pointer
|
||||
|
||||
import "fmt"
|
||||
|
||||
func (c *addrConstraint) String() string {
|
||||
return fmt.Sprintf("addr n%d <- {&n%d}", c.dst, c.src)
|
||||
}
|
||||
|
||||
func (c *copyConstraint) String() string {
|
||||
return fmt.Sprintf("copy n%d <- n%d", c.dst, c.src)
|
||||
}
|
||||
|
||||
func (c *loadConstraint) String() string {
|
||||
return fmt.Sprintf("load n%d <- n%d[%d]", c.dst, c.src, c.offset)
|
||||
}
|
||||
|
||||
func (c *storeConstraint) String() string {
|
||||
return fmt.Sprintf("store n%d[%d] <- n%d", c.dst, c.offset, c.src)
|
||||
}
|
||||
|
||||
func (c *offsetAddrConstraint) String() string {
|
||||
return fmt.Sprintf("offsetAddr n%d <- n%d.#%d", c.dst, c.src, c.offset)
|
||||
}
|
||||
|
||||
func (c *typeAssertConstraint) String() string {
|
||||
return fmt.Sprintf("typeAssert n%d <- n%d.(%s)", c.dst, c.src, c.typ)
|
||||
}
|
||||
|
||||
func (c *invokeConstraint) String() string {
|
||||
return fmt.Sprintf("invoke n%d.%s(n%d ...)", c.iface, c.method.Name(), c.params+1)
|
||||
}
|
||||
|
||||
func (n nodeid) String() string {
|
||||
return fmt.Sprintf("n%d", n)
|
||||
}
|
||||
|
|
@ -0,0 +1,262 @@
|
|||
package pointer
|
||||
|
||||
// This file defines a naive Andersen-style solver for the inclusion
|
||||
// constraint system.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"code.google.com/p/go.tools/go/types"
|
||||
)
|
||||
|
||||
func (a *analysis) solve() {
|
||||
// Initialize points-to sets and complex constraint sets.
|
||||
for _, c := range a.constraints {
|
||||
c.init(a)
|
||||
}
|
||||
a.constraints = nil // aid GC
|
||||
|
||||
work := a.work
|
||||
|
||||
// Now we've initialized all constraints, we populate the
|
||||
// worklist with nodes that point to something initially (due
|
||||
// to addrConstraints) and have other constraints attached.
|
||||
for id, n := range a.nodes {
|
||||
if len(n.pts) > 0 && (n.copyTo != nil || n.complex != nil) {
|
||||
if a.log != nil {
|
||||
fmt.Fprintf(a.log, "Adding to worklist n%d\n", id)
|
||||
}
|
||||
a.addWork(nodeid(id))
|
||||
}
|
||||
}
|
||||
work.swap()
|
||||
|
||||
// Solver main loop.
|
||||
for round := 1; ; round++ {
|
||||
if work.swap() {
|
||||
if a.log != nil {
|
||||
fmt.Fprintf(a.log, "Solving, round %d\n", round)
|
||||
}
|
||||
|
||||
// Next iteration.
|
||||
if work.empty() {
|
||||
break // done
|
||||
}
|
||||
}
|
||||
|
||||
id := work.take()
|
||||
n := a.nodes[id]
|
||||
|
||||
if a.log != nil {
|
||||
fmt.Fprintf(a.log, "\tnode n%d\n", id)
|
||||
}
|
||||
|
||||
// Difference propagation.
|
||||
delta := n.pts.diff(n.prevPts)
|
||||
if delta == nil {
|
||||
continue
|
||||
}
|
||||
n.prevPts = n.pts.clone()
|
||||
|
||||
// Process complex constraints dependent on n.
|
||||
for c := range n.complex {
|
||||
if a.log != nil {
|
||||
fmt.Fprintf(a.log, "\t\tconstraint %s\n", c)
|
||||
}
|
||||
c.solve(a, n, delta)
|
||||
}
|
||||
|
||||
// Process copy constraints.
|
||||
var copySeen nodeset
|
||||
for mid := range n.copyTo {
|
||||
if copySeen.add(mid) {
|
||||
if a.nodes[mid].pts.addAll(delta) {
|
||||
a.addWork(mid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if a.log != nil {
|
||||
fmt.Fprintf(a.log, "\t\tpts(n%d) = %s\n", id, n.pts)
|
||||
}
|
||||
}
|
||||
|
||||
if a.log != nil {
|
||||
fmt.Fprintf(a.log, "Solver done\n")
|
||||
}
|
||||
}
|
||||
|
||||
func (a *analysis) addWork(id nodeid) {
|
||||
a.work.add(id)
|
||||
if a.log != nil {
|
||||
fmt.Fprintf(a.log, "\t\twork: n%d\n", id)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *addrConstraint) init(a *analysis) {
|
||||
a.nodes[c.dst].pts.add(c.src)
|
||||
}
|
||||
func (c *copyConstraint) init(a *analysis) {
|
||||
a.nodes[c.src].copyTo.add(c.dst)
|
||||
}
|
||||
|
||||
// Complex constraints attach themselves to the relevant pointer node.
|
||||
|
||||
func (c *storeConstraint) init(a *analysis) {
|
||||
a.nodes[c.dst].complex.add(c)
|
||||
}
|
||||
func (c *loadConstraint) init(a *analysis) {
|
||||
a.nodes[c.src].complex.add(c)
|
||||
}
|
||||
func (c *offsetAddrConstraint) init(a *analysis) {
|
||||
a.nodes[c.src].complex.add(c)
|
||||
}
|
||||
func (c *typeAssertConstraint) init(a *analysis) {
|
||||
a.nodes[c.src].complex.add(c)
|
||||
}
|
||||
func (c *invokeConstraint) init(a *analysis) {
|
||||
a.nodes[c.iface].complex.add(c)
|
||||
}
|
||||
|
||||
// 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.copyTo.add(dst) {
|
||||
if a.log != nil {
|
||||
fmt.Fprintf(a.log, "\t\t\tdynamic copy n%d <- n%d\n", dst, src)
|
||||
}
|
||||
return a.nodes[dst].pts.addAll(nsrc.pts)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Returns sizeof.
|
||||
// Implicitly adds nodes to worklist.
|
||||
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, n *node, delta nodeset) {
|
||||
var changed bool
|
||||
for k := range delta {
|
||||
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, n *node, delta nodeset) {
|
||||
for k := range delta {
|
||||
koff := k + nodeid(c.offset)
|
||||
if a.onlineCopy(koff, c.src) {
|
||||
a.addWork(koff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *offsetAddrConstraint) solve(a *analysis, n *node, delta nodeset) {
|
||||
dst := a.nodes[c.dst]
|
||||
for k := range delta {
|
||||
if dst.pts.add(k + nodeid(c.offset)) {
|
||||
a.addWork(c.dst)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *typeAssertConstraint) solve(a *analysis, n *node, delta nodeset) {
|
||||
tIface, _ := c.typ.Underlying().(*types.Interface)
|
||||
|
||||
for ifaceObj := range delta {
|
||||
ifaceValue, tConc := a.interfaceValue(ifaceObj)
|
||||
|
||||
if tIface != nil {
|
||||
if types.IsAssignableTo(tConc, tIface) {
|
||||
if a.nodes[c.dst].pts.add(ifaceObj) {
|
||||
a.addWork(c.dst)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if types.IsIdentical(tConc, c.typ) {
|
||||
// Copy entire payload to dst.
|
||||
//
|
||||
// TODO(adonovan): opt: if tConc is
|
||||
// nonpointerlike we can skip this
|
||||
// entire constraint, perhaps. We
|
||||
// only care about pointers among the
|
||||
// fields.
|
||||
a.onlineCopyN(c.dst, ifaceValue, a.sizeof(tConc))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *invokeConstraint) solve(a *analysis, n *node, delta nodeset) {
|
||||
for ifaceObj := range delta {
|
||||
ifaceValue, tConc := a.interfaceValue(ifaceObj)
|
||||
|
||||
// Look up the concrete method.
|
||||
meth := tConc.MethodSet().Lookup(c.method.Pkg(), c.method.Name())
|
||||
if meth == nil {
|
||||
panic(fmt.Sprintf("n%d: type %s has no method %s (iface=n%d)",
|
||||
c.iface, tConc, c.method, ifaceObj))
|
||||
}
|
||||
fn := a.prog.Method(meth)
|
||||
if fn == nil {
|
||||
panic(fmt.Sprintf("n%d: no ssa.Function for %s", c.iface, meth))
|
||||
}
|
||||
sig := fn.Signature
|
||||
|
||||
fnObj := a.funcObj[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.nodes[c.params].pts.add(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, ifaceValue, recvSize)
|
||||
|
||||
// Copy iface object payload to method receiver.
|
||||
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, n *node, delta nodeset) {
|
||||
panic("addr is not a complex constraint")
|
||||
}
|
||||
|
||||
func (c *copyConstraint) solve(a *analysis, n *node, delta nodeset) {
|
||||
panic("copy is not a complex constraint")
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
// +build ignore
|
||||
|
||||
package a
|
||||
|
||||
// This test exercises the synthesis of testmain packages for tests.
|
||||
// The test framework doesn't directly let us perform negative
|
||||
// assertions (i.e. that TestingQuux isn't called, or that its
|
||||
// parameter's PTS is empty) so this test is rather roundabout.
|
||||
|
||||
import "testing"
|
||||
|
||||
func log(f func(*testing.T)) {
|
||||
// The PTS of f is the set of called tests. TestingQuux is not present.
|
||||
print(f) // @pointsto main.Test | main.TestFoo
|
||||
}
|
||||
|
||||
func Test(t *testing.T) {
|
||||
// Don't assert @pointsto(t) since its label contains a fragile line number.
|
||||
log(Test)
|
||||
}
|
||||
|
||||
func TestFoo(t *testing.T) {
|
||||
// Don't assert @pointsto(t) since its label contains a fragile line number.
|
||||
log(TestFoo)
|
||||
}
|
||||
|
||||
func TestingQuux(t *testing.T) {
|
||||
// We can't assert @pointsto(t) since this is dead code.
|
||||
log(TestingQuux)
|
||||
}
|
||||
|
||||
func BenchmarkFoo(b *testing.B) {
|
||||
}
|
||||
|
||||
func ExampleBar() {
|
||||
}
|
||||
|
||||
// Excludes TestingQuux.
|
||||
// @calls testing.tRunner -> main.Test
|
||||
// @calls testing.tRunner -> main.TestFoo
|
||||
// @calls testing.runExample -> main.ExampleBar
|
||||
// @calls (*testing.B).runN -> main.BenchmarkFoo
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
var unknown bool
|
||||
|
||||
type S string
|
||||
|
||||
func incr(x int) int { return x + 1 }
|
||||
|
||||
func main() {
|
||||
var i interface{}
|
||||
i = 1
|
||||
if unknown {
|
||||
i = S("foo")
|
||||
}
|
||||
if unknown {
|
||||
i = (func(int, int))(nil) // NB type compares equal to that below.
|
||||
}
|
||||
// Look, the test harness can handle equal-but-not-String-equal
|
||||
// types because we parse types and using a typemap.
|
||||
if unknown {
|
||||
i = (func(x int, y int))(nil)
|
||||
}
|
||||
if unknown {
|
||||
i = incr
|
||||
}
|
||||
print(i) // @concrete int | S | func(int, int) | func(int) int
|
||||
|
||||
// NB, an interface may never directly alias any global
|
||||
// labels, even though it may contain pointers that do.
|
||||
print(i) // @pointsto makeinterface:func(x int) int | makeinterface:func(x int, y int) | makeinterface:func(int, int) | makeinterface:int | makeinterface:main.S
|
||||
print(i.(func(int) int)) // @pointsto main.incr
|
||||
print(i.(S)) // @pointsto
|
||||
}
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
import "reflect"
|
||||
|
||||
// Test of arrays & slices with reflection.
|
||||
|
||||
var a int
|
||||
|
||||
func arrayreflect1() {
|
||||
sl := make([]*int, 10) // @line ar1make
|
||||
sl[0] = &a
|
||||
|
||||
srv := reflect.ValueOf(sl).Slice(0, 0)
|
||||
print(srv.Interface()) // @concrete []*int
|
||||
print(srv.Interface().([]*int)) // @pointsto makeslice@ar1make:12
|
||||
print(srv.Interface().([]*int)[42]) // @pointsto main.a
|
||||
}
|
||||
|
||||
func arrayreflect2() {
|
||||
var arr [10]*int
|
||||
sl := arr[:]
|
||||
sl[0] = &a
|
||||
|
||||
srv := reflect.ValueOf(sl).Slice(0, 0)
|
||||
print(srv.Interface()) // @concrete []*int
|
||||
print(srv.Interface().([]*int)) // pointsto TODO
|
||||
print(srv.Interface().([]*int)[42]) // @pointsto main.a
|
||||
}
|
||||
|
||||
func arrayreflect3() {
|
||||
srv := reflect.ValueOf("hi").Slice(0, 0)
|
||||
print(srv.Interface()) // @concrete string
|
||||
|
||||
type S string
|
||||
srv2 := reflect.ValueOf(S("hi")).Slice(0, 0)
|
||||
print(srv2.Interface()) // @concrete main.S
|
||||
}
|
||||
|
||||
func arrayreflect4() {
|
||||
rv1 := reflect.ValueOf("hi")
|
||||
rv2 := rv1 // backflow!
|
||||
if unknown {
|
||||
rv2 = reflect.ValueOf(123)
|
||||
}
|
||||
// We see backflow through the assignment above causing an
|
||||
// imprecise result for rv1. This is because the SSA builder
|
||||
// doesn't yet lift structs (like reflect.Value) into
|
||||
// registers so these are all loads/stores to the stack.
|
||||
// Under Das's algorithm, the extra indirection results in
|
||||
// (undirected) unification not (directed) flow edges.
|
||||
// TODO(adonovan): precision: lift aggregates.
|
||||
print(rv1.Interface()) // @concrete string | int
|
||||
print(rv2.Interface()) // @concrete string | int
|
||||
}
|
||||
|
||||
func arrayreflect5() {
|
||||
sl1 := make([]byte, 0)
|
||||
sl2 := make([]byte, 0)
|
||||
|
||||
srv := reflect.ValueOf(sl1)
|
||||
|
||||
print(srv.Interface()) // @concrete []byte
|
||||
print(srv.Interface().([]byte)) // @pointsto makeslice@testdata/arrayreflect.go:62:13
|
||||
print(srv.Bytes()) // @pointsto makeslice@testdata/arrayreflect.go:62:13
|
||||
|
||||
srv2 := reflect.ValueOf(123)
|
||||
srv2.SetBytes(sl2)
|
||||
print(srv2.Interface()) // @concrete []byte | int
|
||||
print(srv2.Interface().([]byte)) // @pointsto makeslice@testdata/arrayreflect.go:63:13
|
||||
print(srv2.Bytes()) // @pointsto makeslice@testdata/arrayreflect.go:63:13
|
||||
}
|
||||
|
||||
func arrayreflect6() {
|
||||
sl1 := []*bool{new(bool)}
|
||||
sl2 := []*int{&a}
|
||||
|
||||
srv1 := reflect.ValueOf(sl1)
|
||||
print(srv1.Index(42).Interface()) // @concrete *bool
|
||||
print(srv1.Index(42).Interface().(*bool)) // @pointsto alloc@testdata/arrayreflect.go:79:20
|
||||
|
||||
srv2 := reflect.ValueOf(sl2)
|
||||
print(srv2.Index(42).Interface()) // @concrete *int
|
||||
print(srv2.Index(42).Interface().(*int)) // @pointsto main.a
|
||||
|
||||
p1 := &sl1[0]
|
||||
p2 := &sl2[0]
|
||||
|
||||
prv1 := reflect.ValueOf(p1)
|
||||
print(prv1.Elem().Interface()) // @concrete *bool
|
||||
print(prv1.Elem().Interface().(*bool)) // @pointsto alloc@testdata/arrayreflect.go:79:20
|
||||
|
||||
prv2 := reflect.ValueOf(p2)
|
||||
print(prv2.Elem().Interface()) // @concrete *int
|
||||
print(prv2.Elem().Interface().(*int)) // @pointsto main.a
|
||||
}
|
||||
|
||||
func main() {
|
||||
arrayreflect1()
|
||||
arrayreflect2()
|
||||
arrayreflect3()
|
||||
arrayreflect4()
|
||||
arrayreflect5()
|
||||
arrayreflect6()
|
||||
}
|
||||
|
||||
var unknown bool
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
var unknown bool // defeat dead-code elimination
|
||||
|
||||
var a, b int
|
||||
|
||||
func array1() {
|
||||
sliceA := make([]*int, 10) // @line a1make
|
||||
sliceA[0] = &a
|
||||
|
||||
var sliceB []*int
|
||||
sliceB = append(sliceB, &b) // @line a1append
|
||||
|
||||
print(sliceA) // @pointsto makeslice@a1make:16
|
||||
print(sliceA[0]) // @pointsto main.a
|
||||
|
||||
print(sliceB) // @pointsto append@a1append:17
|
||||
print(sliceB[100]) // @pointsto main.b
|
||||
}
|
||||
|
||||
func array2() {
|
||||
sliceA := make([]*int, 10) // @line a2make
|
||||
sliceA[0] = &a
|
||||
|
||||
sliceB := sliceA[:]
|
||||
|
||||
print(sliceA) // @pointsto makeslice@a2make:16
|
||||
print(sliceA[0]) // @pointsto main.a
|
||||
|
||||
print(sliceB) // @pointsto makeslice@a2make:16
|
||||
print(sliceB[0]) // @pointsto main.a
|
||||
}
|
||||
|
||||
func array3() {
|
||||
a := []interface{}{"", 1}
|
||||
b := []interface{}{true, func() {}}
|
||||
print(a[0]) // @concrete string | int
|
||||
print(b[0]) // @concrete bool | func()
|
||||
}
|
||||
|
||||
// Test of append, copy, slice.
|
||||
func array4() {
|
||||
var s2 struct { // @line a4L0
|
||||
a [3]int
|
||||
b struct{ c, d int }
|
||||
}
|
||||
var sl1 = make([]*int, 10) // @line a4make
|
||||
var someint int // @line a4L1
|
||||
sl1[1] = &someint
|
||||
sl2 := append(sl1, &s2.a[1]) // @line a4append1
|
||||
print(sl1) // @pointsto makeslice@a4make:16
|
||||
print(sl2) // @pointsto append@a4append1:15 | makeslice@a4make:16
|
||||
print(sl1[0]) // @pointsto someint@a4L1:6 | s2.a[*]@a4L0:6
|
||||
print(sl2[0]) // @pointsto someint@a4L1:6 | s2.a[*]@a4L0:6
|
||||
|
||||
// In z=append(x,y) we should observe flow from y[*] to x[*].
|
||||
var sl3 = make([]*int, 10) // @line a4L2
|
||||
_ = append(sl3, &s2.a[1])
|
||||
print(sl3) // @pointsto makeslice@a4L2:16
|
||||
print(sl3[0]) // @pointsto s2.a[*]@a4L0:6
|
||||
|
||||
var sl4 = []*int{&a} // @line a4L3
|
||||
sl4a := append(sl4) // @line a4L4
|
||||
print(sl4a) // @pointsto slicelit@a4L3:18 | append@a4L4:16
|
||||
print(&sl4a[0]) // @pointsto slicelit[*]@a4L3:18 | append[*]@a4L4:16
|
||||
print(sl4a[0]) // @pointsto main.a
|
||||
|
||||
var sl5 = []*int{&b} // @line a4L5
|
||||
copy(sl5, sl4)
|
||||
print(sl5) // @pointsto slicelit@a4L5:18
|
||||
print(&sl5[0]) // @pointsto slicelit[*]@a4L5:18
|
||||
print(sl5[0]) // @pointsto main.b | main.a
|
||||
|
||||
var sl6 = sl5[:0]
|
||||
print(sl6) // @pointsto slicelit@a4L5:18
|
||||
print(&sl6[0]) // @pointsto slicelit[*]@a4L5:18
|
||||
print(sl6[0]) // @pointsto main.b | main.a
|
||||
}
|
||||
|
||||
func array5() {
|
||||
var arr [2]*int
|
||||
arr[0] = &a
|
||||
arr[1] = &b
|
||||
|
||||
var n int
|
||||
print(arr[n]) // @pointsto main.a | main.b
|
||||
}
|
||||
|
||||
func main() {
|
||||
array1()
|
||||
array2()
|
||||
array3()
|
||||
array4()
|
||||
array5()
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
func incr(x int) int { return x + 1 }
|
||||
|
||||
func decr(x int) int { return x - 1 }
|
||||
|
||||
var unknown bool // defeat dead-code elimination
|
||||
|
||||
func chan1() {
|
||||
chA := make(chan func(int) int, 0) // @line c1makeA
|
||||
chB := make(chan func(int) int, 0) // @line c1makeB
|
||||
chA <- incr
|
||||
chB <- decr
|
||||
chB <- func(int) int { return 1 }
|
||||
|
||||
print(chA) // @pointsto makechan@c1makeA:13
|
||||
print(<-chA) // @pointsto main.incr
|
||||
|
||||
print(chB) // @pointsto makechan@c1makeB:13
|
||||
print(<-chB) // @pointsto main.decr | func@16.9
|
||||
}
|
||||
|
||||
func chan2() {
|
||||
chA := make(chan func(int) int, 0) // @line c2makeA
|
||||
chB := make(chan func(int) int, 0) // @line c2makeB
|
||||
chA <- incr
|
||||
chB <- decr
|
||||
chB <- func(int) int { return 1 }
|
||||
|
||||
// Channels flow together.
|
||||
// Labelsets remain distinct but elements are merged.
|
||||
chAB := chA
|
||||
if unknown {
|
||||
chAB = chB
|
||||
}
|
||||
|
||||
print(chA) // @pointsto makechan@c2makeA:13
|
||||
print(<-chA) // @pointsto main.incr
|
||||
|
||||
print(chB) // @pointsto makechan@c2makeB:13
|
||||
print(<-chB) // @pointsto main.decr | func@30.9
|
||||
|
||||
print(chAB) // @pointsto makechan@c2makeA:13 | makechan@c2makeB:13
|
||||
print(<-chAB) // @pointsto main.incr | main.decr | func@30.9
|
||||
|
||||
(<-chA)(3)
|
||||
}
|
||||
|
||||
// @calls main.chan2 -> main.incr
|
||||
|
||||
func chan3() {
|
||||
chA := make(chan func(int) int, 0) // @line c3makeA
|
||||
chB := make(chan func(int) int, 0) // @line c3makeB
|
||||
chA <- incr
|
||||
chB <- decr
|
||||
chB <- func(int) int { return 1 }
|
||||
print(chA) // @pointsto makechan@c3makeA:13
|
||||
print(<-chA) // @pointsto main.incr
|
||||
print(chB) // @pointsto makechan@c3makeB:13
|
||||
print(<-chB) // @pointsto main.decr | func@58.9
|
||||
|
||||
(<-chA)(3)
|
||||
}
|
||||
|
||||
// @calls main.chan3 -> main.incr
|
||||
|
||||
func chan4() {
|
||||
chA := make(chan func(int) int, 0) // @line c4makeA
|
||||
chB := make(chan func(int) int, 0) // @line c4makeB
|
||||
|
||||
select {
|
||||
case chA <- incr:
|
||||
case chB <- decr:
|
||||
case a := <-chA:
|
||||
print(a) // @pointsto main.incr
|
||||
case b := <-chB:
|
||||
print(b) // @pointsto main.decr
|
||||
default:
|
||||
print(chA) // @pointsto makechan@c4makeA:13
|
||||
print(chB) // @pointsto makechan@c4makeB:13
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
chan1()
|
||||
chan2()
|
||||
chan3()
|
||||
chan4()
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
import "reflect"
|
||||
|
||||
//
|
||||
// This test is very sensitive to line-number perturbations!
|
||||
|
||||
// Test of channels with reflection.
|
||||
|
||||
var a, b int
|
||||
|
||||
func chanreflect1() {
|
||||
ch := make(chan *int, 0)
|
||||
crv := reflect.ValueOf(ch)
|
||||
crv.Send(reflect.ValueOf(&a))
|
||||
print(crv.Interface()) // @concrete chan *int
|
||||
print(crv.Interface().(chan *int)) // @pointsto makechan@testdata/chanreflect.go:15:12
|
||||
print(<-ch) // @pointsto main.a
|
||||
}
|
||||
|
||||
func chanreflect2() {
|
||||
ch := make(chan *int, 0)
|
||||
ch <- &b
|
||||
crv := reflect.ValueOf(ch)
|
||||
r, _ := crv.Recv()
|
||||
print(r.Interface()) // @concrete *int
|
||||
print(r.Interface().(*int)) // @pointsto main.b
|
||||
}
|
||||
|
||||
func chanOfRecv() {
|
||||
// MakeChan(<-chan) is a no-op.
|
||||
t := reflect.ChanOf(reflect.RecvDir, reflect.TypeOf(&a))
|
||||
print(reflect.Zero(t).Interface()) // @concrete <-chan *int
|
||||
print(reflect.MakeChan(t, 0).Interface().(<-chan *int)) // @pointsto
|
||||
}
|
||||
|
||||
func chanOfSend() {
|
||||
// MakeChan(chan<-) is a no-op.
|
||||
t := reflect.ChanOf(reflect.SendDir, reflect.TypeOf(&a))
|
||||
print(reflect.Zero(t).Interface()) // @concrete chan<- *int
|
||||
print(reflect.MakeChan(t, 0).Interface().(chan<- *int)) // @pointsto
|
||||
}
|
||||
|
||||
func chanOfBoth() {
|
||||
t := reflect.ChanOf(reflect.BothDir, reflect.TypeOf(&a))
|
||||
print(reflect.Zero(t).Interface()) // @concrete chan *int
|
||||
ch := reflect.MakeChan(t, 0)
|
||||
print(ch.Interface().(chan *int)) // @pointsto reflectMakechan@testdata/chanreflect.go:49:24
|
||||
ch.Send(reflect.ValueOf(&b))
|
||||
ch.Interface().(chan *int) <- &a
|
||||
r, _ := ch.Recv()
|
||||
print(r.Interface().(*int)) // @pointsto main.a | main.b
|
||||
print(<-ch.Interface().(chan *int)) // @pointsto main.a | main.b
|
||||
}
|
||||
|
||||
var unknownDir reflect.ChanDir // not a constant
|
||||
|
||||
func chanOfUnknown() {
|
||||
// Unknown channel direction: assume all three.
|
||||
// MakeChan only works on the bi-di channel type.
|
||||
t := reflect.ChanOf(unknownDir, reflect.TypeOf(&a))
|
||||
print(reflect.Zero(t).Interface()) // @concrete <-chan *int | chan<- *int | chan *int
|
||||
print(reflect.MakeChan(t, 0).Interface()) // @concrete chan *int
|
||||
}
|
||||
|
||||
func main() {
|
||||
chanreflect1()
|
||||
chanreflect2()
|
||||
chanOfRecv()
|
||||
chanOfSend()
|
||||
chanOfBoth()
|
||||
chanOfUnknown()
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
import "reflect"
|
||||
|
||||
//
|
||||
// This test is very sensitive to line-number perturbations!
|
||||
|
||||
// Test of channels with reflection.
|
||||
|
||||
var a, b int
|
||||
|
||||
func chanreflect1() {
|
||||
ch := make(chan *int, 0)
|
||||
crv := reflect.ValueOf(ch)
|
||||
crv.Send(reflect.ValueOf(&a))
|
||||
print(crv.Interface()) // @concrete chan *int
|
||||
print(crv.Interface().(chan *int)) // @pointsto makechan@testdata/chanreflect.go:15:12
|
||||
print(<-ch) // @pointsto main.a
|
||||
}
|
||||
|
||||
func chanreflect2() {
|
||||
ch := make(chan *int, 0)
|
||||
ch <- &b
|
||||
crv := reflect.ValueOf(ch)
|
||||
r, _ := crv.Recv()
|
||||
print(r.Interface()) // @concrete *int
|
||||
print(r.Interface().(*int)) // @pointsto main.b
|
||||
}
|
||||
|
||||
func main() {
|
||||
chanreflect1()
|
||||
chanreflect2()
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
// Test of context-sensitive treatment of certain function calls,
|
||||
// e.g. static calls to simple accessor methods.
|
||||
|
||||
var a, b int
|
||||
|
||||
type T struct{ x *int }
|
||||
|
||||
func (t *T) SetX(x *int) { t.x = x }
|
||||
func (t *T) GetX() *int { return t.x }
|
||||
|
||||
func context1() {
|
||||
var t1, t2 T
|
||||
t1.SetX(&a)
|
||||
t2.SetX(&b)
|
||||
print(t1.GetX()) // @pointsto main.a
|
||||
print(t2.GetX()) // @pointsto main.b
|
||||
}
|
||||
|
||||
func context2() {
|
||||
id := func(x *int) *int {
|
||||
print(x) // @pointsto main.a | main.b
|
||||
return x
|
||||
}
|
||||
print(id(&a)) // @pointsto main.a
|
||||
print(id(&b)) // @pointsto main.b
|
||||
|
||||
// Same again, but anon func has free vars.
|
||||
var c int // @line context2c
|
||||
id2 := func(x *int) (*int, *int) {
|
||||
print(x) // @pointsto main.a | main.b
|
||||
return x, &c
|
||||
}
|
||||
p, q := id2(&a)
|
||||
print(p) // @pointsto main.a
|
||||
print(q) // @pointsto c@context2c:6
|
||||
r, s := id2(&b)
|
||||
print(r) // @pointsto main.b
|
||||
print(s) // @pointsto c@context2c:6
|
||||
}
|
||||
|
||||
func main() {
|
||||
context1()
|
||||
context2()
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
import "unsafe"
|
||||
|
||||
var a int
|
||||
|
||||
func conv1() {
|
||||
// Conversions of channel direction.
|
||||
ch := make(chan int) // @line c1make
|
||||
print((<-chan int)(ch)) // @pointsto makechan@c1make:12
|
||||
print((chan<- int)(ch)) // @pointsto makechan@c1make:12
|
||||
}
|
||||
|
||||
func conv2() {
|
||||
// []byte/[]rune literal
|
||||
print([]byte("foo")) // @pointsto "foo":[]byte
|
||||
print([]rune("bar")) // @pointsto "bar":[]rune
|
||||
|
||||
// string -> []byte/[]rune conversion
|
||||
s := "foo"
|
||||
ba := []byte(s) // @line c2ba
|
||||
ra := []rune(s) // @line c2ra
|
||||
print(ba) // @pointsto convert@c2ba:14
|
||||
print(ra) // @pointsto convert@c2ra:14
|
||||
}
|
||||
|
||||
func conv3() {
|
||||
// Conversion of same underlying types.
|
||||
type PI *int
|
||||
pi := PI(&a)
|
||||
print(pi) // @pointsto main.a
|
||||
|
||||
pint := (*int)(pi)
|
||||
print(pint) // @pointsto main.a
|
||||
|
||||
// Conversions between pointers to identical base types.
|
||||
var y *PI = &pi
|
||||
var x **int = (**int)(y)
|
||||
print(*x) // @pointsto main.a
|
||||
print(*y) // @pointsto main.a
|
||||
y = (*PI)(x)
|
||||
print(*y) // @pointsto main.a
|
||||
}
|
||||
|
||||
// @warning "main.conv4 contains an unsafe.Pointer conversion"
|
||||
func conv4() {
|
||||
// Handling of unsafe.Pointer conversion is unsound:
|
||||
// we lose the alias to main.a and get something like new(int) instead.
|
||||
// We require users to provide aliasing summaries.
|
||||
p := (*int)(unsafe.Pointer(&a)) // @line c2p
|
||||
print(p) // @pointsto convert@c2p:13
|
||||
}
|
||||
|
||||
func main() {
|
||||
conv1()
|
||||
conv2()
|
||||
conv3()
|
||||
conv4()
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
package main
|
||||
|
||||
import "runtime"
|
||||
|
||||
func final1a(x *int) int {
|
||||
print(x) // @pointsto alloc@newint:10
|
||||
return *x
|
||||
}
|
||||
|
||||
func final1b(x *bool) {
|
||||
print(x) // @pointsto
|
||||
}
|
||||
|
||||
func setfinalizer1() {
|
||||
x := new(int) // @line newint
|
||||
runtime.SetFinalizer(x, final1a) // ok: final1a's result is ignored
|
||||
runtime.SetFinalizer(x, final1b) // param type mismatch: no effect
|
||||
}
|
||||
|
||||
// @calls runtime.SetFinalizer -> main.final1a
|
||||
// @calls main.setfinalizer1 -> runtime.SetFinalizer
|
||||
|
||||
func final2a(x *bool) {
|
||||
print(x) // @pointsto alloc@newbool1:10 | alloc@newbool2:10
|
||||
}
|
||||
|
||||
func final2b(x *bool) {
|
||||
print(x) // @pointsto alloc@newbool1:10 | alloc@newbool2:10
|
||||
}
|
||||
|
||||
func setfinalizer2() {
|
||||
x := new(bool) // @line newbool1
|
||||
f := final2a
|
||||
if unknown {
|
||||
x = new(bool) // @line newbool2
|
||||
f = final2b
|
||||
}
|
||||
runtime.SetFinalizer(x, f)
|
||||
}
|
||||
|
||||
// @calls runtime.SetFinalizer -> main.final2a
|
||||
// @calls runtime.SetFinalizer -> main.final2b
|
||||
// @calls main.setfinalizer2 -> runtime.SetFinalizer
|
||||
|
||||
// type T int
|
||||
|
||||
// func (t *T) finalize() {
|
||||
// print(t) // #@pointsto x
|
||||
// }
|
||||
|
||||
// func setfinalizer3() {
|
||||
// x := new(T)
|
||||
// runtime.SetFinalizer(x, (*T).finalize) // go/types gives wrong type to f.
|
||||
// }
|
||||
|
||||
// #@calls runtime.SetFinalizer -> (*T) finalize
|
||||
|
||||
func funcForPC() {
|
||||
f := runtime.FuncForPC(0) // @line funcforpc
|
||||
print(f) // @pointsto reflectAlloc@funcforpc:25
|
||||
}
|
||||
|
||||
func main() {
|
||||
setfinalizer1()
|
||||
setfinalizer2()
|
||||
// setfinalizer3()
|
||||
funcForPC()
|
||||
}
|
||||
|
||||
var unknown bool // defeat dead-code elimination
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
// Demonstration of directionality of flow edges.
|
||||
|
||||
func f1() {}
|
||||
func f2() {}
|
||||
|
||||
var somepred bool
|
||||
|
||||
// Tracking functions.
|
||||
func flow1() {
|
||||
s := f1
|
||||
p := f2
|
||||
q := p
|
||||
r := q
|
||||
if somepred {
|
||||
r = s
|
||||
}
|
||||
print(s) // @pointsto main.f1
|
||||
print(p) // @pointsto main.f2
|
||||
print(q) // @pointsto main.f2
|
||||
print(r) // @pointsto main.f1 | main.f2
|
||||
}
|
||||
|
||||
// Tracking concrete types in interfaces.
|
||||
func flow2() {
|
||||
var s interface{} = 1
|
||||
var p interface{} = "foo"
|
||||
q := p
|
||||
r := q
|
||||
if somepred {
|
||||
r = s
|
||||
}
|
||||
print(s) // @concrete int
|
||||
print(p) // @concrete string
|
||||
print(q) // @concrete string
|
||||
print(r) // @concrete int | string
|
||||
}
|
||||
|
||||
var g1, g2 int
|
||||
|
||||
// Tracking addresses of globals.
|
||||
func flow3() {
|
||||
s := &g1
|
||||
p := &g2
|
||||
q := p
|
||||
r := q
|
||||
if somepred {
|
||||
r = s
|
||||
}
|
||||
print(s) // @pointsto main.g1
|
||||
print(p) // @pointsto main.g2
|
||||
print(q) // @pointsto main.g2
|
||||
print(r) // @pointsto main.g2 | main.g1
|
||||
}
|
||||
|
||||
func main() {
|
||||
flow1()
|
||||
flow2()
|
||||
flow3()
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
// +build ignore
|
||||
|
||||
// This is a slice of the fmt package.
|
||||
|
||||
package main
|
||||
|
||||
type pp struct {
|
||||
field interface{}
|
||||
}
|
||||
|
||||
func newPrinter() *pp {
|
||||
return new(pp)
|
||||
}
|
||||
|
||||
func Fprintln(a ...interface{}) {
|
||||
p := newPrinter()
|
||||
p.doPrint(a, true, true)
|
||||
}
|
||||
|
||||
func Println(a ...interface{}) {
|
||||
Fprintln(a...)
|
||||
}
|
||||
|
||||
func (p *pp) doPrint(a []interface{}, addspace, addnewline bool) {
|
||||
print(a[0]) // @concrete S | string
|
||||
stringer := a[0].(interface {
|
||||
String() string
|
||||
})
|
||||
|
||||
stringer.String()
|
||||
print(stringer) // @concrete S
|
||||
}
|
||||
|
||||
type S int
|
||||
|
||||
func (S) String() string { return "" }
|
||||
|
||||
func main() {
|
||||
Println("Hello, World!", S(0))
|
||||
}
|
||||
|
||||
// @calls (*main.pp).doPrint -> (main.S).String
|
||||
|
|
@ -0,0 +1,165 @@
|
|||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
var a, b, c int
|
||||
|
||||
var unknown bool // defeat dead-code elimination
|
||||
|
||||
func func1() {
|
||||
var h int // @line f1h
|
||||
f := func(x *int) *int {
|
||||
if unknown {
|
||||
return &b
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// FV(g) = {f, h}
|
||||
g := func(x *int) *int {
|
||||
if unknown {
|
||||
return &h
|
||||
}
|
||||
return f(x)
|
||||
}
|
||||
|
||||
print(g(&a)) // @pointsto main.a | main.b | h@f1h:6
|
||||
print(f(&a)) // @pointsto main.a | main.b
|
||||
print(&a) // @pointsto main.a
|
||||
}
|
||||
|
||||
// @calls main.func1 -> func@19.7
|
||||
// @calls main.func1 -> func@11.7
|
||||
// @calls func@19.7 -> func@11.7
|
||||
|
||||
func func2() {
|
||||
var x, y *int
|
||||
defer func() {
|
||||
x = &a
|
||||
}()
|
||||
go func() {
|
||||
y = &b
|
||||
}()
|
||||
print(x) // @pointsto main.a
|
||||
print(y) // @pointsto main.b
|
||||
}
|
||||
|
||||
func func3() {
|
||||
x, y := func() (x, y *int) {
|
||||
x = &a
|
||||
y = &b
|
||||
if unknown {
|
||||
return nil, &c
|
||||
}
|
||||
return
|
||||
}()
|
||||
print(x) // @pointsto main.a
|
||||
print(y) // @pointsto main.b | main.c
|
||||
}
|
||||
|
||||
func swap(x, y *int) (*int, *int) { // @line swap
|
||||
print(&x) // @pointsto x@swap:11
|
||||
print(x) // @pointsto makeslice[*]@func4make:11
|
||||
print(&y) // @pointsto y@swap:14
|
||||
print(y) // @pointsto j@f4j:5
|
||||
return y, x
|
||||
}
|
||||
|
||||
func func4() {
|
||||
a := make([]int, 10) // @line func4make
|
||||
i, j := 123, 456 // @line f4j
|
||||
p, q := swap(&a[3], &j)
|
||||
print(p) // @pointsto j@f4j:5
|
||||
print(q) // @pointsto makeslice[*]@func4make:11
|
||||
|
||||
f := &b
|
||||
print(f) // @pointsto main.b
|
||||
}
|
||||
|
||||
type T int
|
||||
|
||||
func (t *T) f(x *int) *int {
|
||||
print(t) // @pointsto main.a
|
||||
print(x) // @pointsto main.c
|
||||
return &b
|
||||
}
|
||||
|
||||
func (t *T) g(x *int) *int {
|
||||
print(t) // @pointsto main.a
|
||||
print(x) // @pointsto main.b
|
||||
return &c
|
||||
}
|
||||
|
||||
func (t *T) h(x *int) *int {
|
||||
print(t) // @pointsto main.a
|
||||
print(x) // @pointsto main.b
|
||||
return &c
|
||||
}
|
||||
|
||||
var h func(*T, *int) *int
|
||||
|
||||
func func5() {
|
||||
// Static call of method.
|
||||
t := (*T)(&a)
|
||||
print(t.f(&c)) // @pointsto main.b
|
||||
|
||||
// Static call of method as function
|
||||
print((*T).g(t, &b)) // @pointsto main.c
|
||||
|
||||
// Dynamic call (not invoke) of method.
|
||||
h = (*T).h
|
||||
print(h(t, &b)) // @pointsto main.c
|
||||
}
|
||||
|
||||
// @calls main.func5 -> (*main.T).f
|
||||
// @calls main.func5 -> (*main.T).g
|
||||
// @calls main.func5 -> (*main.T).h
|
||||
|
||||
func func6() {
|
||||
A := &a
|
||||
f := func() *int {
|
||||
return A // (free variable)
|
||||
}
|
||||
print(f()) // @pointsto main.a
|
||||
}
|
||||
|
||||
// @calls main.func6 -> func@120.7
|
||||
|
||||
type I interface {
|
||||
f()
|
||||
}
|
||||
|
||||
type D struct{}
|
||||
|
||||
func (D) f() {}
|
||||
|
||||
func func7() {
|
||||
var i I = D{}
|
||||
imethodClosure := i.f
|
||||
imethodClosure()
|
||||
// @calls main.func7 -> bound$(main.I).f
|
||||
// @calls bound$(main.I).f -> (main.D).f
|
||||
|
||||
var d D
|
||||
cmethodClosure := d.f
|
||||
cmethodClosure()
|
||||
// @calls main.func7 -> bound$(main.D).f
|
||||
// @calls bound$(main.D).f ->(main.D).f
|
||||
|
||||
methodExpr := D.f
|
||||
methodExpr(d)
|
||||
// @calls main.func7 -> (main.D).f
|
||||
}
|
||||
|
||||
func main() {
|
||||
func1()
|
||||
func2()
|
||||
func3()
|
||||
func4()
|
||||
func5()
|
||||
func6()
|
||||
func7()
|
||||
}
|
||||
|
||||
// @calls <root> -> main.main
|
||||
// @calls <root> -> main.init
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
//
|
||||
|
||||
import "reflect"
|
||||
|
||||
var a, b int
|
||||
|
||||
func f(p *int) *int {
|
||||
print(p) // @pointsto
|
||||
return &b
|
||||
}
|
||||
|
||||
func g(p *bool) {
|
||||
}
|
||||
|
||||
func funcreflect1() {
|
||||
rvf := reflect.ValueOf(f)
|
||||
res := rvf.Call([]reflect.Value{reflect.ValueOf(&a)})
|
||||
print(res[0].Interface()) // @concrete
|
||||
print(res[0].Interface().(*int)) // @pointsto
|
||||
}
|
||||
|
||||
// @calls main.funcreflect1 -> main.f
|
||||
|
||||
func main() {
|
||||
funcreflect1()
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
type S int
|
||||
|
||||
var theS S
|
||||
|
||||
func (s *S) String() string {
|
||||
print(s) // @pointsto main.theS
|
||||
return ""
|
||||
}
|
||||
|
||||
func main() {
|
||||
fmt.Println("Hello, World!", &theS)
|
||||
}
|
||||
|
||||
// @calls main.main -> fmt.Println
|
||||
// @calls (*fmt.pp).handleMethods -> (*main.S).String
|
||||
|
|
@ -0,0 +1,152 @@
|
|||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
type I interface {
|
||||
f()
|
||||
}
|
||||
|
||||
type C int
|
||||
|
||||
func (*C) f() {}
|
||||
|
||||
type D struct{ ptr *int }
|
||||
|
||||
func (D) f() {}
|
||||
|
||||
type E struct{}
|
||||
|
||||
func (*E) f() {}
|
||||
|
||||
var a, b int
|
||||
|
||||
var unknown bool // defeat dead-code elimination
|
||||
|
||||
func interface1() {
|
||||
var i interface{} = &a
|
||||
var j interface{} = D{&b}
|
||||
k := j
|
||||
if unknown {
|
||||
k = i
|
||||
}
|
||||
|
||||
print(i) // @concrete *int
|
||||
print(j) // @concrete D
|
||||
print(k) // @concrete *int | D
|
||||
|
||||
print(i.(*int)) // @pointsto main.a
|
||||
print(j.(*int)) // @pointsto
|
||||
print(k.(*int)) // @pointsto main.a
|
||||
|
||||
print(i.(D).ptr) // @pointsto
|
||||
print(j.(D).ptr) // @pointsto main.b
|
||||
print(k.(D).ptr) // @pointsto main.b
|
||||
}
|
||||
|
||||
func interface2() {
|
||||
var i I = (*C)(&a)
|
||||
var j I = D{&a}
|
||||
k := j
|
||||
if unknown {
|
||||
k = i
|
||||
}
|
||||
|
||||
print(i) // @concrete *C
|
||||
print(j) // @concrete D
|
||||
print(k) // @concrete *C | D
|
||||
print(k) // @pointsto makeinterface:main.D | makeinterface:*main.C
|
||||
|
||||
k.f()
|
||||
// @calls main.interface2 -> (*main.C).f
|
||||
// @calls main.interface2 -> (main.D).f
|
||||
|
||||
print(i.(*C)) // @pointsto main.a
|
||||
print(j.(D).ptr) // @pointsto main.a
|
||||
print(k.(*C)) // @pointsto main.a
|
||||
|
||||
switch x := k.(type) {
|
||||
case *C:
|
||||
print(x) // @pointsto main.a
|
||||
case D:
|
||||
print(x.ptr) // @pointsto main.a
|
||||
case *E:
|
||||
print(x) // @pointsto
|
||||
}
|
||||
}
|
||||
|
||||
func interface3() {
|
||||
// There should be no backflow of concrete types from the type-switch to x.
|
||||
var x interface{} = 0
|
||||
print(x) // @concrete int
|
||||
switch y := x.(type) {
|
||||
case int:
|
||||
case string:
|
||||
}
|
||||
}
|
||||
|
||||
func interface4() {
|
||||
var i interface{} = D{&a}
|
||||
if unknown {
|
||||
i = 123
|
||||
}
|
||||
|
||||
print(i) // @concrete int | D
|
||||
|
||||
j := i.(I) // interface narrowing type-assertion
|
||||
print(j) // @concrete D
|
||||
print(j.(D).ptr) // @pointsto main.a
|
||||
|
||||
var l interface{} = j // interface widening assignment.
|
||||
print(l) // @concrete D
|
||||
print(l.(D).ptr) // @pointsto main.a
|
||||
|
||||
m := j.(interface{}) // interface widening type-assertion.
|
||||
print(m) // @concrete D
|
||||
print(m.(D).ptr) // @pointsto main.a
|
||||
}
|
||||
|
||||
// Interface method calls and value flow:
|
||||
|
||||
type J interface {
|
||||
f(*int) *int
|
||||
}
|
||||
|
||||
type P struct {
|
||||
x int
|
||||
}
|
||||
|
||||
func (p *P) f(pi *int) *int {
|
||||
print(p) // @pointsto p@i5p:6
|
||||
print(pi) // @pointsto i@i5i:6
|
||||
return &p.x
|
||||
}
|
||||
|
||||
func interface5() {
|
||||
var p P // @line i5p
|
||||
var j J = &p
|
||||
var i int // @line i5i
|
||||
print(j.f(&i)) // @pointsto p.x@i5p:6
|
||||
print(&i) // @pointsto i@i5i:6
|
||||
|
||||
print(j) // @pointsto makeinterface:*main.P
|
||||
}
|
||||
|
||||
// @calls main.interface5 -> (*main.P).f
|
||||
|
||||
func interface6() {
|
||||
f := I.f
|
||||
print(f) // @pointsto (main.I).f
|
||||
f(new(struct{ D }))
|
||||
}
|
||||
|
||||
// @calls main.interface6 -> (main.I).f
|
||||
// @calls (main.I).f -> (*struct{main.D}).f
|
||||
|
||||
func main() {
|
||||
interface1()
|
||||
interface2()
|
||||
interface3()
|
||||
interface4()
|
||||
interface5()
|
||||
interface6()
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
import "reflect"
|
||||
|
||||
//
|
||||
// This test is very sensitive to line-number perturbations!
|
||||
|
||||
// Test of maps with reflection.
|
||||
|
||||
var a int
|
||||
var b bool
|
||||
|
||||
func mapreflect1() {
|
||||
m := make(map[*int]*bool)
|
||||
m[&a] = &b
|
||||
|
||||
mrv := reflect.ValueOf(m)
|
||||
print(mrv.Interface()) // @concrete map[*int]*bool
|
||||
print(mrv.Interface().(map[*int]*bool)) // @pointsto makemap@testdata/mapreflect.go:16:11
|
||||
|
||||
for _, k := range mrv.MapKeys() {
|
||||
print(k.Interface()) // @concrete *int
|
||||
print(k.Interface().(*int)) // @pointsto main.a
|
||||
|
||||
v := mrv.MapIndex(k)
|
||||
print(v.Interface()) // @concrete *bool
|
||||
print(v.Interface().(*bool)) // @pointsto main.b
|
||||
}
|
||||
}
|
||||
|
||||
func mapreflect2() {
|
||||
m := make(map[*int]*bool)
|
||||
mrv := reflect.ValueOf(m)
|
||||
mrv.SetMapIndex(reflect.ValueOf(&a), reflect.ValueOf(&b))
|
||||
|
||||
print(m[nil]) // @pointsto main.b
|
||||
|
||||
for _, k := range mrv.MapKeys() {
|
||||
print(k.Interface()) // @concrete *int
|
||||
print(k.Interface().(*int)) // @pointsto main.a
|
||||
}
|
||||
|
||||
print(reflect.Zero(reflect.TypeOf(m).Key()).Interface()) // @concrete *int
|
||||
print(reflect.Zero(reflect.TypeOf(m).Elem()).Interface()) // @concrete *bool
|
||||
}
|
||||
|
||||
func main() {
|
||||
mapreflect1()
|
||||
mapreflect2()
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
// Test of maps.
|
||||
|
||||
var a, b, c int
|
||||
|
||||
func maps1() {
|
||||
m1 := map[*int]*int{&a: &b} // @line m1m1
|
||||
m2 := make(map[*int]*int) // @line m1m2
|
||||
m2[&b] = &a
|
||||
|
||||
print(m1[nil]) // @pointsto main.b | main.c
|
||||
print(m2[nil]) // @pointsto main.a
|
||||
|
||||
print(m1) // @pointsto makemap@m1m1:21
|
||||
print(m2) // @pointsto makemap@m1m2:12
|
||||
|
||||
m1[&b] = &c
|
||||
|
||||
for k, v := range m1 {
|
||||
print(k) // @pointsto main.a | main.b
|
||||
print(v) // @pointsto main.b | main.c
|
||||
}
|
||||
|
||||
for k, v := range m2 {
|
||||
print(k) // @pointsto main.b
|
||||
print(v) // @pointsto main.a
|
||||
}
|
||||
|
||||
// Lookup doesn't create any aliases.
|
||||
print(m2[&c]) // @pointsto main.a
|
||||
if m2v, ok := m2[&a]; ok {
|
||||
print(m2[&c]) // @pointsto main.a
|
||||
}
|
||||
}
|
||||
|
||||
func maps2() {
|
||||
m1 := map[*int]*int{&a: &b}
|
||||
m2 := map[*int]*int{&b: &c}
|
||||
m3 := []map[*int]*int{m1, m2} // (no spurious merging of m1, m2)
|
||||
|
||||
print(m1[nil]) // @pointsto main.b
|
||||
print(m2[nil]) // @pointsto main.c
|
||||
}
|
||||
|
||||
func main() {
|
||||
maps1()
|
||||
maps2()
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
// Test of value flow from panic() to recover().
|
||||
// We model them as stores/loads of a global location.
|
||||
// We ignore concrete panic types originating from the runtime.
|
||||
|
||||
var someval int
|
||||
|
||||
type myPanic struct{}
|
||||
|
||||
func f(int) {}
|
||||
|
||||
func g() string { return "" }
|
||||
|
||||
func deadcode() {
|
||||
panic(123) // not reached
|
||||
}
|
||||
|
||||
func main() {
|
||||
switch someval {
|
||||
case 0:
|
||||
panic("oops")
|
||||
case 1:
|
||||
panic(myPanic{})
|
||||
case 2:
|
||||
panic(f)
|
||||
case 3:
|
||||
panic(g)
|
||||
}
|
||||
ex := recover()
|
||||
print(ex) // @concrete myPanic | string | func(int) | func() string
|
||||
print(ex.(func(int))) // @pointsto main.f
|
||||
print(ex.(func() string)) // @pointsto main.g
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
// Analysis abstraction of recursive calls is finite.
|
||||
|
||||
func main() {
|
||||
main()
|
||||
}
|
||||
|
||||
// @calls main.main -> main.main
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
import "reflect"
|
||||
import "unsafe"
|
||||
|
||||
var a, b int
|
||||
|
||||
func reflectIndirect() {
|
||||
ptr := &a
|
||||
// Pointer:
|
||||
print(reflect.Indirect(reflect.ValueOf(&ptr)).Interface().(*int)) // @pointsto main.a
|
||||
// Non-pointer:
|
||||
print(reflect.Indirect(reflect.ValueOf([]*int{ptr})).Interface().([]*int)[0]) // @pointsto main.a
|
||||
}
|
||||
|
||||
func reflectNewAt() {
|
||||
var x [8]byte
|
||||
print(reflect.NewAt(reflect.TypeOf(3), unsafe.Pointer(&x)).Interface()) // @concrete *int
|
||||
}
|
||||
|
||||
// @warning "unsound: main.reflectNewAt contains a reflect.NewAt.. call"
|
||||
|
||||
func reflectTypeOf() {
|
||||
t := reflect.TypeOf(3)
|
||||
if unknown {
|
||||
t = reflect.TypeOf("foo")
|
||||
}
|
||||
print(t) // @concrete *reflect.rtype
|
||||
print(reflect.Zero(t).Interface()) // @concrete int | string
|
||||
newint := reflect.New(t).Interface() // @line rtonew
|
||||
print(newint) // @concrete *int | *string
|
||||
print(newint.(*int)) // @pointsto reflectAlloc@rtonew:23
|
||||
print(newint.(*string)) // @pointsto reflectAlloc@rtonew:23
|
||||
}
|
||||
|
||||
func reflectTypeElem() {
|
||||
print(reflect.Zero(reflect.TypeOf(&a).Elem()).Interface()) // @concrete int
|
||||
print(reflect.Zero(reflect.TypeOf([]string{}).Elem()).Interface()) // @concrete string
|
||||
print(reflect.Zero(reflect.TypeOf(make(chan bool)).Elem()).Interface()) // @concrete bool
|
||||
print(reflect.Zero(reflect.TypeOf(make(map[string]float64)).Elem()).Interface()) // @concrete float64
|
||||
print(reflect.Zero(reflect.TypeOf([3]complex64{}).Elem()).Interface()) // @concrete complex64
|
||||
print(reflect.Zero(reflect.TypeOf(3).Elem()).Interface()) // @concrete
|
||||
}
|
||||
|
||||
func reflectTypeInOut() {
|
||||
var f func(float64, bool) (string, int)
|
||||
print(reflect.Zero(reflect.TypeOf(f).In(0)).Interface()) // @concrete float64
|
||||
print(reflect.Zero(reflect.TypeOf(f).In(1)).Interface()) // @concrete bool
|
||||
print(reflect.Zero(reflect.TypeOf(f).In(-1)).Interface()) // @concrete float64 | bool
|
||||
print(reflect.Zero(reflect.TypeOf(f).In(zero)).Interface()) // @concrete float64 | bool
|
||||
|
||||
print(reflect.Zero(reflect.TypeOf(f).Out(0)).Interface()) // @concrete string
|
||||
print(reflect.Zero(reflect.TypeOf(f).Out(1)).Interface()) // @concrete int
|
||||
print(reflect.Zero(reflect.TypeOf(f).Out(2)).Interface()) // @concrete string | int
|
||||
print(reflect.Zero(reflect.TypeOf(3).Out(0)).Interface()) // @concrete
|
||||
}
|
||||
|
||||
func main() {
|
||||
reflectIndirect()
|
||||
reflectNewAt()
|
||||
reflectTypeOf()
|
||||
reflectTypeElem()
|
||||
reflectTypeInOut()
|
||||
}
|
||||
|
||||
var unknown bool
|
||||
var zero int
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
import "reflect"
|
||||
|
||||
var a, b int
|
||||
|
||||
type A struct {
|
||||
f *int
|
||||
g interface{}
|
||||
h bool
|
||||
}
|
||||
|
||||
func structReflect1() {
|
||||
var a A
|
||||
fld, _ := reflect.TypeOf(a).FieldByName("f") // "f" is ignored
|
||||
// TODO(adonovan): what does interface{} even mean here?
|
||||
print(reflect.Zero(fld.Type).Interface()) // @concrete *int | bool | interface{}
|
||||
// TODO(adonovan): test promotion/embedding.
|
||||
}
|
||||
|
||||
func main() {
|
||||
structReflect1()
|
||||
}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
var unknown bool // defeat dead-code elimination
|
||||
|
||||
var p, q int
|
||||
|
||||
type A struct {
|
||||
f *int
|
||||
g interface{}
|
||||
}
|
||||
|
||||
func (a A) m1() {
|
||||
print(a.f) // @pointsto main.p
|
||||
}
|
||||
|
||||
func (a *A) m2() {
|
||||
print(a) // @pointsto complit.A@struct1s:9
|
||||
print(a.f) // @pointsto main.p
|
||||
}
|
||||
|
||||
type B struct {
|
||||
h *int
|
||||
A
|
||||
}
|
||||
|
||||
func structs1() {
|
||||
b := &B{ // @line struct1s
|
||||
h: &q,
|
||||
}
|
||||
b.f = &p
|
||||
b.g = b
|
||||
|
||||
print(b.h) // @pointsto main.q
|
||||
print(b.f) // @pointsto main.p
|
||||
print(b.g) // @concrete *B
|
||||
|
||||
ptr := &b.f
|
||||
print(*ptr) // @pointsto main.p
|
||||
|
||||
b.m1()
|
||||
b.m2()
|
||||
}
|
||||
|
||||
// @calls main.structs1 -> (main.A).m1
|
||||
// @calls main.structs1 -> (*main.A).m2
|
||||
// @calls (*main.B).m1 -> (main.A).m1
|
||||
// @calls (*main.B).m2 -> (*main.A).m2
|
||||
|
||||
type T struct {
|
||||
x int
|
||||
y int
|
||||
}
|
||||
|
||||
type S struct {
|
||||
a [3]T
|
||||
b *[3]T
|
||||
c [3]*T
|
||||
}
|
||||
|
||||
func structs2() {
|
||||
var s S // @line s2s
|
||||
print(&s) // @pointsto s@s2s:6
|
||||
print(&s.a) // @pointsto s.a@s2s:6
|
||||
print(&s.a[0]) // @pointsto s.a[*]@s2s:6
|
||||
print(&s.a[0].x) // @pointsto s.a[*].x@s2s:6
|
||||
print(&s.a[0].y) // @pointsto s.a[*].y@s2s:6
|
||||
print(&s.b) // @pointsto s.b@s2s:6
|
||||
print(&s.b[0]) // @pointsto
|
||||
print(&s.b[0].x) // @pointsto
|
||||
print(&s.b[0].y) // @pointsto
|
||||
print(&s.c) // @pointsto s.c@s2s:6
|
||||
print(&s.c[0]) // @pointsto s.c[*]@s2s:6
|
||||
print(&s.c[0].x) // @pointsto
|
||||
print(&s.c[0].y) // @pointsto
|
||||
|
||||
var s2 S // @line s2s2
|
||||
s2.b = new([3]T) // @line s2s2b
|
||||
print(s2.b) // @pointsto new@s2s2b:12
|
||||
print(&s2.b) // @pointsto s2.b@s2s2:6
|
||||
print(&s2.b[0]) // @pointsto new[*]@s2s2b:12
|
||||
print(&s2.b[0].x) // @pointsto new[*].x@s2s2b:12
|
||||
print(&s2.b[0].y) // @pointsto new[*].y@s2s2b:12
|
||||
print(&s2.c[0].x) // @pointsto
|
||||
print(&s2.c[0].y) // @pointsto
|
||||
|
||||
var s3 S // @line s2s3
|
||||
s3.c[2] = new(T) // @line s2s3c
|
||||
print(s3.c) // @pointsto
|
||||
print(&s3.c) // @pointsto s3.c@s2s3:6
|
||||
print(s3.c[1]) // @pointsto new@s2s3c:15
|
||||
print(&s3.c[1]) // @pointsto s3.c[*]@s2s3:6
|
||||
print(&s3.c[1].x) // @pointsto new.x@s2s3c:15
|
||||
print(&s3.c[1].y) // @pointsto new.y@s2s3c:15
|
||||
}
|
||||
|
||||
func main() {
|
||||
structs1()
|
||||
structs2()
|
||||
}
|
||||
|
|
@ -0,0 +1,289 @@
|
|||
package pointer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"code.google.com/p/go.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:
|
||||
return CanPoint(T.Underlying())
|
||||
|
||||
case *types.Pointer, *types.Interface, *types.Map, *types.Chan, *types.Signature:
|
||||
return true
|
||||
}
|
||||
|
||||
return false // array struct tuple builtin basic
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
||||
|
||||
// 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.
|
||||
//
|
||||
// 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 _, ok := u.(*types.Interface); ok {
|
||||
// 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.
|
||||
for i, n := 0, t.Len(); 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})
|
||||
}
|
||||
}
|
||||
|
||||
case *types.Builtin:
|
||||
panic("flatten(*types.Builtin)") // not the type of any value
|
||||
|
||||
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)))
|
||||
}
|
||||
|
||||
// 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 map[nodeid]struct{}
|
||||
|
||||
// ---- Accessors ----
|
||||
|
||||
func (ns nodeset) String() string {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteRune('{')
|
||||
var sep string
|
||||
for n := range ns {
|
||||
fmt.Fprintf(&buf, "%sn%d", sep, n)
|
||||
sep = ", "
|
||||
}
|
||||
buf.WriteRune('}')
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// diff returns the set-difference x - y. nil => empty.
|
||||
//
|
||||
// TODO(adonovan): opt: extremely inefficient. BDDs do this in
|
||||
// constant time. Sparse bitvectors are linear but very fast.
|
||||
func (x nodeset) diff(y nodeset) nodeset {
|
||||
var z nodeset
|
||||
for k := range x {
|
||||
if _, ok := y[k]; !ok {
|
||||
z.add(k)
|
||||
}
|
||||
}
|
||||
return z
|
||||
}
|
||||
|
||||
// clone() returns an unaliased copy of x.
|
||||
func (x nodeset) clone() nodeset {
|
||||
return x.diff(nil)
|
||||
}
|
||||
|
||||
// ---- Mutators ----
|
||||
|
||||
func (ns *nodeset) add(n nodeid) bool {
|
||||
sz := len(*ns)
|
||||
if *ns == nil {
|
||||
*ns = make(nodeset)
|
||||
}
|
||||
(*ns)[n] = struct{}{}
|
||||
return len(*ns) > sz
|
||||
}
|
||||
|
||||
func (x *nodeset) addAll(y nodeset) bool {
|
||||
if y == nil {
|
||||
return false
|
||||
}
|
||||
sz := len(*x)
|
||||
if *x == nil {
|
||||
*x = make(nodeset)
|
||||
}
|
||||
for n := range y {
|
||||
(*x)[n] = struct{}{}
|
||||
}
|
||||
return len(*x) > sz
|
||||
}
|
||||
|
||||
// Constraint set -------------------------------------------------------------
|
||||
|
||||
type constraintset map[constraint]struct{}
|
||||
|
||||
func (cs *constraintset) add(c constraint) bool {
|
||||
sz := len(*cs)
|
||||
if *cs == nil {
|
||||
*cs = make(constraintset)
|
||||
}
|
||||
(*cs)[c] = struct{}{}
|
||||
return len(*cs) > sz
|
||||
}
|
||||
|
||||
// Worklist -------------------------------------------------------------------
|
||||
|
||||
// TODO(adonovan): interface may not be general enough for certain
|
||||
// implementations, e.g. priority queue
|
||||
//
|
||||
// Uses double-buffering so nodes can be added during iteration.
|
||||
type worklist interface {
|
||||
empty() bool // Reports whether active buffer is empty.
|
||||
swap() bool // Switches to the shadow buffer if empty().
|
||||
add(nodeid) // Adds a node to the shadow buffer.
|
||||
take() nodeid // Takes a node from the active buffer. Precondition: !empty().
|
||||
}
|
||||
|
||||
// Horribly naive (and nondeterministic) worklist
|
||||
// based on two hash-sets.
|
||||
type mapWorklist struct {
|
||||
active, shadow nodeset
|
||||
}
|
||||
|
||||
func (w *mapWorklist) empty() bool {
|
||||
return len(w.active) == 0
|
||||
}
|
||||
|
||||
func (w *mapWorklist) swap() bool {
|
||||
if w.empty() {
|
||||
w.shadow, w.active = w.active, w.shadow
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (w *mapWorklist) add(n nodeid) {
|
||||
w.shadow[n] = struct{}{}
|
||||
}
|
||||
|
||||
func (w *mapWorklist) take() nodeid {
|
||||
for k := range w.active {
|
||||
delete(w.active, k)
|
||||
return k
|
||||
}
|
||||
panic("worklist.take(): empty active buffer")
|
||||
}
|
||||
|
||||
func makeMapWorklist() worklist {
|
||||
return &mapWorklist{make(nodeset), make(nodeset)}
|
||||
}
|
||||
Loading…
Reference in New Issue