Merge "Extend bootstrap benchmark with memory and size metrics."
diff --git a/LIBRARY-LICENSE b/LIBRARY-LICENSE
index 12674e3..bc69c40 100644
--- a/LIBRARY-LICENSE
+++ b/LIBRARY-LICENSE
@@ -48,3 +48,21 @@
license: ASM license
licenseUrl: http://asm.ow2.org/license.html
url: http://asm.ow2.org/index.html
+- artifact: org.jetbrains.kotlin:kotlin-stdlib:+
+ name: org.jetbrains.kotlin:kotlin-stdlib
+ copyrightHolder: JetBrains s.r.o.
+ license: The Apache License, Version 2.0
+ licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
+ url: https://kotlinlang.org/
+- artifact: org.jetbrains.kotlinx:kotlinx-metadata-jvm:+
+ name: org.jetbrains.kotlinx:kotlinx-metadata-jvm
+ copyrightHolder: JetBrains s.r.o.
+ license: The Apache License, Version 2.0
+ licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
+ url: https://kotlinlang.org/
+- artifact: org.jetbrains:annotations:+
+ name: IntelliJ IDEA Annotations
+ copyrightHolder: JetBrains s.r.o.
+ license: The Apache Software License, Version 2.0
+ licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
+ url: http://www.jetbrains.org
diff --git a/build.gradle b/build.gradle
index 92d7471..7f41caa 100644
--- a/build.gradle
+++ b/build.gradle
@@ -37,6 +37,7 @@
gsonVersion = '2.7'
junitVersion = '4.12'
kotlinVersion = '1.2.30'
+ kotlinExtMetadataJVMVersion = '0.0.2'
protobufVersion = '3.0.0'
smaliVersion = '2.2b4'
}
@@ -74,6 +75,7 @@
repositories {
maven { url 'https://maven.google.com' }
+ maven { url 'https://kotlin.bintray.com/kotlinx' }
mavenCentral()
}
@@ -223,6 +225,7 @@
exclude group: 'org.codehaus.mojo'
})
compile group: 'it.unimi.dsi', name: 'fastutil', version: fastutilVersion
+ compile "org.jetbrains.kotlinx:kotlinx-metadata-jvm:$kotlinExtMetadataJVMVersion"
compile group: 'org.ow2.asm', name: 'asm', version: asmVersion
compile group: 'org.ow2.asm', name: 'asm-commons', version: asmVersion
compile group: 'org.ow2.asm', name: 'asm-tree', version: asmVersion
diff --git a/src/main/java/com/android/tools/r8/ArchiveClassFileProvider.java b/src/main/java/com/android/tools/r8/ArchiveClassFileProvider.java
index af83158..a6389da 100644
--- a/src/main/java/com/android/tools/r8/ArchiveClassFileProvider.java
+++ b/src/main/java/com/android/tools/r8/ArchiveClassFileProvider.java
@@ -12,7 +12,7 @@
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.origin.PathOrigin;
import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.ZipUtils;
import com.google.common.io.ByteStreams;
import java.io.Closeable;
import java.io.IOException;
@@ -21,7 +21,6 @@
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
-import java.nio.file.Paths;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
@@ -73,7 +72,7 @@
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
String name = entry.getName();
- if (FileUtils.isClassFile(Paths.get(name)) && include.test(name)) {
+ if (ZipUtils.isClassFile(name) && include.test(name)) {
descriptors.add(DescriptorUtils.guessTypeDescriptor(name));
}
}
diff --git a/src/main/java/com/android/tools/r8/ArchiveProgramResourceProvider.java b/src/main/java/com/android/tools/r8/ArchiveProgramResourceProvider.java
index 425b3c3..1d6e678 100644
--- a/src/main/java/com/android/tools/r8/ArchiveProgramResourceProvider.java
+++ b/src/main/java/com/android/tools/r8/ArchiveProgramResourceProvider.java
@@ -3,22 +3,18 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8;
-import static com.android.tools.r8.utils.FileUtils.isClassFile;
-import static com.android.tools.r8.utils.FileUtils.isDexFile;
-
import com.android.tools.r8.ProgramResource.Kind;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.origin.ArchiveEntryOrigin;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.origin.PathOrigin;
import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.ZipUtils;
import com.google.common.io.ByteStreams;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
-import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -39,16 +35,15 @@
}
public static boolean includeClassFileEntries(String entry) {
- return isClassFile(Paths.get(entry));
+ return ZipUtils.isClassFile(entry);
}
public static boolean includeDexEntries(String entry) {
- return isDexFile(Paths.get(entry));
+ return ZipUtils.isDexFile(entry);
}
public static boolean includeClassFileOrDexEntries(String entry) {
- Path path = Paths.get(entry);
- return isClassFile(path) || isDexFile(path);
+ return ZipUtils.isClassFile(entry) || ZipUtils.isDexFile(entry);
}
private final Origin origin;
@@ -97,14 +92,13 @@
ZipEntry entry = entries.nextElement();
try (InputStream stream = zipFile.getInputStream(entry)) {
String name = entry.getName();
- Path path = Paths.get(name);
- Origin entryOrigin = new ArchiveEntryOrigin(entry.getName(), origin);
+ Origin entryOrigin = new ArchiveEntryOrigin(name, origin);
if (include.test(name)) {
- if (FileUtils.isDexFile(path)) {
+ if (ZipUtils.isDexFile(name)) {
dexResources.add(
ProgramResource.fromBytes(
entryOrigin, Kind.DEX, ByteStreams.toByteArray(stream), null));
- } else if (isClassFile(path)) {
+ } else if (ZipUtils.isClassFile(name)) {
String descriptor = DescriptorUtils.guessTypeDescriptor(name);
classResources.add(
ProgramResource.fromBytes(
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index b775fd3..f896eee 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
// This field is accessed from release scripts using simple pattern matching.
// Therefore, changing this field could break our release scripts.
- public static final String LABEL = "1.3.0-dev";
+ public static final String LABEL = "1.3.1-dev";
private Version() {
}
diff --git a/src/main/java/com/android/tools/r8/benchmarks/FrameworkIncrementalDexingBenchmark.java b/src/main/java/com/android/tools/r8/benchmarks/FrameworkIncrementalDexingBenchmark.java
index 1336f95..807509a 100644
--- a/src/main/java/com/android/tools/r8/benchmarks/FrameworkIncrementalDexingBenchmark.java
+++ b/src/main/java/com/android/tools/r8/benchmarks/FrameworkIncrementalDexingBenchmark.java
@@ -22,7 +22,6 @@
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.origin.PathOrigin;
import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.ZipUtils;
import com.google.common.collect.ImmutableMap;
@@ -61,7 +60,7 @@
archive.toString(),
(entry, stream) -> {
String name = entry.getName();
- if (FileUtils.isClassFile(Paths.get(name))) {
+ if (ZipUtils.isClassFile(name)) {
String descriptor = DescriptorUtils.guessTypeDescriptor(name);
builder.put(descriptor, ByteStreams.toByteArray(stream));
}
diff --git a/src/main/java/com/android/tools/r8/compatdx/CompatDx.java b/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
index 871f6d8..7d2907a 100644
--- a/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
+++ b/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
@@ -3,13 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.compatdx;
-import static com.android.tools.r8.utils.FileUtils.isApkFile;
-import static com.android.tools.r8.utils.FileUtils.isArchive;
-import static com.android.tools.r8.utils.FileUtils.isClassFile;
-import static com.android.tools.r8.utils.FileUtils.isDexFile;
-import static com.android.tools.r8.utils.FileUtils.isJarFile;
-import static com.android.tools.r8.utils.FileUtils.isZipFile;
-
import com.android.tools.r8.CompatDxHelper;
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.CompilationMode;
@@ -28,6 +21,7 @@
import com.android.tools.r8.utils.ExceptionDiagnostic;
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.ZipUtils;
import com.google.common.collect.ImmutableList;
import com.google.common.io.ByteStreams;
import java.io.File;
@@ -475,7 +469,7 @@
}
if (singleDexFile) {
return new SingleDexFileConsumer(
- isDexFile(output)
+ FileUtils.isDexFile(output)
? new NamedDexFileConsumer(output)
: createDexConsumer(output, inputs, keepClasses));
}
@@ -486,13 +480,13 @@
Path output, List<Path> inputs, boolean keepClasses)
throws DxUsageMessage {
if (keepClasses) {
- if (!isArchive(output)) {
+ if (!FileUtils.isArchive(output)) {
throw new DxCompatOptions.DxUsageMessage(
"Output must be an archive when --keep-classes is set.");
}
return new DexKeepClassesConsumer(output, inputs);
}
- return isArchive(output)
+ return FileUtils.isArchive(output)
? new DexIndexedConsumer.ArchiveConsumer(output)
: new DexIndexedConsumer.DirectoryConsumer(output);
}
@@ -572,12 +566,12 @@
private void writeZipWithClasses(DiagnosticsHandler handler) throws IOException {
// For each input archive file, add all class files within.
for (Path input : inputs) {
- if (isArchive(input)) {
+ if (FileUtils.isArchive(input)) {
try (ZipFile zipFile = new ZipFile(input.toFile(), StandardCharsets.UTF_8)) {
final Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
- if (isClassFile(Paths.get(entry.getName()))) {
+ if (ZipUtils.isClassFile(entry.getName())) {
try (InputStream entryStream = zipFile.getInputStream(entry)) {
outputBuilder.addFile(
entry.getName(), ByteStreams.toByteArray(entryStream), handler);
@@ -604,11 +598,11 @@
return;
}
Path path = file.toPath();
- if (isZipFile(path) || isJarFile(path) || isClassFile(path)) {
+ if (FileUtils.isZipFile(path) || FileUtils.isJarFile(path) || FileUtils.isClassFile(path)) {
files.add(path);
return;
}
- if (isApkFile(path)) {
+ if (FileUtils.isApkFile(path)) {
throw new Unimplemented("apk files not yet supported");
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/DefaultUseRegistry.java b/src/main/java/com/android/tools/r8/graph/DefaultUseRegistry.java
new file mode 100644
index 0000000..7f31098
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/DefaultUseRegistry.java
@@ -0,0 +1,63 @@
+// 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.graph;
+
+public class DefaultUseRegistry extends UseRegistry {
+
+ @Override
+ public boolean registerInvokeVirtual(DexMethod method) {
+ return true;
+ }
+
+ @Override
+ public boolean registerInvokeDirect(DexMethod method) {
+ return true;
+ }
+
+ @Override
+ public boolean registerInvokeStatic(DexMethod method) {
+ return true;
+ }
+
+ @Override
+ public boolean registerInvokeInterface(DexMethod method) {
+ return true;
+ }
+
+ @Override
+ public boolean registerInvokeSuper(DexMethod method) {
+ return true;
+ }
+
+ @Override
+ public boolean registerInstanceFieldWrite(DexField field) {
+ return true;
+ }
+
+ @Override
+ public boolean registerInstanceFieldRead(DexField field) {
+ return true;
+ }
+
+ @Override
+ public boolean registerNewInstance(DexType type) {
+ return true;
+ }
+
+ @Override
+ public boolean registerStaticFieldRead(DexField field) {
+ return true;
+ }
+
+ @Override
+ public boolean registerStaticFieldWrite(DexField field) {
+ return true;
+ }
+
+ @Override
+ public boolean registerTypeReference(DexType type) {
+ return true;
+ }
+}
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 23139c5..2c188da 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.ThrowingConsumer;
import com.google.common.base.MoreObjects;
+import com.google.common.collect.Iterators;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
@@ -96,6 +97,16 @@
}
}
+ public Iterable<DexEncodedField> fields() {
+ return () ->
+ Iterators.concat(Iterators.forArray(instanceFields), Iterators.forArray(staticFields));
+ }
+
+ public Iterable<DexEncodedMethod> methods() {
+ return () ->
+ Iterators.concat(Iterators.forArray(directMethods), Iterators.forArray(virtualMethods));
+ }
+
@Override
void collectMixedSectionItems(MixedSectionCollection mixedItems) {
throw new Unreachable();
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 d2c7701..a9df6a3 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
@@ -195,7 +195,8 @@
transformBridgeMethod();
}
computeNeedsRegister();
- insertArgumentMoves();
+ constrainArgumentIntervals();
+ insertRangeInvokeMoves();
ImmutableList<BasicBlock> blocks = computeLivenessInformation();
// First attempt to allocate register allowing argument reuse. This will fail if spilling
// is required or if we end up using more than 16 registers.
@@ -2598,11 +2599,14 @@
}
}
- private void insertArgumentMoves() {
+ private void constrainArgumentIntervals() {
// Record the constraint that incoming arguments are in consecutive registers.
List<Value> arguments = code.collectArguments();
createArgumentLiveIntervals(arguments);
linkArgumentValuesAndIntervals(arguments);
+ }
+
+ private void insertRangeInvokeMoves() {
for (BasicBlock block : code.blocks) {
InstructionListIterator it = block.listIterator();
while (it.hasNext()) {
diff --git a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
index cf2b2bc..0af41b1 100644
--- a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
+++ b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
@@ -5,19 +5,11 @@
package com.android.tools.r8.kotlin;
import com.android.tools.r8.DiagnosticsHandler;
-import com.android.tools.r8.graph.DexAnnotation;
-import com.android.tools.r8.graph.DexAnnotationElement;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexValue;
-import com.android.tools.r8.graph.DexValue.DexValueArray;
-import com.android.tools.r8.graph.DexValue.DexValueInt;
-import com.android.tools.r8.graph.DexValue.DexValueString;
-import com.android.tools.r8.kotlin.KotlinSyntheticClass.Flavour;
-import com.android.tools.r8.utils.StringDiagnostic;
import com.google.common.collect.Sets;
import java.util.Set;
@@ -58,10 +50,13 @@
public final DexString kotlinStyleLambdaInstanceName = factory.createString("INSTANCE");
+ public final DexType functionBase = factory.createType("Lkotlin/jvm/internal/FunctionBase;");
public final DexType lambdaType = factory.createType("Lkotlin/jvm/internal/Lambda;");
- public final DexMethod lambdaInitializerMethod = factory.createMethod(lambdaType,
- factory.createProto(factory.voidType, factory.intType), factory.constructorMethodName);
+ public final DexMethod lambdaInitializerMethod = factory.createMethod(
+ lambdaType,
+ factory.createProto(factory.voidType, factory.intType),
+ factory.constructorMethodName);
public boolean isFunctionInterface(DexType type) {
return functions.contains(type);
@@ -70,9 +65,14 @@
public final class Metadata {
public final DexType kotlinMetadataType = factory.createType("Lkotlin/Metadata;");
- public final DexString elementNameK = factory.createString("k");
- public final DexString elementNameD1 = factory.createString("d1");
- public final DexString elementNameD2 = factory.createString("d2");
+ public final DexString kind = factory.createString("k");
+ public final DexString metadataVersion = factory.createString("mv");
+ public final DexString bytecodeVersion = factory.createString("bv");
+ public final DexString data1 = factory.createString("d1");
+ public final DexString data2 = factory.createString("d2");
+ public final DexString extraString = factory.createString("xs");
+ public final DexString packageName = factory.createString("pn");
+ public final DexString extraInt = factory.createString("xi");
}
// kotlin.jvm.internal.Intrinsics class
@@ -86,98 +86,6 @@
// Calculates kotlin info for a class.
public KotlinInfo getKotlinInfo(DexClass clazz, DiagnosticsHandler reporter) {
- if (clazz.annotations.isEmpty()) {
- return null;
- }
- DexAnnotation meta = clazz.annotations.getFirstMatching(metadata.kotlinMetadataType);
- if (meta != null) {
- try {
- return createKotlinInfo(clazz, meta);
- } catch (MetadataError e) {
- reporter.warning(
- new StringDiagnostic("Class " + clazz.type.toSourceString() +
- " has malformed kotlin.Metadata: " + e.getMessage()));
- }
- }
- return null;
- }
-
- private KotlinInfo createKotlinInfo(DexClass clazz, DexAnnotation meta) {
- DexAnnotationElement kindElement = getAnnotationElement(meta, metadata.elementNameK);
- if (kindElement == null) {
- throw new MetadataError("element 'k' is missing");
- }
-
- DexValue value = kindElement.value;
- if (!(value instanceof DexValueInt)) {
- throw new MetadataError("invalid 'k' value: " + value.toSourceString());
- }
-
- DexValueInt intValue = (DexValueInt) value;
- switch (intValue.value) {
- case 1:
- return new KotlinClass();
- case 2:
- return new KotlinFile();
- case 3:
- return createSyntheticClass(clazz, meta);
- case 4:
- return new KotlinClassFacade();
- case 5:
- return new KotlinClassPart();
- default:
- throw new MetadataError("unsupported 'k' value: " + value.toSourceString());
- }
- }
-
- private KotlinSyntheticClass createSyntheticClass(DexClass clazz, DexAnnotation meta) {
- if (isKotlinStyleLambda(clazz)) {
- return new KotlinSyntheticClass(Flavour.KotlinStyleLambda);
- }
- if (isJavaStyleLambda(clazz, meta)) {
- return new KotlinSyntheticClass(Flavour.JavaStyleLambda);
- }
- return new KotlinSyntheticClass(Flavour.Unclassified);
- }
-
- private boolean isKotlinStyleLambda(DexClass clazz) {
- // TODO: replace with direct hints from kotlin metadata when available.
- return clazz.superType == this.functional.lambdaType;
- }
-
- private boolean isJavaStyleLambda(DexClass clazz, DexAnnotation meta) {
- assert !isKotlinStyleLambda(clazz);
- return clazz.superType == this.factory.objectType &&
- clazz.interfaces.size() == 1 &&
- isAnnotationElementNotEmpty(meta, metadata.elementNameD1);
- }
-
- private DexAnnotationElement getAnnotationElement(DexAnnotation annotation, DexString name) {
- for (DexAnnotationElement element : annotation.annotation.elements) {
- if (element.name == name) {
- return element;
- }
- }
- return null;
- }
-
- private boolean isAnnotationElementNotEmpty(DexAnnotation annotation, DexString name) {
- for (DexAnnotationElement element : annotation.annotation.elements) {
- if (element.name == name && element.value instanceof DexValueArray) {
- DexValue[] values = ((DexValueArray) element.value).getValues();
- if (values.length == 1 && values[0] instanceof DexValueString) {
- return true;
- }
- // Must be broken metadata.
- assert false;
- }
- }
- return false;
- }
-
- private static class MetadataError extends RuntimeException {
- MetadataError(String cause) {
- super(cause);
- }
+ return KotlinClassMetadataReader.getKotlinInfo(this, clazz, reporter);
}
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
index fc81994..74184a5 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
@@ -4,7 +4,31 @@
package com.android.tools.r8.kotlin;
-public class KotlinClass extends KotlinInfo {
+import kotlinx.metadata.KmClassVisitor;
+import kotlinx.metadata.jvm.KotlinClassMetadata;
+
+public class KotlinClass extends KotlinInfo<KotlinClassMetadata.Class> {
+
+ static KotlinClass fromKotlinClassMetadata(KotlinClassMetadata kotlinClassMetadata) {
+ assert kotlinClassMetadata instanceof KotlinClassMetadata.Class;
+ KotlinClassMetadata.Class kClass = (KotlinClassMetadata.Class) kotlinClassMetadata;
+ return new KotlinClass(kClass);
+ }
+
+ private KotlinClass(KotlinClassMetadata.Class metadata) {
+ super(metadata);
+ }
+
+ @Override
+ void validateMetadata(KotlinClassMetadata.Class metadata) {
+ ClassMetadataVisitor visitor = new ClassMetadataVisitor();
+ // To avoid lazy parsing/verifying metadata.
+ metadata.accept(visitor);
+ }
+
+ private static class ClassMetadataVisitor extends KmClassVisitor {
+ }
+
@Override
public Kind getKind() {
return Kind.Class;
@@ -20,6 +44,4 @@
return this;
}
- KotlinClass() {
- }
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
index e829a27..62db3d1 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
@@ -4,7 +4,26 @@
package com.android.tools.r8.kotlin;
-public final class KotlinClassFacade extends KotlinInfo {
+import kotlinx.metadata.jvm.KotlinClassMetadata;
+
+public final class KotlinClassFacade extends KotlinInfo<KotlinClassMetadata.MultiFileClassFacade> {
+
+ static KotlinClassFacade fromKotlinClassMetadata(KotlinClassMetadata kotlinClassMetadata) {
+ assert kotlinClassMetadata instanceof KotlinClassMetadata.MultiFileClassFacade;
+ KotlinClassMetadata.MultiFileClassFacade multiFileClassFacade =
+ (KotlinClassMetadata.MultiFileClassFacade) kotlinClassMetadata;
+ return new KotlinClassFacade(multiFileClassFacade);
+ }
+
+ private KotlinClassFacade(KotlinClassMetadata.MultiFileClassFacade metadata) {
+ super(metadata);
+ }
+
+ @Override
+ void validateMetadata(KotlinClassMetadata.MultiFileClassFacade metadata) {
+ // No worries about lazy parsing/verifying, since no API to explore metadata details.
+ }
+
@Override
public Kind getKind() {
return Kind.Facade;
@@ -20,7 +39,4 @@
return this;
}
- KotlinClassFacade() {
- super();
- }
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
new file mode 100644
index 0000000..32aebff
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
@@ -0,0 +1,133 @@
+// 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.kotlin;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexAnnotationElement;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.DexValue.DexValueArray;
+import com.android.tools.r8.graph.DexValue.DexValueString;
+import com.android.tools.r8.utils.StringDiagnostic;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import kotlinx.metadata.InconsistentKotlinMetadataException;
+import kotlinx.metadata.jvm.KotlinClassHeader;
+import kotlinx.metadata.jvm.KotlinClassMetadata;
+
+final class KotlinClassMetadataReader {
+
+ static KotlinInfo getKotlinInfo(
+ Kotlin kotlin,
+ DexClass clazz,
+ DiagnosticsHandler reporter) {
+ if (clazz.annotations.isEmpty()) {
+ return null;
+ }
+ DexAnnotation meta = clazz.annotations.getFirstMatching(kotlin.metadata.kotlinMetadataType);
+ if (meta != null) {
+ try {
+ return createKotlinInfo(kotlin, clazz, meta);
+ } catch (ClassCastException | InconsistentKotlinMetadataException | MetadataError e) {
+ reporter.warning(
+ new StringDiagnostic("Class " + clazz.type.toSourceString()
+ + " has malformed kotlin.Metadata: " + e.getMessage()));
+ } catch (Throwable e) {
+ reporter.warning(
+ new StringDiagnostic("Unexpected error while reading " + clazz.type.toSourceString()
+ + "'s kotlin.Metadata: " + e.getMessage()));
+ }
+ }
+ return null;
+ }
+
+ private static KotlinInfo createKotlinInfo(
+ Kotlin kotlin,
+ DexClass clazz,
+ DexAnnotation meta) {
+ Map<DexString, DexAnnotationElement> elementMap = new IdentityHashMap<>();
+ for (DexAnnotationElement element : meta.annotation.elements) {
+ elementMap.put(element.name, element);
+ }
+
+ DexAnnotationElement kind = elementMap.get(kotlin.metadata.kind);
+ if (kind == null) {
+ throw new MetadataError("element 'k' is missing.");
+ }
+ Integer k = (Integer) kind.value.getBoxedValue();
+ DexAnnotationElement metadataVersion = elementMap.get(kotlin.metadata.metadataVersion);
+ int[] mv = metadataVersion == null ? null : getUnboxedIntArray(metadataVersion.value, "mv");
+ DexAnnotationElement bytecodeVersion = elementMap.get(kotlin.metadata.bytecodeVersion);
+ int[] bv = bytecodeVersion == null ? null : getUnboxedIntArray(bytecodeVersion.value, "bv");
+ DexAnnotationElement data1 = elementMap.get(kotlin.metadata.data1);
+ String[] d1 = data1 == null ? null : getUnboxedStringArray(data1.value, "d1");
+ DexAnnotationElement data2 = elementMap.get(kotlin.metadata.data2);
+ String[] d2 = data2 == null ? null : getUnboxedStringArray(data2.value, "d2");
+ DexAnnotationElement extraString = elementMap.get(kotlin.metadata.extraString);
+ String xs = extraString == null ? null : getUnboxedString(extraString.value, "xs");
+ DexAnnotationElement packageName = elementMap.get(kotlin.metadata.packageName);
+ String pn = packageName == null ? null : getUnboxedString(packageName.value, "pn");
+ DexAnnotationElement extraInt = elementMap.get(kotlin.metadata.extraInt);
+ Integer xi = extraInt == null ? null : (Integer) extraInt.value.getBoxedValue();
+
+ KotlinClassHeader header = new KotlinClassHeader(k, mv, bv, d1, d2, xs, pn, xi);
+ KotlinClassMetadata kMetadata = KotlinClassMetadata.read(header);
+
+ if (kMetadata instanceof KotlinClassMetadata.Class) {
+ return KotlinClass.fromKotlinClassMetadata(kMetadata);
+ } else if (kMetadata instanceof KotlinClassMetadata.FileFacade) {
+ return KotlinFile.fromKotlinClassMetadata(kMetadata);
+ } else if (kMetadata instanceof KotlinClassMetadata.MultiFileClassFacade) {
+ return KotlinClassFacade.fromKotlinClassMetadata(kMetadata);
+ } else if (kMetadata instanceof KotlinClassMetadata.MultiFileClassPart) {
+ return KotlinClassPart.fromKotlinClassMetdata(kMetadata);
+ } else if (kMetadata instanceof KotlinClassMetadata.SyntheticClass) {
+ return KotlinSyntheticClass.fromKotlinClassMetadata(kMetadata, kotlin, clazz);
+ } else {
+ throw new MetadataError("unsupported 'k' value: " + k);
+ }
+ }
+
+ private static int[] getUnboxedIntArray(DexValue v, String elementName) {
+ if (!(v instanceof DexValueArray)) {
+ throw new MetadataError("invalid '" + elementName + "' value: " + v.toSourceString());
+ }
+ DexValueArray intArrayValue = (DexValueArray) v;
+ DexValue[] values = intArrayValue.getValues();
+ int[] result = new int [values.length];
+ for (int i = 0; i < values.length; i++) {
+ result[i] = (Integer) values[i].getBoxedValue();
+ }
+ return result;
+ }
+
+ private static String[] getUnboxedStringArray(DexValue v, String elementName) {
+ if (!(v instanceof DexValueArray)) {
+ throw new MetadataError("invalid '" + elementName + "' value: " + v.toSourceString());
+ }
+ DexValueArray stringArrayValue = (DexValueArray) v;
+ DexValue[] values = stringArrayValue.getValues();
+ String[] result = new String [values.length];
+ for (int i = 0; i < values.length; i++) {
+ result[i] = getUnboxedString(values[i], elementName + "[" + i + "]");
+ }
+ return result;
+ }
+
+ private static String getUnboxedString(DexValue v, String elementName) {
+ if (!(v instanceof DexValueString)) {
+ throw new MetadataError("invalid '" + elementName + "' value: " + v.toSourceString());
+ }
+ return ((DexValueString) v).getValue().toString();
+ }
+
+ private static class MetadataError extends RuntimeException {
+ MetadataError(String cause) {
+ super(cause);
+ }
+ }
+
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
index d6da817..da66e6c 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
@@ -4,7 +4,31 @@
package com.android.tools.r8.kotlin;
-public final class KotlinClassPart extends KotlinInfo {
+import kotlinx.metadata.KmPackageVisitor;
+import kotlinx.metadata.jvm.KotlinClassMetadata;
+
+public final class KotlinClassPart extends KotlinInfo<KotlinClassMetadata.MultiFileClassPart> {
+
+ static KotlinClassPart fromKotlinClassMetdata(KotlinClassMetadata kotlinClassMetadata) {
+ assert kotlinClassMetadata instanceof KotlinClassMetadata.MultiFileClassPart;
+ KotlinClassMetadata.MultiFileClassPart multiFileClassPart =
+ (KotlinClassMetadata.MultiFileClassPart) kotlinClassMetadata;
+ return new KotlinClassPart(multiFileClassPart);
+ }
+
+ private KotlinClassPart(KotlinClassMetadata.MultiFileClassPart metadata) {
+ super(metadata);
+ }
+
+ @Override
+ void validateMetadata(KotlinClassMetadata.MultiFileClassPart metadata) {
+ // To avoid lazy parsing/verifying metadata.
+ metadata.accept(new MultiFileClassPartMetadataVisitor());
+ }
+
+ private static class MultiFileClassPartMetadataVisitor extends KmPackageVisitor {
+ }
+
@Override
public Kind getKind() {
return Kind.Part;
@@ -20,6 +44,4 @@
return this;
}
- KotlinClassPart() {
- }
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
index 38a77ad..bcb70ed 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
@@ -4,7 +4,31 @@
package com.android.tools.r8.kotlin;
-public final class KotlinFile extends KotlinInfo {
+import kotlinx.metadata.KmPackageVisitor;
+import kotlinx.metadata.jvm.KotlinClassMetadata;
+
+public final class KotlinFile extends KotlinInfo<KotlinClassMetadata.FileFacade> {
+
+ static KotlinFile fromKotlinClassMetadata(KotlinClassMetadata kotlinClassMetadata) {
+ assert kotlinClassMetadata instanceof KotlinClassMetadata.FileFacade;
+ KotlinClassMetadata.FileFacade fileFacade =
+ (KotlinClassMetadata.FileFacade) kotlinClassMetadata;
+ return new KotlinFile(fileFacade);
+ }
+
+ private KotlinFile(KotlinClassMetadata.FileFacade metadata) {
+ super(metadata);
+ }
+
+ @Override
+ void validateMetadata(KotlinClassMetadata.FileFacade metadata) {
+ // To avoid lazy parsing/verifying metadata.
+ metadata.accept(new FileFacadeMetadataVisitor());
+ }
+
+ private static class FileFacadeMetadataVisitor extends KmPackageVisitor {
+ }
+
@Override
public Kind getKind() {
return Kind.File;
@@ -20,6 +44,4 @@
return this;
}
- KotlinFile() {
- }
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
index 4043bc6..702e0eb 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
@@ -4,11 +4,23 @@
package com.android.tools.r8.kotlin;
+import kotlinx.metadata.jvm.KotlinClassMetadata;
+
// Provides access to kotlin information.
-public abstract class KotlinInfo {
+public abstract class KotlinInfo<MetadataKind extends KotlinClassMetadata> {
+ MetadataKind metadata;
+
KotlinInfo() {
}
+ KotlinInfo(MetadataKind metadata) {
+ validateMetadata(metadata);
+ this.metadata = metadata;
+ }
+
+ // Subtypes will define how to validate the given metadata.
+ abstract void validateMetadata(MetadataKind metadata);
+
public enum Kind {
Class, File, Synthetic, Part, Facade
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
index 68721bb..4660800 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
@@ -4,7 +4,11 @@
package com.android.tools.r8.kotlin;
-public final class KotlinSyntheticClass extends KotlinInfo {
+import com.android.tools.r8.graph.DexClass;
+import kotlinx.metadata.KmLambdaVisitor;
+import kotlinx.metadata.jvm.KotlinClassMetadata;
+
+public final class KotlinSyntheticClass extends KotlinInfo<KotlinClassMetadata.SyntheticClass> {
public enum Flavour {
KotlinStyleLambda,
JavaStyleLambda,
@@ -13,12 +17,40 @@
private final Flavour flavour;
- KotlinSyntheticClass(Flavour flavour) {
+ static KotlinSyntheticClass fromKotlinClassMetadata(
+ KotlinClassMetadata kotlinClassMetadata, Kotlin kotlin, DexClass clazz) {
+ assert kotlinClassMetadata instanceof KotlinClassMetadata.SyntheticClass;
+ KotlinClassMetadata.SyntheticClass syntheticClass =
+ (KotlinClassMetadata.SyntheticClass) kotlinClassMetadata;
+ if (isKotlinStyleLambda(syntheticClass, kotlin, clazz)) {
+ return new KotlinSyntheticClass(Flavour.KotlinStyleLambda, syntheticClass);
+ } else if (isJavaStyleLambda(syntheticClass, kotlin, clazz)) {
+ return new KotlinSyntheticClass(Flavour.JavaStyleLambda, syntheticClass);
+ } else {
+ return new KotlinSyntheticClass(Flavour.Unclassified, syntheticClass);
+ }
+ }
+
+ private KotlinSyntheticClass(Flavour flavour, KotlinClassMetadata.SyntheticClass metadata) {
this.flavour = flavour;
+ validateMetadata(metadata);
+ this.metadata = metadata;
+ }
+
+ @Override
+ void validateMetadata(KotlinClassMetadata.SyntheticClass metadata) {
+ if (metadata.isLambda()) {
+ SyntheticClassMetadataVisitor visitor = new SyntheticClassMetadataVisitor();
+ // To avoid lazy parsing/verifying metadata.
+ metadata.accept(visitor);
+ }
+ }
+
+ private static class SyntheticClassMetadataVisitor extends KmLambdaVisitor {
}
public boolean isLambda() {
- return flavour == Flavour.KotlinStyleLambda || flavour == Flavour.JavaStyleLambda;
+ return isKotlinStyleLambda() || isJavaStyleLambda();
}
public boolean isKotlinStyleLambda() {
@@ -43,4 +75,31 @@
public KotlinSyntheticClass asSyntheticClass() {
return this;
}
+
+ /**
+ * Returns {@code true} if the given {@link DexClass} is a Kotlin-style lambda:
+ * a class that
+ * 1) is recognized as lambda in its Kotlin metadata;
+ * 2) directly extends kotlin.jvm.internal.Lambda
+ */
+ private static boolean isKotlinStyleLambda(
+ KotlinClassMetadata.SyntheticClass metadata, Kotlin kotlin, DexClass clazz) {
+ return metadata.isLambda()
+ && clazz.superType == kotlin.functional.lambdaType;
+ }
+
+ /**
+ * Returns {@code true} if the given {@link DexClass} is a Java-style lambda:
+ * a class that
+ * 1) is recognized as lambda in its Kotlin metadata;
+ * 2) doesn't extend any other class;
+ * 3) directly implements only one Java SAM.
+ */
+ private static boolean isJavaStyleLambda(
+ KotlinClassMetadata.SyntheticClass metadata, Kotlin kotlin, DexClass clazz) {
+ return metadata.isLambda()
+ && clazz.superType == kotlin.factory.objectType
+ && clazz.interfaces.size() == 1;
+ }
+
}
diff --git a/src/main/java/com/android/tools/r8/shaking/FilteredClassPath.java b/src/main/java/com/android/tools/r8/shaking/FilteredClassPath.java
index 1eb11e9..c67af00 100644
--- a/src/main/java/com/android/tools/r8/shaking/FilteredClassPath.java
+++ b/src/main/java/com/android/tools/r8/shaking/FilteredClassPath.java
@@ -46,14 +46,14 @@
return path;
}
- public boolean matchesFile(Path file) {
+ public boolean matchesFile(String name) {
if (isUnfiltered()) {
return true;
}
boolean isNegated = false;
for (String pattern : pattern) {
isNegated = pattern.charAt(0) == '!';
- boolean matches = matchAgainstFileName(file.toString(), 0, pattern, isNegated ? 1 : 0);
+ boolean matches = matchAgainstFileName(name, 0, pattern, isNegated ? 1 : 0);
if (matches) {
return !isNegated;
}
@@ -63,7 +63,7 @@
}
private boolean containsFileSeparator(String string) {
- return string.indexOf(File.separatorChar) != -1;
+ return string.indexOf('/') != -1;
}
private boolean matchAgainstFileName(String fileName, int namePos, String pattern,
@@ -95,7 +95,7 @@
}
} else {
for (int i = namePos; i < fileName.length(); i++) {
- if (!includeFileSeparators && fileName.charAt(i) == File.separatorChar) {
+ if (!includeFileSeparators && fileName.charAt(i) == '/') {
return false;
}
if (matchAgainstFileName(fileName, i, pattern, patternPos + 1)) {
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index d54c95c..4b57209 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.shaking;
import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.graph.DefaultUseRegistry;
import com.android.tools.r8.graph.DexAnnotationSet;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexClass;
@@ -22,6 +23,7 @@
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.ParameterAnnotationsList;
import com.android.tools.r8.graph.PresortedComparable;
+import com.android.tools.r8.graph.UseRegistry;
import com.android.tools.r8.ir.code.Invoke.Type;
import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode;
import com.android.tools.r8.ir.synthetic.SynthesizedCode;
@@ -67,7 +69,7 @@
private final DexApplication application;
private final AppInfoWithLiveness appInfo;
private final GraphLense graphLense;
- private final Map<DexType, DexType> mergedClasses = new IdentityHashMap<>();
+ private final Map<DexType, DexType> mergedClasses = new HashMap<>();
private final Timing timing;
private Collection<DexMethod> invokes;
@@ -84,19 +86,109 @@
// Returns a set of types that must not be merged into other types.
private Set<DexType> getPinnedTypes(Iterable<DexProgramClass> classes) {
- // TODO(christofferqa): Compute types from [classes] that must be pinned in order for vertical
- // class merging to work.
- return new HashSet<>();
+ Set<DexType> pinnedTypes = new HashSet<>();
+ for (DexProgramClass clazz : classes) {
+ for (DexEncodedMethod method : clazz.methods()) {
+ // TODO(christofferqa): Remove the invariant that the graph lense should not modify any
+ // methods from the sets alwaysInline and noSideEffects (see use of assertNotModifiedBy-
+ // Lense).
+ if (appInfo.alwaysInline.contains(method) || appInfo.noSideEffects.containsKey(method)) {
+ DexClass other = appInfo.definitionFor(method.method.proto.returnType);
+ if (other != null && other.isProgramClass()) {
+ // If we were to merge [other] into its sub class, then we would implicitly change the
+ // signature of this method, and therefore break the invariant.
+ pinnedTypes.add(other.type);
+ }
+ for (DexType parameterType : method.method.proto.parameters.values) {
+ other = appInfo.definitionFor(parameterType);
+ if (other != null && other.isProgramClass()) {
+ // If we were to merge [other] into its sub class, then we would implicitly change the
+ // signature of this method, and therefore break the invariant.
+ pinnedTypes.add(other.type);
+ }
+ }
+ }
+
+ // Avoid merging two types if this could remove a NoSuchMethodError, as illustrated by the
+ // following example. (Alternatively, it would be possible to merge A and B and rewrite the
+ // "invoke-super A.m" instruction into "invoke-super Object.m" to preserve the error. This
+ // situation should generally not occur in practice, though.)
+ //
+ // class A {}
+ // class B extends A {
+ // public void m() {}
+ // }
+ // class C extends A {
+ // public void m() {
+ // invoke-super "A.m" <- should yield NoSuchMethodError, cannot merge A and B
+ // }
+ // }
+ if (!method.isStaticMethod()) {
+ method.registerCodeReferences(
+ new DefaultUseRegistry() {
+ @Override
+ public boolean registerInvokeSuper(DexMethod target) {
+ DexClass targetClass = appInfo.definitionFor(target.getHolder());
+ if (targetClass != null
+ && targetClass.isProgramClass()
+ && targetClass.lookupVirtualMethod(target) == null) {
+ pinnedTypes.add(target.getHolder());
+ }
+ return true;
+ }
+ });
+ }
+ }
+ }
+ return pinnedTypes;
}
private boolean isMergeCandidate(DexProgramClass clazz, Set<DexType> pinnedTypes) {
- // We can merge program classes if they are not instantiated, have a single subtype
- // and we do not have to keep them.
- return !clazz.isLibraryClass()
- && !appInfo.instantiatedTypes.contains(clazz.type)
- && !appInfo.isPinned(clazz.type)
- && !pinnedTypes.contains(clazz.type)
- && clazz.type.getSingleSubtype() != null;
+ if (appInfo.instantiatedTypes.contains(clazz.type)
+ || appInfo.isPinned(clazz.type)
+ || pinnedTypes.contains(clazz.type)) {
+ return false;
+ }
+ DexType singleSubtype = clazz.type.getSingleSubtype();
+ if (singleSubtype == null) {
+ // TODO(christofferqa): Even if [clazz] has multiple subtypes, we could still merge it into
+ // its subclass if [clazz] is not live. This should only be done, though, if it does not
+ // lead to members being duplicated.
+ return false;
+ }
+ DexClass targetClass = appInfo.definitionFor(singleSubtype);
+ if (mergeMayLeadToIllegalAccesses(clazz, targetClass)) {
+ return false;
+ }
+ for (DexEncodedField field : clazz.fields()) {
+ if (appInfo.isPinned(field.field)) {
+ return false;
+ }
+ }
+ for (DexEncodedMethod method : clazz.methods()) {
+ if (appInfo.isPinned(method.method)) {
+ return false;
+ }
+ if (method.isInstanceInitializer() && disallowInlining(method)) {
+ // Cannot guarantee that markForceInline() will work.
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private boolean mergeMayLeadToIllegalAccesses(DexClass clazz, DexClass singleSubclass) {
+ if (clazz.type.isSamePackage(singleSubclass.type)) {
+ return false;
+ }
+ // TODO(christofferqa): To merge [clazz] into a class from another package we need to ensure:
+ // (A) All accesses to [clazz] and its members from inside the current package of [clazz] will
+ // continue to work. This is guaranteed if [clazz] is public and all members of [clazz] are
+ // either private or public.
+ // (B) All accesses from [clazz] to classes or members from the current package of [clazz] will
+ // continue to work. This is guaranteed if the methods of [clazz] do not access any private
+ // or protected classes or members from the current package of [clazz].
+ return true;
}
private void addProgramMethods(Set<Wrapper<DexMethod>> set, DexMethod method,
@@ -228,6 +320,11 @@
}
continue;
}
+ // Field resolution first considers the direct interfaces of [targetClass] before it proceeds
+ // to the super class.
+ if (fieldResolutionMayChange(clazz, targetClass)) {
+ continue;
+ }
// Guard against the case where we have two methods that may get the same signature
// if we replace types. This is rare, so we approximate and err on the safe side here.
if (new CollisionDetector(clazz.type, targetClass.type, getInvokes(), mergedClasses)
@@ -273,6 +370,35 @@
return renamedMembersLense.build(graphLense);
}
+ private boolean fieldResolutionMayChange(DexClass source, DexClass target) {
+ if (source.type == target.superType) {
+ // If there is a "iget Target.f" or "iput Target.f" instruction in target, and the class
+ // Target implements an interface that declares a static final field f, this should yield an
+ // IncompatibleClassChangeError.
+ // TODO(christofferqa): In the following we only check if a static field from an interface
+ // shadows an instance field from [source]. We could actually check if there is an iget/iput
+ // instruction whose resolution would be affected by the merge. The situation where a static
+ // field shadows an instance field is probably not widespread in practice, though.
+ FieldSignatureEquivalence equivalence = FieldSignatureEquivalence.get();
+ Set<Wrapper<DexField>> staticFieldsInInterfacesOfTarget = new HashSet<>();
+ for (DexType interfaceType : target.interfaces.values) {
+ DexClass clazz = appInfo.definitionFor(interfaceType);
+ for (DexEncodedField staticField : clazz.staticFields()) {
+ staticFieldsInInterfacesOfTarget.add(equivalence.wrap(staticField.field));
+ }
+ }
+ for (DexEncodedField instanceField : source.instanceFields()) {
+ if (staticFieldsInInterfacesOfTarget.contains(equivalence.wrap(instanceField.field))) {
+ // An instruction "iget Target.f" or "iput Target.f" that used to hit a static field in an
+ // interface would now hit an instance field from [source], so that an IncompatibleClass-
+ // ChangeError would no longer be thrown. Abort merge.
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
private class ClassMerger {
private static final String CONSTRUCTOR_NAME = "constructor";
@@ -298,8 +424,7 @@
// targetClass that are not currently contained.
// Step 1: Merge methods
Set<Wrapper<DexMethod>> existingMethods = new HashSet<>();
- addAll(existingMethods, target.directMethods(), MethodSignatureEquivalence.get());
- addAll(existingMethods, target.virtualMethods(), MethodSignatureEquivalence.get());
+ addAll(existingMethods, target.methods(), MethodSignatureEquivalence.get());
List<DexEncodedMethod> directMethods = new ArrayList<>();
for (DexEncodedMethod directMethod : source.directMethods()) {
@@ -341,8 +466,7 @@
// Record that invoke-super instructions in the target class should be redirected to the
// newly created direct method.
- deferredRenamings.mapVirtualMethodToDirectInType(
- virtualMethod.method, resultingDirectMethod.method, target.type);
+ redirectSuperCallsInTarget(virtualMethod.method, resultingDirectMethod.method);
if (shadowedBy == null) {
// In addition to the newly added direct method, create a virtual method such that we do
@@ -380,8 +504,7 @@
}
// Step 2: Merge fields
Set<Wrapper<DexField>> existingFields = new HashSet<>();
- addAll(existingFields, target.instanceFields(), FieldSignatureEquivalence.get());
- addAll(existingFields, target.staticFields(), FieldSignatureEquivalence.get());
+ addAll(existingFields, target.fields(), FieldSignatureEquivalence.get());
Collection<DexEncodedField> mergedStaticFields = mergeItems(
Iterators.forArray(source.staticFields()),
target.staticFields(),
@@ -432,6 +555,36 @@
return deferredRenamings;
}
+ private void redirectSuperCallsInTarget(DexMethod oldTarget, DexMethod newTarget) {
+ // If we merge class B into class C, and class C contains an invocation super.m(), then it
+ // is insufficient to rewrite "invoke-super B.m()" to "invoke-direct C.m$B()" (the method
+ // C.m$B denotes the direct method that has been created in C for B.m). In particular, there
+ // might be an instruction "invoke-super A.m()" in C that resolves to B.m at runtime (A is
+ // a superclass of B), which also needs to be rewritten to "invoke-direct C.m$B()".
+ //
+ // We handle this by adding a mapping for [target] and all of its supertypes.
+ DexClass holder = target;
+ while (holder != null && holder.isProgramClass()) {
+ DexMethod signatureInHolder =
+ application.dexItemFactory.createMethod(holder.type, oldTarget.proto, oldTarget.name);
+ // Only rewrite the invoke-super call if it does not lead to a NoSuchMethodError.
+ if (resolutionSucceeds(signatureInHolder)) {
+ deferredRenamings.mapVirtualMethodToDirectInType(
+ signatureInHolder, newTarget, target.type);
+ holder = holder.superType != null ? appInfo.definitionFor(holder.superType) : null;
+ } else {
+ break;
+ }
+ }
+ }
+
+ private boolean resolutionSucceeds(DexMethod targetMethod) {
+ DexClass enclosingClass = appInfo.definitionFor(targetMethod.holder);
+ return enclosingClass != null
+ && (enclosingClass.lookupVirtualMethod(targetMethod) != null
+ || appInfo.lookupSuperTarget(targetMethod, enclosingClass.type) != null);
+ }
+
private DexEncodedMethod buildBridgeMethod(
DexEncodedMethod signature, DexMethod invocationTarget) {
DexType holder = target.type;
@@ -468,7 +621,7 @@
}
private <T extends KeyedDexItem<S>, S extends PresortedComparable<S>> void addAll(
- Collection<Wrapper<S>> collection, T[] items, Equivalence<S> equivalence) {
+ Collection<Wrapper<S>> collection, Iterable<T> items, Equivalence<S> equivalence) {
for (T item : items) {
collection.add(equivalence.wrap(item.getKey()));
}
@@ -836,6 +989,86 @@
}
}
+ private static boolean disallowInlining(DexEncodedMethod method) {
+ // TODO(christofferqa): Determine the situations where markForceInline() may fail, and ensure
+ // that we always return true here in these cases.
+ MethodInlineDecision registry = new MethodInlineDecision();
+ method.getCode().registerCodeReferences(registry);
+ return registry.isInliningDisallowed();
+ }
+
+ private static class MethodInlineDecision extends UseRegistry {
+ private boolean disallowInlining = false;
+
+ public boolean isInliningDisallowed() {
+ return disallowInlining;
+ }
+
+ private boolean allowInlining() {
+ return true;
+ }
+
+ private boolean disallowInlining() {
+ disallowInlining = true;
+ return true;
+ }
+
+ @Override
+ public boolean registerInvokeInterface(DexMethod method) {
+ return disallowInlining();
+ }
+
+ @Override
+ public boolean registerInvokeVirtual(DexMethod method) {
+ return disallowInlining();
+ }
+
+ @Override
+ public boolean registerInvokeDirect(DexMethod method) {
+ return allowInlining();
+ }
+
+ @Override
+ public boolean registerInvokeStatic(DexMethod method) {
+ return allowInlining();
+ }
+
+ @Override
+ public boolean registerInvokeSuper(DexMethod method) {
+ return allowInlining();
+ }
+
+ @Override
+ public boolean registerInstanceFieldWrite(DexField field) {
+ return allowInlining();
+ }
+
+ @Override
+ public boolean registerInstanceFieldRead(DexField field) {
+ return allowInlining();
+ }
+
+ @Override
+ public boolean registerNewInstance(DexType type) {
+ return allowInlining();
+ }
+
+ @Override
+ public boolean registerStaticFieldRead(DexField field) {
+ return allowInlining();
+ }
+
+ @Override
+ public boolean registerStaticFieldWrite(DexField field) {
+ return allowInlining();
+ }
+
+ @Override
+ public boolean registerTypeReference(DexType type) {
+ return allowInlining();
+ }
+ }
+
public Collection<DexType> getRemovedClasses() {
return Collections.unmodifiableCollection(mergedClasses.keySet());
}
diff --git a/src/main/java/com/android/tools/r8/utils/ArchiveResourceProvider.java b/src/main/java/com/android/tools/r8/utils/ArchiveResourceProvider.java
index 0f06ca8..a611105 100644
--- a/src/main/java/com/android/tools/r8/utils/ArchiveResourceProvider.java
+++ b/src/main/java/com/android/tools/r8/utils/ArchiveResourceProvider.java
@@ -4,8 +4,6 @@
package com.android.tools.r8.utils;
import static com.android.tools.r8.utils.FileUtils.isArchive;
-import static com.android.tools.r8.utils.FileUtils.isClassFile;
-import static com.android.tools.r8.utils.FileUtils.isDexFile;
import com.android.tools.r8.DataDirectoryResource;
import com.android.tools.r8.DataEntryResource;
@@ -23,8 +21,6 @@
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
-import java.nio.file.Path;
-import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -56,17 +52,16 @@
ZipEntry entry = entries.nextElement();
try (InputStream stream = zipFile.getInputStream(entry)) {
String name = entry.getName();
- Path path = Paths.get(name);
Origin entryOrigin = new ArchiveEntryOrigin(name, origin);
- if (archive.matchesFile(path)) {
- if (isDexFile(path)) {
+ if (archive.matchesFile(name)) {
+ if (ZipUtils.isDexFile(name)) {
if (!ignoreDexInArchive) {
ProgramResource resource =
OneShotByteResource.create(
Kind.DEX, entryOrigin, ByteStreams.toByteArray(stream), null);
dexResources.add(resource);
}
- } else if (isClassFile(path)) {
+ } else if (ZipUtils.isClassFile(name)) {
String descriptor = DescriptorUtils.guessTypeDescriptor(name);
ProgramResource resource =
OneShotByteResource.create(
@@ -111,7 +106,7 @@
final Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
- Path name = Paths.get(entry.getName());
+ String name = entry.getName();
if (archive.matchesFile(name) && !isProgramResourceName(name)) {
if (entry.isDirectory()) {
resourceBrowser.visit(DataDirectoryResource.fromZip(zipFile, entry));
@@ -129,7 +124,7 @@
}
}
- private boolean isProgramResourceName(Path name) {
- return isClassFile(name) || (isDexFile(name) && !ignoreDexInArchive);
+ private boolean isProgramResourceName(String name) {
+ return ZipUtils.isClassFile(name) || (ZipUtils.isDexFile(name) && !ignoreDexInArchive);
}
}
diff --git a/src/main/java/com/android/tools/r8/utils/FilteredArchiveClassFileProvider.java b/src/main/java/com/android/tools/r8/utils/FilteredArchiveClassFileProvider.java
index f933b42..0c6cc14 100644
--- a/src/main/java/com/android/tools/r8/utils/FilteredArchiveClassFileProvider.java
+++ b/src/main/java/com/android/tools/r8/utils/FilteredArchiveClassFileProvider.java
@@ -6,12 +6,11 @@
import com.android.tools.r8.ArchiveClassFileProvider;
import com.android.tools.r8.shaking.FilteredClassPath;
import java.io.IOException;
-import java.nio.file.Paths;
// Internal filtered class-file provider.
class FilteredArchiveClassFileProvider extends ArchiveClassFileProvider {
FilteredArchiveClassFileProvider(FilteredClassPath archive) throws IOException {
- super(archive.getPath(), entry -> archive.matchesFile(Paths.get(entry)));
+ super(archive.getPath(), entry -> archive.matchesFile(entry));
}
}
diff --git a/src/main/java/com/android/tools/r8/utils/ZipUtils.java b/src/main/java/com/android/tools/r8/utils/ZipUtils.java
index 849377b..fd50583 100644
--- a/src/main/java/com/android/tools/r8/utils/ZipUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ZipUtils.java
@@ -3,6 +3,10 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.utils;
+import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
+import static com.android.tools.r8.utils.FileUtils.DEX_EXTENSION;
+import static com.android.tools.r8.utils.FileUtils.MODULE_INFO_CLASS;
+
import com.android.tools.r8.errors.CompilationError;
import com.google.common.io.ByteStreams;
import java.io.File;
@@ -92,4 +96,17 @@
stream.write(content);
stream.closeEntry();
}
+
+ public static boolean isDexFile(String entry) {
+ String name = entry.toLowerCase();
+ return name.endsWith(DEX_EXTENSION);
+ }
+
+ public static boolean isClassFile(String entry) {
+ String name = entry.toLowerCase();
+ if (name.endsWith(MODULE_INFO_CLASS)) {
+ return false;
+ }
+ return name.endsWith(CLASS_EXTENSION);
+ }
}
diff --git a/src/test/apiUsageSample/com/android/tools/apiusagesample/D8ApiUsageSample.java b/src/test/apiUsageSample/com/android/tools/apiusagesample/D8ApiUsageSample.java
index 2ef0123..23dc7e0 100644
--- a/src/test/apiUsageSample/com/android/tools/apiusagesample/D8ApiUsageSample.java
+++ b/src/test/apiUsageSample/com/android/tools/apiusagesample/D8ApiUsageSample.java
@@ -391,8 +391,9 @@
ZipInputStream zip = new ZipInputStream(Files.newInputStream(file), StandardCharsets.UTF_8);
ZipEntry entry;
while (null != (entry = zip.getNextEntry())) {
- if (isClassFile(Paths.get(entry.getName()))) {
- Origin origin = new ArchiveEntryOrigin(entry.getName(), zipOrigin);
+ String name = entry.getName();
+ if (isClassFile(name)) {
+ Origin origin = new ArchiveEntryOrigin(name, zipOrigin);
classfiles.add(new ClassFileContent(origin, readBytes(zip)));
}
}
diff --git a/src/test/apiUsageSample/com/android/tools/apiusagesample/R8ApiUsageSample.java b/src/test/apiUsageSample/com/android/tools/apiusagesample/R8ApiUsageSample.java
index 2c2e8d7..7bce10a 100644
--- a/src/test/apiUsageSample/com/android/tools/apiusagesample/R8ApiUsageSample.java
+++ b/src/test/apiUsageSample/com/android/tools/apiusagesample/R8ApiUsageSample.java
@@ -382,8 +382,9 @@
ZipInputStream zip = new ZipInputStream(Files.newInputStream(file), StandardCharsets.UTF_8);
ZipEntry entry;
while (null != (entry = zip.getNextEntry())) {
- if (isClassFile(Paths.get(entry.getName()))) {
- Origin origin = new ArchiveEntryOrigin(entry.getName(), zipOrigin);
+ String name = entry.getName();
+ if (isClassFile(name)) {
+ Origin origin = new ArchiveEntryOrigin(name, zipOrigin);
classfiles.add(new ClassFileContent(origin, readBytes(zip)));
}
}
diff --git a/src/test/java/com/android/tools/r8/ArchiveClassFileProviderTest.java b/src/test/java/com/android/tools/r8/ArchiveClassFileProviderTest.java
new file mode 100644
index 0000000..6ceb0a2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ArchiveClassFileProviderTest.java
@@ -0,0 +1,43 @@
+// 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;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+public class ArchiveClassFileProviderTest {
+
+ @Rule
+ public TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+ public Path createZip() throws IOException {
+ Path tempRoot = temporaryFolder.getRoot().toPath();
+ Path zipFile = tempRoot.resolve("zipfile.zip");
+ ZipOutputStream zipStream =
+ new ZipOutputStream(new FileOutputStream(zipFile.toFile()), StandardCharsets.UTF_8);
+ ZipEntry entry = new ZipEntry("non-ascii:$\u02CF");
+ zipStream.putNextEntry(entry);
+ zipStream.write(10);
+ zipStream.close();
+ return zipFile;
+ }
+
+ @Test
+ public void testSystemLocale() throws IOException, ResourceException {
+ // Set the locale used for Paths to ASCII which will make Path creation fail
+ // for non-ascii names.
+ System.setProperty("sun.jnu.encoding", "ASCII");
+ Path zipFile = createZip();
+ new ArchiveClassFileProvider(zipFile);
+ ArchiveProgramResourceProvider.fromArchive(zipFile).getProgramResources();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java b/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
index 258aa43..ee82fc4 100644
--- a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
+++ b/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
@@ -1920,11 +1920,6 @@
// 1) t03
// java.lang.AssertionError: expected null, but was:<[I@1b32f32>
- .put("lang.ref.WeakReference.isEnqueued.WeakReference_isEnqueued_A01",
- match(runtimes(Version.DEFAULT, Version.V7_0_0, Version.V6_0_1, Version.V5_1_1)))
- // 1) t03
- // java.lang.AssertionError: reference is not enqueued after 2 sec
-
.put("lang.StackTraceElement.toString.StackTraceElement_toString_A01",
match(runtimes(Version.DEFAULT)))
// 1) t03
@@ -4959,8 +4954,9 @@
.put("lang.ref.PhantomReference.isEnqueued.PhantomReference_isEnqueued_A01",
match(runtimesUpTo(Version.V4_4_4)))
- .put("lang.ref.WeakReference.isEnqueued.WeakReference_isEnqueued_A01",
- match(runtimesUpTo(Version.V4_4_4)))
+ .put("lang.ref.WeakReference.isEnqueued.WeakReference_isEnqueued_A01", any())
+ // 1) t03
+ // java.lang.AssertionError: reference is not enqueued after 2 sec
.put("lang.ref.WeakReference.enqueue.WeakReference_enqueue_A01",
match(runtimesUpTo(Version.V4_4_4)))
diff --git a/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java b/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java
index 6069b4c..da32cce 100644
--- a/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java
@@ -31,7 +31,6 @@
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
-
@RunWith(Parameterized.class)
public class MethodHandleTestRunner extends TestBase {
static final Class<?> CLASS = MethodHandleTest.class;
@@ -169,7 +168,9 @@
Arrays.asList(
"-keep public class com.android.tools.r8.cf.MethodHandleTest {",
" public static void main(...);",
- "}"),
+ "}",
+ // Disallow merging MethodHandleTest$I into MethodHandleTest$Impl
+ "-keep public interface com.android.tools.r8.cf.MethodHandleTest$I"),
Origin.unknown());
}
try {
diff --git a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
index 52e2313..7016163 100644
--- a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
@@ -238,7 +238,6 @@
assertEquals(2, numberOfMoveExceptionInstructions);
}
- @Ignore("b/73958515")
@Test
public void testNoIllegalClassAccess() throws Exception {
String main = "classmerging.SimpleInterfaceAccessTest";
@@ -258,18 +257,36 @@
"classmerging.pkg.SimpleInterfaceImplRetriever",
"classmerging.pkg.SimpleInterfaceImplRetriever$SimpleInterfaceImpl");
runTest(main, programFiles, preservedClassNames);
+ }
+
+ @Ignore("b/73958515")
+ @Test
+ public void testNoIllegalClassAccessWithAccessModifications() throws Exception {
// If access modifications are allowed then SimpleInterface should be merged into
// SimpleInterfaceImpl.
- preservedClassNames =
+ String main = "classmerging.SimpleInterfaceAccessTest";
+ Path[] programFiles =
+ new Path[] {
+ CF_DIR.resolve("SimpleInterface.class"),
+ CF_DIR.resolve("SimpleInterfaceAccessTest.class"),
+ CF_DIR.resolve("pkg/SimpleInterfaceImplRetriever.class"),
+ CF_DIR.resolve("pkg/SimpleInterfaceImplRetriever$SimpleInterfaceImpl.class")
+ };
+ ImmutableSet<String> preservedClassNames =
ImmutableSet.of(
"classmerging.SimpleInterfaceAccessTest",
"classmerging.pkg.SimpleInterfaceImplRetriever",
"classmerging.pkg.SimpleInterfaceImplRetriever$SimpleInterfaceImpl");
+ // Allow access modifications (and prevent SimpleInterfaceImplRetriever from being removed as
+ // a result of inlining).
runTest(
main,
programFiles,
preservedClassNames,
- getProguardConfig(EXAMPLE_KEEP, "-allowaccessmodification"));
+ getProguardConfig(
+ EXAMPLE_KEEP,
+ "-allowaccessmodification",
+ "-keep public class classmerging.pkg.SimpleInterfaceImplRetriever"));
}
@Test
@@ -328,6 +345,7 @@
}
for (String rule : additionalRules) {
builder.append(rule);
+ builder.append(System.lineSeparator());
}
return builder.toString();
}
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeSpecialTest.java b/src/test/java/com/android/tools/r8/graph/InvokeSpecialTest.java
new file mode 100644
index 0000000..2c1479e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/InvokeSpecialTest.java
@@ -0,0 +1,24 @@
+// 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.graph;
+
+import com.android.tools.r8.AsmTestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.graph.invokespecial.Main;
+import com.android.tools.r8.graph.invokespecial.TestClassDump;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class InvokeSpecialTest extends AsmTestBase {
+
+ @Ignore("b/110175213")
+ @Test
+ public void testInvokeSpecial() throws Exception {
+ ensureSameOutput(
+ Main.class.getCanonicalName(),
+ ToolHelper.getClassAsBytes(Main.class),
+ TestClassDump.dump());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/Main.java b/src/test/java/com/android/tools/r8/graph/invokespecial/Main.java
new file mode 100644
index 0000000..deb316d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/Main.java
@@ -0,0 +1,13 @@
+// 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.graph.invokespecial;
+
+public class Main {
+
+ public static void main(String[] args) {
+ TestClass x = new TestClass();
+ x.m(true);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/TestClass.java b/src/test/java/com/android/tools/r8/graph/invokespecial/TestClass.java
new file mode 100644
index 0000000..c08091e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/TestClass.java
@@ -0,0 +1,15 @@
+// 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.graph.invokespecial;
+
+public class TestClass {
+
+ public void m(boolean recurse) {
+ System.out.println(recurse);
+ if (recurse) {
+ m(false);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/TestClassDump.java b/src/test/java/com/android/tools/r8/graph/invokespecial/TestClassDump.java
new file mode 100644
index 0000000..ae336834
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/TestClassDump.java
@@ -0,0 +1,67 @@
+// 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.graph.invokespecial;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+// Generated by running ./tools/asmifier.py build/classes/test/com/android/tools/r8/graph/-
+// invokespecial/TestClass.class, and changing the invoke-virtual TestClass.m() instruction to
+// an invoke-special instruction.
+public class TestClassDump implements Opcodes {
+
+ public static byte[] dump() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(
+ V1_8,
+ ACC_PUBLIC + ACC_SUPER,
+ "com/android/tools/r8/graph/invokespecial/TestClass",
+ null,
+ "java/lang/Object",
+ null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "m", "(Z)V", null, null);
+ mv.visitCode();
+ mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ mv.visitVarInsn(ILOAD, 1);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Z)V", false);
+ mv.visitVarInsn(ILOAD, 1);
+ Label l0 = new Label();
+ mv.visitJumpInsn(IFEQ, l0);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitInsn(ICONST_0);
+ // Note: Changed from INVOKEVIRTUAL to INVOKESPECIAL.
+ mv.visitMethodInsn(
+ INVOKESPECIAL, "com/android/tools/r8/graph/invokespecial/TestClass", "m", "(Z)V", false);
+ mv.visitLabel(l0);
+ mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(2, 2);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
index 74e3d5e..6ebb08b 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
@@ -83,13 +83,16 @@
.addProperty("internalLateInitProp", JAVA_LANG_STRING, Visibility.INTERNAL)
.addProperty("publicLateInitProp", JAVA_LANG_STRING, Visibility.PUBLIC);
- private Consumer<InternalOptions> disableClassInliner = o -> o.enableClassInlining = false;
+ private Consumer<InternalOptions> disableClassInliningAndMerging = o -> {
+ o.enableClassInlining = false;
+ o.enableClassMerging = false;
+ };
@Test
public void testMutableProperty_getterAndSetterAreRemoveIfNotUsed() throws Exception {
String mainClass = addMainToClasspath("properties/MutablePropertyKt",
"mutableProperty_noUseOfProperties");
- runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject classSubject = checkClassIsKept(dexInspector,
MUTABLE_PROPERTY_CLASS.getClassName());
@@ -112,7 +115,7 @@
public void testMutableProperty_privateIsAlwaysInlined() throws Exception {
String mainClass = addMainToClasspath("properties/MutablePropertyKt",
"mutableProperty_usePrivateProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject classSubject = checkClassIsKept(dexInspector,
MUTABLE_PROPERTY_CLASS.getClassName());
@@ -134,7 +137,7 @@
public void testMutableProperty_protectedIsAlwaysInlined() throws Exception {
String mainClass = addMainToClasspath("properties/MutablePropertyKt",
"mutableProperty_useProtectedProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject classSubject = checkClassIsKept(dexInspector,
MUTABLE_PROPERTY_CLASS.getClassName());
@@ -157,7 +160,7 @@
public void testMutableProperty_internalIsAlwaysInlined() throws Exception {
String mainClass = addMainToClasspath("properties/MutablePropertyKt",
"mutableProperty_useInternalProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject classSubject = checkClassIsKept(dexInspector,
MUTABLE_PROPERTY_CLASS.getClassName());
@@ -180,7 +183,7 @@
public void testMutableProperty_publicIsAlwaysInlined() throws Exception {
String mainClass = addMainToClasspath("properties/MutablePropertyKt",
"mutableProperty_usePublicProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject classSubject = checkClassIsKept(dexInspector,
MUTABLE_PROPERTY_CLASS.getClassName());
@@ -203,7 +206,7 @@
public void testMutableProperty_primitivePropertyIsAlwaysInlined() throws Exception {
String mainClass = addMainToClasspath("properties/MutablePropertyKt",
"mutableProperty_usePrimitiveProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject classSubject = checkClassIsKept(dexInspector,
MUTABLE_PROPERTY_CLASS.getClassName());
@@ -228,7 +231,7 @@
public void testLateInitProperty_getterAndSetterAreRemoveIfNotUsed() throws Exception {
String mainClass = addMainToClasspath("properties/LateInitPropertyKt",
"lateInitProperty_noUseOfProperties");
- runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject classSubject = checkClassIsKept(dexInspector,
LATE_INIT_PROPERTY_CLASS.getClassName());
@@ -252,7 +255,7 @@
public void testLateInitProperty_privateIsAlwaysInlined() throws Exception {
String mainClass = addMainToClasspath(
"properties/LateInitPropertyKt", "lateInitProperty_usePrivateLateInitProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject classSubject = checkClassIsKept(dexInspector,
LATE_INIT_PROPERTY_CLASS.getClassName());
@@ -275,7 +278,7 @@
public void testLateInitProperty_protectedIsAlwaysInlined() throws Exception {
String mainClass = addMainToClasspath("properties/LateInitPropertyKt",
"lateInitProperty_useProtectedLateInitProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject classSubject = checkClassIsKept(dexInspector,
LATE_INIT_PROPERTY_CLASS.getClassName());
@@ -296,7 +299,7 @@
public void testLateInitProperty_internalIsAlwaysInlined() throws Exception {
String mainClass = addMainToClasspath(
"properties/LateInitPropertyKt", "lateInitProperty_useInternalLateInitProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject classSubject = checkClassIsKept(dexInspector,
LATE_INIT_PROPERTY_CLASS.getClassName());
@@ -315,7 +318,7 @@
public void testLateInitProperty_publicIsAlwaysInlined() throws Exception {
String mainClass = addMainToClasspath(
"properties/LateInitPropertyKt", "lateInitProperty_usePublicLateInitProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject classSubject = checkClassIsKept(dexInspector,
LATE_INIT_PROPERTY_CLASS.getClassName());
@@ -334,7 +337,7 @@
public void testUserDefinedProperty_getterAndSetterAreRemoveIfNotUsed() throws Exception {
String mainClass = addMainToClasspath(
"properties/UserDefinedPropertyKt", "userDefinedProperty_noUseOfProperties");
- runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject classSubject = checkClassIsKept(dexInspector,
USER_DEFINED_PROPERTY_CLASS.getClassName());
@@ -351,7 +354,7 @@
public void testUserDefinedProperty_publicIsAlwaysInlined() throws Exception {
String mainClass = addMainToClasspath(
"properties/UserDefinedPropertyKt", "userDefinedProperty_useProperties");
- runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject classSubject = checkClassIsKept(dexInspector,
USER_DEFINED_PROPERTY_CLASS.getClassName());
@@ -377,7 +380,7 @@
public void testCompanionProperty_primitivePropertyCannotBeInlined() throws Exception {
String mainClass = addMainToClasspath(
"properties.CompanionPropertiesKt", "companionProperties_usePrimitiveProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject outerClass = checkClassIsKept(dexInspector,
"properties.CompanionProperties");
@@ -408,7 +411,7 @@
public void testCompanionProperty_privatePropertyIsAlwaysInlined() throws Exception {
String mainClass = addMainToClasspath(
"properties.CompanionPropertiesKt", "companionProperties_usePrivateProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject outerClass = checkClassIsKept(dexInspector,
"properties.CompanionProperties");
@@ -442,7 +445,7 @@
public void testCompanionProperty_internalPropertyCannotBeInlined() throws Exception {
String mainClass = addMainToClasspath(
"properties.CompanionPropertiesKt", "companionProperties_useInternalProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject outerClass = checkClassIsKept(dexInspector,
"properties.CompanionProperties");
@@ -473,7 +476,7 @@
public void testCompanionProperty_publicPropertyCannotBeInlined() throws Exception {
String mainClass = addMainToClasspath(
"properties.CompanionPropertiesKt", "companionProperties_usePublicProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject outerClass = checkClassIsKept(dexInspector,
"properties.CompanionProperties");
@@ -505,7 +508,7 @@
final TestKotlinCompanionClass testedClass = COMPANION_LATE_INIT_PROPERTY_CLASS;
String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt",
"companionLateInitProperties_usePrivateLateInitProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject outerClass = checkClassIsKept(dexInspector, testedClass.getOuterClassName());
ClassSubject companionClass = checkClassIsKept(dexInspector, testedClass.getClassName());
@@ -536,7 +539,7 @@
final TestKotlinCompanionClass testedClass = COMPANION_LATE_INIT_PROPERTY_CLASS;
String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt",
"companionLateInitProperties_useInternalLateInitProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject outerClass = checkClassIsKept(dexInspector, testedClass.getOuterClassName());
ClassSubject companionClass = checkClassIsKept(dexInspector, testedClass.getClassName());
@@ -560,7 +563,7 @@
final TestKotlinCompanionClass testedClass = COMPANION_LATE_INIT_PROPERTY_CLASS;
String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt",
"companionLateInitProperties_usePublicLateInitProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
+ runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject outerClass = checkClassIsKept(dexInspector, testedClass.getOuterClassName());
ClassSubject companionClass = checkClassIsKept(dexInspector, testedClass.getClassName());
diff --git a/src/test/java/com/android/tools/r8/shaking/FilteredClassPathTest.java b/src/test/java/com/android/tools/r8/shaking/FilteredClassPathTest.java
index f27813e..7c243ae 100644
--- a/src/test/java/com/android/tools/r8/shaking/FilteredClassPathTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/FilteredClassPathTest.java
@@ -34,16 +34,8 @@
private void testPath(List<String> filters, List<String> positives, List<String> negatives) {
FilteredClassPath path = makeFilteredClassPath(filters);
- Assert.assertTrue(
- positives.stream().map(FilteredClassPathTest::adaptFileSeparator).map(Paths::get)
- .allMatch(path::matchesFile));
- Assert.assertFalse(
- negatives.stream().map(FilteredClassPathTest::adaptFileSeparator).map(Paths::get)
- .allMatch(path::matchesFile));
- }
-
- private static String adaptFileSeparator(String s) {
- return s.replace('/', File.separatorChar);
+ Assert.assertTrue(positives.stream().allMatch(path::matchesFile));
+ Assert.assertFalse(negatives.stream().allMatch(path::matchesFile));
}
private static FilteredClassPath makeFilteredClassPath(List<String> filters) {
@@ -51,9 +43,7 @@
}
private static FilteredClassPath makeFilteredClassPath(Path path, List<String> filters) {
- return new FilteredClassPath(path,
- filters.stream().map(FilteredClassPathTest::adaptFileSeparator)
- .collect(ImmutableList.toImmutableList()));
+ return new FilteredClassPath(path, ImmutableList.copyOf(filters));
}
@Test