Merge "Let dev versions tag dex with version sha-1"
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index d0739b9..76a6ed1 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -117,8 +117,7 @@
return result;
}
- static void runForTesting(AndroidApp app, OutputSink outputSink,
- InternalOptions options)
+ static void runForTesting(AndroidApp app, OutputSink outputSink, InternalOptions options)
throws IOException, CompilationException {
ExecutorService executor = ThreadUtils.getExecutorService(options);
try {
@@ -130,14 +129,14 @@
private static void runForTesting(
AndroidApp app,
- OutputSink outputSink, InternalOptions options,
+ OutputSink outputSink,
+ InternalOptions options,
ExecutorService executor)
throws IOException, CompilationException {
new R8(options).run(app, outputSink, executor);
}
- private void run(AndroidApp inputApp, OutputSink outputSink,
- ExecutorService executorService)
+ private void run(AndroidApp inputApp, OutputSink outputSink, ExecutorService executorService)
throws IOException, CompilationException {
if (options.quiet) {
System.setOut(new PrintStream(ByteStreams.nullOutputStream()));
diff --git a/src/main/java/com/android/tools/r8/cf/CfPrinter.java b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
new file mode 100644
index 0000000..af51fdc
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
@@ -0,0 +1,306 @@
+// 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.cf;
+
+import com.android.tools.r8.cf.code.CfArrayLoad;
+import com.android.tools.r8.cf.code.CfConstNull;
+import com.android.tools.r8.cf.code.CfConstNumber;
+import com.android.tools.r8.cf.code.CfConstString;
+import com.android.tools.r8.cf.code.CfFrame;
+import com.android.tools.r8.cf.code.CfGoto;
+import com.android.tools.r8.cf.code.CfIf;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfLabel;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfNew;
+import com.android.tools.r8.cf.code.CfPop;
+import com.android.tools.r8.cf.code.CfReturn;
+import com.android.tools.r8.cf.code.CfStaticGet;
+import com.android.tools.r8.cf.code.CfStore;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.ValueType;
+import java.util.HashMap;
+import java.util.Map;
+import org.objectweb.asm.Opcodes;
+
+/**
+ * Utility to print CF code and instructions.
+ *
+ * <p>This implementation prints the code formatted according to the Jasmin syntax.
+ */
+public class CfPrinter {
+
+ private final String indent;
+ private final Map<CfLabel, String> labels;
+
+ private final StringBuilder builder = new StringBuilder();
+
+ /** Entry for printing single instructions without global knowledge (eg, label numbers). */
+ public CfPrinter() {
+ indent = "";
+ labels = null;
+ }
+
+ /** Entry for printing a complete code object. */
+ public CfPrinter(CfCode code) {
+ indent = " ";
+ labels = new HashMap<>();
+ int nextLabelNumber = 0;
+ for (CfInstruction instruction : code.getInstructions()) {
+ if (instruction instanceof CfLabel) {
+ labels.put((CfLabel) instruction, "L" + nextLabelNumber++);
+ }
+ }
+ builder.append(".method ");
+ appendMethod(code.getMethod());
+ newline();
+ builder.append(".limit stack ").append(code.getMaxStack());
+ newline();
+ builder.append(".limit locals ").append(code.getMaxLocals());
+ for (CfInstruction instruction : code.getInstructions()) {
+ instruction.print(this);
+ }
+ newline();
+ builder.append(".end method");
+ newline();
+ }
+
+ private void print(String name) {
+ indent();
+ builder.append(name);
+ }
+
+ public void print(CfConstNull constNull) {
+ print("aconst_null");
+ }
+
+ public void print(CfConstNumber constNumber) {
+ indent();
+ // TODO(zerny): Determine when the number matches the tconst_X instructions.
+ switch (constNumber.getType()) {
+ case INT:
+ builder.append("ldc ").append(constNumber.getIntValue());
+ break;
+ case FLOAT:
+ builder.append("ldc ").append(constNumber.getFloatValue());
+ break;
+ case LONG:
+ builder.append("ldc_w ").append(constNumber.getLongValue());
+ break;
+ case DOUBLE:
+ builder.append("ldc_w ").append(constNumber.getDoubleValue());
+ break;
+ default:
+ throw new Unreachable("Unexpected const-number type: " + constNumber.getType());
+ }
+ }
+
+ public void print(CfReturn ret) {
+ print("return");
+ }
+
+ public void print(CfPop pop) {
+ print("pop");
+ }
+
+ public void print(CfConstString constString) {
+ indent();
+ builder.append("ldc ").append(constString.getString());
+ }
+
+ public void print(CfArrayLoad arrayLoad) {
+ switch (arrayLoad.getType()) {
+ case OBJECT:
+ print("aaload");
+ break;
+ case BOOLEAN:
+ case BYTE:
+ print("baload");
+ break;
+ case CHAR:
+ print("caload");
+ break;
+ case SHORT:
+ print("saload");
+ break;
+ case INT:
+ print("iaload");
+ break;
+ case FLOAT:
+ print("faload");
+ break;
+ case LONG:
+ print("laload");
+ break;
+ case DOUBLE:
+ print("daload");
+ break;
+ default:
+ throw new Unreachable("Unexpected array-load type: " + arrayLoad.getType());
+ }
+ }
+
+ public void print(CfInvoke invoke) {
+ indent();
+ switch (invoke.getOpcode()) {
+ case Opcodes.INVOKEVIRTUAL:
+ builder.append("invokevirtual");
+ break;
+ case Opcodes.INVOKESPECIAL:
+ builder.append("invokespecial");
+ break;
+ case Opcodes.INVOKESTATIC:
+ builder.append("invokestatic");
+ break;
+ case Opcodes.INVOKEINTERFACE:
+ builder.append("invokeinterface");
+ break;
+ case Opcodes.INVOKEDYNAMIC:
+ builder.append("invokedynamic");
+ break;
+ default:
+ throw new Unreachable("Unexpected invoke opcode: " + invoke.getOpcode());
+ }
+ builder.append(' ');
+ appendMethod(invoke.getMethod());
+ }
+
+ public void print(CfFrame frame) {
+ comment("frame");
+ }
+
+ public void print(CfStaticGet staticGet) {
+ indent();
+ builder.append("getstatic ");
+ appendField(staticGet.getField());
+ appendDescriptor(staticGet.getField().type);
+ }
+
+ public void print(CfNew newInstance) {
+ indent();
+ builder.append("new ");
+ appendClass(newInstance.getType());
+ }
+
+ public void print(CfLabel label) {
+ newline();
+ builder.append(getLabel(label)).append(':');
+ }
+
+ public void print(CfGoto jump) {
+ indent();
+ builder.append("goto ").append(getLabel(jump.getTarget()));
+ }
+
+ public void print(CfIf conditional) {
+ indent();
+ switch (conditional.getOpcode()) {
+ case Opcodes.IFNULL:
+ builder.append("ifnull");
+ break;
+ case Opcodes.IFNONNULL:
+ builder.append("ifnonnull");
+ break;
+ case Opcodes.IFEQ:
+ builder.append("ifeq");
+ break;
+ case Opcodes.IFNE:
+ builder.append("ifne");
+ break;
+ case Opcodes.IFLT:
+ builder.append("iflt");
+ break;
+ case Opcodes.IFGE:
+ builder.append("ifge");
+ break;
+ case Opcodes.IFGT:
+ builder.append("ifgt");
+ break;
+ case Opcodes.IFLE:
+ builder.append("ifle");
+ break;
+ default:
+ throw new Unreachable();
+ }
+ builder.append(' ').append(getLabel(conditional.getTarget()));
+ }
+
+ public void print(CfLoad load) {
+ print(load.getType(), "load", load.getLocalIndex());
+ }
+
+ public void print(CfStore store) {
+ print(store.getType(), "store", store.getLocalIndex());
+ }
+
+ private void print(ValueType type, String instruction, int local) {
+ indent();
+ builder.append(typePrefix(type)).append(instruction).append(' ').append(local);
+ }
+
+ private char typePrefix(ValueType type) {
+ switch (type) {
+ case OBJECT:
+ return 'a';
+ case INT:
+ return 'i';
+ case FLOAT:
+ return 'f';
+ case LONG:
+ return 'l';
+ case DOUBLE:
+ return 'd';
+ default:
+ throw new Unreachable("Unexpected type for prefix: " + type);
+ }
+ }
+
+ private String getLabel(CfLabel label) {
+ return labels != null ? labels.get(label) : "L?";
+ }
+
+ private void newline() {
+ if (builder.length() > 0) {
+ builder.append('\n');
+ }
+ }
+
+ private void indent() {
+ newline();
+ builder.append(indent);
+ }
+
+ private void comment(String comment) {
+ indent();
+ builder.append("; ").append(comment);
+ }
+
+ private void appendDescriptor(DexType type) {
+ builder.append(type.toDescriptorString());
+ }
+
+ private void appendClass(DexType type) {
+ builder.append(type.getInternalName());
+ }
+
+ private void appendField(DexField field) {
+ appendClass(field.getHolder());
+ builder.append('/').append(field.name);
+ }
+
+ private void appendMethod(DexMethod method) {
+ builder.append(method.qualifiedName());
+ builder.append(method.proto.toDescriptorString());
+ }
+
+ @Override
+ public String toString() {
+ return builder.toString();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
index 9043ab6..3ff24b9 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.cf.code;
+import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.ir.code.MemberType;
import org.objectweb.asm.MethodVisitor;
@@ -16,6 +17,10 @@
this.type = type;
}
+ public MemberType getType() {
+ return type;
+ }
+
private int getLoadType() {
switch (type) {
case OBJECT:
@@ -44,4 +49,9 @@
public void write(MethodVisitor visitor) {
visitor.visitInsn(getLoadType());
}
+
+ @Override
+ public void print(CfPrinter printer) {
+ printer.print(this);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java b/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java
index f022f78..a788a3b 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.cf.code;
+import com.android.tools.r8.cf.CfPrinter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
@@ -12,4 +13,9 @@
public void write(MethodVisitor visitor) {
visitor.visitInsn(Opcodes.ACONST_NULL);
}
+
+ @Override
+ public void print(CfPrinter printer) {
+ printer.print(this);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java b/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
index 1d51bab..c3b4490 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
@@ -3,9 +3,11 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.cf.code;
+import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.ir.code.ValueType;
import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
public class CfConstNumber extends CfInstruction {
@@ -17,23 +19,84 @@
this.type = type;
}
+ public ValueType getType() {
+ return type;
+ }
+
+ public long getRawValue() {
+ return value;
+ }
+
+ public int getIntValue() {
+ assert type == ValueType.INT;
+ return (int) value;
+ }
+
+ public long getLongValue() {
+ assert type == ValueType.LONG;
+ return value;
+ }
+
+ public float getFloatValue() {
+ assert type == ValueType.FLOAT;
+ return Float.intBitsToFloat((int) value);
+ }
+
+ public double getDoubleValue() {
+ assert type == ValueType.DOUBLE;
+ return Double.longBitsToDouble(value);
+ }
+
@Override
public void write(MethodVisitor visitor) {
switch (type) {
case INT:
- visitor.visitLdcInsn((int)value);
- break;
+ {
+ int value = getIntValue();
+ if (-1 <= value && value <= 5) {
+ visitor.visitInsn(Opcodes.ICONST_0 + value);
+ } else {
+ visitor.visitLdcInsn(value);
+ }
+ break;
+ }
case LONG:
- visitor.visitLdcInsn(value);
- break;
+ {
+ long value = getLongValue();
+ if (value == 0 || value == 1) {
+ visitor.visitInsn(Opcodes.LCONST_0 + (int) value);
+ } else {
+ visitor.visitLdcInsn(value);
+ }
+ break;
+ }
case FLOAT:
- visitor.visitLdcInsn(Float.intBitsToFloat((int)value));
- break;
+ {
+ float value = getFloatValue();
+ if (value == 0 || value == 1 || value == 2) {
+ visitor.visitInsn(Opcodes.FCONST_0 + (int) value);
+ } else {
+ visitor.visitLdcInsn(value);
+ }
+ break;
+ }
case DOUBLE:
- visitor.visitLdcInsn(Double.longBitsToDouble(value));
- break;
+ {
+ double value = getDoubleValue();
+ if (value == 0 || value == 1) {
+ visitor.visitInsn(Opcodes.DCONST_0 + (int) value);
+ } else {
+ visitor.visitLdcInsn(value);
+ }
+ break;
+ }
default:
throw new Unreachable("Non supported type in cf backend: " + type);
}
}
+
+ @Override
+ public void print(CfPrinter printer) {
+ printer.print(this);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstString.java b/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
index 62f1537..0f7f9f7 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.cf.code;
+import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.graph.DexString;
import org.objectweb.asm.MethodVisitor;
@@ -14,8 +15,17 @@
this.string = string;
}
+ public DexString getString() {
+ return string;
+ }
+
@Override
public void write(MethodVisitor visitor) {
visitor.visitLdcInsn(string.toString());
}
+
+ @Override
+ public void print(CfPrinter printer) {
+ printer.print(this);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
index 78a350d..1d946b2 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
@@ -5,6 +5,7 @@
import static org.objectweb.asm.Opcodes.F_NEW;
+import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
@@ -12,7 +13,6 @@
import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.Type;
public class CfFrame extends CfInstruction {
@@ -45,7 +45,7 @@
}
switch (type.toShorty()) {
case 'L':
- return Type.getType(type.toDescriptorString()).getInternalName();
+ return type.getInternalName();
case 'I':
return Opcodes.INTEGER;
case 'F':
@@ -63,4 +63,9 @@
public String toString() {
return getClass().getSimpleName();
}
+
+ @Override
+ public void print(CfPrinter printer) {
+ printer.print(this);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfGoto.java b/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
index 2207e6f..c881e7b 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.cf.code;
+import com.android.tools.r8.cf.CfPrinter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
@@ -14,8 +15,17 @@
this.target = target;
}
+ public CfLabel getTarget() {
+ return target;
+ }
+
@Override
public void write(MethodVisitor visitor) {
visitor.visitJumpInsn(Opcodes.GOTO, target.getLabel());
}
+
+ @Override
+ public void print(CfPrinter printer) {
+ printer.print(this);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfIf.java b/src/main/java/com/android/tools/r8/cf/code/CfIf.java
index b8397e5..5c7a2c7 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfIf.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfIf.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.cf.code;
+import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.ir.code.If;
import com.android.tools.r8.ir.code.ValueType;
@@ -21,7 +22,11 @@
this.target = target;
}
- private int getOpcode() {
+ public CfLabel getTarget() {
+ return target;
+ }
+
+ public int getOpcode() {
switch (kind) {
case EQ:
return type.isObject() ? Opcodes.IFNULL : Opcodes.IFEQ;
@@ -41,6 +46,11 @@
}
@Override
+ public void print(CfPrinter printer) {
+ printer.print(this);
+ }
+
+ @Override
public void write(MethodVisitor visitor) {
visitor.visitJumpInsn(getOpcode(), target.getLabel());
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
index 915a491..76806f6 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
@@ -3,14 +3,20 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.cf.code;
+import com.android.tools.r8.cf.CfPrinter;
import org.objectweb.asm.MethodVisitor;
public abstract class CfInstruction {
public abstract void write(MethodVisitor visitor);
+ public abstract void print(CfPrinter printer);
+
@Override
public String toString() {
- return getClass().getSimpleName();
+ CfPrinter printer = new CfPrinter();
+ print(printer);
+ return printer.toString();
}
+
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
index 0e927ac..d90fb92 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
@@ -3,10 +3,10 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.cf.code;
+import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.graph.DexMethod;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.Type;
public class CfInvoke extends CfInstruction {
@@ -19,12 +19,25 @@
this.method = method;
}
+ public DexMethod getMethod() {
+ return method;
+ }
+
+ public int getOpcode() {
+ return opcode;
+ }
+
@Override
public void write(MethodVisitor visitor) {
- String owner = Type.getType(method.getHolder().toDescriptorString()).getInternalName();
+ String owner = method.getHolder().getInternalName();
String name = method.name.toString();
String desc = method.proto.toDescriptorString();
boolean iface = method.holder.isInterface();
visitor.visitMethodInsn(opcode, owner, name, desc, iface);
}
+
+ @Override
+ public void print(CfPrinter printer) {
+ printer.print(this);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfLabel.java b/src/main/java/com/android/tools/r8/cf/code/CfLabel.java
index 9c6560d..13d9078 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfLabel.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfLabel.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.cf.code;
+import com.android.tools.r8.cf.CfPrinter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
@@ -18,6 +19,11 @@
}
@Override
+ public void print(CfPrinter printer) {
+ printer.print(this);
+ }
+
+ @Override
public void write(MethodVisitor visitor) {
visitor.visitLabel(getLabel());
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfLoad.java b/src/main/java/com/android/tools/r8/cf/code/CfLoad.java
index af053a8..7d6be37 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfLoad.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfLoad.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.cf.code;
+import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.ir.code.ValueType;
import org.objectweb.asm.MethodVisitor;
@@ -39,4 +40,17 @@
public void write(MethodVisitor visitor) {
visitor.visitVarInsn(getLoadType(), var);
}
+
+ @Override
+ public void print(CfPrinter printer) {
+ printer.print(this);
+ }
+
+ public ValueType getType() {
+ return type;
+ }
+
+ public int getLocalIndex() {
+ return var;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNew.java b/src/main/java/com/android/tools/r8/cf/code/CfNew.java
index b528ea5..48b2ca3 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNew.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNew.java
@@ -3,10 +3,10 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.cf.code;
+import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.graph.DexType;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.Type;
public class CfNew extends CfInstruction {
@@ -16,8 +16,17 @@
this.type = type;
}
+ public DexType getType() {
+ return type;
+ }
+
@Override
public void write(MethodVisitor visitor) {
- visitor.visitTypeInsn(Opcodes.NEW, Type.getType(type.toDescriptorString()).getInternalName());
+ visitor.visitTypeInsn(Opcodes.NEW, type.getInternalName());
+ }
+
+ @Override
+ public void print(CfPrinter printer) {
+ printer.print(this);
}
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfPop.java b/src/main/java/com/android/tools/r8/cf/code/CfPop.java
index e058df6..68cedb9 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfPop.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfPop.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.cf.code;
+import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.ir.code.ValueType;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
@@ -19,4 +20,9 @@
public void write(MethodVisitor visitor) {
visitor.visitInsn(type.isWide() ? Opcodes.POP2 : Opcodes.POP);
}
+
+ @Override
+ public void print(CfPrinter printer) {
+ printer.print(this);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfReturn.java b/src/main/java/com/android/tools/r8/cf/code/CfReturn.java
index 4cf2908..1a20d29 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfReturn.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfReturn.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.cf.code;
+import com.android.tools.r8.cf.CfPrinter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
@@ -12,4 +13,9 @@
public void write(MethodVisitor visitor) {
visitor.visitInsn(Opcodes.RETURN);
}
+
+ @Override
+ public void print(CfPrinter printer) {
+ printer.print(this);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStaticGet.java b/src/main/java/com/android/tools/r8/cf/code/CfStaticGet.java
index 6e71cb5..c5a68b6 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfStaticGet.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfStaticGet.java
@@ -3,10 +3,10 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.cf.code;
+import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.graph.DexField;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.Type;
public class CfStaticGet extends CfInstruction {
@@ -16,11 +16,20 @@
this.field = field;
}
+ public DexField getField() {
+ return field;
+ }
+
@Override
public void write(MethodVisitor visitor) {
- String owner = Type.getType(field.getHolder().toDescriptorString()).getInternalName();
+ String owner = field.getHolder().getInternalName();
String name = field.name.toString();
String desc = field.type.toDescriptorString();
visitor.visitFieldInsn(Opcodes.GETSTATIC, owner, name, desc);
}
+
+ @Override
+ public void print(CfPrinter printer) {
+ printer.print(this);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStore.java b/src/main/java/com/android/tools/r8/cf/code/CfStore.java
index 0074abc..2526a58 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfStore.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfStore.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.cf.code;
+import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.ir.code.ValueType;
import org.objectweb.asm.MethodVisitor;
@@ -39,4 +40,17 @@
public void write(MethodVisitor visitor) {
visitor.visitVarInsn(getStoreType(), var);
}
+
+ @Override
+ public void print(CfPrinter printer) {
+ printer.print(this);
+ }
+
+ public ValueType getType() {
+ return type;
+ }
+
+ public int getLocalIndex() {
+ return var;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/dex/IndexedItemCollection.java b/src/main/java/com/android/tools/r8/dex/IndexedItemCollection.java
index 73d5d86..4da7302 100644
--- a/src/main/java/com/android/tools/r8/dex/IndexedItemCollection.java
+++ b/src/main/java/com/android/tools/r8/dex/IndexedItemCollection.java
@@ -55,11 +55,11 @@
boolean addMethod(DexMethod method);
/**
- * Adds the given class to the collection.
+ * Adds the given string to the collection.
*
- * <p>Does not add the classes components.
+ * <p>Does not add the string's components.
*
- * @return true if the class was not in the pool before.
+ * @return true if the string was not in the pool before.
*/
boolean addString(DexString string);
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index 7c76969..b0d0827 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -4,35 +4,55 @@
package com.android.tools.r8.graph;
import com.android.tools.r8.ApiLevelException;
+import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.cf.code.CfInstruction;
import com.android.tools.r8.cf.code.CfTryCatch;
import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.utils.InternalOptions;
+import java.util.Collections;
import java.util.List;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Type;
public class CfCode extends Code {
+ private final DexMethod method;
private final int maxStack;
private final int maxLocals;
private final List<CfInstruction> instructions;
private final List<CfTryCatch> tryCatchRanges;
public CfCode(
+ DexMethod method,
int maxStack,
int maxLocals,
List<CfInstruction> instructions,
List<CfTryCatch> tryCatchRanges) {
+ this.method = method;
this.maxStack = maxStack;
this.maxLocals = maxLocals;
this.instructions = instructions;
this.tryCatchRanges = tryCatchRanges;
}
+ public DexMethod getMethod() {
+ return method;
+ }
+
+ public int getMaxStack() {
+ return maxStack;
+ }
+
+ public int getMaxLocals() {
+ return maxLocals;
+ }
+
+ public List<CfInstruction> getInstructions() {
+ return Collections.unmodifiableList(instructions);
+ }
+
@Override
public boolean isCfCode() {
return true;
@@ -59,9 +79,7 @@
start,
end,
target,
- guard == DexItemFactory.catchAllType
- ? null
- : Type.getType(guard.toDescriptorString()).getInternalName());
+ guard == DexItemFactory.catchAllType ? null : guard.getInternalName());
}
}
}
@@ -89,11 +107,7 @@
@Override
public String toString() {
- StringBuilder builder = new StringBuilder();
- for (CfInstruction instruction : instructions) {
- builder.append(instruction.toString()).append('\n');
- }
- return builder.toString();
+ return new CfPrinter(this).toString();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index dcf2498..cb7ff79 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -122,11 +122,11 @@
}
public DexEncodedMethod[] allMethodsSorted() {
- int vLen = virtualMethods().length;
- int dLen = directMethods().length;
- DexEncodedMethod[] result = new DexEncodedMethod[vLen+dLen];
- System.arraycopy(virtualMethods(), 0, result, 0, vLen);
- System.arraycopy(directMethods(), 0, result, vLen, dLen);
+ int vLen = virtualMethods.length;
+ int dLen = directMethods.length;
+ DexEncodedMethod[] result = new DexEncodedMethod[vLen + dLen];
+ System.arraycopy(virtualMethods, 0, result, 0, vLen);
+ System.arraycopy(directMethods, 0, result, vLen, dLen);
Arrays.sort(result,
(DexEncodedMethod a, DexEncodedMethod b) -> a.method.slowCompareTo(b.method));
return result;
@@ -166,6 +166,17 @@
instanceFields = MoreObjects.firstNonNull(values, NO_FIELDS);
}
+ public DexEncodedField[] allFieldsSorted() {
+ int iLen = instanceFields.length;
+ int sLen = staticFields.length;
+ DexEncodedField[] result = new DexEncodedField[iLen + sLen];
+ System.arraycopy(instanceFields, 0, result, 0, iLen);
+ System.arraycopy(staticFields, 0, result, iLen, sLen);
+ Arrays.sort(result,
+ (DexEncodedField a, DexEncodedField b) -> a.field.slowCompareTo(b.field));
+ return result;
+ }
+
/**
* Find direct method in this class matching method
*/
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 8fbcbce..8445c54 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -573,6 +573,7 @@
private boolean returnsConstant = false;
private long returnedConstant = 0;
private boolean forceInline = false;
+ private boolean useIdentifierNameString = false;
private OptimizationInfo() {
// Intentionally left empty.
@@ -584,6 +585,7 @@
returnsConstant = template.returnsConstant;
returnedConstant = template.returnedConstant;
forceInline = template.forceInline;
+ useIdentifierNameString = template.useIdentifierNameString;
}
public boolean returnsArgument() {
@@ -612,6 +614,10 @@
return forceInline;
}
+ public boolean useIdentifierNameString() {
+ return useIdentifierNameString;
+ }
+
private void markReturnsArgument(int argument) {
assert argument >= 0;
assert returnedArgument == -1 || returnedArgument == argument;
@@ -632,6 +638,10 @@
forceInline = true;
}
+ private void markUseIdentifierNameString() {
+ useIdentifierNameString = true;
+ }
+
public OptimizationInfo copy() {
return new OptimizationInfo(this);
}
@@ -673,6 +683,10 @@
ensureMutableOI().markForceInline();
}
+ synchronized public void markUseIdentifierNameString() {
+ ensureMutableOI().markUseIdentifierNameString();
+ }
+
public OptimizationInfo getOptimizationInfo() {
return optimizationInfo;
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemBasedString.java b/src/main/java/com/android/tools/r8/graph/DexItemBasedString.java
new file mode 100644
index 0000000..7777147
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/DexItemBasedString.java
@@ -0,0 +1,48 @@
+// 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.graph;
+
+import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.errors.Unreachable;
+import java.util.Arrays;
+
+public class DexItemBasedString extends DexString {
+ public final IndexedDexItem basedOn;
+
+ DexItemBasedString(DexType basedOn) {
+ super(basedOn.toString());
+ this.basedOn = basedOn;
+ }
+
+ DexItemBasedString(DexField basedOn) {
+ super(basedOn.name.toString());
+ this.basedOn = basedOn;
+ }
+
+ DexItemBasedString(DexMethod basedOn) {
+ super(basedOn.name.toString());
+ this.basedOn = basedOn;
+ }
+
+ @Override
+ public boolean computeEquals(Object other) {
+ if (other instanceof DexItemBasedString) {
+ DexItemBasedString o = (DexItemBasedString) other;
+ return basedOn == o.basedOn && size == o.size && Arrays.equals(content, o.content);
+ }
+ return false;
+ }
+
+ @Override
+ public int computeHashCode() {
+ return super.computeHashCode() + 7 * basedOn.hashCode();
+ }
+
+ @Override
+ public void collectIndexedItems(IndexedItemCollection indexedItems) {
+ // This instance should not exist when collecting indexed items.
+ // {@link IdentifierMinifier} will replace this with an appropriate {@link DexString}.
+ throw new Unreachable("Remaining DexItemBasedString: " + this.toString());
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 8f45a2e..0d4644a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -51,6 +51,10 @@
private final Map<DexString, SetFile> setFiles = new HashMap<>();
private final Map<SetInlineFrame, SetInlineFrame> setInlineFrames = new HashMap<>();
+ // -identifiernamestring canonicalization.
+ private final ConcurrentHashMap<DexItemBasedString, DexItemBasedString> identifiers =
+ new ConcurrentHashMap<>();
+
boolean sorted = false;
public static final DexType catchAllType = new DexType(new DexString("CATCH_ALL"));
@@ -109,10 +113,14 @@
public final DexString objectDescriptor = createString("Ljava/lang/Object;");
public final DexString objectArrayDescriptor = createString("[Ljava/lang/Object;");
public final DexString classDescriptor = createString("Ljava/lang/Class;");
+ public final DexString fieldDescriptor = createString("Ljava/lang/reflect/Field;");
+ public final DexString methodDescriptor = createString("Ljava/lang/reflect/Method;");
public final DexString enumDescriptor = createString("Ljava/lang/Enum;");
public final DexString annotationDescriptor = createString("Ljava/lang/annotation/Annotation;");
public final DexString throwableDescriptor = createString("Ljava/lang/Throwable;");
public final DexString objectsDescriptor = createString("Ljava/util/Objects;");
+ public final DexString stringBuilderDescriptor = createString("Ljava/lang/StringBuilder;");
+ public final DexString stringBufferDescriptor = createString("Ljava/lang/StringBuffer;");
public final DexString varHandleDescriptor = createString("Ljava/lang/invoke/VarHandle;");
public final DexString constructorMethodName = createString(Constants.INSTANCE_INITIALIZER_NAME);
@@ -152,8 +160,8 @@
public final DexType annotationType = createType(annotationDescriptor);
public final DexType throwableType = createType(throwableDescriptor);
- public final DexType stringBuilderType = createType("Ljava/lang/StringBuilder;");
- public final DexType stringBufferType = createType("Ljava/lang/StringBuffer;");
+ public final DexType stringBuilderType = createType(stringBuilderDescriptor);
+ public final DexType stringBufferType = createType(stringBufferDescriptor);
public final DexType varHandleType = createType(varHandleDescriptor);
@@ -244,7 +252,6 @@
}
}
-
public class StringBuildingMethods {
public final DexMethod appendBoolean;
@@ -319,6 +326,24 @@
return canonicalize(strings, new DexString(source));
}
+ // TODO(b/67934123) Unify into one method,
+ public DexItemBasedString createItemBasedString(DexType type) {
+ assert !sorted;
+ return canonicalize(identifiers, new DexItemBasedString(type));
+ }
+
+ // TODO(b/67934123) Unify into one method,
+ public DexItemBasedString createItemBasedString(DexField field) {
+ assert !sorted;
+ return canonicalize(identifiers, new DexItemBasedString(field));
+ }
+
+ // TODO(b/67934123) Unify into one method,
+ public DexItemBasedString createItemBasedString(DexMethod method) {
+ assert !sorted;
+ return canonicalize(identifiers, new DexItemBasedString(method));
+ }
+
// Debugging support to extract marking string.
synchronized public Marker extractMarker() {
// This is slow but it is not needed for any production code yet.
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index 69a71f9..bae6fba 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -457,6 +457,13 @@
return getPackageOrName(false);
}
+ /** Get the fully qualified name using '/' in place of '.', ala the "internal type name" in ASM */
+ public String getInternalName() {
+ assert isClassType();
+ String descriptor = toDescriptorString();
+ return descriptor.substring(1, descriptor.length() - 1);
+ }
+
public boolean isImmediateSubtypeOf(DexType type) {
assert hierarchyLevel != UNKNOWN_LEVEL;
return type.directSubtypes.contains(this);
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
index 0fbac8f..18634c2 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
@@ -58,6 +58,8 @@
case LONG:
case DOUBLE:
case LONG_OR_DOUBLE:
+ assert builder.getOptions().canUseSameArrayAndResultRegisterInArrayGetWide()
+ || dest != array;
instruction = new AgetWide(dest, array, index);
break;
case OBJECT:
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 b925b8b..80917f0 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
@@ -500,6 +500,10 @@
return isConstant() && getConstInstruction().isConstString();
}
+ public boolean isConstClass() {
+ return isConstant() && getConstInstruction().isConstClass();
+ }
+
public boolean isConstant() {
return definition.isOutConstant() && !hasLocalInfo();
}
@@ -556,7 +560,7 @@
/**
* Returns whether this value is known to be the receiver (this argument) in a method body.
* <p>
- * For a receiver value {@link #isNeverNull()} is guarenteed to be <code>true</code> as well.
+ * For a receiver value {@link #isNeverNull()} is guaranteed to be <code>true</code> as well.
*/
public boolean isThis() {
return isThis;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
index 0ad6c6c..f1c3e32 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -37,10 +37,12 @@
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
+import java.util.Set;
public class CfBuilder {
@@ -50,6 +52,7 @@
private Map<Value, DexType> types;
private Map<BasicBlock, CfLabel> labels;
+ private Set<CfLabel> emittedLabels;
private List<CfInstruction> instructions;
private CfRegisterAllocator registerAllocator;
@@ -170,6 +173,7 @@
Stack stack = new Stack();
List<CfTryCatch> tryCatchRanges = new ArrayList<>();
labels = new HashMap<>(code.blocks.size());
+ emittedLabels = new HashSet<>(code.blocks.size());
instructions = new ArrayList<>();
Iterator<BasicBlock> blockIterator = code.listIterator();
BasicBlock block = blockIterator.next();
@@ -182,17 +186,12 @@
// Close try-catch and save the range.
CfLabel tryCatchEnd = getLabel(block);
tryCatchRanges.add(new CfTryCatch(tryCatchStart, tryCatchEnd, tryCatchHandlers, this));
- if (instructions.get(instructions.size() - 1) != tryCatchEnd) {
- instructions.add(tryCatchEnd);
- }
+ emitLabel(tryCatchEnd);
}
if (!handlers.isEmpty()) {
// Open a try-catch.
tryCatchStart = getLabel(block);
- if (instructions.isEmpty()
- || instructions.get(instructions.size() - 1) != tryCatchStart) {
- instructions.add(tryCatchStart);
- }
+ emitLabel(tryCatchStart);
}
tryCatchHandlers = handlers;
}
@@ -201,7 +200,7 @@
buildCfInstructions(block, fallthrough, stack);
if (nextBlock != null && (!fallthrough || nextBlock.getPredecessors().size() > 1)) {
assert stack.isEmpty();
- instructions.add(getLabel(nextBlock));
+ emitLabel(getLabel(nextBlock));
if (nextBlock.getPredecessors().size() > 1) {
addFrame(nextBlock, Collections.emptyList());
}
@@ -210,7 +209,11 @@
} while (block != null);
assert stack.isEmpty();
return new CfCode(
- stack.maxHeight, registerAllocator.registersUsed(), instructions, tryCatchRanges);
+ method.method,
+ stack.maxHeight,
+ registerAllocator.registersUsed(),
+ instructions,
+ tryCatchRanges);
}
private void buildCfInstructions(BasicBlock block, boolean fallthrough, Stack stack) {
@@ -268,9 +271,16 @@
instructions.add(new CfFrame(mapping));
}
+ private void emitLabel(CfLabel label) {
+ if (!emittedLabels.contains(label)) {
+ emittedLabels.add(label);
+ instructions.add(label);
+ }
+ }
+
// Callbacks
- public CfLabel getLabel(BasicBlock target){
+ public CfLabel getLabel(BasicBlock target) {
return labels.computeIfAbsent(target, (block) -> new CfLabel());
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
index 928d3c1..e7677b3 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
@@ -713,6 +713,10 @@
return handlers;
}
+ public InternalOptions getOptions() {
+ return options;
+ }
+
// Dex instruction wrapper with information to compute instruction sizes and offsets for jumps.
private static abstract class Info {
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 6728afe..8207e4c 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
@@ -35,6 +35,7 @@
import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
import com.android.tools.r8.ir.regalloc.RegisterAllocator;
import com.android.tools.r8.logging.Log;
+import com.android.tools.r8.naming.IdentifierNameStringMarker;
import com.android.tools.r8.shaking.protolite.ProtoLitePruner;
import com.android.tools.r8.utils.CfgPrinter;
import com.android.tools.r8.utils.DescriptorUtils;
@@ -78,13 +79,17 @@
private final LensCodeRewriter lensCodeRewriter;
private final Inliner inliner;
private final ProtoLitePruner protoLiteRewriter;
+ private final IdentifierNameStringMarker identifierNameStringMarker;
private OptimizationFeedback ignoreOptimizationFeedback = new OptimizationFeedbackIgnore();
private DexString highestSortingString;
private IRConverter(
- AppInfo appInfo, InternalOptions options, Timing timing,
- CfgPrinter printer, GraphLense graphLense,
+ AppInfo appInfo,
+ InternalOptions options,
+ Timing timing,
+ CfgPrinter printer,
+ GraphLense graphLense,
boolean enableWholeProgramOptimizations) {
assert appInfo != null;
assert options != null;
@@ -108,8 +113,14 @@
this.lensCodeRewriter = new LensCodeRewriter(graphLense, appInfo.withSubtyping());
if (appInfo.hasLiveness()) {
this.protoLiteRewriter = new ProtoLitePruner(appInfo.withLiveness());
+ if (!appInfo.withLiveness().identifierNameStrings.isEmpty()) {
+ this.identifierNameStringMarker = new IdentifierNameStringMarker(appInfo.withLiveness());
+ } else {
+ this.identifierNameStringMarker = null;
+ }
} else {
this.protoLiteRewriter = null;
+ this.identifierNameStringMarker = null;
}
} else {
this.inliner = null;
@@ -117,6 +128,7 @@
this.memberValuePropagation = null;
this.lensCodeRewriter = null;
this.protoLiteRewriter = null;
+ this.identifierNameStringMarker = null;
}
}
@@ -133,7 +145,9 @@
* Create an IR converter for processing methods with full program optimization disabled.
*/
public IRConverter(
- AppInfo appInfo, InternalOptions options, Timing timing,
+ AppInfo appInfo,
+ InternalOptions options,
+ Timing timing,
CfgPrinter printer) {
this(appInfo, options, timing, printer, null, false);
}
@@ -142,7 +156,9 @@
* Create an IR converter for processing methods with full program optimization enabled.
*/
public IRConverter(
- AppInfoWithSubtyping appInfo, InternalOptions options, Timing timing,
+ AppInfoWithSubtyping appInfo,
+ InternalOptions options,
+ Timing timing,
CfgPrinter printer,
GraphLense graphLense) {
this(appInfo, options, timing, printer, graphLense, true);
@@ -507,6 +523,12 @@
assert graphLense.isIdentityLense();
}
}
+
+ if (identifierNameStringMarker != null) {
+ identifierNameStringMarker.decoupleIdentifierNameStrings(method, code);
+ assert code.isConsistentSSA();
+ }
+
if (memberValuePropagation != null) {
memberValuePropagation.rewriteWithConstantValues(code);
}
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index 2cae81a..92e7e05 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -293,13 +293,20 @@
// Close ranges up-to but excluding the first instruction. Ends are exclusive but the values
// might be live upon entering the first instruction (if they are used by it).
int entryIndex = block.entry().getNumber();
+ if (block.entry().isMoveException()) {
+ // Close locals at a move exception since they close as part of the exceptional transfer.
+ entryIndex++;
+ }
{
ListIterator<LocalRange> it = openRanges.listIterator(0);
while (it.hasNext()) {
LocalRange openRange = it.next();
if (openRange.end < entryIndex ||
- // Don't close the local if it is used by the entry instruction.
- (openRange.end == entryIndex && !usesValues(openRange.value, block.entry()))) {
+ // Don't close the local if it is used by the entry instruction. Exclude move
+ // exception instruction that are managed in other way that should be clean.
+ (openRange.end == entryIndex
+ && !block.entry().isMoveException()
+ && !usesValues(openRange.value, block.entry()))) {
it.remove();
assert currentLocals.get(openRange.register) == openRange.local;
currentLocals.remove(openRange.register);
@@ -989,6 +996,49 @@
return position % 2 == 1 ? position : position - 1;
}
+ // Art had a bug (b/68761724) for Android N and O in the arm32 interpreter
+ // where an aget-wide instruction using the same register for the array
+ // and the first register of the result could lead to the wrong exception
+ // being thrown on out of bounds.
+ //
+ // For instructions of the form 'aget-wide regA, regA, regB' where
+ // regB is out of bounds of non-null array in regA, Art would throw a null
+ // pointer exception instead of an ArrayIndexOutOfBounds exception.
+ //
+ // We work around that bug by disallowing aget-wide with the same array
+ // and result register.
+ private boolean needsArrayGetWideWorkaround(LiveIntervals intervals) {
+ if (options.canUseSameArrayAndResultRegisterInArrayGetWide()) {
+ return false;
+ }
+ if (intervals.requiredRegisters() == 1) {
+ // Not the live range for a wide value and therefore not the output of aget-wide.
+ return false;
+ }
+ if (intervals.getValue().isPhi()) {
+ // If this writes a new register pair it will be via a move and not an aget-wide operation.
+ return false;
+ }
+ if (intervals.getSplitParent() != intervals) {
+ // This is a split of a parent interval and therefore if this leads to a write of a
+ // register pair it will be via a move and not an aget-wide operation.
+ return false;
+ }
+ Instruction definition = intervals.getValue().definition;
+ return definition.isArrayGet() && definition.asArrayGet().outType().isWide();
+ }
+
+ // Is the array-get array register the same as the first register we are
+ // allocating for the result?
+ private boolean isArrayGetArrayRegister(LiveIntervals intervals, int register) {
+ assert needsArrayGetWideWorkaround(intervals);
+ Value array = intervals.getValue().definition.asArrayGet().array();
+ int arrayReg =
+ array.getLiveIntervals().getSplitCovering(intervals.getStart()).getRegister();
+ assert arrayReg != NO_REGISTER;
+ return arrayReg == register;
+ }
+
// The dalvik jit had a bug where the long operations add, sub, or, xor and and would write
// the first part of the result long before reading the second part of the input longs.
//
@@ -1271,6 +1321,11 @@
hasOverlappingLongRegisters(unhandledInterval, register)) {
return false;
}
+ // Check for aget-wide bug in recent Art VMs.
+ if (needsArrayGetWideWorkaround(unhandledInterval) &&
+ isArrayGetArrayRegister(unhandledInterval, register)) {
+ return false;
+ }
assignRegisterToUnhandledInterval(unhandledInterval, needsRegisterPair, register);
return true;
}
@@ -1374,6 +1429,21 @@
lastCandidate = candidate;
}
}
+ if (needsArrayGetWideWorkaround(unhandledInterval)) {
+ int lastCandidate = candidate;
+ while (isArrayGetArrayRegister(unhandledInterval, candidate)) {
+ // Make the overlapping register unavailable for allocation and try again.
+ freePositions.set(candidate, 0);
+ candidate = getLargestCandidate(registerConstraint, freePositions, needsRegisterPair, type);
+ // If there are only invalid candidates of the give type we will end up with the same
+ // candidate returned again once we have tried them all. In that case we didn't find a
+ // valid register candidate and we need to broaden the search to other types.
+ if (lastCandidate == candidate) {
+ return REGISTER_CANDIDATE_NOT_FOUND;
+ }
+ lastCandidate = candidate;
+ }
+ }
return candidate;
}
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 b2760e4..d2b2f5e 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -11,7 +11,6 @@
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.utils.InternalOptions;
import java.io.IOException;
import java.io.PrintWriter;
@@ -21,7 +20,6 @@
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Type;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.util.CheckClassAdapter;
@@ -68,13 +66,13 @@
int version = downgrade(clazz.getClassFileVersion());
int access = clazz.accessFlags.getAsCfAccessFlags();
String desc = clazz.type.toDescriptorString();
- String name = internalName(clazz.type);
+ String name = clazz.type.getInternalName();
String signature = null; // TODO(zerny): Support generic signatures.
String superName =
- clazz.type == options.itemFactory.objectType ? null : internalName(clazz.superType);
+ clazz.type == options.itemFactory.objectType ? null : clazz.superType.getInternalName();
String[] interfaces = new String[clazz.interfaces.values.length];
for (int i = 0; i < clazz.interfaces.values.length; i++) {
- interfaces[i] = internalName(clazz.interfaces.values[i]);
+ interfaces[i] = clazz.interfaces.values[i].getInternalName();
}
writer.visit(version, access, name, signature, superName, interfaces);
// TODO(zerny): Methods and fields.
@@ -107,10 +105,6 @@
}
}
- private static String internalName(DexType type) {
- return Type.getType(type.toDescriptorString()).getInternalName();
- }
-
private String printCf(byte[] result) {
ClassReader reader = new ClassReader(result);
ClassNode node = new ClassNode(ASM6);
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java b/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
index 552516d..92c2560 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
@@ -3,47 +3,60 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.naming;
+import static com.android.tools.r8.utils.DescriptorUtils.descriptorToJavaType;
+
import com.android.tools.r8.code.ConstString;
import com.android.tools.r8.code.ConstStringJumbo;
import com.android.tools.r8.code.Instruction;
-import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.Code;
import com.android.tools.r8.graph.DexCode;
+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.DexItem;
+import com.android.tools.r8.graph.DexItemBasedString;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DexValue.DexValueString;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.shaking.ProguardClassFilter;
-import com.android.tools.r8.utils.DescriptorUtils;
import java.util.Map;
+import java.util.Set;
class IdentifierMinifier {
- private final AppInfo appInfo;
+ private final AppInfoWithLiveness appInfo;
private final ProguardClassFilter adaptClassStrings;
private final NamingLens lens;
+ private final Set<DexItem> identifierNameStrings;
IdentifierMinifier(
- AppInfo appInfo,
+ AppInfoWithLiveness appInfo,
ProguardClassFilter adaptClassStrings,
NamingLens lens) {
this.appInfo = appInfo;
this.adaptClassStrings = adaptClassStrings;
this.lens = lens;
+ this.identifierNameStrings = appInfo.identifierNameStrings;
}
void run() {
if (!adaptClassStrings.isEmpty()) {
- handleAdaptClassStrings();
+ adaptClassStrings();
}
- // TODO(b/36799092): Handle influx of string literals from call sites to annotated members.
+ if (!identifierNameStrings.isEmpty()) {
+ replaceIdentifierNameString();
+ }
}
- private void handleAdaptClassStrings() {
- appInfo.classes().forEach(clazz -> {
+ private void adaptClassStrings() {
+ for (DexProgramClass clazz : appInfo.classes()) {
if (!adaptClassStrings.matches(clazz.type)) {
- return;
+ continue;
}
- clazz.forEachField(encodedField -> {
+ for (DexEncodedField encodedField : clazz.allFieldsSorted()) {
if (encodedField.staticValue instanceof DexValueString) {
DexString original = ((DexValueString) encodedField.staticValue).getValue();
DexString renamed = getRenamedStringLiteral(original);
@@ -51,15 +64,15 @@
encodedField.staticValue = new DexValueString(renamed);
}
}
- });
- clazz.forEachMethod(encodedMethod -> {
+ }
+ for (DexEncodedMethod encodedMethod : clazz.allMethodsSorted()) {
// Abstract methods do not have code_item.
if (encodedMethod.accessFlags.isAbstract()) {
- return;
+ continue;
}
Code code = encodedMethod.getCode();
if (code == null) {
- return;
+ continue;
}
assert code.isDexCode();
DexCode dexCode = code.asDexCode();
@@ -74,8 +87,8 @@
cnst.BBBBBBBB = getRenamedStringLiteral(dexString);
}
}
- });
- });
+ }
+ }
}
private DexString getRenamedStringLiteral(DexString originalLiteral) {
@@ -90,11 +103,68 @@
DexString renamed = lens.lookupDescriptor(type);
// Create a new DexString only when the corresponding string literal will be replaced.
if (renamed != originalLiteral) {
- return appInfo.dexItemFactory.createString(
- DescriptorUtils.descriptorToJavaType(renamed.toString()));
+ return appInfo.dexItemFactory.createString(descriptorToJavaType(renamed.toString()));
}
}
return originalLiteral;
}
+ private void replaceIdentifierNameString() {
+ for (DexProgramClass clazz : appInfo.classes()) {
+ for (DexEncodedMethod encodedMethod : clazz.allMethodsSorted()) {
+ if (!encodedMethod.getOptimizationInfo().useIdentifierNameString()) {
+ continue;
+ }
+ Code code = encodedMethod.getCode();
+ if (code == null) {
+ continue;
+ }
+ assert code.isDexCode();
+ DexCode dexCode = code.asDexCode();
+ for (Instruction instr : dexCode.instructions) {
+ if (instr instanceof ConstString
+ && ((ConstString) instr).getString() instanceof DexItemBasedString) {
+ ConstString cnst = (ConstString) instr;
+ DexItemBasedString itemBasedString = (DexItemBasedString) cnst.getString();
+ cnst.BBBB = materialize(itemBasedString);
+ } else if (instr instanceof ConstStringJumbo
+ && ((ConstStringJumbo) instr).getString() instanceof DexItemBasedString) {
+ ConstStringJumbo cnst = (ConstStringJumbo) instr;
+ DexItemBasedString itemBasedString = (DexItemBasedString) cnst.getString();
+ cnst.BBBBBBBB = materialize(itemBasedString);
+ }
+ }
+ }
+ }
+ // Some const strings could be moved to field's static value (from <clinit>).
+ for (DexItem dexItem : identifierNameStrings) {
+ if (!(dexItem instanceof DexField)) {
+ continue;
+ }
+ DexEncodedField encodedField = appInfo.definitionFor((DexField) dexItem);
+ if (!(encodedField.staticValue instanceof DexValueString)) {
+ continue;
+ }
+ DexString original = ((DexValueString) encodedField.staticValue).getValue();
+ if (original instanceof DexItemBasedString) {
+ encodedField.staticValue = new DexValueString(materialize((DexItemBasedString) original));
+ }
+ }
+ }
+
+ private DexString materialize(DexItemBasedString itemBasedString) {
+ if (itemBasedString.basedOn instanceof DexType) {
+ DexString renamed = lens.lookupDescriptor((DexType) itemBasedString.basedOn);
+ if (!renamed.toString().equals(itemBasedString.toString())) {
+ return appInfo.dexItemFactory.createString(descriptorToJavaType(renamed.toString()));
+ }
+ return renamed;
+ } else if (itemBasedString.basedOn instanceof DexMethod) {
+ return lens.lookupName((DexMethod) itemBasedString.basedOn);
+ } else {
+ assert itemBasedString.basedOn instanceof DexField;
+ return lens.lookupName((DexField) itemBasedString.basedOn);
+ }
+ }
+
}
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
new file mode 100644
index 0000000..2f48083
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
@@ -0,0 +1,183 @@
+// 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.naming;
+
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItem;
+import com.android.tools.r8.graph.DexItemBasedString;
+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.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.ConstString;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InstancePut;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.Invoke;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.StaticPut;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.utils.DescriptorUtils;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public class IdentifierNameStringMarker {
+ private final AppInfoWithLiveness appInfo;
+ private final DexItemFactory dexItemFactory;
+ private final Set<DexItem> identifierNameStrings;
+
+ public IdentifierNameStringMarker(AppInfoWithLiveness appInfo) {
+ this.appInfo = appInfo;
+ this.dexItemFactory = appInfo.dexItemFactory;
+ this.identifierNameStrings = appInfo.identifierNameStrings;
+ }
+
+ public void decoupleIdentifierNameStrings(DexEncodedMethod encodedMethod, IRCode code) {
+ for (BasicBlock block : code.blocks) {
+ InstructionListIterator iterator = block.listIterator();
+ while (iterator.hasNext()) {
+ Instruction instruction = iterator.next();
+ if (instruction.isStaticPut()) {
+ StaticPut staticPut = instruction.asStaticPut();
+ DexField field = staticPut.getField();
+ if (identifierNameStrings.contains(field)) {
+ Value in = staticPut.inValue();
+ Value newIn = decoupleTypeIdentifierIfNecessary(code, iterator, staticPut, in);
+ if (newIn != in) {
+ iterator.replaceCurrentInstruction(
+ new StaticPut(staticPut.getType(), newIn, field));
+ encodedMethod.markUseIdentifierNameString();
+ }
+ }
+ } else if (instruction.isInstancePut()) {
+ InstancePut instancePut = instruction.asInstancePut();
+ DexField field = instancePut.getField();
+ if (identifierNameStrings.contains(field)) {
+ Value in = instancePut.value();
+ Value newIn = decoupleTypeIdentifierIfNecessary(code, iterator, instancePut, in);
+ if (newIn != in) {
+ List<Value> values = new ArrayList<>(2);
+ values.add(newIn);
+ values.add(instancePut.object());
+ iterator.replaceCurrentInstruction(
+ new InstancePut(instancePut.getType(), values, field));
+ encodedMethod.markUseIdentifierNameString();
+ }
+ }
+ } else if (instruction.isInvokeMethod()) {
+ InvokeMethod invoke = instruction.asInvokeMethod();
+ DexMethod invokedMethod = invoke.getInvokedMethod();
+ if (identifierNameStrings.contains(invokedMethod)) {
+ List<Value> ins = invoke.arguments();
+ List<Value> newIns;
+ if (isReflectiveCase(invokedMethod.proto)) {
+ Value in = ins.get(1);
+ Value newIn =
+ decoupleReflectiveMemberIdentifier(code, iterator, invoke, in);
+ newIns =
+ ins.stream()
+ .map(i -> i == in ? newIn : i)
+ .collect(Collectors.toList());
+ } else {
+ newIns =
+ ins.stream()
+ .map(in -> decoupleTypeIdentifierIfNecessary(code, iterator, invoke, in))
+ .collect(Collectors.toList());
+ }
+ if (!ins.equals(newIns)) {
+ iterator.replaceCurrentInstruction(
+ Invoke.create(
+ invoke.getType(),
+ invokedMethod,
+ invokedMethod.proto,
+ invoke.outValue(),
+ newIns));
+ encodedMethod.markUseIdentifierNameString();
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private Value decoupleTypeIdentifierIfNecessary(
+ IRCode code, InstructionListIterator iterator, Instruction base, Value in) {
+ if (!in.isConstString()) {
+ return in;
+ }
+ ConstString constString = in.getConstInstruction().asConstString();
+ String maybeDescriptor =
+ DescriptorUtils.javaTypeToDescriptorIfValidJavaType(constString.getValue().toString());
+ if (maybeDescriptor == null) {
+ return in;
+ }
+ DexType type = dexItemFactory.createType(maybeDescriptor);
+ DexItemBasedString typeString = dexItemFactory.createItemBasedString(type);
+ // v_n <- "x.y.z" // in.definition
+ // ...
+ // ... <- ... v_n ..
+ // ...
+ // this.fld <- v_n // base
+ //
+ // ~>
+ //
+ // ...
+ // v_n' <- DexItemBasedString("Lx/y/z;") // decoupled
+ // this.fld <- v_n' // base
+ //
+ // 1) Move the cursor back to $base
+ iterator.previous();
+ // 2) Add $decoupled just before $base
+ Value newIn = code.createValue(in.outType(), in.getLocalInfo());
+ ConstString decoupled = new ConstString(newIn, typeString);
+ decoupled.setPosition(base.getPosition());
+ iterator.add(decoupled);
+ // 3) Restore the cursor
+ iterator.next();
+ return newIn;
+ }
+
+ private Value decoupleReflectiveMemberIdentifier(
+ IRCode code, InstructionListIterator iterator, InvokeMethod invoke, Value in) {
+ // TODO(b/36799092): special reflection cases.
+ return in;
+ }
+
+ private boolean isReflectiveCase(DexProto proto) {
+ // (Class, String) -> java.lang.reflect.Field
+ // (Class, String, Class[]) -> java.lang.reflect.Method
+ int numOfParams = proto.parameters.size();
+ if (numOfParams != 2 && numOfParams != 3) {
+ return false;
+ }
+ if (numOfParams == 2) {
+ if (proto.returnType.descriptor != dexItemFactory.fieldDescriptor) {
+ return false;
+ }
+ } else {
+ if (proto.returnType.descriptor != dexItemFactory.methodDescriptor) {
+ return false;
+ }
+ }
+ if (proto.parameters.values[0].descriptor != dexItemFactory.classDescriptor) {
+ return false;
+ }
+ if (proto.parameters.values[1].descriptor != dexItemFactory.stringDescriptor) {
+ return false;
+ }
+ if (numOfParams == 3) {
+ if (proto.parameters.values[2].toDescriptorString().equals("[Ljava/lang/Class;")) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 5b79fce..6f7926c 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -1158,6 +1158,10 @@
*/
public final Set<DexItem> alwaysInline;
/**
+ * All items with -identifiernamestring rule.
+ */
+ public final Set<DexItem> identifierNameStrings;
+ /**
* Map from the class of an extension to the state it produced.
*/
final Map<Class<?>, Object> extensions;
@@ -1189,6 +1193,7 @@
this.noSideEffects = enqueuer.rootSet.noSideEffects;
this.assumedValues = enqueuer.rootSet.assumedValues;
this.alwaysInline = enqueuer.rootSet.alwaysInline;
+ this.identifierNameStrings = enqueuer.rootSet.identifierNameStrings;
this.extensions = enqueuer.extensionsState;
this.prunedTypes = Collections.emptySet();
assert Sets.intersection(instanceFieldReads, staticFieldReads).size() == 0;
@@ -1220,6 +1225,7 @@
this.staticInvokes = previous.staticInvokes;
this.extensions = previous.extensions;
this.alwaysInline = previous.alwaysInline;
+ this.identifierNameStrings = previous.identifierNameStrings;
this.prunedTypes = mergeSets(previous.prunedTypes, removedClasses);
assert Sets.intersection(instanceFieldReads, staticFieldReads).size() == 0;
assert Sets.intersection(instanceFieldWrites, staticFieldWrites).size() == 0;
@@ -1253,6 +1259,7 @@
this.assumedValues = previous.assumedValues;
assert assertNotModifiedByLense(previous.alwaysInline, lense);
this.alwaysInline = previous.alwaysInline;
+ this.identifierNameStrings = rewriteMixedItems(previous.identifierNameStrings, lense);
this.extensions = previous.extensions;
// Sanity check sets after rewriting.
assert Sets.intersection(instanceFieldReads, staticFieldReads).isEmpty();
@@ -1316,7 +1323,7 @@
private ImmutableSet<DexItem> rewritePinnedItemsToDescriptors(Collection<DexItem> source) {
ImmutableSet.Builder<DexItem> builder = ImmutableSet.builder();
for (DexItem item : source) {
- // TODO(67934123) There should be a common interface to extract this information.
+ // TODO(b/67934123) There should be a common interface to extract this information.
if (item instanceof DexClass) {
builder.add(((DexClass) item).type);
} else if (item instanceof DexEncodedMethod) {
@@ -1344,7 +1351,7 @@
Set<DexItem> original, GraphLense lense) {
ImmutableSet.Builder<DexItem> builder = ImmutableSet.builder();
for (DexItem item : original) {
- // TODO(67934123) There should be a common interface to perform the dispatch.
+ // TODO(b/67934123) There should be a common interface to perform the dispatch.
if (item instanceof DexType) {
builder.add(lense.lookupType((DexType) item, null));
} else if (item instanceof DexMethod) {
@@ -1389,17 +1396,17 @@
return this;
}
- // TODO(67934123) Unify into one method,
+ // TODO(b/67934123) Unify into one method,
public boolean isPinned(DexType item) {
return pinnedItems.contains(item);
}
- // TODO(67934123) Unify into one method,
+ // TODO(b/67934123) Unify into one method,
public boolean isPinned(DexMethod item) {
return pinnedItems.contains(item);
}
- // TODO(67934123) Unify into one method,
+ // TODO(b/67934123) Unify into one method,
public boolean isPinned(DexField item) {
return pinnedItems.contains(item);
}
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index 53f5fb7..25fc6e0 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -63,6 +63,7 @@
new IdentityHashMap<>();
private final Map<DexItem, ProguardMemberRule> noSideEffects = new IdentityHashMap<>();
private final Map<DexItem, ProguardMemberRule> assumedValues = new IdentityHashMap<>();
+ private final Set<DexItem> identifierNameStrings = Sets.newIdentityHashSet();
private final InternalOptions options;
public RootSetBuilder(DexApplication application, AppInfo appInfo,
@@ -207,7 +208,8 @@
markMatchingFields(clazz, memberKeepRules, rule, null);
} else {
assert rule instanceof ProguardIdentifierNameStringRule;
- // TODO(b/36799092): collect string literals while marking class and matching members.
+ markMatchingFields(clazz, memberKeepRules, rule, null);
+ markMatchingMethods(clazz, memberKeepRules, rule, null);
}
}
}
@@ -258,7 +260,8 @@
alwaysInline,
noSideEffects,
assumedValues,
- dependentNoShrinking);
+ dependentNoShrinking,
+ identifierNameStrings);
}
private void markMatchingVisibleMethods(DexClass clazz,
@@ -514,6 +517,12 @@
checkDiscarded.add(item);
} else if (context instanceof ProguardAlwaysInlineRule) {
alwaysInline.add(item);
+ } else if (context instanceof ProguardIdentifierNameStringRule) {
+ if (item instanceof DexEncodedField) {
+ identifierNameStrings.add(((DexEncodedField) item).field);
+ } else if (item instanceof DexEncodedMethod) {
+ identifierNameStrings.add(((DexEncodedMethod) item).method);
+ }
}
}
@@ -529,6 +538,7 @@
public final Map<DexItem, ProguardMemberRule> noSideEffects;
public final Map<DexItem, ProguardMemberRule> assumedValues;
private final Map<DexItem, Map<DexItem, ProguardKeepRule>> dependentNoShrinking;
+ public final Set<DexItem> identifierNameStrings;
private boolean isTypeEncodedMethodOrEncodedField(DexItem item) {
assert item instanceof DexType
@@ -561,7 +571,8 @@
Set<DexItem> alwaysInline,
Map<DexItem, ProguardMemberRule> noSideEffects,
Map<DexItem, ProguardMemberRule> assumedValues,
- Map<DexItem, Map<DexItem, ProguardKeepRule>> dependentNoShrinking) {
+ Map<DexItem, Map<DexItem, ProguardKeepRule>> dependentNoShrinking,
+ Set<DexItem> identifierNameStrings) {
this.noShrinking = Collections.unmodifiableMap(noShrinking);
this.noOptimization = Collections.unmodifiableSet(noOptimization);
this.noObfuscation = Collections.unmodifiableSet(noObfuscation);
@@ -572,6 +583,7 @@
this.noSideEffects = Collections.unmodifiableMap(noSideEffects);
this.assumedValues = Collections.unmodifiableMap(assumedValues);
this.dependentNoShrinking = dependentNoShrinking;
+ this.identifierNameStrings = Collections.unmodifiableSet(identifierNameStrings);
assert legalNoObfuscationItems(noObfuscation);
assert legalDependentNoShrinkingItems(dependentNoShrinking);
}
@@ -615,6 +627,7 @@
builder.append("\nnoSideEffects: " + noSideEffects.size());
builder.append("\nassumedValues: " + assumedValues.size());
builder.append("\ndependentNoShrinking: " + dependentNoShrinking.size());
+ builder.append("\nidentifierNameStrings: " + identifierNameStrings.size());
builder.append("\n\nNo Shrinking:");
noShrinking.keySet().stream()
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 ee3c845..43667e2 100644
--- a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
@@ -59,6 +59,32 @@
}
/**
+ * Convert a Java type name to a descriptor string only if the given {@param typeName} is valid.
+ *
+ * @param typeName the java type name
+ * @return the descriptor string if {@param typeName} is not valid or null otherwise
+ */
+ public static String javaTypeToDescriptorIfValidJavaType(String typeName) {
+ if (isValidJavaType(typeName)) {
+ return javaTypeToDescriptor(typeName);
+ }
+ return null;
+ }
+
+ /**
+ * Determine the given {@param typeName} is valid java type name or not.
+ *
+ * @param typeName the java type name
+ * @return true if and only if the given type name is valid java type
+ */
+ public static boolean isValidJavaType(String typeName) {
+ return typeName.length() > 0
+ && Character.isJavaIdentifierStart(typeName.charAt(0))
+ && typeName.substring(1).chars().allMatch(
+ ch -> Character.isJavaIdentifierPart(ch) || ch == JAVA_PACKAGE_SEPARATOR);
+ }
+
+ /**
* Convert a Java type name to a shorty descriptor string.
*
* @param typeName the java type name
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 62dac5f..5105a46 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -342,4 +342,12 @@
public boolean canUseFilledNewArrayOfObjects() {
return minApiLevel >= AndroidApiLevel.K.getLevel();
}
+
+ // Art had a bug (b/68761724) for Android N and O in the arm32 interpreter
+ // where an aget-wide instruction using the same register for the array
+ // and the first register of the result could lead to the wrong exception
+ // being thrown on out of bounds.
+ public boolean canUseSameArrayAndResultRegisterInArrayGetWide() {
+ return minApiLevel > AndroidApiLevel.O_MR1.getLevel();
+ }
}
diff --git a/src/test/examples/identifiernamestring/A.java b/src/test/examples/identifiernamestring/A.java
new file mode 100644
index 0000000..8b0b705
--- /dev/null
+++ b/src/test/examples/identifiernamestring/A.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.
+package identifiernamestring;
+
+public class A {
+ @IdentifierNameString
+ static String foo = "identifiernamestring.B"; // should be renamed
+
+ @IdentifierNameString
+ String boo;
+
+ A() {
+ boo = "identifiernamestring.B"; // should be renamed
+ }
+
+ @IdentifierNameString
+ void bar(String s) {
+ System.out.println("identifiernamestring.B");
+ System.out.println(boo);
+ System.out.println(s); // renamed one will be passed
+ }
+
+ static String TYPE_A = "identifiernamestring.A"; // should be kept (restored)
+ static String TYPE_B = "identifiernamestring.B"; // should be renamed
+}
diff --git a/src/test/examples/identifiernamestring/B.java b/src/test/examples/identifiernamestring/B.java
new file mode 100644
index 0000000..279ba85
--- /dev/null
+++ b/src/test/examples/identifiernamestring/B.java
@@ -0,0 +1,10 @@
+// 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 identifiernamestring;
+
+public class B {
+ static String foo = "identifiernamestring.B";
+ static String TYPO_A = "identifiernamestring.A";
+ static String TYPO_B = "identifiernamestring.B";
+}
diff --git a/src/test/examples/identifiernamestring/IdentifierNameString.java b/src/test/examples/identifiernamestring/IdentifierNameString.java
new file mode 100644
index 0000000..94132bb
--- /dev/null
+++ b/src/test/examples/identifiernamestring/IdentifierNameString.java
@@ -0,0 +1,15 @@
+// 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 identifiernamestring;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+// It should not be discarded by javac.
+@Retention(RetentionPolicy.CLASS)
+@Target({ElementType.FIELD, ElementType.METHOD})
+public @interface IdentifierNameString {
+}
diff --git a/src/test/examples/identifiernamestring/Main.java b/src/test/examples/identifiernamestring/Main.java
new file mode 100644
index 0000000..5b69b18
--- /dev/null
+++ b/src/test/examples/identifiernamestring/Main.java
@@ -0,0 +1,23 @@
+// 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 identifiernamestring;
+
+public class Main {
+ public static void main(String[] args) throws Exception {
+ A ax = new A();
+ assert ax.boo.equals(A.TYPE_B);
+ // Should be renamed
+ ax.bar("identifiernamestring.B");
+
+ Class a = Class.forName(A.TYPE_A);
+ Class b_a = Class.forName(B.TYPO_A);
+ // A's name is kept.
+ assert a.equals(b_a);
+
+ Class b = Class.forName(ax.boo);
+ Class b_b = Class.forName(B.TYPO_B);
+ // As TYPO_B is not renamed, they will be different.
+ assert !b.equals(b_b);
+ }
+}
\ No newline at end of file
diff --git a/src/test/examples/identifiernamestring/keep-rules-1.txt b/src/test/examples/identifiernamestring/keep-rules-1.txt
new file mode 100644
index 0000000..a339a92
--- /dev/null
+++ b/src/test/examples/identifiernamestring/keep-rules-1.txt
@@ -0,0 +1,14 @@
+# 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.
+
+# Keep the application entry point. Get rid of everything that is not
+# reachable from there.
+-keep public class identifiernamestring.Main {
+ public static void main(...);
+}
+
+-keepnames class identifiernamestring.A
+
+-dontshrink
+-dontoptimize
diff --git a/src/test/examples/identifiernamestring/keep-rules-2.txt b/src/test/examples/identifiernamestring/keep-rules-2.txt
new file mode 100644
index 0000000..096d834
--- /dev/null
+++ b/src/test/examples/identifiernamestring/keep-rules-2.txt
@@ -0,0 +1,19 @@
+# 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.
+
+# Keep the application entry point. Get rid of everything that is not
+# reachable from there.
+-keep public class identifiernamestring.Main {
+ public static void main(...);
+}
+
+-keepnames class identifiernamestring.A
+
+-dontshrink
+-dontoptimize
+
+-identifiernamestring class * {
+ @identifiernamestring.IdentifierNameString *;
+ static java.lang.String TYPE_*;
+}
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 139ee1f..ce46885 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -748,12 +748,25 @@
return runJava(ImmutableList.of(path.toString()), main);
}
+ public static ProcessResult runJavaNoVerify(Class clazz) throws Exception {
+ String main = clazz.getCanonicalName();
+ Path path = getClassPathForTests();
+ return runJavaNoVerify(ImmutableList.of(path.toString()), main);
+ }
+
public static ProcessResult runJava(List<String> classpath, String mainClass) throws IOException {
ProcessBuilder builder = new ProcessBuilder(
getJavaExecutable(), "-cp", String.join(PATH_SEPARATOR, classpath), mainClass);
return runProcess(builder);
}
+ public static ProcessResult runJavaNoVerify(List<String> classpath, String mainClass)
+ throws IOException {
+ ProcessBuilder builder = new ProcessBuilder(
+ getJavaExecutable(), "-cp", String.join(PATH_SEPARATOR, classpath), "-noverify", mainClass);
+ return runProcess(builder);
+ }
+
public static ProcessResult forkD8(Path dir, String... args)
throws IOException, InterruptedException {
return forkJava(dir, D8.class, args);
diff --git a/src/test/java/com/android/tools/r8/jasmin/BooleanByteConfusion.java b/src/test/java/com/android/tools/r8/jasmin/BooleanByteConfusion.java
index c475888..561fa9b 100644
--- a/src/test/java/com/android/tools/r8/jasmin/BooleanByteConfusion.java
+++ b/src/test/java/com/android/tools/r8/jasmin/BooleanByteConfusion.java
@@ -5,6 +5,7 @@
import static org.junit.Assert.assertEquals;
+import com.android.tools.r8.jasmin.JasminBuilder.ClassFileVersion;
import com.google.common.collect.ImmutableList;
import org.junit.Test;
@@ -20,7 +21,7 @@
@Test
public void booleanByteConfusion() throws Exception {
- JasminBuilder builder = new JasminBuilder();
+ JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
// public static void foo(boolean condition) {
diff --git a/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java b/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java
index 2511d79..e572bfa 100644
--- a/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java
+++ b/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.debuginfo.DebugInfoInspector;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.DexDebugInfo;
+import com.android.tools.r8.jasmin.JasminBuilder.ClassFileVersion;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DexInspector;
@@ -170,7 +171,7 @@
// Check that we properly handle switching a local slot from one variable to another.
@Test
public void checkLocalChange() throws Exception {
- JasminBuilder builder = new JasminBuilder();
+ JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
MethodSignature foo = clazz.addStaticMethod("foo", ImmutableList.of("I"), "I",
@@ -341,7 +342,7 @@
@Test
public void argumentLiveAtReturn() throws Exception {
- JasminBuilder builder = new JasminBuilder();
+ JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
/*
@@ -442,7 +443,7 @@
@Test
public void testLocalSwitchRewriteToIfs() throws Exception {
- JasminBuilder builder = new JasminBuilder();
+ JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
/*
@@ -540,7 +541,7 @@
@Test
public void testLocalSwitchRewriteToSwitches() throws Exception {
- JasminBuilder builder = new JasminBuilder();
+ JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
/*
diff --git a/src/test/java/com/android/tools/r8/jasmin/FillBooleanArrayTruncation.java b/src/test/java/com/android/tools/r8/jasmin/FillBooleanArrayTruncation.java
index a648381..a38ee9e 100644
--- a/src/test/java/com/android/tools/r8/jasmin/FillBooleanArrayTruncation.java
+++ b/src/test/java/com/android/tools/r8/jasmin/FillBooleanArrayTruncation.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.jasmin.JasminBuilder.ClassFileVersion;
import com.google.common.collect.ImmutableList;
import org.junit.Test;
@@ -44,7 +45,7 @@
@Test
public void filledArray() throws Exception {
- JasminBuilder builder = new JasminBuilder();
+ JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
// Corresponds to something like the following (which doesn't compile with javac):
diff --git a/src/test/java/com/android/tools/r8/jasmin/InvalidDebugInfoTests.java b/src/test/java/com/android/tools/r8/jasmin/InvalidDebugInfoTests.java
index 2551941..9394995 100644
--- a/src/test/java/com/android/tools/r8/jasmin/InvalidDebugInfoTests.java
+++ b/src/test/java/com/android/tools/r8/jasmin/InvalidDebugInfoTests.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.debuginfo.DebugInfoInspector;
+import com.android.tools.r8.jasmin.JasminBuilder.ClassFileVersion;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.StringUtils;
@@ -26,7 +27,7 @@
// will actually be a read of 'x'.
@Test
public void testInvalidInfoThrow() throws Exception {
- JasminBuilder builder = new JasminBuilder();
+ JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
clazz.addStaticMethod("foo", ImmutableList.of("I"), "V",
@@ -85,7 +86,7 @@
// in this test the scope of "y" (local 2) spans the exceptional edge in which it is not live.
@Test
public void testInvalidInfoBug37722432() throws Exception {
- JasminBuilder builder = new JasminBuilder();
+ JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
clazz.addStaticMethod("foo", ImmutableList.of("I", "I"), "V",
@@ -218,7 +219,7 @@
@Test
public void invalidInfoBug63412730_onMove() throws Throwable {
- JasminBuilder builder = new JasminBuilder();
+ JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
MethodSignature method = clazz.addStaticMethod("bar", ImmutableList.of(), "V",
".limit stack 3",
diff --git a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
index 94f1deb..d44b460 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
@@ -26,16 +26,62 @@
public class JasminBuilder {
- public static class ClassBuilder {
+ public enum ClassFileVersion {
+ JDK_1_1 {
+ @Override
+ public int getMajorVersion() {
+ return 45;
+ }
+
+ @Override
+ public int getMinorVersion() {
+ return 3;
+ }
+ },
+ JDK_1_2 {
+ @Override
+ public int getMajorVersion() {
+ return 46;
+ }
+ },
+ JDK_1_3 {
+ @Override
+ public int getMajorVersion() {
+ return 47;
+ }
+ },
+ JDK_1_4 {
+ @Override
+ public int getMajorVersion() {
+ return 48;
+ }
+ },
+ /** JSE 5 is not fully supported by Jasmin. Interfaces will not work. */
+ JSE_5 {
+ @Override
+ public int getMajorVersion() {
+ return 49;
+ }
+ };
+
+ public abstract int getMajorVersion();
+
+ public int getMinorVersion() {
+ return 0;
+ }
+ }
+
+ public class ClassBuilder {
public final String name;
public final String superName;
public final ImmutableList<String> interfaces;
private final List<String> methods = new ArrayList<>();
private final List<String> fields = new ArrayList<>();
private boolean makeInit = false;
+ private boolean hasInit = false;
private boolean isInterface = false;
- public ClassBuilder(String name) {
+ private ClassBuilder(String name) {
this(name, "java/lang/Object");
}
@@ -130,18 +176,19 @@
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
+ builder.append(".bytecode ").append(majorVersion).append('.').append(minorVersion)
+ .append('\n');
builder.append(".source ").append(getSourceFile()).append('\n');
+ builder.append(".class");
if (isInterface) {
- builder.append(".interface");
- } else {
- builder.append(".class");
+ builder.append(" interface abstract");
}
builder.append(" public ").append(name).append('\n');
builder.append(".super ").append(superName).append('\n');
for (String iface : interfaces) {
builder.append(".implements ").append(iface).append('\n');
}
- if (makeInit) {
+ if (makeInit && !hasInit) {
builder
.append(".method public <init>()V\n")
.append(".limit locals 1\n")
@@ -165,6 +212,8 @@
}
public MethodSignature addDefaultConstructor() {
+ assert !hasInit;
+ hasInit = true;
return addMethod("public", "<init>", Collections.emptyList(), "V",
".limit stack 1",
".limit locals 1",
@@ -175,8 +224,17 @@
}
private final List<ClassBuilder> classes = new ArrayList<>();
+ private final int minorVersion;
+ private final int majorVersion;
- public JasminBuilder() {}
+ public JasminBuilder() {
+ this(ClassFileVersion.JDK_1_4);
+ }
+
+ public JasminBuilder(ClassFileVersion version) {
+ majorVersion = version.getMajorVersion();
+ minorVersion = version.getMinorVersion();
+ }
public ClassBuilder addClass(String name) {
ClassBuilder builder = new ClassBuilder(name);
@@ -197,6 +255,9 @@
}
public ClassBuilder addInterface(String name, String... interfaces) {
+ // Interfaces are broken in Jasmin (the ACC_SUPER access flag is set) and the JSE_5 and later
+ // will not load corresponding classes.
+ assert majorVersion <= ClassFileVersion.JDK_1_4.getMajorVersion();
ClassBuilder builder = new ClassBuilder(name, "java/lang/Object", interfaces);
builder.setIsInterface();
classes.add(builder);
diff --git a/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java b/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
index 6d4a501..84b413a 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
@@ -55,7 +55,7 @@
ByteStreams.copy(input, output);
}
}
- return ToolHelper.runJava(ImmutableList.of(out.getPath()), main);
+ return ToolHelper.runJavaNoVerify(ImmutableList.of(out.getPath()), main);
}
protected String runOnJava(JasminBuilder builder, String main) throws Exception {
diff --git a/src/test/java/com/android/tools/r8/jasmin/JumpSubroutineDebugInfoTests.java b/src/test/java/com/android/tools/r8/jasmin/JumpSubroutineDebugInfoTests.java
index f5254fc..9711c88 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JumpSubroutineDebugInfoTests.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JumpSubroutineDebugInfoTests.java
@@ -6,6 +6,7 @@
import static org.junit.Assert.assertEquals;
import com.android.tools.r8.debuginfo.DebugInfoInspector;
+import com.android.tools.r8.jasmin.JasminBuilder.ClassFileVersion;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.utils.AndroidApp;
import com.google.common.collect.ImmutableList;
@@ -15,7 +16,7 @@
@Test
public void testJsrWithStraightlineAndDebugInfoCode() throws Exception {
- JasminBuilder builder = new JasminBuilder();
+ JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
MethodSignature foo = clazz.addStaticMethod("foo", ImmutableList.of("I"), "I",
diff --git a/src/test/java/com/android/tools/r8/jasmin/JumpSubroutineTests.java b/src/test/java/com/android/tools/r8/jasmin/JumpSubroutineTests.java
index c07f125..5e41a25 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JumpSubroutineTests.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JumpSubroutineTests.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.jasmin.JasminBuilder.ClassFileVersion;
import com.android.tools.r8.utils.AndroidApp;
import com.google.common.collect.ImmutableList;
import org.junit.Test;
@@ -53,7 +54,7 @@
* }
*/
public void testJsrJava130TryFinally() throws Exception {
- JasminBuilder builder = new JasminBuilder();
+ JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_3);
JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
clazz.addMainMethod(
@@ -106,7 +107,7 @@
* }
*/
public void testJsrJava130TryFinallyNested() throws Exception {
- JasminBuilder builder = new JasminBuilder();
+ JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_3);
JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
clazz.addMainMethod(
@@ -160,7 +161,7 @@
@Test
public void testJsrWithStraightlineCode() throws Exception {
- JasminBuilder builder = new JasminBuilder();
+ JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
clazz.addStaticMethod("foo", ImmutableList.of(), "I",
@@ -192,7 +193,7 @@
@Test
public void testJsrWithStraightlineCodeMultiple() throws Exception {
- JasminBuilder builder = new JasminBuilder();
+ JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
clazz.addStaticMethod("foo", ImmutableList.of(), "I",
@@ -232,7 +233,7 @@
@Test
public void testJsrWithStraightlineCodeMultiple2() throws Exception {
- JasminBuilder builder = new JasminBuilder();
+ JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
clazz.addStaticMethod("foo", ImmutableList.of(), "I",
@@ -278,7 +279,7 @@
@Test
public void testJsrWithControlFlowCode() throws Exception {
- JasminBuilder builder = new JasminBuilder();
+ JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
clazz.addStaticMethod("foo", ImmutableList.of(), "V",
@@ -315,7 +316,7 @@
@Test
public void testJsrWithNestedJsr() throws Exception {
- JasminBuilder builder = new JasminBuilder();
+ JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
clazz.addStaticMethod("foo", ImmutableList.of(), "V",
@@ -360,7 +361,7 @@
@Test
public void testJsrWithNestedJsrPopReturnAddress() throws Exception {
- JasminBuilder builder = new JasminBuilder();
+ JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
clazz.addStaticMethod("foo", ImmutableList.of(), "V",
@@ -392,7 +393,7 @@
@Test
public void testJsrWithNestedPopReturnAddress2() throws Exception {
- JasminBuilder builder = new JasminBuilder();
+ JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
clazz.addStaticMethod("foo", ImmutableList.of(), "V",
@@ -418,7 +419,7 @@
@Test
public void testJsrJustThrows() throws Exception {
- JasminBuilder builder = new JasminBuilder();
+ JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
clazz.addStaticMethod("foo", ImmutableList.of(), "V",
@@ -450,7 +451,7 @@
@Test
public void testJsrJustThrows2() throws Exception {
- JasminBuilder builder = new JasminBuilder();
+ JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
clazz.addStaticMethod("foo", ImmutableList.of(), "V",
@@ -489,7 +490,7 @@
@Test
public void testJsrWithException() throws Exception {
- JasminBuilder builder = new JasminBuilder();
+ JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
clazz.addStaticMethod("foo", ImmutableList.of(), "V",
@@ -537,7 +538,7 @@
@Test
public void testJsrWithAddressManipulation() throws Exception {
- JasminBuilder builder = new JasminBuilder();
+ JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
clazz.addStaticMethod("foo", ImmutableList.of(), "V",
@@ -573,7 +574,7 @@
@Test
public void testJsrWithSharedExceptionHandler() throws Exception {
// Regression test for b/37659886
- JasminBuilder builder = new JasminBuilder();
+ JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
clazz.addStaticMethod("foo", ImmutableList.of("I"), "V",
@@ -615,7 +616,7 @@
@Test
public void regressJsrHitParentCatchHandler() throws Exception {
- JasminBuilder builder = new JasminBuilder();
+ JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
clazz.addStaticMethod("test", ImmutableList.of(), "I",
@@ -707,7 +708,7 @@
@Test
public void regressJsrHitParentCatchHandler2() throws Exception {
- JasminBuilder builder = new JasminBuilder();
+ JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
generateRegressJsrHitParentCatchHandler2(clazz, "test1", "java/lang/Exception", false);
@@ -734,7 +735,7 @@
// https://github.com/cbeust/testng/blob/4a8459e36f2b0ed057ffa7e470f1057e8e5b0ff9/src/main/java/org/testng/internal/Invoker.java#L1066
// compiled with some ancient version of javac generating code with jsr for try/finally.
public void regress38156139() throws Exception {
- JasminBuilder builder = new JasminBuilder();
+ JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
clazz.addStaticMethod("foo", ImmutableList.of("Z"), "I",
@@ -787,7 +788,7 @@
@Test
public void regress37767254() throws Exception {
- JasminBuilder builder = new JasminBuilder();
+ JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
// This is the code for the method org.apache.log4j.net.SocketAppender$Connector.run() from
@@ -1302,7 +1303,7 @@
@Test
// This test is based on the example on http://asm.ow2.org/doc/developer-guide.html.
public void testJsrWithNestedJsrRetBasedOnControlFlow() throws Exception {
- JasminBuilder builder = new JasminBuilder();
+ JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_3);
JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
clazz.addStaticMethod("foo", ImmutableList.of("Z"), "I",
@@ -1350,7 +1351,7 @@
@Test
// This test is based on the example on http://asm.ow2.org/doc/developer-guide.html.
public void testJsrWithNestedRetBasedOnControlFlow2() throws Exception {
- JasminBuilder builder = new JasminBuilder();
+ JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
clazz.addStaticMethod("foo", ImmutableList.of("Z"), "I",
@@ -1398,7 +1399,7 @@
@Test
// This test is based on the example on http://asm.ow2.org/doc/developer-guide.html.
public void testJsrWithNestedRetBasedOnControlFlow3() throws Exception {
- JasminBuilder builder = new JasminBuilder();
+ JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
clazz.addStaticMethod("foo", ImmutableList.of("Z"), "I",
diff --git a/src/test/java/com/android/tools/r8/jasmin/MemberResolutionTest.java b/src/test/java/com/android/tools/r8/jasmin/MemberResolutionTest.java
index 8f32184..fb3c067 100644
--- a/src/test/java/com/android/tools/r8/jasmin/MemberResolutionTest.java
+++ b/src/test/java/com/android/tools/r8/jasmin/MemberResolutionTest.java
@@ -3,8 +3,11 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.jasmin;
+import static java.util.Collections.emptyList;
+
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
+import com.android.tools.r8.jasmin.JasminBuilder.ClassFileVersion;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
@@ -15,7 +18,7 @@
@Test
public void lookupStaticFieldFromDiamondInterface() throws Exception {
- JasminBuilder builder = new JasminBuilder();
+ JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
ClassBuilder interfaceA = builder.addInterface("InterfaceA");
interfaceA.addStaticFinalField("aField", "I", "42");
@@ -39,7 +42,7 @@
@Test
public void lookupStaticFieldFromInterfaceNotSuper() throws Exception {
- JasminBuilder builder = new JasminBuilder();
+ JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
ClassBuilder superClass = builder.addClass("SuperClass");
superClass.addStaticFinalField("aField", "I", "42");
@@ -63,7 +66,7 @@
@Test
public void lookupStaticFieldFromSupersInterfaceNotSupersSuper() throws Exception {
- JasminBuilder builder = new JasminBuilder();
+ JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
ClassBuilder superSuperClass = builder.addClass("SuperSuperClass");
superSuperClass.addStaticFinalField("aField", "I", "123");
@@ -117,6 +120,189 @@
ensureICCE(builder);
}
+ @Test
+ @Ignore("b/69101406")
+ public void lookupVirtualMethodWithConflictingPrivate() throws Exception {
+ JasminBuilder builder = new JasminBuilder();
+
+ ClassBuilder superClass = builder.addClass("SuperClass");
+ superClass.addDefaultConstructor();
+ superClass.addVirtualMethod("aMethod", emptyList(), "V",
+ ".limit stack 2",
+ ".limit locals 1",
+ " getstatic java/lang/System/out Ljava/io/PrintStream;",
+ " bipush 42",
+ " invokevirtual java/io/PrintStream/println(I)V",
+ " return");
+
+ ClassBuilder subClass = builder.addClass("SubClass", "SuperClass");
+ subClass.addDefaultConstructor();
+ subClass.addPrivateVirtualMethod("aMethod", emptyList(), "V",
+ ".limit stack 2",
+ ".limit locals 1",
+ " getstatic java/lang/System/out Ljava/io/PrintStream;",
+ " bipush 123",
+ " invokevirtual java/io/PrintStream/println(I)V",
+ " return");
+
+ ClassBuilder mainClass = builder.addClass(MAIN_CLASS);
+ mainClass.addMainMethod(
+ ".limit stack 2",
+ ".limit locals 1",
+ " new SubClass",
+ " dup",
+ " invokespecial SubClass/<init>()V",
+ " invokevirtual SubClass/aMethod()V",
+ " return");
+ ensureIAEExceptJava(builder);
+ }
+
+ @Test
+ @Ignore("b/69152228")
+ public void lookupDirectMethodFromWrongContext() throws Exception {
+ JasminBuilder builder = new JasminBuilder();
+
+ ClassBuilder superClass = builder.addClass("SuperClass");
+ superClass.addDefaultConstructor();
+ superClass.addVirtualMethod("aMethod", emptyList(), "V",
+ ".limit stack 2",
+ ".limit locals 1",
+ " getstatic java/lang/System/out Ljava/io/PrintStream;",
+ " bipush 42",
+ " invokevirtual java/io/PrintStream/println(I)V",
+ " return");
+
+ ClassBuilder subClass = builder.addClass("SubClass", "SuperClass");
+ subClass.addDefaultConstructor();
+ subClass.addPrivateVirtualMethod("aMethod", emptyList(), "V",
+ ".limit stack 2",
+ ".limit locals 1",
+ " getstatic java/lang/System/out Ljava/io/PrintStream;",
+ " bipush 123",
+ " invokevirtual java/io/PrintStream/println(I)V",
+ " return");
+
+ ClassBuilder mainClass = builder.addClass(MAIN_CLASS);
+ mainClass.addMainMethod(
+ ".limit stack 2",
+ ".limit locals 1",
+ " new SubClass",
+ " dup",
+ " invokespecial SubClass/<init>()V",
+ " invokespecial SubClass/aMethod()V",
+ " return");
+ ensureIAEExceptJava(builder);
+ }
+
+ @Test
+ public void lookupPrivateSuperFromSubClass() throws Exception {
+ JasminBuilder builder = new JasminBuilder(ClassFileVersion.JSE_5);
+
+ ClassBuilder superClass = builder.addClass("SuperClass");
+ superClass.addDefaultConstructor();
+ superClass.addPrivateVirtualMethod("aMethod", emptyList(), "V",
+ ".limit stack 2",
+ ".limit locals 1",
+ " getstatic java/lang/System/out Ljava/io/PrintStream;",
+ " bipush 42",
+ " invokevirtual java/io/PrintStream/println(I)V",
+ " return");
+
+ ClassBuilder subClass = builder.addClass("SubClass", "SuperClass");
+ subClass.addDefaultConstructor();
+ subClass.addVirtualMethod("callAMethod", emptyList(), "V",
+ ".limit stack 1",
+ ".limit locals 1",
+ " aload 0",
+ " invokespecial SuperClass/aMethod()V",
+ " return");
+
+ ClassBuilder mainClass = builder.addClass(MAIN_CLASS);
+ mainClass.addMainMethod(
+ ".limit stack 2",
+ ".limit locals 1",
+ " new SubClass",
+ " dup",
+ " invokespecial SubClass/<init>()V",
+ " invokevirtual SubClass/callAMethod()V",
+ " return");
+
+ ensureIAEExceptJava(builder);
+ }
+
+ @Test
+ @Ignore("b/69101406")
+ public void lookupStaticMethodWithConflictingVirtual() throws Exception {
+ JasminBuilder builder = new JasminBuilder();
+
+ ClassBuilder superClass = builder.addClass("SuperClass");
+ superClass.addDefaultConstructor();
+ superClass.addStaticMethod("aMethod", emptyList(), "V",
+ ".limit stack 2",
+ ".limit locals 1",
+ " getstatic java/lang/System/out Ljava/io/PrintStream;",
+ " bipush 42",
+ " invokevirtual java/io/PrintStream/println(I)V",
+ " return");
+
+ ClassBuilder subClass = builder.addClass("SubClass", "SuperClass");
+ subClass.addDefaultConstructor();
+ subClass.addVirtualMethod("aMethod", emptyList(), "V",
+ ".limit stack 2",
+ ".limit locals 1",
+ " getstatic java/lang/System/out Ljava/io/PrintStream;",
+ " bipush 123",
+ " invokevirtual java/io/PrintStream/println(I)V",
+ " return");
+
+ ClassBuilder mainClass = builder.addClass(MAIN_CLASS);
+ mainClass.addMainMethod(
+ ".limit stack 2",
+ ".limit locals 1",
+ " new SubClass",
+ " dup",
+ " invokespecial SubClass/<init>()V",
+ " invokestatic SubClass/aMethod()V",
+ " return");
+ ensureICCE(builder);
+ }
+
+ @Test
+ @Ignore("b/69101406")
+ public void lookupVirtualMethodWithConflictingStatic() throws Exception {
+ JasminBuilder builder = new JasminBuilder();
+
+ ClassBuilder superClass = builder.addClass("SuperClass");
+ superClass.addDefaultConstructor();
+ superClass.addVirtualMethod("aMethod", emptyList(), "V",
+ ".limit stack 2",
+ ".limit locals 1",
+ " getstatic java/lang/System/out Ljava/io/PrintStream;",
+ " bipush 42",
+ " invokevirtual java/io/PrintStream/println(I)V",
+ " return");
+
+ ClassBuilder subClass = builder.addClass("SubClass", "SuperClass");
+ subClass.addDefaultConstructor();
+ subClass.addStaticMethod("aMethod", emptyList(), "V",
+ ".limit stack 2",
+ ".limit locals 1",
+ " getstatic java/lang/System/out Ljava/io/PrintStream;",
+ " bipush 123",
+ " invokevirtual java/io/PrintStream/println(I)V",
+ " return");
+
+ ClassBuilder mainClass = builder.addClass(MAIN_CLASS);
+ mainClass.addMainMethod(
+ ".limit stack 2",
+ ".limit locals 1",
+ " new SubClass",
+ " dup",
+ " invokespecial SubClass/<init>()V",
+ " invokevirtual SubClass/aMethod()V",
+ " return");
+ ensureICCE(builder);
+ }
private void ensureSameOutput(JasminBuilder app) throws Exception {
String dxOutput = runOnArtDx(app, MAIN_CLASS);
@@ -129,13 +315,23 @@
}
private void ensureICCE(JasminBuilder app) throws Exception {
+ ensureRuntimeException(app, IncompatibleClassChangeError.class);
+ }
+
+ private void ensureIAEExceptJava(JasminBuilder app)
+ throws Exception {
+ ensureRuntimeException(app, IllegalAccessError.class);
+ }
+
+ private void ensureRuntimeException(JasminBuilder app, Class exception) throws Exception {
+ String name = exception.getSimpleName();
ProcessResult dxOutput = runOnArtDxRaw(app, MAIN_CLASS);
- Assert.assertTrue(dxOutput.stderr.contains("IncompatibleClassChangeError"));
+ Assert.assertTrue(dxOutput.stderr.contains(name));
ProcessResult d8Output = runOnArtD8Raw(app, MAIN_CLASS);
- Assert.assertTrue(d8Output.stderr.contains("IncompatibleClassChangeError"));
+ Assert.assertTrue(d8Output.stderr.contains(name));
ProcessResult r8Output = runOnArtR8Raw(app, MAIN_CLASS, null);
- Assert.assertTrue(r8Output.stderr.contains("IncompatibleClassChangeError"));
+ Assert.assertTrue(r8Output.stderr.contains(name));
ProcessResult javaOutput = runOnJavaRaw(app, MAIN_CLASS);
- Assert.assertTrue(javaOutput.stderr.contains("IncompatibleClassChangeError"));
+ Assert.assertTrue(javaOutput.stderr.contains(name));
}
}
diff --git a/src/test/java/com/android/tools/r8/jasmin/Regress63598979.java b/src/test/java/com/android/tools/r8/jasmin/Regress63598979.java
index 091a43a..edfaff0 100644
--- a/src/test/java/com/android/tools/r8/jasmin/Regress63598979.java
+++ b/src/test/java/com/android/tools/r8/jasmin/Regress63598979.java
@@ -5,6 +5,7 @@
import static org.junit.Assert.assertEquals;
+import com.android.tools.r8.jasmin.JasminBuilder.ClassFileVersion;
import com.google.common.collect.ImmutableList;
import org.junit.Test;
@@ -12,7 +13,7 @@
@Test
public void testSimplifyIf() throws Exception {
- JasminBuilder builder = new JasminBuilder();
+ JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
clazz.addStaticMethod("test1", ImmutableList.of("Z"), "Z",
diff --git a/src/test/java/com/android/tools/r8/jasmin/Regress64658224.java b/src/test/java/com/android/tools/r8/jasmin/Regress64658224.java
index 8c1df56..4390d30 100644
--- a/src/test/java/com/android/tools/r8/jasmin/Regress64658224.java
+++ b/src/test/java/com/android/tools/r8/jasmin/Regress64658224.java
@@ -5,6 +5,7 @@
import static org.junit.Assert.assertEquals;
+import com.android.tools.r8.jasmin.JasminBuilder.ClassFileVersion;
import com.google.common.collect.ImmutableList;
import org.junit.Test;
@@ -12,7 +13,7 @@
@Test
public void testInvalidTypeInfoFromLocals() throws Exception {
- JasminBuilder builder = new JasminBuilder();
+ JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
clazz.addStaticMethod("foo", ImmutableList.of("I"), "V",
diff --git a/src/test/java/com/android/tools/r8/jasmin/Regress65432240.java b/src/test/java/com/android/tools/r8/jasmin/Regress65432240.java
index df0d6e2..6290e20 100644
--- a/src/test/java/com/android/tools/r8/jasmin/Regress65432240.java
+++ b/src/test/java/com/android/tools/r8/jasmin/Regress65432240.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.code.IfNez;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.jasmin.JasminBuilder.ClassFileVersion;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.utils.AndroidApp;
import com.google.common.collect.ImmutableList;
@@ -19,7 +20,7 @@
@Test
public void testConstantNotIntoEntryBlock() throws Exception {
- JasminBuilder builder = new JasminBuilder();
+ JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
MethodSignature signature = clazz.addStaticMethod("test1", ImmutableList.of("I"), "I",
diff --git a/src/test/java/com/android/tools/r8/jasmin/TryCatchStateTests.java b/src/test/java/com/android/tools/r8/jasmin/TryCatchStateTests.java
index 39c2f50..ff63ed9 100644
--- a/src/test/java/com/android/tools/r8/jasmin/TryCatchStateTests.java
+++ b/src/test/java/com/android/tools/r8/jasmin/TryCatchStateTests.java
@@ -6,6 +6,7 @@
import static org.junit.Assert.assertEquals;
import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.jasmin.JasminBuilder.ClassFileVersion;
import com.google.common.collect.ImmutableList;
import org.junit.Test;
@@ -13,7 +14,7 @@
@Test
public void testTryCatchStackHeight() throws Exception {
- JasminBuilder builder = new JasminBuilder();
+ JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
clazz.addStaticMethod("foo", ImmutableList.of("I"), "I",
@@ -59,7 +60,7 @@
@Test
public void testTryCatchLocals() throws Exception {
- JasminBuilder builder = new JasminBuilder();
+ JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
clazz.addStaticMethod("foo", ImmutableList.of("I"), "I",
@@ -107,7 +108,7 @@
@Test
public void testTryCatchOnUnreachableLabel() throws Exception {
- JasminBuilder builder = new JasminBuilder();
+ JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
clazz.addStaticMethod("foo", ImmutableList.of("I"), "I",
diff --git a/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java b/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
index 191f6cc..ebbacc6 100644
--- a/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
+++ b/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
@@ -90,11 +90,13 @@
@Parameters(name = "test: {0} keep: {1}")
public static Collection<Object[]> data() {
- List<String> tests = Arrays.asList("adaptclassstrings");
+ List<String> tests = Arrays.asList("adaptclassstrings", "identifiernamestring");
Map<String, Consumer<DexInspector>> inspections = new HashMap<>();
inspections.put("adaptclassstrings:keep-rules-1.txt", IdentifierMinifierTest::test1_rule1);
inspections.put("adaptclassstrings:keep-rules-2.txt", IdentifierMinifierTest::test1_rule2);
+ inspections.put("identifiernamestring:keep-rules-1.txt", IdentifierMinifierTest::test2_rule1);
+ inspections.put("identifiernamestring:keep-rules-2.txt", IdentifierMinifierTest::test2_rule2);
return NamingTestBase.createTests(tests, inspections);
}
@@ -145,6 +147,56 @@
assertEquals(1, renamedYetFoundIdentifierCount);
}
+ // Without -identifiernamestring
+ private static void test2_rule1(DexInspector inspector) {
+ ClassSubject mainClass = inspector.clazz("identifiernamestring.Main");
+ MethodSubject main = mainClass.method(DexInspector.MAIN);
+ Code mainCode = main.getMethod().getCode();
+ verifyPresenceOfConstString(mainCode.asDexCode().instructions);
+ int renamedYetFoundIdentifierCount =
+ countRenamedClassIdentifier(inspector, mainCode.asDexCode().instructions);
+ assertEquals(0, renamedYetFoundIdentifierCount);
+
+ ClassSubject aClass = inspector.clazz("identifiernamestring.A");
+ MethodSubject aInit =
+ aClass.method("void", "<init>", ImmutableList.of());
+ Code initCode = aInit.getMethod().getCode();
+ verifyPresenceOfConstString(initCode.asDexCode().instructions);
+ renamedYetFoundIdentifierCount =
+ countRenamedClassIdentifier(inspector, initCode.asDexCode().instructions);
+ assertEquals(0, renamedYetFoundIdentifierCount);
+
+ renamedYetFoundIdentifierCount =
+ countRenamedClassIdentifier(inspector, aClass.getDexClass().staticFields());
+ assertEquals(0, renamedYetFoundIdentifierCount);
+ }
+
+ // With -identifiernamestring for annotations and name-based filters
+ private static void test2_rule2(DexInspector inspector) {
+ ClassSubject mainClass = inspector.clazz("identifiernamestring.Main");
+ MethodSubject main = mainClass.method(DexInspector.MAIN);
+ assertTrue(main.isPresent());
+ Code mainCode = main.getMethod().getCode();
+ assertTrue(mainCode.isDexCode());
+ verifyPresenceOfConstString(mainCode.asDexCode().instructions);
+ int renamedYetFoundIdentifierCount =
+ countRenamedClassIdentifier(inspector, mainCode.asDexCode().instructions);
+ assertEquals(1, renamedYetFoundIdentifierCount);
+
+ ClassSubject aClass = inspector.clazz("identifiernamestring.A");
+ MethodSubject aInit =
+ aClass.method("void", "<init>", ImmutableList.of());
+ Code initCode = aInit.getMethod().getCode();
+ verifyPresenceOfConstString(initCode.asDexCode().instructions);
+ renamedYetFoundIdentifierCount =
+ countRenamedClassIdentifier(inspector, initCode.asDexCode().instructions);
+ assertEquals(1, renamedYetFoundIdentifierCount);
+
+ renamedYetFoundIdentifierCount =
+ countRenamedClassIdentifier(inspector, aClass.getDexClass().staticFields());
+ assertEquals(2, renamedYetFoundIdentifierCount);
+ }
+
private static void verifyPresenceOfConstString(Instruction[] instructions) {
boolean presence =
Arrays.stream(instructions)
@@ -169,7 +221,7 @@
.filter(instr -> instr instanceof ConstString || instr instanceof ConstStringJumbo)
.reduce(0, (cnt, instr) -> {
String cnstString = retrieveString(instr);
- if (!DescriptorUtils.isClassDescriptor(cnstString)) {
+ if (DescriptorUtils.isValidJavaType(cnstString)) {
ClassSubject classSubject = inspector.clazz(cnstString);
if (classSubject.isRenamed()
&& DescriptorUtils.descriptorToJavaType(classSubject.getFinalDescriptor())
@@ -187,7 +239,7 @@
.filter(encodedField -> encodedField.staticValue instanceof DexValueString)
.reduce(0, (cnt, encodedField) -> {
String cnstString = ((DexValueString) encodedField.staticValue).getValue().toString();
- if (!DescriptorUtils.isClassDescriptor(cnstString)) {
+ if (DescriptorUtils.isValidJavaType(cnstString)) {
ClassSubject classSubject = inspector.clazz(cnstString);
if (classSubject.isRenamed()
&& DescriptorUtils.descriptorToJavaType(classSubject.getFinalDescriptor())
diff --git a/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java b/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java
new file mode 100644
index 0000000..1431dd9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java
@@ -0,0 +1,358 @@
+// 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.naming;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.code.ConstString;
+import com.android.tools.r8.code.InvokeDirect;
+import com.android.tools.r8.code.InvokeStatic;
+import com.android.tools.r8.code.InvokeVirtual;
+import com.android.tools.r8.code.IputObject;
+import com.android.tools.r8.code.ReturnVoid;
+import com.android.tools.r8.code.SgetObject;
+import com.android.tools.r8.code.SputObject;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.smali.SmaliBuilder;
+import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
+import com.android.tools.r8.smali.SmaliTestBase;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.List;
+import org.junit.Test;
+
+public class IdentifierNameStringMarkerTest extends SmaliTestBase {
+
+ private final static String BOO = "Boo";
+
+ @Test
+ public void instancePut_singleUseOperand() throws Exception {
+ SmaliBuilder builder = new SmaliBuilder("Example");
+ builder.addInstanceField("aClassName", "Ljava/lang/String;");
+ MethodSignature init = builder.addInitializer(ImmutableList.of(), 1,
+ "invoke-direct {p0}, Ljava/lang/Object;-><init>()V",
+ "const-string v0, \"" + BOO + "\"",
+ "iput-object v0, p0, LExample;->aClassName:Ljava/lang/String;",
+ "return-void");
+
+ List<String> pgConfigs = ImmutableList.of(
+ "-identifiernamestring class Example { java.lang.String aClassName; }",
+ "-keep class Example",
+ "-dontoptimize");
+ Path processedApp = runR8(builder, pgConfigs);
+
+ DexEncodedMethod method = getMethod(processedApp, init);
+ assertNotNull(method);
+
+ DexCode code = method.getCode().asDexCode();
+ assertTrue(code.instructions[0] instanceof InvokeDirect);
+ assertTrue(code.instructions[1] instanceof ConstString);
+ // TODO(b/36799092): DeadCodeRemover should be able to remove this instruction.
+ ConstString constString = (ConstString) code.instructions[1];
+ assertEquals(BOO, constString.getString().toString());
+ assertTrue(code.instructions[2] instanceof ConstString);
+ constString = (ConstString) code.instructions[2];
+ assertEquals(BOO, constString.getString().toString());
+ assertTrue(code.instructions[3] instanceof IputObject);
+ assertTrue(code.instructions[4] instanceof ReturnVoid);
+ }
+
+ @Test
+ public void instancePut_sharedOperand() throws Exception {
+ SmaliBuilder builder = new SmaliBuilder("Example");
+ builder.addInstanceField("aClassName", "Ljava/lang/String;");
+ MethodSignature init = builder.addInitializer(ImmutableList.of(), 2,
+ "invoke-direct {p0}, Ljava/lang/Object;-><init>()V",
+ "sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+ "const-string v1, \"" + BOO + "\"",
+ "invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+ "iput-object v1, p0, LExample;->aClassName:Ljava/lang/String;",
+ "return-void");
+
+ List<String> pgConfigs = ImmutableList.of(
+ "-identifiernamestring class Example { java.lang.String aClassName; }",
+ "-keep class Example",
+ "-dontoptimize");
+ Path processedApp = runR8(builder, pgConfigs);
+
+ DexEncodedMethod method = getMethod(processedApp, init);
+ assertNotNull(method);
+
+ DexCode code = method.getCode().asDexCode();
+ assertTrue(code.instructions[0] instanceof InvokeDirect);
+ assertTrue(code.instructions[1] instanceof SgetObject);
+ assertTrue(code.instructions[2] instanceof ConstString);
+ ConstString constString = (ConstString) code.instructions[2];
+ assertEquals(BOO, constString.getString().toString());
+ assertTrue(code.instructions[3] instanceof InvokeVirtual);
+ assertTrue(code.instructions[4] instanceof ConstString);
+ constString = (ConstString) code.instructions[4];
+ assertEquals(BOO, constString.getString().toString());
+ assertTrue(code.instructions[5] instanceof IputObject);
+ assertTrue(code.instructions[6] instanceof ReturnVoid);
+ }
+
+ @Test
+ public void instancePut_sharedOperand_renamed() throws Exception {
+ SmaliBuilder builder = new SmaliBuilder("Example");
+ builder.addInstanceField("aClassName", "Ljava/lang/String;");
+ MethodSignature init = builder.addInitializer(ImmutableList.of(), 2,
+ "invoke-direct {p0}, Ljava/lang/Object;-><init>()V",
+ "sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+ "const-string v1, \"" + BOO + "\"",
+ "invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+ "iput-object v1, p0, LExample;->aClassName:Ljava/lang/String;",
+ "return-void");
+ builder.addClass(BOO);
+
+ List<String> pgConfigs = ImmutableList.of(
+ "-identifiernamestring class Example { java.lang.String aClassName; }",
+ "-keep class Example",
+ "-keep,allowobfuscation class " + BOO,
+ "-dontoptimize");
+ Path processedApp = runR8(builder, pgConfigs);
+
+ DexEncodedMethod method = getMethod(processedApp, init);
+ assertNotNull(method);
+
+ DexCode code = method.getCode().asDexCode();
+ assertTrue(code.instructions[0] instanceof InvokeDirect);
+ assertTrue(code.instructions[1] instanceof SgetObject);
+ assertTrue(code.instructions[2] instanceof ConstString);
+ ConstString constString = (ConstString) code.instructions[2];
+ assertEquals(BOO, constString.getString().toString());
+ assertTrue(code.instructions[3] instanceof InvokeVirtual);
+ assertTrue(code.instructions[4] instanceof ConstString);
+ constString = (ConstString) code.instructions[4];
+ assertNotEquals(BOO, constString.getString().toString());
+ assertTrue(code.instructions[5] instanceof IputObject);
+ assertTrue(code.instructions[6] instanceof ReturnVoid);
+ }
+
+ @Test
+ public void staticPut_singleUseOperand() throws Exception {
+ SmaliBuilder builder = new SmaliBuilder("Example");
+ builder.addStaticField("sClassName", "Ljava/lang/String;");
+ MethodSignature clinit = builder.addStaticInitializer(1,
+ "const-string v0, \"" + BOO + "\"",
+ "sput-object v0, LExample;->sClassName:Ljava/lang/String;",
+ "return-void");
+
+ List<String> pgConfigs = ImmutableList.of(
+ "-identifiernamestring class Example { static java.lang.String sClassName; }",
+ "-keep class Example",
+ "-dontoptimize");
+ Path processedApp = runR8(builder, pgConfigs);
+
+ DexEncodedMethod method = getMethod(processedApp, clinit);
+ assertNotNull(method);
+
+ DexCode code = method.getCode().asDexCode();
+ // TODO(b/36799092): DeadCodeRemover should be able to remove this instruction.
+ assertTrue(code.instructions[0] instanceof ConstString);
+ ConstString constString = (ConstString) code.instructions[0];
+ assertEquals(BOO, constString.getString().toString());
+ assertTrue(code.instructions[1] instanceof ConstString);
+ constString = (ConstString) code.instructions[1];
+ assertEquals(BOO, constString.getString().toString());
+ assertTrue(code.instructions[2] instanceof SputObject);
+ assertTrue(code.instructions[3] instanceof ReturnVoid);
+ }
+
+ @Test
+ public void staticPut_sharedOperand() throws Exception {
+ SmaliBuilder builder = new SmaliBuilder("Example");
+ builder.addStaticField("sClassName", "Ljava/lang/String;");
+ MethodSignature clinit = builder.addStaticInitializer(2,
+ "sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+ "const-string v1, \"" + BOO + "\"",
+ "invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+ "sput-object v1, LExample;->sClassName:Ljava/lang/String;",
+ "return-void");
+
+ List<String> pgConfigs = ImmutableList.of(
+ "-identifiernamestring class Example { static java.lang.String sClassName; }",
+ "-keep class Example",
+ "-dontoptimize");
+ Path processedApp = runR8(builder, pgConfigs);
+
+ DexEncodedMethod method = getMethod(processedApp, clinit);
+ assertNotNull(method);
+
+ DexCode code = method.getCode().asDexCode();
+ assertTrue(code.instructions[0] instanceof SgetObject);
+ assertTrue(code.instructions[1] instanceof ConstString);
+ ConstString constString = (ConstString) code.instructions[1];
+ assertEquals(BOO, constString.getString().toString());
+ assertTrue(code.instructions[2] instanceof InvokeVirtual);
+ assertTrue(code.instructions[3] instanceof ConstString);
+ constString = (ConstString) code.instructions[3];
+ assertEquals(BOO, constString.getString().toString());
+ assertTrue(code.instructions[4] instanceof SputObject);
+ assertTrue(code.instructions[5] instanceof ReturnVoid);
+ }
+
+ @Test
+ public void staticPut_sharedOperand_renamed() throws Exception {
+ SmaliBuilder builder = new SmaliBuilder("Example");
+ builder.addStaticField("sClassName", "Ljava/lang/String;");
+ MethodSignature clinit = builder.addStaticInitializer(2,
+ "sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+ "const-string v1, \"" + BOO + "\"",
+ "invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+ "sput-object v1, LExample;->sClassName:Ljava/lang/String;",
+ "return-void");
+ builder.addClass(BOO);
+
+ List<String> pgConfigs = ImmutableList.of(
+ "-identifiernamestring class Example { static java.lang.String sClassName; }",
+ "-keep class Example",
+ "-keep,allowobfuscation class " + BOO,
+ "-dontoptimize");
+ Path processedApp = runR8(builder, pgConfigs);
+
+ DexEncodedMethod method = getMethod(processedApp, clinit);
+ assertNotNull(method);
+
+ DexCode code = method.getCode().asDexCode();
+ assertTrue(code.instructions[0] instanceof SgetObject);
+ assertTrue(code.instructions[1] instanceof ConstString);
+ ConstString constString = (ConstString) code.instructions[1];
+ assertEquals(BOO, constString.getString().toString());
+ assertTrue(code.instructions[2] instanceof InvokeVirtual);
+ assertTrue(code.instructions[3] instanceof ConstString);
+ constString = (ConstString) code.instructions[3];
+ assertNotEquals(BOO, constString.getString().toString());
+ assertTrue(code.instructions[4] instanceof SputObject);
+ assertTrue(code.instructions[5] instanceof ReturnVoid);
+ }
+
+ @Test
+ public void invoke_singleUseOperand() throws Exception {
+ SmaliBuilder builder = new SmaliBuilder("Example");
+ builder.addStaticMethod(
+ "void",
+ "foo",
+ ImmutableList.of("java.lang.String", "java.lang.String"),
+ 0,
+ "return-void");
+ MethodSignature foo = builder.addInitializer(ImmutableList.of(), 2,
+ "invoke-direct {p0}, Ljava/lang/Object;-><init>()V",
+ "const-string v0, \"" + BOO + "\"",
+ "const-string v1, \"Mixed/form.Boo\"",
+ "invoke-static {v0, v1}, LExample;->foo(Ljava/lang/String;Ljava/lang/String;)V",
+ "return-void");
+
+ List<String> pgConfigs = ImmutableList.of(
+ "-identifiernamestring class Example { static void foo(...); }",
+ "-keep class Example",
+ "-dontoptimize");
+ Path processedApp = runR8(builder, pgConfigs);
+
+ DexEncodedMethod method = getMethod(processedApp, foo);
+ assertNotNull(method);
+
+ DexCode code = method.getCode().asDexCode();
+ assertTrue(code.instructions[0] instanceof InvokeDirect);
+ // TODO(b/36799092): DeadCodeRemover should be able to remove this instruction.
+ assertTrue(code.instructions[1] instanceof ConstString);
+ ConstString constString = (ConstString) code.instructions[1];
+ assertEquals(BOO, constString.getString().toString());
+ assertTrue(code.instructions[2] instanceof ConstString);
+ constString = (ConstString) code.instructions[2];
+ assertEquals("Mixed/form.Boo", constString.getString().toString());
+ assertTrue(code.instructions[3] instanceof ConstString);
+ constString = (ConstString) code.instructions[3];
+ assertEquals(BOO, constString.getString().toString());
+ assertTrue(code.instructions[4] instanceof InvokeStatic);
+ assertTrue(code.instructions[5] instanceof ReturnVoid);
+ }
+
+ @Test
+ public void invoke_sharedOperand() throws Exception {
+ SmaliBuilder builder = new SmaliBuilder("Example");
+ builder.addStaticMethod(
+ "void",
+ "foo",
+ ImmutableList.of("java.lang.String"),
+ 0,
+ "return-void");
+ MethodSignature foo = builder.addInitializer(ImmutableList.of(), 3,
+ "invoke-direct {p0}, Ljava/lang/Object;-><init>()V",
+ "sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+ "const-string v1, \"" + BOO + "\"",
+ "invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+ "invoke-static {v1}, LExample;->foo(Ljava/lang/String;)V",
+ "return-void");
+
+ List<String> pgConfigs = ImmutableList.of(
+ "-identifiernamestring class Example { static void foo(...); }",
+ "-keep class Example",
+ "-dontoptimize");
+ Path processedApp = runR8(builder, pgConfigs);
+
+ DexEncodedMethod method = getMethod(processedApp, foo);
+ assertNotNull(method);
+
+ DexCode code = method.getCode().asDexCode();
+ assertTrue(code.instructions[0] instanceof InvokeDirect);
+ assertTrue(code.instructions[1] instanceof SgetObject);
+ assertTrue(code.instructions[2] instanceof ConstString);
+ ConstString constString = (ConstString) code.instructions[2];
+ assertEquals(BOO, constString.getString().toString());
+ assertTrue(code.instructions[3] instanceof InvokeVirtual);
+ assertTrue(code.instructions[4] instanceof ConstString);
+ constString = (ConstString) code.instructions[4];
+ assertEquals(BOO, constString.getString().toString());
+ assertTrue(code.instructions[5] instanceof InvokeStatic);
+ assertTrue(code.instructions[6] instanceof ReturnVoid);
+ }
+
+ @Test
+ public void invoke_sharedOperand_renamed() throws Exception {
+ SmaliBuilder builder = new SmaliBuilder("Example");
+ builder.addStaticMethod(
+ "void",
+ "foo",
+ ImmutableList.of("java.lang.String"),
+ 0,
+ "return-void");
+ MethodSignature foo = builder.addInitializer(ImmutableList.of(), 3,
+ "invoke-direct {p0}, Ljava/lang/Object;-><init>()V",
+ "sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+ "const-string v1, \"" + BOO + "\"",
+ "invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+ "invoke-static {v1}, LExample;->foo(Ljava/lang/String;)V",
+ "return-void");
+ builder.addClass(BOO);
+
+ List<String> pgConfigs = ImmutableList.of(
+ "-identifiernamestring class Example { static void foo(...); }",
+ "-keep class Example",
+ "-keep,allowobfuscation class " + BOO,
+ "-dontoptimize");
+ Path processedApp = runR8(builder, pgConfigs);
+
+ DexEncodedMethod method = getMethod(processedApp, foo);
+ assertNotNull(method);
+
+ DexCode code = method.getCode().asDexCode();
+ assertTrue(code.instructions[0] instanceof InvokeDirect);
+ assertTrue(code.instructions[1] instanceof SgetObject);
+ assertTrue(code.instructions[2] instanceof ConstString);
+ ConstString constString = (ConstString) code.instructions[2];
+ assertEquals(BOO, constString.getString().toString());
+ assertTrue(code.instructions[3] instanceof InvokeVirtual);
+ assertTrue(code.instructions[4] instanceof ConstString);
+ constString = (ConstString) code.instructions[4];
+ assertNotEquals(BOO, constString.getString().toString());
+ assertTrue(code.instructions[5] instanceof InvokeStatic);
+ assertTrue(code.instructions[6] instanceof ReturnVoid);
+ }
+
+}
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliBuilder.java b/src/test/java/com/android/tools/r8/smali/SmaliBuilder.java
index ad1e097..b8ea55a 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliBuilder.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliBuilder.java
@@ -252,37 +252,31 @@
return new MethodSignature(currentClassName, name, returnType, parameters);
}
- public MethodSignature addStaticMethod(String returnType, String name, List<String> parameters,
- int locals, String... instructions) {
- StringBuilder builder = new StringBuilder();
- for (String instruction : instructions) {
- builder.append(instruction);
- builder.append("\n");
- }
- return addStaticMethod(returnType, name, parameters, locals, builder.toString());
+ public MethodSignature addStaticMethod(
+ String returnType, String name, List<String> parameters, int locals, String... instructions) {
+ return addStaticMethod(returnType, name, parameters, locals, buildCode(instructions));
}
- public MethodSignature addStaticMethod(String returnType, String name, List<String> parameters,
- int locals, String code) {
+ 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());
+ return addStaticInitializer(locals, buildCode(instructions));
}
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();
+ private MethodSignature addStaticMethod(
+ String flags,
+ String returnType,
+ String name,
+ List<String> parameters,
+ int locals,
+ String code) {
return addMethod("public static " + flags, returnType, name, parameters, locals, code);
}
@@ -291,20 +285,19 @@
return addMethod("public abstract", returnType, name, parameters, -1, null);
}
- public MethodSignature addInstanceMethod(String returnType, String name,
- List<String> parameters,
- int locals, String... instructions) {
- StringBuilder builder = new StringBuilder();
- for (String instruction : instructions) {
- builder.append(instruction);
- builder.append("\n");
- }
- return addInstanceMethod(returnType, name, parameters, locals, builder.toString());
+ public MethodSignature addInitializer(
+ List<String> parameters, int locals, String... instructions) {
+ return addMethod(
+ "public constructor", "void", "<init>", parameters, locals, buildCode(instructions));
}
- public MethodSignature addInstanceMethod(String returnType, String name,
- List<String> parameters,
- int locals, String code) {
+ public MethodSignature addInstanceMethod(
+ String returnType, String name, List<String> parameters, int locals, String... instructions) {
+ return addInstanceMethod(returnType, name, parameters, locals, buildCode(instructions));
+ }
+
+ public MethodSignature addInstanceMethod(
+ String returnType, String name, List<String> parameters, int locals, String code) {
return addMethod("public", returnType, name, parameters, locals, code);
}
@@ -317,11 +310,20 @@
StringBuilder builder = new StringBuilder();
for (String line : source) {
builder.append(line);
- builder.append("\n");
+ builder.append(System.lineSeparator());
}
getSource(currentClassName).add(builder.toString());
}
+ private static String buildCode(String... instructions) {
+ StringBuilder builder = new StringBuilder();
+ for (String instruction : instructions) {
+ builder.append(instruction);
+ builder.append(System.lineSeparator());
+ }
+ return builder.toString();
+ }
+
public List<String> buildSource() {
List<String> result = new ArrayList<>(classes.size());
for (String clazz : classes.keySet()) {
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 4949153..1a864fe 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
@@ -83,6 +83,10 @@
}
}
+ protected Path runR8(SmaliBuilder builder, List<String> proguardConfigurations) {
+ return runR8(builder, proguardConfigurations, pg -> {}, o -> {});
+ }
+
protected Path runR8(
SmaliBuilder builder,
List<String> proguardConfigurations,