@@ -4,10 +4,13 @@ import (
44 "bytes"
55 "fmt"
66 "math/big"
7+ "os"
78 "path/filepath"
89 "sync"
910 "time"
1011
12+ "github.com/crytic/medusa/utils"
13+
1114 "github.com/crytic/medusa/chain"
1215 "github.com/crytic/medusa/fuzzing/calls"
1316 "github.com/crytic/medusa/fuzzing/coverage"
@@ -30,13 +33,8 @@ type Corpus struct {
3033 // coverageMaps describes the total code coverage known to be achieved across all corpus call sequences.
3134 coverageMaps * coverage.CoverageMaps
3235
33- // mutableSequenceFiles represents a corpus directory with files which describe call sequences that should
34- // be used for mutations.
35- mutableSequenceFiles * corpusDirectory [calls.CallSequence ]
36-
37- // immutableSequenceFiles represents a corpus directory with files which describe call sequences that should not be
38- // used for mutations.
39- immutableSequenceFiles * corpusDirectory [calls.CallSequence ]
36+ // callSequenceFiles represents a corpus directory with files that should be used for mutations.
37+ callSequenceFiles * corpusDirectory [calls.CallSequence ]
4038
4139 // testResultSequenceFiles represents a corpus directory with files which describe call sequences that were flagged
4240 // to be saved by a test case provider. These are not used in mutations.
@@ -66,25 +64,25 @@ func NewCorpus(corpusDirectory string) (*Corpus, error) {
6664 corpus := & Corpus {
6765 storageDirectory : corpusDirectory ,
6866 coverageMaps : coverage .NewCoverageMaps (),
69- mutableSequenceFiles : newCorpusDirectory [calls.CallSequence ]("" ),
70- immutableSequenceFiles : newCorpusDirectory [calls.CallSequence ]("" ),
67+ callSequenceFiles : newCorpusDirectory [calls.CallSequence ]("" ),
7168 testResultSequenceFiles : newCorpusDirectory [calls.CallSequence ]("" ),
7269 unexecutedCallSequences : make ([]calls.CallSequence , 0 ),
7370 logger : logging .GlobalLogger .NewSubLogger ("module" , "corpus" ),
7471 }
7572
7673 // If we have a corpus directory set, parse our call sequences.
7774 if corpus .storageDirectory != "" {
78- // Read mutable call sequences.
79- corpus .mutableSequenceFiles .path = filepath .Join (corpus .storageDirectory , "call_sequences" , "mutable" )
80- err = corpus .mutableSequenceFiles .readFiles ("*.json" )
75+ // Migrate the legacy corpus structure
76+ // Note that it is important to call this first since we want to move all the call sequence files before reading
77+ // them into the corpus
78+ err = corpus .migrateLegacyCorpus ()
8179 if err != nil {
8280 return nil , err
8381 }
8482
85- // Read immutable call sequences.
86- corpus .immutableSequenceFiles .path = filepath .Join (corpus .storageDirectory , "call_sequences" , "immutable " )
87- err = corpus .immutableSequenceFiles .readFiles ("*.json" )
83+ // Read call sequences.
84+ corpus .callSequenceFiles .path = filepath .Join (corpus .storageDirectory , "call_sequences" )
85+ err = corpus .callSequenceFiles .readFiles ("*.json" )
8886 if err != nil {
8987 return nil , err
9088 }
@@ -100,26 +98,90 @@ func NewCorpus(corpusDirectory string) (*Corpus, error) {
10098 return corpus , nil
10199}
102100
101+ // migrateLegacyCorpus is used to read in the legacy corpus standard where call sequences were stored in two separate
102+ // directories (mutable/immutable).
103+ func (c * Corpus ) migrateLegacyCorpus () error {
104+ // Check to see if the mutable and/or the immutable directories exist
105+ callSequencePath := filepath .Join (c .storageDirectory , "call_sequences" )
106+ mutablePath := filepath .Join (c .storageDirectory , "call_sequences" , "mutable" )
107+ immutablePath := filepath .Join (c .storageDirectory , "call_sequences" , "immutable" )
108+
109+ // Only return an error if the error is something other than "filepath does not exist"
110+ mutableDirInfo , err := os .Stat (mutablePath )
111+ if err != nil && ! os .IsNotExist (err ) {
112+ return err
113+ }
114+ immutableDirInfo , err := os .Stat (immutablePath )
115+ if err != nil && ! os .IsNotExist (err ) {
116+ return err
117+ }
118+
119+ // Return early if these directories do not exist
120+ if mutableDirInfo == nil && immutableDirInfo == nil {
121+ return nil
122+ }
123+
124+ // Now, we need to notify the user that we have detected a legacy structure
125+ c .logger .Info ("Migrating legacy corpus" )
126+
127+ // If the mutable directory exists, read in all the files and add them to the call sequence files
128+ if mutableDirInfo != nil {
129+ // Discover all corpus files in the given directory.
130+ filePaths , err := filepath .Glob (filepath .Join (mutablePath , "*.json" ))
131+ if err != nil {
132+ return err
133+ }
134+
135+ // Move each file from the mutable directory to the parent call_sequences directory
136+ for _ , filePath := range filePaths {
137+ err = utils .MoveFile (filePath , filepath .Join (callSequencePath , filepath .Base (filePath )))
138+ if err != nil {
139+ return err
140+ }
141+ }
142+
143+ // Delete the mutable directory
144+ err = utils .DeleteDirectory (mutablePath )
145+ if err != nil {
146+ return err
147+ }
148+ }
149+
150+ // If the immutable directory exists, read in all the files and add them to the call sequence files
151+ if immutableDirInfo != nil {
152+ // Discover all corpus files in the given directory.
153+ filePaths , err := filepath .Glob (filepath .Join (immutablePath , "*.json" ))
154+ if err != nil {
155+ return err
156+ }
157+
158+ // Move each file from the immutable directory to the parent call_sequences directory
159+ for _ , filePath := range filePaths {
160+ err = utils .MoveFile (filePath , filepath .Join (callSequencePath , filepath .Base (filePath )))
161+ if err != nil {
162+ return err
163+ }
164+ }
165+
166+ // Delete the immutable directory
167+ err = utils .DeleteDirectory (immutablePath )
168+ if err != nil {
169+ return err
170+ }
171+ }
172+
173+ return nil
174+ }
175+
103176// CoverageMaps exposes coverage details for all call sequences known to the corpus.
104177func (c * Corpus ) CoverageMaps () * coverage.CoverageMaps {
105178 return c .coverageMaps
106179}
107180
108- // CallSequenceEntryCount returns the total number of call sequences entries in the corpus, based on the provided filter
109- // flags. Some call sequences may not be valid for use if they fail validation when initializing the corpus.
110- // Returns the count of the requested call sequence entries.
111- func (c * Corpus ) CallSequenceEntryCount (mutable bool , immutable bool , testResults bool ) int {
112- count := 0
113- if mutable {
114- count += len (c .mutableSequenceFiles .files )
115- }
116- if immutable {
117- count += len (c .immutableSequenceFiles .files )
118- }
119- if testResults {
120- count += len (c .testResultSequenceFiles .files )
121- }
122- return count
181+ // CallSequenceEntryCount returns the total number of call sequences that increased coverage and also any test results
182+ // that led to a failure.
183+ func (c * Corpus ) CallSequenceEntryCount () (int , int ) {
184+ return len (c .callSequenceFiles .files ), len (c .testResultSequenceFiles .files )
123185}
124186
125187// ActiveMutableSequenceCount returns the count of call sequences recorded in the corpus which have been validated
@@ -302,18 +364,13 @@ func (c *Corpus) Initialize(baseTestChain *chain.TestChain, contractDefinitions
302364 return 0 , 0 , err
303365 }
304366
305- err = c .initializeSequences (c .mutableSequenceFiles , testChain , deployedContracts , true )
306- if err != nil {
307- return 0 , 0 , err
308- }
309-
310- err = c .initializeSequences (c .immutableSequenceFiles , testChain , deployedContracts , false )
367+ err = c .initializeSequences (c .callSequenceFiles , testChain , deployedContracts , true )
311368 if err != nil {
312369 return 0 , 0 , err
313370 }
314371
315372 // Calculate corpus health metrics
316- corpusSequencesTotal := len (c .mutableSequenceFiles . files ) + len ( c . immutableSequenceFiles .files ) + len (c .testResultSequenceFiles .files )
373+ corpusSequencesTotal := len (c .callSequenceFiles .files ) + len (c .testResultSequenceFiles .files )
317374 corpusSequencesActive := len (c .unexecutedCallSequences )
318375
319376 return corpusSequencesActive , corpusSequencesTotal , nil
@@ -411,17 +468,9 @@ func (c *Corpus) CheckSequenceCoverageAndUpdate(callSequence calls.CallSequence,
411468 }
412469
413470 // If we had an increase in non-reverted or reverted coverage, we save the sequence.
414- // Note: We only want to save the sequence once. We're most interested if it can be used for mutations first.
415- if coverageUpdated {
416- // If we achieved new non-reverting coverage, save this sequence for mutation purposes.
417- err = c .addCallSequence (c .mutableSequenceFiles , callSequence , true , mutationChooserWeight , flushImmediately )
418- if err != nil {
419- return err
420- }
421- } else if revertedCoverageUpdated {
422- // If we did not achieve new successful coverage, but achieved an increase in reverted coverage, save this
423- // sequence for non-mutation purposes.
424- err = c .addCallSequence (c .immutableSequenceFiles , callSequence , false , mutationChooserWeight , flushImmediately )
471+ if coverageUpdated || revertedCoverageUpdated {
472+ // If we achieved new coverage, save this sequence for mutation purposes.
473+ err = c .addCallSequence (c .callSequenceFiles , callSequence , true , mutationChooserWeight , flushImmediately )
425474 if err != nil {
426475 return err
427476 }
@@ -470,8 +519,8 @@ func (c *Corpus) Flush() error {
470519 c .callSequencesLock .Lock ()
471520 defer c .callSequencesLock .Unlock ()
472521
473- // Write mutation target call sequences.
474- err := c .mutableSequenceFiles .writeFiles ()
522+ // Write all coverage-increasing call sequences.
523+ err := c .callSequenceFiles .writeFiles ()
475524 if err != nil {
476525 return err
477526 }
@@ -482,11 +531,5 @@ func (c *Corpus) Flush() error {
482531 return err
483532 }
484533
485- // Write other call sequences.
486- err = c .immutableSequenceFiles .writeFiles ()
487- if err != nil {
488- return err
489- }
490-
491534 return nil
492535}
0 commit comments