Skip to content

Commit 53918c4

Browse files
Liedtkev8-internal-scoped@luci-project-accounts.iam.gserviceaccount.com
authored andcommitted
[js] PropertyAccessorMutator: Support methods
in classes and objects for static, async and generator functions. Bug: 534927910 Change-Id: I8da31299c59ee2b0c1c060630cb6d7d1e2da0631 Reviewed-on: https://chrome-internal-review.googlesource.com/c/v8/fuzzilli/+/9631735 Reviewed-by: Marja Hölttä <marja@google.com> Commit-Queue: Matthias Liedtke <mliedtke@google.com>
1 parent 13178c4 commit 53918c4

3 files changed

Lines changed: 364 additions & 23 deletions

File tree

Sources/Fuzzilli/Base/ProgramBuilder.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1672,6 +1672,11 @@ public class ProgramBuilder {
16721672
return varMaps.last![variable]!
16731673
}
16741674

1675+
/// Sets a manual mapping for adoption from an input variable to an output variable.
1676+
public func setAdoptionMap(for variable: Variable, to mappedVariable: Variable) {
1677+
varMaps[varMaps.count - 1][variable] = mappedVariable
1678+
}
1679+
16751680
/// Maps a list of variables from the program that is currently configured for adoption into the program being constructed.
16761681
public func adopt<Variables: Collection>(_ variables: Variables) -> [Variable]
16771682
where Variables.Element == Variable {

Sources/Fuzzilli/Mutators/PropertyAccessorMutator.swift

Lines changed: 210 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -31,39 +31,127 @@
3131
/// /* random code */
3232
/// },
3333
/// });
34-
public class PropertyAccessorMutator: BaseInstructionMutator {
34+
///
35+
/// Note that this changes the semantics slightly in the getter which then always returns the
36+
/// initial value, the setter does not update it. This could be changed, however it will change the
37+
/// observable behavior in some way or another (e.g. by introducing a non-enumerable property to
38+
/// store the underlying value).
39+
///
40+
/// Example for methods:
41+
/// Original:
42+
/// let obj = {
43+
/// myMethod(x) {
44+
/// return x + 42;
45+
/// }
46+
/// };
47+
/// Mutated:
48+
/// let obj = {
49+
/// get myMethod() {
50+
/// /* random code */
51+
/// return function(x) {
52+
/// return x + 42;
53+
/// };
54+
/// },
55+
/// set myMethod(v) {
56+
/// /* random code */
57+
/// }
58+
/// };
59+
60+
public class PropertyAccessorMutator: Mutator {
3561

3662
static let budgetPerAccessor = 3
63+
static let maxSimultaneousMutations = defaultMaxSimultaneousCodeGenerations
3764

3865
private enum PropertyTarget {
3966
case property(String)
4067
case element(Int64)
4168
}
4269

4370
public init() {
44-
// Use the lower generation budget as this mutator also performs arbitrary code generation.
45-
super.init(maxSimultaneousMutations: defaultMaxSimultaneousCodeGenerations)
71+
super.init()
4672
}
4773

48-
public override func canMutate(_ instr: Instruction) -> Bool {
49-
switch instr.op.opcode {
50-
case .setProperty, .setElement, .objectLiteralAddProperty, .objectLiteralAddElement,
51-
.classAddProperty, .classAddElement:
52-
true
53-
default:
54-
false
55-
}
56-
}
57-
58-
// 50% for getter & setter, 25% chance for only one of them each.
74+
/// 50% for getter & setter, 25% chance for only one of them each.
5975
private func chooseDefineGetterSetter() -> (getter: Bool, setter: Bool) {
6076
let defineBoth = probability(0.5)
6177
let defineGetter = defineBoth || probability(0.5)
6278
let defineSetter = defineBoth || !defineGetter
6379
return (defineGetter, defineSetter)
6480
}
6581

66-
public override func mutate(_ instr: Instruction, _ b: ProgramBuilder) {
82+
/// Returns whether the method body is valid for the subroutine context it will be run in.
83+
private func methodBodyCanBeAdoptedInSubroutine(
84+
_ program: Program, from startIndex: Int, to endIndex: Int
85+
) -> Bool {
86+
for i in startIndex..<endIndex {
87+
let required = program.code[i].op.requiredContext
88+
if required.contains(.method) || required.contains(.classMethod) {
89+
return false
90+
}
91+
}
92+
return true
93+
}
94+
95+
public override func mutate(_ program: Program, using b: ProgramBuilder, for fuzzer: Fuzzer)
96+
-> Program?
97+
{
98+
var candidates = [Int]()
99+
for instr in program.code {
100+
switch instr.op.opcode {
101+
case .setProperty, .setElement, .objectLiteralAddProperty, .objectLiteralAddElement,
102+
.classAddProperty, .classAddElement:
103+
candidates.append(instr.index)
104+
case .beginObjectLiteralMethod, .beginClassMethod:
105+
if methodBodyCanBeAdoptedInSubroutine(
106+
program, from: instr.index + 1,
107+
to: program.code.findBlockEnd(head: instr.index))
108+
{
109+
candidates.append(instr.index)
110+
}
111+
default:
112+
break
113+
}
114+
}
115+
116+
guard candidates.count > 0 else {
117+
return nil
118+
}
119+
120+
var toMutate = Set<Int>()
121+
for _ in 0..<Int.random(in: 1...Self.maxSimultaneousMutations) {
122+
toMutate.insert(chooseUniform(from: candidates))
123+
}
124+
125+
var skipUntilIndex = -1
126+
127+
b.adopting {
128+
for instr in program.code {
129+
if instr.index <= skipUntilIndex {
130+
continue
131+
}
132+
133+
if toMutate.contains(instr.index) {
134+
if case .beginObjectLiteralMethod(let op) = instr.op.opcode {
135+
skipUntilIndex = program.code.findBlockEnd(head: instr.index)
136+
mutateObjectLiteralMethod(
137+
op, originalBlockHead: instr, using: b, program: program)
138+
} else if case .beginClassMethod(let op) = instr.op.opcode {
139+
skipUntilIndex = program.code.findBlockEnd(head: instr.index)
140+
mutateClassMethod(
141+
op, originalBlockHead: instr, using: b, program: program)
142+
} else {
143+
mutateProperty(instr, b)
144+
}
145+
} else {
146+
b.adopt(instr)
147+
}
148+
}
149+
}
150+
151+
return b.finalize()
152+
}
153+
154+
private func mutateProperty(_ instr: Instruction, _ b: ProgramBuilder) {
67155
switch instr.op.opcode {
68156
case .objectLiteralAddProperty(let op):
69157
let value = b.adopt(instr.input(0))
@@ -162,4 +250,111 @@ public class PropertyAccessorMutator: BaseInstructionMutator {
162250
b.emit(EndClassSetter())
163251
}
164252
}
253+
254+
/// Creates the inner function for the method replacement that contains the body of the
255+
/// original method without changes.
256+
private func buildInnerFunction(
257+
parameters: Parameters,
258+
isAsync: Bool,
259+
isGenerator: Bool,
260+
originalBlockHead instr: Instruction,
261+
getterThis: Variable,
262+
using b: ProgramBuilder,
263+
program: Program
264+
) -> Variable {
265+
assert(program.code[instr.index].isBlockStart)
266+
b.build(n: Self.budgetPerAccessor, by: .generating)
267+
268+
// Create a function with the same parameters as the original one.
269+
let descriptor = ProgramBuilder.SubroutineDescriptor.parameters(parameters)
270+
let defaultValues = b.adopt(instr.inputs)
271+
272+
let buildBlock: ([Variable]) -> Void = { params in
273+
// Map original method parameters (without `this`) to the new function parameters.
274+
for (newParam, originalParam) in zip(params, instr.innerOutputs.dropFirst()) {
275+
b.setAdoptionMap(for: originalParam, to: newParam)
276+
}
277+
278+
// Adopt original method body instructions (excluding start and end).
279+
let endIndex = program.code.findBlockEnd(head: instr.index)
280+
for i in (instr.index + 1)..<endIndex {
281+
b.adopt(program.code[i])
282+
}
283+
}
284+
285+
return
286+
if isAsync && isGenerator
287+
{
288+
b.buildAsyncGeneratorFunction(
289+
with: descriptor, defaultValues: defaultValues, buildBlock)
290+
} else if isAsync {
291+
b.buildAsyncFunction(with: descriptor, defaultValues: defaultValues, buildBlock)
292+
} else if isGenerator {
293+
b.buildGeneratorFunction(
294+
with: descriptor, defaultValues: defaultValues, buildBlock)
295+
} else {
296+
b.buildPlainFunction(with: descriptor, defaultValues: defaultValues, buildBlock)
297+
}
298+
}
299+
300+
private func mutateObjectLiteralMethod(
301+
_ op: BeginObjectLiteralMethod, originalBlockHead instr: Instruction,
302+
using b: ProgramBuilder, program: Program
303+
) {
304+
// We must always define a getter to preserve the original content.
305+
let getterInstr = b.emit(BeginObjectLiteralGetter(propertyName: op.methodName))
306+
let getterThis = getterInstr.innerOutput(0)
307+
// Map the original method's `this` (first inner output) to the getter's `this`.
308+
b.setAdoptionMap(for: instr.innerOutput(0), to: getterThis)
309+
310+
let innerFunction = buildInnerFunction(
311+
parameters: op.parameters,
312+
isAsync: op.isAsync,
313+
isGenerator: op.isGenerator,
314+
originalBlockHead: instr,
315+
getterThis: getterThis,
316+
using: b,
317+
program: program
318+
)
319+
b.doReturn(innerFunction)
320+
b.emit(EndObjectLiteralGetter())
321+
322+
if probability(0.5) {
323+
b.emit(BeginObjectLiteralSetter(propertyName: op.methodName))
324+
b.build(n: Self.budgetPerAccessor, by: .generating)
325+
b.emit(EndObjectLiteralSetter())
326+
}
327+
}
328+
329+
private func mutateClassMethod(
330+
_ op: BeginClassMethod, originalBlockHead instr: Instruction,
331+
using b: ProgramBuilder, program: Program
332+
) {
333+
// We must always define a getter to preserve the original content.
334+
let getterInstr = b.emit(
335+
BeginClassGetter(propertyName: op.methodName, isStatic: op.isStatic))
336+
let getterThis = getterInstr.innerOutput(0)
337+
// Map the original method's `this` (first inner output) to the getter's `this`.
338+
// Note that the meaning of `this` changes depending on whether it's a static method or
339+
// not, still as the getter has the same "staticness", the semantics is preserved.
340+
b.setAdoptionMap(for: instr.innerOutput(0), to: getterThis)
341+
342+
let innerFunction = buildInnerFunction(
343+
parameters: op.parameters,
344+
isAsync: op.isAsync,
345+
isGenerator: op.isGenerator,
346+
originalBlockHead: instr,
347+
getterThis: getterThis,
348+
using: b,
349+
program: program
350+
)
351+
b.doReturn(innerFunction)
352+
b.emit(EndClassGetter())
353+
354+
if probability(0.5) {
355+
b.emit(BeginClassSetter(propertyName: op.methodName, isStatic: op.isStatic))
356+
b.build(n: Self.budgetPerAccessor, by: .generating)
357+
b.emit(EndClassSetter())
358+
}
359+
}
165360
}

0 commit comments

Comments
 (0)