Skip to content

Commit

Permalink
폴더 옭기기 직전
Browse files Browse the repository at this point in the history
  • Loading branch information
rlaau committed Feb 5, 2025
1 parent c403965 commit 2df3931
Show file tree
Hide file tree
Showing 8 changed files with 369 additions and 257 deletions.
54 changes: 52 additions & 2 deletions gnovm/stdlibs/testing/fuzz.gno
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ func (f *F) simulateFF(seed seed) seed {
prror: err,
}
f.logger.addCase(seed, tr)
f.manager.coordinateSeed(seed)
f.Fail()
}
if err != nil {
Expand All @@ -249,6 +250,7 @@ func (f *F) simulateFF(seed seed) seed {
prror: err,
}
f.logger.addCase(seed, tr)
f.manager.coordinateSeed(seed)
f.Fail()
}
return seed
Expand Down Expand Up @@ -323,9 +325,54 @@ func (f *F) handleFail() {
fmt.Fprintln(os.Stderr, "\n--- Trace:")
fmt.Fprintf(os.Stderr, "Last covered line: function \"%s\" in line %d\n", coverage[len(coverage)-1].coName, coverage[len(coverage)-1].coLine)
}
if f.verbose {
f.analyzeResult()
}
return
}

func (f *F) analyzeResult() {
fmt.Fprintln(os.Stderr, "\n--- Analytics:")
if f.verbose {

hashNumber := 0
for f.manager.hashNumber2Priority[hashNumber] != 0 {
fmt.Fprintf(os.Stdout, "coverage %d: [%d frequency | %d actual storage] \n", hashNumber, f.manager.hashNumber2Priority[hashNumber], len(f.manager.hashNumber2Seeds[hashNumber].data))
dataLen := len(f.manager.hashNumber2Seeds[hashNumber].data)
sampleCount := getSampleCount(dataLen) // 샘플 개수 결정
fmt.Fprint(os.Stdout, "- ")
for j := 0; j < sampleCount; j++ {
index := j * (dataLen / sampleCount) // 고른 간격으로 인덱스 선택
s := f.manager.hashNumber2Seeds[hashNumber].data[index]

if j > 0 {
fmt.Fprint(os.Stdout, ", ")
}
fmt.Fprintf(os.Stdout, "%v", contentToString(s.content))
}
fmt.Fprintln(os.Stdout) // 개행 추가

hashNumber++
}
}
}

// 샘플 개수를 결정하는 함수
func getSampleCount(length int) int {
switch {
case length >= 4096:
return 5
case length >= 512:
return 4
case length >= 64:
return 3
case length >= 8:
return 2
default:
return 1
}
}

// coverageToString converts coverage data to a string representation.
func coverageToString(coverage Coverage) string {
var sb strings.Builder
Expand Down Expand Up @@ -487,7 +534,10 @@ func (f *F) checkPreserve(content []interface{}) PreserveFailing {
func (f *F) reportF() {
fmt.Fprintln(os.Stderr, "\n--- PASS:")
fmt.Fprintf(os.Stderr, "Complete %d Trials\n", f.manager.inputCount)
fmt.Fprintf(os.Stderr, "Inspected %d coverage\n", (uint(f.hasher.hashNumberCounter.counter) + uint(1)))
fmt.Fprintf(os.Stderr, "Inspected %d coverage\n", (f.manager.inspectingHashNumbers))
if f.verbose {
f.analyzeResult()
}
}

// migrateMachines abstracts the existing f.manager and relocates the f.manager.
Expand Down Expand Up @@ -531,7 +581,7 @@ func (f *F) migrateMachines() {
func (f *F) updateMachines(parentSeeds []seed) bool {
seedsMap := make(map[hashNumber]*[]seed)
// 1) Generate evolved input from the popped input
childSeeds := evolve(parentSeeds, &f.seedCount, f.stringByteCandidates)
childSeeds := f.evolve(parentSeeds, &f.seedCount, f.stringByteCandidates)
// 2) Simulate and log evolved input to the target function
// If failure occurs, log it and terminate
for _, child := range childSeeds {
Expand Down
1 change: 1 addition & 0 deletions gnovm/stdlibs/testing/fuzz_hasher.gno
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ func (fh *fuzzHasher) registerCoverage2HashNumber(coverage Coverage) hashNumber

// countHashNumber assigns a unique hashNumber to the given internalHash.
// If it exists, returns existing; otherwise create new.
// starting point is 0
func (fh *fuzzHasher) countHashNumber(ih internalHash) hashNumber {
if val, exists := fh.internalHash2hashNumber[ih]; exists {
// if already known, return
Expand Down
8 changes: 4 additions & 4 deletions gnovm/stdlibs/testing/fuzz_logger.gno
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ func (fl *fuzzLogger) addCase(seed seed, r testResult) {
func (fl *fuzzLogger) reportLastFailingInput() {
log := fl.data
lastCrashCase := log[len(log)-1]
fmt.Fprintf(os.Stderr, "Found failing input %s at %d trials\n", escapedToString(lastCrashCase.input), lastCrashCase.inputCount)
fmt.Fprintf(os.Stderr, "Found failing input %s at %d trials, coverage %d \n", escapedToString(lastCrashCase.input), lastCrashCase.inputCount, int(lastCrashCase.hashNumber))
return
}

Expand Down Expand Up @@ -195,7 +195,9 @@ func contentToString(content []interface{}) string {
for i, elem := range content {
switch v := elem.(type) {
case string:
result.WriteString("\"" + v + "\"")
result.WriteString("\"" + escapedToString(v) + "\"")
case []byte:
result.WriteString("\"" + escapedToString(string(v)) + "\"")
case int:
result.WriteString(strconv.Itoa(v))
case int8:
Expand All @@ -220,8 +222,6 @@ func contentToString(content []interface{}) string {
result.WriteString(strconv.FormatFloat(float64(v), 'f', -1, 32))
case float64:
result.WriteString(strconv.FormatFloat(v, 'f', -1, 64))
case []byte:
result.WriteString("\"" + string(v) + "\"")
case bool:
if v {
result.WriteString("true")
Expand Down
4 changes: 2 additions & 2 deletions gnovm/stdlibs/testing/fuzz_manager.gno
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const (
// It represents the length of arrays internally managed by the fuzz manager.
// The maximum length of the array is limited to handle sorting between seeds, out, and in within O(1) time.
// (However, input can be received infinitely.)
MaxCapacity uint64 = 10_000
MaxCapacity uint64 = 500_000

// PartialCapacity is MaxCapacity divided by 5.
PartialCapacity = MaxCapacity / 5
Expand Down Expand Up @@ -91,7 +91,7 @@ func (fm *fuzzManager) coordinateSeed(seed seed) endInfo {
// It prints the progress at certain intervals depending on the verbosity level set via CLI.
func (fm *fuzzManager) conditionallyLogProgress(progressed uint) {
if fm.verbose {
if (fm.inputCount-progressed)%2000 > fm.inputCount%2000 {
if (fm.inputCount-progressed)%5000 > fm.inputCount%5000 {
fmt.Fprintf(os.Stderr, "%d times runned: inspecting %d coverages\n", fm.inputCount, fm.inspectingHashNumbers)
}
} else {
Expand Down
77 changes: 28 additions & 49 deletions gnovm/stdlibs/testing/fuzz_test.gno
Original file line number Diff line number Diff line change
Expand Up @@ -5,45 +5,43 @@ import (
"unicode/utf8"
)

func crashme(s string) {
if len(s) > 0 && s[0] == 'b' {
if len(s) > 1 && s[1] == 'a' {
if len(s) > 2 && s[2] == 'd' {
if len(s) > 3 && s[3] == '!' {
panic("crash triggered")
}
}
}
}
}

func FuzzCrashme(f *testing.F) {
// getCOverageOfRunner의 도움을 받지 않을 시,50_000의 실행에도 오류를 잡지 못함.
// 커버리지의 도움을 받을 시 70_000회에서 에러 발견견
// --- FAIL:
// Found failing input ["bad!"] at 70953 trials

// --- Minimaized input:
// Input: ["bad!"]
// Panic/Error: Panic
// PanicMessage: "crash triggered"

// --- Trace:
// []
// afl 퍼져의 주요 벤치마크는 크게 2개임
// symbolic guided: symbolic한 가이드를 받으며 깊은 depth나 분기를 뚫을 수 있는가
// find edge input: 기계적으로 edge input을 생성할 수 있는가.

// --- Elapsed 26.72s
f.Add("B")
// 퍼져의 주요 벤치마크크 중 "symbolic"한 특성 대한 test: 심볼릭한 경로를 제대로 탐색하는가를 측정.
// 깊은 path depth를 뚫어낼 수 있는지 test하는 함수
func FuzzSymbolicPath(f *testing.F) {
f.Add("k")
f.Add("c")
f.Fuzz(func(t *testing.T, orig ...interface{}) {
v, ok := orig[0].(string)
s, ok := orig[0].(string)
if !ok {
panic("dont match")
}
crashme(v)
if len(s) > 0 && s[0] == 'b' {
if len(s) > 1 && s[1] == 'a' {
if len(s) > 2 && s[2] == 'd' {
if len(s) > 3 && s[3] == '!' {
panic("crash triggered")
}
}
}
}
})
}

func FuzzMock(f *testing.F) {
// 퍼져의 주요 기준 중 "edge"한 특성 대한 tets: edge case를 제대로 생성하는가를 측정.
// find edge input: 기계적으로 edge input을 생성할 수 있는가에 대한 함수.
// 그걸 위한 적절한 최소 genetic gen값과, 뮤테이션, 엣지케이스 정도가 만족해야 함.
// 물론, 유전 알고리즘의 최소 생성 개수를 240에서 2로, 최대 생성을 1920에서 190으로 낮추면, 위의 symbolicPath는 엄청 빨리 찾아짐
// 근데 그렇게 되면, 아래의 edgeCase를 찾지 못함.
// 유전알고리즘의 최소 생성 개수를 좁히면 조건분기에는 능숙한데, 반복문에서의 path폭증 시 그걸 대응하지 못함
// 또한 유전알고리즘의 최대 생성 개수를 줄이면 빠르게 변이를 늘릴 수 있지만, 고착화 문제가 생김
// 즉, 최대와 최소 생성개수에는 최솟값 제약이 걸림. 이것은 failing인풋을 찾는데 걸리는 시간이 길어지게 함.
// 그러나 그럼으로써 안정적인 시간 내에 웬만한 경우에 failing input을 찾도록 함.
// 범용적인 퍼져를 만들수록 퍼징 시간은 길어짐. 일종의 trade off임. (특히나 go의 경우 string값의 아주 넓기에...)
func FuzzEdgeCase(f *testing.F) {
f.Add("apple hello", int(42131231230))
f.Add("rainy day", int(98401132231331))
f.Add("winter comes", int(12349123123))
Expand All @@ -66,22 +64,3 @@ func FuzzMock(f *testing.F) {
}
})
}

func FuzzAnother(f *testing.F) {
f.Add("\xbe", int(400))
f.Fuzz(func(t *testing.T, orig ...interface{}) {
v, ok := orig[0].(string)
if !ok {
panic("dont match")
}
i, ok2 := orig[1].(int)
if !ok2 {
panic("dont match")
}
rev := Reverse(v)
doubleRev := Reverse(rev)
if v != doubleRev && i > 300 && i < 500 {
t.Errorf("Before: %q, after: %q", orig, doubleRev)
}
})
}
Loading

0 comments on commit 2df3931

Please sign in to comment.