From 5798638831dbc3347a0aa557f14ef63ebed704b3 Mon Sep 17 00:00:00 2001 From: TobiasWienand <104697364+TobiasWienand@users.noreply.github.com> Date: Thu, 21 Nov 2024 14:06:08 +0100 Subject: [PATCH] Feature/super expression (#468) The enabled JSOperations are: CallSuperConstructor, SetSuperProperty, GetSuperProperty, SetComputedSuperProperty, GetComputedSuperProperty, CallSuperMethod, UpdateSuperProperty --- Sources/Fuzzilli/Compiler/Compiler.swift | 73 ++++++ Sources/Fuzzilli/Compiler/Parser/parser.js | 18 ++ Sources/Fuzzilli/Protobuf/ast.pb.swift | 265 +++++++++++++++++++++ Sources/Fuzzilli/Protobuf/ast.proto | 16 ++ Tests/FuzzilliTests/CompilerTests/super.js | 35 +++ 5 files changed, 407 insertions(+) create mode 100644 Tests/FuzzilliTests/CompilerTests/super.js diff --git a/Sources/Fuzzilli/Compiler/Compiler.swift b/Sources/Fuzzilli/Compiler/Compiler.swift index 9fe9f71ba..fef246a9d 100644 --- a/Sources/Fuzzilli/Compiler/Compiler.swift +++ b/Sources/Fuzzilli/Compiler/Compiler.swift @@ -696,6 +696,30 @@ public class JavaScriptCompiler { } } } + case .superMemberExpression(let superMemberExpression): + guard superMemberExpression.isOptional == false else { + throw CompilerError.unsupportedFeatureError("Optional chaining is not supported in super member expressions") + } + + guard let property = superMemberExpression.property else { + throw CompilerError.invalidNodeError("Missing property in super member expression") + } + + switch property { + case .name(let name): + if let op = assignmentOperator { + // Example: super.foo += 1 + emit(UpdateSuperProperty(propertyName: name, operator: op), withInputs: [rhs]) + } else { + // Example: super.foo = 1 + emit(SetSuperProperty(propertyName: name), withInputs: [rhs]) + } + + case .expression(let expr): + let property = try compileExpression(expr) + // Example: super[expr] = 1 + emit(SetComputedSuperProperty(), withInputs: [property, rhs]) + } case .identifier(let identifier): @@ -924,6 +948,7 @@ public class JavaScriptCompiler { // See if this is a function or a method call if case .memberExpression(let memberExpression) = callExpression.callee.expression { + // obj.foo(...) or obj[expr](...) let object = try compileExpression(memberExpression.object) guard let property = memberExpression.property else { throw CompilerError.invalidNodeError("missing property in member expression in call expression") } switch property { @@ -941,6 +966,18 @@ public class JavaScriptCompiler { return emit(CallComputedMethod(numArguments: arguments.count, isGuarded: callExpression.isOptional), withInputs: [object, method] + arguments).output } } + } else if case .superMemberExpression(let superMemberExpression) = callExpression.callee.expression { + // super.foo(...) + guard !isSpreading else { + throw CompilerError.unsupportedFeatureError("Spread calls with super are not supported") + } + guard case .name(let methodName) = superMemberExpression.property else { + throw CompilerError.invalidNodeError("Super method calls must use a property name") + } + guard !callExpression.isOptional else { + throw CompilerError.unsupportedFeatureError("Optional chaining with super method calls is not supported") + } + return emit(CallSuperMethod(methodName: methodName, numArguments: arguments.count), withInputs: arguments).output // Now check if it is a V8 intrinsic function } else if case .v8IntrinsicIdentifier(let v8Intrinsic) = callExpression.callee.expression { guard !isSpreading else { throw CompilerError.unsupportedFeatureError("Not currently supporting spread calls to V8 intrinsics") } @@ -957,6 +994,21 @@ public class JavaScriptCompiler { } } + case .callSuperConstructor(let callSuperConstructor): + let (arguments, spreads) = try compileCallArguments(callSuperConstructor.arguments) + let isSpreading = spreads.contains(true) + + if isSpreading { + throw CompilerError.unsupportedFeatureError("Spread arguments are not supported in super constructor calls") + } + guard !callSuperConstructor.isOptional else { + throw CompilerError.unsupportedFeatureError("Optional chaining is not supported in super constructor calls") + } + emit(CallSuperConstructor(numArguments: arguments.count), withInputs: arguments) + // In JS, the result of calling the super constructor is just |this|, but in FuzzIL the operation doesn't have an output (because |this| is always available anyway) + return lookupIdentifier("this")! // we can force unwrap because |this| always exists in the context where |super| exists + + case .newExpression(let newExpression): let callee = try compileExpression(newExpression.callee) let (arguments, spreads) = try compileCallArguments(newExpression.arguments) @@ -981,6 +1033,27 @@ public class JavaScriptCompiler { return emit(GetComputedProperty(isGuarded: memberExpression.isOptional), withInputs: [object, property]).output } } + + case .superMemberExpression(let superMemberExpression): + guard superMemberExpression.isOptional == false else { + throw CompilerError.unsupportedFeatureError("Optional chaining is not supported in super member expressions") + } + guard let property = superMemberExpression.property else { + throw CompilerError.invalidNodeError("Missing property in super member expression") + } + + switch property { + case .name(let name): + return emit(GetSuperProperty(propertyName: name), withInputs: []).output + + case .expression(let expr): + if case .numberLiteral(let literal) = expr.expression, let _ = Int64(exactly: literal.value) { + throw CompilerError.unsupportedFeatureError("GetElement is not supported in super member expressions") + } else { + let compiledProperty = try compileExpression(expr) + return emit(GetComputedSuperProperty(), withInputs: [compiledProperty]).output + } + } case .unaryExpression(let unaryExpression): if unaryExpression.operator == "typeof" { diff --git a/Sources/Fuzzilli/Compiler/Parser/parser.js b/Sources/Fuzzilli/Compiler/Parser/parser.js index 99c8c57f0..237ed9b1b 100644 --- a/Sources/Fuzzilli/Compiler/Parser/parser.js +++ b/Sources/Fuzzilli/Compiler/Parser/parser.js @@ -512,6 +512,12 @@ function parse(script, proto) { } case 'CallExpression': case 'OptionalCallExpression': { + if (node.callee.type === 'Super') { + let arguments = node.arguments.map(visitExpression); + let isOptional = node.type === 'OptionalCallExpression'; + return makeExpression('CallSuperConstructor', { arguments, isOptional }); + } + let callee = visitExpression(node.callee); let arguments = node.arguments.map(visitExpression); let isOptional = node.type === 'OptionalCallExpression'; @@ -524,6 +530,18 @@ function parse(script, proto) { } case 'MemberExpression': case 'OptionalMemberExpression': { + if (node.object && node.object.type === 'Super') { + let out = {}; + if (node.computed) { + out.expression = visitExpression(node.property); + } else { + assert(node.property.type === 'Identifier', "Expected node.property.type to be exactly 'Identifier'"); + assert(node.property.name != 'Super', "super.super(...) is not allowed"); + out.name = node.property.name; + } + out.isOptional = node.type === 'OptionalMemberExpression'; + return makeExpression('SuperMemberExpression', out); + } let object = visitExpression(node.object); let out = { object }; if (node.computed) { diff --git a/Sources/Fuzzilli/Protobuf/ast.pb.swift b/Sources/Fuzzilli/Protobuf/ast.pb.swift index 0236909fc..a71e2b36d 100644 --- a/Sources/Fuzzilli/Protobuf/ast.pb.swift +++ b/Sources/Fuzzilli/Protobuf/ast.pb.swift @@ -1952,6 +1952,66 @@ public struct Compiler_Protobuf_AwaitExpression: @unchecked Sendable { fileprivate var _storage = _StorageClass.defaultInstance } +public struct Compiler_Protobuf_SuperMemberExpression: @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. + + /// Since 'super' is not an expression, there's no object field + public var property: OneOf_Property? { + get {return _storage._property} + set {_uniqueStorage()._property = newValue} + } + + public var name: String { + get { + if case .name(let v)? = _storage._property {return v} + return String() + } + set {_uniqueStorage()._property = .name(newValue)} + } + + public var expression: Compiler_Protobuf_Expression { + get { + if case .expression(let v)? = _storage._property {return v} + return Compiler_Protobuf_Expression() + } + set {_uniqueStorage()._property = .expression(newValue)} + } + + public var isOptional: Bool { + get {return _storage._isOptional} + set {_uniqueStorage()._isOptional = newValue} + } + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + /// Since 'super' is not an expression, there's no object field + public enum OneOf_Property: Equatable, Sendable { + case name(String) + case expression(Compiler_Protobuf_Expression) + + } + + public init() {} + + fileprivate var _storage = _StorageClass.defaultInstance +} + +public struct Compiler_Protobuf_CallSuperConstructor: 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 arguments: [Compiler_Protobuf_Expression] = [] + + public var isOptional: Bool = false + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + public struct Compiler_Protobuf_Expression: @unchecked Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for @@ -2170,6 +2230,22 @@ public struct Compiler_Protobuf_Expression: @unchecked Sendable { set {_uniqueStorage()._expression = .awaitExpression(newValue)} } + public var superMemberExpression: Compiler_Protobuf_SuperMemberExpression { + get { + if case .superMemberExpression(let v)? = _storage._expression {return v} + return Compiler_Protobuf_SuperMemberExpression() + } + set {_uniqueStorage()._expression = .superMemberExpression(newValue)} + } + + public var callSuperConstructor: Compiler_Protobuf_CallSuperConstructor { + get { + if case .callSuperConstructor(let v)? = _storage._expression {return v} + return Compiler_Protobuf_CallSuperConstructor() + } + set {_uniqueStorage()._expression = .callSuperConstructor(newValue)} + } + public var unknownFields = SwiftProtobuf.UnknownStorage() public enum OneOf_Expression: Equatable, Sendable { @@ -2199,6 +2275,8 @@ public struct Compiler_Protobuf_Expression: @unchecked Sendable { case v8IntrinsicIdentifier(Compiler_Protobuf_V8IntrinsicIdentifier) case ternaryExpression(Compiler_Protobuf_TernaryExpression) case awaitExpression(Compiler_Protobuf_AwaitExpression) + case superMemberExpression(Compiler_Protobuf_SuperMemberExpression) + case callSuperConstructor(Compiler_Protobuf_CallSuperConstructor) } @@ -6619,6 +6697,157 @@ extension Compiler_Protobuf_AwaitExpression: SwiftProtobuf.Message, SwiftProtobu } } +extension Compiler_Protobuf_SuperMemberExpression: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".SuperMemberExpression" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "name"), + 2: .same(proto: "expression"), + 3: .same(proto: "isOptional"), + ] + + fileprivate class _StorageClass { + var _property: Compiler_Protobuf_SuperMemberExpression.OneOf_Property? + var _isOptional: Bool = false + + #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) { + _property = source._property + _isOptional = source._isOptional + } + } + + 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: String? + try decoder.decodeSingularStringField(value: &v) + if let v = v { + if _storage._property != nil {try decoder.handleConflictingOneOf()} + _storage._property = .name(v) + } + }() + case 2: try { + var v: Compiler_Protobuf_Expression? + var hadOneofValue = false + if let current = _storage._property { + hadOneofValue = true + if case .expression(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + _storage._property = .expression(v) + } + }() + case 3: try { try decoder.decodeSingularBoolField(value: &_storage._isOptional) }() + 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._property { + case .name?: try { + guard case .name(let v)? = _storage._property else { preconditionFailure() } + try visitor.visitSingularStringField(value: v, fieldNumber: 1) + }() + case .expression?: try { + guard case .expression(let v)? = _storage._property else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + }() + case nil: break + } + if _storage._isOptional != false { + try visitor.visitSingularBoolField(value: _storage._isOptional, fieldNumber: 3) + } + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Compiler_Protobuf_SuperMemberExpression, rhs: Compiler_Protobuf_SuperMemberExpression) -> 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._property != rhs_storage._property {return false} + if _storage._isOptional != rhs_storage._isOptional {return false} + return true + } + if !storagesAreEqual {return false} + } + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Compiler_Protobuf_CallSuperConstructor: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".CallSuperConstructor" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "arguments"), + 2: .same(proto: "isOptional"), + ] + + 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.arguments) }() + case 2: try { try decoder.decodeSingularBoolField(value: &self.isOptional) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if !self.arguments.isEmpty { + try visitor.visitRepeatedMessageField(value: self.arguments, fieldNumber: 1) + } + if self.isOptional != false { + try visitor.visitSingularBoolField(value: self.isOptional, fieldNumber: 2) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Compiler_Protobuf_CallSuperConstructor, rhs: Compiler_Protobuf_CallSuperConstructor) -> Bool { + if lhs.arguments != rhs.arguments {return false} + if lhs.isOptional != rhs.isOptional {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + extension Compiler_Protobuf_Expression: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".Expression" public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ @@ -6648,6 +6877,8 @@ extension Compiler_Protobuf_Expression: SwiftProtobuf.Message, SwiftProtobuf._Me 24: .same(proto: "v8IntrinsicIdentifier"), 25: .same(proto: "ternaryExpression"), 26: .same(proto: "awaitExpression"), + 27: .same(proto: "superMemberExpression"), + 28: .same(proto: "callSuperConstructor"), ] fileprivate class _StorageClass { @@ -7023,6 +7254,32 @@ extension Compiler_Protobuf_Expression: SwiftProtobuf.Message, SwiftProtobuf._Me _storage._expression = .awaitExpression(v) } }() + case 27: try { + var v: Compiler_Protobuf_SuperMemberExpression? + var hadOneofValue = false + if let current = _storage._expression { + hadOneofValue = true + if case .superMemberExpression(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + _storage._expression = .superMemberExpression(v) + } + }() + case 28: try { + var v: Compiler_Protobuf_CallSuperConstructor? + var hadOneofValue = false + if let current = _storage._expression { + hadOneofValue = true + if case .callSuperConstructor(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + _storage._expression = .callSuperConstructor(v) + } + }() default: break } } @@ -7140,6 +7397,14 @@ extension Compiler_Protobuf_Expression: SwiftProtobuf.Message, SwiftProtobuf._Me guard case .awaitExpression(let v)? = _storage._expression else { preconditionFailure() } try visitor.visitSingularMessageField(value: v, fieldNumber: 26) }() + case .superMemberExpression?: try { + guard case .superMemberExpression(let v)? = _storage._expression else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 27) + }() + case .callSuperConstructor?: try { + guard case .callSuperConstructor(let v)? = _storage._expression else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 28) + }() case nil: break } } diff --git a/Sources/Fuzzilli/Protobuf/ast.proto b/Sources/Fuzzilli/Protobuf/ast.proto index 5b0be63e6..c34af1e04 100644 --- a/Sources/Fuzzilli/Protobuf/ast.proto +++ b/Sources/Fuzzilli/Protobuf/ast.proto @@ -425,6 +425,20 @@ message AwaitExpression { Expression argument = 1; } +message SuperMemberExpression { + // Since 'super' is not an expression, there's no object field + oneof property { + string name = 1; + Expression expression = 2; + } + bool isOptional = 3; +} + +message CallSuperConstructor { + repeated Expression arguments = 1; + bool isOptional = 2; +} + message Expression { oneof expression { Identifier identifier = 1; @@ -453,5 +467,7 @@ message Expression { V8IntrinsicIdentifier v8IntrinsicIdentifier = 24; TernaryExpression ternaryExpression = 25; AwaitExpression awaitExpression = 26; + SuperMemberExpression superMemberExpression = 27; + CallSuperConstructor callSuperConstructor = 28; } } diff --git a/Tests/FuzzilliTests/CompilerTests/super.js b/Tests/FuzzilliTests/CompilerTests/super.js new file mode 100644 index 000000000..64693ba15 --- /dev/null +++ b/Tests/FuzzilliTests/CompilerTests/super.js @@ -0,0 +1,35 @@ +class Animal { + constructor(name) { + this.name = name; + } + set name(value) { + this._name = value; + } + get name() { + return this._name; + } + speak() { + console.log("Animal speaks!"); // CallSuperMethod + } + } + + class Dog extends Animal { + constructor(name) { + super(name); // CallSuperConstructor + } + m() { + super.name = "SuperDog"; // SetSuperProperty + console.log(super.name); // GetSuperProperty + super["name"] = "Bello"; // SetComputedSuperProperty + console.log(super["name"]); // GetComputedSuperProperty + super.speak(); // CallSuperMethod + } + updateName() { + super.name += " Updated"; // UpdateSuperProperty + } + } + + const myDog = new Dog("Bello"); + myDog.m(); + myDog.updateName(); + console.log(myDog.name); \ No newline at end of file