Skip to content

Commit 76a2a56

Browse files
rherouart-collabv8-internal-scoped@luci-project-accounts.iam.gserviceaccount.com
authored andcommitted
[Compiler] Add support for parameter destructuring
This CL adds support for compiling JavaScript function parameter destructuring (objects and arrays) into FuzzIL, and lifting it back to JS. Computed Keys and Inner Default Values ain't supported. Bug: 515363087 Change-Id: I6157c3e634c91bcc6bedefeaf7b0861be1fe64b2 Reviewed-on: https://chrome-internal-review.googlesource.com/c/v8/fuzzilli/+/9464375 Reviewed-by: Michael Achenbach <machenbach@google.com> Commit-Queue: Raphaël Hérouart <rherouart@google.com>
1 parent 0cfcfc9 commit 76a2a56

16 files changed

Lines changed: 1059 additions & 363 deletions

File tree

Sources/Fuzzilli/Base/ProgramBuilder.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3498,6 +3498,10 @@ public class ProgramBuilder {
34983498
defaultParameterIndices: defaultParameterIndices))
34993499
}
35003500

3501+
public static func parameters(_ parameters: Parameters) -> SubroutineDescriptor {
3502+
return SubroutineDescriptor(withParameters: parameters)
3503+
}
3504+
35013505
/// Returns a copy of this SubroutineDescriptor but with some parameters turned into default parameters.
35023506
public func withRandomDefaultParameters(
35033507
probability chance: Double, randomVariable: () -> Variable

Sources/Fuzzilli/Compiler/Compiler.swift

Lines changed: 179 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -158,14 +158,14 @@ public class JavaScriptCompiler {
158158

159159
case .ctor(let constructor):
160160
let defaultValues = defaultValuesPerSubroutine.removeLast()
161-
let parameters = convertParameters(constructor.parameters)
161+
let parameters = try convertParameters(constructor.parameters)
162162
let head = emit(
163163
BeginClassConstructor(parameters: parameters), withInputs: defaultValues)
164164

165165
try enterNewScope {
166166
var parameters = head.innerOutputs
167167
map("this", to: parameters.removeFirst())
168-
mapParameters(constructor.parameters, to: parameters)
168+
try mapParameters(constructor.parameters, to: parameters)
169169
for statement in constructor.body {
170170
try compileStatement(statement)
171171
}
@@ -175,7 +175,7 @@ public class JavaScriptCompiler {
175175

176176
case .method(let method):
177177
let defaultValues = defaultValuesPerSubroutine.removeLast()
178-
let parameters = convertParameters(method.parameters)
178+
let parameters = try convertParameters(method.parameters)
179179
let head: Instruction
180180

181181
guard let key = method.key.body else {
@@ -202,7 +202,7 @@ public class JavaScriptCompiler {
202202
try enterNewScope {
203203
var parameters = head.innerOutputs
204204
map("this", to: parameters.removeFirst())
205-
mapParameters(method.parameters, to: parameters)
205+
try mapParameters(method.parameters, to: parameters)
206206
for statement in method.body {
207207
try compileStatement(statement)
208208
}
@@ -442,7 +442,7 @@ public class JavaScriptCompiler {
442442

443443
case .functionDeclaration(let functionDeclaration):
444444
let defaultValues = try compileDefaultValues(for: functionDeclaration.parameters)
445-
let parameters = convertParameters(functionDeclaration.parameters)
445+
let parameters = try convertParameters(functionDeclaration.parameters)
446446
let functionBegin: Operation
447447
let functionEnd: Operation
448448
switch functionDeclaration.type {
@@ -471,7 +471,7 @@ public class JavaScriptCompiler {
471471
// here we may overwrite an existing variable mapping.
472472
mapOrRemap(functionDeclaration.name, to: instr.output)
473473
try enterNewScope {
474-
mapParameters(functionDeclaration.parameters, to: instr.innerOutputs)
474+
try mapParameters(functionDeclaration.parameters, to: instr.innerOutputs)
475475
for statement in functionDeclaration.body {
476476
try compileStatement(statement)
477477
}
@@ -1230,7 +1230,7 @@ public class JavaScriptCompiler {
12301230
}
12311231
case .method(let method):
12321232
let defaultValues = methodDefaultValues.removeLast()
1233-
let parameters = convertParameters(method.parameters)
1233+
let parameters = try convertParameters(method.parameters)
12341234
let head: Instruction
12351235

12361236
guard let key = method.key.body else {
@@ -1256,7 +1256,7 @@ public class JavaScriptCompiler {
12561256
try enterNewScope {
12571257
var parameters = head.innerOutputs
12581258
map("this", to: parameters.removeFirst())
1259-
mapParameters(method.parameters, to: parameters)
1259+
try mapParameters(method.parameters, to: parameters)
12601260
for statement in method.body {
12611261
try compileStatement(statement)
12621262
}
@@ -1361,7 +1361,7 @@ public class JavaScriptCompiler {
13611361

13621362
case .functionExpression(let functionExpression):
13631363
let defaultValues = try compileDefaultValues(for: functionExpression.parameters)
1364-
let parameters = convertParameters(functionExpression.parameters)
1364+
let parameters = try convertParameters(functionExpression.parameters)
13651365
let functionBegin: Operation
13661366
let functionEnd: Operation
13671367
let name = functionExpression.name.isEmpty ? nil : functionExpression.name
@@ -1385,7 +1385,7 @@ public class JavaScriptCompiler {
13851385

13861386
let instr = emit(functionBegin, withInputs: defaultValues)
13871387
try enterNewScope {
1388-
mapParameters(functionExpression.parameters, to: instr.innerOutputs)
1388+
try mapParameters(functionExpression.parameters, to: instr.innerOutputs)
13891389
for statement in functionExpression.body {
13901390
try compileStatement(statement)
13911391
}
@@ -1396,7 +1396,7 @@ public class JavaScriptCompiler {
13961396

13971397
case .arrowFunctionExpression(let arrowFunction):
13981398
let defaultValues = try compileDefaultValues(for: arrowFunction.parameters)
1399-
let parameters = convertParameters(arrowFunction.parameters)
1399+
let parameters = try convertParameters(arrowFunction.parameters)
14001400
let functionBegin: Operation
14011401
let functionEnd: Operation
14021402
switch arrowFunction.type {
@@ -1413,7 +1413,7 @@ public class JavaScriptCompiler {
14131413

14141414
let instr = emit(functionBegin, withInputs: defaultValues)
14151415
try enterNewScope {
1416-
mapParameters(arrowFunction.parameters, to: instr.innerOutputs)
1416+
try mapParameters(arrowFunction.parameters, to: instr.innerOutputs)
14171417
guard let body = arrowFunction.body else {
14181418
throw CompilerError.invalidNodeError("missing body in arrow function")
14191419
}
@@ -1785,10 +1785,66 @@ public class JavaScriptCompiler {
17851785

17861786
private func mapParameters(
17871787
_ parameters: Compiler_Protobuf_Parameters, to variables: ArraySlice<Variable>
1788-
) {
1789-
assert(parameters.parameters.count == variables.count)
1790-
for (param, v) in zip(parameters.parameters, variables) {
1791-
map(param.name, to: v)
1788+
) throws {
1789+
var iter = variables.makeIterator()
1790+
for param in parameters.parameters {
1791+
switch param.id {
1792+
case .objectPattern(let obj):
1793+
// e.g. function foo({a, b}) {}
1794+
try mapDestructuringPattern(obj, iterator: &iter)
1795+
case .arrayPattern(let arr):
1796+
// e.g. function bar([x, y]) {}
1797+
try mapDestructuringPattern(arr, iterator: &iter)
1798+
case .name(let name):
1799+
map(name, to: iter.next()!)
1800+
case nil:
1801+
break
1802+
}
1803+
}
1804+
}
1805+
1806+
private func mapDestructuringTarget<Iter: IteratorProtocol>(
1807+
_ target: Compiler_Protobuf_LValue, iterator: inout Iter
1808+
) throws where Iter.Element == Variable {
1809+
switch target.value {
1810+
case .destructuringPattern(let dp):
1811+
switch dp.pattern {
1812+
case .objectPattern(let obj):
1813+
try mapDestructuringPattern(obj, iterator: &iterator)
1814+
case .arrayPattern(let arr):
1815+
try mapDestructuringPattern(arr, iterator: &iterator)
1816+
case nil:
1817+
throw CompilerError.invalidASTError(
1818+
"Invalid destructuring assignment target in parameter")
1819+
}
1820+
case .identifier(let id):
1821+
map(id.name, to: iterator.next()!)
1822+
default:
1823+
throw CompilerError.invalidASTError(
1824+
"Invalid destructuring assignment target in parameter")
1825+
}
1826+
}
1827+
1828+
private func mapDestructuringPattern<Iter: IteratorProtocol>(
1829+
_ pattern: Compiler_Protobuf_ObjectPattern, iterator: inout Iter
1830+
) throws where Iter.Element == Variable {
1831+
for prop in pattern.properties {
1832+
try mapDestructuringTarget(prop.target, iterator: &iterator)
1833+
}
1834+
if pattern.hasRestTarget {
1835+
try mapDestructuringTarget(pattern.restTarget, iterator: &iterator)
1836+
}
1837+
}
1838+
1839+
private func mapDestructuringPattern<Iter: IteratorProtocol>(
1840+
_ pattern: Compiler_Protobuf_ArrayPattern, iterator: inout Iter
1841+
) throws where Iter.Element == Variable {
1842+
for elem in pattern.elements {
1843+
guard elem.hasTarget else { continue }
1844+
try mapDestructuringTarget(elem.target, iterator: &iterator)
1845+
}
1846+
if pattern.hasRestTarget {
1847+
try mapDestructuringTarget(pattern.restTarget, iterator: &iterator)
17921848
}
17931849
}
17941850

@@ -1804,14 +1860,116 @@ public class JavaScriptCompiler {
18041860
return defaultValues
18051861
}
18061862

1807-
private func convertParameters(_ parameters: Compiler_Protobuf_Parameters) -> Parameters {
1863+
private func convertParameterDestructuringPattern(
1864+
_ pattern: Compiler_Protobuf_DestructuringPattern
1865+
) throws -> DestructuringPattern {
1866+
switch pattern.pattern {
1867+
case .objectPattern(let objProto):
1868+
var properties = [DestructuringPattern.ObjectProperty]()
1869+
for prop in objProto.properties {
1870+
let key: DestructuringPattern.ObjectProperty.Key
1871+
if case .name(let s) = prop.key.body {
1872+
key = .string(s)
1873+
} else if case .index(let i) = prop.key.body {
1874+
// e.g. function foo({1: a}) {}
1875+
key = .string(String(i))
1876+
} else {
1877+
throw CompilerError.invalidASTError(
1878+
"Computed keys not supported in parameter destructuring")
1879+
}
1880+
1881+
let target: DestructuringPattern.Target
1882+
switch prop.target.value {
1883+
case .destructuringPattern(let dp):
1884+
target = .pattern(try convertParameterDestructuringPattern(dp))
1885+
case .identifier:
1886+
target = .flatBinding
1887+
default:
1888+
throw CompilerError.invalidASTError(
1889+
"Invalid destructuring assignment target in parameter")
1890+
}
1891+
1892+
assert(
1893+
!prop.hasDefaultValue,
1894+
"Default values in parameter destructuring are not yet supported")
1895+
properties.append(
1896+
DestructuringPattern.ObjectProperty(
1897+
key: key, target: target, hasDefaultValue: false))
1898+
}
1899+
// FuzzIL operations only need to know if a rest target exists, not its string name.
1900+
// The string name mapping (e.g. "myRestParams" -> v3) is handled separately by `mapDestructuringPattern`.
1901+
return .object(
1902+
DestructuringPattern.ObjectPattern(
1903+
properties: properties, hasRestElement: objProto.hasRestTarget))
1904+
1905+
case .arrayPattern(let arrProto):
1906+
var elements = [DestructuringPattern.ArrayElement]()
1907+
for elem in arrProto.elements {
1908+
guard elem.hasTarget else {
1909+
elements.append(
1910+
DestructuringPattern.ArrayElement(target: nil, hasDefaultValue: false))
1911+
continue
1912+
}
1913+
let target: DestructuringPattern.Target
1914+
switch elem.target.value {
1915+
case .destructuringPattern(let dp):
1916+
target = .pattern(try convertParameterDestructuringPattern(dp))
1917+
case .identifier:
1918+
target = .flatBinding
1919+
default:
1920+
throw CompilerError.invalidASTError(
1921+
"Invalid destructuring assignment target in parameter")
1922+
}
1923+
assert(
1924+
!elem.hasDefaultValue,
1925+
"Default values in parameter destructuring are not yet supported")
1926+
elements.append(
1927+
DestructuringPattern.ArrayElement(
1928+
target: target, hasDefaultValue: false))
1929+
}
1930+
let restTarget: DestructuringPattern.Target?
1931+
if arrProto.hasRestTarget {
1932+
switch arrProto.restTarget.value {
1933+
case .destructuringPattern(let dp):
1934+
restTarget = .pattern(try convertParameterDestructuringPattern(dp))
1935+
default:
1936+
restTarget = .flatBinding
1937+
}
1938+
} else {
1939+
restTarget = nil
1940+
}
1941+
return .array(
1942+
DestructuringPattern.ArrayPattern(elements: elements, restTarget: restTarget))
1943+
1944+
case nil:
1945+
throw CompilerError.invalidASTError("Missing pattern")
1946+
}
1947+
}
1948+
1949+
private func convertParameters(_ parameters: Compiler_Protobuf_Parameters) throws -> Parameters
1950+
{
18081951
let defaultParameterIndices = parameters.parameters.enumerated()
18091952
.filter { $0.element.hasDefaultValue }
18101953
.map { $0.offset }
18111954

1955+
var destructuringParameters = [Int: DestructuringPattern]()
1956+
for (i, param) in parameters.parameters.enumerated() {
1957+
switch param.id {
1958+
case .objectPattern(let obj):
1959+
let dp = Compiler_Protobuf_DestructuringPattern.with { $0.objectPattern = obj }
1960+
destructuringParameters[i] = try convertParameterDestructuringPattern(dp)
1961+
case .arrayPattern(let arr):
1962+
let dp = Compiler_Protobuf_DestructuringPattern.with { $0.arrayPattern = arr }
1963+
destructuringParameters[i] = try convertParameterDestructuringPattern(dp)
1964+
default:
1965+
break
1966+
}
1967+
}
1968+
18121969
return Parameters(
18131970
count: parameters.parameters.count, hasRestParameter: parameters.hasRestElement_p,
1814-
defaultParameterIndices: defaultParameterIndices)
1971+
defaultParameterIndices: defaultParameterIndices,
1972+
destructuringParameters: destructuringParameters)
18151973
}
18161974

18171975
/// Convenience accessor for the currently active scope.
@@ -1978,6 +2136,9 @@ public class JavaScriptCompiler {
19782136

19792137
var hasDefaultValue = false
19802138
if propProto.hasDefaultValue {
2139+
// TODO(rherouart): we should not emit FuzzIL Code unless default is called, otherwise
2140+
// `let {a = default_with_side_effect()} = foo;`
2141+
// may behave differently in original and re-lifted code otherwise.
19812142
let defaultVar = try compileExpression(propProto.defaultValue)
19822143
hasDefaultValue = true
19832144
inputs.append(defaultVar)

0 commit comments

Comments
 (0)