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

Unreachable goto affects program behavior #1354

Closed
CAFxX opened this issue Jan 29, 2022 · 1 comment · Fixed by #1392
Closed

Unreachable goto affects program behavior #1354

CAFxX opened this issue Jan 29, 2022 · 1 comment · Fixed by #1392
Labels
area/core bug Something isn't working
Milestone

Comments

@CAFxX
Copy link

CAFxX commented Jan 29, 2022

The following program sample.go triggers an unexpected result

package main

import (
	"fmt"
	"regexp/syntax"
	"strings"
	"unicode/utf8"
)

const FuzzRegexp = "0"

var (
	_ = syntax.IsWordChar
	_ = strings.Index
)

type modeTypeFuzz uint8

const (
	modeMatchFuzz modeTypeFuzz = iota
	modeFirstFuzz
	modeLongestFuzz
)

// Fuzz implements the regular expression
// "0"
// with flags 212.
type Fuzz struct{}
type stateFuzz struct {
	c   [2]int
	i   int
	pc  int
	cnt int
}

// FindString returns the first leftmost match.
func (e Fuzz) FindString(r string) (matches [1]string, pos int, ok bool) {
	var bt [0]stateFuzz // static storage for backtracking state
	matches, pos, ok = e.doString(r, modeFirstFuzz, bt[:0])
	return
}

// FindLongestString returns the leftmost-longest match.
func (e Fuzz) FindLongestString(r string) (matches [1]string, pos int, ok bool) {
	var bt [0]stateFuzz // static storage for backtracking state
	matches, pos, ok = e.doString(r, modeLongestFuzz, bt[:0])
	return
}

func (e Fuzz) doString(s string, m modeTypeFuzz, bt []stateFuzz) (matches [1]string, pos int, ok bool) {
	var pmatches [1 * 2]int
	pmatches, ok = e.do(s, m, bt)
	pos = pmatches[0]

	for i := range matches {
		if pmatches[i*2] < 0 {
			continue
		}
		matches[i] = s[pmatches[i*2]:pmatches[i*2+1]]
	}

	return
}

func (e Fuzz) do(r string, m modeTypeFuzz, bt []stateFuzz) ([2]int, bool) {
	si := 0 // starting byte index
restart:
	bt = bt[:0]         // fast reset dynamic backtracking state
	c := [2]int{-1, -1} // captures
	var bc [2]int       // captures for the longest match so far
	matched := false    // succesful match flag
	i := si             // current byte index
	// fast prefix search "0"
	if idx := strings.IndexByte(r[si:], '0'); idx >= 0 {
		i += idx // prefix found, skip to it
		si = i
		c[0] = i   // start of match
		i += 1     // prefix length
		goto inst2 // instruction after prefix

	}
	i += len(r[si:]) // no prefix found, skip to the end of the rune slice

	c[0] = i   // start of match
	goto inst1 // initial instruction

	// inst0 unreacheable

	goto unreachable
	goto inst1
inst1: // string "0" -> 2
	if i >= 0 && len(r) >= i {
		if rs := r[i:]; len(rs) >= 1 && rs[:1] == "0" {
			i += 1
			goto inst2
		}
	}
	goto inst1_fail
	goto unreachable
	goto inst1_fail
inst1_fail:
	goto fail

	goto unreachable
	goto inst2
inst2: // match
	c[1] = i // end of match
	goto match

	goto unreachable
	goto fail
fail:
	{
		if i <= len(r) && len(bt) > 0 {
			switch bt[len(bt)-1].pc {
			default:
				panic(bt[len(bt)-1].pc)
			}
		}
		if matched {
			return bc, true
		}
		if len(r) > si {
			i = si
			cr, sz := rune(r[i]), 1
			if cr >= utf8.RuneSelf {
				cr, sz = utf8.DecodeRuneInString(r[i:])
			}

			si += sz
			_ = cr
			goto restart
		}
		return bc, false
	}

	goto unreachable
	goto match
match:
	if !matched || c[1]-c[0] > bc[1]-bc[0] {
		if m == modeMatchFuzz || m == modeFirstFuzz {
			return c, true
		}
		bc = c
		matched = true
	}
	goto fail

	goto unreachable
unreachable:
	panic("unreachable")
}

func FindString(s string) ([]string, int, bool) {
	m, p, f := Fuzz{}.FindString(s)
	return m[:], p, f
}

func main() {
	m, p, f := FindString("1101")
	fmt.Println(m, p, f)
}

Expected result

[0] 2 true

Got

[] 0 false

Yaegi Version

14acf61

Additional Notes

If the goto inst2 immediately before the inst2: label is removed the problem disappears, even though that goto is unreachable and therefore never executed.

Sorry for the verbosity of the sample, the code is automatically generated.

@Briareos
Copy link

Briareos commented Mar 5, 2022

I managed to isolate this issue into a smaller snippet for easier debugging:

package main

import "log"

func main() {
	print(test()) // Go prints true, Yaegi false
}

func test() bool {
	if true {
		goto label
	}
	goto label
label:
	log.Println("Go continues here")
	return true
	log.Println("Yaegi goes straight to this return (this line is never printed)")
	return false
}

@mvertes mvertes added bug Something isn't working area/core labels Apr 8, 2022
@mvertes mvertes added this to the v0.11.x milestone Apr 8, 2022
mvertes added a commit to mvertes/yaegi that referenced this issue Apr 28, 2022
The logic of goto was false due to mixing break label and goto
label, despite being opposite. In case of break, jump to node (the
exit point) instead of node.start. Also always define label symbols
before their use is parsed.

Fixes traefik#1354.
mvertes added a commit that referenced this issue May 19, 2022
The logic of goto was false due to mixing break label and goto
label, despite being opposite. In case of break, jump to node (the
exit point) instead of node.start. Also always define label symbols
before their use is parsed.

* Fix continue label, detect invalid labels for break and continue

* fix multiple goto, break, continue to the same label

Fixes #1354.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/core bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants