diff --git a/Sources/Fuzzilli/Base/ProgramBuilder.swift b/Sources/Fuzzilli/Base/ProgramBuilder.swift index 0df465c1..e8b50f49 100644 --- a/Sources/Fuzzilli/Base/ProgramBuilder.swift +++ b/Sources/Fuzzilli/Base/ProgramBuilder.swift @@ -2346,6 +2346,10 @@ public class ProgramBuilder { let parameters = Parameters(count: parameterTypes.count, hasRestParameter: parameterTypes.hasRestParameter) return SubroutineDescriptor(withParameters: parameters, ofTypes: parameterTypes) } + + public static func parameters(_ parameters: Parameters) -> SubroutineDescriptor { + return SubroutineDescriptor(withParameters: parameters) + } private init(withParameters parameters: Parameters, ofTypes parameterTypes: ParameterList? = nil) { if let types = parameterTypes { @@ -2372,6 +2376,17 @@ public class ProgramBuilder { return instr.output } + @discardableResult + public func buildUnusualFunction(with parameters: Parameters, named functionName: String? = nil, _ body: ([Variable]) -> ()) -> Variable { + // Emit the BeginPlainFunction with the provided Parameters (which carry the detailed patterns). + let instr = emit(BeginPlainFunction(parameters: parameters, functionName: functionName)) + if enableRecursionGuard { hide(instr.output) } + body(Array(instr.innerOutputs)) + if enableRecursionGuard { unhide(instr.output) } + emit(EndPlainFunction()) + return instr.output + } + @discardableResult public func buildArrowFunction(with descriptor: SubroutineDescriptor, _ body: ([Variable]) -> ()) -> Variable { setParameterTypesForNextSubroutine(descriptor.parameterTypes) diff --git a/Sources/Fuzzilli/Compiler/Compiler.swift b/Sources/Fuzzilli/Compiler/Compiler.swift index ead5d884..f96f24fd 100644 --- a/Sources/Fuzzilli/Compiler/Compiler.swift +++ b/Sources/Fuzzilli/Compiler/Compiler.swift @@ -473,7 +473,10 @@ public class JavaScriptCompiler { try enterNewScope { let beginCatch = emit(BeginCatch()) if tryStatement.catch.hasParameter { - map(tryStatement.catch.parameter.name, to: beginCatch.innerOutput) + guard case let .identifierParameter(identifier) = tryStatement.catch.parameter.pattern else { + throw CompilerError.unsupportedFeatureError("Only identifier parameters are supported in catch blocks") + } + map(identifier.name, to: beginCatch.innerOutput) } for statement in tryStatement.catch.body { try compileStatement(statement) @@ -1186,14 +1189,113 @@ public class JavaScriptCompiler { } private func mapParameters(_ parameters: [Compiler_Protobuf_Parameter], to variables: ArraySlice) { - assert(parameters.count == variables.count) - for (param, v) in zip(parameters, variables) { - map(param.name, to: v) + // This function maps all identifiers in the function's signature to variables. + // These variables are the ones that will be used in the function's body. + // Example: Consider f([a, b]). This function has 1 parameter but 2 identifiers. We map a and b, not the parameter. + var flatParameterIdentifiers: [String] = [] + func extractIdentifiers(from param: Compiler_Protobuf_Parameter) { + switch param.pattern { + case .identifierParameter(let identifier): + flatParameterIdentifiers.append(identifier.name) + case .objectParameter(let object): + for property in object.parameters { + extractIdentifiers(from: property.parameterValue) + } + case .arrayParameter(let array): + for element in array.elements { + extractIdentifiers(from: element) + } + case .restParameter(let rest): + extractIdentifiers(from: rest.argument) + case .none: + fatalError("Unexpected parameter type: .none in mapParameters") + } + } + for param in parameters { + extractIdentifiers(from: param) + } + assert(flatParameterIdentifiers.count == variables.count, "The number of variables (\(variables.count)) does not match the number of parameters (\(flatParameterIdentifiers.count)).") + for (name, v) in zip(flatParameterIdentifiers, variables) { + map(name, to: v) } } private func convertParameters(_ parameters: [Compiler_Protobuf_Parameter]) -> Parameters { - return Parameters(count: parameters.count) + // This function creates a Parameters instance from the parameters of a function. + // 'Parameters' is mainly used for lifting and type inference. + // Only if the function has a complex parameter pattern, we store the patterns of the parameters. + var totalParameterCount = 0 + var patterns = [ParameterPattern]() + + func processParameter(_ param: Compiler_Protobuf_Parameter) -> ParameterPattern { + switch param.pattern { + case .identifierParameter: + totalParameterCount += 1 + return .identifier + case .objectParameter(let object): + var properties = [(String, ParameterPattern)]() + for property in object.parameters { + let key = property.parameterKey + let valuePattern = processParameter(property.parameterValue) + properties.append((key, valuePattern)) + } + return .object(properties: properties) + case .arrayParameter(let array): + var elements = [ParameterPattern]() + for element in array.elements { + let elementPattern = processParameter(element) + elements.append(elementPattern) + } + return .array(elements: elements) + case .restParameter(let rest): + return .rest(processParameter(rest.argument)) + default: + fatalError("Unexpected parameter type in convertParameters") + } + } + + for param in parameters { + let pattern = processParameter(param) + patterns.append(pattern) + } + + // Ensure that only the last parameter is a rest parameter. + for (index, pattern) in patterns.enumerated() { + if case .rest(_) = pattern, index != patterns.count - 1 { + fatalError("Only the last parameter can be a rest parameter") + } + } + + var hasRestParameter = false + if patterns.count > 0 { + let lastPattern = patterns[patterns.count - 1] + switch lastPattern { + case .rest(_): + hasRestParameter = true + default: + hasRestParameter = false + } + } + + // Determine if any parameter is complex (i.e. not a plain identifier) + var hasComplexPattern = false + for pattern in patterns { + switch pattern { + case .identifier: + continue + case .rest: + continue + default: + hasComplexPattern = true + break + } + } + // Save memory by only storing the patterns if there is at least one complex pattern. + if hasComplexPattern { + return Parameters(count: totalParameterCount, patterns: patterns, hasRestParameter: hasRestParameter) + } else { + return Parameters(count: totalParameterCount, hasRestParameter: hasRestParameter) + } } /// Convenience accessor for the currently active scope. diff --git a/Sources/Fuzzilli/Compiler/Parser/parser.js b/Sources/Fuzzilli/Compiler/Parser/parser.js index 09e88eeb..9aea992d 100644 --- a/Sources/Fuzzilli/Compiler/Parser/parser.js +++ b/Sources/Fuzzilli/Compiler/Parser/parser.js @@ -73,8 +73,52 @@ function parse(script, proto) { } function visitParameter(param) { - assert(param.type == 'Identifier', "Expected parameter type to have type 'Identifier', found " + param.type); - return make('Parameter', { name: param.name }); + assert(['Identifier', 'ObjectPattern', 'ArrayPattern', 'RestElement'].includes(param.type)); + + switch (param.type) { + case 'Identifier': { + return make('IdentifierParameter', { identifierParameter: { name: param.name } }); + } + case 'ObjectPattern': { + const parameters = param.properties.map(property => { + assert(property.type === 'ObjectProperty'); + assert(property.computed === false); + assert(property.method === false); + let parameterKey; + if (property.key.type === 'Identifier') { + parameterKey = property.key.name; + } else if (property.key.type === 'Literal') { + // Internally, literal keys are stored as strings. + parameterKey = property.key.value.toString(); + } else { + throw new Error('Unsupported property key type: ' + property.key.type); + } + const parameterValue = visitParameter(property.value); + return make('ObjectParameterProperty', { + parameterKey: parameterKey, + parameterValue: parameterValue + }); + }); + return make('ObjectParameter', { objectParameter: { parameters } }); + } + case 'ArrayPattern': { + const elements = param.elements.map(element => { + if (element === null) { + throw new Error('Holes in array parameters are not supported'); + } else { + return visitParameter(element); + } + }); + return make('ArrayParameter', { arrayParameter: { elements } }); + } + case 'RestElement': { + const argument = visitParameter(param.argument); + return make('RestParameter', { restParameter: { argument } }); + } + default: { + throw new Error('Unsupported parameter type: ' + param.type); + } + } } function visitParameters(params) { diff --git a/Sources/Fuzzilli/FuzzIL/Instruction.swift b/Sources/Fuzzilli/FuzzIL/Instruction.swift index c3a150a2..eb4618ed 100644 --- a/Sources/Fuzzilli/FuzzIL/Instruction.swift +++ b/Sources/Fuzzilli/FuzzIL/Instruction.swift @@ -327,6 +327,36 @@ extension Instruction: ProtobufConvertible { return Fuzzilli_Protobuf_Parameters.with { $0.count = UInt32(parameters.count) $0.hasRest_p = parameters.hasRestParameter + // If we have an explicit patterns array, convert it. + if let patterns = parameters.patterns { + $0.patterns = patterns.map { convertParameterPattern($0) } + } + } + } + + func convertParameterPattern(_ pattern: ParameterPattern) -> Fuzzilli_Protobuf_Parameter { + return Fuzzilli_Protobuf_Parameter.with { + switch pattern { + case .identifier: + $0.identifierParameter = Fuzzilli_Protobuf_IdentifierParameter() + case .object(let properties): + $0.objectParameter = Fuzzilli_Protobuf_ObjectParameter.with { + $0.parameters = properties.map { (key, valuePattern) in + return Fuzzilli_Protobuf_ObjectParameterProperty.with { + $0.parameterKey = key + $0.parameterValue = convertParameterPattern(valuePattern) + } + } + } + case .array(let elements): + $0.arrayParameter = Fuzzilli_Protobuf_ArrayParameter.with { + $0.elements = elements.map { convertParameterPattern($0) } + } + case .rest(let inner): + $0.restParameter = Fuzzilli_Protobuf_RestParameter.with { + $0.argument = convertParameterPattern(inner) + } + } } } @@ -1362,7 +1392,30 @@ extension Instruction: ProtobufConvertible { } func convertParameters(_ parameters: Fuzzilli_Protobuf_Parameters) -> Parameters { - return Parameters(count: Int(parameters.count), hasRestParameter: parameters.hasRest_p) + if !parameters.patterns.isEmpty { + let patterns = parameters.patterns.map { convertParameterPattern($0) } + return Parameters(count: Int(parameters.count), patterns: patterns, hasRestParameter: parameters.hasRest_p) + } else { + return Parameters(count: Int(parameters.count), hasRestParameter: parameters.hasRest_p) + } + } + func convertParameterPattern(_ proto: Fuzzilli_Protobuf_Parameter) -> ParameterPattern { + switch proto.pattern { + case .identifierParameter: + return .identifier + case .objectParameter(let object): + let properties = object.parameters.map { property in + return (property.parameterKey, convertParameterPattern(property.parameterValue)) + } + return .object(properties: properties) + case .arrayParameter(let array): + let elements = array.elements.map { convertParameterPattern($0) } + return .array(elements: elements) + case .restParameter(let rest): + return .rest(convertParameterPattern(rest.argument)) + default: + fatalError("Invalid parameter pattern") + } } // Converts to the Wasm world global type diff --git a/Sources/Fuzzilli/FuzzIL/JSTyper.swift b/Sources/Fuzzilli/FuzzIL/JSTyper.swift index 3ba07665..b792515e 100644 --- a/Sources/Fuzzilli/FuzzIL/JSTyper.swift +++ b/Sources/Fuzzilli/FuzzIL/JSTyper.swift @@ -258,8 +258,96 @@ public struct JSTyper: Analyzer { /// Attempts to infer the parameter types of the given subroutine definition. /// If parameter types have been added for this function, they are returned, otherwise generic parameter types (i.e. .anything parameters) for the parameters specified in the operation are generated. - private func inferSubroutineParameterList(of op: BeginAnySubroutine, at index: Int) -> ParameterList { - return signatures[index] ?? ParameterList(numParameters: op.parameters.count, hasRestParam: op.parameters.hasRestParameter) + /// + /// The inference mode is controlled by the `outerView` flag: + /// - When `outerView` is true (the default), one parameter is produced per top-level + /// pattern. This view is used for creating the function signature so that callees know + /// what parameters can be used to call the function. + /// - The 'innerView' refers to the local variables in the function that are created from destructuring patterns. + /// This view is used for updating the types of the local variables in the function. + private func inferSubroutineParameterList(of op: BeginAnySubroutine, at index: Int, outerView: Bool) -> ParameterList { + // If we already have a stored signature, return it. + if let signature = signatures[index] { + return signature + } + + // If no detailed patterns are provided, use a plain conversion. + guard let patterns = op.parameters.patterns else { + return ParameterList(numParameters: op.parameters.count, hasRestParam: op.parameters.hasRestParameter) + } + + var parameterList = ParameterList() + + // Process each top-level parameter pattern. + for (i, pattern) in patterns.enumerated() { + // Determine whether the current parameter should be considered a rest parameter. + let isRest = op.parameters.hasRestParameter && i == patterns.count - 1 + + if outerView { + // Outer view: produce one parameter per top-level pattern. + // If the parameter is destructured, its type reflects its composite structure. + let type = inferOuterParameterType(from: pattern) + let parameter: Parameter = isRest ? .rest(type) : .plain(type) + parameterList.append(parameter) + } else { + // Inner view: recursively flatten the pattern into its individual bindings. + // Each binding becomes its own Parameter. + let parameters = inferInnerParameters(from: pattern, isRest: isRest) + parameterList.append(contentsOf: parameters) + } + } + + return parameterList + } + + /// Recursively infers an ILType for a parameter in the outer view. + /// For identifiers the type is `.anything`, for arrays we require an iterable, + /// and for objects we produce an object type with the property names. + private func inferOuterParameterType(from pattern: ParameterPattern) -> ILType { + switch pattern { + case .identifier: + return .anything + + case .array: + return .iterable + + case .object(let properties): + // For an object pattern, we recursively collect the property names. + var propertyNames = [String]() + for (key, _) in properties { + propertyNames.append(key) + } + return .object(withProperties: propertyNames) + + case .rest: + return .iterable + } + } + + private func inferInnerParameters(from pattern: ParameterPattern, isRest: Bool = false) -> [Parameter] { + switch pattern { + case .identifier: + let param: Parameter = isRest ? .rest(.anything) : .plain(.anything) + return [param] + + case .array(let elements): + var params = [Parameter]() + for (i, element) in elements.enumerated() { + let elementIsRest = isRest && (i == elements.count - 1) + params.append(contentsOf: inferInnerParameters(from: element, isRest: elementIsRest)) + } + return params + + case .object(let properties): + var params = [Parameter]() + for (_, subpattern) in properties { + params.append(contentsOf: inferInnerParameters(from: subpattern)) + } + return params + + case .rest(let inner): + return [.rest(inferOuterParameterType(from: inner))] + } } // Set type to current state and save type change event @@ -285,15 +373,15 @@ public struct JSTyper: Analyzer { case .beginPlainFunction(let op): // Plain functions can also be used as constructors. // The return value type will only be known after fully processing the function definitions. - set(instr.output, .functionAndConstructor(inferSubroutineParameterList(of: op, at: instr.index) => .anything)) + set(instr.output, .functionAndConstructor(inferSubroutineParameterList(of: op, at: instr.index, outerView: true) => .anything)) case .beginArrowFunction(let op as BeginAnyFunction), .beginGeneratorFunction(let op as BeginAnyFunction), .beginAsyncFunction(let op as BeginAnyFunction), .beginAsyncArrowFunction(let op as BeginAnyFunction), .beginAsyncGeneratorFunction(let op as BeginAnyFunction): - set(instr.output, .function(inferSubroutineParameterList(of: op, at: instr.index) => .anything)) + set(instr.output, .function(inferSubroutineParameterList(of: op, at: instr.index, outerView: true) => .anything)) case .beginConstructor(let op): - set(instr.output, .constructor(inferSubroutineParameterList(of: op, at: instr.index) => .anything)) + set(instr.output, .constructor(inferSubroutineParameterList(of: op, at: instr.index, outerView: true) => .anything)) case .beginCodeString: set(instr.output, .string) case .beginClassDefinition(let op): @@ -632,13 +720,13 @@ public struct JSTyper: Analyzer { case .beginObjectLiteralMethod(let op): // The first inner output is the explicit |this| parameter for the constructor set(instr.innerOutput(0), activeObjectLiterals.top) - processParameterDeclarations(instr.innerOutputs(1...), parameters: inferSubroutineParameterList(of: op, at: instr.index)) + processParameterDeclarations(instr.innerOutputs(1...), parameters: inferSubroutineParameterList(of: op, at: instr.index, outerView: false)) activeObjectLiterals.top.add(method: op.methodName) case .beginObjectLiteralComputedMethod(let op): // The first inner output is the explicit |this| parameter for the constructor set(instr.innerOutput(0), activeObjectLiterals.top) - processParameterDeclarations(instr.innerOutputs(1...), parameters: inferSubroutineParameterList(of: op, at: instr.index)) + processParameterDeclarations(instr.innerOutputs(1...), parameters: inferSubroutineParameterList(of: op, at: instr.index, outerView: false)) case .beginObjectLiteralGetter(let op): // The first inner output is the explicit |this| parameter for the constructor @@ -650,7 +738,7 @@ public struct JSTyper: Analyzer { // The first inner output is the explicit |this| parameter for the constructor set(instr.innerOutput(0), activeObjectLiterals.top) assert(instr.numInnerOutputs == 2) - processParameterDeclarations(instr.innerOutputs(1...), parameters: inferSubroutineParameterList(of: op, at: instr.index)) + processParameterDeclarations(instr.innerOutputs(1...), parameters: inferSubroutineParameterList(of: op, at: instr.index, outerView: false)) activeObjectLiterals.top.add(property: op.propertyName) case .endObjectLiteral: @@ -660,7 +748,7 @@ public struct JSTyper: Analyzer { case .beginClassConstructor(let op): // The first inner output is the explicit |this| parameter for the constructor set(instr.innerOutput(0), activeClassDefinitions.top.instanceType) - let parameters = inferSubroutineParameterList(of: op, at: instr.index) + let parameters = inferSubroutineParameterList(of: op, at: instr.index, outerView: false) processParameterDeclarations(instr.innerOutputs(1...), parameters: parameters) activeClassDefinitions.top.constructorParameters = parameters @@ -670,7 +758,7 @@ public struct JSTyper: Analyzer { case .beginClassInstanceMethod(let op): // The first inner output is the explicit |this| set(instr.innerOutput(0), activeClassDefinitions.top.instanceType) - processParameterDeclarations(instr.innerOutputs(1...), parameters: inferSubroutineParameterList(of: op, at: instr.index)) + processParameterDeclarations(instr.innerOutputs(1...), parameters: inferSubroutineParameterList(of: op, at: instr.index, outerView: false)) activeClassDefinitions.top.instanceType.add(method: op.methodName) case .beginClassInstanceGetter(let op): @@ -683,7 +771,7 @@ public struct JSTyper: Analyzer { // The first inner output is the explicit |this| parameter for the constructor set(instr.innerOutput(0), activeClassDefinitions.top.instanceType) assert(instr.numInnerOutputs == 2) - processParameterDeclarations(instr.innerOutputs(1...), parameters: inferSubroutineParameterList(of: op, at: instr.index)) + processParameterDeclarations(instr.innerOutputs(1...), parameters: inferSubroutineParameterList(of: op, at: instr.index, outerView: false)) activeClassDefinitions.top.instanceType.add(property: op.propertyName) case .classAddStaticProperty(let op): @@ -697,7 +785,7 @@ public struct JSTyper: Analyzer { case .beginClassStaticMethod(let op): // The first inner output is the explicit |this| set(instr.innerOutput(0), activeClassDefinitions.top.classType) - processParameterDeclarations(instr.innerOutputs(1...), parameters: inferSubroutineParameterList(of: op, at: instr.index)) + processParameterDeclarations(instr.innerOutputs(1...), parameters: inferSubroutineParameterList(of: op, at: instr.index, outerView: false)) activeClassDefinitions.top.classType.add(method: op.methodName) case .beginClassStaticGetter(let op): @@ -710,18 +798,18 @@ public struct JSTyper: Analyzer { // The first inner output is the explicit |this| parameter for the constructor set(instr.innerOutput(0), activeClassDefinitions.top.classType) assert(instr.numInnerOutputs == 2) - processParameterDeclarations(instr.innerOutputs(1...), parameters: inferSubroutineParameterList(of: op, at: instr.index)) + processParameterDeclarations(instr.innerOutputs(1...), parameters: inferSubroutineParameterList(of: op, at: instr.index, outerView: false)) activeClassDefinitions.top.classType.add(property: op.propertyName) case .beginClassPrivateInstanceMethod(let op): // The first inner output is the explicit |this| set(instr.innerOutput(0), activeClassDefinitions.top.instanceType) - processParameterDeclarations(instr.innerOutputs(1...), parameters: inferSubroutineParameterList(of: op, at: instr.index)) + processParameterDeclarations(instr.innerOutputs(1...), parameters: inferSubroutineParameterList(of: op, at: instr.index, outerView: false)) case .beginClassPrivateStaticMethod(let op): // The first inner output is the explicit |this| set(instr.innerOutput(0), activeClassDefinitions.top.classType) - processParameterDeclarations(instr.innerOutputs(1...), parameters: inferSubroutineParameterList(of: op, at: instr.index)) + processParameterDeclarations(instr.innerOutputs(1...), parameters: inferSubroutineParameterList(of: op, at: instr.index, outerView: false)) case .createArray, .createIntArray, @@ -884,12 +972,12 @@ public struct JSTyper: Analyzer { .beginAsyncFunction(let op as BeginAnyFunction), .beginAsyncArrowFunction(let op as BeginAnyFunction), .beginAsyncGeneratorFunction(let op as BeginAnyFunction): - processParameterDeclarations(instr.innerOutputs, parameters: inferSubroutineParameterList(of: op, at: instr.index)) + processParameterDeclarations(instr.innerOutputs, parameters: inferSubroutineParameterList(of: op, at: instr.index, outerView: false)) case .beginConstructor(let op): // The first inner output is the explicit |this| parameter for the constructor set(instr.innerOutput(0), .object()) - processParameterDeclarations(instr.innerOutputs(1...), parameters: inferSubroutineParameterList(of: op, at: instr.index)) + processParameterDeclarations(instr.innerOutputs(1...), parameters: inferSubroutineParameterList(of: op, at: instr.index, outerView: false)) case .callSuperMethod(let op): let sig = chooseUniform(from: inferMethodSignatures(of: op.methodName, on: currentSuperType())) diff --git a/Sources/Fuzzilli/FuzzIL/JsOperations.swift b/Sources/Fuzzilli/FuzzIL/JsOperations.swift index 29cdfc99..c61118e8 100644 --- a/Sources/Fuzzilli/FuzzIL/JsOperations.swift +++ b/Sources/Fuzzilli/FuzzIL/JsOperations.swift @@ -1225,21 +1225,38 @@ final class TestIn: JsOperation { } +public indirect enum ParameterPattern { + case identifier + case object(properties: [(String, ParameterPattern)]) + case array(elements: [ParameterPattern]) + case rest(ParameterPattern) +} + // The parameters of a FuzzIL subroutine. public struct Parameters { - /// The total number of parameters. - private let numParameters: UInt32 + private let numVariables: UInt32 /// Whether the last parameter is a rest parameter. let hasRestParameter: Bool + /// If necessary, the detailed structure of parameters. + public let patterns: [ParameterPattern]? - /// The total number of parameters. This is equivalent to the number of inner outputs produced from the parameters. + /// The total number of variables (i.e. inner outputs after destructuring parameters). var count: Int { - return Int(numParameters) + return Int(numVariables) } + /// Plain initializer init(count: Int, hasRestParameter: Bool = false) { - self.numParameters = UInt32(count) + self.numVariables = UInt32(count) + self.hasRestParameter = hasRestParameter + self.patterns = nil + } + + /// Full initializer that also specifies the detailed structure of the parameters. + public init(count: Int, patterns: [ParameterPattern], hasRestParameter: Bool = false) { + self.numVariables = UInt32(count) self.hasRestParameter = hasRestParameter + self.patterns = patterns } } diff --git a/Sources/Fuzzilli/Lifting/JavaScriptLifter.swift b/Sources/Fuzzilli/Lifting/JavaScriptLifter.swift index 773f8a97..3e09cf8e 100644 --- a/Sources/Fuzzilli/Lifting/JavaScriptLifter.swift +++ b/Sources/Fuzzilli/Lifting/JavaScriptLifter.swift @@ -1686,7 +1686,36 @@ public class JavaScriptLifter: Lifter { } } + private func liftParameterPattern(_ pattern: ParameterPattern, variables: inout [String]) -> String { + switch pattern { + case .identifier: + return variables.removeFirst() + case .object(let properties): + let liftedProperties = properties.map { (key, valuePattern) -> String in + let liftedValue = liftParameterPattern(valuePattern, variables: &variables) + return "\(key): \(liftedValue)" + } + return "{ " + liftedProperties.joined(separator: ", ") + " }" + case .array(let elements): + let liftedElements = elements.map { element -> String in + return liftParameterPattern(element, variables: &variables) + } + return "[" + liftedElements.joined(separator: ", ") + "]" + case .rest(let inner): + return "..." + liftParameterPattern(inner, variables: &variables) + } + } + + // Lifts the Parameters into a JavaScript parameter list string. + // If an explicit patterns array is present, it uses it (and consumes identifiers from the provided `variables`). + // Otherwise, it simply uses the list of variable names. private func liftParameters(_ parameters: Parameters, as variables: [String]) -> String { + if let patterns = parameters.patterns { + var varsCopy = variables + let lifted = patterns.map { liftParameterPattern($0, variables: &varsCopy) } + return lifted.joined(separator: ", ") + } + assert(parameters.count == variables.count) var paramList = [String]() for v in variables { diff --git a/Sources/Fuzzilli/Protobuf/ast.pb.swift b/Sources/Fuzzilli/Protobuf/ast.pb.swift index 2f6330e2..0a91ac8e 100644 --- a/Sources/Fuzzilli/Protobuf/ast.pb.swift +++ b/Sources/Fuzzilli/Protobuf/ast.pb.swift @@ -127,7 +127,64 @@ public struct Compiler_Protobuf_AST: Sendable { } /// A parameter in a function declaration. Not an expression on its own. -public struct Compiler_Protobuf_Parameter: Sendable { +public struct Compiler_Protobuf_Parameter: @unchecked 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 pattern: OneOf_Pattern? { + get {return _storage._pattern} + set {_uniqueStorage()._pattern = newValue} + } + + public var identifierParameter: Compiler_Protobuf_IdentifierParameter { + get { + if case .identifierParameter(let v)? = _storage._pattern {return v} + return Compiler_Protobuf_IdentifierParameter() + } + set {_uniqueStorage()._pattern = .identifierParameter(newValue)} + } + + public var objectParameter: Compiler_Protobuf_ObjectParameter { + get { + if case .objectParameter(let v)? = _storage._pattern {return v} + return Compiler_Protobuf_ObjectParameter() + } + set {_uniqueStorage()._pattern = .objectParameter(newValue)} + } + + public var arrayParameter: Compiler_Protobuf_ArrayParameter { + get { + if case .arrayParameter(let v)? = _storage._pattern {return v} + return Compiler_Protobuf_ArrayParameter() + } + set {_uniqueStorage()._pattern = .arrayParameter(newValue)} + } + + public var restParameter: Compiler_Protobuf_RestParameter { + get { + if case .restParameter(let v)? = _storage._pattern {return v} + return Compiler_Protobuf_RestParameter() + } + set {_uniqueStorage()._pattern = .restParameter(newValue)} + } + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public enum OneOf_Pattern: Equatable, Sendable { + case identifierParameter(Compiler_Protobuf_IdentifierParameter) + case objectParameter(Compiler_Protobuf_ObjectParameter) + case arrayParameter(Compiler_Protobuf_ArrayParameter) + case restParameter(Compiler_Protobuf_RestParameter) + + } + + public init() {} + + fileprivate var _storage = _StorageClass.defaultInstance +} + +public struct Compiler_Protobuf_IdentifierParameter: 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. @@ -139,6 +196,74 @@ public struct Compiler_Protobuf_Parameter: Sendable { public init() {} } +public struct Compiler_Protobuf_ObjectParameter: 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: [Compiler_Protobuf_ObjectParameterProperty] = [] + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + +public struct Compiler_Protobuf_ObjectParameterProperty: 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 parameterKey: String = String() + + public var parameterValue: Compiler_Protobuf_Parameter { + get {return _parameterValue ?? Compiler_Protobuf_Parameter()} + set {_parameterValue = newValue} + } + /// Returns true if `parameterValue` has been explicitly set. + public var hasParameterValue: Bool {return self._parameterValue != nil} + /// Clears the value of `parameterValue`. Subsequent reads from it will return its default value. + public mutating func clearParameterValue() {self._parameterValue = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _parameterValue: Compiler_Protobuf_Parameter? = nil +} + +public struct Compiler_Protobuf_ArrayParameter: 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 elements: [Compiler_Protobuf_Parameter] = [] + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + +public struct Compiler_Protobuf_RestParameter: @unchecked 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 argument: Compiler_Protobuf_Parameter { + get {return _storage._argument ?? Compiler_Protobuf_Parameter()} + set {_uniqueStorage()._argument = newValue} + } + /// Returns true if `argument` has been explicitly set. + public var hasArgument: Bool {return _storage._argument != nil} + /// Clears the value of `argument`. Subsequent reads from it will return its default value. + public mutating func clearArgument() {_uniqueStorage()._argument = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _storage = _StorageClass.defaultInstance +} + public struct Compiler_Protobuf_EmptyStatement: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for @@ -2364,6 +2489,152 @@ extension Compiler_Protobuf_AST: SwiftProtobuf.Message, SwiftProtobuf._MessageIm extension Compiler_Protobuf_Parameter: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".Parameter" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "identifierParameter"), + 2: .same(proto: "objectParameter"), + 3: .same(proto: "arrayParameter"), + 4: .same(proto: "restParameter"), + ] + + fileprivate class _StorageClass { + var _pattern: Compiler_Protobuf_Parameter.OneOf_Pattern? + + #if swift(>=5.10) + // This property is used as the initial default value for new instances of the type. + // The type itself is protecting the reference to its storage via CoW semantics. + // This will force a copy to be made of this reference when the first mutation occurs; + // hence, it is safe to mark this as `nonisolated(unsafe)`. + static nonisolated(unsafe) let defaultInstance = _StorageClass() + #else + static let defaultInstance = _StorageClass() + #endif + + private init() {} + + init(copying source: _StorageClass) { + _pattern = source._pattern + } + } + + fileprivate mutating func _uniqueStorage() -> _StorageClass { + if !isKnownUniquelyReferenced(&_storage) { + _storage = _StorageClass(copying: _storage) + } + return _storage + } + + public mutating func decodeMessage(decoder: inout D) throws { + _ = _uniqueStorage() + try withExtendedLifetime(_storage) { (_storage: _StorageClass) in + 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 { + var v: Compiler_Protobuf_IdentifierParameter? + var hadOneofValue = false + if let current = _storage._pattern { + hadOneofValue = true + if case .identifierParameter(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + _storage._pattern = .identifierParameter(v) + } + }() + case 2: try { + var v: Compiler_Protobuf_ObjectParameter? + var hadOneofValue = false + if let current = _storage._pattern { + hadOneofValue = true + if case .objectParameter(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + _storage._pattern = .objectParameter(v) + } + }() + case 3: try { + var v: Compiler_Protobuf_ArrayParameter? + var hadOneofValue = false + if let current = _storage._pattern { + hadOneofValue = true + if case .arrayParameter(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + _storage._pattern = .arrayParameter(v) + } + }() + case 4: try { + var v: Compiler_Protobuf_RestParameter? + var hadOneofValue = false + if let current = _storage._pattern { + hadOneofValue = true + if case .restParameter(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + _storage._pattern = .restParameter(v) + } + }() + default: break + } + } + } + } + + public func traverse(visitor: inout V) throws { + try withExtendedLifetime(_storage) { (_storage: _StorageClass) in + // 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 + switch _storage._pattern { + case .identifierParameter?: try { + guard case .identifierParameter(let v)? = _storage._pattern else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + }() + case .objectParameter?: try { + guard case .objectParameter(let v)? = _storage._pattern else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + }() + case .arrayParameter?: try { + guard case .arrayParameter(let v)? = _storage._pattern else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 3) + }() + case .restParameter?: try { + guard case .restParameter(let v)? = _storage._pattern else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 4) + }() + case nil: break + } + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Compiler_Protobuf_Parameter, rhs: Compiler_Protobuf_Parameter) -> Bool { + if lhs._storage !== rhs._storage { + let storagesAreEqual: Bool = withExtendedLifetime((lhs._storage, rhs._storage)) { (_args: (_StorageClass, _StorageClass)) in + let _storage = _args.0 + let rhs_storage = _args.1 + if _storage._pattern != rhs_storage._pattern {return false} + return true + } + if !storagesAreEqual {return false} + } + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Compiler_Protobuf_IdentifierParameter: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".IdentifierParameter" public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .same(proto: "name"), ] @@ -2387,13 +2658,195 @@ extension Compiler_Protobuf_Parameter: SwiftProtobuf.Message, SwiftProtobuf._Mes try unknownFields.traverse(visitor: &visitor) } - public static func ==(lhs: Compiler_Protobuf_Parameter, rhs: Compiler_Protobuf_Parameter) -> Bool { + public static func ==(lhs: Compiler_Protobuf_IdentifierParameter, rhs: Compiler_Protobuf_IdentifierParameter) -> Bool { if lhs.name != rhs.name {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } } +extension Compiler_Protobuf_ObjectParameter: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".ObjectParameter" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "parameters"), + ] + + 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.parameters) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if !self.parameters.isEmpty { + try visitor.visitRepeatedMessageField(value: self.parameters, fieldNumber: 1) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Compiler_Protobuf_ObjectParameter, rhs: Compiler_Protobuf_ObjectParameter) -> Bool { + if lhs.parameters != rhs.parameters {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Compiler_Protobuf_ObjectParameterProperty: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".ObjectParameterProperty" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "parameterKey"), + 2: .same(proto: "parameterValue"), + ] + + 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.parameterKey) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._parameterValue) }() + 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.parameterKey.isEmpty { + try visitor.visitSingularStringField(value: self.parameterKey, fieldNumber: 1) + } + try { if let v = self._parameterValue { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Compiler_Protobuf_ObjectParameterProperty, rhs: Compiler_Protobuf_ObjectParameterProperty) -> Bool { + if lhs.parameterKey != rhs.parameterKey {return false} + if lhs._parameterValue != rhs._parameterValue {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Compiler_Protobuf_ArrayParameter: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".ArrayParameter" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "elements"), + ] + + 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.elements) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if !self.elements.isEmpty { + try visitor.visitRepeatedMessageField(value: self.elements, fieldNumber: 1) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Compiler_Protobuf_ArrayParameter, rhs: Compiler_Protobuf_ArrayParameter) -> Bool { + if lhs.elements != rhs.elements {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Compiler_Protobuf_RestParameter: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".RestParameter" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "argument"), + ] + + fileprivate class _StorageClass { + var _argument: Compiler_Protobuf_Parameter? = nil + + #if swift(>=5.10) + // This property is used as the initial default value for new instances of the type. + // The type itself is protecting the reference to its storage via CoW semantics. + // This will force a copy to be made of this reference when the first mutation occurs; + // hence, it is safe to mark this as `nonisolated(unsafe)`. + static nonisolated(unsafe) let defaultInstance = _StorageClass() + #else + static let defaultInstance = _StorageClass() + #endif + + private init() {} + + init(copying source: _StorageClass) { + _argument = source._argument + } + } + + fileprivate mutating func _uniqueStorage() -> _StorageClass { + if !isKnownUniquelyReferenced(&_storage) { + _storage = _StorageClass(copying: _storage) + } + return _storage + } + + public mutating func decodeMessage(decoder: inout D) throws { + _ = _uniqueStorage() + try withExtendedLifetime(_storage) { (_storage: _StorageClass) in + 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: &_storage._argument) }() + default: break + } + } + } + } + + public func traverse(visitor: inout V) throws { + try withExtendedLifetime(_storage) { (_storage: _StorageClass) in + // 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 = _storage._argument { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Compiler_Protobuf_RestParameter, rhs: Compiler_Protobuf_RestParameter) -> Bool { + if lhs._storage !== rhs._storage { + let storagesAreEqual: Bool = withExtendedLifetime((lhs._storage, rhs._storage)) { (_args: (_StorageClass, _StorageClass)) in + let _storage = _args.0 + let rhs_storage = _args.1 + if _storage._argument != rhs_storage._argument {return false} + return true + } + if !storagesAreEqual {return false} + } + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + extension Compiler_Protobuf_EmptyStatement: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".EmptyStatement" public static let _protobuf_nameMap = SwiftProtobuf._NameMap() diff --git a/Sources/Fuzzilli/Protobuf/ast.proto b/Sources/Fuzzilli/Protobuf/ast.proto index 24559f24..a5101553 100644 --- a/Sources/Fuzzilli/Protobuf/ast.proto +++ b/Sources/Fuzzilli/Protobuf/ast.proto @@ -21,9 +21,35 @@ message AST { // A parameter in a function declaration. Not an expression on its own. message Parameter { + oneof pattern { + IdentifierParameter identifierParameter = 1; + ObjectParameter objectParameter = 2; + ArrayParameter arrayParameter = 3; + RestParameter restParameter = 4; + } +} + +message IdentifierParameter { string name = 1; } +message ObjectParameter { + repeated ObjectParameterProperty parameters = 1; +} + +message ObjectParameterProperty { + string parameterKey = 1; + Parameter parameterValue = 2; +} + +message ArrayParameter { + repeated Parameter elements = 1; +} + +message RestParameter { + Parameter argument = 1; +} + message EmptyStatement { } diff --git a/Sources/Fuzzilli/Protobuf/operations.pb.swift b/Sources/Fuzzilli/Protobuf/operations.pb.swift index 0efd2ada..555fa724 100644 --- a/Sources/Fuzzilli/Protobuf/operations.pb.swift +++ b/Sources/Fuzzilli/Protobuf/operations.pb.swift @@ -831,11 +831,152 @@ public struct Fuzzilli_Protobuf_Parameters: Sendable { public var hasRest_p: Bool = false + /// If provided, specifies the explicit structure of the parameters. + /// When omitted, the parameters are assumed to be plain identifiers. + public var patterns: [Fuzzilli_Protobuf_Parameter] = [] + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + +public struct Fuzzilli_Protobuf_Parameter: @unchecked 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 pattern: OneOf_Pattern? { + get {return _storage._pattern} + set {_uniqueStorage()._pattern = newValue} + } + + public var identifierParameter: Fuzzilli_Protobuf_IdentifierParameter { + get { + if case .identifierParameter(let v)? = _storage._pattern {return v} + return Fuzzilli_Protobuf_IdentifierParameter() + } + set {_uniqueStorage()._pattern = .identifierParameter(newValue)} + } + + public var objectParameter: Fuzzilli_Protobuf_ObjectParameter { + get { + if case .objectParameter(let v)? = _storage._pattern {return v} + return Fuzzilli_Protobuf_ObjectParameter() + } + set {_uniqueStorage()._pattern = .objectParameter(newValue)} + } + + public var arrayParameter: Fuzzilli_Protobuf_ArrayParameter { + get { + if case .arrayParameter(let v)? = _storage._pattern {return v} + return Fuzzilli_Protobuf_ArrayParameter() + } + set {_uniqueStorage()._pattern = .arrayParameter(newValue)} + } + + public var restParameter: Fuzzilli_Protobuf_RestParameter { + get { + if case .restParameter(let v)? = _storage._pattern {return v} + return Fuzzilli_Protobuf_RestParameter() + } + set {_uniqueStorage()._pattern = .restParameter(newValue)} + } + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public enum OneOf_Pattern: Equatable, Sendable { + case identifierParameter(Fuzzilli_Protobuf_IdentifierParameter) + case objectParameter(Fuzzilli_Protobuf_ObjectParameter) + case arrayParameter(Fuzzilli_Protobuf_ArrayParameter) + case restParameter(Fuzzilli_Protobuf_RestParameter) + + } + + public init() {} + + fileprivate var _storage = _StorageClass.defaultInstance +} + +public struct Fuzzilli_Protobuf_IdentifierParameter: 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 name: String = String() + public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} } +public struct Fuzzilli_Protobuf_ObjectParameter: 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_ObjectParameterProperty] = [] + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + +public struct Fuzzilli_Protobuf_ObjectParameterProperty: 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 parameterKey: String = String() + + public var parameterValue: Fuzzilli_Protobuf_Parameter { + get {return _parameterValue ?? Fuzzilli_Protobuf_Parameter()} + set {_parameterValue = newValue} + } + /// Returns true if `parameterValue` has been explicitly set. + public var hasParameterValue: Bool {return self._parameterValue != nil} + /// Clears the value of `parameterValue`. Subsequent reads from it will return its default value. + public mutating func clearParameterValue() {self._parameterValue = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _parameterValue: Fuzzilli_Protobuf_Parameter? = nil +} + +public struct Fuzzilli_Protobuf_ArrayParameter: 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 elements: [Fuzzilli_Protobuf_Parameter] = [] + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + +public struct Fuzzilli_Protobuf_RestParameter: @unchecked 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 argument: Fuzzilli_Protobuf_Parameter { + get {return _storage._argument ?? Fuzzilli_Protobuf_Parameter()} + set {_uniqueStorage()._argument = newValue} + } + /// Returns true if `argument` has been explicitly set. + public var hasArgument: Bool {return _storage._argument != nil} + /// Clears the value of `argument`. Subsequent reads from it will return its default value. + public mutating func clearArgument() {_uniqueStorage()._argument = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _storage = _StorageClass.defaultInstance +} + public struct Fuzzilli_Protobuf_LoadInteger: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for @@ -4581,6 +4722,7 @@ extension Fuzzilli_Protobuf_Parameters: SwiftProtobuf.Message, SwiftProtobuf._Me public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .same(proto: "count"), 2: .same(proto: "hasRest"), + 3: .same(proto: "patterns"), ] public mutating func decodeMessage(decoder: inout D) throws { @@ -4591,6 +4733,7 @@ extension Fuzzilli_Protobuf_Parameters: SwiftProtobuf.Message, SwiftProtobuf._Me switch fieldNumber { case 1: try { try decoder.decodeSingularUInt32Field(value: &self.count) }() case 2: try { try decoder.decodeSingularBoolField(value: &self.hasRest_p) }() + case 3: try { try decoder.decodeRepeatedMessageField(value: &self.patterns) }() default: break } } @@ -4603,12 +4746,376 @@ extension Fuzzilli_Protobuf_Parameters: SwiftProtobuf.Message, SwiftProtobuf._Me if self.hasRest_p != false { try visitor.visitSingularBoolField(value: self.hasRest_p, fieldNumber: 2) } + if !self.patterns.isEmpty { + try visitor.visitRepeatedMessageField(value: self.patterns, fieldNumber: 3) + } try unknownFields.traverse(visitor: &visitor) } public static func ==(lhs: Fuzzilli_Protobuf_Parameters, rhs: Fuzzilli_Protobuf_Parameters) -> Bool { if lhs.count != rhs.count {return false} if lhs.hasRest_p != rhs.hasRest_p {return false} + if lhs.patterns != rhs.patterns {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Fuzzilli_Protobuf_Parameter: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".Parameter" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "identifierParameter"), + 2: .same(proto: "objectParameter"), + 3: .same(proto: "arrayParameter"), + 4: .same(proto: "restParameter"), + ] + + fileprivate class _StorageClass { + var _pattern: Fuzzilli_Protobuf_Parameter.OneOf_Pattern? + + #if swift(>=5.10) + // This property is used as the initial default value for new instances of the type. + // The type itself is protecting the reference to its storage via CoW semantics. + // This will force a copy to be made of this reference when the first mutation occurs; + // hence, it is safe to mark this as `nonisolated(unsafe)`. + static nonisolated(unsafe) let defaultInstance = _StorageClass() + #else + static let defaultInstance = _StorageClass() + #endif + + private init() {} + + init(copying source: _StorageClass) { + _pattern = source._pattern + } + } + + fileprivate mutating func _uniqueStorage() -> _StorageClass { + if !isKnownUniquelyReferenced(&_storage) { + _storage = _StorageClass(copying: _storage) + } + return _storage + } + + public mutating func decodeMessage(decoder: inout D) throws { + _ = _uniqueStorage() + try withExtendedLifetime(_storage) { (_storage: _StorageClass) in + 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 { + var v: Fuzzilli_Protobuf_IdentifierParameter? + var hadOneofValue = false + if let current = _storage._pattern { + hadOneofValue = true + if case .identifierParameter(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + _storage._pattern = .identifierParameter(v) + } + }() + case 2: try { + var v: Fuzzilli_Protobuf_ObjectParameter? + var hadOneofValue = false + if let current = _storage._pattern { + hadOneofValue = true + if case .objectParameter(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + _storage._pattern = .objectParameter(v) + } + }() + case 3: try { + var v: Fuzzilli_Protobuf_ArrayParameter? + var hadOneofValue = false + if let current = _storage._pattern { + hadOneofValue = true + if case .arrayParameter(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + _storage._pattern = .arrayParameter(v) + } + }() + case 4: try { + var v: Fuzzilli_Protobuf_RestParameter? + var hadOneofValue = false + if let current = _storage._pattern { + hadOneofValue = true + if case .restParameter(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + _storage._pattern = .restParameter(v) + } + }() + default: break + } + } + } + } + + public func traverse(visitor: inout V) throws { + try withExtendedLifetime(_storage) { (_storage: _StorageClass) in + // 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 + switch _storage._pattern { + case .identifierParameter?: try { + guard case .identifierParameter(let v)? = _storage._pattern else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + }() + case .objectParameter?: try { + guard case .objectParameter(let v)? = _storage._pattern else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + }() + case .arrayParameter?: try { + guard case .arrayParameter(let v)? = _storage._pattern else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 3) + }() + case .restParameter?: try { + guard case .restParameter(let v)? = _storage._pattern else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 4) + }() + case nil: break + } + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Fuzzilli_Protobuf_Parameter, rhs: Fuzzilli_Protobuf_Parameter) -> Bool { + if lhs._storage !== rhs._storage { + let storagesAreEqual: Bool = withExtendedLifetime((lhs._storage, rhs._storage)) { (_args: (_StorageClass, _StorageClass)) in + let _storage = _args.0 + let rhs_storage = _args.1 + if _storage._pattern != rhs_storage._pattern {return false} + return true + } + if !storagesAreEqual {return false} + } + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Fuzzilli_Protobuf_IdentifierParameter: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".IdentifierParameter" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "name"), + ] + + 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.name) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if !self.name.isEmpty { + try visitor.visitSingularStringField(value: self.name, fieldNumber: 1) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Fuzzilli_Protobuf_IdentifierParameter, rhs: Fuzzilli_Protobuf_IdentifierParameter) -> Bool { + if lhs.name != rhs.name {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Fuzzilli_Protobuf_ObjectParameter: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".ObjectParameter" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "parameters"), + ] + + 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.parameters) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if !self.parameters.isEmpty { + try visitor.visitRepeatedMessageField(value: self.parameters, fieldNumber: 1) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Fuzzilli_Protobuf_ObjectParameter, rhs: Fuzzilli_Protobuf_ObjectParameter) -> Bool { + if lhs.parameters != rhs.parameters {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Fuzzilli_Protobuf_ObjectParameterProperty: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".ObjectParameterProperty" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "parameterKey"), + 2: .same(proto: "parameterValue"), + ] + + 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.parameterKey) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._parameterValue) }() + 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.parameterKey.isEmpty { + try visitor.visitSingularStringField(value: self.parameterKey, fieldNumber: 1) + } + try { if let v = self._parameterValue { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Fuzzilli_Protobuf_ObjectParameterProperty, rhs: Fuzzilli_Protobuf_ObjectParameterProperty) -> Bool { + if lhs.parameterKey != rhs.parameterKey {return false} + if lhs._parameterValue != rhs._parameterValue {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Fuzzilli_Protobuf_ArrayParameter: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".ArrayParameter" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "elements"), + ] + + 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.elements) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if !self.elements.isEmpty { + try visitor.visitRepeatedMessageField(value: self.elements, fieldNumber: 1) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Fuzzilli_Protobuf_ArrayParameter, rhs: Fuzzilli_Protobuf_ArrayParameter) -> Bool { + if lhs.elements != rhs.elements {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Fuzzilli_Protobuf_RestParameter: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".RestParameter" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "argument"), + ] + + fileprivate class _StorageClass { + var _argument: Fuzzilli_Protobuf_Parameter? = nil + + #if swift(>=5.10) + // This property is used as the initial default value for new instances of the type. + // The type itself is protecting the reference to its storage via CoW semantics. + // This will force a copy to be made of this reference when the first mutation occurs; + // hence, it is safe to mark this as `nonisolated(unsafe)`. + static nonisolated(unsafe) let defaultInstance = _StorageClass() + #else + static let defaultInstance = _StorageClass() + #endif + + private init() {} + + init(copying source: _StorageClass) { + _argument = source._argument + } + } + + fileprivate mutating func _uniqueStorage() -> _StorageClass { + if !isKnownUniquelyReferenced(&_storage) { + _storage = _StorageClass(copying: _storage) + } + return _storage + } + + public mutating func decodeMessage(decoder: inout D) throws { + _ = _uniqueStorage() + try withExtendedLifetime(_storage) { (_storage: _StorageClass) in + 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: &_storage._argument) }() + default: break + } + } + } + } + + public func traverse(visitor: inout V) throws { + try withExtendedLifetime(_storage) { (_storage: _StorageClass) in + // 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 = _storage._argument { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Fuzzilli_Protobuf_RestParameter, rhs: Fuzzilli_Protobuf_RestParameter) -> Bool { + if lhs._storage !== rhs._storage { + let storagesAreEqual: Bool = withExtendedLifetime((lhs._storage, rhs._storage)) { (_args: (_StorageClass, _StorageClass)) in + let _storage = _args.0 + let rhs_storage = _args.1 + if _storage._argument != rhs_storage._argument {return false} + return true + } + if !storagesAreEqual {return false} + } if lhs.unknownFields != rhs.unknownFields {return false} return true } diff --git a/Sources/Fuzzilli/Protobuf/operations.proto b/Sources/Fuzzilli/Protobuf/operations.proto index e9ae34bf..32754721 100644 --- a/Sources/Fuzzilli/Protobuf/operations.proto +++ b/Sources/Fuzzilli/Protobuf/operations.proto @@ -19,6 +19,39 @@ package fuzzilli.protobuf; message Parameters { uint32 count = 1; bool hasRest = 2; + // If provided, specifies the explicit structure of the parameters. + // When omitted, the parameters are assumed to be plain identifiers. + repeated Parameter patterns = 3; +} + +message Parameter { + oneof pattern { + IdentifierParameter identifierParameter = 1; + ObjectParameter objectParameter = 2; + ArrayParameter arrayParameter = 3; + RestParameter restParameter = 4; + } +} + +message IdentifierParameter { + string name = 1; +} + +message ObjectParameter { + repeated ObjectParameterProperty parameters = 1; +} + +message ObjectParameterProperty { + string parameterKey = 1; + Parameter parameterValue = 2; +} + +message ArrayParameter { + repeated Parameter elements = 1; +} + +message RestParameter { + Parameter argument = 1; } message LoadInteger { diff --git a/Tests/FuzzilliTests/CompilerTests/parameters.js b/Tests/FuzzilliTests/CompilerTests/parameters.js new file mode 100644 index 00000000..f747d148 --- /dev/null +++ b/Tests/FuzzilliTests/CompilerTests/parameters.js @@ -0,0 +1,54 @@ +function functionSimple(paramA) { + console.log("paramA:", paramA); +} + +function functionNoParam() { + console.log("No parameters here ..."); +} + +function functionWithObjectPattern(argPrimary, { keyA, keyB }) { + console.log("keyB:", keyB); + console.log("argPrimary:", argPrimary); + console.log("keyA:", keyA); +} + +function functionWithArrayPattern(firstElem, [secondElem, thirdElem]) { + console.log("secondElem:", secondElem); + console.log("thirdElem:", thirdElem); + console.log("firstElem:", firstElem); +} + +function functionWithNestedObjectPattern(mainArg, { nestedKey1, nestedKey2: { subKeyX, subKeyY } }) { + console.log("mainArg:", mainArg); + console.log("subKeyY:", subKeyY); + console.log("nestedKey1:", nestedKey1); + console.log("subKeyX:", subKeyX); +} + +function functionWithNestedArrayPattern(primaryElem, [secondaryElem, [nestedElemX, nestedElemY]]) { + console.log("primaryElem:", primaryElem); + console.log("nestedElemY:", nestedElemY); + console.log("secondaryElem:", secondaryElem); + console.log("nestedElemX:", nestedElemX); +} + +function functionWithMixedPattern( + [arrayElem1, { objKey1, objKey2 }], + { arrKey1, arrKey2: [nestedArrElem1, nestedArrElem2] } +) { + console.log("objKey2:", objKey2); + console.log("arrKey1:", arrKey1); + console.log("nestedArrElem1:", nestedArrElem1); + console.log("objKey1:", objKey1); + console.log("arrayElem1:", arrayElem1); + console.log("nestedArrElem2:", nestedArrElem2); +} + +functionWithObjectPattern("foo", { keyA: 23, keyB: 42 }); +functionWithArrayPattern("bar", [9000, 9001]); +functionWithNestedObjectPattern("foo", { nestedKey1: 23, nestedKey2: { subKeyX: 100, subKeyY: 200 } }); +functionWithNestedArrayPattern("bar", [9000, [9001, 9002]]); +functionWithMixedPattern( + ["alpha", { objKey1: 300, objKey2: 400 }], + { arrKey1: 500, arrKey2: [8000, 8001] } +); \ No newline at end of file diff --git a/Tests/FuzzilliTests/LifterTest.swift b/Tests/FuzzilliTests/LifterTest.swift index 8c421c28..9ab56b9a 100644 --- a/Tests/FuzzilliTests/LifterTest.swift +++ b/Tests/FuzzilliTests/LifterTest.swift @@ -1105,6 +1105,45 @@ class LifterTests: XCTestCase { XCTAssertEqual(actual, expected) } + func testUnusualFunctionLifting() { + let fuzzer = makeMockFuzzer() + let b = fuzzer.makeBuilder() + + // numVariables refers to the number of parameters after destructuring, i.e. the local variables in the function. + let numVariables = 5 + let objPattern: ParameterPattern = .object(properties: [ + ("a", .identifier), + ("b", .identifier) + ]) + let arrPattern: ParameterPattern = .array(elements: [.identifier, .identifier]) + let restElem: ParameterPattern = .rest(.identifier) + let params = Parameters(count: numVariables, patterns: [objPattern, arrPattern, restElem], hasRestParameter: false) + + + let f = b.buildUnusualFunction(with: params) { args in + b.doReturn(b.createArray(with: [args[0], args[1], args[2], args[3], args[4]])) + } + + let objArg: Variable = b.createObject(with: ["a" : b.loadInt(1337), "b" : b.loadInt(42)]) + let arrArg: Variable = b.createArray(with: [b.loadInt(9000), b.loadInt(9001)]) + let arrArg2: Variable = b.createArray(with: [b.loadInt(8000), b.loadInt(8001)]) + + b.callFunction(f, withArgs: [objArg, arrArg, arrArg2]) + + + let program = b.finalize() + let actual = fuzzer.lifter.lift(program) + + let expected = """ + function f0({ a: a1, b: a2 }, [a3, a4], ...a5) { + return [a1,a2,a3,a4,a5]; + } + f0({ a: 1337, b: 42 }, [9000,9001], [8000,8001]); + + """ + XCTAssertEqual(actual, expected) + } + func testConditionalOperationLifting() { let fuzzer = makeMockFuzzer() let b = fuzzer.makeBuilder()