Extend stack validator to explicitly check for uninitialized types
Change-Id: I775e80e18b6c7a83fe4009b71c740dad112a132f
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
index 22421de..4fa3241 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
@@ -82,6 +82,6 @@
InitClassLens initClassLens) {
// ..., arrayref →
// ..., length
- frameBuilder.popAndDiscard(factory.objectArrayType).push(factory.intType);
+ frameBuilder.popAndDiscardInitialized(factory.objectArrayType).push(factory.intType);
}
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
index c82e53e..4f851a0 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
@@ -128,7 +128,7 @@
InitClassLens initClassLens) {
// ..., arrayref, index →
// ..., value
- frameBuilder.popAndDiscard(factory.objectArrayType, factory.intType);
+ frameBuilder.popAndDiscardInitialized(factory.objectArrayType, factory.intType);
frameBuilder.push(FrameType.fromMemberType(type, factory));
}
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java
index 5d64188..cc0cd63 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java
@@ -120,6 +120,6 @@
// ...
frameBuilder
.popAndDiscard(FrameType.fromMemberType(type, factory))
- .popAndDiscard(factory.objectArrayType, factory.intType);
+ .popAndDiscardInitialized(factory.objectArrayType, factory.intType);
}
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java b/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
index 721f3f7..277f2e2 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
@@ -102,6 +102,6 @@
InitClassLens initClassLens) {
// ..., objectref →
// ..., objectref
- frameBuilder.popAndDiscard(factory.objectType).push(type);
+ frameBuilder.popAndDiscardInitialized(factory.objectType).push(type);
}
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
index be6176b..2e8eedf 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
@@ -3,6 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.cf.code;
+import static com.android.tools.r8.utils.BiPredicateUtils.or;
+
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
@@ -189,7 +191,7 @@
case Opcodes.GETFIELD:
// ..., objectref →
// ..., value
- frameBuilder.popAndDiscard(field.holder).push(field.type);
+ frameBuilder.popAndDiscardInitialized(field.holder).push(field.type);
return;
case Opcodes.GETSTATIC:
// ..., →
@@ -199,12 +201,18 @@
case Opcodes.PUTFIELD:
// ..., objectref, value →
// ...,
- frameBuilder.popAndDiscard(field.holder, field.type);
+ frameBuilder
+ .popAndDiscardInitialized(field.type)
+ .pop(
+ field.holder,
+ or(
+ frameBuilder::isUninitializedThisAndTarget,
+ frameBuilder::isAssignableAndInitialized));
return;
case Opcodes.PUTSTATIC:
// ..., value →
// ...
- frameBuilder.pop(field.type);
+ frameBuilder.popAndDiscardInitialized(field.type);
return;
default:
throw new Unreachable("Unexpected opcode " + opcode);
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
index 713d764..b2b18b7 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
@@ -452,7 +452,7 @@
DexType returnType,
DexItemFactory factory,
InitClassLens initClassLens) {
- frameBuilder.verifyFrameAndSet(this);
+ frameBuilder.checkFrameAndSet(this);
}
public CfFrame markInstantiated(FrameType uninitializedType, DexType initType) {
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
index 7ce2440..bc0da18 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFrameVerificationHelper.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFrameVerificationHelper.java
@@ -4,6 +4,8 @@
package com.android.tools.r8.cf.code;
+import static com.android.tools.r8.utils.BiPredicateUtils.or;
+
import com.android.tools.r8.cf.code.CfFrame.FrameType;
import com.android.tools.r8.graph.CfCodeStackMapValidatingException;
import com.android.tools.r8.graph.DexItemFactory;
@@ -70,50 +72,60 @@
}
public FrameType readLocal(int index, DexType expectedType) {
- verifyFrameIsSet();
+ checkFrameIsSet();
FrameType frameType = currentFrame.getLocals().get(index);
if (frameType == null) {
throw CfCodeStackMapValidatingException.error("No local at index " + index);
}
- verifyIsAssignable(frameType, expectedType);
+ checkIsAssignable(
+ frameType,
+ expectedType,
+ or(
+ this::isUninitializedThisAndTarget,
+ this::isUninitializedNewAndTarget,
+ this::isAssignableAndInitialized));
return frameType;
}
public void storeLocal(int index, FrameType frameType) {
- verifyFrameIsSet();
+ checkFrameIsSet();
currentFrame.getLocals().put(index, frameType);
}
public FrameType pop() {
- verifyFrameIsSet();
+ checkFrameIsSet();
if (currentFrame.getStack().isEmpty()) {
throw CfCodeStackMapValidatingException.error("Cannot pop() from an empty stack");
}
return currentFrame.getStack().removeLast();
}
- public FrameType pop(DexType expectedType) {
+ public FrameType popInitialized(DexType expectedType) {
+ return pop(expectedType, this::isAssignableAndInitialized);
+ }
+
+ public FrameType pop(DexType expectedType, BiPredicate<FrameType, DexType> isAssignable) {
FrameType frameType = pop();
- verifyIsAssignable(frameType, expectedType);
+ checkIsAssignable(frameType, expectedType, isAssignable);
return frameType;
}
- public CfFrameVerificationHelper popAndDiscard(DexType... expectedTypes) {
- verifyFrameIsSet();
+ public CfFrameVerificationHelper popAndDiscardInitialized(DexType... expectedTypes) {
+ checkFrameIsSet();
for (int i = expectedTypes.length - 1; i >= 0; i--) {
- pop(expectedTypes[i]);
+ popInitialized(expectedTypes[i]);
}
return this;
}
public FrameType pop(FrameType expectedType) {
FrameType frameType = pop();
- verifyIsAssignable(frameType, expectedType);
+ checkIsAssignable(frameType, expectedType);
return frameType;
}
public CfFrameVerificationHelper popAndDiscard(FrameType... expectedTypes) {
- verifyFrameIsSet();
+ checkFrameIsSet();
for (int i = expectedTypes.length - 1; i >= 0; i--) {
pop(expectedTypes[i]);
}
@@ -121,17 +133,20 @@
}
public void popAndInitialize(DexType context, DexType methodHolder) {
- verifyFrameIsSet();
- FrameType objectRef = pop(factory.objectType);
+ checkFrameIsSet();
+ FrameType objectRef =
+ pop(
+ factory.objectType,
+ or(this::isUninitializedThisAndTarget, this::isUninitializedNewAndTarget));
CfFrame newFrame =
currentFrame.markInstantiated(
objectRef, objectRef.isUninitializedNew() ? methodHolder : context);
setNoFrame();
- verifyFrameAndSet(newFrame);
+ checkFrameAndSet(newFrame);
}
public CfFrameVerificationHelper push(FrameType type) {
- verifyFrameIsSet();
+ checkFrameIsSet();
currentFrame.getStack().addLast(type);
return this;
}
@@ -153,7 +168,7 @@
if (destinationFrame == null) {
throw CfCodeStackMapValidatingException.error("No frame for target catch range target");
}
- verifyStackIsAssignable(
+ checkStackIsAssignable(
destinationFrame.getStack(), throwStack, factory, isJavaAssignable);
}
}
@@ -162,15 +177,15 @@
return this;
}
- private void verifyFrameIsSet() {
+ private void checkFrameIsSet() {
if (currentFrame == NO_FRAME) {
throw CfCodeStackMapValidatingException.error("Unexpected state change");
}
}
- public void verifyFrameAndSet(CfFrame newFrame) {
+ public void checkFrameAndSet(CfFrame newFrame) {
if (currentFrame != NO_FRAME) {
- verifyFrame(newFrame);
+ checkFrame(newFrame);
}
setFrame(newFrame);
}
@@ -182,7 +197,7 @@
new Int2ReferenceAVLTreeMap<>(frame.getLocals()), new ArrayDeque<>(frame.getStack()));
}
- public void verifyExceptionEdges() {
+ public void checkExceptionEdges() {
for (CfTryCatch currentCatchRange : currentCatchRanges) {
for (CfLabel target : currentCatchRange.targets) {
CfFrame destinationFrame = stateMap.get(target);
@@ -192,7 +207,7 @@
// We have to check all current handler targets have assignable locals and a 1-element
// stack assignable to throwable. It is not required that the the thrown error is
// handled.
- verifyLocalsIsAssignable(
+ checkLocalsIsAssignable(
currentFrame.getLocals(), destinationFrame.getLocals(), factory, isJavaAssignable);
}
}
@@ -202,19 +217,19 @@
return currentFrame;
}
- public void verifyTarget(CfLabel label) {
- verifyFrame(stateMap.get(label));
+ public void checkTarget(CfLabel label) {
+ checkFrame(stateMap.get(label));
}
- public void verifyFrame(CfFrame destinationFrame) {
+ public void checkFrame(CfFrame destinationFrame) {
if (destinationFrame == null) {
throw CfCodeStackMapValidatingException.error("No destination frame");
}
- verifyFrame(destinationFrame.getLocals(), destinationFrame.getStack());
+ checkFrame(destinationFrame.getLocals(), destinationFrame.getStack());
}
- public void verifyFrame(Int2ReferenceSortedMap<FrameType> locals, Deque<FrameType> stack) {
- verifyIsAssignable(
+ public void checkFrame(Int2ReferenceSortedMap<FrameType> locals, Deque<FrameType> stack) {
+ checkIsAssignable(
currentFrame.getLocals(),
currentFrame.getStack(),
locals,
@@ -227,30 +242,37 @@
currentFrame = NO_FRAME;
}
- public void clearStack() {
- verifyFrameIsSet();
- currentFrame.getStack().clear();
+ public boolean isUninitializedThisAndTarget(FrameType source, DexType target) {
+ if (!source.isUninitializedThis()) {
+ return false;
+ }
+ return target == factory.objectType || graphLens.lookupClassType(target) == context;
}
- public void verifyIsAssignable(FrameType source, DexType target) {
+ public boolean isUninitializedNewAndTarget(FrameType source, DexType target) {
+ if (!source.isUninitializedNew()) {
+ return false;
+ }
+ return target == factory.objectType || graphLens.lookupClassType(target) == context;
+ }
+
+ public boolean isAssignableAndInitialized(FrameType source, DexType target) {
if (!source.isInitialized()) {
- DexType rewrittenTarget = graphLens.lookupClassType(target);
- if (source.isUninitializedThis() && rewrittenTarget == context) {
- return;
- }
- if (rewrittenTarget == factory.objectType) {
- return;
- }
- throw CfCodeStackMapValidatingException.error(
- "The expected type " + source + " is not assignable to " + target.toSourceString());
+ return false;
}
- if (!isJavaAssignable.test(source.getInitializedType(), target)) {
- throw CfCodeStackMapValidatingException.error(
- "The expected type " + source + " is not assignable to " + target.toSourceString());
- }
+ return isJavaAssignable.test(source.getInitializedType(), target);
}
- public void verifyIsAssignable(FrameType source, FrameType target) {
+ public void checkIsAssignable(
+ FrameType source, DexType target, BiPredicate<FrameType, DexType> predicate) {
+ if (predicate.test(source, target)) {
+ return;
+ }
+ throw CfCodeStackMapValidatingException.error(
+ "The expected type " + source + " is not assignable to " + target.toSourceString());
+ }
+
+ public void checkIsAssignable(FrameType source, FrameType target) {
if (!canBeAssigned(source, target, factory, isJavaAssignable)) {
throw CfCodeStackMapValidatingException.error(
"The expected type " + source + " is not assignable to " + target);
@@ -258,18 +280,18 @@
}
// Based on https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.10.1.4.
- public static void verifyIsAssignable(
+ public static void checkIsAssignable(
Int2ReferenceSortedMap<FrameType> sourceLocals,
Deque<FrameType> sourceStack,
Int2ReferenceSortedMap<FrameType> destLocals,
Deque<FrameType> destStack,
DexItemFactory factory,
BiPredicate<DexType, DexType> isJavaAssignable) {
- verifyLocalsIsAssignable(sourceLocals, destLocals, factory, isJavaAssignable);
- verifyStackIsAssignable(sourceStack, destStack, factory, isJavaAssignable);
+ checkLocalsIsAssignable(sourceLocals, destLocals, factory, isJavaAssignable);
+ checkStackIsAssignable(sourceStack, destStack, factory, isJavaAssignable);
}
- private static void verifyLocalsIsAssignable(
+ private static void checkLocalsIsAssignable(
Int2ReferenceSortedMap<FrameType> sourceLocals,
Int2ReferenceSortedMap<FrameType> destLocals,
DexItemFactory factory,
@@ -305,7 +327,7 @@
}
}
- private static void verifyStackIsAssignable(
+ private static void checkStackIsAssignable(
Deque<FrameType> sourceStack,
Deque<FrameType> destStack,
DexItemFactory factory,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfGoto.java b/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
index 2f4a549..b3043c9 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
@@ -97,7 +97,7 @@
DexType returnType,
DexItemFactory factory,
InitClassLens initClassLens) {
- frameBuilder.verifyTarget(target);
+ frameBuilder.checkTarget(target);
frameBuilder.setNoFrame();
}
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfIf.java b/src/main/java/com/android/tools/r8/cf/code/CfIf.java
index b331673..7653d51 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfIf.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfIf.java
@@ -135,8 +135,8 @@
InitClassLens initClassLens) {
// ..., value →
// ...
- frameBuilder.pop(
+ frameBuilder.popAndDiscardInitialized(
type.isObject() ? factory.objectType : type.toPrimitiveType().toDexType(factory));
- frameBuilder.verifyTarget(target);
+ frameBuilder.checkTarget(target);
}
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java b/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java
index 45b426b..3e5c38a 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java
@@ -138,7 +138,7 @@
// ...
DexType type =
this.type.isObject() ? factory.objectType : this.type.toPrimitiveType().toDexType(factory);
- frameBuilder.popAndDiscard(type, type);
- frameBuilder.verifyTarget(target);
+ frameBuilder.popAndDiscardInitialized(type, type);
+ frameBuilder.checkTarget(target);
}
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java b/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
index c18eb57..3cc1f4b 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
@@ -110,6 +110,6 @@
InitClassLens initClassLens) {
// ..., objectref →
// ..., result
- frameBuilder.popAndDiscard(factory.objectType).push(factory.intType);
+ frameBuilder.popAndDiscardInitialized(factory.objectType).push(factory.intType);
}
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
index db81a5f..87cae25 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
@@ -354,11 +354,11 @@
// OR, for static method calls:
// ..., [arg1, [arg2 ...]] →
// ...
- frameBuilder.popAndDiscard(this.method.proto.parameters.values);
+ frameBuilder.popAndDiscardInitialized(this.method.proto.parameters.values);
if (opcode == Opcodes.INVOKESPECIAL && method.isInstanceInitializer(factory)) {
frameBuilder.popAndInitialize(context, method.holder);
} else if (opcode != Opcodes.INVOKESTATIC) {
- frameBuilder.pop(method.holder);
+ frameBuilder.popInitialized(method.holder);
}
if (this.method.proto.returnType != factory.voidType) {
frameBuilder.push(this.method.proto.returnType);
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
index 88ffb82..3a98a8e 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
@@ -167,7 +167,7 @@
InitClassLens initClassLens) {
// ..., [arg1, [arg2 ...]] →
// ...
- frameBuilder.popAndDiscard(callSite.methodProto.parameters.values);
+ frameBuilder.popAndDiscardInitialized(callSite.methodProto.parameters.values);
if (callSite.methodProto.returnType != factory.voidType) {
frameBuilder.push(callSite.methodProto.returnType);
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java b/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
index 9180511..bac5536 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
@@ -114,7 +114,7 @@
// ..., count1, [count2, ...] →
// ..., arrayref
for (int i = 0; i < dimensions; i++) {
- frameBuilder.pop(factory.intType);
+ frameBuilder.popInitialized(factory.intType);
}
frameBuilder.push(type);
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java b/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
index bd36756..78200b7 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
@@ -150,6 +150,6 @@
// ..., count →
// ..., arrayref
assert type.isArrayType();
- frameBuilder.popAndDiscard(factory.intType).push(type);
+ frameBuilder.popAndDiscardInitialized(factory.intType).push(type);
}
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfReturn.java b/src/main/java/com/android/tools/r8/cf/code/CfReturn.java
index 51d1283..2b4eed5 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfReturn.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfReturn.java
@@ -113,7 +113,7 @@
DexItemFactory factory,
InitClassLens initClassLens) {
assert returnType != null;
- frameBuilder.popAndDiscard(returnType);
+ frameBuilder.popAndDiscardInitialized(returnType);
frameBuilder.setNoFrame();
}
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java
index bfdbe0a..4fd9829 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java
@@ -368,7 +368,7 @@
// ...
final FrameType pop = frameBuilder.pop();
if (!pop.isWide()) {
- frameBuilder.verifyIsAssignable(pop, FrameType.oneWord());
+ frameBuilder.checkIsAssignable(pop, FrameType.oneWord());
frameBuilder.pop(FrameType.oneWord());
}
return;
@@ -399,7 +399,7 @@
FrameType value1 = frameBuilder.pop(FrameType.oneWord());
FrameType value2 = frameBuilder.pop();
if (!value2.isWide()) {
- frameBuilder.verifyIsAssignable(value2, FrameType.oneWord());
+ frameBuilder.checkIsAssignable(value2, FrameType.oneWord());
FrameType value3 = frameBuilder.pop(FrameType.oneWord());
frameBuilder.push(value1).push(value3);
} else {
@@ -417,7 +417,7 @@
// ..., value, value
FrameType value1 = frameBuilder.pop();
if (!value1.isWide()) {
- frameBuilder.verifyIsAssignable(value1, FrameType.oneWord());
+ frameBuilder.checkIsAssignable(value1, FrameType.oneWord());
FrameType value2 = frameBuilder.pop(FrameType.oneWord());
frameBuilder.push(value2).push(value1).push(value2);
} else {
@@ -436,7 +436,7 @@
FrameType value1 = frameBuilder.pop();
FrameType value2 = frameBuilder.pop(FrameType.oneWord());
if (!value1.isWide()) {
- frameBuilder.verifyIsAssignable(value1, FrameType.oneWord());
+ frameBuilder.checkIsAssignable(value1, FrameType.oneWord());
FrameType value3 = frameBuilder.pop(FrameType.oneWord());
frameBuilder.push(value2).push(value1).push(value3);
} else {
@@ -466,9 +466,9 @@
FrameType value3 = frameBuilder.pop();
if (!value3.isWide()) {
// (1)
- frameBuilder.verifyIsAssignable(value1, FrameType.oneWord());
- frameBuilder.verifyIsAssignable(value2, FrameType.oneWord());
- frameBuilder.verifyIsAssignable(value3, FrameType.oneWord());
+ frameBuilder.checkIsAssignable(value1, FrameType.oneWord());
+ frameBuilder.checkIsAssignable(value2, FrameType.oneWord());
+ frameBuilder.checkIsAssignable(value3, FrameType.oneWord());
FrameType value4 = frameBuilder.pop(FrameType.oneWord());
frameBuilder
.push(value2)
@@ -479,13 +479,13 @@
.push(value1);
} else {
// (3)
- frameBuilder.verifyIsAssignable(value1, FrameType.oneWord());
- frameBuilder.verifyIsAssignable(value2, FrameType.oneWord());
+ frameBuilder.checkIsAssignable(value1, FrameType.oneWord());
+ frameBuilder.checkIsAssignable(value2, FrameType.oneWord());
frameBuilder.push(value2).push(value1).push(value3).push(value2).push(value1);
}
} else if (!value2.isWide()) {
// (2)
- frameBuilder.verifyIsAssignable(value2, FrameType.oneWord());
+ frameBuilder.checkIsAssignable(value2, FrameType.oneWord());
FrameType value3 = frameBuilder.pop(FrameType.oneWord());
frameBuilder.push(value1).push(value3).push(value2).push(value1);
} else {
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStore.java b/src/main/java/com/android/tools/r8/cf/code/CfStore.java
index d74eb39..b87c87c 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfStore.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfStore.java
@@ -3,6 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.cf.code;
+import static com.android.tools.r8.utils.BiPredicateUtils.or;
+
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.cf.code.CfFrame.FrameType;
import com.android.tools.r8.errors.Unreachable;
@@ -129,24 +131,34 @@
FrameType pop = frameBuilder.pop();
switch (type) {
case OBJECT:
- frameBuilder.verifyIsAssignable(pop, factory.objectType);
+ frameBuilder.checkIsAssignable(
+ pop,
+ factory.objectType,
+ or(
+ frameBuilder::isUninitializedThisAndTarget,
+ frameBuilder::isUninitializedNewAndTarget,
+ frameBuilder::isAssignableAndInitialized));
frameBuilder.storeLocal(var, pop);
return;
case INT:
- frameBuilder.verifyIsAssignable(pop, factory.intType);
+ frameBuilder.checkIsAssignable(
+ pop, factory.intType, frameBuilder::isAssignableAndInitialized);
frameBuilder.storeLocal(var, FrameType.initialized(factory.intType));
return;
case FLOAT:
- frameBuilder.verifyIsAssignable(pop, factory.floatType);
+ frameBuilder.checkIsAssignable(
+ pop, factory.floatType, frameBuilder::isAssignableAndInitialized);
frameBuilder.storeLocal(var, FrameType.initialized(factory.floatType));
return;
case LONG:
- frameBuilder.verifyIsAssignable(pop, factory.longType);
+ frameBuilder.checkIsAssignable(
+ pop, factory.longType, frameBuilder::isAssignableAndInitialized);
frameBuilder.storeLocal(var, FrameType.initialized(factory.longType));
frameBuilder.storeLocal(var + 1, FrameType.initialized(factory.longType));
return;
case DOUBLE:
- frameBuilder.verifyIsAssignable(pop, factory.doubleType);
+ frameBuilder.checkIsAssignable(
+ pop, factory.doubleType, frameBuilder::isAssignableAndInitialized);
frameBuilder.storeLocal(var, FrameType.initialized(factory.doubleType));
frameBuilder.storeLocal(var + 1, FrameType.initialized(factory.doubleType));
return;
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java b/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java
index f07011a..b84fc14 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java
@@ -149,10 +149,10 @@
InitClassLens initClassLens) {
// ..., index/key →
// ...
- frameBuilder.pop(factory.intType);
- frameBuilder.verifyTarget(defaultTarget);
+ frameBuilder.popInitialized(factory.intType);
+ frameBuilder.checkTarget(defaultTarget);
for (CfLabel target : targets) {
- frameBuilder.verifyTarget(target);
+ frameBuilder.checkTarget(target);
}
frameBuilder.setNoFrame();
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfThrow.java b/src/main/java/com/android/tools/r8/cf/code/CfThrow.java
index aad20a8..90d25c5 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfThrow.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfThrow.java
@@ -95,7 +95,7 @@
InitClassLens initClassLens) {
// ..., objectref →
// objectref
- frameBuilder.pop(factory.throwableType);
+ frameBuilder.popInitialized(factory.throwableType);
// The exception edges are verified in CfCode since this is a throwing instruction.
frameBuilder.setNoFrame();
}
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 ed43ca9..025a20e 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -305,7 +305,8 @@
LensCodeRewriterUtils rewriter,
MethodVisitor visitor) {
GraphLens graphLens = appView.graphLens();
- assert verifyFrames(method.getDefinition(), appView, null, false);
+ assert verifyFrames(method.getDefinition(), appView, null, false)
+ : "Could not validate stack map frames";
DexItemFactory dexItemFactory = appView.dexItemFactory();
InitClassLens initClassLens = appView.initClassLens();
InternalOptions options = appView.options();
@@ -793,9 +794,9 @@
appView.graphLens());
if (stateMap.containsKey(null)) {
assert !shouldComputeInitialFrame();
- builder.verifyFrameAndSet(stateMap.get(null));
+ builder.checkFrameAndSet(stateMap.get(null));
} else if (shouldComputeInitialFrame()) {
- builder.verifyFrameAndSet(
+ builder.checkFrameAndSet(
new CfFrame(
computeInitialLocals(context, method, rewrittenDescription), new ArrayDeque<>()));
}
@@ -807,7 +808,7 @@
// affect the exceptional transfer (the exception edge is always a singleton stack).
if (instruction.canThrow()) {
assert !instruction.isStore();
- builder.verifyExceptionEdges();
+ builder.checkExceptionEdges();
}
instruction.evaluate(
builder, context, returnType, appView.dexItemFactory(), appView.initClassLens());
diff --git a/src/main/java/com/android/tools/r8/utils/BiPredicateUtils.java b/src/main/java/com/android/tools/r8/utils/BiPredicateUtils.java
index f470d35..5245876 100644
--- a/src/main/java/com/android/tools/r8/utils/BiPredicateUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/BiPredicateUtils.java
@@ -15,4 +15,16 @@
public static <S, T> BiPredicate<S, T> alwaysTrue() {
return (s, t) -> true;
}
+
+ @SafeVarargs
+ public static <S, T> BiPredicate<S, T> or(BiPredicate<S, T>... predicates) {
+ return (s, t) -> {
+ for (BiPredicate<S, T> predicate : predicates) {
+ if (predicate.test(s, t)) {
+ return true;
+ }
+ }
+ return false;
+ };
+ }
}
diff --git a/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedGetFieldTest.java b/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedGetFieldTest.java
new file mode 100644
index 0000000..58ecdb8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedGetFieldTest.java
@@ -0,0 +1,192 @@
+// 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.cf.stackmap;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.cf.stackmap.UninitializedGetFieldTest.UninitializedGetFieldTest$MainDump.dump;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class UninitializedGetFieldTest extends TestBase {
+
+ private final String[] EXPECTED = new String[] {"Main::foo"};
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+ }
+
+ public UninitializedGetFieldTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addProgramClassFileData(dump())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @Test()
+ public void testD8Cf() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(dump())
+ .setMinApi(parameters.getApiLevel())
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertNoWarningsMatch(
+ diagnosticMessage(containsString("The expected type uninitialized")));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @Test
+ public void testD8Dex() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(dump())
+ .setMinApi(parameters.getApiLevel())
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertNoWarningsMatch(
+ diagnosticMessage(containsString("The expected type uninitialized")));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ public static class Main {
+
+ private Object object;
+
+ private Main() {
+ this.object = new Object();
+ foo(this.object);
+ }
+
+ private void foo(Object object) {
+ System.out.println("Main::foo");
+ }
+
+ public static void main(String[] args) {
+ new Main();
+ }
+ }
+
+ // The dump is generated from the above code. The change made is to Main::<init> where we
+ // now putfield before initializing.
+ static class UninitializedGetFieldTest$MainDump implements Opcodes {
+
+ static byte[] dump() throws Exception {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ FieldVisitor fieldVisitor;
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(
+ V1_8,
+ ACC_PUBLIC | ACC_SUPER,
+ "com/android/tools/r8/cf/stackmap/UninitializedGetFieldTest$Main",
+ null,
+ "java/lang/Object",
+ null);
+
+ classWriter.visitInnerClass(
+ "com/android/tools/r8/cf/stackmap/UninitializedGetFieldTest$Main",
+ "com/android/tools/r8/cf/stackmap/UninitializedGetFieldTest",
+ "Main",
+ ACC_PUBLIC | ACC_STATIC);
+
+ {
+ fieldVisitor =
+ classWriter.visitField(ACC_PRIVATE, "object", "Ljava/lang/Object;", null, null);
+ fieldVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(ACC_PRIVATE, "<init>", "()V", null, null);
+ methodVisitor.visitCode();
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitTypeInsn(NEW, "java/lang/Object");
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ methodVisitor.visitFieldInsn(
+ PUTFIELD,
+ "com/android/tools/r8/cf/stackmap/UninitializedGetFieldTest$Main",
+ "object",
+ "Ljava/lang/Object;");
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitFieldInsn(
+ GETFIELD,
+ "com/android/tools/r8/cf/stackmap/UninitializedGetFieldTest$Main",
+ "object",
+ "Ljava/lang/Object;");
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(
+ INVOKESPECIAL,
+ "com/android/tools/r8/cf/stackmap/UninitializedGetFieldTest$Main",
+ "foo",
+ "(Ljava/lang/Object;)V",
+ false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(3, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(ACC_PRIVATE, "foo", "(Ljava/lang/Object;)V", null, null);
+ methodVisitor.visitCode();
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitLdcInsn("Main::foo");
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 2);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+ methodVisitor.visitCode();
+ methodVisitor.visitTypeInsn(
+ NEW, "com/android/tools/r8/cf/stackmap/UninitializedGetFieldTest$Main");
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitMethodInsn(
+ INVOKESPECIAL,
+ "com/android/tools/r8/cf/stackmap/UninitializedGetFieldTest$Main",
+ "<init>",
+ "()V",
+ false);
+ methodVisitor.visitInsn(POP);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 1);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedInstanceOfTest.java b/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedInstanceOfTest.java
new file mode 100644
index 0000000..30b2a78
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedInstanceOfTest.java
@@ -0,0 +1,133 @@
+// 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.cf.stackmap;
+
+import static com.android.tools.r8.cf.stackmap.UninitializedInstanceOfTest.MainDump.dump;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class UninitializedInstanceOfTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addProgramClassFileData(dump())
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatThrows(VerifyError.class);
+ }
+
+ @Test(expected = CompilationFailedException.class)
+ public void testD8Cf() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(dump())
+ .setMinApi(parameters.getApiLevel())
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertWarningMessageThatMatches(
+ containsString("The expected type uninitialized new is not assignable"));
+ diagnostics.assertErrorMessageThatMatches(
+ containsString("Could not validate stack map frames"));
+ });
+ }
+
+ @Test()
+ public void testD8Dex() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ boolean expectFailure = parameters.getDexRuntimeVersion().isAtLeast(Version.V7_0_0);
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(dump())
+ .setMinApi(parameters.getApiLevel())
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertWarningMessageThatMatches(
+ containsString("The expected type uninitialized new is not assignable"));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .applyIf(
+ expectFailure,
+ result -> result.assertFailureWithErrorThatThrows(VerifyError.class),
+ TestRunResult::assertSuccessWithOutputLines);
+ }
+
+ public UninitializedInstanceOfTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ public static class Main {
+
+ // The dump is generated from the following code, where we just swap the instanceof with the
+ // initializer call.
+ public static void main(String[] args) {
+ boolean m = (new Object() instanceof Main);
+ }
+ }
+
+ static class MainDump implements Opcodes {
+
+ static byte[] dump() throws Exception {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(
+ V1_8,
+ ACC_PUBLIC | ACC_SUPER,
+ "com/android/tools/r8/cf/stackmap/UninitializedInstanceOfTest$Main",
+ null,
+ "java/lang/Object",
+ null);
+
+ classWriter.visitInnerClass(
+ "com/android/tools/r8/cf/stackmap/UninitializedInstanceOfTest$Main",
+ "com/android/tools/r8/cf/stackmap/UninitializedInstanceOfTest",
+ "Main",
+ ACC_PUBLIC | ACC_STATIC);
+
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+ methodVisitor.visitCode();
+ methodVisitor.visitTypeInsn(NEW, "java/lang/Object");
+ methodVisitor.visitInsn(DUP);
+ // INSTANCEOF and INVOKESPECIAL is swapped.
+ methodVisitor.visitTypeInsn(
+ INSTANCEOF, "com/android/tools/r8/cf/stackmap/UninitializedInstanceOfTest$Main");
+ methodVisitor.visitVarInsn(ISTORE, 1);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 2);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedNewCheckCastTest.java b/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedNewCheckCastTest.java
new file mode 100644
index 0000000..86429af
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedNewCheckCastTest.java
@@ -0,0 +1,127 @@
+// 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.cf.stackmap;
+
+import static com.android.tools.r8.cf.stackmap.UninitializedNewCheckCastTest.MainDump.dump;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class UninitializedNewCheckCastTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addProgramClassFileData(dump())
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatThrows(VerifyError.class);
+ }
+
+ @Test(expected = CompilationFailedException.class)
+ public void testD8Cf() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(dump())
+ .setMinApi(parameters.getApiLevel())
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertWarningMessageThatMatches(
+ containsString("The expected type uninitialized new is not assignable"));
+ diagnostics.assertErrorMessageThatMatches(
+ containsString("Could not validate stack map frames"));
+ });
+ }
+
+ @Test
+ public void testD8Dex() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(dump())
+ .setMinApi(parameters.getApiLevel())
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertWarningMessageThatMatches(
+ containsString("The expected type uninitialized new is not assignable"));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatThrows(VerifyError.class);
+ }
+
+ public UninitializedNewCheckCastTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ public static class Main {
+
+ // The dump is generated from the following code, where we just swap the CheckCast with the
+ // initializer call.
+ public static void main(String[] args) {
+ Main m = (Main) new Object();
+ }
+ }
+
+ static class MainDump implements Opcodes {
+
+ static byte[] dump() {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(
+ V1_8,
+ ACC_PUBLIC | ACC_SUPER,
+ "com/android/tools/r8/cf/stackmap/UninitializedNewCheckCastTest$Main",
+ null,
+ "java/lang/Object",
+ null);
+
+ classWriter.visitInnerClass(
+ "com/android/tools/r8/cf/stackmap/UninitializedNewCheckCastTest$Main",
+ "com/android/tools/r8/cf/stackmap/UninitializedNewCheckCastTest",
+ "Main",
+ ACC_PUBLIC | ACC_STATIC);
+
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+ methodVisitor.visitCode();
+ methodVisitor.visitTypeInsn(NEW, "java/lang/Object");
+ methodVisitor.visitInsn(DUP);
+ // CHECK-CAST has swapped position with INVOKESPECIAL.
+ methodVisitor.visitTypeInsn(
+ CHECKCAST, "com/android/tools/r8/cf/stackmap/UninitializedNewCheckCastTest$Main");
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ methodVisitor.visitVarInsn(ASTORE, 1);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 2);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest.java b/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest.java
new file mode 100644
index 0000000..9a52445
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest.java
@@ -0,0 +1,198 @@
+// 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.cf.stackmap;
+
+import static com.android.tools.r8.cf.stackmap.UninitializedPutFieldSelfTest.MainDump.dump;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class UninitializedPutFieldSelfTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addProgramClassFileData(dump())
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatThrows(VerifyError.class);
+ }
+
+ @Test(expected = CompilationFailedException.class)
+ public void testD8Cf() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(dump())
+ .setMinApi(parameters.getApiLevel())
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertWarningMessageThatMatches(
+ containsString("The expected type uninitialized this is not assignable"));
+ diagnostics.assertErrorMessageThatMatches(
+ containsString("Could not validate stack map frames"));
+ });
+ }
+
+ @Test
+ public void testD8Dex() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ boolean willFailVerification =
+ parameters.getDexRuntimeVersion().isOlderThan(Version.V5_1_1)
+ || parameters.getDexRuntimeVersion().isNewerThan(Version.V6_0_1);
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(dump())
+ .setMinApi(parameters.getApiLevel())
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertWarningMessageThatMatches(
+ containsString("The expected type uninitialized this is not assignable"));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatThrowsIf(willFailVerification, VerifyError.class)
+ .assertSuccessWithOutputLinesIf(!willFailVerification, "Main::foo");
+ }
+
+ public UninitializedPutFieldSelfTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ public static class Main {
+
+ private Main main;
+
+ private Main() {
+ this.main = this;
+ this.main.foo();
+ }
+
+ private void foo() {
+ System.out.println("Main::foo");
+ }
+
+ public static void main(String[] args) {
+ new Main();
+ }
+ }
+
+ // The dump is generated from the above code. The change that is made is to Main::<init> where we
+ // now putfield before initializing. That will try and assign an uninstantiated type to a field
+ // which is not allowed.
+ public static class MainDump implements Opcodes {
+
+ public static byte[] dump() {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ FieldVisitor fieldVisitor;
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(
+ V1_8,
+ ACC_PUBLIC | ACC_SUPER,
+ "com/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest$Main",
+ null,
+ "java/lang/Object",
+ null);
+
+ classWriter.visitInnerClass(
+ "com/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest$Main",
+ "com/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest",
+ "Main",
+ ACC_PUBLIC | ACC_STATIC);
+
+ {
+ fieldVisitor =
+ classWriter.visitField(
+ ACC_PRIVATE,
+ "main",
+ "Lcom/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest$Main;",
+ null,
+ null);
+ fieldVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(ACC_PRIVATE, "<init>", "()V", null, null);
+ methodVisitor.visitCode();
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitFieldInsn(
+ PUTFIELD,
+ "com/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest$Main",
+ "main",
+ "Lcom/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest$Main;");
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitFieldInsn(
+ GETFIELD,
+ "com/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest$Main",
+ "main",
+ "Lcom/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest$Main;");
+ methodVisitor.visitMethodInsn(
+ INVOKESPECIAL,
+ "com/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest$Main",
+ "foo",
+ "()V",
+ false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(ACC_PRIVATE, "foo", "()V", null, null);
+ methodVisitor.visitCode();
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitLdcInsn("Main::foo");
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+ methodVisitor.visitCode();
+ methodVisitor.visitTypeInsn(
+ NEW, "com/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest$Main");
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitMethodInsn(
+ INVOKESPECIAL,
+ "com/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest$Main",
+ "<init>",
+ "()V",
+ false);
+ methodVisitor.visitInsn(POP);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 1);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedPutFieldTest.java b/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedPutFieldTest.java
new file mode 100644
index 0000000..e943ebc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedPutFieldTest.java
@@ -0,0 +1,192 @@
+// 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.cf.stackmap;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.cf.stackmap.UninitializedPutFieldTest.MainDump.dump;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class UninitializedPutFieldTest extends TestBase {
+
+ private final String[] EXPECTED = new String[] {"Main::foo"};
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addProgramClassFileData(dump())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @Test()
+ public void testD8Cf() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(dump())
+ .setMinApi(parameters.getApiLevel())
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertNoWarningsMatch(
+ diagnosticMessage(containsString("The expected type uninitialized")));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @Test
+ public void testD8Dex() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(dump())
+ .setMinApi(parameters.getApiLevel())
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertNoWarningsMatch(
+ diagnosticMessage(containsString("The expected type uninitialized")));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ public UninitializedPutFieldTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ public static class Main {
+
+ private Object object;
+
+ private Main() {
+ this.object = new Object();
+ foo(this.object);
+ }
+
+ private void foo(Object object) {
+ System.out.println("Main::foo");
+ }
+
+ public static void main(String[] args) {
+ new Main();
+ }
+ }
+
+ // The dump is generated from the above code. The change made is to Main::<init> where we
+ // now putfield before initializing this.
+ static class MainDump implements Opcodes {
+
+ static byte[] dump() throws Exception {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ FieldVisitor fieldVisitor;
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(
+ V1_8,
+ ACC_PUBLIC | ACC_SUPER,
+ "com/android/tools/r8/cf/stackmap/UninitializedPutFieldTest$Main",
+ null,
+ "java/lang/Object",
+ null);
+
+ classWriter.visitInnerClass(
+ "com/android/tools/r8/cf/stackmap/UninitializedPutFieldTest$Main",
+ "com/android/tools/r8/cf/stackmap/UninitializedPutFieldTest",
+ "Main",
+ ACC_PUBLIC | ACC_STATIC);
+
+ {
+ fieldVisitor =
+ classWriter.visitField(ACC_PRIVATE, "object", "Ljava/lang/Object;", null, null);
+ fieldVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(ACC_PRIVATE, "<init>", "()V", null, null);
+ methodVisitor.visitCode();
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitTypeInsn(NEW, "java/lang/Object");
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ methodVisitor.visitFieldInsn(
+ PUTFIELD,
+ "com/android/tools/r8/cf/stackmap/UninitializedPutFieldTest$Main",
+ "object",
+ "Ljava/lang/Object;");
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitFieldInsn(
+ GETFIELD,
+ "com/android/tools/r8/cf/stackmap/UninitializedPutFieldTest$Main",
+ "object",
+ "Ljava/lang/Object;");
+ methodVisitor.visitMethodInsn(
+ INVOKESPECIAL,
+ "com/android/tools/r8/cf/stackmap/UninitializedPutFieldTest$Main",
+ "foo",
+ "(Ljava/lang/Object;)V",
+ false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(3, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(ACC_PRIVATE, "foo", "(Ljava/lang/Object;)V", null, null);
+ methodVisitor.visitCode();
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitLdcInsn("Main::foo");
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 2);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+ methodVisitor.visitCode();
+ methodVisitor.visitTypeInsn(
+ NEW, "com/android/tools/r8/cf/stackmap/UninitializedPutFieldTest$Main");
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitMethodInsn(
+ INVOKESPECIAL,
+ "com/android/tools/r8/cf/stackmap/UninitializedPutFieldTest$Main",
+ "<init>",
+ "()V",
+ false);
+ methodVisitor.visitInsn(POP);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 1);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+ }
+}