Skip to content

Commit 0cde341

Browse files
ManishearthV8-internal LUCI CQ
authored andcommitted
[temporal] Add support for generating options bags
Temporal (and Intl, and some new Wasm APIs) uses a fair number of "options bags"; objects with a bunch of optional, typically enumerated/integral fields. Currently the fuzzer knows when such a type has been generated, but can only generate these types by stumbling upon them. This adds an OptionsBag type that is optimized for representing these; and a code generator that is capable of generating them. Bug: 439921647 Change-Id: I6a6a6964e1f4fb9a52d975c20a9a2630d5827eb9 Reviewed-on: https://chrome-internal-review.googlesource.com/c/v8/fuzzilli/+/8532481 Reviewed-by: Matthias Liedtke <mliedtke@google.com> Commit-Queue: Manish Goregaokar <manishearth@google.com>
1 parent 570db99 commit 0cde341

3 files changed

Lines changed: 166 additions & 8 deletions

File tree

Sources/Fuzzilli/Base/ProgramBuilder.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -691,6 +691,13 @@ public class ProgramBuilder {
691691
}
692692
}
693693

694+
// If we have a producing generator, we aren't going to get this type from elsewhere
695+
// so try and generate it using the generator in most cases
696+
let producingGenerator = self.fuzzer.environment.getProducingGenerator(ofType: type);
697+
if let producingGenerator {
698+
return producingGenerator(self)
699+
}
700+
694701
let producingMethods = self.fuzzer.environment.getProducingMethods(ofType: type)
695702
let producingProperties = self.fuzzer.environment.getProducingProperties(ofType: type)
696703
let globalProperties = producingProperties.filter() {(group: String, property: String) in

Sources/Fuzzilli/Environment/JavaScriptEnvironment.swift

Lines changed: 122 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -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
}

Tests/FuzzilliTests/JSTyperTests.swift

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1718,4 +1718,41 @@ class JSTyperTests: XCTestCase {
17181718
let wasmModule = b.construct(wasmModuleConstructor) // In theory this needs arguments.
17191719
XCTAssert(b.type(of: wasmModule).Is(.object(ofGroup: "WebAssembly.Module")))
17201720
}
1721+
1722+
func testProducingGenerators() {
1723+
// Make a simple object
1724+
let mockEnum = ILType.enumeration(ofName: "mockField", withValues: ["mockValue"]);
1725+
let mockObject = ObjectGroup(
1726+
name: "MockObject",
1727+
instanceType: nil,
1728+
properties: [
1729+
"mockField" : mockEnum
1730+
],
1731+
methods: [:]
1732+
)
1733+
1734+
// Some things to keep track of how the generator was called
1735+
var callCount = 0
1736+
var returnedVar: Variable? = nil
1737+
// A simple generator
1738+
func generate(builder: ProgramBuilder) -> Variable {
1739+
callCount += 1
1740+
let val = builder.loadString("mockValue")
1741+
let variable = builder.createObject(with: ["mockField": val])
1742+
returnedVar = variable
1743+
return variable
1744+
}
1745+
let fuzzer = makeMockFuzzer()
1746+
fuzzer.environment.registerObjectGroup(mockObject)
1747+
fuzzer.environment.addProducingGenerator(forType: mockObject.instanceType, with: generate)
1748+
let b = fuzzer.makeBuilder()
1749+
b.buildPrefix()
1750+
1751+
// Try to get it to invoke the generator
1752+
let variable = b.findOrGenerateType(mockObject.instanceType)
1753+
// Test that the generator was invoked
1754+
XCTAssertEqual(callCount, 1)
1755+
// Test that the returned variable matches the generated one
1756+
XCTAssertEqual(variable, returnedVar)
1757+
}
17211758
}

0 commit comments

Comments
 (0)