Repository: d5/tengo (go)

Issue:
Deadcode elimination optimizatio pass breaking (invalid jump position: 0)
Hi, first of all thanks for such a great project! We wanted to report a recent issue we found.

When using return statements that don't return any value, we got the next error from the compiler:
```
panic: invalid jump position: 0
```

This triggers usually when there are multiple exit points in the function (returns), and also there are conditional jumps (IFs or FORs loops with return statements). This is a small example that triggers the bug:

```python
fmt := import("fmt")

fmt.println("Hello")

f := func(a, b) {
    if a > 0 {
        return
    }

    // This standalone return statement causes the deadcode optimization pass to break,
    // if you remove it, everything works as expected.
    return

    return 123
}

r := f(0, 2)

fmt.println("Bye")
fmt.println(r)
fmt.println(is_undefined(r))
```

I think the issue must be or in the optimization pass or even before on how the bytecode for the standalone return statement is generated. At the moment I didn't have time to investigate further, but simply disabling for the time being the deadcode elimination solves the problem for us as a stopgap solution.

Let us know if you need more details or how we can help. Will post here if I discover more details.

We are running with version: v2.10.0 (4846cf524306)

Pull request description:
fix deadcode optimization
fixes #376

essentially we need to check and ignore the jump positions before we mark a return for elimination

Patch:
diff --git a/compiler.go b/compiler.go
index e4e0430..6591603 100644
--- a/compiler.go
+++ b/compiler.go
@@ -1220,14 +1220,14 @@ func (c *Compiler) optimizeFunc(node parser.Node) {
 	iterateInstructions(c.scopes[c.scopeIndex].Instructions,
 		func(pos int, opcode parser.Opcode, operands []int) bool {
 			switch {
+			case dsts[pos]:
+				dstIdx++
+				deadCode = false
 			case opcode == parser.OpReturn:
 				if deadCode {
 					return true
 				}
 				deadCode = true
-			case dsts[pos]:
-				dstIdx++
-				deadCode = false
 			case deadCode:
 				return true
 			}
@@ -1242,6 +1242,7 @@ func (c *Compiler) optimizeFunc(node parser.Node) {
 	var appendReturn bool
 	endPos := len(c.scopes[c.scopeIndex].Instructions)
 	newEndPost := len(newInsts)
+
 	iterateInstructions(newInsts,
 		func(pos int, opcode parser.Opcode, operands []int) bool {
 			switch opcode {


Test patch:
diff --git a/compiler_test.go b/compiler_test.go
index 21d1f02..fd1d422 100644
--- a/compiler_test.go
+++ b/compiler_test.go
@@ -1159,6 +1159,30 @@ func() {
 				tengo.MakeInstruction(parser.OpReturn, 1),
 				tengo.MakeInstruction(parser.OpConstant, 1),
 				tengo.MakeInstruction(parser.OpReturn, 1)))))
+
+	expectCompile(t, `
+func() {
+	if true {
+		return
+	}
+
+    return
+
+    return 123
+}`, bytecode(
+		concatInsts(
+			tengo.MakeInstruction(parser.OpConstant, 1),
+			tengo.MakeInstruction(parser.OpPop),
+			tengo.MakeInstruction(parser.OpSuspend)),
+		objectsArray(
+			intObject(123),
+			compiledFunction(0, 0,
+				tengo.MakeInstruction(parser.OpTrue),
+				tengo.MakeInstruction(parser.OpJumpFalsy, 6),
+				tengo.MakeInstruction(parser.OpReturn, 0),
+				tengo.MakeInstruction(parser.OpReturn, 0),
+				tengo.MakeInstruction(parser.OpConstant, 0),
+				tengo.MakeInstruction(parser.OpReturn, 1)))))
 }
 
 func TestCompilerScopes(t *testing.T) {