Files
moxa/interp/cfg.go
Marc Vertes 6657e9a18b feat: add support for named output variables (#113)
* feat: add support for named output variables

Function output parameters are located at the start of the function
frame, uninitialized, as they could be only set through return
statements.

Supporting named output variables requires to:

- identify and allocate symbols corresponding to output names:
  done at ident parsing, with the funcRet() helper,

- compute the location of output name in the frame:
  done with retRank() helper,

- initialize the frame entry with the zero value of symbol type,
  as the symbol can be accessed and written at multiple times,
  and return not setting the variable (opposite to unnamed).
  Done with frameType() helper, which now takes into account
  output parameters.

* refactor: simplify memory management

Perform function frame analysis at pre-order, instead of post-order, to
initialize memory frame, and track value types. Remove all operation
involving unitialized types. Frame memory layout is now build
incrementally, instead of having to perform dedicated tree walks on AST.
2019-03-12 19:58:02 +01:00

1257 lines
33 KiB
Go

package interp
import (
"fmt"
"reflect"
"unicode"
)
// A CfgError represents an error during CFG build stage
type CfgError error
// Cfg generates a control flow graph (CFG) from AST (wiring successors in AST)
// and pre-compute frame sizes and indexes for all un-named (temporary) and named
// variables. A list of nodes of init functions is returned.
// Following this pass, the CFG is ready to run
func (interp *Interpreter) Cfg(root *Node) ([]*Node, error) {
scope := interp.universe
var loop, loopRestart *Node
var initNodes []*Node
var iotaValue int
var pkgName string
var err error
if root.kind != File {
// Set default package namespace for incremental parse
pkgName = "_"
if _, ok := interp.scope[pkgName]; !ok {
interp.scope[pkgName] = scope.pushBloc()
}
scope = interp.scope[pkgName]
}
root.Walk(func(n *Node) bool {
// Pre-order processing
if err != nil {
return false
}
switch n.kind {
case AssignStmt, Define:
if l := len(n.child); n.anc.kind == ConstDecl && l == 1 {
// Implicit iota assignment. TODO: replicate previous explicit assignment
n.child = append(n.child, &Node{anc: n, interp: interp, kind: Ident, ident: "iota"})
}
case BlockStmt:
if n.anc != nil && n.anc.kind == RangeStmt {
// For range block: ensure that array or map type is propagated to iterators
// prior to process block. We cannot perform this at RangeStmt pre-order because
// type of array like value is not yet known. This could be fixed in ast structure
// by setting array/map node as 1st child of ForRangeStmt instead of 3rd child of
// RangeStmt. The following workaround is less elegant but ok.
k, v := n.anc.child[0], n.anc.child[1]
var ktyp, vtyp *Type
switch n.anc.child[2].typ.cat {
case ValueT:
typ := n.anc.child[2].typ.rtype
switch typ.Kind() {
case reflect.Map:
n.anc.gen = rangeMap
ktyp = &Type{cat: ValueT, rtype: typ.Key()}
vtyp = &Type{cat: ValueT, rtype: typ.Elem()}
case reflect.Array, reflect.Slice:
ktyp = scope.getType("int")
vtyp = &Type{cat: ValueT, rtype: typ.Elem()}
}
case MapT:
n.anc.gen = rangeMap
ktyp = n.anc.child[2].typ.key
vtyp = n.anc.child[2].typ.val
case ArrayT:
ktyp = scope.getType("int")
vtyp = n.anc.child[2].typ.val
}
kindex := scope.add(ktyp)
scope.sym[k.ident] = &Symbol{index: kindex, kind: Var, typ: ktyp}
k.typ = ktyp
k.findex = kindex
vindex := scope.add(vtyp)
scope.sym[v.ident] = &Symbol{index: vindex, kind: Var, typ: vtyp}
v.typ = vtyp
v.findex = vindex
}
scope = scope.pushBloc()
case CaseClause:
scope = scope.pushBloc()
if sn := n.anc.anc; sn.kind == TypeSwitch && sn.child[1].action == Assign {
// Type switch clause with a var defined in switch guard
var typ *Type
if len(n.child) == 2 {
// 1 type in clause: define the var with this type in the case clause scope
switch sym, _, ok := scope.lookup(n.child[0].ident); {
case ok && sym.kind == Typ:
typ = sym.typ
case n.child[0].ident == "nil":
typ = scope.getType("interface{}")
default:
err = n.cfgError("%s is not a type", n.child[0].ident)
return false
}
} else {
// define the var with the type in the switch guard expression
typ = sn.child[1].child[1].child[0].typ
}
node := n.lastChild().child[0]
index := scope.add(typ)
scope.sym[node.ident] = &Symbol{index: index, kind: Var, typ: typ}
node.findex = index
node.typ = typ
}
case CommClause:
scope = scope.pushBloc()
if n.child[0].action == Assign {
ch := n.child[0].child[1].child[0]
if sym, _, ok := scope.lookup(ch.ident); ok {
assigned := n.child[0].child[0]
index := scope.add(sym.typ.val)
scope.sym[assigned.ident] = &Symbol{index: index, kind: Var, typ: sym.typ.val}
assigned.findex = index
assigned.typ = sym.typ.val
}
}
case CompositeLitExpr:
if n.child[0].isType(scope) {
// Get type from 1st child
n.typ, err = nodeType(interp, scope, n.child[0])
} else {
// Get type from ancestor (implicit type)
if n.anc.kind == KeyValueExpr && n == n.anc.child[0] {
n.typ = n.anc.typ.key
} else if n.anc.typ != nil {
n.typ = n.anc.typ.val
}
n.typ.untyped = true
}
// Propagate type to children, to handle implicit types
for _, c := range n.child {
c.typ = n.typ
}
case File:
pkgName = n.child[0].ident
if _, ok := interp.scope[pkgName]; !ok {
interp.scope[pkgName] = scope.pushBloc()
}
scope = interp.scope[pkgName]
n.findex = -1
case For0, ForRangeStmt:
loop, loopRestart = n, n.child[0]
scope = scope.pushBloc()
case For1, For2, For3, For3a, For4:
loop, loopRestart = n, n.lastChild()
scope = scope.pushBloc()
case FuncLit:
n.typ = nil // to force nodeType to recompute the type
n.typ, err = nodeType(interp, scope, n)
n.findex = scope.add(n.typ)
fallthrough
case FuncDecl:
n.val = n
// Add a frame indirection level as we enter in a func
scope = scope.pushFunc()
scope.def = n
if len(n.child[2].child) == 2 {
// Allocate frame space for return values, define output symbols
for _, c := range n.child[2].child[1].child {
var typ *Type
typ, err = nodeType(interp, scope, c.lastChild())
if len(c.child) > 1 {
for _, cc := range c.child[:len(c.child)-1] {
scope.sym[cc.ident] = &Symbol{index: scope.add(typ), kind: Var, typ: typ}
}
} else {
scope.add(typ)
}
}
}
if len(n.child[0].child) > 0 {
// define receiver symbol
var typ *Type
recvName := n.child[0].child[0].child[0].ident
typ, err = nodeType(interp, scope, n.child[0].child[0].lastChild())
scope.sym[recvName] = &Symbol{index: scope.add(typ), kind: Var, typ: typ}
}
for _, c := range n.child[2].child[0].child {
// define input parameter symbols
var typ *Type
typ, err = nodeType(interp, scope, c.lastChild())
if typ.variadic {
typ = &Type{cat: ArrayT, val: typ}
}
for _, cc := range c.child[:len(c.child)-1] {
scope.sym[cc.ident] = &Symbol{index: scope.add(typ), kind: Var, typ: typ}
}
}
if n.child[1].ident == "init" {
initNodes = append(initNodes, n)
}
case If0, If1, If2, If3:
scope = scope.pushBloc()
case Switch, SwitchIf, TypeSwitch:
// Make sure default clause is in last position
c := n.lastChild().child
if i, l := getDefault(n), len(c)-1; i >= 0 && i != l {
c[i], c[l] = c[l], c[i]
}
scope = scope.pushBloc()
loop = n
case ImportSpec, TypeSpec:
// processing already done in GTA pass
return false
case ArrayType, BasicLit, ChanType, FuncType, MapType, StructType:
n.typ, err = nodeType(interp, scope, n)
return false
}
return true
}, func(n *Node) {
// Post-order processing
if err != nil {
return
}
switch n.kind {
case Address:
wireChild(n)
n.typ = &Type{cat: PtrT, val: n.child[0].typ}
n.findex = scope.add(n.typ)
case AssignStmt, Define:
if n.anc.kind == TypeSwitch && n.anc.child[1] == n {
// type switch guard assignment: assign dest to concrete value of src
n.gen = nop
break
}
if n.anc.kind == CommClause {
n.gen = nop
break
}
dest, src := n.child[0], n.lastChild()
var sym *Symbol
var level int
if n.kind == Define {
if src.typ.cat == NilT {
err = src.cfgError("use of untyped nil")
break
}
switch {
case len(n.child) == 3:
// type is provided in var declaration
dest.typ, err = nodeType(interp, scope, n.child[1])
case isRegularCall(src) || isBinCall(src):
dest.typ = getReturnedType(src.child[0])
default:
dest.typ = src.typ
}
if scope.global {
// Do not overload existings symbols (defined in GTA) in global scope
sym, _, _ = scope.lookup(dest.ident)
} else {
sym = &Symbol{index: scope.add(dest.typ), kind: Var, global: scope.global}
scope.sym[dest.ident] = sym
}
dest.val = src.val
dest.recv = src.recv
dest.findex = sym.index
if src.kind == BasicLit {
sym.val = src.val
}
} else {
sym, level, _ = scope.lookup(dest.ident)
}
wireChild(n)
// Detect invalid float truncate
if isInt(dest.typ) && isFloat(src.typ) {
err = src.cfgError("invalid float truncate")
break
}
n.findex = dest.findex
n.val = dest.val
n.rval = dest.rval
// Propagate type
// TODO: Check that existing destination type matches source type
switch {
case src.action == Recv:
// Assign by reading from a receiving channel
n.gen = nop
src.findex = dest.findex // Set recv address to LHS
dest.typ = src.typ.val
case src.action == CompositeLit:
n.gen = nop
src.findex = dest.findex
src.level = level
case src.kind == BasicLit:
// TODO: perform constant folding and propagation here
if dest.typ.cat == InterfaceT {
src.val = reflect.ValueOf(src.val)
} else {
// Convert literal value to destination type
src.val = reflect.ValueOf(src.val).Convert(dest.typ.TypeOf())
src.typ = dest.typ
}
}
n.typ = dest.typ
if sym != nil {
sym.typ = n.typ
sym.recv = src.recv
}
n.level = level
// If LHS is an indirection, get reference instead of value, to allow setting
if dest.action == GetIndex {
if dest.child[0].typ.cat == MapT {
n.gen = assignMap
dest.gen = nop // skip getIndexMap
}
}
if n.anc.kind == ConstDecl {
iotaValue++
}
case IncDecStmt:
wireChild(n)
n.findex = n.child[0].findex
n.level = n.child[0].level
n.typ = n.child[0].typ
if sym, level, ok := scope.lookup(n.child[0].ident); ok {
sym.typ = n.typ
n.level = level
}
case AssignXStmt:
wireChild(n)
l := len(n.child) - 1
switch n.child[l].kind {
case IndexExpr:
n.child[l].gen = getIndexMap2
n.gen = nop
case TypeAssertExpr:
n.child[l].gen = typeAssert2
n.gen = nop
}
case DefineX:
wireChild(n)
l := len(n.child) - 1
var types []*Type
switch n.child[l].kind {
case CallExpr:
if funtype := n.child[l].child[0].typ; funtype.cat == ValueT {
// Handle functions imported from runtime
for i := 0; i < funtype.rtype.NumOut(); i++ {
types = append(types, &Type{cat: ValueT, rtype: funtype.rtype.Out(i)})
}
} else {
types = funtype.ret
}
if l > len(types) {
n.gen = assignX2
}
case IndexExpr:
types = append(types, n.child[l].child[0].typ.val, scope.getType("bool"))
n.child[l].gen = getIndexMap2
n.gen = nop
case TypeAssertExpr:
types = append(types, n.child[l].child[1].typ, scope.getType("bool"))
n.child[l].gen = typeAssert2
n.gen = nop
case UnaryExpr:
if n.child[l].action == Recv {
types = append(types, n.child[l].child[0].typ.val, scope.getType("bool"))
}
default:
err = n.cfgError("unsupported assign expression")
return
}
for i, t := range types {
index := scope.add(t)
scope.sym[n.child[i].ident] = &Symbol{index: index, kind: Var, typ: t}
n.child[i].typ = t
n.child[i].findex = index
}
case BinaryExpr:
wireChild(n)
nilSym := interp.universe.sym["nil"]
if t0, t1 := n.child[0].typ, n.child[1].typ; !t0.untyped && !t1.untyped && t0.id() != t1.id() {
err = n.cfgError("mismatched types %s and %s", t0.id(), t1.id())
break
}
switch n.action {
case NotEqual:
n.typ = scope.getType("bool")
if n.child[0].sym == nilSym || n.child[1].sym == nilSym {
n.gen = isNotNil
}
case Equal:
n.typ = scope.getType("bool")
if n.child[0].sym == nilSym || n.child[1].sym == nilSym {
n.gen = isNil
}
case Greater, GreaterEqual, Lower, LowerEqual:
n.typ = scope.getType("bool")
default:
n.typ, err = nodeType(interp, scope, n)
}
// TODO: Possible optimisation: if type is bool and not in assignment or call, then skip result store
n.findex = scope.add(n.typ)
case IndexExpr:
wireChild(n)
n.typ = n.child[0].typ.val
n.findex = scope.add(n.typ)
n.recv = &Receiver{node: n}
if n.child[0].typ.cat == MapT {
n.gen = getIndexMap
} else if n.child[0].typ.cat == ArrayT {
n.gen = getIndexArray
}
case BlockStmt:
wireChild(n)
if len(n.child) > 0 {
n.findex = n.lastChild().findex
n.val = n.lastChild().val
n.sym = n.lastChild().sym
n.typ = n.lastChild().typ
}
scope = scope.pop()
case ConstDecl:
wireChild(n)
iotaValue = 0
case DeclStmt, ExprStmt, VarDecl, SendStmt:
wireChild(n)
n.findex = n.lastChild().findex
n.val = n.lastChild().val
n.sym = n.lastChild().sym
n.typ = n.lastChild().typ
case Break:
n.tnext = loop
case CallExpr:
wireChild(n)
switch {
case isBuiltinCall(n):
n.gen = n.child[0].sym.builtin
n.child[0].typ = &Type{cat: BuiltinT}
switch n.child[0].ident {
case "append":
if n.typ = scope.getType(n.child[1].ident); n.typ == nil {
n.typ, err = nodeType(interp, scope, n.child[1])
}
case "cap", "len":
n.typ = scope.getType("int")
case "make":
if n.typ = scope.getType(n.child[1].ident); n.typ == nil {
n.typ, err = nodeType(interp, scope, n.child[1])
}
n.child[1].val = n.typ
n.child[1].kind = BasicLit
case "recover":
n.typ = scope.getType("interface{}")
}
if n.typ != nil {
n.findex = scope.add(n.typ)
}
case n.child[0].isType(scope):
// Type conversion expression
n.gen = convert
n.typ = n.child[0].typ
n.findex = scope.add(n.typ)
case isBinCall(n):
n.gen = callBin
if typ := n.child[0].typ.rtype; typ.NumOut() > 0 {
n.typ = &Type{cat: ValueT, rtype: typ.Out(0)}
n.findex = scope.add(n.typ)
for i := 1; i < typ.NumOut(); i++ {
scope.add(&Type{cat: ValueT, rtype: typ.Out(i)})
}
}
default:
if n.child[0].action == GetFunc {
// allocate frame entry for anonymous function
scope.add(n.child[0].typ)
}
if typ := n.child[0].typ; len(typ.ret) > 0 {
n.typ = typ.ret[0]
n.findex = scope.add(n.typ)
for _, t := range typ.ret[1:] {
scope.add(t)
}
} else {
n.findex = -1
}
}
case CaseBody:
wireChild(n)
if typeSwichAssign(n) && len(n.child) > 1 {
n.start = n.child[1].start
} else {
n.start = n.child[0].start
}
case CaseClause:
scope = scope.pop()
case CommClause:
wireChild(n)
if len(n.child) > 1 {
n.start = n.child[1].start // Skip chan operation, performed by select
} else {
n.start = n.child[0].start // default clause
}
n.lastChild().tnext = n.anc.anc // exit node is SelectStmt
scope = scope.pop()
case CompositeLitExpr:
wireChild(n)
if n.anc.action != Assign {
n.findex = scope.add(n.typ)
}
// TODO: Check that composite literal expr matches corresponding type
switch n.typ.cat {
case ArrayT:
n.gen = arrayLit
case MapT:
n.gen = mapLit
case StructT:
if n.lastChild().kind == KeyValueExpr {
n.gen = compositeSparse
} else {
n.gen = compositeLit
}
case ValueT:
n.gen = compositeBin
}
case Continue:
n.tnext = loopRestart
case Fallthrough:
if n.anc.kind != CaseBody {
err = n.cfgError("fallthrough statement out of place")
}
case File:
wireChild(n)
scope = scope.pop()
case For0: // for {}
body := n.child[0]
n.start = body.start
body.tnext = n.start
loop, loopRestart = nil, nil
scope = scope.pop()
case For1: // for cond {}
cond, body := n.child[0], n.child[1]
n.start = cond.start
cond.tnext = body.start
cond.fnext = n
body.tnext = cond.start
loop, loopRestart = nil, nil
scope = scope.pop()
case For2: // for init; cond; {}
init, cond, body := n.child[0], n.child[1], n.child[2]
n.start = init.start
init.tnext = cond.start
cond.tnext = body.start
cond.fnext = n
body.tnext = cond.start
loop, loopRestart = nil, nil
scope = scope.pop()
case For3: // for ; cond; post {}
cond, post, body := n.child[0], n.child[1], n.child[2]
n.start = cond.start
cond.tnext = body.start
cond.fnext = n
body.tnext = post.start
post.tnext = cond.start
loop, loopRestart = nil, nil
scope = scope.pop()
case For3a: // for int; ; post {}
init, post, body := n.child[0], n.child[1], n.child[2]
n.start = init.start
init.tnext = body.start
body.tnext = post.start
post.tnext = body.start
loop, loopRestart = nil, nil
scope = scope.pop()
case For4: // for init; cond; post {}
init, cond, post, body := n.child[0], n.child[1], n.child[2], n.child[3]
n.start = init.start
init.tnext = cond.start
cond.tnext = body.start
cond.fnext = n
body.tnext = post.start
post.tnext = cond.start
loop, loopRestart = nil, nil
scope = scope.pop()
case ForRangeStmt:
loop, loopRestart = nil, nil
n.start = n.child[0].start
n.child[0].fnext = n
scope = scope.pop()
case FuncDecl:
n.types = scope.types
scope = scope.pop()
funcName := n.child[1].ident
n.start = n.child[3].start
interp.scope[pkgName].sym[funcName].index = -1 // to force value to n.val
interp.scope[pkgName].sym[funcName].typ = n.typ
interp.scope[pkgName].sym[funcName].kind = Func
interp.scope[pkgName].sym[funcName].node = n
case FuncLit:
n.types = scope.types
scope = scope.pop()
case GoStmt:
wireChild(n)
case Ident:
if isKey(n) || isNewDefine(n) {
break
} else if sym, level, ok := scope.lookup(n.ident); ok {
// Found symbol, populate node info
n.typ, n.findex, n.level = sym.typ, sym.index, level
if n.findex < 0 {
n.val = sym.node
} else {
n.sym = sym
switch {
case sym.kind == Const && sym.val != nil:
n.val = sym.val
n.kind = BasicLit
case n.ident == "iota":
n.val = iotaValue
n.kind = BasicLit
case n.ident == "nil":
n.kind = BasicLit
n.val = nil
case sym.kind == Bin:
if sym.val == nil {
n.kind = Rtype
} else {
n.kind = Rvalue
}
n.typ = sym.typ
n.rval = sym.val.(reflect.Value)
case sym.kind == Bltn:
if n.anc.kind != CallExpr {
err = n.cfgError("use of builtin %s not in function call", n.ident)
}
}
}
if n.sym != nil {
n.recv = n.sym.recv
}
} else {
err = n.cfgError("undefined: %s", n.ident)
}
case If0: // if cond {}
cond, tbody := n.child[0], n.child[1]
n.start = cond.start
cond.tnext = tbody.start
cond.fnext = n
tbody.tnext = n
scope = scope.pop()
case If1: // if cond {} else {}
cond, tbody, fbody := n.child[0], n.child[1], n.child[2]
n.start = cond.start
cond.tnext = tbody.start
cond.fnext = fbody.start
tbody.tnext = n
fbody.tnext = n
scope = scope.pop()
case If2: // if init; cond {}
init, cond, tbody := n.child[0], n.child[1], n.child[2]
n.start = init.start
tbody.tnext = n
init.tnext = cond.start
cond.tnext = tbody.start
cond.fnext = n
scope = scope.pop()
case If3: // if init; cond {} else {}
init, cond, tbody, fbody := n.child[0], n.child[1], n.child[2], n.child[3]
n.start = init.start
init.tnext = cond.start
cond.tnext = tbody.start
cond.fnext = fbody.start
tbody.tnext = n
fbody.tnext = n
scope = scope.pop()
case KeyValueExpr:
wireChild(n)
case LandExpr:
n.start = n.child[0].start
n.child[0].tnext = n.child[1].start
n.child[0].fnext = n
n.child[1].tnext = n
n.typ = n.child[0].typ
n.findex = scope.add(n.typ)
case LorExpr:
n.start = n.child[0].start
n.child[0].tnext = n
n.child[0].fnext = n.child[1].start
n.child[1].tnext = n
n.typ = n.child[0].typ
n.findex = scope.add(n.typ)
case ParenExpr:
wireChild(n)
n.findex = n.lastChild().findex
n.typ = n.lastChild().typ
case RangeStmt:
n.start = n.child[2] // Get array or map object
n.child[2].tnext = n.child[0].start // then go to iterator init
n.child[0].tnext = n // then go to range function
n.tnext = n.child[3].start // then go to range body
n.child[3].tnext = n // then body go to range function (loop)
n.child[0].gen = empty // init filled later by generator
case ReturnStmt:
wireChild(n)
n.tnext = nil
for i, c := range n.child {
if c.typ.cat == NilT {
// nil: Set node value to zero of return type
f := scope.def
var typ *Type
typ, err = nodeType(interp, scope, f.child[2].child[1].child[i].lastChild())
if err != nil {
break
}
if c.val, err = typ.zero(); err != nil {
break
}
}
}
case SelectorExpr:
wireChild(n)
n.typ = n.child[0].typ
n.recv = n.child[0].recv
if n.typ == nil {
err = n.cfgError("undefined type")
break
}
if n.typ.cat == ValueT {
// Handle object defined in runtime, try to find field or method
// Search for method first, as it applies both to types T and *T
// Search for field must then be performed on type T only (not *T)
switch method, ok := n.typ.rtype.MethodByName(n.child[1].ident); {
case ok:
n.val = method.Index
n.gen = getIndexBinMethod
n.typ = &Type{cat: ValueT, rtype: method.Type}
n.recv = &Receiver{node: n.child[0]}
case n.typ.rtype.Kind() == reflect.Ptr:
if field, ok := n.typ.rtype.Elem().FieldByName(n.child[1].ident); ok {
n.typ = &Type{cat: ValueT, rtype: field.Type}
n.val = field.Index
n.gen = getPtrIndexSeq
} else {
err = n.cfgError("undefined field or method: %s", n.child[1].ident)
}
case n.typ.rtype.Kind() == reflect.Struct:
if field, ok := n.typ.rtype.FieldByName(n.child[1].ident); ok {
n.typ = &Type{cat: ValueT, rtype: field.Type}
n.val = field.Index
n.gen = getIndexSeq
} else {
err = n.cfgError("undefined field or method: %s", n.child[1].ident)
}
default:
err = n.cfgError("undefined field or method: %s", n.child[1].ident)
}
} else if n.typ.cat == PtrT && n.typ.val.cat == ValueT {
// Handle pointer on object defined in runtime
if field, ok := n.typ.val.rtype.FieldByName(n.child[1].ident); ok {
n.typ = &Type{cat: ValueT, rtype: field.Type}
n.val = field.Index
n.gen = getPtrIndexSeq
} else if method, ok := n.typ.val.rtype.MethodByName(n.child[1].ident); ok {
n.val = method.Index
n.typ = &Type{cat: ValueT, rtype: method.Type}
n.recv = &Receiver{node: n.child[0]}
n.gen = getIndexBinMethod
} else if method, ok := reflect.PtrTo(n.typ.val.rtype).MethodByName(n.child[1].ident); ok {
n.val = method.Index
n.gen = getIndexBinMethod
n.typ = &Type{cat: ValueT, rtype: method.Type}
n.recv = &Receiver{node: n.child[0]}
} else {
err = n.cfgError("undefined selector: %s", n.child[1].ident)
}
} else if n.typ.cat == BinPkgT {
// Resolve binary package symbol: a type or a value
name := n.child[1].ident
pkg := n.child[0].sym.path
if s, ok := interp.binValue[pkg][name]; ok {
n.kind = Rvalue
n.rval = s
n.typ = &Type{cat: ValueT, rtype: s.Type()}
n.gen = nop
} else if s, ok := interp.binType[pkg][name]; ok {
n.kind = Rtype
n.typ = &Type{cat: ValueT, rtype: s}
n.gen = nop
} else {
err = n.cfgError("package %s \"%s\" has no symbol %s", n.child[0].ident, pkg, name)
}
} else if n.typ.cat == SrcPkgT {
pkg, name := n.child[0].ident, n.child[1].ident
// Resolve source package symbol
if sym, ok := interp.scope[pkg].sym[name]; ok {
n.findex = sym.index
n.val = sym.node
n.gen = nop
n.typ = sym.typ
n.sym = sym
} else {
err = n.cfgError("undefined selector: %s", n.child[1].ident)
}
} else if m, lind := n.typ.lookupMethod(n.child[1].ident); m != nil {
// Handle method
n.gen = getMethod
n.val = m
n.typ = m.typ
n.recv = &Receiver{node: n.child[0], index: lind}
} else if ti := n.typ.lookupField(n.child[1].ident); len(ti) > 0 {
// Handle struct field
n.val = ti
switch n.typ.cat {
case InterfaceT:
n.typ = n.typ.fieldSeq(ti)
n.gen = getMethodByName
n.action = Method
case PtrT:
n.typ = n.typ.fieldSeq(ti)
n.gen = getPtrIndexSeq
default:
n.gen = getIndexSeq
n.typ = n.typ.fieldSeq(ti)
if n.typ.cat == FuncT {
// function in a struct field is always wrapped in reflect.Value
rtype := n.typ.TypeOf()
n.typ = &Type{cat: ValueT, rtype: rtype}
}
}
} else {
err = n.cfgError("undefined selector: %s", n.child[1].ident)
}
if n.findex != -1 {
n.findex = scope.add(n.typ)
}
case SelectStmt:
wireChild(n)
// Move action to block statement, so select node can be an exit point
n.child[0].gen = _select
n.start = n.child[0]
case StarExpr:
switch {
case n.anc.kind == Define && len(n.anc.child) == 3 && n.anc.child[1] == n:
// pointer type expression in a var definition
n.gen = nop
case n.anc.kind == ValueSpec && n.anc.lastChild() == n:
// pointer type expression in a value spec
n.gen = nop
case n.anc.kind == Field:
// pointer type expression in a field expression (arg or struct field)
n.gen = nop
default:
// dereference expression
wireChild(n)
n.typ = n.child[0].typ.val
n.findex = scope.add(n.typ)
}
case TypeSwitch:
// Check that cases expressions are all different
usedCase := map[string]bool{}
for _, c := range n.lastChild().child {
for _, t := range c.child[:len(c.child)-1] {
tid := t.typ.id()
if usedCase[tid] {
err = c.cfgError("duplicate case %s in type switch", t.ident)
return
}
usedCase[tid] = true
}
}
fallthrough
case Switch:
sbn := n.lastChild() // switch block node
clauses := sbn.child
l := len(clauses)
// Chain case clauses
for i, c := range clauses[:l-1] {
c.fnext = clauses[i+1] // chain to next clause
body := c.lastChild()
c.tnext = body.start
if len(body.child) > 0 && body.lastChild().kind == Fallthrough {
if n.kind == TypeSwitch {
err = body.lastChild().cfgError("cannot fallthrough in type switch")
}
body.tnext = clauses[i+1].lastChild().start
} else {
body.tnext = n
}
}
c := clauses[l-1]
c.tnext = c.lastChild().start
if n.child[0].action == Assign &&
(n.child[0].child[0].kind != TypeAssertExpr || len(n.child[0].child[0].child) > 1) {
// switch init statement is defined
n.start = n.child[0].start
n.child[0].tnext = sbn.start
} else {
n.start = sbn.start
}
scope = scope.pop()
loop = nil
case SwitchIf: // like an if-else chain
sbn := n.lastChild() // switch block node
clauses := sbn.child
l := len(clauses)
// Wire case clauses in reverse order so the next start node is already resolved when used.
for i := l - 1; i >= 0; i-- {
c := clauses[i]
c.gen = nop
body := c.lastChild()
if len(c.child) > 1 {
cond := c.child[0]
cond.tnext = body.start
if i == l-1 {
cond.fnext = n
} else {
cond.fnext = clauses[i+1].start
}
c.start = cond.start
} else {
c.start = body.start
}
// If last case body statement is a fallthrough, then jump to next case body
if i < l-1 && len(body.child) > 0 && body.lastChild().kind == Fallthrough {
body.tnext = clauses[i+1].lastChild().start
}
}
sbn.start = clauses[0].start
if n.child[0].action == Assign {
// switch init statement is defined
n.start = n.child[0].start
n.child[0].tnext = sbn.start
} else {
n.start = sbn.start
}
scope = scope.pop()
loop = nil
case TypeAssertExpr:
if len(n.child) > 1 {
if n.child[1].typ == nil {
n.child[1].typ = scope.getType(n.child[1].ident)
}
if n.anc.action != AssignX {
n.typ = n.child[1].typ
n.findex = scope.add(n.typ)
}
} else {
n.gen = nop
}
case SliceExpr, UnaryExpr:
wireChild(n)
n.typ = n.child[0].typ
// TODO: Optimisation: avoid allocation if boolean branch op (i.e. '!' in an 'if' expr)
n.findex = scope.add(n.typ)
case ValueSpec:
l := len(n.child) - 1
if n.typ = n.child[l].typ; n.typ == nil {
n.typ, err = nodeType(interp, scope, n.child[l])
if err != nil {
return
}
}
for _, c := range n.child[:l] {
index := scope.add(n.typ)
scope.sym[c.ident] = &Symbol{index: index, kind: Var, typ: n.typ}
c.typ = n.typ
c.findex = index
}
}
})
if scope != interp.universe {
scope.pop()
}
return initNodes, err
}
func (n *Node) cfgError(format string, a ...interface{}) CfgError {
a = append([]interface{}{n.fset.Position(n.pos)}, a...)
return CfgError(fmt.Errorf("%s: "+format, a...))
}
func genRun(node *Node) error {
var err CfgError
node.Walk(func(n *Node) bool {
if err != nil {
return false
}
switch n.kind {
case FuncType:
if len(n.anc.child) == 4 {
// function body entry point
setExec(n.anc.child[3].start)
}
case ConstDecl, VarDecl:
setExec(n.start)
return false
}
return true
}, nil)
return err
}
// Find default case clause index of a switch statement, if any
func getDefault(n *Node) int {
for i, c := range n.lastChild().child {
if len(c.child) == 1 {
return i
}
}
return -1
}
// isType returns true if node refers to a type definition, false otherwise
func (n *Node) isType(scope *Scope) bool {
switch n.kind {
case ArrayType, ChanType, FuncType, MapType, StructType, Rtype:
return true
case SelectorExpr:
pkg, name := n.child[0].ident, n.child[1].ident
if sym, _, ok := scope.lookup(pkg); ok {
if p, ok := n.interp.binType[sym.path]; ok && p[name] != nil {
return true // Imported binary type
}
if p, ok := n.interp.scope[pkg]; ok && p.sym[name] != nil && p.sym[name].kind == Typ {
return true // Imported source type
}
}
case Ident:
return scope.getType(n.ident) != nil
}
return false
}
// wireChild wires AST nodes for CFG in subtree
func wireChild(n *Node) {
// Set start node, in subtree (propagated to ancestors by post-order processing)
for _, child := range n.child {
switch child.kind {
case ArrayType, ChanType, FuncDecl, ImportDecl, MapType, BasicLit, Ident, TypeDecl:
continue
default:
n.start = child.start
}
break
}
// Chain sequential operations inside a block (next is right sibling)
for i := 1; i < len(n.child); i++ {
switch n.child[i].kind {
case FuncDecl:
n.child[i-1].tnext = n.child[i]
default:
n.child[i-1].tnext = n.child[i].start
}
}
// Chain subtree next to self
for i := len(n.child) - 1; i >= 0; i-- {
switch n.child[i].kind {
case ArrayType, ChanType, ImportDecl, MapType, FuncDecl, BasicLit, Ident, TypeDecl:
continue
case Break, Continue, ReturnStmt:
// tnext is already computed, no change
default:
n.child[i].tnext = n
}
break
}
}
// last returns the last child of a node
func (n *Node) lastChild() *Node { return n.child[len(n.child)-1] }
func isKey(n *Node) bool {
return n.anc.kind == File ||
(n.anc.kind == SelectorExpr && n.anc.child[0] != n) ||
(n.anc.kind == KeyValueExpr && n.anc.child[0] == n)
}
func isNewDefine(n *Node) bool {
if n.ident == "_" {
return true
}
if n.anc.kind == Define && n.anc.child[0] == n {
return true
}
if n.anc.kind == DefineX && n.anc.lastChild() != n {
return true
}
if n.anc.kind == RangeStmt && (n.anc.child[0] == n || n.anc.child[1] == n) {
return true
}
if n.anc.kind == ValueSpec && n.anc.lastChild() != n {
return true
}
return false
}
func isBuiltinCall(n *Node) bool {
return n.kind == CallExpr && n.child[0].sym != nil && n.child[0].sym.kind == Bltn
}
func isBinCall(n *Node) bool {
return n.kind == CallExpr && n.child[0].typ.cat == ValueT && n.child[0].typ.rtype.Kind() == reflect.Func
}
func isRegularCall(n *Node) bool {
return n.kind == CallExpr && n.child[0].typ.cat == FuncT
}
func variadicPos(n *Node) int {
if len(n.child[0].typ.arg) == 0 {
return -1
}
last := len(n.child[0].typ.arg) - 1
if n.child[0].typ.arg[last].variadic {
return last
}
return -1
}
func canExport(name string) bool {
if r := []rune(name); len(r) > 0 && unicode.IsUpper(r[0]) {
return true
}
return false
}
func getExec(n *Node) Builtin {
if n == nil {
return nil
}
if n.exec == nil {
setExec(n)
}
return n.exec
}
// setExec recursively sets the node exec builtin function by walking the CFG
// from the entry point (first node to exec).
func setExec(n *Node) {
if n.exec != nil {
return
}
seen := map[*Node]bool{}
var set func(n *Node)
set = func(n *Node) {
if n == nil || n.exec != nil {
return
}
seen[n] = true
if n.tnext != nil && n.tnext.exec == nil {
if seen[n.tnext] {
m := n.tnext
n.tnext.exec = func(f *Frame) Builtin { return m.exec(f) }
} else {
set(n.tnext)
}
}
if n.fnext != nil && n.fnext.exec == nil {
if seen[n.fnext] {
m := n.fnext
n.fnext.exec = func(f *Frame) Builtin { return m.exec(f) }
} else {
set(n.fnext)
}
}
n.gen(n)
}
set(n)
}
func getReturnedType(n *Node) *Type {
switch n.typ.cat {
case BuiltinT:
return n.anc.typ
case ValueT:
if n.typ.rtype.NumOut() > 0 {
return &Type{cat: ValueT, rtype: n.typ.rtype.Out(0)}
}
return &Type{cat: ValueT, rtype: n.typ.rtype}
}
return n.typ.ret[0]
}
func typeSwichAssign(n *Node) bool {
ts := n.anc.anc.anc
return ts.kind == TypeSwitch && ts.child[1].action == Assign
}