Move frame verification logic to CfFrameVerifier
Change-Id: Id6e11b815982c63c9ccaebb661982c959fb6c9a2
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFrameVerificationHelper.java b/src/main/java/com/android/tools/r8/cf/code/CfFrameVerificationHelper.java
deleted file mode 100644
index a4131e9..0000000
--- a/src/main/java/com/android/tools/r8/cf/code/CfFrameVerificationHelper.java
+++ /dev/null
@@ -1,226 +0,0 @@
-// Copyright (c) 2020, 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.cf.code;
-
-import com.android.tools.r8.cf.code.CfAssignability.AssignabilityResult;
-import com.android.tools.r8.cf.code.frame.FrameType;
-import com.android.tools.r8.cf.code.frame.PreciseFrameType;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
-import com.android.tools.r8.graph.CfCodeDiagnostics;
-import com.android.tools.r8.graph.CfCodeStackMapValidatingException;
-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.GraphLens;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
-import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
-import com.android.tools.r8.optimize.interfaces.analysis.ConcreteCfFrameState;
-import com.android.tools.r8.utils.TraversalContinuation;
-import com.android.tools.r8.utils.collections.ImmutableDeque;
-import com.google.common.collect.Sets;
-import java.util.ArrayDeque;
-import java.util.Deque;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-public class CfFrameVerificationHelper implements CfAnalysisConfig {
-
- private final AppView<?> appView;
- private final CfAssignability assignability;
- private final CfCode code;
- private final GraphLens codeLens;
- private final DexItemFactory factory;
- private final ProgramMethod method;
- private final DexMethod previousMethod;
-
- private final Map<CfLabel, CfFrame> stateMap;
- private final List<CfTryCatch> tryCatchRanges;
-
- private final Deque<CfTryCatch> currentCatchRanges = new ArrayDeque<>();
- private final Set<CfLabel> tryCatchRangeLabels;
-
- public CfFrameVerificationHelper(
- AppView<?> appView,
- CfCode code,
- GraphLens codeLens,
- ProgramMethod method,
- Map<CfLabel, CfFrame> stateMap,
- List<CfTryCatch> tryCatchRanges) {
- this.appView = appView;
- this.assignability = new CfAssignability(appView);
- this.code = code;
- this.codeLens = codeLens;
- this.method = method;
- this.previousMethod =
- appView.graphLens().getOriginalMethodSignature(method.getReference(), codeLens);
- this.stateMap = stateMap;
- this.tryCatchRanges = tryCatchRanges;
- this.factory = appView.dexItemFactory();
- // Compute all labels that marks a start or end to catch ranges.
- tryCatchRangeLabels = Sets.newIdentityHashSet();
- for (CfTryCatch tryCatchRange : tryCatchRanges) {
- tryCatchRangeLabels.add(tryCatchRange.start);
- tryCatchRangeLabels.add(tryCatchRange.end);
- }
- }
-
- @Override
- public CfAssignability getAssignability() {
- return assignability;
- }
-
- @Override
- public DexMethod getCurrentContext() {
- return previousMethod;
- }
-
- @Override
- public int getMaxLocals() {
- return code.getMaxLocals();
- }
-
- @Override
- public int getMaxStack() {
- return code.getMaxStack();
- }
-
- @Override
- public boolean isImmediateSuperClassOfCurrentContext(DexType type) {
- // If the code is rewritten according to the graph lens, we perform a strict check that the
- // given type is the same as the current holder's super class.
- if (codeLens == appView.graphLens()) {
- return type == method.getHolder().getSuperType();
- }
- // Otherwise, we don't know what the super class of the current class was at the point of the
- // code lens. We return true, which has the consequence that we may accept a constructor call
- // for an uninitialized-this value where the constructor is not defined in the immediate parent
- // class.
- return true;
- }
-
- @Override
- public boolean isStrengthenFramesEnabled() {
- return false;
- }
-
- public void seenLabel(CfLabel label) {
- if (tryCatchRangeLabels.contains(label)) {
- for (CfTryCatch tryCatchRange : tryCatchRanges) {
- if (tryCatchRange.start == label) {
- currentCatchRanges.add(tryCatchRange);
- }
- }
- currentCatchRanges.removeIf(currentRange -> currentRange.end == label);
- }
- }
-
- public CfCodeDiagnostics checkTryCatchRanges() {
- for (CfTryCatch tryCatchRange : tryCatchRanges) {
- CfCodeDiagnostics diagnostics = checkTryCatchRange(tryCatchRange);
- if (diagnostics != null) {
- return diagnostics;
- }
- }
- return null;
- }
-
- public CfCodeDiagnostics checkTryCatchRange(CfTryCatch tryCatchRange) {
- // According to the spec:
- // https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.10.1
- // saying ` and the handler's target (the initial instruction of the handler code) is type
- // safe assuming an incoming type state T. The type state T is derived from ExcStackFrame
- // by replacing the operand stack with a stack whose sole element is the handler's
- // exception class.
- for (CfLabel target : tryCatchRange.getTargets()) {
- CfFrame destinationFrame = stateMap.get(target);
- if (destinationFrame == null) {
- return CfCodeStackMapValidatingException.invalidTryCatchRange(
- method, tryCatchRange, "No frame for target catch range target", appView);
- }
- // From the spec: the handler's exception class is assignable to the class Throwable.
- for (DexType guard : tryCatchRange.guards) {
- if (!assignability.isAssignable(guard, factory.throwableType)) {
- return CfCodeStackMapValidatingException.invalidTryCatchRange(
- method,
- tryCatchRange,
- "Could not assign " + guard.getTypeName() + " to java.lang.Throwable",
- appView);
- }
- Deque<PreciseFrameType> sourceStack =
- ImmutableDeque.of(FrameType.initializedNonNullReference(guard));
- AssignabilityResult assignabilityResult =
- assignability.isStackAssignable(sourceStack, destinationFrame.getStack());
- if (assignabilityResult.isFailed()) {
- return CfCodeStackMapValidatingException.invalidTryCatchRange(
- method, tryCatchRange, assignabilityResult.asFailed().getMessage(), appView);
- }
- }
- }
- return null;
- }
-
- public CfFrameState checkExceptionEdges(CfFrameState state) {
- for (CfTryCatch currentCatchRange : currentCatchRanges) {
- for (CfLabel target : currentCatchRange.getTargets()) {
- CfFrame destinationFrame = stateMap.get(target);
- if (destinationFrame == null) {
- return CfFrameState.error("No frame for target catch range target");
- }
- state = state.checkLocals(this, destinationFrame);
- }
- }
- return state;
- }
-
- public CfFrameState checkTarget(CfFrameState state, CfLabel label) {
- CfFrame destinationFrame = getDestinationFrame(label);
- return destinationFrame != null
- ? state.checkLocals(this, destinationFrame).checkStack(this, destinationFrame)
- : CfFrameState.error("No destination frame");
- }
-
- private CfFrame getDestinationFrame(CfLabel label) {
- return stateMap.get(label);
- }
-
- public TraversalContinuation<CfCodeDiagnostics, CfFrameState> computeStateForNextInstruction(
- CfInstruction instruction, int instructionIndex, CfFrameState state) {
- if (!instruction.isJump()) {
- return TraversalContinuation.doContinue(state);
- }
- if (instructionIndex == code.getInstructions().size() - 1) {
- return TraversalContinuation.doContinue(CfFrameState.bottom());
- }
- if (instructionIndex == code.getInstructions().size() - 2
- && code.getInstructions().get(instructionIndex + 1).isLabel()) {
- return TraversalContinuation.doContinue(CfFrameState.bottom());
- }
- if (instruction.asJump().hasFallthrough()) {
- return TraversalContinuation.doContinue(state);
- }
- int nextInstructionIndex = instructionIndex + 1;
- CfInstruction nextInstruction = code.getInstructions().get(nextInstructionIndex);
- CfFrame nextFrame = null;
- if (nextInstruction.isFrame()) {
- nextFrame = nextInstruction.asFrame();
- } else if (nextInstruction.isLabel()) {
- nextFrame = getDestinationFrame(nextInstruction.asLabel());
- }
- if (nextFrame != null) {
- CfFrame currentFrameCopy = nextFrame.mutableCopy();
- return TraversalContinuation.doContinue(
- new ConcreteCfFrameState(
- currentFrameCopy.getMutableLocals(),
- currentFrameCopy.getMutableStack(),
- currentFrameCopy.computeStackSize()));
- }
- return TraversalContinuation.doBreak(
- CfCodeStackMapValidatingException.invalidStackMapForInstruction(
- method, nextInstructionIndex, nextInstruction, "Expected frame instruction", appView));
- }
-}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFrameVerifier.java b/src/main/java/com/android/tools/r8/cf/code/CfFrameVerifier.java
new file mode 100644
index 0000000..db01f47
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFrameVerifier.java
@@ -0,0 +1,446 @@
+// Copyright (c) 2020, 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.cf.code;
+
+import com.android.tools.r8.cf.CfVersion;
+import com.android.tools.r8.cf.code.CfAssignability.AssignabilityResult;
+import com.android.tools.r8.cf.code.frame.FrameType;
+import com.android.tools.r8.cf.code.frame.PreciseFrameType;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.CfCodeDiagnostics;
+import com.android.tools.r8.graph.CfCodeStackMapValidatingException;
+import com.android.tools.r8.graph.DexEncodedMethod;
+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.GraphLens;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
+import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
+import com.android.tools.r8.optimize.interfaces.analysis.ConcreteCfFrameState;
+import com.android.tools.r8.utils.TraversalContinuation;
+import com.android.tools.r8.utils.collections.ImmutableDeque;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+public class CfFrameVerifier {
+
+ private final AppView<?> appView;
+ private final CfCode code;
+ private final CfAnalysisConfig config;
+ private final CfFrameVerifierEventConsumer eventConsumer;
+ private final DexItemFactory factory;
+ private final ProgramMethod method;
+ private final Optional<DexMethod> previousMethod;
+ private final boolean previousMethodIsInstance;
+
+ private final Deque<CfTryCatch> activeCatchHandlers = new ArrayDeque<>();
+ private final Set<CfLabel> tryCatchRangeLabels;
+
+ public CfFrameVerifier(
+ AppView<?> appView,
+ CfCode code,
+ CfAnalysisConfig config,
+ CfFrameVerifierEventConsumer eventConsumer,
+ ProgramMethod method,
+ Optional<DexMethod> previousMethod,
+ boolean previousMethodIsInstance) {
+ this.appView = appView;
+ this.code = code;
+ this.config = config;
+ this.eventConsumer = eventConsumer;
+ this.factory = appView.dexItemFactory();
+ this.method = method;
+ this.previousMethod = previousMethod;
+ this.previousMethodIsInstance = previousMethodIsInstance;
+ this.tryCatchRangeLabels = code.getTryCatchRangeLabels();
+ }
+
+ public static Builder builder(AppView<?> appView, CfCode code, ProgramMethod method) {
+ return new Builder(appView, code, method);
+ }
+
+ public StackMapStatus run() {
+ if (!appView.options().canUseInputStackMaps()
+ || appView.options().testing.disableStackMapVerification) {
+ return StackMapStatus.NOT_PRESENT;
+ }
+
+ DexEncodedMethod definition = method.getDefinition();
+ if (definition.hasClassFileVersion()
+ && definition.getClassFileVersion().isLessThan(CfVersion.V1_7)) {
+ return StackMapStatus.NOT_PRESENT;
+ }
+
+ // Build a map from labels to frames.
+ TraversalContinuation<CfCodeDiagnostics, Map<CfLabel, CfFrame>> labelToFrameMapOrError =
+ buildLabelToFrameMap();
+ if (labelToFrameMapOrError.shouldBreak()) {
+ return fail(labelToFrameMapOrError);
+ }
+ Map<CfLabel, CfFrame> labelToFrameMap = labelToFrameMapOrError.asContinue().getValue();
+
+ // Check try catch ranges.
+ CfCodeDiagnostics diagnostics = checkTryCatchRanges(labelToFrameMap);
+ if (diagnostics != null) {
+ return fail(diagnostics);
+ }
+
+ // Compute initial state.
+ TraversalContinuation<CfCodeDiagnostics, CfFrameState> initialState = computeInitialState();
+ if (initialState.shouldBreak()) {
+ return fail(initialState);
+ }
+
+ // Linear scan over instructions.
+ CfFrameState state = initialState.asContinue().getValue();
+ for (int i = 0; i < code.getInstructions().size(); i++) {
+ CfInstruction instruction = code.getInstruction(i);
+ assert !state.isError();
+ // Check the exceptional edge prior to evaluating the instruction. The local state is stable
+ // at this point as store operations are not throwing and the current stack does not
+ // affect the exceptional transfer (the exception edge is always a singleton stack).
+ if (instruction.canThrow()) {
+ assert !instruction.isStore();
+ state = checkExceptionEdges(state, labelToFrameMap);
+ }
+ if (instruction.isLabel()) {
+ updateActiveCatchHandlers(instruction.asLabel());
+ }
+ state = instruction.evaluate(state, appView, config);
+ if (instruction.isJumpWithNormalTarget()) {
+ CfInstruction fallthroughInstruction =
+ (i + 1) < code.getInstructions().size() ? code.getInstruction(i + 1) : null;
+ TraversalContinuation<CfCodeDiagnostics, CfFrameState> traversalContinuation =
+ instruction.traverseNormalTargets(
+ (target, currentState) -> {
+ if (target != fallthroughInstruction) {
+ assert target.isLabel();
+ currentState = checkTarget(currentState, target.asLabel(), labelToFrameMap);
+ }
+ return TraversalContinuation.doContinue(currentState);
+ },
+ fallthroughInstruction,
+ state);
+ state = traversalContinuation.asContinue().getValue();
+ }
+ TraversalContinuation<CfCodeDiagnostics, CfFrameState> traversalContinuation =
+ computeStateForNextInstruction(instruction, i, state, labelToFrameMap);
+ if (traversalContinuation.isContinue()) {
+ state = traversalContinuation.asContinue().getValue();
+ } else {
+ return fail(traversalContinuation);
+ }
+ if (state.isError()) {
+ return fail(
+ CfCodeStackMapValidatingException.invalidStackMapForInstruction(
+ method, i, instruction, state.asError().getMessage(), appView));
+ }
+ }
+ return StackMapStatus.VALID;
+ }
+
+ private TraversalContinuation<CfCodeDiagnostics, Map<CfLabel, CfFrame>> buildLabelToFrameMap() {
+ Map<CfLabel, CfFrame> labelToFrameMap = new IdentityHashMap<>();
+ List<CfLabel> labels = new ArrayList<>();
+ boolean requireStackMapFrame = !code.getTryCatchRanges().isEmpty();
+ for (CfInstruction instruction : code.getInstructions()) {
+ if (instruction.isFrame()) {
+ CfFrame frame = instruction.asFrame();
+ if (!labels.isEmpty()) {
+ for (CfLabel label : labels) {
+ if (labelToFrameMap.containsKey(label)) {
+ return TraversalContinuation.doBreak(
+ CfCodeStackMapValidatingException.multipleFramesForLabel(method, appView));
+ }
+ labelToFrameMap.put(label, frame);
+ }
+ } else if (instruction != code.getInstruction(0)) {
+ // From b/168212806, it is possible that the first instruction is a frame.
+ return TraversalContinuation.doBreak(
+ CfCodeStackMapValidatingException.unexpectedStackMapFrame(method, appView));
+ }
+ }
+ // We are trying to map a frame to a label, but we can have positions in between, so skip
+ // those.
+ if (instruction.isPosition()) {
+ continue;
+ } else if (instruction.isLabel()) {
+ labels.add(instruction.asLabel());
+ } else {
+ labels.clear();
+ }
+ if (!requireStackMapFrame) {
+ requireStackMapFrame = instruction.isJump() && !isFinalAndExitInstruction(instruction);
+ }
+ }
+ // If there are no frames but we have seen a jump instruction, we cannot verify the stack map.
+ if (requireStackMapFrame && labelToFrameMap.isEmpty()) {
+ return TraversalContinuation.doBreak(
+ CfCodeStackMapValidatingException.noFramesForMethodWithJumps(method, appView));
+ }
+ return TraversalContinuation.doContinue(labelToFrameMap);
+ }
+
+ private StackMapStatus fail(TraversalContinuation<CfCodeDiagnostics, ?> traversalContinuation) {
+ assert traversalContinuation.shouldBreak();
+ return fail(traversalContinuation.asBreak().getValue());
+ }
+
+ private StackMapStatus fail(CfCodeDiagnostics diagnostics) {
+ eventConsumer.acceptError(diagnostics);
+ return StackMapStatus.INVALID;
+ }
+
+ private void updateActiveCatchHandlers(CfLabel label) {
+ if (tryCatchRangeLabels.contains(label)) {
+ for (CfTryCatch tryCatchRange : code.getTryCatchRanges()) {
+ if (tryCatchRange.start == label) {
+ activeCatchHandlers.add(tryCatchRange);
+ }
+ }
+ activeCatchHandlers.removeIf(currentRange -> currentRange.end == label);
+ }
+ }
+
+ private CfCodeDiagnostics checkTryCatchRanges(Map<CfLabel, CfFrame> labelToFrameMap) {
+ for (CfTryCatch tryCatchRange : code.getTryCatchRanges()) {
+ CfCodeDiagnostics diagnostics = checkTryCatchRange(tryCatchRange, labelToFrameMap);
+ if (diagnostics != null) {
+ return diagnostics;
+ }
+ }
+ return null;
+ }
+
+ private CfCodeDiagnostics checkTryCatchRange(
+ CfTryCatch tryCatchRange, Map<CfLabel, CfFrame> labelToFrameMap) {
+ // According to the spec:
+ // https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.10.1
+ // saying ` and the handler's target (the initial instruction of the handler code) is type
+ // safe assuming an incoming type state T. The type state T is derived from ExcStackFrame
+ // by replacing the operand stack with a stack whose sole element is the handler's
+ // exception class.
+ for (CfLabel target : tryCatchRange.getTargets()) {
+ CfFrame destinationFrame = labelToFrameMap.get(target);
+ if (destinationFrame == null) {
+ return CfCodeStackMapValidatingException.invalidTryCatchRange(
+ method, tryCatchRange, "No frame for target catch range target", appView);
+ }
+ // From the spec: the handler's exception class is assignable to the class Throwable.
+ for (DexType guard : tryCatchRange.guards) {
+ if (!config.getAssignability().isAssignable(guard, factory.throwableType)) {
+ return CfCodeStackMapValidatingException.invalidTryCatchRange(
+ method,
+ tryCatchRange,
+ "Could not assign " + guard.getTypeName() + " to java.lang.Throwable",
+ appView);
+ }
+ Deque<PreciseFrameType> sourceStack =
+ ImmutableDeque.of(FrameType.initializedNonNullReference(guard));
+ AssignabilityResult assignabilityResult =
+ config.getAssignability().isStackAssignable(sourceStack, destinationFrame.getStack());
+ if (assignabilityResult.isFailed()) {
+ return CfCodeStackMapValidatingException.invalidTryCatchRange(
+ method, tryCatchRange, assignabilityResult.asFailed().getMessage(), appView);
+ }
+ }
+ }
+ return null;
+ }
+
+ private CfFrameState checkExceptionEdges(
+ CfFrameState state, Map<CfLabel, CfFrame> labelToFrameMap) {
+ for (CfTryCatch currentCatchRange : activeCatchHandlers) {
+ for (CfLabel target : currentCatchRange.getTargets()) {
+ CfFrame destinationFrame = labelToFrameMap.get(target);
+ if (destinationFrame == null) {
+ return CfFrameState.error("No frame for target catch range target");
+ }
+ state = state.checkLocals(config, destinationFrame);
+ }
+ }
+ return state;
+ }
+
+ private CfFrameState checkTarget(
+ CfFrameState state, CfLabel label, Map<CfLabel, CfFrame> labelToFrameMap) {
+ CfFrame destinationFrame = labelToFrameMap.get(label);
+ return destinationFrame != null
+ ? state.checkLocals(config, destinationFrame).checkStack(config, destinationFrame)
+ : CfFrameState.error("No destination frame");
+ }
+
+ private TraversalContinuation<CfCodeDiagnostics, CfFrameState> computeInitialState() {
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
+ CfFrameState state = new ConcreteCfFrameState();
+ int localIndex = 0;
+ DexMethod context = previousMethod.orElse(method.getReference());
+ if (method.getDefinition().isInstance() || previousMethodIsInstance) {
+ state =
+ state.storeLocal(
+ localIndex,
+ context.isInstanceInitializer(dexItemFactory)
+ || context.mustBeInlinedIntoInstanceInitializer(appView)
+ || context.isHorizontallyMergedInstanceInitializer(dexItemFactory)
+ ? FrameType.uninitializedThis()
+ : FrameType.initializedNonNullReference(context.getHolderType()),
+ config);
+ localIndex++;
+ }
+ for (DexType parameter : context.getParameters()) {
+ state = state.storeLocal(localIndex, FrameType.initialized(parameter), config);
+ localIndex += parameter.getRequiredRegisters();
+ }
+ if (state.isError()) {
+ return TraversalContinuation.doBreak(
+ CfCodeStackMapValidatingException.invalidStackMapForInstruction(
+ method, 0, code.getInstruction(0), state.asError().getMessage(), appView));
+ }
+ return TraversalContinuation.doContinue(state);
+ }
+
+ private TraversalContinuation<CfCodeDiagnostics, CfFrameState> computeStateForNextInstruction(
+ CfInstruction instruction,
+ int instructionIndex,
+ CfFrameState state,
+ Map<CfLabel, CfFrame> labelToFrameMap) {
+ if (!instruction.isJump()) {
+ return TraversalContinuation.doContinue(state);
+ }
+ if (instructionIndex == code.getInstructions().size() - 1) {
+ return TraversalContinuation.doContinue(CfFrameState.bottom());
+ }
+ if (instructionIndex == code.getInstructions().size() - 2
+ && code.getInstruction(instructionIndex + 1).isLabel()) {
+ return TraversalContinuation.doContinue(CfFrameState.bottom());
+ }
+ if (instruction.asJump().hasFallthrough()) {
+ return TraversalContinuation.doContinue(state);
+ }
+ int nextInstructionIndex = instructionIndex + 1;
+ CfInstruction nextInstruction = code.getInstruction(nextInstructionIndex);
+ CfFrame nextFrame = null;
+ if (nextInstruction.isFrame()) {
+ nextFrame = nextInstruction.asFrame();
+ } else if (nextInstruction.isLabel()) {
+ nextFrame = labelToFrameMap.get(nextInstruction.asLabel());
+ }
+ if (nextFrame != null) {
+ CfFrame currentFrameCopy = nextFrame.mutableCopy();
+ return TraversalContinuation.doContinue(
+ new ConcreteCfFrameState(
+ currentFrameCopy.getMutableLocals(),
+ currentFrameCopy.getMutableStack(),
+ currentFrameCopy.computeStackSize()));
+ }
+ return TraversalContinuation.doBreak(
+ CfCodeStackMapValidatingException.invalidStackMapForInstruction(
+ method, nextInstructionIndex, nextInstruction, "Expected frame instruction", appView));
+ }
+
+ private boolean isFinalAndExitInstruction(CfInstruction instruction) {
+ boolean isReturnOrThrow = instruction.isThrow() || instruction.isReturn();
+ if (!isReturnOrThrow) {
+ return false;
+ }
+ for (int i = code.getInstructions().size() - 1; i >= 0; i--) {
+ CfInstruction instr = code.getInstruction(i);
+ if (instr == instruction) {
+ return true;
+ }
+ if (instr.isPosition() || instr.isLabel()) {
+ continue;
+ }
+ return false;
+ }
+ throw new Unreachable("Instruction " + instruction + " should be in instructions");
+ }
+
+ public enum StackMapStatus {
+ NOT_VERIFIED,
+ NOT_PRESENT,
+ INVALID,
+ VALID;
+
+ public boolean isValidOrNotPresent() {
+ return this == VALID || this == NOT_PRESENT;
+ }
+
+ public boolean isInvalidOrNotPresent() {
+ return this == INVALID || this == NOT_PRESENT;
+ }
+ }
+
+ public static class Builder {
+
+ private final AppView<?> appView;
+ private final CfCode code;
+ private final ProgramMethod method;
+
+ private CfAnalysisConfig config;
+ private CfFrameVerifierEventConsumer eventConsumer;
+ private Optional<DexMethod> previousMethod = Optional.empty();
+ private boolean previousMethodIsInstance;
+
+ Builder(AppView<?> appView, CfCode code, ProgramMethod method) {
+ this.appView = appView;
+ this.code = code;
+ this.method = method;
+ }
+
+ public Builder setCodeLens(GraphLens codeLens) {
+ if (codeLens != appView.graphLens()) {
+ this.previousMethod =
+ Optional.of(
+ appView.graphLens().getOriginalMethodSignature(method.getReference(), codeLens));
+ this.previousMethodIsInstance =
+ method.getDefinition().isInstance()
+ || appView
+ .graphLens()
+ .lookupPrototypeChangesForMethodDefinition(method.getReference(), codeLens)
+ .getArgumentInfoCollection()
+ .isConvertedToStaticMethod();
+ }
+ return this;
+ }
+
+ public Builder setConfig(CfAnalysisConfig config) {
+ this.config = config;
+ return this;
+ }
+
+ public Builder setEventConsumer(CfFrameVerifierEventConsumer eventConsumer) {
+ this.eventConsumer = eventConsumer;
+ return this;
+ }
+
+ public CfFrameVerifier build() {
+ assert eventConsumer != null;
+ return new CfFrameVerifier(
+ appView,
+ code,
+ buildConfig(),
+ eventConsumer,
+ method,
+ previousMethod,
+ previousMethodIsInstance);
+ }
+
+ private CfAnalysisConfig buildConfig() {
+ return config != null
+ ? config
+ : new CfFrameVerifierDefaultAnalysisConfig(appView, code, method, previousMethod);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFrameVerifierDefaultAnalysisConfig.java b/src/main/java/com/android/tools/r8/cf/code/CfFrameVerifierDefaultAnalysisConfig.java
new file mode 100644
index 0000000..82ea7ef
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFrameVerifierDefaultAnalysisConfig.java
@@ -0,0 +1,68 @@
+// Copyright (c) 2022, 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.cf.code;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
+import java.util.Optional;
+
+public class CfFrameVerifierDefaultAnalysisConfig implements CfAnalysisConfig {
+
+ private final CfAssignability assignability;
+ private final CfCode code;
+ private final ProgramMethod method;
+ private final Optional<DexMethod> previousMethod;
+
+ CfFrameVerifierDefaultAnalysisConfig(
+ AppView<?> appView, CfCode code, ProgramMethod method, Optional<DexMethod> previousMethod) {
+ this.assignability = new CfAssignability(appView);
+ this.code = code;
+ this.method = method;
+ this.previousMethod = previousMethod;
+ }
+
+ @Override
+ public CfAssignability getAssignability() {
+ return assignability;
+ }
+
+ @Override
+ public DexMethod getCurrentContext() {
+ return previousMethod.orElse(method.getReference());
+ }
+
+ @Override
+ public int getMaxLocals() {
+ return code.getMaxLocals();
+ }
+
+ @Override
+ public int getMaxStack() {
+ return code.getMaxStack();
+ }
+
+ @Override
+ public boolean isImmediateSuperClassOfCurrentContext(DexType type) {
+ // If the code is rewritten according to the graph lens, we perform a strict check that the
+ // given type is the same as the current holder's super class.
+ if (!previousMethod.isPresent()) {
+ return type == method.getHolder().getSuperType();
+ }
+ // Otherwise, we don't know what the super class of the current class was at the point of the
+ // code lens. We return true, which has the consequence that we may accept a constructor call
+ // for an uninitialized-this value where the constructor is not defined in the immediate parent
+ // class.
+ return true;
+ }
+
+ @Override
+ public boolean isStrengthenFramesEnabled() {
+ return false;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFrameVerifierEventConsumer.java b/src/main/java/com/android/tools/r8/cf/code/CfFrameVerifierEventConsumer.java
new file mode 100644
index 0000000..a149a5f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFrameVerifierEventConsumer.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2022, 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.cf.code;
+
+import com.android.tools.r8.graph.CfCodeDiagnostics;
+
+public interface CfFrameVerifierEventConsumer {
+
+ void acceptError(CfCodeDiagnostics diagnostics);
+}
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index ec420af..0d4f9c9 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -9,7 +9,9 @@
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.cf.CfVersion;
import com.android.tools.r8.cf.code.CfFrame;
-import com.android.tools.r8.cf.code.CfFrameVerificationHelper;
+import com.android.tools.r8.cf.code.CfFrameVerifier;
+import com.android.tools.r8.cf.code.CfFrameVerifier.StackMapStatus;
+import com.android.tools.r8.cf.code.CfFrameVerifierEventConsumer;
import com.android.tools.r8.cf.code.CfIinc;
import com.android.tools.r8.cf.code.CfInstruction;
import com.android.tools.r8.cf.code.CfLabel;
@@ -17,7 +19,6 @@
import com.android.tools.r8.cf.code.CfPosition;
import com.android.tools.r8.cf.code.CfReturnVoid;
import com.android.tools.r8.cf.code.CfTryCatch;
-import com.android.tools.r8.cf.code.frame.FrameType;
import com.android.tools.r8.dex.code.CfOrDexInstruction;
import com.android.tools.r8.dex.code.DexBase5Format;
import com.android.tools.r8.errors.InvalidDebugInfoException;
@@ -39,47 +40,29 @@
import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.naming.NamingLens;
-import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
-import com.android.tools.r8.optimize.interfaces.analysis.ConcreteCfFrameState;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.TraversalContinuation;
import com.android.tools.r8.utils.structural.CompareToVisitor;
import com.android.tools.r8.utils.structural.HashingVisitor;
import com.android.tools.r8.utils.structural.StructuralItem;
import com.android.tools.r8.utils.structural.StructuralMapping;
import com.google.common.base.Strings;
+import com.google.common.collect.Sets;
import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
-import java.util.IdentityHashMap;
import java.util.List;
import java.util.ListIterator;
-import java.util.Map;
+import java.util.Set;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
public class CfCode extends Code implements CfWritableCode, StructuralItem<CfCode> {
- public enum StackMapStatus {
- NOT_VERIFIED,
- NOT_PRESENT,
- INVALID,
- VALID;
-
- public boolean isValid() {
- return this == VALID || this == NOT_PRESENT;
- }
-
- public boolean isInvalidOrNotPresent() {
- return this == INVALID || this == NOT_PRESENT;
- }
- }
-
public static class LocalVariableInfo {
private final int index;
@@ -149,7 +132,7 @@
private List<CfInstruction> instructions;
private final List<CfTryCatch> tryCatchRanges;
private final List<LocalVariableInfo> localVariables;
- private StackMapStatus stackMapStatus = StackMapStatus.NOT_VERIFIED;
+ private StackMapStatus stackMapStatus = CfFrameVerifier.StackMapStatus.NOT_VERIFIED;
private final com.android.tools.r8.position.Position diagnosticPosition;
private final BytecodeMetadata<CfInstruction> metadata;
@@ -261,7 +244,7 @@
}
public StackMapStatus getStackMapStatus() {
- assert stackMapStatus != StackMapStatus.NOT_VERIFIED;
+ assert stackMapStatus != CfFrameVerifier.StackMapStatus.NOT_VERIFIED;
return stackMapStatus;
}
@@ -281,6 +264,15 @@
return tryCatchRanges;
}
+ public Set<CfLabel> getTryCatchRangeLabels() {
+ Set<CfLabel> tryCatchRangeLabels = Sets.newIdentityHashSet();
+ for (CfTryCatch tryCatchRange : getTryCatchRanges()) {
+ tryCatchRangeLabels.add(tryCatchRange.start);
+ tryCatchRangeLabels.add(tryCatchRange.end);
+ }
+ return tryCatchRangeLabels;
+ }
+
public CfInstruction getInstruction(int index) {
return instructions.get(index);
}
@@ -415,7 +407,8 @@
LensCodeRewriterUtils rewriter,
MethodVisitor visitor) {
GraphLens graphLens = appView.graphLens();
- assert verifyFrames(method, appView).isValid() : "Could not validate stack map frames";
+ assert verifyFrames(method, appView).isValidOrNotPresent()
+ : "Could not validate stack map frames";
DexItemFactory dexItemFactory = appView.dexItemFactory();
InitClassLens initClassLens = appView.initClassLens();
InternalOptions options = appView.options();
@@ -553,7 +546,7 @@
private void verifyFramesOrRemove(ProgramMethod method, AppView<?> appView, GraphLens codeLens) {
stackMapStatus = verifyFrames(method, appView, codeLens);
- if (!stackMapStatus.isValid()) {
+ if (!stackMapStatus.isValidOrNotPresent()) {
ArrayList<CfInstruction> copy = new ArrayList<>(instructions);
copy.removeIf(CfInstruction::isFrame);
setInstructions(copy);
@@ -896,184 +889,23 @@
}
public StackMapStatus verifyFrames(ProgramMethod method, AppView<?> appView, GraphLens codeLens) {
- GraphLens graphLens = appView.graphLens();
- DexEncodedMethod definition = method.getDefinition();
- if (!appView.options().canUseInputStackMaps()
- || appView.options().testing.disableStackMapVerification) {
- return StackMapStatus.NOT_PRESENT;
- }
- if (definition.hasClassFileVersion()
- && definition.getClassFileVersion().isLessThan(CfVersion.V1_7)) {
- return StackMapStatus.NOT_PRESENT;
- }
+ CfFrameVerifierEventConsumer eventConsumer =
+ new CfFrameVerifierEventConsumer() {
- RewrittenPrototypeDescription protoChanges =
- graphLens.lookupPrototypeChangesForMethodDefinition(method.getReference(), codeLens);
-
- DexMethod previousMethodSignature =
- graphLens.getOriginalMethodSignature(method.getReference(), codeLens);
- boolean previousMethodSignatureIsInstance =
- method.getDefinition().isInstance()
- || protoChanges.getArgumentInfoCollection().isConvertedToStaticMethod();
-
- // Build a map from labels to frames.
- Map<CfLabel, CfFrame> stateMap = new IdentityHashMap<>();
- List<CfLabel> labels = new ArrayList<>();
- boolean requireStackMapFrame = !tryCatchRanges.isEmpty();
- for (CfInstruction instruction : instructions) {
- if (instruction.isFrame()) {
- CfFrame frame = instruction.asFrame();
- if (!labels.isEmpty()) {
- for (CfLabel label : labels) {
- if (stateMap.containsKey(label)) {
- return reportStackMapError(
- CfCodeStackMapValidatingException.multipleFramesForLabel(method, appView),
- appView);
- }
- stateMap.put(label, frame);
+ @Override
+ public void acceptError(CfCodeDiagnostics diagnostics) {
+ // Stack maps was required from version V1_6 (50), but the JVM gave a grace-period and
+ // only started enforcing stack maps from 51 in JVM 8. As a consequence, we have
+ // different android libraries that has V1_7 code but has no stack maps. To not fail on
+ // compilations we only report a warning.
+ appView.options().reporter.warning(diagnostics);
}
- } else if (instruction != instructions.get(0)) {
- // From b/168212806, it is possible that the first instruction is a frame.
- return reportStackMapError(
- CfCodeStackMapValidatingException.unexpectedStackMapFrame(method, appView), appView);
- }
- }
- // We are trying to map a frame to a label, but we can have positions in between, so skip
- // those.
- if (instruction.isPosition()) {
- continue;
- } else if (instruction.isLabel()) {
- labels.add(instruction.asLabel());
- } else {
- labels.clear();
- }
- if (!requireStackMapFrame) {
- requireStackMapFrame = instruction.isJump() && !finalAndExitInstruction(instruction);
- }
- }
- // If there are no frames but we have seen a jump instruction, we cannot verify the stack map.
- if (requireStackMapFrame && stateMap.isEmpty()) {
- return reportStackMapError(
- CfCodeStackMapValidatingException.noFramesForMethodWithJumps(method, appView), appView);
- }
- CfFrameVerificationHelper helper =
- new CfFrameVerificationHelper(appView, this, codeLens, method, stateMap, tryCatchRanges);
- CfCodeDiagnostics diagnostics = helper.checkTryCatchRanges();
- if (diagnostics != null) {
- return reportStackMapError(diagnostics, appView);
- }
- TraversalContinuation<CfCodeDiagnostics, CfFrameState> initialState =
- computeInitialState(
- appView, helper, method, previousMethodSignature, previousMethodSignatureIsInstance);
- if (initialState.shouldBreak()) {
- return reportStackMapError(initialState.asBreak().getValue(), appView);
- }
- CfFrameState state = initialState.asContinue().getValue();
- for (int i = 0; i < instructions.size(); i++) {
- CfInstruction instruction = instructions.get(i);
- assert !state.isError();
- // Check the exceptional edge prior to evaluating the instruction. The local state is stable
- // at this point as store operations are not throwing and the current stack does not
- // affect the exceptional transfer (the exception edge is always a singleton stack).
- if (instruction.canThrow()) {
- assert !instruction.isStore();
- state = helper.checkExceptionEdges(state);
- }
- if (instruction.isLabel()) {
- helper.seenLabel(instruction.asLabel());
- }
- state = instruction.evaluate(state, appView, helper);
- if (instruction.isJumpWithNormalTarget()) {
- CfInstruction fallthroughInstruction =
- (i + 1) < instructions.size() ? instructions.get(i + 1) : null;
- TraversalContinuation<CfCodeDiagnostics, CfFrameState> traversalContinuation =
- instruction.traverseNormalTargets(
- (target, currentState) -> {
- if (target != fallthroughInstruction) {
- assert target.isLabel();
- currentState = helper.checkTarget(currentState, target.asLabel());
- }
- return TraversalContinuation.doContinue(currentState);
- },
- fallthroughInstruction,
- state);
- state = traversalContinuation.asContinue().getValue();
- }
- TraversalContinuation<CfCodeDiagnostics, CfFrameState> traversalContinuation =
- helper.computeStateForNextInstruction(instruction, i, state);
- if (traversalContinuation.isContinue()) {
- state = traversalContinuation.asContinue().getValue();
- } else {
- return reportStackMapError(traversalContinuation.asBreak().getValue(), appView);
- }
- if (state.isError()) {
- return reportStackMapError(
- CfCodeStackMapValidatingException.invalidStackMapForInstruction(
- method, i, instruction, state.asError().getMessage(), appView),
- appView);
- }
- }
- return StackMapStatus.VALID;
- }
-
- private StackMapStatus reportStackMapError(CfCodeDiagnostics diagnostics, AppView<?> appView) {
- // Stack maps was required from version V1_6 (50), but the JVM gave a grace-period and only
- // started enforcing stack maps from 51 in JVM 8. As a consequence, we have different android
- // libraries that has V1_7 code but has no stack maps. To not fail on compilations we only
- // report a warning.
- appView.options().reporter.warning(diagnostics);
- return StackMapStatus.INVALID;
- }
-
- private boolean finalAndExitInstruction(CfInstruction instruction) {
- boolean isReturnOrThrow = instruction.isThrow() || instruction.isReturn();
- if (!isReturnOrThrow) {
- return false;
- }
- for (int i = instructions.size() - 1; i >= 0; i--) {
- CfInstruction instr = instructions.get(i);
- if (instr == instruction) {
- return true;
- }
- if (instr.isPosition() || instr.isLabel()) {
- continue;
- }
- return false;
- }
- throw new Unreachable("Instruction " + instruction + " should be in instructions");
- }
-
- private TraversalContinuation<CfCodeDiagnostics, CfFrameState> computeInitialState(
- AppView<?> appView,
- CfFrameVerificationHelper helper,
- ProgramMethod method,
- DexMethod previousMethodSignature,
- boolean previousMethodSignatureIsInstance) {
- DexItemFactory dexItemFactory = appView.dexItemFactory();
- CfFrameState state = new ConcreteCfFrameState();
- int localIndex = 0;
- if (previousMethodSignatureIsInstance) {
- state =
- state.storeLocal(
- localIndex,
- previousMethodSignature.isInstanceInitializer(dexItemFactory)
- || previousMethodSignature.mustBeInlinedIntoInstanceInitializer(appView)
- || previousMethodSignature.isHorizontallyMergedInstanceInitializer(
- dexItemFactory)
- ? FrameType.uninitializedThis()
- : FrameType.initializedNonNullReference(previousMethodSignature.getHolderType()),
- helper);
- localIndex++;
- }
- for (DexType parameter : previousMethodSignature.getParameters()) {
- state = state.storeLocal(localIndex, FrameType.initialized(parameter), helper);
- localIndex += parameter.getRequiredRegisters();
- }
- if (state.isError()) {
- return TraversalContinuation.doBreak(
- CfCodeStackMapValidatingException.invalidStackMapForInstruction(
- method, 0, instructions.get(0), state.asError().getMessage(), appView));
- }
- return TraversalContinuation.doContinue(state);
+ };
+ CfFrameVerifier helper =
+ CfFrameVerifier.builder(appView, this, method)
+ .setCodeLens(codeLens)
+ .setEventConsumer(eventConsumer)
+ .build();
+ return helper.run();
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
index e6228f7..cb3bf73 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -193,7 +193,7 @@
DexBuilder.removeRedundantDebugPositions(code);
CfCode code = buildCfCode();
assert verifyInvokeInterface(code, appView);
- assert code.verifyFrames(method, appView, appView.graphLens()).isValid();
+ assert code.verifyFrames(method, appView, appView.graphLens()).isValidOrNotPresent();
return code;
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
index 8a18a09..bffd1fe 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
@@ -6,6 +6,7 @@
import static it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMaps.emptyMap;
import com.android.tools.r8.cf.code.CfFrame;
+import com.android.tools.r8.cf.code.CfFrameVerifier;
import com.android.tools.r8.cf.code.CfGoto;
import com.android.tools.r8.cf.code.CfInstruction;
import com.android.tools.r8.cf.code.CfLabel;
@@ -21,7 +22,6 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.CfCode.LocalVariableInfo;
-import com.android.tools.r8.graph.CfCode.StackMapStatus;
import com.android.tools.r8.graph.CfCodeDiagnostics;
import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DebugLocalInfo.PrintLevel;
@@ -693,7 +693,7 @@
@Override
public DexType getPhiTypeForBlock(
int register, int blockOffset, ValueTypeConstraint constraint, RegisterReadType readType) {
- assert code.getStackMapStatus() != StackMapStatus.NOT_VERIFIED;
+ assert code.getStackMapStatus() != CfFrameVerifier.StackMapStatus.NOT_VERIFIED;
if (code.getStackMapStatus().isInvalidOrNotPresent()) {
return null;
}
@@ -881,7 +881,7 @@
@Override
public boolean hasValidTypesFromStackMap() {
- return code.getStackMapStatus() == StackMapStatus.VALID;
+ return code.getStackMapStatus() == CfFrameVerifier.StackMapStatus.VALID;
}
@Override