// valueOffsetNode ascertains the node for tuple/struct value v, // then returns the node for its subfield #index. // func (a *analysis) valueOffsetNode(v ssa.Value, index int) nodeid { id := a.valueNode(v) if id == 0 { panic(fmt.Sprintf("cannot offset within n0: %s = %s", v.Name(), v)) } return id + nodeid(a.offsetOf(v.Type(), index)) }
// describePointer runs the pointer analysis of the selected SSA value. func describePointer(o *Oracle, v ssa.Value, indirect bool) (ptrs []pointerResult, err error) { buildSSA(o) // TODO(adonovan): don't run indirect pointer analysis on non-ptr-ptrlike types. o.config.Queries = map[ssa.Value]pointer.Indirect{v: pointer.Indirect(indirect)} ptares := ptrAnalysis(o) // Combine the PT sets from all contexts. pointers := ptares.Queries[v] if pointers == nil { return nil, fmt.Errorf("PTA did not encounter this expression (dead code?)") } pts := pointer.PointsToCombined(pointers) if pointer.CanHaveDynamicTypes(v.Type()) { // Show concrete types for interface/reflect.Value expression. if concs := pts.DynamicTypes(); concs.Len() > 0 { concs.Iterate(func(conc types.Type, pta interface{}) { combined := pointer.PointsToCombined(pta.([]pointer.Pointer)) labels := combined.Labels() sort.Sort(byPosAndString(labels)) // to ensure determinism ptrs = append(ptrs, pointerResult{conc, labels}) }) } } else { // Show labels for other expressions. labels := pts.Labels() sort.Sort(byPosAndString(labels)) // to ensure determinism ptrs = append(ptrs, pointerResult{v.Type(), labels}) } sort.Sort(byTypeString(ptrs)) // to ensure determinism return ptrs, nil }
// valueNode returns the id of the value node for v, creating it (and // the association) as needed. It may return zero for uninteresting // values containing no pointers. // // Nodes for locals are created en masse during genFunc and are // implicitly contextualized by the function currently being analyzed // (i.e. parameter to genFunc). // func (a *analysis) valueNode(v ssa.Value) nodeid { id, ok := a.valNode[v] if !ok { switch v := v.(type) { case *ssa.Function: id = a.makeFunction(v) case *ssa.Global: id = a.makeGlobal(v) case *ssa.Const: id = a.makeConstant(v) case *ssa.Capture: // TODO(adonovan): treat captures context-sensitively. id = a.addNodes(v.Type(), "capture") default: // *ssa.Parameters and ssa.Instruction values // are created by genFunc. // *Builtins are not true values. panic(v) } a.setValueNode(v, id) } return id }
// setValueNode associates node id with the value v. // TODO(adonovan): disambiguate v by its CallGraphNode, if it's a local. func (a *analysis) setValueNode(v ssa.Value, id nodeid) { a.valNode[v] = id if a.log != nil { fmt.Fprintf(a.log, "\tval[%s] = n%d (%T)\n", v.Name(), id, v) } // Record the (v, id) relation if the client has queried v. if indirect, ok := a.config.Queries[v]; ok { if indirect { tmp := a.addNodes(v.Type(), "query.indirect") a.load(tmp, id, a.sizeof(v.Type())) id = tmp } a.queries[v] = append(a.queries[v], ptr{a, id}) } }
// setValueNode associates node id with the value v. // cgn identifies the context iff v is a local variable. // func (a *analysis) setValueNode(v ssa.Value, id nodeid, cgn *cgnode) { if cgn != nil { a.localval[v] = id } else { a.globalval[v] = id } if a.log != nil { fmt.Fprintf(a.log, "\tval[%s] = n%d (%T)\n", v.Name(), id, v) } // Record the (v, id) relation if the client has queried v. if indirect, ok := a.config.Queries[v]; ok { if indirect { tmp := a.addNodes(v.Type(), "query.indirect") a.genLoad(cgn, tmp, v, 0, a.sizeof(v.Type())) id = tmp } a.result.Queries[v] = append(a.result.Queries[v], ptr{a, cgn, id}) } }
// runPTA runs the pointer analysis of the selected SSA value or address. func runPTA(o *Oracle, v ssa.Value, isAddr bool) (ptrs []pointerResult, err error) { buildSSA(o) if isAddr { o.ptaConfig.AddIndirectQuery(v) } else { o.ptaConfig.AddQuery(v) } ptares := ptrAnalysis(o) // Combine the PT sets from all contexts. var pointers []pointer.Pointer if isAddr { pointers = ptares.IndirectQueries[v] } else { pointers = ptares.Queries[v] } if pointers == nil { return nil, fmt.Errorf("pointer analysis did not find expression (dead code?)") } pts := pointer.PointsToCombined(pointers) if pointer.CanHaveDynamicTypes(v.Type()) { // Show concrete types for interface/reflect.Value expression. if concs := pts.DynamicTypes(); concs.Len() > 0 { concs.Iterate(func(conc types.Type, pta interface{}) { combined := pointer.PointsToCombined(pta.([]pointer.Pointer)) labels := combined.Labels() sort.Sort(byPosAndString(labels)) // to ensure determinism ptrs = append(ptrs, pointerResult{conc, labels}) }) } } else { // Show labels for other expressions. labels := pts.Labels() sort.Sort(byPosAndString(labels)) // to ensure determinism ptrs = append(ptrs, pointerResult{v.Type(), labels}) } sort.Sort(byTypeString(ptrs)) // to ensure determinism return ptrs, nil }
// valueNode returns the id of the value node for v, creating it (and // the association) as needed. It may return zero for uninteresting // values containing no pointers. // func (a *analysis) valueNode(v ssa.Value) nodeid { // Value nodes for locals are created en masse by genFunc. if id, ok := a.localval[v]; ok { return id } // Value nodes for globals are created on demand. id, ok := a.globalval[v] if !ok { var comment string if a.log != nil { comment = v.String() } id = a.addOneNode(v.Type(), comment, nil) if obj := a.objectNode(nil, v); obj != 0 { a.addressOf(id, obj) } a.setValueNode(v, id, nil) } return id }
func (fr *frame) value(v ssa.Value) (result *LLVMValue) { switch v := v.(type) { case nil: return nil case *ssa.Function: result, ok := fr.funcvals[v] if ok { return result } // fr.globals[v] has the function in raw pointer form; // we must convert it to <f,ctx> form. If the function // does not have a receiver, then create a wrapper // function that has an additional "context" parameter. f := fr.resolveFunction(v) if v.Signature.Recv() == nil && len(v.FreeVars) == 0 { f = contextFunction(fr.compiler, f) } pair := llvm.ConstNull(fr.llvmtypes.ToLLVM(f.Type())) fnptr := llvm.ConstBitCast(f.LLVMValue(), pair.Type().StructElementTypes()[0]) pair = llvm.ConstInsertValue(pair, fnptr, []uint32{0}) result = fr.NewValue(pair, f.Type()) fr.funcvals[v] = result return result case *ssa.Const: return fr.NewConstValue(v.Value, v.Type()) case *ssa.Global: if g, ok := fr.globals[v]; ok { return g } // Create an external global. Globals for this package are defined // on entry to translatePackage, and have initialisers. llelemtyp := fr.llvmtypes.ToLLVM(deref(v.Type())) llglobal := llvm.AddGlobal(fr.module.Module, llelemtyp, v.String()) global := fr.NewValue(llglobal, v.Type()) fr.globals[v] = global return global } if value, ok := fr.env[v]; ok { return value } // Instructions are not necessarily visited before they are used (e.g. Phi // edges) so we must "backpatch": create a value with the resultant type, // and then replace it when we visit the instruction. if b, ok := fr.backpatch[v]; ok { return b } if fr.backpatch == nil { fr.backpatch = make(map[ssa.Value]*LLVMValue) } // Note: we must not create a constant here (e.g. Undef/ConstNull), as // it is not permissible to replace a constant with a non-constant. // We must create the value in its own standalone basic block, so we can // dispose of it after replacing. currBlock := fr.builder.GetInsertBlock() fr.builder.SetInsertPointAtEnd(llvm.AddBasicBlock(currBlock.Parent(), "")) placeholder := fr.compiler.builder.CreatePHI(fr.llvmtypes.ToLLVM(v.Type()), "") fr.builder.SetInsertPointAtEnd(currBlock) value := fr.NewValue(placeholder, v.Type()) fr.backpatch[v] = value return value }
func describeValue(o *Oracle, qpos *QueryPos, path []ast.Node) (*describeValueResult, error) { var expr ast.Expr var obj types.Object switch n := path[0].(type) { case *ast.ValueSpec: // ambiguous ValueSpec containing multiple names return nil, fmt.Errorf("multiple value specification") case *ast.Ident: obj = qpos.info.ObjectOf(n) expr = n case ast.Expr: expr = n default: // Is this reachable? return nil, fmt.Errorf("unexpected AST for expr: %T", n) } typ := qpos.info.TypeOf(expr) constVal := qpos.info.ValueOf(expr) // From this point on, we cannot fail with an error. // Failure to run the pointer analysis will be reported later. // // Our disposition to pointer analysis may be one of the following: // - ok: ssa.Value was const or func. // - error: no ssa.Value for expr (e.g. trivially dead code) // - ok: ssa.Value is non-pointerlike // - error: no Pointer for ssa.Value (e.g. analytically unreachable) // - ok: Pointer has empty points-to set // - ok: Pointer has non-empty points-to set // ptaErr is non-nil only in the "error:" cases. var ptaErr error var ptrs []pointerResult // Only run pointer analysis on pointerlike expression types. if pointer.CanPoint(typ) { // Determine the ssa.Value for the expression. var value ssa.Value if obj != nil { // def/ref of func/var/const object value, ptaErr = ssaValueForIdent(o.prog, qpos.info, obj, path) } else { // any other expression if qpos.info.ValueOf(path[0].(ast.Expr)) == nil { // non-constant? value, ptaErr = ssaValueForExpr(o.prog, qpos.info, path) } } if value != nil { // TODO(adonovan): IsIdentical may be too strict; // perhaps we need is-assignable or even // has-same-underlying-representation? indirect := types.IsIdentical(types.NewPointer(typ), value.Type()) ptrs, ptaErr = describePointer(o, value, indirect) } } return &describeValueResult{ qpos: qpos, expr: expr, typ: typ, constVal: constVal, obj: obj, ptaErr: ptaErr, ptrs: ptrs, }, nil }
// objectNode returns the object to which v points, if known. // In other words, if the points-to set of v is a singleton, it // returns the sole label, zero otherwise. // // We exploit this information to make the generated constraints less // dynamic. For example, a complex load constraint can be replaced by // a simple copy constraint when the sole destination is known a priori. // // Some SSA instructions always have singletons points-to sets: // Alloc, Function, Global, MakeChan, MakeClosure, MakeInterface, MakeMap, MakeSlice. // Others may be singletons depending on their operands: // Capture, Const, Convert, FieldAddr, IndexAddr, Slice. // // Idempotent. Objects are created as needed, possibly via recursion // down the SSA value graph, e.g IndexAddr(FieldAddr(Alloc))). // func (a *analysis) objectNode(cgn *cgnode, v ssa.Value) nodeid { if cgn == nil { // Global object. obj, ok := a.globalobj[v] if !ok { switch v := v.(type) { case *ssa.Global: obj = a.nextNode() a.addNodes(mustDeref(v.Type()), "global") a.endObject(obj, nil, v) case *ssa.Function: obj = a.makeFunctionObject(v, nil) case *ssa.Const: if t, ok := v.Type().Underlying().(*types.Slice); ok && !v.IsNil() { // Non-nil []byte or []rune constant. obj = a.nextNode() a.addNodes(sliceToArray(t), "array in slice constant") a.endObject(obj, nil, v) } case *ssa.Capture: // For now, Captures have the same cardinality as globals. // TODO(adonovan): treat captures context-sensitively. } if a.log != nil { fmt.Fprintf(a.log, "\tglobalobj[%s] = n%d\n", v, obj) } a.globalobj[v] = obj } return obj } // Local object. obj, ok := a.localobj[v] if !ok { switch v := v.(type) { case *ssa.Alloc: obj = a.nextNode() a.addNodes(mustDeref(v.Type()), "alloc") a.endObject(obj, cgn, v) case *ssa.MakeSlice: obj = a.nextNode() a.addNodes(sliceToArray(v.Type()), "makeslice") a.endObject(obj, cgn, v) case *ssa.MakeChan: obj = a.nextNode() a.addNodes(v.Type().Underlying().(*types.Chan).Elem(), "makechan") a.endObject(obj, cgn, v) case *ssa.MakeMap: obj = a.nextNode() tmap := v.Type().Underlying().(*types.Map) a.addNodes(tmap.Key(), "makemap.key") a.addNodes(tmap.Elem(), "makemap.value") a.endObject(obj, cgn, v) case *ssa.MakeInterface: tConc := v.X.Type() obj = a.makeTagged(tConc, cgn, v) // Copy the value into it, if nontrivial. if x := a.valueNode(v.X); x != 0 { a.copy(obj+1, x, a.sizeof(tConc)) } case *ssa.FieldAddr: if xobj := a.objectNode(cgn, v.X); xobj != 0 { obj = xobj + nodeid(a.offsetOf(mustDeref(v.X.Type()), v.Field)) } case *ssa.IndexAddr: if xobj := a.objectNode(cgn, v.X); xobj != 0 { obj = xobj + 1 } case *ssa.Slice: obj = a.objectNode(cgn, v.X) case *ssa.Convert: // TODO(adonovan): opt: handle these cases too: // - unsafe.Pointer->*T conversion acts like Alloc // - string->[]byte/[]rune conversion acts like MakeSlice } if a.log != nil { fmt.Fprintf(a.log, "\tlocalobj[%s] = n%d\n", v.Name(), obj) } a.localobj[v] = obj } return obj }