diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml index ea0f69ec6..cc7b8617f 100644 --- a/.github/workflows/swift.yml +++ b/.github/workflows/swift.yml @@ -18,13 +18,19 @@ jobs: - uses: actions/setup-node@v4 with: node-version: 25-nightly - - uses: swift-actions/setup-swift@v2 + + # Is it failing to run tests b/c we're overriding + # what swift binary to use? + - name: Setup Swift for Ubuntu + if: runner.os == 'Linux' + uses: swift-actions/setup-swift@v2 with: swift-version: "6.0.3" - - name: Swift Version - run: swift --version + - uses: actions/checkout@v2 + - name: Build run: swift build -v + - name: Run tests run: swift test -v diff --git a/Package.swift b/Package.swift index 53820a440..5e2ef58d5 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.3 +// swift-tools-version:5.7 // // Copyright 2019 Google LLC // @@ -19,7 +19,7 @@ import PackageDescription let package = Package( name: "Fuzzilli", platforms: [ - .macOS(.v11), + .macOS(.v13), ], products: [ .library(name: "Fuzzilli",targets: ["Fuzzilli"]), @@ -28,6 +28,10 @@ let package = Package( .package(url: "https://github.com/apple/swift-protobuf.git", from: "1.31.0"), .package(url: "https://github.com/swift-server/RediStack.git", from: "1.4.1"), .package(url: "https://github.com/apple/swift-nio.git", from: "2.0.0"), + .package( + url: "https://github.com/apple/swift-collections.git", + .upToNextMinor(from: "1.2.0") + ), ], targets: [ .target(name: "libsocket", @@ -46,6 +50,7 @@ let package = Package( .product(name: "SwiftProtobuf", package: "swift-protobuf"), .product(name: "NIO", package: "swift-nio"), .product(name: "RediStack", package: "RediStack"), + .product(name: "Collections", package: "swift-collections"), "libsocket", "libreprl", "libcoverage"], @@ -60,13 +65,13 @@ let package = Package( .copy("Protobuf/ast.proto"), .copy("Compiler/Parser")]), - .target(name: "REPRLRun", + .executableTarget(name: "REPRLRun", dependencies: ["libreprl"]), - .target(name: "FuzzilliCli", + .executableTarget(name: "FuzzilliCli", dependencies: ["Fuzzilli"]), - .target(name: "FuzzILTool", + .executableTarget(name: "FuzzILTool", dependencies: ["Fuzzilli"]), .testTarget(name: "FuzzilliTests", diff --git a/Sources/Fuzzilli/Base/ContextGraph.swift b/Sources/Fuzzilli/Base/ContextGraph.swift new file mode 100644 index 000000000..38d6992dd --- /dev/null +++ b/Sources/Fuzzilli/Base/ContextGraph.swift @@ -0,0 +1,222 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import Collections + +public class ContextGraph { + // This is an edge, it holds all Generators that provide the `to` context at some point. + // Another invariant is that each Generator will keep the original context, i.e. it will return to the `from` conetxt. + struct EdgeKey: Hashable { + let from: Context + let to: Context + + public init(from: Context, to: Context) { + self.from = from + self.to = to + } + } + + // This struct describes the value of an edge in this ContextGraph. + // It holds all `CodeGenerator`s that go from one context to another in a direct transition. + public struct GeneratorEdge { + var generators: [CodeGenerator] = [] + + // Adds a generator to this Edge. + public mutating func addGenerator(_ generator: CodeGenerator) { + generators.append(generator) + } + } + + // This is a Path that goes from one Context to another via (usually more than one) `GeneratorEdge`. It is a full path in the Graph. + // Every Edge in a path may be provided by various CodeGenerators. + public struct Path { + let edges: [GeneratorEdge] + + // For each edge, pick a random Generator that provides that edge. + public func randomConcretePath() -> [CodeGenerator] { + edges.map { edge in + chooseUniform(from: edge.generators) + } + } + } + + // This is the Graph, each pair of from and to, maps to a `GeneratorEdge`. + var edges: [EdgeKey: GeneratorEdge] = [:] + + public init(for generators: WeightedList, withLogger logger: Logger) { + // Technically we don't need any generator to emit the .javascript context, as this is provided by the toplevel. + var providedContexts = Set([.javascript]) + var requiredContexts = Set() + + for generator in generators { + generator.providedContexts.forEach { ctx in + providedContexts.insert(ctx) + } + + requiredContexts.insert(generator.requiredContext) + } + + // Check that every part that provides something is used by the next part of the Generator. This is a simple consistency check. + for generator in generators where generator.parts.count > 1 { + var currentContext = Context(generator.parts[0].providedContext) + + for i in 1.. [[EdgeKey]] { + // Do simple BFS to find all possible paths. + var queue: Deque<[Context]> = [[src]] + var paths: [[Context]] = [] + var seenNodes = Set([src]) + + while !queue.isEmpty { + // use popFirst here from the deque. + let currentPath = queue.popFirst()! + + let currentNode = currentPath.last! + + if currentNode == dst { + paths.append(currentPath) + } + + // Get all possible edges from here on and push all of those to the queue. + for edge in self.edges where edge.key.from == currentNode && !seenNodes.contains(edge.key.to) { + // Prevent cycles, we don't care about complicated paths, but rather simple direct paths. + seenNodes.insert(edge.key.to) + queue.append(currentPath + [edge.key.to]) + } + } + + if paths.isEmpty { + return [] + } + + // Reduce this to Edges structs that we can easily look up. + var edgePaths: [[EdgeKey]] = [] + for path in paths { + var edgePath: [EdgeKey] = [] + for i in 0..<(path.count - 1) { + let edge = EdgeKey(from: path[i], to: path[i+1]) + edgePath.append(edge) + } + edgePaths.append(edgePath) + } + + return edgePaths + } + + func getGenerators(from src: Context, to dst: Context) -> GeneratorEdge? { + self.edges[EdgeKey(from: src, to: dst)] + } + + // TODO(cffsmith) implement this to filter for generators that are actually reachable, we can use this to avoid picking a .javascript generator when we're in .wasmFunction context for example. + // This is needed since we cannot close Contexts or go back in time yet. This essentially calculates all reachable destinations from `src`. + // The caller can then use `fuzzer.codeGenerators.filter { $0.requiredContext.contains() }` and pick a random one from that subset. + func getReachableContexts(from src: Context) -> [Context] { + // Do simple BFS to find all possible paths. + var queue: Deque<[Context]> = [[src]] + var paths: [[Context]] = [] + var seenNodes = Set([src]) + + while !queue.isEmpty { + let currentPath = queue.popFirst()! + + let currentNode = currentPath.last! + + var stillExploring = false + + // Get all possible edges from here on and push all of those to the queue. + for edge in self.edges where edge.key.from == currentNode && !seenNodes.contains(edge.key.to) { + // Prevent cycles, we don't care about complicated paths, but rather simple direct paths. + stillExploring = true + seenNodes.insert(edge.key.to) + queue.append(currentPath + [edge.key.to]) + } + + // If we haven't added another node, it means we have found an "end". + if !stillExploring { + paths.append(currentPath) + } + } + + if paths.isEmpty { + return [] + } + + // Map to all reachable contexts. so all that are on the path. + let contextSet = paths.reduce(Set()) { res, path in + res.union(path) + } + return Array(contextSet) + } + + // The return value is a list of possible Paths. + // A Path is a possible way from one context to another. + public func getCodeGeneratorPaths(from src: Context, to dst: Context) -> [Path]? { + + let paths = getAllPaths(from: src, to: dst) + + if paths.isEmpty { + return nil + } + + return paths.map { edges in + Path(edges: edges.map { edge in + self.edges[edge]! + }) + } + } +} diff --git a/Sources/Fuzzilli/Base/ProgramBuilder.swift b/Sources/Fuzzilli/Base/ProgramBuilder.swift index 7899a9ce0..46c8de95c 100644 --- a/Sources/Fuzzilli/Base/ProgramBuilder.swift +++ b/Sources/Fuzzilli/Base/ProgramBuilder.swift @@ -76,7 +76,7 @@ public class ProgramBuilder { /// Visible variables management. /// The `scopes` stack contains one entry per currently open scope containing all variables created in that scope. - private var scopes = Stack<[Variable]>([[]]) + private(set) var scopes = Stack<[Variable]>([[]]) /// The `variablesInScope` array simply contains all variables that are currently in scope. It is effectively the `scopes` stack flattened. private var variablesInScope = [Variable]() @@ -102,6 +102,17 @@ public class ProgramBuilder { /// literals is created inside a method/getter/setter of another object literals. private var activeObjectLiterals = Stack() + /// If we open a new function, we save its Variable here. + /// This allows CodeGenerators to refer to their Variable after they emit the + /// `End*Function` operation. This allows them to call the function after closing it. + /// Since they cannot refer to the Variable as it usually is created in the head part of the Generator. + private var lastFunctionVariables = Stack() + + /// Just a getter to get the top most, i.e. last function Variable. + public var lastFunctionVariable: Variable { + return lastFunctionVariables.top + } + /// When building object literals, the state for the current literal is exposed through this member and /// can be used to add fields to the literal or to determine if some field already exists. public var currentObjectLiteral: ObjectLiteral { @@ -133,6 +144,9 @@ public class ProgramBuilder { return activeClassDefinitions.top } + /// The remaining CodeGenerators to call as part of a building / CodeGen step, these will "clean up" the state and fix the contexts. + public var scheduled: Stack = Stack() + /// Stack of active switch blocks. private var activeSwitchBlocks = Stack() @@ -179,6 +193,10 @@ public class ProgramBuilder { self.fuzzer = fuzzer self.jsTyper = JSTyper(for: fuzzer.environment) self.parent = parent + + if fuzzer.config.logLevel.isAtLeast(.verbose) { + self.buildLog = BuildLog() + } } /// Resets this builder. @@ -195,10 +213,12 @@ public class ProgramBuilder { jsTyper.reset() activeObjectLiterals.removeAll() activeClassDefinitions.removeAll() + buildLog?.reset() } /// Finalizes and returns the constructed program, then resets this builder so it can be reused for building another program. public func finalize() -> Program { + assert(scheduled.isEmpty) let program = Program(code: code, parent: parent, comments: comments, contributors: contributors) reset() return program @@ -591,7 +611,6 @@ public class ProgramBuilder { public func findOrGenerateType(_ type: ILType, maxNumberOfVariablesToGenerate: Int = 100) -> Variable { assert(context.contains(.javascript)) -// assert(argumentGenerationVariableBudget == nil) argumentGenerationVariableBudget.push(numVariables + maxNumberOfVariablesToGenerate) @@ -646,7 +665,11 @@ public class ProgramBuilder { (.integer, { return self.loadInt(self.randomInt()) }), (.string, { if type.isEnumeration { - return self.loadString(type.enumValues.randomElement()!) + return self.loadEnum(type) + } + if let typeName = type.group, + let customStringGen = self.fuzzer.environment.getNamedStringGenerator(ofName: typeName) { + return self.loadString(customStringGen(), customName: typeName) } return self.loadString(self.randomString()) }), (.boolean, { return self.loadBool(probability(0.5)) }), @@ -670,6 +693,16 @@ public class ProgramBuilder { // Note that builtin constructors are handled above in the maybeGenerateConstructorAsPath call. return self.randomVariable(forUseAs: .constructor()) }), + (.wasmTypeDef(), { + // Call into the WasmTypeGroup generator (or other that provide a .wasmTypeDef) + let generators = self.fuzzer.codeGenerators.filter { gen in + gen.produces.contains { type in + type.Is(.wasmTypeDef()) + } + } + let _ = self.complete(generator: generators.randomElement(), withBudget: 5) + return self.randomVariable(ofType: .wasmTypeDef())! + }), (.object(), { func useMethodToProduce(_ method: (group: String, method: String)) -> Variable { let group = self.fuzzer.environment.type(ofGroup: method.group) @@ -751,17 +784,22 @@ public class ProgramBuilder { } else if let property = maybeProperty { return usePropertyToProduce(property) } - let codeGenerators = - self.fuzzer.codeGenerators.filter({$0.requiredContext.isSubset(of: self.context) && - !$0.isRecursive && $0.produces != nil && $0.produces!.Is(type)}) - if codeGenerators.count > 0 { - let generator = codeGenerators.randomElement() - self.run(generator) - // The generator we ran above is supposed to generate the - // requested type. If no variable of that type exists - // now, then either the generator or its annotation is - // wrong. - return self.randomVariable(ofTypeOrSubtype: type)! + let generators = self.fuzzer.codeGenerators.filter({ + // Right now only use generators that require a single context. + $0.parts.last!.requiredContext.isSingle && + $0.parts.last!.requiredContext.satisfied(by: self.context) && + $0.parts.last!.produces.contains(where: { producedType in + producedType.Is(type) + }) + }) + if generators.count > 0 { + let generator = generators.randomElement() + let _ = self.complete(generator: generator, withBudget: 10) + // The generator we ran above is supposed to generate the + // requested type. If no variable of that type exists + // now, then either the generator or its annotation is + // wrong. + return self.randomVariable(ofTypeOrSubtype: type)! } // Otherwise this is one of the following: // 1. an object with more type information, i.e. it has a group, but no associated builtin, e.g. we cannot construct it with new. @@ -1648,12 +1686,6 @@ public class ProgramBuilder { // parent budget and the (absolute) threshold for recursive code generation. // - /// The first "knob": this mainly determines the shape of generated code as it determines how large block bodies are relative to their surrounding code. - /// This also influences the nesting depth of the generated code, as recursive code generators are only invoked if enough "budget" is still available. - /// These are writable so they can be reconfigured in tests. - var minRecursiveBudgetRelativeToParentBudget = 0.05 - var maxRecursiveBudgetRelativeToParentBudget = 0.50 - /// The second "knob": the minimum budget required to be able to invoke recursive code generators. public static let minBudgetForRecursiveCodeGeneration = 5 @@ -1667,24 +1699,102 @@ public class ProgramBuilder { case generatingAndSplicing } - // Keeps track of the state of one buildInternal() invocation. These are tracked in a stack, one entry for each recursive call. - // This is a class so that updating the currently active state is possible without push/pop. - private class BuildingState { - let initialBudget: Int - let mode: BuildingMode - var recursiveBuildingAllowed = true - var nextRecursiveBlockOfCurrentGenerator = 1 - var totalRecursiveBlocksOfCurrentGenerator: Int? = nil - // An optional budget for recursive building. - var recursiveBudget: Int? = nil + struct BuildLog { + enum ActionOutcome: CustomStringConvertible { + case success + case failed(String?) + case started + + var description: String { + switch self { + case .success: + return "✅" + case .failed(let reason): + if let reason { + return "❌: \(reason)" + } else { + return "❌" + } + case .started: + return "started" + } + } + } + + struct BuildAction { + var name: String + var outcome = ActionOutcome.started + var produces: [ILType] + } + + var pendingActions: Stack = Stack() + var actions = [(BuildAction, Int)]() + var indent = 0 + + mutating func startAction(_ actionName: String, produces: [ILType]) { + let action = BuildAction(name: actionName, produces: produces) + // Mark this action as `.started`. + actions.append((action, indent)) + // Push the action onto the pending stack, we will need to complete or fail it later. + pendingActions.push(action) + indent += 1 + } + + mutating func succeedAction(_ newlyCreatedVariableTypes: [ILType]) { + indent -= 1 + var finishedAction = pendingActions.pop() + finishedAction.outcome = .success + actions.append((finishedAction, indent)) + #if DEBUG + // Now Check that we've seen these new types. + for t in finishedAction.produces { + if !newlyCreatedVariableTypes.contains(where: { + $0.Is(t) + }) { + var fatalErrorString = "" + fatalErrorString += "Action: \(finishedAction.name)\n" + fatalErrorString += "Action guaranteed it would produce: \(finishedAction.produces)\n" + fatalErrorString += "\(getLogString())\n" + fatalErrorString += "newlyCreatedVariableTypes: \(newlyCreatedVariableTypes) does not contain expected type \(t)" + fatalError(fatalErrorString) + } + } + #endif + } - init(initialBudget: Int, mode: BuildingMode) { - assert(initialBudget > 0) - self.initialBudget = initialBudget - self.mode = mode + mutating func reportFailure(reason: String? = nil) { + indent -= 1 + var failedAction = pendingActions.pop() + failedAction.outcome = .failed(reason) + actions.append((failedAction, indent)) + } + + func getLogString() -> String { + var logString = "Build log:\n" + for (action, indent) in actions { + let tab = String(repeating: " ", count: indent) + logString.append("\(tab)\(action.name): \(action.outcome)\n") + } + return logString + } + + mutating func reset() { + assert(pendingActions.isEmpty, "We should have completed all pending build actions, either failed or succeeded.") + // This is basically equivalent with the statement above. + assert(indent == 0) + actions.removeAll() } } - private var buildStack = Stack() + + + // The BuildLog records all `run` and `complete` calls on this ProgramBuilder, be it through mutation or generation. + #if DEBUG + // We definitely want to have the BuildLog in DEBUG builds. + var buildLog: BuildLog? = BuildLog() + #else + // We initialize this depending on the LogLevel in the initializer. + var buildLog: BuildLog? = nil + #endif /// Build random code at the current position in the program. /// @@ -1695,118 +1805,42 @@ public class ProgramBuilder { /// Building code requires that there are visible variables available as inputs for CodeGenerators or as replacement variables for splicing. /// When building new programs, `buildPrefix()` can be used to generate some initial variables. `build()` purposely does not call /// `buildPrefix()` itself so that the budget isn't accidentally spent just on prefix code (which is probably less interesting). - public func build(n: Int = 1, by mode: BuildingMode = .generatingAndSplicing) { - assert(buildStack.isEmpty) - buildInternal(initialBuildingBudget: n, mode: mode) - assert(buildStack.isEmpty) - } - - /// Recursive code building. Used by CodeGenerators for example to fill the bodies of generated blocks. - public func buildRecursive(block: Int = 1, of numBlocks: Int = 1, n optionalBudget: Int? = nil) { - assert(!buildStack.isEmpty) - let parentState = buildStack.top - - assert(parentState.mode != .splicing) - assert(parentState.recursiveBuildingAllowed) // If this fails, a recursive CodeGenerator is probably not marked as recursive. - assert(numBlocks >= 1) - assert(block >= 1 && block <= numBlocks) - assert(parentState.nextRecursiveBlockOfCurrentGenerator == block, "next = \(parentState.nextRecursiveBlockOfCurrentGenerator), block = \(block)") - assert((parentState.totalRecursiveBlocksOfCurrentGenerator ?? numBlocks) == numBlocks) - - parentState.nextRecursiveBlockOfCurrentGenerator = block + 1 - parentState.totalRecursiveBlocksOfCurrentGenerator = numBlocks - - // Determine the budget for this recursive call as a fraction of the parent's initial budget. - var recursiveBudget: Double - if let specifiedBudget = parentState.recursiveBudget { - assert(specifiedBudget > 0) - recursiveBudget = Double(specifiedBudget) - } else { - let factor = Double.random(in: minRecursiveBudgetRelativeToParentBudget...maxRecursiveBudgetRelativeToParentBudget) - assert(factor > 0.0 && factor < 1.0) - let parentBudget = parentState.initialBudget - recursiveBudget = Double(parentBudget) * factor - } + public func build(n budget: Int, by buildingMode: BuildingMode = .generatingAndSplicing) { - // Now split the budget between all sibling blocks. - recursiveBudget /= Double(numBlocks) - recursiveBudget.round(.up) - assert(recursiveBudget >= 1.0) + /// The number of CodeGenerators we want to call per level. + let splitFactor = 2 - // Finally, if a custom budget was requested, choose the smaller of the two values. - if let requestedBudget = optionalBudget { - assert(requestedBudget > 0) - recursiveBudget = min(recursiveBudget, Double(requestedBudget)) + // If the corpus is empty, we have to pick generating here, this is only relevant for the first sample. + let mode: BuildingMode = if fuzzer.corpus.isEmpty { + .generating + } else { + if buildingMode == .generatingAndSplicing { + chooseUniform(from: [.generating, .splicing]) + } else { + buildingMode + } } - buildInternal(initialBuildingBudget: Int(recursiveBudget), mode: parentState.mode) - } - - private func buildInternal(initialBuildingBudget: Int, mode: BuildingMode) { - assert(initialBuildingBudget > 0) + // Now depending on the budget we will do one of these things: + // 1. Large budget is still here. Pick a scheduled CodeGenerator, or a random CodeGenerator. + // a. See if we can execute it immediately and call into build if it yields. (and split budgets). + // b. if not, schedule it, pick a generator that get's us closer to the target context. + // c. see if we need to solve input constraints of scheduled generators. + // 2. budget is low + // a. Call scheduled GeneratorStubs or return. // Both splicing and code generation can sometimes fail, for example if no other program with the necessary features exists. // To avoid infinite loops, we bail out after a certain number of consecutive failures. var consecutiveFailures = 0 - let state = BuildingState(initialBudget: initialBuildingBudget, mode: mode) - buildStack.push(state) - defer { buildStack.pop() } - var remainingBudget = initialBuildingBudget + var remainingBudget = budget // Unless we are only splicing, find all generators that have the required context. We must always have at least one suitable code generator. let origContext = context - var availableGenerators = WeightedList() - if state.mode != .splicing { - availableGenerators = fuzzer.codeGenerators.filter({ $0.requiredContext.isSubset(of: origContext) }) - assert(!availableGenerators.isEmpty) - } - - struct BuildLog { - enum ActionOutcome { - case success - case failed - } - - struct BuildAction { - var action: String - var outcome: ActionOutcome? - } - - var actions = [BuildAction]() - - mutating func startAction(_ action: String) { - // Make sure that we have either completed our last build step or we haven't started any build steps yet. - assert(actions.isEmpty || actions[actions.count - 1].outcome != nil) - actions.append(BuildAction(action: action)) - } - - mutating func endAction(withOutcome outcome: ActionOutcome) { - assert(!actions.isEmpty && actions[actions.count - 1].outcome == nil) - actions[actions.count - 1].outcome = outcome - } - - } - - var buildLog = fuzzer.config.logLevel.isAtLeast(.verbose) ? BuildLog() : nil while remainingBudget > 0 { assert(context == origContext, "Code generation or splicing must not change the current context") - if state.recursiveBuildingAllowed && - remainingBudget < ProgramBuilder.minBudgetForRecursiveCodeGeneration && - availableGenerators.contains(where: { !$0.isRecursive }) { - // No more recursion at this point since the remaining budget is too small. - state.recursiveBuildingAllowed = false - availableGenerators = availableGenerators.filter({ !$0.isRecursive }) - assert(state.mode == .splicing || !availableGenerators.isEmpty) - } - - var mode = state.mode - if mode == .generatingAndSplicing { - mode = fuzzer.corpus.isEmpty ? .generating : chooseUniform(from: [.generating, .splicing]) - } - let codeSizeBefore = code.count switch mode { case .generating: @@ -1814,18 +1848,51 @@ public class ProgramBuilder { // visible Variables. Therefore we should always have some Variables visible if we want to use them. assert(hasVisibleVariables, "CodeGenerators assume that there are visible variables to use. Use buildPrefix() to generate some initial variables in a new program") - // Reset the code generator specific part of the state. - state.nextRecursiveBlockOfCurrentGenerator = 1 - state.totalRecursiveBlocksOfCurrentGenerator = nil + var generator: CodeGenerator? = nil + + // If the budget is low, we will pick a CodeGenerator that is directly usable from the current context. + // If we still have budget left, we will instead pick any CodeGenerator that is reachable from the current context, which means that we might go from .javascript to .wasmFunction. + if remainingBudget < ProgramBuilder.minBudgetForRecursiveCodeGeneration { + generator = fuzzer.codeGenerators.filter({ + $0.requiredContext.isSubset(of: context) + }).randomElement() + + guard generator != nil else { + fatalError("need a callable generator from every context!") + } + } else { + var counter = 0 + // We now try to assemble a Generator that we want to use. + while generator == nil { + // If we haven't managed to find a suitable CodeGenerator, we will try again but only consider CodeGenerators that are reachable from the current context. This should always work. + if counter == 10 { + generator = fuzzer.codeGenerators.filter({ + $0.requiredContext.isSubset(of: context) + }).randomElement()! + break + } + // Select a random CodeGenerator that is reachable from the current context and run it. + let reachableContexts = fuzzer.contextGraph.getReachableContexts(from: context) + let possibleGenerators = fuzzer.codeGenerators.filter({ generator in + reachableContexts.reduce(false) { res, reachable in + return res || generator.requiredContext.isSubset(of: reachable) + } + }) + + assert(!possibleGenerators.isEmpty) + let randomGenerator = possibleGenerators.randomElement() + // After having picked a generator, we might need to nest it in other generators that provide the necessary contexts. + generator = assembleSyntheticGenerator(for: randomGenerator) + counter += 1 + } + } - // Select a random generator and run it. - let generator = availableGenerators.randomElement() - buildLog?.startAction(generator.name) - run(generator) + // TODO: think about this and if we want to split this so that we get more CodeGenerators on the same level? + let _ = complete(generator: generator!, withBudget: remainingBudget / splitFactor) case .splicing: let program = fuzzer.corpus.randomElementForSplicing() - buildLog?.startAction("splicing") + buildLog?.startAction("splicing", produces: []) splice(from: program) default: @@ -1836,25 +1903,30 @@ public class ProgramBuilder { let emittedInstructions = codeSizeAfter - codeSizeBefore remainingBudget -= emittedInstructions if emittedInstructions > 0 { - buildLog?.endAction(withOutcome: .success) - consecutiveFailures = 0 + if mode == .splicing { + buildLog?.succeedAction([]) + } } else { - buildLog?.endAction(withOutcome: .failed) + if mode == .splicing { + buildLog?.reportFailure() + } consecutiveFailures += 1 guard consecutiveFailures < 10 else { // When splicing, this is somewhat expected as we may not find code to splice if we're in a restricted // context (e.g. we're inside a switch, but can't find another program with switch-cases). // However, when generating code this should happen very rarely since we should always be able to // generate code, not matter what context we are currently in. - if state.mode != .splicing { - logger.warning("Too many consecutive failures during code building with mode .\(state.mode). Bailing out.") - if let actions = buildLog?.actions { - logger.verbose("Build log:") - for action in actions { - logger.verbose(" \(action.action): \(action.outcome!)") - } + if mode != .splicing { + if let log = buildLog { + logger.verbose(log.getLogString()) } } + // If we have requested generatingAndSplicing initially and + // then decided to splice and fail here, we generate as a + // fallback. + if buildingMode == .generatingAndSplicing && mode == .splicing { + build(n: budget, by: .generating) + } return } } @@ -1865,9 +1937,6 @@ public class ProgramBuilder { /// Returns both the number of generated instructions and of newly created variables. @discardableResult public func buildValues(_ n: Int) -> (generatedInstructions: Int, generatedVariables: Int) { - // Either we are in .javascript and see no variables, or we are in a wasm function and also don't see any variables. - assert(context.isValueBuildableContext) - var valueGenerators = fuzzer.codeGenerators.filter({ $0.isValueGenerator }) // Filter for the current context valueGenerators = valueGenerators.filter { context.contains($0.requiredContext) } @@ -1881,18 +1950,17 @@ public class ProgramBuilder { // budget and allows us to run code generators when building recursively. We probably don't want to run // splicing here since splicing isn't as careful as code generation and may lead to invalid code more quickly. // The `initialBudget` isn't really used (since we specify a `recursiveBudget`), so can be an arbitrary value. - let state = BuildingState(initialBudget: 2 * n, mode: .generating) - state.recursiveBudget = n - buildStack.push(state) - defer { buildStack.pop() } + + let currentBudget = 2 * n while numberOfVisibleVariables - previousNumberOfVisibleVariables < n { + let generator = valueGenerators.randomElement() - assert(generator.requiredContext.isValueBuildableContext && generator.inputs.count == 0) - state.nextRecursiveBlockOfCurrentGenerator = 1 - state.totalRecursiveBlocksOfCurrentGenerator = nil - let numberOfGeneratedInstructions = run(generator) + // Just fully run the generator without yielding back. + // Think about changing this and calling into the higher level build logic? + // TODO arbitrary budget here right now, change this to some split factor? + let numberOfGeneratedInstructions = self.complete(generator: generator, withBudget: currentBudget / 5) assert(numberOfGeneratedInstructions > 0, "ValueGenerators must always succeed") totalNumberOfGeneratedInstructions += numberOfGeneratedInstructions @@ -1911,7 +1979,7 @@ public class ProgramBuilder { public func buildPrefix() { // Each value generators should generate at least 3 variables, and we probably want to run at least a // few of them (maybe roughly >= 3), so the number of variables to build shouldn't be set too low. - assert(CodeGenerator.numberOfValuesToGenerateByValueGenerators == 3) + assert(GeneratorStub.numberOfValuesToGenerateByValueGenerators == 3) let numValuesToBuild = Int.random(in: 10...15) trace("Start of prefix code") @@ -1930,7 +1998,7 @@ public class ProgramBuilder { // We need to update the inputs later, so take note of the visible variables here. let oldVisibleVariables = visibleVariables - build(n: defaultCodeGenerationAmount, by: mode) + build(n: defaultCodeGenerationAmount) let newVisibleVariables = visibleVariables.filter { v in let t = type(of: v) @@ -1948,12 +2016,248 @@ public class ProgramBuilder { append(Instruction(newOp, inouts: Array(newInputs) + newOutputs, flags: instr.flags)) } + // This function knows its own budget, and splits it to its yield points. + public func complete(generator: CodeGenerator, withBudget budget: Int) -> Int { + trace("Executing Generator \(generator.expandedName)") + let actionName = "Generator: " + generator.expandedName + buildLog?.startAction(actionName, produces: generator.produces) + let visibleVariablesBefore = visibleVariables + + let depth = scheduled.count + + // Split budget evenly at yield points. + let budgetPerYieldPoint = budget / generator.parts.count + + var numberOfGeneratedInstructions = 0 + + // calculate all input requirements of this CodeGenerator. + let inputTypes = Set(generator.parts.reduce([]) { res, gen in + return res + gen.inputs.types + }) + + var availableTypes = inputTypes.filter { + randomVariable(ofType: $0) != nil + } + + // Add the current context to the seen Contexts as well. + var seenContexts: [Context] = [context] + + let contextsAndTypes = generator.parts.map { ($0.providedContext, $0.inputs.types) } + + // Check if the can be produced along this generator, otherwise we need to bail. + for (contexts, types) in contextsAndTypes { + // We've seen the current context. + for context in contexts { + seenContexts.append(context) + } + + for type in types { + // If we don't have the type available, check if we can produce it in the current context or a seen context. + if !availableTypes.contains(where: { + type.Is($0) + }) { + // Check if we have generators that can produce the type reachable from this context. + let reachableContexts: Context = seenContexts.reduce(Context.empty) { res, ctx in [res, fuzzer.contextGraph.getReachableContexts(from: ctx).reduce(Context.empty) { res, ctx in [res, ctx]}] + } + + // Right now this checks if the generator is a subset of the full reachable context (a single bitfield with all reachable contexts). + // TODO: We need to also do some graph thingies here and add our requested types to the queue to see if we can fulfill the requested types. if we see that a generator produces a type, we need to put its input requirements onto the queue and start over? + // Maybe overkill, but also cool. + let callableGenerators = fuzzer.codeGenerators.filter { + $0.requiredContext.isSubset(of: reachableContexts) + } + + // Filter to see if they produce this type. Crucially to avoid dependency cycles, these also need to be valuegenerators. + let canProduceThisType = callableGenerators.contains(where: { generator in + generator.produces.contains(where: { $0.Is(type) }) + }) + + // We cannot run if this is false. + if !canProduceThisType { + // TODO(cffsmith): track some statistics on how often this happens. + buildLog?.reportFailure(reason: "Cannot produce type \(type) starting in original context \(context).") + return 0 + } else { + // Mark the type as available. + availableTypes.insert(type) + } + } + } + } + + // Try to create the types that we need for this generator. + // At this point we've guaranteed that we can produce the types somewhere along the yield points of this generator. + createRequiredInputVariables(forTypes: inputTypes) + + // Push the remaining stubs, we need to call them to close all Contexts properly. + for part in generator.tail.reversed() { + scheduled.push(part) + } + + // This runs the first part of the generator. + numberOfGeneratedInstructions += self.run(generator.head) + + // If this generator says it provides a context, it must do so, it cannot fail because we would not be able to continue with the rest of the generator. + // TODO(cffsmith): implement some forking / merging mode for the Code Object? that way we could "roll back" some changes. + let subsetContext = generator.head.providedContext.reduce(Context.empty) { res, context in + return [res, context] + } + + assert(subsetContext.isSubset(of: context), "Generators that claim to provide contexts cannot fail to provide those contexts \(generator.head.name).") + + // While our local stack is not removed, we need to call into build and call the scheduled stubs. + while scheduled.count > depth { + let codeSizePre = code.count + // Check if we need to or can create types here. + createRequiredInputVariables(forTypes: inputTypes) + // Build into the block. + build(n: budgetPerYieldPoint) + // Call the next scheduled stub. + let _ = callNext() + numberOfGeneratedInstructions += code.count - codeSizePre + } + + if numberOfGeneratedInstructions > 0 { + buildLog?.succeedAction( + Set(visibleVariables) + .subtracting(Set(visibleVariablesBefore)) + .map(self.type) + ) + } else { + buildLog?.reportFailure() + } + + // I guess this is kind of implied by the logic above, yet if someone calls an extra closer in build somehow this would catch it. + assert(depth == scheduled.count, "Build stack is not balanced") + return numberOfGeneratedInstructions + } + + // Todo, the context graph could also find ideal paths that allow type creation. + private func createRequiredInputVariables(forTypes types: Set) { + for type in types { + if type.Is(.jsAnything) && context.contains(.javascript) { + let _ = findOrGenerateType(type) + } else { + if type.Is(.wasmAnything) && context.contains(.wasmFunction) { + // Check if we can produce it with findOrGenerateWasmVar + let _ = currentWasmFunction.generateRandomWasmVar(ofType: type) + } + if randomVariable(ofType: type) == nil { + // Check for other CodeGenerators that can produce the given type in this context. + let usableGenerators = fuzzer.codeGenerators.filter { + $0.requiredContext.isSubset(of: context) && + $0.produces.contains { + $0.Is(type) + } + } + + // Cannot build type here. + if usableGenerators.isEmpty { + // Continue here though, as we might be able to create Variables for other types. + continue + } + + let generator = usableGenerators.randomElement() + + let _ = complete(generator: generator, withBudget: 5) + } + } + } + } + + // The `mainGenerator` is the actual generator that we want to run, we now might need to schedule other generators first to reach a necessary context. + public func assembleSyntheticGenerator(for mainGenerator: CodeGenerator) -> CodeGenerator? { + // We can directly run this CodeGenerator here. + if context.contains(mainGenerator.requiredContext) { + return mainGenerator + } + + // Get all the generators for each edge and pick one of them. + + // We might be in a context that is a union, e.g. .javascript | .subroutine. We then need to get all Paths from both possible single contexts. + let paths: [ContextGraph.Path] = Context.allCases.reduce([]) { pathArray, possibleContext in + + if context.contains(possibleContext) { + // Walk through generated Graph and find a path. + let paths = fuzzer.contextGraph.getCodeGeneratorPaths(from: possibleContext, to: mainGenerator.requiredContext) ?? [] + return pathArray + paths + } + + return pathArray + } + + if paths.isEmpty { + logger.warning("Found no paths in context \(context) for requested generator \(mainGenerator.name)") + return nil + } + + // Pick a random path. + let path = chooseUniform(from: paths) + + // For each edge in the path, pick a random generator. + // This is now a list of CodeGenerators, i.e. pairs of logical units. + let generators: [CodeGenerator] = path.randomConcretePath() + + // So we can now assemble a synthetic generator that invokes our picked generator. + // We start by taking our first CodeGenerator, that will open the next context that is necessary. + // This is an incomplete CodeGenerator right now, as it only contains one part that opens a new context. + var syntheticGenerator = generators[0].parts + + // For all other Stubs we will now successively insert the next CodeGenerator stubs into the right "spots". + // + // We now try to insert the next part of the first CodeGenerator into our synthetic generator. + // [some context] + // We now go to the next Edge of our path and try to insert that head in the correct spot. + // [some context] [another context] + // Since every part of the CodeGenerator requires its previous context, we can insert them in the right spot. + // At the very end, we will insert the actual CodeGenerator that we want to call. + // This essentially amounts to an insertion sort. + // (Because we might have CodeGenerators with multiple parts and different contexts, we cannot do this without sorting) + for subGenerator in generators[1...] + [mainGenerator] { + for (idx, part) in syntheticGenerator.enumerated() { + if part.providedContext.contains(where: { ctx in + subGenerator.head.requiredContext.satisfied(by: ctx) + }) { + // Insert this codegenerator here after this stub. + syntheticGenerator.insert(contentsOf: subGenerator.parts, at: idx + 1) + break + } + } + } + + // This is our synthetic CodeGenerator. + return CodeGenerator("Synthetic", syntheticGenerator) + } + + // Calls the next scheduled generator. + private func callNext() -> Int { + // Check if we should pass variables to this closer somehow? + if !scheduled.isEmpty { + let generator = scheduled.pop() + let generatedInstructions = self.run(generator) + + let subsetContext = generator.providedContext.reduce(Context.empty) { res, context in + return [res, context] + } + + assert(subsetContext.isSubset(of: context), "Generators that claim to provide contexts cannot fail to provide those contexts \(generator.name).") + + + return generatedInstructions + } else { + return 0 + } + } + /// Runs a code generator in the current context and returns the number of generated instructions. @discardableResult - private func run(_ generator: CodeGenerator) -> Int { - assert(generator.requiredContext.isSubset(of: context)) + private func run(_ generator: GeneratorStub) -> Int { + // Any of the required Context constraints need to be satisfied. + assert(generator.requiredContext.satisfied(by: context)) + let visibleVariablesBefore = visibleVariables trace("Executing code generator \(generator.name)") + buildLog?.startAction(generator.name, produces: generator.produces) var inputs = [Variable]() switch generator.inputs.mode { case .loose: @@ -1965,6 +2269,11 @@ public class ProgramBuilder { for inputType in generator.inputs.types { guard let input = randomVariable(ofType: inputType) else { // Cannot run this generator + if generator.providedContext != [] { + fatalError("This generator is supposed to provide a context but cannot as we've failed to find the necessary inputs.") + } + // This early return also needs to report a failure. + buildLog?.reportFailure(reason: "Cannot find variable that satifies input constraints \(inputType).") return 0 } inputs.append(input) @@ -1975,6 +2284,13 @@ public class ProgramBuilder { if numGeneratedInstructions > 0 { contributors.insert(generator) + buildLog?.succeedAction( + Set(visibleVariables) + .subtracting(Set(visibleVariablesBefore)) + .map(self.type) + ) + } else { + buildLog?.reportFailure(reason: "Generator itself failed to produce any instructions.") } return numGeneratedInstructions @@ -2004,7 +2320,7 @@ public class ProgramBuilder { } @discardableResult - private func emit(_ op: Operation, withInputs inputs: [Variable] = [], types: [ILType]? = nil) -> Instruction { + public func emit(_ op: Operation, withInputs inputs: [Variable] = [], types: [ILType]? = nil) -> Instruction { var inouts = inputs for _ in 0.. Variable { - return emit(LoadString(value: value)).output + public func loadString(_ value: String, customName: String? = nil) -> Variable { + return emit(LoadString(value: value, customName: customName)).output + } + + @discardableResult + public func loadEnum(_ type: ILType) -> Variable { + assert(type.isEnumeration) + return loadString(chooseUniform(from: type.enumValues), customName: type.group) } @discardableResult @@ -2191,6 +2513,7 @@ public class ProgramBuilder { public fileprivate(set) var instanceElements: [Int64] = [] public fileprivate(set) var instanceComputedProperties: [Variable] = [] public fileprivate(set) var instanceMethods: [String] = [] + public fileprivate(set) var instanceComputedMethods: [Variable] = [] public fileprivate(set) var instanceGetters: [String] = [] public fileprivate(set) var instanceSetters: [String] = [] @@ -2198,6 +2521,7 @@ public class ProgramBuilder { public fileprivate(set) var staticElements: [Int64] = [] public fileprivate(set) var staticComputedProperties: [Variable] = [] public fileprivate(set) var staticMethods: [String] = [] + public fileprivate(set) var staticComputedMethods: [Variable] = [] public fileprivate(set) var staticGetters: [String] = [] public fileprivate(set) var staticSetters: [String] = [] @@ -2249,6 +2573,13 @@ public class ProgramBuilder { b.emit(EndClassInstanceMethod()) } + public func addInstanceComputedMethod(_ name: Variable, with descriptor: SubroutineDescriptor, _ body: ([Variable]) -> ()) { + b.setParameterTypesForNextSubroutine(descriptor.parameterTypes) + let instr = b.emit(BeginClassInstanceComputedMethod(parameters: descriptor.parameters), withInputs: [name]) + body(Array(instr.innerOutputs)) + b.emit(EndClassInstanceComputedMethod()) + } + public func addInstanceGetter(for name: String, _ body: (_ this: Variable) -> ()) { let instr = b.emit(BeginClassInstanceGetter(propertyName: name)) body(instr.innerOutput) @@ -2289,6 +2620,13 @@ public class ProgramBuilder { b.emit(EndClassStaticMethod()) } + public func addStaticComputedMethod(_ name: Variable, with descriptor: SubroutineDescriptor, _ body: ([Variable]) -> ()) { + b.setParameterTypesForNextSubroutine(descriptor.parameterTypes) + let instr = b.emit(BeginClassStaticComputedMethod(parameters: descriptor.parameters), withInputs: [name]) + body(Array(instr.innerOutputs)) + b.emit(EndClassStaticComputedMethod()) + } + public func addStaticGetter(for name: String, _ body: (_ this: Variable) -> ()) { let instr = b.emit(BeginClassStaticGetter(propertyName: name)) body(instr.innerOutput) @@ -2769,6 +3107,16 @@ public class ProgramBuilder { return createNamedVariable(builtinName, declarationMode: .none) } + @discardableResult + public func createNamedDisposableVariable(_ name: String, _ initialValue: Variable) -> Variable { + return emit(CreateNamedDisposableVariable(name), withInputs: [initialValue]).output + } + + @discardableResult + public func createNamedAsyncDisposableVariable(_ name: String, _ initialValue: Variable) -> Variable { + return emit(CreateNamedAsyncDisposableVariable(name), withInputs: [initialValue]).output + } + @discardableResult public func eval(_ string: String, with arguments: [Variable] = [], hasOutput: Bool = false) -> Variable? { let instr = emit(Eval(string, numArguments: arguments.count, hasOutput: hasOutput), withInputs: arguments) @@ -3703,19 +4051,20 @@ public class ProgramBuilder { if type.Is(.wasmGenericRef) { // TODO(cffsmith): Can we improve this once we have better support for ad hoc // code generation in other contexts? - switch type.wasmReferenceType!.kind { - case .Abstract(let heapType): - if heapType == .WasmI31 { - // Prefer generating a non-null value. - return probability(0.2) && type.wasmReferenceType!.nullability - ? self.wasmRefNull(type: type) - : self.wasmRefI31(self.consti32(Int32(truncatingIfNeeded: b.randomInt()))) - } - assert(type.wasmReferenceType!.nullability) - return self.wasmRefNull(type: type) - case .Index(_): - break // Unimplemented + switch type.wasmReferenceType?.kind { + case .Abstract(let heapType): + if heapType == .WasmI31 { + // Prefer generating a non-null value. + return probability(0.2) && type.wasmReferenceType!.nullability + ? self.wasmRefNull(type: type) + : self.wasmRefI31(self.consti32(Int32(truncatingIfNeeded: b.randomInt()))) } + assert(type.wasmReferenceType!.nullability) + return self.wasmRefNull(type: type) + case .Index(_), + .none: + break // Unimplemented + } } else { return nil } @@ -4063,16 +4412,31 @@ public class ProgramBuilder { return (dynamicOffset, alignedStaticOffset) } - public func randomWasmGlobal() -> WasmGlobal { - // TODO: Add simd128 and nullrefs. - withEqualProbability( - {.wasmf32(Float32(self.randomFloat()))}, - {.wasmf64(self.randomFloat())}, - {.wasmi32(Int32(truncatingIfNeeded: self.randomInt()))}, - {.wasmi64(self.randomInt())}, - {.externref}, - {.exnref}, - {.i31ref}) + /// Produces a WasmGlobal that is valid to create in the given Context. + public func randomWasmGlobal(forContext context: Context) -> WasmGlobal { + switch context { + case .javascript: + // These are valid in JS according to: https://webassembly.github.io/spec/js-api/#globals. + // TODO: add simd128 and anyfunc. + return withEqualProbability( + {.wasmf32(Float32(self.randomFloat()))}, + {.wasmf64(self.randomFloat())}, + {.wasmi32(Int32(truncatingIfNeeded: self.randomInt()))}, + {.wasmi64(self.randomInt())}, + {.externref}) + case .wasm: + // TODO: Add simd128 and nullrefs. + return withEqualProbability( + {.wasmf32(Float32(self.randomFloat()))}, + {.wasmf64(self.randomFloat())}, + {.wasmi32(Int32(truncatingIfNeeded: self.randomInt()))}, + {.wasmi64(self.randomInt())}, + {.externref}, + {.exnref}, + {.i31ref}) + default: + fatalError("Unsupported context \(context) for a WasmGlobal.") + } } public func randomTagParameters() -> [ILType] { @@ -4170,6 +4534,11 @@ public class ProgramBuilder { return Array(emit(WasmEndTypeGroup(typesCount: types.count), withInputs: types).outputs) } + @discardableResult + func wasmDefineSignatureType(signature: WasmSignature, indexTypes: [Variable]) -> Variable { + return emit(WasmDefineSignatureType(signature: signature), withInputs: indexTypes).output + } + @discardableResult func wasmDefineArrayType(elementType: ILType, mutability: Bool, indexType: Variable? = nil) -> Variable { let inputs = indexType != nil ? [indexType!] : [] @@ -4235,7 +4604,7 @@ public class ProgramBuilder { /// Set the parameter types for the next function, method, or constructor, which must be the the start of a function or method definition. /// Parameter types (and signatures in general) are only valid for the duration of the program generation, as they cannot be preserved across mutations. /// As such, the parameter types are linked to their instruction through the index of the instruction in the program. - private func setParameterTypesForNextSubroutine(_ parameterTypes: ParameterList) { + public func setParameterTypesForNextSubroutine(_ parameterTypes: ParameterList) { jsTyper.setParameters(forSubroutineStartingAt: code.count, to: parameterTypes) } @@ -4315,6 +4684,8 @@ public class ProgramBuilder { activeClassDefinitions.top.instanceComputedProperties.append(instr.input(0)) case .beginClassInstanceMethod(let op): activeClassDefinitions.top.instanceMethods.append(op.methodName) + case .beginClassInstanceComputedMethod: + activeClassDefinitions.top.instanceComputedMethods.append(instr.input(0)) case .beginClassInstanceGetter(let op): activeClassDefinitions.top.instanceGetters.append(op.propertyName) case .beginClassInstanceSetter(let op): @@ -4327,6 +4698,8 @@ public class ProgramBuilder { activeClassDefinitions.top.staticComputedProperties.append(instr.input(0)) case .beginClassStaticMethod(let op): activeClassDefinitions.top.staticMethods.append(op.methodName) + case .beginClassStaticComputedMethod: + activeClassDefinitions.top.staticComputedMethods.append(instr.input(0)) case .beginClassStaticGetter(let op): activeClassDefinitions.top.staticGetters.append(op.propertyName) case .beginClassStaticSetter(let op): @@ -4393,6 +4766,23 @@ public class ProgramBuilder { .wasmEndTryTable(_), .wasmEndBlock(_): activeWasmModule!.blockSignatures.pop() + case .beginPlainFunction(_), + .beginArrowFunction(_), + .beginAsyncArrowFunction(_), + .beginAsyncFunction(_), + .beginAsyncGeneratorFunction(_), + .beginCodeString(_), + .beginGeneratorFunction(_): + assert(instr.numOutputs == 1) + lastFunctionVariables.push(instr.output) + case .endPlainFunction(_), + .endArrowFunction(_), + .endAsyncArrowFunction(_), + .endAsyncFunction(_), + .endAsyncGeneratorFunction(_), + .endCodeString(_), + .endGeneratorFunction(_): + lastFunctionVariables.pop() default: assert(!instr.op.requiredContext.contains(.objectLiteral)) @@ -4414,7 +4804,7 @@ public class ProgramBuilder { // and let the mutator prune things let dict: [String : Variable] = bag.properties.filter {_ in probability(0.8)}.mapValues { if $0.isEnumeration { - return loadString(chooseUniform(from: $0.enumValues)) + return loadEnum($0) // relativeTo doesn't have an ObjectGroup so we cannot just register a producingGenerator for it } else if $0.Is(OptionsBag.jsTemporalRelativeTo) { return findOrGenerateType(chooseUniform(from: [.jsTemporalZonedDateTime, .jsTemporalPlainDateTime, @@ -4442,17 +4832,40 @@ public class ProgramBuilder { // Generate a random time zone identifier @discardableResult func randomTimeZone() -> Variable { + return loadString(ProgramBuilder.randomTimeZoneString(), customName: "TemporalTimeZoneString") + } + + @discardableResult + static func randomTimeZoneString() -> String { // Bias towards knownTimeZoneIdentifiers since it's a larger array if probability(0.7) { - return loadString(chooseUniform(from: TimeZone.knownTimeZoneIdentifiers)) + return chooseUniform(from: TimeZone.knownTimeZoneIdentifiers) } else { - return loadString(chooseUniform(from: TimeZone.abbreviationDictionary.keys)) + return chooseUniform(from: TimeZone.abbreviationDictionary.keys) } } @discardableResult - func randomUTCOffset() -> Variable { - return loadString(randomUTCOffsetString(mayHaveSeconds: true)) + func randomUTCOffset(mayHaveSeconds: Bool) -> Variable { + return loadString(ProgramBuilder.randomUTCOffsetString(mayHaveSeconds: mayHaveSeconds), customName: "TemporalTimeZoneString") + } + + @discardableResult + static func randomUTCOffsetString(mayHaveSeconds: Bool) -> String { + let hours = Int.random(in: 0..<24) + // Bias towards zero minutes since that's what most time zones do. + let zeroMinutes = probability(0.8) + let minutes = zeroMinutes ? 0 : Int.random(in: 0..<60) + let plusminus = Bool.random() ? "+" : "-"; + var offset = String(format: "%@%02d:%02d", plusminus, hours, minutes) + if !zeroMinutes && mayHaveSeconds && probability(0.3) { + let seconds = Int.random(in: 0..<60) + offset = String(format: "%@:%02d", offset, seconds) + if probability(0.3) { + offset = String(format: "%@:.%09d", offset, Int.random(in: 0...999999999)) + } + } + return offset } // Generate an object with fields from @@ -4582,7 +4995,7 @@ public class ProgramBuilder { if (!forWith) { if Bool.random() { // Time zones can be offsets, but cannot have seconds - generatedOffset = loadString(randomUTCOffsetString(mayHaveSeconds: false)) + generatedOffset = randomUTCOffset(mayHaveSeconds: false) properties["timeZone"] = generatedOffset } else { properties["timeZone"] = randomTimeZone() @@ -4599,7 +5012,7 @@ public class ProgramBuilder { properties["offset"] = generatedOffset! } else if probability(0.3) { // Otherwise, with a low probability, generate a random offset. - properties["offset"] = loadString(randomUTCOffsetString(mayHaveSeconds: true)) + properties["offset"] = randomUTCOffset(mayHaveSeconds: true) } } return createObject(with: properties) @@ -4748,23 +5161,75 @@ public class ProgramBuilder { } } -} + @discardableResult + static func constructIntlLocaleString() -> String { + // TODO(Manishearth) Generate more interesting locales than just the builtins + return chooseUniform(from: Locale.availableIdentifiers) + } + + // Obtained by calling Intl.supportedValuesOf("unit") in a browser + fileprivate static let allUnits = ["acre", "bit", "byte", "celsius", "centimeter", "day", "degree", "fahrenheit", "fluid-ounce", "foot", "gallon", "gigabit", "gigabyte", "gram", "hectare", "hour", "inch", "kilobit", "kilobyte", "kilogram", "kilometer", "liter", "megabit", "megabyte", "meter", "microsecond", "mile", "mile-scandinavian", "milliliter", "millimeter", "millisecond", "minute", "month", "nanosecond", "ounce", "percent", "petabyte", "pound", "second", "stone", "terabit", "terabyte", "week", "yard", "year"] + + @discardableResult + static func constructIntlUnit() -> String { + let firstUnit = chooseUniform(from: allUnits) + // Intl is able to format combinations of units too, like hectares-per-gallon + if probability(0.7) { + return firstUnit + } else { + return "\(firstUnit)-per-\(chooseUniform(from: allUnits))" + } + } + // Generic generators for Intl types. + private func constructIntlType(type: String, optionsBag: OptionsBag) -> Variable { + let intl = createNamedVariable(forBuiltin: "Intl") + let constructor = getProperty(type, of: intl) + var args: [Variable] = [] + if probability(0.7) { + args.append(findOrGenerateType(.jsIntlLocaleLike)) -fileprivate func randomUTCOffsetString(mayHaveSeconds: Bool) -> String { - let hours = Int.random(in: 0..<24) - // Bias towards zero minutes since that's what most time zones do. - let zeroMinutes = probability(0.8) - let minutes = zeroMinutes ? 0 : Int.random(in: 0..<60) - let plusminus = Bool.random() ? "+" : "-"; - var offset = String(format: "%@%02d:%02d", plusminus, hours, minutes) - if !zeroMinutes && mayHaveSeconds && probability(0.3) { - let seconds = Int.random(in: 0..<60) - offset = String(format: "%@:%02d", offset, seconds) - if probability(0.3) { - offset = String(format: "%@:.%09d", offset, Int.random(in: 0...999999999)) + if probability(0.7) { + args.append(createOptionsBag(optionsBag)) + } } + return construct(constructor, withArgs: args) + } + + @discardableResult + func constructIntlDateTimeFormat() -> Variable { + return constructIntlType(type: "DateTimeFormat", optionsBag: .jsIntlDateTimeFormatSettings) + } + + @discardableResult + func constructIntlCollator() -> Variable { + return constructIntlType(type: "Collator", optionsBag: .jsIntlCollatorSettings) + } + + @discardableResult + func constructIntlListFormat() -> Variable { + return constructIntlType(type: "ListFormat", optionsBag: .jsIntlListFormatSettings) + } + + @discardableResult + func constructIntlNumberFormat() -> Variable { + return constructIntlType(type: "NumberFormat", optionsBag: .jsIntlNumberFormatSettings) + } + + @discardableResult + func constructIntlPluralRules() -> Variable { + return constructIntlType(type: "PluralRules", optionsBag: .jsIntlPluralRulesSettings) + } + + @discardableResult + func constructIntlRelativeTimeFormat() -> Variable { + return constructIntlType(type: "RelativeTimeFormat", optionsBag: .jsIntlRelativeTimeFormatSettings) + } + + @discardableResult + func constructIntlSegmenter() -> Variable { + return constructIntlType(type: "Segmenter", optionsBag: .jsIntlSegmenterSettings) } - return offset } + diff --git a/Sources/Fuzzilli/CodeGen/CodeGenerator.swift b/Sources/Fuzzilli/CodeGen/CodeGenerator.swift index edb6251c5..08b3ff5b8 100644 --- a/Sources/Fuzzilli/CodeGen/CodeGenerator.swift +++ b/Sources/Fuzzilli/CodeGen/CodeGenerator.swift @@ -23,7 +23,7 @@ fileprivate struct ValueGeneratorAdapter: GeneratorAdapter { let f: ValueGeneratorFunc func run(in b: ProgramBuilder, with inputs: [Variable]) { assert(inputs.isEmpty) - f(b, CodeGenerator.numberOfValuesToGenerateByValueGenerators) + f(b, GeneratorStub.numberOfValuesToGenerateByValueGenerators) } } @@ -77,20 +77,17 @@ fileprivate struct GeneratorAdapter4Args: GeneratorAdapter { } } -public class CodeGenerator: Contributor { +public class GeneratorStub: Contributor { /// Whether this code generator is a value generator. A value generator will create at least one new variable containing /// a newly created value (e.g. a primitive value or some kind of object). Further, value generators must be able to /// run even if there are no existing variables. This way, they can be used to "bootstrap" code generation. - public let isValueGenerator: Bool + public var isValueStub: Bool { + self.inputs.isEmpty && !self.produces.isEmpty + } /// How many different values of the same type ValueGenerators should aim to generate. public static let numberOfValuesToGenerateByValueGenerators = 3 - /// Whether this code generator is recursive, i.e. will generate further code for example to generate the body of a block. - /// This is used to determie whether to run a certain code generator. For example, if only a few more instructions should - /// be generated during program building, calling a recursive code generator will likely result in too many instructions. - public let isRecursive: Bool - /// Describes the inputs expected by a CodeGenerator. public struct Inputs { /// How the inputs should be treated. @@ -111,6 +108,10 @@ public class CodeGenerator: Contributor { return types.count } + public var isEmpty: Bool { + types.count == 0 + } + // No inputs. public static var none: Inputs { return Inputs(types: [], mode: .loose) @@ -164,27 +165,92 @@ public class CodeGenerator: Contributor { /// The inputs expected by this generator. public let inputs: Inputs - public let produces: ILType? + /// The types this CodeGenerator produces + /// ProgramBuilding will assert that these types are (newly) available after running this CodeGenerator. + public let produces: [ILType] + + public enum ContextRequirement { + // If this GeneratorStub has a single Context requirement, which may still be comprised of multiple Context values. + // E.g. .single([.javascript | .method]) or .single([.javascript, .method]) (they're equivalent). + case single(Context) + // If this GeneratorStub has any one of the Context requirements. + // E.g. .either([.javascript, .classDefinition]) which is basically either one of .javascript or .classDefinition + case either([Context]) + + var isSingle : Bool { + self.getSingle() != nil + } + + var isJavascript: Bool { + self.getSingle() == .javascript + } + + func getSingle() -> Context? { + switch self { + case .single(let ctx): + return ctx + default: + return nil + } + } + + // Whether the ContextRequirement is satisified for the given (current) context. + func satisfied(by context: Context) -> Bool { + switch self { + case .single(let ctx): + return ctx.isSubset(of: context) + case .either(let ctxs): + // We do this check here since we cannot check on construction whether we have used .either correctly. + assert(ctxs.count > 1, "Something seems wrong, one should have more than a single context here") + // Any of the Contexts needs to be satisfied. + return ctxs.contains(where: { ctx in + ctx.isSubset(of: context) + }) + } + } + + // Whether the provided Context is exactly the required context. + // Usually this means that if it is `.either` all the those Contexts need to be there. + // This is used in consistency checking in `ContextGraph` initialization where a previous generator might open multiple contexts. + func matches(_ context: Context) -> Bool { + switch self { + case .single(let ctx): + return ctx == context + case .either(let ctxs): + // We do this check here since we cannot check on construction whether we have used .either correctly. + assert(ctxs.count > 1, "Something seems wrong, one should have more than a single context here") + // All contexts need to be in the current context. + return ctxs.allSatisfy { ctx in + ctx.isSubset(of: context) + } + } + } + } /// The contexts in which this code generator can run. - /// This code generator will only be executed if requiredContext.isSubset(of: currentContext) - public let requiredContext: Context + /// This is an Array of subsets of Contexts, one of them has to be runnable. + /// An invariant is now, that a Generator *cannot* `provide` any Context if it might open either one of multiple Contexts. + /// We are checking the other direction of this in the initialization of `ContextGraph`. + /// This code generator will only be executed if any requiredContext.isSubset(of: currentContext) + public let requiredContext: ContextRequirement + + /// The context that is provided by running this Generator. + /// This is always a list of the single contexts that are provided, e.g. [.javascript] + public let providedContext: [Context] /// Warpper around the actual generator function called. private let adapter: GeneratorAdapter - fileprivate init(name: String, isValueGenerator: Bool, isRecursive: Bool, inputs: Inputs, produces: ILType? = nil, context: Context, adapter: GeneratorAdapter) { - assert(!isValueGenerator || context.isValueBuildableContext) - assert(!isValueGenerator || inputs.count == 0) - assert(inputs.count == adapter.expectedNumberOfInputs) + fileprivate init(name: String, inputs: Inputs, produces: [ILType] = [], context: ContextRequirement, providedContext: [Context] = [], adapter: GeneratorAdapter) { - self.isValueGenerator = isValueGenerator - self.isRecursive = isRecursive self.inputs = inputs self.produces = produces self.requiredContext = context + self.providedContext = providedContext self.adapter = adapter super.init(name: name) + + assert(inputs.count == adapter.expectedNumberOfInputs) } /// Execute this code generator, generating new code at the current position in the ProgramBuilder. @@ -199,56 +265,120 @@ public class CodeGenerator: Contributor { return addedInstructions } - public convenience init(_ name: String, inContext context: Context = .javascript, produces: ILType? = nil, _ f: @escaping GeneratorFuncNoArgs) { - self.init(name: name, isValueGenerator: false, isRecursive: false, inputs: .none, produces: produces, context: context, adapter: GeneratorAdapterNoArgs(f: f)) + public convenience init(_ name: String, inContext context: ContextRequirement = .single(.javascript), produces: [ILType] = [], provides: [Context] = [], _ f: @escaping GeneratorFuncNoArgs) { + self.init(name: name, inputs: .none, produces: produces, context: context, providedContext: provides, adapter: GeneratorAdapterNoArgs(f: f)) } - public convenience init(_ name: String, inContext context: Context = .javascript, inputs: Inputs, produces: ILType? = nil, _ f: @escaping GeneratorFunc1Arg) { + public convenience init(_ name: String, inContext context: ContextRequirement = .single(.javascript), inputs: Inputs, produces: [ILType] = [], provides: [Context] = [], _ f: @escaping GeneratorFunc1Arg) { assert(inputs.count == 1) - self.init(name: name, isValueGenerator: false, isRecursive: false, inputs: inputs, produces: produces, context: context, adapter: GeneratorAdapter1Arg(f: f)) + self.init(name: name, inputs: inputs, produces: produces, context: context, providedContext: provides, adapter: GeneratorAdapter1Arg(f: f)) } - public convenience init(_ name: String, inContext context: Context = .javascript, inputs: Inputs, produces: ILType? = nil, _ f: @escaping GeneratorFunc2Args) { + public convenience init(_ name: String, inContext context: ContextRequirement = .single(.javascript), inputs: Inputs, produces: [ILType] = [], provides: [Context] = [], _ f: @escaping GeneratorFunc2Args) { assert(inputs.count == 2) - self.init(name: name, isValueGenerator: false, isRecursive: false, inputs: inputs, produces: produces, context: context, adapter: GeneratorAdapter2Args(f: f)) + self.init(name: name, inputs: inputs, produces: produces, context: context, providedContext: provides, adapter: GeneratorAdapter2Args(f: f)) } - public convenience init(_ name: String, inContext context: Context = .javascript, inputs: Inputs, produces: ILType? = nil, _ f: @escaping GeneratorFunc3Args) { + public convenience init(_ name: String, inContext context: ContextRequirement = .single(.javascript), inputs: Inputs, produces: [ILType] = [], provides: [Context] = [], _ f: @escaping GeneratorFunc3Args) { assert(inputs.count == 3) - self.init(name: name, isValueGenerator: false, isRecursive: false, inputs: inputs, produces: produces, context: context, adapter: GeneratorAdapter3Args(f: f)) + self.init(name: name, inputs: inputs, produces: produces, context: context, providedContext: provides, adapter: GeneratorAdapter3Args(f: f)) } - public convenience init(_ name: String, inContext context: Context = .javascript, inputs: Inputs, produces: ILType? = nil, _ f: @escaping GeneratorFunc4Args) { + public convenience init(_ name: String, inContext context: ContextRequirement = .single(.javascript), inputs: Inputs, produces: [ILType] = [], provides: [Context] = [], _ f: @escaping GeneratorFunc4Args) { assert(inputs.count == 4) - self.init(name: name, isValueGenerator: false, isRecursive: false, inputs: inputs, produces: produces, context: context, adapter: GeneratorAdapter4Args(f: f)) + self.init(name: name, inputs: inputs, produces: produces, context: context, providedContext: provides, adapter: GeneratorAdapter4Args(f: f)) } } -// Constructors for recursive CodeGenerators. -public func RecursiveCodeGenerator(_ name: String, inContext context: Context = .javascript, _ f: @escaping GeneratorFuncNoArgs) -> CodeGenerator { - return CodeGenerator(name: name, isValueGenerator: false, isRecursive: true, inputs: .none, context: context, adapter: GeneratorAdapterNoArgs(f: f)) -} +public class CodeGenerator { + let parts: [GeneratorStub] + public let name: String + // This is a pre-calculated array of contexts that is provided by this CodeGenerator + // Here, I think we might have different Contexts that each yield point could provide, e.g. [.javascript | .subroutine, .wasmFunction]. Unsure if there is a situation where this matters? instead of having .javascript | .subroutine | .wasmFunction? + public let providedContexts: [Context] -public func RecursiveCodeGenerator(_ name: String, inContext context: Context = .javascript, inputs: CodeGenerator.Inputs, _ f: @escaping GeneratorFunc1Arg) -> CodeGenerator { - assert(inputs.count == 1) - return CodeGenerator(name: name, isValueGenerator: false, isRecursive: true, inputs: inputs, context: context, adapter: GeneratorAdapter1Arg(f: f)) -} -public func RecursiveCodeGenerator(_ name: String, inContext context: Context = .javascript, inputs: CodeGenerator.Inputs, _ f: @escaping GeneratorFunc2Args) -> CodeGenerator { - assert(inputs.count == 2) - return CodeGenerator(name: name, isValueGenerator: false, isRecursive: true, inputs: inputs, context: context, adapter: GeneratorAdapter2Args(f: f)) -} + public init(_ name: String, _ generators: [GeneratorStub]) { + self.parts = generators + self.name = name -// Constructors for ValueGenerators. A ValueGenerator is a CodeGenerator that produces one or more variables containing newly created values/objects. -// Further, a ValueGenerator must be able to run when there are no existing variables so that it can be used to bootstrap code generation. -public func ValueGenerator(_ name: String, _ f: @escaping ValueGeneratorFunc) -> CodeGenerator { - return CodeGenerator(name: name, isValueGenerator: true, isRecursive: false, inputs: .none, context: .javascript, adapter: ValueGeneratorAdapter(f: f)) -} + // Calculate all contexts provided at any time by this CodeGenerator. + var ctxSet = Set() + generators.forEach { gen in + gen.providedContext.forEach { ctx in + ctxSet.insert(ctx) + } + } -public func ValueGenerator(_ name: String, inContext context: Context, _ f: @escaping ValueGeneratorFunc) -> CodeGenerator { - return CodeGenerator(name: name, isValueGenerator: true, isRecursive: false, inputs: .none, context: context, adapter: ValueGeneratorAdapter(f: f)) -} + self.providedContexts = Array(ctxSet) + } -public func RecursiveValueGenerator(_ name: String, _ f: @escaping ValueGeneratorFunc) -> CodeGenerator { - return CodeGenerator(name: name, isValueGenerator: true, isRecursive: true, inputs: .none, context: .javascript, adapter: ValueGeneratorAdapter(f: f)) + // This essentially means that all stubs have no requirements. + // Usually there is only a single element in the CodeGenerator if it is a ValueGenerator. + public var isValueGenerator: Bool { + return self.parts.allSatisfy {$0.isValueStub} + } + + // This is the context required by the first part of the CodeGenerator. + public var requiredContext: Context { + // This has to be a single one for the first Generator. + // Current limitation of the Generation Logic. + assert(self.parts.first!.requiredContext.isSingle) + return self.parts.first!.requiredContext.getSingle()! + } + + // TODO(cffsmith): Maybe return an array of ILType Arrays, essentially describing at which yield point which type is available? + // This would allow us to maybe use "innerOutputs" to find suitable points to insert other Generators (that require such types). + // Slight complication is that some variables will only be in scope inside the CodeGenerator, e.g. if there is an EndWasmModule somewhere all Wasm variables will go out of scope. + public var produces: [ILType] { + return self.parts.last!.produces + } + + public var head: GeneratorStub { + return self.parts.first! + } + + // The tail of this CodeGenerator. + public var tail: Array.SubSequence { + return self.parts[1...] + } + + public var expandedName : String { + if self.name == "Synthetic" { + return "Synthetic(\(self.parts.map{$0.name}.joined(separator: ",")))" + } else { + return self.name + } + } + + public convenience init(_ name: String, inContext context: GeneratorStub.ContextRequirement = .single(.javascript), produces: [ILType] = [], provides: [Context] = [], _ f: @escaping GeneratorFuncNoArgs) { + self.init(name, [GeneratorStub(name: name, inputs: .none, produces: produces, context: context, providedContext: provides, adapter: GeneratorAdapterNoArgs(f: f))]) + } + + public convenience init(_ name: String, inContext context: GeneratorStub.ContextRequirement = .single(.javascript), inputs: GeneratorStub.Inputs, produces: [ILType] = [], provides: [Context] = [], _ f: @escaping GeneratorFunc1Arg) { + assert(inputs.count == 1) + self.init(name, [GeneratorStub(name: name, inputs: inputs, produces: produces, context: context, providedContext: provides, adapter: GeneratorAdapter1Arg(f: f))]) + } + + public convenience init(_ name: String, inContext context: GeneratorStub.ContextRequirement = .single(.javascript), inputs: GeneratorStub.Inputs, produces: [ILType] = [], provides: [Context] = [], _ f: @escaping GeneratorFunc2Args) { + assert(inputs.count == 2) + self.init(name, [GeneratorStub(name: name, inputs: inputs, produces: produces, context: context, providedContext: provides, adapter: GeneratorAdapter2Args(f: f))]) + } + + public convenience init(_ name: String, inContext context: GeneratorStub.ContextRequirement = .single(.javascript), inputs: GeneratorStub.Inputs, produces: [ILType] = [], provides: [Context] = [], _ f: @escaping GeneratorFunc3Args) { + assert(inputs.count == 3) + self.init(name, [GeneratorStub(name: name, inputs: inputs, produces: produces, context: context, providedContext: provides, adapter: GeneratorAdapter3Args(f: f))]) + } + + public convenience init(_ name: String, inContext context: GeneratorStub.ContextRequirement = .single(.javascript), inputs: GeneratorStub.Inputs, produces: [ILType] = [], provides: [Context] = [], _ f: @escaping GeneratorFunc4Args) { + assert(inputs.count == 4) + self.init(name, [GeneratorStub(name: name, inputs: inputs, produces: produces, context: context, providedContext: provides, adapter: GeneratorAdapter4Args(f: f))]) + } } + +extension CodeGenerator: CustomStringConvertible { + public var description: String { + let names = self.parts.map { $0.name } + return names.joined(separator: ",") + } +} \ No newline at end of file diff --git a/Sources/Fuzzilli/CodeGen/CodeGeneratorWeights.swift b/Sources/Fuzzilli/CodeGen/CodeGeneratorWeights.swift index 3db5d39a9..79543bb19 100644 --- a/Sources/Fuzzilli/CodeGen/CodeGeneratorWeights.swift +++ b/Sources/Fuzzilli/CodeGen/CodeGeneratorWeights.swift @@ -45,6 +45,7 @@ public let codeGeneratorWeights = [ "BuiltinOverwriteGenerator": 3, "BuiltinObjectPrototypeCallGenerator": 5, "BuiltinTemporalGenerator": 4, + "BuiltinIntlGenerator": 4, "LoadNewTargetGenerator": 3, "DisposableVariableGenerator": 5, "AsyncDisposableVariableGenerator": 5, @@ -63,14 +64,15 @@ public let codeGeneratorWeights = [ "ObjectLiteralComputedMethodGenerator": 3, "ObjectLiteralGetterGenerator": 3, "ObjectLiteralSetterGenerator": 3, - - // The following generators determine how frequently different - // types of fields are generated in class definitions. - "ClassConstructorGenerator": 10, // Will only run if no constructor exists yet +// +// // The following generators determine how frequently different +// // types of fields are generated in class definitions. + "ClassConstructorGenerator": 10, "ClassInstancePropertyGenerator": 5, "ClassInstanceElementGenerator": 5, "ClassInstanceComputedPropertyGenerator": 5, "ClassInstanceMethodGenerator": 10, + "ClassInstanceComputedMethodGenerator": 5, "ClassInstanceGetterGenerator": 3, "ClassInstanceSetterGenerator": 3, "ClassStaticPropertyGenerator": 3, @@ -78,6 +80,7 @@ public let codeGeneratorWeights = [ "ClassStaticComputedPropertyGenerator": 3, "ClassStaticInitializerGenerator": 3, "ClassStaticMethodGenerator": 5, + "ClassStaticComputedMethodGenerator": 3, "ClassStaticGetterGenerator": 2, "ClassStaticSetterGenerator": 2, "ClassPrivateInstancePropertyGenerator": 5, @@ -176,7 +179,9 @@ public let codeGeneratorWeights = [ "SwitchCaseBreakGenerator": 5, "LoopBreakGenerator": 5, "ContinueGenerator": 5, + "TryCatchFinallyGenerator": 5, "TryCatchGenerator": 5, + "TryFinallyGenerator": 5, "ThrowGenerator": 1, "BlockStatementGenerator": 1, @@ -219,7 +224,6 @@ public let codeGeneratorWeights = [ // This weight is important as we need to have a module for the other generators to work. // As they all require .wasm context. "WasmModuleGenerator": 35, - "WasmTypeAndModuleGenerator": 35, "WasmDefineMemoryGenerator": 8, "WasmDefineDataSegmentGenerator": 8, "WasmDropDataSegmentGenerator": 5, @@ -338,7 +342,7 @@ public let codeGeneratorWeights = [ // Wasm-gc type generators // These run in the javascript context and define types to be used within wasm modules. - "WasmRecursiveTypeGroupGenerator": 5, + "WasmTypeGroupGenerator": 5, "WasmArrayTypeGenerator": 5, "WasmStructTypeGenerator": 5, "WasmSelfReferenceGenerator": 5, diff --git a/Sources/Fuzzilli/CodeGen/CodeGenerators.swift b/Sources/Fuzzilli/CodeGen/CodeGenerators.swift index 803029901..aaeb788c8 100644 --- a/Sources/Fuzzilli/CodeGen/CodeGenerators.swift +++ b/Sources/Fuzzilli/CodeGen/CodeGenerators.swift @@ -19,7 +19,7 @@ // public let CodeGenerators: [CodeGenerator] = [ // - // Value Generators: Code Generators that generate one or more new values. + // Value Generators: Code Generators that generate one or more new values, i.e. they have a `produces` annotation. // // These behave like any other CodeGenerator in that they will be randomly chosen to generate code // and have a weight assigned to them to determine how frequently they are selected, but in addition @@ -33,73 +33,65 @@ public let CodeGenerators: [CodeGenerator] = [ // - Should generate |n| different values of the same type, but may generate fewer. // - May be recursive, for example to fill bodies of newly created blocks. // - ValueGenerator("IntegerGenerator") { b, n in - for _ in 0..= maxProperties) - let properties = Array(b.fuzzer.environment.customProperties.shuffled().prefix(Int.random(in: 1...maxProperties))) + let properties = Array( + b.fuzzer.environment.customProperties.shuffled().prefix( + Int.random(in: 1...maxProperties))) // Define a constructor function... let c = b.buildConstructor(with: b.randomParameters()) { args in @@ -298,21 +301,25 @@ public let CodeGenerators: [CodeGenerator] = [ // Add a few random properties to the |this| object. for property in properties { - let value = b.hasVisibleVariables ? b.randomJsVariable() : b.loadInt(b.randomInt()) + let value = + b.hasVisibleVariables + ? b.randomJsVariable() : b.loadInt(b.randomInt()) b.setProperty(property, of: this, to: value) } } assert(b.type(of: c).signature != nil) - assert(b.type(of: c).signature!.outputType.Is(.object(withProperties: properties))) + assert( + b.type(of: c).signature!.outputType.Is( + .object(withProperties: properties))) // and create a few instances with it. - for _ in 0..= 10 { + propertyName = String.random(ofLength: Int.random(in: 1...5)) + break + } propertyName = b.randomCustomPropertyName() attempts += 1 } while b.currentObjectLiteral.properties.contains(propertyName) - b.currentObjectLiteral.addProperty(propertyName, as: b.randomJsVariable()) + b.currentObjectLiteral.addProperty( + propertyName, as: b.randomJsVariable()) }, - CodeGenerator("ObjectLiteralElementGenerator", inContext: .objectLiteral, inputs: .one) { b, value in - assert(b.context.contains(.objectLiteral) && !b.context.contains(.javascript)) + CodeGenerator( + "ObjectLiteralElementGenerator", inContext: .single(.objectLiteral), inputs: .one + ) { b, value in + assert( + b.context.contains(.objectLiteral) + && !b.context.contains(.javascript)) // Select an element that hasn't already been added to this literal. var index = b.randomIndex() @@ -464,14 +508,23 @@ public let CodeGenerators: [CodeGenerator] = [ b.currentObjectLiteral.addElement(index, as: value) }, - CodeGenerator("ObjectLiteralComputedPropertyGenerator", inContext: .objectLiteral, inputs: .one) { b, value in - assert(b.context.contains(.objectLiteral) && !b.context.contains(.javascript)) + CodeGenerator( + "ObjectLiteralComputedPropertyGenerator", inContext: .single(.objectLiteral), + inputs: .one + ) { b, value in + assert( + b.context.contains(.objectLiteral) + && !b.context.contains(.javascript)) // Try to find a computed property that hasn't already been added to this literal. var propertyName: Variable var attempts = 0 repeat { - guard attempts < 10 else { return } + if attempts >= 10 { + // Could not find anything. + // Since this CodeGenerator does not produce anything it is fine to bail. + return + } propertyName = b.randomJsVariable() attempts += 1 } while b.currentObjectLiteral.computedProperties.contains(propertyName) @@ -479,13 +532,21 @@ public let CodeGenerators: [CodeGenerator] = [ b.currentObjectLiteral.addComputedProperty(propertyName, as: value) }, - CodeGenerator("ObjectLiteralCopyPropertiesGenerator", inContext: .objectLiteral, inputs: .preferred(.object())) { b, object in - assert(b.context.contains(.objectLiteral) && !b.context.contains(.javascript)) + CodeGenerator( + "ObjectLiteralCopyPropertiesGenerator", inContext: .single(.objectLiteral), + inputs: .preferred(.object()) + ) { b, object in + assert( + b.context.contains(.objectLiteral) + && !b.context.contains(.javascript)) b.currentObjectLiteral.copyProperties(from: object) }, - CodeGenerator("ObjectLiteralPrototypeGenerator", inContext: .objectLiteral) { b in - assert(b.context.contains(.objectLiteral) && !b.context.contains(.javascript)) + CodeGenerator("ObjectLiteralPrototypeGenerator", inContext: .single(.objectLiteral)) + { b in + assert( + b.context.contains(.objectLiteral) + && !b.context.contains(.javascript)) // There should only be one __proto__ field in an object literal. guard !b.currentObjectLiteral.hasPrototype else { return } @@ -494,101 +555,221 @@ public let CodeGenerators: [CodeGenerator] = [ b.currentObjectLiteral.setPrototype(to: proto) }, - RecursiveCodeGenerator("ObjectLiteralMethodGenerator", inContext: .objectLiteral) { b in - assert(b.context.contains(.objectLiteral) && !b.context.contains(.javascript)) - - // Try to find a method that hasn't already been added to this literal. - var methodName: String - var attempts = 0 - repeat { - guard attempts < 10 else { return } - methodName = b.randomCustomMethodName() - attempts += 1 - } while b.currentObjectLiteral.methods.contains(methodName) - - b.currentObjectLiteral.addMethod(methodName, with: b.randomParameters()) { args in - b.buildRecursive() - b.doReturn(b.randomJsVariable()) - } - }, - - RecursiveCodeGenerator("ObjectLiteralComputedMethodGenerator", inContext: .objectLiteral) { b in - assert(b.context.contains(.objectLiteral) && !b.context.contains(.javascript)) - - // Try to find a computed method name that hasn't already been added to this literal. - var methodName: Variable - var attempts = 0 - repeat { - guard attempts < 10 else { return } - methodName = b.randomJsVariable() - attempts += 1 - } while b.currentObjectLiteral.computedMethods.contains(methodName) - - b.currentObjectLiteral.addComputedMethod(methodName, with: b.randomParameters()) { args in - b.buildRecursive() - b.doReturn(b.randomJsVariable()) - } - }, - - RecursiveCodeGenerator("ObjectLiteralGetterGenerator", inContext: .objectLiteral) { b in - assert(b.context.contains(.objectLiteral) && !b.context.contains(.javascript)) - - // Try to find a property that hasn't already been added and for which a getter has not yet been installed. - var propertyName: String - var attempts = 0 - repeat { - guard attempts < 10 else { return } - propertyName = b.randomCustomPropertyName() - attempts += 1 - } while b.currentObjectLiteral.properties.contains(propertyName) || b.currentObjectLiteral.getters.contains(propertyName) - - b.currentObjectLiteral.addGetter(for: propertyName) { this in - b.buildRecursive() - b.doReturn(b.randomJsVariable()) - } - }, - - RecursiveCodeGenerator("ObjectLiteralSetterGenerator", inContext: .objectLiteral) { b in - assert(b.context.contains(.objectLiteral) && !b.context.contains(.javascript)) - - // Try to find a property that hasn't already been added and for which a setter has not yet been installed. - var propertyName: String - var attempts = 0 - repeat { - guard attempts < 10 else { return } - propertyName = b.randomCustomPropertyName() - attempts += 1 - } while b.currentObjectLiteral.properties.contains(propertyName) || b.currentObjectLiteral.setters.contains(propertyName) - - b.currentObjectLiteral.addSetter(for: propertyName) { this, v in - b.buildRecursive() - } - }, - - RecursiveCodeGenerator("ClassConstructorGenerator", inContext: .classDefinition) { b in - assert(b.context.contains(.classDefinition) && !b.context.contains(.javascript)) - - guard !b.currentClassDefinition.hasConstructor else { - // There must only be one constructor - return - } + CodeGenerator( + "ObjectLiteralMethodGenerator", + [ + GeneratorStub( + "ObjectLiteralMethodBeginGenerator", inContext: .single(.objectLiteral), + provides: [.javascript, .subroutine, .method] + ) { b in + assert( + b.context.contains(.objectLiteral) + && !b.context.contains(.javascript)) + + // Try to find a method that hasn't already been added to this literal. + var methodName: String + var attempts = 0 + repeat { + if attempts >= 10 { + methodName = String.random( + ofLength: Int.random(in: 1...5)) + break + } + methodName = b.randomCustomMethodName() + attempts += 1 + } while b.currentObjectLiteral.methods.contains(methodName) + + let randomParameters = b.randomParameters() + b.setParameterTypesForNextSubroutine( + randomParameters.parameterTypes) + b.emit( + BeginObjectLiteralMethod( + methodName: methodName, + parameters: randomParameters.parameters)) + }, + GeneratorStub("ObjectLiteralMethodEndGenerator", inContext: .single([.javascript, .subroutine, .method])) { b in + b.emit(EndObjectLiteralMethod()) + }, + ]), + + CodeGenerator( + "ObjectLiteralComputedMethodGenerator", + [ + GeneratorStub( + "ObjectLiteralComputedMethodBeginGenerator", + inContext: .single(.objectLiteral), + provides: [.javascript, .subroutine, .method] + ) { b in + assert( + b.context.contains(.objectLiteral) + && !b.context.contains(.javascript)) + + // Try to find a computed method name that hasn't already been added to this literal. + var methodName: Variable + var attempts = 0 + repeat { + if attempts >= 10 { + methodName = b.loadString( + String.random(ofLength: Int.random(in: 1...5))) + break + } + methodName = b.randomJsVariable() + attempts += 1 + } while b.currentObjectLiteral.computedMethods.contains( + methodName) + let parameters = b.randomParameters() + b.setParameterTypesForNextSubroutine(parameters.parameterTypes) + b.emit( + BeginObjectLiteralComputedMethod( + parameters: parameters.parameters), + withInputs: [methodName]) + }, + GeneratorStub( + "ObjectLiteralComputedMethodEndGenerator", + inContext: .single([.javascript, .subroutine, .method]), + inputs: .one + ) { b, inp in + b.doReturn(inp) + b.emit(EndObjectLiteralComputedMethod()) + + }, + ]), + + CodeGenerator( + "ObjectLiteralGetterGenerator", + [ + GeneratorStub( + "ObjectLiteralGetterBeginGenerator", + inContext: .single(.objectLiteral), + provides: [.javascript, .subroutine, .method] + ) { b in + assert( + b.context.contains(.objectLiteral) + && !b.context.contains(.javascript)) + + // Try to find a property that hasn't already been added and for which a getter has not yet been installed. + var propertyName: String + var attempts = 0 + repeat { + if attempts >= 10 { + // We should not fail here but also don't produce a syntax error. + propertyName = String.random( + ofLength: Int.random(in: 1...5)) + break + } + propertyName = b.randomCustomPropertyName() + attempts += 1 + } while b.currentObjectLiteral.properties.contains(propertyName) + || b.currentObjectLiteral.getters.contains(propertyName) + + b.emit(BeginObjectLiteralGetter(propertyName: propertyName)) + + }, + GeneratorStub( + "ObjectLiteralGetterEndGenerator", + inContext: .single([.javascript, .subroutine, .method]), + inputs: .one + ) { b, inp in + b.doReturn(inp) + b.emit(EndObjectLiteralGetter()) + }, + ]), + + CodeGenerator( + "ObjectLiteralSetterGenerator", + [ + GeneratorStub( + "ObjectLiteralSetterBeginGenerator", + inContext: .single(.objectLiteral), + provides: [.javascript, .subroutine, .method] + ) { b in + assert( + b.context.contains(.objectLiteral) + && !b.context.contains(.javascript)) + + // Try to find a property that hasn't already been added and for which a setter has not yet been installed. + var propertyName: String + var attempts = 0 + repeat { + if attempts >= 10 { + // We should not fail here but also don't produce a syntax error. + propertyName = String.random( + ofLength: Int.random(in: 1...5)) + break + } + propertyName = b.randomCustomPropertyName() + attempts += 1 + } while b.currentObjectLiteral.properties.contains(propertyName) + || b.currentObjectLiteral.setters.contains(propertyName) + + b.emit(BeginObjectLiteralSetter(propertyName: propertyName)) + }, + GeneratorStub( + "ObjectLiteralSetterEndGenerator", + inContext: .single([.javascript, .subroutine, .method]) + ) { b in + b.emit(EndObjectLiteralSetter()) + }, + ]), + + CodeGenerator( + "ClassConstructorGenerator", + [ + GeneratorStub( + "ClassConstructorBeginGenerator", + inContext: .single(.classDefinition), + provides: [] + ) { b in + assert( + b.context.contains(.classDefinition) + && !b.context.contains(.javascript)) + + guard !b.currentClassDefinition.hasConstructor else { + // There must only be one constructor + // If we bail here, we are *not* in .javaScript context so we cannot provide it here for other chains. + return + } - b.currentClassDefinition.addConstructor(with: b.randomParameters()) { args in - let this = args[0] - // Derived classes must call `super()` before accessing this, but non-derived classes must not call `super()`. - if b.currentClassDefinition.isDerivedClass { - b.hide(this) // We need to hide |this| so it isn't used as argument for `super()` - let signature = b.currentSuperConstructorType().signature ?? Signature.forUnknownFunction - let args = b.randomArguments(forCallingFunctionWithSignature: signature) - b.callSuperConstructor(withArgs: args) - b.unhide(this) - } - b.buildRecursive() - } - }, + let randomParameters = b.randomParameters() + + b.setParameterTypesForNextSubroutine( + randomParameters.parameterTypes) + + let args = b.emit( + BeginClassConstructor( + parameters: randomParameters.parameters) + ).innerOutputs + + let this = args[0] + // Derived classes must call `super()` before accessing this, but non-derived classes must not call `super()`. + if b.currentClassDefinition.isDerivedClass { + b.hide(this) // We need to hide |this| so it isn't used as argument for `super()` + let signature = + b.currentSuperConstructorType().signature + ?? Signature.forUnknownFunction + let args = b.randomArguments( + forCallingFunctionWithSignature: signature) + b.callSuperConstructor(withArgs: args) + b.unhide(this) + } + }, + GeneratorStub( + "ClassConstructorEndGenerator", + // This can run in either context and will do different things + // depending on if the previous generator succeeded. + inContext: .either([.javascript, .classDefinition]) + ) { b in + if b.context.contains(.javascript) { + b.emit(EndClassConstructor()) + } + }, + ]), - CodeGenerator("ClassInstancePropertyGenerator", inContext: .classDefinition) { b in - assert(b.context.contains(.classDefinition) && !b.context.contains(.javascript)) + CodeGenerator("ClassInstancePropertyGenerator", inContext: .single(.classDefinition)) + { b in + assert( + b.context.contains(.classDefinition) + && !b.context.contains(.javascript)) // Try to find a property that hasn't already been added to this literal. var propertyName: String @@ -597,14 +778,18 @@ public let CodeGenerators: [CodeGenerator] = [ guard attempts < 10 else { return } propertyName = b.randomCustomPropertyName() attempts += 1 - } while b.currentClassDefinition.instanceProperties.contains(propertyName) + } while b.currentClassDefinition.instanceProperties.contains( + propertyName) var value: Variable? = probability(0.5) ? b.randomJsVariable() : nil b.currentClassDefinition.addInstanceProperty(propertyName, value: value) }, - CodeGenerator("ClassInstanceElementGenerator", inContext: .classDefinition) { b in - assert(b.context.contains(.classDefinition) && !b.context.contains(.javascript)) + CodeGenerator("ClassInstanceElementGenerator", inContext: .single(.classDefinition)) + { b in + assert( + b.context.contains(.classDefinition) + && !b.context.contains(.javascript)) // Select an element that hasn't already been added to this literal. var index = b.randomIndex() @@ -617,8 +802,12 @@ public let CodeGenerators: [CodeGenerator] = [ b.currentClassDefinition.addInstanceElement(index, value: value) }, - CodeGenerator("ClassInstanceComputedPropertyGenerator", inContext: .classDefinition) { b in - assert(b.context.contains(.classDefinition) && !b.context.contains(.javascript)) + CodeGenerator( + "ClassInstanceComputedPropertyGenerator", inContext: .single(.classDefinition) + ) { b in + assert( + b.context.contains(.classDefinition) + && !b.context.contains(.javascript)) // Try to find a computed property that hasn't already been added to this literal. var propertyName: Variable @@ -627,73 +816,186 @@ public let CodeGenerators: [CodeGenerator] = [ guard attempts < 10 else { return } propertyName = b.randomJsVariable() attempts += 1 - } while b.currentClassDefinition.instanceComputedProperties.contains(propertyName) + } while b.currentClassDefinition.instanceComputedProperties.contains( + propertyName) let value = probability(0.5) ? b.randomJsVariable() : nil - b.currentClassDefinition.addInstanceComputedProperty(propertyName, value: value) - }, - - RecursiveCodeGenerator("ClassInstanceMethodGenerator", inContext: .classDefinition) { b in - assert(b.context.contains(.classDefinition) && !b.context.contains(.javascript)) - - // Try to find a method that hasn't already been added to this class. - var methodName: String - var attempts = 0 - repeat { - guard attempts < 10 else { return } - methodName = b.randomCustomMethodName() - attempts += 1 - } while b.currentClassDefinition.instanceMethods.contains(methodName) - - b.currentClassDefinition.addInstanceMethod(methodName, with: b.randomParameters()) { args in - b.buildRecursive() - b.doReturn(b.randomJsVariable()) - } - }, - - RecursiveCodeGenerator("ClassInstanceGetterGenerator", inContext: .classDefinition) { b in - assert(b.context.contains(.classDefinition) && !b.context.contains(.javascript)) - - // Try to find a property that hasn't already been added and for which a getter has not yet been installed. - var propertyName: String - var attempts = 0 - repeat { - guard attempts < 10 else { return } - propertyName = b.randomCustomPropertyName() - attempts += 1 - } while b.currentClassDefinition.instanceProperties.contains(propertyName) || b.currentClassDefinition.instanceGetters.contains(propertyName) - - b.currentClassDefinition.addInstanceGetter(for: propertyName) { this in - b.buildRecursive() - b.doReturn(b.randomJsVariable()) - } - }, - - RecursiveCodeGenerator("ClassInstanceSetterGenerator", inContext: .classDefinition) { b in - assert(b.context.contains(.classDefinition) && !b.context.contains(.javascript)) - - // Try to find a property that hasn't already been added and for which a setter has not yet been installed. - var propertyName: String - var attempts = 0 - repeat { - guard attempts < 10 else { return } - propertyName = b.randomCustomPropertyName() - attempts += 1 - } while b.currentClassDefinition.instanceProperties.contains(propertyName) || b.currentClassDefinition.instanceSetters.contains(propertyName) - - b.currentClassDefinition.addInstanceSetter(for: propertyName) { this, v in - b.buildRecursive() - } - }, - - CodeGenerator("ClassStaticPropertyGenerator", inContext: .classDefinition) { b in - assert(b.context.contains(.classDefinition) && !b.context.contains(.javascript)) + b.currentClassDefinition.addInstanceComputedProperty( + propertyName, value: value) + }, + + CodeGenerator( + "ClassInstanceMethodGenerator", + [ + GeneratorStub( + "ClassInstanceMethodBeginGenerator", + inContext: .single(.classDefinition), + provides: [.javascript, .subroutine, .method, .classMethod] + ) { b in + assert( + b.context.contains(.classDefinition) + && !b.context.contains(.javascript)) + + // Try to find a method that hasn't already been added to this class. + var methodName: String + var attempts = 0 + repeat { + if attempts >= 10 { + methodName = String.random( + ofLength: Int.random(in: 1...5)) + break + } + methodName = b.randomCustomMethodName() + attempts += 1 + } while b.currentClassDefinition.instanceMethods.contains( + methodName) + + let parameters = b.randomParameters() + b.setParameterTypesForNextSubroutine(parameters.parameterTypes) + b.emit( + BeginClassInstanceMethod( + methodName: methodName, + parameters: parameters.parameters)) + }, + GeneratorStub( + "ClassInstanceMethodEndGenerator", + inContext: .single([.javascript, .subroutine, .method, .classMethod]) + ) { b in + b.doReturn(b.randomJsVariable()) + b.emit(EndClassInstanceMethod()) + }, + ]), + + CodeGenerator( + "ClassInstanceComputedMethodGenerator", + [ + GeneratorStub( + "ClassInstanceComputedMethodBeginGenerator", + inContext: .single(.classDefinition), + provides: [.javascript, .subroutine, .method, .classMethod] + ) { b in + assert( + b.context.contains(.classDefinition) + && !b.context.contains(.javascript)) + + // Try to find a method that hasn't already been added to this class. + var methodName = b.randomJsVariable() + var attempts = 0 + repeat { + guard attempts < 10 else { break } + methodName = b.randomJsVariable() + attempts += 1 + } while b.currentClassDefinition.instanceComputedMethods.contains( + methodName) + + let parameters = b.randomParameters() + b.setParameterTypesForNextSubroutine(parameters.parameterTypes) + b.emit( + BeginClassInstanceComputedMethod( + parameters: parameters.parameters), + withInputs: [methodName]) + }, + GeneratorStub( + "ClassInstanceComputedMethodEndGenerator", + inContext: .single([.javascript, .subroutine, .method, .classMethod]) + ) { b in + b.doReturn(b.randomJsVariable()) + b.emit(EndClassInstanceComputedMethod()) + }, + ]), + + CodeGenerator( + "ClassInstanceGetterGenerator", + [ + GeneratorStub( + "ClassInstanceGetterBeginGenerator", + inContext: .single(.classDefinition), + provides: [.javascript, .subroutine, .method, .classMethod] + ) { b in + assert( + b.context.contains(.classDefinition) + && !b.context.contains(.javascript)) + + // Try to find a property that hasn't already been added and for which a getter has not yet been installed. + var propertyName: String + var attempts = 0 + repeat { + if attempts >= 10 { + propertyName = String.random( + ofLength: Int.random(in: 1...5)) + break + } + propertyName = b.randomCustomPropertyName() + attempts += 1 + } while b.currentClassDefinition.instanceProperties.contains( + propertyName) + || b.currentClassDefinition.instanceGetters.contains( + propertyName) + + b.emit(BeginClassInstanceGetter(propertyName: propertyName)) + }, + GeneratorStub( + "ClassInstanceGetterEndGenerator", inContext: .single([.javascript, .subroutine, .method, .classMethod]) + ) { b in + b.doReturn(b.randomJsVariable()) + b.emit(EndClassInstanceGetter()) + }, + ]), + + CodeGenerator( + "ClassInstanceSetterGenerator", + [ + GeneratorStub( + "ClassInstanceSetterBeginGenerator", + inContext: .single(.classDefinition), + provides: [.javascript, .subroutine, .method, .classMethod] + ) { b in + assert( + b.context.contains(.classDefinition) + && !b.context.contains(.javascript)) + + // Try to find a property that hasn't already been added and for which a setter has not yet been installed. + var propertyName: String + var attempts = 0 + repeat { + if attempts >= 10 { + propertyName = String.random( + ofLength: Int.random(in: 1...5)) + break + } + propertyName = b.randomCustomPropertyName() + attempts += 1 + } while b.currentClassDefinition.instanceProperties.contains( + propertyName) + || b.currentClassDefinition.instanceSetters.contains( + propertyName) + + b.emit(BeginClassInstanceSetter(propertyName: propertyName)) + }, + GeneratorStub( + "ClassInstanceSetterEndGenerator", + inContext: .single([.javascript, .method, .subroutine, .classMethod]) + ) { b in + b.emit(EndClassInstanceSetter()) + }, + + ]), + + CodeGenerator("ClassStaticPropertyGenerator", inContext: .single(.classDefinition)) { + b in + assert( + b.context.contains(.classDefinition) + && !b.context.contains(.javascript)) // Try to find a property that hasn't already been added to this literal. var propertyName: String var attempts = 0 repeat { - guard attempts < 10 else { return } + if attempts >= 10 { + propertyName = String.random( + ofLength: Int.random(in: 1...5)) + break + } propertyName = b.randomCustomPropertyName() attempts += 1 } while b.currentClassDefinition.staticProperties.contains(propertyName) @@ -702,8 +1004,11 @@ public let CodeGenerators: [CodeGenerator] = [ b.currentClassDefinition.addStaticProperty(propertyName, value: value) }, - CodeGenerator("ClassStaticElementGenerator", inContext: .classDefinition) { b in - assert(b.context.contains(.classDefinition) && !b.context.contains(.javascript)) + CodeGenerator("ClassStaticElementGenerator", inContext: .single(.classDefinition)) { + b in + assert( + b.context.contains(.classDefinition) + && !b.context.contains(.javascript)) // Select an element that hasn't already been added to this literal. var index = b.randomIndex() @@ -716,85 +1021,218 @@ public let CodeGenerators: [CodeGenerator] = [ b.currentClassDefinition.addStaticElement(index, value: value) }, - CodeGenerator("ClassStaticComputedPropertyGenerator", inContext: .classDefinition) { b in - assert(b.context.contains(.classDefinition) && !b.context.contains(.javascript)) + CodeGenerator( + "ClassStaticComputedPropertyGenerator", inContext: .single(.classDefinition) + ) { b in + assert( + b.context.contains(.classDefinition) + && !b.context.contains(.javascript)) // Try to find a computed property that hasn't already been added to this literal. var propertyName: Variable var attempts = 0 repeat { - guard attempts < 10 else { return } + guard attempts < 10 else { + // We are in .classDefinition context here and cannot create new JavaScript variables, so just bail here. + return + } propertyName = b.randomJsVariable() attempts += 1 - } while b.currentClassDefinition.staticComputedProperties.contains(propertyName) + } while b.currentClassDefinition.staticComputedProperties.contains( + propertyName) let value = probability(0.5) ? b.randomJsVariable() : nil - b.currentClassDefinition.addStaticComputedProperty(propertyName, value: value) - }, - - RecursiveCodeGenerator("ClassStaticInitializerGenerator", inContext: .classDefinition) { b in - assert(b.context.contains(.classDefinition) && !b.context.contains(.javascript)) - - b.currentClassDefinition.addStaticInitializer { this in - b.buildRecursive() - } - }, - - RecursiveCodeGenerator("ClassStaticMethodGenerator", inContext: .classDefinition) { b in - assert(b.context.contains(.classDefinition) && !b.context.contains(.javascript)) - - // Try to find a method that hasn't already been added to this class. - var methodName: String - var attempts = 0 - repeat { - guard attempts < 10 else { return } - methodName = b.randomCustomMethodName() - attempts += 1 - } while b.currentClassDefinition.staticMethods.contains(methodName) - - b.currentClassDefinition.addStaticMethod(methodName, with: b.randomParameters()) { args in - b.buildRecursive() - b.doReturn(b.randomJsVariable()) - } - }, - - RecursiveCodeGenerator("ClassStaticGetterGenerator", inContext: .classDefinition) { b in - assert(b.context.contains(.classDefinition) && !b.context.contains(.javascript)) - - // Try to find a property that hasn't already been added and for which a getter has not yet been installed. - var propertyName: String - var attempts = 0 - repeat { - guard attempts < 10 else { return } - propertyName = b.randomCustomPropertyName() - attempts += 1 - } while b.currentClassDefinition.staticProperties.contains(propertyName) || b.currentClassDefinition.staticGetters.contains(propertyName) - - b.currentClassDefinition.addStaticGetter(for: propertyName) { this in - b.buildRecursive() - b.doReturn(b.randomJsVariable()) - } - }, - - RecursiveCodeGenerator("ClassStaticSetterGenerator", inContext: .classDefinition) { b in - assert(b.context.contains(.classDefinition) && !b.context.contains(.javascript)) - - // Try to find a property that hasn't already been added and for which a setter has not yet been installed. - var propertyName: String - var attempts = 0 - repeat { - guard attempts < 10 else { return } - propertyName = b.randomCustomPropertyName() - attempts += 1 - } while b.currentClassDefinition.staticProperties.contains(propertyName) || b.currentClassDefinition.staticSetters.contains(propertyName) - - b.currentClassDefinition.addStaticSetter(for: propertyName) { this, v in - b.buildRecursive() - } - }, - - CodeGenerator("ClassPrivateInstancePropertyGenerator", inContext: .classDefinition) { b in - assert(b.context.contains(.classDefinition) && !b.context.contains(.javascript)) + b.currentClassDefinition.addStaticComputedProperty( + propertyName, value: value) + }, + + CodeGenerator( + "ClassStaticInitializerGenerator", + [ + GeneratorStub( + "ClassStaticInitializerBeginGenerator", + inContext: .single(.classDefinition), + provides: [.javascript, .method, .classMethod] + ) { b in + assert( + b.context.contains(.classDefinition) + && !b.context.contains(.javascript)) + + b.emit(BeginClassStaticInitializer()) + }, + GeneratorStub( + "ClassStaticInitializerEndGenerator", + inContext: .single([.javascript, .method, .classMethod]) + ) { b in + b.emit(EndClassStaticInitializer()) + }, + ]), + + CodeGenerator( + "ClassStaticMethodGenerator", + [ + GeneratorStub( + "ClassStaticMethodBeginGenerator", + inContext: .single(.classDefinition), + provides: [.javascript, .method, .subroutine, .classMethod] + ) { b in + assert( + b.context.contains(.classDefinition) + && !b.context.contains(.javascript)) + + // Try to find a method that hasn't already been added to this class. + var methodName: String + var attempts = 0 + repeat { + if attempts >= 10 { + methodName = String.random( + ofLength: Int.random(in: 1...5)) + break + } + methodName = b.randomCustomMethodName() + attempts += 1 + } while b.currentClassDefinition.staticMethods.contains( + methodName) + + let parameters = b.randomParameters() + + b.setParameterTypesForNextSubroutine(parameters.parameterTypes) + b.emit( + BeginClassStaticMethod( + methodName: methodName, + parameters: parameters.parameters)) + + }, + GeneratorStub( + "ClassStaticMethodEndGenerator", + inContext: .single([.javascript, .classMethod, .subroutine, .method]) + ) { b in + b.doReturn(b.randomJsVariable()) + b.emit(EndClassStaticMethod()) + }, + ]), + + CodeGenerator( + "ClassStaticComputedMethodGenerator", + [ + GeneratorStub( + "ClassStaticComputedMethodBeginGenerator", + inContext: .single(.classDefinition), + provides: [.javascript, .subroutine, .method, .classMethod] + ) { b in + assert( + b.context.contains(.classDefinition) + && !b.context.contains(.javascript)) + + // Try to find a method that hasn't already been added to this class. + var methodName = b.randomJsVariable() + var attempts = 0 + repeat { + guard attempts < 10 else { break } + methodName = b.randomJsVariable() + attempts += 1 + } while b.currentClassDefinition.staticComputedMethods.contains( + methodName) + + let parameters = b.randomParameters() + b.setParameterTypesForNextSubroutine(parameters.parameterTypes) + b.emit( + BeginClassStaticComputedMethod( + parameters: parameters.parameters), + withInputs: [methodName]) + }, + GeneratorStub( + "ClassStaticComputedMethodEndGenerator", + inContext: .single([.javascript, .subroutine, .method, .classMethod]) + ) { b in + b.doReturn(b.randomJsVariable()) + b.emit(EndClassStaticComputedMethod()) + }, + ]), + + CodeGenerator( + "ClassStaticGetterGenerator", + [ + GeneratorStub( + "ClassStaticGetterBeginGenerator", + inContext: .single(.classDefinition), + provides: [.javascript, .subroutine, .method, .classMethod] + ) { b in + assert( + b.context.contains(.classDefinition) + && !b.context.contains(.javascript)) + + // Try to find a property that hasn't already been added and for which a getter has not yet been installed. + var propertyName: String + var attempts = 0 + repeat { + if attempts >= 10 { + propertyName = String.random( + ofLength: Int.random(in: 1...5)) + break + } + propertyName = b.randomCustomPropertyName() + attempts += 1 + } while b.currentClassDefinition.staticProperties.contains( + propertyName) + || b.currentClassDefinition.staticGetters.contains( + propertyName) + + b.emit(BeginClassStaticGetter(propertyName: propertyName)) + }, + GeneratorStub( + "ClassStaticGetterEndGenerator", + inContext: .single([.javascript, .subroutine, .method, .classMethod]) + ) { b in + b.doReturn(b.randomJsVariable()) + b.emit(EndClassStaticGetter()) + }, + ]), + + CodeGenerator( + "ClassStaticSetterGenerator", + [ + GeneratorStub( + "ClassStaticSetterBeginGenerator", + inContext: .single(.classDefinition), + provides: [.javascript, .subroutine, .method, .classMethod] + ) { b in + assert( + b.context.contains(.classDefinition) + && !b.context.contains(.javascript)) + + // Try to find a property that hasn't already been added and for which a setter has not yet been installed. + var propertyName: String + var attempts = 0 + repeat { + if attempts >= 10 { + propertyName = String.random(ofLength: Int.random(in: 1...5)) + break + } + propertyName = b.randomCustomPropertyName() + attempts += 1 + } while b.currentClassDefinition.staticProperties.contains( + propertyName) + || b.currentClassDefinition.staticSetters.contains( + propertyName) + + b.emit(BeginClassStaticSetter(propertyName: propertyName)) + + }, + GeneratorStub( + "ClassStaticSetterEndGenerator", + inContext: .single([.javascript, .subroutine, .method, .classMethod]) + ) { b in + b.emit(EndClassStaticSetter()) + }, + ]), + + CodeGenerator( + "ClassPrivateInstancePropertyGenerator", inContext: .single(.classDefinition) + ) { b in + assert( + b.context.contains(.classDefinition) + && !b.context.contains(.javascript)) // Try to find a private field that hasn't already been added to this literal. var propertyName: String @@ -806,29 +1244,56 @@ public let CodeGenerators: [CodeGenerator] = [ } while b.currentClassDefinition.privateFields.contains(propertyName) var value = probability(0.5) ? b.randomJsVariable() : nil - b.currentClassDefinition.addPrivateInstanceProperty(propertyName, value: value) - }, - - RecursiveCodeGenerator("ClassPrivateInstanceMethodGenerator", inContext: .classDefinition) { b in - assert(b.context.contains(.classDefinition) && !b.context.contains(.javascript)) - - // Try to find a private field that hasn't already been added to this class. - var methodName: String - var attempts = 0 - repeat { - guard attempts < 10 else { return } - methodName = b.randomCustomMethodName() - attempts += 1 - } while b.currentClassDefinition.privateFields.contains(methodName) - - b.currentClassDefinition.addPrivateInstanceMethod(methodName, with: b.randomParameters()) { args in - b.buildRecursive() - b.doReturn(b.randomJsVariable()) - } - }, + b.currentClassDefinition.addPrivateInstanceProperty( + propertyName, value: value) + }, + + CodeGenerator( + "ClassPrivateInstanceMethodGenerator", + [ + GeneratorStub( + "ClassPrivateInstanceMethodBeginGenerator", + inContext: .single(.classDefinition), + provides: [.javascript, .subroutine, .method, .classMethod] + ) { b in + assert( + b.context.contains(.classDefinition) + && !b.context.contains(.javascript)) + + // Try to find a private field that hasn't already been added to this class. + var methodName: String + var attempts = 0 + repeat { + if attempts >= 10 { + methodName = String.random(ofLength: Int.random(in: 1...5)) + break + } + methodName = b.randomCustomMethodName() + attempts += 1 + } while b.currentClassDefinition.privateFields.contains( + methodName) + + let parameters = b.randomParameters() + b.emit( + BeginClassPrivateInstanceMethod( + methodName: methodName, + parameters: parameters.parameters)) + }, + GeneratorStub( + "ClassPrivateInstanceMethodEndGenerator", + inContext: .single([.javascript, .subroutine, .method, .classMethod]) + ) { b in + b.doReturn(b.randomJsVariable()) + b.emit(EndClassPrivateInstanceMethod()) + }, + ]), - CodeGenerator("ClassPrivateStaticPropertyGenerator", inContext: .classDefinition) { b in - assert(b.context.contains(.classDefinition) && !b.context.contains(.javascript)) + CodeGenerator( + "ClassPrivateStaticPropertyGenerator", inContext: .single(.classDefinition) + ) { b in + assert( + b.context.contains(.classDefinition) + && !b.context.contains(.javascript)) // Try to find a private field that hasn't already been added to this literal. var propertyName: String @@ -840,28 +1305,58 @@ public let CodeGenerators: [CodeGenerator] = [ } while b.currentClassDefinition.privateFields.contains(propertyName) var value = probability(0.5) ? b.randomJsVariable() : nil - b.currentClassDefinition.addPrivateStaticProperty(propertyName, value: value) - }, - - RecursiveCodeGenerator("ClassPrivateStaticMethodGenerator", inContext: .classDefinition) { b in - assert(b.context.contains(.classDefinition) && !b.context.contains(.javascript)) - - // Try to find a private field that hasn't already been added to this class. - var methodName: String - var attempts = 0 - repeat { - guard attempts < 10 else { return } - methodName = b.randomCustomMethodName() - attempts += 1 - } while b.currentClassDefinition.privateFields.contains(methodName) + b.currentClassDefinition.addPrivateStaticProperty( + propertyName, value: value) + }, + + CodeGenerator( + "ClassPrivateStaticMethodGenerator", + [ + GeneratorStub( + "ClassPrivateStaticMethodBeginGenerator", + inContext: .single(.classDefinition), + provides: [.javascript, .subroutine, .method, .classMethod] + ) { b in + assert( + b.context.contains(.classDefinition) + && !b.context.contains(.javascript)) + + // Try to find a private field that hasn't already been added to this class. + var methodName: String + var attempts = 0 + repeat { + if attempts >= 10 { + methodName = String.random( + ofLength: Int.random(in: 1...5)) + break + } + methodName = b.randomCustomMethodName() + attempts += 1 + } while b.currentClassDefinition.privateFields.contains( + methodName) + + let parameters = b.randomParameters() + b.emit( + BeginClassPrivateStaticMethod( + methodName: methodName, + parameters: parameters.parameters)) + }, + GeneratorStub( + "ClassPrivateStaticMethodEndGenerator", + inContext: .single([.javascript, .subroutine, .method, .classMethod]) + ) { b in + b.doReturn(b.randomJsVariable()) + b.emit(EndClassPrivateStaticMethod()) + }, - b.currentClassDefinition.addPrivateStaticMethod(methodName, with: b.randomParameters()) { args in - b.buildRecursive() - b.doReturn(b.randomJsVariable()) - } - }, + ]), - CodeGenerator("ArrayWithSpreadGenerator") { b in + // Setting the input to one, makes this *not* a value generator, as we use b.randomJsVariable inside. + CodeGenerator( + "ArrayWithSpreadGenerator", + inputs: .one, + produces: [.jsArray] + ) { b, _ in var initialValues = [Variable]() for _ in 0..() for _ in 0..() for _ in 0..= 4) propertyNames.shuffle() @@ -1995,18 +3080,20 @@ public let CodeGenerators: [CodeGenerator] = [ } }, - CodeGenerator("IteratorGenerator") { b in + CodeGenerator("IteratorGenerator", produces: [.iterable]) { b in let Symbol = b.createNamedVariable(forBuiltin: "Symbol") b.hide(Symbol) let iteratorSymbol = b.getProperty("iterator", of: Symbol) b.hide(iteratorSymbol) let iterableObject = b.buildObjectLiteral { obj in - obj.addComputedMethod(iteratorSymbol, with: .parameters(n: 0)) { _ in + obj.addComputedMethod(iteratorSymbol, with: .parameters(n: 0)) { + _ in let counter = b.loadInt(10) let iterator = b.buildObjectLiteral { obj in obj.addMethod("next", with: .parameters(n: 0)) { _ in b.unary(.PostDec, counter) - let done = b.compare(counter, with: b.loadInt(0), using: .equal) + let done = b.compare( + counter, with: b.loadInt(0), using: .equal) let result = b.buildObjectLiteral { obj in obj.addProperty("done", as: done) obj.addProperty("value", as: counter) @@ -2022,45 +3109,53 @@ public let CodeGenerators: [CodeGenerator] = [ b.setType(ofVariable: iterableObject, to: .iterable + .object()) }, - CodeGenerator("LoadNewTargetGenerator", inContext: .subroutine) { b in + CodeGenerator("LoadNewTargetGenerator", inContext: .single(.subroutine)) { b in assert(b.context.contains(.subroutine)) b.loadNewTarget() }, // TODO: think about merging this with the regular ConstructorCallGenerator. - CodeGenerator("ApiConstructorCallGenerator", inputs: .required(.constructor())) { b, c in + CodeGenerator( + "ApiConstructorCallGenerator", inputs: .required(.constructor()) + ) { b, c in let signature = b.type(of: c).signature ?? Signature.forUnknownFunction - b.buildTryCatchFinally(tryBody: { - let args = b.findOrGenerateArguments(forSignature: signature) - b.construct(c, withArgs: args) - }, catchBody: { _ in }) + b.buildTryCatchFinally( + tryBody: { + let args = b.findOrGenerateArguments(forSignature: signature) + b.construct(c, withArgs: args) + }, catchBody: { _ in }) }, // TODO: think about merging this with the regular MethodCallGenerator. - CodeGenerator("ApiMethodCallGenerator", inputs: .required(.object())) { b, o in + CodeGenerator("ApiMethodCallGenerator", inputs: .required(.object())) { + b, o in let methodName = b.type(of: o).randomMethod() ?? b.randomMethodName() - let signature = chooseUniform(from: b.methodSignatures(of: methodName, on: o)) + let signature = chooseUniform( + from: b.methodSignatures(of: methodName, on: o)) - b.buildTryCatchFinally(tryBody: { - let args = b.findOrGenerateArguments(forSignature: signature) - b.callMethod(methodName, on: o, withArgs: args) - }, catchBody: { _ in }) + b.buildTryCatchFinally( + tryBody: { + let args = b.findOrGenerateArguments(forSignature: signature) + b.callMethod(methodName, on: o, withArgs: args) + }, catchBody: { _ in }) }, - CodeGenerator("ApiFunctionCallGenerator", inputs: .required(.function())) { b, f in + CodeGenerator("ApiFunctionCallGenerator", inputs: .required(.function())) { + b, f in let signature = b.type(of: f).signature ?? Signature.forUnknownFunction - b.buildTryCatchFinally(tryBody: { - let args = b.findOrGenerateArguments(forSignature: signature) - b.callFunction(f, withArgs: args) - }, catchBody: { _ in }) + b.buildTryCatchFinally( + tryBody: { + let args = b.findOrGenerateArguments(forSignature: signature) + b.callFunction(f, withArgs: args) + }, catchBody: { _ in }) }, ] -extension Array where Element == CodeGenerator { - public func get(_ name: String) -> CodeGenerator { +extension Array where Element == GeneratorStub { + public func get(_ name: String) -> GeneratorStub { for generator in self { if generator.name == name { return generator diff --git a/Sources/Fuzzilli/CodeGen/WasmCodeGenerators.swift b/Sources/Fuzzilli/CodeGen/WasmCodeGenerators.swift index 14b2a1a2b..f1816e9a8 100644 --- a/Sources/Fuzzilli/CodeGen/WasmCodeGenerators.swift +++ b/Sources/Fuzzilli/CodeGen/WasmCodeGenerators.swift @@ -22,11 +22,19 @@ public let WasmCodeGenerators: [CodeGenerator] = [ /// Wasm related generators in JavaScript - CodeGenerator("WasmGlobalGenerator", inContext: .javascript) { b in - b.createWasmGlobal(value: b.randomWasmGlobal(), isMutable: probability(0.5)) - }, - - CodeGenerator("WasmMemoryGenerator", inContext: .javascript) { b in + CodeGenerator( + "WasmGlobalGenerator", + inContext: .single(.javascript), + produces: [.object(ofGroup: "WasmGlobal")] + ) { b in + b.createWasmGlobal(value: b.randomWasmGlobal(forContext: .javascript), isMutable: probability(0.5)) + }, + + CodeGenerator( + "WasmMemoryGenerator", + inContext: .single(.javascript), + produces: [.object(ofGroup: "WasmMemory")] + ) { b in let minPages = Int.random(in: 0..<10) let isShared = probability(0.5) var maxPages: Int? = nil @@ -36,121 +44,128 @@ public let WasmCodeGenerators: [CodeGenerator] = [ b.createWasmMemory(minPages: minPages, maxPages: maxPages, isShared: isShared) }, - CodeGenerator("WasmTagGenerator", inContext: .javascript) { b in + CodeGenerator( + "WasmTagGenerator", + inContext: .single(.javascript), + produces: [.object(ofGroup: "WasmTag")] + ) { b in if probability(0.5) { b.createWasmJSTag() } else { b.createWasmTag(parameterTypes: b.randomTagParameters()) } }, - + // // Wasm Module Generator, this is fairly important as it creates the context necessary to run the Wasm CodeGenerators. - RecursiveCodeGenerator("WasmModuleGenerator", inContext: .javascript) { b in - let m = b.buildWasmModule { m in - b.buildRecursive() - } - - let exports = m.loadExports() - }, - - RecursiveCodeGenerator("WasmTypeAndModuleGenerator", inContext: .javascript) { b in - // TODO(cffsmith): We might want to lower this once we can CodeGen into a TypeGroup. - let numRandomTypes = Int.random(in: 0...5) - - for i in 1..<(numRandomTypes + 1) { - b.wasmDefineTypeGroup { - b.buildRecursive(block: i, of: numRandomTypes + 1) - } - } - - let m = b.buildWasmModule { m in - b.buildRecursive(block: numRandomTypes + 1, of: numRandomTypes + 1) - } - - let exports = m.loadExports() - }, - - RecursiveCodeGenerator("WasmLegacyTryCatchComplexGenerator", inContext: .javascript) { b in - let emitCatchAll = Int.random(in: 0...1) - let catchCount = Int.random(in: 0...3) - var blockIndex = 1 - let blockCount = 2 + catchCount + emitCatchAll - // Create a few tags in JS. - b.createWasmJSTag() - b.createWasmTag(parameterTypes: b.randomTagParameters()) - let m = b.buildWasmModule { m in - // Create a few tags in Wasm. - m.addTag(parameterTypes: b.randomTagParameters()) - m.addTag(parameterTypes: b.randomTagParameters()) - // Build some other wasm module stuff (tables, memories, gobals, ...) - b.buildRecursive(block: blockIndex, of: blockCount, n: 4) - blockIndex += 1 - let functionSignature = b.randomWasmSignature() - m.addWasmFunction(with: functionSignature) { function, functionLabel, args in - b.buildPrefix() - function.wasmBuildLegacyTry(with: [] => [], args: [], body: {label, _ in - b.buildRecursive(block: blockIndex, of: blockCount, n: 4) - blockIndex += 1 - for _ in 0.. [], args: []) { label, args in - b.buildRecursive() - } - }, - - RecursiveCodeGenerator("WasmBlockWithSignatureGenerator", inContext: .wasmFunction) { b in - let function = b.currentWasmModule.currentWasmFunction - // Choose a few random wasm values as arguments if available. - let args = b.randomWasmBlockArguments(upTo: 5) - let parameters = args.map {b.type(of: $0)} - let outputTypes = b.randomWasmBlockOutputTypes(upTo: 5) - function.wasmBuildBlockWithResults(with: parameters => outputTypes, args: args) { label, args in - b.buildRecursive() - return outputTypes.map(function.findOrGenerateWasmVar) - } - }, - - RecursiveCodeGenerator("WasmLoopGenerator", inContext: .wasmFunction) { b in + CodeGenerator( + "WasmBlockGenerator", + [ + GeneratorStub( + "WasmBeginBlockGenerator", + inContext: .single(.wasmFunction), + provides: [.wasmFunction] + ) { b in + b.emit(WasmBeginBlock(with: [] => [])) + }, + GeneratorStub( + "WasmEndBlockGenerator", + inContext: .single(.wasmFunction) + ) { b in + b.emit(WasmEndBlock(outputTypes: [])) + }, + ]), + + CodeGenerator( + "WasmBlockWithSignatureGenerator", + [ + GeneratorStub( + "WasmBeginBlockGenerator", + inContext: .single(.wasmFunction), + provides: [.wasmFunction] + ) { b in + let args = b.randomWasmBlockArguments(upTo: 5) + let parameters = args.map(b.type) + + let outputTypes = b.randomWasmBlockOutputTypes(upTo: 5) + let signature = parameters => outputTypes + b.emit(WasmBeginBlock(with: signature), withInputs: args) + }, + GeneratorStub( + "WasmEndBlockGenerator", + inContext: .single(.wasmFunction) + ) { b in + let signature = b.currentWasmSignature + let function = b.currentWasmFunction + let outputs = signature.outputTypes.map( + function.findOrGenerateWasmVar) + b.emit( + WasmEndBlock(outputTypes: signature.outputTypes), + withInputs: outputs) + }, + ]), + + // TODO: think about how we can turn this into a mulit-part Generator + CodeGenerator("WasmLoopGenerator", inContext: .single(.wasmFunction)) { b in let function = b.currentWasmModule.currentWasmFunction let loopCtr = function.consti32(10) function.wasmBuildLoop(with: [] => []) { label, args in - let result = function.wasmi32BinOp(loopCtr, function.consti32(1), binOpKind: .Sub) + let result = function.wasmi32BinOp( + loopCtr, function.consti32(1), binOpKind: .Sub) function.wasmReassign(variable: loopCtr, to: result) - b.buildRecursive() + b.build(n: defaultCodeGenerationAmount) // Backedge of loop, we continue if it is not equal to zero. - let isNotZero = function.wasmi32CompareOp(loopCtr, function.consti32(0), using: .Ne) - function.wasmBranchIf(isNotZero, to: label, hint: b.randomWasmBranchHint()) + let isNotZero = function.wasmi32CompareOp( + loopCtr, function.consti32(0), using: .Ne) + function.wasmBranchIf( + isNotZero, to: label, hint: b.randomWasmBranchHint()) } }, - RecursiveCodeGenerator("WasmLoopWithSignatureGenerator", inContext: .wasmFunction) { b in + CodeGenerator("WasmLoopWithSignatureGenerator", inContext: .single(.wasmFunction)) { + b in let function = b.currentWasmModule.currentWasmFunction // Count upwards here to make it slightly more different from the other loop generator. // Also, instead of using reassign, this generator uses the signature to pass and update the loop counter. let randomArgs = b.randomWasmBlockArguments(upTo: 5) - let randomArgTypes = randomArgs.map{b.type(of: $0)} + let randomArgTypes = randomArgs.map { b.type(of: $0) } let args = [function.consti32(0)] + randomArgs let parameters = args.map(b.type) let outputTypes = b.randomWasmBlockOutputTypes(upTo: 5) // Note that due to the do-while style implementation, the actual iteration count is at least 1. let iterationCount = Int32.random(in: 0...16) - function.wasmBuildLoop(with: parameters => outputTypes, args: args) { label, loopArgs in - b.buildRecursive() - let loopCtr = function.wasmi32BinOp(args[0], function.consti32(1), binOpKind: .Add) - let condition = function.wasmi32CompareOp(loopCtr, function.consti32(iterationCount), using: .Lt_s) - let backedgeArgs = [loopCtr] + randomArgTypes.map{b.randomVariable(ofType: $0)!} - function.wasmBranchIf(condition, to: label, args: backedgeArgs, hint: b.randomWasmBranchHint()) + function.wasmBuildLoop(with: parameters => outputTypes, args: args) { + label, loopArgs in + b.build(n: defaultCodeGenerationAmount) + let loopCtr = function.wasmi32BinOp( + args[0], function.consti32(1), binOpKind: .Add) + let condition = function.wasmi32CompareOp( + loopCtr, function.consti32(iterationCount), using: .Lt_s) + let backedgeArgs = + [loopCtr] + randomArgTypes.map { b.randomVariable(ofType: $0)! } + function.wasmBranchIf( + condition, to: label, args: backedgeArgs, + hint: b.randomWasmBranchHint()) return outputTypes.map(function.findOrGenerateWasmVar) } }, - RecursiveCodeGenerator("WasmLegacyTryCatchGenerator", inContext: .wasmFunction) { b in + // TODO Turn this into a multi-part Generator + CodeGenerator("WasmLegacyTryCatchGenerator", inContext: .single(.wasmFunction)) { + b in let function = b.currentWasmModule.currentWasmFunction // Choose a few random wasm values as arguments if available. let args = b.randomWasmBlockArguments(upTo: 5) let parameters = args.map(b.type) - let tags = (0.. [], args: args) { label, args in - b.buildRecursive(block: 1, of: recursiveCallCount, n: 4) + function.wasmBuildLegacyTry(with: parameters => [], args: args) { + label, args in + b.build(n: 4) for (i, tag) in tags.enumerated() { function.WasmBuildLegacyCatch(tag: tag) { _, _, _ in - b.buildRecursive(block: 2 + i, of: recursiveCallCount, n: 4) + b.build(n: 4) } } } catchAllBody: { label in - b.buildRecursive(block: 2 + tags.count, of: recursiveCallCount, n: 4) + b.build(n: 4) } }, - RecursiveCodeGenerator("WasmLegacyTryCatchWithResultGenerator", inContext: .wasmFunction) { b in + CodeGenerator( + "WasmLegacyTryCatchWithResultGenerator", inContext: .single(.wasmFunction) + ) { b in let function = b.currentWasmModule.currentWasmFunction // Choose a few random wasm values as arguments if available. let args = b.randomWasmBlockArguments(upTo: 5) let parameters = args.map(b.type) - let tags = (0.. outputTypes let recursiveCallCount = 2 + tags.count - function.wasmBuildLegacyTryWithResult(with: signature, args: args, body: { label, args in - b.buildRecursive(block: 1, of: recursiveCallCount, n: 4) - return outputTypes.map(function.findOrGenerateWasmVar) - }, catchClauses: tags.enumerated().map {i, tag in (tag, {_, _, _ in - b.buildRecursive(block: 2 + i, of: recursiveCallCount, n: 4) - return outputTypes.map(function.findOrGenerateWasmVar) - })}, catchAllBody: { label in - b.buildRecursive(block: 2 + tags.count, of: recursiveCallCount, n: 4) - return outputTypes.map(function.findOrGenerateWasmVar) - }) + function.wasmBuildLegacyTryWithResult( + with: signature, args: args, + body: { label, args in + b.build(n: 4) + return outputTypes.map(function.findOrGenerateWasmVar) + }, + catchClauses: tags.enumerated().map { i, tag in + ( + tag, + { _, _, _ in + b.build(n: 4) + return outputTypes.map(function.findOrGenerateWasmVar) + } + ) + }, + catchAllBody: { label in + b.build(n: 4) + return outputTypes.map(function.findOrGenerateWasmVar) + }) }, - RecursiveCodeGenerator("WasmLegacyTryDelegateGenerator", inContext: .wasmFunction, inputs: .required(.anyLabel)) { b, label in + // TODO split this into a multi-part Generator. + CodeGenerator( + "WasmLegacyTryCatchWithResultGenerator", inContext: .single(.wasmFunction) + ) { b in let function = b.currentWasmModule.currentWasmFunction // Choose a few random wasm values as arguments if available. let args = b.randomWasmBlockArguments(upTo: 5) - let outputTypes = b.randomWasmBlockOutputTypes(upTo: 3) let parameters = args.map(b.type) - function.wasmBuildLegacyTryDelegateWithResult(with: parameters => outputTypes, args: args, body: { _, _ in - b.buildRecursive() - return outputTypes.map(function.findOrGenerateWasmVar) - }, delegate: label) - }, - - // The variable we reassign to has to be a numerical primitive, e.g. something that looks like a number (can be a global) - // We cannot reassign to a .wasmFuncRef or .wasmExternRef though, as they need to be in a local slot. - RecursiveCodeGenerator("WasmIfElseGenerator", inContext: .wasmFunction, inputs: .required(.wasmi32, .wasmNumericalPrimitive)) { b, conditionVar, outputVar in - let function = b.currentWasmModule.currentWasmFunction - - let assignProb = probability(0.2) - - function.wasmBuildIfElse(conditionVar, hint: b.randomWasmBranchHint()) { - b.buildRecursive(block: 1, of: 2, n: 4) - if let variable = b.randomVariable(ofType: b.type(of: outputVar)) { - function.wasmReassign(variable: variable, to: outputVar) - } - } elseBody: { - b.buildRecursive(block: 2, of: 2, n: 4) - if let variable = b.randomVariable(ofType: b.type(of: outputVar)) { - function.wasmReassign(variable: variable, to: outputVar) - } - } + let tags = (0.. outputTypes + let recursiveCallCount = 2 + tags.count + function.wasmBuildLegacyTryWithResult( + with: signature, args: args, + body: { label, args in + b.build(n: 4) + return outputTypes.map(function.findOrGenerateWasmVar) + }, + catchClauses: tags.enumerated().map { i, tag in + ( + tag, + { _, _, _ in + b.build(n: 4) + return outputTypes.map(function.findOrGenerateWasmVar) + } + ) + }, + catchAllBody: { label in + b.build(n: 4) + return outputTypes.map(function.findOrGenerateWasmVar) + }) }, - RecursiveCodeGenerator("WasmIfElseWithSignatureGenerator", inContext: .wasmFunction, inputs: .required(.wasmi32)) { b, conditionVar in + CodeGenerator( + "WasmLegacyTryDelegateGenerator", inContext: .single(.wasmFunction), + inputs: .required(.anyLabel) + ) { b, label in let function = b.currentWasmModule.currentWasmFunction // Choose a few random wasm values as arguments if available. let args = b.randomWasmBlockArguments(upTo: 5) + let outputTypes = b.randomWasmBlockOutputTypes(upTo: 3) let parameters = args.map(b.type) - let outputTypes = b.randomWasmBlockOutputTypes(upTo: 5) - function.wasmBuildIfElseWithResult(conditionVar, hint: b.randomWasmBranchHint(), signature: parameters => outputTypes, args: args) { label, args in - b.buildRecursive(block: 1, of: 2, n: 4) - return outputTypes.map(function.findOrGenerateWasmVar) - } elseBody: { label, args in - b.buildRecursive(block: 2, of: 2, n: 4) - return outputTypes.map(function.findOrGenerateWasmVar) - } - }, - - CodeGenerator("WasmSelectGenerator", inContext: .wasmFunction, inputs: .required(.wasmi32)) { b, condition in + function.wasmBuildLegacyTryDelegateWithResult( + with: parameters => outputTypes, args: args, + body: { _, _ in + b.build(n: 4) + return outputTypes.map(function.findOrGenerateWasmVar) + }, delegate: label) + }, + + CodeGenerator( + "WasmIfElseGenerator", + [ + GeneratorStub( + "WasmBeginIfGenerator", + inContext: .single(.wasmFunction), + inputs: .required(.wasmi32), + provides: [.wasmFunction] + ) { b, condition in + b.emit( + WasmBeginIf(hint: b.randomWasmBranchHint()), + withInputs: [condition]) + }, + GeneratorStub( + "WasmBeginElseGenerator", + inContext: .single(.wasmFunction), + provides: [.wasmFunction] + ) { b in + b.emit(WasmBeginElse()) + }, + GeneratorStub( + "WasmEndIfElseGenerator", + inContext: .single(.wasmFunction) + ) { b in + b.emit(WasmEndIf()) + }, + ]), + + CodeGenerator( + "WasmIfElseWithSignatureGenerator", + [ + GeneratorStub( + "WasmBeginIfGenerator", + inContext: .single(.wasmFunction), + inputs: .required(.wasmi32), + provides: [.wasmFunction] + ) { b, condition in + let args = b.randomWasmBlockArguments(upTo: 5) + let parameters = args.map(b.type) + let outputTypes = b.randomWasmBlockOutputTypes(upTo: 5) + b.emit( + WasmBeginIf( + with: parameters => outputTypes, + hint: b.randomWasmBranchHint()), + withInputs: args + [condition]) + }, + GeneratorStub( + "WasmBeginElseGenerator", + inContext: .single(.wasmFunction), + provides: [.wasmFunction] + ) { b in + let function = b.currentWasmFunction + let signature = b.currentWasmSignature + let trueResults = signature.outputTypes.map( + function.findOrGenerateWasmVar) + b.emit(WasmBeginElse(with: signature), withInputs: trueResults) + }, + GeneratorStub( + "WasmEndIfGenerator", + inContext: .single(.wasmFunction) + ) { b in + let function = b.currentWasmFunction + let signature = b.currentWasmSignature + let falseResults = signature.outputTypes.map( + function.findOrGenerateWasmVar) + b.emit( + WasmEndIf(outputTypes: signature.outputTypes), + withInputs: falseResults) + }, + ]), + + CodeGenerator( + "WasmSelectGenerator", + inContext: .single(.wasmFunction), + inputs: .required(.wasmi32) + ) { b, condition in let function = b.currentWasmModule.currentWasmFunction // The condition is an i32, so we should always find at least that one as a possible input. let trueValue = b.randomVariable(ofType: .wasmPrimitive)! let falseValue = b.randomVariable(ofType: b.type(of: trueValue))! - function.wasmSelect(on: condition, trueValue: trueValue, falseValue: falseValue) + function.wasmSelect( + on: condition, trueValue: trueValue, falseValue: falseValue) }, - CodeGenerator("WasmThrowGenerator", inContext: .wasmFunction, inputs: .required(.object(ofGroup: "WasmTag"))) { b, tag in + CodeGenerator( + "WasmThrowGenerator", + inContext: .single(.wasmFunction), + inputs: .required(.object(ofGroup: "WasmTag")) + ) { b, tag in let function = b.currentWasmModule.currentWasmFunction let wasmTagType = b.type(of: tag).wasmTagType! if wasmTagType.isJSTag { @@ -1069,125 +1537,191 @@ public let WasmCodeGenerators: [CodeGenerator] = [ function.WasmBuildThrow(tag: tag, inputs: args) }, - CodeGenerator("WasmLegacyRethrowGenerator", inContext: .wasmFunction, inputs: .required(.exceptionLabel)) { b, exception in + CodeGenerator( + "WasmLegacyRethrowGenerator", + inContext: .single(.wasmFunction), + inputs: .required(.exceptionLabel) + ) { b, exception in let function = b.currentWasmModule.currentWasmFunction function.wasmBuildLegacyRethrow(exception) }, - CodeGenerator("WasmThrowRefGenerator", inContext: .wasmFunction, inputs: .required(.wasmExnRef)) { b, exception in + CodeGenerator( + "WasmThrowRefGenerator", inContext: .single(.wasmFunction), + inputs: .required(.wasmExnRef) + ) { b, exception in let function = b.currentWasmModule.currentWasmFunction function.wasmBuildThrowRef(exception: exception) }, - CodeGenerator("WasmDefineTagGenerator", inContext: .wasm) {b in + CodeGenerator( + "WasmDefineTagGenerator", inContext: .single(.wasm), + produces: [.object(ofGroup: "WasmTag")] + ) { b in b.currentWasmModule.addTag(parameterTypes: b.randomTagParameters()) }, - CodeGenerator("WasmBranchGenerator", inContext: .wasmFunction, inputs: .required(.anyLabel)) { b, label in + CodeGenerator( + "WasmBranchGenerator", + inContext: .single(.wasmFunction), + inputs: .required(.anyLabel) + ) { b, label in let function = b.currentWasmModule.currentWasmFunction - let args = b.type(of: label).wasmLabelType!.parameters.map(function.findOrGenerateWasmVar) + let args = b.type(of: label).wasmLabelType!.parameters.map( + function.findOrGenerateWasmVar) function.wasmBranch(to: label, args: args) }, - CodeGenerator("WasmBranchIfGenerator", inContext: .wasmFunction, inputs: .required(.anyLabel, .wasmi32)) { b, label, conditionVar in + CodeGenerator( + "WasmBranchIfGenerator", inContext: .single(.wasmFunction), + inputs: .required(.anyLabel, .wasmi32) + ) { b, label, conditionVar in let function = b.currentWasmModule.currentWasmFunction - let args = b.type(of: label).wasmLabelType!.parameters.map(function.findOrGenerateWasmVar) - function.wasmBranchIf(conditionVar, to: label, args: args, hint: b.randomWasmBranchHint()) + let args = b.type(of: label).wasmLabelType!.parameters.map( + function.findOrGenerateWasmVar) + function.wasmBranchIf( + conditionVar, to: label, args: args, hint: b.randomWasmBranchHint()) }, - RecursiveCodeGenerator("WasmBranchTableGenerator", inContext: .wasmFunction, inputs: .required(.wasmi32)) { b, value in + // TODO split this into a multi-part Generator. + CodeGenerator( + "WasmBranchTableGenerator", inContext: .single(.wasmFunction), + inputs: .required(.wasmi32) + ) { b, value in let function = b.currentWasmModule.currentWasmFunction // Choose parameter types for the br_table. If we can find an existing label, just use that // label types as it allows use to reuse existing (and therefore more interesting) blocks. - let parameterTypes = if let label = b.randomVariable(ofType: .anyLabel) { - b.type(of: label).wasmLabelType!.parameters - } else { - b.randomWasmBlockOutputTypes(upTo: 3) - } + let parameterTypes = + if let label = b.randomVariable(ofType: .anyLabel) { + b.type(of: label).wasmLabelType!.parameters + } else { + b.randomWasmBlockOutputTypes(upTo: 3) + } let extraBlockCount = Int.random(in: 1...5) let valueCount = Int.random(in: 0...20) let signature = [] => parameterTypes (0.. outputTypes, args: []) + return outputTypes + } + // Look up the labels. In most cases these will be exactly the ones produced by the blocks + // above but also any other matching existing block could be used. (Similar, tags with the + // same parameter types could also be mapped to the same block.) + let labels = outputTypesList.map { outputTypes in + b.randomVariable(ofType: .label(outputTypes))! + } + let catches = zip(tags, withExnRef).map { + tag, withExnRef -> WasmBeginTryTable.CatchKind in + tag == nil + ? (withExnRef ? .AllRef : .AllNoRef) + : (withExnRef ? .Ref : .NoRef) + } - let outputTypesList = zip(tags, withExnRef).map { tag, withExnRef in - var outputTypes: [ILType] = if let tag = tag { - b.type(of: tag).wasmTagType!.parameters - } else { - [] - } - if withExnRef { - outputTypes.append(.wasmExnRef) + var tryArgs = b.randomWasmBlockArguments(upTo: 5) + let tryParameters = tryArgs.map { b.type(of: $0) } + let tryOutputTypes = b.randomWasmBlockOutputTypes(upTo: 5) + tryArgs += zip(tags, labels).map { tag, label in + tag == nil ? [label] : [tag!, label] + }.joined() + function.wasmBuildTryTable( + with: tryParameters => tryOutputTypes, args: tryArgs, + catches: catches + ) { _, _ in + b.build(n: defaultCodeGenerationAmount) + return tryOutputTypes.map(function.findOrGenerateWasmVar) + } + outputTypesList.reversed().enumerated().forEach { + n, outputTypes in + let results = outputTypes.map( + function.findOrGenerateWasmVar) + function.wasmEndBlock( + outputTypes: outputTypes, args: results) + b.build(n: defaultCodeGenerationAmount) + } } - function.wasmBeginBlock(with: [] => outputTypes, args: []) - return outputTypes - } - // Look up the labels. In most cases these will be exactly the ones produced by the blocks - // above but also any other matching existing block could be used. (Similar, tags with the - // same parameter types could also be mapped to the same block.) - let labels = outputTypesList.map {outputTypes in b.randomVariable(ofType: .label(outputTypes))!} - let catches = zip(tags, withExnRef).map { tag, withExnRef -> WasmBeginTryTable.CatchKind in - tag == nil ? (withExnRef ? .AllRef : .AllNoRef) : (withExnRef ? .Ref : .NoRef) - } - - var tryArgs = b.randomWasmBlockArguments(upTo: 5) - let tryParameters = tryArgs.map {b.type(of: $0)} - let tryOutputTypes = b.randomWasmBlockOutputTypes(upTo: 5) - tryArgs += zip(tags, labels).map {tag, label in tag == nil ? [label] : [tag!, label]}.joined() - function.wasmBuildTryTable(with: tryParameters => tryOutputTypes, args: tryArgs, catches: catches) { _, _ in - b.buildRecursive(block: 1, of: tags.count + 1, n: 4) - return tryOutputTypes.map(function.findOrGenerateWasmVar) - } - outputTypesList.reversed().enumerated().forEach { n, outputTypes in - let results = outputTypes.map(function.findOrGenerateWasmVar) - function.wasmEndBlock(outputTypes: outputTypes, args: results) - b.buildRecursive(block: n + 2, of: tags.count + 1, n: 4) - } - }, + ]), + + CodeGenerator( + "ConstSimd128Generator", inContext: .single(.wasmFunction), + produces: [.wasmSimd128] + ) { b in + let function = b.currentWasmModule.currentWasmFunction + function.constSimd128( + value: (0..<16).map { _ in UInt8.random(in: UInt8.min...UInt8.max) } + ) + }, + + CodeGenerator( + "WasmSimd128IntegerUnOpGenerator", inContext: .single(.wasmFunction), + inputs: .required(.wasmSimd128), produces: [] + ) { b, input in + let shape = chooseUniform( + from: WasmSimd128Shape.allCases.filter { return !$0.isFloat() }) + let unOpKind = chooseUniform( + from: WasmSimd128IntegerUnOpKind.allCases.filter { + return $0.isValidForShape(shape: shape) + }) - CodeGenerator("ConstSimd128Generator", inContext: .wasmFunction) { b in let function = b.currentWasmModule.currentWasmFunction - function.constSimd128(value: (0 ..< 16).map { _ in UInt8.random(in: UInt8.min ... UInt8.max) }) - }, - - CodeGenerator("WasmSimd128IntegerUnOpGenerator", inContext: .wasmFunction, inputs: .required(.wasmSimd128)) { b, input in - let shape = chooseUniform(from: WasmSimd128Shape.allCases.filter{ !$0.isFloat() }) - let unOpKind = chooseUniform(from: WasmSimd128IntegerUnOpKind.allCases.filter{ - $0.isValidForShape(shape: shape) - }) - - let function = b.currentWasmModule.currentWasmFunction; function.wasmSimd128IntegerUnOp(input, shape, unOpKind) }, - CodeGenerator("WasmSimd128IntegerBinOpGenerator", inContext: .wasmFunction, inputs: .required(.wasmSimd128)) { b, lhs in - let shape = chooseUniform(from: WasmSimd128Shape.allCases.filter{ !$0.isFloat() }) - let binOpKind = chooseUniform(from: WasmSimd128IntegerBinOpKind.allCases.filter{ - $0.isValidForShape(shape: shape) - }) - let function = b.currentWasmModule.currentWasmFunction; + CodeGenerator( + "WasmSimd128IntegerBinOpGenerator", inContext: .single(.wasmFunction), + inputs: .required(.wasmSimd128), produces: [.wasmSimd128] + ) { b, lhs in + let shape = chooseUniform( + from: WasmSimd128Shape.allCases.filter { return !$0.isFloat() }) + let binOpKind = chooseUniform( + from: WasmSimd128IntegerBinOpKind.allCases.filter { + return $0.isValidForShape(shape: shape) + }) + let function = b.currentWasmModule.currentWasmFunction // Shifts take an i32 as an rhs input, the others take a regular .wasmSimd128 input. let rhsType = binOpKind.isShift() ? ILType.wasmi32 : .wasmSimd128 @@ -1195,7 +1729,7 @@ public let WasmCodeGenerators: [CodeGenerator] = [ function.wasmSimd128IntegerBinOp(lhs, rhs, shape, binOpKind) }, - CodeGenerator("WasmSimd128IntegerTernaryOpGenerator", inContext: .wasmFunction, inputs: .required(.wasmSimd128, .wasmSimd128, .wasmSimd128)) { b, left, mid, right in + CodeGenerator("WasmSimd128IntegerTernaryOpGenerator", inContext: .single(.wasmFunction), inputs: .required(.wasmSimd128, .wasmSimd128, .wasmSimd128)) { b, left, mid, right in let shape = chooseUniform(from: WasmSimd128Shape.allCases.filter{ !$0.isFloat() } ) let ternaryOpKind = chooseUniform(from: WasmSimd128IntegerTernaryOpKind.allCases.filter{ $0.isValidForShape(shape: shape) @@ -1205,27 +1739,37 @@ public let WasmCodeGenerators: [CodeGenerator] = [ function.wasmSimd128IntegerTernaryOp(left, mid, right, shape, ternaryOpKind) }, - CodeGenerator("WasmSimd128FloatUnOpGenerator", inContext: .wasmFunction, inputs: .required(.wasmSimd128)) { b, input in - let shape = chooseUniform(from: WasmSimd128Shape.allCases.filter{ $0.isFloat() }) - let unOpKind = chooseUniform(from: WasmSimd128FloatUnOpKind.allCases.filter{ - $0.isValidForShape(shape: shape) - }) + CodeGenerator( + "WasmSimd128FloatUnOpGenerator", inContext: .single(.wasmFunction), + inputs: .required(.wasmSimd128), produces: [.wasmSimd128] + ) { b, input in + let shape = chooseUniform( + from: WasmSimd128Shape.allCases.filter { return $0.isFloat() }) + let unOpKind = chooseUniform( + from: WasmSimd128FloatUnOpKind.allCases.filter { + return $0.isValidForShape(shape: shape) + }) - let function = b.currentWasmModule.currentWasmFunction; + let function = b.currentWasmModule.currentWasmFunction function.wasmSimd128FloatUnOp(input, shape, unOpKind) }, - CodeGenerator("WasmSimd128FloatBinOpGenerator", inContext: .wasmFunction, inputs: .required(.wasmSimd128, .wasmSimd128)) { b, lhs, rhs in - let shape = chooseUniform(from: WasmSimd128Shape.allCases.filter{ $0.isFloat() }) - let binOpKind = chooseUniform(from: WasmSimd128FloatBinOpKind.allCases.filter{ - $0.isValidForShape(shape: shape) - }) + CodeGenerator( + "WasmSimd128FloatBinOpGenerator", inContext: .single(.wasmFunction), + inputs: .required(.wasmSimd128, .wasmSimd128), produces: [.wasmSimd128] + ) { b, lhs, rhs in + let shape = chooseUniform( + from: WasmSimd128Shape.allCases.filter { return $0.isFloat() }) + let binOpKind = chooseUniform( + from: WasmSimd128FloatBinOpKind.allCases.filter { + return $0.isValidForShape(shape: shape) + }) - let function = b.currentWasmModule.currentWasmFunction; + let function = b.currentWasmModule.currentWasmFunction function.wasmSimd128FloatBinOp(lhs, rhs, shape, binOpKind) }, - CodeGenerator("WasmSimd128FloatTernaryOpGenerator", inContext: .wasmFunction, inputs: .required(.wasmSimd128, .wasmSimd128, .wasmSimd128)) { b, left, mid, right in + CodeGenerator("WasmSimd128FloatTernaryOpGenerator", inContext: .single(.wasmFunction), inputs: .required(.wasmSimd128, .wasmSimd128, .wasmSimd128)) { b, left, mid, right in let shape = chooseUniform(from: WasmSimd128Shape.allCases.filter{ $0.isFloat() } ) let ternaryOpKind = chooseUniform(from: WasmSimd128FloatTernaryOpKind.allCases.filter{ $0.isValidForShape(shape: shape) @@ -1235,7 +1779,10 @@ public let WasmCodeGenerators: [CodeGenerator] = [ function.wasmSimd128FloatTernaryOp(left, mid, right, shape, ternaryOpKind) }, - CodeGenerator("WasmSimd128CompareGenerator", inContext: .wasmFunction, inputs: .required(.wasmSimd128, .wasmSimd128)) { b, lhs, rhs in + CodeGenerator( + "WasmSimd128CompareGenerator", inContext: .single(.wasmFunction), + inputs: .required(.wasmSimd128, .wasmSimd128), produces: [.wasmSimd128] + ) { b, lhs, rhs in let shape = chooseUniform(from: WasmSimd128Shape.allCases) let compareOpKind = b.randomSimd128CompareOpKind(shape) @@ -1243,52 +1790,81 @@ public let WasmCodeGenerators: [CodeGenerator] = [ function.wasmSimd128Compare(lhs, rhs, shape, compareOpKind) }, - CodeGenerator("WasmSimdSplatGenerator", inContext: .wasmFunction) {b in - let function = b.currentWasmModule.currentWasmFunction; + CodeGenerator("WasmSimdSplatGenerator", inContext: .single(.wasmFunction)) { b in + let function = b.currentWasmModule.currentWasmFunction let kind = chooseUniform(from: WasmSimdSplat.Kind.allCases) - function.wasmSimdSplat(kind: kind, function.findOrGenerateWasmVar(ofType: kind.laneType())) + function.wasmSimdSplat( + kind: kind, function.findOrGenerateWasmVar(ofType: kind.laneType())) }, - CodeGenerator("WasmSimdExtractLaneGenerator", inContext: .wasmFunction, inputs: .required(.wasmSimd128)) { b, input in + CodeGenerator( + "WasmSimdExtractLaneGenerator", inContext: .single(.wasmFunction), + inputs: .required(.wasmSimd128) + ) { b, input in let function = b.currentWasmModule.currentWasmFunction let kind = chooseUniform(from: WasmSimdExtractLane.Kind.allCases) - function.wasmSimdExtractLane(kind: kind, input, Int.random(in: 0.. Variable { // The expressions for property values and computed properties need to be emitted before the class declaration is opened. var propertyValues = [Variable]() - var computedPropertyKeys = [Variable]() + var computedKeys = [Variable]() for field in fields { guard let field = field.field else { throw CompilerError.invalidNodeError("missing concrete field in class declaration") @@ -80,15 +80,20 @@ public class JavaScriptCompiler { if property.hasValue { propertyValues.append(try compileExpression(property.value)) } - if case .expression(let key) = property.key { - computedPropertyKeys.append(try compileExpression(key)) + if case .expression(let expression) = property.key.body { + computedKeys.append(try compileExpression(expression)) + } + } + if case .method(let method) = field { + if case .expression(let expression) = method.key.body { + computedKeys.append(try compileExpression(expression)) } } } // Reverse the arrays since we'll remove the elements in FIFO order. propertyValues.reverse() - computedPropertyKeys.reverse() + computedKeys.reverse() let classDecl: Instruction if let superClass = superClass { @@ -104,7 +109,7 @@ public class JavaScriptCompiler { for field in fields { switch field.field! { case .property(let property): - guard let key = property.key else { + guard let key = property.key.body else { throw CompilerError.invalidNodeError("Missing key in class property") } @@ -124,7 +129,7 @@ public class JavaScriptCompiler { op = ClassAddInstanceElement(index: index, hasValue: property.hasValue) } case .expression: - inputs.append(computedPropertyKeys.removeLast()) + inputs.append(computedKeys.removeLast()) if property.isStatic { op = ClassAddStaticComputedProperty(hasValue: property.hasValue) } else { @@ -154,10 +159,25 @@ public class JavaScriptCompiler { case .method(let method): let parameters = convertParameters(method.parameters) let head: Instruction - if method.isStatic { - head = emit(BeginClassStaticMethod(methodName: method.name, parameters: parameters)) - } else { - head = emit(BeginClassInstanceMethod(methodName: method.name, parameters: parameters)) + + guard let key = method.key.body else { + throw CompilerError.invalidNodeError("Missing key in class property") + } + switch key { + case .name(let name): + if method.isStatic { + head = emit(BeginClassStaticMethod(methodName: name, parameters: parameters)) + } else { + head = emit(BeginClassInstanceMethod(methodName: name, parameters: parameters)) + } + case .index: + throw CompilerError.invalidNodeError("Not supported") + case .expression: + if method.isStatic { + head = emit(BeginClassStaticComputedMethod(parameters: parameters), withInputs: [computedKeys.removeLast()]) + } else { + head = emit(BeginClassInstanceComputedMethod(parameters: parameters), withInputs: [computedKeys.removeLast()]) + } } try enterNewScope { @@ -169,10 +189,21 @@ public class JavaScriptCompiler { } } - if method.isStatic { - emit(EndClassStaticMethod()) - } else { - emit(EndClassInstanceMethod()) + switch key { + case .name: + if method.isStatic { + emit(EndClassStaticMethod()) + } else { + emit(EndClassInstanceMethod()) + } + case .index: + throw CompilerError.invalidNodeError("Not supported") + case .expression: + if method.isStatic { + emit(EndClassStaticComputedMethod()) + } else { + emit(EndClassInstanceComputedMethod()) + } } case .getter(let getter): @@ -238,6 +269,14 @@ public class JavaScriptCompiler { return classDecl.output } + private func compileInitialDeclarationValue(_ decl: Compiler_Protobuf_VariableDeclarator) throws -> Variable { + if decl.hasValue { + return try compileExpression(decl.value) + } else { + // TODO(saelo): consider caching the `undefined` value for future uses + return emit(LoadUndefined()).output + } + } private func compileStatement(_ node: StatementNode) throws { guard let stmt = node.statement else { @@ -260,13 +299,7 @@ public class JavaScriptCompiler { case .variableDeclaration(let variableDeclaration): for decl in variableDeclaration.declarations { - let initialValue: Variable - if decl.hasValue { - initialValue = try compileExpression(decl.value) - } else { - // TODO(saelo): consider caching the `undefined` value for future uses - initialValue = emit(LoadUndefined()).output - } + let initialValue = try compileInitialDeclarationValue(decl) let declarationMode: NamedVariableDeclarationMode switch variableDeclaration.kind { @@ -286,6 +319,23 @@ public class JavaScriptCompiler { mapOrRemap(decl.name, to: v) } + case .disposableVariableDeclaration(let variableDeclaration): + for decl in variableDeclaration.declarations { + let initialValue = try compileInitialDeclarationValue(decl) + + let v: Variable; + switch variableDeclaration.kind { + case .using: + v = emit(CreateNamedDisposableVariable(decl.name), withInputs: [initialValue]).output + case .awaitUsing: + v = emit(CreateNamedAsyncDisposableVariable(decl.name), withInputs: [initialValue]).output + case .UNRECOGNIZED(let type): + throw CompilerError.invalidNodeError("invalid disposable variable declaration type \(type)") + } + assert(!currentScope.keys.contains(decl.name)) + mapOrRemap(decl.name, to: v) + } + case .functionDeclaration(let functionDeclaration): let parameters = convertParameters(functionDeclaration.parameters) let functionBegin, functionEnd: Operation @@ -764,7 +814,7 @@ public class JavaScriptCompiler { case .objectExpression(let objectExpression): // The expressions for property values and computed properties need to be emitted before the object literal is opened. var propertyValues = [Variable]() - var computedPropertyKeys = [Variable]() + var computedKeys = [Variable]() for field in objectExpression.fields { guard let field = field.field else { throw CompilerError.invalidNodeError("missing concrete field in object expression") @@ -772,18 +822,18 @@ public class JavaScriptCompiler { if case .property(let property) = field { propertyValues.append(try compileExpression(property.value)) if case .expression(let expression) = property.key { - computedPropertyKeys.append(try compileExpression(expression)) + computedKeys.append(try compileExpression(expression)) } } else if case .method(let method) = field { if case .expression(let expression) = method.key { - computedPropertyKeys.append(try compileExpression(expression)) + computedKeys.append(try compileExpression(expression)) } } } // Reverse the arrays since we'll remove the elements in FIFO order. propertyValues.reverse() - computedPropertyKeys.reverse() + computedKeys.reverse() // Now build the object literal. emit(BeginObjectLiteral()) @@ -800,7 +850,7 @@ public class JavaScriptCompiler { case .index(let index): emit(ObjectLiteralAddElement(index: index), withInputs: inputs) case .expression: - emit(ObjectLiteralAddComputedProperty(), withInputs: [computedPropertyKeys.removeLast()] + inputs) + emit(ObjectLiteralAddComputedProperty(), withInputs: [computedKeys.removeLast()] + inputs) } case .method(let method): let parameters = convertParameters(method.parameters) @@ -809,7 +859,7 @@ public class JavaScriptCompiler { if case .name(let name) = method.key { instr = emit(BeginObjectLiteralMethod(methodName: name, parameters: parameters)) } else { - instr = emit(BeginObjectLiteralComputedMethod(parameters: parameters), withInputs: [computedPropertyKeys.removeLast()]) + instr = emit(BeginObjectLiteralComputedMethod(parameters: parameters), withInputs: [computedKeys.removeLast()]) } try enterNewScope { diff --git a/Sources/Fuzzilli/Compiler/Parser/parser.js b/Sources/Fuzzilli/Compiler/Parser/parser.js index f42fe00dd..081449c34 100644 --- a/Sources/Fuzzilli/Compiler/Parser/parser.js +++ b/Sources/Fuzzilli/Compiler/Parser/parser.js @@ -34,7 +34,7 @@ function tryReadFile(path) { // Parse the given JavaScript script and return an AST compatible with Fuzzilli's protobuf-based AST format. function parse(script, proto) { - let ast = Parser.parse(script, { plugins: ["v8intrinsic"] }); + let ast = Parser.parse(script, { plugins: ["explicitResourceManagement", "v8intrinsic"] }); function assertNoError(err) { if (err) throw err; @@ -98,12 +98,22 @@ function parse(script, proto) { function visitVariableDeclaration(node) { let kind; + let disposable; if (node.kind === "var") { kind = 0; + disposable = false; } else if (node.kind === "let") { kind = 1; + disposable = false; } else if (node.kind === "const") { kind = 2; + disposable = false; + } else if (node.kind === "using") { + kind = 0; + disposable = true; + } else if (node.kind === "await using") { + kind = 1; + disposable = true; } else { throw "Unknown variable declaration kind: " + node.kind; } @@ -118,7 +128,26 @@ function parse(script, proto) { declarations.push(make('VariableDeclarator', outDecl)); } - return { kind, declarations }; + const type = disposable ? 'DisposableVariableDeclaration' : 'VariableDeclaration' + return [type, { kind, declarations }]; + } + + function visitMemberKey(member) { + let body = {} + if (member.computed) { + body.expression = visitExpression(member.key); + } else { + if (member.key.type === 'Identifier') { + body.name = member.key.name; + } else if (member.key.type === 'NumericLiteral') { + body.index = member.key.value; + } else if (member.key.type === 'StringLiteral') { + body.name = member.key.value; + } else { + throw "Unknown member key type: " + member.key.type + " in class declaration"; + } + } + return make('PropertyKey', body); } function visitClass(node, isExpression) { @@ -139,34 +168,19 @@ function parse(script, proto) { if (field.value !== null) { property.value = visitExpression(field.value); } - if (field.computed) { - property.expression = visitExpression(field.key); - } else { - if (field.key.type === 'Identifier') { - property.name = field.key.name; - } else if (field.key.type === 'NumericLiteral') { - property.index = field.key.value; - } else if (field.key.type === 'StringLiteral') { - property.name = field.key.value; - } else { - throw "Unknown property key type: " + field.key.type + " in class declaration"; - } - } + property.key = visitMemberKey(field); cls.fields.push(make('ClassField', { property: make('ClassProperty', property) })); } else if (field.type === 'ClassMethod') { assert(!field.shorthand, 'Expected field.shorthand to be false'); - assert(!field.computed, 'Expected field.computed to be false'); assert(!field.generator, 'Expected field.generator to be false'); assert(!field.async, 'Expected field.async to be false'); - assert(field.key.type === 'Identifier', "Expected field.key.type to be exactly 'Identifier'"); let method = field; field = {}; - let name = method.key.name; let isStatic = method.static; if (method.kind === 'constructor') { assert(method.body.type === 'BlockStatement', "Expected method.body.type to be exactly 'BlockStatement'"); - assert(name === 'constructor', "Expected name to be exactly 'constructor'"); + assert(method.key.name === 'constructor', "Expected name to be exactly 'constructor'"); assert(!isStatic, "Expected isStatic to be false"); let parameters = visitParameters(method.params); @@ -177,21 +191,28 @@ function parse(script, proto) { let parameters = visitParameters(method.params); let body = visitBody(method.body); - field.method = make('ClassMethod', { name, isStatic, parameters, body }); + let key = visitMemberKey(method); + field.method = make('ClassMethod', { key, isStatic, parameters, body }); } else if (method.kind === 'get') { + assert(!method.computed, 'Expected method.computed to be false'); + assert(method.key.type === 'Identifier', "Expected method.key.type to be exactly 'Identifier'"); assert(method.params.length === 0, "Expected method.params.length to be exactly 0"); assert(!method.generator && !method.async, "Expected both conditions to hold: !method.generator and !method.async"); assert(method.body.type === 'BlockStatement', "Expected method.body.type to be exactly 'BlockStatement'"); let body = visitBody(method.body); + const name = method.key.name; field.getter = make('ClassGetter', { name, isStatic, body }); } else if (method.kind === 'set') { + assert(!method.computed, 'Expected method.computed to be false'); + assert(method.key.type === 'Identifier', "Expected method.key.type to be exactly 'Identifier'"); assert(method.params.length === 1, "Expected method.params.length to be exactly 1"); assert(!method.generator && !method.async, "Expected both conditions to hold: !method.generator and !method.async"); assert(method.body.type === 'BlockStatement', "Expected method.body.type to be exactly 'BlockStatement'"); let parameter = visitParameter(method.params[0]); let body = visitBody(method.body); + const name = method.key.name; field.setter = make('ClassSetter', { name, isStatic, parameter, body }); } else { throw "Unknown method kind: " + method.kind; @@ -222,7 +243,7 @@ function parse(script, proto) { return makeStatement('ExpressionStatement', { expression }); } case 'VariableDeclaration': { - return makeStatement('VariableDeclaration', visitVariableDeclaration(node)); + return makeStatement(...visitVariableDeclaration(node)); } case 'FunctionDeclaration': { assert(node.id.type === 'Identifier', "Expected an identifier as function declaration name"); @@ -275,7 +296,9 @@ function parse(script, proto) { let forLoop = {}; if (node.init !== null) { if (node.init.type === 'VariableDeclaration') { - forLoop.declaration = make('VariableDeclaration', visitVariableDeclaration(node.init)); + let [type, config] = visitVariableDeclaration(node.init); + assert(type != 'DisposableVariableDeclaration', 'Disposable variables in for loops are not yet supported') + forLoop.declaration = make(type, config); } else { forLoop.expression = visitExpression(node.init); } diff --git a/Sources/Fuzzilli/Environment/JavaScriptEnvironment.swift b/Sources/Fuzzilli/Environment/JavaScriptEnvironment.swift index 34a2b477e..488c1cdd3 100644 --- a/Sources/Fuzzilli/Environment/JavaScriptEnvironment.swift +++ b/Sources/Fuzzilli/Environment/JavaScriptEnvironment.swift @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +import Foundation + public class JavaScriptEnvironment: ComponentBase { // Possible return values of the 'typeof' operator. public static let jsTypeNames = ["undefined", "boolean", "number", "string", "symbol", "function", "object", "bigint"] @@ -295,14 +297,17 @@ public class JavaScriptEnvironment: ComponentBase { private var builtinTypes: [String: ILType] = [:] private var groups: [String: ObjectGroup] = [:] + private var enums: [String: ILType] = [:] // Producing generators, keyed on `type.group` private var producingGenerators: [String: (generator: EnvironmentValueGenerator, probability: Double)] = [:] + // Named string generators, keyed on `type.group` + private var namedStringGenerators: [String: () -> String] = [:] private var producingMethods: [ILType: [(group: String, method: String)]] = [:] private var producingProperties: [ILType: [(group: String, property: String)]] = [:] private var subtypes: [ILType: [ILType]] = [:] - public init(additionalBuiltins: [String: ILType] = [:], additionalObjectGroups: [ObjectGroup] = []) { + public init(additionalBuiltins: [String: ILType] = [:], additionalObjectGroups: [ObjectGroup] = [], additionalEnumerations: [ILType] = []) { super.init(name: "JavaScriptEnvironment") // Build model of the JavaScript environment @@ -408,11 +413,82 @@ public class JavaScriptEnvironment: ComponentBase { registerObjectGroup(.jsTemporalZonedDateTime) registerObjectGroup(.jsTemporalZonedDateTimeConstructor) registerObjectGroup(.jsTemporalZonedDateTimePrototype) + registerObjectGroup(.jsIntlObject) + registerObjectGroup(.jsIntlCollator) + registerObjectGroup(.jsIntlCollatorConstructor) + registerObjectGroup(.jsIntlCollatorPrototype) + registerObjectGroup(.jsIntlDateTimeFormat) + registerObjectGroup(.jsIntlDateTimeFormatConstructor) + registerObjectGroup(.jsIntlDateTimeFormatPrototype) + registerObjectGroup(.jsIntlListFormat) + registerObjectGroup(.jsIntlListFormatConstructor) + registerObjectGroup(.jsIntlListFormatPrototype) + registerObjectGroup(.jsIntlNumberFormat) + registerObjectGroup(.jsIntlNumberFormatConstructor) + registerObjectGroup(.jsIntlNumberFormatPrototype) + registerObjectGroup(.jsIntlPluralRules) + registerObjectGroup(.jsIntlPluralRulesConstructor) + registerObjectGroup(.jsIntlPluralRulesPrototype) + registerObjectGroup(.jsIntlRelativeTimeFormat) + registerObjectGroup(.jsIntlRelativeTimeFormatConstructor) + registerObjectGroup(.jsIntlRelativeTimeFormatPrototype) + registerObjectGroup(.jsIntlSegmenter) + registerObjectGroup(.jsIntlSegmenterConstructor) + registerObjectGroup(.jsIntlSegmenterPrototype) + registerObjectGroup(.jsIntlSegmenterSegments) for group in additionalObjectGroups { registerObjectGroup(group) } + registerEnumeration(.jsTemporalCalendarEnum) + registerEnumeration(ObjectGroup.jsTemporalDirectionParam) + registerEnumeration(ObjectGroup.jsIntlRelativeTimeFormatUnitEnum) + registerEnumeration(ObjectGroup.jsIntlSupportedValuesEnum) + registerEnumeration(OptionsBag.jsTemporalUnitEnum) + registerEnumeration(OptionsBag.jsTemporalRoundingModeEnum) + registerEnumeration(OptionsBag.jsTemporalShowCalendarEnum) + registerEnumeration(OptionsBag.jsTemporalShowOffsetEnum) + registerEnumeration(OptionsBag.jsTemporalShowTimeZoneEnum) + registerEnumeration(OptionsBag.jsTemporalOverflowEnum) + registerEnumeration(OptionsBag.jsTemporalDisambiguationEnum) + registerEnumeration(OptionsBag.jsTemporalOffsetEnum) + registerEnumeration(OptionsBag.jsIntlLocaleMatcherEnum) + registerEnumeration(OptionsBag.jsIntlNumberingSystemEnum) + registerEnumeration(OptionsBag.jsIntlHourCycleEnum) + registerEnumeration(OptionsBag.jsIntlLongShortNarrowEnum) + registerEnumeration(OptionsBag.jsIntlLongShortEnum) + registerEnumeration(OptionsBag.jsIntlAutoAlwaysEnum) + registerEnumeration(OptionsBag.jsIntlNumeric2DigitEnum) + registerEnumeration(OptionsBag.jsIntlMonthEnum) + registerEnumeration(OptionsBag.jsIntlTimeZoneNameEnum) + registerEnumeration(OptionsBag.jsIntlFormatMatcherEnum) + registerEnumeration(OptionsBag.jsIntlFullLongMediumShort) + registerEnumeration(OptionsBag.jsIntlCollatorUsageEnum) + registerEnumeration(OptionsBag.jsIntlCollationEnum) + registerEnumeration(OptionsBag.jsIntlCaseFirstEnum) + registerEnumeration(OptionsBag.jsIntlCollatorSensitivityEnum) + registerEnumeration(OptionsBag.jsIntlListFormatTypeEnum) + registerEnumeration(OptionsBag.jsIntlNumberFormatStyleEnum) + registerEnumeration(OptionsBag.jsIntlCurrencySystemEnum) + registerEnumeration(OptionsBag.jsIntlCurrencyDisplayEnum) + registerEnumeration(OptionsBag.jsIntlCurrencySignEnum) + registerEnumeration(OptionsBag.jsIntlRoundingPriorityEnum) + registerEnumeration(OptionsBag.jsIntlRoundingModeEnum) + registerEnumeration(OptionsBag.jsIntlTrailingZeroDisplayEnum) + registerEnumeration(OptionsBag.jsIntlNumberFormatNotationEnum) + registerEnumeration(OptionsBag.jsIntlNumberFormatGroupingEnum) + registerEnumeration(OptionsBag.jsIntlSignDisplayEnum) + registerEnumeration(OptionsBag.jsIntlPluralRulesTypeEnum) + registerEnumeration(OptionsBag.jsIntlSegmenterGranularityEnum) + registerEnumeration(OptionsBag.base64Alphabet) + registerEnumeration(OptionsBag.base64LastChunkHandling) + + for enumeration in additionalEnumerations { + assert(enumeration.isEnumeration) + registerEnumeration(enumeration) + } + registerOptionsBag(.jsTemporalDifferenceSettingOrRoundTo) registerOptionsBag(.jsTemporalToStringSettings) registerOptionsBag(.jsTemporalOverflowSettings) @@ -421,6 +497,15 @@ public class JavaScriptEnvironment: ComponentBase { registerOptionsBag(.jsTemporalDurationTotalOfSettings) registerOptionsBag(.toBase64Settings) registerOptionsBag(.fromBase64Settings) + registerOptionsBag(.jsTemporalPlainDateToZDTSettings) + registerOptionsBag(.jsIntlDateTimeFormatSettings) + registerOptionsBag(.jsIntlCollatorSettings) + registerOptionsBag(.jsIntlListFormatSettings) + registerOptionsBag(.jsIntlNumberFormatSettings) + registerOptionsBag(.jsIntlPluralRulesSettings) + registerOptionsBag(.jsIntlRelativeTimeFormatSettings) + registerOptionsBag(.jsIntlSegmenterSettings) + registerOptionsBag(.jsIntlLocaleMatcherSettings) registerTemporalFieldsObject(.jsTemporalPlainTimeLikeObject, forWith: false, dateFields: false, timeFields: true, zonedFields: false) registerTemporalFieldsObject(.jsTemporalPlainDateLikeObject, forWith: false, dateFields: true, timeFields: false, zonedFields: false) @@ -433,6 +518,16 @@ public class JavaScriptEnvironment: ComponentBase { // This isn't a normal "temporal fields object" but is similar, and needs a similar producing generator registerObjectGroup(.jsTemporalDurationLikeObject) addProducingGenerator(forType: ObjectGroup.jsTemporalDurationLikeObject.instanceType, with: { b in b.createTemporalDurationFieldsObject() }) + addNamedStringGenerator(forType: ObjectGroup.jsTemporalTimeZoneLike, + with: { + if Bool.random() { + return ProgramBuilder.randomTimeZoneString() + } else { + return ProgramBuilder.randomUTCOffsetString(mayHaveSeconds: true) + } + }) + addNamedStringGenerator(forType: .jsIntlLocaleLike, with: { ProgramBuilder.constructIntlLocaleString() }) + addNamedStringGenerator(forType: .jsIntlUnit, with: { ProgramBuilder.constructIntlUnit() }) // Temporal types are produced by a large number of methods; which means findOrGenerateType(), when asked to produce // a Temporal type, will tend towards trying to call a method on another Temporal type, which needs more Temporal types, @@ -498,6 +593,7 @@ public class JavaScriptEnvironment: ComponentBase { registerBuiltin("NaN", ofType: .jsNaN) registerBuiltin("Infinity", ofType: .jsInfinity) registerBuiltin("Temporal", ofType: .jsTemporalObject) + registerBuiltin("Intl", ofType: .jsIntlObject) registerBuiltin("WebAssembly", ofType: ObjectGroup.jsWebAssembly.instanceType) for (builtin, type) in additionalBuiltins { @@ -575,9 +671,16 @@ public class JavaScriptEnvironment: ComponentBase { // a lot of these API calls need more Temporal objects, leading to runaway recursion attempting // to generate a large number of Temporal objects. Instead, we bias heavily towards the producing generator. public func addProducingGenerator(forType type: ILType, with generator: @escaping EnvironmentValueGenerator, probability: Double = 1.0) { + assert(type.Is(.object()), "Producing generators can only be registered for objects, found \(type)") producingGenerators[type.group!] = (generator, probability) } + // Register a generator for a custom named string. + public func addNamedStringGenerator(forType type: ILType, with generator: @escaping () -> String) { + assert(type.Is(.string), "Named string generators can only be registered for strings, found \(type)") + namedStringGenerators[type.group!] = generator + } + private func addProducingMethod(forType type: ILType, by method: String, on group: String) { if producingMethods[type] == nil { producingMethods[type] = [] @@ -599,13 +702,20 @@ public class JavaScriptEnvironment: ComponentBase { return actualType } + public func registerEnumeration(_ type: ILType) { + assert(type.isEnumeration) + let group = type.group! + assert(enums[group] == nil, "Registered duplicate enum \(group)") + enums[group] = type + } + public func registerObjectGroup(_ group: ObjectGroup) { - assert(groups[group.name] == nil) + assert(groups[group.name] == nil, "Registered duplicate enum \(group.name)") groups[group.name] = group builtinProperties.formUnion(group.properties.keys) builtinMethods.formUnion(group.methods.keys) - // + //func register // Step 1: Initialize `subtypes` // subtypes[group.instanceType] = [group.instanceType] @@ -683,6 +793,13 @@ public class JavaScriptEnvironment: ComponentBase { public func registerOptionsBag(_ bag: OptionsBag) { registerObjectGroup(bag.group) + + for property in bag.properties.values { + if property.isEnumeration { + assert(enums[property.group!] != nil, "Enum \(property.group!) used in options bag but not registered on the JavaScriptEnvironment") + } + } + addProducingGenerator(forType: bag.group.instanceType, with: { b in b.createOptionsBag(bag) }) } @@ -737,6 +854,10 @@ public class JavaScriptEnvironment: ComponentBase { return [.forUnknownFunction] } + public func getEnum(ofName name: String) -> ILType? { + return enums[name] + } + public func getProducingMethods(ofType type: ILType) -> [(group: String, method: String)] { guard let array = producingMethods[type] else { return [] @@ -750,6 +871,13 @@ public class JavaScriptEnvironment: ComponentBase { type.group.flatMap {producingGenerators[$0]} } + // For named strings, get a generator that is registered as being able to produce this + // named string. + public func getNamedStringGenerator(ofName name: String) -> (() -> String)? { + namedStringGenerators[name] + } + + // If the object group refers to a constructor, get its path. public func getPathIfConstructor(ofGroup groupName: String) -> [String]? { guard let group = groups[groupName] else { @@ -837,7 +965,12 @@ public struct OptionsBag { self.properties = properties let properties = properties.mapValues { // This list can be expanded over time as long as createOptionsBag() supports this - assert($0.isEnumeration || $0.Is(.number | .integer | .boolean) || $0.Is(OptionsBag.jsTemporalRelativeTo), "Found unsupported option type \($0) in options bag \(name)") + assert($0.isEnumeration || $0.Is(.number | .integer | .boolean) || + // Has a producing generator registered + $0.Is(.jsTemporalPlainTime) || + // Has explicit support in createOptionsBag + $0.Is(OptionsBag.jsTemporalRelativeTo), + "Found unsupported option type \($0) in options bag \(name)") return $0 | .undefined; } self.group = ObjectGroup(name: name, instanceType: nil, properties: properties, overloads: [:]) @@ -1166,8 +1299,17 @@ public extension ObjectGroup { // so Fuzzilli can generate sequences like `Date.prototype.getTime.call(new Date())`. static func createPrototypeObjectGroup(_ receiver: ObjectGroup) -> ObjectGroup { let name = receiver.name + ".prototype" - let properties = Dictionary(uniqueKeysWithValues: receiver.methods.map { + var properties = Dictionary(uniqueKeysWithValues: receiver.methods.map { ($0.0, ILType.unboundFunction($0.1.first, receiver: receiver.instanceType)) }) + // These functions are getters instead of regular functions, and error when called + // on the prototype. We hide them from the prototype object to avoid + // generating `let v0 = Intl.DateTimeFormat.prototype.format`. + // https://tc39.es/ecma402/#sec-intl.datetimeformat.prototype.format + if receiver.name == "Intl.DateTimeFormat" || receiver.name == "Intl.NumberFormat" { + properties.removeValue(forKey: "format") + } else if receiver.name == "Intl.Collator" { + properties.removeValue(forKey: "compare") + } return ObjectGroup( name: name, instanceType: .object(ofGroup: name, withProperties: properties.map {$0.0}, withMethods: []), @@ -2199,6 +2341,13 @@ public extension ObjectGroup { return possibleParams.map { [.plain($0)] => .boolean } } + private static func temporalTimeZoneLikeSignature(signature: (ILType) -> Signature) -> [Signature] { + return [ + signature(jsTemporalTimeZoneLike), + signature(.jsTemporalZonedDateTime), + ] + } + /// Object group modelling the JavaScript Temporal builtin static let jsTemporalObject = ObjectGroup( name: "Temporal", @@ -2221,14 +2370,13 @@ public extension ObjectGroup { name: "Temporal.Now", instanceType: .jsTemporalNow, properties: [:], - methods: [ - "timeZoneId": [] => .string, - "instant": [] => .jsTemporalInstant, - // TODO(manishearth, 439921647) Potentially hint to the generator that these are timezone-like - "plainDateTimeISO": [.opt(.string)] => .jsTemporalPlainDateTime, - "zonedDateTimeISO": [.opt(.string)] => .jsTemporalZonedDateTime, - "plainDateISO": [.opt(.string)] => .jsTemporalPlainDate, - "plainTimeISO": [.opt(.string)] => .jsTemporalPlainTime, + overloads: [ + "timeZoneId": [[] => .string], + "instant": [[] => .jsTemporalInstant], + "plainDateTimeISO": temporalTimeZoneLikeSignature { [.opt($0)] => .jsTemporalPlainDateTime }, + "zonedDateTimeISO": temporalTimeZoneLikeSignature { [.opt($0)] => .jsTemporalZonedDateTime }, + "plainDateISO": temporalTimeZoneLikeSignature { [.opt($0)] => .jsTemporalPlainDate }, + "plainTimeISO": temporalTimeZoneLikeSignature { [.opt($0)] => .jsTemporalPlainTime }, ] ) @@ -2250,7 +2398,7 @@ public extension ObjectGroup { "toString": [[.opt(jsTemporalToStringSettings)] => .string], "toJSON": [[] => .string], "toLocaleString": [[.opt(.string), .opt(jsTemporalToLocaleStringSettings)] => .string], - "toZonedDateTimeISO": [[jsTemporalTimeZoneLike] => .jsTemporalZonedDateTime], + "toZonedDateTimeISO": temporalTimeZoneLikeSignature { [.plain($0)] => .jsTemporalZonedDateTime }, ] ) @@ -2462,7 +2610,9 @@ public extension ObjectGroup { "since": temporalUntilSinceSignature(possibleParams: jsTemporalPlainDateLikeParameters), "equals": temporalEqualsSignature(possibleParams: jsTemporalPlainDateLikeParameters), "toPlainDateTime": jsTemporalPlainTimeLikeParameters.map {[.opt($0)] => .jsTemporalPlainDateTime}, - "toZonedDateTime": [[.jsAnything] => .jsAnything], + "toZonedDateTime": temporalTimeZoneLikeSignature { [.plain($0)] => .jsTemporalZonedDateTime } + // Can also accept an object with timezone/time fields + + [[.plain(OptionsBag.jsTemporalPlainDateToZDTSettings.group.instanceType)] => .jsTemporalZonedDateTime], "toString": [[.opt(jsTemporalToStringSettings)] => .string], "toJSON": [[] => .string], "toLocaleString": [[.opt(.string), .opt(jsTemporalToLocaleStringSettings)] => .string], @@ -2502,7 +2652,7 @@ public extension ObjectGroup { "toString": [[.opt(jsTemporalToStringSettings)] => .string], "toJSON": [[] => .string], "toLocaleString": [[.opt(.string), .opt(jsTemporalToLocaleStringSettings)] => .string], - "toZonedDateTime": [[.string, .opt(OptionsBag.jsTemporalZonedInterpretationSettings.group.instanceType)] => .jsTemporalZonedDateTime], + "toZonedDateTime": temporalTimeZoneLikeSignature { [.plain($0), .opt(OptionsBag.jsTemporalZonedInterpretationSettings.group.instanceType)] => .jsTemporalZonedDateTime }, "toPlainDate": [[] => .jsTemporalPlainDate], "toPlainTime": [[] => .jsTemporalPlainTime], ] @@ -2687,7 +2837,7 @@ public extension ObjectGroup { // Stringy objects // TODO(manishearth, 439921647) support stringy objects - fileprivate static let jsTemporalTimeZoneLike = Parameter.string + fileprivate static let jsTemporalTimeZoneLike = ILType.namedString(ofName: "TemporalTimeZoneString") // Options objects fileprivate static let jsTemporalDifferenceSettings = OptionsBag.jsTemporalDifferenceSettingOrRoundTo.group.instanceType @@ -2781,6 +2931,456 @@ extension OptionsBag { "relativeTo": jsTemporalRelativeTo, ] ) + + static let jsTemporalPlainDateToZDTSettings = OptionsBag( + name: "TemporalPlainDateToZDTSettings", + properties: [ + "timeZone": ObjectGroup.jsTemporalTimeZoneLike, + "plainTime": .jsTemporalPlainTime, + ] + ) +} + +// Intl +extension ILType { + // Intl types + static let jsIntlObject = ILType.object(ofGroup: "Intl", withProperties: ["DateTimeFormat", "Collator", "ListFormat", "NumberFormat", "PluralRules", "RelativeTimeFormat", "Segmenter"], withMethods: ["getCanonicalLocales", "supportedValuesOf"]) + + static let jsIntlCollator = ILType.object(ofGroup: "Intl.Collator", withProperties: [], withMethods: ["compare", "resolvedOptions"]) + static let jsIntlCollatorConstructor = ILType.functionAndConstructor([.opt(.jsIntlLocaleLike), .opt(OptionsBag.jsIntlCollatorSettings.group.instanceType)] => .jsIntlCollator) + .object(ofGroup: "IntlCollatorConstructor", withProperties: ["prototype"], withMethods: ["supportedLocalesOf"]) + + static let jsIntlDateTimeFormat = ILType.object(ofGroup: "Intl.DateTimeFormat", withProperties: [], withMethods: ["format", "formatRange", "formatRangeToParts", "formatToParts", "resolvedOptions"]) + static let jsIntlDateTimeFormatConstructor = ILType.functionAndConstructor([.opt(.jsIntlLocaleLike), .opt(OptionsBag.jsIntlDateTimeFormatSettings.group.instanceType)] => .jsIntlDateTimeFormat) + .object(ofGroup: "IntlDateTimeFormatConstructor", withProperties: ["prototype"], withMethods: ["supportedLocalesOf"]) + + static let jsIntlListFormat = ILType.object(ofGroup: "Intl.ListFormat", withProperties: [], withMethods: ["format", "formatToParts", "resolvedOptions"]) + static let jsIntlListFormatConstructor = ILType.functionAndConstructor([.opt(.jsIntlLocaleLike), .opt(OptionsBag.jsIntlListFormatSettings.group.instanceType)] => .jsIntlListFormat) + .object(ofGroup: "IntlListFormatConstructor", withProperties: ["prototype"], withMethods: ["supportedLocalesOf"]) + + static let jsIntlNumberFormat = ILType.object(ofGroup: "Intl.NumberFormat", withProperties: [], withMethods: ["format", "formatRange", "formatRangeToParts", "formatToParts", "resolvedOptions"]) + static let jsIntlNumberFormatConstructor = ILType.functionAndConstructor([.opt(.jsIntlLocaleLike), .opt(OptionsBag.jsIntlNumberFormatSettings.group.instanceType)] => .jsIntlNumberFormat) + .object(ofGroup: "IntlNumberFormatConstructor", withProperties: ["prototype"], withMethods: ["supportedLocalesOf"]) + + static let jsIntlPluralRules = ILType.object(ofGroup: "Intl.PluralRules", withProperties: [], withMethods: ["select", "selectRange", "resolvedOptions"]) + static let jsIntlPluralRulesConstructor = ILType.functionAndConstructor([.opt(.jsIntlLocaleLike), .opt(OptionsBag.jsIntlPluralRulesSettings.group.instanceType)] => .jsIntlPluralRules) + .object(ofGroup: "IntlPluralRulesConstructor", withProperties: ["prototype"], withMethods: ["supportedLocalesOf"]) + + static let jsIntlRelativeTimeFormat = ILType.object(ofGroup: "Intl.RelativeTimeFormat", withProperties: [], withMethods: ["format", "formatToParts", "resolvedOptions"]) + static let jsIntlRelativeTimeFormatConstructor = ILType.functionAndConstructor([.opt(.jsIntlLocaleLike), .opt(OptionsBag.jsIntlRelativeTimeFormatSettings.group.instanceType)] => .jsIntlRelativeTimeFormat) + .object(ofGroup: "IntlRelativeTimeFormatConstructor", withProperties: ["prototype"], withMethods: ["supportedLocalesOf"]) + + static let jsIntlSegmenter = ILType.object(ofGroup: "Intl.Segmenter", withProperties: [], withMethods: ["segment", "resolvedOptions"]) + static let jsIntlSegmenterConstructor = ILType.functionAndConstructor([.opt(.jsIntlLocaleLike), .opt(OptionsBag.jsIntlSegmenterSettings.group.instanceType)] => .jsIntlSegmenter) + .object(ofGroup: "IntlSegmenterConstructor", withProperties: ["prototype"], withMethods: ["supportedLocalesOf"]) + + static let jsIntlSegmenterSegments = ILType.object(ofGroup: "IntlSegmenterSegments", withProperties: [], withMethods: ["containing"]) + + static let jsIntlLocaleLike = ILType.namedString(ofName: "IntlLocaleString") + static let jsIntlUnit = ILType.namedString(ofName: "IntlUnitString") +} + +extension ObjectGroup { + static let jsIntlSupportedValuesEnum = ILType.enumeration(ofName: "IntlSupportedValues", withValues: ["calendar", "collation", "currency", "numberingSystem", "timeZone"]) + + static let jsIntlObject = ObjectGroup( + name: "Intl", + instanceType: .jsIntlObject, + properties: [ + "Collator" : .jsIntlCollatorConstructor, + "DateTimeFormat" : .jsIntlDateTimeFormatConstructor, + "ListFormat" : .jsIntlListFormatConstructor, + "NumberFormat" : .jsIntlNumberFormatConstructor, + "PluralRules" : .jsIntlPluralRulesConstructor, + "RelativeTimeFormat" : .jsIntlRelativeTimeFormatConstructor, + "Segmenter" : .jsIntlSegmenterConstructor, + ], + methods: [ + "getCanonicalLocales": [] => .jsArray, + "supportedValuesOf": [.plain(jsIntlSupportedValuesEnum)] => .jsArray, + ] + ) + + static let jsIntlCollator = ObjectGroup( + name: "Intl.Collator", + instanceType: .jsIntlCollator, + properties: [:], + methods: [ + "compare": [.string, .string] => .integer, + "resolvedOptions": [] => .object(), + ] + ) + + static let jsIntlCollatorPrototype = createPrototypeObjectGroup(jsIntlCollator) + + static let jsIntlCollatorConstructor = ObjectGroup( + name: "IntlCollatorConstructor", + constructorPath: "Intl.Collator", + instanceType: .jsIntlCollatorConstructor, + properties: [ + "prototype" : jsIntlCollatorPrototype.instanceType + ], + methods: [ + // TODO(manishearth) this also accepts arrays of locale-likes + "supportedLocalesOf": [.opt(.jsIntlLocaleLike), .opt(OptionsBag.jsIntlLocaleMatcherSettings.group.instanceType)] => .jsArray, + ] + ) + + fileprivate static func dateTimeFormatSignature(_ returnType: ILType) -> [Signature] { + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/format#date + // No ZonedDateTime as stated in the docs. + [ILType.jsDate, .jsTemporalPlainDate, .jsTemporalPlainTime, .jsTemporalPlainMonthDay, .jsTemporalPlainYearMonth, .jsTemporalPlainDateTime].map { + [.opt($0)] => returnType + } + } + fileprivate static func dateTimeFormatRangeSignature(_ returnType: ILType) -> [Signature] { + // Docs list all Temporal types except ZDT, but we don't wish to explode the signatures so we just pick + // the two main relevant ones. + [ILType.jsDate, .jsTemporalPlainDateTime].flatMap { firstParam in + [ILType.jsDate, .jsTemporalPlainDateTime].map { + [.opt(firstParam), .opt($0)] => returnType + } + } + } + static let jsIntlDateTimeFormat = ObjectGroup( + name: "Intl.DateTimeFormat", + instanceType: .jsIntlDateTimeFormat, + properties: [:], + overloads: [ + "format": dateTimeFormatSignature(.string), + "formatRange": dateTimeFormatRangeSignature(.string), + "formatRangeToParts": dateTimeFormatRangeSignature(.jsArray), + "formatToParts": dateTimeFormatSignature(.jsArray), + "resolvedOptions": [[] => .object()], + ] + ) + + static let jsIntlDateTimeFormatPrototype = createPrototypeObjectGroup(jsIntlDateTimeFormat) + + static let jsIntlDateTimeFormatConstructor = ObjectGroup( + name: "IntlDateTimeFormatConstructor", + constructorPath: "Intl.DateTimeFormat", + instanceType: .jsIntlDateTimeFormatConstructor, + properties: [ + "prototype" : jsIntlDateTimeFormatPrototype.instanceType + ], + methods: [ + // TODO(manishearth) this also accepts arrays of locale-likes + "supportedLocalesOf": [.opt(.jsIntlLocaleLike), .opt(OptionsBag.jsIntlLocaleMatcherSettings.group.instanceType)] => .jsArray, + ] + ) + + static let jsIntlListFormat = ObjectGroup( + name: "Intl.ListFormat", + instanceType: .jsIntlListFormat, + properties: [:], + methods: [ + "format": [.plain(.jsArray)] => .string, + "formatToParts": [.plain(.jsArray)] => .jsArray, + "resolvedOptions": [] => .object(), + ] + ) + + static let jsIntlListFormatPrototype = createPrototypeObjectGroup(jsIntlListFormat) + + static let jsIntlListFormatConstructor = ObjectGroup( + name: "IntlListFormatConstructor", + constructorPath: "Intl.ListFormat", + instanceType: .jsIntlListFormatConstructor, + properties: [ + "prototype" : jsIntlListFormatPrototype.instanceType + ], + methods: [ + // TODO(manishearth) this also accepts arrays of locale-likes + "supportedLocalesOf": [.opt(.jsIntlLocaleLike), .opt(OptionsBag.jsIntlLocaleMatcherSettings.group.instanceType)] => .jsArray, + ] + ) + + fileprivate static func numberFormatSignature(_ returnType: ILType) -> [Signature] { + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/format + [ILType.number, .bigint, .string].map { + [.opt($0)] => returnType + } + } + fileprivate static func numberFormatRangeSignature(_ returnType: ILType) -> [Signature] { + [ILType.number, .bigint, .string].flatMap { firstParam in + [ILType.number, .bigint, .string].map { + [.opt(firstParam), .opt($0)] => returnType + } + } + } + + static let jsIntlNumberFormat = ObjectGroup( + name: "Intl.NumberFormat", + instanceType: .jsIntlNumberFormat, + properties: [:], + overloads: [ + "format": numberFormatSignature(.string), + "formatRange": numberFormatRangeSignature(.string), + "formatRangeToParts": numberFormatRangeSignature(.jsArray), + "formatToParts": numberFormatSignature(.jsArray), + "resolvedOptions": [[] => .object()], + ] + ) + + static let jsIntlNumberFormatPrototype = createPrototypeObjectGroup(jsIntlNumberFormat) + + static let jsIntlNumberFormatConstructor = ObjectGroup( + name: "IntlNumberFormatConstructor", + constructorPath: "Intl.NumberFormat", + instanceType: .jsIntlNumberFormatConstructor, + properties: [ + "prototype" : jsIntlNumberFormatPrototype.instanceType + ], + methods: [ + // TODO(manishearth) this also accepts arrays of locale-likes + "supportedLocalesOf": [.opt(.jsIntlLocaleLike), .opt(OptionsBag.jsIntlLocaleMatcherSettings.group.instanceType)] => .jsArray, + ] + ) + + static let jsIntlPluralRules = ObjectGroup( + name: "Intl.PluralRules", + instanceType: .jsIntlPluralRules, + properties: [:], + methods: [ + "select": [.number] => .string, + "selectRange": [.number, .number] => .string, + "resolvedOptions": [] => .object(), + ] + ) + + static let jsIntlPluralRulesPrototype = createPrototypeObjectGroup(jsIntlPluralRules) + + static let jsIntlPluralRulesConstructor = ObjectGroup( + name: "IntlPluralRulesConstructor", + constructorPath: "Intl.PluralRules", + instanceType: .jsIntlPluralRulesConstructor, + properties: [ + "prototype" : jsIntlPluralRulesPrototype.instanceType + ], + methods: [ + // TODO(manishearth) this also accepts arrays of locale-likes + "supportedLocalesOf": [.opt(.jsIntlLocaleLike), .opt(OptionsBag.jsIntlLocaleMatcherSettings.group.instanceType)] => .jsArray, + ] + ) + + static let jsIntlRelativeTimeFormatUnitEnum = ILType.enumeration(ofName: "IntlRelativeTimeFormatUnit", withValues: ["year", "quarter", "month", "week", "day", "hour", "minute", "second"]) + + static let jsIntlRelativeTimeFormat = ObjectGroup( + name: "Intl.RelativeTimeFormat", + instanceType: .jsIntlRelativeTimeFormat, + properties: [:], + methods: [ + "format": [.number, .plain(jsIntlRelativeTimeFormatUnitEnum)] => .string, + "formatToParts": [.number, .plain(jsIntlRelativeTimeFormatUnitEnum)] => .jsArray, + "resolvedOptions": [] => .object(), + ] + ) + + static let jsIntlRelativeTimeFormatPrototype = createPrototypeObjectGroup(jsIntlRelativeTimeFormat) + + static let jsIntlRelativeTimeFormatConstructor = ObjectGroup( + name: "IntlRelativeTimeFormatConstructor", + constructorPath: "Intl.RelativeTimeFormat", + instanceType: .jsIntlRelativeTimeFormatConstructor, + properties: [ + "prototype" : jsIntlRelativeTimeFormatPrototype.instanceType + ], + methods: [ + // TODO(manishearth) this also accepts arrays of locale-likes + "supportedLocalesOf": [.opt(.jsIntlLocaleLike), .opt(OptionsBag.jsIntlLocaleMatcherSettings.group.instanceType)] => .jsArray, + ] + ) + + static let jsIntlSegmenter = ObjectGroup( + name: "Intl.Segmenter", + instanceType: .jsIntlSegmenter, + properties: [:], + methods: [ + "segment": [.string] => .jsIntlSegmenterSegments, + "resolvedOptions": [] => .object(), + ] + ) + + static let jsIntlSegmenterPrototype = createPrototypeObjectGroup(jsIntlSegmenter) + + static let jsIntlSegmenterConstructor = ObjectGroup( + name: "IntlSegmenterConstructor", + constructorPath: "Intl.Segmenter", + instanceType: .jsIntlSegmenterConstructor, + properties: [ + "prototype" : jsIntlSegmenterPrototype.instanceType + ], + methods: [ + // TODO(manishearth) this also accepts arrays of locale-likes + "supportedLocalesOf": [.opt(.jsIntlLocaleLike), .opt(OptionsBag.jsIntlLocaleMatcherSettings.group.instanceType)] => .jsArray, + ] + ) + static let jsIntlSegmenterSegments = ObjectGroup( + name: "IntlSegmenterSegments", + instanceType: .jsIntlSegmenterSegments, + properties: [:], + methods: [ + "containing": [.number] => .object(), + ] + ) +} + +extension OptionsBag { + fileprivate static let jsIntlLocaleMatcherEnum = ILType.enumeration(ofName: "IntlLocaleMatcher", withValues: ["lookup", "best fit"]) + fileprivate static let jsIntlNumberingSystemEnum = ILType.enumeration(ofName: "IntlNumberingSystem", withValues: Locale.NumberingSystem.availableNumberingSystems.map { $0.identifier }) + fileprivate static let jsIntlHourCycleEnum = ILType.enumeration(ofName: "IntlHourCycle", withValues: ["h11", "h12", "h23", "h24"]) + fileprivate static let jsIntlLongShortNarrowEnum = ILType.enumeration(ofName: "IntlLongShortNarrow", withValues: ["long", "short", "narrow"]) + fileprivate static let jsIntlLongShortEnum = ILType.enumeration(ofName: "IntlLongShort", withValues: ["long", "short"]) + fileprivate static let jsIntlAutoAlwaysEnum = ILType.enumeration(ofName: "IntlAutoAlways", withValues: ["auto", "always"]) + fileprivate static let jsIntlNumeric2DigitEnum = ILType.enumeration(ofName: "IntlNumeric2Digit", withValues: ["numeric", "2-digit"]) + fileprivate static let jsIntlMonthEnum = ILType.enumeration(ofName: "IntlMonth", withValues: ["numeric", "2-digit", "long", "short", "narrow"]) + fileprivate static let jsIntlTimeZoneNameEnum = ILType.enumeration(ofName: "IntlTimeZoneName", withValues: ["long", "short", "shortOffset", "longOffset", "shortGeneric", "longGeneric"]) + fileprivate static let jsIntlFormatMatcherEnum = ILType.enumeration(ofName: "IntlFormatMatcher", withValues: ["basic", "best fit"]) + fileprivate static let jsIntlFullLongMediumShort = ILType.enumeration(ofName: "IntlFullLongMediumShort", withValues: ["full", "long", "medium", "short"]) + fileprivate static let jsIntlCollatorUsageEnum = ILType.enumeration(ofName: "IntlCollatorUsage", withValues: ["sort", "search"]) + fileprivate static let jsIntlCollationEnum = ILType.enumeration(ofName: "IntlCollation", withValues: ["emoji", "pinyin", "stroke"]) + fileprivate static let jsIntlCaseFirstEnum = ILType.enumeration(ofName: "IntlCaseFirst", withValues: ["upper", "lower", "false"]) + fileprivate static let jsIntlCollatorSensitivityEnum = ILType.enumeration(ofName: "IntlCollatorSensitivity", withValues: ["base", "accent", "case", "variant"]) + fileprivate static let jsIntlListFormatTypeEnum = ILType.enumeration(ofName: "IntlListFormatTypeEnum", withValues: ["conjunction", "disjunction", "unit"]) + fileprivate static let jsIntlNumberFormatStyleEnum = ILType.enumeration(ofName: "IntlNumberFormatStyleEnum", withValues: ["decimal", "currency", "percent", "unit"]) + fileprivate static let jsIntlCurrencySystemEnum = ILType.enumeration(ofName: "IntlCurrency", withValues: Locale.Currency.isoCurrencies.map { $0.identifier }) + fileprivate static let jsIntlCurrencyDisplayEnum = ILType.enumeration(ofName: "IntlCurrencyDisplayEnum", withValues: ["code", "symbol", "narrowSymbol", "name"]) + fileprivate static let jsIntlCurrencySignEnum = ILType.enumeration(ofName: "IntlCurrencySignEnum", withValues: ["standard", "accounting"]) + fileprivate static let jsIntlRoundingPriorityEnum = ILType.enumeration(ofName: "IntlRoundingPriority", withValues: ["auto", "morePrecision", "lessPrecision"]) + fileprivate static let jsIntlRoundingModeEnum = ILType.enumeration(ofName: "IntlRoundingMode", withValues: ["ceil", "floor", "expand", "trunc", "halfCeil", "halfFloor", "halfExpand", "halfTrunc", "halfEven"]) + fileprivate static let jsIntlTrailingZeroDisplayEnum = ILType.enumeration(ofName: "IntlTrailingZeroDisplay", withValues: ["auto", "stripIfInteger"]) + fileprivate static let jsIntlNumberFormatNotationEnum = ILType.enumeration(ofName: "IntlNumberFormatNotation", withValues: ["standard", "scientific", "engineering", "compact"]) + fileprivate static let jsIntlNumberFormatGroupingEnum = ILType.enumeration(ofName: "IntlNumberFormatGrouping", withValues: ["always", "auto", "min2", "true", "false"]) + fileprivate static let jsIntlSignDisplayEnum = ILType.enumeration(ofName: "IntlSignDisplay", withValues: ["auto", "always", "exceptZero", "negative", "never"]) + fileprivate static let jsIntlPluralRulesTypeEnum = ILType.enumeration(ofName: "IntlPluralRulesTypeEnum", withValues: ["cardinal", "ordinal"]) + fileprivate static let jsIntlSegmenterGranularityEnum = ILType.enumeration(ofName: "IntlSegmenterGranularityEnum", withValues: ["grapheme", "word", "sentence"]) + + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#parameters + static let jsIntlDateTimeFormatSettings = OptionsBag( + name: "IntlLocaleSettings", + properties: [ + // Locale options + "localeMatcher": jsIntlLocaleMatcherEnum, + "calendar": .jsTemporalCalendarEnum, + "numberingSystem": jsIntlNumberingSystemEnum, + "hour12": .boolean, + "hourCycle": jsIntlHourCycleEnum, + "timeZone": ObjectGroup.jsTemporalTimeZoneLike, + // datetime options + "weekday": jsIntlLongShortNarrowEnum, + "era": jsIntlLongShortNarrowEnum, + "year": jsIntlNumeric2DigitEnum, + "month": jsIntlMonthEnum, + "day": jsIntlNumeric2DigitEnum, + "dayPeriod": jsIntlLongShortNarrowEnum, + "hour": jsIntlNumeric2DigitEnum, + "minute": jsIntlNumeric2DigitEnum, + "second": jsIntlNumeric2DigitEnum, + "fractionalSecondDigits": .integer, // Technically only 1-3 + "timeZoneName": jsIntlTimeZoneNameEnum, + "formatMatcher": jsIntlFormatMatcherEnum, + // style options + "dateStyle": jsIntlFullLongMediumShort, + "timeStyle": jsIntlFullLongMediumShort, + ] + ) + + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Collator/Collator#options + static let jsIntlCollatorSettings = OptionsBag( + name: "IntlCollatorSettings", + properties: [ + "usage": jsIntlCollatorUsageEnum, + "localeMatcher": jsIntlLocaleMatcherEnum, + "collation": jsIntlCollationEnum, + "numeric": .boolean, + "caseFirst": jsIntlCaseFirstEnum, + "sensitivity": jsIntlCollatorSensitivityEnum, + "ignorePunctuation": .boolean, + ] + ) + + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/ListFormat/ListFormat#options + static let jsIntlListFormatSettings = OptionsBag( + name: "IntlListFormatSettings", + properties: [ + "localeMatcher": jsIntlLocaleMatcherEnum, + "type": jsIntlListFormatTypeEnum, + "style": jsIntlLongShortNarrowEnum, + ] + ) + + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat#options + static let jsIntlNumberFormatSettings = OptionsBag( + name: "IntlNumberFormatSettings", + properties: [ + // locale options + "localeMatcher": jsIntlLocaleMatcherEnum, + "numberingSystem": jsIntlNumberingSystemEnum, + // style options + "style": jsIntlNumberFormatStyleEnum, + "currency": jsIntlCurrencySystemEnum, + "currencyDisplay": jsIntlCurrencyDisplayEnum, + "currencySign": jsIntlCurrencySignEnum, + "unit": .jsIntlUnit, + "unitDisplay": jsIntlLongShortNarrowEnum, + // digit options + "minimumIntegerDigits": .integer, + "minimumFractionDigits": .integer, + "maximumFractionDigits": .integer, + "minimumSignificantDigits": .integer, + "maximumSignificantDigits": .integer, + "roundingPriority": jsIntlRoundingPriorityEnum, + "roundingIncrement": .integer, + "roundingMode": jsIntlRoundingModeEnum, + "trailingZeroDisplay": jsIntlTrailingZeroDisplayEnum, + // other options + "notation": jsIntlNumberFormatNotationEnum, + "compactDisplay": jsIntlLongShortEnum, + "useGrouping": jsIntlNumberFormatGroupingEnum, + "signDisplay": jsIntlSignDisplayEnum, + ] + ) + + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/PluralRules/PluralRules#options + static let jsIntlPluralRulesSettings = OptionsBag( + name: "IntlPluralRulesSettings", + properties: [ + "localeMatcher": jsIntlLocaleMatcherEnum, + "type": jsIntlPluralRulesTypeEnum, + + // NumberFormat digit options + "minimumIntegerDigits": .integer, + "minimumFractionDigits": .integer, + "maximumFractionDigits": .integer, + "minimumSignificantDigits": .integer, + "maximumSignificantDigits": .integer, + "roundingPriority": jsIntlRoundingPriorityEnum, + "roundingIncrement": .integer, + "roundingMode": jsIntlRoundingModeEnum, + + ] + ) + + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/RelativeTimeFormat/RelativeTimeFormat#options + static let jsIntlRelativeTimeFormatSettings = OptionsBag( + name: "IntlRelativeTimeFormatSettings", + properties: [ + "localeMatcher": jsIntlLocaleMatcherEnum, + "numberingSystem": jsIntlNumberingSystemEnum, + "style": jsIntlLongShortNarrowEnum, + "numeric": jsIntlAutoAlwaysEnum, + ] + ) + + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Segmenter/Segmenter#options + static let jsIntlSegmenterSettings = OptionsBag( + name: "IntlSegmenterSettings", + properties: [ + "localeMatcher": jsIntlLocaleMatcherEnum, + "granularity": jsIntlSegmenterGranularityEnum, + ] + ) + + static let jsIntlLocaleMatcherSettings = OptionsBag( + name: "IntlLocaleMatcherSettings", + properties: [ + "localeMatcher": jsIntlLocaleMatcherEnum, + ] + ) } // Base64 diff --git a/Sources/Fuzzilli/FuzzIL/Context.swift b/Sources/Fuzzilli/FuzzIL/Context.swift index 84d582fac..550610934 100644 --- a/Sources/Fuzzilli/FuzzIL/Context.swift +++ b/Sources/Fuzzilli/FuzzIL/Context.swift @@ -22,14 +22,12 @@ public struct Context: OptionSet, Hashable, CaseIterable { .method, .classMethod, .loop, - .with, .objectLiteral, .classDefinition, .switchBlock, .switchCase, .wasm, .wasmFunction, - .wasmBlock, .wasmTypeGroup, .empty, ] @@ -59,34 +57,24 @@ public struct Context: OptionSet, Hashable, CaseIterable { public static let classMethod = Context(rawValue: 1 << 5) // Inside a loop. public static let loop = Context(rawValue: 1 << 6) - // Inside a with statement. - public static let with = Context(rawValue: 1 << 7) // Inside an object literal. - public static let objectLiteral = Context(rawValue: 1 << 8) + public static let objectLiteral = Context(rawValue: 1 << 7) // Inside a class definition. - public static let classDefinition = Context(rawValue: 1 << 9) + public static let classDefinition = Context(rawValue: 1 << 8) // Inside a switch block. - public static let switchBlock = Context(rawValue: 1 << 10) + public static let switchBlock = Context(rawValue: 1 << 9) // Inside a switch case. - public static let switchCase = Context(rawValue: 1 << 11) + public static let switchCase = Context(rawValue: 1 << 10) // Inside a wasm module - public static let wasm = Context(rawValue: 1 << 12) + public static let wasm = Context(rawValue: 1 << 11) // Inside a function in a wasm module - public static let wasmFunction = Context(rawValue: 1 << 13) - // Inside a block of a wasm function, allows branches - public static let wasmBlock = Context(rawValue: 1 << 14) + public static let wasmFunction = Context(rawValue: 1 << 12) // Inside a wasm recursive type group definition. - public static let wasmTypeGroup = Context(rawValue: 1 << 15) + public static let wasmTypeGroup = Context(rawValue: 1 << 13) public static let empty = Context([]) - - // These contexts have ValueGenerators and as such we can .buildPrefix in them. - public var isValueBuildableContext: Bool { - self.contains(.wasmFunction) || self.contains(.javascript) - } - + public var inWasm: Bool { - // .wasmBlock is propagating surrounding context self.contains(.wasm) || self.contains(.wasmFunction) } } @@ -115,9 +103,6 @@ extension Context: CustomStringConvertible { if self.contains(.loop) { strings.append(".loop") } - if self.contains(.with) { - strings.append(".with") - } if self.contains(.objectLiteral) { strings.append(".objectLiteral") } @@ -136,9 +121,6 @@ extension Context: CustomStringConvertible { if self.contains(.wasmFunction) { strings.append(".wasmFunction") } - if self.contains(.wasmBlock) { - strings.append(".wasmBlock") - } if self.contains(.wasmTypeGroup) { strings.append(".wasmTypeGroup") } diff --git a/Sources/Fuzzilli/FuzzIL/Instruction.swift b/Sources/Fuzzilli/FuzzIL/Instruction.swift index bb0a72e95..ee017f20a 100644 --- a/Sources/Fuzzilli/FuzzIL/Instruction.swift +++ b/Sources/Fuzzilli/FuzzIL/Instruction.swift @@ -549,7 +549,12 @@ extension Instruction: ProtobufConvertible { case .loadFloat(let op): $0.loadFloat = Fuzzilli_Protobuf_LoadFloat.with { $0.value = op.value } case .loadString(let op): - $0.loadString = Fuzzilli_Protobuf_LoadString.with { $0.value = op.value } + $0.loadString = Fuzzilli_Protobuf_LoadString.with { + $0.value = op.value; + if let customName = op.customName { + $0.customName = customName + } + } case .loadBoolean(let op): $0.loadBoolean = Fuzzilli_Protobuf_LoadBoolean.with { $0.value = op.value } case .loadUndefined: @@ -627,6 +632,12 @@ extension Instruction: ProtobufConvertible { } case .endClassInstanceMethod: $0.endClassInstanceMethod = Fuzzilli_Protobuf_EndClassInstanceMethod() + case .beginClassInstanceComputedMethod(let op): + $0.beginClassInstanceComputedMethod = Fuzzilli_Protobuf_BeginClassInstanceComputedMethod.with { + $0.parameters = convertParameters(op.parameters) + } + case .endClassInstanceComputedMethod: + $0.endClassInstanceComputedMethod = Fuzzilli_Protobuf_EndClassInstanceComputedMethod() case .beginClassInstanceGetter(let op): $0.beginClassInstanceGetter = Fuzzilli_Protobuf_BeginClassInstanceGetter.with { $0.propertyName = op.propertyName } case .endClassInstanceGetter: @@ -658,6 +669,12 @@ extension Instruction: ProtobufConvertible { } case .endClassStaticMethod: $0.endClassStaticMethod = Fuzzilli_Protobuf_EndClassStaticMethod() + case .beginClassStaticComputedMethod(let op): + $0.beginClassStaticComputedMethod = Fuzzilli_Protobuf_BeginClassStaticComputedMethod.with { + $0.parameters = convertParameters(op.parameters) + } + case .endClassStaticComputedMethod: + $0.endClassStaticComputedMethod = Fuzzilli_Protobuf_EndClassStaticComputedMethod() case .beginClassStaticGetter(let op): $0.beginClassStaticGetter = Fuzzilli_Protobuf_BeginClassStaticGetter.with { $0.propertyName = op.propertyName } case .endClassStaticGetter: @@ -913,6 +930,14 @@ extension Instruction: ProtobufConvertible { $0.variableName = op.variableName $0.declarationMode = convertEnum(op.declarationMode, NamedVariableDeclarationMode.allCases) } + case .createNamedDisposableVariable(let op): + $0.createNamedDisposableVariable = Fuzzilli_Protobuf_CreateNamedDisposableVariable.with { + $0.variableName = op.variableName + } + case .createNamedAsyncDisposableVariable(let op): + $0.createNamedAsyncDisposableVariable = Fuzzilli_Protobuf_CreateNamedAsyncDisposableVariable.with { + $0.variableName = op.variableName + } case .eval(let op): $0.eval = Fuzzilli_Protobuf_Eval.with { $0.code = op.code @@ -1534,6 +1559,11 @@ extension Instruction: ProtobufConvertible { $0.wasmBeginTypeGroup = Fuzzilli_Protobuf_WasmBeginTypeGroup() case .wasmEndTypeGroup(_): $0.wasmEndTypeGroup = Fuzzilli_Protobuf_WasmEndTypeGroup() + case .wasmDefineSignatureType(let op): + $0.wasmDefineSignatureType = Fuzzilli_Protobuf_WasmDefineSignatureType.with { + $0.parameterTypes = op.signature.parameterTypes.map(ILTypeToWasmTypeEnum) + $0.outputTypes = op.signature.outputTypes.map(ILTypeToWasmTypeEnum) + } case .wasmDefineArrayType(let op): $0.wasmDefineArrayType = Fuzzilli_Protobuf_WasmDefineArrayType.with { $0.elementType = ILTypeToWasmTypeEnum(op.elementType) @@ -1815,7 +1845,8 @@ extension Instruction: ProtobufConvertible { case .loadFloat(let p): op = LoadFloat(value: p.value) case .loadString(let p): - op = LoadString(value: p.value) + let customName = p.customName.isEmpty ? nil: p.customName; + op = LoadString(value: p.value, customName: customName) case .loadBoolean(let p): op = LoadBoolean(value: p.value) case .loadUndefined: @@ -1878,6 +1909,10 @@ extension Instruction: ProtobufConvertible { op = BeginClassInstanceMethod(methodName: p.methodName, parameters: convertParameters(p.parameters)) case .endClassInstanceMethod: op = EndClassInstanceMethod() + case .beginClassInstanceComputedMethod(let p): + op = BeginClassInstanceComputedMethod(parameters: convertParameters(p.parameters)) + case .endClassInstanceComputedMethod: + op = EndClassInstanceComputedMethod() case .beginClassInstanceGetter(let p): op = BeginClassInstanceGetter(propertyName: p.propertyName) case .endClassInstanceGetter: @@ -1900,6 +1935,10 @@ extension Instruction: ProtobufConvertible { op = BeginClassStaticMethod(methodName: p.methodName, parameters: convertParameters(p.parameters)) case .endClassStaticMethod: op = EndClassStaticMethod() + case .beginClassStaticComputedMethod(let p): + op = BeginClassStaticComputedMethod(parameters: convertParameters(p.parameters)) + case .endClassStaticComputedMethod: + op = EndClassStaticComputedMethod() case .beginClassStaticGetter(let p): op = BeginClassStaticGetter(propertyName: p.propertyName) case .endClassStaticGetter: @@ -2073,6 +2112,10 @@ extension Instruction: ProtobufConvertible { op = Compare(try convertEnum(p.op, Comparator.allCases)) case .createNamedVariable(let p): op = CreateNamedVariable(p.variableName, declarationMode: try convertEnum(p.declarationMode, NamedVariableDeclarationMode.allCases)) + case .createNamedDisposableVariable(let p): + op = CreateNamedDisposableVariable(p.variableName) + case .createNamedAsyncDisposableVariable(let p): + op = CreateNamedAsyncDisposableVariable(p.variableName) case .eval(let p): let numArguments = inouts.count - (p.hasOutput_p ? 1 : 0) op = Eval(p.code, numArguments: numArguments, hasOutput: p.hasOutput_p) @@ -2521,6 +2564,8 @@ extension Instruction: ProtobufConvertible { op = WasmEndTypeGroup(typesCount: inouts.count / 2) case .wasmDefineArrayType(let p): op = WasmDefineArrayType(elementType: WasmTypeEnumToILType(p.elementType), mutability: p.mutability) + case .wasmDefineSignatureType(let p): + op = WasmDefineSignatureType(signature: p.parameterTypes.map(WasmTypeEnumToILType) => p.outputTypes.map(WasmTypeEnumToILType)) case .wasmDefineStructType(let p): op = WasmDefineStructType(fields: p.fields.map { field in return WasmDefineStructType.Field(type: WasmTypeEnumToILType(field.type), mutability: field.mutability) diff --git a/Sources/Fuzzilli/FuzzIL/JSTyper.swift b/Sources/Fuzzilli/FuzzIL/JSTyper.swift index e1a8a2ab8..6157ea39d 100644 --- a/Sources/Fuzzilli/FuzzIL/JSTyper.swift +++ b/Sources/Fuzzilli/FuzzIL/JSTyper.swift @@ -432,6 +432,55 @@ public struct JSTyper: Analyzer { } } + mutating func addSignatureType(def: Variable, signature: WasmSignature, inputs: ArraySlice) { + var inputs = inputs.makeIterator() + let tgIndex = isWithinTypeGroup ? typeGroups.count - 1 : -1 + + // Temporary variable to use by the resolveType capture. It would be nicer to use + // higher-order functions for this but resolveType has to be a mutating func which doesn't + // seem to work well with escaping functions. + var isParameter = true + let resolveType = { (i: Int, paramType: ILType) in + if paramType.requiredInputCount() == 0 { + return paramType + } + assert(paramType.Is(.wasmRef(.Index(), nullability: true))) + let typeDef = inputs.next()! + let elementDesc = type(of: typeDef).wasmTypeDefinition!.description! + if elementDesc == .selfReference { + // Register a resolver callback. See `addArrayType` for details. + if isParameter { + selfReferences[typeDef, default: []].append({typer, replacement in + let desc = typer.type(of: def).wasmTypeDefinition!.description as! WasmSignatureTypeDescription + var params = desc.signature.parameterTypes + params[i] = typer.type(of: replacement ?? def) + desc.signature = params => desc.signature.outputTypes + }) + } else { + selfReferences[typeDef, default: []].append({typer, replacement in + let desc = typer.type(of: def).wasmTypeDefinition!.description as! WasmSignatureTypeDescription + var outputTypes = desc.signature.outputTypes + let nullability = outputTypes[i].wasmReferenceType!.nullability + outputTypes[i] = typer.type(of: replacement ?? def).wasmTypeDefinition!.getReferenceTypeTo(nullability: nullability) + desc.signature = desc.signature.parameterTypes => outputTypes + }) + } + + } + registerTypeGroupDependency(from: tgIndex, to: elementDesc.typeGroupIndex) + return type(of: typeDef).wasmTypeDefinition! + .getReferenceTypeTo(nullability: paramType.wasmReferenceType!.nullability) + } + + let resolvedParameterTypes = signature.parameterTypes.enumerated().map(resolveType) + isParameter = false // TODO(mliedtke): Is there a nicer way to capture this? + let resolvedOutputTypes = signature.outputTypes.enumerated().map(resolveType) + set(def, .wasmTypeDef(description: WasmSignatureTypeDescription(signature: resolvedParameterTypes => resolvedOutputTypes, typeGroupIndex: tgIndex))) + if isWithinTypeGroup { + typeGroups[typeGroups.count - 1].append(def) + } + } + mutating func addArrayType(def: Variable, elementType: ILType, mutability: Bool, elementRef: Variable? = nil) { let tgIndex = isWithinTypeGroup ? typeGroups.count - 1 : -1 let resolvedElementType: ILType @@ -1169,9 +1218,11 @@ public struct JSTyper: Analyzer { .beginConstructor, .beginClassConstructor, .beginClassInstanceMethod, + .beginClassInstanceComputedMethod, .beginClassInstanceGetter, .beginClassInstanceSetter, .beginClassStaticMethod, + .beginClassStaticComputedMethod, .beginClassStaticGetter, .beginClassStaticSetter, .beginClassPrivateInstanceMethod, @@ -1191,9 +1242,11 @@ public struct JSTyper: Analyzer { .endConstructor, .endClassConstructor, .endClassInstanceMethod, + .endClassInstanceComputedMethod, .endClassInstanceGetter, .endClassInstanceSetter, .endClassStaticMethod, + .endClassStaticComputedMethod, .endClassStaticGetter, .endClassStaticSetter, .endClassPrivateInstanceMethod, @@ -1368,8 +1421,17 @@ public struct JSTyper: Analyzer { case .loadFloat: set(instr.output, .float) - case .loadString: - set(instr.output, .jsString) + case .loadString(let op): + if let customName = op.customName { + if let enumTy = environment.getEnum(ofName: customName) { + set(instr.output, enumTy) + } else { + set(instr.output, .namedString(ofName: customName)) + } + + } else { + set(instr.output, .jsString) + } case .loadBoolean: set(instr.output, .boolean) @@ -1401,6 +1463,12 @@ public struct JSTyper: Analyzer { case .loadAsyncDisposableVariable: set(instr.output, type(ofInput: 0)) + case .createNamedDisposableVariable: + set(instr.output, type(ofInput: 0)) + + case .createNamedAsyncDisposableVariable: + set(instr.output, type(ofInput: 0)) + case .loadNewTarget: set(instr.output, .function() | .undefined) @@ -1464,6 +1532,11 @@ public struct JSTyper: Analyzer { processParameterDeclarations(instr.innerOutputs(1...), parameters: inferSubroutineParameterList(of: op, at: instr.index)) dynamicObjectGroupManager.addMethod(methodName: op.methodName, of: .jsClass) + case .beginClassInstanceComputedMethod(let op): + // The first inner output is the explicit |this| + set(instr.innerOutput(0), dynamicObjectGroupManager.top.instanceType) + processParameterDeclarations(instr.innerOutputs(1...), parameters: inferSubroutineParameterList(of: op, at: instr.index)) + case .beginClassInstanceGetter(let op): // The first inner output is the explicit |this| parameter for the constructor set(instr.innerOutput(0), dynamicObjectGroupManager.top.instanceType) @@ -1491,6 +1564,11 @@ public struct JSTyper: Analyzer { processParameterDeclarations(instr.innerOutputs(1...), parameters: inferSubroutineParameterList(of: op, at: instr.index)) dynamicObjectGroupManager.addClassStaticMethod(methodName: op.methodName) + case .beginClassStaticComputedMethod(let op): + // The first inner output is the explicit |this| + set(instr.innerOutput(0), dynamicObjectGroupManager.activeClasses.top.objectGroup.instanceType) + processParameterDeclarations(instr.innerOutputs(1...), parameters: inferSubroutineParameterList(of: op, at: instr.index)) + case .beginClassStaticGetter(let op): // The first inner output is the explicit |this| parameter for the constructor set(instr.innerOutput(0), dynamicObjectGroupManager.activeClasses.top.objectGroup.instanceType) @@ -1804,6 +1882,9 @@ public struct JSTyper: Analyzer { } finishTypeGroup() + case .wasmDefineSignatureType(let op): + addSignatureType(def: instr.output, signature: op.signature, inputs: instr.inputs) + case .wasmDefineArrayType(let op): let elementRef = op.elementType.requiredInputCount() == 1 ? instr.input(0) : nil addArrayType(def: instr.output, elementType: op.elementType, mutability: op.mutability, elementRef: elementRef) diff --git a/Sources/Fuzzilli/FuzzIL/JsOperations.swift b/Sources/Fuzzilli/FuzzIL/JsOperations.swift index 9f3007b16..d8dcbe154 100644 --- a/Sources/Fuzzilli/FuzzIL/JsOperations.swift +++ b/Sources/Fuzzilli/FuzzIL/JsOperations.swift @@ -183,9 +183,11 @@ final class LoadString: JsOperation { override var opcode: Opcode { .loadString(self) } let value: String + let customName: String? - init(value: String) { + init(value: String, customName: String? = nil) { self.value = value + self.customName = customName super.init(numOutputs: 1, attributes: [.isMutable]) } } @@ -322,6 +324,18 @@ final class LoadDisposableVariable: JsOperation { } } +final class CreateNamedDisposableVariable: JsOperation { + override var opcode: Opcode { .createNamedDisposableVariable(self) } + + let variableName: String + + init(_ name: String) { + self.variableName = name + // TODO: Add support for block context, see details above. + super.init(numInputs: 1, numOutputs: 1, requiredContext: [.javascript, .subroutine]) + } +} + final class LoadAsyncDisposableVariable: JsOperation { override var opcode: Opcode { .loadAsyncDisposableVariable(self) } @@ -330,6 +344,17 @@ final class LoadAsyncDisposableVariable: JsOperation { } } +final class CreateNamedAsyncDisposableVariable: JsOperation { + override var opcode: Opcode { .createNamedAsyncDisposableVariable(self) } + + let variableName: String + + init(_ name: String) { + self.variableName = name + super.init(numInputs: 1, numOutputs: 1, requiredContext: [.javascript, .asyncFunction]) + } +} + public struct RegExpFlags: OptionSet, Hashable { public let rawValue: UInt32 @@ -727,6 +752,19 @@ final class EndClassInstanceMethod: EndAnySubroutine { override var opcode: Opcode { .endClassInstanceMethod(self) } } +final class BeginClassInstanceComputedMethod: BeginAnySubroutine { + override var opcode: Opcode { .beginClassInstanceComputedMethod(self) } + + init(parameters: Parameters) { + // First inner output is the explicit |this| parameter + super.init(parameters: parameters, numInputs: 1, numInnerOutputs: parameters.count + 1, attributes: [.isBlockStart], requiredContext: .classDefinition, contextOpened: [.javascript, .subroutine, .method, .classMethod]) + } +} + +final class EndClassInstanceComputedMethod: EndAnySubroutine { + override var opcode: Opcode { .endClassInstanceComputedMethod(self) } +} + final class BeginClassInstanceGetter: BeginAnySubroutine { override var opcode: Opcode { .beginClassInstanceGetter(self) } @@ -833,6 +871,19 @@ final class EndClassStaticMethod: EndAnySubroutine { override var opcode: Opcode { .endClassStaticMethod(self) } } +final class BeginClassStaticComputedMethod: BeginAnySubroutine { + override var opcode: Opcode { .beginClassStaticComputedMethod(self) } + + init(parameters: Parameters) { + // First inner output is the explicit |this| parameter + super.init(parameters: parameters, numInputs: 1, numInnerOutputs: parameters.count + 1, attributes: [.isBlockStart], requiredContext: .classDefinition, contextOpened: [.javascript, .subroutine, .method, .classMethod]) + } +} + +final class EndClassStaticComputedMethod: EndAnySubroutine { + override var opcode: Opcode { .endClassStaticComputedMethod(self) } +} + final class BeginClassStaticGetter: BeginAnySubroutine { override var opcode: Opcode { .beginClassStaticGetter(self) } @@ -1799,7 +1850,7 @@ final class BeginWith: JsOperation { override var opcode: Opcode { .beginWith(self) } init() { - super.init(numInputs: 1, attributes: [.isBlockStart, .propagatesSurroundingContext], contextOpened: [.javascript, .with]) + super.init(numInputs: 1, attributes: [.isBlockStart, .propagatesSurroundingContext], contextOpened: [.javascript]) } } @@ -2536,8 +2587,6 @@ class CreateWasmTag: JsOperation { init(parameterTypes: [ILType]) { self.parameterTypes = parameterTypes - // Note that tags in wasm are nominal (differently to types) meaning that two tags with the same input are not - // the same, therefore this operation is not considered to be .pure. super.init(numOutputs: 1, attributes: [], requiredContext: [.javascript]) } } @@ -2591,6 +2640,18 @@ class WasmDefineStructType: WasmTypeOperation { } } +class WasmDefineSignatureType: WasmTypeOperation { + override var opcode: Opcode { .wasmDefineSignatureType(self) } + let signature: WasmSignature + + init(signature: WasmSignature) { + self.signature = signature + let numInputs = (signature.outputTypes + signature.parameterTypes).map { + $0.requiredInputCount() }.reduce(0) { $0 + $1 } + super.init(numInputs: numInputs, numOutputs: 1, requiredContext: [.wasmTypeGroup]) + } +} + class WasmDefineForwardOrSelfReference: WasmTypeOperation { override var opcode: Opcode { .wasmDefineForwardOrSelfReference(self) } diff --git a/Sources/Fuzzilli/FuzzIL/Opcodes.swift b/Sources/Fuzzilli/FuzzIL/Opcodes.swift index cdfce3a66..f9fda2f00 100644 --- a/Sources/Fuzzilli/FuzzIL/Opcodes.swift +++ b/Sources/Fuzzilli/FuzzIL/Opcodes.swift @@ -67,6 +67,8 @@ enum Opcode { case classAddInstanceComputedProperty(ClassAddInstanceComputedProperty) case beginClassInstanceMethod(BeginClassInstanceMethod) case endClassInstanceMethod(EndClassInstanceMethod) + case beginClassInstanceComputedMethod(BeginClassInstanceComputedMethod) + case endClassInstanceComputedMethod(EndClassInstanceComputedMethod) case beginClassInstanceGetter(BeginClassInstanceGetter) case endClassInstanceGetter(EndClassInstanceGetter) case beginClassInstanceSetter(BeginClassInstanceSetter) @@ -78,6 +80,8 @@ enum Opcode { case endClassStaticInitializer(EndClassStaticInitializer) case beginClassStaticMethod(BeginClassStaticMethod) case endClassStaticMethod(EndClassStaticMethod) + case beginClassStaticComputedMethod(BeginClassStaticComputedMethod) + case endClassStaticComputedMethod(EndClassStaticComputedMethod) case beginClassStaticGetter(BeginClassStaticGetter) case endClassStaticGetter(EndClassStaticGetter) case beginClassStaticSetter(BeginClassStaticSetter) @@ -353,7 +357,10 @@ enum Opcode { case wasmExternConvertAny(WasmExternConvertAny) case wasmMemoryCopy(WasmMemoryCopy) case wasmDefineElementSegment(WasmDefineElementSegment) - case wasmDropElementSegment(WasmDropElementSegment) case wasmTableInit(WasmTableInit) + case wasmDropElementSegment(WasmDropElementSegment) case wasmTableCopy(WasmTableCopy) + case wasmDefineSignatureType(WasmDefineSignatureType) + case createNamedDisposableVariable(CreateNamedDisposableVariable) + case createNamedAsyncDisposableVariable(CreateNamedAsyncDisposableVariable) } diff --git a/Sources/Fuzzilli/FuzzIL/Semantics.swift b/Sources/Fuzzilli/FuzzIL/Semantics.swift index ba9b93ca5..968003082 100644 --- a/Sources/Fuzzilli/FuzzIL/Semantics.swift +++ b/Sources/Fuzzilli/FuzzIL/Semantics.swift @@ -146,6 +146,8 @@ extension Operation { return endOp is EndClassConstructor case .beginClassInstanceMethod: return endOp is EndClassInstanceMethod + case .beginClassInstanceComputedMethod: + return endOp is EndClassInstanceComputedMethod case .beginClassInstanceGetter: return endOp is EndClassInstanceGetter case .beginClassInstanceSetter: @@ -154,6 +156,8 @@ extension Operation { return endOp is EndClassStaticInitializer case .beginClassStaticMethod: return endOp is EndClassStaticMethod + case .beginClassStaticComputedMethod: + return endOp is EndClassStaticComputedMethod case .beginClassStaticGetter: return endOp is EndClassStaticGetter case .beginClassStaticSetter: diff --git a/Sources/Fuzzilli/FuzzIL/TypeSystem.swift b/Sources/Fuzzilli/FuzzIL/TypeSystem.swift index 850d5a60a..c0e81beaf 100644 --- a/Sources/Fuzzilli/FuzzIL/TypeSystem.swift +++ b/Sources/Fuzzilli/FuzzIL/TypeSystem.swift @@ -161,6 +161,15 @@ public struct ILType: Hashable { return ILType(definiteType: .string, ext: ext) } + /// Constructs an named string: this is a string that typically has some complex format. + /// + /// Most code will treat these as strings, but the JavaScriptEnvironment can register + /// producingGenerators for them so they can be generated more intelligently. + public static func namedString(ofName name: String) -> ILType { + let ext = TypeExtension(group: name, properties: Set(), methods: Set(), signature: nil, wasmExt: nil) + return ILType(definiteType: .string, ext: ext) + } + /// An object for which it is not known what properties or methods it has, if any. public static let unknownObject: ILType = .object() @@ -484,7 +493,7 @@ public struct ILType: Hashable { } public var isEnumeration : Bool { - return Is(.string) && ext != nil + return Is(.string) && ext != nil && !ext!.properties.isEmpty } public var group: String? { @@ -2080,6 +2089,25 @@ class WasmTypeDescription: Hashable, CustomStringConvertible { } } +class WasmSignatureTypeDescription: WasmTypeDescription { + var signature: WasmSignature + + init(signature: WasmSignature, typeGroupIndex: Int) { + self.signature = signature + super.init(typeGroupIndex: typeGroupIndex, superType: .WasmFunc) + } + + override func format(abbreviate: Bool) -> String { + let abbreviated = "\(super.format(abbreviate: abbreviate)) Func" + if abbreviate { + return abbreviated + } + let paramTypes = signature.parameterTypes.map {$0.abbreviated}.joined(separator: ", ") + let outputTypes = signature.outputTypes.map {$0.abbreviated}.joined(separator: ", ") + return "\(abbreviated)[[\(paramTypes)] => [\(outputTypes)]]" + } +} + class WasmArrayTypeDescription: WasmTypeDescription { var elementType: ILType let mutability: Bool diff --git a/Sources/Fuzzilli/FuzzIL/WasmOperations.swift b/Sources/Fuzzilli/FuzzIL/WasmOperations.swift index 398c13268..c05115eba 100644 --- a/Sources/Fuzzilli/FuzzIL/WasmOperations.swift +++ b/Sources/Fuzzilli/FuzzIL/WasmOperations.swift @@ -880,8 +880,6 @@ final class WasmDefineTag: WasmOperation { init(parameterTypes: [ILType]) { self.parameterTypes = parameterTypes - // Note that tags in wasm are nominal (differently to types) meaning that two tags with the same input are not - // the same, therefore this operation is not considered to be .pure. super.init(numOutputs: 1, attributes: [], requiredContext: [.wasm]) } } @@ -1253,7 +1251,7 @@ final class WasmBeginBlock: WasmOperation { init(with signature: WasmSignature) { self.signature = signature - super.init(numInputs: signature.parameterTypes.count, numInnerOutputs: signature.parameterTypes.count + 1, attributes: [.isBlockStart, .propagatesSurroundingContext], requiredContext: [.wasmFunction], contextOpened: [.wasmBlock]) + super.init(numInputs: signature.parameterTypes.count, numInnerOutputs: signature.parameterTypes.count + 1, attributes: [.isBlockStart, .propagatesSurroundingContext], requiredContext: [.wasmFunction]) } } @@ -1264,7 +1262,7 @@ final class WasmEndBlock: WasmOperation { init(outputTypes: [ILType]) { self.outputTypes = outputTypes - super.init(numInputs: outputTypes.count, numOutputs: outputTypes.count, attributes: [.isBlockEnd, .resumesSurroundingContext], requiredContext: [.wasmFunction, .wasmBlock]) + super.init(numInputs: outputTypes.count, numOutputs: outputTypes.count, attributes: [.isBlockEnd, .resumesSurroundingContext], requiredContext: [.wasmFunction]) } } @@ -1288,7 +1286,7 @@ final class WasmBeginIf: WasmOperation { // value stack and that the condition is the first value to be removed from the stack, so // it needs to be the last one pushed to it. // Inner outputs: 1 label (used for branch instructions) plus all the parameters. - super.init(numInputs: signature.parameterTypes.count + 1, numInnerOutputs: 1 + signature.parameterTypes.count, attributes: [.isBlockStart, .propagatesSurroundingContext, .isMutable], requiredContext: [.wasmFunction], contextOpened: [.wasmBlock]) + super.init(numInputs: signature.parameterTypes.count + 1, numInnerOutputs: 1 + signature.parameterTypes.count, attributes: [.isBlockStart, .propagatesSurroundingContext, .isMutable], requiredContext: [.wasmFunction]) } } @@ -1301,7 +1299,7 @@ final class WasmBeginElse: WasmOperation { // The WasmBeginElse acts both as a block end for the true case and as a block start for the // false case. As such, its input types are the results from the true block and its inner // output types are the same as for the corresponding WasmBeginIf. - super.init(numInputs: signature.outputTypes.count, numInnerOutputs: 1 + signature.parameterTypes.count, attributes: [.isBlockStart, .isBlockEnd, .propagatesSurroundingContext], requiredContext: [.wasmFunction], contextOpened: [.wasmBlock]) + super.init(numInputs: signature.outputTypes.count, numInnerOutputs: 1 + signature.parameterTypes.count, attributes: [.isBlockStart, .isBlockEnd, .propagatesSurroundingContext], requiredContext: [.wasmFunction]) } } @@ -1311,7 +1309,7 @@ final class WasmEndIf: WasmOperation { init(outputTypes: [ILType] = []) { self.outputTypes = outputTypes - super.init(numInputs: outputTypes.count, numOutputs: outputTypes.count, attributes: [.isBlockEnd], requiredContext: [.wasmBlock, .wasmFunction]) + super.init(numInputs: outputTypes.count, numOutputs: outputTypes.count, attributes: [.isBlockEnd], requiredContext: [.wasmFunction]) } } @@ -1354,7 +1352,7 @@ final class WasmBeginTryTable: WasmOperation { self.catches = catches let inputTagCount = catches.count {$0 == .Ref || $0 == .NoRef} let inputLabelCount = catches.count - super.init(numInputs: signature.parameterTypes.count + inputLabelCount + inputTagCount , numInnerOutputs: signature.parameterTypes.count + 1, attributes: [.isBlockStart, .propagatesSurroundingContext], requiredContext: [.wasmFunction], contextOpened: [.wasmBlock]) + super.init(numInputs: signature.parameterTypes.count + inputLabelCount + inputTagCount , numInnerOutputs: signature.parameterTypes.count + 1, attributes: [.isBlockStart, .propagatesSurroundingContext], requiredContext: [.wasmFunction]) } } @@ -1364,7 +1362,7 @@ final class WasmEndTryTable: WasmOperation { init(outputTypes: [ILType]) { self.outputTypes = outputTypes - super.init(numInputs: outputTypes.count, numOutputs: outputTypes.count, attributes: [.isBlockEnd, .resumesSurroundingContext], requiredContext: [.wasmFunction, .wasmBlock]) + super.init(numInputs: outputTypes.count, numOutputs: outputTypes.count, attributes: [.isBlockEnd, .resumesSurroundingContext], requiredContext: [.wasmFunction]) } } @@ -1374,7 +1372,7 @@ final class WasmBeginTry: WasmOperation { init(with signature: WasmSignature) { self.signature = signature - super.init(numInputs: signature.parameterTypes.count, numInnerOutputs: signature.parameterTypes.count + 1, attributes: [.isBlockStart, .propagatesSurroundingContext], requiredContext: [.wasmFunction], contextOpened: [.wasmBlock]) + super.init(numInputs: signature.parameterTypes.count, numInnerOutputs: signature.parameterTypes.count + 1, attributes: [.isBlockStart, .propagatesSurroundingContext], requiredContext: [.wasmFunction]) } } diff --git a/Sources/Fuzzilli/Fuzzer.swift b/Sources/Fuzzilli/Fuzzer.swift index 58dddc1ee..3be0dadb1 100644 --- a/Sources/Fuzzilli/Fuzzer.swift +++ b/Sources/Fuzzilli/Fuzzer.swift @@ -42,6 +42,9 @@ public class Fuzzer { /// The active code generators. It is possible to change these (temporarily) at runtime. This is e.g. done by some ProgramTemplates. public private(set) var codeGenerators: WeightedList + // This needs to stay in sync with the provided codeGenerators. + public private(set) var contextGraph: ContextGraph + /// The active program templates. These are only used if the HybridEngine is enabled. public let programTemplates: WeightedList @@ -169,6 +172,7 @@ public class Fuzzer { self.engine = engine self.mutators = mutators self.codeGenerators = codeGenerators + self.programTemplates = programTemplates self.evaluator = evaluator self.environment = environment @@ -177,6 +181,7 @@ public class Fuzzer { self.runner = scriptRunner self.minimizer = minimizer self.logger = Logger(withLabel: "Fuzzer") + self.contextGraph = ContextGraph(for: codeGenerators, withLogger: self.logger) // Pass-through any postprocessor to the generative engine. if let postProcessor = engine.postProcessor { @@ -216,6 +221,8 @@ public class Fuzzer { guard generators.contains(where: { $0.isValueGenerator }) else { fatalError("Code generators must contain at least one value generator") } + // This builds a graph that we need later for scheduling generators. + self.contextGraph = ContextGraph(for: generators, withLogger: self.logger) self.codeGenerators = generators } @@ -272,17 +279,19 @@ public class Fuzzer { let nameMaxLength = self.codeGenerators.map({ $0.name.count }).max()! for generator in self.codeGenerators { - if generator.invocationCount > 100 && generator.invocationSuccessRate! < 0.2 { - let percentage = Statistics.percentageOrNa(generator.invocationSuccessRate, 7) - let name = generator.name.rightPadded(toLength: nameMaxLength) - let invocations = String(format: "%12d", generator.invocationCount) - self.logger.warning("Code generator \(name) might have too restrictive dynamic requirements. Its successful invocation rate is only \(percentage)% after \(invocations) invocations") - } - if generator.totalSamples >= 100 && generator.correctnessRate! < 0.05 { - let name = generator.name.rightPadded(toLength: nameMaxLength) - let percentage = Statistics.percentageOrNa(generator.correctnessRate, 7) - let totalSamples = String(format: "%10d", generator.totalSamples) - self.logger.warning("Code generator \(name) might be broken. Correctness rate is only \(percentage)% after \(totalSamples) generated samples") + for stub in generator.parts { + if stub.invocationCount > 100 && stub.invocationSuccessRate! < 0.2 { + let percentage = Statistics.percentageOrNa(stub.invocationSuccessRate, 7) + let name = stub.name.rightPadded(toLength: nameMaxLength) + let invocations = String(format: "%12d", stub.invocationCount) + self.logger.warning("Code generator \(name) might have too restrictive dynamic requirements. Its successful invocation rate is only \(percentage)% after \(invocations) invocations") + } + if stub.totalSamples >= 100 && stub.correctnessRate! < 0.05 { + let name = stub.name.rightPadded(toLength: nameMaxLength) + let percentage = Statistics.percentageOrNa(stub.correctnessRate, 7) + let totalSamples = String(format: "%10d", stub.totalSamples) + self.logger.warning("Code generator \(name) might be broken. Correctness rate is only \(percentage)% after \(totalSamples) generated samples") + } } } for template in self.programTemplates { diff --git a/Sources/Fuzzilli/Lifting/FuzzILLifter.swift b/Sources/Fuzzilli/Lifting/FuzzILLifter.swift index 8776f036b..9a4e77785 100644 --- a/Sources/Fuzzilli/Lifting/FuzzILLifter.swift +++ b/Sources/Fuzzilli/Lifting/FuzzILLifter.swift @@ -47,7 +47,11 @@ public class FuzzILLifter: Lifter { w.emit("\(output()) <- LoadFloat '\(op.value)'") case .loadString(let op): - w.emit("\(output()) <- LoadString '\(op.value)'") + if let customName = op.customName { + w.emit("\(output()) <- LoadString '\(op.value)' \(customName)") + } else { + w.emit("\(output()) <- LoadString '\(op.value)'") + } case .loadRegExp(let op): w.emit("\(output()) <- LoadRegExp '\(op.pattern)' '\(op.flags.asString())'") @@ -74,6 +78,12 @@ public class FuzzILLifter: Lifter { w.emit("\(output()) <- CreateNamedVariable '\(op.variableName)', '\(op.declarationMode)'") } + case .createNamedDisposableVariable(let op): + w.emit("\(output()) <- CreateNamedDisposableVariable '\(op.variableName)', \(input(0))") + + case .createNamedAsyncDisposableVariable(let op): + w.emit("\(output()) <- CreateNamedAsyncDisposableVariable '\(op.variableName)', \(input(0))") + case .loadDisposableVariable: w.emit("\(output()) <- LoadDisposableVariable \(input(0))") @@ -187,6 +197,15 @@ public class FuzzILLifter: Lifter { w.decreaseIndentionLevel() w.emit("EndClassInstanceMethod") + case .beginClassInstanceComputedMethod: + let params = instr.innerOutputs.map(lift).joined(separator: ", ") + w.emit("BeginClassInstanceComputedMethod \(input(0)) -> \(params)") + w.increaseIndentionLevel() + + case .endClassInstanceComputedMethod: + w.decreaseIndentionLevel() + w.emit("EndClassInstanceComputedMethod") + case .beginClassInstanceGetter(let op): let params = instr.innerOutputs.map(lift).joined(separator: ", ") w.emit("BeginClassInstanceGetter `\(op.propertyName)` -> \(params)") @@ -243,6 +262,15 @@ public class FuzzILLifter: Lifter { w.decreaseIndentionLevel() w.emit("EndClassStaticMethod") + case .beginClassStaticComputedMethod: + let params = instr.innerOutputs.map(lift).joined(separator: ", ") + w.emit("BeginClassStaticComputedMethod \(input(0)) -> \(params)") + w.increaseIndentionLevel() + + case .endClassStaticComputedMethod: + w.decreaseIndentionLevel() + w.emit("EndClassStaticComputedMethod") + case .beginClassStaticGetter(let op): let params = instr.innerOutputs.map(lift).joined(separator: ", ") w.emit("BeginClassStaticGetter `\(op.propertyName)` -> \(params)") @@ -1322,6 +1350,10 @@ public class FuzzILLifter: Lifter { let outputs = instr.outputs.map(lift).joined(separator: ", ") w.emit("\(outputs) <- WasmEndTypeGroup [\(inputs)]") + case .wasmDefineSignatureType(let op): + let inputs = instr.inputs.map(lift).joined(separator: ", ") + w.emit("\(output()) <- WasmDefineSignatureType(\(op.signature)) [\(inputs)]") + case .wasmDefineArrayType(let op): let typeInput = op.elementType.requiredInputCount() == 1 ? " \(input(0))" : "" w.emit("\(output()) <- WasmDefineArrayType \(op.elementType) mutability=\(op.mutability)\(typeInput)") diff --git a/Sources/Fuzzilli/Lifting/JavaScriptLifter.swift b/Sources/Fuzzilli/Lifting/JavaScriptLifter.swift index cfae09dfb..1cf8b75b9 100644 --- a/Sources/Fuzzilli/Lifting/JavaScriptLifter.swift +++ b/Sources/Fuzzilli/Lifting/JavaScriptLifter.swift @@ -323,6 +323,14 @@ public class JavaScriptLifter: Lifter { } w.declare(instr.output, as: op.variableName) + case .createNamedDisposableVariable(let op): + w.emit("using \(op.variableName) = \(input(0));"); + w.declare(instr.output, as: op.variableName) + + case .createNamedAsyncDisposableVariable(let op): + w.emit("await using \(op.variableName) = \(input(0));"); + w.declare(instr.output, as: op.variableName) + case .loadDisposableVariable: let V = w.declare(instr.output); w.emit("using \(V) = \(input(0));"); @@ -492,6 +500,14 @@ public class JavaScriptLifter: Lifter { w.enterNewBlock() bindVariableToThis(instr.innerOutput(0)) + case .beginClassInstanceComputedMethod(let op): + let vars = w.declareAll(instr.innerOutputs.dropFirst(), usePrefix: "a") + let PARAMS = liftParameters(op.parameters, as: vars) + let METHOD = input(0) + w.emit("[\(METHOD)](\(PARAMS)) {") + w.enterNewBlock() + bindVariableToThis(instr.innerOutput(0)) + case .beginClassInstanceGetter(let op): let PROPERTY = op.propertyName w.emit("get \(PROPERTY)() {") @@ -508,6 +524,7 @@ public class JavaScriptLifter: Lifter { bindVariableToThis(instr.innerOutput(0)) case .endClassInstanceMethod, + .endClassInstanceComputedMethod, .endClassInstanceGetter, .endClassInstanceSetter: w.leaveCurrentBlock() @@ -553,6 +570,14 @@ public class JavaScriptLifter: Lifter { w.enterNewBlock() bindVariableToThis(instr.innerOutput(0)) + case .beginClassStaticComputedMethod(let op): + let vars = w.declareAll(instr.innerOutputs.dropFirst(), usePrefix: "a") + let PARAMS = liftParameters(op.parameters, as: vars) + let METHOD = input(0) + w.emit("static [\(METHOD)](\(PARAMS)) {") + w.enterNewBlock() + bindVariableToThis(instr.innerOutput(0)) + case .beginClassStaticGetter(let op): assert(instr.numInnerOutputs == 1) let PROPERTY = op.propertyName @@ -571,6 +596,7 @@ public class JavaScriptLifter: Lifter { case .endClassStaticInitializer, .endClassStaticMethod, + .endClassStaticComputedMethod, .endClassStaticGetter, .endClassStaticSetter: w.leaveCurrentBlock() @@ -1695,6 +1721,7 @@ public class JavaScriptLifter: Lifter { .wasmSimdLoad(_), .wasmBeginTypeGroup(_), .wasmEndTypeGroup(_), + .wasmDefineSignatureType(_), .wasmDefineArrayType(_), .wasmDefineStructType(_), .wasmDefineForwardOrSelfReference(_), diff --git a/Sources/Fuzzilli/Lifting/WasmLifter.swift b/Sources/Fuzzilli/Lifting/WasmLifter.swift index 09fe73331..287d1cc5e 100644 --- a/Sources/Fuzzilli/Lifting/WasmLifter.swift +++ b/Sources/Fuzzilli/Lifting/WasmLifter.swift @@ -605,6 +605,16 @@ public class WasmLifter { data += try encodeType(field.type) data += [field.mutability ? 1 : 0] } + } else if let signatureDesc = desc as? WasmSignatureTypeDescription { + data += [0x60] + data += Leb128.unsignedEncode(signatureDesc.signature.parameterTypes.count) + for parameterType in signatureDesc.signature.parameterTypes { + data += try encodeType(parameterType) + } + data += Leb128.unsignedEncode(signatureDesc.signature.outputTypes.count) + for outputType in signatureDesc.signature.outputTypes { + data += try encodeType(outputType) + } } else { fatalError("Unsupported WasmTypeDescription!") } @@ -881,7 +891,7 @@ public class WasmLifter { } } - // Active element segments + // Active element segments for case let .table(instruction) in self.exports { let table = instruction!.op as! WasmDefineTable let definedEntries = table.definedEntries diff --git a/Sources/Fuzzilli/Minimization/BlockReducer.swift b/Sources/Fuzzilli/Minimization/BlockReducer.swift index d86f0c453..5a1efac08 100644 --- a/Sources/Fuzzilli/Minimization/BlockReducer.swift +++ b/Sources/Fuzzilli/Minimization/BlockReducer.swift @@ -38,10 +38,12 @@ struct BlockReducer: Reducer { case .beginClassConstructor, .beginClassInstanceMethod, + .beginClassInstanceComputedMethod, .beginClassInstanceGetter, .beginClassInstanceSetter, .beginClassStaticInitializer, .beginClassStaticMethod, + .beginClassStaticComputedMethod, .beginClassStaticGetter, .beginClassStaticSetter, .beginClassPrivateInstanceMethod, diff --git a/Sources/Fuzzilli/Minimization/InliningReducer.swift b/Sources/Fuzzilli/Minimization/InliningReducer.swift index 1876b8634..be6bed5d6 100644 --- a/Sources/Fuzzilli/Minimization/InliningReducer.swift +++ b/Sources/Fuzzilli/Minimization/InliningReducer.swift @@ -62,10 +62,12 @@ struct InliningReducer: Reducer { .beginObjectLiteralSetter, .beginClassConstructor, .beginClassInstanceMethod, + .beginClassInstanceComputedMethod, .beginClassInstanceGetter, .beginClassInstanceSetter, .beginClassStaticInitializer, .beginClassStaticMethod, + .beginClassStaticComputedMethod, .beginClassStaticGetter, .beginClassStaticSetter, .beginClassPrivateInstanceMethod, @@ -84,10 +86,12 @@ struct InliningReducer: Reducer { .endObjectLiteralSetter, .endClassConstructor, .endClassInstanceMethod, + .endClassInstanceComputedMethod, .endClassInstanceGetter, .endClassInstanceSetter, .endClassStaticInitializer, .endClassStaticMethod, + .endClassStaticComputedMethod, .endClassStaticGetter, .endClassStaticSetter, .endClassPrivateInstanceMethod, diff --git a/Sources/Fuzzilli/Modules/Statistics.swift b/Sources/Fuzzilli/Modules/Statistics.swift index 47c2e0c58..e974e7648 100644 --- a/Sources/Fuzzilli/Modules/Statistics.swift +++ b/Sources/Fuzzilli/Modules/Statistics.swift @@ -235,14 +235,16 @@ public class Statistics: Module { let nameMaxLength = fuzzer.codeGenerators.map({ $0.name.count }).max()! for generator in fuzzer.codeGenerators { - let name = generator.name.rightPadded(toLength: nameMaxLength) - let correctnessRate = Self.percentageOrNa(generator.correctnessRate, 7) - let interestingSamplesRate = Self.percentageOrNa(generator.interestingSamplesRate, 7) - let timeoutRate = Self.percentageOrNa(generator.timeoutRate, 6) - let avgInstructionsAdded = String(format: "%.2f", generator.avgNumberOfInstructionsGenerated).leftPadded(toLength: 5) - let invocationSuccessRate = Self.percentageOrNa(generator.invocationSuccessRate, 6) - let samplesGenerated = generator.totalSamples - self.logger.verbose(" \(name) : Invocation Success: \(invocationSuccessRate), Correctness rate: \(correctnessRate), Interesting sample rate: \(interestingSamplesRate), Timeout rate: \(timeoutRate), Avg. # of instructions added: \(avgInstructionsAdded), Total # of generated samples: \(samplesGenerated)") + for stub in generator.parts { + let name = stub.name.rightPadded(toLength: nameMaxLength) + let correctnessRate = Self.percentageOrNa(stub.correctnessRate, 7) + let interestingSamplesRate = Self.percentageOrNa(stub.interestingSamplesRate, 7) + let timeoutRate = Self.percentageOrNa(stub.timeoutRate, 6) + let avgInstructionsAdded = String(format: "%.2f", stub.avgNumberOfInstructionsGenerated).leftPadded(toLength: 5) + let invocationSuccessRate = Self.percentageOrNa(stub.invocationSuccessRate, 6) + let samplesGenerated = stub.totalSamples + self.logger.verbose(" \(name) : Invocation Success: \(invocationSuccessRate), Correctness rate: \(correctnessRate), Interesting sample rate: \(interestingSamplesRate), Timeout rate: \(timeoutRate), Avg. # of instructions added: \(avgInstructionsAdded), Total # of generated samples: \(samplesGenerated)") + } } } } diff --git a/Sources/Fuzzilli/Mutators/OperationMutator.swift b/Sources/Fuzzilli/Mutators/OperationMutator.swift index bfea45d4a..dfe3438df 100644 --- a/Sources/Fuzzilli/Mutators/OperationMutator.swift +++ b/Sources/Fuzzilli/Mutators/OperationMutator.swift @@ -51,6 +51,18 @@ public class OperationMutator: BaseInstructionMutator { case .loadFloat(_): newOp = LoadFloat(value: b.randomFloat()) case .loadString(let op): + if let customName = op.customName { + // Half the time we want to just hit the regular path + if Bool.random() { + if let type = b.fuzzer.environment.getEnum(ofName: customName) { + newOp = LoadString(value: chooseUniform(from: type.enumValues), customName: customName) + break + } else if let gen = b.fuzzer.environment.getNamedStringGenerator(ofName: customName) { + newOp = LoadString(value: gen(), customName: customName) + break + } + } + } let charSetAlNum = Array("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") // TODO(mliedtke): Should we also use some more esoteric characters in initial string // creation, e.g. ProgramBuilder.randomString? @@ -84,6 +96,8 @@ public class OperationMutator: BaseInstructionMutator { return result } ) + // Note: This explicitly discards customName since we may have created a string that no longer + // matches the original schema. newOp = LoadString(value: newString) case .loadRegExp(let op): newOp = withEqualProbability({ @@ -516,6 +530,10 @@ public class OperationMutator: BaseInstructionMutator { .beginClassConstructor(_), .endClassConstructor(_), .classAddInstanceComputedProperty(_), + .beginClassInstanceComputedMethod(_), + .endClassInstanceComputedMethod(_), + .beginClassStaticComputedMethod(_), + .endClassStaticComputedMethod(_), .endClassInstanceMethod(_), .endClassInstanceGetter(_), .endClassInstanceSetter(_), @@ -615,6 +633,8 @@ public class OperationMutator: BaseInstructionMutator { .explore(_), .probe(_), .fixup(_), + .createNamedDisposableVariable(_), + .createNamedAsyncDisposableVariable(_), .loadDisposableVariable(_), .loadAsyncDisposableVariable(_), .void(_), @@ -691,6 +711,7 @@ public class OperationMutator: BaseInstructionMutator { .wasmEndTypeGroup(_), .wasmDefineArrayType(_), .wasmDefineStructType(_), + .wasmDefineSignatureType(_), .wasmDefineForwardOrSelfReference(_), .wasmResolveForwardReference(_), .wasmArrayNewFixed(_), @@ -708,8 +729,8 @@ public class OperationMutator: BaseInstructionMutator { .wasmDropElementSegment(_), .wasmTableInit(_), .wasmTableCopy(_): - assert(!instr.isOperationMutable) - fatalError("Unexpected Operation") + let mutability = instr.isOperationMutable ? "mutable" : "immutable" + fatalError("Unexpected operation \(instr.op.opcode), marked as \(mutability)") } // This assert is here to prevent subtle bugs if we ever decide to add flags that are "alive" during program building / mutation. diff --git a/Sources/Fuzzilli/Protobuf/ast.pb.swift b/Sources/Fuzzilli/Protobuf/ast.pb.swift index 2d83a4153..61f091cb1 100644 --- a/Sources/Fuzzilli/Protobuf/ast.pb.swift +++ b/Sources/Fuzzilli/Protobuf/ast.pb.swift @@ -72,6 +72,40 @@ public enum Compiler_Protobuf_VariableDeclarationKind: SwiftProtobuf.Enum, Swift } +public enum Compiler_Protobuf_DisposableVariableDeclarationKind: SwiftProtobuf.Enum, Swift.CaseIterable { + public typealias RawValue = Int + case using // = 0 + case awaitUsing // = 1 + case UNRECOGNIZED(Int) + + public init() { + self = .using + } + + public init?(rawValue: Int) { + switch rawValue { + case 0: self = .using + case 1: self = .awaitUsing + default: self = .UNRECOGNIZED(rawValue) + } + } + + public var rawValue: Int { + switch self { + case .using: return 0 + case .awaitUsing: return 1 + case .UNRECOGNIZED(let i): return i + } + } + + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Compiler_Protobuf_DisposableVariableDeclarationKind] = [ + .using, + .awaitUsing, + ] + +} + public enum Compiler_Protobuf_FunctionType: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int case plain // = 0 @@ -202,6 +236,20 @@ public struct Compiler_Protobuf_VariableDeclaration: Sendable { public init() {} } +public struct Compiler_Protobuf_DisposableVariableDeclaration: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var kind: Compiler_Protobuf_DisposableVariableDeclarationKind = .using + + public var declarations: [Compiler_Protobuf_VariableDeclarator] = [] + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + public struct Compiler_Protobuf_FunctionDeclaration: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for @@ -220,40 +268,69 @@ public struct Compiler_Protobuf_FunctionDeclaration: Sendable { public init() {} } -public struct Compiler_Protobuf_ClassProperty: Sendable { +public struct Compiler_Protobuf_PropertyKey: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. - public var key: Compiler_Protobuf_ClassProperty.OneOf_Key? = nil + public var body: Compiler_Protobuf_PropertyKey.OneOf_Body? = nil /// A "regular" property. public var name: String { get { - if case .name(let v)? = key {return v} + if case .name(let v)? = body {return v} return String() } - set {key = .name(newValue)} + set {body = .name(newValue)} } /// An element. public var index: Int64 { get { - if case .index(let v)? = key {return v} + if case .index(let v)? = body {return v} return 0 } - set {key = .index(newValue)} + set {body = .index(newValue)} } /// A computed property. public var expression: Compiler_Protobuf_Expression { get { - if case .expression(let v)? = key {return v} + if case .expression(let v)? = body {return v} return Compiler_Protobuf_Expression() } - set {key = .expression(newValue)} + set {body = .expression(newValue)} + } + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public enum OneOf_Body: Equatable, Sendable { + /// A "regular" property. + case name(String) + /// An element. + case index(Int64) + /// A computed property. + case expression(Compiler_Protobuf_Expression) + } + public init() {} +} + +public struct Compiler_Protobuf_ClassProperty: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var key: Compiler_Protobuf_PropertyKey { + get {return _key ?? Compiler_Protobuf_PropertyKey()} + set {_key = newValue} + } + /// Returns true if `key` has been explicitly set. + public var hasKey: Bool {return self._key != nil} + /// Clears the value of `key`. Subsequent reads from it will return its default value. + public mutating func clearKey() {self._key = nil} + public var isStatic: Bool = false /// The value is optional @@ -268,18 +345,9 @@ public struct Compiler_Protobuf_ClassProperty: Sendable { public var unknownFields = SwiftProtobuf.UnknownStorage() - public enum OneOf_Key: Equatable, Sendable { - /// A "regular" property. - case name(String) - /// An element. - case index(Int64) - /// A computed property. - case expression(Compiler_Protobuf_Expression) - - } - public init() {} + fileprivate var _key: Compiler_Protobuf_PropertyKey? = nil fileprivate var _value: Compiler_Protobuf_Expression? = nil } @@ -302,7 +370,14 @@ public struct Compiler_Protobuf_ClassMethod: Sendable { // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. - public var name: String = String() + public var key: Compiler_Protobuf_PropertyKey { + get {return _key ?? Compiler_Protobuf_PropertyKey()} + set {_key = newValue} + } + /// Returns true if `key` has been explicitly set. + public var hasKey: Bool {return self._key != nil} + /// Clears the value of `key`. Subsequent reads from it will return its default value. + public mutating func clearKey() {self._key = nil} public var isStatic: Bool = false @@ -313,6 +388,8 @@ public struct Compiler_Protobuf_ClassMethod: Sendable { public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} + + fileprivate var _key: Compiler_Protobuf_PropertyKey? = nil } public struct Compiler_Protobuf_ClassGetter: Sendable { @@ -1169,6 +1246,14 @@ public struct Compiler_Protobuf_Statement: @unchecked Sendable { set {_uniqueStorage()._statement = .switchStatement(newValue)} } + public var disposableVariableDeclaration: Compiler_Protobuf_DisposableVariableDeclaration { + get { + if case .disposableVariableDeclaration(let v)? = _storage._statement {return v} + return Compiler_Protobuf_DisposableVariableDeclaration() + } + set {_uniqueStorage()._statement = .disposableVariableDeclaration(newValue)} + } + public var unknownFields = SwiftProtobuf.UnknownStorage() public enum OneOf_Statement: Equatable, Sendable { @@ -1192,6 +1277,7 @@ public struct Compiler_Protobuf_Statement: @unchecked Sendable { case throwStatement(Compiler_Protobuf_ThrowStatement) case withStatement(Compiler_Protobuf_WithStatement) case switchStatement(Compiler_Protobuf_SwitchStatement) + case disposableVariableDeclaration(Compiler_Protobuf_DisposableVariableDeclaration) } @@ -2359,6 +2445,10 @@ extension Compiler_Protobuf_VariableDeclarationKind: SwiftProtobuf._ProtoNamePro public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0VAR\0\u{1}LET\0\u{1}CONST\0") } +extension Compiler_Protobuf_DisposableVariableDeclarationKind: SwiftProtobuf._ProtoNameProviding { + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0USING\0\u{1}AWAIT_USING\0") +} + extension Compiler_Protobuf_FunctionType: SwiftProtobuf._ProtoNameProviding { public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0PLAIN\0\u{1}GENERATOR\0\u{1}ASYNC\0\u{1}ASYNC_GENERATOR\0") } @@ -2584,6 +2674,41 @@ extension Compiler_Protobuf_VariableDeclaration: SwiftProtobuf.Message, SwiftPro } } +extension Compiler_Protobuf_DisposableVariableDeclaration: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".DisposableVariableDeclaration" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}kind\0\u{1}declarations\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularEnumField(value: &self.kind) }() + case 2: try { try decoder.decodeRepeatedMessageField(value: &self.declarations) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if self.kind != .using { + try visitor.visitSingularEnumField(value: self.kind, fieldNumber: 1) + } + if !self.declarations.isEmpty { + try visitor.visitRepeatedMessageField(value: self.declarations, fieldNumber: 2) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Compiler_Protobuf_DisposableVariableDeclaration, rhs: Compiler_Protobuf_DisposableVariableDeclaration) -> Bool { + if lhs.kind != rhs.kind {return false} + if lhs.declarations != rhs.declarations {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + extension Compiler_Protobuf_FunctionDeclaration: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".FunctionDeclaration" public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}name\0\u{1}type\0\u{1}parameters\0\u{1}body\0") @@ -2629,9 +2754,9 @@ extension Compiler_Protobuf_FunctionDeclaration: SwiftProtobuf.Message, SwiftPro } } -extension Compiler_Protobuf_ClassProperty: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - public static let protoMessageName: String = _protobuf_package + ".ClassProperty" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}name\0\u{1}index\0\u{1}expression\0\u{1}isStatic\0\u{1}value\0") +extension Compiler_Protobuf_PropertyKey: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".PropertyKey" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}name\0\u{1}index\0\u{1}expression\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -2643,33 +2768,31 @@ extension Compiler_Protobuf_ClassProperty: SwiftProtobuf.Message, SwiftProtobuf. var v: String? try decoder.decodeSingularStringField(value: &v) if let v = v { - if self.key != nil {try decoder.handleConflictingOneOf()} - self.key = .name(v) + if self.body != nil {try decoder.handleConflictingOneOf()} + self.body = .name(v) } }() case 2: try { var v: Int64? try decoder.decodeSingularInt64Field(value: &v) if let v = v { - if self.key != nil {try decoder.handleConflictingOneOf()} - self.key = .index(v) + if self.body != nil {try decoder.handleConflictingOneOf()} + self.body = .index(v) } }() case 3: try { var v: Compiler_Protobuf_Expression? var hadOneofValue = false - if let current = self.key { + if let current = self.body { hadOneofValue = true if case .expression(let m) = current {v = m} } try decoder.decodeSingularMessageField(value: &v) if let v = v { if hadOneofValue {try decoder.handleConflictingOneOf()} - self.key = .expression(v) + self.body = .expression(v) } }() - case 4: try { try decoder.decodeSingularBoolField(value: &self.isStatic) }() - case 5: try { try decoder.decodeSingularMessageField(value: &self._value) }() default: break } } @@ -2680,32 +2803,68 @@ extension Compiler_Protobuf_ClassProperty: SwiftProtobuf.Message, SwiftProtobuf. // allocates stack space for every if/case branch local when no optimizations // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and // https://github.com/apple/swift-protobuf/issues/1182 - switch self.key { + switch self.body { case .name?: try { - guard case .name(let v)? = self.key else { preconditionFailure() } + guard case .name(let v)? = self.body else { preconditionFailure() } try visitor.visitSingularStringField(value: v, fieldNumber: 1) }() case .index?: try { - guard case .index(let v)? = self.key else { preconditionFailure() } + guard case .index(let v)? = self.body else { preconditionFailure() } try visitor.visitSingularInt64Field(value: v, fieldNumber: 2) }() case .expression?: try { - guard case .expression(let v)? = self.key else { preconditionFailure() } + guard case .expression(let v)? = self.body else { preconditionFailure() } try visitor.visitSingularMessageField(value: v, fieldNumber: 3) }() case nil: break } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Compiler_Protobuf_PropertyKey, rhs: Compiler_Protobuf_PropertyKey) -> Bool { + if lhs.body != rhs.body {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Compiler_Protobuf_ClassProperty: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".ClassProperty" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}key\0\u{1}isStatic\0\u{1}value\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularMessageField(value: &self._key) }() + case 2: try { try decoder.decodeSingularBoolField(value: &self.isStatic) }() + case 3: try { try decoder.decodeSingularMessageField(value: &self._value) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._key { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() if self.isStatic != false { - try visitor.visitSingularBoolField(value: self.isStatic, fieldNumber: 4) + try visitor.visitSingularBoolField(value: self.isStatic, fieldNumber: 2) } try { if let v = self._value { - try visitor.visitSingularMessageField(value: v, fieldNumber: 5) + try visitor.visitSingularMessageField(value: v, fieldNumber: 3) } }() try unknownFields.traverse(visitor: &visitor) } public static func ==(lhs: Compiler_Protobuf_ClassProperty, rhs: Compiler_Protobuf_ClassProperty) -> Bool { - if lhs.key != rhs.key {return false} + if lhs._key != rhs._key {return false} if lhs.isStatic != rhs.isStatic {return false} if lhs._value != rhs._value {return false} if lhs.unknownFields != rhs.unknownFields {return false} @@ -2750,7 +2909,7 @@ extension Compiler_Protobuf_ClassConstructor: SwiftProtobuf.Message, SwiftProtob extension Compiler_Protobuf_ClassMethod: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".ClassMethod" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}name\0\u{1}isStatic\0\u{1}parameters\0\u{1}body\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}key\0\u{1}isStatic\0\u{1}parameters\0\u{1}body\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -2758,7 +2917,7 @@ extension Compiler_Protobuf_ClassMethod: SwiftProtobuf.Message, SwiftProtobuf._M // allocates stack space for every case branch when no optimizations are // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { - case 1: try { try decoder.decodeSingularStringField(value: &self.name) }() + case 1: try { try decoder.decodeSingularMessageField(value: &self._key) }() case 2: try { try decoder.decodeSingularBoolField(value: &self.isStatic) }() case 3: try { try decoder.decodeRepeatedMessageField(value: &self.parameters) }() case 4: try { try decoder.decodeRepeatedMessageField(value: &self.body) }() @@ -2768,9 +2927,13 @@ extension Compiler_Protobuf_ClassMethod: SwiftProtobuf.Message, SwiftProtobuf._M } public func traverse(visitor: inout V) throws { - if !self.name.isEmpty { - try visitor.visitSingularStringField(value: self.name, fieldNumber: 1) - } + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._key { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() if self.isStatic != false { try visitor.visitSingularBoolField(value: self.isStatic, fieldNumber: 2) } @@ -2784,7 +2947,7 @@ extension Compiler_Protobuf_ClassMethod: SwiftProtobuf.Message, SwiftProtobuf._M } public static func ==(lhs: Compiler_Protobuf_ClassMethod, rhs: Compiler_Protobuf_ClassMethod) -> Bool { - if lhs.name != rhs.name {return false} + if lhs._key != rhs._key {return false} if lhs.isStatic != rhs.isStatic {return false} if lhs.parameters != rhs.parameters {return false} if lhs.body != rhs.body {return false} @@ -4331,7 +4494,7 @@ extension Compiler_Protobuf_SwitchCase: SwiftProtobuf.Message, SwiftProtobuf._Me extension Compiler_Protobuf_Statement: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".Statement" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}emptyStatement\0\u{1}blockStatement\0\u{1}variableDeclaration\0\u{1}functionDeclaration\0\u{1}classDeclaration\0\u{1}returnStatement\0\u{1}directiveStatement\0\u{1}expressionStatement\0\u{1}ifStatement\0\u{1}whileLoop\0\u{1}doWhileLoop\0\u{1}forLoop\0\u{1}forInLoop\0\u{1}forOfLoop\0\u{1}breakStatement\0\u{1}continueStatement\0\u{1}tryStatement\0\u{1}throwStatement\0\u{1}withStatement\0\u{1}switchStatement\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}emptyStatement\0\u{1}blockStatement\0\u{1}variableDeclaration\0\u{1}functionDeclaration\0\u{1}classDeclaration\0\u{1}returnStatement\0\u{1}directiveStatement\0\u{1}expressionStatement\0\u{1}ifStatement\0\u{1}whileLoop\0\u{1}doWhileLoop\0\u{1}forLoop\0\u{1}forInLoop\0\u{1}forOfLoop\0\u{1}breakStatement\0\u{1}continueStatement\0\u{1}tryStatement\0\u{1}throwStatement\0\u{1}withStatement\0\u{1}switchStatement\0\u{1}disposableVariableDeclaration\0") fileprivate class _StorageClass { var _statement: Compiler_Protobuf_Statement.OneOf_Statement? @@ -4624,6 +4787,19 @@ extension Compiler_Protobuf_Statement: SwiftProtobuf.Message, SwiftProtobuf._Mes _storage._statement = .switchStatement(v) } }() + case 21: try { + var v: Compiler_Protobuf_DisposableVariableDeclaration? + var hadOneofValue = false + if let current = _storage._statement { + hadOneofValue = true + if case .disposableVariableDeclaration(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + _storage._statement = .disposableVariableDeclaration(v) + } + }() default: break } } @@ -4717,6 +4893,10 @@ extension Compiler_Protobuf_Statement: SwiftProtobuf.Message, SwiftProtobuf._Mes guard case .switchStatement(let v)? = _storage._statement else { preconditionFailure() } try visitor.visitSingularMessageField(value: v, fieldNumber: 20) }() + case .disposableVariableDeclaration?: try { + guard case .disposableVariableDeclaration(let v)? = _storage._statement else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 21) + }() case nil: break } } diff --git a/Sources/Fuzzilli/Protobuf/ast.proto b/Sources/Fuzzilli/Protobuf/ast.proto index bac50eb8a..56fd5fbdc 100644 --- a/Sources/Fuzzilli/Protobuf/ast.proto +++ b/Sources/Fuzzilli/Protobuf/ast.proto @@ -48,6 +48,16 @@ message VariableDeclaration { repeated VariableDeclarator declarations = 2; } +enum DisposableVariableDeclarationKind { + USING = 0; + AWAIT_USING = 1; +} + +message DisposableVariableDeclaration { + DisposableVariableDeclarationKind kind = 1; + repeated VariableDeclarator declarations = 2; +} + enum FunctionType { PLAIN = 0; GENERATOR = 1; @@ -62,8 +72,8 @@ message FunctionDeclaration { repeated Statement body = 4; } -message ClassProperty { - oneof key { +message PropertyKey { + oneof body { // A "regular" property. string name = 1; // An element. @@ -71,9 +81,13 @@ message ClassProperty { // A computed property. Expression expression = 3; } - bool isStatic = 4; +} + +message ClassProperty { + PropertyKey key = 1; + bool isStatic = 2; // The value is optional - Expression value = 5; + Expression value = 3; } message ClassConstructor { @@ -82,7 +96,7 @@ message ClassConstructor { } message ClassMethod { - string name = 1; + PropertyKey key = 1; bool isStatic = 2; repeated Parameter parameters = 3; repeated Statement body = 4; @@ -251,6 +265,7 @@ message Statement { ThrowStatement throwStatement = 18; WithStatement withStatement = 19; SwitchStatement switchStatement = 20; + DisposableVariableDeclaration disposableVariableDeclaration = 21; } } diff --git a/Sources/Fuzzilli/Protobuf/operations.pb.swift b/Sources/Fuzzilli/Protobuf/operations.pb.swift index 01584f4f0..7a1549674 100644 --- a/Sources/Fuzzilli/Protobuf/operations.pb.swift +++ b/Sources/Fuzzilli/Protobuf/operations.pb.swift @@ -1654,9 +1654,20 @@ public struct Fuzzilli_Protobuf_LoadString: Sendable { public var value: String = String() + public var customName: String { + get {return _customName ?? String()} + set {_customName = newValue} + } + /// Returns true if `customName` has been explicitly set. + public var hasCustomName: Bool {return self._customName != nil} + /// Clears the value of `customName`. Subsequent reads from it will return its default value. + public mutating func clearCustomName() {self._customName = nil} + public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} + + fileprivate var _customName: String? = nil } public struct Fuzzilli_Protobuf_LoadBoolean: Sendable { @@ -2045,6 +2056,37 @@ public struct Fuzzilli_Protobuf_EndClassInstanceMethod: Sendable { public init() {} } +public struct Fuzzilli_Protobuf_BeginClassInstanceComputedMethod: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var parameters: Fuzzilli_Protobuf_Parameters { + get {return _parameters ?? Fuzzilli_Protobuf_Parameters()} + set {_parameters = newValue} + } + /// Returns true if `parameters` has been explicitly set. + public var hasParameters: Bool {return self._parameters != nil} + /// Clears the value of `parameters`. Subsequent reads from it will return its default value. + public mutating func clearParameters() {self._parameters = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _parameters: Fuzzilli_Protobuf_Parameters? = nil +} + +public struct Fuzzilli_Protobuf_EndClassInstanceComputedMethod: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + public struct Fuzzilli_Protobuf_BeginClassInstanceGetter: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for @@ -2182,6 +2224,37 @@ public struct Fuzzilli_Protobuf_EndClassStaticMethod: Sendable { public init() {} } +public struct Fuzzilli_Protobuf_BeginClassStaticComputedMethod: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var parameters: Fuzzilli_Protobuf_Parameters { + get {return _parameters ?? Fuzzilli_Protobuf_Parameters()} + set {_parameters = newValue} + } + /// Returns true if `parameters` has been explicitly set. + public var hasParameters: Bool {return self._parameters != nil} + /// Clears the value of `parameters`. Subsequent reads from it will return its default value. + public mutating func clearParameters() {self._parameters = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _parameters: Fuzzilli_Protobuf_Parameters? = nil +} + +public struct Fuzzilli_Protobuf_EndClassStaticComputedMethod: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + public struct Fuzzilli_Protobuf_BeginClassStaticGetter: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for @@ -3179,6 +3252,30 @@ public struct Fuzzilli_Protobuf_CreateNamedVariable: Sendable { public init() {} } +public struct Fuzzilli_Protobuf_CreateNamedDisposableVariable: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var variableName: String = String() + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + +public struct Fuzzilli_Protobuf_CreateNamedAsyncDisposableVariable: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var variableName: String = String() + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + public struct Fuzzilli_Protobuf_Eval: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for @@ -5598,6 +5695,20 @@ public struct Fuzzilli_Protobuf_WasmEndTypeGroup: Sendable { public init() {} } +public struct Fuzzilli_Protobuf_WasmDefineSignatureType: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var parameterTypes: [Fuzzilli_Protobuf_WasmILType] = [] + + public var outputTypes: [Fuzzilli_Protobuf_WasmILType] = [] + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + public struct Fuzzilli_Protobuf_WasmDefineArrayType: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for @@ -6208,7 +6319,7 @@ extension Fuzzilli_Protobuf_LoadFloat: SwiftProtobuf.Message, SwiftProtobuf._Mes extension Fuzzilli_Protobuf_LoadString: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".LoadString" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}value\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}value\0\u{1}customName\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -6217,20 +6328,29 @@ extension Fuzzilli_Protobuf_LoadString: SwiftProtobuf.Message, SwiftProtobuf._Me // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { case 1: try { try decoder.decodeSingularStringField(value: &self.value) }() + case 2: try { try decoder.decodeSingularStringField(value: &self._customName) }() default: break } } } public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 if !self.value.isEmpty { try visitor.visitSingularStringField(value: self.value, fieldNumber: 1) } + try { if let v = self._customName { + try visitor.visitSingularStringField(value: v, fieldNumber: 2) + } }() try unknownFields.traverse(visitor: &visitor) } public static func ==(lhs: Fuzzilli_Protobuf_LoadString, rhs: Fuzzilli_Protobuf_LoadString) -> Bool { if lhs.value != rhs.value {return false} + if lhs._customName != rhs._customName {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -7025,6 +7145,59 @@ extension Fuzzilli_Protobuf_EndClassInstanceMethod: SwiftProtobuf.Message, Swift } } +extension Fuzzilli_Protobuf_BeginClassInstanceComputedMethod: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".BeginClassInstanceComputedMethod" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}parameters\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularMessageField(value: &self._parameters) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._parameters { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Fuzzilli_Protobuf_BeginClassInstanceComputedMethod, rhs: Fuzzilli_Protobuf_BeginClassInstanceComputedMethod) -> Bool { + if lhs._parameters != rhs._parameters {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Fuzzilli_Protobuf_EndClassInstanceComputedMethod: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".EndClassInstanceComputedMethod" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap() + + public mutating func decodeMessage(decoder: inout D) throws { + // Load everything into unknown fields + while try decoder.nextFieldNumber() != nil {} + } + + public func traverse(visitor: inout V) throws { + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Fuzzilli_Protobuf_EndClassInstanceComputedMethod, rhs: Fuzzilli_Protobuf_EndClassInstanceComputedMethod) -> Bool { + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + extension Fuzzilli_Protobuf_BeginClassInstanceGetter: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".BeginClassInstanceGetter" public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}propertyName\0") @@ -7319,6 +7492,59 @@ extension Fuzzilli_Protobuf_EndClassStaticMethod: SwiftProtobuf.Message, SwiftPr } } +extension Fuzzilli_Protobuf_BeginClassStaticComputedMethod: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".BeginClassStaticComputedMethod" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}parameters\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularMessageField(value: &self._parameters) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._parameters { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Fuzzilli_Protobuf_BeginClassStaticComputedMethod, rhs: Fuzzilli_Protobuf_BeginClassStaticComputedMethod) -> Bool { + if lhs._parameters != rhs._parameters {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Fuzzilli_Protobuf_EndClassStaticComputedMethod: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".EndClassStaticComputedMethod" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap() + + public mutating func decodeMessage(decoder: inout D) throws { + // Load everything into unknown fields + while try decoder.nextFieldNumber() != nil {} + } + + public func traverse(visitor: inout V) throws { + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Fuzzilli_Protobuf_EndClassStaticComputedMethod, rhs: Fuzzilli_Protobuf_EndClassStaticComputedMethod) -> Bool { + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + extension Fuzzilli_Protobuf_BeginClassStaticGetter: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".BeginClassStaticGetter" public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}propertyName\0") @@ -9485,6 +9711,66 @@ extension Fuzzilli_Protobuf_CreateNamedVariable: SwiftProtobuf.Message, SwiftPro } } +extension Fuzzilli_Protobuf_CreateNamedDisposableVariable: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".CreateNamedDisposableVariable" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}variableName\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularStringField(value: &self.variableName) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if !self.variableName.isEmpty { + try visitor.visitSingularStringField(value: self.variableName, fieldNumber: 1) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Fuzzilli_Protobuf_CreateNamedDisposableVariable, rhs: Fuzzilli_Protobuf_CreateNamedDisposableVariable) -> Bool { + if lhs.variableName != rhs.variableName {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Fuzzilli_Protobuf_CreateNamedAsyncDisposableVariable: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".CreateNamedAsyncDisposableVariable" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}variableName\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularStringField(value: &self.variableName) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if !self.variableName.isEmpty { + try visitor.visitSingularStringField(value: self.variableName, fieldNumber: 1) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Fuzzilli_Protobuf_CreateNamedAsyncDisposableVariable, rhs: Fuzzilli_Protobuf_CreateNamedAsyncDisposableVariable) -> Bool { + if lhs.variableName != rhs.variableName {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + extension Fuzzilli_Protobuf_Eval: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".Eval" public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}code\0\u{1}hasOutput\0") @@ -14673,6 +14959,41 @@ extension Fuzzilli_Protobuf_WasmEndTypeGroup: SwiftProtobuf.Message, SwiftProtob } } +extension Fuzzilli_Protobuf_WasmDefineSignatureType: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".WasmDefineSignatureType" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}parameterTypes\0\u{1}outputTypes\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeRepeatedMessageField(value: &self.parameterTypes) }() + case 2: try { try decoder.decodeRepeatedMessageField(value: &self.outputTypes) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if !self.parameterTypes.isEmpty { + try visitor.visitRepeatedMessageField(value: self.parameterTypes, fieldNumber: 1) + } + if !self.outputTypes.isEmpty { + try visitor.visitRepeatedMessageField(value: self.outputTypes, fieldNumber: 2) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Fuzzilli_Protobuf_WasmDefineSignatureType, rhs: Fuzzilli_Protobuf_WasmDefineSignatureType) -> Bool { + if lhs.parameterTypes != rhs.parameterTypes {return false} + if lhs.outputTypes != rhs.outputTypes {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + extension Fuzzilli_Protobuf_WasmDefineArrayType: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".WasmDefineArrayType" public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}elementType\0\u{1}mutability\0") diff --git a/Sources/Fuzzilli/Protobuf/operations.proto b/Sources/Fuzzilli/Protobuf/operations.proto index c8f81a97a..72266a1fb 100644 --- a/Sources/Fuzzilli/Protobuf/operations.proto +++ b/Sources/Fuzzilli/Protobuf/operations.proto @@ -35,6 +35,7 @@ message LoadFloat { message LoadString { string value = 1; + optional string customName = 2; } message LoadBoolean { @@ -150,6 +151,13 @@ message BeginClassInstanceMethod { message EndClassInstanceMethod { } +message BeginClassInstanceComputedMethod { + Parameters parameters = 1; +} + +message EndClassInstanceComputedMethod { +} + message BeginClassInstanceGetter { string propertyName = 1; } @@ -192,6 +200,13 @@ message BeginClassStaticMethod { message EndClassStaticMethod { } +message BeginClassStaticComputedMethod { + Parameters parameters = 1; +} + +message EndClassStaticComputedMethod { +} + message BeginClassStaticGetter { string propertyName = 1; } @@ -601,6 +616,14 @@ message CreateNamedVariable { NamedVariableDeclarationMode declarationMode = 2; } +message CreateNamedDisposableVariable { + string variableName = 1; +} + +message CreateNamedAsyncDisposableVariable { + string variableName = 1; +} + message Eval { string code = 1; bool hasOutput = 2; @@ -1475,6 +1498,11 @@ message WasmBeginTypeGroup { message WasmEndTypeGroup { } +message WasmDefineSignatureType { + repeated WasmILType parameterTypes = 1; + repeated WasmILType outputTypes = 2; +} + message WasmDefineArrayType { WasmILType elementType = 1; bool mutability = 2; diff --git a/Sources/Fuzzilli/Protobuf/program.pb.swift b/Sources/Fuzzilli/Protobuf/program.pb.swift index e0cf07d1d..e13009f1f 100644 --- a/Sources/Fuzzilli/Protobuf/program.pb.swift +++ b/Sources/Fuzzilli/Protobuf/program.pb.swift @@ -2665,6 +2665,62 @@ public struct Fuzzilli_Protobuf_Instruction: Sendable { set {operation = .wasmTableCopy(newValue)} } + public var wasmDefineSignatureType: Fuzzilli_Protobuf_WasmDefineSignatureType { + get { + if case .wasmDefineSignatureType(let v)? = operation {return v} + return Fuzzilli_Protobuf_WasmDefineSignatureType() + } + set {operation = .wasmDefineSignatureType(newValue)} + } + + public var createNamedDisposableVariable: Fuzzilli_Protobuf_CreateNamedDisposableVariable { + get { + if case .createNamedDisposableVariable(let v)? = operation {return v} + return Fuzzilli_Protobuf_CreateNamedDisposableVariable() + } + set {operation = .createNamedDisposableVariable(newValue)} + } + + public var createNamedAsyncDisposableVariable: Fuzzilli_Protobuf_CreateNamedAsyncDisposableVariable { + get { + if case .createNamedAsyncDisposableVariable(let v)? = operation {return v} + return Fuzzilli_Protobuf_CreateNamedAsyncDisposableVariable() + } + set {operation = .createNamedAsyncDisposableVariable(newValue)} + } + + public var beginClassInstanceComputedMethod: Fuzzilli_Protobuf_BeginClassInstanceComputedMethod { + get { + if case .beginClassInstanceComputedMethod(let v)? = operation {return v} + return Fuzzilli_Protobuf_BeginClassInstanceComputedMethod() + } + set {operation = .beginClassInstanceComputedMethod(newValue)} + } + + public var endClassInstanceComputedMethod: Fuzzilli_Protobuf_EndClassInstanceComputedMethod { + get { + if case .endClassInstanceComputedMethod(let v)? = operation {return v} + return Fuzzilli_Protobuf_EndClassInstanceComputedMethod() + } + set {operation = .endClassInstanceComputedMethod(newValue)} + } + + public var beginClassStaticComputedMethod: Fuzzilli_Protobuf_BeginClassStaticComputedMethod { + get { + if case .beginClassStaticComputedMethod(let v)? = operation {return v} + return Fuzzilli_Protobuf_BeginClassStaticComputedMethod() + } + set {operation = .beginClassStaticComputedMethod(newValue)} + } + + public var endClassStaticComputedMethod: Fuzzilli_Protobuf_EndClassStaticComputedMethod { + get { + if case .endClassStaticComputedMethod(let v)? = operation {return v} + return Fuzzilli_Protobuf_EndClassStaticComputedMethod() + } + set {operation = .endClassStaticComputedMethod(newValue)} + } + public var unknownFields = SwiftProtobuf.UnknownStorage() public enum OneOf_Operation: Equatable, Sendable { @@ -2995,6 +3051,13 @@ public struct Fuzzilli_Protobuf_Instruction: Sendable { case wasmTableInit(Fuzzilli_Protobuf_WasmTableInit) case wasmDropElementSegment(Fuzzilli_Protobuf_WasmDropElementSegment) case wasmTableCopy(Fuzzilli_Protobuf_WasmTableCopy) + case wasmDefineSignatureType(Fuzzilli_Protobuf_WasmDefineSignatureType) + case createNamedDisposableVariable(Fuzzilli_Protobuf_CreateNamedDisposableVariable) + case createNamedAsyncDisposableVariable(Fuzzilli_Protobuf_CreateNamedAsyncDisposableVariable) + case beginClassInstanceComputedMethod(Fuzzilli_Protobuf_BeginClassInstanceComputedMethod) + case endClassInstanceComputedMethod(Fuzzilli_Protobuf_EndClassInstanceComputedMethod) + case beginClassStaticComputedMethod(Fuzzilli_Protobuf_BeginClassStaticComputedMethod) + case endClassStaticComputedMethod(Fuzzilli_Protobuf_EndClassStaticComputedMethod) } @@ -3043,7 +3106,7 @@ fileprivate let _protobuf_package = "fuzzilli.protobuf" extension Fuzzilli_Protobuf_Instruction: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".Instruction" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}inouts\0\u{1}opIdx\0\u{1}nop\0\u{1}loadInteger\0\u{1}loadBigInt\0\u{1}loadFloat\0\u{1}loadString\0\u{1}loadBoolean\0\u{1}loadUndefined\0\u{1}loadNull\0\u{1}loadThis\0\u{1}loadArguments\0\u{1}createNamedVariable\0\u{1}loadDisposableVariable\0\u{1}loadAsyncDisposableVariable\0\u{1}loadRegExp\0\u{1}beginObjectLiteral\0\u{1}objectLiteralAddProperty\0\u{1}objectLiteralAddElement\0\u{1}objectLiteralAddComputedProperty\0\u{1}objectLiteralCopyProperties\0\u{1}objectLiteralSetPrototype\0\u{1}beginObjectLiteralMethod\0\u{1}endObjectLiteralMethod\0\u{1}beginObjectLiteralComputedMethod\0\u{1}endObjectLiteralComputedMethod\0\u{1}beginObjectLiteralGetter\0\u{1}endObjectLiteralGetter\0\u{1}beginObjectLiteralSetter\0\u{1}endObjectLiteralSetter\0\u{1}endObjectLiteral\0\u{1}beginClassDefinition\0\u{1}beginClassConstructor\0\u{1}endClassConstructor\0\u{1}classAddInstanceProperty\0\u{1}classAddInstanceElement\0\u{1}classAddInstanceComputedProperty\0\u{1}beginClassInstanceMethod\0\u{1}endClassInstanceMethod\0\u{1}beginClassInstanceGetter\0\u{1}endClassInstanceGetter\0\u{1}beginClassInstanceSetter\0\u{1}endClassInstanceSetter\0\u{1}classAddStaticProperty\0\u{1}classAddStaticElement\0\u{1}classAddStaticComputedProperty\0\u{1}beginClassStaticInitializer\0\u{1}endClassStaticInitializer\0\u{1}beginClassStaticMethod\0\u{1}endClassStaticMethod\0\u{1}beginClassStaticGetter\0\u{1}endClassStaticGetter\0\u{1}beginClassStaticSetter\0\u{1}endClassStaticSetter\0\u{1}classAddPrivateInstanceProperty\0\u{1}beginClassPrivateInstanceMethod\0\u{1}endClassPrivateInstanceMethod\0\u{1}classAddPrivateStaticProperty\0\u{1}beginClassPrivateStaticMethod\0\u{1}endClassPrivateStaticMethod\0\u{1}endClassDefinition\0\u{1}createArray\0\u{1}createIntArray\0\u{1}createFloatArray\0\u{1}createArrayWithSpread\0\u{1}createTemplateString\0\u{1}getProperty\0\u{1}setProperty\0\u{1}updateProperty\0\u{1}deleteProperty\0\u{1}configureProperty\0\u{1}getElement\0\u{1}setElement\0\u{1}updateElement\0\u{1}deleteElement\0\u{1}configureElement\0\u{1}getComputedProperty\0\u{1}setComputedProperty\0\u{1}updateComputedProperty\0\u{1}deleteComputedProperty\0\u{1}configureComputedProperty\0\u{1}typeOf\0\u{1}void\0\u{1}testInstanceOf\0\u{1}testIn\0\u{1}beginPlainFunction\0\u{1}endPlainFunction\0\u{1}beginArrowFunction\0\u{1}endArrowFunction\0\u{1}beginGeneratorFunction\0\u{1}endGeneratorFunction\0\u{1}beginAsyncFunction\0\u{1}endAsyncFunction\0\u{1}beginAsyncArrowFunction\0\u{1}endAsyncArrowFunction\0\u{1}beginAsyncGeneratorFunction\0\u{1}endAsyncGeneratorFunction\0\u{1}beginConstructor\0\u{1}endConstructor\0\u{1}directive\0\u{1}return\0\u{1}yield\0\u{1}yieldEach\0\u{1}await\0\u{1}callFunction\0\u{1}callFunctionWithSpread\0\u{1}construct\0\u{1}constructWithSpread\0\u{1}callMethod\0\u{1}callMethodWithSpread\0\u{1}callComputedMethod\0\u{1}callComputedMethodWithSpread\0\u{1}unaryOperation\0\u{1}binaryOperation\0\u{1}ternaryOperation\0\u{1}update\0\u{1}dup\0\u{1}reassign\0\u{1}destructArray\0\u{1}destructArrayAndReassign\0\u{1}destructObject\0\u{1}destructObjectAndReassign\0\u{1}compare\0\u{1}eval\0\u{1}beginWith\0\u{1}endWith\0\u{1}callSuperConstructor\0\u{1}callSuperMethod\0\u{1}getPrivateProperty\0\u{1}setPrivateProperty\0\u{1}updatePrivateProperty\0\u{1}callPrivateMethod\0\u{1}getSuperProperty\0\u{1}setSuperProperty\0\u{1}getComputedSuperProperty\0\u{1}setComputedSuperProperty\0\u{1}updateSuperProperty\0\u{1}beginIf\0\u{1}beginElse\0\u{1}endIf\0\u{1}beginWhileLoopHeader\0\u{1}beginWhileLoopBody\0\u{1}endWhileLoop\0\u{1}beginDoWhileLoopBody\0\u{1}beginDoWhileLoopHeader\0\u{1}endDoWhileLoop\0\u{1}beginForLoopInitializer\0\u{1}beginForLoopCondition\0\u{1}beginForLoopAfterthought\0\u{1}beginForLoopBody\0\u{1}endForLoop\0\u{1}beginForInLoop\0\u{1}endForInLoop\0\u{1}beginForOfLoop\0\u{1}beginForOfLoopWithDestruct\0\u{1}endForOfLoop\0\u{1}beginRepeatLoop\0\u{1}endRepeatLoop\0\u{1}loopBreak\0\u{1}loopContinue\0\u{1}beginTry\0\u{1}beginCatch\0\u{1}beginFinally\0\u{1}endTryCatchFinally\0\u{1}throwException\0\u{1}beginCodeString\0\u{1}endCodeString\0\u{1}beginBlockStatement\0\u{1}endBlockStatement\0\u{1}beginSwitch\0\u{1}beginSwitchCase\0\u{1}beginSwitchDefaultCase\0\u{1}endSwitchCase\0\u{1}endSwitch\0\u{1}switchBreak\0\u{1}loadNewTarget\0\u{1}print\0\u{1}explore\0\u{1}probe\0\u{1}fixup\0\u{1}beginWasmModule\0\u{1}endWasmModule\0\u{1}createWasmGlobal\0\u{1}createWasmMemory\0\u{1}createWasmTable\0\u{1}createWasmJSTag\0\u{1}createWasmTag\0\u{1}wrapPromising\0\u{1}wrapSuspending\0\u{1}bindMethod\0\u{1}bindFunction\0\u{1}consti64\0\u{1}consti32\0\u{1}constf32\0\u{1}constf64\0\u{1}wasmReturn\0\u{1}wasmJsCall\0\u{1}wasmi32CompareOp\0\u{1}wasmi64CompareOp\0\u{1}wasmf32CompareOp\0\u{1}wasmf64CompareOp\0\u{1}wasmi32EqualZero\0\u{1}wasmi64EqualZero\0\u{1}wasmi32BinOp\0\u{1}wasmi64BinOp\0\u{1}wasmi32UnOp\0\u{1}wasmi64UnOp\0\u{1}wasmf32BinOp\0\u{1}wasmf64BinOp\0\u{1}wasmf32UnOp\0\u{1}wasmf64UnOp\0\u{1}wasmWrapi64Toi32\0\u{1}wasmTruncatef32Toi32\0\u{1}wasmTruncatef64Toi32\0\u{1}wasmExtendi32Toi64\0\u{1}wasmTruncatef32Toi64\0\u{1}wasmTruncatef64Toi64\0\u{1}wasmConverti32Tof32\0\u{1}wasmConverti64Tof32\0\u{1}wasmDemotef64Tof32\0\u{1}wasmConverti32Tof64\0\u{1}wasmConverti64Tof64\0\u{1}wasmPromotef32Tof64\0\u{1}wasmReinterpretf32Asi32\0\u{1}wasmReinterpretf64Asi64\0\u{1}wasmReinterpreti32Asf32\0\u{1}wasmReinterpreti64Asf64\0\u{1}wasmSignExtend8Intoi32\0\u{1}wasmSignExtend16Intoi32\0\u{1}wasmSignExtend8Intoi64\0\u{1}wasmSignExtend16Intoi64\0\u{1}wasmSignExtend32Intoi64\0\u{1}wasmTruncateSatf32Toi32\0\u{1}wasmTruncateSatf64Toi32\0\u{1}wasmTruncateSatf32Toi64\0\u{1}wasmTruncateSatf64Toi64\0\u{1}wasmReassign\0\u{1}wasmDefineGlobal\0\u{1}wasmDefineTable\0\u{1}wasmDefineMemory\0\u{1}wasmDefineDataSegment\0\u{1}wasmLoadGlobal\0\u{1}wasmStoreGlobal\0\u{1}wasmTableGet\0\u{1}wasmTableSet\0\u{1}wasmTableSize\0\u{1}wasmTableGrow\0\u{1}wasmCallIndirect\0\u{1}wasmCallDirect\0\u{1}wasmReturnCallDirect\0\u{1}wasmReturnCallIndirect\0\u{1}wasmMemoryLoad\0\u{1}wasmMemoryStore\0\u{1}wasmAtomicLoad\0\u{1}wasmAtomicStore\0\u{1}wasmAtomicRMW\0\u{1}wasmAtomicCmpxchg\0\u{1}wasmMemorySize\0\u{1}wasmMemoryGrow\0\u{1}wasmMemoryFill\0\u{1}wasmMemoryInit\0\u{1}wasmDropDataSegment\0\u{1}beginWasmFunction\0\u{1}endWasmFunction\0\u{1}wasmBeginBlock\0\u{1}wasmEndBlock\0\u{1}wasmBeginLoop\0\u{1}wasmEndLoop\0\u{1}wasmBranch\0\u{1}wasmBranchIf\0\u{1}wasmBranchTable\0\u{1}wasmNop\0\u{1}wasmBeginIf\0\u{1}wasmBeginElse\0\u{1}wasmEndIf\0\u{1}wasmBeginTryTable\0\u{1}wasmEndTryTable\0\u{1}wasmBeginTry\0\u{1}wasmBeginCatchAll\0\u{1}wasmBeginCatch\0\u{1}wasmEndTry\0\u{1}wasmBeginTryDelegate\0\u{1}wasmEndTryDelegate\0\u{1}wasmThrow\0\u{1}wasmRethrow\0\u{1}wasmThrowRef\0\u{1}wasmDefineTag\0\u{1}constSimd128\0\u{1}wasmSimd128Compare\0\u{1}wasmSimd128IntegerUnOp\0\u{1}wasmSimd128IntegerBinOp\0\u{1}wasmSimd128IntegerTernaryOp\0\u{1}wasmSimd128FloatUnOp\0\u{1}wasmSimd128FloatBinOp\0\u{1}wasmSimd128FloatTernaryOp\0\u{1}wasmSimdSplat\0\u{1}wasmSimdExtractLane\0\u{1}wasmSimdReplaceLane\0\u{1}wasmSimdStoreLane\0\u{1}wasmSimdLoadLane\0\u{1}wasmSimdLoad\0\u{1}wasmUnreachable\0\u{1}wasmSelect\0\u{1}wasmBeginTypeGroup\0\u{1}wasmEndTypeGroup\0\u{1}wasmDefineArrayType\0\u{1}wasmDefineStructType\0\u{1}wasmDefineForwardOrSelfReference\0\u{1}wasmResolveForwardReference\0\u{1}wasmArrayNewFixed\0\u{1}wasmArrayNewDefault\0\u{1}wasmArrayLen\0\u{1}wasmArrayGet\0\u{1}wasmArraySet\0\u{1}wasmStructNewDefault\0\u{1}wasmStructGet\0\u{1}wasmStructSet\0\u{1}wasmRefNull\0\u{1}wasmRefIsNull\0\u{1}wasmRefI31\0\u{1}wasmI31Get\0\u{1}wasmAnyConvertExtern\0\u{1}wasmExternConvertAny\0\u{1}wasmMemoryCopy\0\u{1}wasmDefineElementSegment\0\u{1}wasmTableInit\0\u{1}wasmDropElementSegment\0\u{1}wasmTableCopy\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}inouts\0\u{1}opIdx\0\u{1}nop\0\u{1}loadInteger\0\u{1}loadBigInt\0\u{1}loadFloat\0\u{1}loadString\0\u{1}loadBoolean\0\u{1}loadUndefined\0\u{1}loadNull\0\u{1}loadThis\0\u{1}loadArguments\0\u{1}createNamedVariable\0\u{1}loadDisposableVariable\0\u{1}loadAsyncDisposableVariable\0\u{1}loadRegExp\0\u{1}beginObjectLiteral\0\u{1}objectLiteralAddProperty\0\u{1}objectLiteralAddElement\0\u{1}objectLiteralAddComputedProperty\0\u{1}objectLiteralCopyProperties\0\u{1}objectLiteralSetPrototype\0\u{1}beginObjectLiteralMethod\0\u{1}endObjectLiteralMethod\0\u{1}beginObjectLiteralComputedMethod\0\u{1}endObjectLiteralComputedMethod\0\u{1}beginObjectLiteralGetter\0\u{1}endObjectLiteralGetter\0\u{1}beginObjectLiteralSetter\0\u{1}endObjectLiteralSetter\0\u{1}endObjectLiteral\0\u{1}beginClassDefinition\0\u{1}beginClassConstructor\0\u{1}endClassConstructor\0\u{1}classAddInstanceProperty\0\u{1}classAddInstanceElement\0\u{1}classAddInstanceComputedProperty\0\u{1}beginClassInstanceMethod\0\u{1}endClassInstanceMethod\0\u{1}beginClassInstanceGetter\0\u{1}endClassInstanceGetter\0\u{1}beginClassInstanceSetter\0\u{1}endClassInstanceSetter\0\u{1}classAddStaticProperty\0\u{1}classAddStaticElement\0\u{1}classAddStaticComputedProperty\0\u{1}beginClassStaticInitializer\0\u{1}endClassStaticInitializer\0\u{1}beginClassStaticMethod\0\u{1}endClassStaticMethod\0\u{1}beginClassStaticGetter\0\u{1}endClassStaticGetter\0\u{1}beginClassStaticSetter\0\u{1}endClassStaticSetter\0\u{1}classAddPrivateInstanceProperty\0\u{1}beginClassPrivateInstanceMethod\0\u{1}endClassPrivateInstanceMethod\0\u{1}classAddPrivateStaticProperty\0\u{1}beginClassPrivateStaticMethod\0\u{1}endClassPrivateStaticMethod\0\u{1}endClassDefinition\0\u{1}createArray\0\u{1}createIntArray\0\u{1}createFloatArray\0\u{1}createArrayWithSpread\0\u{1}createTemplateString\0\u{1}getProperty\0\u{1}setProperty\0\u{1}updateProperty\0\u{1}deleteProperty\0\u{1}configureProperty\0\u{1}getElement\0\u{1}setElement\0\u{1}updateElement\0\u{1}deleteElement\0\u{1}configureElement\0\u{1}getComputedProperty\0\u{1}setComputedProperty\0\u{1}updateComputedProperty\0\u{1}deleteComputedProperty\0\u{1}configureComputedProperty\0\u{1}typeOf\0\u{1}void\0\u{1}testInstanceOf\0\u{1}testIn\0\u{1}beginPlainFunction\0\u{1}endPlainFunction\0\u{1}beginArrowFunction\0\u{1}endArrowFunction\0\u{1}beginGeneratorFunction\0\u{1}endGeneratorFunction\0\u{1}beginAsyncFunction\0\u{1}endAsyncFunction\0\u{1}beginAsyncArrowFunction\0\u{1}endAsyncArrowFunction\0\u{1}beginAsyncGeneratorFunction\0\u{1}endAsyncGeneratorFunction\0\u{1}beginConstructor\0\u{1}endConstructor\0\u{1}directive\0\u{1}return\0\u{1}yield\0\u{1}yieldEach\0\u{1}await\0\u{1}callFunction\0\u{1}callFunctionWithSpread\0\u{1}construct\0\u{1}constructWithSpread\0\u{1}callMethod\0\u{1}callMethodWithSpread\0\u{1}callComputedMethod\0\u{1}callComputedMethodWithSpread\0\u{1}unaryOperation\0\u{1}binaryOperation\0\u{1}ternaryOperation\0\u{1}update\0\u{1}dup\0\u{1}reassign\0\u{1}destructArray\0\u{1}destructArrayAndReassign\0\u{1}destructObject\0\u{1}destructObjectAndReassign\0\u{1}compare\0\u{1}eval\0\u{1}beginWith\0\u{1}endWith\0\u{1}callSuperConstructor\0\u{1}callSuperMethod\0\u{1}getPrivateProperty\0\u{1}setPrivateProperty\0\u{1}updatePrivateProperty\0\u{1}callPrivateMethod\0\u{1}getSuperProperty\0\u{1}setSuperProperty\0\u{1}getComputedSuperProperty\0\u{1}setComputedSuperProperty\0\u{1}updateSuperProperty\0\u{1}beginIf\0\u{1}beginElse\0\u{1}endIf\0\u{1}beginWhileLoopHeader\0\u{1}beginWhileLoopBody\0\u{1}endWhileLoop\0\u{1}beginDoWhileLoopBody\0\u{1}beginDoWhileLoopHeader\0\u{1}endDoWhileLoop\0\u{1}beginForLoopInitializer\0\u{1}beginForLoopCondition\0\u{1}beginForLoopAfterthought\0\u{1}beginForLoopBody\0\u{1}endForLoop\0\u{1}beginForInLoop\0\u{1}endForInLoop\0\u{1}beginForOfLoop\0\u{1}beginForOfLoopWithDestruct\0\u{1}endForOfLoop\0\u{1}beginRepeatLoop\0\u{1}endRepeatLoop\0\u{1}loopBreak\0\u{1}loopContinue\0\u{1}beginTry\0\u{1}beginCatch\0\u{1}beginFinally\0\u{1}endTryCatchFinally\0\u{1}throwException\0\u{1}beginCodeString\0\u{1}endCodeString\0\u{1}beginBlockStatement\0\u{1}endBlockStatement\0\u{1}beginSwitch\0\u{1}beginSwitchCase\0\u{1}beginSwitchDefaultCase\0\u{1}endSwitchCase\0\u{1}endSwitch\0\u{1}switchBreak\0\u{1}loadNewTarget\0\u{1}print\0\u{1}explore\0\u{1}probe\0\u{1}fixup\0\u{1}beginWasmModule\0\u{1}endWasmModule\0\u{1}createWasmGlobal\0\u{1}createWasmMemory\0\u{1}createWasmTable\0\u{1}createWasmJSTag\0\u{1}createWasmTag\0\u{1}wrapPromising\0\u{1}wrapSuspending\0\u{1}bindMethod\0\u{1}bindFunction\0\u{1}consti64\0\u{1}consti32\0\u{1}constf32\0\u{1}constf64\0\u{1}wasmReturn\0\u{1}wasmJsCall\0\u{1}wasmi32CompareOp\0\u{1}wasmi64CompareOp\0\u{1}wasmf32CompareOp\0\u{1}wasmf64CompareOp\0\u{1}wasmi32EqualZero\0\u{1}wasmi64EqualZero\0\u{1}wasmi32BinOp\0\u{1}wasmi64BinOp\0\u{1}wasmi32UnOp\0\u{1}wasmi64UnOp\0\u{1}wasmf32BinOp\0\u{1}wasmf64BinOp\0\u{1}wasmf32UnOp\0\u{1}wasmf64UnOp\0\u{1}wasmWrapi64Toi32\0\u{1}wasmTruncatef32Toi32\0\u{1}wasmTruncatef64Toi32\0\u{1}wasmExtendi32Toi64\0\u{1}wasmTruncatef32Toi64\0\u{1}wasmTruncatef64Toi64\0\u{1}wasmConverti32Tof32\0\u{1}wasmConverti64Tof32\0\u{1}wasmDemotef64Tof32\0\u{1}wasmConverti32Tof64\0\u{1}wasmConverti64Tof64\0\u{1}wasmPromotef32Tof64\0\u{1}wasmReinterpretf32Asi32\0\u{1}wasmReinterpretf64Asi64\0\u{1}wasmReinterpreti32Asf32\0\u{1}wasmReinterpreti64Asf64\0\u{1}wasmSignExtend8Intoi32\0\u{1}wasmSignExtend16Intoi32\0\u{1}wasmSignExtend8Intoi64\0\u{1}wasmSignExtend16Intoi64\0\u{1}wasmSignExtend32Intoi64\0\u{1}wasmTruncateSatf32Toi32\0\u{1}wasmTruncateSatf64Toi32\0\u{1}wasmTruncateSatf32Toi64\0\u{1}wasmTruncateSatf64Toi64\0\u{1}wasmReassign\0\u{1}wasmDefineGlobal\0\u{1}wasmDefineTable\0\u{1}wasmDefineMemory\0\u{1}wasmDefineDataSegment\0\u{1}wasmLoadGlobal\0\u{1}wasmStoreGlobal\0\u{1}wasmTableGet\0\u{1}wasmTableSet\0\u{1}wasmTableSize\0\u{1}wasmTableGrow\0\u{1}wasmCallIndirect\0\u{1}wasmCallDirect\0\u{1}wasmReturnCallDirect\0\u{1}wasmReturnCallIndirect\0\u{1}wasmMemoryLoad\0\u{1}wasmMemoryStore\0\u{1}wasmAtomicLoad\0\u{1}wasmAtomicStore\0\u{1}wasmAtomicRMW\0\u{1}wasmAtomicCmpxchg\0\u{1}wasmMemorySize\0\u{1}wasmMemoryGrow\0\u{1}wasmMemoryFill\0\u{1}wasmMemoryInit\0\u{1}wasmDropDataSegment\0\u{1}beginWasmFunction\0\u{1}endWasmFunction\0\u{1}wasmBeginBlock\0\u{1}wasmEndBlock\0\u{1}wasmBeginLoop\0\u{1}wasmEndLoop\0\u{1}wasmBranch\0\u{1}wasmBranchIf\0\u{1}wasmBranchTable\0\u{1}wasmNop\0\u{1}wasmBeginIf\0\u{1}wasmBeginElse\0\u{1}wasmEndIf\0\u{1}wasmBeginTryTable\0\u{1}wasmEndTryTable\0\u{1}wasmBeginTry\0\u{1}wasmBeginCatchAll\0\u{1}wasmBeginCatch\0\u{1}wasmEndTry\0\u{1}wasmBeginTryDelegate\0\u{1}wasmEndTryDelegate\0\u{1}wasmThrow\0\u{1}wasmRethrow\0\u{1}wasmThrowRef\0\u{1}wasmDefineTag\0\u{1}constSimd128\0\u{1}wasmSimd128Compare\0\u{1}wasmSimd128IntegerUnOp\0\u{1}wasmSimd128IntegerBinOp\0\u{1}wasmSimd128IntegerTernaryOp\0\u{1}wasmSimd128FloatUnOp\0\u{1}wasmSimd128FloatBinOp\0\u{1}wasmSimd128FloatTernaryOp\0\u{1}wasmSimdSplat\0\u{1}wasmSimdExtractLane\0\u{1}wasmSimdReplaceLane\0\u{1}wasmSimdStoreLane\0\u{1}wasmSimdLoadLane\0\u{1}wasmSimdLoad\0\u{1}wasmUnreachable\0\u{1}wasmSelect\0\u{1}wasmBeginTypeGroup\0\u{1}wasmEndTypeGroup\0\u{1}wasmDefineArrayType\0\u{1}wasmDefineStructType\0\u{1}wasmDefineForwardOrSelfReference\0\u{1}wasmResolveForwardReference\0\u{1}wasmArrayNewFixed\0\u{1}wasmArrayNewDefault\0\u{1}wasmArrayLen\0\u{1}wasmArrayGet\0\u{1}wasmArraySet\0\u{1}wasmStructNewDefault\0\u{1}wasmStructGet\0\u{1}wasmStructSet\0\u{1}wasmRefNull\0\u{1}wasmRefIsNull\0\u{1}wasmRefI31\0\u{1}wasmI31Get\0\u{1}wasmAnyConvertExtern\0\u{1}wasmExternConvertAny\0\u{1}wasmMemoryCopy\0\u{1}wasmDefineElementSegment\0\u{1}wasmTableInit\0\u{1}wasmDropElementSegment\0\u{1}wasmTableCopy\0\u{1}wasmDefineSignatureType\0\u{1}createNamedDisposableVariable\0\u{1}createNamedAsyncDisposableVariable\0\u{1}beginClassInstanceComputedMethod\0\u{1}endClassInstanceComputedMethod\0\u{1}beginClassStaticComputedMethod\0\u{1}endClassStaticComputedMethod\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -7298,6 +7361,97 @@ extension Fuzzilli_Protobuf_Instruction: SwiftProtobuf.Message, SwiftProtobuf._M self.operation = .wasmTableCopy(v) } }() + case 329: try { + var v: Fuzzilli_Protobuf_WasmDefineSignatureType? + var hadOneofValue = false + if let current = self.operation { + hadOneofValue = true + if case .wasmDefineSignatureType(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.operation = .wasmDefineSignatureType(v) + } + }() + case 330: try { + var v: Fuzzilli_Protobuf_CreateNamedDisposableVariable? + var hadOneofValue = false + if let current = self.operation { + hadOneofValue = true + if case .createNamedDisposableVariable(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.operation = .createNamedDisposableVariable(v) + } + }() + case 331: try { + var v: Fuzzilli_Protobuf_CreateNamedAsyncDisposableVariable? + var hadOneofValue = false + if let current = self.operation { + hadOneofValue = true + if case .createNamedAsyncDisposableVariable(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.operation = .createNamedAsyncDisposableVariable(v) + } + }() + case 332: try { + var v: Fuzzilli_Protobuf_BeginClassInstanceComputedMethod? + var hadOneofValue = false + if let current = self.operation { + hadOneofValue = true + if case .beginClassInstanceComputedMethod(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.operation = .beginClassInstanceComputedMethod(v) + } + }() + case 333: try { + var v: Fuzzilli_Protobuf_EndClassInstanceComputedMethod? + var hadOneofValue = false + if let current = self.operation { + hadOneofValue = true + if case .endClassInstanceComputedMethod(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.operation = .endClassInstanceComputedMethod(v) + } + }() + case 334: try { + var v: Fuzzilli_Protobuf_BeginClassStaticComputedMethod? + var hadOneofValue = false + if let current = self.operation { + hadOneofValue = true + if case .beginClassStaticComputedMethod(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.operation = .beginClassStaticComputedMethod(v) + } + }() + case 335: try { + var v: Fuzzilli_Protobuf_EndClassStaticComputedMethod? + var hadOneofValue = false + if let current = self.operation { + hadOneofValue = true + if case .endClassStaticComputedMethod(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.operation = .endClassStaticComputedMethod(v) + } + }() default: break } } @@ -8620,6 +8774,34 @@ extension Fuzzilli_Protobuf_Instruction: SwiftProtobuf.Message, SwiftProtobuf._M guard case .wasmTableCopy(let v)? = self.operation else { preconditionFailure() } try visitor.visitSingularMessageField(value: v, fieldNumber: 328) }() + case .wasmDefineSignatureType?: try { + guard case .wasmDefineSignatureType(let v)? = self.operation else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 329) + }() + case .createNamedDisposableVariable?: try { + guard case .createNamedDisposableVariable(let v)? = self.operation else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 330) + }() + case .createNamedAsyncDisposableVariable?: try { + guard case .createNamedAsyncDisposableVariable(let v)? = self.operation else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 331) + }() + case .beginClassInstanceComputedMethod?: try { + guard case .beginClassInstanceComputedMethod(let v)? = self.operation else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 332) + }() + case .endClassInstanceComputedMethod?: try { + guard case .endClassInstanceComputedMethod(let v)? = self.operation else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 333) + }() + case .beginClassStaticComputedMethod?: try { + guard case .beginClassStaticComputedMethod(let v)? = self.operation else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 334) + }() + case .endClassStaticComputedMethod?: try { + guard case .endClassStaticComputedMethod(let v)? = self.operation else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 335) + }() case nil: break } try unknownFields.traverse(visitor: &visitor) diff --git a/Sources/Fuzzilli/Protobuf/program.proto b/Sources/Fuzzilli/Protobuf/program.proto index 152bf07c3..46c3f7a9c 100644 --- a/Sources/Fuzzilli/Protobuf/program.proto +++ b/Sources/Fuzzilli/Protobuf/program.proto @@ -352,6 +352,13 @@ message Instruction { WasmTableInit wasmTableInit = 326; WasmDropElementSegment wasmDropElementSegment = 327; WasmTableCopy wasmTableCopy = 328; + WasmDefineSignatureType wasmDefineSignatureType = 329; + CreateNamedDisposableVariable createNamedDisposableVariable = 330; + CreateNamedAsyncDisposableVariable createNamedAsyncDisposableVariable = 331; + BeginClassInstanceComputedMethod beginClassInstanceComputedMethod = 332; + EndClassInstanceComputedMethod endClassInstanceComputedMethod = 333; + BeginClassStaticComputedMethod beginClassStaticComputedMethod = 334; + EndClassStaticComputedMethod endClassStaticComputedMethod = 335; } } diff --git a/Sources/Fuzzilli/Util/MockFuzzer.swift b/Sources/Fuzzilli/Util/MockFuzzer.swift index 85827b427..a7bab9463 100644 --- a/Sources/Fuzzilli/Util/MockFuzzer.swift +++ b/Sources/Fuzzilli/Util/MockFuzzer.swift @@ -117,7 +117,7 @@ public func makeMockFuzzer(config maybeConfiguration: Configuration? = nil, engi let codeGenerators = WeightedList( (CodeGenerators + WasmCodeGenerators).map { guard let weight = codeGeneratorWeights[$0.name] else { - fatalError("Missing weight for code generator \($0.name) in CodeGeneratorWeights.swift") + fatalError("Missing weight for CodeGenerator \($0.name) in CodeGeneratorWeights.swift") } return ($0, weight) } + additionalCodeGenerators) diff --git a/Sources/FuzzilliCli/Profiles/DuktapeProfile.swift b/Sources/FuzzilliCli/Profiles/DuktapeProfile.swift index c277af56e..853aeae20 100644 --- a/Sources/FuzzilliCli/Profiles/DuktapeProfile.swift +++ b/Sources/FuzzilliCli/Profiles/DuktapeProfile.swift @@ -47,7 +47,7 @@ let duktapeProfile = Profile( additionalProgramTemplates: WeightedList([]), disabledCodeGenerators: [], - + disabledMutators: [], additionalBuiltins: [ @@ -63,5 +63,7 @@ let duktapeProfile = Profile( additionalObjectGroups: [], + additionalEnumerations: [], + optionalPostProcessor: nil ) diff --git a/Sources/FuzzilliCli/Profiles/JSCProfile.swift b/Sources/FuzzilliCli/Profiles/JSCProfile.swift index 3607b2ff8..e415ed64c 100644 --- a/Sources/FuzzilliCli/Profiles/JSCProfile.swift +++ b/Sources/FuzzilliCli/Profiles/JSCProfile.swift @@ -127,5 +127,7 @@ let jscProfile = Profile( additionalObjectGroups: [], + additionalEnumerations: [], + optionalPostProcessor: nil ) diff --git a/Sources/FuzzilliCli/Profiles/JerryscriptProfile.swift b/Sources/FuzzilliCli/Profiles/JerryscriptProfile.swift index b000043f9..b59833a4c 100644 --- a/Sources/FuzzilliCli/Profiles/JerryscriptProfile.swift +++ b/Sources/FuzzilliCli/Profiles/JerryscriptProfile.swift @@ -47,7 +47,7 @@ let jerryscriptProfile = Profile( additionalProgramTemplates: WeightedList([]), disabledCodeGenerators: [], - + disabledMutators: [], additionalBuiltins: [ @@ -59,5 +59,7 @@ let jerryscriptProfile = Profile( additionalObjectGroups: [], + additionalEnumerations: [], + optionalPostProcessor: nil ) diff --git a/Sources/FuzzilliCli/Profiles/NjsProfile.swift b/Sources/FuzzilliCli/Profiles/NjsProfile.swift index 6d6efe243..5b629c028 100644 --- a/Sources/FuzzilliCli/Profiles/NjsProfile.swift +++ b/Sources/FuzzilliCli/Profiles/NjsProfile.swift @@ -55,5 +55,7 @@ let njsProfile = Profile( additionalObjectGroups: [], + additionalEnumerations: [], + optionalPostProcessor: nil -) \ No newline at end of file +) diff --git a/Sources/FuzzilliCli/Profiles/Profile.swift b/Sources/FuzzilliCli/Profiles/Profile.swift index 9a814fdad..5815f716b 100644 --- a/Sources/FuzzilliCli/Profiles/Profile.swift +++ b/Sources/FuzzilliCli/Profiles/Profile.swift @@ -35,6 +35,7 @@ struct Profile { let additionalBuiltins: [String: ILType] let additionalObjectGroups: [ObjectGroup] + let additionalEnumerations: [ILType] // An optional post-processor that is executed for every sample generated for fuzzing and can modify it. let optionalPostProcessor: FuzzingPostProcessor? diff --git a/Sources/FuzzilliCli/Profiles/QjsProfile.swift b/Sources/FuzzilliCli/Profiles/QjsProfile.swift index 6bd8e60ec..83dd77d2c 100644 --- a/Sources/FuzzilliCli/Profiles/QjsProfile.swift +++ b/Sources/FuzzilliCli/Profiles/QjsProfile.swift @@ -57,5 +57,7 @@ let qjsProfile = Profile( additionalObjectGroups: [], + additionalEnumerations: [], + optionalPostProcessor: nil ) diff --git a/Sources/FuzzilliCli/Profiles/QtjsProfile.swift b/Sources/FuzzilliCli/Profiles/QtjsProfile.swift index d77968663..0ba7ff22c 100644 --- a/Sources/FuzzilliCli/Profiles/QtjsProfile.swift +++ b/Sources/FuzzilliCli/Profiles/QtjsProfile.swift @@ -65,5 +65,7 @@ let qtjsProfile = Profile( additionalObjectGroups: [], + additionalEnumerations: [], + optionalPostProcessor: nil ) diff --git a/Sources/FuzzilliCli/Profiles/Serenity.swift b/Sources/FuzzilliCli/Profiles/Serenity.swift index ac6f25e3a..286b5a520 100644 --- a/Sources/FuzzilliCli/Profiles/Serenity.swift +++ b/Sources/FuzzilliCli/Profiles/Serenity.swift @@ -52,5 +52,7 @@ let serenityProfile = Profile( additionalObjectGroups: [], + additionalEnumerations: [], + optionalPostProcessor: nil ) diff --git a/Sources/FuzzilliCli/Profiles/SpidermonkeyProfile.swift b/Sources/FuzzilliCli/Profiles/SpidermonkeyProfile.swift index fac719046..a7ce9123d 100644 --- a/Sources/FuzzilliCli/Profiles/SpidermonkeyProfile.swift +++ b/Sources/FuzzilliCli/Profiles/SpidermonkeyProfile.swift @@ -118,5 +118,7 @@ let spidermonkeyProfile = Profile( additionalObjectGroups: [], + additionalEnumerations: [], + optionalPostProcessor: nil ) diff --git a/Sources/FuzzilliCli/Profiles/V8CommonProfile.swift b/Sources/FuzzilliCli/Profiles/V8CommonProfile.swift index 56e925526..fb06b05d0 100644 --- a/Sources/FuzzilliCli/Profiles/V8CommonProfile.swift +++ b/Sources/FuzzilliCli/Profiles/V8CommonProfile.swift @@ -96,7 +96,7 @@ public let TurbofanVerifyTypeGenerator = CodeGenerator("TurbofanVerifyTypeGenera b.eval("%VerifyType(%@)", with: [v]) } -public let WorkerGenerator = RecursiveCodeGenerator("WorkerGenerator") { b in +public let WorkerGenerator = CodeGenerator("WorkerGenerator") { b in let workerSignature = Signature(withParameterCount: Int.random(in: 0...3)) // TODO(cffsmith): currently Fuzzilli does not know that this code is sent @@ -110,11 +110,11 @@ public let WorkerGenerator = RecursiveCodeGenerator("WorkerGenerator") { b in // Generate a random onmessage handler for incoming messages. let onmessageFunction = b.buildPlainFunction(with: .parameters(n: 1)) { args in - b.buildRecursive(block: 1, of: 2) + b.build(n: Int.random(in: 2...5)) } b.setProperty("onmessage", of: this, to: onmessageFunction) - b.buildRecursive(block: 2, of: 2) + b.build(n: Int.random(in: 3...10)) } let workerConstructor = b.createNamedVariable(forBuiltin: "Worker") @@ -135,6 +135,10 @@ public let WasmArrayGenerator = CodeGenerator("WasmArrayGenerator") { b in b.eval("%WasmArray()", hasOutput: true); } +public let PretenureAllocationSiteGenerator = CodeGenerator("PretenureAllocationSiteGenerator", inputs: .required(.object())) { b, obj in + b.eval("%PretenureAllocationSite(%@)", with: [obj]); +} + public let MapTransitionFuzzer = ProgramTemplate("MapTransitionFuzzer") { b in // This template is meant to stress the v8 Map transition mechanisms. // Basically, it generates a bunch of CreateObject, GetProperty, SetProperty, FunctionDefinition, @@ -169,37 +173,33 @@ public let MapTransitionFuzzer = ProgramTemplate("MapTransitionFuzzer") { b in } // Temporarily overwrite the active code generators with the following generators... - let primitiveValueGenerator = ValueGenerator("PrimitiveValue") { b, n in - for _ in 0..([ - (primitiveValueGenerator, 2), + (primitiveCodeGenerator, 2), (createObjectGenerator, 1), (objectMakerGenerator, 1), (objectConstructorGenerator, 1), @@ -294,7 +294,7 @@ public let MapTransitionFuzzer = ProgramTemplate("MapTransitionFuzzer") { b in (functionJitCallGenerator, 2) ])) - // ... run some of the ValueGenerators to create some initial objects ... + // ... run some of the CodeGenerators to create some initial objects ... b.buildPrefix() // ... and generate a bunch of code. b.build(n: 100, by: .generating) diff --git a/Sources/FuzzilliCli/Profiles/V8DebugProfile.swift b/Sources/FuzzilliCli/Profiles/V8DebugProfile.swift index 42cc7a1bc..02b36285f 100644 --- a/Sources/FuzzilliCli/Profiles/V8DebugProfile.swift +++ b/Sources/FuzzilliCli/Profiles/V8DebugProfile.swift @@ -95,5 +95,8 @@ let v8DebugProfile = Profile( additionalObjectGroups: [jsD8, jsD8Test, jsD8FastCAPI, gcOptions], + // The other v8 configs have this as well + additionalEnumerations: [.gcTypeEnum, .gcExecutionEnum], + optionalPostProcessor: nil ) diff --git a/Sources/FuzzilliCli/Profiles/V8HoleFuzzingProfile.swift b/Sources/FuzzilliCli/Profiles/V8HoleFuzzingProfile.swift index 3162811c1..d292c4cba 100644 --- a/Sources/FuzzilliCli/Profiles/V8HoleFuzzingProfile.swift +++ b/Sources/FuzzilliCli/Profiles/V8HoleFuzzingProfile.swift @@ -17,7 +17,7 @@ import Fuzzilli // This value generator inserts Hole leaks into the program. Use this if you // want to fuzz for Memory Corruption using holes, this should be used in // conjunction with the --hole-fuzzing runtime flag. -fileprivate let HoleLeakGenerator = ValueGenerator("HoleLeakGenerator") { b, args in +fileprivate let HoleLeakGenerator = CodeGenerator("HoleLeakGenerator", produces: [.jsAnything]) { b in b.eval("%LeakHole()", hasOutput: true) } @@ -35,13 +35,19 @@ let v8HoleFuzzingProfile = Profile( ] return args }, + processEnv: [:], + maxExecsBeforeRespawn: 1000, + timeout: 250, + codePrefix: """ """, + codeSuffix: """ """, + ecmaVersion: ECMAScriptVersion.es6, startupTests: [ @@ -67,15 +73,23 @@ let v8HoleFuzzingProfile = Profile( (V8GcGenerator, 10), (HoleLeakGenerator, 25), ], + additionalProgramTemplates: WeightedList([ ]), + disabledCodeGenerators: [], + disabledMutators: [], + additionalBuiltins: [ - "gc" : .function([] => (.undefined | .jsPromise)), + "gc" : .function([.opt(gcOptions.instanceType)] => (.undefined | .jsPromise)), "d8" : .object(), "Worker" : .constructor([.jsAnything, .object()] => .object(withMethods: ["postMessage","getMessage"])), ], - additionalObjectGroups: [], + + additionalObjectGroups: [jsD8, jsD8Test, jsD8FastCAPI, gcOptions], + + additionalEnumerations: [.gcTypeEnum, .gcExecutionEnum], + optionalPostProcessor: nil ) diff --git a/Sources/FuzzilliCli/Profiles/V8Profile.swift b/Sources/FuzzilliCli/Profiles/V8Profile.swift index 8b1cf70d1..f85c88470 100644 --- a/Sources/FuzzilliCli/Profiles/V8Profile.swift +++ b/Sources/FuzzilliCli/Profiles/V8Profile.swift @@ -14,7 +14,6 @@ import Fuzzilli - let v8Profile = Profile( processArgs: { randomize in var args = [ @@ -74,7 +73,7 @@ let v8Profile = Profile( // // Future features that should sometimes be enabled. // - if probability(0.25) { + if probability(0.1) { args.append("--minor-ms") } @@ -90,10 +89,11 @@ let v8Profile = Profile( args.append("--turboshaft-typed-optimizations") } - if probability(0.4) { + if probability(0.5) { args.append("--turbolev") - } else if probability(0.15) { - args.append("--turbolev-future") + if probability(0.82) { + args.append("--turbolev-future") + } } if probability(0.1) { @@ -120,6 +120,15 @@ let v8Profile = Profile( args.append("--precise-object-pinning") } + if probability(0.1) { + args.append("--handle-weak-ref-weakly-in-minor-gc") + } + + if probability(0.1) { + let stackSize = Int.random(in: 54...863) + args.append("--stack-size=\(stackSize)") + } + // Temporarily enable the three flags below with high probability to // stress-test JSPI. // Lower the probabilities once we have enough coverage. @@ -285,6 +294,7 @@ let v8Profile = Profile( (WasmStructGenerator, 15), (WasmArrayGenerator, 15), + (PretenureAllocationSiteGenerator, 5), ], additionalProgramTemplates: WeightedList([ @@ -310,5 +320,7 @@ let v8Profile = Profile( additionalObjectGroups: [jsD8, jsD8Test, jsD8FastCAPI, gcOptions], + additionalEnumerations: [.gcTypeEnum, .gcExecutionEnum], + optionalPostProcessor: nil ) diff --git a/Sources/FuzzilliCli/Profiles/V8SandboxProfile.swift b/Sources/FuzzilliCli/Profiles/V8SandboxProfile.swift index e3b168ff1..633150068 100644 --- a/Sources/FuzzilliCli/Profiles/V8SandboxProfile.swift +++ b/Sources/FuzzilliCli/Profiles/V8SandboxProfile.swift @@ -493,12 +493,14 @@ let v8SandboxProfile = Profile( disabledMutators: [], additionalBuiltins: [ - "gc" : .function([] => (.undefined | .jsPromise)), + "gc" : .function([.opt(gcOptions.instanceType)] => (.undefined | .jsPromise)), "d8" : .object(), "Worker" : .constructor([.jsAnything, .object()] => .object(withMethods: ["postMessage","getMessage"])), ], - additionalObjectGroups: [], + additionalObjectGroups: [jsD8, jsD8Test, jsD8FastCAPI, gcOptions], + + additionalEnumerations: [.gcTypeEnum, .gcExecutionEnum], optionalPostProcessor: SandboxFuzzingPostProcessor() ) diff --git a/Sources/FuzzilliCli/Profiles/XSProfile.swift b/Sources/FuzzilliCli/Profiles/XSProfile.swift index 30dfa58da..2abb68b2c 100644 --- a/Sources/FuzzilliCli/Profiles/XSProfile.swift +++ b/Sources/FuzzilliCli/Profiles/XSProfile.swift @@ -58,17 +58,17 @@ fileprivate let HardenGenerator = CodeGenerator("HardenGenerator", inputs: .requ b.callFunction(harden, withArgs: [obj]) } -fileprivate let ModuleSourceGenerator = RecursiveCodeGenerator("ModuleSourceGenerator") { b in +fileprivate let ModuleSourceGenerator = CodeGenerator("ModuleSourceGenerator") { b in let moduleSourceConstructor = b.createNamedVariable(forBuiltin: "ModuleSource") let code = b.buildCodeString() { - b.buildRecursive() + b.build(n: 5) } b.construct(moduleSourceConstructor, withArgs: [code]) } -fileprivate let CompartmentGenerator = RecursiveCodeGenerator("CompartmentGenerator") { b in +fileprivate let CompartmentGenerator = CodeGenerator("CompartmentGenerator") { b in let compartmentConstructor = b.createNamedVariable(forBuiltin: "Compartment") var endowments = [String: Variable]() // may be used as endowments argument or globalLexicals @@ -84,16 +84,16 @@ fileprivate let CompartmentGenerator = RecursiveCodeGenerator("CompartmentGenera // to do: populate moduleMap let moduleMapObject = b.createObject(with: moduleMap) let resolveHook = b.buildPlainFunction(with: .parameters(n: 2)) { _ in - b.buildRecursive(block: 1, of: 4) + b.build(n: 5) b.doReturn(b.randomJsVariable()) } let moduleMapHook = b.buildPlainFunction(with: .parameters(n: 1)) { _ in - b.buildRecursive(block: 2, of: 4) + b.build(n: 5) b.doReturn(b.randomJsVariable()) } let loadNowHook = b.dup(moduleMapHook) let loadHook = b.buildAsyncFunction(with: .parameters(n: 1)) { _ in - b.buildRecursive(block: 3, of: 4) + b.build(n: 5) b.doReturn(b.randomJsVariable()) } options["resolveHook"] = resolveHook @@ -111,7 +111,7 @@ fileprivate let CompartmentGenerator = RecursiveCodeGenerator("CompartmentGenera if probability(0.5) { let code = b.buildCodeString() { - b.buildRecursive(block: 4, of: 4) + b.build(n: 5) } b.callMethod("evaluate", on: compartment, withArgs: [code]) } @@ -131,7 +131,7 @@ fileprivate let UnicodeStringGenerator = CodeGenerator("UnicodeStringGenerator") fileprivate let CompartmentEvaluateGenerator = CodeGenerator("CompartmentEvaluateGenerator", inputs: .required(.object(ofGroup: "Compartment"))) { b, target in let code = b.buildCodeString() { - b.buildRecursive() + b.build(n: 5) } b.callMethod("evaluate", on: target, withArgs: [code]) } @@ -352,5 +352,7 @@ let xsProfile = Profile( additionalObjectGroups: [jsCompartments, jsCompartmentConstructor, jsModuleSources, jsModuleSourceConstructor], + additionalEnumerations: [], + optionalPostProcessor: nil ) diff --git a/Sources/FuzzilliCli/main.swift b/Sources/FuzzilliCli/main.swift index 373c3870f..33f07b33d 100644 --- a/Sources/FuzzilliCli/main.swift +++ b/Sources/FuzzilliCli/main.swift @@ -316,7 +316,7 @@ if swarmTesting { logger.info("Weight | CodeGenerator") } -let disabledGenerators = Set(profile.disabledCodeGenerators) +let disableCodeGenerators = Set(profile.disabledCodeGenerators) let additionalCodeGenerators = profile.additionalCodeGenerators let codeGeneratorsToUse = if enableWasm { @@ -328,14 +328,14 @@ let codeGeneratorsToUse = if enableWasm { let standardCodeGenerators: [(CodeGenerator, Int)] = codeGeneratorsToUse.map { guard let weight = codeGeneratorWeights[$0.name] else { - logger.fatal("Missing weight for code generator \($0.name) in CodeGeneratorWeights.swift") + logger.fatal("Missing weight for CodeGenerator \($0.name) in CodeGeneratorWeights.swift") } return ($0, weight) } var codeGenerators: WeightedList = WeightedList([]) for (generator, var weight) in (additionalCodeGenerators + standardCodeGenerators) { - if disabledGenerators.contains(generator.name) { + if disableCodeGenerators.contains(generator.name) { continue } @@ -463,13 +463,16 @@ func makeFuzzer(with configuration: Configuration) -> Fuzzer { } // The environment containing available builtins, property names, and method names. - let environment = JavaScriptEnvironment(additionalBuiltins: profile.additionalBuiltins, additionalObjectGroups: profile.additionalObjectGroups) + let environment = JavaScriptEnvironment(additionalBuiltins: profile.additionalBuiltins, additionalObjectGroups: profile.additionalObjectGroups, additionalEnumerations: profile.additionalEnumerations) if !profile.additionalBuiltins.isEmpty { logger.verbose("Loaded additional builtins from profile: \(profile.additionalBuiltins.map { $0.key })") } if !profile.additionalObjectGroups.isEmpty { logger.verbose("Loaded additional ObjectGroups from profile: \(profile.additionalObjectGroups.map { $0.name })") } + if !profile.additionalEnumerations.isEmpty { + logger.verbose("Loaded additional Enumerations from profile: \(profile.additionalEnumerations.map { $0.group! })") + } // A lifter to translate FuzzIL programs to JavaScript. let lifter = JavaScriptLifter(prefix: profile.codePrefix, diff --git a/Targets/JavaScriptCore/README.md b/Targets/JavaScriptCore/README.md index f54e3eedf..97c689aa3 100644 --- a/Targets/JavaScriptCore/README.md +++ b/Targets/JavaScriptCore/README.md @@ -6,4 +6,4 @@ To build JavaScriptCore (jsc) for fuzzing: 2. Apply Patches/\*. The patches should apply cleanly to the git revision specified in [./REVISION](./REVISION) (_Note_: If you clone WebKit from `git.webkit.org`, the commit hash will differ) 3. Run the fuzzbuild.sh script in the webkit root directory -4. FuzzBuild/Debug/bin/jsc will be the JavaScript shell for the fuzzer +4. WebKitBuild/Fuzzilli/bin/jsc will be the JavaScript shell for the fuzzer diff --git a/Tests/FuzzilliTests/AnalyzerTest.swift b/Tests/FuzzilliTests/AnalyzerTest.swift index 4bffb5cde..546e99cd7 100644 --- a/Tests/FuzzilliTests/AnalyzerTest.swift +++ b/Tests/FuzzilliTests/AnalyzerTest.swift @@ -120,14 +120,14 @@ class AnalyzerTests: XCTestCase { XCTAssertEqual(b.context, .javascript) let obj = b.loadString("HelloWorld") b.buildWith(obj) { - XCTAssertEqual(b.context, [.javascript, .with]) + XCTAssertEqual(b.context, [.javascript]) b.buildPlainFunction(with: .parameters(n: 3)) { _ in XCTAssertEqual(b.context, [.javascript, .subroutine]) b.buildWith(obj) { - XCTAssertEqual(b.context, [.javascript, .subroutine, .with]) + XCTAssertEqual(b.context, [.javascript, .subroutine]) } } - XCTAssertEqual(b.context, [.javascript, .with]) + XCTAssertEqual(b.context, [.javascript]) b.createNamedVariable(b.randomPropertyName(), declarationMode: .none) } diff --git a/Tests/FuzzilliTests/CompilerTests/computed_and_indexed_properties.js b/Tests/FuzzilliTests/CompilerTests/computed_and_indexed_properties.js new file mode 100644 index 000000000..369b93090 --- /dev/null +++ b/Tests/FuzzilliTests/CompilerTests/computed_and_indexed_properties.js @@ -0,0 +1,387 @@ +// TODO(https://crbug.com/446634535): Not yet supported cases are commented +// out below. + +console.log("Computed object property"); +(() => { + const p = 'theAnswerIs'; + const obj = { [p] : 42 }; + console.log(obj.theAnswerIs); +})(); + +console.log("Computed object method"); +(() => { + const p = 'theAnswerIs'; + const obj = { [p]() { return 42; } }; + console.log(obj.theAnswerIs()); +})(); + +console.log("Computed class property (field)"); +(() => { + function classify (name) { + return class { + [name] = 42; + } + } + + console.log(new (classify("theAnswerIs"))().theAnswerIs); +})(); + +console.log("Computed static class property (field)"); +(() => { + function classify (name) { + return class { + static [name] = 42; + } + } + + console.log((classify("theAnswerIs")).theAnswerIs); +})(); + +/* +console.log("Computed class property (getter/setter)"); +(() => { + function classify (name) { + return class { + answer = 7; + + get [name]() { + console.log("Heavy calculations"); + return this.answer; + } + + set [name](answer) { + console.log(`The answer was ${this.answer}`); + this.answer = answer; + console.log(`Now the answer is ${this.answer}`); + } + } + } + + const c = new (classify("theAnswerIs"))(); + console.log(c.theAnswerIs); + c.theAnswerIs = 42; + console.log(c.theAnswerIs); +})(); +*/ + +/* +console.log("Computed static class property (getter/setter)"); +(() => { + function classify (name) { + return class { + static answer = 7; + + static get [name]() { + console.log("Heavy calculations"); + return this.answer; + } + + static set [name](answer) { + console.log(`The answer was ${this.answer}`); + this.answer = answer; + console.log(`Now the answer is ${this.answer}`); + } + } + } + + const c = classify("theAnswerIs"); + console.log(c.theAnswerIs); + c.theAnswerIs = 42; + console.log(c.theAnswerIs); +})(); +*/ + +console.log("Computed class property (method)"); +(() => { + function classify (name) { + return class { + [name]() { + console.log("Heavy calculations"); + return 42; + } + } + } + + console.log(new (classify("theAnswerIs"))().theAnswerIs()); +})(); + +console.log("Computed static class property (method)"); +(() => { + function classify (name) { + return class { + static [name]() { + console.log("Heavy calculations"); + return 42; + } + } + } + + console.log(classify("theAnswerIs").theAnswerIs()); +})(); + +console.log("Indexed class property (field)"); +(() => { + class C { + 42 = 42; + } + const c = new C(); + console.log(c[42]); +})(); + +console.log("Indexed static class property (field)"); +(() => { + class C { + static 42 = 42; + } + console.log(C[42]); +})(); + +/* +console.log("Indexed class property (method)"); +(() => { + class C { + 42() { + console.log("Heavy calculations"); + return 42; + } + } + const c = new C(); + console.log(c[42]()); +})(); +*/ + +/* +console.log("Indexed static class property (method)"); +(() => { + class C { + static 42() { + console.log("Heavy calculations"); + return 42; + } + } + console.log(C[42]()); +})(); +*/ + +/* +console.log("Indexed class property (getter/setter)"); +(() => { + class C { + answer = 7; + + get 42() { + console.log("Heavy calculations"); + return this.answer; + } + + set 42(answer) { + console.log(`The answer was ${this.answer}`); + this.answer = answer; + console.log(`Now the answer is ${this.answer}`); + } + } + const c = new C(); + console.log(c[42]); + c[42] = 42; + console.log(c[42]); +})(); +*/ + +/* +console.log("Indexed static class property (getter/setter)"); +(() => { + class C { + static answer = 7; + + static get 42() { + console.log("Heavy calculations"); + return this.answer; + } + + static set 42(answer) { + console.log(`The answer was ${this.answer}`); + this.answer = answer; + console.log(`Now the answer is ${this.answer}`); + } + } + console.log(C[42]); + C[42] = 42; + console.log(C[42]); +})(); +*/ + +console.log("String-indexed class property (field)"); +(() => { + class C { + "42" = 42; + } + const c = new C(); + console.log(c["42"]); +})(); + +console.log("String-indexed static class property (field)"); +(() => { + class C { + static "42" = 42; + } + console.log(C["42"]); +})(); + +console.log("String-indexed class property (method)"); +(() => { + class C { + "42"() { + console.log("Heavy calculations"); + return 42; + } + } + const c = new C(); + console.log(c["42"]()); +})(); + +console.log("String-indexed static class property (method)"); +(() => { + class C { + static "42"() { + console.log("Heavy calculations"); + return 42; + } + } + console.log(C["42"]()); +})(); + +/* +console.log("String-indexed class property (getter/setter)"); +(() => { + class C { + constructor() { + this.answer = 7; + } + + get "42"() { + console.log("Heavy calculations"); + return this.answer; + } + + set "42"(answer) { + console.log(`The answer was ${this.answer}`); + this.answer = answer; + console.log(`Now the answer is ${this.answer}`); + } + } + const c = new C(); + console.log(c["42"]); + c["42"] = 42; + console.log(c["42"]); +})(); +*/ + +/* +console.log("String-indexed static class property (getter/setter)"); +(() => { + class C { + static answer = 7; + + static get "42"() { + console.log("Heavy calculations"); + return this.answer; + } + + static set "42"(answer) { + console.log(`The answer was ${this.answer}`); + this.answer = answer; + console.log(`Now the answer is ${this.answer}`); + } + } + console.log(C["42"]); + C["42"] = 42; + console.log(C["42"]); +})(); +*/ + +console.log("String-literal class property (field)"); +(() => { + class C { + "theAnswerIs" = 42; + } + const c = new C(); + console.log(c.theAnswerIs); +})(); + +console.log("String-literal static class property (field)"); +(() => { + class C { + static "theAnswerIs" = 42; + } + console.log(C.theAnswerIs); +})(); + +console.log("String-literal class property (method)"); +(() => { + class C { + "theAnswerIs"() { + console.log("Heavy calculations"); + return 42; + } + } + const c = new C(); + console.log(c.theAnswerIs()); +})(); + +console.log("String-literal static class property (method)"); +(() => { + class C { + static "theAnswerIs"() { + console.log("Heavy calculations"); + return 42; + } + } + console.log(C.theAnswerIs()); +})(); + +/* +console.log("String-literal class property (getter/setter)"); +(() => { + class C { + answer = 7; + + get "theAnswerIs"() { + console.log("Heavy calculations"); + return this.answer; + } + + set "theAnswerIs"(answer) { + console.log(`The answer was ${this.answer}`); + this.answer = answer; + console.log(`Now the answer is ${this.answer}`); + } + } + const c = new C(); + console.log(c.theAnswerIs); + c.theAnswerIs = 42; + console.log(c.theAnswerIs); +})(); +*/ + +/* +console.log("String-literal static class property (getter/setter)"); +(() => { + class C { + static answer = 7; + + static get "theAnswerIs"() { + console.log("Heavy calculations"); + return this.answer; + } + + static set "theAnswerIs"(answer) { + console.log(`The answer was ${this.answer}`); + this.answer = answer; + console.log(`Now the answer is ${this.answer}`); + } + } + console.log(C.theAnswerIs); + C.theAnswerIs = 42; + console.log(C.theAnswerIs); +})(); +*/ diff --git a/Tests/FuzzilliTests/CompilerTests/explicit_resource_management.js b/Tests/FuzzilliTests/CompilerTests/explicit_resource_management.js new file mode 100644 index 000000000..2e0a50637 --- /dev/null +++ b/Tests/FuzzilliTests/CompilerTests/explicit_resource_management.js @@ -0,0 +1,56 @@ +(() => { + function f() { + using x = null; + console.log(x); + } + f(); +})(); + +(() => { + async function f() { + await using x = null; + console.log(x); + } + f(); +})(); + +(() => { + function * f() { + try { + yield 1; + yield 2; + yield 3; + } finally { + console.log("finally"); + } + } + + { + using o = f(), p = f(); + console.log(o.next()); + console.log(p.next()); + console.log(o.next()); + } + console.log("the end") +})(); + +async function test(){ + async function * f() { + try { + yield 1; + yield 2; + yield 3; + } finally { + console.log("finally"); + } + } + + { + await using o = f(), p = f(); + console.log(await o.next()); + console.log(await p.next()); + console.log(await o.next()); + } + console.log("the end") +}; +test(); diff --git a/Tests/FuzzilliTests/ContextGraphTest.swift b/Tests/FuzzilliTests/ContextGraphTest.swift new file mode 100644 index 000000000..1ffdbc914 --- /dev/null +++ b/Tests/FuzzilliTests/ContextGraphTest.swift @@ -0,0 +1,63 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import XCTest +@testable import Fuzzilli + +class ContextGraphTests: XCTestCase { + func testReachabilityCalculation() { + let fuzzer = makeMockFuzzer() + let contextGraph = ContextGraph(for: fuzzer.codeGenerators, withLogger: Logger(withLabel: "Test")) + + let reachableContexts = Set(contextGraph.getReachableContexts(from: .javascript)) + + let reachableContexts2 = Set(contextGraph.getReachableContexts(from: .javascript)) + + XCTAssertEqual(reachableContexts, reachableContexts2) + + XCTAssertEqual(reachableContexts, + Set([.javascript, + .method, + .classMethod, + .switchCase, + .classDefinition, + .switchBlock, + .asyncFunction, + .wasmFunction, + .wasm, + .loop, + .generatorFunction, + .objectLiteral, + .subroutine, + .wasmTypeGroup])) + } + + func testSubsetReachabilityCalculation() { + let fuzzer = makeMockFuzzer() + let contextGraph = ContextGraph(for: fuzzer.codeGenerators, withLogger: Logger(withLabel: "Test")) + let reachableContextsWasm = Set(contextGraph.getReachableContexts(from: .wasm)) + let reachableContextsWasm2 = Set(contextGraph.getReachableContexts(from: .wasm)) + + XCTAssertEqual(reachableContextsWasm, reachableContextsWasm2) + + let reachableContextsWasmFunction = Set(contextGraph.getReachableContexts(from: .wasmFunction)) + let reachableContextsJavaScript = Set(contextGraph.getReachableContexts(from: .javascript)) + + XCTAssertTrue(reachableContextsWasmFunction.isSubset(of: reachableContextsWasm)) + XCTAssertEqual(reachableContextsWasm, + Set([.wasmFunction, + .wasm])) + XCTAssertTrue(reachableContextsWasm.isSubset(of: reachableContextsJavaScript)) + } +} diff --git a/Tests/FuzzilliTests/EnvironmentTest.swift b/Tests/FuzzilliTests/EnvironmentTest.swift index 3bbf52ba9..d8dbcf271 100644 --- a/Tests/FuzzilliTests/EnvironmentTest.swift +++ b/Tests/FuzzilliTests/EnvironmentTest.swift @@ -73,6 +73,63 @@ class EnvironmentTests: XCTestCase { testForOutput(program: jsProg, runner: runner, outputString: "") } + /// Test that all interesting properties on the globalThis object are registered as builtins. + func testJSEnvironmentRegisteredBuiltins() throws { + let runner = try GetJavaScriptExecutorOrSkipTest(type: .user, withArguments: []) + let liveTestConfig = Configuration(logLevel: .error, enableInspection: true) + let fuzzer = makeMockFuzzer(config: liveTestConfig, environment: JavaScriptEnvironment()) + let b = fuzzer.makeBuilder() + + let globalThis = b.createNamedVariable(forBuiltin: "globalThis") + let object = b.createNamedVariable(forBuiltin: "Object") + let names = b.callMethod("getOwnPropertyNames", on: object, withArgs: [globalThis]) + let namesString = b.callMethod("join", on: names, withArgs: [b.loadString(",")]) + b.callFunction(b.createNamedVariable(forBuiltin: "output"), withArgs: [namesString]) + + let prog = b.finalize() + let jsProg = fuzzer.lifter.lift(prog, withOptions: []) + let jsEnvironment = b.fuzzer.environment + let result = testExecuteScript(program: jsProg, runner: runner) + XCTAssert(result.isSuccess, "\(result.error)\n\(result.output)") + var output = result.output + XCTAssertEqual(output.removeLast(), "\n") + + // Global builtins available in d8 that should not be fuzzed. + let skipped = [ + "fuzzilli", "testRunner", "quit", "load", "read", "readline", "readbuffer", + "writeFile", "write", "print", "printErr", "version", "os", "d8", "arguments", "Realm" + ] + // Global builtins that we probably should register but haven't done so, yet. + let TODO = [ + "globalThis", + "Iterator", + "setTimeout", + "console", + "escape", + "unescape", + "encodeURIComponent", + "encodeURI", + "decodeURIComponent", + "decodeURI", + // https://github.com/tc39/proposal-ecmascript-sharedmem/tree/main + "Atomics", + // https://github.com/tc39/proposal-explicit-resource-management + "DisposableStack", + "AsyncDisposableStack", + // https://github.com/tc39/proposal-float16array + "Float16Array", + // Web APIs + "performance", + "Worker", + ] + let ignore = Set(skipped + TODO) + + for builtin in output.split(separator: ",") where !ignore.contains(String(builtin)) { + XCTAssert(jsEnvironment.builtins.contains(String(builtin)), + "Unregistered builtin \(builtin)") + } + } + func convertTypedArrayToHex(_ b: ProgramBuilder, _ array: Variable) -> Variable { let toHex = b.buildArrowFunction(with: .parameters(n: 1)) { args in let hex = b.callMethod("toString", on: args[0], withArgs: [b.loadInt(16)]) diff --git a/Tests/FuzzilliTests/JSTyperTests.swift b/Tests/FuzzilliTests/JSTyperTests.swift index f95d45f08..56ab52d11 100644 --- a/Tests/FuzzilliTests/JSTyperTests.swift +++ b/Tests/FuzzilliTests/JSTyperTests.swift @@ -1824,7 +1824,7 @@ class JSTyperTests: XCTestCase { func testProducingGenerators() { // Make a simple object - let mockEnum = ILType.enumeration(ofName: "mockField", withValues: ["mockValue"]); + let mockEnum = ILType.enumeration(ofName: "MockEnum", withValues: ["mockValue"]); let mockObject = ObjectGroup( name: "MockObject", instanceType: nil, @@ -1837,17 +1837,28 @@ class JSTyperTests: XCTestCase { // Some things to keep track of how the generator was called var callCount = 0 var returnedVar: Variable? = nil + var generatedEnum: Variable? = nil // A simple generator - func generate(builder: ProgramBuilder) -> Variable { + func generateObject(builder: ProgramBuilder) -> Variable { callCount += 1 - let val = builder.loadString("mockValue") + let val = builder.loadEnum(mockEnum) + generatedEnum = val let variable = builder.createObject(with: ["mockField": val]) returnedVar = variable return variable } + + let mockNamedString = ILType.namedString(ofName: "NamedString"); + func generateString() -> String { + callCount += 1 + return "mockStringValue" + } + let fuzzer = makeMockFuzzer() fuzzer.environment.registerObjectGroup(mockObject) - fuzzer.environment.addProducingGenerator(forType: mockObject.instanceType, with: generate) + fuzzer.environment.registerEnumeration(mockEnum) + fuzzer.environment.addProducingGenerator(forType: mockObject.instanceType, with: generateObject) + fuzzer.environment.addNamedStringGenerator(forType: mockNamedString, with: generateString) let b = fuzzer.makeBuilder() b.buildPrefix() @@ -1857,6 +1868,25 @@ class JSTyperTests: XCTestCase { XCTAssertEqual(callCount, 1) // Test that the returned variable matches the generated one XCTAssertEqual(variable, returnedVar) + + + // Try to get it to invoke the string generator + let variable2 = b.findOrGenerateType(mockNamedString) + // Test that the generator was invoked + XCTAssertEqual(callCount, 2) + + // Test that the returned variable gets typed correctly + XCTAssert(b.type(of: variable2).Is(mockNamedString)) + XCTAssertEqual(b.type(of: variable2).group, "NamedString") + + // We already generated a mockEnum, look for it. + let foundEnum = b.randomVariable(ofType: mockEnum)! + // Test that it picked up the existing generated variable. + XCTAssertEqual(generatedEnum, foundEnum) + + // Test that the returned variable gets typed correctly. + XCTAssert(b.type(of: foundEnum).Is(mockEnum)) + XCTAssertEqual(b.type(of: foundEnum).group, "MockEnum") } func testFindConstructor() { diff --git a/Tests/FuzzilliTests/LifterTest.swift b/Tests/FuzzilliTests/LifterTest.swift index 14991b65f..8d020d82d 100644 --- a/Tests/FuzzilliTests/LifterTest.swift +++ b/Tests/FuzzilliTests/LifterTest.swift @@ -653,6 +653,10 @@ class LifterTests: XCTestCase { let two = b.loadInt(2) let baz = b.loadString("baz") let baz42 = b.binary(baz, i, with: .Add) + let toPrimitive = b.getProperty( + "toPrimitive", + of: b.createNamedVariable(forBuiltin: "Symbol")) + let sm = b.loadString("sm") let C = b.buildClassDefinition() { cls in cls.addInstanceProperty("foo") cls.addInstanceProperty("bar", value: baz) @@ -670,6 +674,11 @@ class LifterTests: XCTestCase { let foo = b.getProperty("foo", of: this) b.doReturn(foo) } + cls.addInstanceComputedMethod(toPrimitive, with: .parameters(n: 0)) { params in + let this = params[0] + let foo = b.getProperty("foo", of: this) + b.doReturn(foo) + } cls.addInstanceGetter(for: "baz") { this in b.doReturn(b.loadInt(1337)) } @@ -691,6 +700,11 @@ class LifterTests: XCTestCase { let foo = b.getProperty("foo", of: this) b.doReturn(foo) } + cls.addInstanceComputedMethod(sm, with: .parameters(n: 0)) { params in + let this = params[0] + let foo = b.getProperty("foo", of: this) + b.doReturn(foo) + } cls.addStaticGetter(for: "baz") { this in b.doReturn(b.loadInt(1337)) } @@ -732,7 +746,8 @@ class LifterTests: XCTestCase { let expected = """ const v3 = "baz" + 42; - class C4 { + const v5 = Symbol.toPrimitive; + class C7 { foo; bar = "baz"; 0 = 42; @@ -740,16 +755,19 @@ class LifterTests: XCTestCase { [-1]; [v3]; [2] = v3; - constructor(a6) { - this.foo = a6; + constructor(a9) { + this.foo = a9; } m() { return this.foo; } + [v5]() { + return this.foo; + } get baz() { return 1337; } - set baz(a12) { + set baz(a17) { } static foo; static { @@ -764,36 +782,39 @@ class LifterTests: XCTestCase { static m() { return this.foo; } + ["sm"]() { + return this.foo; + } static get baz() { return 1337; } - static set baz(a19) { + static set baz(a26) { } #ifoo; #ibar = "baz"; #im() { - const v21 = this.#ifoo; - this.#ibar = v21; - return v21; + const v28 = this.#ifoo; + this.#ibar = v28; + return v28; } - #in(a23) { + #in(a30) { this.#im(); - this.#ibar += a23; + this.#ibar += a30; } static #sfoo; static #sbar = "baz"; static #sm() { - const v26 = this.#sfoo; - this.#sbar = v26; - return v26; + const v33 = this.#sfoo; + this.#sbar = v33; + return v33; } - static #sn(a28) { + static #sn(a35) { this.#sm(); - this.#sbar += a28; + this.#sbar += a35; } } - new C4(42); - C4 = Uint8Array; + new C7(42); + C7 = Uint8Array; """ @@ -3138,7 +3159,9 @@ class LifterTests: XCTestCase { XCTAssertEqual(actual, expected) } - func testLoadDisposableVariableLifting() { + // This test is parameterized for normal and named variables with the + // respective concrete cases below. + func _testDisposableVariableLifting(_ variableName : String, generateVariable: (ProgramBuilder, Variable) -> Void) { let fuzzer = makeMockFuzzer() let b = fuzzer.makeBuilder() @@ -3152,7 +3175,7 @@ class LifterTests: XCTestCase { b.doReturn(v2) } } - b.loadDisposableVariable(disposableVariable) + generateVariable(b, disposableVariable) } b.callFunction(f) @@ -3168,15 +3191,29 @@ class LifterTests: XCTestCase { return 42; }, }; - using v7 = v6; + using %@ = v6; } f0(); """ - XCTAssertEqual(actual, expected) + XCTAssertEqual(actual, String(format: expected, variableName)) } - func testLoadAsyncDisposableVariableLifting() { + func testLoadDisposableVariableLifting() { + _testDisposableVariableLifting("v7", generateVariable: { (b: ProgramBuilder, v: Variable) in + b.loadDisposableVariable(v) + }) + } + + func testCreateNamedDisposableVariableLifting() { + _testDisposableVariableLifting("dis", generateVariable: { (b: ProgramBuilder, v: Variable) in + b.createNamedDisposableVariable("dis", v) + }) + } + + // This test is parameterized for normal and named variables with the + // respective concrete cases below. + func _testAsyncDisposableVariableLifting(_ variableName : String, generateVariable: (ProgramBuilder, Variable) -> Void) { let fuzzer = makeMockFuzzer() let b = fuzzer.makeBuilder() @@ -3190,7 +3227,7 @@ class LifterTests: XCTestCase { b.doReturn(v2) } } - b.loadAsyncDisposableVariable(asyncDisposableVariable) + generateVariable(b, asyncDisposableVariable) } let g = b.buildAsyncFunction(with: .parameters(n: 0)) { args in @@ -3210,7 +3247,7 @@ class LifterTests: XCTestCase { return 42; }, }; - await using v7 = v6; + await using %@ = v6; } async function f8() { await f0(); @@ -3218,7 +3255,19 @@ class LifterTests: XCTestCase { f8(); """ - XCTAssertEqual(actual, expected) + XCTAssertEqual(actual, String(format: expected, variableName)) + } + + func testLoadAsyncDisposableVariableLifting() { + _testAsyncDisposableVariableLifting("v7", generateVariable: { (b: ProgramBuilder, v: Variable) in + b.loadAsyncDisposableVariable(v) + }) + } + + func testCreateNamedAsyncDisposableVariableLifting() { + _testAsyncDisposableVariableLifting("dis", generateVariable: { (b: ProgramBuilder, v: Variable) in + b.createNamedAsyncDisposableVariable("dis", v) + }) } func testImportAnalysisMisTypedJS() { diff --git a/Tests/FuzzilliTests/MutatorTests.swift b/Tests/FuzzilliTests/MutatorTests.swift index 18cac018c..69137207b 100644 --- a/Tests/FuzzilliTests/MutatorTests.swift +++ b/Tests/FuzzilliTests/MutatorTests.swift @@ -97,4 +97,65 @@ class MutatorTests: XCTestCase { XCTAssertEqual(newEndTypeGroupInstructions.count, 1) XCTAssertGreaterThan(newEndTypeGroupInstructions[0].numInputs, 1) } + + func testCodeGenMutatorNamedStrings() { + // A generator that deterministically generates a different value each time. + var called = false + func generateString() -> String { + if called { + return "newValue" + } else { + called = true + return "originalValue" + } + } + let mockNamedString = ILType.namedString(ofName: "NamedString"); + + let env = JavaScriptEnvironment() + env.addNamedStringGenerator(forType: mockNamedString, with: generateString) + + let config = Configuration(logLevel: .error) + let fuzzer = makeMockFuzzer(config: config, environment: env) + let b = fuzzer.makeBuilder() + + // We need a minimum number of visible variables for codeGeneration. + b.loadInt(1) + b.loadInt(2) + + let _ = b.findOrGenerateType(mockNamedString) + XCTAssert(called) + + let prog = b.finalize() + + let originalLoadInstruction = prog.code.filter { instr in + instr.op is LoadString + } + + XCTAssertEqual(originalLoadInstruction.count, 1) + let originalLoad = originalLoadInstruction[0].op as! LoadString + XCTAssertEqual(originalLoad.value, "originalValue") + + // Mutator is probabalistic, try 10 times to ensure we are very likely + // to hit the generateString call. + let mutator = OperationMutator() + for _ in 1...10 { + let newBuilder = fuzzer.makeBuilder() + newBuilder.adopting(from: prog) { + mutator.mutate(originalLoadInstruction[0], newBuilder) + } + + let mutatedProg = newBuilder.finalize() + + let newLoadInstruction = mutatedProg.code.filter { instr in + instr.op is LoadString + } + + XCTAssertEqual(newLoadInstruction.count, 1) + let newLoad = newLoadInstruction[0].op as! LoadString + if newLoad.value == "newValue" { + return; + } + } + XCTFail("Mutator ran 10 times without rerunning custom string generator") + } } diff --git a/Tests/FuzzilliTests/ProgramBuilderTest.swift b/Tests/FuzzilliTests/ProgramBuilderTest.swift index 64c36ac67..ad5977ab0 100644 --- a/Tests/FuzzilliTests/ProgramBuilderTest.swift +++ b/Tests/FuzzilliTests/ProgramBuilderTest.swift @@ -42,6 +42,30 @@ class ProgramBuilderTests: XCTestCase { XCTAssertLessThanOrEqual(averageSize, 2*N) } + func testTemplateBuilding() { + let fuzzer = makeMockFuzzer() + let b = fuzzer.makeBuilder() + let numPrograms = 100 + // let maxExpectedProgramSize = 1000 + var sumOfProgramSizes = 0 + + for _ in 0..([ @@ -105,27 +136,25 @@ class ProgramBuilderTests: XCTestCase { for _ in 0..<10 { b.buildPrefix() let prefixSize = b.currentNumberOfInstructions - b.build(n: 100, by: .generating) + b.build(n: N, by: .generating) let program = b.finalize() // In this case, the size of the generated code must be exactly the requested size. - XCTAssertEqual(program.size - prefixSize, 100) + XCTAssertEqual(program.size - prefixSize, N) } } func testShapeOfGeneratedCode2() { let fuzzer = makeMockFuzzer() let b = fuzzer.makeBuilder() + let N = 100 - b.minRecursiveBudgetRelativeToParentBudget = 0.25 - b.maxRecursiveBudgetRelativeToParentBudget = 0.25 - - let simpleGenerator = ValueGenerator("SimpleGenerator") { b, _ in + let simpleGenerator = CodeGenerator("SimpleGenerator", produces: [.integer]) { b in b.loadInt(Int64.random(in: 0..<100)) } - let recursiveGenerator = RecursiveCodeGenerator("RecursiveGenerator") { b in + let recursiveGenerator = CodeGenerator("RecursiveGenerator") { b in b.buildRepeatLoop(n: 5) { _ in - b.buildRecursive() + b.build(n: 5) } } fuzzer.setCodeGenerators(WeightedList([ @@ -135,15 +164,19 @@ class ProgramBuilderTests: XCTestCase { for _ in 0..<10 { b.buildPrefix() - let prefixSize = b.currentNumberOfInstructions - b.build(n: 100, by: .generating) - let program = b.finalize() + let _ = b.currentNumberOfInstructions + // let prefixSize = b.currentNumberOfInstructions + b.build(n: N, by: .generating) + let _ = b.finalize() + // let program = b.finalize() // Uncomment to see the "shape" of generated programs on the console. //print(FuzzILLifter().lift(program)) - // The size may be larger, but only roughly by 100 * 0.25 + 100 * 0.25**2 + 100 * 0.25**3 ... (each block may overshoot its budget by roughly the maximum recursive block size). - XCTAssertLessThan(program.size - prefixSize, 150) + // TODO: We need some robust testing that tests whether we emit code + // in the correct distributions, instead of just checking here + // against a hard number, which might fail sparsely. + // XCTAssertLessThan(program.size - prefixSize, N * 4) } } @@ -652,6 +685,10 @@ class ProgramBuilderTests: XCTestCase { cls.addInstanceMethod("bar", with: .parameters(n: 0)) { args in } XCTAssert(cls.instanceMethods.contains("bar")) + XCTAssertFalse(cls.instanceComputedMethods.contains(s)) + cls.addInstanceComputedMethod(s, with: .parameters(n: 0)) { args in } + XCTAssert(cls.instanceComputedMethods.contains(s)) + XCTAssertFalse(cls.instanceGetters.contains("foobar")) cls.addInstanceGetter(for: "foobar") { this in } XCTAssert(cls.instanceGetters.contains("foobar")) @@ -676,6 +713,10 @@ class ProgramBuilderTests: XCTestCase { cls.addStaticMethod("bar", with: .parameters(n: 0)) { args in } XCTAssert(cls.staticMethods.contains("bar")) + XCTAssertFalse(cls.staticComputedMethods.contains(s)) + cls.addStaticComputedMethod(s, with: .parameters(n: 0)) { args in } + XCTAssert(cls.staticComputedMethods.contains(s)) + XCTAssertFalse(cls.staticGetters.contains("foobar")) cls.addStaticGetter(for: "foobar") { this in } XCTAssert(cls.staticGetters.contains("foobar")) @@ -726,7 +767,7 @@ class ProgramBuilderTests: XCTestCase { } let program = b.finalize() - XCTAssertEqual(program.size, 32) + XCTAssertEqual(program.size, 36) } func testSwitchBlockBuilding() { @@ -2549,12 +2590,10 @@ class ProgramBuilderTests: XCTestCase { // Check that the intermediate variables were generated as part of the recursion. let d8 = b.randomVariable(ofType: jsD8) - XCTAssertNotNil(d8) - XCTAssert(b.type(of: d8!).Is(jsD8)) + XCTAssert(d8 != nil && b.type(of: d8!).Is(jsD8)) let d8Test = b.randomVariable(ofType: jsD8Test) - XCTAssertNotNil(d8Test) - XCTAssert(b.type(of: d8Test!).Is(jsD8Test)) + XCTAssert(d8Test != nil && b.type(of: d8Test!).Is(jsD8Test)) } func testFindOrGenerateTypeWithGlobalConstructor() { @@ -2641,10 +2680,10 @@ class ProgramBuilderTests: XCTestCase { XCTAssert(b.type(of: var3).Is(type3)) // Get a random variable and then change the type let var1 = b.randomVariable(ofTypeOrSubtype: type1) - XCTAssertNotNil(var1) - XCTAssertEqual(var1, var3) + XCTAssert(var1 != nil) + XCTAssert(var1 == var3) let var4 = b.randomVariable(ofTypeOrSubtype: type4) - XCTAssertNil(var4) + XCTAssert(var4 == nil) } func testFindOrGenerateTypeWithSubtype() { @@ -2737,7 +2776,7 @@ class ProgramBuilderTests: XCTestCase { let type1 = ILType.object(ofGroup: "group1", withProperties: [], withMethods: []) let group1 = ObjectGroup(name: "group1", instanceType: type1, properties: [:], methods: [:]) - let testGenerator = CodeGenerator("testGenerator", produces: type1) { b in + let testGenerator = CodeGenerator("testGenerator", produces: [type1]) { b in let builtin = b.createNamedVariable(forBuiltin: "foo") b.callFunction(builtin) } @@ -2749,9 +2788,10 @@ class ProgramBuilderTests: XCTestCase { let fuzzer = makeMockFuzzer(config: config, environment: env, codeGenerators: [(testGenerator, 1)]) let b = fuzzer.makeBuilder() - b.buildPrefix() + // Manually create a Variable, we don't want to use buildPrefix as we then might accidentally already create variable of type `type1`. + b.loadInt(42) - XCTAssertNil(b.randomVariable(ofTypeOrSubtype: type1)) + XCTAssertTrue(b.randomVariable(ofTypeOrSubtype: type1) == nil) let obj = b.findOrGenerateType(type1) XCTAssert(b.type(of: obj).Is(type1)) } @@ -2905,4 +2945,107 @@ class ProgramBuilderTests: XCTestCase { let expected = b.finalize() XCTAssertEqual(actual, expected) } + + func testWasmBranchGeneratorSchedulingTest() { + let fuzzer = makeMockFuzzer() + let b = fuzzer.makeBuilder() + b.buildPrefix() + + // Pick the Branch Generator. + let generator = fuzzer.codeGenerators.filter { + $0.name == "WasmBranchGenerator" + }[0] + + // Now build this. + let syntheticGenerator = b.assembleSyntheticGenerator(for: generator) + XCTAssertNotNil(syntheticGenerator) + + // TODO: Hm I guess we're missing the block generator that produces a label in the CodeGenerator. + // See WasmBlockGenerator. + // There should be some logic that allows some nesting of a WasmBlockGenerator. + let _ = b.complete(generator: syntheticGenerator!, withBudget: 30) + // XCTAssertGreaterThan(numGeneratedInstructions, 30) + } + + func testWasmCallDirectGeneratorSchedulingTest() { + let fuzzer = makeMockFuzzer() + let b = fuzzer.makeBuilder() + b.buildPrefix() + + // Pick the Branch Generator. + let generator = fuzzer.codeGenerators.filter { + $0.name == "WasmStructNewDefaultGenerator" + }[0] + + // Now build this. + let syntheticGenerator = b.assembleSyntheticGenerator(for: generator) + XCTAssertNotNil(syntheticGenerator) + + let numGeneratedInstructions = b.complete(generator: syntheticGenerator!, withBudget: 30) + XCTAssertGreaterThan(numGeneratedInstructions, 0) + } + + func testWasmMemorySizeSchedulingTest() { + let fuzzer = makeMockFuzzer() + let numPrograms = 100 + + for _ in 0...numPrograms { + let b = fuzzer.makeBuilder() + b.buildPrefix() + + let generator = fuzzer.codeGenerators.filter { + $0.name == "WasmMemorySizeGenerator" + }[0] + + // Now build this. + let syntheticGenerator = b.assembleSyntheticGenerator(for: generator) + XCTAssertNotNil(syntheticGenerator) + + let N = 30 + // We might generate a lot more than 30 instructions to fulfill the constraints. + let numGeneratedInstructions = b.complete(generator: syntheticGenerator!, withBudget: N) + + let program = b.finalize() + + XCTAssertTrue(program.code.contains(where: { instr in + if case .wasmMemorySize = instr.op.opcode { + return true + } else { + return false + } + })) + XCTAssertGreaterThan(numGeneratedInstructions, 0) + } + } + + func testThatGeneratorsExistAndAreBuildableFromJs() { + let fuzzer = makeMockFuzzer() + let tries: Int = 10 + + var failures: [String: Int] = [:] + + for generator in fuzzer.codeGenerators { + let b = fuzzer.makeBuilder() + b.buildPrefix() + + if let syntheticGenerator = b.assembleSyntheticGenerator(for: generator) { + let generatedInstructions = b.complete(generator: syntheticGenerator, withBudget: 40) + + if generatedInstructions == 0 { + failures[generator.name, default: 0] += 1 + } + XCTAssertLessThan(syntheticGenerator.parts.count, 10) + } else { + XCTFail("Unable to generate synthetic CodeGenerator for \(generator.name) from JS.") + } + } + + for (name, failureCount) in failures { + if failureCount == tries { + // This might fail very sparsely, if so, we might want to check the offending Generator to see if we can improve handling for it. + // OTOH this is a fuzzer and we sometimes have weird situations... :) + XCTFail("\(name) always failed to complete.") + } + } + } } diff --git a/Tests/FuzzilliTests/TypeSystemTest.swift b/Tests/FuzzilliTests/TypeSystemTest.swift index f626372c9..dedccad09 100644 --- a/Tests/FuzzilliTests/TypeSystemTest.swift +++ b/Tests/FuzzilliTests/TypeSystemTest.swift @@ -1023,6 +1023,16 @@ class TypeSystemTests: XCTestCase { } + func testNamedStrings() { + let namedA = ILType.namedString(ofName: "A") + XCTAssert(namedA.Is(.string)) + let namedB = ILType.namedString(ofName: "B") + XCTAssertEqual(namedA | namedB, .string) + XCTAssertEqual(namedA & namedB, .nothing) + let objectA = ILType.object(ofGroup: "A", withProperties: ["a"]) + XCTAssertEqual(namedA & objectA, .nothing) + } + func testTypeDescriptions() { // Test primitive types XCTAssertEqual(ILType.undefined.description, ".undefined") @@ -1137,6 +1147,12 @@ class TypeSystemTests: XCTestCase { XCTAssertEqual(structDef.description, ".wasmTypeDef(1 Struct[mutable .wasmf32, " + "immutable .wasmRef(null Index 1 Struct), mutable .wasmRef(null Index 0 Array)])") + let signatureDesc = WasmSignatureTypeDescription( + signature: [.wasmi32, arrayRef] => [structRef, .wasmNullRef], typeGroupIndex: 0) + let signatureDef = ILType.wasmTypeDef(description: signatureDesc) + XCTAssertEqual(signatureDef.description, + ".wasmTypeDef(0 Func[[.wasmi32, .wasmRef(null Index 0 Array)] => " + + "[.wasmRef(Index 1 Struct), .wasmRef(.Abstract(null WasmNone))]])") // A generic index type without a type description. // These are e.g. used by the element types for arrays and structs inside the operation as diff --git a/Tests/FuzzilliTests/WasmTests.swift b/Tests/FuzzilliTests/WasmTests.swift index a193db418..0932b2421 100644 --- a/Tests/FuzzilliTests/WasmTests.swift +++ b/Tests/FuzzilliTests/WasmTests.swift @@ -4131,7 +4131,7 @@ class WasmFoundationTests: XCTestCase { b.buildWasmModule { wasmModule in let function = wasmModule.addWasmFunction(with: [] => []) { _, _, _ in return []} let segment = wasmModule.addElementSegment(elementsType: .wasmFunctionDef(), elements: [function]) - wasmModule.addWasmFunction(with: [] => []) { f, _, _ in + wasmModule.addWasmFunction(with: [] => []) { f, _, _ in f.wasmDropElementSegment(elementSegment: segment) return [] } @@ -4365,6 +4365,40 @@ class WasmGCTests: XCTestCase { testForOutput(program: jsProg, runner: runner, outputString: "-100,156,42,42,-10000,55536\n") } + func testSignature() throws { + let runner = try GetJavaScriptExecutorOrSkipTest() + let jsProg = buildAndLiftProgram { b in + let typeGroup = b.wasmDefineTypeGroup { + let arrayi32 = b.wasmDefineArrayType(elementType: .wasmi32, mutability: true) + let selfRef = b.wasmDefineForwardOrSelfReference() + let signature = b.wasmDefineSignatureType( + signature: [.wasmRef(.Index(), nullability: true), .wasmi32] => + [.wasmi32, .wasmRef(.Index(), nullability: true)], + indexTypes: [arrayi32, selfRef]) + return [arrayi32, signature] + } + + let module = b.buildWasmModule { wasmModule in + wasmModule.addWasmFunction(with: [] => [.wasmFuncRef]) { function, label, args in + // TODO(mliedtke): Do something more useful with the signature type than + // defining a null value for it and testing that it's implicitly convertible to + // .wasmFuncRef. + // TODO(mliedtke): Also properly test for self and forward references in both + // parameter and return types as well as type group dependencies once signatures + // are usable with more interesting operations. + [function.wasmRefNull(typeDef: typeGroup[1])] + } + } + + let exports = module.loadExports() + let outputFunc = b.createNamedVariable(forBuiltin: "output") + let wasmOut = b.callMethod(module.getExportedMethod(at: 0), on: exports, withArgs: []) + b.callFunction(outputFunc, withArgs: [wasmOut]) + } + + testForOutput(program: jsProg, runner: runner, outputString: "null\n") + } + func testSelfReferenceType() throws { let runner = try GetJavaScriptExecutorOrSkipTest() let liveTestConfig = Configuration(logLevel: .error, enableInspection: true)