Skip to content

Commit

Permalink
pkg/proc: implement composite struct literals
Browse files Browse the repository at this point in the history
This change adds support for evaluating composite
struct literals.

Updates go-delve#1465
  • Loading branch information
firelizzard18 committed Apr 2, 2024
1 parent bae4dfb commit 2a45e72
Show file tree
Hide file tree
Showing 6 changed files with 170 additions and 4 deletions.
2 changes: 1 addition & 1 deletion _fixtures/testvariables2.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ 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}}
upnil := unsafe.Pointer(nil)
up1 := unsafe.Pointer(&i1)
Expand Down
23 changes: 23 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,26 @@ func (scope *EvalScope) evalStructSelector(op *evalop.Select, stack *evalStack)
stack.pushErr(xv.structMember(op.Name))
}

func (scope *EvalScope) evalCompositeLit(op *evalop.CompositeLit, stack *evalStack) {
typ, ok := op.DwarfType.(*godwarf.StructType)
if !ok {
stack.err = fmt.Errorf("composite literals of %v not supported", op.DwarfType)
return
}

val := newVariable("", 0x0, op.DwarfType, scope.BinInfo, scope.Mem)
val.Len = int64(op.Count)
val.Children = make([]Variable, op.Count)
for i := 0; i < op.Count; i++ {
j := op.Count - i - 1
value := stack.pop()
value.Name = typ.Field[j].Name
val.Children[j] = *value
}

stack.push(val)
}

// Evaluates expressions <subexpr>.(<type>)
func (scope *EvalScope) evalTypeAssert(op *evalop.TypeAssert, stack *evalStack) {
xv := stack.pop()
Expand Down
124 changes: 124 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,126 @@ 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)
default:
return fmt.Errorf("composite literals of %v not supported", typ)
}
}

func (ctx *compileCtx) compileStructLit(typ *godwarf.StructType, elements []ast.Expr) error {
if len(elements) > len(typ.Field) {
return fmt.Errorf("wrong number of fields for %v: want %v, got %v", typ, len(typ.Field), len(elements))
}

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 {
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("%s has already been set", ident.Name)
}
values[i] = kv.Value
}
if isPos && len(elements) < len(typ.Field) {
return fmt.Errorf("wrong number of fields 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
switch typ.Field[i].Type.Common().ReflectKind {
case reflect.Bool:
ctx.pushOp(&PushConst{constant.MakeBool(false)})

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)})

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

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

case reflect.Struct:
err := ctx.compileStructLit(typ.Field[i].Type.(*godwarf.StructType), nil)
if err != nil {
return err
}

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

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

return nil
}

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 }
12 changes: 12 additions & 0 deletions pkg/proc/variables_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -752,6 +752,18 @@ 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("wrong number of fields for struct main.astruct: want 2, got 1")},
{"main.astruct{1, 2, 3}", false, "", "", "", fmt.Errorf("wrong number of fields for struct main.astruct: want 2, got 3")},
{"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},

// 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

0 comments on commit 2a45e72

Please sign in to comment.