blob: 2f5144a8a6e38978329e2166928bf90ac9589e3c [file] [log] [blame]
// Copyright (c) 2021, 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.desugar.icce;
import com.android.tools.r8.cf.code.CfConstNull;
import com.android.tools.r8.cf.code.CfConstNumber;
import com.android.tools.r8.cf.code.CfInstruction;
import com.android.tools.r8.cf.code.CfInvoke;
import com.android.tools.r8.cf.code.CfStackInstruction;
import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DexTypeList;
import com.android.tools.r8.graph.MethodResolutionResult;
import com.android.tools.r8.graph.MethodResolutionResult.FailedResolutionResult;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.code.ValueType;
import com.android.tools.r8.ir.desugar.CfInstructionDesugaring;
import com.android.tools.r8.ir.desugar.CfInstructionDesugaringCollection;
import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
import com.android.tools.r8.ir.desugar.DesugarDescription;
import com.android.tools.r8.ir.desugar.DesugarDescription.ScanCallback;
import com.android.tools.r8.ir.desugar.FreshLocalProvider;
import com.android.tools.r8.ir.desugar.LocalStackAllocator;
import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations;
import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations.MethodSynthesizerConsumer;
import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations.UtilityMethodForCodeOptimizations;
import java.util.ArrayList;
import java.util.Collection;
public class AlwaysThrowingInstructionDesugaring implements CfInstructionDesugaring {
private final AppView<? extends AppInfoWithClassHierarchy> appView;
public AlwaysThrowingInstructionDesugaring(AppView<? extends AppInfoWithClassHierarchy> appView) {
this.appView = appView;
}
@Override
public Collection<CfInstruction> desugarInstruction(
CfInstruction instruction,
FreshLocalProvider freshLocalProvider,
LocalStackAllocator localStackAllocator,
CfInstructionDesugaringEventConsumer eventConsumer,
ProgramMethod context,
MethodProcessingContext methodProcessingContext,
CfInstructionDesugaringCollection desugaringCollection,
DexItemFactory dexItemFactory) {
return computeDesugarDescription(instruction)
.desugarInstruction(
freshLocalProvider,
localStackAllocator,
eventConsumer,
context,
methodProcessingContext,
dexItemFactory);
}
@Override
public boolean needsDesugaring(CfInstruction instruction, ProgramMethod context) {
return computeDesugarDescription(instruction).needsDesugaring();
}
private DesugarDescription computeDesugarDescription(CfInstruction instruction) {
if (instruction.isInvoke()) {
CfInvoke invoke = instruction.asInvoke();
DexMethod invokedMethod = invoke.getMethod();
MethodResolutionResult resolutionResult =
appView.appInfo().resolveMethod(invokedMethod, invoke.isInterface());
if (shouldRewriteInvokeToThrow(invoke, resolutionResult)) {
return computeInvokeAsThrowRewrite(appView, invoke, resolutionResult);
}
}
return DesugarDescription.nothing();
}
private boolean shouldRewriteInvokeToThrow(
CfInvoke invoke, MethodResolutionResult resolutionResult) {
if (resolutionResult.isArrayCloneMethodResult()) {
return false;
}
if (resolutionResult.isFailedResolution()) {
// For now don't materialize NSMEs from failed resolutions.
return resolutionResult.asFailedResolution().hasMethodsCausingError();
}
assert resolutionResult.isSingleResolution();
return resolutionResult.getResolvedMethod().isStatic() != invoke.isInvokeStatic();
}
public static DesugarDescription computeInvokeAsThrowRewrite(
AppView<?> appView, CfInvoke invoke, MethodResolutionResult resolutionResult) {
return DesugarDescription.builder()
.setDesugarRewrite(
(freshLocalProvider,
localStackAllocator,
eventConsumer,
context,
methodProcessingContext,
dexItemFactory) ->
getThrowInstructions(
appView,
invoke,
localStackAllocator,
eventConsumer,
context,
methodProcessingContext,
getMethodSynthesizerForThrowing(appView, invoke, resolutionResult, context)))
.build();
}
public static DesugarDescription computeInvokeAsThrowNSMERewrite(
AppView<?> appView, CfInvoke invoke, ScanCallback scanCallback) {
DesugarDescription.Builder builder =
DesugarDescription.builder()
.setDesugarRewrite(
(freshLocalProvider,
localStackAllocator,
eventConsumer,
context,
methodProcessingContext,
dexItemFactory) ->
getThrowInstructions(
appView,
invoke,
localStackAllocator,
eventConsumer,
context,
methodProcessingContext,
UtilityMethodsForCodeOptimizations
::synthesizeThrowNoSuchMethodErrorMethod));
builder.addScanEffect(scanCallback);
return builder.build();
}
private static Collection<CfInstruction> getThrowInstructions(
AppView<?> appView,
CfInvoke invoke,
LocalStackAllocator localStackAllocator,
CfInstructionDesugaringEventConsumer eventConsumer,
ProgramMethod context,
MethodProcessingContext methodProcessingContext,
MethodSynthesizerConsumer methodSynthesizerConsumer) {
if (methodSynthesizerConsumer == null) {
assert false;
return null;
}
// Replace the entire effect of the invoke by by call to the throwing helper:
// ...
// invoke <method> [receiver] args*
// =>
// ...
// (pop arg)*
// [pop receiver]
// invoke <throwing-method>
// pop exception result
// [push fake result for <method>]
UtilityMethodForCodeOptimizations throwMethod =
methodSynthesizerConsumer.synthesizeMethod(appView, methodProcessingContext);
ProgramMethod throwProgramMethod = throwMethod.uncheckedGetMethod();
eventConsumer.acceptThrowMethod(throwProgramMethod, context);
ArrayList<CfInstruction> replacement = new ArrayList<>();
DexTypeList parameters = invoke.getMethod().getParameters();
for (int i = parameters.values.length - 1; i >= 0; i--) {
replacement.add(
new CfStackInstruction(
parameters.get(i).isWideType()
? CfStackInstruction.Opcode.Pop2
: CfStackInstruction.Opcode.Pop));
}
if (!invoke.isInvokeStatic()) {
replacement.add(new CfStackInstruction(CfStackInstruction.Opcode.Pop));
}
CfInvoke throwInvoke =
new CfInvoke(
org.objectweb.asm.Opcodes.INVOKESTATIC, throwProgramMethod.getReference(), false);
assert throwInvoke.getMethod().getReturnType().isClassType();
replacement.add(throwInvoke);
replacement.add(new CfStackInstruction(CfStackInstruction.Opcode.Pop));
DexType returnType = invoke.getMethod().getReturnType();
if (!returnType.isVoidType()) {
replacement.add(
returnType.isPrimitiveType()
? new CfConstNumber(0, ValueType.fromDexType(returnType))
: new CfConstNull());
} else {
// If the return type is void, the stack may need an extra slot to fit the return type of
// the call to the throwing method.
localStackAllocator.allocateLocalStack(1);
}
return replacement;
}
private static MethodSynthesizerConsumer getMethodSynthesizerForThrowing(
AppView<?> appView,
CfInvoke invoke,
MethodResolutionResult resolutionResult,
ProgramMethod context) {
if (resolutionResult == null) {
return UtilityMethodsForCodeOptimizations::synthesizeThrowNoSuchMethodErrorMethod;
} else if (resolutionResult.isSingleResolution()) {
if (resolutionResult.getResolvedMethod().isStatic() != invoke.isInvokeStatic()) {
return UtilityMethodsForCodeOptimizations
::synthesizeThrowIncompatibleClassChangeErrorMethod;
}
} else if (resolutionResult.isFailedResolution()) {
FailedResolutionResult failedResolutionResult = resolutionResult.asFailedResolution();
AppInfoWithClassHierarchy appInfo = appView.appInfoForDesugaring();
if (failedResolutionResult.isIllegalAccessErrorResult(context.getHolder(), appInfo)) {
return UtilityMethodsForCodeOptimizations::synthesizeThrowIllegalAccessErrorMethod;
} else if (failedResolutionResult.isNoSuchMethodErrorResult(context.getHolder(), appInfo)) {
return UtilityMethodsForCodeOptimizations::synthesizeThrowNoSuchMethodErrorMethod;
} else if (failedResolutionResult.isIncompatibleClassChangeErrorResult()) {
return UtilityMethodsForCodeOptimizations
::synthesizeThrowIncompatibleClassChangeErrorMethod;
}
}
return null;
}
}