diff --git a/Sources/Fuzzilli/Compiler/Compiler.swift b/Sources/Fuzzilli/Compiler/Compiler.swift index b67142a17..736b195c8 100644 --- a/Sources/Fuzzilli/Compiler/Compiler.swift +++ b/Sources/Fuzzilli/Compiler/Compiler.swift @@ -910,6 +910,11 @@ public class JavaScriptCompiler { } emit(functionEnd) + // Map the function name if it exists + if !functionExpression.name.isEmpty { + map(functionExpression.name, to: instr.output) + } + return instr.output case .arrowFunctionExpression(let arrowFunction): @@ -1060,8 +1065,35 @@ public class JavaScriptCompiler { let argument = try compileExpression(unaryExpression.argument) return emit(TypeOf(), withInputs: [argument]).output } else if unaryExpression.operator == "void" { - let argument = try compileExpression(unaryExpression.argument) - return emit(Void_(), withInputs: [argument]).output + if case let .functionExpression(functionExpression) = unaryExpression.argument.expression { + // Check if the function has a name + if !functionExpression.name.isEmpty { + let functionName = functionExpression.name + // Check if the function name is already mapped in the current scope + if let existingVariable = lookupIdentifier(functionName) { + // Emit the void operation using the existing variable + let voidOutput = emit(Void_(), withInputs: [existingVariable]).output + return voidOutput + } else { + // Emit the function and map its name + let funcOutput = try emitFunction(functionExpression, name: functionName) + map(functionName, to: funcOutput) + // Emit the void operation using the emitted function's output + let voidOutput = emit(Void_(), withInputs: [funcOutput]).output + return voidOutput + } + } else { + // Emit the anonymous function + let funcOutput = try emitFunction(functionExpression, name: nil) + // Emit the void operation using the anonymous function's output + let voidOutput = emit(Void_(), withInputs: [funcOutput]).output + return voidOutput + } + } else { + // Fallback for non-function expressions + let argument = try compileExpression(unaryExpression.argument) + return emit(Void_(), withInputs: [argument]).output + } } else if unaryExpression.operator == "delete" { guard case .memberExpression(let memberExpression) = unaryExpression.argument.expression else { throw CompilerError.invalidNodeError("delete operator must be applied to a member expression") @@ -1278,4 +1310,21 @@ public class JavaScriptCompiler { scopes.removeAll() nextVariable = 0 } + + private func emitFunction(_ functionExpression: Compiler_Protobuf_FunctionExpression, name: String?) throws -> Variable { + let parameters = convertParameters(functionExpression.parameters) + let functionBegin = BeginPlainFunction(parameters: parameters, isStrict: false) + let functionEnd = EndPlainFunction() + + let funcInstr = emit(functionBegin) + + try enterNewScope { + mapParameters(functionExpression.parameters, to: funcInstr.innerOutputs) + for statement in functionExpression.body { + try compileStatement(statement) + } + } + emit(functionEnd) + return funcInstr.output + } } diff --git a/Sources/Fuzzilli/Compiler/Parser/parser.js b/Sources/Fuzzilli/Compiler/Parser/parser.js index 237ed9b1b..622b48558 100644 --- a/Sources/Fuzzilli/Compiler/Parser/parser.js +++ b/Sources/Fuzzilli/Compiler/Parser/parser.js @@ -492,7 +492,8 @@ function parse(script, proto) { let parameters = node.params.map(visitParameter); assert(node.body.type === 'BlockStatement', "Expected block statement as function expression body, found " + node.body.type); let body = node.body.body.map(visitStatement); - return makeExpression('FunctionExpression', { type, parameters, body }); + const functionName = node.id ? node.id.name : null; + return makeExpression('FunctionExpression', { name: functionName, type, parameters, body }); } case 'ArrowFunctionExpression': { assert(node.id == null, "Expected node.id to be equal to null"); diff --git a/Sources/Fuzzilli/Protobuf/ast.pb.swift b/Sources/Fuzzilli/Protobuf/ast.pb.swift index a71e2b36d..a84f93cf5 100644 --- a/Sources/Fuzzilli/Protobuf/ast.pb.swift +++ b/Sources/Fuzzilli/Protobuf/ast.pb.swift @@ -1,5 +1,6 @@ // DO NOT EDIT. // swift-format-ignore-file +// swiftlint:disable all // // Generated by the Swift generator plugin for the protocol buffer compiler. // Source: ast.proto @@ -21,7 +22,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -import Foundation import SwiftProtobuf // If the compiler emits an error on this type, it is because this file @@ -1560,6 +1560,8 @@ public struct Compiler_Protobuf_FunctionExpression: Sendable { public var body: [Compiler_Protobuf_Statement] = [] + public var name: String = String() + public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} @@ -5584,6 +5586,7 @@ extension Compiler_Protobuf_FunctionExpression: SwiftProtobuf.Message, SwiftProt 1: .same(proto: "type"), 2: .same(proto: "parameters"), 3: .same(proto: "body"), + 4: .same(proto: "name"), ] public mutating func decodeMessage(decoder: inout D) throws { @@ -5595,6 +5598,7 @@ extension Compiler_Protobuf_FunctionExpression: SwiftProtobuf.Message, SwiftProt case 1: try { try decoder.decodeSingularEnumField(value: &self.type) }() case 2: try { try decoder.decodeRepeatedMessageField(value: &self.parameters) }() case 3: try { try decoder.decodeRepeatedMessageField(value: &self.body) }() + case 4: try { try decoder.decodeSingularStringField(value: &self.name) }() default: break } } @@ -5610,6 +5614,9 @@ extension Compiler_Protobuf_FunctionExpression: SwiftProtobuf.Message, SwiftProt if !self.body.isEmpty { try visitor.visitRepeatedMessageField(value: self.body, fieldNumber: 3) } + if !self.name.isEmpty { + try visitor.visitSingularStringField(value: self.name, fieldNumber: 4) + } try unknownFields.traverse(visitor: &visitor) } @@ -5617,6 +5624,7 @@ extension Compiler_Protobuf_FunctionExpression: SwiftProtobuf.Message, SwiftProt if lhs.type != rhs.type {return false} if lhs.parameters != rhs.parameters {return false} if lhs.body != rhs.body {return false} + if lhs.name != rhs.name {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } diff --git a/Sources/Fuzzilli/Protobuf/ast.proto b/Sources/Fuzzilli/Protobuf/ast.proto index c34af1e04..5c2079bf2 100644 --- a/Sources/Fuzzilli/Protobuf/ast.proto +++ b/Sources/Fuzzilli/Protobuf/ast.proto @@ -344,6 +344,7 @@ message FunctionExpression { FunctionType type = 1; repeated Parameter parameters = 2; repeated Statement body = 3; + string name = 4; } message ArrowFunctionExpression { diff --git a/Tests/FuzzilliTests/CompilerTests/basic_void.js b/Tests/FuzzilliTests/CompilerTests/basic_void.js index 1f0d0cbc7..0996b2824 100644 --- a/Tests/FuzzilliTests/CompilerTests/basic_void.js +++ b/Tests/FuzzilliTests/CompilerTests/basic_void.js @@ -1,4 +1,5 @@ -output(void 1); +const test1 = void 1; +output(test1); // Expected output: undefined void output('expression evaluated'); @@ -7,3 +8,22 @@ void output('expression evaluated'); void (function iife() { output('iife is executed'); })(); + +iife(); + +// The test below will fail since the current FuzzIL support for void will be: +// function f14() { +// console.log("test function executed"); +// } +// void f14; +// then executing f14() will not return undefined + +// void function test2() { +// console.log('test function executed'); +// }; +// try { +// test2(); +// } catch (e) { +// console.log('test function is not defined'); +// // Expected output: "test function is not defined" +// } \ No newline at end of file