From 5fb8f98f3c6ab613a62e86dd28df2fee9234627e Mon Sep 17 00:00:00 2001 From: Tobias Wienand Date: Wed, 9 Oct 2024 17:49:04 +0200 Subject: [PATCH 1/8] Implements compilation of new parameter types This initial commit enables compilation of shallow objectPattern and arrayPattern parameters. Shallow means that all elements of the objectPattern and arrayPattern are identifierParameters and particularly not nested object- or arrayPatterns --- Sources/Fuzzilli/Compiler/Compiler.swift | 72 +++++- Sources/Fuzzilli/Compiler/Parser/parser.js | 24 +- Sources/Fuzzilli/FuzzIL/JsOperations.swift | 18 +- .../Fuzzilli/Lifting/JavaScriptLifter.swift | 28 ++- Sources/Fuzzilli/Protobuf/ast.pb.swift | 223 +++++++++++++++++- Sources/Fuzzilli/Protobuf/ast.proto | 16 ++ .../CompilerTests/advanced_params.js | 20 ++ 7 files changed, 387 insertions(+), 14 deletions(-) create mode 100644 Tests/FuzzilliTests/CompilerTests/advanced_params.js diff --git a/Sources/Fuzzilli/Compiler/Compiler.swift b/Sources/Fuzzilli/Compiler/Compiler.swift index e9f9bbd2f..7d3d240b4 100644 --- a/Sources/Fuzzilli/Compiler/Compiler.swift +++ b/Sources/Fuzzilli/Compiler/Compiler.swift @@ -468,7 +468,14 @@ public class JavaScriptCompiler { try enterNewScope { let beginCatch = emit(BeginCatch()) if tryStatement.catch.hasParameter { - map(tryStatement.catch.parameter.name, to: beginCatch.innerOutput) + let parameter = tryStatement.catch.parameter + switch parameter.parameter { + case .identifierParameter(let identifier): + map(identifier.name, to: beginCatch.innerOutput) + + default: + throw CompilerError.unsupportedFeatureError("Unsupported parameter type") + } } for statement in tryStatement.catch.body { try compileStatement(statement) @@ -1071,14 +1078,69 @@ 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) + var flatParameters: [String] = [] + var expectedVariableCount = 0 + for param in parameters { + switch param.parameter { + case .identifierParameter(let identifier): + flatParameters.append(identifier.name) + expectedVariableCount += 1 + case .objectParameter(let object): + for subParam in object.parameters { + flatParameters.append(subParam.name) + expectedVariableCount += 1 + } + case .arrayParameter(let array): + for element in array.elements { + flatParameters.append(element.name) + expectedVariableCount += 1 + } + default: + break + } + } + assert(expectedVariableCount == variables.count, "The number of variables does not match the number of parameters.") + for (name, v) in zip(flatParameters, variables) { + map(name, to: v) } } private func convertParameters(_ parameters: [Compiler_Protobuf_Parameter]) -> Parameters { - return Parameters(count: parameters.count) + var totalParameterCount = 0 + var parameterTypes = [Parameters.ParameterType]() + var objectPropertyNames = [[String]]() + + for param in parameters { + switch param.parameter { + case .identifierParameter(_): + totalParameterCount += 1 + parameterTypes.append(.identifier) + case .objectParameter(let object): + let objectCount = object.parameters.count + totalParameterCount += objectCount + if (objectCount == 1) { + parameterTypes.append(.standaloneObject) + } else { + parameterTypes.append(.objectStart) + parameterTypes.append(contentsOf: Array(repeating: .objectMiddle, count: max(0, objectCount - 2))) + parameterTypes.append(.objectEnd) + } + objectPropertyNames.append(object.parameters.map { $0.name }) + case .arrayParameter(let array): + let arrayCount = array.elements.count + totalParameterCount += arrayCount + if arrayCount == 1 { + parameterTypes.append(.standaloneArray) + } else { + parameterTypes.append(.arrayStart) + parameterTypes.append(contentsOf: Array(repeating: .arrayMiddle, count: max(0, arrayCount - 2))) + parameterTypes.append(.arrayEnd) + } + default: + break + } + } + return Parameters(count: totalParameterCount, parameterTypes: parameterTypes, objectPropertyNames: objectPropertyNames) } /// 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..487299541 100644 --- a/Sources/Fuzzilli/Compiler/Parser/parser.js +++ b/Sources/Fuzzilli/Compiler/Parser/parser.js @@ -73,9 +73,27 @@ function parse(script, proto) { } function visitParameter(param) { - assert(param.type == 'Identifier'); - return make('Parameter', { name: param.name }); - } + assert(param.type == 'Identifier' || param.type == 'ObjectPattern' || param.type == 'ArrayPattern'); + if (param.type === 'Identifier') { + return make('IdentifierParameter', { identifierParameter: { name: param.name } }); + } else if (param.type === 'ObjectPattern') { + const parameters = param.properties.map(property => { + assert(property.type === 'ObjectProperty'); + assert(property.computed === false); + assert(property.extra && property.extra.shorthand === true); + assert(property.method === false); + assert(property.key.type === 'Identifier'); + return { name: property.key.name }; + }); + return make('ObjectParameter', { objectParameter: { parameters } }); + } else if (param.type === 'ArrayPattern') { + const elements = param.elements.map(element => { + assert(element.type === 'Identifier'); + return { name: element.name }; + }); + return make('ArrayParameter', { arrayParameter: { elements } }); + } + } function visitVariableDeclaration(node) { let kind; diff --git a/Sources/Fuzzilli/FuzzIL/JsOperations.swift b/Sources/Fuzzilli/FuzzIL/JsOperations.swift index bab2bd749..7e7ee7696 100644 --- a/Sources/Fuzzilli/FuzzIL/JsOperations.swift +++ b/Sources/Fuzzilli/FuzzIL/JsOperations.swift @@ -1059,9 +1059,25 @@ public struct Parameters { return Int(numParameters) } - init(count: Int, hasRestParameter: Bool = false) { + enum ParameterType { + case identifier + case objectStart + case objectEnd + case objectMiddle + case standaloneObject + case arrayStart + case arrayEnd + case arrayMiddle + case standaloneArray + } + + var parameterTypes: [ParameterType] + var objectPropertyNames: [[String]] + init(count: Int, hasRestParameter: Bool = false, parameterTypes: [ParameterType] = [], objectPropertyNames: [[String]] = []) { self.numParameters = UInt32(count) self.hasRestParameter = hasRestParameter + self.parameterTypes = parameterTypes + self.objectPropertyNames = objectPropertyNames } } diff --git a/Sources/Fuzzilli/Lifting/JavaScriptLifter.swift b/Sources/Fuzzilli/Lifting/JavaScriptLifter.swift index 240f3ea99..6da936615 100644 --- a/Sources/Fuzzilli/Lifting/JavaScriptLifter.swift +++ b/Sources/Fuzzilli/Lifting/JavaScriptLifter.swift @@ -1356,11 +1356,31 @@ 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 { + var objectPropertyIndex = 0 + for (index, v) in variables.enumerated() { + let type = parameters.parameterTypes[index] + switch type { + case .identifier, .standaloneObject, .standaloneArray: paramList.append(v) + case .objectStart: + let propertyNames = parameters.objectPropertyNames[objectPropertyIndex] + let firstProperty = propertyNames.first! + paramList.append("{ \(firstProperty): \(v)") + case .objectMiddle: + let propertyNames = parameters.objectPropertyNames[objectPropertyIndex] + let middleProperty = propertyNames[index % propertyNames.count] + paramList.append("\(middleProperty): \(v)") + case .objectEnd: + let propertyNames = parameters.objectPropertyNames[objectPropertyIndex] + let lastProperty = propertyNames.last! + paramList.append("\(lastProperty): \(v) }") + objectPropertyIndex += 1 + case .arrayStart: + paramList.append("[\(v)") + case .arrayMiddle: + paramList.append("\(v)") + case .arrayEnd: + paramList.append("\(v)]") } } return paramList.joined(separator: ", ") diff --git a/Sources/Fuzzilli/Protobuf/ast.pb.swift b/Sources/Fuzzilli/Protobuf/ast.pb.swift index 60d2eb9c5..39184e08a 100644 --- a/Sources/Fuzzilli/Protobuf/ast.pb.swift +++ b/Sources/Fuzzilli/Protobuf/ast.pb.swift @@ -132,6 +132,51 @@ 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)} + } + + /// Added ArrayParameter + 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) + /// Added ArrayParameter + 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 +184,30 @@ 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_IdentifierParameter] = [] + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + +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_IdentifierParameter] = [] + + 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 +2301,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 +2412,77 @@ 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_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..21741f50c 100644 --- a/Sources/Fuzzilli/Protobuf/ast.proto +++ b/Sources/Fuzzilli/Protobuf/ast.proto @@ -21,9 +21,25 @@ 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; // Added ArrayParameter + } +} + +message IdentifierParameter { string name = 1; } +message ObjectParameter { + repeated IdentifierParameter parameters = 1; +} + +message ArrayParameter { + repeated IdentifierParameter 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..5081c8609 --- /dev/null +++ b/Tests/FuzzilliTests/CompilerTests/advanced_params.js @@ -0,0 +1,20 @@ +function functionWithObjectPattern(param0, { param1, param2 }) { + console.log("Param0:", param0); + console.log("Param1:", param1); + console.log("Param2:", param2); +} + +function functionWithArrayPattern(param0, [param1, param2]) { + console.log("Param0:", param0); + console.log("Param1:", param1); + console.log("Param2:", param2); +} + +functionWithObjectPattern("foo", { param1: 23, param2: 42 }); +functionWithArrayPattern("bar", [9000, 9001]); + +/* TODO +1) Property renaming in object pattern parameters (e.g. {a: b, c: d}) +2) Nested objected/array patterns (e.g. {a: {b, c}, d: [e, f]}) +3) Default values for parameters (e.g. function f(a = 42) { ... }) +*/ \ No newline at end of file From 2a0816b55cfa4bc7c9ca316dcf9db15e85eb562e Mon Sep 17 00:00:00 2001 From: Tobias Wienand Date: Wed, 9 Oct 2024 18:22:08 +0200 Subject: [PATCH 2/8] Removes unnecessary comment --- Sources/Fuzzilli/Protobuf/ast.pb.swift | 2 -- Sources/Fuzzilli/Protobuf/ast.proto | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/Sources/Fuzzilli/Protobuf/ast.pb.swift b/Sources/Fuzzilli/Protobuf/ast.pb.swift index 39184e08a..439501d6a 100644 --- a/Sources/Fuzzilli/Protobuf/ast.pb.swift +++ b/Sources/Fuzzilli/Protobuf/ast.pb.swift @@ -150,7 +150,6 @@ public struct Compiler_Protobuf_Parameter: Sendable { set {parameter = .objectParameter(newValue)} } - /// Added ArrayParameter public var arrayParameter: Compiler_Protobuf_ArrayParameter { get { if case .arrayParameter(let v)? = parameter {return v} @@ -164,7 +163,6 @@ public struct Compiler_Protobuf_Parameter: Sendable { public enum OneOf_Parameter: Equatable, Sendable { case identifierParameter(Compiler_Protobuf_IdentifierParameter) case objectParameter(Compiler_Protobuf_ObjectParameter) - /// Added ArrayParameter case arrayParameter(Compiler_Protobuf_ArrayParameter) } diff --git a/Sources/Fuzzilli/Protobuf/ast.proto b/Sources/Fuzzilli/Protobuf/ast.proto index 21741f50c..d2e3b6eb8 100644 --- a/Sources/Fuzzilli/Protobuf/ast.proto +++ b/Sources/Fuzzilli/Protobuf/ast.proto @@ -24,7 +24,7 @@ message Parameter { oneof parameter { IdentifierParameter identifierParameter = 1; ObjectParameter objectParameter = 2; - ArrayParameter arrayParameter = 3; // Added ArrayParameter + ArrayParameter arrayParameter = 3; } } From 6e8fde4f29a6aac7caa92e8f4674b069ff3f5956 Mon Sep 17 00:00:00 2001 From: Tobias Wienand Date: Mon, 21 Oct 2024 14:45:45 +0200 Subject: [PATCH 3/8] Fixes some nits - Improved clarity in CompilerError message - Improved brevity of tryStatement code because IdentifierParameter is the only allowed parameter - None case is in parameter type switch statement --- Sources/Fuzzilli/Compiler/Compiler.swift | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/Sources/Fuzzilli/Compiler/Compiler.swift b/Sources/Fuzzilli/Compiler/Compiler.swift index 7d3d240b4..fdd4384ed 100644 --- a/Sources/Fuzzilli/Compiler/Compiler.swift +++ b/Sources/Fuzzilli/Compiler/Compiler.swift @@ -468,14 +468,10 @@ public class JavaScriptCompiler { try enterNewScope { let beginCatch = emit(BeginCatch()) if tryStatement.catch.hasParameter { - let parameter = tryStatement.catch.parameter - switch parameter.parameter { - case .identifierParameter(let identifier): - map(identifier.name, to: beginCatch.innerOutput) - - default: - throw CompilerError.unsupportedFeatureError("Unsupported parameter type") + 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) @@ -1095,7 +1091,7 @@ public class JavaScriptCompiler { flatParameters.append(element.name) expectedVariableCount += 1 } - default: + case .none: break } } From 5905832e3301e0749c45b08b1c0574abaa0997fa Mon Sep 17 00:00:00 2001 From: Tobias Wienand Date: Sun, 27 Oct 2024 19:40:17 +0100 Subject: [PATCH 4/8] Implementes compilation of (nested) array- and object pattern parameters --- Sources/Fuzzilli/Compiler/Compiler.swift | 83 +++++++++---------- Sources/Fuzzilli/Compiler/Parser/parser.js | 61 +++++++++----- Sources/Fuzzilli/FuzzIL/JSTyper.swift | 31 ++++++- Sources/Fuzzilli/FuzzIL/JsOperations.swift | 36 ++++---- .../Fuzzilli/Lifting/JavaScriptLifter.swift | 52 ++++++------ Sources/Fuzzilli/Protobuf/ast.pb.swift | 69 ++++++++++++++- Sources/Fuzzilli/Protobuf/ast.proto | 9 +- .../CompilerTests/advanced_params.js | 41 +++++++-- 8 files changed, 264 insertions(+), 118 deletions(-) diff --git a/Sources/Fuzzilli/Compiler/Compiler.swift b/Sources/Fuzzilli/Compiler/Compiler.swift index fdd4384ed..7572efdba 100644 --- a/Sources/Fuzzilli/Compiler/Compiler.swift +++ b/Sources/Fuzzilli/Compiler/Compiler.swift @@ -122,7 +122,7 @@ public class JavaScriptCompiler { } case .functionDeclaration(let functionDeclaration): - let parameters = convertParameters(functionDeclaration.parameters) + let parameters = try convertParameters(functionDeclaration.parameters) let functionBegin, functionEnd: Operation switch functionDeclaration.type { case .plain: @@ -218,7 +218,7 @@ public class JavaScriptCompiler { emit(op, withInputs: inputs) case .ctor(let constructor): - let parameters = convertParameters(constructor.parameters) + let parameters = try convertParameters(constructor.parameters) let head = emit(BeginClassConstructor(parameters: parameters)) try enterNewScope { @@ -233,7 +233,7 @@ public class JavaScriptCompiler { emit(EndClassConstructor()) case .method(let method): - let parameters = convertParameters(method.parameters) + let parameters = try convertParameters(method.parameters) let head: Instruction if method.isStatic { head = emit(BeginClassStaticMethod(methodName: method.name, parameters: parameters)) @@ -780,7 +780,7 @@ public class JavaScriptCompiler { emit(ObjectLiteralAddComputedProperty(), withInputs: [computedPropertyKeys.removeLast()] + inputs) } case .method(let method): - let parameters = convertParameters(method.parameters) + let parameters = try convertParameters(method.parameters) let instr: Instruction if case .name(let name) = method.key { @@ -861,7 +861,7 @@ public class JavaScriptCompiler { } case .functionExpression(let functionExpression): - let parameters = convertParameters(functionExpression.parameters) + let parameters = try convertParameters(functionExpression.parameters) let functionBegin, functionEnd: Operation switch functionExpression.type { case .plain: @@ -892,7 +892,7 @@ public class JavaScriptCompiler { return instr.output case .arrowFunctionExpression(let arrowFunction): - let parameters = convertParameters(arrowFunction.parameters) + let parameters = try convertParameters(arrowFunction.parameters) let functionBegin, functionEnd: Operation switch arrowFunction.type { case .plain: @@ -1074,69 +1074,68 @@ public class JavaScriptCompiler { } private func mapParameters(_ parameters: [Compiler_Protobuf_Parameter], to variables: ArraySlice) { - var flatParameters: [String] = [] - var expectedVariableCount = 0 - for param in parameters { + var flatParameters: [String] = [] + func extractIdentifiers(from param: Compiler_Protobuf_Parameter) { switch param.parameter { case .identifierParameter(let identifier): flatParameters.append(identifier.name) - expectedVariableCount += 1 case .objectParameter(let object): - for subParam in object.parameters { - flatParameters.append(subParam.name) - expectedVariableCount += 1 + for property in object.parameters { + extractIdentifiers(from: property.parameterValue) } case .arrayParameter(let array): for element in array.elements { - flatParameters.append(element.name) - expectedVariableCount += 1 + extractIdentifiers(from: element) } case .none: break } } - assert(expectedVariableCount == variables.count, "The number of variables does not match the number of parameters.") + 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 { + private func convertParameters(_ parameters: [Compiler_Protobuf_Parameter]) throws -> Parameters { var totalParameterCount = 0 - var parameterTypes = [Parameters.ParameterType]() - var objectPropertyNames = [[String]]() - - for param in parameters { + var patterns = [ParameterPattern]() + func processParameter(_ param: Compiler_Protobuf_Parameter) throws -> ParameterPattern { switch param.parameter { case .identifierParameter(_): totalParameterCount += 1 - parameterTypes.append(.identifier) + return .identifier case .objectParameter(let object): - let objectCount = object.parameters.count - totalParameterCount += objectCount - if (objectCount == 1) { - parameterTypes.append(.standaloneObject) - } else { - parameterTypes.append(.objectStart) - parameterTypes.append(contentsOf: Array(repeating: .objectMiddle, count: max(0, objectCount - 2))) - parameterTypes.append(.objectEnd) + var properties = [ObjectPatternProperty]() + for property in object.parameters { + let key = property.parameterKey + let valuePattern = try processParameter(property.parameterValue) + properties.append(ObjectPatternProperty(key: key, value: valuePattern)) } - objectPropertyNames.append(object.parameters.map { $0.name }) + return .object(properties: properties) case .arrayParameter(let array): - let arrayCount = array.elements.count - totalParameterCount += arrayCount - if arrayCount == 1 { - parameterTypes.append(.standaloneArray) - } else { - parameterTypes.append(.arrayStart) - parameterTypes.append(contentsOf: Array(repeating: .arrayMiddle, count: max(0, arrayCount - 2))) - parameterTypes.append(.arrayEnd) + var elements = [ParameterPattern]() + for element in array.elements { + let elementPattern = try processParameter(element) + elements.append(elementPattern) } - default: - break + return .array(elements: elements) + case .none: + throw CompilerError.unsupportedFeatureError("Unexpected parameter type: .none in convertParameters") } } - return Parameters(count: totalParameterCount, parameterTypes: parameterTypes, objectPropertyNames: objectPropertyNames) + for param in parameters { + let pattern = try 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 487299541..b4156c3d1 100644 --- a/Sources/Fuzzilli/Compiler/Parser/parser.js +++ b/Sources/Fuzzilli/Compiler/Parser/parser.js @@ -73,27 +73,48 @@ function parse(script, proto) { } function visitParameter(param) { - assert(param.type == 'Identifier' || param.type == 'ObjectPattern' || param.type == 'ArrayPattern'); - if (param.type === 'Identifier') { - return make('IdentifierParameter', { identifierParameter: { name: param.name } }); - } else if (param.type === 'ObjectPattern') { - const parameters = param.properties.map(property => { - assert(property.type === 'ObjectProperty'); - assert(property.computed === false); - assert(property.extra && property.extra.shorthand === true); - assert(property.method === false); - assert(property.key.type === 'Identifier'); - return { name: property.key.name }; - }); - return make('ObjectParameter', { objectParameter: { parameters } }); - } else if (param.type === 'ArrayPattern') { - const elements = param.elements.map(element => { - assert(element.type === 'Identifier'); - return { name: element.name }; - }); - return make('ArrayParameter', { arrayParameter: { elements } }); + 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) { let kind; diff --git a/Sources/Fuzzilli/FuzzIL/JSTyper.swift b/Sources/Fuzzilli/FuzzIL/JSTyper.swift index dc9858454..f7b2ffcc9 100644 --- a/Sources/Fuzzilli/FuzzIL/JSTyper.swift +++ b/Sources/Fuzzilli/FuzzIL/JSTyper.swift @@ -170,7 +170,36 @@ 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: + 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 7e7ee7696..9e722aa9d 100644 --- a/Sources/Fuzzilli/FuzzIL/JsOperations.swift +++ b/Sources/Fuzzilli/FuzzIL/JsOperations.swift @@ -1047,36 +1047,36 @@ final class TestIn: JsOperation { } +enum ParameterPattern { + case identifier + case object(properties: [ObjectPatternProperty]) + case array(elements: [ParameterPattern]) +} + +struct ObjectPatternProperty { + let key: String + let value: ParameterPattern +} + // The parameters of a FuzzIL subroutine. public struct Parameters { - /// The total number of parameters. private let numParameters: UInt32 - /// Whether the last parameter is a rest parameter. 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) } - enum ParameterType { - case identifier - case objectStart - case objectEnd - case objectMiddle - case standaloneObject - case arrayStart - case arrayEnd - case arrayMiddle - case standaloneArray - } - - var parameterTypes: [ParameterType] var objectPropertyNames: [[String]] - init(count: Int, hasRestParameter: Bool = false, parameterTypes: [ParameterType] = [], objectPropertyNames: [[String]] = []) { + var patterns: [ParameterPattern] + init( + count: Int, + hasRestParameter: Bool = false, + objectPropertyNames: [[String]] = [] + ) { self.numParameters = UInt32(count) self.hasRestParameter = hasRestParameter - self.parameterTypes = parameterTypes + self.patterns = [] self.objectPropertyNames = objectPropertyNames } } diff --git a/Sources/Fuzzilli/Lifting/JavaScriptLifter.swift b/Sources/Fuzzilli/Lifting/JavaScriptLifter.swift index 6da936615..3cf4ee6db 100644 --- a/Sources/Fuzzilli/Lifting/JavaScriptLifter.swift +++ b/Sources/Fuzzilli/Lifting/JavaScriptLifter.swift @@ -1355,35 +1355,33 @@ public class JavaScriptLifter: Lifter { private func liftParameters(_ parameters: Parameters, as variables: [String]) -> String { assert(parameters.count == variables.count) - var paramList = [String]() - var objectPropertyIndex = 0 - for (index, v) in variables.enumerated() { - let type = parameters.parameterTypes[index] - switch type { - case .identifier, .standaloneObject, .standaloneArray: - paramList.append(v) - case .objectStart: - let propertyNames = parameters.objectPropertyNames[objectPropertyIndex] - let firstProperty = propertyNames.first! - paramList.append("{ \(firstProperty): \(v)") - case .objectMiddle: - let propertyNames = parameters.objectPropertyNames[objectPropertyIndex] - let middleProperty = propertyNames[index % propertyNames.count] - paramList.append("\(middleProperty): \(v)") - case .objectEnd: - let propertyNames = parameters.objectPropertyNames[objectPropertyIndex] - let lastProperty = propertyNames.last! - paramList.append("\(lastProperty): \(v) }") - objectPropertyIndex += 1 - case .arrayStart: - paramList.append("[\(v)") - case .arrayMiddle: - paramList.append("\(v)") - case .arrayEnd: - 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 { property -> String in + let key = property.key + let value = liftPattern(property.value) + 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 439501d6a..546b82d43 100644 --- a/Sources/Fuzzilli/Protobuf/ast.pb.swift +++ b/Sources/Fuzzilli/Protobuf/ast.pb.swift @@ -187,19 +187,42 @@ public struct Compiler_Protobuf_ObjectParameter: Sendable { // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. - public var parameters: [Compiler_Protobuf_IdentifierParameter] = [] + 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_IdentifierParameter] = [] + public var elements: [Compiler_Protobuf_Parameter] = [] public var unknownFields = SwiftProtobuf.UnknownStorage() @@ -2449,6 +2472,48 @@ extension Compiler_Protobuf_ObjectParameter: SwiftProtobuf.Message, SwiftProtobu } } +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 = [ diff --git a/Sources/Fuzzilli/Protobuf/ast.proto b/Sources/Fuzzilli/Protobuf/ast.proto index d2e3b6eb8..c257d279f 100644 --- a/Sources/Fuzzilli/Protobuf/ast.proto +++ b/Sources/Fuzzilli/Protobuf/ast.proto @@ -33,11 +33,16 @@ message IdentifierParameter { } message ObjectParameter { - repeated IdentifierParameter parameters = 1; + repeated ObjectParameterProperty parameters = 1; +} + +message ObjectParameterProperty { + string parameterKey = 1; + Parameter parameterValue = 2; } message ArrayParameter { - repeated IdentifierParameter elements = 1; + repeated Parameter elements = 1; } message EmptyStatement { diff --git a/Tests/FuzzilliTests/CompilerTests/advanced_params.js b/Tests/FuzzilliTests/CompilerTests/advanced_params.js index 5081c8609..d309b04a3 100644 --- a/Tests/FuzzilliTests/CompilerTests/advanced_params.js +++ b/Tests/FuzzilliTests/CompilerTests/advanced_params.js @@ -1,3 +1,11 @@ +function functionSimple(param0) { + console.log("Param0:", param0); +} + +function functionNoParam() { + console.log("No parameters here ..."); +} + function functionWithObjectPattern(param0, { param1, param2 }) { console.log("Param0:", param0); console.log("Param1:", param1); @@ -10,11 +18,32 @@ function functionWithArrayPattern(param0, [param1, param2]) { console.log("Param2:", param2); } +function functionWithNestedObjectPattern(param0, { param1, param2: { subParam1, subParam2 } }) { + console.log("Param0:", param0); + console.log("Param1:", param1); + console.log("SubParam1:", subParam1); + console.log("SubParam2:", subParam2); +} + +function functionWithNestedArrayPattern(param0, [param1, [subParam1, subParam2]]) { + console.log("Param0:", param0); + console.log("Param1:", param1); + console.log("SubParam1:", subParam1); + console.log("SubParam2:", subParam2); +} + +function functionWithMixedPattern([param0, { objParam1, objParam2 }], { arrParam1, arrParam2: [subArr1, subArr2] }) { + console.log("Param0 (Array pattern):", param0); + console.log("ObjParam1 (in Array pattern):", objParam1); + console.log("ObjParam2 (in Array pattern):", objParam2); + console.log("ArrParam1 (in Object pattern):", arrParam1); + console.log("SubArr1 (in Array within Object pattern):", subArr1); + console.log("SubArr2 (in Array within Object pattern):", subArr2); +} + + functionWithObjectPattern("foo", { param1: 23, param2: 42 }); functionWithArrayPattern("bar", [9000, 9001]); - -/* TODO -1) Property renaming in object pattern parameters (e.g. {a: b, c: d}) -2) Nested objected/array patterns (e.g. {a: {b, c}, d: [e, f]}) -3) Default values for parameters (e.g. function f(a = 42) { ... }) -*/ \ No newline at end of file +functionWithNestedObjectPattern("foo", { param1: 23, param2: { subParam1: 100, subParam2: 200 } }); +functionWithNestedArrayPattern("bar", [9000, [9001, 9002]]); +functionWithMixedPattern(["alpha", { objParam1: 300, objParam2: 400 }], { arrParam1: 500, arrParam2: [8000, 8001] }); \ No newline at end of file From d4169b71b553b085e292258e3e174750fc3d74c1 Mon Sep 17 00:00:00 2001 From: Tobias Wienand Date: Fri, 8 Nov 2024 12:33:29 +0100 Subject: [PATCH 5/8] Refactoring Parameter Types --- Sources/Fuzzilli/Compiler/Compiler.swift | 29 ++++++++++++---------- Sources/Fuzzilli/FuzzIL/JSTyper.swift | 1 + Sources/Fuzzilli/FuzzIL/JsOperations.swift | 15 +++++------ 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/Sources/Fuzzilli/Compiler/Compiler.swift b/Sources/Fuzzilli/Compiler/Compiler.swift index 7572efdba..b11ea555a 100644 --- a/Sources/Fuzzilli/Compiler/Compiler.swift +++ b/Sources/Fuzzilli/Compiler/Compiler.swift @@ -122,7 +122,7 @@ public class JavaScriptCompiler { } case .functionDeclaration(let functionDeclaration): - let parameters = try convertParameters(functionDeclaration.parameters) + let parameters = convertParameters(functionDeclaration.parameters) let functionBegin, functionEnd: Operation switch functionDeclaration.type { case .plain: @@ -218,7 +218,7 @@ public class JavaScriptCompiler { emit(op, withInputs: inputs) case .ctor(let constructor): - let parameters = try convertParameters(constructor.parameters) + let parameters = convertParameters(constructor.parameters) let head = emit(BeginClassConstructor(parameters: parameters)) try enterNewScope { @@ -233,7 +233,7 @@ public class JavaScriptCompiler { emit(EndClassConstructor()) case .method(let method): - let parameters = try convertParameters(method.parameters) + let parameters = convertParameters(method.parameters) let head: Instruction if method.isStatic { head = emit(BeginClassStaticMethod(methodName: method.name, parameters: parameters)) @@ -780,7 +780,7 @@ public class JavaScriptCompiler { emit(ObjectLiteralAddComputedProperty(), withInputs: [computedPropertyKeys.removeLast()] + inputs) } case .method(let method): - let parameters = try convertParameters(method.parameters) + let parameters = convertParameters(method.parameters) let instr: Instruction if case .name(let name) = method.key { @@ -861,7 +861,7 @@ public class JavaScriptCompiler { } case .functionExpression(let functionExpression): - let parameters = try convertParameters(functionExpression.parameters) + let parameters = convertParameters(functionExpression.parameters) let functionBegin, functionEnd: Operation switch functionExpression.type { case .plain: @@ -892,7 +892,7 @@ public class JavaScriptCompiler { return instr.output case .arrowFunctionExpression(let arrowFunction): - let parameters = try convertParameters(arrowFunction.parameters) + let parameters = convertParameters(arrowFunction.parameters) let functionBegin, functionEnd: Operation switch arrowFunction.type { case .plain: @@ -1074,6 +1074,8 @@ public class JavaScriptCompiler { } private func mapParameters(_ parameters: [Compiler_Protobuf_Parameter], to variables: ArraySlice) { + // 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 { @@ -1088,7 +1090,7 @@ public class JavaScriptCompiler { extractIdentifiers(from: element) } case .none: - break + fatalError("Unexpected parameter type: .none in mapParameters") } } for param in parameters { @@ -1100,10 +1102,11 @@ public class JavaScriptCompiler { } } - private func convertParameters(_ parameters: [Compiler_Protobuf_Parameter]) throws -> Parameters { + private func convertParameters(_ parameters: [Compiler_Protobuf_Parameter]) -> Parameters { + // Converts a protobuf signature to a FuzzIL signature. var totalParameterCount = 0 var patterns = [ParameterPattern]() - func processParameter(_ param: Compiler_Protobuf_Parameter) throws -> ParameterPattern { + func processParameter(_ param: Compiler_Protobuf_Parameter) -> ParameterPattern { switch param.parameter { case .identifierParameter(_): totalParameterCount += 1 @@ -1112,23 +1115,23 @@ public class JavaScriptCompiler { var properties = [ObjectPatternProperty]() for property in object.parameters { let key = property.parameterKey - let valuePattern = try processParameter(property.parameterValue) + let valuePattern = processParameter(property.parameterValue) properties.append(ObjectPatternProperty(key: key, value: valuePattern)) } return .object(properties: properties) case .arrayParameter(let array): var elements = [ParameterPattern]() for element in array.elements { - let elementPattern = try processParameter(element) + let elementPattern = processParameter(element) elements.append(elementPattern) } return .array(elements: elements) case .none: - throw CompilerError.unsupportedFeatureError("Unexpected parameter type: .none in convertParameters") + fatalError("Unexpected parameter type: .none in convertParameters") } } for param in parameters { - let pattern = try processParameter(param) + let pattern = processParameter(param) patterns.append(pattern) } var params = Parameters( diff --git a/Sources/Fuzzilli/FuzzIL/JSTyper.swift b/Sources/Fuzzilli/FuzzIL/JSTyper.swift index f7b2ffcc9..a3454d7ab 100644 --- a/Sources/Fuzzilli/FuzzIL/JSTyper.swift +++ b/Sources/Fuzzilli/FuzzIL/JSTyper.swift @@ -198,6 +198,7 @@ public struct JSTyper: Analyzer { case .array: return .iterable case .object: + // TODO be more precise here: Describe the object parameters structure return .object() } } diff --git a/Sources/Fuzzilli/FuzzIL/JsOperations.swift b/Sources/Fuzzilli/FuzzIL/JsOperations.swift index 9e722aa9d..02a4ac98a 100644 --- a/Sources/Fuzzilli/FuzzIL/JsOperations.swift +++ b/Sources/Fuzzilli/FuzzIL/JsOperations.swift @@ -1060,24 +1060,25 @@ struct ObjectPatternProperty { // The parameters of a FuzzIL subroutine. public struct 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 var count: Int { - return Int(numParameters) + return Int(numVariables) } - var objectPropertyNames: [[String]] var patterns: [ParameterPattern] init( count: Int, - hasRestParameter: Bool = false, - objectPropertyNames: [[String]] = [] + hasRestParameter: Bool = false ) { - self.numParameters = UInt32(count) + self.numVariables = UInt32(count) self.hasRestParameter = hasRestParameter self.patterns = [] - self.objectPropertyNames = objectPropertyNames } } From 3466c112268a8d921223211d20aae12d076305ea Mon Sep 17 00:00:00 2001 From: Tobias Wienand Date: Sun, 10 Nov 2024 23:48:23 +0100 Subject: [PATCH 6/8] Improves tests and removes ObjectPatternProperty --- Sources/Fuzzilli/Compiler/Compiler.swift | 4 +- Sources/Fuzzilli/FuzzIL/JsOperations.swift | 7 +- .../Fuzzilli/Lifting/JavaScriptLifter.swift | 5 +- .../CompilerTests/advanced_params.js | 69 ++++++++++--------- 4 files changed, 42 insertions(+), 43 deletions(-) diff --git a/Sources/Fuzzilli/Compiler/Compiler.swift b/Sources/Fuzzilli/Compiler/Compiler.swift index b11ea555a..69054893a 100644 --- a/Sources/Fuzzilli/Compiler/Compiler.swift +++ b/Sources/Fuzzilli/Compiler/Compiler.swift @@ -1112,11 +1112,11 @@ public class JavaScriptCompiler { totalParameterCount += 1 return .identifier case .objectParameter(let object): - var properties = [ObjectPatternProperty]() + var properties = [(String, ParameterPattern)]() for property in object.parameters { let key = property.parameterKey let valuePattern = processParameter(property.parameterValue) - properties.append(ObjectPatternProperty(key: key, value: valuePattern)) + properties.append((key, valuePattern)) } return .object(properties: properties) case .arrayParameter(let array): diff --git a/Sources/Fuzzilli/FuzzIL/JsOperations.swift b/Sources/Fuzzilli/FuzzIL/JsOperations.swift index 02a4ac98a..320689769 100644 --- a/Sources/Fuzzilli/FuzzIL/JsOperations.swift +++ b/Sources/Fuzzilli/FuzzIL/JsOperations.swift @@ -1049,15 +1049,10 @@ final class TestIn: JsOperation { enum ParameterPattern { case identifier - case object(properties: [ObjectPatternProperty]) + case object(properties: [(String, ParameterPattern)]) case array(elements: [ParameterPattern]) } -struct ObjectPatternProperty { - let key: String - let value: ParameterPattern -} - // The parameters of a FuzzIL subroutine. public struct Parameters { /// The total number of variables after destructuring object- and array patterns. diff --git a/Sources/Fuzzilli/Lifting/JavaScriptLifter.swift b/Sources/Fuzzilli/Lifting/JavaScriptLifter.swift index 3cf4ee6db..16cbcecda 100644 --- a/Sources/Fuzzilli/Lifting/JavaScriptLifter.swift +++ b/Sources/Fuzzilli/Lifting/JavaScriptLifter.swift @@ -1364,9 +1364,8 @@ public class JavaScriptLifter: Lifter { return variableName case .object(let properties): - let liftedProperties = properties.map { property -> String in - let key = property.key - let value = liftPattern(property.value) + let liftedProperties = properties.map { (key, valuePattern) -> String in + let value = liftPattern(valuePattern) return "\(key): \(value)" } return "{ " + liftedProperties.joined(separator: ", ") + " }" diff --git a/Tests/FuzzilliTests/CompilerTests/advanced_params.js b/Tests/FuzzilliTests/CompilerTests/advanced_params.js index d309b04a3..1f898c926 100644 --- a/Tests/FuzzilliTests/CompilerTests/advanced_params.js +++ b/Tests/FuzzilliTests/CompilerTests/advanced_params.js @@ -1,49 +1,54 @@ -function functionSimple(param0) { - console.log("Param0:", param0); +function functionSimple(paramA) { + console.log("paramA:", paramA); } function functionNoParam() { console.log("No parameters here ..."); } -function functionWithObjectPattern(param0, { param1, param2 }) { - console.log("Param0:", param0); - console.log("Param1:", param1); - console.log("Param2:", param2); -} - -function functionWithArrayPattern(param0, [param1, param2]) { - console.log("Param0:", param0); - console.log("Param1:", param1); - console.log("Param2:", param2); +function functionWithObjectPattern(argPrimary, { keyA, keyB }) { + console.log("keyB:", keyB); + console.log("argPrimary:", argPrimary); + console.log("keyA:", keyA); } -function functionWithNestedObjectPattern(param0, { param1, param2: { subParam1, subParam2 } }) { - console.log("Param0:", param0); - console.log("Param1:", param1); - console.log("SubParam1:", subParam1); - console.log("SubParam2:", subParam2); +function functionWithArrayPattern(firstElem, [secondElem, thirdElem]) { + console.log("secondElem:", secondElem); + console.log("thirdElem:", thirdElem); + console.log("firstElem:", firstElem); } -function functionWithNestedArrayPattern(param0, [param1, [subParam1, subParam2]]) { - console.log("Param0:", param0); - console.log("Param1:", param1); - console.log("SubParam1:", subParam1); - console.log("SubParam2:", subParam2); +function functionWithNestedObjectPattern(mainArg, { nestedKey1, nestedKey2: { subKeyX, subKeyY } }) { + console.log("mainArg:", mainArg); + console.log("subKeyY:", subKeyY); + console.log("nestedKey1:", nestedKey1); + console.log("subKeyX:", subKeyX); } -function functionWithMixedPattern([param0, { objParam1, objParam2 }], { arrParam1, arrParam2: [subArr1, subArr2] }) { - console.log("Param0 (Array pattern):", param0); - console.log("ObjParam1 (in Array pattern):", objParam1); - console.log("ObjParam2 (in Array pattern):", objParam2); - console.log("ArrParam1 (in Object pattern):", arrParam1); - console.log("SubArr1 (in Array within Object pattern):", subArr1); - console.log("SubArr2 (in Array within Object pattern):", subArr2); +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", { param1: 23, param2: 42 }); +functionWithObjectPattern("foo", { keyA: 23, keyB: 42 }); functionWithArrayPattern("bar", [9000, 9001]); -functionWithNestedObjectPattern("foo", { param1: 23, param2: { subParam1: 100, subParam2: 200 } }); +functionWithNestedObjectPattern("foo", { nestedKey1: 23, nestedKey2: { subKeyX: 100, subKeyY: 200 } }); functionWithNestedArrayPattern("bar", [9000, [9001, 9002]]); -functionWithMixedPattern(["alpha", { objParam1: 300, objParam2: 400 }], { arrParam1: 500, arrParam2: [8000, 8001] }); \ No newline at end of file +functionWithMixedPattern( + ["alpha", { objKey1: 300, objKey2: 400 }], + { arrKey1: 500, arrKey2: [8000, 8001] } +); From 97fa1fb124c1c051183af6222b2e914a8bf6fcd1 Mon Sep 17 00:00:00 2001 From: Tobias Wienand Date: Mon, 11 Nov 2024 01:09:45 +0100 Subject: [PATCH 7/8] Implements temporary fix The problem that is addressed with this commit can be seen when using testFunctionLifting. The test fails because the expected f0(a1) is instead f0() - i.e. somehow the parameter a1 wasn't lifted. This happens because the patterns array in the new Parameter array was initialized empty and only populated afterwards. In a later commit we will make patterns a constructor parameter and immutable afterwards. --- Sources/Fuzzilli/FuzzIL/JsOperations.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Fuzzilli/FuzzIL/JsOperations.swift b/Sources/Fuzzilli/FuzzIL/JsOperations.swift index 320689769..62862d157 100644 --- a/Sources/Fuzzilli/FuzzIL/JsOperations.swift +++ b/Sources/Fuzzilli/FuzzIL/JsOperations.swift @@ -1073,7 +1073,7 @@ public struct Parameters { ) { self.numVariables = UInt32(count) self.hasRestParameter = hasRestParameter - self.patterns = [] + self.patterns = Array(repeating: .identifier, count: count) } } From 6d57a33c9aead4884fe2d03862163006702ca7b5 Mon Sep 17 00:00:00 2001 From: Tobias Wienand Date: Mon, 11 Nov 2024 03:58:53 +0100 Subject: [PATCH 8/8] Additional tests for lifting advanced parameters in LifterTest.swift The tests pass --- Sources/Fuzzilli/Base/ProgramBuilder.swift | 4 ++ Tests/FuzzilliTests/LifterTest.swift | 46 +++++++++++++++++++++- 2 files changed, 49 insertions(+), 1 deletion(-) 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/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)