Merge "Replace clinit static puts with initial values for static fields"
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index c94fc3c..1ba7755 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -15,7 +15,7 @@
public final DexField field;
public final DexAccessFlags accessFlags;
public DexAnnotationSet annotations;
- public final DexValue staticValue;
+ public DexValue staticValue;
public DexEncodedField(DexField field, DexAccessFlags accessFlags, DexAnnotationSet annotations,
DexValue staticValue) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
index d53c577..1880668 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
@@ -48,6 +48,10 @@
return outValue;
}
+ public boolean getBooleanValue() {
+ return !isZero();
+ }
+
public int getIntValue() {
assert type == ConstType.INT || type == ConstType.INT_OR_FLOAT;
return (int) value;
diff --git a/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java b/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java
index 9f28eea..c8a17dc2 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java
@@ -110,6 +110,39 @@
};
}
+ /**
+ * Returns an iterator over all dominator blocks of <code>dominated</code>.
+ *
+ * Iteration order is always the immediate dominator of the previously returned block. The
+ * iteration starts by returning <code>dominated</code>.
+ */
+ public Iterable<BasicBlock> dominatorBlocks(BasicBlock dominated) {
+ return () -> new Iterator<BasicBlock>() {
+ private BasicBlock current = dominated;
+
+ @Override
+ public boolean hasNext() {
+ return current != null;
+ }
+
+ @Override
+ public BasicBlock next() {
+ if (!hasNext()) {
+ return null;
+ } else {
+ BasicBlock result = current;
+ if (current.getNumber() == 0) {
+ current = null;
+ } else {
+ current = immediateDominator(current);
+ assert current != result;
+ }
+ return result;
+ }
+ }
+ };
+ }
+
public BasicBlock[] getSortedBlocks() {
return sorted;
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
index 48c8eb0..dad58d8 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
@@ -14,7 +14,6 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.graph.UseRegistry;
-import com.android.tools.r8.ir.code.Invoke;
import com.android.tools.r8.ir.code.Invoke.Type;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
@@ -328,16 +327,57 @@
this.graph = graph;
}
- private void processInvoke(DexEncodedMethod source, Invoke.Type type, DexMethod method) {
+ private void addClassInitializerTarget(DexClass clazz) {
+ assert clazz != null;
+ if (clazz.hasClassInitializer() && !clazz.isLibraryClass()) {
+ DexEncodedMethod possibleTarget = clazz.getClassInitializer();
+ addTarget(possibleTarget);
+ }
+ }
+
+ private void addClassInitializerTarget(DexType type) {
+ if (type.isArrayType()) {
+ type = type.toBaseType(appInfo.dexItemFactory);
+ }
+ DexClass clazz = appInfo.definitionFor(type);
+ if (clazz != null) {
+ addClassInitializerTarget(clazz);
+ }
+ }
+
+ private void addTarget(DexEncodedMethod target) {
+ Node callee = graph.ensureMethodNode(target);
+ graph.addCall(caller, callee);
+ }
+
+ private void addPossibleTarget(DexEncodedMethod possibleTarget) {
+ DexClass possibleTargetClass =
+ appInfo.definitionFor(possibleTarget.method.getHolder());
+ if (possibleTargetClass != null && !possibleTargetClass.isLibraryClass()) {
+ addTarget(possibleTarget);
+ }
+ }
+
+ private void addPossibleTargets(
+ DexEncodedMethod definition, Set<DexEncodedMethod> possibleTargets) {
+ for (DexEncodedMethod possibleTarget : possibleTargets) {
+ if (possibleTarget != definition) {
+ addPossibleTarget(possibleTarget);
+ }
+ }
+ }
+
+ private void processInvoke(Type type, DexMethod method) {
+ DexEncodedMethod source = caller.method;
method = graphLense.lookupMethod(method, source);
DexEncodedMethod definition = appInfo.lookup(type, method);
if (definition != null) {
assert !source.accessFlags.isBridge() || definition != caller.method;
- DexType definitionHolder = definition.method.getHolder();
- assert definitionHolder.isClassType();
- if (!appInfo.definitionFor(definitionHolder).isLibraryClass()) {
- Node callee = graph.ensureMethodNode(definition);
- graph.addCall(caller, callee);
+ DexClass definitionHolder = appInfo.definitionFor(definition.method.getHolder());
+ assert definitionHolder != null;
+ if (!definitionHolder.isLibraryClass()) {
+ addClassInitializerTarget(definitionHolder);
+ addTarget(definition);
// For virtual and interface calls add all potential targets that could be called.
if (type == Type.VIRTUAL || type == Type.INTERFACE) {
Set<DexEncodedMethod> possibleTargets;
@@ -346,73 +386,74 @@
} else {
possibleTargets = appInfo.lookupVirtualTargets(definition.method);
}
- for (DexEncodedMethod possibleTarget : possibleTargets) {
- if (possibleTarget != definition) {
- DexClass possibleTargetClass =
- appInfo.definitionFor(possibleTarget.method.getHolder());
- if (possibleTargetClass != null && !possibleTargetClass.isLibraryClass()) {
- callee = graph.ensureMethodNode(possibleTarget);
- graph.addCall(caller, callee);
- }
- }
- }
+ addPossibleTargets(definition, possibleTargets);
}
}
}
}
+ private void processFieldAccess(DexField field) {
+ // Any field access implicitly calls the class initializer.
+ addClassInitializerTarget(field.getHolder());
+ }
+
@Override
public boolean registerInvokeVirtual(DexMethod method) {
- processInvoke(caller.method, Type.VIRTUAL, method);
+ processInvoke(Type.VIRTUAL, method);
return false;
}
@Override
public boolean registerInvokeDirect(DexMethod method) {
- processInvoke(caller.method, Type.DIRECT, method);
+ processInvoke(Type.DIRECT, method);
return false;
}
@Override
public boolean registerInvokeStatic(DexMethod method) {
- processInvoke(caller.method, Type.STATIC, method);
+ processInvoke(Type.STATIC, method);
return false;
}
@Override
public boolean registerInvokeInterface(DexMethod method) {
- processInvoke(caller.method, Type.INTERFACE, method);
+ processInvoke(Type.INTERFACE, method);
return false;
}
@Override
public boolean registerInvokeSuper(DexMethod method) {
- processInvoke(caller.method, Type.SUPER, method);
+ processInvoke(Type.SUPER, method);
return false;
}
@Override
public boolean registerInstanceFieldWrite(DexField field) {
+ processFieldAccess(field);
return false;
}
@Override
public boolean registerInstanceFieldRead(DexField field) {
+ processFieldAccess(field);
return false;
}
@Override
public boolean registerNewInstance(DexType type) {
+ addClassInitializerTarget(type);
return false;
}
@Override
public boolean registerStaticFieldRead(DexField field) {
+ processFieldAccess(field);
return false;
}
@Override
public boolean registerStaticFieldWrite(DexField field) {
+ processFieldAccess(field);
return false;
}
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 23b6b70..3cd40f7 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
@@ -463,6 +463,7 @@
codeRewriter.foldConstants(code);
codeRewriter.rewriteSwitch(code);
codeRewriter.simplifyIf(code);
+ 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 48a64b8..eab1dfa 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
@@ -6,13 +6,26 @@
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.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
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.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.DexValueNull;
+import com.android.tools.r8.graph.DexValue.DexValueShort;
+import com.android.tools.r8.graph.DexValue.DexValueString;
import com.android.tools.r8.ir.code.ArrayPut;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.Binop;
@@ -55,6 +68,7 @@
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Maps;
import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
@@ -605,6 +619,102 @@
}
}
+ public void collectClassInitializerDefaults(DexEncodedMethod method, IRCode code) {
+ if (!method.isClassInitializer()) {
+ return;
+ }
+
+ // Collect all 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).
+ DominatorTree dominatorTree = new DominatorTree(code);
+ BasicBlock exit = code.getNormalExitBlock();
+ if (exit == null) {
+ return;
+ }
+ Map<DexField, StaticPut> puts = Maps.newIdentityHashMap();
+ for (BasicBlock block : dominatorTree.dominatorBlocks(exit)) {
+ 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 (!(field.type.isPrimitiveType()
+ || field.type == dexItemFactory.stringType)
+ || field.getHolder() != method.method.getHolder()) {
+ continue;
+ }
+ if (put.inValue().isConstant()) {
+ // Collect put as a potential default value.
+ puts.put(put.getField(), 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());
+ }
+ }
+ }
+ }
+
+ if (!puts.isEmpty()) {
+ // Set initial values for static fields from the definitive static put instructions collected.
+ for (StaticPut put : puts.values()) {
+ DexField field = put.getField();
+ DexEncodedField encodedField = appInfo.definitionFor(field);
+ if (field.type == dexItemFactory.stringType) {
+ if (put.inValue().getConstInstruction().isConstNumber()) {
+ assert put.inValue().getConstInstruction().asConstNumber().isZero();
+ encodedField.staticValue = DexValueNull.NULL;
+ } else {
+ ConstString cnst = put.inValue().getConstInstruction().asConstString();
+ encodedField.staticValue = new DexValueString(cnst.getValue());
+ }
+ } else {
+ ConstNumber cnst = put.inValue().getConstInstruction().asConstNumber();
+ if (field.type == dexItemFactory.booleanType) {
+ encodedField.staticValue = DexValueBoolean.create(cnst.getBooleanValue());
+ } else if (field.type == dexItemFactory.byteType) {
+ encodedField.staticValue = DexValueByte.create((byte) cnst.getIntValue());
+ } else if (field.type == dexItemFactory.shortType) {
+ encodedField.staticValue = DexValueShort.create((short) cnst.getIntValue());
+ } else if (field.type == dexItemFactory.intType) {
+ encodedField.staticValue = DexValueInt.create(cnst.getIntValue());
+ } else if (field.type == dexItemFactory.longType) {
+ encodedField.staticValue = DexValueLong.create(cnst.getLongValue());
+ } else if (field.type == dexItemFactory.floatType) {
+ encodedField.staticValue = DexValueFloat.create(cnst.getFloatValue());
+ } else if (field.type == dexItemFactory.doubleType) {
+ encodedField.staticValue = DexValueDouble.create(cnst.getDoubleValue());
+ } else if (field.type == dexItemFactory.charType) {
+ encodedField.staticValue = DexValueChar.create((char) cnst.getIntValue());
+ } else {
+ throw new Unreachable("Unexpected field type.");
+ }
+ }
+ }
+
+ // Remove the static put instructions now replaced by static filed initial values.
+ for (BasicBlock block : dominatorTree.dominatorBlocks(exit)) {
+ InstructionListIterator iterator = block.listIterator();
+ while (iterator.hasNext()) {
+ Instruction current = iterator.next();
+ if (current.isStaticPut() && puts.values().contains(current.asStaticPut())) {
+ iterator.remove();
+ }
+ }
+ }
+
+ }
+ }
+
/**
* Due to inlining, we might see chains of casts on subtypes. It suffices to cast to the lowest
* subtype, as that will fail if a cast on a supertype would have failed.
diff --git a/src/main/java/com/android/tools/r8/utils/EncodedValueUtils.java b/src/main/java/com/android/tools/r8/utils/EncodedValueUtils.java
index 8903525..d6848a4 100644
--- a/src/main/java/com/android/tools/r8/utils/EncodedValueUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/EncodedValueUtils.java
@@ -13,10 +13,11 @@
long result = 0;
int shift = 0;
for (int i = 1; i < numberOfBytes; i++) {
- result |= ((long) ((file.get() & 0xFF))) << shift;
+ result |= ((long) (file.get() & 0xFF)) << shift;
shift += 8;
}
- return result | (file.get() << shift);
+ // Let the last byte sign-extend into any remaining bytes.
+ return result | (((long) file.get()) << shift);
}
// Inspired by com.android.dex.EncodedValueCodec
diff --git a/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValues.java b/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValues.java
new file mode 100644
index 0000000..085b069
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValues.java
@@ -0,0 +1,198 @@
+// 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.rewrite.staticvalues;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.smali.SmaliTestBase;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.InternalOptions;
+import org.junit.Test;
+
+public class StaticValues extends SmaliTestBase {
+
+ @Test
+ public void testAllTypes() {
+ SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+
+ builder.addStaticField("booleanField", "Z");
+ builder.addStaticField("byteField", "B");
+ builder.addStaticField("shortField", "S");
+ builder.addStaticField("intField", "I");
+ builder.addStaticField("longField", "J");
+ builder.addStaticField("floatField", "F");
+ builder.addStaticField("doubleField", "D");
+ builder.addStaticField("charField", "C");
+ builder.addStaticField("stringField", "Ljava/lang/String;");
+
+ builder.addStaticInitializer(
+ 2,
+ "const v0, 1",
+ "sput-byte v0, LTest;->booleanField:Z",
+ "sput-byte v0, LTest;->byteField:B",
+ "const v0, 2",
+ "sput-short v0, LTest;->shortField:S",
+ "const v0, 3",
+ "sput v0, LTest;->intField:I",
+ "const-wide v0, 4",
+ "sput-wide v0, LTest;->longField:J",
+ "const v0, 0x40a00000", // 5.0.
+ "sput v0, LTest;->floatField:F",
+ "const-wide v0, 0x4018000000000000L", // 6.0.
+ "sput-wide v0, LTest;->doubleField:D",
+ "const v0, 0x37", // ASCII 7.
+ "sput-char v0, LTest;->charField:C",
+ "const-string v0, \"8\"",
+ "sput-object v0, LTest;->stringField:Ljava/lang/String;",
+ "return-void"
+ );
+ builder.addMainMethod(
+ 3,
+ "sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+ "sget-boolean v1, LTest;->booleanField:Z",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Z)V",
+ "sget-byte v1, LTest;->byteField:B",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(I)V",
+ "sget-short v1, LTest;->shortField:S",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(I)V",
+ "sget v1, LTest;->intField:I",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(I)V",
+ "sget-wide v1, LTest;->longField:J",
+ "invoke-virtual { v0, v1, v2 }, Ljava/io/PrintStream;->println(J)V",
+ "sget v1, LTest;->floatField:F",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(F)V",
+ "sget-wide v1, LTest;->doubleField:D",
+ "invoke-virtual { v0, v1, v2 }, Ljava/io/PrintStream;->println(D)V",
+ "sget-char v1, LTest;->charField:C",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(C)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);
+ MethodSubject clinit = inspector.clazz("Test").clinit();
+ // The const-string and return-void instructions are left.
+ assertEquals(2, clinit.getMethod().getCode().asDexCode().instructions.length);
+
+ String result = runArt(processedApplication, options);
+
+ assertEquals("true\n1\n2\n3\n4\n5.0\n6.0\n7\n8\n", result);
+ }
+
+ @Test
+ public void getBeforePut() {
+ SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+
+ builder.addStaticField("field1", "I", "1");
+ builder.addStaticField("field2", "I", "2");
+
+ builder.addStaticInitializer(
+ 1,
+ "sget v0, LTest;->field1:I",
+ "sput v0, LTest;->field2:I",
+ "const v0, 0",
+ "sput v0, LTest;->field1:I",
+ "return-void"
+ );
+ builder.addMainMethod(
+ 2,
+ "sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+ "sget v1, LTest;->field1:I",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(I)V",
+ "sget v1, LTest;->field2:I",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(I)V",
+ "return-void"
+ );
+
+ InternalOptions options = new InternalOptions();
+ DexApplication originalApplication = buildApplication(builder, options);
+ DexApplication processedApplication = processApplication(originalApplication, options);
+
+ DexInspector inspector = new DexInspector(processedApplication);
+ MethodSubject clinit = inspector.clazz("Test").clinit();
+ // Nothing changed in the class initializer.
+ assertEquals(5, clinit.getMethod().getCode().asDexCode().instructions.length);
+
+ String result = runArt(processedApplication, options);
+
+ assertEquals("0\n1\n", result);
+ }
+
+ @Test
+ public void nullString() {
+ SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+
+ builder.addStaticField("stringField", "Ljava/lang/String;", "Hello");
+
+ builder.addStaticInitializer(
+ 1,
+ "const v0, 0",
+ "sput-object v0, LTest;->stringField:Ljava/lang/String;",
+ "return-void"
+ );
+ builder.addMainMethod(
+ 2,
+ "sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+ "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);
+ MethodSubject clinit = inspector.clazz("Test").clinit();
+ // The return-void instruction is left.
+ assertEquals(1, clinit.getMethod().getCode().asDexCode().instructions.length);
+
+ String result = runArt(processedApplication, options);
+
+ assertEquals("null\n", result);
+ }
+
+ @Test
+ public void fieldOnOtherClass() {
+ SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+
+ builder.addStaticInitializer(
+ 1,
+ "const v0, 2",
+ "sput v0, LOther;->field:I",
+ "return-void"
+ );
+ builder.addMainMethod(
+ 2,
+ "sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+ "sget v1, LOther;->field:I",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(I)V",
+ "return-void"
+ );
+
+ builder.addClass("Other");
+ builder.addStaticField("field", "I", "1");
+
+ InternalOptions options = new InternalOptions();
+ DexApplication originalApplication = buildApplication(builder, options);
+ DexApplication processedApplication = processApplication(originalApplication, options);
+
+ DexInspector inspector = new DexInspector(processedApplication);
+ MethodSubject clinit = inspector.clazz("Test").clinit();
+ // Nothing changed in the class initializer.
+ assertEquals(3, clinit.getMethod().getCode().asDexCode().instructions.length);
+
+ String result = runArt(processedApplication, options);
+
+ assertEquals("2\n", result);
+ }
+
+}
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/smali/SmaliTestBase.java b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
index f070130..ab9bf96 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
@@ -71,6 +71,10 @@
this.returnType = returnType;
this.parameterTypes = parameterTypes;
}
+
+ public static MethodSignature staticInitializer(String clazz) {
+ return new MethodSignature(clazz, "<clinit>", "void", ImmutableList.of());
+ }
}
public static class SmaliBuilder {
@@ -210,6 +214,29 @@
);
}
+ public void addStaticField(String name, String type, String defaultValue) {
+ StringBuilder builder = new StringBuilder();
+ builder.append(".field static ");
+ builder.append(name);
+ builder.append(":");
+ builder.append(type);
+ if (defaultValue != null) {
+ builder.append(" = ");
+ if (type.equals("Ljava/lang/String;")) {
+ builder.append('"');
+ builder.append(defaultValue);
+ builder.append('"');
+ } else {
+ builder.append(defaultValue);
+ }
+ }
+ getSource(currentClassName).add(builder.toString());
+ }
+
+ public void addStaticField(String name, String type) {
+ addStaticField(name, type, null);
+ }
+
public MethodSignature addStaticMethod(String returnType, String name, List<String> parameters,
int locals, String... instructions) {
StringBuilder builder = new StringBuilder();
@@ -222,8 +249,30 @@
public MethodSignature addStaticMethod(String returnType, String name, List<String> parameters,
int locals, String code) {
+ return addStaticMethod("", returnType, name, parameters, locals, code);
+ }
+
+ public MethodSignature addStaticInitializer(int locals, String... instructions) {
+ StringBuilder builder = new StringBuilder();
+ for (String instruction : instructions) {
+ builder.append(instruction);
+ builder.append("\n");
+ }
+ return addStaticInitializer(locals, builder.toString());
+ }
+
+ public MethodSignature addStaticInitializer(int locals, String code) {
+ return addStaticMethod("constructor", "void", "<clinit>", ImmutableList.of(), locals, code);
+ }
+
+ private MethodSignature addStaticMethod(String flags, String returnType, String name,
+ List<String> parameters, int locals, String code) {
StringBuilder builder = new StringBuilder();
builder.append(".method public static ");
+ if (flags != null && flags.length() > 0) {
+ builder.append(flags);
+ builder.append(" ");
+ }
builder.append(name);
builder.append("(");
for (String parameter : parameters) {
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 61c3c14..16c791d 100644
--- a/src/test/java/com/android/tools/r8/utils/DexInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/DexInspector.java
@@ -267,6 +267,10 @@
public abstract MethodSubject method(String returnType, String name, List<String> parameters);
+ public MethodSubject clinit() {
+ return method("void", "<clinit>", ImmutableList.of());
+ }
+
public MethodSubject method(MethodSignature signature) {
return method(signature.type, signature.name, ImmutableList.copyOf(signature.parameters));
}