-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathfuzz_auto_test.go
More file actions
147 lines (137 loc) · 6.75 KB
/
Copy pathfuzz_auto_test.go
File metadata and controls
147 lines (137 loc) · 6.75 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
// fuzz_auto_test.go — AUTO-mode promotion fuzz harness (issue: CI
// fuzzed P3/P4 through force-all only; production runs auto).
//
// FuzzP4ForceAllPromote / the P3 difftest suites drive promotion with
// SetForceAllPromote(true), which bypasses the natural-heat half of
// the bridge: threshold crossing MID-RUN, recheckCompilabilityRuntime
// on the natural path, PromotionGater, and the short-proto floor +
// FloorExempter (all auto-only; issue #67 was an auto-only bug). This
// harness fuzzes that half: same P1-vs-tiered differential shape, but
// the tiered State promotes via lowered natural-heat thresholds
// (SetHotThresholds(2, 4)) instead of force. Each input is Run TWICE
// on the same tiered State — run 1 crosses thresholds and promotes
// mid-run, run 2 executes tier-mixed from the first call — and each
// run must byte-equal the interpreter result.
//
// The threshold override changes only WHEN the auto decision runs;
// the decision chain itself (capability recheck, profitability gate,
// floor, exemption) is production code exercised unchanged.
//go:build (wangshu_p3 || wangshu_p4) && wangshu_profile
package wangshu_test
import (
"github.com/Liam0205/wangshu/internal/fuzzbudget"
"strings"
"testing"
"github.com/Liam0205/wangshu"
)
// FuzzAutoPromote: arbitrary sources must not panic and must stay
// byte-equal between the never-promoting interpreter path and the
// auto-promoting tiered path (P3 or P4, per build tag), including
// across the mid-run promotion flip.
func FuzzAutoPromote(f *testing.F) {
// Seeds bias toward shapes that CROSS the lowered thresholds
// (repeat-called kernels, loops) plus decline shapes (vararg,
// metamethods) so the promote-some-decline-some mix is explored.
seeds := []string{
`local function f(x) return x * 2 + 1 end; local s = 0; for i = 1, 10 do s = s + f(i) end; return s`,
`local function sum(n) local s = 0; for i = 1, n do s = s + i end; return s end; return sum(10) + sum(20)`,
`local t = {x = 1}; local function g(tt) return tt.x end; local s = 0; for i = 1, 8 do s = s + g(t) end; return s`,
`local a = {1, 2, 3}; local function p(arr, i) return arr[i] end; local s = 0; for i = 1, 9 do s = s + p(a, (i % 3) + 1) end; return s`,
`local o = {n = 0}; function o:add(x) self.n = self.n + x; return self.n end; local r = 0; for i = 1, 8 do r = o:add(i) end; return r`,
`local function fib(n) if n < 2 then return n end return fib(n-1) + fib(n-2) end; return fib(10)`,
`local function make() local c = 0; return function() c = c + 1; return c end end; local f1 = make(); return f1() + f1() + f1()`,
`local function tiny(x) return x + 1 end; local s = 0; for i = 1, 8 do s = s + tiny(i) end; return s`,
`local function v(...) local a, b = ...; return (a or 0) + (b or 0) end; local s = 0; for i = 1, 6 do s = s + v(i, 1) end; return s`,
`local function cat(i) return "x" .. i end; local out = ""; for i = 1, 6 do out = out .. cat(i) end; return out`,
`local mt = {__add = function(a, b) return {v = a.v + b.v} end}; local x = setmetatable({v = 1}, mt); local y = setmetatable({v = 2}, mt); return (x + y).v`,
`local function alloc(i) return {i, i * 2} end; local s = 0; for i = 1, 8 do local p = alloc(i); s = s + p[1] + p[2] end; return s`,
}
for _, s := range seeds {
f.Add(s)
}
f.Fuzz(func(t *testing.T, src string) {
if raceEnabled {
// Same boundary as FuzzP4ForceAllPromote: P4 mmap-segment
// execution trips the race runtime's stack unwinder; the
// correctness assertions are covered by non-race jobs.
t.Skip("tiered mmap/wasm paths not race-safe; covered by non-race jobs")
}
if len(src) > 1<<14 {
t.Skip()
}
recordFuzzExec("FuzzAutoPromote", src)
prog, err := wangshu.Compile([]byte(src), "fuzz-auto")
if err != nil {
return // compile errors are a legal outcome
}
// Interpreter baseline: thresholds unreachable on this same
// build — promotion cannot happen. It runs the SAME number of
// times as the auto State and is compared run-for-run: scripts
// that mutate globals legitimately behave differently on later
// runs (state persists across Run on one State), so comparing
// auto run 2 against a single baseline run would flag ordinary
// cross-run state drift as a tier divergence (found by this
// fuzz target's first CI run, seed 861f54880d2009d5).
// MaxArenaBytes (issues #127/#130): quadratic-concat garbage
// ballooned the uncapped arena to 2 GiB / 13 GiB Go-side per
// exec and killed nightly workers; 64 MiB bounds it. Both
// States get the SAME cap so the comparison stays symmetric.
st1 := wangshu.NewState(wangshu.Options{MaxArenaBytes: 64 << 20})
st1.SetStepBudget(fuzzbudget.Steps)
st1.SetHotThresholds(^uint32(0), ^uint32(0))
// Auto path: lowered thresholds, two runs on one State. Run 1
// promotes mid-run; run 2 is tier-mixed from the first call.
stA := wangshu.NewState(wangshu.Options{MaxArenaBytes: 64 << 20})
stA.SetStepBudget(fuzzbudget.Steps)
stA.SetHotThresholds(2, 4)
for run := 1; run <= 2; run++ {
resP1, errP1 := prog.Run(st1)
resA, errA := prog.Run(stA)
// Error-existence divergence: budget/timing class is
// exempted (step counting differs between tiers on
// boundary inputs); semantic divergence hard-fails —
// same discipline as FuzzP4ForceAllPromote.
if (errP1 == nil) != (errA == nil) {
// Resource-limit class (step budget OR arena cap) is
// exempted: tiers charge steps and allocate at
// different points, so boundary inputs can trip a
// limit on one leg only.
budgetTiming := isResourceLimitErr(errP1) || isResourceLimitErr(errA)
if budgetTiming {
t.Skipf("budget/timing divergence (not a byte-equal violation): P1=%v auto=%v", errP1, errA)
return
}
t.Errorf("error-existence divergence on auto run %d (suspected miscompile): P1=%v auto=%v",
run, errP1, errA)
return
}
if errP1 != nil {
return // both errored: existence equivalence is the contract here
}
if len(resP1) != len(resA) {
t.Errorf("auto run %d: result count P1=%d auto=%d", run, len(resP1), len(resA))
return
}
for i := range resP1 {
if resP1[i].Display() != resA[i].Display() {
t.Errorf("auto run %d: result[%d] mismatch: P1=%q auto=%q",
run, i, resP1[i].Display(), resA[i].Display())
return
}
}
}
})
}
// isResourceLimitErr reports whether err is a harness resource limit
// (step budget or the fuzz arena cap) rather than a semantic result.
// Divergence checks skip when either leg trips one: tiers charge
// steps and allocate at different points, so boundary inputs can
// legally trip a limit on one leg only.
func isResourceLimitErr(err error) bool {
if err == nil {
return false
}
msg := err.Error()
return strings.Contains(msg, "instruction budget exceeded") ||
strings.Contains(msg, "internal VM panic: arena:")
}