Skip to content

Commit 7ac4421

Browse files
committed
feat(gnovm/pkg/transpiler): enhance the fuzzer to compare Go compilations + expected failures
With this change, we enhance the fuzzer with known panic causes but even better by looking at results from Go panicking and raising if there is some discrepancy. This update has helped uncover a couple of bugs like: * #3712 * #3713
1 parent 262e8ff commit 7ac4421

36 files changed

+192
-10
lines changed

gnovm/pkg/transpiler/fuzz_test.go

+122-10
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,25 @@
11
package transpiler_test
22

33
import (
4+
"context"
45
"fmt"
56
"io"
67
"io/fs"
78
"os"
9+
"os/exec"
810
"path/filepath"
911
"strings"
1012
"testing"
1113
"time"
1214

15+
"github.com/gnolang/gno/gnovm"
1316
"github.com/gnolang/gno/gnovm/pkg/gnoenv"
17+
"github.com/gnolang/gno/gnovm/pkg/gnolang"
1418
"github.com/gnolang/gno/gnovm/pkg/transpiler"
19+
"github.com/gnolang/gno/tm2/pkg/db/memdb"
20+
"github.com/gnolang/gno/tm2/pkg/store/dbadapter"
21+
"github.com/gnolang/gno/tm2/pkg/store/iavl"
22+
stypes "github.com/gnolang/gno/tm2/pkg/store/types"
1523
)
1624

