| // Copyright 2013 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package pointer |
| |
| // This package defines the treatment of intrinsics, i.e. library |
| // functions requiring special analytical treatment. |
| // |
| // Most of these are C or assembly functions, but even some Go |
| // functions require may special treatment if the analysis completely |
| // replaces the implementation of an API such as reflection. |
| |
| // TODO(adonovan): support a means of writing analytic summaries in |
| // the target code, so that users can summarise the effects of their |
| // own C functions using a snippet of Go. |
| |
| import ( |
| "fmt" |
| "go/types" |
| |
| "golang.org/x/tools/go/ssa" |
| ) |
| |
| // Instances of 'intrinsic' generate analysis constraints for calls to |
| // intrinsic functions. |
| // Implementations may exploit information from the calling site |
| // via cgn.callersite; for shared contours this is nil. |
| type intrinsic func(a *analysis, cgn *cgnode) |
| |
| // Initialized in explicit init() to defeat (spurious) initialization |
| // cycle error. |
| var intrinsicsByName = make(map[string]intrinsic) |
| |
| func init() { |
| // Key strings are from Function.String(). |
| // That little dot ۰ is an Arabic zero numeral (U+06F0), |
| // categories [Nd]. |
| for name, fn := range map[string]intrinsic{ |
| // Other packages. |
| "bytes.Equal": ext۰NoEffect, |
| "bytes.IndexByte": ext۰NoEffect, |
| "crypto/aes.decryptBlockAsm": ext۰NoEffect, |
| "crypto/aes.encryptBlockAsm": ext۰NoEffect, |
| "crypto/aes.expandKeyAsm": ext۰NoEffect, |
| "crypto/aes.hasAsm": ext۰NoEffect, |
| "crypto/md5.block": ext۰NoEffect, |
| "crypto/rc4.xorKeyStream": ext۰NoEffect, |
| "crypto/sha1.block": ext۰NoEffect, |
| "crypto/sha256.block": ext۰NoEffect, |
| "hash/crc32.castagnoliSSE42": ext۰NoEffect, |
| "hash/crc32.haveSSE42": ext۰NoEffect, |
| "math.Abs": ext۰NoEffect, |
| "math.Acos": ext۰NoEffect, |
| "math.Asin": ext۰NoEffect, |
| "math.Atan": ext۰NoEffect, |
| "math.Atan2": ext۰NoEffect, |
| "math.Ceil": ext۰NoEffect, |
| "math.Cos": ext۰NoEffect, |
| "math.Dim": ext۰NoEffect, |
| "math.Exp": ext۰NoEffect, |
| "math.Exp2": ext۰NoEffect, |
| "math.Expm1": ext۰NoEffect, |
| "math.Float32bits": ext۰NoEffect, |
| "math.Float32frombits": ext۰NoEffect, |
| "math.Float64bits": ext۰NoEffect, |
| "math.Float64frombits": ext۰NoEffect, |
| "math.Floor": ext۰NoEffect, |
| "math.Frexp": ext۰NoEffect, |
| "math.Hypot": ext۰NoEffect, |
| "math.Ldexp": ext۰NoEffect, |
| "math.Log": ext۰NoEffect, |
| "math.Log10": ext۰NoEffect, |
| "math.Log1p": ext۰NoEffect, |
| "math.Log2": ext۰NoEffect, |
| "math.Max": ext۰NoEffect, |
| "math.Min": ext۰NoEffect, |
| "math.Mod": ext۰NoEffect, |
| "math.Modf": ext۰NoEffect, |
| "math.Remainder": ext۰NoEffect, |
| "math.Sin": ext۰NoEffect, |
| "math.Sincos": ext۰NoEffect, |
| "math.Sqrt": ext۰NoEffect, |
| "math.Tan": ext۰NoEffect, |
| "math.Trunc": ext۰NoEffect, |
| "math/big.addMulVVW": ext۰NoEffect, |
| "math/big.addVV": ext۰NoEffect, |
| "math/big.addVW": ext۰NoEffect, |
| "math/big.bitLen": ext۰NoEffect, |
| "math/big.divWVW": ext۰NoEffect, |
| "math/big.divWW": ext۰NoEffect, |
| "math/big.mulAddVWW": ext۰NoEffect, |
| "math/big.mulWW": ext۰NoEffect, |
| "math/big.shlVU": ext۰NoEffect, |
| "math/big.shrVU": ext۰NoEffect, |
| "math/big.subVV": ext۰NoEffect, |
| "math/big.subVW": ext۰NoEffect, |
| "net.runtime_Semacquire": ext۰NoEffect, |
| "net.runtime_Semrelease": ext۰NoEffect, |
| "net.runtime_pollClose": ext۰NoEffect, |
| "net.runtime_pollOpen": ext۰NoEffect, |
| "net.runtime_pollReset": ext۰NoEffect, |
| "net.runtime_pollServerInit": ext۰NoEffect, |
| "net.runtime_pollSetDeadline": ext۰NoEffect, |
| "net.runtime_pollUnblock": ext۰NoEffect, |
| "net.runtime_pollWait": ext۰NoEffect, |
| "net.runtime_pollWaitCanceled": ext۰NoEffect, |
| "os.epipecheck": ext۰NoEffect, |
| // All other runtime functions are treated as NoEffect. |
| "runtime.SetFinalizer": ext۰runtime۰SetFinalizer, |
| "strings.IndexByte": ext۰NoEffect, |
| "sync.runtime_Semacquire": ext۰NoEffect, |
| "sync.runtime_Semrelease": ext۰NoEffect, |
| "sync.runtime_Syncsemacquire": ext۰NoEffect, |
| "sync.runtime_Syncsemcheck": ext۰NoEffect, |
| "sync.runtime_Syncsemrelease": ext۰NoEffect, |
| "sync.runtime_procPin": ext۰NoEffect, |
| "sync.runtime_procUnpin": ext۰NoEffect, |
| "sync.runtime_registerPool": ext۰NoEffect, |
| "sync/atomic.AddInt32": ext۰NoEffect, |
| "sync/atomic.AddInt64": ext۰NoEffect, |
| "sync/atomic.AddUint32": ext۰NoEffect, |
| "sync/atomic.AddUint64": ext۰NoEffect, |
| "sync/atomic.AddUintptr": ext۰NoEffect, |
| "sync/atomic.CompareAndSwapInt32": ext۰NoEffect, |
| "sync/atomic.CompareAndSwapUint32": ext۰NoEffect, |
| "sync/atomic.CompareAndSwapUint64": ext۰NoEffect, |
| "sync/atomic.CompareAndSwapUintptr": ext۰NoEffect, |
| "sync/atomic.LoadInt32": ext۰NoEffect, |
| "sync/atomic.LoadInt64": ext۰NoEffect, |
| "sync/atomic.LoadPointer": ext۰NoEffect, // ignore unsafe.Pointers |
| "sync/atomic.LoadUint32": ext۰NoEffect, |
| "sync/atomic.LoadUint64": ext۰NoEffect, |
| "sync/atomic.LoadUintptr": ext۰NoEffect, |
| "sync/atomic.StoreInt32": ext۰NoEffect, |
| "sync/atomic.StorePointer": ext۰NoEffect, // ignore unsafe.Pointers |
| "sync/atomic.StoreUint32": ext۰NoEffect, |
| "sync/atomic.StoreUintptr": ext۰NoEffect, |
| "syscall.Close": ext۰NoEffect, |
| "syscall.Exit": ext۰NoEffect, |
| "syscall.Getpid": ext۰NoEffect, |
| "syscall.Getwd": ext۰NoEffect, |
| "syscall.Kill": ext۰NoEffect, |
| "syscall.RawSyscall": ext۰NoEffect, |
| "syscall.RawSyscall6": ext۰NoEffect, |
| "syscall.Syscall": ext۰NoEffect, |
| "syscall.Syscall6": ext۰NoEffect, |
| "syscall.runtime_AfterFork": ext۰NoEffect, |
| "syscall.runtime_BeforeFork": ext۰NoEffect, |
| "syscall.setenv_c": ext۰NoEffect, |
| "time.Sleep": ext۰NoEffect, |
| "time.now": ext۰NoEffect, |
| "time.startTimer": ext۰time۰startTimer, |
| "time.stopTimer": ext۰NoEffect, |
| } { |
| intrinsicsByName[name] = fn |
| } |
| } |
| |
| // findIntrinsic returns the constraint generation function for an |
| // intrinsic function fn, or nil if the function should be handled normally. |
| // |
| func (a *analysis) findIntrinsic(fn *ssa.Function) intrinsic { |
| // Consult the *Function-keyed cache. |
| // A cached nil indicates a normal non-intrinsic function. |
| impl, ok := a.intrinsics[fn] |
| if !ok { |
| impl = intrinsicsByName[fn.String()] // may be nil |
| |
| if a.isReflect(fn) { |
| if !a.config.Reflection { |
| impl = ext۰NoEffect // reflection disabled |
| } else if impl == nil { |
| // Ensure all "reflect" code is treated intrinsically. |
| impl = ext۰NotYetImplemented |
| } |
| } else if impl == nil && fn.Pkg != nil && fn.Pkg.Pkg.Path() == "runtime" { |
| // Ignore "runtime" (except SetFinalizer): |
| // it has few interesting effects on aliasing |
| // and is full of unsafe code we can't analyze. |
| impl = ext۰NoEffect |
| } |
| |
| a.intrinsics[fn] = impl |
| } |
| return impl |
| } |
| |
| // isReflect reports whether fn belongs to the "reflect" package. |
| func (a *analysis) isReflect(fn *ssa.Function) bool { |
| if a.reflectValueObj == nil { |
| return false // "reflect" package not loaded |
| } |
| reflectPackage := a.reflectValueObj.Pkg() |
| if fn.Pkg != nil && fn.Pkg.Pkg == reflectPackage { |
| return true |
| } |
| // Synthetic wrappers have a nil Pkg, so they slip through the |
| // previous check. Check the receiver package. |
| // TODO(adonovan): should synthetic wrappers have a non-nil Pkg? |
| if recv := fn.Signature.Recv(); recv != nil { |
| if named, ok := deref(recv.Type()).(*types.Named); ok { |
| if named.Obj().Pkg() == reflectPackage { |
| return true // e.g. wrapper of (reflect.Value).f |
| } |
| } |
| } |
| return false |
| } |
| |
| // A trivial intrinsic suitable for any function that does not: |
| // 1) induce aliases between its arguments or any global variables; |
| // 2) call any functions; or |
| // 3) create any labels. |
| // |
| // Many intrinsics (such as CompareAndSwapInt32) have a fourth kind of |
| // effect: loading or storing through a pointer. Though these could |
| // be significant, we deliberately ignore them because they are |
| // generally not worth the effort. |
| // |
| // We sometimes violate condition #3 if the function creates only |
| // non-function labels, as the control-flow graph is still sound. |
| // |
| func ext۰NoEffect(a *analysis, cgn *cgnode) {} |
| |
| func ext۰NotYetImplemented(a *analysis, cgn *cgnode) { |
| fn := cgn.fn |
| a.warnf(fn.Pos(), "unsound: intrinsic treatment of %s not yet implemented", fn) |
| } |
| |
| // ---------- func runtime.SetFinalizer(x, f interface{}) ---------- |
| |
| // runtime.SetFinalizer(x, f) |
| type runtimeSetFinalizerConstraint struct { |
| targets nodeid // (indirect) |
| f nodeid // (ptr) |
| x nodeid |
| } |
| |
| func (c *runtimeSetFinalizerConstraint) ptr() nodeid { return c.f } |
| func (c *runtimeSetFinalizerConstraint) presolve(h *hvn) { |
| h.markIndirect(onodeid(c.targets), "SetFinalizer.targets") |
| } |
| func (c *runtimeSetFinalizerConstraint) renumber(mapping []nodeid) { |
| c.targets = mapping[c.targets] |
| c.f = mapping[c.f] |
| c.x = mapping[c.x] |
| } |
| |
| func (c *runtimeSetFinalizerConstraint) String() string { |
| return fmt.Sprintf("runtime.SetFinalizer(n%d, n%d)", c.x, c.f) |
| } |
| |
| func (c *runtimeSetFinalizerConstraint) solve(a *analysis, delta *nodeset) { |
| for _, fObj := range delta.AppendTo(a.deltaSpace) { |
| tDyn, f, indirect := a.taggedValue(nodeid(fObj)) |
| if indirect { |
| // TODO(adonovan): we'll need to implement this |
| // when we start creating indirect tagged objects. |
| panic("indirect tagged object") |
| } |
| |
| tSig, ok := tDyn.Underlying().(*types.Signature) |
| if !ok { |
| continue // not a function |
| } |
| if tSig.Recv() != nil { |
| panic(tSig) |
| } |
| if tSig.Params().Len() != 1 { |
| continue // not a unary function |
| } |
| |
| // Extract x to tmp. |
| tx := tSig.Params().At(0).Type() |
| tmp := a.addNodes(tx, "SetFinalizer.tmp") |
| a.typeAssert(tx, tmp, c.x, false) |
| |
| // Call f(tmp). |
| a.store(f, tmp, 1, a.sizeof(tx)) |
| |
| // Add dynamic call target. |
| if a.onlineCopy(c.targets, f) { |
| a.addWork(c.targets) |
| } |
| } |
| } |
| |
| func ext۰runtime۰SetFinalizer(a *analysis, cgn *cgnode) { |
| // This is the shared contour, used for dynamic calls. |
| targets := a.addOneNode(tInvalid, "SetFinalizer.targets", nil) |
| cgn.sites = append(cgn.sites, &callsite{targets: targets}) |
| params := a.funcParams(cgn.obj) |
| a.addConstraint(&runtimeSetFinalizerConstraint{ |
| targets: targets, |
| x: params, |
| f: params + 1, |
| }) |
| } |
| |
| // ---------- func time.startTimer(t *runtimeTimer) ---------- |
| |
| // time.StartTimer(t) |
| type timeStartTimerConstraint struct { |
| targets nodeid // (indirect) |
| t nodeid // (ptr) |
| } |
| |
| func (c *timeStartTimerConstraint) ptr() nodeid { return c.t } |
| func (c *timeStartTimerConstraint) presolve(h *hvn) { |
| h.markIndirect(onodeid(c.targets), "StartTimer.targets") |
| } |
| func (c *timeStartTimerConstraint) renumber(mapping []nodeid) { |
| c.targets = mapping[c.targets] |
| c.t = mapping[c.t] |
| } |
| |
| func (c *timeStartTimerConstraint) String() string { |
| return fmt.Sprintf("time.startTimer(n%d)", c.t) |
| } |
| |
| func (c *timeStartTimerConstraint) solve(a *analysis, delta *nodeset) { |
| for _, tObj := range delta.AppendTo(a.deltaSpace) { |
| t := nodeid(tObj) |
| |
| // We model startTimer as if it was defined thus: |
| // func startTimer(t *runtimeTimer) { t.f(t.arg) } |
| |
| // We hard-code the field offsets of time.runtimeTimer: |
| // type runtimeTimer struct { |
| // 0 __identity__ |
| // 1 i int32 |
| // 2 when int64 |
| // 3 period int64 |
| // 4 f func(int64, interface{}) |
| // 5 arg interface{} |
| // } |
| f := t + 4 |
| arg := t + 5 |
| |
| // store t.arg to t.f.params[0] |
| // (offset 1 => skip identity) |
| a.store(f, arg, 1, 1) |
| |
| // Add dynamic call target. |
| if a.onlineCopy(c.targets, f) { |
| a.addWork(c.targets) |
| } |
| } |
| } |
| |
| func ext۰time۰startTimer(a *analysis, cgn *cgnode) { |
| // This is the shared contour, used for dynamic calls. |
| targets := a.addOneNode(tInvalid, "startTimer.targets", nil) |
| cgn.sites = append(cgn.sites, &callsite{targets: targets}) |
| params := a.funcParams(cgn.obj) |
| a.addConstraint(&timeStartTimerConstraint{ |
| targets: targets, |
| t: params, |
| }) |
| } |