@@ -289,8 +289,14 @@ public class JavaScriptEnvironment: ComponentBase {
289289 public private( set) var builtinProperties = Set < String > ( )
290290 public private( set) var builtinMethods = Set < String > ( )
291291
292+
293+ // Something that can generate a variable
294+ public typealias EnvironmentValueGenerator = ( ProgramBuilder ) -> Variable
295+
292296 private var builtinTypes : [ String : ILType ] = [ : ]
293297 private var groups : [ String : ObjectGroup ] = [ : ]
298+ // Producing generators, keyed on `type.group`
299+ private var producingGenerators : [ String : EnvironmentValueGenerator ] = [ : ]
294300 private var producingMethods : [ ILType : [ ( group: String , method: String ) ] ] = [ : ]
295301 private var producingProperties : [ ILType : [ ( group: String , property: String ) ] ] = [ : ]
296302 private var subtypes : [ ILType : [ ILType ] ] = [ : ]
@@ -381,6 +387,9 @@ public class JavaScriptEnvironment: ComponentBase {
381387 registerObjectGroup ( group)
382388 }
383389
390+ registerOptionsBag ( . jsTemporalDifferenceSettingOrRoundTo)
391+ registerOptionsBag ( . jsTemporalToStringSettings)
392+ registerOptionsBag ( . jsTemporalOverflowSettings)
384393
385394 // Register builtins that should be available for fuzzing.
386395 // Here it is easy to selectively disable/enable some APIs for fuzzing by
@@ -494,6 +503,14 @@ public class JavaScriptEnvironment: ComponentBase {
494503 return self . groups. keys. contains ( name)
495504 }
496505
506+ // Add a generator that produces an object of the provided `type`.
507+ //
508+ // This is for groups that are never generated as return types of other objects; so we register
509+ // a generator that can be called.
510+ public func addProducingGenerator( forType type: ILType , with generator: @escaping EnvironmentValueGenerator ) {
511+ producingGenerators [ type. group!] = generator
512+ }
513+
497514 private func addProducingMethod( forType type: ILType , by method: String , on group: String ) {
498515 if producingMethods [ type] == nil {
499516 producingMethods [ type] = [ ]
@@ -587,6 +604,11 @@ public class JavaScriptEnvironment: ComponentBase {
587604 }
588605 }
589606
607+ public func registerOptionsBag( _ bag: OptionsBag ) {
608+ registerObjectGroup ( bag. group)
609+ addProducingGenerator ( forType: bag. group. instanceType, with: { b in b. createOptionsBag ( bag) } )
610+ }
611+
590612 public func type( ofBuiltin builtinName: String ) -> ILType {
591613 if let type = builtinTypes [ builtinName] {
592614 return type
@@ -645,6 +667,12 @@ public class JavaScriptEnvironment: ComponentBase {
645667 return array
646668 }
647669
670+ // For ObjectGroups, get a generator that is registered as being able to produce this
671+ // ObjectGroup.
672+ public func getProducingGenerator( ofType type: ILType ) -> EnvironmentValueGenerator ? {
673+ type. group. flatMap { producingGenerators [ $0] }
674+ }
675+
648676 public func getProducingProperties( ofType type: ILType ) -> [ ( group: String , property: String ) ] {
649677 guard let array = producingProperties [ type] else {
650678 return [ ]
@@ -702,6 +730,27 @@ public struct ObjectGroup {
702730
703731}
704732
733+ // Some APIs take an "options bag", a list of options like {foo: "something", bar: "something", baz: 1}
734+ //
735+ // It is useful to be able to represent simple options bags so that we can efficiently codegen them
736+ public struct OptionsBag {
737+ // The type of each property, not including `| .undefined`.
738+ // We may extend this once we start supporting more complex bags
739+ public var properties : [ String : ILType ]
740+ // An ObjectGroup representing this bag
741+ public var group : ObjectGroup
742+
743+ public init ( name: String , properties: [ String : ILType ] ) {
744+ self . properties = properties
745+ let properties = properties. mapValues {
746+ // This list can be expanded over time as long as OptionsBagGenerator supports this
747+ assert ( $0. isEnumeration || $0. Is ( . number) || $0. Is ( . integer) , " Found unsupported option type \( $0) in options bag \( name) " )
748+ return $0 | . undefined;
749+ }
750+ self . group = ObjectGroup ( name: name, instanceType: nil , properties: properties, overloads: [ : ] )
751+ }
752+ }
753+
705754// Types of builtin objects, functions, and values.
706755//
707756// Most objects have a number of common fields, such as .constructor or the various methods defined on the object prototype.
@@ -1886,7 +1935,7 @@ public extension ObjectGroup {
18861935 " subtract " : [ jsTemporalDurationLike] => . jsTemporalInstant,
18871936 " until " : [ jsTemporalInstantLike, . opt( jsTemporalDifferenceSettings) ] => . jsTemporalDuration,
18881937 " since " : [ jsTemporalInstantLike, . opt( jsTemporalDifferenceSettings) ] => . jsTemporalDuration,
1889- " round " : [ jsTemporalRoundTo] => . jsTemporalInstant,
1938+ " round " : [ . plain ( jsTemporalRoundTo) ] => . jsTemporalInstant,
18901939 " equals " : [ jsTemporalInstantLike] => . boolean,
18911940 " toString " : [ . opt( jsTemporalToStringSettings) ] => . string,
18921941 " toJSON " : [ ] => . string,
@@ -1936,7 +1985,7 @@ public extension ObjectGroup {
19361985 " abs " : [ ] => . jsTemporalDuration,
19371986 " add " : [ jsTemporalDurationLike] => . jsTemporalDuration,
19381987 " subtract " : [ jsTemporalDurationLike] => . jsTemporalDuration,
1939- " round " : [ jsTemporalRoundTo ] => . jsTemporalDuration,
1988+ " round " : [ jsTemporalDurationRoundTo ] => . jsTemporalDuration,
19401989 " total " : [ jsTemporalDurationTotalOf] => . jsTemporalDuration,
19411990 " toString " : [ . opt( jsTemporalToStringSettings) ] => . string,
19421991 " toJSON " : [ ] => . string,
@@ -1975,7 +2024,7 @@ public extension ObjectGroup {
19752024 " with " : [ . plain( jsTemporalPlainTimeLikeObject. instanceType) , . opt( jsTemporalOverflowSettings) ] => . jsTemporalPlainTime,
19762025 " until " : [ . plain( jsTemporalPlainTimeLike) , . opt( jsTemporalDifferenceSettings) ] => . jsTemporalDuration,
19772026 " since " : [ . plain( jsTemporalPlainTimeLike) , . opt( jsTemporalDifferenceSettings) ] => . jsTemporalDuration,
1978- " round " : [ jsTemporalRoundTo] => . jsTemporalPlainTime,
2027+ " round " : [ . plain ( jsTemporalRoundTo) ] => . jsTemporalPlainTime,
19792028 " equals " : [ . plain( jsTemporalPlainTimeLike) ] => . boolean,
19802029 " toString " : [ . opt( jsTemporalToStringSettings) ] => . string,
19812030 " toJSON " : [ ] => . string,
@@ -2117,12 +2166,77 @@ public extension ObjectGroup {
21172166 fileprivate static let jsTemporalTimeZoneLike = Parameter . string
21182167
21192168 // Options objects
2120- // TODO(manishearth, 439921647) fill in options objects
2121- fileprivate static let jsTemporalDifferenceSettings = ILType . jsAnything
2122- fileprivate static let jsTemporalRoundTo = Parameter . jsAnything
2169+ fileprivate static let jsTemporalDifferenceSettings = OptionsBag . jsTemporalDifferenceSettingOrRoundTo. group. instanceType
2170+ // roundTo accepts a subset of fields compared to differenceSettings; we merge the two bag types but retain separate
2171+ // variables for clarity in the signatures above.
2172+ fileprivate static let jsTemporalRoundTo = OptionsBag . jsTemporalDifferenceSettingOrRoundTo. group. instanceType
2173+ fileprivate static let jsTemporalToStringSettings = OptionsBag . jsTemporalToStringSettings. group. instanceType
2174+ fileprivate static let jsTemporalOverflowSettings = OptionsBag . jsTemporalOverflowSettings. group. instanceType
2175+ // TODO(manishearth, 439921647) These are tricky and need other Temporal types
21232176 fileprivate static let jsTemporalDurationRoundTo = Parameter . jsAnything
21242177 fileprivate static let jsTemporalDurationTotalOf = Parameter . jsAnything
2178+ // This is huge and comes from Intl, not Temporal
2179+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#parameters
21252180 fileprivate static let jsTemporalToLocaleStringSettings = ILType . jsAnything
2126- fileprivate static let jsTemporalToStringSettings = ILType . jsAnything
2127- fileprivate static let jsTemporalOverflowSettings = ILType . jsAnything
2181+ }
2182+
2183+ extension OptionsBag {
2184+ // Individual enums
2185+ fileprivate static let jsTemporalUnitEnum = ILType . enumeration ( ofName: " temporalUnit " , withValues: [ " auto " , " year " , " month " , " week " , " day " , " hour " , " minute " , " second " , " millisecond " , " microsecond " , " nanosecond " , " auto " , " years " , " months " , " weeks " , " days " , " hours " , " minutes " , " seconds " , " milliseconds " , " microseconds " , " nanoseconds " ] )
2186+ fileprivate static let jsTemporalRoundingModeEnum = ILType . enumeration ( ofName: " temporalRoundingMode " , withValues: [ " ceil " , " floor " , " expand " , " trunc " , " halfCeil " , " halfFloor " , " halfExpand " , " halfTrunc " , " halfEven " ] )
2187+ fileprivate static let jsTemporalShowCalendarEnum = ILType . enumeration ( ofName: " temporalShowCalendar " , withValues: [ " auto " , " always " , " never " , " critical " ] )
2188+ fileprivate static let jsTemporalShowOffsetEnum = ILType . enumeration ( ofName: " temporalShowOffset " , withValues: [ " auto " , " never " ] )
2189+ fileprivate static let jsTemporalShowTimeZoneEnum = ILType . enumeration ( ofName: " temporalShowOfTimeZone " , withValues: [ " auto " , " never " , " critical " ] )
2190+
2191+ // differenceSettings and roundTo are mostly the same, with differenceSettings accepting an additional largestUnit
2192+ // note that the Duration roundTo option is different
2193+ static let jsTemporalDifferenceSettingOrRoundTo = OptionsBag (
2194+ name: " TemporalDifferenceSettingsObject " ,
2195+ properties: [
2196+ " smallestUnit " : jsTemporalUnitEnum,
2197+ " largestUnit " : jsTemporalUnitEnum,
2198+ " roundingMode " : jsTemporalRoundingModeEnum,
2199+ " roundingIncrement " : . integer,
2200+ ] )
2201+
2202+ // Technically some of these parameters are only read by ZonedDateTime; but they're
2203+ // otherwise largely shared
2204+ static let jsTemporalToStringSettings = OptionsBag (
2205+ name: " TemporalToStringSettingsObject " ,
2206+ properties: [
2207+ " calendarName " : jsTemporalShowCalendarEnum,
2208+ " fractionalSecondDigits " : . number,
2209+ " offset " : jsTemporalShowOffsetEnum,
2210+ " timeZoneName " : jsTemporalShowTimeZoneEnum,
2211+ " roundingMode " : jsTemporalRoundingModeEnum,
2212+ " smallestUnit " : jsTemporalUnitEnum,
2213+ ] )
2214+
2215+ static let jsTemporalOverflowSettings = OptionsBag (
2216+ name: " jsTemporalOverflowSettingsObject " ,
2217+ properties: [
2218+ " overflow " : ILType . enumeration ( ofName: " temporalOverflow " , withValues: [ " constrain " , " reject " ] ) ,
2219+ ] )
2220+ }
2221+
2222+ // Some APIs accept ObjectGroups that are not produced by other APIs,
2223+ // so we instead register a generator that allows the fuzzer a greater chance of generating
2224+ // one when needed.
2225+ //
2226+ // These can be registered on the environment with addProducingGenerator()
2227+
2228+ fileprivate extension ProgramBuilder {
2229+ @discardableResult
2230+ func createOptionsBag( _ bag: OptionsBag ) -> Variable {
2231+ // We run .filter() to pick a subset of fields, but we generally want to set as many as possible
2232+ // and let the mutator prune things
2233+ let dict : [ String : Variable ] = bag. properties. filter { _ in probability ( 0.8 ) } . mapValues {
2234+ if $0. isEnumeration {
2235+ return loadString ( chooseUniform ( from: $0. enumValues) )
2236+ } else {
2237+ return findOrGenerateType ( $0)
2238+ }
2239+ }
2240+ return createObject ( with: dict)
2241+ }
21282242}
0 commit comments