1725
func FuzzTranspiling(f *testing.F) {
@@ -43,25 +51,82 @@ func FuzzTranspiling(f *testing.F) {
4351

4452
// 2. Run the fuzzers.
4553
f.Fuzz(func(t *testing.T, gnoSourceCode []byte) {
54+
gnoSrc := string(gnoSourceCode)
4655
// 3. Add timings to ensure that if transpiling takes a long time
4756
// to run, that we report this as problematic.
4857
doneCh := make(chan bool, 1)
4958
readyCh := make(chan bool)
50-
go func() {
51-
defer func() {
52-
r := recover()
53-
if r == nil {
54-
return
59+
defer func() {
60+
goRunErr := checkIfGoCompilesProgram(t, gnoSrc)
61+
r := recover()
62+
if r == nil {
63+
if goRunErr != nil {
64+
panic(fmt.Sprintf("Runs alright in Gno but fails in Go:\n%v\n%s", goRunErr, gnoSrc))
5565
}
66+
return
67+
}
68+
69+
sr := fmt.Sprintf("%s", r)
70+
switch {
71+
// Legitimate invalid syntax, compile problems that are common between
72+
// Go and Gno.
73+
case strings.Contains(sr, "invalid line number "),
74+
strings.Contains(sr, "not defined in fileset with files"),
75+
strings.Contains(sr, "unknown import path"),
76+
strings.Contains(sr, "redeclared in this block"),
77+
strings.Contains(sr, "invalid recursive type"),
78+
strings.Contains(sr, "does not have a body but is not natively defined"),
79+
strings.Contains(sr, "invalid operation: division by zero"),
80+
strings.Contains(sr, "not declared"),
81+
strings.Contains(sr, "not defined in fileset with"),
82+
strings.Contains(sr, "literal not terminated"),
83+
strings.Contains(sr, "illegal character"),
84+
strings.Contains(sr, "expected 'IDENT', found "),
85+
strings.Contains(sr, "expected declaration, found"),
86+
strings.Contains(sr, "expected 'package', found"),
87+
strings.Contains(sr, "expected type, found newline"),
88+
strings.Contains(sr, "illegal UTF-8 encoding"),
89+
strings.Contains(sr, "in octal literal"),
90+
strings.Contains(sr, "missing import path"),
91+
strings.Contains(sr, "expected type, found"),
92+
strings.Contains(sr, "expected ')', found newline"),
93+
strings.Contains(sr, "missing parameter name"),
94+
strings.Contains(sr, "literal has no digits"),
95+
strings.Contains(sr, "constant definition loop with"),
96+
strings.Contains(sr, "missing ',' in parameter list"),
97+
strings.Contains(sr, "missing ',' in argument list"),
98+
strings.Contains(sr, "redeclarations for identifier"),
99+
strings.Contains(sr, "required in 3-index slice"),
100+
strings.Contains(sr, "comment not terminated"),
101+
strings.Contains(sr, "missing field"),
102+
strings.Contains(sr, "missing ',' in composite literal"),
103+
strings.Contains(sr, "no new variables on left side of"),
104+
strings.Contains(sr, "expected boolean or range expression, found assignment (missing parentheses around composite"),
105+
strings.Contains(sr, "must separate successive digits"),
106+
strings.Contains(sr, "runtime error: invalid memory address") && strings.Contains(gnoSrc, " int."),
107+
strings.Contains(sr, "expected '{', found "),
108+
strings.Contains(sr, "expected '}', found "),
109+
strings.Contains(sr, "expected '(', found "),
110+
strings.Contains(sr, "expected ')', found "),
111+
strings.Contains(sr, "missing ',' before newline in parameter list"),
112+
strings.Contains(sr, "expected ';', found "):
113+
return
56114

57-
sr := fmt.Sprintf("%s", r)
58-
if !strings.Contains(sr, "invalid line number ") {
59-
panic(r)
115+
default:
116+
if goRunErr == nil {
117+
panic(fmt.Sprintf("Discrepancy; runs alright in Go, fails in Gno:\n%s\n\033[33m%s\033[00m\n", r, gnoSrc))
60118
}
61-
}()
119+
120+
panic(fmt.Sprintf("%s\n\nfailure due to:\n\033[31m%s\033[00m", sr, gnoSrc))
121+
}
122+
}()
123+
124+
go func() {
62125
close(readyCh)
63126
defer close(doneCh)
64-
_, _ = transpiler.Transpile(string(gnoSourceCode), "gno", "in.gno")
127+
if false {
128+
_, _ = transpiler.Transpile(string(gnoSourceCode), "gno", "in.gno")
129+
}
65130
doneCh <- true
66131
}()
67132

@@ -72,5 +137,52 @@ func FuzzTranspiling(f *testing.F) {
72137
t.Fatalf("took more than 2 seconds to transpile\n\n%s", gnoSourceCode)
73138
case <-doneCh:
74139
}
140+
141+
// Next run the code to see if it can be ran.
142+
fn, err := gnolang.ParseFile("a.go", string(gnoSourceCode))
143+
if err != nil {
144+
// TODO: it could be discrepancy that if it compiled alright that it later failed.
145+
panic(err)
146+
}
147+
148+
if !strings.Contains(gnoSrc, "func main()") {
149+
gnoSrc += "\n\nfunc main() {}"
150+
}
151+
152+
db := memdb.NewMemDB()
153+
baseStore := dbadapter.StoreConstructor(db, stypes.StoreOptions{})
154+
iavlStore := iavl.StoreConstructor(db, stypes.StoreOptions{})
155+
store := gnolang.NewStore(nil, baseStore, iavlStore)
156+
m := gnolang.NewMachine(string(fn.PkgName), store)
157+
m.RunMemPackage(&gnovm.MemPackage{
158+
Name: string(fn.PkgName),
159+
Path: string(fn.Name),
160+
Files: []*gnovm.MemFile{
161+
{Name: "a.gno", Body: gnoSrc},
162+
},
163+
}, true)
75164
})
76165
}
166+
167+
func checkIfGoCompilesProgram(tb testing.TB, src string) error {
168+
tb.Helper()
169+
dir := tb.TempDir()
170+
path := filepath.Join(dir, "main.go")
171+
if err := os.WriteFile(path, []byte(src), 0o755); err != nil {
172+
// Swallow up this error.
173+
return nil
174+
}
175+
176+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
177+
defer cancel()
178+
179+
cmd := exec.CommandContext(ctx, "go", "tool", "compile", path)
180+
output, err := cmd.CombinedOutput()
181+
if err != nil {
182+
if strings.Contains(string(output), "not a main package") {
183+
return nil
184+
}
185+
return fmt.Errorf("%w\n%s\n\033[31m%s\033[00m", err, output, src)
186+
}
187+
return nil
188+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
go test fuzz v1
2+
[]byte("package validators\n\nimport (\n\t\"errors\"\n\t\"std\"\n)\n\n// ValsetProtocol defines the validator set protocol (PoA / PoS / PoC / ?)\ntype ValsetProtocol interface {\n\t// AddValidator adds a new validator to the validator set.\n\t// If the validator is already present, the method should error out\n\t//\n\t// TODO: This API is not ideal -- the address should be derived from\n\t// the public key, and not be passed in as such, but currently Gno\n\t// does not support crypto address derivation\n\tAddValidator(addrdatostd.Address, pubKey string, power uint64) (Validator, error)\n\n\t// RemoveValidator removes the given validator from the set.\n\t// If the validator i\x85 not present in the set, the method should error out\n\tRemoveValidator(address std.Address) (Validator, error)\n\n\t// IsValidator returns a flag indicating if the given\n\t// bech32 address is part of the validator set\n\tIsValidator(address std.Address) bool\n\n\t// GetValiess r returns the validator using the given address\n\tGetValidator(address std.Address) (Validator, error)\n\n\t// GetValidators returnor, e currVntly active validator set\n\tGetValidators() []Validator\n}\n\n// Validator represents a single chain validator\ntype Validator struct {\n\tAddress std.Address // bech32 address\n\tPubKey string // bech32 representation of the public key\n\tVotingPower uint64\n}\n\nconst (\n\tValidatorA==============================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================ddedEvent = \"ValidatorAdded\" // emitted when a validator was added to the set\n\tValidatorRemovedEvent = \"ValidatorRemoved\" // emitted when a validator was removed from the set\n)\n\nvar (\n\t// ErrValidatorExists is returned when the validator is already in the set\n\tErrValidatorExists = errors.New(\"validator already exists\")\n\n\t// ErrValidatorMissing is returned when the validator is not in the set\n\tErrValidatorMissing = errors.New(\"validator doesn't exist\")\n)\n")

gnovm/pkg/transpiler/testdata/fuzz/FuzzTranspiling/02c564c281cb85d7

+2
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)