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

fix: labels error handling #1877

Merged
merged 4 commits into from
Apr 8, 2024
Merged
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
86 changes: 86 additions & 0 deletions gnovm/pkg/gnolang/gno_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,92 @@ import (
"github.com/jaekwon/testify/require"
)

func TestRunInvalidLabels(t *testing.T) {
tests := []struct {
code string
output string
}{
{
code: `
package test
func main(){}
func invalidLabel() {
FirstLoop:
for i := 0; i < 10; i++ {
}
for i := 0; i < 10; i++ {
break FirstLoop
}
}
`,
output: `cannot find branch label "FirstLoop"`,
},
Comment on lines +26 to +39
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For next time, please place tests like these in gnovm/tests/files. Goes for all tests which simply run some code and try to test the output or any panic, like these ones

If you're testing specific functions or doing unit tests, then they should go in gnolang, but if they are tests which just run some code against the GnoVM then they should go there

{
code: `
package test
func main(){}

func undefinedLabel() {
for i := 0; i < 10; i++ {
break UndefinedLabel
}
}
`,
output: `label UndefinedLabel undefined`,
},
{
code: `
package test
func main(){}

func labelOutsideScope() {
for i := 0; i < 10; i++ {
continue FirstLoop
}
FirstLoop:
for i := 0; i < 10; i++ {
}
}
`,
output: `cannot find branch label "FirstLoop"`,
},
{
code: `
package test
func main(){}

func invalidLabelStatement() {
if true {
break InvalidLabel
}
}
`,
output: `label InvalidLabel undefined`,
},
}

for n, s := range tests {
n := n
t.Run(fmt.Sprintf("%v\n", n), func(t *testing.T) {
defer func() {
if r := recover(); r != nil {
es := fmt.Sprintf("%v\n", r)
if !strings.Contains(es, s.output) {
t.Fatalf("invalid label test: `%v` missing expected error: %+v got: %v\n", n, s.output, es)
}
} else {
t.Fatalf("invalid label test: `%v` should have failed but didn't\n", n)
}
}()

m := NewMachine("test", nil)
nn := MustParseFile("main.go", s.code)
m.RunFiles(nn)
m.RunMain()
})
}
}

func TestBuiltinIdentifiersShadowing(t *testing.T) {
t.Parallel()
tests := map[string]string{}
Expand Down
64 changes: 64 additions & 0 deletions gnovm/pkg/gnolang/preprocess.go
Original file line number Diff line number Diff line change
Expand Up @@ -1659,7 +1659,14 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node {
case *BranchStmt:
switch n.Op {
case BREAK:
if !isSwitchLabel(ns, n.Label) {
findBranchLabel(last, n.Label)
}
case CONTINUE:
if isSwitchLabel(ns, n.Label) {
panic(fmt.Sprintf("invalid continue label %q\n", n.Label))
}
findBranchLabel(last, n.Label)
case GOTO:
_, depth, index := findGotoLabel(last, n.Label)
n.Depth = depth
Expand Down Expand Up @@ -1977,6 +1984,23 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node {
return nn
}

func isSwitchLabel(ns []Node, label Name) bool {
for {
swch := lastSwitch(ns)
if swch == nil {
break
}

if swch.GetLabel() == label && label != "" {
return true
}

ns = ns[:len(ns)-1]
}

return false
}

func pushInitBlock(bn BlockNode, last *BlockNode, stack *[]BlockNode) {
if !bn.IsInitialized() {
bn.InitStaticBlock(bn, *last)
Expand Down Expand Up @@ -2245,6 +2269,46 @@ func funcOf(last BlockNode) (BlockNode, *FuncTypeExpr) {
}
}

func findBranchLabel(last BlockNode, label Name) (
bn BlockNode, depth uint8, bodyIdx int,
) {
for {
switch cbn := last.(type) {
case *BlockStmt, *ForStmt, *IfCaseStmt, *RangeStmt, *SelectCaseStmt, *SwitchClauseStmt, *SwitchStmt:
lbl := cbn.GetLabel()
if label == lbl {
bn = cbn
return
}
last = cbn.GetParentNode(nil)
depth += 1
case *IfStmt:
// These are faux blocks -- shouldn't happen.
panic("unexpected faux blocknode")
case *FileNode:
panic("unexpected file blocknode")
case *PackageNode:
panic("unexpected package blocknode")
case *FuncLitExpr:
body := cbn.GetBody()
_, bodyIdx = body.GetLabeledStmt(label)
if bodyIdx != -1 {
bn = cbn
return
}
panic(fmt.Sprintf(
"cannot find branch label %q",
label))
case *FuncDecl:
panic(fmt.Sprintf(
"cannot find branch label %q",
label))
default:
panic("unexpected block node")
}
}
}

func findGotoLabel(last BlockNode, label Name) (
bn BlockNode, depth uint8, bodyIdx int,
) {
Expand Down
Loading