Skip to content

Commit

Permalink
Implementes compilation of (nested) array- and object pattern parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
TobiasWienand committed Oct 27, 2024
1 parent 6e8fde4 commit 5905832
Show file tree
Hide file tree
Showing 8 changed files with 264 additions and 118 deletions.
83 changes: 41 additions & 42 deletions Sources/Fuzzilli/Compiler/Compiler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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 {
Expand All @@ -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))
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -1074,69 +1074,68 @@ public class JavaScriptCompiler {
}

private func mapParameters(_ parameters: [Compiler_Protobuf_Parameter], to variables: ArraySlice<Variable>) {
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.
Expand Down
61 changes: 41 additions & 20 deletions Sources/Fuzzilli/Compiler/Parser/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
31 changes: 30 additions & 1 deletion Sources/Fuzzilli/FuzzIL/JSTyper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
36 changes: 18 additions & 18 deletions Sources/Fuzzilli/FuzzIL/JsOperations.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Expand Down
52 changes: 25 additions & 27 deletions Sources/Fuzzilli/Lifting/JavaScriptLifter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Loading

0 comments on commit 5905832

Please sign in to comment.