diff options
| -rw-r--r-- | prog/encoding.go | 13 | ||||
| -rw-r--r-- | prog/expr.go | 12 | ||||
| -rw-r--r-- | prog/minimization.go | 2 | ||||
| -rw-r--r-- | prog/rand.go | 2 | ||||
| -rw-r--r-- | prog/test/fuzz_test.go | 3 | ||||
| -rw-r--r-- | prog/types.go | 10 | ||||
| -rw-r--r-- | prog/validation.go | 14 |
7 files changed, 41 insertions, 15 deletions
diff --git a/prog/encoding.go b/prog/encoding.go index b5d5114ca..e925e7918 100644 --- a/prog/encoding.go +++ b/prog/encoding.go @@ -257,9 +257,13 @@ func (target *Target) Deserialize(data []byte, mode DeserializeMode) (*Prog, err // This validation is done even in non-debug mode because deserialization // procedure does not catch all bugs (e.g. mismatched types). // And we can receive bad programs from corpus and hub. - if err := prog.validate(); err != nil { + if err := prog.validateWithOpts(validationOptions{ + // Don't validate auto-set conditional fields. We'll patch them later. + ignoreTransient: true, + }); err != nil { return nil, err } + p.fixupConditionals(prog) if p.autos != nil { p.fixupAutos(prog) } @@ -1154,6 +1158,13 @@ func (p *parser) fixupAutos(prog *Prog) { } } +func (p *parser) fixupConditionals(prog *Prog) { + for _, c := range prog.Calls { + // Only overwrite transient union fields. + c.setDefaultConditions(p.target, true) + } +} + func (p *parser) Scan() bool { if p.e != nil || len(p.data) == 0 { return false diff --git a/prog/expr.go b/prog/expr.go index 5ecbef8eb..38358b214 100644 --- a/prog/expr.go +++ b/prog/expr.go @@ -174,7 +174,7 @@ func matchingUnionArgs(typ *UnionType, finder ArgFinder) []int { func (p *Prog) checkConditions() error { for _, c := range p.Calls { - err := c.checkConditions(p.Target) + err := c.checkConditions(p.Target, false) if err != nil { return err } @@ -184,12 +184,15 @@ func (p *Prog) checkConditions() error { var ErrViolatedConditions = errors.New("conditional fields rules violation") -func (c *Call) checkConditions(target *Target) error { +func (c *Call) checkConditions(target *Target, ignoreTransient bool) error { var ret error makeArgFinder := argFinderConstructor(target, c) forEachStaleUnion(target, c, makeArgFinder, func(a *UnionArg, t *UnionType, okIndices []int) { + if ignoreTransient && a.transient { + return + } ret = fmt.Errorf("%w union %s field is #%d(%s), but %v satisfy conditions", ErrViolatedConditions, t.Name(), a.Index, t.Fields[a.Index].Name, okIndices) @@ -197,7 +200,7 @@ func (c *Call) checkConditions(target *Target) error { return ret } -func (c *Call) setDefaultConditions(target *Target) bool { +func (c *Call) setDefaultConditions(target *Target, transientOnly bool) bool { var anyReplaced bool // Replace stale conditions with the default values of their correct types. for { @@ -205,6 +208,9 @@ func (c *Call) setDefaultConditions(target *Target) bool { makeArgFinder := argFinderConstructor(target, c) forEachStaleUnion(target, c, makeArgFinder, func(unionArg *UnionArg, unionType *UnionType, okIndices []int) { + if transientOnly && !unionArg.transient { + return + } // If several union options match, take the first one. idx := okIndices[0] field := unionType.Fields[idx] diff --git a/prog/minimization.go b/prog/minimization.go index a9864aca2..1760d180f 100644 --- a/prog/minimization.go +++ b/prog/minimization.go @@ -281,7 +281,7 @@ func minimizeInt(ctx *minimizeArgsCtx, arg Arg, path string) bool { // By mutating an integer, we risk violating conditional fields. // If the fields are patched, the minimization process must be restarted. - patched := ctx.call.setDefaultConditions(ctx.p.Target) + patched := ctx.call.setDefaultConditions(ctx.p.Target, false) if ctx.pred(ctx.p, ctx.callIndex0) { *ctx.p0 = ctx.p ctx.triedPaths[path] = true diff --git a/prog/rand.go b/prog/rand.go index aca3163bd..742dbaa7c 100644 --- a/prog/rand.go +++ b/prog/rand.go @@ -886,7 +886,7 @@ func (a *UnionType) generate(r *randGen, s *state, dir Dir) (arg Arg, calls []*C if a.isConditional() { // Conditions may reference other fields that may not have already // been generated. We'll fill them in later. - return a.DefaultTransientArg(dir), nil + return a.DefaultArg(dir), nil } index := r.Intn(len(a.Fields)) optType, optDir := a.Fields[index].Type, a.Fields[index].Dir(dir) diff --git a/prog/test/fuzz_test.go b/prog/test/fuzz_test.go index f01905ea3..2d063c469 100644 --- a/prog/test/fuzz_test.go +++ b/prog/test/fuzz_test.go @@ -30,6 +30,9 @@ test$res2() test$res2() test$res2() `, + `r=test$res0() +test$recur2(&(293324893027559)={r}) +`, } { t.Logf("test #%v: %q", i, data) inp := []byte(data)[:len(data):len(data)] diff --git a/prog/types.go b/prog/types.go index 00440163b..4e9d8455e 100644 --- a/prog/types.go +++ b/prog/types.go @@ -735,13 +735,9 @@ func (t *UnionType) String() string { func (t *UnionType) DefaultArg(dir Dir) Arg { idx := t.defaultField() f := t.Fields[idx] - return MakeUnionArg(t, dir, f.DefaultArg(f.Dir(dir)), idx) -} - -func (t *UnionType) DefaultTransientArg(dir Dir) Arg { - unionArg := t.DefaultArg(dir).(*UnionArg) - unionArg.transient = true - return unionArg + arg := MakeUnionArg(t, dir, f.DefaultArg(f.Dir(dir)), idx) + arg.transient = t.isConditional() + return arg } func (t *UnionType) defaultField() int { diff --git a/prog/validation.go b/prog/validation.go index 13b7c33db..f38dcd1e8 100644 --- a/prog/validation.go +++ b/prog/validation.go @@ -21,15 +21,25 @@ func (p *Prog) debugValidate() { } } +func (p *Prog) validate() error { + return p.validateWithOpts(validationOptions{}) +} + type validCtx struct { target *Target + opts validationOptions args map[Arg]bool uses map[Arg]Arg } -func (p *Prog) validate() error { +type validationOptions struct { + ignoreTransient bool +} + +func (p *Prog) validateWithOpts(opts validationOptions) error { ctx := &validCtx{ target: p.Target, + opts: opts, args: make(map[Arg]bool), uses: make(map[Arg]Arg), } @@ -65,7 +75,7 @@ func (ctx *validCtx) validateCall(c *Call) error { return err } } - if err := c.checkConditions(ctx.target); err != nil { + if err := c.checkConditions(ctx.target, ctx.opts.ignoreTransient); err != nil { return err } return ctx.validateRet(c) |
