Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature/additional parameters #498

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions Sources/Fuzzilli/Base/ProgramBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2346,6 +2346,10 @@ public class ProgramBuilder {
let parameters = Parameters(count: parameterTypes.count, hasRestParameter: parameterTypes.hasRestParameter)
return SubroutineDescriptor(withParameters: parameters, ofTypes: parameterTypes)
}

public static func parameters(_ parameters: Parameters) -> SubroutineDescriptor {
return SubroutineDescriptor(withParameters: parameters)
}

private init(withParameters parameters: Parameters, ofTypes parameterTypes: ParameterList? = nil) {
if let types = parameterTypes {
Expand All @@ -2372,6 +2376,17 @@ public class ProgramBuilder {
return instr.output
}

@discardableResult
public func buildUnusualFunction(with parameters: Parameters, named functionName: String? = nil, _ body: ([Variable]) -> ()) -> Variable {
// Emit the BeginPlainFunction with the provided Parameters (which carry the detailed patterns).
let instr = emit(BeginPlainFunction(parameters: parameters, functionName: functionName))
if enableRecursionGuard { hide(instr.output) }
body(Array(instr.innerOutputs))
if enableRecursionGuard { unhide(instr.output) }
emit(EndPlainFunction())
return instr.output
}

@discardableResult
public func buildArrowFunction(with descriptor: SubroutineDescriptor, _ body: ([Variable]) -> ()) -> Variable {
setParameterTypesForNextSubroutine(descriptor.parameterTypes)
Expand Down
112 changes: 107 additions & 5 deletions Sources/Fuzzilli/Compiler/Compiler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,10 @@ public class JavaScriptCompiler {
try enterNewScope {
let beginCatch = emit(BeginCatch())
if tryStatement.catch.hasParameter {
map(tryStatement.catch.parameter.name, to: beginCatch.innerOutput)
guard case let .identifierParameter(identifier) = tryStatement.catch.parameter.pattern else {
throw CompilerError.unsupportedFeatureError("Only identifier parameters are supported in catch blocks")
}
map(identifier.name, to: beginCatch.innerOutput)
}
for statement in tryStatement.catch.body {
try compileStatement(statement)
Expand Down Expand Up @@ -1186,14 +1189,113 @@ public class JavaScriptCompiler {
}

private func mapParameters(_ parameters: [Compiler_Protobuf_Parameter], to variables: ArraySlice<Variable>) {
assert(parameters.count == variables.count)
for (param, v) in zip(parameters, variables) {
map(param.name, to: v)
// This function maps all identifiers in the function's signature to variables.
// These variables are the ones that will be used in the function's body.
// Example: Consider f([a, b]). This function has 1 parameter but 2 identifiers. We map a and b, not the parameter.
var flatParameterIdentifiers: [String] = []
func extractIdentifiers(from param: Compiler_Protobuf_Parameter) {
switch param.pattern {
case .identifierParameter(let identifier):
flatParameterIdentifiers.append(identifier.name)
case .objectParameter(let object):
for property in object.parameters {
extractIdentifiers(from: property.parameterValue)
}
case .arrayParameter(let array):
for element in array.elements {
extractIdentifiers(from: element)
}
case .restParameter(let rest):
extractIdentifiers(from: rest.argument)
case .none:
fatalError("Unexpected parameter type: .none in mapParameters")
}
}
for param in parameters {
extractIdentifiers(from: param)
}
assert(flatParameterIdentifiers.count == variables.count, "The number of variables (\(variables.count)) does not match the number of parameters (\(flatParameterIdentifiers.count)).")
for (name, v) in zip(flatParameterIdentifiers, variables) {
map(name, to: v)
}
}

private func convertParameters(_ parameters: [Compiler_Protobuf_Parameter]) -> Parameters {
return Parameters(count: parameters.count)
// This function creates a Parameters instance from the parameters of a function.
// 'Parameters' is mainly used for lifting and type inference.
// Only if the function has a complex parameter pattern, we store the patterns of the parameters.
var totalParameterCount = 0
var patterns = [ParameterPattern]()

func processParameter(_ param: Compiler_Protobuf_Parameter) -> ParameterPattern {
switch param.pattern {
case .identifierParameter:
totalParameterCount += 1
return .identifier
case .objectParameter(let object):
var properties = [(String, ParameterPattern)]()
for property in object.parameters {
let key = property.parameterKey
let valuePattern = processParameter(property.parameterValue)
properties.append((key, valuePattern))
}
return .object(properties: properties)
case .arrayParameter(let array):
var elements = [ParameterPattern]()
for element in array.elements {
let elementPattern = processParameter(element)
elements.append(elementPattern)
}
return .array(elements: elements)
case .restParameter(let rest):
return .rest(processParameter(rest.argument))
default:
fatalError("Unexpected parameter type in convertParameters")
}
}

for param in parameters {
let pattern = processParameter(param)
patterns.append(pattern)
}

// Ensure that only the last parameter is a rest parameter.
for (index, pattern) in patterns.enumerated() {
if case .rest(_) = pattern, index != patterns.count - 1 {
fatalError("Only the last parameter can be a rest parameter")
}
}

var hasRestParameter = false
if patterns.count > 0 {
let lastPattern = patterns[patterns.count - 1]
switch lastPattern {
case .rest(_):
hasRestParameter = true
default:
hasRestParameter = false
}
}

// Determine if any parameter is complex (i.e. not a plain identifier)
var hasComplexPattern = false
for pattern in patterns {
switch pattern {
case .identifier:
continue
case .rest:
continue
default:
hasComplexPattern = true
break
}
}
// Save memory by only storing the patterns if there is at least one complex pattern.
if hasComplexPattern {
return Parameters(count: totalParameterCount, patterns: patterns, hasRestParameter: hasRestParameter)
} else {
return Parameters(count: totalParameterCount, hasRestParameter: hasRestParameter)
}
}

/// Convenience accessor for the currently active scope.
Expand Down
48 changes: 46 additions & 2 deletions Sources/Fuzzilli/Compiler/Parser/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,52 @@ function parse(script, proto) {
}

function visitParameter(param) {
assert(param.type == 'Identifier', "Expected parameter type to have type 'Identifier', found " + param.type);
return make('Parameter', { name: param.name });
assert(['Identifier', 'ObjectPattern', 'ArrayPattern', 'RestElement'].includes(param.type));

switch (param.type) {
case 'Identifier': {
return make('IdentifierParameter', { identifierParameter: { name: param.name } });
}
case 'ObjectPattern': {
const parameters = param.properties.map(property => {
assert(property.type === 'ObjectProperty');
assert(property.computed === false);
assert(property.method === false);
let parameterKey;
if (property.key.type === 'Identifier') {
parameterKey = property.key.name;
} else if (property.key.type === 'Literal') {
// Internally, literal keys are stored as strings.
parameterKey = property.key.value.toString();
} else {
throw new Error('Unsupported property key type: ' + property.key.type);
}
const parameterValue = visitParameter(property.value);
return make('ObjectParameterProperty', {
parameterKey: parameterKey,
parameterValue: parameterValue
});
});
return make('ObjectParameter', { objectParameter: { parameters } });
}
case 'ArrayPattern': {
const elements = param.elements.map(element => {
if (element === null) {
throw new Error('Holes in array parameters are not supported');
} else {
return visitParameter(element);
}
});
return make('ArrayParameter', { arrayParameter: { elements } });
}
case 'RestElement': {
const argument = visitParameter(param.argument);
return make('RestParameter', { restParameter: { argument } });
}
default: {
throw new Error('Unsupported parameter type: ' + param.type);
}
}
}

function visitParameters(params) {
Expand Down
55 changes: 54 additions & 1 deletion Sources/Fuzzilli/FuzzIL/Instruction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,36 @@ extension Instruction: ProtobufConvertible {
return Fuzzilli_Protobuf_Parameters.with {
$0.count = UInt32(parameters.count)
$0.hasRest_p = parameters.hasRestParameter
// If we have an explicit patterns array, convert it.
if let patterns = parameters.patterns {
$0.patterns = patterns.map { convertParameterPattern($0) }
}
}
}

func convertParameterPattern(_ pattern: ParameterPattern) -> Fuzzilli_Protobuf_Parameter {
return Fuzzilli_Protobuf_Parameter.with {
switch pattern {
case .identifier:
$0.identifierParameter = Fuzzilli_Protobuf_IdentifierParameter()
case .object(let properties):
$0.objectParameter = Fuzzilli_Protobuf_ObjectParameter.with {
$0.parameters = properties.map { (key, valuePattern) in
return Fuzzilli_Protobuf_ObjectParameterProperty.with {
$0.parameterKey = key
$0.parameterValue = convertParameterPattern(valuePattern)
}
}
}
case .array(let elements):
$0.arrayParameter = Fuzzilli_Protobuf_ArrayParameter.with {
$0.elements = elements.map { convertParameterPattern($0) }
}
case .rest(let inner):
$0.restParameter = Fuzzilli_Protobuf_RestParameter.with {
$0.argument = convertParameterPattern(inner)
}
}
}
}

Expand Down Expand Up @@ -1362,7 +1392,30 @@ extension Instruction: ProtobufConvertible {
}

func convertParameters(_ parameters: Fuzzilli_Protobuf_Parameters) -> Parameters {
return Parameters(count: Int(parameters.count), hasRestParameter: parameters.hasRest_p)
if !parameters.patterns.isEmpty {
let patterns = parameters.patterns.map { convertParameterPattern($0) }
return Parameters(count: Int(parameters.count), patterns: patterns, hasRestParameter: parameters.hasRest_p)
} else {
return Parameters(count: Int(parameters.count), hasRestParameter: parameters.hasRest_p)
}
}
func convertParameterPattern(_ proto: Fuzzilli_Protobuf_Parameter) -> ParameterPattern {
switch proto.pattern {
case .identifierParameter:
return .identifier
case .objectParameter(let object):
let properties = object.parameters.map { property in
return (property.parameterKey, convertParameterPattern(property.parameterValue))
}
return .object(properties: properties)
case .arrayParameter(let array):
let elements = array.elements.map { convertParameterPattern($0) }
return .array(elements: elements)
case .restParameter(let rest):
return .rest(convertParameterPattern(rest.argument))
default:
fatalError("Invalid parameter pattern")
}
}

// Converts to the Wasm world global type
Expand Down
Loading
Loading