Merge "Fix splitting for exception overlapping in release mode."
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 0914e7b..9f97f88 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -99,6 +99,7 @@
private Code code;
private CompilationState compilationState = CompilationState.NOT_PROCESSED;
private OptimizationInfo optimizationInfo = DefaultOptimizationInfo.DEFAULT;
+ private int classFileVersion = -1;
public DexEncodedMethod(
DexMethod method,
@@ -114,6 +115,17 @@
assert code == null || !accessFlags.isAbstract();
}
+ public DexEncodedMethod(
+ DexMethod method,
+ MethodAccessFlags flags,
+ DexAnnotationSet annotationSet,
+ ParameterAnnotationsList annotationsList,
+ Code code,
+ int classFileVersion) {
+ this(method, flags, annotationSet, annotationsList, code);
+ this.classFileVersion = classFileVersion;
+ }
+
public boolean isProcessed() {
return compilationState != CompilationState.NOT_PROCESSED;
}
@@ -303,6 +315,21 @@
return code.asDexCode().hasDebugPositions();
}
+ public int getClassFileVersion() {
+ assert classFileVersion >= 0;
+ return classFileVersion;
+ }
+
+ public boolean hasClassFileVersion() {
+ return classFileVersion >= 0;
+ }
+
+ public void upgradeClassFileVersion(int version) {
+ assert version >= 0;
+ assert !hasClassFileVersion() || version >= getClassFileVersion();
+ classFileVersion = version;
+ }
+
public String qualifiedName() {
return method.qualifiedName();
}
@@ -753,6 +780,16 @@
return optimizationInfo;
}
+ public void copyMetadataFromInlinee(DexEncodedMethod inlinee) {
+ // Record that the current method uses identifier name string if the inlinee did so.
+ if (inlinee.getOptimizationInfo().useIdentifierNameString()) {
+ markUseIdentifierNameString();
+ }
+ if (inlinee.classFileVersion > classFileVersion) {
+ upgradeClassFileVersion(inlinee.getClassFileVersion());
+ }
+ }
+
private static Builder builder(DexEncodedMethod from) {
return new Builder(from);
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index c1b843f..0a33000 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -356,9 +356,14 @@
}
public void setClassFileVersion(int classFileVersion) {
+ assert classFileVersion >= 0;
this.classFileVersion = classFileVersion;
}
+ public boolean hasClassFileVersion() {
+ return classFileVersion >= 0;
+ }
+
public int getClassFileVersion() {
assert classFileVersion != -1;
return classFileVersion;
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index 02f0801..64ba508 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.graph;
+import static org.objectweb.asm.ClassReader.SKIP_CODE;
import static org.objectweb.asm.ClassReader.SKIP_FRAMES;
import static org.objectweb.asm.Opcodes.ACC_DEPRECATED;
import static org.objectweb.asm.Opcodes.ASM6;
@@ -90,7 +91,7 @@
ClassReader reader = new ClassReader(input);
reader.accept(
new CreateDexClassVisitor(origin, classKind, reader.b, application, classConsumer),
- SKIP_FRAMES);
+ SKIP_FRAMES | SKIP_CODE);
}
private static int cleanAccessFlags(int access) {
@@ -560,6 +561,11 @@
@Override
public void visitCode() {
+ throw new Unreachable("visitCode() should not be called when SKIP_CODE is set");
+ }
+
+ @Override
+ public void visitEnd() {
if (!flags.isAbstract() && !flags.isNative() && parent.classKind == ClassKind.PROGRAM) {
if (parent.application.options.enableCfFrontend) {
code = new LazyCfCode(method, parent.origin, parent.context, parent.application);
@@ -567,12 +573,6 @@
code = new JarCode(method, parent.origin, parent.context, parent.application);
}
}
- }
-
- @Override
- public void visitEnd() {
- assert flags.isAbstract() || flags.isNative() || parent.classKind != ClassKind.PROGRAM
- || code != null;
ParameterAnnotationsList annotationsList;
if (parameterAnnotationsLists == null) {
annotationsList = ParameterAnnotationsList.empty();
@@ -595,8 +595,14 @@
parameterFlags.toArray(new DexValue[parameterFlags.size()]),
parent.application.getFactory()));
}
- DexEncodedMethod dexMethod = new DexEncodedMethod(method, flags,
- createAnnotationSet(annotations), annotationsList, code);
+ DexEncodedMethod dexMethod =
+ new DexEncodedMethod(
+ method,
+ flags,
+ createAnnotationSet(annotations),
+ annotationsList,
+ code,
+ parent.version);
if (flags.isStatic() || flags.isConstructor() || flags.isPrivate()) {
parent.directMethods.add(dexMethod);
} else {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index d4cb8a8..b75597d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -468,10 +468,7 @@
method.accessFlags.unsetBridge();
}
- // Record that the current method uses identifier name string if the inlinee did so.
- if (target.getOptimizationInfo().useIdentifierNameString()) {
- method.markUseIdentifierNameString();
- }
+ method.copyMetadataFromInlinee(target);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index 2e5b703..6cb8f6e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -64,6 +64,7 @@
import java.util.Map.Entry;
import java.util.Set;
import java.util.function.BiConsumer;
+import org.objectweb.asm.Opcodes;
/**
* Support class for implementing outlining (i.e. extracting common code patterns as methods).
@@ -872,6 +873,8 @@
Constants.ACC_PUBLIC | Constants.ACC_STATIC, false);
DexString methodName = dexItemFactory.createString(OutlineOptions.METHOD_PREFIX + count);
DexMethod method = outline.buildMethod(type, methodName);
+ List<DexEncodedMethod> sites = outlineSites.get(outline);
+ assert !sites.isEmpty();
direct[count] =
new DexEncodedMethod(
method,
@@ -879,6 +882,9 @@
DexAnnotationSet.empty(),
ParameterAnnotationsList.empty(),
new OutlineCode(outline));
+ if (options.isGeneratingClassFiles()) {
+ direct[count].upgradeClassFileVersion(sites.get(0).getClassFileVersion());
+ }
generatedOutlines.put(outline, method);
count++;
}
@@ -908,10 +914,9 @@
DexEncodedMethod.EMPTY_ARRAY, // Virtual methods.
options.itemFactory.getSkipNameValidationForTesting());
if (options.isGeneratingClassFiles()) {
- // Don't set class file version below 50.0 (JDK release 1.6).
- clazz.setClassFileVersion(Math.max(50, getClassFileVersion(outlines)));
+ // All program classes must have a class-file version. Use Java 6.
+ clazz.setClassFileVersion(Opcodes.V1_6);
}
-
return clazz;
}
@@ -927,26 +932,6 @@
return result;
}
- private int getClassFileVersion(List<Outline> outlines) {
- assert options.isGeneratingClassFiles();
- int classFileVersion = -1;
- Set<DexType> seen = Sets.newIdentityHashSet();
- for (Outline outline : outlines) {
- List<DexEncodedMethod> methods = outlineSites.get(outline);
- for (DexEncodedMethod method : methods) {
- DexType holder = method.method.holder;
- if (seen.add(holder)) {
- DexProgramClass programClass = appInfo.definitionFor(holder).asProgramClass();
- assert programClass != null : "Attempt to outline from library class";
- assert programClass.originatesFromClassResource()
- : "Attempt to outline from non-classfile input to classfile output";
- classFileVersion = Math.max(classFileVersion, programClass.getClassFileVersion());
- }
- }
- }
- return classFileVersion;
- }
-
public void applyOutliningCandidate(IRCode code, DexEncodedMethod method) {
assert !(method.getCode() instanceof OutlineCode);
ListIterator<BasicBlock> blocksIterator = code.blocks.listIterator();
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 2970fb4..7a25e1a 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -108,7 +108,7 @@
private void writeClass(DexProgramClass clazz, ClassFileConsumer consumer) throws IOException {
ClassWriter writer = new ClassWriter(0);
writer.visitSource(clazz.sourceFile != null ? clazz.sourceFile.toString() : null, null);
- int version = clazz.getClassFileVersion();
+ int version = getClassFileVersion(clazz);
int access = clazz.accessFlags.getAsCfAccessFlags();
String desc = namingLens.lookupDescriptor(clazz.type).toString();
String name = namingLens.lookupInternalName(clazz.type);
@@ -161,6 +161,17 @@
options.reporter, handler -> consumer.accept(result, desc, handler));
}
+ private int getClassFileVersion(DexProgramClass clazz) {
+ int version = clazz.getClassFileVersion();
+ for (DexEncodedMethod method : clazz.directMethods()) {
+ version = Math.max(version, method.getClassFileVersion());
+ }
+ for (DexEncodedMethod method : clazz.virtualMethods()) {
+ version = Math.max(version, method.getClassFileVersion());
+ }
+ return version;
+ }
+
private DexValue getSystemAnnotationValue(DexAnnotationSet annotations, DexType type) {
DexAnnotation annotation = annotations.getFirstMatching(type);
if (annotation == null) {
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 d718026..19fe18e 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -460,10 +460,11 @@
DexType baseType = type.toBaseType(appInfo.dexItemFactory);
if (baseType.isClassType()) {
DexClass baseClass = appInfo.definitionFor(baseType);
- if (baseClass != null && !baseClass.isLibraryClass()) {
+ if (baseClass != null && baseClass.isProgramClass()
+ && baseClass.hasDefaultInitializer()) {
markClassAsInstantiatedWithCompatRule(baseClass);
} else {
- // This handles reporting of missing classes.
+ // This also handles reporting of missing classes.
markTypeAsLive(baseType);
}
return true;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/InliningClassVersionTest.java b/src/test/java/com/android/tools/r8/shaking/examples/InliningClassVersionTest.java
new file mode 100644
index 0000000..7fcb318
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/examples/InliningClassVersionTest.java
@@ -0,0 +1,156 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking.examples;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import com.android.tools.r8.ArchiveClassFileProvider;
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.google.common.io.ByteStreams;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Collections;
+import java.util.List;
+import org.junit.Test;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Opcodes;
+
+public class InliningClassVersionTest extends TestBase {
+
+ private final int OLD_VERSION = Opcodes.V1_6;
+ private final String BASE_DESCRIPTOR = DescriptorUtils.javaTypeToDescriptor(Base.class.getName());
+
+ private static class Base {
+
+ public static void main(String[] args) {
+ System.out.println(Inlinee.foo());
+ }
+ }
+
+ private static class Inlinee {
+ public static String foo() {
+ return "Hello from Inlinee!";
+ }
+ }
+
+ private static class DowngradeVisitor extends ClassVisitor {
+
+ private final int version;
+
+ DowngradeVisitor(ClassVisitor cv, int version) {
+ super(Opcodes.ASM6, cv);
+ this.version = version;
+ }
+
+ @Override
+ public void visit(
+ int version,
+ int access,
+ String name,
+ String signature,
+ String superName,
+ String[] interfaces) {
+ assert version > this.version
+ : "Going from " + version + " to " + this.version + " is not a downgrade";
+ super.visit(this.version, access, name, signature, superName, interfaces);
+ }
+ }
+
+ private static byte[] downgradeClass(byte[] classBytes, int version) {
+ ClassWriter writer = new ClassWriter(0);
+ new ClassReader(classBytes).accept(new DowngradeVisitor(writer, version), 0);
+ return writer.toByteArray();
+ }
+
+ @Test
+ public void test() throws Exception {
+ Path inputJar = writeInput();
+ assertEquals(OLD_VERSION, getBaseClassVersion(inputJar));
+ ProcessResult runInput = run(inputJar);
+ assertEquals(0, runInput.exitCode);
+ Path outputJar = runR8(inputJar);
+ ProcessResult runOutput = run(outputJar);
+ assertEquals(runInput.toString(), runOutput.toString());
+ assertNotEquals(
+ "Inliner did not upgrade classfile version", OLD_VERSION, getBaseClassVersion(outputJar));
+ }
+
+ private int getBaseClassVersion(Path jar) throws Exception {
+ return getClassVersion(jar, BASE_DESCRIPTOR);
+ }
+
+ private int getClassVersion(Path jar, String descriptor) throws Exception {
+
+ class ClassVersionReader extends ClassVisitor {
+ private int version = -1;
+
+ private ClassVersionReader() {
+ super(Opcodes.ASM6);
+ }
+
+ @Override
+ public void visit(
+ int version,
+ int access,
+ String name,
+ String signature,
+ String superName,
+ String[] interfaces) {
+ super.visit(version, access, name, signature, superName, interfaces);
+ assert version != -1;
+ this.version = version;
+ }
+ }
+
+ byte[] bytes =
+ ByteStreams.toByteArray(
+ new ArchiveClassFileProvider(jar).getProgramResource(descriptor).getByteStream());
+ ClassVersionReader reader = new ClassVersionReader();
+ new ClassReader(bytes).accept(reader, 0);
+ assert reader.version != -1;
+ return reader.version;
+ }
+
+ private Path writeInput() throws Exception {
+ Path inputJar = temp.getRoot().toPath().resolve("input.jar");
+ ClassFileConsumer consumer = new ClassFileConsumer.ArchiveConsumer(inputJar);
+ consumer.accept(
+ downgradeClass(ToolHelper.getClassAsBytes(Base.class), OLD_VERSION), BASE_DESCRIPTOR, null);
+ consumer.accept(
+ ToolHelper.getClassAsBytes(Inlinee.class),
+ DescriptorUtils.javaTypeToDescriptor(Inlinee.class.getName()),
+ null);
+ consumer.finished(null);
+ return inputJar;
+ }
+
+ private ProcessResult run(Path jar) throws Exception {
+ return ToolHelper.runJava(jar, Base.class.getName());
+ }
+
+ private Path runR8(Path inputJar) throws Exception {
+ List<String> keepRule =
+ Collections.singletonList(
+ "-keep class " + Base.class.getName() + " { public static void main(...); }");
+ Path outputJar = temp.getRoot().toPath().resolve("output.jar");
+ ToolHelper.runR8(
+ R8Command.builder()
+ .addProgramFiles(inputJar)
+ .addLibraryFiles(Paths.get(ToolHelper.JAVA_8_RUNTIME))
+ .addProguardConfiguration(keepRule, Origin.unknown())
+ .setOutput(outputJar, OutputMode.ClassFile)
+ .build());
+ return outputJar;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/smali/DexMoveInstructionsTest.java b/src/test/java/com/android/tools/r8/smali/DexMoveInstructionsTest.java
new file mode 100644
index 0000000..c92b73b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/smali/DexMoveInstructionsTest.java
@@ -0,0 +1,95 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.smali;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.DescriptorUtils;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import org.junit.Test;
+
+public class DexMoveInstructionsTest extends SmaliTestBase {
+
+ public static final String CLASS = "Test";
+
+ @Test
+ public void testValidObjectMoves() throws Throwable {
+ ProcessResult result = testMoves("ExpectedToPass", "Ljava/lang/String;", Arrays.asList(
+ "move-object",
+ "move-object/from16",
+ "move-object/16"));
+ assertEquals(result.toString(), 0, result.exitCode);
+ }
+
+ @Test
+ public void testInvalidObjectMoves() throws Throwable {
+ ProcessResult result = testMoves("ExpectedToFail", "Ljava/lang/String;", Arrays.asList(
+ "move",
+ "move/from16",
+ "move/16"));
+ assertEquals(result.toString(), 1, result.exitCode);
+ assertTrue("Did not find 'Verification error' in " + result.stderr,
+ result.stderr.contains("Verification error") || result.stderr.contains("VerifyError"));
+ }
+
+ @Test
+ public void testValidSingleMoves() throws Throwable {
+ ProcessResult result = testMoves("ExpectedToPass", "I", Arrays.asList(
+ "move",
+ "move/from16",
+ "move/16"));
+ assertEquals(result.toString(), 0, result.exitCode);
+ }
+
+ @Test
+ public void testInvalidSingleMoves() throws Throwable {
+ ProcessResult result = testMoves("ExpectedToFail", "I", Arrays.asList(
+ "move-object",
+ "move-object/from16",
+ "move-object/16"));
+ assertEquals(result.toString(), 1, result.exitCode);
+ assertTrue("Did not find 'Verification error' in " + result.stderr,
+ result.stderr.contains("Verification error") || result.stderr.contains("VerifyError"));
+ }
+
+ private ProcessResult testMoves(String clazz, String typeDesc, List<String> moveOps)
+ throws Throwable {
+ String typeName = DescriptorUtils.descriptorToJavaType(typeDesc);
+
+ SmaliBuilder builder = new SmaliBuilder(clazz);
+ int i = 0;
+ for (String moveOp : moveOps) {
+ builder.addStaticMethod(typeName, "test" + i++, Collections.singletonList(typeName),
+ 1,
+ " " + moveOp + " v0, p0",
+ typeDesc.startsWith("L") ? "return-object v0" : " return v0"
+ );
+ }
+
+ List<String> main = new ArrayList<>();
+ main.add(" sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;");
+ main.add(" const v2, 0");
+ i = 0;
+ for (String moveOp : moveOps) {
+ main.add(" invoke-static { v2 }, L" + clazz + ";->test" + i++
+ + "(" + typeDesc + ")" + typeDesc);
+ if (typeDesc.startsWith("L")) {
+ main.add(" move-result-object v1");
+ } else {
+ main.add(" move-result v1");
+ }
+ main.add(" invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->print(" + typeDesc + ")V");
+ }
+ main.add(" return-void");
+ builder.addMainMethod(3, main.toArray(new String[0]));
+
+ return runOnArtRaw(builder.build(), clazz);
+ }
+}