|
31 | 31 | /// /* random code */ |
32 | 32 | /// }, |
33 | 33 | /// }); |
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 { |
35 | 61 |
|
36 | 62 | static let budgetPerAccessor = 3 |
| 63 | + static let maxSimultaneousMutations = defaultMaxSimultaneousCodeGenerations |
37 | 64 |
|
38 | 65 | private enum PropertyTarget { |
39 | 66 | case property(String) |
40 | 67 | case element(Int64) |
41 | 68 | } |
42 | 69 |
|
43 | 70 | public init() { |
44 | | - // Use the lower generation budget as this mutator also performs arbitrary code generation. |
45 | | - super.init(maxSimultaneousMutations: defaultMaxSimultaneousCodeGenerations) |
| 71 | + super.init() |
46 | 72 | } |
47 | 73 |
|
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. |
59 | 75 | private func chooseDefineGetterSetter() -> (getter: Bool, setter: Bool) { |
60 | 76 | let defineBoth = probability(0.5) |
61 | 77 | let defineGetter = defineBoth || probability(0.5) |
62 | 78 | let defineSetter = defineBoth || !defineGetter |
63 | 79 | return (defineGetter, defineSetter) |
64 | 80 | } |
65 | 81 |
|
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) { |
67 | 155 | switch instr.op.opcode { |
68 | 156 | case .objectLiteralAddProperty(let op): |
69 | 157 | let value = b.adopt(instr.input(0)) |
@@ -162,4 +250,111 @@ public class PropertyAccessorMutator: BaseInstructionMutator { |
162 | 250 | b.emit(EndClassSetter()) |
163 | 251 | } |
164 | 252 | } |
| 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 | + } |
165 | 360 | } |
0 commit comments