11package corpus
22
33import (
4+ "math/rand"
5+
46 "bytes"
7+ "context"
58 "fmt"
69 "math/big"
710 "os"
@@ -455,14 +458,13 @@ func (c *Corpus) AddTestResultCallSequence(callSequence calls.CallSequence, muta
455458 return c .addCallSequence (c .testResultSequenceFiles , callSequence , false , mutationChooserWeight , flushImmediately )
456459}
457460
458- // CheckSequenceCoverageAndUpdate checks if the most recent call executed in the provided call sequence achieved
459- // coverage the Corpus did not with any of its call sequences. If it did, the call sequence is added to the corpus
460- // and the Corpus coverage maps are updated accordingly.
461- // Returns an error if one occurs.
462- func (c * Corpus ) CheckSequenceCoverageAndUpdate (callSequence calls.CallSequence , mutationChooserWeight * big.Int , flushImmediately bool ) error {
461+ // checkSequenceCoverageAndUpdate checks if the most recent call executed in the provided call sequence achieved
462+ // coverage the not already included in coverageMaps. If it did, coverageMaps is updated accordingly.
463+ // Returns a boolean indicating whether any change happened, and an error if one occurs.
464+ func checkSequenceCoverageAndUpdate (callSequence calls.CallSequence , coverageMaps * coverage.CoverageMaps ) (bool , error ) {
463465 // If we have coverage-guided fuzzing disabled or no calls in our sequence, there is nothing to do.
464466 if len (callSequence ) == 0 {
465- return nil
467+ return false , nil
466468 }
467469
468470 // Obtain our coverage maps for our last call.
@@ -473,14 +475,22 @@ func (c *Corpus) CheckSequenceCoverageAndUpdate(callSequence calls.CallSequence,
473475
474476 // If we have none, because a coverage tracer wasn't attached when processing this call, we can stop.
475477 if lastMessageCoverageMaps == nil {
476- return nil
478+ return false , nil
477479 }
478480
479481 // Memory optimization: Remove them from the results now that we obtained them, to free memory later.
480482 coverage .RemoveCoverageTracerResults (lastMessageResult )
481483
482484 // Merge the coverage maps into our total coverage maps and check if we had an update.
483- coverageUpdated , err := c .coverageMaps .Update (lastMessageCoverageMaps )
485+ return coverageMaps .Update (lastMessageCoverageMaps )
486+ }
487+
488+ // CheckSequenceCoverageAndUpdate checks if the most recent call executed in the provided call sequence achieved
489+ // coverage the Corpus did not with any of its call sequences. If it did, the call sequence is added to the corpus
490+ // and the Corpus coverage maps are updated accordingly.
491+ // Returns an error if one occurs.
492+ func (c * Corpus ) CheckSequenceCoverageAndUpdate (callSequence calls.CallSequence , mutationChooserWeight * big.Int , flushImmediately bool ) error {
493+ coverageUpdated , err := checkSequenceCoverageAndUpdate (callSequence , c .coverageMaps )
484494 if err != nil {
485495 return err
486496 }
@@ -551,3 +561,77 @@ func (c *Corpus) Flush() error {
551561
552562 return nil
553563}
564+
565+ // PruneSequences removes unnecessary entries from the corpus. It does this by:
566+ // - Initialize a blank coverage map tmpMap
567+ // - Grab all sequences in the corpus
568+ // - Randomize the order
569+ // - For each transaction, see whether it adds anything new to tmpMap.
570+ // If it does, add the new coverage and continue.
571+ // If it doesn't, remove it from the corpus.
572+ //
573+ // By doing this, we hope to find a smaller set of txn sequences that still preserves our current coverage.
574+ // PruneSequences takes a chain.TestChain parameter used to run transactions.
575+ // It returns an int indicating the number of sequences removed from the corpus, and an error if any occurred.
576+ func (c * Corpus ) PruneSequences (ctx context.Context , chain * chain.TestChain ) (int , error ) {
577+ chainOriginalIndex := uint64 (len (chain .CommittedBlocks ()))
578+ tmpMap := coverage .NewCoverageMaps ()
579+
580+ c .callSequencesLock .Lock ()
581+ seqs := make ([]calls.CallSequence , len (c .mutationTargetSequenceChooser .Choices ))
582+ for i , seq := range c .mutationTargetSequenceChooser .Choices {
583+ seqCloned , err := seq .Data .Clone ()
584+ if err != nil {
585+ c .callSequencesLock .Unlock ()
586+ return 0 , err
587+ }
588+ seqs [i ] = seqCloned
589+ }
590+ c .callSequencesLock .Unlock ()
591+ // We don't need to lock during the next part as long as the ordering of Choices doesn't change.
592+ // New items could get added in the meantime, but older items won't be touched.
593+
594+ toRemove := map [int ]bool {}
595+
596+ // Iterate seqs in a random order
597+ for _ , i := range rand .Perm (len (seqs )) {
598+ if utils .CheckContextDone (ctx ) {
599+ return 0 , nil
600+ }
601+
602+ seq := seqs [i ]
603+
604+ fetchElementFunc := func (currentIndex int ) (* calls.CallSequenceElement , error ) {
605+ if currentIndex >= len (seq ) {
606+ return nil , nil
607+ }
608+ return seq [currentIndex ], nil
609+ }
610+
611+ // Never quit early
612+ executionCheckFunc := func (currentlyExecutedSequence calls.CallSequence ) (bool , error ) { return false , nil }
613+
614+ seq , err := calls .ExecuteCallSequenceIteratively (chain , fetchElementFunc , executionCheckFunc )
615+ if err != nil {
616+ return 0 , err
617+ }
618+
619+ coverageUpdated , err := checkSequenceCoverageAndUpdate (seq , tmpMap )
620+ if err != nil {
621+ return 0 , err
622+ }
623+
624+ if ! coverageUpdated {
625+ // No new coverage was added. We can remove this from the corpus.
626+ toRemove [i ] = true
627+ }
628+
629+ err = chain .RevertToBlockIndex (chainOriginalIndex )
630+ if err != nil {
631+ return 0 , err
632+ }
633+ }
634+
635+ c .mutationTargetSequenceChooser .RemoveChoices (toRemove )
636+ return len (toRemove ), nil
637+ }
0 commit comments