Merge "Do not alter already native/abstract methods while tree pruning."
diff --git a/.gitignore b/.gitignore
index 3bb46e9..1e383d2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -27,6 +27,8 @@
third_party/android_jar/lib.tar.gz
third_party/android_jar/lib-v[0-9][0-9]
third_party/android_jar/lib-v[0-9][0-9].tar.gz
+third_party/benchmarks/kotlin-benches
+third_party/benchmarks/kotlin-benches.tar.gz
third_party/benchmarks/santa-tracker
third_party/benchmarks/santa-tracker.tar.gz
third_party/desugar/desugar_*/
@@ -74,7 +76,8 @@
gradlew
gradlew.bat
gradle/*
-r8.iml
+*.dex
+*.iml
r8.ipr
r8.iws
#*#
diff --git a/AUTHORS b/AUTHORS
index 4d02335..d4aa190 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -4,3 +4,4 @@
# Name/Organization <email address>
Google Inc.
+Uber Technologies Inc.
diff --git a/LIBRARY-LICENSE b/LIBRARY-LICENSE
index 889b9ad..58b6798 100644
--- a/LIBRARY-LICENSE
+++ b/LIBRARY-LICENSE
@@ -21,31 +21,31 @@
- artifact: org.ow2.asm:asm-commons:+
name: ASM Commons
copyrightHolder: INRIA, France Telecom
- license: ASM license
+ license: BSD
licenseUrl: http://asm.ow2.org/license.html
url: http://asm.ow2.org/index.html
- artifact: org.ow2.asm:asm-tree:+
name: ASM Tree
copyrightHolder: INRIA, France Telecom
- license: ASM license
+ license: BSD
licenseUrl: http://asm.ow2.org/license.html
url: http://asm.ow2.org/index.html
- artifact: org.ow2.asm:asm-util:+
name: ASM Util
copyrightHolder: INRIA, France Telecom
- license: ASM license
+ license: BSD
licenseUrl: http://asm.ow2.org/license.html
url: http://asm.ow2.org/index.html
- artifact: org.ow2.asm:asm-analysis:+
name: ASM Util
copyrightHolder: INRIA, France Telecom
- license: ASM license
+ license: BSD
licenseUrl: http://asm.ow2.org/license.html
url: http://asm.ow2.org/index.html
- artifact: org.ow2.asm:asm:+
name: ASM Core
copyrightHolder: INRIA, France Telecom
- license: ASM license
+ license: BSD
licenseUrl: http://asm.ow2.org/license.html
url: http://asm.ow2.org/index.html
- artifact: org.jetbrains.kotlin:kotlin-stdlib:+
diff --git a/build.gradle b/build.gradle
index 52200e6..6825b7f 100644
--- a/build.gradle
+++ b/build.gradle
@@ -10,7 +10,7 @@
ext {
androidSupportVersion = '25.4.0'
apacheCommonsVersion = '1.12'
- asmVersion = '6.0'
+ asmVersion = '6.2.1'
autoValueVersion = '1.5'
espressoVersion = '3.0.0'
fastutilVersion = '7.2.0'
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index e998206..400e5ba 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -63,8 +63,6 @@
// Hidden ASM "synthetic attribute" bit we need to clear.
private static final int ACC_SYNTHETIC_ATTRIBUTE = 0x40000;
- // Descriptor used by ASM for missing annotations.
- public static final String SYNTHETIC_ANNOTATION = "Ljava/lang/Synthetic;";
private final JarApplicationReader application;
private final Consumer<DexClass> classConsumer;
@@ -461,7 +459,7 @@
private final int parameterCount;
private List<DexAnnotation> annotations = null;
private DexValue defaultAnnotation = null;
- private int fakeParameterAnnotations = 0;
+ private int annotableParameterCount = -1;
private List<List<DexAnnotation>> parameterAnnotationsLists = null;
private List<DexValue> parameterNames = null;
private List<DexValue> parameterFlags = null;
@@ -512,33 +510,30 @@
}
@Override
- public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) {
- // ASM decided to workaround a javac bug that incorrectly deals with synthesized parameter
- // annotations. However, that leads us to have different behavior than javac+jvm and
- // dx+art. The workaround is to use a non-existing descriptor "Ljava/lang/Synthetic;" for
- // exactly this case. In order to remove the workaround we ignore all annotations
- // with that descriptor. If javac is fixed, the ASM workaround will not be hit and we will
- // never see this non-existing annotation descriptor. ASM uses the same check to make
- // sure to undo their workaround for the javac bug in their MethodWriter.
- if (desc.equals(SYNTHETIC_ANNOTATION)) {
- // We can iterate through all the parameters twice. Once for visible and once for
- // invisible parameter annotations. We only record the number of fake parameter
- // annotations once.
- if (parameterAnnotationsLists == null) {
- fakeParameterAnnotations++;
- }
- return null;
+ public void visitAnnotableParameterCount(int parameterCount, boolean visible) {
+ if (annotableParameterCount != -1) {
+ // TODO(113565942): We assume that the runtime visible and runtime invisible parameter
+ // count is always the same. It doesn't have to be according to the classfile format, so
+ // we really should split it into two sets.
+ assert annotableParameterCount == parameterCount;
}
+ annotableParameterCount = parameterCount;
+ }
+
+ @Override
+ public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) {
if (parameterAnnotationsLists == null) {
- int adjustedParameterCount = parameterCount - fakeParameterAnnotations;
- parameterAnnotationsLists = new ArrayList<>(adjustedParameterCount);
- for (int i = 0; i < adjustedParameterCount; i++) {
+ if (annotableParameterCount == -1) {
+ annotableParameterCount = parameterCount;
+ }
+ parameterAnnotationsLists = new ArrayList<>(annotableParameterCount);
+ for (int i = 0; i < annotableParameterCount; i++) {
parameterAnnotationsLists.add(new ArrayList<>());
}
}
assert mv == null;
return createAnnotationVisitor(desc, visible,
- parameterAnnotationsLists.get(parameter - fakeParameterAnnotations), parent.application);
+ parameterAnnotationsLists.get(parameter), parent.application);
}
@Override
@@ -596,7 +591,7 @@
for (int i = 0; i < parameterAnnotationsLists.size(); i++) {
sets[i] = createAnnotationSet(parameterAnnotationsLists.get(i));
}
- annotationsList = new ParameterAnnotationsList(sets, fakeParameterAnnotations);
+ annotationsList = new ParameterAnnotationsList(sets);
}
InternalOptions internalOptions = parent.application.options;
if (parameterNames != null && internalOptions.canUseParameterNameAnnotations()) {
diff --git a/src/main/java/com/android/tools/r8/graph/ParameterAnnotationsList.java b/src/main/java/com/android/tools/r8/graph/ParameterAnnotationsList.java
index 23d5a38..343b392 100644
--- a/src/main/java/com/android/tools/r8/graph/ParameterAnnotationsList.java
+++ b/src/main/java/com/android/tools/r8/graph/ParameterAnnotationsList.java
@@ -60,6 +60,10 @@
this.missingParameterAnnotations = missingParameterAnnotations;
}
+ public int getAnnotableParameterCount() {
+ return size();
+ }
+
@Override
public int hashCode() {
return Arrays.hashCode(values);
diff --git a/src/main/java/com/android/tools/r8/ir/code/Phi.java b/src/main/java/com/android/tools/r8/ir/code/Phi.java
index 81e2fff..29b5e56 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Phi.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Phi.java
@@ -70,10 +70,36 @@
if (block.getPredecessors().size() == 0) {
throwUndefinedValueError();
}
+
+ List<Value> operands = new ArrayList<>(block.getPredecessors().size());
for (BasicBlock pred : block.getPredecessors()) {
EdgeType edgeType = pred.getEdgeType(block);
- // Since this read has been delayed we must provide the local info for the value.
Value operand = builder.readRegister(register, type, pred, edgeType, readType);
+ operands.add(operand);
+ }
+
+ if (readType == RegisterReadType.DEBUG) {
+ for (Value operand : operands) {
+ if (type.meet(operand.outType()) == null) {
+ BasicBlock block = getBlock();
+ InstructionListIterator it = block.listIterator();
+ Value value = new Value(builder.getValueNumberGenerator().next(), type, null);
+ Position position = block.getPosition();
+ Instruction definition = new DebugLocalUninitialized(value);
+ definition.setBlock(block);
+ definition.setPosition(position);
+ it.add(definition);
+ // Update current definition and all users.
+ block.updateCurrentDefinition(register, value, EdgeType.NON_EDGE);
+ replaceUsers(value);
+ // Remove the phi from the block.
+ block.removePhi(this);
+ return;
+ }
+ }
+ }
+
+ for (Value operand : operands) {
operand.constrainType(type);
appendOperand(operand);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index ec39754..a835c08 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.ir.code;
import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.DebugLocalInfo;
@@ -29,7 +30,11 @@
public class Value {
public void constrainType(ValueType constraint) {
- type = type.meet(constraint);
+ ValueType meet = type.meet(constraint);
+ if (meet == null) {
+ throw new CompilationError("Cannot compute meet of types: " + type + " and " + constraint);
+ }
+ type = meet;
}
public void markNonDebugLocalRead() {
diff --git a/src/main/java/com/android/tools/r8/ir/code/ValueType.java b/src/main/java/com/android/tools/r8/ir/code/ValueType.java
index 69618b3..99f949d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ValueType.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ValueType.java
@@ -4,7 +4,6 @@
package com.android.tools.r8.ir.code;
-import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.InternalCompilerError;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DexType;
@@ -50,7 +49,7 @@
}
if (isPreciseType() && other.isPreciseType()) {
// Precise types must be identical, hitting the first check above.
- throw new CompilationError("Cannot compute meet of types: " + this + " and " + other);
+ return null;
}
switch (this) {
case OBJECT:
@@ -100,11 +99,10 @@
default:
throw new Unreachable("Unexpected value-type in meet: " + this);
}
- throw new CompilationError("Cannot compute meet of types: " + this + " and " + other);
+ return null;
}
public boolean verifyCompatible(ValueType other) {
- // Computing meet will throw on incompatible types.
assert meet(other) != null;
return true;
}
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 ae2e4dc..6fbafcb 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
@@ -49,6 +49,7 @@
public class CfSourceCode implements SourceCode {
private BlockInfo currentBlockInfo;
+ private boolean hasExitingInstruction = false;
private static class TryHandlerList {
@@ -264,12 +265,14 @@
return instructionIndex;
}
// If the throwable instruction is "throw" it closes the block.
+ hasExitingInstruction |= instruction instanceof CfThrow;
return (instruction instanceof CfThrow) ? instructionIndex : -1;
}
if (isControlFlow(instruction)) {
for (int target : getTargets(instructionIndex)) {
builder.ensureNormalSuccessorBlock(instructionIndex, target);
}
+ hasExitingInstruction |= instruction.isReturn();
return instructionIndex;
}
return -1;
@@ -328,7 +331,6 @@
setLocalVariableLists();
buildArgumentInstructions(builder);
recordStateForTarget(0, state.getSnapshot());
- // TODO: addDebugLocalUninitialized + addDebugLocalStart for non-argument locals live at 0
// TODO(b/109789541): Generate method synchronization for DEX backend.
inPrelude = false;
}
@@ -385,6 +387,20 @@
builder.addDebugLocalStart(entry.getIntKey(), entry.getValue());
}
}
+
+ // If there are no explicit exits from the method (ie, this method is a loop without an explict
+ // return or an unhandled throw) then we cannot guarentee that a local live in a successor will
+ // ensure it is marked as such (via an explict 'end' marker) and thus be live in predecessors.
+ // In this case we insert an 'end' point on all explicit goto instructions ensuring that any
+ // back-edge will explicitly keep locals live at that point.
+ if (!hasExitingInstruction && code.getInstructions().get(predecessorOffset) instanceof CfGoto) {
+ assert !isExceptional;
+ for (Entry<DebugLocalInfo> entry : atSource.int2ObjectEntrySet()) {
+ if (atTarget.get(entry.getIntKey()) == entry.getValue()) {
+ builder.addDebugLocalEnd(entry.getIntKey(), entry.getValue());
+ }
+ }
+ }
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 22084a0..bb29da9 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -463,10 +463,12 @@
it.previous();
for (List<Value> values : uninitializedDebugLocalValues.values()) {
for (Value value : values) {
- Instruction def = new DebugLocalUninitialized(value);
- def.setBlock(entryBlock);
- def.setPosition(Position.none());
- it.add(def);
+ if (value.isUsed()) {
+ Instruction def = new DebugLocalUninitialized(value);
+ def.setBlock(entryBlock);
+ def.setPosition(Position.none());
+ it.add(def);
+ }
}
}
}
@@ -1891,7 +1893,7 @@
}
// Move all normal successors to the new block.
- currentBlockInfo.normalSuccessors.forEach(info::addNormalSuccessor);
+ currentBlockInfo.normalSuccessors.forEach((Integer offset) -> info.addNormalSuccessor(offset));
currentBlockInfo.normalSuccessors.clear();
// Link the two blocks.
@@ -2237,6 +2239,10 @@
return type != NumericType.FLOAT && type != NumericType.DOUBLE && type != NumericType.LONG;
}
+ public ValueNumberGenerator getValueNumberGenerator() {
+ return valueNumberGenerator;
+ }
+
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
index 7099296..34e43e3 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
@@ -190,6 +190,8 @@
private final DexMethod originalMethod;
private final Position callerPosition;
+ private boolean hasExitingInstruction = false;
+
public JarSourceCode(
DexType clazz,
MethodNode node,
@@ -400,6 +402,7 @@
}
private void computeBlockEntryJarStates(IRBuilder builder) {
+ hasExitingInstruction = false;
Int2ReferenceSortedMap<BlockInfo> CFG = builder.getCFG();
Queue<JarStateWorklistItem> worklist = new LinkedList<>();
BlockInfo entry = CFG.get(IRBuilder.INITIAL_BLOCK_OFFSET);
@@ -419,21 +422,26 @@
blockEnd += 1;
}
for (int i = item.instructionIndex; i < blockEnd; ++i) {
- state.beginTransaction(i + 1, i + 1 != blockEnd);
+ boolean hasNextInstruction = i + 1 != blockEnd;
+ state.beginTransaction(i + 1, hasNextInstruction);
AbstractInsnNode insn = getInstruction(i);
updateState(insn);
state.endTransaction();
+ if (!hasNextInstruction) {
+ hasExitingInstruction |=
+ isReturn(insn) || (isThrow(insn) && item.blockInfo.exceptionalSuccessors.isEmpty());
+ }
}
// At the end of the current block, propagate the state to all successors and add the ones
// that changed to the worklist.
- item.blockInfo.normalSuccessors.iterator().forEachRemaining(offset -> {
+ item.blockInfo.normalSuccessors.iterator().forEachRemaining((Integer offset) -> {
if (state.recordStateForTarget(offset)) {
if (offset >= 0) {
worklist.add(new JarStateWorklistItem(CFG.get(offset.intValue()), offset));
}
}
});
- item.blockInfo.exceptionalSuccessors.iterator().forEachRemaining(offset -> {
+ item.blockInfo.exceptionalSuccessors.iterator().forEachRemaining((Integer offset) -> {
if (state.recordStateForExceptionalTarget(offset)) {
if (offset >= 0) {
worklist.add(new JarStateWorklistItem(CFG.get(offset.intValue()), offset));
@@ -488,6 +496,18 @@
for (Local toOpen : localChange.getLocalsToOpen()) {
builder.addDebugLocalStart(toOpen.slot.register, toOpen.info);
}
+
+ // If there are no explicit exits from the method (ie, this method is a loop without an explict
+ // return or an unhandled throw) then we cannot guarentee that a local live in a successor will
+ // ensure it is marked as such (via an explict 'end' marker) and thus be live in predecessors.
+ // In this case we insert an 'end' point on all explicit goto instructions ensuring that any
+ // back-edge will explicitly keep locals live at that point.
+ if (!hasExitingInstruction && getInstruction(predecessorOffset).getOpcode() == Opcodes.GOTO) {
+ assert !isExceptional;
+ for (Local local : localChange.getLocalsToPreserve()) {
+ builder.addDebugLocalEnd(local.slot.register, local.info);
+ }
+ }
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/JarState.java b/src/main/java/com/android/tools/r8/ir/conversion/JarState.java
index 22faeb1..cd08984 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/JarState.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/JarState.java
@@ -271,6 +271,17 @@
this.state = state;
}
+ public List<Local> getLocalsToPreserve() {
+ List<Local> live = new ArrayList<>(atExit.live.size());
+ for (LocalNodeInfo liveAtExit : atExit.live) {
+ if (atEntry.isLive(liveAtExit)) {
+ int register = state.getLocalRegister(liveAtExit.node.index, liveAtExit.type);
+ live.add(new Local(new Slot(register, liveAtExit.type), liveAtExit.info));
+ }
+ }
+ return live;
+ }
+
public List<Local> getLocalsToClose() {
List<Local> toClose = new ArrayList<>(atExit.live.size());
for (LocalNodeInfo liveAtExit : atExit.live) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index b9ba46f..b91d9c7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -1422,18 +1422,24 @@
// Check if the static put is a constant derived from the class holding the method.
// This checks for java.lang.Class.getName and java.lang.Class.getSimpleName.
- private boolean isClassNameConstant(DexEncodedMethod method, StaticPut put) {
+ private boolean isClassNameConstantOf(DexClass clazz, StaticPut put) {
if (put.getField().type != dexItemFactory.stringType) {
return false;
}
- if (put.inValue().definition != null && put.inValue().definition.isInvokeVirtual()) {
- InvokeVirtual invoke = put.inValue().definition.asInvokeVirtual();
+ if (put.inValue().definition != null) {
+ return isClassNameConstantOf(clazz, put.inValue().definition);
+ }
+ return false;
+ }
+
+ private boolean isClassNameConstantOf(DexClass clazz, Instruction instruction) {
+ if (instruction.isInvokeVirtual()) {
+ InvokeVirtual invoke = instruction.asInvokeVirtual();
if ((invoke.getInvokedMethod() == dexItemFactory.classMethods.getSimpleName
|| invoke.getInvokedMethod() == dexItemFactory.classMethods.getName)
&& !invoke.inValues().get(0).isPhi()
&& invoke.inValues().get(0).definition.isConstClass()
- && invoke.inValues().get(0).definition.asConstClass().getValue()
- == method.method.getHolder()) {
+ && invoke.inValues().get(0).definition.asConstClass().getValue() == clazz.type) {
return true;
}
}
@@ -1445,62 +1451,21 @@
return;
}
- // Collect relevant static put which are dominated by the exit block, and not dominated by a
- // static get.
- // This does not check for instructions that can throw. However, as classes which throw in the
- // class initializer are never visible to the program (see Java Virtual Machine Specification
- // section 5.5, https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.5), this
- // does not matter (except maybe for removal of const-string instructions, but that is
- // acceptable).
- if (code.computeNormalExitBlocks().isEmpty()) {
+ DexClass clazz = definitionFor(method.method.getHolder());
+ if (clazz == null) {
return;
}
- DexClass clazz = definitionFor(method.method.getHolder());
- assert clazz != null;
- DominatorTree dominatorTree = new DominatorTree(code);
+
+ // Collect straight-line static puts up to the first side-effect that is not
+ // a static put on a field on this class with a value that can be hoisted to
+ // the field initial value.
Set<StaticPut> puts = Sets.newIdentityHashSet();
- Map<DexField, StaticPut> dominatingPuts = Maps.newIdentityHashMap();
- for (BasicBlock block : dominatorTree.normalExitDominatorBlocks()) {
- InstructionListIterator iterator = block.listIterator(block.getInstructions().size());
- while (iterator.hasPrevious()) {
- Instruction current = iterator.previous();
- if (current.isStaticPut()) {
- StaticPut put = current.asStaticPut();
- DexField field = put.getField();
- if (clazz.definesStaticField(field)) {
- if (put.inValue().isConstant()) {
- if ((field.type.isClassType() || field.type.isArrayType())
- && put.inValue().isZero()) {
- // Collect put of zero as a potential default value.
- dominatingPuts.putIfAbsent(put.getField(), put);
- puts.add(put);
- } else if (field.type.isPrimitiveType() || field.type == dexItemFactory.stringType) {
- // Collect put as a potential default value.
- dominatingPuts.putIfAbsent(put.getField(), put);
- puts.add(put);
- }
- } else if (isClassNameConstant(method, put)) {
- // Collect put of class name constant as a potential default value.
- dominatingPuts.putIfAbsent(put.getField(), put);
- puts.add(put);
- }
- }
- }
- if (current.isStaticGet()) {
- // If a static field is read, any collected potential default value cannot be a
- // default value.
- DexField field = current.asStaticGet().getField();
- if (dominatingPuts.containsKey(field)) {
- dominatingPuts.remove(field);
- Iterables.removeIf(puts, put -> put.getField() == field);
- }
- }
- }
- }
+ Map<DexField, StaticPut> finalFieldPut = Maps.newIdentityHashMap();
+ computeUnnecessaryStaticPuts(code, method, clazz, puts, finalFieldPut);
if (!puts.isEmpty()) {
// Set initial values for static fields from the definitive static put instructions collected.
- for (StaticPut put : dominatingPuts.values()) {
+ for (StaticPut put : finalFieldPut.values()) {
DexField field = put.getField();
DexEncodedField encodedField = appInfo.definitionFor(field);
if (field.type == dexItemFactory.stringType) {
@@ -1554,24 +1519,22 @@
}
}
- // Remove the static put instructions now replaced by static filed initial values.
+ // Remove the static put instructions now replaced by static field initial values.
List<Instruction> toRemove = new ArrayList<>();
- for (BasicBlock block : dominatorTree.normalExitDominatorBlocks()) {
- InstructionListIterator iterator = block.listIterator();
- while (iterator.hasNext()) {
- Instruction current = iterator.next();
- if (current.isStaticPut() && puts.contains(current.asStaticPut())) {
- iterator.remove();
- // Collect, for removal, the instruction that created the value for the static put,
- // if all users are gone. This is done even if these instructions can throw as for
- // the current patterns matched these exceptions are not detectable.
- StaticPut put = current.asStaticPut();
- if (put.inValue().uniqueUsers().size() == 0) {
- if (put.inValue().isConstString()) {
- toRemove.add(put.inValue().definition);
- } else if (put.inValue().definition.isInvokeVirtual()) {
- toRemove.add(put.inValue().definition);
- }
+ InstructionIterator iterator = code.instructionIterator();
+ while (iterator.hasNext()) {
+ Instruction current = iterator.next();
+ if (current.isStaticPut() && puts.contains(current.asStaticPut())) {
+ iterator.remove();
+ // Collect, for removal, the instruction that created the value for the static put,
+ // if all users are gone. This is done even if these instructions can throw as for
+ // the current patterns matched these exceptions are not detectable.
+ StaticPut put = current.asStaticPut();
+ if (put.inValue().uniqueUsers().size() == 0) {
+ if (put.inValue().isConstString()) {
+ toRemove.add(put.inValue().definition);
+ } else if (put.inValue().definition.isInvokeVirtual()) {
+ toRemove.add(put.inValue().definition);
}
}
}
@@ -1579,18 +1542,70 @@
// Remove the instructions collected for removal.
if (toRemove.size() > 0) {
- for (BasicBlock block : dominatorTree.normalExitDominatorBlocks()) {
- InstructionListIterator iterator = block.listIterator();
- while (iterator.hasNext()) {
- if (toRemove.contains(iterator.next())) {
- iterator.remove();
- }
+ iterator = code.instructionIterator();
+ while (iterator.hasNext()) {
+ if (toRemove.contains(iterator.next())) {
+ iterator.remove();
}
}
}
}
}
+ private void computeUnnecessaryStaticPuts(IRCode code, DexEncodedMethod clinit, DexClass clazz,
+ Set<StaticPut> puts, Map<DexField, StaticPut> finalFieldPut) {
+ final int color = code.reserveMarkingColor();
+ try {
+ BasicBlock block = code.blocks.getFirst();
+ while (!block.isMarked(color) && block.getPredecessors().size() <= 1) {
+ block.mark(color);
+ InstructionListIterator it = block.listIterator();
+ while (it.hasNext()) {
+ Instruction instruction = it.next();
+ if (instructionHasSideEffects(instruction)) {
+ if (isClassNameConstantOf(clazz, instruction)) {
+ continue;
+ } else if (instruction.isStaticPut()) {
+ StaticPut put = instruction.asStaticPut();
+ if (put.getField().clazz != clazz.type) {
+ // Can cause clinit on another class which can read uninitialized static fields
+ // of this class.
+ return;
+ }
+ DexField field = put.getField();
+ if (clazz.definesStaticField(field)) {
+ if (put.inValue().isConstant()) {
+ if ((field.type.isClassType() || field.type.isArrayType())
+ && put.inValue().isZero()) {
+ finalFieldPut.put(put.getField(), put);
+ puts.add(put);
+ } else if (field.type.isPrimitiveType()
+ || field.type == dexItemFactory.stringType) {
+ finalFieldPut.put(put.getField(), put);
+ puts.add(put);
+ }
+ } else if (isClassNameConstantOf(clazz, put)) {
+ // Collect put of class name constant as a potential default value.
+ finalFieldPut.put(put.getField(), put);
+ puts.add(put);
+ }
+ }
+ } else if (!(instruction.isConstString() || instruction.isConstClass())) {
+ // Allow const string and const class which can only throw exceptions as their
+ // side-effect. Bail out for anything else.
+ return;
+ }
+ }
+ }
+ if (block.exit().isGoto()) {
+ block = block.exit().asGoto().getTarget();
+ }
+ }
+ } finally {
+ code.returnMarkingColor(color);
+ }
+ }
+
public DexClass definitionFor(DexType type) {
return converter.definitionFor(type);
}
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index 1498be4..65bb387 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -34,7 +34,6 @@
import com.android.tools.r8.graph.DexValue.UnknownDexValue;
import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.graph.InnerClassAttribute;
-import com.android.tools.r8.graph.JarClassFileReader;
import com.android.tools.r8.graph.ParameterAnnotationsList;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.naming.ProguardMapSupplier;
@@ -283,19 +282,19 @@
private void writeParameterAnnotations(
MethodVisitor visitor, ParameterAnnotationsList parameterAnnotations) {
+ // TODO(113565942): We currently assume that the annotable parameter count
+ // it the same for visible and invisible annotations. That doesn't actually
+ // seem to be the case in the class file format.
+ visitor.visitAnnotableParameterCount(
+ parameterAnnotations.getAnnotableParameterCount(), true);
+ visitor.visitAnnotableParameterCount(
+ parameterAnnotations.getAnnotableParameterCount(), false);
+
for (int i = 0; i < parameterAnnotations.size(); i++) {
- if (parameterAnnotations.isMissing(i)) {
- AnnotationVisitor av =
- visitor.visitParameterAnnotation(i, JarClassFileReader.SYNTHETIC_ANNOTATION, false);
- if (av != null) {
- av.visitEnd();
- }
- } else {
- int iFinal = i;
- writeAnnotations(
- (d, vis) -> visitor.visitParameterAnnotation(iFinal, d, vis),
- parameterAnnotations.get(i).annotations);
- }
+ int iFinal = i;
+ writeAnnotations(
+ (d, vis) -> visitor.visitParameterAnnotation(iFinal, d, vis),
+ parameterAnnotations.get(i).annotations);
}
}
diff --git a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
index a846ea7..a8ec999 100644
--- a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
@@ -368,7 +368,7 @@
assert name.endsWith(CLASS_EXTENSION) :
"Name " + name + " must have " + CLASS_EXTENSION + " suffix";
String descriptor = name.substring(0, name.length() - CLASS_EXTENSION.length());
- if (descriptor.contains(".")) {
+ if (descriptor.indexOf(JAVA_PACKAGE_SEPARATOR) != -1) {
throw new CompilationError("Unexpected class file name: " + name);
}
return 'L' + descriptor + ';';
@@ -383,6 +383,6 @@
public static String getPathFromJavaType(String typeName) {
assert isValidJavaType(typeName);
- return typeName.replace(".", "/") + ".class";
+ return typeName.replace(JAVA_PACKAGE_SEPARATOR, DESCRIPTOR_PACKAGE_SEPARATOR) + ".class";
}
}
diff --git a/src/test/java/com/android/tools/r8/AsmTestBase.java b/src/test/java/com/android/tools/r8/AsmTestBase.java
index cb4a4af..35767fb 100644
--- a/src/test/java/com/android/tools/r8/AsmTestBase.java
+++ b/src/test/java/com/android/tools/r8/AsmTestBase.java
@@ -144,14 +144,6 @@
byte[] dump() throws Exception;
}
- protected static Class loadClassFromAsmClass(AsmDump asmDump) {
- try {
- return loadClassFromDump(asmDump.dump());
- } catch (Exception e) {
- throw new ClassFormatError(e.toString());
- }
- }
-
protected static byte[] getBytesFromAsmClass(AsmDump asmDump) {
try {
return asmDump.dump();
diff --git a/src/test/java/com/android/tools/r8/CfFrontendExamplesTest.java b/src/test/java/com/android/tools/r8/CfFrontendExamplesTest.java
index 70e8445..6f40632 100644
--- a/src/test/java/com/android/tools/r8/CfFrontendExamplesTest.java
+++ b/src/test/java/com/android/tools/r8/CfFrontendExamplesTest.java
@@ -205,8 +205,8 @@
"regress_37658666.Regress",
(expectedBytes, actualBytes) -> {
// javac emits LDC(-0.0f) instead of the shorter FCONST_0 FNEG emitted by CfConstNumber.
- String ldc = "mv.visitLdcInsn(new Float(\"-0.0\"));";
- String constNeg = "mv.visitInsn(FCONST_0);\nmv.visitInsn(FNEG);";
+ String ldc = "methodVisitor.visitLdcInsn(new Float(\"-0.0\"));";
+ String constNeg = "methodVisitor.visitInsn(FCONST_0);\nmethodVisitor.visitInsn(FNEG);";
assertEquals(asmToString(expectedBytes).replace(ldc, constNeg), asmToString(actualBytes));
});
}
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 182326e..bf900a9 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -6,6 +6,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import static org.objectweb.asm.Opcodes.ASM6;
import com.android.tools.r8.DataResourceProvider.Visitor;
import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
@@ -52,6 +53,8 @@
import java.util.zip.ZipOutputStream;
import org.junit.Rule;
import org.junit.rules.TemporaryFolder;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
public class TestBase {
public enum Backend {
@@ -602,7 +605,7 @@
protected ProcessResult runOnJavaRawNoVerify(String main, List<byte[]> classes, List<String> args)
throws IOException {
- return ToolHelper.runJavaNoVerify(Collections.singletonList(writeToZip(classes)), main, args);
+ return ToolHelper.runJavaNoVerify(Collections.singletonList(writeToJar(classes)), main, args);
}
protected ProcessResult runOnJavaRaw(String main, byte[]... classes) throws IOException {
@@ -615,7 +618,7 @@
mainAndArgs.add(main);
mainAndArgs.addAll(args);
return ToolHelper.runJava(
- Collections.singletonList(writeToZip(classes)), mainAndArgs.toArray(new String[0]));
+ Collections.singletonList(writeToJar(classes)), mainAndArgs.toArray(new String[0]));
}
protected ProcessResult runOnJavaRaw(AndroidApp app, String mainClass, List<String> args)
@@ -628,33 +631,57 @@
return ToolHelper.runJava(out, mainAndArgs.toArray(new String[0]));
}
- protected Path writeToZip(List<byte[]> classes) throws IOException {
- File result = temp.newFile("tmp.zip");
- try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(result.toPath(),
- StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING))) {
+ private String extractClassName(byte[] ccc) {
+ class ClassNameExtractor extends ClassVisitor {
+ private String className;
+
+ private ClassNameExtractor() {
+ super(ASM6);
+ }
+
+ @Override
+ public void visit(
+ int version,
+ int access,
+ String name,
+ String signature,
+ String superName,
+ String[] interfaces) {
+ className =
+ name.replace(
+ DescriptorUtils.DESCRIPTOR_PACKAGE_SEPARATOR,
+ DescriptorUtils.JAVA_PACKAGE_SEPARATOR);
+ }
+
+ String getClassName() {
+ return className;
+ }
+ }
+
+ ClassReader reader = new ClassReader(ccc);
+ ClassNameExtractor extractor = new ClassNameExtractor();
+ reader.accept(
+ extractor, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
+ return extractor.getClassName();
+ }
+
+ protected Path writeToJar(List<byte[]> classes) throws IOException {
+ Path result = File.createTempFile("junit", ".jar", temp.getRoot()).toPath();
+ try (ZipOutputStream out =
+ new ZipOutputStream(
+ Files.newOutputStream(
+ result, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING))) {
for (byte[] clazz : classes) {
- String name = loadClassFromDump(clazz).getTypeName();
+ String name = extractClassName(clazz);
ZipUtils.writeToZipStream(
out, DescriptorUtils.getPathFromJavaType(name), clazz, ZipEntry.STORED);
}
}
- return result.toPath();
+ return result;
}
- protected Path writeToZip(JasminBuilder jasminBuilder) throws Exception {
- return writeToZip(jasminBuilder.buildClasses());
- }
-
- protected static Class loadClassFromDump(byte[] dump) {
- return new DumpLoader().loadClass(dump);
- }
-
- private static class DumpLoader extends ClassLoader {
-
- @SuppressWarnings("deprecation")
- public Class loadClass(byte[] clazz) {
- return defineClass(clazz, 0, clazz.length);
- }
+ protected Path writeToJar(JasminBuilder jasminBuilder) throws Exception {
+ return writeToJar(jasminBuilder.buildClasses());
}
/**
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/AccessRelaxationTestBase.java b/src/test/java/com/android/tools/r8/accessrelaxation/AccessRelaxationTestBase.java
index 80541e8..6387b81 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/AccessRelaxationTestBase.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/AccessRelaxationTestBase.java
@@ -9,52 +9,62 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
-import com.android.tools.r8.DexIndexedConsumer;
import com.android.tools.r8.R8Command;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.VmTestRunner;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import org.junit.runner.RunWith;
-@RunWith(VmTestRunner.class)
abstract class AccessRelaxationTestBase extends TestBase {
- static R8Command.Builder loadProgramFiles(Iterable<Class> classes) {
+ final Backend backend;
+
+ AccessRelaxationTestBase(Backend backend) {
+ this.backend = backend;
+ }
+
+ static R8Command.Builder loadProgramFiles(Backend backend, Iterable<Class> classes) {
R8Command.Builder builder = R8Command.builder();
for (Class clazz : classes) {
builder.addProgramFiles(ToolHelper.getClassFileForTestClass(clazz));
}
- builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
- builder.setMinApiLevel(ToolHelper.getMinApiLevelForDexVm().getLevel());
+ builder.setProgramConsumer(emptyConsumer(backend)).addLibraryFiles(runtimeJar(backend));
+ if (backend == Backend.DEX) {
+ builder.setMinApiLevel(ToolHelper.getMinApiLevelForDexVm().getLevel());
+ }
return builder;
}
- static R8Command.Builder loadProgramFiles(Package p, Class... classes) throws Exception {
+ static R8Command.Builder loadProgramFiles(Backend backend, Package p, Class... classes)
+ throws Exception {
R8Command.Builder builder = R8Command.builder();
builder.addProgramFiles(ToolHelper.getClassFilesForTestPackage(p));
for (Class clazz : classes) {
builder.addProgramFiles(ToolHelper.getClassFileForTestClass(clazz));
}
- builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
- builder.setMinApiLevel(ToolHelper.getMinApiLevelForDexVm().getLevel());
+ builder.setProgramConsumer(emptyConsumer(backend)).addLibraryFiles(runtimeJar(backend));
+ if (backend == Backend.DEX) {
+ builder.setMinApiLevel(ToolHelper.getMinApiLevelForDexVm().getLevel());
+ }
return builder;
}
- void compareJvmAndArt(AndroidApp app, Class mainClass) throws Exception {
+ void compareReferenceJVMAndProcessed(AndroidApp app, Class mainClass) throws Exception {
// Run on Jvm.
String jvmOutput = runOnJava(mainClass);
-
- // Run on Art to check generated code against verifier.
- String artOutput = runOnArt(app, mainClass);
-
- String adjustedArtOutput = artOutput.replace(
- "java.lang.IncompatibleClassChangeError", "java.lang.IllegalAccessError");
- assertEquals(jvmOutput, adjustedArtOutput);
+ String output;
+ if (backend == Backend.DEX) {
+ output = runOnArt(app, mainClass);
+ } else {
+ assert backend == Backend.CF;
+ output = runOnJava(app, mainClass);
+ }
+ String adjustedOutput =
+ output.replace("java.lang.IncompatibleClassChangeError", "java.lang.IllegalAccessError");
+ assertEquals(jvmOutput, adjustedOutput);
}
static void assertPublic(CodeInspector codeInspector, Class clazz, MethodSignature signature) {
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/ConstructorRelaxationTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/ConstructorRelaxationTest.java
index 48b511c..2b66d93 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/ConstructorRelaxationTest.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/ConstructorRelaxationTest.java
@@ -15,8 +15,12 @@
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.List;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
class L1 {
private final String x;
@@ -154,16 +158,26 @@
}
}
+@RunWith(Parameterized.class)
public final class ConstructorRelaxationTest extends AccessRelaxationTestBase {
private static final String INIT= "<init>";
private static final List<Class> CLASSES =
ImmutableList.of(L1.class, L2_1.class, L2_2.class, L3_1.class, L3_2.class);
+ @Parameterized.Parameters(name = "Backend: {0}")
+ public static Collection<Backend> data() {
+ return Arrays.asList(Backend.values());
+ }
+
+ public ConstructorRelaxationTest(Backend backend) {
+ super(backend);
+ }
+
@Test
public void test() throws Exception {
Class mainClass = CtorTestMain.class;
R8Command.Builder builder =
- loadProgramFiles(Iterables.concat(CLASSES, ImmutableList.of(mainClass)));
+ loadProgramFiles(backend, Iterables.concat(CLASSES, ImmutableList.of(mainClass)));
builder.addProguardConfiguration(
ImmutableList.of(
"-keep class " + mainClass.getCanonicalName() + "{",
@@ -186,7 +200,7 @@
options.enableInlining = false;
options.enableVerticalClassMerging = false;
});
- compareJvmAndArt(app, mainClass);
+ compareReferenceJVMAndProcessed(app, mainClass);
CodeInspector codeInspector = new CodeInspector(app);
for (Class clazz : CLASSES) {
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/NonConstructorRelaxationTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/NonConstructorRelaxationTest.java
index 5b641dc..c63f425 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/NonConstructorRelaxationTest.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/NonConstructorRelaxationTest.java
@@ -20,15 +20,30 @@
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
+import java.util.Arrays;
+import java.util.Collection;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+@RunWith(Parameterized.class)
public final class NonConstructorRelaxationTest extends AccessRelaxationTestBase {
+
+ @Parameterized.Parameters(name = "Backend: {0}")
+ public static Collection<Backend> data() {
+ return Arrays.asList(Backend.values());
+ }
+
+ public NonConstructorRelaxationTest(Backend backend) {
+ super(backend);
+ }
+
private static final String STRING = "java.lang.String";
@Test
public void testStaticMethodRelaxation() throws Exception {
Class mainClass = C.class;
- R8Command.Builder builder = loadProgramFiles(mainClass.getPackage());
+ R8Command.Builder builder = loadProgramFiles(backend, mainClass.getPackage());
// Note: we use '-checkdiscard' to indirectly check that the access relaxation is
// done which leads to inlining of all pB*** methods so they are removed. Without
@@ -60,7 +75,7 @@
Origin.unknown());
AndroidApp app = ToolHelper.runR8(builder.build());
- compareJvmAndArt(app, mainClass);
+ compareReferenceJVMAndProcessed(app, mainClass);
CodeInspector codeInspector = new CodeInspector(app);
assertPublic(codeInspector, A.class,
@@ -82,7 +97,7 @@
@Test
public void testInstanceMethodRelaxation() throws Exception {
Class mainClass = TestMain.class;
- R8Command.Builder builder = loadProgramFiles(mainClass.getPackage());
+ R8Command.Builder builder = loadProgramFiles(backend, mainClass.getPackage());
builder.addProguardConfiguration(
ImmutableList.of(
@@ -108,7 +123,7 @@
Origin.unknown());
AndroidApp app = ToolHelper.runR8(builder.build());
- compareJvmAndArt(app, mainClass);
+ compareReferenceJVMAndProcessed(app, mainClass);
CodeInspector codeInspector = new CodeInspector(app);
assertPublic(codeInspector, Base.class,
diff --git a/src/test/java/com/android/tools/r8/debug/DebugStreamComparator.java b/src/test/java/com/android/tools/r8/debug/DebugStreamComparator.java
index 3a83e30..83dbd9e 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugStreamComparator.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugStreamComparator.java
@@ -83,10 +83,9 @@
}
currentLine = nextLine;
while (frameEntryLines.size() > nextDepth + 1) {
- // If the depth decreases by more than one we have popped the filtered frames.
- // Verify they are placeholder lines.
- int placeholder = frameEntryLines.pop();
- assert placeholder == PLACEHOLDER_LINE;
+ // If the depth decreases by more than one we have popped the filtered frames or an
+ // exception has unwinded the stack.
+ frameEntryLines.pop();
}
int lineOnEntry = frameEntryLines.pop();
assert nextDepth == frameEntryLines.size();
diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
index 50f67d4..8af8922 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -1171,6 +1171,22 @@
Assert.fail("expected local '" + localName + "' is not present at " + locationString);
}
+ private void checkIncorrectLocal(String localName, Value expected, Value actual) {
+ if (expected.equals(actual)) {
+ return;
+ }
+ String locationString = convertCurrentLocationToString();
+ Assert.fail(
+ "Incorrect value for local '"
+ + localName
+ + "' at "
+ + locationString
+ + ", expected "
+ + expected
+ + ", actual "
+ + actual);
+ }
+
@Override
public void checkNoLocal(String localName) {
Optional<Variable> localVar = getVariableAt(mirror, getLocation(), localName);
@@ -1207,8 +1223,7 @@
assert valuesCount == 1;
Value localValue = replyPacket.getNextValueAsValue();
- Assert.assertEquals("Incorrect value for local '" + localName + "'",
- expectedValue, localValue);
+ checkIncorrectLocal(localName, expectedValue, localValue);
}
/**
diff --git a/src/test/java/com/android/tools/r8/debug/NonExitingMethodTest.java b/src/test/java/com/android/tools/r8/debug/NonExitingMethodTest.java
new file mode 100644
index 0000000..967b7aa
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/NonExitingMethodTest.java
@@ -0,0 +1,33 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.debug;
+
+public class NonExitingMethodTest {
+
+ private static int bValue = 0;
+
+ private static int b() {
+ if (bValue == 1) throw new RuntimeException();
+ return ++bValue;
+ }
+
+ public void foo(int arg) {
+ int x = 1;
+ int y = 2;
+ while (true) {
+ int z = b();
+ x = y;
+ y = z;
+ }
+ }
+
+ public static void main(String[] args) {
+ try {
+ new NonExitingMethodTest().foo(42);
+ } catch (RuntimeException e) {
+ return;
+ }
+ throw new RuntimeException("Expected exception...");
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/NonExitingMethodTestRunner.java b/src/test/java/com/android/tools/r8/debug/NonExitingMethodTestRunner.java
new file mode 100644
index 0000000..3b78abd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/NonExitingMethodTestRunner.java
@@ -0,0 +1,60 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.debug;
+
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.ToolHelper;
+import java.io.IOException;
+import java.nio.file.Path;
+import org.junit.Assume;
+import org.junit.Test;
+
+public class NonExitingMethodTestRunner extends DebugTestBase {
+
+ public static final Class CLASS = NonExitingMethodTest.class;
+ public static final String FILE = CLASS.getSimpleName() + ".java";
+
+ private static Path getClassFilePath() {
+ return ToolHelper.getClassFileForTestClass(CLASS);
+ }
+
+ public DebugTestConfig cfConfig() {
+ return new CfDebugTestConfig(ToolHelper.getClassPathForTests());
+ }
+
+ public DebugTestConfig d8Config() {
+ return new D8DebugTestConfig().compileAndAdd(temp, getClassFilePath());
+ }
+
+ public DebugTestConfig r8CfConfig() throws CompilationFailedException, IOException {
+ Path path = temp.getRoot().toPath().resolve("out.jar");
+ ToolHelper.runR8(
+ R8Command.builder()
+ .setMode(CompilationMode.DEBUG)
+ .addProgramFiles(getClassFilePath())
+ .setProgramConsumer(new ClassFileConsumer.ArchiveConsumer(path))
+ .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
+ .build(),
+ options -> options.enableCfFrontend = true);
+ return new CfDebugTestConfig().addPaths(path);
+ }
+
+ @Test
+ public void test() throws Exception {
+ Assume.assumeTrue(
+ "Skipping test "
+ + testName.getMethodName()
+ + " because debug tests are not yet supported on Windows",
+ !ToolHelper.isWindows());
+ new DebugStreamComparator()
+ .add("CF", streamDebugTest(cfConfig(), CLASS.getCanonicalName(), NO_FILTER))
+ .add("D8", streamDebugTest(d8Config(), CLASS.getCanonicalName(), NO_FILTER))
+ .add("R8/CF", streamDebugTest(r8CfConfig(), CLASS.getCanonicalName(), NO_FILTER))
+ .setFilter(s -> s.getSourceFile().equals(FILE))
+ .compare();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java b/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
index 4405565..060d87b 100644
--- a/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
+++ b/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
@@ -324,7 +324,7 @@
"return"
);
- Path output = writeToZip(builder);
+ Path output = writeToJar(builder);
addExtraClasspath(output);
return JASMIN_MAIN_CLASS;
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
index a3aa5e1..036d960 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
@@ -476,7 +476,7 @@
"return"
);
- Path javaOutput = writeToZip(jasminBuilder);
+ Path javaOutput = writeToJar(jasminBuilder);
ProcessResult javaResult = ToolHelper.runJava(javaOutput, "Foo");
if (javaResult.exitCode != 0) {
System.err.println(javaResult.stderr);
diff --git a/src/test/java/com/android/tools/r8/regress/b113326860/B113326860.java b/src/test/java/com/android/tools/r8/regress/b113326860/B113326860.java
new file mode 100644
index 0000000..294dbbc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b113326860/B113326860.java
@@ -0,0 +1,159 @@
+// Copyright (c) 2018 the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.regress.b113326860;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.code.Sput;
+import com.android.tools.r8.code.SputBoolean;
+import com.android.tools.r8.code.SputObject;
+import com.android.tools.r8.ir.code.SingleConstant;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+
+class TestClassDoOptimize {
+ private static boolean b = false;
+ private static int i = 3;
+ private static Class clazz;
+ static {
+ clazz = TestClassDoOptimize.class;
+ b = true;
+ i = 42;
+ }
+}
+
+class TestClassDoNotOptimize {
+
+ private static boolean initialized = false;
+ private static final TestClassDoNotOptimize INSTANCE;
+
+ TestClassDoNotOptimize() {
+ if (!initialized) {
+ System.out.println("Not initialized as expected");
+ }
+ }
+
+ static {
+ INSTANCE = new TestClassDoNotOptimize();
+ initialized = true;
+ }
+}
+
+class TestClassDoNotOptimize2 {
+ static boolean b = false;
+ static {
+ boolean forcingOtherClassInit = TestClassDoNotOptimize3.b;
+ b = true;
+ }
+
+ static void m() {
+ System.out.println(b);
+ }
+}
+
+class TestClassDoNotOptimize3 {
+ static boolean b = false;
+ static {
+ TestClassDoNotOptimize2.m();
+ }
+}
+
+class TestDoWhileLoop {
+ static int i = 0;
+ static int j = 0;
+ static {
+ do {
+ i = 42;
+ System.out.println(i);
+ j = j + 1;
+ i = 10;
+ } while (j < 10);
+ }
+
+ public static void main(String[] args) {
+ System.out.println(TestDoWhileLoop.i);
+ }
+}
+
+public class B113326860 {
+
+ private CodeInspector compileTestClasses(List<Class> classes)
+ throws IOException, CompilationFailedException, ExecutionException {
+ D8Command.Builder builder = D8Command.builder().setMode(CompilationMode.RELEASE);
+ for (Class c : classes) {
+ builder.addClassProgramData(ToolHelper.getClassAsBytes(c), Origin.unknown());
+ }
+ AndroidApp app = ToolHelper.runD8(builder);
+ return new CodeInspector(app);
+ }
+
+ @Test
+ public void optimizedClassInitializer()
+ throws IOException, CompilationFailedException, ExecutionException {
+ CodeInspector inspector = compileTestClasses(ImmutableList.of(TestClassDoOptimize.class));
+ ClassSubject clazz = inspector.clazz(TestClassDoOptimize.class);
+ assertThat(clazz, isPresent());
+ MethodSubject method = clazz.method("void", "<clinit>", ImmutableList.of());
+ assertThat(method, isPresent());
+ assertFalse(Arrays.stream(method.getMethod().getCode().asDexCode().instructions)
+ .anyMatch(i -> i instanceof SputBoolean || i instanceof Sput));
+ assertTrue(Arrays.stream(method.getMethod().getCode().asDexCode().instructions)
+ .anyMatch(i -> i instanceof SputObject));
+ }
+
+ @Test
+ public void nonOptimizedClassInitializer()
+ throws ExecutionException, CompilationFailedException, IOException {
+ CodeInspector inspector =
+ compileTestClasses(ImmutableList.of(TestClassDoNotOptimize.class));
+ ClassSubject clazz = inspector.clazz(TestClassDoNotOptimize.class);
+ assertThat(clazz, isPresent());
+ MethodSubject method = clazz.method("void", "<clinit>", ImmutableList.of());
+ assertThat(method, isPresent());
+ assertTrue(Arrays.stream(method.getMethod().getCode().asDexCode().instructions)
+ .anyMatch(i -> i instanceof SputBoolean));
+ }
+
+ @Test
+ public void nonOptimizedClassInitializer2()
+ throws ExecutionException, CompilationFailedException, IOException {
+ CodeInspector inspector = compileTestClasses(
+ ImmutableList.of(TestClassDoNotOptimize2.class, TestClassDoNotOptimize3.class));
+ ClassSubject clazz = inspector.clazz(TestClassDoNotOptimize2.class);
+ assertThat(clazz, isPresent());
+ MethodSubject method = clazz.method("void", "<clinit>", ImmutableList.of());
+ assertThat(method, isPresent());
+ assertTrue(Arrays.stream(method.getMethod().getCode().asDexCode().instructions)
+ .anyMatch(i -> i instanceof SputBoolean));
+ }
+
+ @Test
+ public void doWhileLoop() throws ExecutionException, CompilationFailedException, IOException {
+ CodeInspector inspector = compileTestClasses(ImmutableList.of(TestDoWhileLoop.class));
+ ClassSubject clazz = inspector.clazz(TestDoWhileLoop.class);
+ assertThat(clazz, isPresent());
+ MethodSubject method = clazz.method("void", "<clinit>", ImmutableList.of());
+ assertThat(method, isPresent());
+ // Leave the const 42 and the assignment in there!
+ assertTrue(Arrays.stream(method.getMethod().getCode().asDexCode().instructions)
+ .anyMatch(i -> i instanceof SingleConstant && (((SingleConstant) i).decodedValue() == 42)));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b113347830/B113347830.j b/src/test/java/com/android/tools/r8/regress/b113347830/B113347830.j
new file mode 100644
index 0000000..acae3df
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b113347830/B113347830.j
@@ -0,0 +1,487 @@
+;; Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+;; for details. All rights reserved. Use of this source code is governed by a
+;; BSD-style license that can be found in the LICENSE file.
+.class Test
+.super java/lang/Object
+.method test()Z
+.limit stack 10
+.limit locals 21
+.var 8 is foobar F from L27 to L68
+.catch all from L1 to L5 using L10
+.catch all from L6 to L9 using L10
+.catch all from L10 to L11 using L10
+L0:
+ aload 0
+ getfield com/example/Foo/bar Lcom/example/Baz;
+ astore 1
+ aload 1
+ monitorenter
+L1:
+ nop
+L2:
+ aload 0
+ getfield com/example/Foo/bar Lcom/example/Baz;
+ invokevirtual com.example.Baz.valid()Z
+ ifne L6
+ aload 0
+ getfield com/example/Foo/bar Lcom/example/Baz;
+ invokevirtual com.example.Baz.parse()Z
+ ifne L6
+L3:
+ ldc 0
+L4:
+ istore 4
+L5:
+ aload 1
+ monitorexit
+ iload 4
+ ireturn
+L6:
+ nop
+L7:
+ getstatic kotlin/Unit/INSTANCE Lkotlin/Unit;
+L8:
+ astore 3
+L9:
+ aload 1
+ monitorexit
+ goto L12
+L10:
+ astore 3
+L11:
+ aload 1
+ monitorexit
+ aload 3
+ athrow
+L12:
+ aload 0
+ getfield com/example/Foo/bar Lcom/example/Baz;
+ invokevirtual com.example.Baz.getLong()J
+ lstore 1
+L13:
+ aload 0
+ getfield com/example/Foo/bar Lcom/example/Baz;
+ invokevirtual com.example.Baz.getLength()I
+ istore 3
+L14:
+ aload 0
+ iload 3
+ ldc 4
+ imul
+ newarray float
+ putfield com/example/Foo/rects [F
+L15:
+ iload 3
+ ifne L17
+L16:
+ ldc 1
+ ireturn
+L17:
+ ldc 127
+ istore 4
+L18:
+ ldc 0
+ istore 5
+L19:
+ ldc 0
+ istore 6
+ iload 3
+ istore 7
+L20:
+ iload 6
+ iload 7
+ if_icmpge L25
+L21:
+ aload 0
+ getfield com/example/Foo/bar Lcom/example/Baz;
+ iload 6
+ invokevirtual com.example.Baz.getInt(I)I
+ istore 8
+ iload 4
+ iload 8
+ invokestatic java.lang.Math.min(II)I
+ istore 4
+L22:
+ aload 0
+ getfield com/example/Foo/bar Lcom/example/Baz;
+ iload 6
+ invokevirtual com.example.Baz.getInt(I)I
+ istore 8
+ iload 5
+ iload 8
+ invokestatic java.lang.Math.max(II)I
+ istore 5
+L23:
+ iinc 6 1
+L24:
+ goto L20
+L25:
+ aload 0
+ getfield com/example/Foo/height F
+ ldc 2
+ i2f
+ aload 0
+ getfield com/example/Foo/margin F
+ fmul
+ fsub
+ fstore 6
+L26:
+ iload 5
+ iload 4
+ isub
+ ldc 1
+ iadd
+ istore 7
+L27:
+ aload 0
+ getfield com/example/Foo/rMinGap F
+ aload 0
+ getfield com/example/Foo/rMaxGap F
+ fcmpg
+ ifge L28
+ ldc 1
+ goto L29
+L28:
+ ldc 0
+L29:
+ istore 11
+ iload 11
+ ifne L30
+ ldc "Failed requirement."
+ astore 12
+ new java/lang/IllegalArgumentException
+ dup
+ aload 12
+ invokevirtual java.lang.Object.toString()Ljava/lang/String;
+ invokespecial java.lang.IllegalArgumentException.<init>(Ljava/lang/String;)V
+ checkcast java/lang/Throwable
+ athrow
+L30:
+ aload 0
+ getfield com/example/Foo/rMinHeight F
+ aload 0
+ getfield com/example/Foo/rMaxHeight F
+ fcmpg
+ ifge L31
+ ldc 1
+ goto L32
+L31:
+ ldc 0
+L32:
+ istore 11
+ iload 11
+ ifne L33
+ ldc "Failed requirement."
+ astore 12
+ new java/lang/IllegalArgumentException
+ dup
+ aload 12
+ invokevirtual java.lang.Object.toString()Ljava/lang/String;
+ invokespecial java.lang.IllegalArgumentException.<init>(Ljava/lang/String;)V
+ checkcast java/lang/Throwable
+ athrow
+L33:
+ iload 7
+ ldc 1
+ if_icmpgt L37
+L34:
+ aload 0
+ getfield com/example/Foo/rMaxHeight F
+ fstore 9
+L35:
+ fload 9
+ aload 0
+ getfield com/example/Foo/rMaxGap F
+ fadd
+ fstore 8
+L36:
+ aload 0
+ getfield com/example/Foo/margin F
+ fload 6
+ fload 9
+ fsub
+ ldc 2
+ i2f
+ fdiv
+ fadd
+ fstore 10
+ goto L54
+L37:
+ aload 0
+ getfield com/example/Foo/rMinHeight F
+ iload 7
+ i2f
+ fmul
+ aload 0
+ getfield com/example/Foo/rMinGap F
+ iload 7
+ ldc 1
+ isub
+ i2f
+ fmul
+ fadd
+ fload 6
+ fcmpl
+ ifle L41
+L38:
+ aload 0
+ getfield com/example/Foo/rMinHeight F
+ fstore 9
+L39:
+ fload 6
+ fload 9
+ fsub
+ iload 7
+ ldc 1
+ isub
+ i2f
+ fdiv
+ fstore 8
+L40:
+ aload 0
+ getfield com/example/Foo/margin F
+ fstore 10
+ goto L54
+L41:
+ aload 0
+ getfield com/example/Foo/rMaxHeight F
+ iload 7
+ i2f
+ fmul
+ aload 0
+ getfield com/example/Foo/rMinGap F
+ iload 7
+ ldc 1
+ isub
+ i2f
+ fmul
+ fadd
+ fload 6
+ fcmpl
+ ifle L45
+L42:
+ fload 6
+ aload 0
+ getfield com/example/Foo/rMinGap F
+ iload 7
+ ldc 1
+ isub
+ i2f
+ fmul
+ fsub
+ iload 7
+ i2f
+ fdiv
+ fstore 9
+L43:
+ fload 9
+ aload 0
+ getfield com/example/Foo/rMinGap F
+ fadd
+ fstore 8
+L44:
+ aload 0
+ getfield com/example/Foo/margin F
+ fstore 10
+ goto L54
+L45:
+ aload 0
+ getfield com/example/Foo/rMaxHeight F
+ iload 7
+ i2f
+ fmul
+ aload 0
+ getfield com/example/Foo/rMaxGap F
+ iload 7
+ ldc 1
+ isub
+ i2f
+ fmul
+ fadd
+ fload 6
+ fcmpl
+ ifle L51
+L46:
+ aload 0
+ getfield com/example/Foo/rMaxHeight F
+ fstore 9
+L47:
+ fload 6
+ aload 0
+ getfield com/example/Foo/rMaxHeight F
+ iload 7
+ i2f
+ fmul
+ fsub
+ iload 7
+ ldc 1
+ isub
+ i2f
+ fdiv
+ fstore 11
+L48:
+ fload 9
+ fload 11
+ fadd
+ fstore 8
+L49:
+ aload 0
+ getfield com/example/Foo/margin F
+ fstore 10
+L50:
+ goto L54
+L51:
+ aload 0
+ getfield com/example/Foo/rMaxHeight F
+ fstore 9
+L52:
+ fload 9
+ aload 0
+ getfield com/example/Foo/rMaxGap F
+ fadd
+ fstore 8
+L53:
+ aload 0
+ getfield com/example/Foo/margin F
+ fload 6
+ fload 9
+ iload 7
+ i2f
+ fmul
+ aload 0
+ getfield com/example/Foo/rMaxGap F
+ iload 7
+ ldc 1
+ isub
+ i2f
+ fmul
+ fadd
+ fsub
+ ldc 2
+ i2f
+ fdiv
+ fadd
+ fstore 10
+L54:
+ aload 0
+ getfield com/example/Foo/converter Lcom/example/FooBar;
+ invokevirtual com.example.FooBar.getFloat()F
+ fstore 11
+L55:
+ ldc 0
+ istore 12
+ iload 3
+ istore 13
+L56:
+ iload 12
+ iload 13
+ if_icmpge L67
+L57:
+ iload 12
+ ldc 4
+ imul
+ istore 14
+L58:
+ aload 0
+ getfield com/example/Foo/rects [F
+ iload 14
+ ldc 0
+ iadd
+ fload 11
+ aload 0
+ aload 0
+ getfield com/example/Foo/bar Lcom/example/Baz;
+ iload 12
+ invokevirtual com.example.Baz.getLong(I)J
+ lload 1
+ aload 0
+ getfield com/example/Foo/bpm I
+ invokespecial com.example.FooBar.getDouble(JJI)D
+ d2f
+ fmul
+ fastore
+L59:
+ aload 0
+ getfield com/example/Foo/rects [F
+L60:
+ iload 14
+ ldc 2
+ iadd
+L61:
+ aload 0
+ getfield com/example/Foo/rects [F
+ iload 14
+ ldc 0
+ iadd
+ faload
+ ldc 1.0
+ fstore 15
+ fload 11
+ aload 0
+ aload 0
+ getfield com/example/Foo/bar Lcom/example/Baz;
+ iload 12
+ invokevirtual com.example.Baz.getLong(I)J
+ lload 1
+ aload 0
+ getfield com/example/Foo/bpm I
+ invokespecial com.example.FooBar.getDouble(JJI)D
+ d2f
+ fmul
+ fstore 16
+ fstore 19
+ istore 18
+ astore 17
+ fload 15
+ fload 16
+ invokestatic java.lang.Math.max(FF)F
+ fstore 20
+ aload 17
+ iload 18
+ fload 19
+ fload 20
+L62:
+ fadd
+ fastore
+L63:
+ aload 0
+ getfield com/example/Foo/rects [F
+ iload 14
+ ldc 1
+ iadd
+ fload 10
+ iload 5
+ aload 0
+ getfield com/example/Foo/bar Lcom/example/Baz;
+ iload 12
+ invokevirtual com.example.Baz.getInt(I)I
+ isub
+ i2f
+ fload 8
+ fmul
+ fadd
+ fastore
+L64:
+ aload 0
+ getfield com/example/Foo/rects [F
+ iload 14
+ ldc 3
+ iadd
+ aload 0
+ getfield com/example/Foo/rects [F
+ iload 14
+ ldc 1
+ iadd
+ faload
+ fload 9
+ fadd
+ fastore
+L65:
+ iinc 12 1
+L66:
+ goto L56
+L67:
+ ldc 1
+ ireturn
+L68:
+.end method
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/regress/b113347830/B113347830.java b/src/test/java/com/android/tools/r8/regress/b113347830/B113347830.java
new file mode 100644
index 0000000..a62622e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b113347830/B113347830.java
@@ -0,0 +1,42 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.regress.b113347830;
+
+import com.android.tools.r8.D8;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.origin.Origin;
+import jasmin.ClassFile;
+import java.io.ByteArrayOutputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import org.junit.Test;
+
+public class B113347830 {
+
+ public static final Class CLASS = B113347830.class;
+ public static final String NAME = CLASS.getSimpleName();
+
+ @Test
+ public void test() throws Exception {
+ ClassFile jasminFile = new ClassFile();
+ jasminFile.readJasmin(
+ Files.newBufferedReader(
+ Paths.get(
+ ToolHelper.TESTS_DIR, "java", CLASS.getCanonicalName().replace('.', '/') + ".j")),
+ "Test",
+ false);
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ jasminFile.write(out);
+ byte[] bytes = out.toByteArray();
+
+ D8.run(
+ D8Command.builder()
+ .addClassProgramData(bytes, Origin.unknown())
+ .setDisableDesugaring(true)
+ .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+ .build());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java b/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java
index d3784ee..7937f4e 100644
--- a/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java
@@ -8,7 +8,9 @@
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.code.IfEqz;
import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.code.SgetBoolean;
import com.android.tools.r8.code.Sput;
import com.android.tools.r8.code.SputObject;
import com.android.tools.r8.graph.DexCode;
@@ -387,28 +389,20 @@
CodeInspector inspector = new CodeInspector(processedApplication);
assertTrue(inspector.clazz("Test").clinit().isPresent());
- DexValue value;
assertTrue(inspector.clazz("Test").field("int", "intField").hasExplicitStaticValue());
- value = inspector.clazz("Test").field("int", "intField").getStaticValue();
+ DexValue value = inspector.clazz("Test").field("int", "intField").getStaticValue();
assertTrue(value instanceof DexValueInt);
- assertEquals(3, ((DexValueInt) value).getValue());
+ assertEquals(1, ((DexValueInt) value).getValue());
assertTrue(inspector.clazz("Test").field("java.lang.String", "stringField").hasExplicitStaticValue());
value = inspector.clazz("Test").field("java.lang.String", "stringField").getStaticValue();
assertTrue(value instanceof DexValueString);
- assertEquals(("7"), ((DexValueString) value).getValue().toString());
+ // We stop at control-flow and therefore the initial value for the string field will be 5.
+ assertEquals(("5"), ((DexValueString) value).getValue().toString());
DexCode code = inspector.clazz("Test").clinit().getMethod().getCode().asDexCode();
- for (Instruction instruction : code.instructions) {
- if (instruction instanceof Sput) {
- Sput put = (Sput) instruction;
- // Only int put ot intField2.
- assertEquals(put.getField().name.toString(), "intField2");
- } else {
- // No Object (String) puts.
- assertFalse(instruction instanceof SputObject);
- }
- }
+ assertTrue(code.instructions[0] instanceof SgetBoolean);
+ assertTrue(code.instructions[1] instanceof IfEqz);
String result = runArt(processedApplication);
diff --git a/src/test/java/com/android/tools/r8/shaking/MissingInterfaceTest.java b/src/test/java/com/android/tools/r8/shaking/MissingInterfaceTest.java
index a93db7a..acf4488 100644
--- a/src/test/java/com/android/tools/r8/shaking/MissingInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/MissingInterfaceTest.java
@@ -55,7 +55,7 @@
@Before
public void setUp() throws Exception {
- libJar = writeToZip(ImmutableList.of(ToolHelper.getClassAsBytes(GoingToBeMissed.class)));
+ libJar = writeToJar(ImmutableList.of(ToolHelper.getClassAsBytes(GoingToBeMissed.class)));
libDex = temp.getRoot().toPath().resolve("lib.zip");
AndroidApp libApp = ToolHelper.runD8(readClasses(GoingToBeMissed.class));
libApp.writeToZip(libDex, OutputMode.DexIndexed);