Version 0.1.10.
This merges all <clinit> related fixes, so that D8 never removed empty
<clinit> methods, and ensures correct stepping in <clinit> code.
Merge: Fix more tests
CL: https://r8-review.googlesource.com/c/r8/+/5642
Merge: Update test
CL: https://r8-review.googlesource.com/c/r8/+/5640
Merge: Don't remove empty <clinit> methods in D8
CL: https://r8-review.googlesource.com/c/r8/+/5620
Merge (resolved conflicts): Test debug stepping in <clinit>
CL: https://r8-review.googlesource.com/c/r8/+/5401
Merge (resolved conflicts): Update the <clinit> rewriting
CL: https://r8-review.googlesource.com/c/r8/+/5360
Change-Id: Ia9140f95fe2f5549eb4c663bd8bc7587e40714e7
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index dda9a39..f7e65d9 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -55,7 +55,7 @@
*/
public final class D8 {
- private static final String VERSION = "v0.1.9";
+ private static final String VERSION = "v0.1.10";
private static final int STATUS_ERROR = 1;
private D8() {}
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index a79a5cd..edfe01e 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -71,7 +71,7 @@
public class R8 {
- private static final String VERSION = "v0.1.9";
+ private static final String VERSION = "v0.1.10";
private final Timing timing = new Timing("R8");
private final InternalOptions options;
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index 8bd13f0..edac717 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -81,6 +81,17 @@
this.debugInfo = debugInfo;
}
+ public boolean hasDebugPositions() {
+ if (debugInfo != null) {
+ for (DexDebugEvent event : debugInfo.events) {
+ if (event instanceof DexDebugEvent.Default) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
public DexDebugInfo debugInfoWithAdditionalFirstParameter(DexString name) {
if (debugInfo == null) {
return null;
@@ -142,7 +153,7 @@
return false;
}
- boolean isEmptyVoidMethod() {
+ public boolean isEmptyVoidMethod() {
return instructions.length == 1 && instructions[0] instanceof ReturnVoid;
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index e66b984..493b56f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -206,6 +206,11 @@
code = null;
}
+ public boolean hasDebugPositions() {
+ assert code != null && code.isDexCode();
+ return code.asDexCode().hasDebugPositions();
+ }
+
public String qualifiedName() {
return method.qualifiedName();
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexValue.java b/src/main/java/com/android/tools/r8/graph/DexValue.java
index 08c8a1c..73b3242 100644
--- a/src/main/java/com/android/tools/r8/graph/DexValue.java
+++ b/src/main/java/com/android/tools/r8/graph/DexValue.java
@@ -151,6 +151,10 @@
return value == DEFAULT.value ? DEFAULT : new DexValueByte(value);
}
+ public byte getValue() {
+ return value;
+ }
+
@Override
public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
writeHeader(VALUE_BYTE, 0, dest);
@@ -196,6 +200,10 @@
return value == DEFAULT.value ? DEFAULT : new DexValueShort(value);
}
+ public short getValue() {
+ return value;
+ }
+
@Override
public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
writeIntegerTo(VALUE_SHORT, value, Short.BYTES, dest);
@@ -240,6 +248,10 @@
return value == DEFAULT.value ? DEFAULT : new DexValueChar(value);
}
+ public char getValue() {
+ return value;
+ }
+
@Override
public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
dest.forward(1);
@@ -288,6 +300,10 @@
return value == DEFAULT.value ? DEFAULT : new DexValueInt(value);
}
+ public int getValue() {
+ return value;
+ }
+
@Override
public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
writeIntegerTo(VALUE_INT, value, Integer.BYTES, dest);
@@ -332,6 +348,10 @@
return value == DEFAULT.value ? DEFAULT : new DexValueLong(value);
}
+ public long getValue() {
+ return value;
+ }
+
@Override
public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
writeIntegerTo(VALUE_LONG, value, Long.BYTES, dest);
@@ -376,6 +396,10 @@
return Float.compare(value, DEFAULT.value) == 0 ? DEFAULT : new DexValueFloat(value);
}
+ public float getValue() {
+ return value;
+ }
+
@Override
public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
dest.forward(1);
@@ -420,6 +444,10 @@
return Double.compare(value, DEFAULT.value) == 0 ? DEFAULT : new DexValueDouble(value);
}
+ public double getValue() {
+ return value;
+ }
+
@Override
public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
dest.forward(1);
@@ -508,6 +536,10 @@
super(value);
}
+ public DexString getValue() {
+ return value;
+ }
+
@Override
protected byte getValueKind() {
return VALUE_STRING;
@@ -710,6 +742,10 @@
private DexValueNull() {
}
+ public Object getValue() {
+ return null;
+ }
+
@Override
public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
writeHeader(VALUE_NULL, 0, dest);
@@ -751,6 +787,10 @@
return value ? TRUE : FALSE;
}
+ public boolean getValue() {
+ return value;
+ }
+
@Override
public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
writeHeader(VALUE_BOOLEAN, value ? 1 : 0, dest);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index fad378e..357c2ce 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -233,9 +233,6 @@
}
ThreadUtils.awaitFutures(futures);
-
- // Get rid of <clinit> methods with no code.
- removeEmptyClassInitializers();
}
private void convertMethodToDex(DexEncodedMethod method) {
@@ -281,9 +278,6 @@
}, executorService);
timing.end();
- // Get rid of <clinit> methods with no code.
- removeEmptyClassInitializers();
-
// Build a new application with jumbo string info.
Builder builder = new Builder(application);
builder.setHighestSortingString(highestSortingString);
@@ -325,16 +319,6 @@
return builder.build();
}
- private void removeEmptyClassInitializers() {
- application.classes().forEach(this::removeEmptyClassInitializer);
- }
-
- private void removeEmptyClassInitializer(DexProgramClass clazz) {
- if (clazz.hasTrivialClassInitializer()) {
- clazz.removeStaticMethod(clazz.getClassInitializer());
- }
- }
-
private void clearDexMethodCompilationState() {
application.classes().forEach(this::clearDexMethodCompilationState);
}
@@ -473,7 +457,9 @@
codeRewriter.foldConstants(code);
codeRewriter.rewriteSwitch(code);
codeRewriter.simplifyIf(code);
- codeRewriter.collectClassInitializerDefaults(method, code);
+ if (!options.debug) {
+ codeRewriter.collectClassInitializerDefaults(method, code);
+ }
if (Log.ENABLED) {
Log.debug(getClass(), "Intermediate (SSA) flow graph for %s:\n%s",
method.toSourceString(), code);
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 0c75ef3..ff350a6 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
@@ -68,8 +68,11 @@
import com.google.common.base.Equivalence.Wrapper;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
@@ -660,7 +663,8 @@
if (exit == null) {
return;
}
- Map<DexField, StaticPut> puts = Maps.newIdentityHashMap();
+ Set<StaticPut> puts = Sets.newIdentityHashSet();
+ Map<DexField, StaticPut> dominatingPuts = Maps.newIdentityHashMap();
for (BasicBlock block : dominatorTree.dominatorBlocks(exit)) {
InstructionListIterator iterator = block.listIterator(block.getInstructions().size());
while (iterator.hasPrevious()) {
@@ -674,22 +678,27 @@
&& put.inValue().getConstInstruction().isConstNumber() &&
put.inValue().getConstInstruction().asConstNumber().isZero()) {
// Collect put of zero as a potential default value.
- puts.put(put.getField(), put);
+ dominatingPuts.putIfAbsent(put.getField(), put);
+ puts.add(put);
} else if (field.type.isPrimitiveType() || field.type == dexItemFactory.stringType) {
// Collect put as a potential default value.
- puts.put(put.getField(), put);
+ dominatingPuts.putIfAbsent(put.getField(), put);
+ puts.add(put);
}
} else if (isClassNameConstant(method, put)) {
// Collect put of class name constant as a potential default value.
- puts.put(put.getField(), put);
+ 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.
- if (puts.containsKey(current.asStaticGet().getField())) {
- puts.remove(current.asStaticGet().getField());
+ DexField field = current.asStaticGet().getField();
+ if (dominatingPuts.containsKey(field)) {
+ dominatingPuts.remove(field);
+ Iterables.removeIf(puts, put -> put.getField() == field);
}
}
}
@@ -697,7 +706,7 @@
if (!puts.isEmpty()) {
// Set initial values for static fields from the definitive static put instructions collected.
- for (StaticPut put : puts.values()) {
+ for (StaticPut put : dominatingPuts.values()) {
DexField field = put.getField();
DexEncodedField encodedField = appInfo.definitionFor(field);
if (field.type == dexItemFactory.stringType) {
@@ -758,7 +767,7 @@
InstructionListIterator iterator = block.listIterator();
while (iterator.hasNext()) {
Instruction current = iterator.next();
- if (current.isStaticPut() && puts.values().contains(current.asStaticPut())) {
+ 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
diff --git a/src/test/debugTestResources/ClassInitializerAssignmentInitialization.java b/src/test/debugTestResources/ClassInitializerAssignmentInitialization.java
new file mode 100644
index 0000000..fa6078a
--- /dev/null
+++ b/src/test/debugTestResources/ClassInitializerAssignmentInitialization.java
@@ -0,0 +1,16 @@
+// Copyright (c) 2017, 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.
+
+public class ClassInitializerAssignmentInitialization {
+
+ static int x = 1;
+ static int y;
+
+ static int z = 2;
+
+ public static void main(String[] args) {
+ System.out.println("x=" + x);
+ System.out.println("y=" + y);
+ }
+}
diff --git a/src/test/debugTestResources/ClassInitializerEmpty.java b/src/test/debugTestResources/ClassInitializerEmpty.java
new file mode 100644
index 0000000..9062d62
--- /dev/null
+++ b/src/test/debugTestResources/ClassInitializerEmpty.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2017, 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.
+
+public class ClassInitializerEmpty {
+
+ static {
+ }
+
+ public static void main(String[] args) {
+ }
+}
diff --git a/src/test/debugTestResources/ClassInitializerMixedInitialization.java b/src/test/debugTestResources/ClassInitializerMixedInitialization.java
new file mode 100644
index 0000000..504db6c
--- /dev/null
+++ b/src/test/debugTestResources/ClassInitializerMixedInitialization.java
@@ -0,0 +1,25 @@
+// Copyright (c) 2017, 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.
+
+public class ClassInitializerMixedInitialization {
+
+ static boolean b;
+ static int x = 1;
+ static int y;
+
+ static {
+ x = 2;
+ if (b) {
+ y = 1;
+ } else {
+ y = 2;
+ }
+ x = 3;
+ }
+
+ public static void main(String[] args) {
+ System.out.println("x=" + x);
+ System.out.println("y=" + y);
+ }
+}
diff --git a/src/test/debugTestResources/ClassInitializerStaticBlockInitialization.java b/src/test/debugTestResources/ClassInitializerStaticBlockInitialization.java
new file mode 100644
index 0000000..78fe9a0
--- /dev/null
+++ b/src/test/debugTestResources/ClassInitializerStaticBlockInitialization.java
@@ -0,0 +1,26 @@
+// Copyright (c) 2017, 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.
+
+public class ClassInitializerStaticBlockInitialization {
+
+ static boolean b;
+ static int x;
+ static int y;
+
+ static {
+ x = 1;
+ x = 2;
+ if (b) {
+ y = 1;
+ } else {
+ y = 2;
+ }
+ x = 3;
+ }
+
+ public static void main(String[] args) {
+ System.out.println("x=" + x);
+ System.out.println("y=" + y);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index eee820f..8b83058 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -14,15 +14,11 @@
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.shaking.ProguardConfiguration;
import com.android.tools.r8.shaking.ProguardConfigurationParser;
-import com.android.tools.r8.shaking.ProguardConfigurationRule;
import com.android.tools.r8.shaking.ProguardRuleParserException;
-import com.android.tools.r8.shaking.RootSetBuilder;
-import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -93,6 +89,10 @@
return compareTo(other) > 0;
}
+ public boolean isOlderThanOrEqual(DexVm other) {
+ return compareTo(other) <= 0;
+ }
+
private DexVm(String shortName) {
this.shortName = shortName;
}
diff --git a/src/test/java/com/android/tools/r8/debug/ClassInitializationTest.java b/src/test/java/com/android/tools/r8/debug/ClassInitializationTest.java
new file mode 100644
index 0000000..e90bca7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/ClassInitializationTest.java
@@ -0,0 +1,110 @@
+// Copyright (c) 2017, 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 org.apache.harmony.jpda.tests.framework.jdwp.Value;
+import org.junit.Test;
+
+public class ClassInitializationTest extends DebugTestBase {
+
+ @Test
+ public void testStaticAssingmentInitialization() throws Throwable {
+ final String SOURCE_FILE = "ClassInitializerAssignmentInitialization.java";
+ final String CLASS = "ClassInitializerAssignmentInitialization";
+
+ runDebugTest(CLASS,
+ breakpoint(CLASS, "<clinit>"),
+ run(),
+ checkLine(SOURCE_FILE, 7),
+ checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(0)),
+ checkStaticFieldClinitSafe(CLASS, "y", null, Value.createInt(0)),
+ checkStaticFieldClinitSafe(CLASS, "z", null, Value.createInt(0)),
+ stepOver(),
+ checkLine(SOURCE_FILE, 10),
+ checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(1)),
+ checkStaticFieldClinitSafe(CLASS, "y", null, Value.createInt(0)),
+ checkStaticFieldClinitSafe(CLASS, "z", null, Value.createInt(0)),
+ breakpoint(CLASS, "main"),
+ run(),
+ checkStaticField(CLASS, "x", null, Value.createInt(1)),
+ checkStaticField(CLASS, "y", null, Value.createInt(0)),
+ checkStaticField(CLASS, "z", null, Value.createInt(2)),
+ run());
+ }
+
+ @Test
+ public void testBreakpointInEmptyClassInitializer() throws Throwable {
+ final String SOURCE_FILE = "ClassInitializerEmpty.java";
+ final String CLASS = "ClassInitializerEmpty";
+
+ runDebugTest(CLASS,
+ breakpoint(CLASS, "<clinit>"),
+ run(),
+ checkLine(SOURCE_FILE, 8),
+ run());
+ }
+
+ @Test
+ public void testStaticBlockInitialization() throws Throwable {
+ final String SOURCE_FILE = "ClassInitializerStaticBlockInitialization.java";
+ final String CLASS = "ClassInitializerStaticBlockInitialization";
+
+ runDebugTest(CLASS,
+ breakpoint(CLASS, "<clinit>"),
+ run(),
+ checkLine(SOURCE_FILE, 12),
+ checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(0)),
+ stepOver(),
+ checkLine(SOURCE_FILE, 13),
+ checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(1)),
+ stepOver(),
+ checkLine(SOURCE_FILE, 14),
+ checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(2)),
+ stepOver(),
+ checkLine(SOURCE_FILE, 17),
+ stepOver(),
+ checkLine(SOURCE_FILE, 19),
+ breakpoint(CLASS, "main"),
+ run(),
+ checkLine(SOURCE_FILE, 23),
+ checkStaticField(CLASS, "x", null, Value.createInt(3)),
+ run());
+ }
+
+ @Test
+ public void testStaticMixedInitialization() throws Throwable {
+ final String SOURCE_FILE = "ClassInitializerMixedInitialization.java";
+ final String CLASS = "ClassInitializerMixedInitialization";
+
+ runDebugTest(CLASS,
+ breakpoint(CLASS, "<clinit>"),
+ run(),
+ checkLine(SOURCE_FILE, 8),
+ checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(0)),
+ checkStaticFieldClinitSafe(CLASS, "y", null, Value.createInt(0)),
+ stepOver(),
+ checkLine(SOURCE_FILE, 12),
+ checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(1)),
+ checkStaticFieldClinitSafe(CLASS, "y", null, Value.createInt(0)),
+ stepOver(),
+ checkLine(SOURCE_FILE, 13),
+ checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(2)),
+ checkStaticFieldClinitSafe(CLASS, "y", null, Value.createInt(0)),
+ stepOver(),
+ checkLine(SOURCE_FILE, 16),
+ checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(2)),
+ checkStaticFieldClinitSafe(CLASS, "y", null, Value.createInt(0)),
+ stepOver(),
+ checkLine(SOURCE_FILE, 18),
+ checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(2)),
+ checkStaticFieldClinitSafe(CLASS, "y", null, Value.createInt(2)),
+ breakpoint(CLASS, "main"),
+ run(),
+ checkLine(SOURCE_FILE, 22),
+ checkStaticField(CLASS, "x", null, Value.createInt(3)),
+ checkStaticField(CLASS, "y", null, Value.createInt(2)),
+ run());
+ }
+}
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 6340728..106aab4 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -10,9 +10,12 @@
import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
import com.android.tools.r8.ToolHelper.DexVm;
import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.OffOrAuto;
import com.google.common.collect.ImmutableList;
+import it.unimi.dsi.fastutil.longs.LongArrayList;
+import it.unimi.dsi.fastutil.longs.LongList;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
@@ -62,8 +65,10 @@
import org.junit.rules.TestName;
/**
+ * Base class for debugging tests.
*
- * Base class for debugging tests
+ * The protocol messages are described here:
+ * https://docs.oracle.com/javase/8/docs/platform/jpda/jdwp/jdwp-protocol.html
*/
public abstract class DebugTestBase {
@@ -347,6 +352,27 @@
});
}
+ protected final JUnit3Wrapper.Command checkStaticFieldClinitSafe(
+ String className, String fieldName, String fieldSignature, Value expectedValue) {
+ return inspect(t -> {
+ // TODO(65148874): The current Art from AOSP master hangs when requesting static fields
+ // when breaking in <clinit>. Last known good version is 7.0.0.
+ Assume.assumeTrue(
+ "Skipping test " + testName.getMethodName() + " because ART version is not supported",
+ isRunningJava() || ToolHelper.getDexVm().isOlderThanOrEqual(DexVm.ART_7_0_0));
+ checkStaticField(className, fieldName, fieldSignature, expectedValue);
+ });
+ }
+
+ protected final JUnit3Wrapper.Command checkStaticField(
+ String className, String fieldName, String fieldSignature, Value expectedValue) {
+ return inspect(t -> {
+ Value value = t.getStaticField(className, fieldName, fieldSignature);
+ Assert.assertEquals("Incorrect value for static '" + className + "." + fieldName + "'",
+ expectedValue, value);
+ });
+ }
+
protected final JUnit3Wrapper.Command inspect(Consumer<JUnit3Wrapper.DebuggeeState> inspector) {
return t -> inspector.accept(t.debuggeeState);
}
@@ -561,6 +587,7 @@
public static class DebuggeeState implements FrameInspector {
private class DebuggeeFrame implements FrameInspector {
+
private final long frameId;
private final Location location;
@@ -819,6 +846,77 @@
public String getMethodSignature() {
return getTopFrame().getMethodSignature();
}
+
+ public Value getStaticField(String className, String fieldName, String fieldSignature) {
+ String classSignature = DescriptorUtils.javaTypeToDescriptor(className);
+ byte typeTag = TypeTag.CLASS;
+ long classId = getMirror().getClassID(classSignature);
+ Assert.assertFalse("No class named " + className + " found", classId == -1);
+
+ // The class is available, lookup and read the field.
+ long fieldId = findField(getMirror(), classId, fieldName, fieldSignature);
+ return getField(getMirror(), classId, fieldId);
+ }
+
+ private long findField(VmMirror mirror, long classId, String fieldName,
+ String fieldSignature) {
+
+ boolean withGenericSignature = true;
+ CommandPacket commandPacket = new CommandPacket(ReferenceTypeCommandSet.CommandSetID,
+ ReferenceTypeCommandSet.FieldsWithGenericCommand);
+ commandPacket.setNextValueAsReferenceTypeID(classId);
+ ReplyPacket replyPacket = mirror.performCommand(commandPacket);
+ if (replyPacket.getErrorCode() != Error.NONE) {
+ // Retry with older command ReferenceType.Fields.
+ withGenericSignature = false;
+ commandPacket.setCommand(ReferenceTypeCommandSet.FieldsCommand);
+ replyPacket = mirror.performCommand(commandPacket);
+ assert replyPacket.getErrorCode() == Error.NONE;
+ }
+
+ int fieldsCount = replyPacket.getNextValueAsInt();
+ LongList matchingFieldIds = new LongArrayList();
+ for (int i = 0; i < fieldsCount; ++i) {
+ long currentFieldId = replyPacket.getNextValueAsFieldID();
+ String currentFieldName = replyPacket.getNextValueAsString();
+ String currentFieldSignature = replyPacket.getNextValueAsString();
+ if (withGenericSignature) {
+ replyPacket.getNextValueAsString(); // Skip generic signature.
+ }
+ replyPacket.getNextValueAsInt(); // Skip modifiers.
+
+ // Filter fields based on name (and signature if there is).
+ if (fieldName.equals(currentFieldName)) {
+ if (fieldSignature == null || fieldSignature.equals(currentFieldSignature)) {
+ matchingFieldIds.add(currentFieldId);
+ }
+ }
+ }
+ Assert.assertTrue(replyPacket.isAllDataRead());
+
+ Assert.assertFalse("No field named " + fieldName + " found", matchingFieldIds.isEmpty());
+ // There must be only one matching field.
+ Assert.assertEquals("More than 1 field found: please specify a signature", 1,
+ matchingFieldIds.size());
+ return matchingFieldIds.getLong(0);
+ }
+
+ private Value getField(VmMirror mirror, long classId, long fieldId) {
+
+ CommandPacket commandPacket = new CommandPacket(ReferenceTypeCommandSet.CommandSetID,
+ ReferenceTypeCommandSet.GetValuesCommand);
+ commandPacket.setNextValueAsReferenceTypeID(classId);
+ commandPacket.setNextValueAsInt(1);
+ commandPacket.setNextValueAsFieldID(fieldId);
+ ReplyPacket replyPacket = mirror.performCommand(commandPacket);
+ assert replyPacket.getErrorCode() == Error.NONE;
+
+ int fieldsCount = replyPacket.getNextValueAsInt();
+ assert fieldsCount == 1;
+ Value result = replyPacket.getNextValueAsValue();
+ Assert.assertTrue(replyPacket.isAllDataRead());
+ return result;
+ }
}
private static boolean inScope(long index, Variable var) {
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 195c3aa..58d2b77 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
@@ -7,7 +7,21 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.code.Sput;
+import com.android.tools.r8.code.SputObject;
import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.DexValue.DexValueBoolean;
+import com.android.tools.r8.graph.DexValue.DexValueByte;
+import com.android.tools.r8.graph.DexValue.DexValueChar;
+import com.android.tools.r8.graph.DexValue.DexValueDouble;
+import com.android.tools.r8.graph.DexValue.DexValueFloat;
+import com.android.tools.r8.graph.DexValue.DexValueInt;
+import com.android.tools.r8.graph.DexValue.DexValueLong;
+import com.android.tools.r8.graph.DexValue.DexValueShort;
+import com.android.tools.r8.graph.DexValue.DexValueString;
import com.android.tools.r8.smali.SmaliTestBase;
import com.android.tools.r8.utils.DexInspector;
import com.android.tools.r8.utils.DexInspector.MethodSubject;
@@ -80,7 +94,55 @@
DexApplication processedApplication = processApplication(originalApplication, options);
DexInspector inspector = new DexInspector(processedApplication);
- assertFalse(inspector.clazz("Test").clinit().isPresent());
+ // Test is running without tree-shaking, so the empty <clinit> is not removed.
+ assertTrue(
+ inspector.clazz("Test").clinit().getMethod().getCode().asDexCode().isEmptyVoidMethod());
+
+ DexValue value;
+ assertTrue(inspector.clazz("Test").field("boolean", "booleanField").hasStaticValue());
+ value = inspector.clazz("Test").field("boolean", "booleanField").getStaticValue();
+ assertTrue(value instanceof DexValueBoolean);
+ assertEquals(true, ((DexValueBoolean) value).getValue());
+
+ assertTrue(inspector.clazz("Test").field("byte", "byteField").hasStaticValue());
+ value = inspector.clazz("Test").field("byte", "byteField").getStaticValue();
+ assertTrue(value instanceof DexValueByte);
+ assertEquals(1, ((DexValueByte) value).getValue());
+
+ assertTrue(inspector.clazz("Test").field("short", "shortField").hasStaticValue());
+ value = inspector.clazz("Test").field("short", "shortField").getStaticValue();
+ assertTrue(value instanceof DexValueShort);
+ assertEquals(2, ((DexValueShort) value).getValue());
+
+ assertTrue(inspector.clazz("Test").field("int", "intField").hasStaticValue());
+ value = inspector.clazz("Test").field("int", "intField").getStaticValue();
+ assertTrue(value instanceof DexValueInt);
+ assertEquals(3, ((DexValueInt) value).getValue());
+
+ assertTrue(inspector.clazz("Test").field("long", "longField").hasStaticValue());
+ value = inspector.clazz("Test").field("long", "longField").getStaticValue();
+ assertTrue(value instanceof DexValueLong);
+ assertEquals(4, ((DexValueLong) value).getValue());
+
+ assertTrue(inspector.clazz("Test").field("float", "floatField").hasStaticValue());
+ value = inspector.clazz("Test").field("float", "floatField").getStaticValue();
+ assertTrue(value instanceof DexValueFloat);
+ assertEquals(5.0f, ((DexValueFloat) value).getValue(), 0.0);
+
+ assertTrue(inspector.clazz("Test").field("double", "doubleField").hasStaticValue());
+ value = inspector.clazz("Test").field("double", "doubleField").getStaticValue();
+ assertTrue(value instanceof DexValueDouble);
+ assertEquals(6.0f, ((DexValueDouble) value).getValue(), 0.0);
+
+ assertTrue(inspector.clazz("Test").field("char", "charField").hasStaticValue());
+ value = inspector.clazz("Test").field("char", "charField").getStaticValue();
+ assertTrue(value instanceof DexValueChar);
+ assertEquals(0x30 + 7, ((DexValueChar) value).getValue());
+
+ assertTrue(inspector.clazz("Test").field("java.lang.String", "stringField").hasStaticValue());
+ value = inspector.clazz("Test").field("java.lang.String", "stringField").getStaticValue();
+ assertTrue(value instanceof DexValueString);
+ assertEquals(("8"), ((DexValueString) value).getValue().toString());
String result = runArt(processedApplication, options);
@@ -159,7 +221,9 @@
DexApplication processedApplication = processApplication(originalApplication, options);
DexInspector inspector = new DexInspector(processedApplication);
- assertFalse(inspector.clazz("Test").clinit().isPresent());
+ // Test is running without tree-shaking, so the empty <clinit> is not removed.
+ assertTrue(
+ inspector.clazz("Test").clinit().getMethod().getCode().asDexCode().isEmptyVoidMethod());
String result = runArt(processedApplication, options);
@@ -200,7 +264,9 @@
DexApplication processedApplication = processApplication(originalApplication, options);
DexInspector inspector = new DexInspector(processedApplication);
- assertFalse(inspector.clazz("Test").clinit().isPresent());
+ // Test is running without tree-shaking, so the empty <clinit> is not removed.
+ assertTrue(
+ inspector.clazz("Test").clinit().getMethod().getCode().asDexCode().isEmptyVoidMethod());
String result = runArt(processedApplication, options);
@@ -208,6 +274,152 @@
}
@Test
+ public void testMultiplePuts() {
+ SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+
+ builder.addStaticField("intField", "I");
+ builder.addStaticField("stringField", "Ljava/lang/String;");
+
+ builder.addStaticInitializer(
+ 1,
+ "const v0, 0",
+ "sput v0, LTest;->intField:I",
+ "const-string v0, \"4\"",
+ "sput-object v0, LTest;->stringField:Ljava/lang/String;",
+ "const v0, 1",
+ "sput v0, LTest;->intField:I",
+ "const-string v0, \"5\"",
+ "sput-object v0, LTest;->stringField:Ljava/lang/String;",
+ "const v0, 2",
+ "sput v0, LTest;->intField:I",
+ "const-string v0, \"6\"",
+ "sput-object v0, LTest;->stringField:Ljava/lang/String;",
+ "const v0, 3",
+ "sput v0, LTest;->intField:I",
+ "const-string v0, \"7\"",
+ "sput-object v0, LTest;->stringField:Ljava/lang/String;",
+ "return-void"
+ );
+ builder.addMainMethod(
+ 2,
+ "sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+ "sget v1, LTest;->intField:I",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(I)V",
+ "sget-object v1, LTest;->stringField:Ljava/lang/String;",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+ "return-void"
+ );
+
+ InternalOptions options = new InternalOptions();
+ DexApplication originalApplication = buildApplication(builder, options);
+ DexApplication processedApplication = processApplication(originalApplication, options);
+
+ DexInspector inspector = new DexInspector(processedApplication);
+ // Test is running without tree-shaking, so the empty <clinit> is not removed.
+ assertTrue(
+ inspector.clazz("Test").clinit().getMethod().getCode().asDexCode().isEmptyVoidMethod());
+
+ DexValue value;
+ assertTrue(inspector.clazz("Test").field("int", "intField").hasStaticValue());
+ value = inspector.clazz("Test").field("int", "intField").getStaticValue();
+ assertTrue(value instanceof DexValueInt);
+ assertEquals(3, ((DexValueInt) value).getValue());
+
+ assertTrue(inspector.clazz("Test").field("java.lang.String", "stringField").hasStaticValue());
+ value = inspector.clazz("Test").field("java.lang.String", "stringField").getStaticValue();
+ assertTrue(value instanceof DexValueString);
+ assertEquals(("7"), ((DexValueString) value).getValue().toString());
+
+ String result = runArt(processedApplication, options);
+
+ assertEquals("3\n7\n", result);
+ }
+
+
+ @Test
+ public void testMultiplePutsWithControlFlow() {
+ SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+
+ builder.addStaticField("booleanField", "Z");
+ builder.addStaticField("intField", "I");
+ builder.addStaticField("intField2", "I");
+ builder.addStaticField("stringField", "Ljava/lang/String;");
+
+ builder.addStaticInitializer(
+ 1,
+ "const v0, 0",
+ "sput v0, LTest;->intField:I",
+ "const-string v0, \"4\"",
+ "sput-object v0, LTest;->stringField:Ljava/lang/String;",
+ "const v0, 1",
+ "sput v0, LTest;->intField:I",
+ "const-string v0, \"5\"",
+ "sput-object v0, LTest;->stringField:Ljava/lang/String;",
+ "sget-boolean v0, LTest;->booleanField:Z",
+ "if-eqz v0, :label_1",
+ "const v0, 8",
+ "sput v0, LTest;->intField2:I",
+ ":label_1",
+ "const v0, 9",
+ "sput v0, LTest;->intField2:I",
+ "goto :label_2",
+ ":label_2",
+ "const v0, 2",
+ "sput v0, LTest;->intField:I",
+ "const-string v0, \"6\"",
+ "sput-object v0, LTest;->stringField:Ljava/lang/String;",
+ "const v0, 3",
+ "sput v0, LTest;->intField:I",
+ "const-string v0, \"7\"",
+ "sput-object v0, LTest;->stringField:Ljava/lang/String;",
+ "return-void"
+ );
+ builder.addMainMethod(
+ 2,
+ "sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+ "sget v1, LTest;->intField:I",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(I)V",
+ "sget-object v1, LTest;->stringField:Ljava/lang/String;",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+ "return-void"
+ );
+
+ InternalOptions options = new InternalOptions();
+ DexApplication originalApplication = buildApplication(builder, options);
+ DexApplication processedApplication = processApplication(originalApplication, options);
+
+ DexInspector inspector = new DexInspector(processedApplication);
+ assertTrue(inspector.clazz("Test").clinit().isPresent());
+
+ DexValue value;
+ assertTrue(inspector.clazz("Test").field("int", "intField").hasStaticValue());
+ value = inspector.clazz("Test").field("int", "intField").getStaticValue();
+ assertTrue(value instanceof DexValueInt);
+ assertEquals(3, ((DexValueInt) value).getValue());
+
+ assertTrue(inspector.clazz("Test").field("java.lang.String", "stringField").hasStaticValue());
+ value = inspector.clazz("Test").field("java.lang.String", "stringField").getStaticValue();
+ assertTrue(value instanceof DexValueString);
+ assertEquals(("7"), ((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);
+ }
+ }
+
+ String result = runArt(processedApplication, options);
+
+ assertEquals("3\n7\n", result);
+ }
+
+ @Test
public void testInitializationToOwnClassName() {
String className = "org.example.Test";
SmaliBuilder builder = new SmaliBuilder(className);
@@ -266,7 +478,9 @@
DexInspector inspector = new DexInspector(processedApplication);
assertTrue(inspector.clazz(className).isPresent());
- assertFalse(inspector.clazz(className).clinit().isPresent());
+ // Test is running without tree-shaking, so the empty <clinit> is not removed.
+ assertTrue(
+ inspector.clazz(className).clinit().getMethod().getCode().asDexCode().isEmptyVoidMethod());
String result = runArt(processedApplication, options, className);
diff --git a/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java b/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
index 03df65c..4cb6736 100644
--- a/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
@@ -126,7 +126,9 @@
private static void inspectShaking1(PrintUsageInspector inspector) {
assertTrue(inspector.clazz("shaking1.Unused").isPresent());
- assertFalse(inspector.clazz("shaking1.Used").isPresent());
+ assertTrue(inspector.clazz("shaking1.Used").isPresent());
+ ClassSubject used = inspector.clazz("shaking1.Used").get();
+ assertTrue(used.method("void", "<clinit>", ImmutableList.of()));
}
private static void inspectShaking2(PrintUsageInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
index 51b6ab5..ba16e33 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
@@ -150,6 +150,9 @@
used.method("java.lang.String", "aMethodThatIsNotUsedButKept", Collections.emptyList())
.isPresent());
Assert.assertTrue(used.field("int", "aStaticFieldThatIsNotUsedButKept").isPresent());
+ // Rewriting of <clinit> moves the initialization of aStaticFieldThatIsNotUsedButKept
+ // from <clinit> code into statics value section of the dex file.
+ Assert.assertFalse(used.clinit().isPresent());
}
public static void shaking1IsCorrectlyRepackaged(DexInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/utils/DexInspector.java b/src/test/java/com/android/tools/r8/utils/DexInspector.java
index 0ad240c..26de96c 100644
--- a/src/test/java/com/android/tools/r8/utils/DexInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/DexInspector.java
@@ -56,6 +56,7 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexValue;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.naming.ClassNaming;
import com.android.tools.r8.naming.MemberNaming;
@@ -691,8 +692,11 @@
}
public abstract class FieldSubject extends MemberSubject {
+ public abstract boolean hasStaticValue();
public abstract DexEncodedField getField();
+
+ public abstract DexValue getStaticValue();
}
public class AbsentFieldSubject extends FieldSubject {
@@ -733,6 +737,16 @@
}
@Override
+ public boolean hasStaticValue() {
+ return false;
+ }
+
+ @Override
+ public DexValue getStaticValue() {
+ return null;
+ }
+
+ @Override
public DexEncodedField getField() {
return null;
}
@@ -791,6 +805,16 @@
}
@Override
+ public boolean hasStaticValue() {
+ return dexField.staticValue != null;
+ }
+
+ @Override
+ public DexValue getStaticValue() {
+ return dexField.staticValue;
+ }
+
+ @Override
public DexEncodedField getField() {
return dexField;
}