@@ -19,6 +19,38 @@ import Foundation
1919/// This provides methods for constructing and appending random
2020/// instances of the different kinds of operations in a program.
2121public class ProgramBuilder {
22+
23+ /// Runtime data used by code generators to share data between different stubs inside the same
24+ /// code generator. It is strictly required that all pushed values are also popped again in the
25+ /// same code generator.
26+ public class GeneratorRuntimeData {
27+ private var data = [ String : Stack < Variable > ] ( )
28+
29+ public func push( _ key: String , _ value: Variable ) {
30+ data [ key, default: . init( ) ] . push ( value)
31+ }
32+
33+ public func pop( _ key: String ) -> Variable {
34+ assert ( data [ key] != nil )
35+ return data [ key] !. pop ( )
36+ }
37+
38+ // Fetch the most recent value for this key and push it back.
39+ public func popAndPush( _ key: String ) -> Variable {
40+ assert ( data [ key] != nil )
41+ return data [ key] !. top
42+ }
43+
44+ func reset( ) {
45+ #if DEBUG
46+ for (key, value) in data {
47+ assert ( value. isEmpty, " Stale entries for runtime data ' \( key) ' " )
48+ }
49+ #endif
50+ data. removeAll ( keepingCapacity: false )
51+ }
52+ }
53+
2254 /// The fuzzer instance for which this builder is active.
2355 public let fuzzer : Fuzzer
2456
@@ -147,6 +179,9 @@ public class ProgramBuilder {
147179 /// The remaining CodeGenerators to call as part of a building / CodeGen step, these will "clean up" the state and fix the contexts.
148180 public var scheduled : Stack < GeneratorStub > = Stack ( )
149181
182+ // Runtime data that can be shared between different stubs within a CodeGenerator.
183+ var runtimeData = GeneratorRuntimeData ( )
184+
150185 /// Stack of active switch blocks.
151186 private var activeSwitchBlocks = Stack < SwitchBlock > ( )
152187
@@ -214,6 +249,7 @@ public class ProgramBuilder {
214249 activeObjectLiterals. removeAll ( )
215250 activeClassDefinitions. removeAll ( )
216251 buildLog? . reset ( )
252+ runtimeData. reset ( )
217253 }
218254
219255 /// Finalizes and returns the constructed program, then resets this builder so it can be reused for building another program.
@@ -483,6 +519,36 @@ public class ProgramBuilder {
483519 return probability ( 0.5 ) ? randomBuiltinMethodName ( ) : randomCustomMethodName ( )
484520 }
485521
522+ private static func generateConstrained< T: Equatable > (
523+ _ generator: ( ) -> T ,
524+ notIn: any Collection < T > ,
525+ fallback: ( ) -> T ) -> T {
526+ var result : T
527+ var attempts = 0
528+ repeat {
529+ if attempts >= 10 {
530+ return fallback ( )
531+ }
532+ result = generator ( )
533+ attempts += 1
534+ } while notIn. contains ( result)
535+ return result
536+ }
537+
538+ // Generate a string not already contained in `notIn` using the provided `generator`. If it fails
539+ // repeatedly, return a random string instead.
540+ public func generateString( _ generator: ( ) -> String , notIn: any Collection < String > ) -> String {
541+ Self . generateConstrained ( generator, notIn: notIn,
542+ fallback: { String . random ( ofLength: Int . random ( in: 1 ... 5 ) ) } )
543+ }
544+
545+ // Find a random variable to use as a string that isn't contained in `notIn`. If it fails
546+ // repeatedly, create a random string literal instead.
547+ public func findOrGenerateStringLikeVariable( notIn: any Collection < Variable > ) -> Variable {
548+ return Self . generateConstrained ( randomJsVariable, notIn: notIn,
549+ fallback: { loadString ( String . random ( ofLength: Int . random ( in: 1 ... 5 ) ) ) } )
550+ }
551+
486552 // Settings and constants controlling the behavior of randomParameters() below.
487553 // This determines how many variables of a given type need to be visible before
488554 // that type is considered a candidate for a parameter type. For example, if this
@@ -500,15 +566,15 @@ public class ProgramBuilder {
500566 //
501567 // This will attempt to find a parameter types for which at least a few variables of a compatible types are
502568 // currently available to (potentially) later be used as arguments for calling the generated subroutine.
503- public func randomParameters( n wantedNumberOfParameters: Int ? = nil ) -> SubroutineDescriptor {
569+ public func randomParameters( n wantedNumberOfParameters: Int ? = nil , withRestParameterProbability restProbability : Double = 0.2 ) -> SubroutineDescriptor {
504570 assert ( probabilityOfUsingAnythingAsParameterTypeIfAvoidable >= 0 && probabilityOfUsingAnythingAsParameterTypeIfAvoidable <= 1 )
505571
506572 // If the caller didn't specify how many parameters to generated, find an appropriate
507573 // number of parameters based on how many variables are currently visible (and can
508574 // therefore later be used as arguments for calling the new function).
509575 let n : Int
510576 if let requestedN = wantedNumberOfParameters {
511- assert ( requestedN > 0 )
577+ assert ( requestedN >= 0 )
512578 n = requestedN
513579 } else {
514580 switch numberOfVisibleVariables {
@@ -537,15 +603,27 @@ public class ProgramBuilder {
537603 }
538604
539605 var params = ParameterList ( )
540- for _ in 0 ..< n {
606+
607+ let generateRestParameter = n > 0 && probability ( restProbability)
608+ let numPlainParams = generateRestParameter ? n - 1 : n
609+
610+ func randomParamType( ) -> ILType {
541611 if probability ( probabilityOfUsingAnythingAsParameterTypeIfAvoidable) {
542- params . append ( . jsAnything)
612+ return . jsAnything
543613 } else {
544- params . append ( . plain ( chooseUniform ( from: candidates) ) )
614+ return chooseUniform ( from: candidates)
545615 }
546616 }
547617
548- // TODO: also generate rest parameters and maybe even optional ones sometimes?
618+ for _ in 0 ..< numPlainParams {
619+ params. append ( . plain( randomParamType ( ) ) )
620+ }
621+
622+ if generateRestParameter {
623+ params. append ( . rest( randomParamType ( ) ) )
624+ }
625+
626+ // TODO: also generate optional parameters sometimes?
549627
550628 return . parameters( params)
551629 }
@@ -2045,7 +2123,7 @@ public class ProgramBuilder {
20452123
20462124 var numberOfGeneratedInstructions = 0
20472125
2048- // calculate all input requirements of this CodeGenerator.
2126+ // Calculate all input requirements of this CodeGenerator.
20492127 let inputTypes = Set ( generator. parts. reduce ( [ ] ) { res, gen in
20502128 return res + gen. inputs. types
20512129 } )
0 commit comments