Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pkg/proc: implement composite literals #3691

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions _fixtures/testvariables2.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,8 +250,9 @@ func main() {
}
var mnil map[string]astruct = nil
m2 := map[int]*astruct{1: &astruct{10, 11}}
m3 := map[astruct]int{{1, 1}: 42, {2, 2}: 43}
m3 := map[astruct]int{{1, 1}: 42, {2, 2}: 43, {1, 0}: 44, {0, 1}: 45, {}: 46}
m4 := map[astruct]astruct{{1, 1}: {11, 11}, {2, 2}: {22, 22}}
var mint map[int]int
upnil := unsafe.Pointer(nil)
up1 := unsafe.Pointer(&i1)
i4 := 800
Expand Down Expand Up @@ -391,6 +392,8 @@ func main() {
int3chan <- ThreeInts{a: 2}
int3chan <- ThreeInts{a: 3}

m8 := map[[2]int]int{{1, 1}: 42, {2, 2}: 43, {1, 0}: 44, {0, 1}: 45, {}: 46}

var ptrinf2 pptr
ptrinf2 = &ptrinf2

Expand All @@ -404,5 +407,5 @@ func main() {
longslice := make([]int, 100, 100)

runtime.Breakpoint()
fmt.Println(i1, i2, i3, p1, pp1, amb1, s1, s3, a0, a1, p2, p3, s2, as1, str1, f1, fn1, fn2, nilslice, nilptr, ch1, chnil, m1, mnil, m2, m3, m4, m5, upnil, up1, i4, i5, i6, err1, err2, errnil, iface1, iface2, ifacenil, arr1, parr, cpx1, const1, iface3, iface4, recursive1, recursive1.x, iface5, iface2fn1, iface2fn2, bencharr, benchparr, mapinf, mainMenu, b, b2, sd, anonstruct1, anonstruct2, anoniface1, anonfunc, mapanonstruct1, ifacearr, efacearr, ni8, ni16, ni32, ni64, pinf, ninf, nan, zsvmap, zsslice, zsvar, tm, rettm, errtypednil, emptyslice, emptymap, byteslice, bytestypeslice, runeslice, bytearray, bytetypearray, runearray, longstr, nilstruct, as2, as2.NonPointerReceiverMethod, s4, iface2map, issue1578, ll, unread, w2, w3, w4, w5, longarr, longslice, val, m6, m7, cl, tim1, tim2, typedstringvar, namedA1, namedA2, astructName1(namedA2), badslice, tim3, int3chan, longbyteslice)
fmt.Println(i1, i2, i3, p1, pp1, amb1, s1, s3, a0, a1, p2, p3, s2, as1, str1, f1, fn1, fn2, nilslice, nilptr, ch1, chnil, m1, mnil, mint, m2, m3, m4, m5, upnil, up1, i4, i5, i6, err1, err2, errnil, iface1, iface2, ifacenil, arr1, parr, cpx1, const1, iface3, iface4, recursive1, recursive1.x, iface5, iface2fn1, iface2fn2, bencharr, benchparr, mapinf, mainMenu, b, b2, sd, anonstruct1, anonstruct2, anoniface1, anonfunc, mapanonstruct1, ifacearr, efacearr, ni8, ni16, ni32, ni64, pinf, ninf, nan, zsvmap, zsslice, zsvar, tm, rettm, errtypednil, emptyslice, emptymap, byteslice, bytestypeslice, runeslice, bytearray, bytetypearray, runearray, longstr, nilstruct, as2, as2.NonPointerReceiverMethod, s4, iface2map, issue1578, ll, unread, w2, w3, w4, w5, longarr, longslice, val, m6, m7, cl, tim1, tim2, typedstringvar, namedA1, namedA2, astructName1(namedA2), badslice, tim3, int3chan, longbyteslice, m8)
}
41 changes: 41 additions & 0 deletions pkg/proc/eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -1047,6 +1047,9 @@ func (stack *evalStack) executeOp() {
case *evalop.TypeAssert:
scope.evalTypeAssert(op, stack)

case *evalop.CompositeLit:
scope.evalCompositeLit(op, stack)

case *evalop.PointerDeref:
scope.evalPointerDeref(op, stack)

Expand Down Expand Up @@ -1782,6 +1785,44 @@ func (scope *EvalScope) evalStructSelector(op *evalop.Select, stack *evalStack)
stack.pushErr(xv.structMember(op.Name))
}

func (scope *EvalScope) evalCompositeLit(op *evalop.CompositeLit, stack *evalStack) {
// TODO Validate types of children

val := newVariable("", 0x0, op.DwarfType, scope.BinInfo, scope.Mem)
val.Len = int64(op.Count)
val.Cap = int64(op.Count)
val.Children = make([]Variable, op.Count)

// in reverse order, since it's a LIFO stack
for i := 0; i < op.Count; i++ {
j := op.Count - i - 1
val.Children[j] = *stack.pop()
}

switch typ := op.DwarfType.(type) {
case *godwarf.StructType:
// apply field names
for i := range val.Children {
val.Children[i].Name = typ.Field[i].Name
}

case *godwarf.MapType:
// adjust the length/cap
val.Len /= 2
val.Cap /= 2

case *godwarf.ArrayType,
*godwarf.SliceType:
// nothing to do here

default:
stack.err = fmt.Errorf("composite literals of %v not supported", op.DwarfType)
return
}

stack.push(val)
}

// Evaluates expressions <subexpr>.(<type>)
func (scope *EvalScope) evalTypeAssert(op *evalop.TypeAssert, stack *evalStack) {
xv := stack.pop()
Expand Down
219 changes: 219 additions & 0 deletions pkg/proc/evalop/evalcompile.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"go/printer"
"go/scanner"
"go/token"
"reflect"
"strconv"
"strings"

Expand Down Expand Up @@ -290,6 +291,9 @@ func (ctx *compileCtx) compileAST(t ast.Expr) error {
case *ast.BasicLit:
ctx.pushOp(&PushConst{constant.MakeFromLiteral(node.Value, node.Kind, 0)})

case *ast.CompositeLit:
return ctx.compileCompositeLit(node)

default:
return fmt.Errorf("expression %T not implemented", t)
}
Expand Down Expand Up @@ -574,6 +578,221 @@ func (ctx *compileCtx) compileFunctionCall(node *ast.CallExpr) error {
return nil
}

func (ctx *compileCtx) compileCompositeLit(node *ast.CompositeLit) error {
typ, err := ctx.FindTypeExpr(node.Type)
if err != nil {
return err
}

switch typ := typ.(type) {
case *godwarf.StructType:
return ctx.compileStructLit(typ, node.Elts)
case *godwarf.ArrayType:
return ctx.compileArrayOrSliceLit(typ, typ.Type, typ.Count, node.Elts)
case *godwarf.SliceType:
return ctx.compileArrayOrSliceLit(typ, typ.ElemType, -1, node.Elts)
case *godwarf.MapType:
return ctx.compileMapLit(typ, node.Elts)
default:
return fmt.Errorf("composite literals of %v not supported", typ)
}
}

func (ctx *compileCtx) compileStructLit(typ *godwarf.StructType, elements []ast.Expr) error {
fields := map[string]int{}
for i, f := range typ.Field {
fields[f.Name] = i
}

var isNamed, isPos bool
values := make([]ast.Expr, len(typ.Field))
for i, el := range elements {
kv, ok := el.(*ast.KeyValueExpr)
if ok {
isNamed = true
} else {
isPos = true
}
if isNamed && isPos {
return errors.New("cannot mix positional and named values in a composite literal")
}

if isPos {
if i >= len(typ.Field) {
return fmt.Errorf("too many values for %v: want %v, got %v", typ, len(typ.Field), len(elements))
}
values[i] = el
continue
}

ident, ok := kv.Key.(*ast.Ident)
if !ok {
return fmt.Errorf("expression %T not supported as a composite literal key for a struct type", kv.Key)
}

i, ok := fields[ident.Name]
if !ok {
return fmt.Errorf("%s is not a field of %v", ident.Name, typ)
}
if values[i] != nil {
return fmt.Errorf("duplicate field %s in struct literal", ident.Name)
}
values[i] = kv.Value
}
if isPos && len(elements) < len(typ.Field) {
return fmt.Errorf("too few values for %v: want %v, got %v", typ, len(typ.Field), len(elements))
}

// push values
for i, v := range values {
if v != nil {
err := ctx.compileAST(v)
if err != nil {
return err
}
continue
}

// add the default value for the field
err := ctx.pushZero(typ.Field[i].Type)
if err != nil {
return err
}
}

ctx.pushOp(&CompositeLit{typ, len(values)})

return nil
}

func (ctx *compileCtx) compileArrayOrSliceLit(typ, elTyp godwarf.Type, count int64, elements []ast.Expr) error {
var values []ast.Expr
if count >= 0 {
values = make([]ast.Expr, count)
}

i := -1
for _, el := range elements {
i++
if kv, ok := el.(*ast.KeyValueExpr); ok {
lit, ok := kv.Key.(*ast.BasicLit)
if !ok {
return fmt.Errorf("unsupported non-constant index for array or slice literal")
}
cv := constant.MakeFromLiteral(lit.Value, lit.Kind, 0)
j, ok := constant.Int64Val(cv)
if !ok {
return fmt.Errorf("cannot use a %v as an index for a array or slice literal", cv.Kind())
}
i = int(j)
el = kv.Value
}

if count >= 0 && i >= int(count) {
return fmt.Errorf("index %d out of bounds for array or slice literal", i)
} else if len(values) <= i {
values = append(values, make([]ast.Expr, i-len(values)+1)...)
}

if values[i] != nil {
return fmt.Errorf("duplicate index %d in array or slice literal", i)
}
values[i] = el
}

// push values
for _, v := range values {
if v != nil {
err := ctx.compileAST(v)
if err != nil {
return err
}
continue
}

// add the default value for the field
err := ctx.pushZero(elTyp)
if err != nil {
return err
}
}

ctx.pushOp(&CompositeLit{typ, len(values)})

return nil
}

func (ctx *compileCtx) compileMapLit(typ *godwarf.MapType, elements []ast.Expr) error {
for _, el := range elements {
kv, ok := el.(*ast.KeyValueExpr)
if !ok {
// should not happen
panic(fmt.Errorf("map literal contains a %T!?", el))
}

// parse the key and value
err := ctx.compileAST(kv.Key)
if err != nil {
return err
}
err = ctx.compileAST(kv.Value)
if err != nil {
return err
}
}

ctx.pushOp(&CompositeLit{typ, len(elements) * 2})

return nil
}

func (ctx *compileCtx) pushZero(typ godwarf.Type) error {
// add the default value for the field
switch typ.Common().ReflectKind {
case reflect.Bool:
ctx.pushOp(&PushConst{constant.MakeBool(false)})
return nil

case reflect.Int,
reflect.Int8,
reflect.Int16,
reflect.Int32,
reflect.Int64,
reflect.Uint,
reflect.Uint8,
reflect.Uint16,
reflect.Uint32,
reflect.Uint64,
reflect.Uintptr,
reflect.Float32,
reflect.Float64,
reflect.Complex64,
reflect.Complex128:
ctx.pushOp(&PushConst{constant.MakeInt64(0)})
return nil

case reflect.Chan,
reflect.Func,
reflect.Interface,
reflect.Map,
reflect.Pointer,
reflect.Slice:
ctx.pushOp(&PushNil{})
return nil

case reflect.String:
ctx.pushOp(&PushConst{constant.MakeString("")})
return nil

case reflect.Struct:
return ctx.compileStructLit(typ.(*godwarf.StructType), nil)

default:
// TODO reflect.UnsafePointer, reflect.Array
return fmt.Errorf("unsupported struct literal field type %v", typ)
}
}

func Listing(depth []int, ops []Op) string {
if depth == nil {
depth = make([]int, len(ops)+1)
Expand Down
7 changes: 7 additions & 0 deletions pkg/proc/evalop/ops.go
Original file line number Diff line number Diff line change
Expand Up @@ -251,3 +251,10 @@ type SetValue struct {
}

func (*SetValue) depthCheck() (npop, npush int) { return 2, 0 }

type CompositeLit struct {
DwarfType godwarf.Type
Count int
}

func (v *CompositeLit) depthCheck() (npop, npush int) { return v.Count, 1 }
30 changes: 30 additions & 0 deletions pkg/proc/variables_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -752,6 +752,36 @@ func getEvalExpressionTestCases() []varTest {
{"string(byteslice[0])", false, `"t"`, `"t"`, "string", nil},
{"string(runeslice[0])", false, `"t"`, `"t"`, "string", nil},

// composite literals
{"main.astruct{1, 2}", false, "main.astruct {A: 1, B: 2}", "main.astruct {A: 1, B: 2}", "main.astruct", nil},
{"main.astruct{A: 1, B: 2}", false, "main.astruct {A: 1, B: 2}", "main.astruct {A: 1, B: 2}", "main.astruct", nil},
{"main.astruct{A: 1, 2}", false, "", "", "", fmt.Errorf("cannot mix positional and named values in a composite literal")},
{"main.astruct{A: 1}", false, "main.astruct {A: 1, B: 0}", "main.astruct {A: 1, B: 0}", "main.astruct", nil},
{"main.astruct{2}", false, "", "", "", fmt.Errorf("too few values for struct main.astruct: want 2, got 1")},
{"main.astruct{1, 2, 3}", false, "", "", "", fmt.Errorf("too many values for struct main.astruct: want 2, got 3")},

{"[2]int{}", false, "[2]int [0,0]", "[2]int [0,0]", "[2]int", nil},
{"[2]int{1, 2}", false, "[2]int [1,2]", "[2]int [1,2]", "[2]int", nil},
{"[5]int{3: 1, 2}", false, "[5]int [0,0,0,1,2]", "[5]int [0,0,0,1,2]", "[5]int", nil},
{"[0]int{}", false, "[0]int []", "[0]int []", "[0]int", nil},

{"[]int{}", false, "[]int len: 0, cap: 0, nil", "[]int len: 0, cap: 0, nil", "[]int", nil},
{"[]int{1, 2}", false, "[]int len: 2, cap: 2, [1,2]", "[]int len: 2, cap: 2, [1,2]", "[]int", nil},
{"[]int{0: 1, 2}", false, "[]int len: 2, cap: 2, [1,2]", "[]int len: 2, cap: 2, [1,2]", "[]int", nil},
{"[]int{3: 1, 2}", false, "[]int len: 5, cap: 5, [0,0,0,1,2]", "[]int len: 5, cap: 5, [0,0,0,1,2]", "[]int", nil},

{"map[int]int{1: 2}", false, "map[int]int [1: 2, ]", "map[int]int [1: 2, ]", "map[int]int", nil},

{"m3[main.astruct{A: 1, B: 1}]", false, "42", "42", "int", nil},
{"m3[main.astruct{A: 1}]", false, "44", "44", "int", nil},
{"m3[main.astruct{B: 1}]", false, "45", "45", "int", nil},
{"m3[main.astruct{}]", false, "46", "46", "int", nil},

{"m8[[2]int{1, 1}]", false, "42", "42", "int", nil},
{"m8[[2]int{0: 1}]", false, "44", "44", "int", nil},
{"m8[[2]int{1: 1}]", false, "45", "45", "int", nil},
{"m8[[2]int{}]", false, "46", "46", "int", nil},

// misc
{"i1", true, "1", "1", "int", nil},
{"mainMenu", true, `main.Menu len: 3, cap: 3, [{Name: "home", Route: "/", Active: 1},{Name: "About", Route: "/about", Active: 1},{Name: "Login", Route: "/login", Active: 1}]`, `main.Menu len: 3, cap: 3, [...]`, "main.Menu", nil},
Expand Down
6 changes: 3 additions & 3 deletions service/dap/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6134,7 +6134,7 @@ func TestSetVariable(t *testing.T) {

// Args of foobar(baz string, bar FooBar)
checkVarExact(t, locals, 1, "bar", "bar", `main.FooBar {Baz: 10, Bur: "lorem"}`, "main.FooBar", hasChildren)
tester.failSetVariable(localsScope, "bar", `main.FooBar {Baz: 42, Bur: "ipsum"}`, "*ast.CompositeLit not implemented")
tester.failSetVariable(localsScope, "bar", `main.FooBar {Baz: 42, Bur: "ipsum"}`, "can not set variables of type struct")

// Nested field.
barRef := checkVarExact(t, locals, 1, "bar", "bar", `main.FooBar {Baz: 10, Bur: "lorem"}`, "main.FooBar", hasChildren)
Expand All @@ -6160,7 +6160,7 @@ func TestSetVariable(t *testing.T) {
tester.expectSetVariable(a4Ref, "[1]", "-7")
tester.evaluate("a4", "[2]int [1,-7]", hasChildren)

tester.failSetVariable(localsScope, "a4", "[2]int{3, 4}", "not implemented")
tester.failSetVariable(localsScope, "a4", "[2]int{3, 4}", "composite literals of [2]int not supported")

// slice of int
a5Ref := checkVarExact(t, locals, -1, "a5", "a5", "[]int len: 5, cap: 5, [1,2,3,4,5]", "[]int", hasChildren)
Expand Down Expand Up @@ -6258,7 +6258,7 @@ func TestSetVariable(t *testing.T) {
tester.evaluate(elem1.EvaluateName, "main.astruct {A: -9999, B: 10000}", hasChildren)

// map: struct -> int
m3Ref := checkVarExact(t, locals, -1, "m3", "m3", "map[main.astruct]int [{A: 1, B: 1}: 42, {A: 2, B: 2}: 43, ]", "map[main.astruct]int", hasChildren)
m3Ref := checkVarExact(t, locals, -1, "m3", "m3", "map[main.astruct]int [{A: 1, B: 1}: 42, {A: 2, B: 2}: 43, {A: 1, B: 0}: 44, {A: 0, B: 1}: 45, {A: 0, B: 0}: 46, ]", "map[main.astruct]int", hasChildren)
tester.expectSetVariable(m3Ref, "main.astruct {A: 1, B: 1}", "8888")
// note: updating keys is possible, but let's not promise anything.
tester.evaluateRegex("m3", `.*\[\{A: 1, B: 1\}: 8888,.*`, hasChildren)
Expand Down