// Copyright 2019 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /// A mutator that mutates the Operations in the given program. public class OperationMutator: BaseInstructionMutator { public init() { super.init(maxSimultaneousMutations: defaultMaxSimultaneousMutations) } public override func canMutate(_ instr: Instruction) -> Bool { // The OperationMutator handles both mutable and variadic operations since both require // modifying the operation and both types of mutations are approximately equally "useful", // so there's no need for a dedicated "VariadicOperationMutator". return instr.isOperationMutable || instr.isVariadic } public override func mutate(_ instr: Instruction, _ b: ProgramBuilder) { b.trace("Mutating next operation") let newInstr: Instruction if instr.isOperationMutable && instr.isVariadic { newInstr = probability(0.5) ? mutateOperation(instr, b) : extendVariadicOperation(instr, b) } else if instr.isOperationMutable { newInstr = mutateOperation(instr, b) } else { assert(instr.isVariadic) newInstr = extendVariadicOperation(instr, b) } b.adopt(newInstr) } private func mutateOperation(_ instr: Instruction, _ b: ProgramBuilder) -> Instruction { let newOp: Operation switch instr.op.opcode { case .loadInteger(_): newOp = LoadInteger(value: b.randomInt()) case .loadBigInt(_): newOp = LoadBigInt(value: b.randomInt()) case .loadFloat(_): newOp = LoadFloat(value: b.randomFloat()) case .loadString(_): newOp = LoadString(value: b.randomString()) case .loadRegExp(let op): newOp = withEqualProbability({ let (pattern, flags) = b.randomRegExpPatternAndFlags() return LoadRegExp(pattern: pattern, flags: flags) }, { return LoadRegExp(pattern: b.randomRegExpPattern(compatibleWithFlags: op.flags), flags: op.flags) }, { return LoadRegExp(pattern: op.pattern, flags: RegExpFlags.random()) }) case .loadBoolean(let op): newOp = LoadBoolean(value: !op.value) case .createTemplateString(let op): var newParts = op.parts replaceRandomElement(in: &newParts, generatingRandomValuesWith: { return b.randomString() }) newOp = CreateTemplateString(parts: newParts) case .loadBuiltin(_): newOp = LoadBuiltin(builtinName: b.randomBuiltin()) case .objectLiteralAddProperty: newOp = ObjectLiteralAddProperty(propertyName: b.randomPropertyName()) case .objectLiteralAddElement: newOp = ObjectLiteralAddElement(index: b.randomIndex()) case .beginObjectLiteralMethod(let op): newOp = BeginObjectLiteralMethod(methodName: b.randomMethodName(), parameters: op.parameters) case .beginObjectLiteralGetter: newOp = BeginObjectLiteralGetter(propertyName: b.randomPropertyName()) case .beginObjectLiteralSetter: newOp = BeginObjectLiteralSetter(propertyName: b.randomPropertyName()) case .classAddInstanceProperty(let op): newOp = ClassAddInstanceProperty(propertyName: b.randomPropertyName(), hasValue: op.hasValue) case .classAddInstanceElement(let op): newOp = ClassAddInstanceElement(index: b.randomIndex(), hasValue: op.hasValue) case .beginClassInstanceMethod(let op): newOp = BeginClassInstanceMethod(methodName: b.randomMethodName(), parameters: op.parameters) case .beginClassInstanceGetter: newOp = BeginClassInstanceGetter(propertyName: b.randomPropertyName()) case .beginClassInstanceSetter: newOp = BeginClassInstanceSetter(propertyName: b.randomPropertyName()) case .classAddStaticProperty(let op): newOp = ClassAddStaticProperty(propertyName: b.randomPropertyName(), hasValue: op.hasValue) case .classAddStaticElement(let op): newOp = ClassAddStaticElement(index: b.randomIndex(), hasValue: op.hasValue) case .beginClassStaticMethod(let op): newOp = BeginClassStaticMethod(methodName: b.randomMethodName(), parameters: op.parameters) case .beginClassStaticGetter: newOp = BeginClassStaticGetter(propertyName: b.randomPropertyName()) case .beginClassStaticSetter: newOp = BeginClassStaticSetter(propertyName: b.randomPropertyName()) case .createIntArray: var values = [Int64]() for _ in 0.. Instruction { var instr = instr let numInputsToAdd = Int.random(in: 1...3) for _ in 0.. Instruction { // Without visible variables, we can't add a new input to this instruction. // This should happen rarely, so just skip this mutation. guard b.hasVisibleVariables else { return instr } let newOp: Operation var inputs = instr.inputs switch instr.op.opcode { case .createArray(let op): newOp = CreateArray(numInitialValues: op.numInitialValues + 1) inputs.append(b.randomVariable()) case .createArrayWithSpread(let op): let spreads = op.spreads + [Bool.random()] inputs.append(b.randomVariable()) newOp = CreateArrayWithSpread(spreads: spreads) case .callFunction(let op): inputs.append(b.randomVariable()) newOp = CallFunction(numArguments: op.numArguments + 1, isGuarded: op.isGuarded) case .callFunctionWithSpread(let op): let spreads = op.spreads + [Bool.random()] inputs.append(b.randomVariable()) newOp = CallFunctionWithSpread(numArguments: op.numArguments + 1, spreads: spreads, isGuarded: op.isGuarded) case .construct(let op): inputs.append(b.randomVariable()) newOp = Construct(numArguments: op.numArguments + 1, isGuarded: op.isGuarded) case .constructWithSpread(let op): let spreads = op.spreads + [Bool.random()] inputs.append(b.randomVariable()) newOp = ConstructWithSpread(numArguments: op.numArguments + 1, spreads: spreads, isGuarded: op.isGuarded) case .callMethod(let op): inputs.append(b.randomVariable()) newOp = CallMethod(methodName: op.methodName, numArguments: op.numArguments + 1, isGuarded: op.isGuarded) case .callMethodWithSpread(let op): let spreads = op.spreads + [Bool.random()] inputs.append(b.randomVariable()) newOp = CallMethodWithSpread(methodName: op.methodName, numArguments: op.numArguments + 1, spreads: spreads, isGuarded: op.isGuarded) case .callComputedMethod(let op): inputs.append(b.randomVariable()) newOp = CallComputedMethod(numArguments: op.numArguments + 1, isGuarded: op.isGuarded) case .callComputedMethodWithSpread(let op): let spreads = op.spreads + [Bool.random()] inputs.append(b.randomVariable()) newOp = CallComputedMethodWithSpread(numArguments: op.numArguments + 1, spreads: spreads, isGuarded: op.isGuarded) case .callSuperConstructor(let op): inputs.append(b.randomVariable()) newOp = CallSuperConstructor(numArguments: op.numArguments + 1) case .callPrivateMethod(let op): inputs.append(b.randomVariable()) newOp = CallPrivateMethod(methodName: op.methodName, numArguments: op.numArguments + 1) case .callSuperMethod(let op): inputs.append(b.randomVariable()) newOp = CallSuperMethod(methodName: op.methodName, numArguments: op.numArguments + 1) case .createTemplateString(let op): var parts = op.parts parts.append(b.randomString()) inputs.append(b.randomVariable()) newOp = CreateTemplateString(parts: parts) default: fatalError("Unhandled Operation: \(type(of: instr.op))") } assert(inputs.count != instr.inputs.count) let inouts = inputs + instr.outputs + instr.innerOutputs return Instruction(newOp, inouts: inouts) } private func replaceRandomElement(in elements: inout Array, generatingRandomValuesWith generator: () -> T) { // Pick a random index to replace. guard let index = elements.indices.randomElement() else { return } // Try to find a replacement value that does not already exist. for _ in 0...5 { let newElem = generator() // Ensure that we neither add an element that already exists nor add one that we just removed if !elements.contains(newElem) { elements[index] = newElem return } } // Failed to find a replacement value, so just leave the array unmodified. } }