diff --git a/Sources/Fuzzilli/Base/ProgramBuilder.swift b/Sources/Fuzzilli/Base/ProgramBuilder.swift index 3eb4cb968..4b38f8af6 100644 --- a/Sources/Fuzzilli/Base/ProgramBuilder.swift +++ b/Sources/Fuzzilli/Base/ProgramBuilder.swift @@ -2095,6 +2095,10 @@ public class ProgramBuilder { 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 { assert(types.areValid()) diff --git a/Sources/Fuzzilli/Compiler/Compiler.swift b/Sources/Fuzzilli/Compiler/Compiler.swift index e9f9bbd2f..69054893a 100644 --- a/Sources/Fuzzilli/Compiler/Compiler.swift +++ b/Sources/Fuzzilli/Compiler/Compiler.swift @@ -468,7 +468,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.parameter 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) @@ -1071,14 +1074,71 @@ 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) + // Maps parameters of a function to variables that are used in that function's scope. + // func extractIdentifiers and var flatParameters help to process object and array patterns. + var flatParameters: [String] = [] + func extractIdentifiers(from param: Compiler_Protobuf_Parameter) { + switch param.parameter { + case .identifierParameter(let identifier): + flatParameters.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 .none: + fatalError("Unexpected parameter type: .none in mapParameters") + } + } + for param in parameters { + extractIdentifiers(from: param) + } + assert(flatParameters.count == variables.count, "The number of variables (\(variables.count)) does not match the number of parameters (\(flatParameters.count)).") + for (name, v) in zip(flatParameters, variables) { + map(name, to: v) } } private func convertParameters(_ parameters: [Compiler_Protobuf_Parameter]) -> Parameters { - return Parameters(count: parameters.count) + // Converts a protobuf signature to a FuzzIL signature. + var totalParameterCount = 0 + var patterns = [ParameterPattern]() + func processParameter(_ param: Compiler_Protobuf_Parameter) -> ParameterPattern { + switch param.parameter { + 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 .none: + fatalError("Unexpected parameter type: .none in convertParameters") + } + } + for param in parameters { + let pattern = processParameter(param) + patterns.append(pattern) + } + var params = Parameters( + count: totalParameterCount + ) + params.patterns = patterns + return params } /// Convenience accessor for the currently active scope. diff --git a/Sources/Fuzzilli/Compiler/Parser/parser.js b/Sources/Fuzzilli/Compiler/Parser/parser.js index 07c886919..b4156c3d1 100644 --- a/Sources/Fuzzilli/Compiler/Parser/parser.js +++ b/Sources/Fuzzilli/Compiler/Parser/parser.js @@ -73,8 +73,47 @@ function parse(script, proto) { } function visitParameter(param) { - assert(param.type == 'Identifier'); - return make('Parameter', { name: param.name }); + assert(['Identifier', 'ObjectPattern', 'ArrayPattern'].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. So we can convert them to strings here. + 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 } }); + } + default: { + throw new Error('Unsupported parameter type: ' + param.type); + } + } } function visitVariableDeclaration(node) { diff --git a/Sources/Fuzzilli/FuzzIL/JSTyper.swift b/Sources/Fuzzilli/FuzzIL/JSTyper.swift index dc9858454..a3454d7ab 100644 --- a/Sources/Fuzzilli/FuzzIL/JSTyper.swift +++ b/Sources/Fuzzilli/FuzzIL/JSTyper.swift @@ -170,7 +170,37 @@ 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) + if let signature = signatures[index] { + return signature + } else { + var parameterList = ParameterList() + let patterns = op.parameters.patterns + let hasRestParam = op.parameters.hasRestParameter + + for (i, pattern) in patterns.enumerated() { + let ilType = inferParameterType(from: pattern) + let parameter: Parameter + if hasRestParam && i == patterns.count - 1 { + parameter = .rest(ilType) + } else { + parameter = .plain(ilType) + } + parameterList.append(parameter) + } + return parameterList + } + } + + private func inferParameterType(from pattern: ParameterPattern) -> ILType { + switch pattern { + case .identifier: + return .anything + case .array: + return .iterable + case .object: + // TODO be more precise here: Describe the object parameters structure + return .object() + } } // Set type to current state and save type change event diff --git a/Sources/Fuzzilli/FuzzIL/JsOperations.swift b/Sources/Fuzzilli/FuzzIL/JsOperations.swift index bab2bd749..62862d157 100644 --- a/Sources/Fuzzilli/FuzzIL/JsOperations.swift +++ b/Sources/Fuzzilli/FuzzIL/JsOperations.swift @@ -1047,21 +1047,33 @@ final class TestIn: JsOperation { } +enum ParameterPattern { + case identifier + case object(properties: [(String, ParameterPattern)]) + case array(elements: [ParameterPattern]) +} + // The parameters of a FuzzIL subroutine. public struct Parameters { - /// The total number of parameters. - private let numParameters: UInt32 + /// The total number of variables after destructuring object- and array patterns. + /// Example: f({a, b}, [c, d]) has 4 variables, not 2. + private let numVariables: UInt32 /// Whether the last parameter is a rest parameter. + /// TODO make the rest parameter an actual parameter type. let hasRestParameter: Bool - /// The total number of parameters. This is equivalent to the number of inner outputs produced from the parameters. var count: Int { - return Int(numParameters) + return Int(numVariables) } - init(count: Int, hasRestParameter: Bool = false) { - self.numParameters = UInt32(count) + var patterns: [ParameterPattern] + init( + count: Int, + hasRestParameter: Bool = false + ) { + self.numVariables = UInt32(count) self.hasRestParameter = hasRestParameter + self.patterns = Array(repeating: .identifier, count: count) } } diff --git a/Sources/Fuzzilli/Lifting/JavaScriptLifter.swift b/Sources/Fuzzilli/Lifting/JavaScriptLifter.swift index 240f3ea99..16cbcecda 100644 --- a/Sources/Fuzzilli/Lifting/JavaScriptLifter.swift +++ b/Sources/Fuzzilli/Lifting/JavaScriptLifter.swift @@ -1355,15 +1355,32 @@ public class JavaScriptLifter: Lifter { private func liftParameters(_ parameters: Parameters, as variables: [String]) -> String { assert(parameters.count == variables.count) - var paramList = [String]() - for v in variables { - if parameters.hasRestParameter && v == variables.last { - paramList.append("..." + v) - } else { - paramList.append(v) + var variableIndex = 0 + func liftPattern(_ pattern: ParameterPattern) -> String { + switch pattern { + case .identifier: + let variableName = variables[variableIndex] + variableIndex += 1 + return variableName + + case .object(let properties): + let liftedProperties = properties.map { (key, valuePattern) -> String in + let value = liftPattern(valuePattern) + return "\(key): \(value)" + } + return "{ " + liftedProperties.joined(separator: ", ") + " }" + + case .array(let elements): + let liftedElements = elements.map { element -> String in + return liftPattern(element) + } + return "[ " + liftedElements.joined(separator: ", ") + " ]" } } - return paramList.joined(separator: ", ") + let liftedParams = parameters.patterns.map { pattern in + return liftPattern(pattern) + } + return liftedParams.joined(separator: ", ") } private func liftFunctionDefinitionBegin(_ instr: Instruction, keyword FUNCTION: String, using w: inout JavaScriptWriter) { diff --git a/Sources/Fuzzilli/Protobuf/ast.pb.swift b/Sources/Fuzzilli/Protobuf/ast.pb.swift index 60d2eb9c5..546b82d43 100644 --- a/Sources/Fuzzilli/Protobuf/ast.pb.swift +++ b/Sources/Fuzzilli/Protobuf/ast.pb.swift @@ -132,6 +132,49 @@ public struct Compiler_Protobuf_Parameter: Sendable { // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. + public var parameter: Compiler_Protobuf_Parameter.OneOf_Parameter? = nil + + public var identifierParameter: Compiler_Protobuf_IdentifierParameter { + get { + if case .identifierParameter(let v)? = parameter {return v} + return Compiler_Protobuf_IdentifierParameter() + } + set {parameter = .identifierParameter(newValue)} + } + + public var objectParameter: Compiler_Protobuf_ObjectParameter { + get { + if case .objectParameter(let v)? = parameter {return v} + return Compiler_Protobuf_ObjectParameter() + } + set {parameter = .objectParameter(newValue)} + } + + public var arrayParameter: Compiler_Protobuf_ArrayParameter { + get { + if case .arrayParameter(let v)? = parameter {return v} + return Compiler_Protobuf_ArrayParameter() + } + set {parameter = .arrayParameter(newValue)} + } + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public enum OneOf_Parameter: Equatable, Sendable { + case identifierParameter(Compiler_Protobuf_IdentifierParameter) + case objectParameter(Compiler_Protobuf_ObjectParameter) + case arrayParameter(Compiler_Protobuf_ArrayParameter) + + } + + public init() {} +} + +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. + public var name: String = String() public var unknownFields = SwiftProtobuf.UnknownStorage() @@ -139,6 +182,53 @@ 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_EmptyStatement: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for @@ -2232,6 +2322,94 @@ 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"), + ] + + 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 { + var v: Compiler_Protobuf_IdentifierParameter? + var hadOneofValue = false + if let current = self.parameter { + hadOneofValue = true + if case .identifierParameter(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.parameter = .identifierParameter(v) + } + }() + case 2: try { + var v: Compiler_Protobuf_ObjectParameter? + var hadOneofValue = false + if let current = self.parameter { + hadOneofValue = true + if case .objectParameter(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.parameter = .objectParameter(v) + } + }() + case 3: try { + var v: Compiler_Protobuf_ArrayParameter? + var hadOneofValue = false + if let current = self.parameter { + hadOneofValue = true + if case .arrayParameter(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.parameter = .arrayParameter(v) + } + }() + 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 + switch self.parameter { + case .identifierParameter?: try { + guard case .identifierParameter(let v)? = self.parameter else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + }() + case .objectParameter?: try { + guard case .objectParameter(let v)? = self.parameter else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + }() + case .arrayParameter?: try { + guard case .arrayParameter(let v)? = self.parameter else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 3) + }() + case nil: break + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Compiler_Protobuf_Parameter, rhs: Compiler_Protobuf_Parameter) -> Bool { + if lhs.parameter != rhs.parameter {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"), ] @@ -2255,13 +2433,119 @@ 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_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 3db9a50c4..c257d279f 100644 --- a/Sources/Fuzzilli/Protobuf/ast.proto +++ b/Sources/Fuzzilli/Protobuf/ast.proto @@ -21,9 +21,30 @@ message AST { // A parameter in a function declaration. Not an expression on its own. message Parameter { + oneof parameter { + IdentifierParameter identifierParameter = 1; + ObjectParameter objectParameter = 2; + ArrayParameter arrayParameter = 3; + } +} + +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 EmptyStatement { } diff --git a/Tests/FuzzilliTests/CompilerTests/advanced_params.js b/Tests/FuzzilliTests/CompilerTests/advanced_params.js new file mode 100644 index 000000000..1f898c926 --- /dev/null +++ b/Tests/FuzzilliTests/CompilerTests/advanced_params.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] } +); diff --git a/Tests/FuzzilliTests/LifterTest.swift b/Tests/FuzzilliTests/LifterTest.swift index 8371f9f16..9f1167694 100644 --- a/Tests/FuzzilliTests/LifterTest.swift +++ b/Tests/FuzzilliTests/LifterTest.swift @@ -842,6 +842,36 @@ class LifterTests: XCTestCase { } b.reassign(f, to: f2) b.callFunction(f) + var arrayParameter = Parameters(count: 2) + arrayParameter.patterns = [.array(elements: [.identifier, .identifier])] + let descriptor = ProgramBuilder.SubroutineDescriptor.parameters(arrayParameter) + + let f3 = b.buildPlainFunction(with: descriptor) { args in + let elem0 = args[0] + let elem1 = args[1] + + let print = b.loadBuiltin("print") + b.callFunction(print, withArgs: [elem0]) + b.callFunction(print, withArgs: [elem1]) + } + let arrayArg = b.createArray(with: [b.loadInt(42), b.loadInt(43)]) + b.callFunction(f3, withArgs: [arrayArg]) + + var objectParameter = Parameters(count: 2) + objectParameter.patterns = [.object(properties: [("key1", .identifier), ("key2", .identifier)])] + let descriptorObj = ProgramBuilder.SubroutineDescriptor.parameters(objectParameter) + + let f4 = b.buildPlainFunction(with: descriptorObj) { args in + let key1Value = args[0] + let key2Value = args[1] + + let print = b.loadBuiltin("print") + b.callFunction(print, withArgs: [key1Value]) + b.callFunction(print, withArgs: [key2Value]) + } + + let objArg = b.createObject(with: ["key1": b.loadInt(9000), "key2": b.loadInt(9001)]) + b.callFunction(f4, withArgs: [objArg]) let program = b.finalize() let actual = fuzzer.lifter.lift(program) @@ -856,7 +886,21 @@ class LifterTests: XCTestCase { }; f0 = v4; f0(); - + function f7([ a8, a9 ]) { + print(a8); + print(a9); + } + f7([42,43]); + function f17({ key1: a18, key2: a19 }) { + print(a18); + print(a19); + } + const o25 = { + "key1": 9000, + "key2": 9001, + }; + f17(o25); + """ XCTAssertEqual(actual, expected)