| // Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| package com.android.tools.r8.ir.optimize.lambda; |
| |
| import com.android.tools.r8.errors.Unreachable; |
| import com.android.tools.r8.graph.DexEncodedMethod; |
| import com.android.tools.r8.graph.DexField; |
| import com.android.tools.r8.graph.DexItemFactory; |
| import com.android.tools.r8.graph.DexType; |
| import com.android.tools.r8.ir.code.BasicBlock; |
| import com.android.tools.r8.ir.code.CheckCast; |
| import com.android.tools.r8.ir.code.ConstClass; |
| import com.android.tools.r8.ir.code.ConstMethodHandle; |
| import com.android.tools.r8.ir.code.ConstMethodType; |
| import com.android.tools.r8.ir.code.IRCode; |
| import com.android.tools.r8.ir.code.InstanceGet; |
| import com.android.tools.r8.ir.code.InstancePut; |
| import com.android.tools.r8.ir.code.Instruction; |
| import com.android.tools.r8.ir.code.InstructionListIterator; |
| import com.android.tools.r8.ir.code.Invoke; |
| import com.android.tools.r8.ir.code.InvokeMethod; |
| import com.android.tools.r8.ir.code.NewArrayEmpty; |
| import com.android.tools.r8.ir.code.NewInstance; |
| import com.android.tools.r8.ir.code.StaticGet; |
| import com.android.tools.r8.ir.code.StaticPut; |
| import com.android.tools.r8.kotlin.Kotlin; |
| import java.util.ListIterator; |
| import java.util.function.Function; |
| |
| // Performs processing of the method code (all methods) needed by lambda rewriter. |
| // |
| // Functionality can be modified by strategy (specific to lambda group) and lambda |
| // type visitor. |
| // |
| // In general it is used to |
| // (a) track the code for illegal lambda type usages inside the code, and |
| // (b) patching valid lambda type references to point to lambda group classes instead. |
| // |
| // This class is also used inside particular strategies as a context of the instruction |
| // being checked or patched, it provides access to code, block and instruction iterators. |
| public abstract class CodeProcessor { |
| // Strategy (specific to lambda group) for detecting valid references to the |
| // lambda classes (of this group) and patching them with group class references. |
| public interface Strategy { |
| LambdaGroup group(); |
| |
| boolean isValidStaticFieldWrite(CodeProcessor context, DexField field); |
| |
| boolean isValidStaticFieldRead(CodeProcessor context, DexField field); |
| |
| boolean isValidInstanceFieldWrite(CodeProcessor context, DexField field); |
| |
| boolean isValidInstanceFieldRead(CodeProcessor context, DexField field); |
| |
| boolean isValidInvoke(CodeProcessor context, InvokeMethod invoke); |
| |
| boolean isValidNewInstance(CodeProcessor context, NewInstance invoke); |
| |
| void patch(CodeProcessor context, NewInstance newInstance); |
| |
| void patch(CodeProcessor context, InvokeMethod invoke); |
| |
| void patch(CodeProcessor context, InstancePut instancePut); |
| |
| void patch(CodeProcessor context, InstanceGet instanceGet); |
| |
| void patch(CodeProcessor context, StaticPut staticPut); |
| |
| void patch(CodeProcessor context, StaticGet staticGet); |
| } |
| |
| // No-op strategy. |
| static final Strategy NoOp = new Strategy() { |
| @Override |
| public LambdaGroup group() { |
| return null; |
| } |
| |
| @Override |
| public boolean isValidInstanceFieldWrite(CodeProcessor context, DexField field) { |
| return false; |
| } |
| |
| @Override |
| public boolean isValidInstanceFieldRead(CodeProcessor context, DexField field) { |
| return false; |
| } |
| |
| @Override |
| public boolean isValidStaticFieldWrite(CodeProcessor context, DexField field) { |
| return false; |
| } |
| |
| @Override |
| public boolean isValidStaticFieldRead(CodeProcessor context, DexField field) { |
| return false; |
| } |
| |
| @Override |
| public boolean isValidInvoke(CodeProcessor context, InvokeMethod invoke) { |
| return false; |
| } |
| |
| @Override |
| public boolean isValidNewInstance(CodeProcessor context, NewInstance invoke) { |
| return false; |
| } |
| |
| @Override |
| public void patch(CodeProcessor context, NewInstance newInstance) { |
| throw new Unreachable(); |
| } |
| |
| @Override |
| public void patch(CodeProcessor context, InvokeMethod invoke) { |
| throw new Unreachable(); |
| } |
| |
| @Override |
| public void patch(CodeProcessor context, InstancePut instancePut) { |
| throw new Unreachable(); |
| } |
| |
| @Override |
| public void patch(CodeProcessor context, InstanceGet instanceGet) { |
| throw new Unreachable(); |
| } |
| |
| @Override |
| public void patch(CodeProcessor context, StaticPut staticPut) { |
| throw new Unreachable(); |
| } |
| |
| @Override |
| public void patch(CodeProcessor context, StaticGet staticGet) { |
| throw new Unreachable(); |
| } |
| }; |
| |
| public final DexItemFactory factory; |
| public final Kotlin kotlin; |
| |
| // Defines a factory providing a strategy for a lambda type, returns |
| // NoOp strategy if the type is not a lambda. |
| private final Function<DexType, Strategy> strategyProvider; |
| |
| // Visitor for lambda type references seen in unexpected places. Either |
| // invalidates the lambda or asserts depending on the processing phase. |
| private final LambdaTypeVisitor lambdaChecker; |
| |
| // Specify the context of the current instruction: method/code/blocks/instructions. |
| public final DexEncodedMethod method; |
| public final IRCode code; |
| public final ListIterator<BasicBlock> blocks; |
| private InstructionListIterator instructions; |
| |
| CodeProcessor(DexItemFactory factory, |
| Function<DexType, Strategy> strategyProvider, |
| LambdaTypeVisitor lambdaChecker, |
| DexEncodedMethod method, IRCode code) { |
| |
| this.strategyProvider = strategyProvider; |
| this.factory = factory; |
| this.kotlin = factory.kotlin; |
| this.lambdaChecker = lambdaChecker; |
| this.method = method; |
| this.code = code; |
| this.blocks = code.listIterator(); |
| } |
| |
| public final InstructionListIterator instructions() { |
| assert instructions != null; |
| return instructions; |
| } |
| |
| final void processCode() { |
| while (blocks.hasNext()) { |
| BasicBlock block = blocks.next(); |
| instructions = block.listIterator(); |
| while (instructions.hasNext()) { |
| onInstruction(instructions.next()); |
| } |
| } |
| } |
| |
| private void onInstruction(Instruction instruction) { |
| if (instruction.isInvoke()) { |
| handle(instruction.asInvoke()); |
| } else if (instruction.isNewInstance()) { |
| handle(instruction.asNewInstance()); |
| } else if (instruction.isCheckCast()) { |
| handle(instruction.asCheckCast()); |
| } else if (instruction.isNewArrayEmpty()) { |
| handle(instruction.asNewArrayEmpty()); |
| } else if (instruction.isConstClass()) { |
| handle(instruction.asConstClass()); |
| } else if (instruction.isConstMethodType()) { |
| handle(instruction.asConstMethodType()); |
| } else if (instruction.isConstMethodHandle()) { |
| handle(instruction.asConstMethodHandle()); |
| } else if (instruction.isInstanceGet()) { |
| handle(instruction.asInstanceGet()); |
| } else if (instruction.isInstancePut()) { |
| handle(instruction.asInstancePut()); |
| } else if (instruction.isStaticGet()) { |
| handle(instruction.asStaticGet()); |
| } else if (instruction.isStaticPut()) { |
| handle(instruction.asStaticPut()); |
| } |
| } |
| |
| private void handle(Invoke invoke) { |
| if (invoke.isInvokeNewArray()) { |
| lambdaChecker.accept(invoke.asInvokeNewArray().getReturnType()); |
| return; |
| } |
| if (invoke.isInvokeMultiNewArray()) { |
| lambdaChecker.accept(invoke.asInvokeMultiNewArray().getReturnType()); |
| return; |
| } |
| if (invoke.isInvokeCustom()) { |
| lambdaChecker.accept(invoke.asInvokeCustom().getCallSite()); |
| return; |
| } |
| |
| InvokeMethod invokeMethod = invoke.asInvokeMethod(); |
| Strategy strategy = strategyProvider.apply(invokeMethod.getInvokedMethod().holder); |
| if (strategy.isValidInvoke(this, invokeMethod)) { |
| // Invalidate signature, there still should not be lambda references. |
| lambdaChecker.accept(invokeMethod.getInvokedMethod().proto); |
| // Only rewrite references to lambda classes if we are outside the class. |
| if (invokeMethod.getInvokedMethod().holder != this.method.method.holder) { |
| process(strategy, invokeMethod); |
| } |
| return; |
| } |
| |
| // For the rest invalidate any references. |
| if (invoke.isInvokePolymorphic()) { |
| lambdaChecker.accept(invoke.asInvokePolymorphic().getProto()); |
| } |
| lambdaChecker.accept(invokeMethod.getInvokedMethod(), null); |
| } |
| |
| private void handle(NewInstance newInstance) { |
| Strategy strategy = strategyProvider.apply(newInstance.clazz); |
| if (strategy.isValidNewInstance(this, newInstance)) { |
| // Only rewrite references to lambda classes if we are outside the class. |
| if (newInstance.clazz != this.method.method.holder) { |
| process(strategy, newInstance); |
| } |
| } |
| } |
| |
| private void handle(CheckCast checkCast) { |
| lambdaChecker.accept(checkCast.getType()); |
| } |
| |
| private void handle(NewArrayEmpty newArrayEmpty) { |
| lambdaChecker.accept(newArrayEmpty.type); |
| } |
| |
| private void handle(ConstClass constClass) { |
| lambdaChecker.accept(constClass.getValue()); |
| } |
| |
| private void handle(ConstMethodType constMethodType) { |
| lambdaChecker.accept(constMethodType.getValue()); |
| } |
| |
| private void handle(ConstMethodHandle constMethodHandle) { |
| lambdaChecker.accept(constMethodHandle.getValue()); |
| } |
| |
| private void handle(InstanceGet instanceGet) { |
| DexField field = instanceGet.getField(); |
| Strategy strategy = strategyProvider.apply(field.clazz); |
| if (strategy.isValidInstanceFieldRead(this, field)) { |
| if (field.clazz != this.method.method.holder) { |
| // Only rewrite references to lambda classes if we are outside the class. |
| process(strategy, instanceGet); |
| } |
| } else { |
| lambdaChecker.accept(field.type); |
| } |
| |
| // We avoid fields with type being lambda class, it is possible for |
| // a lambda to capture another lambda, but we don't support it for now. |
| lambdaChecker.accept(field.type); |
| } |
| |
| private void handle(InstancePut instancePut) { |
| DexField field = instancePut.getField(); |
| Strategy strategy = strategyProvider.apply(field.clazz); |
| if (strategy.isValidInstanceFieldWrite(this, field)) { |
| if (field.clazz != this.method.method.holder) { |
| // Only rewrite references to lambda classes if we are outside the class. |
| process(strategy, instancePut); |
| } |
| } else { |
| lambdaChecker.accept(field.type); |
| } |
| |
| // We avoid fields with type being lambda class, it is possible for |
| // a lambda to capture another lambda, but we don't support it for now. |
| lambdaChecker.accept(field.type); |
| } |
| |
| private void handle(StaticGet staticGet) { |
| DexField field = staticGet.getField(); |
| Strategy strategy = strategyProvider.apply(field.clazz); |
| if (strategy.isValidStaticFieldRead(this, field)) { |
| if (field.clazz != this.method.method.holder) { |
| // Only rewrite references to lambda classes if we are outside the class. |
| process(strategy, staticGet); |
| } |
| } else { |
| lambdaChecker.accept(field.type); |
| lambdaChecker.accept(field.clazz); |
| } |
| } |
| |
| private void handle(StaticPut staticPut) { |
| DexField field = staticPut.getField(); |
| Strategy strategy = strategyProvider.apply(field.clazz); |
| if (strategy.isValidStaticFieldWrite(this, field)) { |
| if (field.clazz != this.method.method.holder) { |
| // Only rewrite references to lambda classes if we are outside the class. |
| process(strategy, staticPut); |
| } |
| } else { |
| lambdaChecker.accept(field.type); |
| lambdaChecker.accept(field.clazz); |
| } |
| } |
| |
| abstract void process(Strategy strategy, InvokeMethod invokeMethod); |
| |
| abstract void process(Strategy strategy, NewInstance newInstance); |
| |
| abstract void process(Strategy strategy, InstancePut instancePut); |
| |
| abstract void process(Strategy strategy, InstanceGet instanceGet); |
| |
| abstract void process(Strategy strategy, StaticPut staticPut); |
| |
| abstract void process(Strategy strategy, StaticGet staticGet); |
| } |