Merge "Reapply "The meet of precise types is only defined for identical types.""
diff --git a/src/main/java/com/android/tools/r8/ArchiveProgramResourceProvider.java b/src/main/java/com/android/tools/r8/ArchiveProgramResourceProvider.java
index 1d6e678..99c97a4 100644
--- a/src/main/java/com/android/tools/r8/ArchiveProgramResourceProvider.java
+++ b/src/main/java/com/android/tools/r8/ArchiveProgramResourceProvider.java
@@ -29,6 +29,10 @@
@KeepForSubclassing
public class ArchiveProgramResourceProvider implements ProgramResourceProvider {
+ interface ArchiveEntryConsumer {
+ void accept(ArchiveEntryOrigin entry, InputStream stream) throws IOException;
+ }
+
@KeepForSubclassing
public interface ZipFileSupplier {
ZipFile open() throws IOException;
@@ -83,48 +87,51 @@
this.include = include;
}
- private List<ProgramResource> readArchive() throws IOException {
- List<ProgramResource> dexResources = new ArrayList<>();
- List<ProgramResource> classResources = new ArrayList<>();
+ void readArchive(ArchiveEntryConsumer consumer) throws IOException {
try (ZipFile zipFile = supplier.open()) {
final Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
try (InputStream stream = zipFile.getInputStream(entry)) {
- String name = entry.getName();
- Origin entryOrigin = new ArchiveEntryOrigin(name, origin);
- if (include.test(name)) {
- if (ZipUtils.isDexFile(name)) {
- dexResources.add(
- ProgramResource.fromBytes(
- entryOrigin, Kind.DEX, ByteStreams.toByteArray(stream), null));
- } else if (ZipUtils.isClassFile(name)) {
- String descriptor = DescriptorUtils.guessTypeDescriptor(name);
- classResources.add(
- ProgramResource.fromBytes(
- entryOrigin,
- Kind.CF,
- ByteStreams.toByteArray(stream),
- Collections.singleton(descriptor)));
- }
- }
+ consumer.accept(new ArchiveEntryOrigin(entry.getName(), origin), stream);
}
}
} catch (ZipException e) {
throw new CompilationError("Zip error while reading archive" + e.getMessage(), e, origin);
}
- if (!dexResources.isEmpty() && !classResources.isEmpty()) {
- throw new CompilationError(
- "Cannot create android app from an archive containing both DEX and Java-bytecode content",
- origin);
- }
- return !dexResources.isEmpty() ? dexResources : classResources;
}
@Override
public Collection<ProgramResource> getProgramResources() throws ResourceException {
try {
- return readArchive();
+ List<ProgramResource> dexResources = new ArrayList<>();
+ List<ProgramResource> classResources = new ArrayList<>();
+ readArchive(
+ (entry, stream) -> {
+ String name = entry.getEntryName();
+ if (include.test(name)) {
+ if (ZipUtils.isDexFile(name)) {
+ dexResources.add(
+ ProgramResource.fromBytes(
+ entry, Kind.DEX, ByteStreams.toByteArray(stream), null));
+ } else if (ZipUtils.isClassFile(name)) {
+ String descriptor = DescriptorUtils.guessTypeDescriptor(name);
+ classResources.add(
+ ProgramResource.fromBytes(
+ entry,
+ Kind.CF,
+ ByteStreams.toByteArray(stream),
+ Collections.singleton(descriptor)));
+ }
+ }
+ });
+ if (!dexResources.isEmpty() && !classResources.isEmpty()) {
+ throw new CompilationError(
+ "Cannot create android app from an archive containing both DEX and Java-bytecode "
+ + "content",
+ origin);
+ }
+ return !dexResources.isEmpty() ? dexResources : classResources;
} catch (IOException e) {
throw new ResourceException(origin, e);
}
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
index 5980a0c..1a59ef8 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -136,6 +136,12 @@
mode = defaultCompilationMode();
}
+ // Internal constructor for testing.
+ Builder(AndroidApp app, DiagnosticsHandler diagnosticsHandler) {
+ super(AndroidApp.builder(app, new Reporter(diagnosticsHandler)));
+ mode = defaultCompilationMode();
+ }
+
/**
* Get current compilation mode.
*/
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 327d206..9f12853 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.origin.CommandLineOrigin;
@@ -159,6 +160,7 @@
options.enableMinification = false;
options.enableInlining = false;
options.enableClassInlining = false;
+ options.enableClassStaticizer = false;
options.outline.enabled = false;
DexApplication app = new ApplicationReader(inputApp, options, timing).read(executor);
@@ -191,6 +193,7 @@
options,
marker == null ? null : ImmutableList.copyOf(markers),
null,
+ GraphLense.getIdentityLense(),
NamingLens.getIdentityLens(),
null,
null)
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 8c6c02b..99b0a1e 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -237,6 +237,8 @@
internal.enableInlining = false;
assert internal.enableClassInlining;
internal.enableClassInlining = false;
+ assert internal.enableClassStaticizer;
+ internal.enableClassStaticizer = false;
assert internal.enableSwitchMapRemoval;
internal.enableSwitchMapRemoval = false;
assert internal.outline.enabled;
diff --git a/src/main/java/com/android/tools/r8/DataEntryResource.java b/src/main/java/com/android/tools/r8/DataEntryResource.java
index aa9c73f..6b8f9c5 100644
--- a/src/main/java/com/android/tools/r8/DataEntryResource.java
+++ b/src/main/java/com/android/tools/r8/DataEntryResource.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.origin.ArchiveEntryOrigin;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.origin.PathOrigin;
+import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
@@ -21,6 +22,10 @@
/** Get the bytes of the data entry resource. */
InputStream getByteStream() throws ResourceException;
+ static DataEntryResource fromBytes(byte[] bytes, String name, Origin origin) {
+ return new ByteDataEntryResource(bytes, name, origin);
+ }
+
static DataEntryResource fromFile(Path dir, Path file) {
return new LocalDataEntryResource(dir.resolve(file).toFile(),
file.toString().replace(File.separatorChar, SEPARATOR));
@@ -30,6 +35,38 @@
return new ZipDataEntryResource(zip, entry);
}
+ default DataEntryResource withName(String name) {
+ return new NestedDataEntryResource(name, null, this);
+ }
+
+ class ByteDataEntryResource implements DataEntryResource {
+
+ private final byte[] bytes;
+ private final String name;
+ private final Origin origin;
+
+ public ByteDataEntryResource(byte[] bytes, String name, Origin origin) {
+ this.bytes = bytes;
+ this.name = name;
+ this.origin = origin;
+ }
+
+ @Override
+ public InputStream getByteStream() {
+ return new ByteArrayInputStream(bytes);
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public Origin getOrigin() {
+ return origin;
+ }
+ }
+
class ZipDataEntryResource implements DataEntryResource {
private final ZipFile zip;
private final ZipEntry entry;
@@ -91,4 +128,35 @@
}
}
}
+
+ /**
+ * A resource that has the same contents as another resource, but with a different name or origin.
+ */
+ class NestedDataEntryResource implements DataEntryResource {
+
+ private final String name;
+ private final Origin origin;
+ private final DataEntryResource resource;
+
+ public NestedDataEntryResource(String name, Origin origin, DataEntryResource resource) {
+ this.name = name;
+ this.origin = origin;
+ this.resource = resource;
+ }
+
+ @Override
+ public InputStream getByteStream() throws ResourceException {
+ return resource.getByteStream();
+ }
+
+ @Override
+ public String getName() {
+ return name != null ? name : resource.getName();
+ }
+
+ @Override
+ public Origin getOrigin() {
+ return origin != null ? origin : resource.getOrigin();
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/DexFileMergerHelper.java b/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
index 5987b99..a26924e 100644
--- a/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
+++ b/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.ExceptionUtils;
@@ -91,8 +92,17 @@
List<Marker> markers = app.dexItemFactory.extractMarkers();
assert !options.hasMethodsFilter();
- new ApplicationWriter(app, options, markers, null, NamingLens.getIdentityLens(), null, null)
- .write(executor);
+ ApplicationWriter writer =
+ new ApplicationWriter(
+ app,
+ options,
+ markers,
+ null,
+ GraphLense.getIdentityLense(),
+ NamingLens.getIdentityLens(),
+ null,
+ null);
+ writer.write(executor);
options.printWarnings();
} catch (ExecutionException e) {
R8.unwrapExecutionException(e);
diff --git a/src/main/java/com/android/tools/r8/DexSplitterHelper.java b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
index 3e299f2..68dc702 100644
--- a/src/main/java/com/android/tools/r8/DexSplitterHelper.java
+++ b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexApplication.Builder;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.utils.ExceptionUtils;
@@ -96,6 +97,7 @@
options,
markers,
null,
+ GraphLense.getIdentityLense(),
NamingLens.getIdentityLens(),
null,
null,
diff --git a/src/main/java/com/android/tools/r8/DiagnosticsHandler.java b/src/main/java/com/android/tools/r8/DiagnosticsHandler.java
index 74a9a96..666e358 100644
--- a/src/main/java/com/android/tools/r8/DiagnosticsHandler.java
+++ b/src/main/java/com/android/tools/r8/DiagnosticsHandler.java
@@ -4,6 +4,7 @@
package com.android.tools.r8;
import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
/**
* A DiagnosticsHandler can be provided to customize handling of diagnostics information.
@@ -20,7 +21,11 @@
*/
default void error(Diagnostic error) {
if (error.getOrigin() != Origin.unknown()) {
- System.err.print("Error in " + error.getOrigin() + ":\n ");
+ System.err.print("Error in " + error.getOrigin());
+ if (error.getPosition() != Position.UNKNOWN) {
+ System.err.print(" at " + error.getPosition().getDescription());
+ }
+ System.err.println(":");
} else {
System.err.print("Error: ");
}
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexList.java b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
index 26097c1..fe946da 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexList.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.shaking.Enqueuer;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.shaking.MainDexListBuilder;
@@ -41,7 +42,7 @@
AppInfoWithSubtyping appInfo = new AppInfoWithSubtyping(application);
RootSet mainDexRootSet =
new RootSetBuilder(appInfo, application, options.mainDexKeepRules, options).run(executor);
- Enqueuer enqueuer = new Enqueuer(appInfo, options, true);
+ Enqueuer enqueuer = new Enqueuer(appInfo, GraphLense.getIdentityLense(), options, true);
AppInfoWithLiveness mainDexAppInfo = enqueuer.traceMainDex(mainDexRootSet, executor, timing);
// LiveTypes is the result.
Set<DexType> mainDexClasses =
diff --git a/src/main/java/com/android/tools/r8/PrintSeeds.java b/src/main/java/com/android/tools/r8/PrintSeeds.java
index 782c91d..4410d8a 100644
--- a/src/main/java/com/android/tools/r8/PrintSeeds.java
+++ b/src/main/java/com/android/tools/r8/PrintSeeds.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.dex.ApplicationReader;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.shaking.Enqueuer;
import com.android.tools.r8.shaking.RootSetBuilder;
import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
@@ -84,7 +85,7 @@
new RootSetBuilder(
appInfo, application, options.proguardConfiguration.getRules(), options)
.run(executor);
- Enqueuer enqueuer = new Enqueuer(appInfo, options, false);
+ Enqueuer enqueuer = new Enqueuer(appInfo, GraphLense.getIdentityLense(), options, false);
appInfo = enqueuer.traceApplication(rootSet, executor, timing);
RootSetBuilder.writeSeeds(
appInfo.withLiveness(),
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 805bdf7..215ced9 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -176,6 +176,7 @@
ExecutorService executorService,
DexApplication application,
String deadCode,
+ GraphLense graphLense,
NamingLens namingLens,
String proguardSeedsData,
InternalOptions options,
@@ -188,6 +189,7 @@
application,
options,
deadCode,
+ graphLense,
namingLens,
proguardSeedsData,
proguardMapSupplier)
@@ -198,6 +200,7 @@
options,
marker == null ? null : Collections.singletonList(marker),
deadCode,
+ graphLense,
namingLens,
proguardSeedsData,
proguardMapSupplier)
@@ -235,7 +238,7 @@
System.setOut(new PrintStream(ByteStreams.nullOutputStream()));
}
// TODO(b/65390962): Remove this warning once the CF backend is complete.
- if (options.isGeneratingClassFiles()) {
+ if (options.isGeneratingClassFiles() && !options.testing.suppressExperimentalCfBackendWarning) {
options.reporter.warning(new StringDiagnostic(
"R8 support for generating Java classfiles is incomplete and experimental. "
+ "Even if R8 appears to succeed, the generated output is likely incorrect."));
@@ -292,6 +295,7 @@
Enqueuer enqueuer =
new Enqueuer(
appView.getAppInfo(),
+ appView.getGraphLense(),
options,
options.forceProguardCompatibility,
compatibility,
@@ -369,7 +373,7 @@
if (options.enableClassMerging && options.enableInlining) {
timing.begin("ClassMerger");
VerticalClassMerger classMerger =
- new VerticalClassMerger(application, appViewWithLiveness, timing);
+ new VerticalClassMerger(application, appViewWithLiveness, executorService, timing);
appView.setGraphLense(classMerger.run());
timing.end();
application = application.asDirect().rewrittenWithLense(appView.getGraphLense());
@@ -380,10 +384,9 @@
.rewrittenWithLense(application.asDirect(), appView.getGraphLense()));
}
// Collect switch maps and ordinals maps.
+ appViewWithLiveness.setAppInfo(new SwitchMapCollector(appViewWithLiveness, options).run());
appViewWithLiveness.setAppInfo(
- new SwitchMapCollector(appViewWithLiveness.getAppInfo(), options).run());
- appViewWithLiveness.setAppInfo(
- new EnumOrdinalMapCollector(appViewWithLiveness.getAppInfo(), options).run());
+ new EnumOrdinalMapCollector(appViewWithLiveness, options).run());
// TODO(b/79143143): re-enable once fixed.
// graphLense = new BridgeMethodAnalysis(graphLense, appInfo.withLiveness()).run();
@@ -396,6 +399,7 @@
new IRConverter(
appView.getAppInfo(), options, timing, printer, appView.getGraphLense());
application = converter.optimize(application, executorService);
+ appView.setGraphLense(converter.getGraphLense());
} finally {
timing.end();
}
@@ -419,7 +423,8 @@
if (!options.mainDexKeepRules.isEmpty()) {
appView.setAppInfo(new AppInfoWithSubtyping(application));
- Enqueuer enqueuer = new Enqueuer(appView.getAppInfo(), options, true);
+ Enqueuer enqueuer =
+ new Enqueuer(appView.getAppInfo(), appView.getGraphLense(), options, true);
// Lets find classes which may have code executed before secondary dex files installation.
RootSet mainDexRootSet =
new RootSetBuilder(appView.getAppInfo(), application, options.mainDexKeepRules, options)
@@ -443,7 +448,11 @@
timing.begin("Post optimization code stripping");
try {
Enqueuer enqueuer =
- new Enqueuer(appView.getAppInfo(), options, options.forceProguardCompatibility);
+ new Enqueuer(
+ appView.getAppInfo(),
+ appView.getGraphLense(),
+ options,
+ options.forceProguardCompatibility);
appView.setAppInfo(enqueuer.traceApplication(rootSet, executorService, timing));
AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
@@ -485,6 +494,7 @@
ClassNameMapper classNameMapper =
LineNumberOptimizer.run(
application,
+ appView.getGraphLense(),
namingLens,
options.lineNumberOptimization == LineNumberOptimization.IDENTITY_MAPPING);
timing.end();
@@ -507,6 +517,7 @@
executorService,
application,
application.deadCode,
+ appView.getGraphLense(),
namingLens,
proguardSeedsData,
options,
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 584b06d..e3904fb 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -83,6 +83,10 @@
super(app);
}
+ private Builder(AndroidApp app, DiagnosticsHandler diagnosticsHandler) {
+ super(app, diagnosticsHandler);
+ }
+
// Internal
void internalForceProguardCompatibility() {
@@ -455,6 +459,11 @@
return new Builder(app);
}
+ // Internal builder to start from an existing AndroidApp.
+ static Builder builder(AndroidApp app, DiagnosticsHandler diagnosticsHandler) {
+ return new Builder(app, diagnosticsHandler);
+ }
+
/**
* Parse the R8 command-line.
*
@@ -571,6 +580,7 @@
// TODO(zerny): Should we support inlining in debug mode? b/62937285
internal.enableInlining = false;
internal.enableClassInlining = false;
+ internal.enableClassStaticizer = false;
// TODO(zerny): Should we support outlining in debug mode? b/62937285
internal.outline.enabled = false;
}
@@ -618,6 +628,13 @@
internal.proguardCompatibilityRulesOutput = proguardCompatibilityRulesOutput;
internal.dataResourceConsumer = internal.programConsumer.getDataResourceConsumer();
+ // Default is to remove Java assertion code as Dalvik and Art does not reliable support
+ // Java assertions. When generation class file output always keep the Java assertions code.
+ assert internal.disableAssertions;
+ if (internal.isGeneratingClassFiles()) {
+ internal.disableAssertions = false;
+ }
+
// EXPERIMENTAL flags.
assert !internal.forceProguardCompatibility;
internal.forceProguardCompatibility = forceProguardCompatibility;
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index b7fc6b3..6f9464c 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.7-dev";
+ public static final String LABEL = "1.3.14-dev";
private Version() {
}
diff --git a/src/main/java/com/android/tools/r8/bisect/Bisect.java b/src/main/java/com/android/tools/r8/bisect/Bisect.java
index 7ef43a3..60167c5 100644
--- a/src/main/java/com/android/tools/r8/bisect/Bisect.java
+++ b/src/main/java/com/android/tools/r8/bisect/Bisect.java
@@ -177,7 +177,8 @@
throws IOException, ExecutionException {
InternalOptions options = new InternalOptions();
AndroidAppConsumers compatSink = new AndroidAppConsumers(options);
- ApplicationWriter writer = new ApplicationWriter(app, options, null, null, null, null, null);
+ ApplicationWriter writer =
+ new ApplicationWriter(app, options, null, null, null, null, null, null);
writer.write(executor);
compatSink.build().writeToDirectory(output, OutputMode.DexIndexed);
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstString.java b/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
index 54ae1c6..b2114b3 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
@@ -13,7 +13,7 @@
public class CfConstString extends CfInstruction {
- private final DexString string;
+ private DexString string;
public CfConstString(DexString string) {
this.string = string;
@@ -23,6 +23,10 @@
return string;
}
+ public void setString(DexString string) {
+ this.string = string;
+ }
+
@Override
public void write(MethodVisitor visitor, NamingLens lens) {
visitor.visitLdcInsn(string.toString());
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index e1905ff..f9e95ec 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -25,6 +25,7 @@
import com.android.tools.r8.graph.DexTypeList;
import com.android.tools.r8.graph.DexValue;
import com.android.tools.r8.graph.EnclosingMethodAttribute;
+import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.graph.InnerClassAttribute;
import com.android.tools.r8.graph.ObjectToOffsetMapping;
import com.android.tools.r8.graph.ParameterAnnotationsList;
@@ -33,15 +34,18 @@
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.ExceptionUtils;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringDiagnostic;
import com.android.tools.r8.utils.ThreadUtils;
import com.google.common.collect.ObjectArrays;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
@@ -51,6 +55,7 @@
public final DexApplication application;
public final String deadCode;
+ public final GraphLense graphLense;
public final NamingLens namingLens;
public final String proguardSeedsData;
public final InternalOptions options;
@@ -120,6 +125,7 @@
InternalOptions options,
List<Marker> markers,
String deadCode,
+ GraphLense graphLense,
NamingLens namingLens,
String proguardSeedsData,
ProguardMapSupplier proguardMapSupplier) {
@@ -128,6 +134,7 @@
options,
markers,
deadCode,
+ graphLense,
namingLens,
proguardSeedsData,
proguardMapSupplier,
@@ -139,6 +146,7 @@
InternalOptions options,
List<Marker> markers,
String deadCode,
+ GraphLense graphLense,
NamingLens namingLens,
String proguardSeedsData,
ProguardMapSupplier proguardMapSupplier,
@@ -154,6 +162,7 @@
}
}
this.deadCode = deadCode;
+ this.graphLense = graphLense;
this.namingLens = namingLens;
this.proguardSeedsData = proguardSeedsData;
this.proguardMapSupplier = proguardMapSupplier;
@@ -258,7 +267,13 @@
options.reporter.failIfPendingErrors();
// Supply info to all additional resource consumers.
supplyAdditionalConsumers(
- application, namingLens, options, deadCode, proguardMapSupplier, proguardSeedsData);
+ application,
+ graphLense,
+ namingLens,
+ options,
+ deadCode,
+ proguardMapSupplier,
+ proguardSeedsData);
} finally {
application.timing.end();
}
@@ -266,6 +281,7 @@
public static void supplyAdditionalConsumers(
DexApplication application,
+ GraphLense graphLense,
NamingLens namingLens,
InternalOptions options,
String deadCode,
@@ -296,28 +312,38 @@
}
DataResourceConsumer dataResourceConsumer = options.dataResourceConsumer;
if (dataResourceConsumer != null) {
-
List<DataResourceProvider> dataResourceProviders = application.programResourceProviders
.stream()
.map(ProgramResourceProvider::getDataResourceProvider)
.filter(Objects::nonNull)
.collect(Collectors.toList());
+ ResourceAdapter resourceAdapter =
+ new ResourceAdapter(application.dexItemFactory, graphLense, namingLens, options);
+ Set<String> generatedResourceNames = new HashSet<>();
+
for (DataResourceProvider dataResourceProvider : dataResourceProviders) {
try {
- dataResourceProvider.accept(new Visitor() {
- @Override
- public void visit(DataDirectoryResource directory) {
- dataResourceConsumer.accept(directory, options.reporter);
- options.reporter.failIfPendingErrors();
- }
+ dataResourceProvider.accept(
+ new Visitor() {
+ @Override
+ public void visit(DataDirectoryResource directory) {
+ dataResourceConsumer.accept(directory, options.reporter);
+ options.reporter.failIfPendingErrors();
+ }
- @Override
- public void visit(DataEntryResource file) {
- dataResourceConsumer.accept(file, options.reporter);
- options.reporter.failIfPendingErrors();
- }
- });
+ @Override
+ public void visit(DataEntryResource file) {
+ DataEntryResource adapted = resourceAdapter.adaptIfNeeded(file);
+ if (generatedResourceNames.add(adapted.getName())) {
+ dataResourceConsumer.accept(adapted, options.reporter);
+ } else {
+ options.reporter.warning(
+ new StringDiagnostic("Resource '" + file.getName() + "' already exists."));
+ }
+ options.reporter.failIfPendingErrors();
+ }
+ });
} catch (ResourceException e) {
throw new CompilationError(e.getMessage(), e);
}
diff --git a/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java b/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java
index 519bd58..b151ef8 100644
--- a/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java
+++ b/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java
@@ -123,7 +123,7 @@
DexCode code = method.getCode().asDexCode();
// As we have rewritten the code, we now know that its highest string index that is not
// a jumbo-string is firstJumboString (actually the previous string, but we do not have that).
- method.setDexCode(new DexCode(
+ method.setCode(new DexCode(
code.registerSize,
code.incomingRegisterSize,
code.outgoingRegisterSize,
diff --git a/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java b/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java
new file mode 100644
index 0000000..673fa6e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java
@@ -0,0 +1,376 @@
+// 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.dex;
+
+import com.android.tools.r8.DataEntryResource;
+import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.ProguardPathFilter;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.ExceptionDiagnostic;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringDiagnostic;
+import com.google.common.io.ByteStreams;
+import it.unimi.dsi.fastutil.ints.IntArrayList;
+import it.unimi.dsi.fastutil.ints.IntStack;
+import java.io.InputStream;
+import java.nio.charset.Charset;
+
+public class ResourceAdapter {
+
+ private final DexItemFactory dexItemFactory;
+ private final GraphLense graphLense;
+ private final NamingLens namingLense;
+ private final InternalOptions options;
+
+ public ResourceAdapter(
+ DexItemFactory dexItemFactory,
+ GraphLense graphLense,
+ NamingLens namingLense,
+ InternalOptions options) {
+ this.dexItemFactory = dexItemFactory;
+ this.graphLense = graphLense;
+ this.namingLense = namingLense;
+ this.options = options;
+ }
+
+ public DataEntryResource adaptIfNeeded(DataEntryResource file) {
+ // Adapt name, if needed.
+ ProguardPathFilter adaptResourceFileNamesFilter =
+ options.proguardConfiguration.getAdaptResourceFilenames();
+ String name =
+ adaptResourceFileNamesFilter.isEnabled()
+ && !file.getName().toLowerCase().endsWith(FileUtils.CLASS_EXTENSION)
+ && adaptResourceFileNamesFilter.matches(file.getName())
+ ? adaptFilename(file)
+ : file.getName();
+ assert name != null;
+ // Adapt contents, if needed.
+ ProguardPathFilter adaptResourceFileContentsFilter =
+ options.proguardConfiguration.getAdaptResourceFileContents();
+ byte[] contents =
+ adaptResourceFileContentsFilter.isEnabled()
+ && !file.getName().toLowerCase().endsWith(FileUtils.CLASS_EXTENSION)
+ && adaptResourceFileContentsFilter.matches(file.getName())
+ ? adaptFileContents(file)
+ : null;
+ // Return a new resource if the name or contents changed. Otherwise return the original
+ // resource as it was.
+ if (contents != null) {
+ // File contents was adapted. Return a new resource that has the new contents, and a new name,
+ // if the filename was adapted.
+ return DataEntryResource.fromBytes(contents, name, file.getOrigin());
+ }
+ if (!name.equals(file.getName())) {
+ // File contents was not adapted, but filename was.
+ return file.withName(name);
+ }
+ // Neither file contents nor filename was adapted.
+ return file;
+ }
+
+ private String adaptFilename(DataEntryResource file) {
+ FilenameAdapter adapter = new FilenameAdapter(file.getName());
+ if (adapter.run()) {
+ return adapter.getResult();
+ }
+ return file.getName();
+ }
+
+ // According to the Proguard documentation, the resource files should be parsed and written using
+ // the platform's default character set.
+ private byte[] adaptFileContents(DataEntryResource file) {
+ try (InputStream in = file.getByteStream()) {
+ byte[] bytes = ByteStreams.toByteArray(in);
+ String contents = new String(bytes, Charset.defaultCharset());
+
+ FileContentsAdapter adapter = new FileContentsAdapter(contents);
+ if (adapter.run()) {
+ return adapter.getResult().getBytes(Charset.defaultCharset());
+ }
+ } catch (ResourceException e) {
+ options.reporter.error(
+ new StringDiagnostic("Failed to open input: " + e.getMessage(), file.getOrigin()));
+ } catch (Exception e) {
+ options.reporter.error(new ExceptionDiagnostic(e, file.getOrigin()));
+ }
+ // Return null to signal that the file contents did not change. Otherwise we would have to copy
+ // the original file for no reason.
+ return null;
+ }
+
+ private abstract class StringAdapter {
+
+ protected final String contents;
+ private final StringBuilder result = new StringBuilder();
+
+ // If any type names in `contents` have been updated. If this flag is still true in the end,
+ // then we can simply use the resource as it was.
+ private boolean changed = false;
+ private int outputFrom = 0;
+ private int position = 0;
+
+ // When renaming Java type names, the adapter always looks for the longest name to rewrite.
+ // For example, if there is a resource with the name "foo/bar/C$X$Y.txt", then the adapter will
+ // check if there is a renaming for the type "foo.bar.C$X$Y". If there is no such renaming, then
+ // -adaptresourcefilenames works in such a way that "foo/bar/C$X" should be rewritten if there
+ // is a renaming for the type "foo.bar.C$X". Therefore, when scanning forwards to read the
+ // substring "foo/bar/C$X$Y", this adapter records the positions of the two '$' characters in
+ // the stack `prefixEndPositionsExclusive`, such that it can easily backtrack to the previously
+ // valid, but shorter Java type name.
+ //
+ // Note that there is no backtracking for -adaptresourcefilecontents.
+ private final IntStack prefixEndPositionsExclusive;
+
+ public StringAdapter(String contents) {
+ this.contents = contents;
+ this.prefixEndPositionsExclusive = allowRenamingOfPrefixes() ? new IntArrayList() : null;
+ }
+
+ public boolean run() {
+ do {
+ handleMisc();
+ handleJavaType();
+ } while (!eof());
+ if (changed) {
+ // At least one type was renamed. We need to flush all characters in `contents` that follow
+ // the last type that was renamed.
+ outputRangeFromInput(outputFrom, contents.length());
+ } else {
+ // No types were renamed. In this case the adapter should simply have scanned through
+ // `contents`, without outputting anything to `result`.
+ assert outputFrom == 0;
+ assert result.toString().isEmpty();
+ }
+ return changed;
+ }
+
+ public String getResult() {
+ assert changed;
+ return result.toString();
+ }
+
+ // Forwards the cursor until the current character is a Java identifier part.
+ private void handleMisc() {
+ while (!eof() && !Character.isJavaIdentifierPart(contents.charAt(position))) {
+ position++;
+ }
+ }
+
+ // Reads a Java type from the current position in `contents`, and then checks if the given
+ // type has been renamed.
+ private void handleJavaType() {
+ if (eof()) {
+ return;
+ }
+
+ assert !allowRenamingOfPrefixes() || prefixEndPositionsExclusive.isEmpty();
+
+ assert Character.isJavaIdentifierPart(contents.charAt(position));
+ int start = position++;
+ while (!eof()) {
+ char currentChar = contents.charAt(position);
+ if (Character.isJavaIdentifierPart(currentChar)) {
+ if (allowRenamingOfPrefixes()
+ && shouldRecordPrefix(currentChar)
+ && isRenamingCandidate(start, position)) {
+ prefixEndPositionsExclusive.push(position);
+ }
+ position++;
+ continue;
+ }
+ if (currentChar == getClassNameSeparator()
+ && !eof(position + 1)
+ && Character.isJavaIdentifierPart(contents.charAt(position + 1))) {
+ if (allowRenamingOfPrefixes()
+ && shouldRecordPrefix(currentChar)
+ && isRenamingCandidate(start, position)) {
+ prefixEndPositionsExclusive.push(position);
+ }
+ // Consume the dot and the Java identifier part that follows the dot.
+ position += 2;
+ continue;
+ }
+
+ // Not a valid extension of the type name.
+ break;
+ }
+
+ boolean renamingSucceeded =
+ isRenamingCandidate(start, position) && renameJavaTypeInRange(start, position);
+ if (!renamingSucceeded && allowRenamingOfPrefixes()) {
+ while (!prefixEndPositionsExclusive.isEmpty() && !renamingSucceeded) {
+ int prefixEndExclusive = prefixEndPositionsExclusive.popInt();
+ assert isRenamingCandidate(start, prefixEndExclusive);
+ renamingSucceeded = handlePrefix(start, prefixEndExclusive);
+ }
+ }
+
+ if (allowRenamingOfPrefixes()) {
+ while (!prefixEndPositionsExclusive.isEmpty()) {
+ prefixEndPositionsExclusive.popInt();
+ }
+ }
+ }
+
+ // Returns true if the Java type in the range [from; toExclusive[ was renamed.
+ protected boolean renameJavaTypeInRange(int from, int toExclusive) {
+ String javaType = contents.substring(from, toExclusive);
+ if (getClassNameSeparator() != '.') {
+ javaType = javaType.replace(getClassNameSeparator(), '.');
+ }
+ DexString descriptor =
+ dexItemFactory.lookupString(
+ DescriptorUtils.javaTypeToDescriptorIgnorePrimitives(javaType));
+ DexType dexType = descriptor != null ? dexItemFactory.lookupType(descriptor) : null;
+ if (dexType != null) {
+ DexString renamedDescriptor = namingLense.lookupDescriptor(graphLense.lookupType(dexType));
+ if (!descriptor.equals(renamedDescriptor)) {
+ String renamedJavaType =
+ DescriptorUtils.descriptorToJavaType(renamedDescriptor.toSourceString());
+ // Need to flush all changes up to and excluding 'from', and then output the renamed
+ // type.
+ outputRangeFromInput(outputFrom, from);
+ outputJavaType(
+ getClassNameSeparator() != '.'
+ ? renamedJavaType.replace('.', getClassNameSeparator())
+ : renamedJavaType);
+ outputFrom = toExclusive;
+ changed = true;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // Returns true if the Java package in the range [from; toExclusive[ was renamed.
+ protected boolean renameJavaPackageInRange(int from, int toExclusive) {
+ String javaPackage = contents.substring(from, toExclusive);
+ if (getClassNameSeparator() != '/') {
+ javaPackage = javaPackage.replace(getClassNameSeparator(), '/');
+ }
+ String minifiedJavaPackage = namingLense.lookupPackageName(javaPackage);
+ if (!javaPackage.equals(minifiedJavaPackage)) {
+ outputRangeFromInput(outputFrom, from);
+ outputJavaType(
+ getClassNameSeparator() != '/'
+ ? minifiedJavaPackage.replace('/', getClassNameSeparator())
+ : minifiedJavaPackage);
+ outputFrom = toExclusive;
+ changed = true;
+ return true;
+ }
+ return false;
+ }
+
+ protected abstract char getClassNameSeparator();
+
+ protected abstract boolean allowRenamingOfPrefixes();
+
+ protected abstract boolean shouldRecordPrefix(char c);
+
+ protected abstract boolean handlePrefix(int from, int toExclusive);
+
+ protected abstract boolean isRenamingCandidate(int from, int toExclusive);
+
+ private void outputRangeFromInput(int from, int toExclusive) {
+ if (from < toExclusive) {
+ result.append(contents.substring(from, toExclusive));
+ }
+ }
+
+ private void outputJavaType(String s) {
+ result.append(s);
+ }
+
+ protected boolean eof() {
+ return eof(position);
+ }
+
+ protected boolean eof(int position) {
+ return position == contents.length();
+ }
+ }
+
+ private class FileContentsAdapter extends StringAdapter {
+
+ public FileContentsAdapter(String fileContents) {
+ super(fileContents);
+ }
+
+ @Override
+ public char getClassNameSeparator() {
+ return '.';
+ }
+
+ @Override
+ public boolean allowRenamingOfPrefixes() {
+ return false;
+ }
+
+ @Override
+ public boolean shouldRecordPrefix(char c) {
+ throw new Unreachable();
+ }
+
+ @Override
+ protected boolean handlePrefix(int from, int toExclusive) {
+ throw new Unreachable();
+ }
+
+ @Override
+ public boolean isRenamingCandidate(int from, int toExclusive) {
+ // If the Java type starts with '-' or '.', it should not be renamed.
+ return (from <= 0 || !isDashOrDot(contents.charAt(from - 1)))
+ && (eof(toExclusive) || !isDashOrDot(contents.charAt(toExclusive)));
+ }
+
+ private boolean isDashOrDot(char c) {
+ return c == '.' || c == '-';
+ }
+ }
+
+ private class FilenameAdapter extends StringAdapter {
+
+ public FilenameAdapter(String filename) {
+ super(filename);
+ }
+
+ @Override
+ public char getClassNameSeparator() {
+ return '/';
+ }
+
+ @Override
+ public boolean allowRenamingOfPrefixes() {
+ return true;
+ }
+
+ @Override
+ public boolean shouldRecordPrefix(char c) {
+ return !Character.isLetterOrDigit(c);
+ }
+
+ @Override
+ protected boolean handlePrefix(int from, int toExclusive) {
+ assert !eof(toExclusive);
+ if (contents.charAt(toExclusive) == '/') {
+ return renameJavaPackageInRange(from, toExclusive);
+ }
+ return renameJavaTypeInRange(from, toExclusive);
+ }
+
+ @Override
+ public boolean isRenamingCandidate(int from, int toExclusive) {
+ return from == 0 && !eof(toExclusive);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java b/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java
index b0a5a3c..1956e8e 100644
--- a/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java
+++ b/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java
@@ -10,19 +10,29 @@
import com.android.tools.r8.DexSplitterHelper;
import com.android.tools.r8.DiagnosticsHandler;
import com.android.tools.r8.Keep;
+import com.android.tools.r8.origin.PathOrigin;
import com.android.tools.r8.utils.AbortException;
import com.android.tools.r8.utils.DefaultDiagnosticsHandler;
+import com.android.tools.r8.utils.ExceptionDiagnostic;
import com.android.tools.r8.utils.ExceptionUtils;
import com.android.tools.r8.utils.FeatureClassMapping;
import com.android.tools.r8.utils.FeatureClassMapping.FeatureMappingException;
import com.android.tools.r8.utils.OptionsParsing;
import com.android.tools.r8.utils.OptionsParsing.ParseContext;
import com.android.tools.r8.utils.StringDiagnostic;
+import com.android.tools.r8.utils.ZipUtils;
import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
+import java.util.Enumeration;
import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
@Keep
public final class DexSplitter {
@@ -61,7 +71,18 @@
}
return featureName;
}
+ }
+ private static class ZipFileOrigin extends PathOrigin {
+
+ public ZipFileOrigin(Path path) {
+ super(path);
+ }
+
+ @Override
+ public String part() {
+ return "splitting of file '" + super.part() + "'";
+ }
}
@Keep
@@ -73,6 +94,7 @@
private String output = DEFAULT_OUTPUT_DIR;
private String featureSplitMapping;
private String proguardMap;
+ private boolean splitNonClassResources = false;
public DiagnosticsHandler getDiagnosticsHandler() {
return diagnosticsHandler;
@@ -126,6 +148,10 @@
featureJars.add(new FeatureJar(jar, outputName));
}
+ public void setSplitNonClassResources(boolean value) {
+ splitNonClassResources = value;
+ }
+
public ImmutableList<String> getInputArchives() {
return ImmutableList.copyOf(inputArchives);
}
@@ -193,6 +219,11 @@
options.setFeatureSplitMapping(featureSplit);
continue;
}
+ Boolean b = OptionsParsing.tryParseBoolean(context, "--split-non-class-resources");
+ if (b != null) {
+ options.setSplitNonClassResources(b);
+ continue;
+ }
throw new RuntimeException(String.format("Unknown options: '%s'.", context.head()));
}
return options;
@@ -246,6 +277,39 @@
DexSplitterHelper.run(
builder.build(), featureClassMapping, options.getOutput(), options.getProguardMap());
+
+ if (options.splitNonClassResources) {
+ splitNonClassResources(options, featureClassMapping);
+ }
+ }
+
+ private static void splitNonClassResources(Options options,
+ FeatureClassMapping featureClassMapping) {
+ for (String s : options.inputArchives) {
+ try (ZipFile zipFile = new ZipFile(s, StandardCharsets.UTF_8)) {
+ Enumeration<? extends ZipEntry> entries = zipFile.entries();
+ while (entries.hasMoreElements()) {
+ ZipEntry entry = entries.nextElement();
+ String name = entry.getName();
+ if (!ZipUtils.isDexFile(name) && !ZipUtils.isClassFile(name)) {
+ String feature = featureClassMapping.featureForNonClass(name);
+ Path outputDir = Paths.get(options.getOutput()).resolve(feature);
+ try (InputStream stream = zipFile.getInputStream(entry)) {
+ Path outputFile = outputDir.resolve(name);
+ Path parent = outputFile.getParent();
+ if (parent != null) {
+ Files.createDirectories(parent);
+ }
+ Files.copy(stream, outputFile);
+ }
+ }
+ }
+ } catch (IOException e) {
+ options.getDiagnosticsHandler().error(
+ new ExceptionDiagnostic(e, new ZipFileOrigin(Paths.get(s))));
+ throw new AbortException();
+ }
+ }
}
public static void main(String[] args) {
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index 55c4134..c29453a 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -489,17 +489,22 @@
}
public boolean canTriggerStaticInitializer(DexType type, boolean ignoreTypeItself) {
+ DexClass clazz = definitionFor(type);
+ assert clazz != null;
+ return canTriggerStaticInitializer(clazz, ignoreTypeItself);
+ }
+
+ public boolean canTriggerStaticInitializer(DexClass clazz, boolean ignoreTypeItself) {
Set<DexType> knownInterfaces = Sets.newIdentityHashSet();
// Process superclass chain.
- DexType clazz = type;
- while (clazz != null && clazz != dexItemFactory.objectType) {
- DexClass definition = definitionFor(clazz);
- if (canTriggerStaticInitializer(definition) && (!ignoreTypeItself || clazz != type)) {
+ DexClass current = clazz;
+ while (current != null && current.type != dexItemFactory.objectType) {
+ if (canTriggerStaticInitializer(current) && (!ignoreTypeItself || current != clazz)) {
return true;
}
- knownInterfaces.addAll(Arrays.asList(definition.interfaces.values));
- clazz = definition.superType;
+ knownInterfaces.addAll(Arrays.asList(current.interfaces.values));
+ current = current.superType != null ? definitionFor(current.superType) : null;
}
// Process interfaces.
@@ -523,7 +528,7 @@
return false;
}
- private static boolean canTriggerStaticInitializer(DexClass clazz) {
+ public static boolean canTriggerStaticInitializer(DexClass clazz) {
// Assume it *may* trigger if we didn't find the definition.
return clazz == null || clazz.hasClassInitializer();
}
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index 776cc42..d588ab5 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -201,27 +201,35 @@
@Override
public IRCode buildIR(
- DexEncodedMethod encodedMethod, AppInfo appInfo, InternalOptions options, Origin origin) {
- return internalBuild(encodedMethod, appInfo, options, null, null, origin);
+ DexEncodedMethod encodedMethod,
+ AppInfo appInfo,
+ GraphLense graphLense,
+ InternalOptions options,
+ Origin origin) {
+ assert getOwner() == encodedMethod;
+ return internalBuild(encodedMethod, appInfo, graphLense, options, null, null, origin);
}
@Override
public IRCode buildInliningIR(
DexEncodedMethod encodedMethod,
AppInfo appInfo,
+ GraphLense graphLense,
InternalOptions options,
ValueNumberGenerator valueNumberGenerator,
Position callerPosition,
Origin origin) {
+ assert getOwner() == encodedMethod;
assert valueNumberGenerator != null;
assert callerPosition != null;
return internalBuild(
- encodedMethod, appInfo, options, valueNumberGenerator, callerPosition, origin);
+ encodedMethod, appInfo, graphLense, options, valueNumberGenerator, callerPosition, origin);
}
private IRCode internalBuild(
DexEncodedMethod encodedMethod,
AppInfo appInfo,
+ GraphLense graphLense,
InternalOptions options,
ValueNumberGenerator generator,
Position callerPosition,
@@ -235,14 +243,11 @@
new CfSourceCode(
this,
encodedMethod,
+ graphLense.getOriginalMethodSignature(encodedMethod.method),
callerPosition,
origin,
options.lineNumberOptimization == LineNumberOptimization.ON);
- IRBuilder builder =
- (generator == null)
- ? new IRBuilder(encodedMethod, appInfo, source, options)
- : new IRBuilder(encodedMethod, appInfo, source, options, generator);
- return builder.build();
+ return new IRBuilder(encodedMethod, appInfo, source, options, generator).build();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/graph/ClassKind.java b/src/main/java/com/android/tools/r8/graph/ClassKind.java
index 94b2076..92ae40e 100644
--- a/src/main/java/com/android/tools/r8/graph/ClassKind.java
+++ b/src/main/java/com/android/tools/r8/graph/ClassKind.java
@@ -1,3 +1,7 @@
+// 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.ProgramResource;
diff --git a/src/main/java/com/android/tools/r8/graph/Code.java b/src/main/java/com/android/tools/r8/graph/Code.java
index 60a5ef5..187756a 100644
--- a/src/main/java/com/android/tools/r8/graph/Code.java
+++ b/src/main/java/com/android/tools/r8/graph/Code.java
@@ -16,12 +16,29 @@
public abstract class Code extends CachedHashValueDexItem {
+ private DexEncodedMethod owner;
+
+ public void setOwner(DexEncodedMethod encodedMethod) {
+ // When this Code is un/linked to DexEncodedMethod, the ownership should be updated accordingly.
+ owner = encodedMethod;
+ }
+
+ public DexEncodedMethod getOwner() {
+ // build*IR() will check if the current Code belongs to the given DexEncodedMethod.
+ return owner;
+ }
+
public abstract IRCode buildIR(
- DexEncodedMethod encodedMethod, AppInfo appInfo, InternalOptions options, Origin origin);
+ DexEncodedMethod encodedMethod,
+ AppInfo appInfo,
+ GraphLense graphLense,
+ InternalOptions options,
+ Origin origin);
public IRCode buildInliningIR(
DexEncodedMethod encodedMethod,
AppInfo appInfo,
+ GraphLense graphLense,
InternalOptions options,
ValueNumberGenerator valueNumberGenerator,
Position callerPosition,
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 64c85f3..fc1ef7b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -8,7 +8,6 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.kotlin.KotlinInfo;
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;
@@ -138,16 +137,6 @@
}
}
- public <E extends Throwable> void forEachMethodThrowing(
- ThrowingConsumer<DexEncodedMethod, E> consumer) throws E {
- for (DexEncodedMethod method : directMethods()) {
- consumer.accept(method);
- }
- for (DexEncodedMethod method : virtualMethods()) {
- consumer.accept(method);
- }
- }
-
public DexEncodedMethod[] allMethodsSorted() {
int vLen = virtualMethods.length;
int dLen = directMethods.length;
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index b1b5f8a..d5f8001 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -62,6 +62,17 @@
hashCode(); // Cache the hash code eagerly.
}
+ public DexCode withoutThisParameter() {
+ // Note that we assume the original code has a register associated with 'this'
+ // argument of the (former) instance method. We also assume (but do not check)
+ // that 'this' register is never used, so when we decrease incoming register size
+ // by 1, it becomes just a regular register which is never used, and thus will be
+ // gone when we build an IR from this code. Rebuilding IR for methods 'staticized'
+ // this way is highly recommended to improve register allocation.
+ return new DexCode(registerSize, incomingRegisterSize - 1, outgoingRegisterSize,
+ instructions, tries, handlers, debugInfoWithoutFirstParameter(), highestSortingString);
+ }
+
@Override
public boolean isDexCode() {
return true;
@@ -107,6 +118,19 @@
return new DexDebugInfo(debugInfo.startLine, newParameters, debugInfo.events);
}
+ public DexDebugInfo debugInfoWithoutFirstParameter() {
+ if (debugInfo == null) {
+ return null;
+ }
+ DexString[] parameters = debugInfo.parameters;
+ if(parameters.length == 0) {
+ return debugInfo;
+ }
+ DexString[] newParameters = new DexString[parameters.length - 1];
+ System.arraycopy(parameters, 1, newParameters, 0, parameters.length - 1);
+ return new DexDebugInfo(debugInfo.startLine, newParameters, debugInfo.events);
+ }
+
public int codeSizeInBytes() {
Instruction last = instructions[instructions.length - 1];
return last.getOffset() + last.getSize();
@@ -164,10 +188,19 @@
@Override
public IRCode buildIR(
- DexEncodedMethod encodedMethod, AppInfo appInfo, InternalOptions options, Origin origin) {
+ DexEncodedMethod encodedMethod,
+ AppInfo appInfo,
+ GraphLense graphLense,
+ InternalOptions options,
+ Origin origin) {
+ assert getOwner() == encodedMethod;
DexSourceCode source =
new DexSourceCode(
- this, encodedMethod, null, options.lineNumberOptimization == LineNumberOptimization.ON);
+ this,
+ encodedMethod,
+ graphLense.getOriginalMethodSignature(encodedMethod.method),
+ null,
+ options.lineNumberOptimization == LineNumberOptimization.ON);
IRBuilder builder = new IRBuilder(encodedMethod, appInfo, source, options);
return builder.build();
}
@@ -176,14 +209,17 @@
public IRCode buildInliningIR(
DexEncodedMethod encodedMethod,
AppInfo appInfo,
+ GraphLense graphLense,
InternalOptions options,
ValueNumberGenerator valueNumberGenerator,
Position callerPosition,
Origin origin) {
+ assert getOwner() == encodedMethod;
DexSourceCode source =
new DexSourceCode(
this,
encodedMethod,
+ graphLense.getOriginalMethodSignature(encodedMethod.method),
callerPosition,
options.lineNumberOptimization == LineNumberOptimization.ON);
IRBuilder builder =
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugPositionState.java b/src/main/java/com/android/tools/r8/graph/DexDebugPositionState.java
index a02e546..e17446d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugPositionState.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugPositionState.java
@@ -1,3 +1,7 @@
+// 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.graph.DexDebugEvent.AdvanceLine;
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 3586d6d..c540d24 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -24,6 +24,7 @@
import com.android.tools.r8.dex.IndexedItemCollection;
import com.android.tools.r8.dex.JumboStringRewriter;
import com.android.tools.r8.dex.MixedSectionCollection;
+import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfo.ResolutionResult;
import com.android.tools.r8.graph.ParameterUsagesInfo.ParameterUsage;
import com.android.tools.r8.ir.code.IRCode;
@@ -32,7 +33,7 @@
import com.android.tools.r8.ir.code.ValueNumberGenerator;
import com.android.tools.r8.ir.code.ValueType;
import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.Inliner.Reason;
import com.android.tools.r8.ir.regalloc.RegisterAllocator;
import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode;
@@ -57,6 +58,7 @@
* <p>
* We also use this enum to encode under what constraints a method may be inlined.
*/
+ // TODO(b/111080693): Need to extend this to a state with the context.
public enum CompilationState {
/**
* Has not been processed, yet.
@@ -72,7 +74,7 @@
*/
PROCESSED_INLINING_CANDIDATE_ANY,
/**
- * Code also contains instructions that access protected entities that reside in a differnt
+ * Code also contains instructions that access protected entities that reside in a different
* package and hence require subclass relationship to be visible.
*/
PROCESSED_INLINING_CANDIDATE_SUBCLASS,
@@ -96,6 +98,8 @@
public DexAnnotationSet annotations;
public ParameterAnnotationsList parameterAnnotationsList;
private Code code;
+ // TODO(b/111080693): towards finer-grained inlining constraints,
+ // we need to maintain a set of states with (potentially different) contexts.
private CompilationState compilationState = CompilationState.NOT_PROCESSED;
private OptimizationInfo optimizationInfo = DefaultOptimizationInfo.DEFAULT;
private int classFileVersion = -1;
@@ -112,6 +116,7 @@
this.parameterAnnotationsList = parameterAnnotationsList;
this.code = code;
assert code == null || !accessFlags.isAbstract();
+ setCodeOwnership();
}
public DexEncodedMethod(
@@ -208,6 +213,7 @@
assert isInliningCandidate(containerType, Reason.SIMPLE, appInfo);
return true;
}
+ // TODO(b/111080693): inlining candidate should satisfy all states if multiple states are there.
switch (compilationState) {
case PROCESSED_INLINING_CANDIDATE_ANY:
return true;
@@ -222,9 +228,9 @@
}
}
- public boolean markProcessed(Constraint state) {
+ public boolean markProcessed(ConstraintWithTarget state) {
CompilationState prevCompilationState = compilationState;
- switch (state) {
+ switch (state.constraint) {
case ALWAYS:
compilationState = PROCESSED_INLINING_CANDIDATE_ANY;
break;
@@ -248,27 +254,32 @@
compilationState = CompilationState.NOT_PROCESSED;
}
- public IRCode buildIR(AppInfo appInfo, InternalOptions options, Origin origin) {
- return code == null ? null : code.buildIR(this, appInfo, options, origin);
+ public IRCode buildIR(
+ AppInfo appInfo, GraphLense graphLense, InternalOptions options, Origin origin) {
+ return code == null ? null : code.buildIR(this, appInfo, graphLense, options, origin);
}
public IRCode buildInliningIRForTesting(
InternalOptions options, ValueNumberGenerator valueNumberGenerator) {
- return buildInliningIR(null, options, valueNumberGenerator, null, Origin.unknown());
+ return buildInliningIR(
+ null, GraphLense.getIdentityLense(), options, valueNumberGenerator, null, Origin.unknown());
}
public IRCode buildInliningIR(
AppInfo appInfo,
+ GraphLense graphLense,
InternalOptions options,
ValueNumberGenerator valueNumberGenerator,
Position callerPosition,
Origin origin) {
return code.buildInliningIR(
- this, appInfo, options, valueNumberGenerator, callerPosition, origin);
+ this, appInfo, graphLense, options, valueNumberGenerator, callerPosition, origin);
}
public void setCode(Code code) {
+ voidCodeOwnership();
this.code = code;
+ setCodeOwnership();
}
public void setCode(
@@ -276,7 +287,7 @@
RegisterAllocator registerAllocator,
InternalOptions options) {
final DexBuilder builder = new DexBuilder(ir, registerAllocator, options);
- code = builder.build(method.getArity());
+ setCode(builder.build(method.getArity()));
}
@Override
@@ -308,12 +319,21 @@
return code;
}
- public void setDexCode(DexCode code) {
- this.code = code;
+ public void removeCode() {
+ voidCodeOwnership();
+ code = null;
}
- public void removeCode() {
- code = null;
+ private void setCodeOwnership() {
+ if (code != null) {
+ code.setOwner(this);
+ }
+ }
+
+ private void voidCodeOwnership() {
+ if (code != null) {
+ code.setOwner(null);
+ }
}
public boolean hasDebugPositions() {
@@ -381,6 +401,7 @@
public DexEncodedMethod toAbstractMethod() {
accessFlags.setAbstract();
+ voidCodeOwnership();
this.code = null;
return this;
}
@@ -534,6 +555,14 @@
return builder.build();
}
+ public DexEncodedMethod toStaticMethodWithoutThis() {
+ assert !accessFlags.isStatic();
+ Builder builder = builder(this);
+ builder.setStatic();
+ builder.withoutThisParameter();
+ return builder.build();
+ }
+
/**
* Rewrites the code in this method to have JumboString bytecode if required by mapping.
* <p>
@@ -634,6 +663,7 @@
// class inliner, null value indicates that the method is not eligible.
private ClassInlinerEligibility classInlinerEligibility = null;
private TrivialInitializer trivialInitializerInfo = null;
+ private boolean initializerEnablingJavaAssertions = false;
private ParameterUsagesInfo parametersUsages = null;
private BitSet kotlinNotNullParamHints = null;
@@ -650,6 +680,7 @@
forceInline = template.forceInline;
useIdentifierNameString = template.useIdentifierNameString;
checksNullReceiverBeforeAnySideEffect = template.checksNullReceiverBeforeAnySideEffect;
+ trivialInitializerInfo = template.trivialInitializerInfo;
}
public void setParameterUsages(ParameterUsagesInfo parametersUsages) {
@@ -705,6 +736,14 @@
return this.trivialInitializerInfo;
}
+ private void setInitializerEnablingJavaAssertions() {
+ this.initializerEnablingJavaAssertions = true;
+ }
+
+ public boolean isInitializerEnablingJavaAssertions() {
+ return initializerEnablingJavaAssertions;
+ }
+
public long getReturnedConstant() {
assert returnsConstant();
return returnedConstant;
@@ -754,6 +793,10 @@
forceInline = true;
}
+ private void unsetForceInline() {
+ forceInline = false;
+ }
+
private void markPublicized() {
publicized = true;
}
@@ -831,10 +874,18 @@
ensureMutableOI().setTrivialInitializer(info);
}
+ synchronized public void setInitializerEnablingJavaAssertions() {
+ ensureMutableOI().setInitializerEnablingJavaAssertions();
+ }
+
synchronized public void markForceInline() {
ensureMutableOI().markForceInline();
}
+ public synchronized void unsetForceInline() {
+ ensureMutableOI().unsetForceInline();
+ }
+
synchronized public void markPublicized() {
ensureMutableOI().markPublicized();
}
@@ -900,6 +951,19 @@
this.method = method;
}
+ public void setStatic() {
+ this.accessFlags.setStatic();
+ }
+
+ public void withoutThisParameter() {
+ assert code != null;
+ if (code.isDexCode()) {
+ code = code.asDexCode().withoutThisParameter();
+ } else {
+ throw new Unreachable("Code " + code.getClass().getSimpleName() + " is not supported.");
+ }
+ }
+
public void setCode(Code code) {
this.code = code;
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexField.java b/src/main/java/com/android/tools/r8/graph/DexField.java
index 98c523d..dcab5ae 100644
--- a/src/main/java/com/android/tools/r8/graph/DexField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexField.java
@@ -117,6 +117,10 @@
return clazz;
}
+ public String qualifiedName() {
+ return clazz + "." + name;
+ }
+
@Override
public String toSmaliString() {
return clazz.toSmaliString() + "->" + name + ":" + type.toSmaliString();
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 91b1dc7..3f02009 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -573,6 +573,10 @@
return canonicalize(strings, new DexString(source));
}
+ public DexString lookupString(String source) {
+ return strings.get(new DexString(source));
+ }
+
// TODO(b/67934123) Unify into one method,
public DexItemBasedString createItemBasedString(DexType type) {
assert !sorted;
@@ -635,7 +639,7 @@
return createType(createString(descriptor));
}
- synchronized public DexType lookupType(DexString descriptor) {
+ public DexType lookupType(DexString descriptor) {
return types.get(descriptor);
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index 02d2898..006da92 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -118,6 +118,10 @@
return this == other || isStrictSubtypeOf(other, appInfo);
}
+ public boolean hasSubtypes() {
+ return !directSubtypes.isEmpty();
+ }
+
public boolean isStrictSubtypeOf(DexType other, AppInfo appInfo) {
if (this == other) {
return false;
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLense.java b/src/main/java/com/android/tools/r8/graph/GraphLense.java
index 8de4a6e..9b90df2 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLense.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.graph;
import com.android.tools.r8.ir.code.Invoke.Type;
+import com.google.common.collect.BiMap;
import com.google.common.collect.ImmutableSet;
import java.util.HashSet;
import java.util.IdentityHashMap;
@@ -78,7 +79,8 @@
if (typeMap.isEmpty() && methodMap.isEmpty() && fieldMap.isEmpty()) {
return previousLense;
}
- return new NestedGraphLense(typeMap, methodMap, fieldMap, previousLense, dexItemFactory);
+ return new NestedGraphLense(
+ typeMap, methodMap, fieldMap, null, null, previousLense, dexItemFactory);
}
}
@@ -87,6 +89,29 @@
return new Builder();
}
+ public abstract DexField getOriginalFieldSignature(DexField field);
+
+ public abstract DexMethod getOriginalMethodSignature(DexMethod method);
+
+ public abstract DexField getRenamedFieldSignature(DexField originalField);
+
+ public abstract DexMethod getRenamedMethodSignature(DexMethod originalMethod);
+
+ public final DexEncodedMethod mapDexEncodedMethod(
+ AppInfo appInfo, DexEncodedMethod originalEncodedMethod) {
+ DexMethod newMethod = getRenamedMethodSignature(originalEncodedMethod.method);
+ if (newMethod != originalEncodedMethod.method) {
+ // We can't directly use AppInfo#definitionFor(DexMethod) since definitions may not be
+ // updated either yet.
+ DexClass newHolder = appInfo.definitionFor(newMethod.holder);
+ assert newHolder != null;
+ DexEncodedMethod newEncodedMethod = newHolder.lookupMethod(newMethod);
+ assert newEncodedMethod != null;
+ return newEncodedMethod;
+ }
+ return originalEncodedMethod;
+ }
+
public abstract DexType lookupType(DexType type);
// This overload can be used when the graph lense is known to be context insensitive.
@@ -134,7 +159,7 @@
return this instanceof IdentityGraphLense;
}
- public boolean assertNotModified(Iterable<DexItem> items) {
+ public <T extends DexItem> boolean assertNotModified(Iterable<T> items) {
for (DexItem item : items) {
if (item instanceof DexClass) {
DexType type = ((DexClass) item).type;
@@ -156,6 +181,26 @@
private static class IdentityGraphLense extends GraphLense {
@Override
+ public DexField getOriginalFieldSignature(DexField field) {
+ return field;
+ }
+
+ @Override
+ public DexMethod getOriginalMethodSignature(DexMethod method) {
+ return method;
+ }
+
+ @Override
+ public DexField getRenamedFieldSignature(DexField originalField) {
+ return originalField;
+ }
+
+ @Override
+ public DexMethod getRenamedMethodSignature(DexMethod originalMethod) {
+ return originalMethod;
+ }
+
+ @Override
public DexType lookupType(DexType type) {
return type;
}
@@ -197,16 +242,65 @@
protected final Map<DexMethod, DexMethod> methodMap;
protected final Map<DexField, DexField> fieldMap;
- public NestedGraphLense(Map<DexType, DexType> typeMap, Map<DexMethod, DexMethod> methodMap,
- Map<DexField, DexField> fieldMap, GraphLense previousLense, DexItemFactory dexItemFactory) {
+ // Maps that store the original signature of fields and methods that have been affected by
+ // vertical class merging. Needed to generate a correct Proguard map in the end.
+ private final BiMap<DexField, DexField> originalFieldSignatures;
+ private final BiMap<DexMethod, DexMethod> originalMethodSignatures;
+
+ public NestedGraphLense(
+ Map<DexType, DexType> typeMap,
+ Map<DexMethod, DexMethod> methodMap,
+ Map<DexField, DexField> fieldMap,
+ BiMap<DexField, DexField> originalFieldSignatures,
+ BiMap<DexMethod, DexMethod> originalMethodSignatures,
+ GraphLense previousLense,
+ DexItemFactory dexItemFactory) {
this.typeMap = typeMap.isEmpty() ? null : typeMap;
this.methodMap = methodMap;
this.fieldMap = fieldMap;
+ this.originalFieldSignatures = originalFieldSignatures;
+ this.originalMethodSignatures = originalMethodSignatures;
this.previousLense = previousLense;
this.dexItemFactory = dexItemFactory;
}
@Override
+ public DexField getOriginalFieldSignature(DexField field) {
+ DexField originalField =
+ originalFieldSignatures != null
+ ? originalFieldSignatures.getOrDefault(field, field)
+ : field;
+ return previousLense.getOriginalFieldSignature(originalField);
+ }
+
+ @Override
+ public DexMethod getOriginalMethodSignature(DexMethod method) {
+ DexMethod originalMethod =
+ originalMethodSignatures != null
+ ? originalMethodSignatures.getOrDefault(method, method)
+ : method;
+ return previousLense.getOriginalMethodSignature(originalMethod);
+ }
+
+ @Override
+ public DexField getRenamedFieldSignature(DexField originalField) {
+ DexField renamedField =
+ originalFieldSignatures != null
+ ? originalFieldSignatures.inverse().getOrDefault(originalField, originalField)
+ : originalField;
+ return previousLense.getRenamedFieldSignature(renamedField);
+ }
+
+ @Override
+ public DexMethod getRenamedMethodSignature(DexMethod originalMethod) {
+ DexMethod renamedMethod =
+ originalMethodSignatures != null
+ ? originalMethodSignatures.inverse().getOrDefault(originalMethod, originalMethod)
+ : originalMethod;
+ return previousLense.getRenamedMethodSignature(renamedMethod);
+ }
+
+ @Override
public DexType lookupType(DexType type) {
if (type.isArrayType()) {
synchronized (this) {
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 64ba508..e998206 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -27,15 +27,21 @@
import com.android.tools.r8.graph.DexValue.DexValueString;
import com.android.tools.r8.graph.DexValue.DexValueType;
import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.FieldSignatureEquivalence;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.MethodSignatureEquivalence;
+import com.android.tools.r8.utils.StringDiagnostic;
+import com.google.common.base.Equivalence.Wrapper;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashSet;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import org.objectweb.asm.AnnotationVisitor;
@@ -148,8 +154,10 @@
private List<DexAnnotationElement> defaultAnnotations = null;
private final List<DexEncodedField> staticFields = new ArrayList<>();
private final List<DexEncodedField> instanceFields = new ArrayList<>();
+ private final Set<Wrapper<DexField>> fieldSignatures = new HashSet<>();
private final List<DexEncodedMethod> directMethods = new ArrayList<>();
private final List<DexEncodedMethod> virtualMethods = new ArrayList<>();
+ private final Set<Wrapper<DexMethod>> methodSignatures = new HashSet<>();
public CreateDexClassVisitor(
Origin origin,
@@ -380,13 +388,20 @@
public void visitEnd() {
FieldAccessFlags flags = FieldAccessFlags.fromCfAccessFlags(cleanAccessFlags(access));
DexField dexField = parent.application.getField(parent.type, name, desc);
- DexAnnotationSet annotationSet = createAnnotationSet(annotations);
- DexValue staticValue = flags.isStatic() ? getStaticValue(value, dexField.type) : null;
- DexEncodedField field = new DexEncodedField(dexField, flags, annotationSet, staticValue);
- if (flags.isStatic()) {
- parent.staticFields.add(field);
+ Wrapper<DexField> signature = FieldSignatureEquivalence.get().wrap(dexField);
+ if (parent.fieldSignatures.add(signature)) {
+ DexAnnotationSet annotationSet = createAnnotationSet(annotations);
+ DexValue staticValue = flags.isStatic() ? getStaticValue(value, dexField.type) : null;
+ DexEncodedField field = new DexEncodedField(dexField, flags, annotationSet, staticValue);
+ if (flags.isStatic()) {
+ parent.staticFields.add(field);
+ } else {
+ parent.instanceFields.add(field);
+ }
} else {
- parent.instanceFields.add(field);
+ parent.application.options.reporter.warning(
+ new StringDiagnostic(
+ String.format("Field `%s` has multiple definitions", dexField.toSourceString())));
}
}
@@ -603,10 +618,20 @@
annotationsList,
code,
parent.version);
- if (flags.isStatic() || flags.isConstructor() || flags.isPrivate()) {
- parent.directMethods.add(dexMethod);
+ Wrapper<DexMethod> signature = MethodSignatureEquivalence.get().wrap(method);
+ if (parent.methodSignatures.add(signature)) {
+ if (flags.isStatic() || flags.isConstructor() || flags.isPrivate()) {
+ parent.directMethods.add(dexMethod);
+ } else {
+ parent.virtualMethods.add(dexMethod);
+ }
} else {
- parent.virtualMethods.add(dexMethod);
+ internalOptions.reporter.warning(
+ new StringDiagnostic(
+ String.format(
+ "Ignoring an implementation of the method `%s` because it has multiple "
+ + "definitions",
+ method.toSourceString())));
}
if (defaultAnnotation != null) {
parent.addDefaultAnnotation(name, defaultAnnotation);
diff --git a/src/main/java/com/android/tools/r8/graph/JarCode.java b/src/main/java/com/android/tools/r8/graph/JarCode.java
index 2c37c6a..629017b 100644
--- a/src/main/java/com/android/tools/r8/graph/JarCode.java
+++ b/src/main/java/com/android/tools/r8/graph/JarCode.java
@@ -10,7 +10,7 @@
import com.android.tools.r8.ir.code.ValueNumberGenerator;
import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.ir.conversion.JarSourceCode;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.jar.InliningConstraintVisitor;
import com.android.tools.r8.jar.JarRegisterEffectsVisitor;
import com.android.tools.r8.naming.ClassNameMapper;
@@ -108,59 +108,70 @@
@Override
public IRCode buildIR(
- DexEncodedMethod encodedMethod, AppInfo appInfo, InternalOptions options, Origin origin) {
+ DexEncodedMethod encodedMethod,
+ AppInfo appInfo,
+ GraphLense graphLense,
+ InternalOptions options,
+ Origin origin) {
+ assert getOwner() == encodedMethod;
triggerDelayedParsingIfNeccessary();
return options.debug
- ? internalBuildWithLocals(encodedMethod, appInfo, options, null, null)
- : internalBuild(encodedMethod, appInfo, options, null, null);
+ ? internalBuildWithLocals(encodedMethod, appInfo, graphLense, options, null, null)
+ : internalBuild(encodedMethod, appInfo, graphLense, options, null, null);
}
@Override
public IRCode buildInliningIR(
DexEncodedMethod encodedMethod,
AppInfo appInfo,
+ GraphLense graphLense,
InternalOptions options,
ValueNumberGenerator generator,
Position callerPosition,
Origin origin) {
+ assert getOwner() == encodedMethod;
assert generator != null;
triggerDelayedParsingIfNeccessary();
return options.debug
- ? internalBuildWithLocals(encodedMethod, appInfo, options, generator, callerPosition)
- : internalBuild(encodedMethod, appInfo, options, generator, callerPosition);
+ ? internalBuildWithLocals(
+ encodedMethod, appInfo, graphLense, options, generator, callerPosition)
+ : internalBuild(encodedMethod, appInfo, graphLense, options, generator, callerPosition);
}
private IRCode internalBuildWithLocals(
DexEncodedMethod encodedMethod,
AppInfo appInfo,
+ GraphLense graphLense,
InternalOptions options,
ValueNumberGenerator generator,
Position callerPosition) {
try {
- return internalBuild(encodedMethod, appInfo, options, generator, callerPosition);
+ return internalBuild(encodedMethod, appInfo, graphLense, options, generator, callerPosition);
} catch (InvalidDebugInfoException e) {
options.warningInvalidDebugInfo(encodedMethod, origin, e);
node.localVariables.clear();
- return internalBuild(encodedMethod, appInfo, options, generator, callerPosition);
+ return internalBuild(encodedMethod, appInfo, graphLense, options, generator, callerPosition);
}
}
private IRCode internalBuild(
DexEncodedMethod encodedMethod,
AppInfo appInfo,
+ GraphLense graphLense,
InternalOptions options,
ValueNumberGenerator generator,
Position callerPosition) {
if (!options.debug) {
node.localVariables.clear();
}
- JarSourceCode source = new JarSourceCode(
- method.getHolder(), node, application, encodedMethod.method, callerPosition);
- IRBuilder builder =
- (generator == null)
- ? new IRBuilder(encodedMethod, appInfo, source, options)
- : new IRBuilder(encodedMethod, appInfo, source, options, generator);
- return builder.build();
+ JarSourceCode source =
+ new JarSourceCode(
+ method.getHolder(),
+ node,
+ application,
+ graphLense.getOriginalMethodSignature(encodedMethod.method),
+ callerPosition);
+ return new IRBuilder(encodedMethod, appInfo, source, options, generator).build();
}
@Override
@@ -173,7 +184,7 @@
DescriptorUtils.getDescriptorFromClassBinaryName(tryCatchBlockNode.type))));
}
- public Constraint computeInliningConstraint(
+ public ConstraintWithTarget computeInliningConstraint(
DexEncodedMethod encodedMethod,
AppInfoWithLiveness appInfo,
GraphLense graphLense,
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
index afc2969..f1c1700 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -146,6 +146,8 @@
assert this.context != null;
this.code = code;
this.context = null;
+ // Propagate the ownership of LazyCfCode to CfCode.
+ code.setOwner(this.getOwner());
}
@Override
@@ -175,20 +177,34 @@
@Override
public IRCode buildIR(
- DexEncodedMethod encodedMethod, AppInfo appInfo, InternalOptions options, Origin origin) {
- return asCfCode().buildIR(encodedMethod, appInfo, options, origin);
+ DexEncodedMethod encodedMethod,
+ AppInfo appInfo,
+ GraphLense graphLense,
+ InternalOptions options,
+ Origin origin) {
+ assert getOwner() == encodedMethod;
+ return asCfCode().buildIR(encodedMethod, appInfo, graphLense, options, origin);
}
@Override
public IRCode buildInliningIR(
DexEncodedMethod encodedMethod,
AppInfo appInfo,
+ GraphLense graphLense,
InternalOptions options,
ValueNumberGenerator valueNumberGenerator,
Position callerPosition,
Origin origin) {
- return asCfCode().buildInliningIR(
- encodedMethod, appInfo, options, valueNumberGenerator, callerPosition, origin);
+ assert getOwner() == encodedMethod;
+ return asCfCode()
+ .buildInliningIR(
+ encodedMethod,
+ appInfo,
+ graphLense,
+ options,
+ valueNumberGenerator,
+ callerPosition,
+ origin);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/graph/SmaliWriter.java b/src/main/java/com/android/tools/r8/graph/SmaliWriter.java
index 7f7ec36..024c0c9 100644
--- a/src/main/java/com/android/tools/r8/graph/SmaliWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/SmaliWriter.java
@@ -1,3 +1,7 @@
+// 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.dex.ApplicationReader;
diff --git a/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingUser.java b/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingUser.java
index 6948a08..20d7bf4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingUser.java
+++ b/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingUser.java
@@ -8,7 +8,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.utils.InternalOptions;
@@ -58,7 +58,7 @@
}
@Override
- public Constraint inliningConstraint(
+ public ConstraintWithTarget inliningConstraint(
InliningConstraints inliningConstraints, DexType invocationContext) {
return inliningConstraints.forAlwaysMaterializingUser();
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Argument.java b/src/main/java/com/android/tools/r8/ir/code/Argument.java
index cff655a..c8240a0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Argument.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Argument.java
@@ -10,7 +10,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.utils.InternalOptions;
@@ -76,7 +76,7 @@
}
@Override
- public Constraint inliningConstraint(
+ public ConstraintWithTarget inliningConstraint(
InliningConstraints inliningConstraints, DexType invocationContext) {
return inliningConstraints.forArgument();
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
index 01101f3..758252b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
@@ -21,7 +21,7 @@
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.ir.regalloc.RegisterAllocator;
import java.util.Arrays;
@@ -133,7 +133,7 @@
}
@Override
- public Constraint inliningConstraint(
+ public ConstraintWithTarget inliningConstraint(
InliningConstraints inliningConstraints, DexType invocationContext) {
return inliningConstraints.forArrayGet();
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
index cc358f0..445c9ed 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
@@ -13,7 +13,7 @@
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.ir.regalloc.RegisterAllocator;
import java.util.function.Function;
@@ -90,7 +90,7 @@
}
@Override
- public Constraint inliningConstraint(
+ public ConstraintWithTarget inliningConstraint(
InliningConstraints inliningConstraints, DexType invocationContext) {
return inliningConstraints.forArrayLength();
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
index 023afce..0282ffa 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
@@ -17,7 +17,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.ir.regalloc.RegisterAllocator;
import com.android.tools.r8.utils.InternalOptions;
@@ -154,7 +154,7 @@
}
@Override
- public Constraint inliningConstraint(
+ public ConstraintWithTarget inliningConstraint(
InliningConstraints inliningConstraints, DexType invocationContext) {
return inliningConstraints.forArrayPut();
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Binop.java b/src/main/java/com/android/tools/r8/ir/code/Binop.java
index e5190bb..30fdc59 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Binop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Binop.java
@@ -12,7 +12,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.type.PrimitiveTypeLatticeElement;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.ir.regalloc.RegisterAllocator;
import java.util.function.Function;
@@ -124,7 +124,7 @@
}
@Override
- public Constraint inliningConstraint(
+ public ConstraintWithTarget inliningConstraint(
InliningConstraints inliningConstraints, DexType invocationContext) {
return inliningConstraints.forBinop();
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
index dba575f..cee01eb 100644
--- a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
+++ b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
@@ -15,7 +15,7 @@
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import java.util.function.Function;
@@ -113,7 +113,7 @@
}
@Override
- public Constraint inliningConstraint(
+ public ConstraintWithTarget inliningConstraint(
InliningConstraints inliningConstraints, DexType invocationContext) {
return inliningConstraints.forCheckCast(type, invocationContext);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
index 3041aaf..c5a0a11 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
@@ -13,7 +13,7 @@
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.utils.InternalOptions;
import java.util.function.Function;
@@ -99,7 +99,7 @@
}
@Override
- public Constraint inliningConstraint(
+ public ConstraintWithTarget inliningConstraint(
InliningConstraints inliningConstraints, DexType invocationContext) {
return inliningConstraints.forConstClass(clazz, invocationContext);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstInstruction.java b/src/main/java/com/android/tools/r8/ir/code/ConstInstruction.java
index f6e212d..a6ac01d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstInstruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstInstruction.java
@@ -4,7 +4,7 @@
package com.android.tools.r8.ir.code;
import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
public abstract class ConstInstruction extends Instruction {
@@ -29,7 +29,7 @@
}
@Override
- public Constraint inliningConstraint(
+ public ConstraintWithTarget inliningConstraint(
InliningConstraints inliningConstraints, DexType invocationContext) {
return inliningConstraints.forConstInstruction();
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java b/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java
index 637faf7..9c6b342 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java
@@ -8,7 +8,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.utils.InternalOptions;
@@ -60,7 +60,7 @@
}
@Override
- public Constraint inliningConstraint(
+ public ConstraintWithTarget inliningConstraint(
InliningConstraints inliningConstraints, DexType invocationContext) {
return inliningConstraints.forDebugLocalRead();
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java b/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java
index 53791e5..52c8dc6 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java
@@ -9,7 +9,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.StringUtils;
@@ -100,7 +100,7 @@
}
@Override
- public Constraint inliningConstraint(
+ public ConstraintWithTarget inliningConstraint(
InliningConstraints inliningConstraints, DexType invocationContext) {
return inliningConstraints.forDebugLocalsChange();
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java b/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
index aed2128..5a611aa 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
@@ -9,7 +9,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.utils.InternalOptions;
@@ -57,7 +57,7 @@
}
@Override
- public Constraint inliningConstraint(
+ public ConstraintWithTarget inliningConstraint(
InliningConstraints inliningConstraints, DexType invocationContext) {
return inliningConstraints.forDebugPosition();
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Goto.java b/src/main/java/com/android/tools/r8/ir/code/Goto.java
index cd5fcc8..80a76fe 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Goto.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Goto.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.utils.CfgPrinter;
import java.util.List;
+import java.util.ListIterator;
public class Goto extends JumpInstruction {
@@ -103,6 +104,18 @@
// Nothing to do.
}
+ public boolean isTrivialGotoToTheNextBlock(IRCode code) {
+ BasicBlock thisBlock = getBlock();
+ ListIterator<BasicBlock> blockIterator = code.blocks.listIterator();
+ while (blockIterator.hasNext()) {
+ BasicBlock block = blockIterator.next();
+ if (thisBlock == block) {
+ return blockIterator.hasNext() && blockIterator.next() == getTarget();
+ }
+ }
+ return false;
+ }
+
@Override
public void buildCf(CfBuilder builder) {
builder.add(new CfGoto(builder.getLabel(getTarget())));
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
index 1083669..50fc2ed 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
@@ -22,7 +22,7 @@
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import java.util.function.Function;
import org.objectweb.asm.Opcodes;
@@ -114,7 +114,7 @@
}
@Override
- public Constraint inliningConstraint(
+ public ConstraintWithTarget inliningConstraint(
InliningConstraints inliningConstraints, DexType invocationContext) {
return inliningConstraints.forInstanceGet(field, invocationContext);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java b/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
index 53fd31c..7a973f8 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
@@ -13,7 +13,7 @@
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import java.util.function.Function;
@@ -81,7 +81,7 @@
}
@Override
- public Constraint inliningConstraint(
+ public ConstraintWithTarget inliningConstraint(
InliningConstraints inliningConstraints, DexType invocationContext) {
return inliningConstraints.forInstanceOf(type, invocationContext);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
index 3585348..e8d917d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
@@ -19,7 +19,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import java.util.Arrays;
import org.objectweb.asm.Opcodes;
@@ -113,7 +113,7 @@
}
@Override
- public Constraint inliningConstraint(
+ public ConstraintWithTarget inliningConstraint(
InliningConstraints inliningConstraints, DexType invocationContext) {
return inliningConstraints.forInstancePut(field, invocationContext);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index eb85222..deae051 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -16,7 +16,7 @@
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.ir.regalloc.RegisterAllocator;
import com.android.tools.r8.utils.CfgPrinter;
@@ -266,6 +266,14 @@
block = null;
}
+ public void removeOrReplaceByDebugLocalRead() {
+ getBlock().listIterator(this).removeOrReplaceByDebugLocalRead();
+ }
+
+ public void replace(Instruction newInstruction) {
+ getBlock().listIterator(this).replaceCurrentInstruction(newInstruction);
+ }
+
/**
* Returns true if the instruction is in the IR and therefore has a block.
*/
@@ -1059,7 +1067,7 @@
*
* <p>The type is used to judge visibility constraints and also for dispatch decisions.
*/
- public abstract Constraint inliningConstraint(
+ public abstract ConstraintWithTarget inliningConstraint(
InliningConstraints inliningConstraints, DexType invocationContext);
public abstract void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper);
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java b/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
index a6e6fd1..c12333a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
@@ -11,7 +11,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import java.util.List;
@@ -101,7 +101,7 @@
}
@Override
- public Constraint inliningConstraint(
+ public ConstraintWithTarget inliningConstraint(
InliningConstraints inliningConstraints, DexType invocationContext) {
return inliningConstraints.forInvokeCustom();
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
index 88716dc..46d2e5a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
@@ -12,7 +12,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import java.util.Collection;
@@ -113,7 +113,7 @@
}
@Override
- public Constraint inliningConstraint(
+ public ConstraintWithTarget inliningConstraint(
InliningConstraints inliningConstraints, DexType invocationContext) {
return inliningConstraints.forInvokeDirect(getInvokedMethod(), invocationContext);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java b/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
index 06d368d..5f49322 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
@@ -12,7 +12,7 @@
import com.android.tools.r8.ir.analysis.type.TypeEnvironment;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import java.util.Collection;
@@ -95,7 +95,7 @@
}
@Override
- public Constraint inliningConstraint(
+ public ConstraintWithTarget inliningConstraint(
InliningConstraints inliningConstraints, DexType invocationContext) {
return inliningConstraints.forInvokeInterface(getInvokedMethod(), invocationContext);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
index e34d25f..821d707 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
@@ -12,7 +12,7 @@
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import java.util.List;
import java.util.function.Function;
@@ -69,7 +69,7 @@
}
@Override
- public Constraint inliningConstraint(
+ public ConstraintWithTarget inliningConstraint(
InliningConstraints inliningConstraints, DexType invocationContext) {
return inliningConstraints.forInvokeMultiNewArray(type, invocationContext);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
index a2a9c36..17f36df 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
@@ -13,7 +13,7 @@
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import java.util.List;
import java.util.function.Function;
@@ -99,7 +99,7 @@
}
@Override
- public Constraint inliningConstraint(
+ public ConstraintWithTarget inliningConstraint(
InliningConstraints inliningConstraints, DexType invocationContext) {
return inliningConstraints.forInvokeNewArray(type, invocationContext);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java b/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
index 8f90630..b45e984 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
@@ -13,7 +13,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.ir.optimize.InliningOracle;
@@ -126,7 +126,7 @@
}
@Override
- public Constraint inliningConstraint(
+ public ConstraintWithTarget inliningConstraint(
InliningConstraints inliningConstraints, DexType invocationContext) {
return inliningConstraints.forInvokePolymorphic(getInvokedMethod(), invocationContext);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
index d2183c0..93660c7 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
@@ -11,7 +11,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.ir.optimize.InliningOracle;
@@ -103,7 +103,7 @@
}
@Override
- public Constraint inliningConstraint(
+ public ConstraintWithTarget inliningConstraint(
InliningConstraints inliningConstraints, DexType invocationContext) {
return inliningConstraints.forInvokeStatic(getInvokedMethod(), invocationContext);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java b/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java
index 920d94b..141d4d9 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java
@@ -13,7 +13,7 @@
import com.android.tools.r8.ir.analysis.type.TypeEnvironment;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import java.util.Collection;
@@ -112,7 +112,7 @@
}
@Override
- public Constraint inliningConstraint(
+ public ConstraintWithTarget inliningConstraint(
InliningConstraints inliningConstraints, DexType invocationContext) {
return inliningConstraints.forInvokeSuper(getInvokedMethod(), invocationContext);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
index f55a407..51bed08 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
@@ -12,7 +12,7 @@
import com.android.tools.r8.ir.analysis.type.TypeEnvironment;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import java.util.Collection;
@@ -95,7 +95,7 @@
}
@Override
- public Constraint inliningConstraint(
+ public ConstraintWithTarget inliningConstraint(
InliningConstraints inliningConstraints, DexType invocationContext) {
return inliningConstraints.forInvokeVirtual(getInvokedMethod(), invocationContext);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/JumpInstruction.java b/src/main/java/com/android/tools/r8/ir/code/JumpInstruction.java
index 6096292..ded6002 100644
--- a/src/main/java/com/android/tools/r8/ir/code/JumpInstruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/JumpInstruction.java
@@ -4,7 +4,7 @@
package com.android.tools.r8.ir.code;
import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.utils.InternalOptions;
import java.util.List;
@@ -47,7 +47,7 @@
}
@Override
- public Constraint inliningConstraint(
+ public ConstraintWithTarget inliningConstraint(
InliningConstraints inliningConstraints, DexType invocationContext) {
return inliningConstraints.forJumpInstruction();
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Load.java b/src/main/java/com/android/tools/r8/ir/code/Load.java
index 32654b8..5981249 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Load.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Load.java
@@ -11,7 +11,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
public class Load extends Instruction {
@@ -51,7 +51,7 @@
}
@Override
- public Constraint inliningConstraint(
+ public ConstraintWithTarget inliningConstraint(
InliningConstraints inliningConstraints, DexType invocationContext) {
return inliningConstraints.forLoad();
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Monitor.java b/src/main/java/com/android/tools/r8/ir/code/Monitor.java
index 6b3c89c..70af068 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Monitor.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Monitor.java
@@ -14,7 +14,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
public class Monitor extends Instruction {
@@ -34,6 +34,14 @@
return inValues.get(0);
}
+ public boolean isEnter() {
+ return type == Type.ENTER;
+ }
+
+ public boolean isExit() {
+ return type == Type.EXIT;
+ }
+
@Override
public void buildDex(DexBuilder builder) {
// If the monitor object is an argument, we use the argument register for all the monitor
@@ -89,7 +97,7 @@
}
@Override
- public Constraint inliningConstraint(
+ public ConstraintWithTarget inliningConstraint(
InliningConstraints inliningConstraints, DexType invocationContext) {
return inliningConstraints.forMonitor();
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Move.java b/src/main/java/com/android/tools/r8/ir/code/Move.java
index e474bb8..6223501 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Move.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Move.java
@@ -12,7 +12,7 @@
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import java.util.function.Function;
@@ -100,7 +100,7 @@
}
@Override
- public Constraint inliningConstraint(
+ public ConstraintWithTarget inliningConstraint(
InliningConstraints inliningConstraints, DexType invocationContext) {
return inliningConstraints.forMove();
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/MoveException.java b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
index eabf7b2..e7d6e63 100644
--- a/src/main/java/com/android/tools/r8/ir/code/MoveException.java
+++ b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
@@ -12,7 +12,7 @@
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.utils.InternalOptions;
import java.util.HashSet;
@@ -75,7 +75,7 @@
}
@Override
- public Constraint inliningConstraint(
+ public ConstraintWithTarget inliningConstraint(
InliningConstraints inliningConstraints, DexType invocationContext) {
return inliningConstraints.forMoveException();
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
index edca979..69867e6 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
@@ -13,7 +13,7 @@
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import java.util.function.Function;
@@ -84,7 +84,7 @@
}
@Override
- public Constraint inliningConstraint(
+ public ConstraintWithTarget inliningConstraint(
InliningConstraints inliningConstraints, DexType invocationContext) {
return inliningConstraints.forNewArrayEmpty(type, invocationContext);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
index ac46cc7..8dac404 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
@@ -11,7 +11,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.utils.InternalOptions;
import java.util.Arrays;
@@ -112,7 +112,7 @@
}
@Override
- public Constraint inliningConstraint(
+ public ConstraintWithTarget inliningConstraint(
InliningConstraints inliningConstraints, DexType invocationContext) {
return inliningConstraints.forNewArrayFilledData();
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
index 75931ad..92e7f3e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
@@ -12,7 +12,7 @@
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import java.util.function.Function;
@@ -81,7 +81,7 @@
}
@Override
- public Constraint inliningConstraint(
+ public ConstraintWithTarget inliningConstraint(
InliningConstraints inliningConstraints, DexType invocationContext) {
return inliningConstraints.forNewInstance(clazz, invocationContext);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/NonNull.java b/src/main/java/com/android/tools/r8/ir/code/NonNull.java
index 462af06..d909144 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NonNull.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NonNull.java
@@ -10,7 +10,7 @@
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import java.util.function.Function;
@@ -85,7 +85,7 @@
}
@Override
- public Constraint inliningConstraint(
+ public ConstraintWithTarget inliningConstraint(
InliningConstraints inliningConstraints, DexType invocationContext) {
return inliningConstraints.forNonNull();
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Pop.java b/src/main/java/com/android/tools/r8/ir/code/Pop.java
index 0fb1949..8ea55dd 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Pop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Pop.java
@@ -9,7 +9,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.utils.InternalOptions;
@@ -50,7 +50,7 @@
}
@Override
- public Constraint inliningConstraint(
+ public ConstraintWithTarget inliningConstraint(
InliningConstraints inliningConstraints, DexType invocationContext) {
return inliningConstraints.forPop();
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Return.java b/src/main/java/com/android/tools/r8/ir/code/Return.java
index 37cfaf0..bb750c2 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Return.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Return.java
@@ -15,7 +15,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
public class Return extends JumpInstruction {
@@ -116,7 +116,7 @@
}
@Override
- public Constraint inliningConstraint(
+ public ConstraintWithTarget inliningConstraint(
InliningConstraints inliningConstraints, DexType invocationContext) {
return inliningConstraints.forReturn();
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
index 486f9e2..108beee 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
@@ -21,7 +21,7 @@
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import java.util.function.Function;
import org.objectweb.asm.Opcodes;
@@ -109,7 +109,7 @@
}
@Override
- public Constraint inliningConstraint(
+ public ConstraintWithTarget inliningConstraint(
InliningConstraints inliningConstraints, DexType invocationContext) {
return inliningConstraints.forStaticGet(field, invocationContext);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
index f910ff6..8824d54 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
@@ -18,7 +18,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import org.objectweb.asm.Opcodes;
@@ -107,7 +107,7 @@
}
@Override
- public Constraint inliningConstraint(
+ public ConstraintWithTarget inliningConstraint(
InliningConstraints inliningConstraints, DexType invocationContext) {
return inliningConstraints.forStaticPut(field, invocationContext);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Store.java b/src/main/java/com/android/tools/r8/ir/code/Store.java
index b81af69..cec5501 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Store.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Store.java
@@ -12,7 +12,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.utils.InternalOptions;
@@ -53,7 +53,7 @@
}
@Override
- public Constraint inliningConstraint(
+ public ConstraintWithTarget inliningConstraint(
InliningConstraints inliningConstraints, DexType invocationContext) {
return inliningConstraints.forStore();
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Throw.java b/src/main/java/com/android/tools/r8/ir/code/Throw.java
index 4295dbe..7bda1c8 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Throw.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Throw.java
@@ -9,7 +9,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
public class Throw extends JumpInstruction {
@@ -65,7 +65,7 @@
}
@Override
- public Constraint inliningConstraint(
+ public ConstraintWithTarget inliningConstraint(
InliningConstraints inliningConstraints, DexType invocationContext) {
return inliningConstraints.forThrow();
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Unop.java b/src/main/java/com/android/tools/r8/ir/code/Unop.java
index 2f6ed7f..b29afd0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Unop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Unop.java
@@ -9,7 +9,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.type.PrimitiveTypeLatticeElement;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import java.util.function.Function;
@@ -48,7 +48,7 @@
}
@Override
- public Constraint inliningConstraint(
+ public ConstraintWithTarget inliningConstraint(
InliningConstraints inliningConstraints, DexType invocationContext) {
return inliningConstraints.forUnop();
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
index 650757e..382dd64 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.ir.conversion;
+import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
@@ -15,16 +16,23 @@
import com.android.tools.r8.graph.GraphLense.GraphLenseLookupResult;
import com.android.tools.r8.graph.UseRegistry;
import com.android.tools.r8.ir.code.Invoke.Type;
+import com.android.tools.r8.logging.Log;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.ThrowingBiConsumer;
+import com.android.tools.r8.utils.Timing;
import com.google.common.collect.Sets;
+import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
+import java.util.Deque;
+import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -56,7 +64,7 @@
this.shuffle = options.testing.irOrdering;
}
- private static class Node {
+ public static class Node {
public final DexEncodedMethod method;
private int invokeCount = 0;
@@ -68,7 +76,7 @@
// Incoming calls to this method.
private final Set<Node> callers = new LinkedHashSet<>();
- private Node(DexEncodedMethod method) {
+ public Node(DexEncodedMethod method) {
this.method = method;
}
@@ -76,19 +84,20 @@
return method.accessFlags.isBridge();
}
- private void addCallee(Node method) {
+ public void addCallee(Node method) {
callees.add(method);
+ method.callers.add(this);
}
- private void addCaller(Node method) {
- callers.add(method);
+ public boolean hasCallee(Node method) {
+ return callees.contains(method);
}
boolean isSelfRecursive() {
return isSelfRecursive;
}
- boolean isLeaf() {
+ public boolean isLeaf() {
return callees.isEmpty();
}
@@ -96,7 +105,7 @@
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("MethodNode for: ");
- builder.append(method.qualifiedName());
+ builder.append(method.toSourceString());
builder.append(" (");
builder.append(callees.size());
builder.append(" callees, ");
@@ -114,7 +123,7 @@
builder.append("Callees:\n");
for (Node call : callees) {
builder.append(" ");
- builder.append(call.method.qualifiedName());
+ builder.append(call.method.toSourceString());
builder.append("\n");
}
}
@@ -122,7 +131,7 @@
builder.append("Callers:\n");
for (Node caller : callers) {
builder.append(" ");
- builder.append(caller.method.qualifiedName());
+ builder.append(caller.method.toSourceString());
builder.append("\n");
}
}
@@ -136,8 +145,12 @@
private final Set<DexEncodedMethod> singleCallSite = Sets.newIdentityHashSet();
private final Set<DexEncodedMethod> doubleCallSite = Sets.newIdentityHashSet();
- public static CallGraph build(DexApplication application, AppInfoWithLiveness appInfo,
- GraphLense graphLense, InternalOptions options) {
+ public static CallGraph build(
+ DexApplication application,
+ AppInfoWithLiveness appInfo,
+ GraphLense graphLense,
+ InternalOptions options,
+ Timing timing) {
CallGraph graph = new CallGraph(options);
DexClass[] classes = application.classes().toArray(new DexClass[application.classes().size()]);
Arrays.sort(classes, (DexClass a, DexClass b) -> a.type.slowCompareTo(b.type));
@@ -149,8 +162,13 @@
}
}
assert allMethodsExists(application, graph);
- graph.breakCycles();
- assert graph.breakCycles() == 0; // This time the cycles should be gone.
+
+ timing.begin("Cycle elimination");
+ CycleEliminator cycleEliminator = new CycleEliminator(graph.nodes.values(), options);
+ cycleEliminator.breakCycles();
+ timing.end();
+ assert cycleEliminator.breakCycles() == 0; // This time the cycles should be gone.
+
graph.fillCallSiteSets(appInfo);
return graph;
}
@@ -196,9 +214,10 @@
/**
* Extract the next set of leaves (nodes with an call (outgoing) degree of 0) if any.
- * <p>
- * All nodes in the graph are extracted if called repeatedly until null is returned.
- * Please note that there are no cycles in this graph (see {@link #breakCycles}).
+ *
+ * <p>All nodes in the graph are extracted if called repeatedly until null is returned. Please
+ * note that there are no cycles in this graph (see {@link CycleEliminator#breakCycles}).
+ *
* <p>
*/
private Set<DexEncodedMethod> extractLeaves() {
@@ -215,48 +234,197 @@
.collect(Collectors.toCollection(LinkedHashSet::new)));
}
- private int traverse(Node node, Set<Node> stack, Set<Node> marked) {
- int numberOfCycles = 0;
- if (!marked.contains(node)) {
- assert !stack.contains(node);
- stack.add(node);
- ArrayList<Node> toBeRemoved = null;
- // Sort the callees before calling traverse recursively.
- // This will ensure cycles are broken the same way across
- // multiple invocations of the R8 compiler.
+ public static class CycleEliminator {
+
+ public static final String CYCLIC_FORCE_INLINING_MESSAGE =
+ "Unable to satisfy force inlining constraints due to cyclic force inlining";
+
+ private static class CallEdge {
+
+ private final Node caller;
+ private final Node callee;
+
+ public CallEdge(Node caller, Node callee) {
+ this.caller = caller;
+ this.callee = callee;
+ }
+ }
+
+ private final Collection<Node> nodes;
+ private final InternalOptions options;
+
+ // DFS stack.
+ private Deque<Node> stack = new ArrayDeque<>();
+
+ // Set of nodes on the DFS stack.
+ private Set<Node> stackSet = Sets.newIdentityHashSet();
+
+ // Set of nodes that have been visited entirely.
+ private Set<Node> marked = Sets.newIdentityHashSet();
+
+ private int numberOfCycles = 0;
+
+ public CycleEliminator(Collection<Node> nodes, InternalOptions options) {
+ this.options = options;
+
+ // Call to reorderNodes must happen after assigning options.
+ this.nodes =
+ options.testing.nondeterministicCycleElimination
+ ? reorderNodes(new ArrayList<>(nodes))
+ : nodes;
+ }
+
+ public int breakCycles() {
+ // Break cycles in this call graph by removing edges causing cycles.
+ for (Node node : nodes) {
+ traverse(node);
+ }
+ int result = numberOfCycles;
+ reset();
+ return result;
+ }
+
+ private void reset() {
+ assert stack.isEmpty();
+ assert stackSet.isEmpty();
+ marked.clear();
+ numberOfCycles = 0;
+ }
+
+ private void traverse(Node node) {
+ if (marked.contains(node)) {
+ // Already visited all nodes that can be reached from this node.
+ return;
+ }
+
+ push(node);
+
+ // Sort the callees before calling traverse recursively. This will ensure cycles are broken
+ // the same way across multiple invocations of the R8 compiler.
Node[] callees = node.callees.toArray(new Node[node.callees.size()]);
Arrays.sort(callees, (Node a, Node b) -> a.method.method.slowCompareTo(b.method.method));
+ if (options.testing.nondeterministicCycleElimination) {
+ reorderNodes(Arrays.asList(callees));
+ }
+
for (Node callee : callees) {
- if (stack.contains(callee)) {
- if (toBeRemoved == null) {
- toBeRemoved = new ArrayList<>();
+ if (stackSet.contains(callee)) {
+ // Found a cycle that needs to be eliminated.
+ numberOfCycles++;
+
+ if (edgeRemovalIsSafe(node, callee)) {
+ // Break the cycle by removing the edge node->callee.
+ callee.callers.remove(node);
+ node.callees.remove(callee);
+
+ if (Log.ENABLED) {
+ Log.info(
+ CallGraph.class,
+ "Removed call edge from method '%s' to '%s'",
+ node.method.toSourceString(),
+ callee.method.toSourceString());
+ }
+ } else {
+ // The cycle has a method that is marked as force inline.
+ LinkedList<Node> cycle = extractCycle(callee);
+
+ if (Log.ENABLED) {
+ Log.info(
+ CallGraph.class, "Extracted cycle to find an edge that can safely be removed");
+ }
+
+ // Break the cycle by finding an edge that can be removed without breaking force
+ // inlining. If that is not possible, this call fails with a compilation error.
+ CallEdge edge = findCallEdgeForRemoval(cycle);
+
+ // The edge will be null if this cycle has already been eliminated as a result of
+ // another cycle elimination.
+ if (edge != null) {
+ assert edgeRemovalIsSafe(edge.caller, edge.callee);
+
+ // Break the cycle by removing the edge caller->callee.
+ edge.caller.callees.remove(edge.callee);
+ edge.callee.callers.remove(edge.caller);
+
+ if (Log.ENABLED) {
+ Log.info(
+ CallGraph.class,
+ "Removed call edge from force inlined method '%s' to '%s' to ensure that "
+ + "force inlining will succeed",
+ node.method.toSourceString(),
+ callee.method.toSourceString());
+ }
+ }
+
+ // Recover the stack.
+ recoverStack(cycle);
}
- // We have a cycle; break it by removing node->callee.
- toBeRemoved.add(callee);
- callee.callers.remove(node);
} else {
- numberOfCycles += traverse(callee, stack, marked);
+ traverse(callee);
}
}
- if (toBeRemoved != null) {
- numberOfCycles += toBeRemoved.size();
- node.callees.removeAll(toBeRemoved);
- }
- stack.remove(node);
+ pop(node);
marked.add(node);
}
- return numberOfCycles;
- }
- private int breakCycles() {
- // Break cycles in this call graph by removing edges causing cycles.
- int numberOfCycles = 0;
- Set<Node> stack = Sets.newIdentityHashSet();
- Set<Node> marked = Sets.newIdentityHashSet();
- for (Node node : nodes.values()) {
- numberOfCycles += traverse(node, stack, marked);
+ private void push(Node node) {
+ stack.push(node);
+ boolean changed = stackSet.add(node);
+ assert changed;
}
- return numberOfCycles;
+
+ private void pop(Node node) {
+ Node popped = stack.pop();
+ assert popped == node;
+ boolean changed = stackSet.remove(node);
+ assert changed;
+ }
+
+ private LinkedList<Node> extractCycle(Node entry) {
+ LinkedList<Node> cycle = new LinkedList<>();
+ do {
+ assert !stack.isEmpty();
+ cycle.add(stack.pop());
+ } while (cycle.getLast() != entry);
+ return cycle;
+ }
+
+ private CallEdge findCallEdgeForRemoval(LinkedList<Node> extractedCycle) {
+ Node callee = extractedCycle.getLast();
+ for (Node caller : extractedCycle) {
+ if (!caller.callees.contains(callee)) {
+ // No need to break any edges since this cycle has already been broken previously.
+ assert !callee.callers.contains(caller);
+ return null;
+ }
+ if (edgeRemovalIsSafe(caller, callee)) {
+ return new CallEdge(caller, callee);
+ }
+ callee = caller;
+ }
+ throw new CompilationError(CYCLIC_FORCE_INLINING_MESSAGE);
+ }
+
+ private static boolean edgeRemovalIsSafe(Node caller, Node callee) {
+ // All call edges where the callee is a method that should be force inlined must be kept,
+ // to guarantee that the IR converter will process the callee before the caller.
+ return !callee.method.getOptimizationInfo().forceInline();
+ }
+
+ private void recoverStack(LinkedList<Node> extractedCycle) {
+ Iterator<Node> descendingIt = extractedCycle.descendingIterator();
+ while (descendingIt.hasNext()) {
+ stack.push(descendingIt.next());
+ }
+ }
+
+ private Collection<Node> reorderNodes(List<Node> nodes) {
+ assert options.testing.nondeterministicCycleElimination;
+ if (!InternalOptions.DETERMINISTIC_DEBUGGING) {
+ Collections.shuffle(nodes);
+ }
+ return nodes;
+ }
}
synchronized private Node ensureMethodNode(DexEncodedMethod method) {
@@ -268,7 +436,6 @@
assert callee != null;
if (caller != callee) {
caller.addCallee(callee);
- callee.addCaller(caller);
} else {
caller.isSelfRecursive = true;
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
index f276cb2..35ce779 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -76,6 +76,7 @@
private final Int2ReferenceMap<DebugLocalInfo> emittedLocals = new Int2ReferenceOpenHashMap<>();
private Int2ReferenceMap<DebugLocalInfo> pendingLocals = null;
private boolean pendingLocalChanges = false;
+ private BasicBlock pendingFrame = null;
private final List<LocalVariableInfo> localVariablesTable = new ArrayList<>();
private final Int2ReferenceMap<LocalVariableInfo> openLocalVariables =
@@ -259,7 +260,6 @@
BasicBlock block = blockIterator.next();
CfLabel tryCatchStart = null;
CatchHandlers<BasicBlock> tryCatchHandlers = CatchHandlers.EMPTY_BASIC_BLOCK;
- BasicBlock pendingFrame = null;
boolean previousFallthrough = false;
do {
assert stack.isEmpty();
@@ -286,17 +286,6 @@
pendingFrame = block;
emitLabel(getLabel(block));
}
- if (pendingFrame != null) {
- boolean advancesPC = hasMaterializingInstructions(block, nextBlock);
- // If block has no materializing instructions, then we postpone emitting the frame
- // until the next block. In this case, nextBlock must be non-null
- // (or we would fall off the edge of the method).
- assert advancesPC || nextBlock != null;
- if (advancesPC) {
- addFrame(pendingFrame, Collections.emptyList());
- pendingFrame = null;
- }
- }
JumpInstruction exit = block.exit();
boolean fallthrough =
(exit.isGoto() && exit.asGoto().getTarget() == nextBlock)
@@ -308,7 +297,7 @@
pendingLocals = new Int2ReferenceOpenHashMap<>(locals);
pendingLocalChanges = true;
}
- buildCfInstructions(block, fallthrough, stack);
+ buildCfInstructions(block, nextBlock, fallthrough, stack);
block = nextBlock;
previousFallthrough = fallthrough;
} while (block != null);
@@ -346,7 +335,19 @@
return false;
}
- private void buildCfInstructions(BasicBlock block, boolean fallthrough, Stack stack) {
+ private void buildCfInstructions(
+ BasicBlock block, BasicBlock nextBlock, boolean fallthrough, Stack stack) {
+ if (pendingFrame != null) {
+ boolean advancesPC = hasMaterializingInstructions(block, nextBlock);
+ // If block has no materializing instructions, then we postpone emitting the frame
+ // until the next block. In this case, nextBlock must be non-null
+ // (or we would fall off the edge of the method).
+ assert advancesPC || nextBlock != null;
+ if (advancesPC) {
+ addFrame(pendingFrame, Collections.emptyList());
+ pendingFrame = null;
+ }
+ }
InstructionIterator it = block.iterator();
while (it.hasNext()) {
Instruction instruction = it.next();
@@ -385,36 +386,16 @@
boolean didPositionChange =
position.isSome()
&& position != currentPosition
+ // Ignore synthetic positions prior to any actual position, except when inlined
+ // (that is, callerPosition != null).
+ && !(currentPosition.isNone() && position.synthetic && position.callerPosition == null)
&& (options.debug || instruction.instructionTypeCanThrow());
if (!didLocalsChange && !didPositionChange) {
return;
}
CfLabel label = ensureLabel();
if (didLocalsChange) {
- Int2ReferenceSortedMap<DebugLocalInfo> ending =
- DebugLocalInfo.endingLocals(emittedLocals, pendingLocals);
- Int2ReferenceSortedMap<DebugLocalInfo> starting =
- DebugLocalInfo.startingLocals(emittedLocals, pendingLocals);
- assert !ending.isEmpty() || !starting.isEmpty();
- for (Entry<DebugLocalInfo> entry : ending.int2ReferenceEntrySet()) {
- int localIndex = entry.getIntKey();
- LocalVariableInfo info = openLocalVariables.remove(localIndex);
- info.setEnd(label);
- localVariablesTable.add(info);
- DebugLocalInfo removed = emittedLocals.remove(localIndex);
- assert removed == entry.getValue();
- }
- if (!starting.isEmpty()) {
- for (Entry<DebugLocalInfo> entry : starting.int2ReferenceEntrySet()) {
- int localIndex = entry.getIntKey();
- assert !emittedLocals.containsKey(localIndex);
- assert !openLocalVariables.containsKey(localIndex);
- openLocalVariables.put(
- localIndex, new LocalVariableInfo(localIndex, entry.getValue(), label));
- emittedLocals.put(localIndex, entry.getValue());
- }
- }
- pendingLocalChanges = false;
+ updateLocals(label);
}
if (didPositionChange) {
add(new CfPosition(label, position));
@@ -422,6 +403,33 @@
}
}
+ private void updateLocals(CfLabel label) {
+ Int2ReferenceSortedMap<DebugLocalInfo> ending =
+ DebugLocalInfo.endingLocals(emittedLocals, pendingLocals);
+ Int2ReferenceSortedMap<DebugLocalInfo> starting =
+ DebugLocalInfo.startingLocals(emittedLocals, pendingLocals);
+ assert !ending.isEmpty() || !starting.isEmpty();
+ for (Entry<DebugLocalInfo> entry : ending.int2ReferenceEntrySet()) {
+ int localIndex = entry.getIntKey();
+ LocalVariableInfo info = openLocalVariables.remove(localIndex);
+ info.setEnd(label);
+ localVariablesTable.add(info);
+ DebugLocalInfo removed = emittedLocals.remove(localIndex);
+ assert removed == entry.getValue();
+ }
+ if (!starting.isEmpty()) {
+ for (Entry<DebugLocalInfo> entry : starting.int2ReferenceEntrySet()) {
+ int localIndex = entry.getIntKey();
+ assert !emittedLocals.containsKey(localIndex);
+ assert !openLocalVariables.containsKey(localIndex);
+ openLocalVariables.put(
+ localIndex, new LocalVariableInfo(localIndex, entry.getValue(), label));
+ emittedLocals.put(localIndex, entry.getValue());
+ }
+ }
+ pendingLocalChanges = false;
+ }
+
private boolean localsChanged() {
if (!pendingLocalChanges) {
return false;
@@ -458,11 +466,25 @@
Collection<Value> locals = registerAllocator.getLocalsAtBlockEntry(block);
Int2ReferenceSortedMap<FrameType> mapping = new Int2ReferenceAVLTreeMap<>();
-
for (Value local : locals) {
mapping.put(getLocalRegister(local), getFrameType(block, local));
}
- instructions.add(new CfFrame(mapping, stackTypes));
+ CfFrame frame = new CfFrame(mapping, stackTypes);
+
+ // Make sure to end locals on this transition before the synthetic CfFrame instruction.
+ // Otherwise we might extend the live range of a local across a CfFrame instruction that
+ // the local is not live across. For example if we have a return followed by a move exception
+ // where the locals end. Inserting a CfFrame instruction between the return and the move
+ // exception without ending the locals will lead to having the local alive on the CfFrame
+ // instruction which is not correct and will cause us to not be able to build IR from the
+ // CfCode.
+ boolean didLocalsChange = localsChanged();
+ if (didLocalsChange) {
+ CfLabel label = ensureLabel();
+ updateLocals(label);
+ }
+
+ instructions.add(frame);
}
private FrameType getFrameType(BasicBlock liveBlock, Value local) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
index 92645c4..7892afb 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
@@ -20,6 +20,7 @@
import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.code.CanonicalPositions;
import com.android.tools.r8.ir.code.CatchHandlers;
@@ -182,7 +183,6 @@
private CfState state;
private final CfCode code;
private final DexEncodedMethod method;
- private final Position callerPosition;
private final Origin origin;
private final Reference2IntMap<CfLabel> labelOffsets = new Reference2IntOpenHashMap<>();
@@ -200,12 +200,12 @@
public CfSourceCode(
CfCode code,
DexEncodedMethod method,
+ DexMethod originalMethod,
Position callerPosition,
Origin origin,
boolean preserveCaller) {
this.code = code;
this.method = method;
- this.callerPosition = callerPosition;
this.origin = origin;
int cfPositionCount = 0;
for (int i = 0; i < code.getInstructions().size(); i++) {
@@ -219,7 +219,7 @@
}
this.state = new CfState(origin);
canonicalPositions =
- new CanonicalPositions(callerPosition, preserveCaller, cfPositionCount, this.method.method);
+ new CanonicalPositions(callerPosition, preserveCaller, cfPositionCount, originalMethod);
}
@Override
@@ -328,7 +328,7 @@
public void buildPrelude(IRBuilder builder) {
assert !inPrelude;
inPrelude = true;
- state.buildPrelude();
+ state.buildPrelude(canonicalPositions.getPreamblePosition());
setLocalVariableLists();
buildArgumentInstructions(builder);
recordStateForTarget(0, state.getSnapshot());
@@ -395,14 +395,8 @@
if (instruction instanceof CfPosition) {
CfPosition cfPosition = (CfPosition) instruction;
Position position = cfPosition.getPosition();
- Position newPosition =
- canonicalPositions.getCanonical(
- new Position(
- position.line,
- position.file,
- position.method,
- canonicalPositions.canonicalizeCallerPosition(position.callerPosition)));
- CfPosition newCfPosition = new CfPosition(cfPosition.getLabel(), newPosition);
+ CfPosition newCfPosition =
+ new CfPosition(cfPosition.getLabel(), getCanonicalPosition(position));
newCfPosition.buildIR(builder, state, this);
} else {
instruction.buildIR(builder, state, this);
@@ -441,7 +435,8 @@
for (int i = 0; i < stack.length; i++) {
stack[i] = convertUninitialized(frame.getStack().get(i));
}
- state.setStateFromFrame(locals, stack, getDebugPositionAtOffset(currentInstructionIndex));
+ state.setStateFromFrame(
+ locals, stack, getCanonicalDebugPositionAtOffset(currentInstructionIndex));
}
private DexType convertUninitialized(FrameType type) {
@@ -624,7 +619,7 @@
}
@Override
- public Position getDebugPositionAtOffset(int offset) {
+ public Position getCanonicalDebugPositionAtOffset(int offset) {
while (offset + 1 < code.getInstructions().size()) {
CfInstruction insn = code.getInstructions().get(offset);
if (!(insn instanceof CfLabel) && !(insn instanceof CfFrame)) {
@@ -636,13 +631,22 @@
offset -= 1;
}
if (offset < 0) {
- return Position.noneWithMethod(method.method, callerPosition);
+ return canonicalPositions.getPreamblePosition();
}
- return ((CfPosition) code.getInstructions().get(offset)).getPosition();
+ return getCanonicalPosition(((CfPosition) code.getInstructions().get(offset)).getPosition());
}
@Override
public Position getCurrentPosition() {
return state.getPosition();
}
+
+ private Position getCanonicalPosition(Position position) {
+ return canonicalPositions.getCanonical(
+ new Position(
+ position.line,
+ position.file,
+ position.method,
+ canonicalPositions.canonicalizeCallerPosition(position.callerPosition)));
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfState.java b/src/main/java/com/android/tools/r8/ir/conversion/CfState.java
index 8174797..7f4ee72 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfState.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfState.java
@@ -74,8 +74,8 @@
private static final int MAX_UPDATES = 4;
- public void buildPrelude() {
- current = new BaseSnapshot();
+ public void buildPrelude(Position preamblePosition) {
+ current = new BaseSnapshot(preamblePosition);
}
public void clear() {
@@ -92,9 +92,9 @@
current = new BaseSnapshot(locals, stack, position);
}
- public void merge(Snapshot snapshot) {
+ public void merge(Snapshot snapshot, Position preamblePosition) {
if (current == null) {
- current = snapshot == null ? new BaseSnapshot() : snapshot;
+ current = snapshot == null ? new BaseSnapshot(preamblePosition) : snapshot;
} else {
current = merge(current, snapshot, origin);
}
@@ -312,8 +312,8 @@
final SlotType[] stack;
final Position position;
- BaseSnapshot() {
- this(0, 0, Position.none());
+ BaseSnapshot(Position preamblePosition) {
+ this(0, 0, preamblePosition);
}
BaseSnapshot(int locals, int stack, Position position) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
index 9a9b836..94516a2 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
@@ -75,26 +75,30 @@
private List<DexDebugEntry> debugEntries = null;
// In case of inlining the position of the invoke in the caller.
- private final DexMethod method;
+ private final DexMethod originalMethod;
public DexSourceCode(
- DexCode code, DexEncodedMethod method, Position callerPosition, boolean preserveCaller) {
+ DexCode code,
+ DexEncodedMethod method,
+ DexMethod originalMethod,
+ Position callerPosition,
+ boolean preserveCaller) {
this.code = code;
this.proto = method.method.proto;
this.accessFlags = method.accessFlags;
- this.method = method.method;
+ this.originalMethod = originalMethod;
argumentTypes = computeArgumentTypes();
DexDebugInfo info = code.getDebugInfo();
if (info != null) {
- debugEntries = info.computeEntries(method.method);
+ debugEntries = info.computeEntries(originalMethod);
}
canonicalPositions =
new CanonicalPositions(
callerPosition,
preserveCaller,
debugEntries == null ? 0 : debugEntries.size(),
- this.method);
+ originalMethod);
}
@Override
@@ -184,7 +188,7 @@
}
@Override
- public Position getDebugPositionAtOffset(int offset) {
+ public Position getCanonicalDebugPositionAtOffset(int offset) {
DexDebugEntry entry = getDebugEntryAtOffset(offset);
return entry == null
? canonicalPositions.getPreamblePosition()
@@ -253,7 +257,7 @@
private Position getCanonicalPositionAppendCaller(DexDebugEntry entry) {
// If this instruction has already been inlined then this.method must be the outermost caller.
assert entry.callerPosition == null
- || entry.callerPosition.getOutermostCaller().method == method;
+ || entry.callerPosition.getOutermostCaller().method == originalMethod;
return canonicalPositions.getCanonical(
new Position(
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 963cf41..e66ddfc 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -87,6 +87,7 @@
import com.android.tools.r8.utils.Pair;
import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntArraySet;
@@ -309,6 +310,7 @@
// Basic blocks. Added after processing from the worklist.
private final LinkedList<BasicBlock> blocks = new LinkedList<>();
+ private BasicBlock entryBlock = null;
private BasicBlock currentBlock = null;
final private ValueNumberGenerator valueNumberGenerator;
private final DexEncodedMethod method;
@@ -324,24 +326,31 @@
private Value previousLocalValue = null;
private final List<Value> debugLocalReads = new ArrayList<>();
+ // Lazily populated list of local values that are referenced without being actually defined.
+ private Int2ReferenceMap<List<Value>> uninitializedDebugLocalValues = null;
+
private int nextBlockNumber = 0;
// Flag indicating if the instructions define values with imprecise types.
private boolean hasImpreciseInstructionOutValueTypes = false;
- public IRBuilder(DexEncodedMethod method, AppInfo appInfo,
- SourceCode source, InternalOptions options) {
+ public IRBuilder(
+ DexEncodedMethod method, AppInfo appInfo, SourceCode source, InternalOptions options) {
this(method, appInfo, source, options, new ValueNumberGenerator());
}
public IRBuilder(
- DexEncodedMethod method, AppInfo appInfo, SourceCode source,
- InternalOptions options, ValueNumberGenerator valueNumberGenerator) {
+ DexEncodedMethod method,
+ AppInfo appInfo,
+ SourceCode source,
+ InternalOptions options,
+ ValueNumberGenerator valueNumberGenerator) {
assert source != null;
this.method = method;
this.appInfo = appInfo;
this.source = source;
- this.valueNumberGenerator = valueNumberGenerator;
+ this.valueNumberGenerator =
+ valueNumberGenerator != null ? valueNumberGenerator : new ValueNumberGenerator();
this.options = options;
}
@@ -416,6 +425,7 @@
processedInstructions = null;
setCurrentBlock(targets.get(INITIAL_BLOCK_OFFSET).block);
+ entryBlock = currentBlock;
source.buildPrelude(this);
// Process normal blocks reachable from the entry block using a worklist of reachable
@@ -434,6 +444,21 @@
// Insert debug positions so all position changes are marked by an explicit instruction.
boolean hasDebugPositions = insertDebugPositions();
+ // Insert definitions for all uninitialized local values.
+ if (uninitializedDebugLocalValues != null) {
+ InstructionListIterator it = entryBlock.listIterator();
+ it.nextUntil(i -> !i.isArgument());
+ it.previous();
+ for (List<Value> values : uninitializedDebugLocalValues.values()) {
+ for (Value value : values) {
+ Instruction def = new DebugLocalUninitialized(value);
+ def.setBlock(entryBlock);
+ def.setPosition(Position.none());
+ it.add(def);
+ }
+ }
+ }
+
// Clear all reaching definitions to free up memory (and avoid invalid use).
for (BasicBlock block : blocks) {
block.clearCurrentDefinitions();
@@ -592,7 +617,7 @@
int moveExceptionDest = source.getMoveExceptionRegister(targetIndex);
if (moveExceptionDest >= 0) {
Value out = writeRegister(moveExceptionDest, ValueType.OBJECT, ThrowingInfo.NO_THROW, null);
- Position position = source.getDebugPositionAtOffset(moveExceptionItem.targetOffset);
+ Position position = source.getCanonicalDebugPositionAtOffset(moveExceptionItem.targetOffset);
MoveException moveException = new MoveException(out);
moveException.setPosition(position);
currentBlock.add(moveException);
@@ -644,15 +669,6 @@
addInstruction(new Argument(value));
}
- public void addDebugUninitialized(int register, ValueType type) {
- if (!options.debug) {
- return;
- }
- Value value = writeRegister(register, type, ThrowingInfo.NO_THROW, null);
- assert !value.hasLocalInfo();
- addInstruction(new DebugLocalUninitialized(value));
- }
-
private void addDebugLocalWrite(ValueType type, int dest, Value in) {
assert options.debug;
Value out = writeRegister(dest, type, ThrowingInfo.NO_THROW);
@@ -666,7 +682,7 @@
assert local != null;
assert local == getIncomingLocal(register);
ValueType valueType = ValueType.fromDexType(local.type);
- return readRegisterIgnoreLocal(register, valueType);
+ return readRegisterIgnoreLocal(register, valueType, local);
}
private static boolean isValidFor(Value value, DebugLocalInfo local) {
@@ -693,7 +709,7 @@
assert local == getOutgoingLocal(register);
ValueType valueType = ValueType.fromDexType(local.type);
// TODO(mathiasr): Here we create a Phi with type based on debug info. That's just wrong!
- Value incomingValue = readRegisterIgnoreLocal(register, valueType);
+ Value incomingValue = readRegisterIgnoreLocal(register, valueType, local);
// TODO(mathiasr): This can be simplified once trivial phi removal is local-info aware.
if (incomingValue.isPhi() || incomingValue.getLocalInfo() != local) {
@@ -1668,8 +1684,7 @@
return value;
}
- private Value readRegisterIgnoreLocal(int register, ValueType type) {
- DebugLocalInfo local = getIncomingLocal(register);
+ private Value readRegisterIgnoreLocal(int register, ValueType type, DebugLocalInfo local) {
return readRegister(register, type, currentBlock, EdgeType.NON_EDGE, local);
}
@@ -1703,7 +1718,14 @@
}
// If the register still has unknown value create a phi value for it.
if (value == null) {
- if (!block.isSealed()) {
+ if (block == entryBlock && local != null) {
+ assert block.getPredecessors().isEmpty();
+ // If a debug-local is referenced at the entry block, lazily introduce an SSA value for it.
+ // This value must *not* be added to the entry blocks current definitions since
+ // uninitialized debug-locals may be reference at the same register/local-index yet be of
+ // different types (eg, int in one part of the CFG and float in a disjoint part).
+ value = getUninitializedDebugLocalValue(register, type, local);
+ } else if (!block.isSealed()) {
assert !blocks.isEmpty() : "No write to " + register;
Phi phi = new Phi(valueNumberGenerator.next(), block, type, local);
block.addIncompletePhi(register, phi, readingEdge);
@@ -1730,6 +1752,29 @@
return value;
}
+ private Value getUninitializedDebugLocalValue(
+ int register, ValueType type, DebugLocalInfo local) {
+ assert type == ValueType.fromDexType(local.type);
+ if (uninitializedDebugLocalValues == null) {
+ uninitializedDebugLocalValues = new Int2ReferenceOpenHashMap<>();
+ }
+ List<Value> values = uninitializedDebugLocalValues.get(register);
+ if (values != null) {
+ for (Value value : values) {
+ if (value.outType() == type) {
+ return value;
+ }
+ }
+ } else {
+ uninitializedDebugLocalValues.put(register, new ArrayList<>(2));
+ }
+ // Create a new SSA value for the uninitialized local value.
+ // Note that the uninitialized local value *does not* itself have local information!
+ Value value = new Value(valueNumberGenerator.next(), type, null);
+ uninitializedDebugLocalValues.get(register).add(value);
+ return value;
+ }
+
public Value readNumericRegister(int register, NumericType type) {
return readRegister(register, ValueType.fromNumericType(type));
}
@@ -1781,7 +1826,7 @@
previousLocalValue =
(incomingLocal == null || incomingLocal != outgoingLocal)
? null
- : readRegisterIgnoreLocal(register, type);
+ : readRegisterIgnoreLocal(register, type, incomingLocal);
return writeRegister(register, type, throwing, outgoingLocal);
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index d5f923e..15627e0 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexApplication.Builder;
+import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
@@ -41,7 +42,7 @@
import com.android.tools.r8.ir.optimize.DeadCodeRemover;
import com.android.tools.r8.ir.optimize.Devirtualizer;
import com.android.tools.r8.ir.optimize.Inliner;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.MemberValuePropagation;
import com.android.tools.r8.ir.optimize.NonNullTracker;
import com.android.tools.r8.ir.optimize.Outliner;
@@ -49,8 +50,10 @@
import com.android.tools.r8.ir.optimize.RedundantFieldLoadElimination;
import com.android.tools.r8.ir.optimize.classinliner.ClassInliner;
import com.android.tools.r8.ir.optimize.lambda.LambdaMerger;
+import com.android.tools.r8.ir.optimize.staticizer.ClassStaticizer;
import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
import com.android.tools.r8.ir.regalloc.RegisterAllocator;
+import com.android.tools.r8.kotlin.KotlinInfo;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.naming.IdentifierNameStringMarker;
import com.android.tools.r8.shaking.protolite.ProtoLitePruner;
@@ -92,9 +95,10 @@
private final InterfaceMethodRewriter interfaceMethodRewriter;
private final LambdaMerger lambdaMerger;
private final ClassInliner classInliner;
+ private final ClassStaticizer classStaticizer;
private final InternalOptions options;
private final CfgPrinter printer;
- private final GraphLense graphLense;
+ private GraphLense graphLense;
private final CodeRewriter codeRewriter;
private final MemberValuePropagation memberValuePropagation;
private final LensCodeRewriter lensCodeRewriter;
@@ -105,9 +109,10 @@
private final Devirtualizer devirtualizer;
private final CovariantReturnTypeAnnotationTransformer covariantReturnTypeAnnotationTransformer;
- private final boolean enableWholeProgramOptimizations;
+ public final boolean enableWholeProgramOptimizations;
private final OptimizationFeedback ignoreOptimizationFeedback = new OptimizationFeedbackIgnore();
+ private final OptimizationFeedback simpleOptimizationFeedback = new OptimizationFeedbackSimple();
private DexString highestSortingString;
private IRConverter(
@@ -141,8 +146,8 @@
if (enableWholeProgramOptimizations) {
assert appInfo.hasLiveness();
this.nonNullTracker = new NonNullTracker();
- this.inliner = new Inliner(appInfo.withLiveness(), graphLense, options);
- this.outliner = new Outliner(appInfo.withLiveness(), options);
+ this.inliner = new Inliner(this, options);
+ this.outliner = new Outliner(appInfo.withLiveness(), options, this);
this.memberValuePropagation =
options.enableValuePropagation ?
new MemberValuePropagation(appInfo.withLiveness()) : null;
@@ -176,8 +181,20 @@
}
this.classInliner =
(options.enableClassInlining && options.enableInlining && inliner != null)
- ? new ClassInliner(appInfo.dexItemFactory, options.classInliningInstructionLimit)
+ ? new ClassInliner(
+ appInfo.dexItemFactory, lambdaRewriter, options.classInliningInstructionLimit)
: null;
+ this.classStaticizer = options.enableClassStaticizer && appInfo.hasLiveness()
+ ? new ClassStaticizer(appInfo.withLiveness(), this) : null;
+ }
+
+ public void setGraphLense(GraphLense graphLense) {
+ assert graphLense != null;
+ this.graphLense = graphLense;
+ }
+
+ public GraphLense getGraphLense() {
+ return graphLense;
}
/**
@@ -253,6 +270,18 @@
}
}
+ private void staticizeClasses(OptimizationFeedback feedback) {
+ if (classStaticizer != null) {
+ classStaticizer.staticizeCandidates(feedback);
+ }
+ }
+
+ private void collectStaticizerCandidates(DexApplication application) {
+ if (classStaticizer != null) {
+ classStaticizer.collectCandidates(application);
+ }
+ }
+
private void desugarInterfaceMethods(
Builder<?> builder, InterfaceMethodRewriter.Flavor includeAllResources) {
if (interfaceMethodRewriter != null) {
@@ -356,24 +385,34 @@
ExecutorService executor) throws ExecutionException {
List<Future<?>> futures = new ArrayList<>();
for (DexProgramClass clazz : classes) {
- futures.add(
- executor.submit(
- () -> {
- clazz.forEachMethodThrowing(this::convertMethodToDex);
- return null; // we want a Callable not a Runnable to be able to throw
- }));
+ futures.add(executor.submit(() -> convertMethodsToDex(clazz)));
}
ThreadUtils.awaitFutures(futures);
}
- void convertMethodToDex(DexEncodedMethod method) {
+ private void convertMethodsToDex(DexProgramClass clazz) {
+ // When converting all methods on a class always convert <clinit> first.
+ for (DexEncodedMethod method : clazz.directMethods()) {
+ if (method.isClassInitializer()) {
+ convertMethodToDex(method);
+ break;
+ }
+ }
+ clazz.forEachMethod(method -> {
+ if (!method.isClassInitializer()) {
+ convertMethodToDex(method);
+ }
+ });
+ }
+
+ private void convertMethodToDex(DexEncodedMethod method) {
assert options.isGeneratingDex();
if (method.getCode() != null) {
boolean matchesMethodFilter = options.methodMatchesFilter(method);
if (matchesMethodFilter) {
if (!(options.passthroughDexCode && method.getCode().isDexCode())) {
// We do not process in call graph order, so anything could be a leaf.
- rewriteCode(method, ignoreOptimizationFeedback, x -> true, CallSiteInformation.empty(),
+ rewriteCode(method, simpleOptimizationFeedback, x -> true, CallSiteInformation.empty(),
Outliner::noProcessing);
}
updateHighestSortingStrings(method);
@@ -394,6 +433,7 @@
throws ExecutionException {
removeLambdaDeserializationMethods();
collectLambdaMergingCandidates(application);
+ collectStaticizerCandidates(application);
// The process is in two phases.
// 1) Subject all DexEncodedMethods to optimization (except outlining).
@@ -405,8 +445,8 @@
OptimizationFeedback directFeedback = new OptimizationFeedbackDirect();
{
timing.begin("Build call graph");
- CallGraph callGraph = CallGraph
- .build(application, appInfo.withLiveness(), graphLense, options);
+ CallGraph callGraph =
+ CallGraph.build(application, appInfo.withLiveness(), graphLense, options, timing);
timing.end();
timing.begin("IR conversion phase 1");
BiConsumer<IRCode, DexEncodedMethod> outlineHandler =
@@ -424,6 +464,8 @@
Builder<?> builder = application.builder();
builder.setHighestSortingString(highestSortingString);
+ staticizeClasses(directFeedback);
+
// Second inlining pass for dealing with double inline callers.
if (inliner != null) {
// Use direct feedback still, since methods after inlining may
@@ -481,7 +523,8 @@
executorService.submit(
() -> {
IRCode code =
- method.buildIR(appInfo, options, appInfo.originFor(method.method.holder));
+ method.buildIR(
+ appInfo, graphLense, options, appInfo.originFor(method.method.holder));
assert code != null;
assert !method.getCode().isOutlineCode();
// Instead of repeating all the optimizations of rewriteCode(), only run the
@@ -565,7 +608,7 @@
try {
codeRewriter.enterCachedClass(clazz);
// Process the generated class, but don't apply any outlining.
- clazz.forEachMethodThrowing(this::optimizeSynthesizedMethod);
+ clazz.forEachMethod(this::optimizeSynthesizedMethod);
} finally {
codeRewriter.leaveCachedClass(clazz);
}
@@ -593,7 +636,7 @@
rewriteCode(method, feedback, isProcessedConcurrently, callSiteInformation, outlineHandler);
} else {
// Mark abstract methods as processed as well.
- method.markProcessed(Constraint.NEVER);
+ method.markProcessed(ConstraintWithTarget.NEVER);
}
}
@@ -620,12 +663,13 @@
method.toSourceString(), logCode(options, method));
}
if (options.skipIR) {
- feedback.markProcessed(method, Constraint.NEVER);
+ feedback.markProcessed(method, ConstraintWithTarget.NEVER);
return;
}
- IRCode code = method.buildIR(appInfo, options, appInfo.originFor(method.method.holder));
+ IRCode code =
+ method.buildIR(appInfo, graphLense, options, appInfo.originFor(method.method.holder));
if (code == null) {
- feedback.markProcessed(method, Constraint.NEVER);
+ feedback.markProcessed(method, ConstraintWithTarget.NEVER);
return;
}
if (Log.ENABLED) {
@@ -635,9 +679,10 @@
printC1VisualizerHeader(method);
printMethod(code, "Initial IR (SSA)");
- if (method.getCode() != null && method.getCode().isJarCode() &&
- appInfo.definitionFor(method.method.holder).hasKotlinInfo()) {
- computeKotlinNotNullParamHints(feedback, method, code);
+ DexClass holder = appInfo.definitionFor(method.method.holder);
+ if (method.getCode() != null && method.getCode().isJarCode()
+ && holder.hasKotlinInfo()) {
+ computeKotlinNotNullParamHints(feedback, holder.getKotlinInfo(), method, code);
}
if (options.canHaveArtStringNewInitBug()) {
@@ -659,6 +704,11 @@
}
}
+ if (classStaticizer != null) {
+ classStaticizer.fixupMethodCode(method, code);
+ assert code.isConsistentSSA();
+ }
+
if (identifierNameStringMarker != null) {
identifierNameStringMarker.decoupleIdentifierNameStringsInMethod(method, code);
assert code.isConsistentSSA();
@@ -672,7 +722,7 @@
codeRewriter.removeSwitchMaps(code);
}
if (options.disableAssertions) {
- codeRewriter.disableAssertions(code);
+ codeRewriter.disableAssertions(appInfo, method, code, feedback);
}
if (options.enableNonNullTracking && nonNullTracker != null) {
nonNullTracker.addNonNull(code);
@@ -733,11 +783,6 @@
assert code.isConsistentSSA();
}
- if (interfaceMethodRewriter != null) {
- interfaceMethodRewriter.rewriteMethodReferences(method, code);
- assert code.isConsistentSSA();
- }
-
if (classInliner != null) {
// Class inliner should work before lambda merger, so if it inlines the
// lambda, it is not get collected by merger.
@@ -755,6 +800,11 @@
assert code.isConsistentSSA();
}
+ if (interfaceMethodRewriter != null) {
+ interfaceMethodRewriter.rewriteMethodReferences(method, code);
+ assert code.isConsistentSSA();
+ }
+
if (lambdaMerger != null) {
lambdaMerger.processMethodCode(method, code);
assert code.isConsistentSSA();
@@ -785,6 +835,9 @@
codeRewriter.identifyTrivialInitializer(method, code, feedback);
}
codeRewriter.identifyParameterUsages(method, code, feedback);
+ if (classStaticizer != null) {
+ classStaticizer.examineMethodCode(method, code);
+ }
if (options.canHaveNumberConversionRegisterAllocationBug()) {
codeRewriter.workaroundNumberConversionRegisterAllocationBug(code);
@@ -795,8 +848,19 @@
}
private void computeKotlinNotNullParamHints(
- OptimizationFeedback feedback, DexEncodedMethod method, IRCode code) {
- // Try to infer Kotlin non-null parameter check to use it as a hint.
+ OptimizationFeedback feedback, KotlinInfo kotlinInfo, DexEncodedMethod method, IRCode code) {
+ // Use non-null parameter hints in Kotlin metadata if available.
+ if (kotlinInfo.hasNonNullParameterHints()) {
+ BitSet hintFromMetadata = kotlinInfo.lookupNonNullParameterHint(
+ method.method.name.toString(), method.method.proto.toDescriptorString());
+ if (hintFromMetadata != null) {
+ if (hintFromMetadata.length() > 0) {
+ feedback.setKotlinNotNullParamHints(method, hintFromMetadata);
+ }
+ return;
+ }
+ }
+ // Otherwise, fall back to inspecting the code.
List<Value> arguments = code.collectArguments(true);
BitSet paramsCheckedForNull = new BitSet();
DexMethod checkParameterIsNotNull =
@@ -857,9 +921,9 @@
private void markProcessed(DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
// After all the optimizations have take place, we compute whether method should be inlinedex.
- Constraint state;
+ ConstraintWithTarget state;
if (!options.enableInlining || inliner == null) {
- state = Constraint.NEVER;
+ state = ConstraintWithTarget.NEVER;
} else {
state = inliner.computeInliningConstraint(code, method);
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
index 23d9a73..01c4dd0 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
@@ -29,7 +29,6 @@
import com.android.tools.r8.ir.conversion.JarState.Slot;
import com.android.tools.r8.logging.Log;
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
-import it.unimi.dsi.fastutil.ints.Int2ReferenceMap.Entry;
import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
import java.io.PrintWriter;
@@ -187,20 +186,20 @@
// Cooked position to indicate positions in synthesized code (ie, for synchronization).
private Position syntheticPosition = null;
- private final DexMethod method;
+ private final DexMethod originalMethod;
private final Position callerPosition;
public JarSourceCode(
DexType clazz,
MethodNode node,
JarApplicationReader application,
- DexMethod method,
+ DexMethod originalMethod,
Position callerPosition) {
assert node != null;
assert node.desc != null;
this.node = node;
this.application = application;
- this.method = method;
+ this.originalMethod = originalMethod;
this.clazz = clazz;
this.callerPosition = callerPosition;
parameterTypes = Arrays.asList(application.getArgumentTypes(node.desc));
@@ -275,7 +274,6 @@
// Record types for arguments.
Int2ReferenceMap<ValueType> argumentLocals = recordArgumentTypes();
Int2ReferenceMap<ValueType> initializedLocals = new Int2ReferenceOpenHashMap<>(argumentLocals);
- Int2ReferenceMap<ValueType> uninitializedLocals = new Int2ReferenceOpenHashMap<>();
// Initialize all non-argument locals to ensure safe insertion of debug-local instructions.
for (Object o : node.localVariables) {
LocalVariableNode local = (LocalVariableNode) o;
@@ -320,11 +318,9 @@
int localRegister = state.getLocalRegister(local.index, localType);
ValueType existingLocalType = initializedLocals.get(localRegister);
if (existingLocalType == null) {
- // For uninitialized entries write the local to ensure it exists in the local state.
int writeRegister = state.writeLocal(local.index, localType);
assert writeRegister == localRegister;
initializedLocals.put(localRegister, localValueType);
- uninitializedLocals.put(localRegister, localValueType);
}
}
@@ -335,10 +331,6 @@
// for arguments.
buildArgumentInstructions(builder);
- for (Entry<ValueType> entry : uninitializedLocals.int2ReferenceEntrySet()) {
- builder.addDebugUninitialized(entry.getIntKey(), entry.getValue());
- }
-
// Add debug information for all locals at the initial label.
for (Local local : state.getLocalsToOpen()) {
if (!argumentLocals.containsKey(local.slot.register)) {
@@ -493,7 +485,7 @@
// Don't include line changes when processing a label. Doing so will end up emitting local
// writes after the line has changed and thus causing locals to become visible too late.
currentPosition =
- getDebugPositionAtOffset(
+ getCanonicalDebugPositionAtOffset(
((instructionIndex > 0) && (insn instanceof LabelNode))
? instructionIndex - 1
: instructionIndex);
@@ -2832,7 +2824,7 @@
}
@Override
- public Position getDebugPositionAtOffset(int offset) {
+ public Position getCanonicalDebugPositionAtOffset(int offset) {
if (offset == EXCEPTIONAL_SYNC_EXIT_OFFSET) {
return getExceptionalExitPosition();
}
@@ -2862,12 +2854,12 @@
private Position getCanonicalPosition(int line) {
return canonicalPositions.computeIfAbsent(
- line, l -> new Position(l, null, method, callerPosition));
+ line, l -> new Position(l, null, originalMethod, callerPosition));
}
private Position getPreamblePosition() {
if (preamblePosition == null) {
- preamblePosition = Position.synthetic(0, method, null);
+ preamblePosition = Position.synthetic(0, originalMethod, null);
}
return preamblePosition;
}
@@ -2891,8 +2883,8 @@
}
syntheticPosition =
(min == Integer.MAX_VALUE)
- ? Position.noneWithMethod(method, callerPosition)
- : Position.synthetic(min < max ? min - 1 : min, method, callerPosition);
+ ? Position.noneWithMethod(originalMethod, callerPosition)
+ : Position.synthetic(min < max ? min - 1 : min, originalMethod, callerPosition);
}
return syntheticPosition;
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/JarState.java b/src/main/java/com/android/tools/r8/ir/conversion/JarState.java
index 99bca1c..b67d12e 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/JarState.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/JarState.java
@@ -339,6 +339,12 @@
}
int start = source.getOffset(node.start);
int end = source.getOffset(node.end);
+ // If the locals information is invalid the node start or end could be a label that does
+ // not exist in the program.
+ if (start == -1 || end == -1) {
+ throw new InvalidDebugInfoException(
+ "Locals information for '" + node.name + "' has undefined start or end point.");
+ }
// Ensure that there is an entry at the starting point of the local.
{
LocalsAtOffset atStart;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 1877053..e9b789a 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -39,17 +39,17 @@
import com.android.tools.r8.ir.code.StaticPut;
import com.android.tools.r8.ir.code.Value;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
public class LensCodeRewriter {
private final GraphLense graphLense;
private final AppInfoWithSubtyping appInfo;
- private final Map<DexProto, DexProto> protoFixupCache = new HashMap<>();
+ private final Map<DexProto, DexProto> protoFixupCache = new ConcurrentHashMap<>();
public LensCodeRewriter(GraphLense graphLense, AppInfoWithSubtyping appInfo) {
this.graphLense = graphLense;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java
index c26aecf..0833579 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java
@@ -8,7 +8,7 @@
import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
import com.android.tools.r8.graph.ParameterUsagesInfo;
import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import java.util.BitSet;
public interface OptimizationFeedback {
@@ -16,11 +16,12 @@
void methodReturnsConstant(DexEncodedMethod method, long value);
void methodNeverReturnsNull(DexEncodedMethod method);
void methodNeverReturnsNormally(DexEncodedMethod method);
- void markProcessed(DexEncodedMethod method, Constraint state);
+ void markProcessed(DexEncodedMethod method, ConstraintWithTarget state);
void markCheckNullReceiverBeforeAnySideEffect(DexEncodedMethod method, boolean mark);
void markTriggerClassInitBeforeAnySideEffect(DexEncodedMethod method, boolean mark);
void setClassInlinerEligibility(DexEncodedMethod method, ClassInlinerEligibility eligibility);
void setTrivialInitializer(DexEncodedMethod method, TrivialInitializer info);
+ void setInitializerEnablingJavaAssertions(DexEncodedMethod method);
void setParameterUsages(DexEncodedMethod method, ParameterUsagesInfo parameterUsagesInfo);
void setKotlinNotNullParamHints(DexEncodedMethod method, BitSet hints);
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDirect.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDirect.java
index 2b1a6db..16267f6 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDirect.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDirect.java
@@ -8,7 +8,7 @@
import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
import com.android.tools.r8.graph.ParameterUsagesInfo;
import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import java.util.BitSet;
public class OptimizationFeedbackDirect implements OptimizationFeedback {
@@ -34,7 +34,7 @@
}
@Override
- public void markProcessed(DexEncodedMethod method, Constraint state) {
+ public void markProcessed(DexEncodedMethod method, ConstraintWithTarget state) {
method.markProcessed(state);
}
@@ -60,6 +60,11 @@
}
@Override
+ public void setInitializerEnablingJavaAssertions(DexEncodedMethod method) {
+ method.setInitializerEnablingJavaAssertions();
+ }
+
+ @Override
public void setParameterUsages(DexEncodedMethod method, ParameterUsagesInfo parameterUsagesInfo) {
method.setParameterUsages(parameterUsagesInfo);
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java
index a547b36..a27f200 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java
@@ -8,7 +8,7 @@
import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
import com.android.tools.r8.graph.ParameterUsagesInfo;
import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import java.util.BitSet;
public class OptimizationFeedbackIgnore implements OptimizationFeedback {
@@ -26,7 +26,7 @@
public void methodNeverReturnsNormally(DexEncodedMethod method) {}
@Override
- public void markProcessed(DexEncodedMethod method, Constraint state) {}
+ public void markProcessed(DexEncodedMethod method, ConstraintWithTarget state) {}
@Override
public void markCheckNullReceiverBeforeAnySideEffect(DexEncodedMethod method, boolean mark) {}
@@ -44,6 +44,10 @@
}
@Override
+ public void setInitializerEnablingJavaAssertions(DexEncodedMethod method) {
+ }
+
+ @Override
public void setParameterUsages(DexEncodedMethod method, ParameterUsagesInfo parameterUsagesInfo) {
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackSimple.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackSimple.java
new file mode 100644
index 0000000..2583f8f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackSimple.java
@@ -0,0 +1,77 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.conversion;
+
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
+import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer;
+import com.android.tools.r8.graph.ParameterUsagesInfo;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
+import java.util.BitSet;
+
+public class OptimizationFeedbackSimple implements OptimizationFeedback {
+
+ @Override
+ public void methodReturnsArgument(DexEncodedMethod method, int argument) {
+ // Ignored.
+ }
+
+ @Override
+ public void methodReturnsConstant(DexEncodedMethod method, long value) {
+ // Ignored.
+ }
+
+ @Override
+ public void methodNeverReturnsNull(DexEncodedMethod method) {
+ // Ignored.
+ }
+
+ @Override
+ public void methodNeverReturnsNormally(DexEncodedMethod method) {
+ // Ignored.
+ }
+
+ @Override
+ public void markProcessed(DexEncodedMethod method, ConstraintWithTarget state) {
+ // Just as processed, don't provide any inlining constraints.
+ method.markProcessed(ConstraintWithTarget.NEVER);
+ }
+
+ @Override
+ public void markCheckNullReceiverBeforeAnySideEffect(DexEncodedMethod method, boolean mark) {
+ // Ignored.
+ }
+
+ @Override
+ public void markTriggerClassInitBeforeAnySideEffect(DexEncodedMethod method, boolean mark) {
+ // Ignored.
+ }
+
+ @Override
+ public void setClassInlinerEligibility(
+ DexEncodedMethod method, ClassInlinerEligibility eligibility) {
+ // Ignored.
+ }
+
+ @Override
+ public void setTrivialInitializer(DexEncodedMethod method, TrivialInitializer info) {
+ // Ignored.
+ }
+
+ @Override
+ public void setInitializerEnablingJavaAssertions(DexEncodedMethod method) {
+ method.setInitializerEnablingJavaAssertions();
+ }
+
+ @Override
+ public void setParameterUsages(DexEncodedMethod method, ParameterUsagesInfo parameterUsagesInfo) {
+ // Ignored.
+ }
+
+ @Override
+ public void setKotlinNotNullParamHints(DexEncodedMethod method, BitSet hints) {
+ // Ignored.
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java
index bf2b51f..2bc2aa1 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java
@@ -27,7 +27,7 @@
Position getCurrentPosition();
- Position getDebugPositionAtOffset(int offset);
+ Position getCanonicalDebugPositionAtOffset(int offset);
/**
* Trace block structure of the source-program.
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
index f4a76e6..c538e86 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.graph.ClassAccessFlags;
import com.android.tools.r8.graph.Code;
import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
@@ -18,11 +19,15 @@
import com.android.tools.r8.graph.DexTypeList;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.origin.SynthesizedOrigin;
+import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Deque;
+import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
// Default and static method interface desugaring processor for interfaces.
//
@@ -78,7 +83,7 @@
}
// Remove bridge methods.
- if (!virtual.accessFlags.isBridge()) {
+ if (interfaceMethodRemovalChangesApi(virtual, iface)) {
remainingMethods.add(virtual);
}
}
@@ -180,6 +185,38 @@
companionClasses.put(iface, companionClass);
}
+ // Returns true if the given interface method must be kept on [iface] after moving its
+ // implementation to the companion class of [iface]. This is always the case for non-bridge
+ // methods. Bridge methods that does not override an implementation in a super-interface must
+ // also be kept (such a situation can happen if the vertical class merger merges two interfaces).
+ private boolean interfaceMethodRemovalChangesApi(DexEncodedMethod method, DexClass iface) {
+ if (method.accessFlags.isBridge()) {
+ Deque<DexType> worklist = new ArrayDeque<>();
+ Set<DexType> seenBefore = new HashSet<>();
+ if (iface.superType != null) {
+ worklist.add(iface.superType);
+ }
+ Collections.addAll(worklist, iface.interfaces.values);
+ while (!worklist.isEmpty()) {
+ DexType superType = worklist.pop();
+ if (!seenBefore.add(superType)) {
+ continue;
+ }
+ DexClass clazz = rewriter.findDefinitionFor(superType);
+ if (clazz != null) {
+ if (clazz.lookupVirtualMethod(method.method) != null) {
+ return false;
+ }
+ if (clazz.superType != null) {
+ worklist.add(clazz.superType);
+ }
+ Collections.addAll(worklist, clazz.interfaces.values);
+ }
+ }
+ }
+ return true;
+ }
+
private boolean isStaticMethod(DexEncodedMethod method) {
if (method.accessFlags.isNative()) {
throw new Unimplemented("Native interface methods are not yet supported.");
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index 81407bd..2fc5e56 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -29,11 +29,13 @@
import com.android.tools.r8.ir.code.Invoke;
import com.android.tools.r8.ir.synthetic.SynthesizedCode;
import com.android.tools.r8.origin.SynthesizedOrigin;
+import com.google.common.base.Suppliers;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Supplier;
/**
* Represents lambda class generated for a lambda descriptor in context
@@ -64,6 +66,8 @@
final Target target;
final AtomicBoolean addToMainDexList = new AtomicBoolean(false);
private final Collection<DexProgramClass> synthesizedFrom = new ArrayList<DexProgramClass>(1);
+ private final Supplier<DexProgramClass> lazyDexClass =
+ Suppliers.memoize(this::synthesizeLambdaClass); // NOTE: thread-safe.
LambdaClass(LambdaRewriter rewriter, DexType accessedFrom,
DexType lambdaClassType, LambdaDescriptor descriptor) {
@@ -119,7 +123,11 @@
return rewriter.factory.createType(lambdaClassDescriptor.toString());
}
- final DexProgramClass synthesizeLambdaClass() {
+ final DexProgramClass getLambdaClass() {
+ return lazyDexClass.get();
+ }
+
+ private DexProgramClass synthesizeLambdaClass() {
return new DexProgramClass(
type,
null,
@@ -171,7 +179,7 @@
Constants.ACC_PUBLIC | Constants.ACC_FINAL, false),
DexAnnotationSet.empty(),
ParameterAnnotationsList.empty(),
- new SynthesizedCode(new LambdaMainMethodSourceCode(this, mainMethod)));
+ new SynthesizedCode(() -> new LambdaMainMethodSourceCode(this, mainMethod)));
// Synthesize bridge methods.
for (DexProto bridgeProto : descriptor.bridges) {
@@ -188,7 +196,7 @@
DexAnnotationSet.empty(),
ParameterAnnotationsList.empty(),
new SynthesizedCode(
- new LambdaBridgeMethodSourceCode(this, mainMethod, bridgeMethod)));
+ () -> new LambdaBridgeMethodSourceCode(this, mainMethod, bridgeMethod)));
}
return methods;
}
@@ -208,7 +216,7 @@
true),
DexAnnotationSet.empty(),
ParameterAnnotationsList.empty(),
- new SynthesizedCode(new LambdaConstructorSourceCode(this)));
+ new SynthesizedCode(() -> new LambdaConstructorSourceCode(this)));
// Class constructor for stateless lambda classes.
if (stateless) {
@@ -489,15 +497,21 @@
if (implMethod.match(encodedMethod)) {
// We need to create a new static method with the same code to be able to safely
// relax its accessibility without making it virtual.
- DexEncodedMethod newMethod = new DexEncodedMethod(
- callTarget, encodedMethod.accessFlags, encodedMethod.annotations,
- encodedMethod.parameterAnnotationsList, encodedMethod.getCode());
- // TODO(ager): Should we give the new first parameter an actual name? Maybe 'this'?
- encodedMethod.accessFlags.setStatic();
- encodedMethod.accessFlags.unsetPrivate();
+ MethodAccessFlags newAccessFlags = encodedMethod.accessFlags.copy();
+ newAccessFlags.setStatic();
+ newAccessFlags.unsetPrivate();
// Always make the method public to provide access when r8 minification is allowed to move
// the lambda class accessing this method to another package (-allowaccessmodification).
- encodedMethod.accessFlags.setPublic();
+ newAccessFlags.setPublic();
+ DexEncodedMethod newMethod =
+ new DexEncodedMethod(
+ callTarget,
+ newAccessFlags,
+ encodedMethod.annotations,
+ encodedMethod.parameterAnnotationsList,
+ encodedMethod.getCode());
+ rewriter.methodMapping.put(encodedMethod.method, callTarget);
+ // TODO(ager): Should we give the new first parameter an actual name? Maybe 'this'?
DexCode dexCode = newMethod.getCode().asDexCode();
dexCode.setDebugInfo(dexCode.debugInfoWithAdditionalFirstParameter(null));
assert (dexCode.getDebugInfo() == null)
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index e7686db..cb6856c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -27,6 +27,8 @@
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.code.ValueType;
import com.android.tools.r8.ir.conversion.IRConverter;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.List;
@@ -62,6 +64,8 @@
final DexString deserializeLambdaMethodName;
final DexProto deserializeLambdaMethodProto;
+ final BiMap<DexMethod, DexMethod> methodMapping = HashBiMap.create();
+
// Maps call sites seen so far to inferred lambda descriptor. It is intended
// to help avoid re-matching call sites we already seen. Note that same call
// site may match one or several lambda classes.
@@ -141,7 +145,6 @@
if (method.name == deserializeLambdaMethodName &&
method.proto == deserializeLambdaMethodProto) {
assert encoded.accessFlags.isStatic();
- assert encoded.accessFlags.isPrivate();
assert encoded.accessFlags.isSynthetic();
DexEncodedMethod[] newMethods = new DexEncodedMethod[methodCount - 1];
@@ -166,14 +169,28 @@
// referenced symbols to make them accessible. This can result in
// method access relaxation or creation of accessor method.
for (LambdaClass lambdaClass : knownLambdaClasses.values()) {
+ // This call may cause methodMapping to be updated.
lambdaClass.target.ensureAccessibility();
}
+ if (converter.enableWholeProgramOptimizations && !methodMapping.isEmpty()) {
+ converter.setGraphLense(
+ new LambdaRewriterGraphLense(methodMapping, converter.getGraphLense(), factory));
+ }
+ }
+
+ /**
+ * Returns a synthetic class for desugared lambda or `null` if the `type`
+ * does not represent one. Method can be called concurrently.
+ */
+ public DexProgramClass getLambdaClass(DexType type) {
+ LambdaClass lambdaClass = getKnown(knownLambdaClasses, type);
+ return lambdaClass == null ? null : lambdaClass.getLambdaClass();
}
/** Generates lambda classes and adds them to the builder. */
public void synthesizeLambdaClasses(Builder<?> builder) {
for (LambdaClass lambdaClass : knownLambdaClasses.values()) {
- DexProgramClass synthesizedClass = lambdaClass.synthesizeLambdaClass();
+ DexProgramClass synthesizedClass = lambdaClass.getLambdaClass();
converter.optimizeSynthesizedClass(synthesizedClass);
builder.addSynthesizedClass(synthesizedClass, lambdaClass.addToMainDexList.get());
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriterGraphLense.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriterGraphLense.java
new file mode 100644
index 0000000..26f890e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriterGraphLense.java
@@ -0,0 +1,40 @@
+// 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.ir.desugar;
+
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
+import com.android.tools.r8.ir.code.Invoke.Type;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.ImmutableBiMap;
+import com.google.common.collect.ImmutableMap;
+
+class LambdaRewriterGraphLense extends NestedGraphLense {
+
+ LambdaRewriterGraphLense(
+ BiMap<DexMethod, DexMethod> methodMapping, GraphLense previous, DexItemFactory factory) {
+ super(
+ ImmutableMap.of(),
+ methodMapping,
+ ImmutableMap.of(),
+ ImmutableBiMap.of(),
+ methodMapping.inverse(),
+ previous,
+ factory);
+ }
+
+ @Override
+ protected Type mapInvocationType(
+ DexMethod newMethod, DexMethod originalMethod, DexEncodedMethod context, Type type) {
+ if (methodMap.get(originalMethod) == newMethod) {
+ assert type == Type.VIRTUAL || type == Type.DIRECT;
+ return Type.STATIC;
+ }
+ return super.mapInvocationType(newMethod, originalMethod, context, type);
+ }
+
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 94e8147..df6e65a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -953,6 +953,14 @@
continue;
}
+ if (insn.isGoto()) {
+ // Trivial goto to the next block.
+ if (insn.asGoto().isTrivialGotoToTheNextBlock(code)) {
+ continue;
+ }
+ return null;
+ }
+
// Other instructions make the instance initializer not eligible.
return null;
}
@@ -1265,7 +1273,29 @@
* }
* </pre>
*/
- public void disableAssertions(IRCode code) {
+ public void disableAssertions(
+ AppInfo appInfo, DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
+ if (method.isClassInitializer()) {
+ if (!hasJavacClinitAssertionCode(code)) {
+ return;
+ }
+ // Mark the clinit as having code to turn on assertions.
+ feedback.setInitializerEnablingJavaAssertions(method);
+ } else {
+ // If the clinit of this class did not have have code to turn on assertions don't try to
+ // remove assertion code from the method.
+ DexClass clazz = appInfo.definitionFor(method.method.holder);
+ if (clazz == null) {
+ return;
+ }
+ DexEncodedMethod clinit = clazz.getClassInitializer();
+ if (clinit == null
+ || !clinit.isProcessed()
+ || !clinit.getOptimizationInfo().isInitializerEnablingJavaAssertions()) {
+ return;
+ }
+ }
+
InstructionIterator iterator = code.instructionIterator();
while (iterator.hasNext()) {
Instruction current = iterator.next();
@@ -1288,6 +1318,78 @@
}
}
+ private boolean isClassDesiredAssertionStatusInvoke(Instruction instruction) {
+ return instruction.isInvokeMethod()
+ && instruction.asInvokeMethod().getInvokedMethod()
+ == dexItemFactory.classMethods.desiredAssertionStatus;
+ }
+
+ private boolean isAssertionsDisabledFieldPut(Instruction instruction) {
+ return instruction.isStaticPut()
+ && instruction.asStaticPut().getField().name == dexItemFactory.assertionsDisabled;
+ }
+
+ private boolean isNotDebugInstruction(Instruction instruction) {
+ return !instruction.isDebugInstruction();
+ }
+
+ private Value blockWithSingleConstNumberAndGoto(BasicBlock block) {
+ InstructionIterator iterator = block.iterator();
+ Instruction constNumber = iterator.nextUntil(this::isNotDebugInstruction);
+ if (constNumber == null || !constNumber.isConstNumber()) {
+ return null;
+ }
+ Instruction exit = iterator.nextUntil(this::isNotDebugInstruction);
+ return exit != null && exit.isGoto() ? constNumber.outValue() : null;
+ }
+
+ private Value blockWithAssertionsDisabledFieldPut(BasicBlock block) {
+ InstructionIterator iterator = block.iterator();
+ Instruction fieldPut = iterator.nextUntil(this::isNotDebugInstruction);
+ return fieldPut != null
+ && isAssertionsDisabledFieldPut(fieldPut) ? fieldPut.inValues().get(0) : null;
+ }
+
+ private boolean hasJavacClinitAssertionCode(IRCode code) {
+ InstructionIterator iterator = code.instructionIterator();
+ Instruction current = iterator.nextUntil(this::isClassDesiredAssertionStatusInvoke);
+ if (current == null) {
+ return false;
+ }
+
+ Value DesiredAssertionStatus = current.outValue();
+ assert iterator.hasNext();
+ current = iterator.next();
+ if (!current.isIf()
+ || !current.asIf().isZeroTest()
+ || current.asIf().inValues().get(0) != DesiredAssertionStatus) {
+ return false;
+ }
+
+ If theIf = current.asIf();
+ BasicBlock trueTarget = theIf.getTrueTarget();
+ BasicBlock falseTarget = theIf.fallthroughBlock();
+ if (trueTarget == falseTarget) {
+ return false;
+ }
+
+ Value trueValue = blockWithSingleConstNumberAndGoto(trueTarget);
+ Value falseValue = blockWithSingleConstNumberAndGoto(falseTarget);
+ if (trueValue == null
+ || falseValue == null
+ || (trueTarget.exit().asGoto().getTarget() != falseTarget.exit().asGoto().getTarget())) {
+ return false;
+ }
+
+ BasicBlock target = trueTarget.exit().asGoto().getTarget();
+ Value storeValue = blockWithAssertionsDisabledFieldPut(target);
+ return storeValue != null
+ && storeValue.isPhi()
+ && storeValue.asPhi().getOperands().size() == 2
+ && storeValue.asPhi().getOperands().contains(trueValue)
+ && storeValue.asPhi().getOperands().contains(falseValue);
+ }
+
// Check if the static put is a constant derived from the class holding the method.
// This checks for java.lang.Class.getName and java.lang.Class.getSimpleName.
private boolean isClassNameConstant(DexEncodedMethod method, StaticPut put) {
@@ -2346,16 +2448,73 @@
// Zero test with a value range, or comparison between between two values,
// each with a value ranges.
if (theIf.isZeroTest()) {
- if (!inValues.get(0).isValueInRange(0)) {
- int cond = Long.signum(inValues.get(0).getValueRange().getMin());
- simplifyIfWithKnownCondition(code, block, theIf, cond);
+ LongInterval interval = inValues.get(0).getValueRange();
+ if (!interval.containsValue(0)) {
+ // Interval doesn't contain zero at all.
+ int sign = Long.signum(interval.getMin());
+ simplifyIfWithKnownCondition(code, block, theIf, sign);
+ } else {
+ // Interval contains zero.
+ switch (theIf.getType()) {
+ case GE:
+ case LT:
+ // [a, b] >= 0 is always true if a >= 0.
+ // [a, b] < 0 is always false if a >= 0.
+ // In both cases a zero condition takes the right branch.
+ if (interval.getMin() == 0) {
+ simplifyIfWithKnownCondition(code, block, theIf, 0);
+ }
+ break;
+ case LE:
+ case GT:
+ // [a, b] <= 0 is always true if b <= 0.
+ // [a, b] > 0 is always false if b <= 0.
+ if (interval.getMax() == 0) {
+ simplifyIfWithKnownCondition(code, block, theIf, 0);
+ }
+ break;
+ case EQ:
+ case NE:
+ // Only a single element interval [0, 0] can be dealt with here.
+ // Such intervals should have been replaced by constants.
+ assert !interval.isSingleValue();
+ break;
+ }
}
} else {
LongInterval leftRange = inValues.get(0).getValueRange();
LongInterval rightRange = inValues.get(1).getValueRange();
+ // Two overlapping ranges. Check for single point overlap.
if (!leftRange.overlapsWith(rightRange)) {
+ // No overlap.
int cond = Long.signum(leftRange.getMin() - rightRange.getMin());
simplifyIfWithKnownCondition(code, block, theIf, cond);
+ } else {
+ // The two intervals overlap. We can simplify if they overlap at the end points.
+ switch (theIf.getType()) {
+ case LT:
+ case GE:
+ // [a, b] < [c, d] is always false when a == d.
+ // [a, b] >= [c, d] is always true when a == d.
+ // In both cases 0 condition will choose the right branch.
+ if (leftRange.getMin() == rightRange.getMax()) {
+ simplifyIfWithKnownCondition(code, block, theIf, 0);
+ }
+ break;
+ case GT:
+ case LE:
+ // [a, b] > [c, d] is always false when b == c.
+ // [a, b] <= [c, d] is always true when b == c.
+ // In both cases 0 condition will choose the right branch.
+ if (leftRange.getMax() == rightRange.getMin()) {
+ simplifyIfWithKnownCondition(code, block, theIf, 0);
+ }
+ break;
+ case EQ:
+ case NE:
+ // Since there is overlap EQ and NE cannot be determined.
+ break;
+ }
}
}
} else if (theIf.isZeroTest() && !inValues.get(0).isConstNumber()
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index 631f234..998ebe9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -93,11 +93,11 @@
private Reason computeInliningReason(DexEncodedMethod target) {
if (target.getOptimizationInfo().forceInline()
|| (inliner.appInfo.hasLiveness()
- && inliner.appInfo.withLiveness().forceInline.contains(target))) {
+ && inliner.appInfo.withLiveness().forceInline.contains(target.method))) {
return Reason.FORCE;
}
if (inliner.appInfo.hasLiveness()
- && inliner.appInfo.withLiveness().alwaysInline.contains(target)) {
+ && inliner.appInfo.withLiveness().alwaysInline.contains(target.method)) {
return Reason.ALWAYS;
}
if (callSiteInformation.hasSingleCallSite(target)) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
index e6de678..eb3511b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
@@ -18,7 +18,7 @@
import com.android.tools.r8.ir.code.InvokeVirtual;
import com.android.tools.r8.ir.code.NonNull;
import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
@@ -86,8 +86,9 @@
continue;
}
// Due to the potential downcast below, make sure the new target holder is visible.
- Constraint visibility = Constraint.classIsVisible(invocationContext, holderType, appInfo);
- if (visibility == Constraint.NEVER) {
+ ConstraintWithTarget visibility =
+ ConstraintWithTarget.classIsVisible(invocationContext, holderType, appInfo);
+ if (visibility == ConstraintWithTarget.NEVER) {
continue;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/EnumOrdinalMapCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/EnumOrdinalMapCollector.java
index 1b68f7d..e23b534 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/EnumOrdinalMapCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/EnumOrdinalMapCollector.java
@@ -3,10 +3,12 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.optimize;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionIterator;
@@ -30,12 +32,14 @@
public class EnumOrdinalMapCollector {
private final AppInfoWithLiveness appInfo;
+ private final GraphLense graphLense;
private final InternalOptions options;
private final Map<DexType, Reference2IntMap<DexField>> ordinalsMaps = new IdentityHashMap<>();
- public EnumOrdinalMapCollector(AppInfoWithLiveness appInfo, InternalOptions options) {
- this.appInfo = appInfo;
+ public EnumOrdinalMapCollector(AppView<AppInfoWithLiveness> appView, InternalOptions options) {
+ this.appInfo = appView.getAppInfo();
+ this.graphLense = appView.getGraphLense();
this.options = options;
}
@@ -55,7 +59,8 @@
return;
}
DexEncodedMethod initializer = clazz.getClassInitializer();
- IRCode code = initializer.getCode().buildIR(initializer, appInfo, options, clazz.origin);
+ IRCode code =
+ initializer.getCode().buildIR(initializer, appInfo, graphLense, options, clazz.origin);
Reference2IntMap<DexField> ordinalsMap = new Reference2IntArrayMap<>();
ordinalsMap.defaultReturnValue(-1);
InstructionIterator it = code.instructionIterator();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
index d3d1e2f..85f7a39 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
@@ -60,7 +60,8 @@
@Override
public void ensureMethodProcessed(DexEncodedMethod target, IRCode inlinee) {
- assert target.isProcessed();
+ // Do nothing. If the method is not yet processed, we still should
+ // be able to build IR for inlining, though.
}
@Override
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 db674bd..04cd083 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
@@ -43,8 +43,8 @@
public class Inliner {
private static final int INITIAL_INLINING_INSTRUCTION_ALLOWANCE = 1500;
+ private final IRConverter converter;
protected final AppInfoWithLiveness appInfo;
- private final GraphLense graphLense;
final InternalOptions options;
// State for inlining methods which are known to be called twice.
@@ -55,12 +55,9 @@
private final Set<DexMethod> blackList = Sets.newIdentityHashSet();
- public Inliner(
- AppInfoWithLiveness appInfo,
- GraphLense graphLense,
- InternalOptions options) {
- this.appInfo = appInfo;
- this.graphLense = graphLense;
+ public Inliner(IRConverter converter, InternalOptions options) {
+ this.converter = converter;
+ this.appInfo = converter.appInfo.withLiveness();
this.options = options;
fillInBlackList(appInfo);
}
@@ -71,30 +68,33 @@
}
public boolean isBlackListed(DexEncodedMethod method) {
- return blackList.contains(method.method) || appInfo.neverInline.contains(method);
+ return blackList.contains(method.method) || appInfo.neverInline.contains(method.method);
}
- private Constraint instructionAllowedForInlining(
+ private ConstraintWithTarget instructionAllowedForInlining(
Instruction instruction, InliningConstraints inliningConstraints, DexType invocationContext) {
- Constraint result = instruction.inliningConstraint(inliningConstraints, invocationContext);
- if (result == Constraint.NEVER && instruction.isDebugInstruction()) {
- return Constraint.ALWAYS;
+ ConstraintWithTarget result =
+ instruction.inliningConstraint(inliningConstraints, invocationContext);
+ if (result == ConstraintWithTarget.NEVER && instruction.isDebugInstruction()) {
+ return ConstraintWithTarget.ALWAYS;
}
return result;
}
- public Constraint computeInliningConstraint(IRCode code, DexEncodedMethod method) {
- Constraint result = Constraint.ALWAYS;
+ public ConstraintWithTarget computeInliningConstraint(IRCode code, DexEncodedMethod method) {
+ ConstraintWithTarget result = ConstraintWithTarget.ALWAYS;
InliningConstraints inliningConstraints = new InliningConstraints(appInfo);
InstructionIterator it = code.instructionIterator();
while (it.hasNext()) {
Instruction instruction = it.next();
- Constraint state =
+ ConstraintWithTarget state =
instructionAllowedForInlining(instruction, inliningConstraints, method.method.holder);
- result = Constraint.min(result, state);
- if (result == Constraint.NEVER) {
+ if (state == ConstraintWithTarget.NEVER) {
+ result = state;
break;
}
+ // TODO(b/111080693): we may need to collect all meaningful constraints.
+ result = ConstraintWithTarget.meet(result, state, appInfo);
}
return result;
}
@@ -176,11 +176,17 @@
*/
public enum Constraint {
// The ordinal values are important so please do not reorder.
- NEVER, // Never inline this.
- SAMECLASS, // Only inline this into methods with same holder.
- PACKAGE, // Only inline this into methods with holders from same package.
- SUBCLASS, // Only inline this into methods with holders from a subclass.
- ALWAYS; // No restrictions for inlining this.
+ NEVER(1), // Never inline this.
+ SAMECLASS(2), // Inlineable into methods with same holder.
+ PACKAGE(4), // Inlineable into methods with holders from the same package.
+ SUBCLASS(8), // Inlineable into methods with holders from a subclass in a different package.
+ ALWAYS(16); // No restrictions for inlining this.
+
+ int value;
+
+ Constraint(int value) {
+ this.value = value;
+ }
static {
assert NEVER.ordinal() < SAMECLASS.ordinal();
@@ -189,7 +195,67 @@
assert SUBCLASS.ordinal() < ALWAYS.ordinal();
}
- public static Constraint deriveConstraint(
+ boolean isSet(int value) {
+ return (this.value & value) != 0;
+ }
+ }
+
+ /**
+ * Encodes the constraints for inlining, along with the target holder.
+ * <p>
+ * Constraint itself cannot determine whether or not the method can be inlined if instructions in
+ * the method have different constraints with different targets. For example,
+ * SUBCLASS of x.A v.s. PACKAGE of y.B
+ * Without any target holder information, min of those two Constraints is PACKAGE, meaning that
+ * the current method can be inlined to any method whose holder is in package y. This could cause
+ * an illegal access error due to protect members in x.A. Because of different target holders,
+ * those constraints should not be combined.
+ * <p>
+ * Instead, a right constraint for inlining constraint for the example above is: a method whose
+ * holder is a subclass of x.A _and_ in the same package of y.B can inline this method.
+ */
+ public static class ConstraintWithTarget {
+ public final Constraint constraint;
+ // Note that this is not context---where this constraint is encoded.
+ // It literally refers to the holder type of the target, which could be:
+ // invoked method in invocations, field in field instructions, type of check-cast, etc.
+ final DexType targetHolder;
+
+ public static final ConstraintWithTarget NEVER = new ConstraintWithTarget(Constraint.NEVER);
+ public static final ConstraintWithTarget ALWAYS = new ConstraintWithTarget(Constraint.ALWAYS);
+
+ private ConstraintWithTarget(Constraint constraint) {
+ assert constraint == Constraint.NEVER || constraint == Constraint.ALWAYS;
+ this.constraint = constraint;
+ this.targetHolder = null;
+ }
+
+ ConstraintWithTarget(Constraint constraint, DexType targetHolder) {
+ assert constraint != Constraint.NEVER && constraint != Constraint.ALWAYS;
+ assert targetHolder != null;
+ this.constraint = constraint;
+ this.targetHolder = targetHolder;
+ }
+
+ @Override
+ public int hashCode() {
+ if (targetHolder == null) {
+ return constraint.ordinal();
+ }
+ return constraint.ordinal() * targetHolder.computeHashCode();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof ConstraintWithTarget)) {
+ return false;
+ }
+ ConstraintWithTarget o = (ConstraintWithTarget) other;
+ return this.constraint.ordinal() == o.constraint.ordinal()
+ && this.targetHolder == o.targetHolder;
+ }
+
+ public static ConstraintWithTarget deriveConstraint(
DexType contextHolder,
DexType targetHolder,
AccessFlags flags,
@@ -197,23 +263,25 @@
if (flags.isPublic()) {
return ALWAYS;
} else if (flags.isPrivate()) {
- return targetHolder == contextHolder ? SAMECLASS : NEVER;
+ return targetHolder == contextHolder
+ ? new ConstraintWithTarget(Constraint.SAMECLASS, targetHolder) : NEVER;
} else if (flags.isProtected()) {
if (targetHolder.isSamePackage(contextHolder)) {
// Even though protected, this is visible via the same package from the context.
- return PACKAGE;
+ return new ConstraintWithTarget(Constraint.PACKAGE, targetHolder);
} else if (contextHolder.isSubtypeOf(targetHolder, appInfo)) {
- return SUBCLASS;
+ return new ConstraintWithTarget(Constraint.SUBCLASS, targetHolder);
}
return NEVER;
} else {
/* package-private */
- return targetHolder.isSamePackage(contextHolder) ? PACKAGE : NEVER;
+ return targetHolder.isSamePackage(contextHolder)
+ ? new ConstraintWithTarget(Constraint.PACKAGE, targetHolder) : NEVER;
}
}
- public static Constraint classIsVisible(DexType context, DexType clazz,
- AppInfoWithSubtyping appInfo) {
+ public static ConstraintWithTarget classIsVisible(
+ DexType context, DexType clazz, AppInfoWithSubtyping appInfo) {
if (clazz.isArrayType()) {
return classIsVisible(context, clazz.toArrayElementType(appInfo.dexItemFactory), appInfo);
}
@@ -227,8 +295,76 @@
: deriveConstraint(context, clazz, definition.accessFlags, appInfo);
}
- public static Constraint min(Constraint one, Constraint other) {
- return one.ordinal() < other.ordinal() ? one : other;
+ public static ConstraintWithTarget meet(
+ ConstraintWithTarget one, ConstraintWithTarget other, AppInfoWithSubtyping appInfo) {
+ if (one.equals(other)) {
+ return one;
+ }
+ if (other.constraint.ordinal() < one.constraint.ordinal()) {
+ return meet(other, one, appInfo);
+ }
+ // From now on, one.constraint.ordinal() <= other.constraint.ordinal()
+ if (one == NEVER) {
+ return NEVER;
+ }
+ if (other == ALWAYS) {
+ return one;
+ }
+ int constraint = one.constraint.value | other.constraint.value;
+ assert !Constraint.NEVER.isSet(constraint);
+ assert !Constraint.ALWAYS.isSet(constraint);
+ // SAMECLASS <= SAMECLASS, PACKAGE, SUBCLASS
+ if (Constraint.SAMECLASS.isSet(constraint)) {
+ assert one.constraint == Constraint.SAMECLASS;
+ if (other.constraint == Constraint.SAMECLASS) {
+ assert one.targetHolder != other.targetHolder;
+ return NEVER;
+ }
+ if (other.constraint == Constraint.PACKAGE) {
+ if (one.targetHolder.isSamePackage(other.targetHolder)) {
+ return one;
+ }
+ return NEVER;
+ }
+ assert other.constraint == Constraint.SUBCLASS;
+ if (one.targetHolder.isSubtypeOf(other.targetHolder, appInfo)) {
+ return one;
+ }
+ return NEVER;
+ }
+ // PACKAGE <= PACKAGE, SUBCLASS
+ if (Constraint.PACKAGE.isSet(constraint)) {
+ assert one.constraint == Constraint.PACKAGE;
+ if (other.constraint == Constraint.PACKAGE) {
+ assert one.targetHolder != other.targetHolder;
+ if (one.targetHolder.isSamePackage(other.targetHolder)) {
+ return one;
+ }
+ // PACKAGE of x and PACKAGE of y cannot be satisfied together.
+ return NEVER;
+ }
+ assert other.constraint == Constraint.SUBCLASS;
+ if (other.targetHolder.isSamePackage(one.targetHolder)) {
+ // Then, PACKAGE is more restrictive constraint.
+ return one;
+ }
+ // TODO(b/111080693): towards finer-grained constraints, we need both.
+ // The target method is still inlineable to methods with a holder from the same package of
+ // one's holder and a subtype of other's holder.
+ return NEVER;
+ }
+ // SUBCLASS <= SUBCLASS
+ assert Constraint.SUBCLASS.isSet(constraint);
+ assert one.constraint == other.constraint;
+ assert one.targetHolder != other.targetHolder;
+ if (one.targetHolder.isSubtypeOf(other.targetHolder, appInfo)) {
+ return one;
+ }
+ if (other.targetHolder.isSubtypeOf(one.targetHolder, appInfo)) {
+ return other;
+ }
+ // SUBCLASS of x and SUBCLASS of y while x and y are not a subtype of each other.
+ return NEVER;
}
}
@@ -270,7 +406,8 @@
Position callerPosition) {
// Build the IR for a yet not processed method, and perform minimal IR processing.
Origin origin = appInfo.originFor(target.method.holder);
- IRCode code = target.buildInliningIR(appInfo, options, generator, callerPosition, origin);
+ IRCode code =
+ target.buildInliningIR(appInfo, graphLense, options, generator, callerPosition, origin);
if (!target.isProcessed()) {
new LensCodeRewriter(graphLense, appInfo).rewrite(code, target);
}
@@ -447,11 +584,12 @@
invokePosition = Position.noneWithMethod(method.method, null);
}
assert invokePosition.callerPosition == null
- || invokePosition.getOutermostCaller().method == method.method;
+ || invokePosition.getOutermostCaller().method
+ == converter.getGraphLense().getOriginalMethodSignature(method.method);
IRCode inlinee =
- result.buildInliningIR(
- code.valueNumberGenerator, appInfo, graphLense, options, invokePosition);
+ result.buildInliningIR(code.valueNumberGenerator,
+ appInfo, converter.getGraphLense(), options, invokePosition);
if (inlinee != null) {
// TODO(64432527): Get rid of this additional check by improved inlining.
if (block.hasCatchHandlers() && inlinee.computeNormalExitBlocks().isEmpty()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
index 073d6ae..129e9cb 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
@@ -15,6 +15,7 @@
import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.ir.code.Invoke.Type;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import java.util.Collection;
@@ -45,71 +46,71 @@
this.graphLense = graphLense;
}
- public Constraint forAlwaysMaterializingUser() {
- return Constraint.ALWAYS;
+ public ConstraintWithTarget forAlwaysMaterializingUser() {
+ return ConstraintWithTarget.ALWAYS;
}
- public Constraint forArgument() {
- return Constraint.ALWAYS;
+ public ConstraintWithTarget forArgument() {
+ return ConstraintWithTarget.ALWAYS;
}
- public Constraint forArrayGet() {
- return Constraint.ALWAYS;
+ public ConstraintWithTarget forArrayGet() {
+ return ConstraintWithTarget.ALWAYS;
}
- public Constraint forArrayLength() {
- return Constraint.ALWAYS;
+ public ConstraintWithTarget forArrayLength() {
+ return ConstraintWithTarget.ALWAYS;
}
- public Constraint forArrayPut() {
- return Constraint.ALWAYS;
+ public ConstraintWithTarget forArrayPut() {
+ return ConstraintWithTarget.ALWAYS;
}
- public Constraint forBinop() {
- return Constraint.ALWAYS;
+ public ConstraintWithTarget forBinop() {
+ return ConstraintWithTarget.ALWAYS;
}
- public Constraint forCheckCast(DexType type, DexType invocationContext) {
- return Constraint.classIsVisible(invocationContext, type, appInfo);
+ public ConstraintWithTarget forCheckCast(DexType type, DexType invocationContext) {
+ return ConstraintWithTarget.classIsVisible(invocationContext, type, appInfo);
}
- public Constraint forConstClass(DexType type, DexType invocationContext) {
- return Constraint.classIsVisible(invocationContext, type, appInfo);
+ public ConstraintWithTarget forConstClass(DexType type, DexType invocationContext) {
+ return ConstraintWithTarget.classIsVisible(invocationContext, type, appInfo);
}
- public Constraint forConstInstruction() {
- return Constraint.ALWAYS;
+ public ConstraintWithTarget forConstInstruction() {
+ return ConstraintWithTarget.ALWAYS;
}
- public Constraint forDebugLocalRead() {
- return Constraint.ALWAYS;
+ public ConstraintWithTarget forDebugLocalRead() {
+ return ConstraintWithTarget.ALWAYS;
}
- public Constraint forDebugLocalsChange() {
- return Constraint.ALWAYS;
+ public ConstraintWithTarget forDebugLocalsChange() {
+ return ConstraintWithTarget.ALWAYS;
}
- public Constraint forDebugPosition() {
- return Constraint.ALWAYS;
+ public ConstraintWithTarget forDebugPosition() {
+ return ConstraintWithTarget.ALWAYS;
}
- public Constraint forInstanceGet(DexField field, DexType invocationContext) {
+ public ConstraintWithTarget forInstanceGet(DexField field, DexType invocationContext) {
DexField lookup = graphLense.lookupField(field);
return forFieldInstruction(
lookup, appInfo.lookupInstanceTarget(lookup.clazz, lookup), invocationContext);
}
- public Constraint forInstanceOf(DexType type, DexType invocationContext) {
- return Constraint.classIsVisible(invocationContext, type, appInfo);
+ public ConstraintWithTarget forInstanceOf(DexType type, DexType invocationContext) {
+ return ConstraintWithTarget.classIsVisible(invocationContext, type, appInfo);
}
- public Constraint forInstancePut(DexField field, DexType invocationContext) {
+ public ConstraintWithTarget forInstancePut(DexField field, DexType invocationContext) {
DexField lookup = graphLense.lookupField(field);
return forFieldInstruction(
lookup, appInfo.lookupInstanceTarget(lookup.clazz, lookup), invocationContext);
}
- public Constraint forInvoke(DexMethod method, Type type, DexType invocationContext) {
+ public ConstraintWithTarget forInvoke(DexMethod method, Type type, DexType invocationContext) {
switch (type) {
case DIRECT:
return forInvokeDirect(method, invocationContext);
@@ -130,162 +131,165 @@
}
}
- public Constraint forInvokeCustom() {
- return Constraint.NEVER;
+ public ConstraintWithTarget forInvokeCustom() {
+ return ConstraintWithTarget.NEVER;
}
- public Constraint forInvokeDirect(DexMethod method, DexType invocationContext) {
+ public ConstraintWithTarget forInvokeDirect(DexMethod method, DexType invocationContext) {
DexMethod lookup = graphLense.lookupMethod(method);
return forSingleTargetInvoke(lookup, appInfo.lookupDirectTarget(lookup), invocationContext);
}
- public Constraint forInvokeInterface(DexMethod method, DexType invocationContext) {
+ public ConstraintWithTarget forInvokeInterface(DexMethod method, DexType invocationContext) {
DexMethod lookup = graphLense.lookupMethod(method);
return forVirtualInvoke(lookup, appInfo.lookupInterfaceTargets(lookup), invocationContext);
}
- public Constraint forInvokeMultiNewArray(DexType type, DexType invocationContext) {
- return Constraint.classIsVisible(invocationContext, type, appInfo);
+ public ConstraintWithTarget forInvokeMultiNewArray(DexType type, DexType invocationContext) {
+ return ConstraintWithTarget.classIsVisible(invocationContext, type, appInfo);
}
- public Constraint forInvokeNewArray(DexType type, DexType invocationContext) {
- return Constraint.classIsVisible(invocationContext, type, appInfo);
+ public ConstraintWithTarget forInvokeNewArray(DexType type, DexType invocationContext) {
+ return ConstraintWithTarget.classIsVisible(invocationContext, type, appInfo);
}
- public Constraint forInvokePolymorphic(DexMethod method, DexType invocationContext) {
- return Constraint.NEVER;
+ public ConstraintWithTarget forInvokePolymorphic(DexMethod method, DexType invocationContext) {
+ return ConstraintWithTarget.NEVER;
}
- public Constraint forInvokeStatic(DexMethod method, DexType invocationContext) {
+ public ConstraintWithTarget forInvokeStatic(DexMethod method, DexType invocationContext) {
DexMethod lookup = graphLense.lookupMethod(method);
return forSingleTargetInvoke(lookup, appInfo.lookupStaticTarget(lookup), invocationContext);
}
- public Constraint forInvokeSuper(DexMethod method, DexType invocationContext) {
+ public ConstraintWithTarget forInvokeSuper(DexMethod method, DexType invocationContext) {
// The semantics of invoke super depend on the context.
- return Constraint.SAMECLASS;
+ return new ConstraintWithTarget(Constraint.SAMECLASS, invocationContext);
}
- public Constraint forInvokeVirtual(DexMethod method, DexType invocationContext) {
+ public ConstraintWithTarget forInvokeVirtual(DexMethod method, DexType invocationContext) {
DexMethod lookup = graphLense.lookupMethod(method);
return forVirtualInvoke(lookup, appInfo.lookupVirtualTargets(lookup), invocationContext);
}
- public Constraint forJumpInstruction() {
- return Constraint.ALWAYS;
+ public ConstraintWithTarget forJumpInstruction() {
+ return ConstraintWithTarget.ALWAYS;
}
- public Constraint forLoad() {
- return Constraint.ALWAYS;
+ public ConstraintWithTarget forLoad() {
+ return ConstraintWithTarget.ALWAYS;
}
- public Constraint forMonitor() {
+ public ConstraintWithTarget forMonitor() {
// Conservative choice.
- return Constraint.NEVER;
+ return ConstraintWithTarget.NEVER;
}
- public Constraint forMove() {
- return Constraint.ALWAYS;
+ public ConstraintWithTarget forMove() {
+ return ConstraintWithTarget.ALWAYS;
}
- public Constraint forMoveException() {
+ public ConstraintWithTarget forMoveException() {
// TODO(64432527): Revisit this constraint.
- return Constraint.NEVER;
+ return ConstraintWithTarget.NEVER;
}
- public Constraint forNewArrayEmpty(DexType type, DexType invocationContext) {
- return Constraint.classIsVisible(invocationContext, type, appInfo);
+ public ConstraintWithTarget forNewArrayEmpty(DexType type, DexType invocationContext) {
+ return ConstraintWithTarget.classIsVisible(invocationContext, type, appInfo);
}
- public Constraint forNewArrayFilledData() {
- return Constraint.ALWAYS;
+ public ConstraintWithTarget forNewArrayFilledData() {
+ return ConstraintWithTarget.ALWAYS;
}
- public Constraint forNewInstance(DexType type, DexType invocationContext) {
- return Constraint.classIsVisible(invocationContext, type, appInfo);
+ public ConstraintWithTarget forNewInstance(DexType type, DexType invocationContext) {
+ return ConstraintWithTarget.classIsVisible(invocationContext, type, appInfo);
}
- public Constraint forNonNull() {
- return Constraint.ALWAYS;
+ public ConstraintWithTarget forNonNull() {
+ return ConstraintWithTarget.ALWAYS;
}
- public Constraint forPop() {
- return Constraint.ALWAYS;
+ public ConstraintWithTarget forPop() {
+ return ConstraintWithTarget.ALWAYS;
}
- public Constraint forReturn() {
- return Constraint.ALWAYS;
+ public ConstraintWithTarget forReturn() {
+ return ConstraintWithTarget.ALWAYS;
}
- public Constraint forStaticGet(DexField field, DexType invocationContext) {
+ public ConstraintWithTarget forStaticGet(DexField field, DexType invocationContext) {
DexField lookup = graphLense.lookupField(field);
return forFieldInstruction(
lookup, appInfo.lookupStaticTarget(lookup.clazz, lookup), invocationContext);
}
- public Constraint forStaticPut(DexField field, DexType invocationContext) {
+ public ConstraintWithTarget forStaticPut(DexField field, DexType invocationContext) {
DexField lookup = graphLense.lookupField(field);
return forFieldInstruction(
lookup, appInfo.lookupStaticTarget(lookup.clazz, lookup), invocationContext);
}
- public Constraint forStore() {
- return Constraint.ALWAYS;
+ public ConstraintWithTarget forStore() {
+ return ConstraintWithTarget.ALWAYS;
}
- public Constraint forThrow() {
- return Constraint.ALWAYS;
+ public ConstraintWithTarget forThrow() {
+ return ConstraintWithTarget.ALWAYS;
}
- public Constraint forUnop() {
- return Constraint.ALWAYS;
+ public ConstraintWithTarget forUnop() {
+ return ConstraintWithTarget.ALWAYS;
}
- private Constraint forFieldInstruction(
+ private ConstraintWithTarget forFieldInstruction(
DexField field, DexEncodedField target, DexType invocationContext) {
// Resolve the field if possible and decide whether the instruction can inlined.
DexType fieldHolder = graphLense.lookupType(field.clazz);
DexClass fieldClass = appInfo.definitionFor(fieldHolder);
if (target != null && fieldClass != null) {
- Constraint fieldConstraint =
- Constraint.deriveConstraint(invocationContext, fieldHolder, target.accessFlags, appInfo);
- Constraint classConstraint =
- Constraint.deriveConstraint(
+ ConstraintWithTarget fieldConstraintWithTarget =
+ ConstraintWithTarget.deriveConstraint(
+ invocationContext, fieldHolder, target.accessFlags, appInfo);
+ ConstraintWithTarget classConstraintWithTarget =
+ ConstraintWithTarget.deriveConstraint(
invocationContext, fieldHolder, fieldClass.accessFlags, appInfo);
- return Constraint.min(fieldConstraint, classConstraint);
+ return ConstraintWithTarget.meet(
+ fieldConstraintWithTarget, classConstraintWithTarget, appInfo);
}
- return Constraint.NEVER;
+ return ConstraintWithTarget.NEVER;
}
- private Constraint forSingleTargetInvoke(
+ private ConstraintWithTarget forSingleTargetInvoke(
DexMethod method, DexEncodedMethod target, DexType invocationContext) {
if (method.holder.isArrayType()) {
- return Constraint.ALWAYS;
+ return ConstraintWithTarget.ALWAYS;
}
if (target != null) {
DexType methodHolder = graphLense.lookupType(target.method.holder);
DexClass methodClass = appInfo.definitionFor(methodHolder);
if (methodClass != null) {
- Constraint methodConstraint =
- Constraint.deriveConstraint(
+ ConstraintWithTarget methodConstraintWithTarget =
+ ConstraintWithTarget.deriveConstraint(
invocationContext, methodHolder, target.accessFlags, appInfo);
// We also have to take the constraint of the enclosing class into account.
- Constraint classConstraint =
- Constraint.deriveConstraint(
+ ConstraintWithTarget classConstraintWithTarget =
+ ConstraintWithTarget.deriveConstraint(
invocationContext, methodHolder, methodClass.accessFlags, appInfo);
- return Constraint.min(methodConstraint, classConstraint);
+ return ConstraintWithTarget.meet(
+ methodConstraintWithTarget, classConstraintWithTarget, appInfo);
}
}
- return Constraint.NEVER;
+ return ConstraintWithTarget.NEVER;
}
- private Constraint forVirtualInvoke(
+ private ConstraintWithTarget forVirtualInvoke(
DexMethod method, Collection<DexEncodedMethod> targets, DexType invocationContext) {
if (method.holder.isArrayType()) {
- return Constraint.ALWAYS;
+ return ConstraintWithTarget.ALWAYS;
}
if (targets == null) {
- return Constraint.NEVER;
+ return ConstraintWithTarget.NEVER;
}
// Perform resolution and derive inlining constraints based on the accessibility of the
@@ -294,25 +298,26 @@
DexEncodedMethod resolutionTarget = resolutionResult.asResultOfResolve();
if (resolutionTarget == null) {
// This will fail at runtime.
- return Constraint.NEVER;
+ return ConstraintWithTarget.NEVER;
}
DexType methodHolder = graphLense.lookupType(resolutionTarget.method.holder);
DexClass methodClass = appInfo.definitionFor(methodHolder);
assert methodClass != null;
- Constraint methodConstraint =
- Constraint.deriveConstraint(
+ ConstraintWithTarget methodConstraintWithTarget =
+ ConstraintWithTarget.deriveConstraint(
invocationContext, methodHolder, resolutionTarget.accessFlags, appInfo);
// We also have to take the constraint of the enclosing class of the resolution result
// into account. We do not allow inlining this method if it is calling something that
// is inaccessible. Inlining in that case could move the code to another package making a
// call succeed that should not succeed. Conversely, if the resolution result is accessible,
// we have to make sure that inlining cannot make it inaccessible.
- Constraint classConstraint =
- Constraint.deriveConstraint(
+ ConstraintWithTarget classConstraintWithTarget =
+ ConstraintWithTarget.deriveConstraint(
invocationContext, methodHolder, methodClass.accessFlags, appInfo);
- Constraint result = Constraint.min(methodConstraint, classConstraint);
- if (result == Constraint.NEVER) {
+ ConstraintWithTarget result =
+ ConstraintWithTarget.meet(methodConstraintWithTarget, classConstraintWithTarget, appInfo);
+ if (result == ConstraintWithTarget.NEVER) {
return result;
}
@@ -321,10 +326,11 @@
for (DexEncodedMethod target : targets) {
methodHolder = graphLense.lookupType(target.method.holder);
assert appInfo.definitionFor(methodHolder) != null;
- methodConstraint =
- Constraint.deriveConstraint(invocationContext, methodHolder, target.accessFlags, appInfo);
- result = Constraint.min(result, methodConstraint);
- if (result == Constraint.NEVER) {
+ methodConstraintWithTarget =
+ ConstraintWithTarget.deriveConstraint(
+ invocationContext, methodHolder, target.accessFlags, appInfo);
+ result = ConstraintWithTarget.meet(result, methodConstraintWithTarget, appInfo);
+ if (result == ConstraintWithTarget.NEVER) {
return result;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MethodPoolCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/MethodPoolCollection.java
new file mode 100644
index 0000000..bd3acb6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MethodPoolCollection.java
@@ -0,0 +1,223 @@
+// 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.ir.optimize;
+
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.MethodSignatureEquivalence;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.Timing;
+import com.google.common.base.Equivalence;
+import com.google.common.base.Equivalence.Wrapper;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Deque;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.function.Predicate;
+
+// Per-class collection of method signatures.
+//
+// Example use case: to determine if a certain method can be publicized or not.
+public class MethodPoolCollection {
+
+ private static final Equivalence<DexMethod> equivalence = MethodSignatureEquivalence.get();
+
+ private final DexApplication application;
+ private final Map<DexClass, MethodPool> methodPools = new ConcurrentHashMap<>();
+
+ public MethodPoolCollection(DexApplication application) {
+ this.application = application;
+ }
+
+ public void buildAll(ExecutorService executorService, Timing timing) throws ExecutionException {
+ timing.begin("Building method pool collection");
+ try {
+ List<Future<?>> futures = new ArrayList<>();
+ @SuppressWarnings("unchecked")
+ List<DexClass> classes = (List) application.classes();
+ submitAll(classes, futures, executorService);
+ ThreadUtils.awaitFutures(futures);
+ } finally {
+ timing.end();
+ }
+ }
+
+ public MethodPool buildForHierarchy(
+ DexClass clazz, ExecutorService executorService, Timing timing) throws ExecutionException {
+ timing.begin("Building method pool collection");
+ try {
+ List<Future<?>> futures = new ArrayList<>();
+ submitAll(
+ getAllSuperTypesInclusive(clazz, methodPools::containsKey), futures, executorService);
+ submitAll(getAllSubTypesExclusive(clazz, methodPools::containsKey), futures, executorService);
+ ThreadUtils.awaitFutures(futures);
+ } finally {
+ timing.end();
+ }
+ return get(clazz);
+ }
+
+ public MethodPool get(DexClass clazz) {
+ assert methodPools.containsKey(clazz);
+ return methodPools.get(clazz);
+ }
+
+ public boolean markIfNotSeen(DexClass clazz, DexMethod method) {
+ MethodPool methodPool = get(clazz);
+ Wrapper<DexMethod> key = equivalence.wrap(method);
+ if (methodPool.hasSeen(key)) {
+ return true;
+ }
+ methodPool.seen(key);
+ return false;
+ }
+
+ private void submitAll(
+ Iterable<DexClass> classes, List<Future<?>> futures, ExecutorService executorService) {
+ for (DexClass clazz : classes) {
+ futures.add(executorService.submit(computeMethodPoolPerClass(clazz)));
+ }
+ }
+
+ private Runnable computeMethodPoolPerClass(DexClass clazz) {
+ return () -> {
+ MethodPool methodPool = methodPools.computeIfAbsent(clazz, k -> new MethodPool());
+ clazz.forEachMethod(
+ encodedMethod -> {
+ // We will add private instance methods when we promote them.
+ if (!encodedMethod.isPrivateMethod() || encodedMethod.isStaticMethod()) {
+ methodPool.seen(equivalence.wrap(encodedMethod.method));
+ }
+ });
+ if (clazz.superType != null) {
+ DexClass superClazz = application.definitionFor(clazz.superType);
+ if (superClazz != null) {
+ MethodPool superPool = methodPools.computeIfAbsent(superClazz, k -> new MethodPool());
+ superPool.linkSubtype(methodPool);
+ methodPool.linkSupertype(superPool);
+ }
+ }
+ if (clazz.isInterface()) {
+ clazz.type.forAllImplementsSubtypes(
+ implementer -> {
+ DexClass subClazz = application.definitionFor(implementer);
+ if (subClazz != null) {
+ MethodPool childPool = methodPools.computeIfAbsent(subClazz, k -> new MethodPool());
+ childPool.linkInterface(methodPool);
+ }
+ });
+ }
+ };
+ }
+
+ private Set<DexClass> getAllSuperTypesInclusive(
+ DexClass subject, Predicate<DexClass> stoppingCriterion) {
+ Set<DexClass> superTypes = new HashSet<>();
+ Deque<DexClass> worklist = new ArrayDeque<>();
+ worklist.add(subject);
+ while (!worklist.isEmpty()) {
+ DexClass clazz = worklist.pop();
+ if (stoppingCriterion.test(clazz)) {
+ continue;
+ }
+ if (superTypes.add(clazz)) {
+ if (clazz.superType != null) {
+ addNonNull(worklist, application.definitionFor(clazz.superType));
+ }
+ for (DexType interfaceType : clazz.interfaces.values) {
+ addNonNull(worklist, application.definitionFor(interfaceType));
+ }
+ }
+ }
+ return superTypes;
+ }
+
+ private Set<DexClass> getAllSubTypesExclusive(
+ DexClass subject, Predicate<DexClass> stoppingCriterion) {
+ Set<DexClass> subTypes = new HashSet<>();
+ Deque<DexClass> worklist = new ArrayDeque<>();
+ subject.type.forAllExtendsSubtypes(
+ type -> addNonNull(worklist, application.definitionFor(type)));
+ subject.type.forAllImplementsSubtypes(
+ type -> addNonNull(worklist, application.definitionFor(type)));
+ while (!worklist.isEmpty()) {
+ DexClass clazz = worklist.pop();
+ if (stoppingCriterion.test(clazz)) {
+ continue;
+ }
+ if (subTypes.add(clazz)) {
+ clazz.type.forAllExtendsSubtypes(
+ type -> addNonNull(worklist, application.definitionFor(type)));
+ clazz.type.forAllImplementsSubtypes(
+ type -> addNonNull(worklist, application.definitionFor(type)));
+ }
+ }
+ return subTypes;
+ }
+
+ public static class MethodPool {
+ private MethodPool superType;
+ private final Set<MethodPool> interfaces = new HashSet<>();
+ private final Set<MethodPool> subTypes = new HashSet<>();
+ private final Set<Wrapper<DexMethod>> methodPool = new HashSet<>();
+
+ private MethodPool() {}
+
+ synchronized void linkSupertype(MethodPool superType) {
+ assert this.superType == null;
+ this.superType = superType;
+ }
+
+ synchronized void linkSubtype(MethodPool subType) {
+ boolean added = subTypes.add(subType);
+ assert added;
+ }
+
+ synchronized void linkInterface(MethodPool itf) {
+ boolean added = interfaces.add(itf);
+ assert added;
+ }
+
+ public void seen(DexMethod method) {
+ seen(MethodSignatureEquivalence.get().wrap(method));
+ }
+
+ public synchronized void seen(Wrapper<DexMethod> method) {
+ boolean added = methodPool.add(method);
+ assert added;
+ }
+
+ public boolean hasSeen(Wrapper<DexMethod> method) {
+ return hasSeenUpwardRecursive(method) || hasSeenDownwardRecursive(method);
+ }
+
+ private boolean hasSeenUpwardRecursive(Wrapper<DexMethod> method) {
+ return methodPool.contains(method)
+ || (superType != null && superType.hasSeenUpwardRecursive(method))
+ || interfaces.stream().anyMatch(itf -> itf.hasSeenUpwardRecursive(method));
+ }
+
+ private boolean hasSeenDownwardRecursive(Wrapper<DexMethod> method) {
+ return methodPool.contains(method)
+ || subTypes.stream().anyMatch(subType -> subType.hasSeenDownwardRecursive(method));
+ }
+ }
+
+ private static <T> void addNonNull(Collection<T> collection, T item) {
+ if (item != null) {
+ collection.add(item);
+ }
+ }
+}
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 c3a3781..5a7cba6 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
@@ -20,6 +20,7 @@
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.ParameterAnnotationsList;
import com.android.tools.r8.graph.UseRegistry;
@@ -43,8 +44,9 @@
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.code.ValueType;
import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.ir.conversion.SourceCode;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.origin.SynthesizedOrigin;
@@ -107,6 +109,7 @@
final private AppInfoWithLiveness appInfo;
final private DexItemFactory dexItemFactory;
+ final private IRConverter converter;
private final InliningConstraints inliningConstraints;
// Representation of an outline.
@@ -476,8 +479,9 @@
// See whether we could move this invoke somewhere else. We reuse the logic from inlining
// here, as the constraints are the same.
- Constraint constraint = invoke.inliningConstraint(inliningConstraints, method.method.holder);
- if (constraint != Constraint.ALWAYS) {
+ ConstraintWithTarget constraint =
+ invoke.inliningConstraint(inliningConstraints, method.method.holder);
+ if (constraint != ConstraintWithTarget.ALWAYS) {
return false;
}
// Find the number of in-going arguments, if adding this instruction.
@@ -816,9 +820,10 @@
}
}
- public Outliner(AppInfoWithLiveness appInfo, InternalOptions options) {
+ public Outliner(AppInfoWithLiveness appInfo, InternalOptions options, IRConverter converter) {
this.appInfo = appInfo;
this.dexItemFactory = appInfo.dexItemFactory;
+ this.converter = converter;
this.inliningConstraints = new InliningConstraints(appInfo);
this.options = options;
}
@@ -850,7 +855,10 @@
assert outlineSites.size() == 0;
for (List<DexEncodedMethod> outlineMethods : candidateMethodLists) {
if (outlineMethods.size() >= options.outline.threshold) {
- methodsSelectedForOutlining.addAll(outlineMethods);
+ for (DexEncodedMethod outlineMethod : outlineMethods) {
+ methodsSelectedForOutlining.add(
+ converter.getGraphLense().mapDexEncodedMethod(appInfo, outlineMethod));
+ }
}
}
candidateMethodLists.clear();
@@ -1106,7 +1114,7 @@
}
@Override
- public Position getDebugPositionAtOffset(int offset) {
+ public Position getCanonicalDebugPositionAtOffset(int offset) {
throw new Unreachable();
}
@@ -1163,10 +1171,14 @@
@Override
public IRCode buildIR(
- DexEncodedMethod encodedMethod, AppInfo appInfo, InternalOptions options, Origin origin) {
+ DexEncodedMethod encodedMethod,
+ AppInfo appInfo,
+ GraphLense graphLense,
+ InternalOptions options,
+ Origin origin) {
+ assert getOwner() == encodedMethod;
OutlineSourceCode source = new OutlineSourceCode(outline);
- IRBuilder builder = new IRBuilder(encodedMethod, appInfo, source, options);
- return builder.build();
+ return new IRBuilder(encodedMethod, appInfo, source, options).build();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
index dee935e..9b0ab04 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
@@ -112,6 +112,11 @@
assert !couldBeVolatile(field);
if (instruction.isInstanceGet() && !instruction.outValue().hasLocalInfo()) {
Value object = instruction.asInstanceGet().object();
+ // Values from NonNull instructions will always be replaced with their original
+ // value before code is generated.
+ if (!object.isPhi() && object.definition.isNonNull()) {
+ object = object.definition.asNonNull().src();
+ }
FieldAndObject fieldAndObject = new FieldAndObject(field, object);
if (activeInstanceFields.containsKey(fieldAndObject)) {
Instruction active = activeInstanceFields.get(fieldAndObject);
@@ -132,7 +137,8 @@
}
}
}
- if (instruction.isMonitor() || instruction.isInvokeMethod()) {
+ if ((instruction.isMonitor() && instruction.asMonitor().isEnter())
+ || instruction.isInvokeMethod()) {
killAllActiveFields();
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/SwitchMapCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/SwitchMapCollector.java
index a586355..c2fbc9e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/SwitchMapCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/SwitchMapCollector.java
@@ -3,12 +3,14 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.optimize;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionIterator;
@@ -61,14 +63,16 @@
public class SwitchMapCollector {
private final AppInfoWithLiveness appInfo;
+ private final GraphLense graphLense;
private final InternalOptions options;
private final DexString switchMapPrefix;
private final DexType intArrayType;
private final Map<DexField, Int2ReferenceMap<DexField>> switchMaps = new IdentityHashMap<>();
- public SwitchMapCollector(AppInfoWithLiveness appInfo, InternalOptions options) {
- this.appInfo = appInfo;
+ public SwitchMapCollector(AppView<AppInfoWithLiveness> appView, InternalOptions options) {
+ this.appInfo = appView.getAppInfo();
+ this.graphLense = appView.getGraphLense();
this.options = options;
switchMapPrefix = appInfo.dexItemFactory.createString("$SwitchMap$");
intArrayType = appInfo.dexItemFactory.createType("[I");
@@ -92,7 +96,8 @@
List<DexEncodedField> switchMapFields = Arrays.stream(clazz.staticFields())
.filter(this::maybeIsSwitchMap).collect(Collectors.toList());
if (!switchMapFields.isEmpty()) {
- IRCode initializer = clazz.getClassInitializer().buildIR(appInfo, options, clazz.origin);
+ IRCode initializer =
+ clazz.getClassInitializer().buildIR(appInfo, graphLense, options, clazz.origin);
switchMapFields.forEach(field -> extractSwitchMap(field, initializer));
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
index 5bf2c74..3a2371d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
@@ -8,10 +8,10 @@
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.desugar.LambdaRewriter;
import com.android.tools.r8.ir.optimize.CodeRewriter;
import com.android.tools.r8.ir.optimize.Inliner.InliningInfo;
import com.android.tools.r8.ir.optimize.InliningOracle;
@@ -27,15 +27,18 @@
public final class ClassInliner {
private final DexItemFactory factory;
+ private final LambdaRewriter lambdaRewriter;
private final int totalMethodInstructionLimit;
- private final ConcurrentHashMap<DexType, Boolean> knownClasses = new ConcurrentHashMap<>();
+ private final ConcurrentHashMap<DexClass, Boolean> knownClasses = new ConcurrentHashMap<>();
public interface InlinerAction {
void inline(Map<InvokeMethod, InliningInfo> methods);
}
- public ClassInliner(DexItemFactory factory, int totalMethodInstructionLimit) {
+ public ClassInliner(DexItemFactory factory,
+ LambdaRewriter lambdaRewriter, int totalMethodInstructionLimit) {
this.factory = factory;
+ this.lambdaRewriter = lambdaRewriter;
this.totalMethodInstructionLimit = totalMethodInstructionLimit;
}
@@ -142,8 +145,8 @@
while (rootsIterator.hasNext()) {
Instruction root = rootsIterator.next();
InlineCandidateProcessor processor =
- new InlineCandidateProcessor(factory, appInfo,
- type -> isClassEligible(appInfo, type),
+ new InlineCandidateProcessor(factory, appInfo, lambdaRewriter,
+ clazz -> isClassEligible(appInfo, clazz),
isProcessedConcurrently, method, root);
// Assess eligibility of instance and class.
@@ -180,7 +183,7 @@
} while (repeat);
}
- private boolean isClassEligible(AppInfo appInfo, DexType clazz) {
+ private boolean isClassEligible(AppInfo appInfo, DexClass clazz) {
Boolean eligible = knownClasses.get(clazz);
if (eligible == null) {
Boolean computed = computeClassEligible(appInfo, clazz);
@@ -195,15 +198,14 @@
// - is not an abstract class or interface
// - does not declare finalizer
// - does not trigger any static initializers except for its own
- private boolean computeClassEligible(AppInfo appInfo, DexType clazz) {
- DexClass definition = appInfo.definitionFor(clazz);
- if (definition == null || definition.isLibraryClass() ||
- definition.accessFlags.isAbstract() || definition.accessFlags.isInterface()) {
+ private boolean computeClassEligible(AppInfo appInfo, DexClass clazz) {
+ if (clazz == null || clazz.isLibraryClass() ||
+ clazz.accessFlags.isAbstract() || clazz.accessFlags.isInterface()) {
return false;
}
// Class must not define finalizer.
- for (DexEncodedMethod method : definition.virtualMethods()) {
+ for (DexEncodedMethod method : clazz.virtualMethods()) {
if (method.method.name == factory.finalizeMethodName &&
method.method.proto == factory.objectMethods.finalize.proto) {
return false;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index 57ce42f..5fedf37 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -28,6 +28,7 @@
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.desugar.LambdaRewriter;
import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
import com.android.tools.r8.ir.optimize.Inliner.InliningInfo;
import com.android.tools.r8.ir.optimize.Inliner.Reason;
@@ -53,7 +54,8 @@
private final DexItemFactory factory;
private final AppInfoWithLiveness appInfo;
- private final Predicate<DexType> isClassEligible;
+ private final LambdaRewriter lambdaRewriter;
+ private final Predicate<DexClass> isClassEligible;
private final Predicate<DexEncodedMethod> isProcessedConcurrently;
private final DexEncodedMethod method;
private final Instruction root;
@@ -61,6 +63,7 @@
private Value eligibleInstance;
private DexType eligibleClass;
private DexClass eligibleClassDefinition;
+ private boolean isDesugaredLambda;
private final Map<InvokeMethod, InliningInfo> methodCallsOnInstance
= new IdentityHashMap<>();
@@ -73,10 +76,11 @@
InlineCandidateProcessor(
DexItemFactory factory, AppInfoWithLiveness appInfo,
- Predicate<DexType> isClassEligible,
+ LambdaRewriter lambdaRewriter, Predicate<DexClass> isClassEligible,
Predicate<DexEncodedMethod> isProcessedConcurrently,
DexEncodedMethod method, Instruction root) {
this.factory = factory;
+ this.lambdaRewriter = lambdaRewriter;
this.isClassEligible = isClassEligible;
this.method = method;
this.root = root;
@@ -99,6 +103,11 @@
eligibleClass = isNewInstance() ?
root.asNewInstance().clazz : root.asStaticGet().getField().type;
eligibleClassDefinition = appInfo.definitionFor(eligibleClass);
+ if (eligibleClassDefinition == null && lambdaRewriter != null) {
+ // Check if the class is synthesized for a desugared lambda
+ eligibleClassDefinition = lambdaRewriter.getLambdaClass(eligibleClass);
+ isDesugaredLambda = eligibleClassDefinition != null;
+ }
return eligibleClassDefinition != null;
}
@@ -114,7 +123,7 @@
// * class has class initializer marked as TrivialClassInitializer, and
// class initializer initializes the field we are reading here.
boolean isClassAndUsageEligible() {
- if (!isClassEligible.test(eligibleClass)) {
+ if (!isClassEligible.test(eligibleClassDefinition)) {
return false;
}
@@ -129,6 +138,11 @@
assert root.isStaticGet();
+ // We know that desugared lambda classes satisfy eligibility requirements.
+ if (isDesugaredLambda) {
+ return true;
+ }
+
// Checking if we can safely inline class implemented following singleton-like
// pattern, by which we assume a static final field holding on to the reference
// initialized in class constructor.
@@ -444,7 +458,7 @@
: "Inlined constructor? [invoke: " + initInvoke +
", expected class: " + eligibleClass + "]";
- DexEncodedMethod definition = appInfo.definitionFor(init);
+ DexEncodedMethod definition = findSingleTarget(init, true);
if (definition == null || isProcessedConcurrently.test(definition)) {
return null;
}
@@ -455,6 +469,12 @@
return null;
}
+ if (isDesugaredLambda) {
+ // Lambda desugaring synthesizes eligible constructors.
+ markSizeForInlining(definition);
+ return new InliningInfo(definition, eligibleClass);
+ }
+
// If the superclass of the initializer is NOT java.lang.Object, the super class
// initializer being called must be classified as TrivialInstanceInitializer.
//
@@ -499,7 +519,7 @@
private InliningInfo isEligibleMethodCall(boolean allowMethodsWithoutNormalReturns,
DexMethod callee, Predicate<ClassInlinerEligibility> eligibilityAcceptanceCheck) {
- DexEncodedMethod singleTarget = findSingleTarget(callee);
+ DexEncodedMethod singleTarget = findSingleTarget(callee, false);
if (singleTarget == null || isProcessedConcurrently.test(singleTarget)) {
return null;
}
@@ -507,6 +527,16 @@
return null; // Don't inline itself.
}
+ if (isDesugaredLambda) {
+ // If this is the call to method of the desugared lambda, we consider only calls
+ // to main lambda method eligible (for both direct and indirect calls).
+ if (singleTarget.accessFlags.isBridge()) {
+ return null;
+ }
+ markSizeForInlining(singleTarget);
+ return new InliningInfo(singleTarget, eligibleClass);
+ }
+
OptimizationInfo optimizationInfo = singleTarget.getOptimizationInfo();
ClassInlinerEligibility eligibility = optimizationInfo.getClassInlinerEligibility();
@@ -652,6 +682,9 @@
private boolean exemptFromInstructionLimit(DexEncodedMethod inlinee) {
DexType inlineeHolder = inlinee.method.holder;
+ if (isDesugaredLambda && inlineeHolder == eligibleClass) {
+ return true;
+ }
if (appInfo.isPinned(inlineeHolder)) {
return false;
}
@@ -674,14 +707,16 @@
return root.isNewInstance();
}
- private DexEncodedMethod findSingleTarget(DexMethod callee) {
+ private DexEncodedMethod findSingleTarget(DexMethod callee, boolean isDirect) {
// We don't use computeSingleTarget(...) on invoke since it sometimes fails to
// find the single target, while this code may be more successful since we exactly
// know what is the actual type of the receiver.
// Note that we also intentionally limit ourselves to methods directly defined in
// the instance's class. This may be improved later.
- return eligibleClassDefinition.lookupVirtualMethod(callee);
+ return isDirect
+ ? eligibleClassDefinition.lookupDirectMethod(callee)
+ : eligibleClassDefinition.lookupVirtualMethod(callee);
}
private void removeInstruction(Instruction instruction) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
new file mode 100644
index 0000000..9b5af03
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
@@ -0,0 +1,628 @@
+// 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.ir.optimize.staticizer;
+
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InvokeDirect;
+import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+import com.android.tools.r8.ir.code.NewInstance;
+import com.android.tools.r8.ir.code.StaticGet;
+import com.android.tools.r8.ir.code.StaticPut;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.OptimizationFeedback;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BiConsumer;
+
+public final class ClassStaticizer {
+ final AppInfoWithLiveness appInfo;
+ final DexItemFactory factory;
+ final IRConverter converter;
+
+ private enum Phase {
+ None, Examine, Fixup
+ }
+
+ private Phase phase = Phase.None;
+ private BiConsumer<DexEncodedMethod, IRCode> fixupStrategy = null;
+
+ // Represents a staticizing candidate with all information
+ // needed for staticizing.
+ final class CandidateInfo {
+ final DexProgramClass candidate;
+ final DexEncodedField singletonField;
+ final AtomicBoolean preserveRead = new AtomicBoolean(false);
+ // Number of singleton field writes.
+ final AtomicInteger fieldWrites = new AtomicInteger();
+ // Number of instances created.
+ final AtomicInteger instancesCreated = new AtomicInteger();
+ final Set<DexEncodedMethod> referencedFrom = Sets.newConcurrentHashSet();
+ final AtomicReference<DexEncodedMethod> constructor = new AtomicReference<>();
+
+ CandidateInfo(DexProgramClass candidate, DexEncodedField singletonField) {
+ assert candidate != null;
+ assert singletonField != null;
+ this.candidate = candidate;
+ this.singletonField = singletonField;
+
+ // register itself
+ candidates.put(candidate.type, this);
+ }
+
+ boolean isHostClassInitializer(DexEncodedMethod method) {
+ return factory.isClassConstructor(method.method) && method.method.holder == hostType();
+ }
+
+ DexType hostType() {
+ return singletonField.field.clazz;
+ }
+
+ DexClass hostClass() {
+ DexClass hostClass = appInfo.definitionFor(hostType());
+ assert hostClass != null;
+ return hostClass;
+ }
+
+ CandidateInfo invalidate() {
+ candidates.remove(candidate.type);
+ return null;
+ }
+ }
+
+ // The map storing all the potential candidates for staticizing.
+ final ConcurrentHashMap<DexType, CandidateInfo> candidates = new ConcurrentHashMap<>();
+
+ public ClassStaticizer(AppInfoWithLiveness appInfo, IRConverter converter) {
+ this.appInfo = appInfo;
+ this.factory = appInfo.dexItemFactory;
+ this.converter = converter;
+ }
+
+ // Before doing any usage-based analysis we collect a set of classes that can be
+ // candidates for staticizing. This analysis is very simple, but minimizes the
+ // set of eligible classes staticizer tracks and thus time and memory it needs.
+ public final void collectCandidates(DexApplication app) {
+ Set<DexType> notEligible = Sets.newIdentityHashSet();
+ Map<DexType, DexEncodedField> singletonFields = new HashMap<>();
+
+ app.classes().forEach(cls -> {
+ // We only consider classes eligible for staticizing if there is just
+ // one single static field in the whole app which has a type of this
+ // class. This field will be considered to be a candidate for a singleton
+ // field. The requirements for the initialization of this field will be
+ // checked later.
+ for (DexEncodedField field : cls.staticFields()) {
+ DexType type = field.field.type;
+ if (singletonFields.put(type, field) != null) {
+ // There is already candidate singleton field found.
+ notEligible.add(type);
+ }
+ }
+
+ // Don't allow fields with this candidate types.
+ for (DexEncodedField field : cls.instanceFields()) {
+ notEligible.add(field.field.type);
+ }
+
+ // Let's also assume no methods should take or return a
+ // value of this type.
+ for (DexEncodedMethod method : cls.methods()) {
+ DexProto proto = method.method.proto;
+ notEligible.add(proto.returnType);
+ notEligible.addAll(Arrays.asList(proto.parameters.values));
+ }
+
+ // High-level limitations on what classes we consider eligible.
+ if (cls.isInterface() || // Must not be an interface or an abstract class.
+ cls.accessFlags.isAbstract() ||
+ // Don't support candidates with instance fields
+ cls.instanceFields().length > 0 ||
+ // Only support classes directly extending java.lang.Object
+ cls.superType != factory.objectType ||
+ // Instead of requiring the class being final,
+ // just ensure it does not have subtypes
+ cls.type.hasSubtypes() ||
+ // Staticizing classes implementing interfaces is more
+ // difficult, so don't support it until we really need it.
+ !cls.interfaces.isEmpty()) {
+ notEligible.add(cls.type);
+ }
+ });
+
+ // Finalize the set of the candidates.
+ app.classes().forEach(cls -> {
+ DexType type = cls.type;
+ if (!notEligible.contains(type)) {
+ DexEncodedField field = singletonFields.get(type);
+ if (field != null && // Singleton field found
+ !field.accessFlags.isVolatile() && // Don't remove volatile fields.
+ !isPinned(cls, field)) { // Don't remove pinned objects.
+ assert field.accessFlags.isStatic();
+ // Note: we don't check that the field is final, since we will analyze
+ // later how and where it is initialized.
+ new CandidateInfo(cls, field); // will self-register
+ }
+ }
+ });
+
+ // Next phase -- examine code for candidate usages
+ phase = Phase.Examine;
+ }
+
+ private boolean isPinned(DexClass clazz, DexEncodedField singletonField) {
+ if (appInfo.isPinned(clazz.type) || appInfo.isPinned(singletonField.field)) {
+ return true;
+ }
+ for (DexEncodedMethod method : clazz.methods()) {
+ if (!method.isStaticMethod() && appInfo.isPinned(method.method)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // Check staticizing candidates' usages to ensure the candidate can be staticized.
+ //
+ // The criteria for type CANDIDATE to be eligible for staticizing fall into
+ // these categories:
+ //
+ // * checking that there is only one instance of the class created, and it is created
+ // inside the host class initializer, and it is guaranteed that nobody can access this
+ // field before it is assigned.
+ //
+ // * no other singleton field writes (except for those used to store the only candidate
+ // class instance described above) are allowed.
+ //
+ // * values read from singleton field should only be used for instance method calls.
+ //
+ // NOTE: there are more criteria eligible class needs to satisfy to be actually staticized,
+ // those will be checked later in staticizeCandidates().
+ //
+ // This method also collects all DexEncodedMethod instances that need to be rewritten if
+ // appropriate candidate is staticized. Essentially anything that references instance method
+ // or field defined in the class.
+ //
+ // NOTE: can be called concurrently.
+ public final void examineMethodCode(DexEncodedMethod method, IRCode code) {
+ if (phase != Phase.Examine) {
+ return;
+ }
+
+ Set<Instruction> alreadyProcessed = Sets.newIdentityHashSet();
+
+ CandidateInfo receiverClassCandidateInfo = candidates.get(method.method.holder);
+ Value receiverValue = code.getThis(); // NOTE: is null for static methods.
+ if (receiverClassCandidateInfo != null && receiverValue != null) {
+ // We are inside an instance method of candidate class (not an instance initializer
+ // which we will check later), check if all the references to 'this' are valid
+ // (the call will invalidate the candidate if some of them are not valid).
+ analyzeAllValueUsers(receiverClassCandidateInfo,
+ receiverValue, factory.isConstructor(method.method));
+
+ // If the candidate is still valid, ignore all instructions
+ // we treat as valid usages on receiver.
+ if (candidates.get(method.method.holder) != null) {
+ alreadyProcessed.addAll(receiverValue.uniqueUsers());
+ }
+ }
+
+ ListIterator<Instruction> iterator =
+ Lists.newArrayList(code.instructionIterator()).listIterator();
+ while (iterator.hasNext()) {
+ Instruction instruction = iterator.next();
+ if (alreadyProcessed.contains(instruction)) {
+ continue;
+ }
+
+ if (instruction.isNewInstance()) {
+ // Check the class being initialized against valid staticizing candidates.
+ NewInstance newInstance = instruction.asNewInstance();
+ CandidateInfo candidateInfo = processInstantiation(method, iterator, newInstance);
+ if (candidateInfo != null) {
+ // For host class initializers having eligible instantiation we also want to
+ // ensure that the rest of the initializer consist of code w/o side effects.
+ // This must guarantee that removing field access will not result in missing side
+ // effects, otherwise we can still staticize, but cannot remove singleton reads.
+ while (iterator.hasNext()) {
+ if (!isAllowedInHostClassInitializer(method.method.holder, iterator.next(), code)) {
+ candidateInfo.preserveRead.set(true);
+ iterator.previous();
+ break;
+ }
+ // Ignore just read instruction.
+ }
+ candidateInfo.referencedFrom.add(method);
+ }
+ continue;
+ }
+
+ if (instruction.isStaticPut()) {
+ // Check the field being written to: no writes to singleton fields are allowed
+ // except for those processed in processInstantiation(...).
+ DexType candidateType = instruction.asStaticPut().getField().type;
+ CandidateInfo candidateInfo = candidates.get(candidateType);
+ if (candidateInfo != null) {
+ candidateInfo.invalidate();
+ }
+ continue;
+ }
+
+ if (instruction.isStaticGet()) {
+ // Check the field being read: make sure all usages are valid.
+ CandidateInfo info = processStaticFieldRead(instruction.asStaticGet());
+ if (info != null) {
+ info.referencedFrom.add(method);
+ // If the candidate still valid, ignore all usages in further analysis.
+ Value value = instruction.outValue();
+ if (value != null) {
+ alreadyProcessed.addAll(value.uniqueUsers());
+ }
+ }
+ continue;
+ }
+
+ if (instruction.isInvokeMethodWithReceiver()) {
+ DexMethod invokedMethod = instruction.asInvokeMethodWithReceiver().getInvokedMethod();
+ CandidateInfo candidateInfo = candidates.get(invokedMethod.holder);
+ if (candidateInfo != null) {
+ // A call to instance method of the candidate class we don't know how to deal with.
+ candidateInfo.invalidate();
+ }
+ continue;
+ }
+
+ if (instruction.isInvokeCustom()) {
+ // Just invalidate any candidates referenced from non-static context.
+ CallSiteReferencesInvalidator invalidator = new CallSiteReferencesInvalidator();
+ invalidator.registerCallSite(instruction.asInvokeCustom().getCallSite());
+ continue;
+ }
+
+ if (instruction.isInstanceGet() || instruction.isInstancePut()) {
+ DexField fieldReferenced = instruction.asFieldInstruction().getField();
+ CandidateInfo candidateInfo = candidates.get(fieldReferenced.clazz);
+ if (candidateInfo != null) {
+ // Reads/writes to instance field of the candidate class are not supported.
+ candidateInfo.invalidate();
+ }
+ continue;
+ }
+ }
+ }
+
+ private boolean isAllowedInHostClassInitializer(
+ DexType host, Instruction insn, IRCode code) {
+ return (insn.isStaticPut() && insn.asStaticPut().getField().clazz == host) ||
+ insn.isConstNumber() ||
+ insn.isConstString() ||
+ (insn.isGoto() && insn.asGoto().isTrivialGotoToTheNextBlock(code)) ||
+ insn.isReturn();
+ }
+
+ private CandidateInfo processInstantiation(
+ DexEncodedMethod method, ListIterator<Instruction> iterator, NewInstance newInstance) {
+
+ DexType candidateType = newInstance.clazz;
+ CandidateInfo candidateInfo = candidates.get(candidateType);
+ if (candidateInfo == null) {
+ return null; // Not interested.
+ }
+
+ if (iterator.previousIndex() != 0) {
+ // Valid new instance must be the first instruction in the class initializer
+ return candidateInfo.invalidate();
+ }
+
+ if (!candidateInfo.isHostClassInitializer(method)) {
+ // A valid candidate must only have one instantiation which is
+ // done in the static initializer of the host class.
+ return candidateInfo.invalidate();
+ }
+
+ if (candidateInfo.instancesCreated.incrementAndGet() > 1) {
+ // Only one instance must be ever created.
+ return candidateInfo.invalidate();
+ }
+
+ Value candidateValue = newInstance.dest();
+ if (candidateValue == null) {
+ // Must be assigned to a singleton field.
+ return candidateInfo.invalidate();
+ }
+
+ if (candidateValue.numberOfPhiUsers() > 0) {
+ return candidateInfo.invalidate();
+ }
+
+ if (candidateValue.numberOfUsers() != 2) {
+ // We expect only two users for each instantiation: constructor call and
+ // static field write. We only check count here, since the exact instructions
+ // will be checked later.
+ return candidateInfo.invalidate();
+ }
+
+ // Check usages. Currently we only support the patterns like:
+ //
+ // static constructor void <clinit>() {
+ // new-instance v0, <candidate-type>
+ // (opt) const/4 v1, #int 0 // (optional)
+ // invoke-direct {v0, ...}, void <candidate-type>.<init>(...)
+ // sput-object v0, <instance-field>
+ // ...
+ // ...
+ //
+ // In case we guarantee candidate constructor does not access <instance-field>
+ // directly or indirectly we can guarantee that all the potential reads get
+ // same non-null value.
+
+ // Skip potential constant instructions
+ while (iterator.hasNext() && isNonThrowingConstInstruction(iterator.next())) {
+ // Intentionally empty.
+ }
+ iterator.previous();
+
+ if (!iterator.hasNext()) {
+ return candidateInfo.invalidate();
+ }
+ if (!isValidInitCall(candidateInfo, iterator.next(), candidateValue, candidateType)) {
+ iterator.previous();
+ return candidateInfo.invalidate();
+ }
+
+ if (!iterator.hasNext()) {
+ return candidateInfo.invalidate();
+ }
+ if (!isValidStaticPut(candidateInfo, iterator.next())) {
+ iterator.previous();
+ return candidateInfo.invalidate();
+ }
+ if (candidateInfo.fieldWrites.incrementAndGet() > 1) {
+ return candidateInfo.invalidate();
+ }
+
+ return candidateInfo;
+ }
+
+ private boolean isNonThrowingConstInstruction(Instruction instruction) {
+ return instruction.isConstInstruction() && !instruction.instructionTypeCanThrow();
+ }
+
+ private boolean isValidInitCall(
+ CandidateInfo info, Instruction instruction, Value candidateValue, DexType candidateType) {
+ if (!instruction.isInvokeDirect()) {
+ return false;
+ }
+
+ // Check constructor.
+ InvokeDirect invoke = instruction.asInvokeDirect();
+ DexEncodedMethod methodInvoked = appInfo.lookupDirectTarget(invoke.getInvokedMethod());
+ List<Value> values = invoke.inValues();
+
+ if (values.lastIndexOf(candidateValue) != 0 ||
+ methodInvoked == null || methodInvoked.method.holder != candidateType) {
+ return false;
+ }
+
+ // Check arguments.
+ for (int i = 1; i < values.size(); i++) {
+ if (!values.get(i).definition.isConstInstruction()) {
+ return false;
+ }
+ }
+
+ DexEncodedMethod previous = info.constructor.getAndSet(methodInvoked);
+ assert previous == null;
+ return true;
+ }
+
+ private boolean isValidStaticPut(CandidateInfo info, Instruction instruction) {
+ if (!instruction.isStaticPut()) {
+ return false;
+ }
+ // Allow single assignment to a singleton field.
+ StaticPut staticPut = instruction.asStaticPut();
+ DexEncodedField fieldAccessed =
+ appInfo.lookupStaticTarget(staticPut.getField().clazz, staticPut.getField());
+ return fieldAccessed == info.singletonField;
+ }
+
+ // Static field get: can be a valid singleton field for a
+ // candidate in which case we should check if all the usages of the
+ // value read are eligible.
+ private CandidateInfo processStaticFieldRead(StaticGet staticGet) {
+ DexField field = staticGet.getField();
+ DexType candidateType = field.type;
+ CandidateInfo candidateInfo = candidates.get(candidateType);
+ if (candidateInfo == null) {
+ return null;
+ }
+
+ assert candidateInfo.singletonField == appInfo.lookupStaticTarget(field.clazz, field)
+ : "Added reference after collectCandidates(...)?";
+
+ Value singletonValue = staticGet.dest();
+ if (singletonValue != null) {
+ candidateInfo = analyzeAllValueUsers(candidateInfo, singletonValue, false);
+ }
+ return candidateInfo;
+ }
+
+ private CandidateInfo analyzeAllValueUsers(
+ CandidateInfo candidateInfo, Value value, boolean ignoreSuperClassInitInvoke) {
+ assert value != null;
+
+ if (value.numberOfPhiUsers() > 0) {
+ return candidateInfo.invalidate();
+ }
+
+ for (Instruction user : value.uniqueUsers()) {
+ if (user.isInvokeVirtual() || user.isInvokeDirect() /* private methods */) {
+ InvokeMethodWithReceiver invoke = user.asInvokeMethodWithReceiver();
+ DexMethod methodReferenced = invoke.getInvokedMethod();
+ if (factory.isConstructor(methodReferenced)) {
+ assert user.isInvokeDirect();
+ if (ignoreSuperClassInitInvoke &&
+ invoke.inValues().lastIndexOf(value) == 0 &&
+ methodReferenced == factory.objectMethods.constructor) {
+ // If we are inside candidate constructor and analyzing usages
+ // of the receiver, we want to ignore invocations of superclass
+ // constructor which will be removed after staticizing.
+ continue;
+ }
+ return candidateInfo.invalidate();
+ }
+ DexEncodedMethod methodInvoked = user.isInvokeDirect()
+ ? appInfo.lookupDirectTarget(methodReferenced)
+ : appInfo.lookupVirtualTarget(methodReferenced.holder, methodReferenced);
+ if (invoke.inValues().lastIndexOf(value) == 0 &&
+ methodInvoked != null && methodInvoked.method.holder == candidateInfo.candidate.type) {
+ continue;
+ }
+ }
+
+ // All other users are not allowed.
+ return candidateInfo.invalidate();
+ }
+
+ return candidateInfo;
+ }
+
+ // Perform staticizing candidates:
+ //
+ // 1. After filtering candidates based on usage, finalize the list of candidates by
+ // filtering out candidates which don't satisfy the requirements:
+ //
+ // * there must be one instance of the class
+ // * constructor of the class used to create this instance must be a trivial one
+ // * class initializer should only be present if candidate itself is own host
+ // * no abstract or native instance methods
+ //
+ // 2. Rewrite instance methods of classes being staticized into static ones
+ // 3. Rewrite methods referencing staticized members, also remove instance creation
+ //
+ public final void staticizeCandidates(OptimizationFeedback feedback) {
+ phase = Phase.None; // We are done with processing/examining methods.
+ new StaticizingProcessor(this).run(feedback);
+ }
+
+ public final void fixupMethodCode(DexEncodedMethod method, IRCode code) {
+ if (phase == Phase.Fixup) {
+ assert fixupStrategy != null;
+ fixupStrategy.accept(method, code);
+ }
+ }
+
+ void setFixupStrategy(BiConsumer<DexEncodedMethod, IRCode> strategy) {
+ assert phase == Phase.None;
+ assert strategy != null;
+ phase = Phase.Fixup;
+ fixupStrategy = strategy;
+ }
+
+ void cleanFixupStrategy() {
+ assert phase == Phase.Fixup;
+ assert fixupStrategy != null;
+ phase = Phase.None;
+ fixupStrategy = null;
+ }
+
+ private class CallSiteReferencesInvalidator extends UseRegistry {
+ private boolean registerMethod(DexMethod method) {
+ registerTypeReference(method.holder);
+ registerProto(method.proto);
+ return true;
+ }
+
+ private boolean registerField(DexField field) {
+ registerTypeReference(field.clazz);
+ registerTypeReference(field.type);
+ return true;
+ }
+
+ @Override
+ public boolean registerInvokeVirtual(DexMethod method) {
+ return registerMethod(method);
+ }
+
+ @Override
+ public boolean registerInvokeDirect(DexMethod method) {
+ return registerMethod(method);
+ }
+
+ @Override
+ public boolean registerInvokeStatic(DexMethod method) {
+ return registerMethod(method);
+ }
+
+ @Override
+ public boolean registerInvokeInterface(DexMethod method) {
+ return registerMethod(method);
+ }
+
+ @Override
+ public boolean registerInvokeSuper(DexMethod method) {
+ return registerMethod(method);
+ }
+
+ @Override
+ public boolean registerInstanceFieldWrite(DexField field) {
+ return registerField(field);
+ }
+
+ @Override
+ public boolean registerInstanceFieldRead(DexField field) {
+ return registerField(field);
+ }
+
+ @Override
+ public boolean registerNewInstance(DexType type) {
+ return registerTypeReference(type);
+ }
+
+ @Override
+ public boolean registerStaticFieldRead(DexField field) {
+ return registerField(field);
+ }
+
+ @Override
+ public boolean registerStaticFieldWrite(DexField field) {
+ return registerField(field);
+ }
+
+ @Override
+ public boolean registerTypeReference(DexType type) {
+ CandidateInfo candidateInfo = candidates.get(type);
+ if (candidateInfo != null) {
+ candidateInfo.invalidate();
+ }
+ return true;
+ }
+ }
+}
+
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerGraphLense.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerGraphLense.java
new file mode 100644
index 0000000..4f6c33c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerGraphLense.java
@@ -0,0 +1,39 @@
+// 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.ir.optimize.staticizer;
+
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
+import com.android.tools.r8.ir.code.Invoke.Type;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.ImmutableMap;
+
+class ClassStaticizerGraphLense extends NestedGraphLense {
+ ClassStaticizerGraphLense(GraphLense previous, DexItemFactory factory,
+ BiMap<DexField, DexField> fieldMapping, BiMap<DexMethod, DexMethod> methodMapping) {
+ super(ImmutableMap.of(),
+ methodMapping,
+ fieldMapping,
+ fieldMapping.inverse(),
+ methodMapping.inverse(),
+ previous,
+ factory);
+ }
+
+ @Override
+ protected Type mapInvocationType(
+ DexMethod newMethod, DexMethod originalMethod,
+ DexEncodedMethod context, Type type) {
+ if (methodMap.get(originalMethod) == newMethod) {
+ assert type == Type.VIRTUAL || type == Type.DIRECT;
+ return Type.STATIC;
+ }
+ return super.mapInvocationType(newMethod, originalMethod, context, type);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
new file mode 100644
index 0000000..c5993dd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
@@ -0,0 +1,444 @@
+// 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.ir.optimize.staticizer;
+
+import com.android.tools.r8.graph.DebugLocalInfo;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionIterator;
+import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.code.MemberType;
+import com.android.tools.r8.ir.code.StaticGet;
+import com.android.tools.r8.ir.code.StaticPut;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.conversion.CallSiteInformation;
+import com.android.tools.r8.ir.conversion.OptimizationFeedback;
+import com.android.tools.r8.ir.optimize.Outliner;
+import com.android.tools.r8.ir.optimize.staticizer.ClassStaticizer.CandidateInfo;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
+import com.google.common.collect.Sets;
+import com.google.common.collect.Streams;
+import java.util.ArrayList;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.function.BiConsumer;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+final class StaticizingProcessor {
+ private final ClassStaticizer classStaticizer;
+
+ private final Set<DexEncodedMethod> referencingExtraMethods = Sets.newIdentityHashSet();
+ private final Map<DexEncodedMethod, CandidateInfo> hostClassInits = new IdentityHashMap<>();
+ private final Set<DexEncodedMethod> methodsToBeStaticized = Sets.newIdentityHashSet();
+ private final Map<DexField, CandidateInfo> singletonFields = new IdentityHashMap<>();
+ private final Map<DexType, DexType> candidateToHostMapping = new IdentityHashMap<>();
+
+ StaticizingProcessor(ClassStaticizer classStaticizer) {
+ this.classStaticizer = classStaticizer;
+ }
+
+ final void run(OptimizationFeedback feedback) {
+ // Filter out candidates based on the information we collected
+ // while examining methods.
+ finalEligibilityCheck();
+
+ // Prepare interim data.
+ prepareCandidates();
+
+ // Process all host class initializers (only remove instantiations).
+ processMethods(hostClassInits.keySet().stream(), this::removeCandidateInstantiation, feedback);
+
+ // Process instance methods to be staticized (only remove references to 'this').
+ processMethods(methodsToBeStaticized.stream(), this::removeReferencesToThis, feedback);
+
+ // Convert instance methods into static methods with an extra parameter.
+ Set<DexEncodedMethod> staticizedMethods = staticizeMethodSymbols();
+
+ // Process all other methods that may reference singleton fields
+ // and call methods on them. (Note that we exclude the former instance methods,
+ // but include new static methods created as a result of staticizing.
+ Stream<DexEncodedMethod> methods = Streams.concat(
+ referencingExtraMethods.stream(),
+ staticizedMethods.stream(),
+ hostClassInits.keySet().stream());
+ processMethods(methods, this::rewriteReferences, feedback);
+ }
+
+ private void finalEligibilityCheck() {
+ Iterator<Entry<DexType, CandidateInfo>> it =
+ classStaticizer.candidates.entrySet().iterator();
+ while (it.hasNext()) {
+ Entry<DexType, CandidateInfo> entry = it.next();
+ DexType candidateType = entry.getKey();
+ CandidateInfo info = entry.getValue();
+ DexProgramClass candidateClass = info.candidate;
+ DexType candidateHostType = info.hostType();
+ DexEncodedMethod constructorUsed = info.constructor.get();
+
+ int instancesCreated = info.instancesCreated.get();
+ assert instancesCreated == info.fieldWrites.get();
+ assert instancesCreated <= 1;
+ assert (instancesCreated == 0) == (constructorUsed == null);
+
+ // CHECK: One instance, one singleton field, known constructor
+ if (instancesCreated == 0) {
+ // Give up on the candidate, if there are any reads from instance
+ // field the user should read null.
+ it.remove();
+ continue;
+ }
+
+ // CHECK: instance initializer used to create an instance is trivial.
+ // NOTE: Along with requirement that candidate does not have instance
+ // fields this should guarantee that the constructor is empty.
+ assert candidateClass.instanceFields().length == 0;
+ assert constructorUsed.isProcessed();
+ TrivialInitializer trivialInitializer =
+ constructorUsed.getOptimizationInfo().getTrivialInitializerInfo();
+ if (trivialInitializer == null) {
+ it.remove();
+ continue;
+ }
+
+ // CHECK: class initializer should only be present if candidate itself is its own host.
+ DexEncodedMethod classInitializer = candidateClass.getClassInitializer();
+ assert classInitializer != null || candidateType != candidateHostType;
+ if (classInitializer != null && candidateType != candidateHostType) {
+ it.remove();
+ continue;
+ }
+
+ // CHECK: no abstract or native instance methods.
+ if (Streams.stream(candidateClass.methods()).anyMatch(
+ method -> !method.isStaticMethod() &&
+ (method.accessFlags.isAbstract() || method.accessFlags.isNative()))) {
+ it.remove();
+ continue;
+ }
+ }
+ }
+
+ private void prepareCandidates() {
+ Set<DexEncodedMethod> removedInstanceMethods = Sets.newIdentityHashSet();
+
+ for (CandidateInfo candidate : classStaticizer.candidates.values()) {
+ DexProgramClass candidateClass = candidate.candidate;
+ // Host class initializer
+ DexClass hostClass = candidate.hostClass();
+ DexEncodedMethod hostClassInitializer = hostClass.getClassInitializer();
+ assert hostClassInitializer != null;
+ CandidateInfo previous = hostClassInits.put(hostClassInitializer, candidate);
+ assert previous == null;
+
+ // Collect instance methods to be staticized.
+ for (DexEncodedMethod method : candidateClass.methods()) {
+ if (!method.isStaticMethod()) {
+ removedInstanceMethods.add(method);
+ if (!factory().isConstructor(method.method)) {
+ methodsToBeStaticized.add(method);
+ }
+ }
+ }
+ singletonFields.put(candidate.singletonField.field, candidate);
+ referencingExtraMethods.addAll(candidate.referencedFrom);
+ }
+
+ referencingExtraMethods.removeAll(removedInstanceMethods);
+ }
+
+ private void processMethods(Stream<DexEncodedMethod> methods,
+ BiConsumer<DexEncodedMethod, IRCode> strategy, OptimizationFeedback feedback) {
+ classStaticizer.setFixupStrategy(strategy);
+ methods.sorted(DexEncodedMethod::slowCompare).forEach(
+ method -> classStaticizer.converter.processMethod(method, feedback,
+ x -> false, CallSiteInformation.empty(), Outliner::noProcessing));
+ classStaticizer.cleanFixupStrategy();
+ }
+
+ private void removeCandidateInstantiation(DexEncodedMethod method, IRCode code) {
+ CandidateInfo candidateInfo = hostClassInits.get(method);
+ assert candidateInfo != null;
+
+ // Find and remove instantiation and its users.
+ InstructionIterator iterator = code.instructionIterator();
+ while (iterator.hasNext()) {
+ Instruction instruction = iterator.next();
+ if (instruction.isNewInstance() &&
+ instruction.asNewInstance().clazz == candidateInfo.candidate.type) {
+ // Remove all usages
+ // NOTE: requiring (a) the instance initializer to be trivial, (b) not allowing
+ // candidates with instance fields and (c) requiring candidate to directly
+ // extend java.lang.Object guarantees that the constructor is actually
+ // empty and does not need to be inlined.
+ assert candidateInfo.candidate.superType == factory().objectType;
+ assert candidateInfo.candidate.instanceFields().length == 0;
+
+ Value singletonValue = instruction.outValue();
+ assert singletonValue != null;
+ singletonValue.uniqueUsers().forEach(Instruction::removeOrReplaceByDebugLocalRead);
+ instruction.removeOrReplaceByDebugLocalRead();
+ return;
+ }
+ }
+
+ assert false : "Must always be able to find and remove the instantiation";
+ }
+
+ private void removeReferencesToThis(DexEncodedMethod method, IRCode code) {
+ fixupStaticizedValueUsers(code, code.getThis());
+ }
+
+ private void rewriteReferences(DexEncodedMethod method, IRCode code) {
+ // Process all singleton field reads and rewrite their users.
+ List<StaticGet> singletonFieldReads =
+ Streams.stream(code.instructionIterator())
+ .filter(Instruction::isStaticGet)
+ .map(Instruction::asStaticGet)
+ .filter(get -> singletonFields.containsKey(get.getField()))
+ .collect(Collectors.toList());
+
+ singletonFieldReads.forEach(read -> {
+ CandidateInfo candidateInfo = singletonFields.get(read.getField());
+ assert candidateInfo != null;
+ Value value = read.dest();
+ if (value != null) {
+ fixupStaticizedValueUsers(code, value);
+ }
+ if (!candidateInfo.preserveRead.get()) {
+ read.removeOrReplaceByDebugLocalRead();
+ }
+ });
+
+ if (!candidateToHostMapping.isEmpty()) {
+ remapMovedCandidates(code);
+ }
+ }
+
+ // Fixup value usages: rewrites all method calls so that they point to static methods.
+ private void fixupStaticizedValueUsers(IRCode code, Value thisValue) {
+ assert thisValue != null;
+ assert thisValue.numberOfPhiUsers() == 0;
+
+ for (Instruction user : thisValue.uniqueUsers()) {
+ assert user.isInvokeVirtual() || user.isInvokeDirect();
+ InvokeMethodWithReceiver invoke = user.asInvokeMethodWithReceiver();
+ Value newValue = null;
+ Value outValue = invoke.outValue();
+ if (outValue != null) {
+ newValue = code.createValue(outValue.outType());
+ DebugLocalInfo localInfo = outValue.getLocalInfo();
+ if (localInfo != null) {
+ newValue.setLocalInfo(localInfo);
+ }
+ }
+ List<Value> args = invoke.inValues();
+ invoke.replace(new InvokeStatic(
+ invoke.getInvokedMethod(), newValue, args.subList(1, args.size())));
+ }
+
+ assert thisValue.numberOfUsers() == 0;
+ }
+
+ private void remapMovedCandidates(IRCode code) {
+ InstructionIterator it = code.instructionIterator();
+ while (it.hasNext()) {
+ Instruction instruction = it.next();
+
+ if (instruction.isStaticGet()) {
+ StaticGet staticGet = instruction.asStaticGet();
+ DexField field = mapFieldIfMoved(staticGet.getField());
+ if (field != staticGet.getField()) {
+ Value outValue = staticGet.dest();
+ assert outValue != null;
+ it.replaceCurrentInstruction(
+ new StaticGet(
+ MemberType.fromDexType(field.type),
+ code.createValue(ValueType.fromDexType(field.type), outValue.getLocalInfo()),
+ field
+ )
+ );
+ }
+ continue;
+ }
+
+ if (instruction.isStaticPut()) {
+ StaticPut staticPut = instruction.asStaticPut();
+ DexField field = mapFieldIfMoved(staticPut.getField());
+ if (field != staticPut.getField()) {
+ it.replaceCurrentInstruction(
+ new StaticPut(MemberType.fromDexType(field.type), staticPut.inValue(), field)
+ );
+ }
+ continue;
+ }
+
+ if (instruction.isInvokeStatic()) {
+ InvokeStatic invoke = instruction.asInvokeStatic();
+ DexMethod method = invoke.getInvokedMethod();
+ DexType hostType = candidateToHostMapping.get(method.holder);
+ if (hostType != null) {
+ DexMethod newMethod = factory().createMethod(hostType, method.proto, method.name);
+ Value outValue = invoke.outValue();
+ Value newOutValue = method.proto.returnType.isVoidType() ? null
+ : code.createValue(
+ ValueType.fromDexType(method.proto.returnType),
+ outValue == null ? null : outValue.getLocalInfo());
+ it.replaceCurrentInstruction(
+ new InvokeStatic(newMethod, newOutValue, invoke.inValues()));
+ }
+ continue;
+ }
+ }
+ }
+
+ private DexField mapFieldIfMoved(DexField field) {
+ DexType hostType = candidateToHostMapping.get(field.clazz);
+ if (hostType != null) {
+ field = factory().createField(hostType, field.type, field.name);
+ }
+ hostType = candidateToHostMapping.get(field.type);
+ if (hostType != null) {
+ field = factory().createField(field.clazz, hostType, field.name);
+ }
+ return field;
+ }
+
+ private Set<DexEncodedMethod> staticizeMethodSymbols() {
+ BiMap<DexMethod, DexMethod> methodMapping = HashBiMap.create();
+ BiMap<DexField, DexField> fieldMapping = HashBiMap.create();
+
+ Set<DexEncodedMethod> staticizedMethods = Sets.newIdentityHashSet();
+ for (CandidateInfo candidate : classStaticizer.candidates.values()) {
+ DexProgramClass candidateClass = candidate.candidate;
+
+ // Move instance methods into static ones.
+ List<DexEncodedMethod> newDirectMethods = new ArrayList<>();
+ for (DexEncodedMethod method : candidateClass.methods()) {
+ if (method.isStaticMethod()) {
+ newDirectMethods.add(method);
+ } else if (!factory().isConstructor(method.method)) {
+ DexEncodedMethod staticizedMethod = method.toStaticMethodWithoutThis();
+ newDirectMethods.add(staticizedMethod);
+ staticizedMethods.add(staticizedMethod);
+ methodMapping.put(method.method, staticizedMethod.method);
+ }
+ }
+ candidateClass.setVirtualMethods(DexEncodedMethod.EMPTY_ARRAY);
+ candidateClass.setDirectMethods(
+ newDirectMethods.toArray(new DexEncodedMethod[newDirectMethods.size()]));
+
+ // Consider moving static members from candidate into host.
+ DexType hostType = candidate.hostType();
+ if (candidateClass.type != hostType) {
+ DexClass hostClass = classStaticizer.appInfo.definitionFor(hostType);
+ assert hostClass != null;
+ if (!classMembersConflict(candidateClass, hostClass)) {
+ // Move all members of the candidate class into host class.
+ moveMembersIntoHost(staticizedMethods,
+ candidateClass, hostType, hostClass, methodMapping, fieldMapping);
+ }
+ }
+ }
+
+ if (!methodMapping.isEmpty() || fieldMapping.isEmpty()) {
+ classStaticizer.converter.setGraphLense(
+ new ClassStaticizerGraphLense(
+ classStaticizer.converter.getGraphLense(),
+ classStaticizer.factory,
+ fieldMapping,
+ methodMapping));
+ }
+ return staticizedMethods;
+ }
+
+ private boolean classMembersConflict(DexClass a, DexClass b) {
+ assert Streams.stream(a.methods()).allMatch(DexEncodedMethod::isStaticMethod);
+ assert a.instanceFields().length == 0;
+ return Stream.of(a.staticFields()).anyMatch(fld -> b.lookupField(fld.field) != null) ||
+ Streams.stream(a.methods()).anyMatch(method -> b.lookupMethod(method.method) != null);
+ }
+
+ private void moveMembersIntoHost(Set<DexEncodedMethod> staticizedMethods,
+ DexProgramClass candidateClass,
+ DexType hostType, DexClass hostClass,
+ BiMap<DexMethod, DexMethod> methodMapping,
+ BiMap<DexField, DexField> fieldMapping) {
+ candidateToHostMapping.put(candidateClass.type, hostType);
+
+ // Process static fields.
+ // Append fields first.
+ if (candidateClass.staticFields().length > 0) {
+ DexEncodedField[] oldFields = hostClass.staticFields();
+ DexEncodedField[] extraFields = candidateClass.staticFields();
+ DexEncodedField[] newFields = new DexEncodedField[oldFields.length + extraFields.length];
+ System.arraycopy(oldFields, 0, newFields, 0, oldFields.length);
+ System.arraycopy(extraFields, 0, newFields, oldFields.length, extraFields.length);
+ hostClass.setStaticFields(newFields);
+ }
+
+ // Fixup field types.
+ DexEncodedField[] staticFields = hostClass.staticFields();
+ for (int i = 0; i < staticFields.length; i++) {
+ DexEncodedField field = staticFields[i];
+ DexField newField = mapCandidateField(field.field, candidateClass.type, hostType);
+ if (newField != field.field) {
+ staticFields[i] = field.toTypeSubstitutedField(newField);
+ fieldMapping.put(field.field, newField);
+ }
+ }
+
+ // Process static methods.
+ if (candidateClass.directMethods().length > 0) {
+ DexEncodedMethod[] oldMethods = hostClass.directMethods();
+ DexEncodedMethod[] extraMethods = candidateClass.directMethods();
+ DexEncodedMethod[] newMethods = new DexEncodedMethod[oldMethods.length + extraMethods.length];
+ System.arraycopy(oldMethods, 0, newMethods, 0, oldMethods.length);
+ for (int i = 0; i < extraMethods.length; i++) {
+ DexEncodedMethod method = extraMethods[i];
+ DexEncodedMethod newMethod = method.toTypeSubstitutedMethod(
+ factory().createMethod(hostType, method.method.proto, method.method.name));
+ newMethods[oldMethods.length + i] = newMethod;
+ staticizedMethods.add(newMethod);
+ staticizedMethods.remove(method);
+ DexMethod originalMethod = methodMapping.inverse().get(method.method);
+ if (originalMethod == null) {
+ methodMapping.put(method.method, newMethod.method);
+ } else {
+ methodMapping.put(originalMethod, newMethod.method);
+ }
+ }
+ hostClass.setDirectMethods(newMethods);
+ }
+ }
+
+ private DexField mapCandidateField(DexField field, DexType candidateType, DexType hostType) {
+ return field.clazz != candidateType && field.type != candidateType ? field
+ : factory().createField(
+ field.clazz == candidateType ? hostType : field.clazz,
+ field.type == candidateType ? hostType : field.type,
+ field.name);
+ }
+
+ private DexItemFactory factory() {
+ return classStaticizer.factory;
+ }
+}
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 a9df6a3..8fd09d0 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
@@ -40,8 +40,10 @@
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap.Entry;
import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
+import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntArraySet;
import it.unimi.dsi.fastutil.ints.IntIterator;
+import it.unimi.dsi.fastutil.ints.IntList;
import it.unimi.dsi.fastutil.ints.IntSet;
import it.unimi.dsi.fastutil.objects.Reference2IntArrayMap;
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
@@ -144,6 +146,10 @@
// List of intervals that no register has been allocated to sorted by first live range.
protected PriorityQueue<LiveIntervals> unhandled = new PriorityQueue<>();
+ // The registers that have been released as a result of advancing to the next live intervals.
+ // A register is released if an active or inactive interval becomes handled.
+ private IntList expiredHere = new IntArrayList();
+
// List of intervals for the result of move-exception instructions.
// Always empty in mode ALLOW_ARGUMENT_REUSE.
private List<LiveIntervals> moveExceptionIntervals = new ArrayList<>();
@@ -826,6 +832,7 @@
// Go through each unhandled live interval and find a register for it.
while (!unhandled.isEmpty()) {
assert invariantsHold(mode);
+ expiredHere.clear();
LiveIntervals unhandledInterval = unhandled.poll();
@@ -849,6 +856,12 @@
if (start >= activeIntervals.getEnd()) {
activeIterator.remove();
freeOccupiedRegistersForIntervals(activeIntervals);
+ if (start == activeIntervals.getEnd()) {
+ expiredHere.add(activeIntervals.getRegister());
+ if (activeIntervals.getType().isWide()) {
+ expiredHere.add(activeIntervals.getRegister() + 1);
+ }
+ }
} else if (!activeIntervals.overlapsPosition(start)) {
activeIterator.remove();
assert activeIntervals.getRegister() != NO_REGISTER;
@@ -863,6 +876,12 @@
LiveIntervals inactiveIntervals = inactiveIterator.next();
if (start >= inactiveIntervals.getEnd()) {
inactiveIterator.remove();
+ if (start == inactiveIntervals.getEnd()) {
+ expiredHere.add(inactiveIntervals.getRegister());
+ if (inactiveIntervals.getType().isWide()) {
+ expiredHere.add(inactiveIntervals.getRegister() + 1);
+ }
+ }
} else if (inactiveIntervals.overlapsPosition(start)) {
inactiveIterator.remove();
assert inactiveIntervals.getRegister() != NO_REGISTER;
@@ -1153,17 +1172,137 @@
return false;
}
- private int getSpillRegister(LiveIntervals intervals) {
+ private int getNewSpillRegister(LiveIntervals intervals) {
if (intervals.isArgumentInterval()) {
return intervals.getSplitParent().getRegister();
}
int register = maxRegisterNumber + 1;
increaseCapacity(maxRegisterNumber + intervals.requiredRegisters());
+ return register;
+ }
+
+ private int getSpillRegister(LiveIntervals intervals, IntList excludedRegisters) {
+ if (intervals.isArgumentInterval()) {
+ return intervals.getSplitParent().getRegister();
+ }
+
+ TreeSet<Integer> previousFreeRegisters = new TreeSet<>(freeRegisters);
+ int previousMaxRegisterNumber = maxRegisterNumber;
+ freeRegisters.removeAll(expiredHere);
+ if (excludedRegisters != null) {
+ freeRegisters.removeAll(excludedRegisters);
+ }
+
+ // Check if we can use a register that was previously used as a register for intervals.
+ // This could lead to fewer moves during resolution.
+ int register = -1;
+ for (LiveIntervals split : intervals.getSplitParent().getSplitChildren()) {
+ int candidate = split.getRegister();
+ if (candidate != NO_REGISTER
+ && registersAreFreeAndConsecutive(candidate, intervals.getType().isWide())
+ && maySpillLiveIntervalsToRegister(intervals, candidate, previousMaxRegisterNumber)) {
+ register = candidate;
+ break;
+ }
+ }
+
+ if (register == -1) {
+ do {
+ // If the register needs to fit in 4 bits at the next use, then prioritize a small register.
+ // If we can find a small register, we do not need to insert a move at the next use.
+ boolean prioritizeSmallRegisters =
+ !intervals.getUses().isEmpty()
+ && intervals.getUses().first().getLimit() == Constants.U4BIT_MAX;
+ register =
+ getFreeConsecutiveRegisters(intervals.requiredRegisters(), prioritizeSmallRegisters);
+ } while (!maySpillLiveIntervalsToRegister(intervals, register, previousMaxRegisterNumber));
+ }
+
+ // Going to spill to the register (pair).
+ freeRegisters = previousFreeRegisters;
+ // If getFreeConsecutiveRegisters had to increment |maxRegisterNumber|, we need to update
+ // freeRegisters.
+ for (int i = previousMaxRegisterNumber + 1; i <= maxRegisterNumber; ++i) {
+ freeRegisters.add(i);
+ }
assert registersAreFree(register, intervals.getType().isWide());
return register;
}
+ private boolean maySpillLiveIntervalsToRegister(
+ LiveIntervals intervals, int register, int previousMaxRegisterNumber) {
+ if (register > previousMaxRegisterNumber) {
+ // Nothing can prevent us from spilling to an entirely fresh register.
+ return true;
+ }
+
+ // If we are about to spill to an argument register, we need to be careful that the live range
+ // that is being spilled does not overlap with the live range of the corresponding argument.
+ //
+ // Note that this is *not* guaranteed when overlapsInactiveIntervals is null, because it is
+ // possible that some live ranges of the argument are still in the unhandled set.
+ if (register < numberOfArgumentRegisters) {
+ // Find the first argument value that uses the given register.
+ LiveIntervals argumentLiveIntervals = firstArgumentValue.getLiveIntervals();
+ while (!argumentLiveIntervals.usesRegister(register, intervals.getType().isWide())) {
+ argumentLiveIntervals = argumentLiveIntervals.getNextConsecutive();
+ assert argumentLiveIntervals != null;
+ }
+ do {
+ if (argumentLiveIntervals.anySplitOverlaps(intervals)) {
+ // Remove so that next invocation of getFreeConsecutiveRegisters does not consider this.
+ freeRegisters.remove(register);
+ // We have just established that there is an overlap between the live range of the
+ // current argument and the live range we need to find a register for. Therefore, if
+ // the argument is wide, and the current register corresponds to the low register of the
+ // argument, we know that the subsequent register will not work either.
+ if (register == argumentLiveIntervals.getRegister()
+ && argumentLiveIntervals.getType().isWide()) {
+ freeRegisters.remove(register + 1);
+ }
+ return false;
+ }
+ // The next argument live interval may also use the register, if it is a wide register pair.
+ argumentLiveIntervals = argumentLiveIntervals.getNextConsecutive();
+ } while (argumentLiveIntervals != null
+ && argumentLiveIntervals.usesRegister(register, intervals.getType().isWide()));
+ }
+
+ // Check for overlap with inactive intervals.
+ LiveIntervals overlapsInactiveIntervals = null;
+ for (LiveIntervals inactiveIntervals : inactive) {
+ if (inactiveIntervals.usesRegister(register, intervals.getType().isWide())
+ && intervals.overlaps(inactiveIntervals)) {
+ overlapsInactiveIntervals = inactiveIntervals;
+ break;
+ }
+ }
+ if (overlapsInactiveIntervals != null) {
+ // Remove so that next invocation of getFreeConsecutiveRegisters does not consider this.
+ freeRegisters.remove(register);
+ if (register == overlapsInactiveIntervals.getRegister()
+ && overlapsInactiveIntervals.getType().isWide()) {
+ freeRegisters.remove(register + 1);
+ }
+ return false;
+ }
+
+ // Check for overlap with the move exception interval.
+ boolean overlapsMoveExceptionInterval =
+ hasDedicatedMoveExceptionRegister()
+ && (register == getMoveExceptionRegister()
+ || (intervals.getType().isWide() && register + 1 == getMoveExceptionRegister()))
+ && overlapsMoveExceptionInterval(intervals);
+ if (overlapsMoveExceptionInterval) {
+ // Remove so that next invocation of getFreeConsecutiveRegisters does not consider this.
+ freeRegisters.remove(register);
+ return false;
+ }
+
+ return true;
+ }
+
private int toInstructionPosition(int position) {
return position % 2 == 0 ? position : position + 1;
}
@@ -1496,7 +1635,7 @@
// of finding another candidate to spill via allocateBlockedRegister.
if (!unhandledInterval.getUses().first().hasConstraint()) {
int nextConstrainedPosition = unhandledInterval.firstUseWithConstraint().getPosition();
- int register = getSpillRegister(unhandledInterval);
+ int register = getSpillRegister(unhandledInterval, null);
LiveIntervals split = unhandledInterval.splitBefore(nextConstrainedPosition);
assignFreeRegisterToUnhandledInterval(unhandledInterval, register);
unhandled.add(split);
@@ -1839,7 +1978,8 @@
int splitPosition = unhandledInterval.getFirstUse();
LiveIntervals split = unhandledInterval.splitBefore(splitPosition);
assert split != unhandledInterval;
- int registerNumber = getSpillRegister(unhandledInterval);
+ // Experiments show that it has a positive impact on code size to use a fresh register here.
+ int registerNumber = getNewSpillRegister(unhandledInterval);
assignFreeRegisterToUnhandledInterval(unhandledInterval, registerNumber);
unhandledInterval.setSpilled(true);
unhandled.add(split);
@@ -1926,6 +2066,18 @@
LiveIntervals unhandledInterval, int candidate, boolean candidateIsWide) {
assert unhandledInterval.getRegister() == NO_REGISTER;
assert atLeastOneOfRegistersAreTaken(candidate, candidateIsWide);
+ // Registers that we cannot choose for spilling.
+ IntList excludedRegisters = new IntArrayList(candidateIsWide ? 2 : 1);
+ excludedRegisters.add(candidate);
+ if (candidateIsWide) {
+ excludedRegisters.add(candidate + 1);
+ }
+ if (unhandledInterval.isArgumentInterval()
+ && unhandledInterval != unhandledInterval.getSplitParent()) {
+ // This live interval will become active in its original argument register and in the
+ // candidate register simultaneously.
+ unhandledInterval.getSplitParent().forEachRegister(excludedRegisters::add);
+ }
// Spill overlapping active intervals.
List<LiveIntervals> newActive = new ArrayList<>();
Iterator<LiveIntervals> activeIterator = active.iterator();
@@ -1934,7 +2086,7 @@
assert registersForIntervalsAreTaken(intervals);
if (intervals.usesRegister(candidate, candidateIsWide)) {
activeIterator.remove();
- int registerNumber = getSpillRegister(intervals);
+ int registerNumber = getSpillRegister(intervals, excludedRegisters);
// Important not to free the registers for intervals before finding a spill register,
// because we might otherwise end up spilling to the current registers of intervals,
// depending on getSpillRegister.
@@ -2662,8 +2814,33 @@
}
private int getFreeConsecutiveRegisters(int numberOfRegisters) {
+ return getFreeConsecutiveRegisters(numberOfRegisters, false);
+ }
+
+ private int getFreeConsecutiveRegisters(int numberOfRegisters, boolean prioritizeSmallRegisters) {
int oldMaxRegisterNumber = maxRegisterNumber;
- Iterator<Integer> freeRegistersIterator = freeRegisters.iterator();
+ TreeSet<Integer> freeRegistersWithDesiredOrdering = this.freeRegisters;
+ if (prioritizeSmallRegisters) {
+ freeRegistersWithDesiredOrdering =
+ new TreeSet<>(
+ (Integer x, Integer y) -> {
+ boolean xIsArgument = x < numberOfArgumentRegisters;
+ boolean yIsArgument = y < numberOfArgumentRegisters;
+ // If x is an argument and y is not, then prioritize y.
+ if (xIsArgument && !yIsArgument) {
+ return 1;
+ }
+ // If x is not an argument and y is, then prioritize x.
+ if (!xIsArgument && yIsArgument) {
+ return -1;
+ }
+ // Otherwise use their normal ordering.
+ return x - y;
+ });
+ freeRegistersWithDesiredOrdering.addAll(this.freeRegisters);
+ }
+
+ Iterator<Integer> freeRegistersIterator = freeRegistersWithDesiredOrdering.iterator();
int first = getNextFreeRegister(freeRegistersIterator);
int current = first;
while (current - first + 1 != numberOfRegisters) {
@@ -2692,6 +2869,22 @@
return first;
}
+ private boolean registersAreFreeAndConsecutive(int register, boolean registerIsWide) {
+ if (!freeRegisters.contains(register)) {
+ return false;
+ }
+ if (registerIsWide) {
+ if (!freeRegisters.contains(register + 1)) {
+ return false;
+ }
+ if (register == numberOfArgumentRegisters - 1) {
+ // Will not be consecutive after reordering the arguments and temporaries.
+ return false;
+ }
+ }
+ return true;
+ }
+
private int getNextFreeRegister(Iterator<Integer> freeRegistersIterator) {
if (freeRegistersIterator.hasNext()) {
return freeRegistersIterator.next();
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java
index a5807c3..df273a7 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java
@@ -8,27 +8,36 @@
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.Code;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.graph.UseRegistry;
import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.ValueNumberGenerator;
import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.ir.conversion.SourceCode;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.InternalOptions;
import java.util.function.Consumer;
+import java.util.function.Supplier;
public final class SynthesizedCode extends Code {
- private final SourceCode sourceCode;
+ private final Supplier<SourceCode> sourceCodeProvider;
private final Consumer<UseRegistry> registryCallback;
- public SynthesizedCode(SourceCode sourceCode) {
- this.sourceCode = sourceCode;
+ public SynthesizedCode(SourceCode sourceCodeProvider) {
+ this(() -> sourceCodeProvider);
+ }
+
+ public SynthesizedCode(Supplier<SourceCode> sourceCodeProvider) {
+ this.sourceCodeProvider = sourceCodeProvider;
this.registryCallback = SynthesizedCode::registerReachableDefinitionsDefault;
}
- public SynthesizedCode(SourceCode sourceCode, Consumer<UseRegistry> callback) {
- this.sourceCode = sourceCode;
+ public SynthesizedCode(
+ SourceCode sourceCode, Consumer<UseRegistry> callback) {
+ this.sourceCodeProvider = () -> sourceCode;
this.registryCallback = callback;
}
@@ -39,8 +48,29 @@
@Override
public final IRCode buildIR(
- DexEncodedMethod encodedMethod, AppInfo appInfo, InternalOptions options, Origin origin) {
- return new IRBuilder(encodedMethod, appInfo, sourceCode, options).build();
+ DexEncodedMethod encodedMethod,
+ AppInfo appInfo,
+ GraphLense graphLense,
+ InternalOptions options,
+ Origin origin) {
+ assert getOwner() == encodedMethod;
+ return new IRBuilder(encodedMethod, appInfo, sourceCodeProvider.get(), options).build();
+ }
+
+ @Override
+ public IRCode buildInliningIR(
+ DexEncodedMethod encodedMethod,
+ AppInfo appInfo,
+ GraphLense graphLense,
+ InternalOptions options,
+ ValueNumberGenerator valueNumberGenerator,
+ Position callerPosition,
+ Origin origin) {
+ assert getOwner() == encodedMethod;
+ IRBuilder builder =
+ new IRBuilder(
+ encodedMethod, appInfo, sourceCodeProvider.get(), options, valueNumberGenerator);
+ return builder.build();
}
@Override
@@ -59,17 +89,16 @@
@Override
protected final int computeHashCode() {
- return sourceCode.hashCode();
+ throw new Unreachable();
}
@Override
protected final boolean computeEquals(Object other) {
- return other instanceof SynthesizedCode &&
- this.sourceCode.equals(((SynthesizedCode) other).sourceCode);
+ throw new Unreachable();
}
@Override
public final String toString(DexEncodedMethod method, ClassNameMapper naming) {
- return "SynthesizedCode: " + sourceCode.toString();
+ return "SynthesizedCode";
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
index c64d8ed..991d371 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
@@ -214,7 +214,7 @@
}
@Override
- public Position getDebugPositionAtOffset(int offset) {
+ public Position getCanonicalDebugPositionAtOffset(int offset) {
throw new Unreachable();
}
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 7a25e1a..6537667 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -31,6 +31,7 @@
import com.android.tools.r8.graph.DexValue.DexValueString;
import com.android.tools.r8.graph.DexValue.DexValueType;
import com.android.tools.r8.graph.DexValue.UnknownDexValue;
+import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.graph.InnerClassAttribute;
import com.android.tools.r8.graph.JarClassFileReader;
import com.android.tools.r8.graph.ParameterAnnotationsList;
@@ -61,6 +62,7 @@
private static final boolean PRINT_CF = false;
private final DexApplication application;
+ private final GraphLense graphLense;
private final NamingLens namingLens;
private final InternalOptions options;
@@ -72,10 +74,12 @@
DexApplication application,
InternalOptions options,
String deadCode,
+ GraphLense graphLense,
NamingLens namingLens,
String proguardSeedsData,
ProguardMapSupplier proguardMapSupplier) {
this.application = application;
+ this.graphLense = graphLense;
this.namingLens = namingLens;
this.options = options;
this.proguardMapSupplier = proguardMapSupplier;
@@ -102,7 +106,13 @@
}
}
ApplicationWriter.supplyAdditionalConsumers(
- application, namingLens, options, deadCode, proguardMapSupplier, proguardSeedsData);
+ application,
+ graphLense,
+ namingLens,
+ options,
+ deadCode,
+ proguardMapSupplier,
+ proguardSeedsData);
}
private void writeClass(DexProgramClass clazz, ClassFileConsumer consumer) throws IOException {
diff --git a/src/main/java/com/android/tools/r8/jar/InliningConstraintVisitor.java b/src/main/java/com/android/tools/r8/jar/InliningConstraintVisitor.java
index 459d7de..c707968 100644
--- a/src/main/java/com/android/tools/r8/jar/InliningConstraintVisitor.java
+++ b/src/main/java/com/android/tools/r8/jar/InliningConstraintVisitor.java
@@ -17,7 +17,7 @@
import com.android.tools.r8.graph.JarApplicationReader;
import com.android.tools.r8.ir.code.Invoke;
import com.android.tools.r8.ir.conversion.JarSourceCode;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import org.objectweb.asm.MethodVisitor;
@@ -40,7 +40,7 @@
private final DexEncodedMethod method;
private final DexType invocationContext;
- private Constraint constraint;
+ private ConstraintWithTarget constraint;
public InliningConstraintVisitor(
JarApplicationReader application,
@@ -58,22 +58,22 @@
this.invocationContext = invocationContext;
// Model a synchronized method as having a monitor instruction.
- this.constraint =
- method.accessFlags.isSynchronized() ? inliningConstraints.forMonitor() : Constraint.ALWAYS;
+ this.constraint = method.accessFlags.isSynchronized()
+ ? inliningConstraints.forMonitor() : ConstraintWithTarget.ALWAYS;
}
- public Constraint getConstraint() {
+ public ConstraintWithTarget getConstraint() {
return constraint;
}
- private void updateConstraint(Constraint other) {
- constraint = Constraint.min(constraint, other);
+ private void updateConstraint(ConstraintWithTarget other) {
+ constraint = ConstraintWithTarget.meet(constraint, other, appInfo);
}
// Used to signal that the result is ready, such that we do not need to visit all instructions of
// the method, if we can see early on that it cannot be inlined anyway.
public boolean isFinished() {
- return constraint == Constraint.NEVER;
+ return constraint == ConstraintWithTarget.NEVER;
}
public void accept(TryCatchBlockNode tryCatchBlock) {
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 05c7b92..fb6ef33 100644
--- a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
+++ b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
@@ -10,11 +10,20 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import java.util.Set;
/** Class provides basic information about symbols related to Kotlin support. */
public final class Kotlin {
+ // Simply "Lkotlin/", but to avoid being renamed by Shadow.relocate
+ private static final String KOTLIN =
+ String.join("", ImmutableList.of("L", "k", "o", "t", "l", "i", "n", "/"));
+
+ static String addKotlinPrefix(String str) {
+ return KOTLIN + str;
+ }
+
public final DexItemFactory factory;
public final Functional functional;
@@ -44,14 +53,15 @@
//
// This implementation just ignores lambdas with arity > 22.
for (int i = 0; i <= 22; i++) {
- functions.add(factory.createType("Lkotlin/jvm/functions/Function" + i + ";"));
+ functions.add(factory.createType(addKotlinPrefix("jvm/functions/Function") + i + ";"));
}
}
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 DexType functionBase =
+ factory.createType(addKotlinPrefix("jvm/internal/FunctionBase;"));
+ public final DexType lambdaType = factory.createType(addKotlinPrefix("jvm/internal/Lambda;"));
public final DexMethod lambdaInitializerMethod = factory.createMethod(
lambdaType,
@@ -64,7 +74,7 @@
}
public final class Metadata {
- public final DexType kotlinMetadataType = factory.createType("Lkotlin/Metadata;");
+ public final DexType kotlinMetadataType = factory.createType(addKotlinPrefix("Metadata;"));
public final DexString kind = factory.createString("k");
public final DexString metadataVersion = factory.createString("mv");
public final DexString bytecodeVersion = factory.createString("bv");
@@ -77,7 +87,7 @@
// kotlin.jvm.internal.Intrinsics class
public final class Intrinsics {
- public final DexType type = factory.createType("Lkotlin/jvm/internal/Intrinsics;");
+ public final DexType type = factory.createType(addKotlinPrefix("jvm/internal/Intrinsics;"));
public final DexMethod throwParameterIsNullException = factory.createMethod(type,
factory.createProto(factory.voidType, factory.stringType), "throwParameterIsNullException");
public final DexMethod checkParameterIsNotNull = factory.createMethod(type,
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 74184a5..d15ed5a 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,12 @@
package com.android.tools.r8.kotlin;
+import static kotlinx.metadata.Flag.Property.IS_VAR;
+
import kotlinx.metadata.KmClassVisitor;
+import kotlinx.metadata.KmConstructorVisitor;
+import kotlinx.metadata.KmFunctionVisitor;
+import kotlinx.metadata.KmPropertyVisitor;
import kotlinx.metadata.jvm.KotlinClassMetadata;
public class KotlinClass extends KotlinInfo<KotlinClassMetadata.Class> {
@@ -20,13 +25,30 @@
}
@Override
- void validateMetadata(KotlinClassMetadata.Class metadata) {
- ClassMetadataVisitor visitor = new ClassMetadataVisitor();
+ void processMetadata(KotlinClassMetadata.Class metadata) {
// To avoid lazy parsing/verifying metadata.
- metadata.accept(visitor);
+ metadata.accept(new ClassVisitorForNonNullParameterHints());
}
- private static class ClassMetadataVisitor extends KmClassVisitor {
+ private class ClassVisitorForNonNullParameterHints extends KmClassVisitor {
+ @Override
+ public KmFunctionVisitor visitFunction(int functionFlags, String functionName) {
+ return new NonNullParameterHintCollector.FunctionVisitor(nonNullparamHints);
+ }
+
+ @Override
+ public KmConstructorVisitor visitConstructor(int ctorFlags) {
+ return new NonNullParameterHintCollector.ConstructorVisitor(nonNullparamHints);
+ }
+
+ @Override
+ public KmPropertyVisitor visitProperty(
+ int propertyFlags, String name, int getterFlags, int setterFlags) {
+ if (IS_VAR.invoke(propertyFlags)) {
+ return new NonNullParameterHintCollector.PropertyVisitor(nonNullparamHints);
+ }
+ return null;
+ }
}
@Override
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 62db3d1..de8a12a 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
@@ -20,7 +20,7 @@
}
@Override
- void validateMetadata(KotlinClassMetadata.MultiFileClassFacade metadata) {
+ void processMetadata(KotlinClassMetadata.MultiFileClassFacade metadata) {
// No worries about lazy parsing/verifying, since no API to explore metadata details.
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
index 32aebff..389a8f8 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
@@ -36,7 +36,7 @@
new StringDiagnostic("Class " + clazz.type.toSourceString()
+ " has malformed kotlin.Metadata: " + e.getMessage()));
} catch (Throwable e) {
- reporter.warning(
+ reporter.warning(
new StringDiagnostic("Unexpected error while reading " + clazz.type.toSourceString()
+ "'s kotlin.Metadata: " + e.getMessage()));
}
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 da66e6c..225e63a 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,11 @@
package com.android.tools.r8.kotlin;
+import static kotlinx.metadata.Flag.Property.IS_VAR;
+
+import kotlinx.metadata.KmFunctionVisitor;
import kotlinx.metadata.KmPackageVisitor;
+import kotlinx.metadata.KmPropertyVisitor;
import kotlinx.metadata.jvm.KotlinClassMetadata;
public final class KotlinClassPart extends KotlinInfo<KotlinClassMetadata.MultiFileClassPart> {
@@ -21,12 +25,25 @@
}
@Override
- void validateMetadata(KotlinClassMetadata.MultiFileClassPart metadata) {
+ void processMetadata(KotlinClassMetadata.MultiFileClassPart metadata) {
// To avoid lazy parsing/verifying metadata.
- metadata.accept(new MultiFileClassPartMetadataVisitor());
+ metadata.accept(new PackageVisitorForNonNullParameterHints());
}
- private static class MultiFileClassPartMetadataVisitor extends KmPackageVisitor {
+ private class PackageVisitorForNonNullParameterHints extends KmPackageVisitor {
+ @Override
+ public KmFunctionVisitor visitFunction(int functionFlags, String functionName) {
+ return new NonNullParameterHintCollector.FunctionVisitor(nonNullparamHints);
+ }
+
+ @Override
+ public KmPropertyVisitor visitProperty(
+ int propertyFlags, String name, int getterFlags, int setterFlags) {
+ if (IS_VAR.invoke(propertyFlags)) {
+ return new NonNullParameterHintCollector.PropertyVisitor(nonNullparamHints);
+ }
+ return null;
+ }
}
@Override
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 bcb70ed..2f9477e 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,11 @@
package com.android.tools.r8.kotlin;
+import static kotlinx.metadata.Flag.Property.IS_VAR;
+
+import kotlinx.metadata.KmFunctionVisitor;
import kotlinx.metadata.KmPackageVisitor;
+import kotlinx.metadata.KmPropertyVisitor;
import kotlinx.metadata.jvm.KotlinClassMetadata;
public final class KotlinFile extends KotlinInfo<KotlinClassMetadata.FileFacade> {
@@ -21,12 +25,25 @@
}
@Override
- void validateMetadata(KotlinClassMetadata.FileFacade metadata) {
+ void processMetadata(KotlinClassMetadata.FileFacade metadata) {
// To avoid lazy parsing/verifying metadata.
- metadata.accept(new FileFacadeMetadataVisitor());
+ metadata.accept(new PackageVisitorForNonNullParameterHints());
}
- private static class FileFacadeMetadataVisitor extends KmPackageVisitor {
+ private class PackageVisitorForNonNullParameterHints extends KmPackageVisitor {
+ @Override
+ public KmFunctionVisitor visitFunction(int functionFlags, String functionName) {
+ return new NonNullParameterHintCollector.FunctionVisitor(nonNullparamHints);
+ }
+
+ @Override
+ public KmPropertyVisitor visitProperty(
+ int propertyFlags, String name, int getterFlags, int setterFlags) {
+ if (IS_VAR.invoke(propertyFlags)) {
+ return new NonNullParameterHintCollector.PropertyVisitor(nonNullparamHints);
+ }
+ return null;
+ }
}
@Override
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 702e0eb..7cc8aa1 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
@@ -4,22 +4,25 @@
package com.android.tools.r8.kotlin;
+import com.google.common.collect.HashBasedTable;
+import java.util.BitSet;
import kotlinx.metadata.jvm.KotlinClassMetadata;
// Provides access to kotlin information.
public abstract class KotlinInfo<MetadataKind extends KotlinClassMetadata> {
MetadataKind metadata;
+ final HashBasedTable<String, String, BitSet> nonNullparamHints = HashBasedTable.create();
KotlinInfo() {
}
KotlinInfo(MetadataKind metadata) {
- validateMetadata(metadata);
+ processMetadata(metadata);
this.metadata = metadata;
}
- // Subtypes will define how to validate the given metadata.
- abstract void validateMetadata(MetadataKind metadata);
+ // Subtypes will define how to process the given metadata.
+ abstract void processMetadata(MetadataKind metadata);
public enum Kind {
Class, File, Synthetic, Part, Facade
@@ -66,4 +69,12 @@
public KotlinClassFacade asClassFacade() {
return null;
}
+
+ public boolean hasNonNullParameterHints() {
+ return !nonNullparamHints.isEmpty();
+ }
+
+ public BitSet lookupNonNullParameterHint(String name, String descriptor) {
+ return nonNullparamHints.get(name, descriptor);
+ }
}
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 4660800..fc11f8c 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.kotlin;
import com.android.tools.r8.graph.DexClass;
+import kotlinx.metadata.KmFunctionVisitor;
import kotlinx.metadata.KmLambdaVisitor;
import kotlinx.metadata.jvm.KotlinClassMetadata;
@@ -32,21 +33,23 @@
}
private KotlinSyntheticClass(Flavour flavour, KotlinClassMetadata.SyntheticClass metadata) {
+ super(metadata);
this.flavour = flavour;
- validateMetadata(metadata);
- this.metadata = metadata;
}
@Override
- void validateMetadata(KotlinClassMetadata.SyntheticClass metadata) {
+ void processMetadata(KotlinClassMetadata.SyntheticClass metadata) {
if (metadata.isLambda()) {
- SyntheticClassMetadataVisitor visitor = new SyntheticClassMetadataVisitor();
// To avoid lazy parsing/verifying metadata.
- metadata.accept(visitor);
+ metadata.accept(new LambdaVisitorForNonNullParameterHints());
}
}
- private static class SyntheticClassMetadataVisitor extends KmLambdaVisitor {
+ private class LambdaVisitorForNonNullParameterHints extends KmLambdaVisitor {
+ @Override
+ public KmFunctionVisitor visitFunction(int functionFlags, String functionName) {
+ return new NonNullParameterHintCollector.FunctionVisitor(nonNullparamHints);
+ }
}
public boolean isLambda() {
diff --git a/src/main/java/com/android/tools/r8/kotlin/NonNullParameterHintCollector.java b/src/main/java/com/android/tools/r8/kotlin/NonNullParameterHintCollector.java
new file mode 100644
index 0000000..21b9409
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/NonNullParameterHintCollector.java
@@ -0,0 +1,184 @@
+// 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 static kotlinx.metadata.Flag.Type.IS_NULLABLE;
+
+import com.google.common.collect.HashBasedTable;
+import java.util.BitSet;
+import kotlinx.metadata.KmConstructorExtensionVisitor;
+import kotlinx.metadata.KmConstructorVisitor;
+import kotlinx.metadata.KmExtensionType;
+import kotlinx.metadata.KmFunctionExtensionVisitor;
+import kotlinx.metadata.KmFunctionVisitor;
+import kotlinx.metadata.KmPropertyExtensionVisitor;
+import kotlinx.metadata.KmPropertyVisitor;
+import kotlinx.metadata.KmTypeVisitor;
+import kotlinx.metadata.KmValueParameterVisitor;
+import kotlinx.metadata.jvm.JvmConstructorExtensionVisitor;
+import kotlinx.metadata.jvm.JvmFieldSignature;
+import kotlinx.metadata.jvm.JvmFunctionExtensionVisitor;
+import kotlinx.metadata.jvm.JvmMethodSignature;
+import kotlinx.metadata.jvm.JvmPropertyExtensionVisitor;
+
+class NonNullParameterHintCollector {
+
+ static class FunctionVisitor extends KmFunctionVisitor {
+
+ private final HashBasedTable<String, String, BitSet> paramHints;
+
+ private BitSet paramHint = new BitSet();
+ private int paramIndex = 0;
+ private String name = "";
+ private String descriptor = "";
+
+ FunctionVisitor(HashBasedTable<String, String, BitSet> paramHints) {
+ this.paramHints = paramHints;
+ }
+
+ @Override
+ public KmTypeVisitor visitReceiverParameterType(int typeFlags) {
+ if (!IS_NULLABLE.invoke(typeFlags)) {
+ paramHint.set(paramIndex);
+ }
+ paramIndex++;
+ return null;
+ }
+
+ @Override
+ public KmValueParameterVisitor visitValueParameter(int paramFlags, String paramName) {
+ return new KmValueParameterVisitor() {
+ @Override
+ public KmTypeVisitor visitType(int typeFlags) {
+ if (!IS_NULLABLE.invoke(typeFlags)) {
+ paramHint.set(paramIndex);
+ }
+ paramIndex++;
+ return null;
+ }
+ };
+ }
+
+ @Override
+ public KmFunctionExtensionVisitor visitExtensions(KmExtensionType type) {
+ if (type != JvmFunctionExtensionVisitor.TYPE) {
+ return null;
+ }
+ return new JvmFunctionExtensionVisitor() {
+ @Override
+ public void visit(JvmMethodSignature desc) {
+ name = desc.getName();
+ descriptor = desc.getDesc();
+ }
+ };
+ }
+
+ @Override
+ public void visitEnd() {
+ if (name.isEmpty() || descriptor.isEmpty()) {
+ return;
+ }
+ paramHints.put(name, descriptor, paramHint);
+ }
+ }
+
+ static class ConstructorVisitor extends KmConstructorVisitor {
+ private final HashBasedTable<String, String, BitSet> paramHints;
+
+ private BitSet paramHint = new BitSet();
+ private int paramIndex = 0;
+ private final String name = "<init>";
+ private String descriptor = "";
+
+ ConstructorVisitor(HashBasedTable<String, String, BitSet> paramHints) {
+ this.paramHints = paramHints;
+ }
+
+ @Override
+ public KmValueParameterVisitor visitValueParameter(int paramFlags, String paramName) {
+ return new KmValueParameterVisitor() {
+ @Override
+ public KmTypeVisitor visitType(int typeFlags) {
+ if (!IS_NULLABLE.invoke(typeFlags)) {
+ paramHint.set(paramIndex);
+ }
+ paramIndex++;
+ return null;
+ }
+ };
+ }
+
+ @Override
+ public KmConstructorExtensionVisitor visitExtensions(KmExtensionType type) {
+ if (type != JvmConstructorExtensionVisitor.TYPE) {
+ return null;
+ }
+ return new JvmConstructorExtensionVisitor() {
+ @Override
+ public void visit(JvmMethodSignature desc) {
+ assert name.equals(desc.getName());
+ descriptor = desc.getDesc();
+ }
+ };
+ }
+
+ @Override
+ public void visitEnd() {
+ if (descriptor.isEmpty()) {
+ return;
+ }
+ paramHints.put(name, descriptor, paramHint);
+ }
+ }
+
+ static class PropertyVisitor extends KmPropertyVisitor {
+ private final HashBasedTable<String, String, BitSet> paramHints;
+
+ private BitSet paramHint = new BitSet();
+ private int paramIndex = 0;
+ private String name = "";
+ private String descriptor = "";
+
+ PropertyVisitor(HashBasedTable<String, String, BitSet> paramHints) {
+ this.paramHints = paramHints;
+ }
+
+ @Override
+ public KmTypeVisitor visitReturnType(int typeFlags) {
+ if (!IS_NULLABLE.invoke(typeFlags)) {
+ paramHint.set(paramIndex);
+ }
+ paramIndex++;
+ return null;
+ }
+
+ @Override
+ public KmPropertyExtensionVisitor visitExtensions(KmExtensionType type) {
+ if (type != JvmPropertyExtensionVisitor.TYPE) {
+ return null;
+ }
+ return new JvmPropertyExtensionVisitor() {
+ @Override
+ public void visit(
+ JvmFieldSignature fieldDesc,
+ JvmMethodSignature getterDesc,
+ JvmMethodSignature setterDesc) {
+ if (setterDesc != null) {
+ name = setterDesc.getName();
+ descriptor = setterDesc.getDesc();
+ }
+ }
+ };
+ }
+
+ @Override
+ public void visitEnd() {
+ if (name.isEmpty() || descriptor.isEmpty()) {
+ return;
+ }
+ paramHints.put(name, descriptor, paramHint);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
index 5b908af..e405bfb 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
@@ -25,17 +25,20 @@
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Comparator;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
public class ClassNameMapper implements ProguardMap {
public static class Builder extends ProguardMap.Builder {
- final ImmutableMap.Builder<String, ClassNamingForNameMapper.Builder> mapBuilder;
+ private final ImmutableMap.Builder<String, ClassNamingForNameMapper.Builder> mapBuilder;
private Builder() {
- mapBuilder = ImmutableMap.builder();
+ this.mapBuilder = ImmutableMap.builder();
}
@Override
@@ -150,7 +153,12 @@
}
public void write(Writer writer) throws IOException {
- for (ClassNamingForNameMapper naming : classNameMappings.values()) {
+ // Sort classes by their original name such that the generated Proguard map is deterministic
+ // (and easy to navigate manually).
+ List<ClassNamingForNameMapper> classNamingForNameMappers =
+ new ArrayList<>(classNameMappings.values());
+ classNamingForNameMappers.sort(Comparator.comparing(x -> x.originalName));
+ for (ClassNamingForNameMapper naming : classNamingForNameMappers) {
naming.write(writer);
}
}
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
index 01fff01..bf5dd5d 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -31,6 +31,7 @@
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.Timing;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import java.lang.reflect.GenericSignatureFormatError;
@@ -38,6 +39,7 @@
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@@ -92,7 +94,18 @@
states.computeIfAbsent("", k -> topLevelState);
}
- Map<DexType, DexString> computeRenaming(Timing timing) {
+ static class ClassRenaming {
+ protected final Map<String, String> packageRenaming;
+ protected final Map<DexType, DexString> classRenaming;
+
+ private ClassRenaming(
+ Map<DexType, DexString> classRenaming, Map<String, String> packageRenaming) {
+ this.classRenaming = classRenaming;
+ this.packageRenaming = packageRenaming;
+ }
+ }
+
+ ClassRenaming computeRenaming(Timing timing) {
// Use deterministic class order to make sure renaming is deterministic.
Iterable<DexProgramClass> classes = appInfo.classesWithDeterministicOrder();
// Collect names we have to keep.
@@ -128,7 +141,19 @@
appInfo.dexItemFactory.forAllTypes(this::renameArrayTypeIfNeeded);
timing.end();
- return Collections.unmodifiableMap(renaming);
+ return new ClassRenaming(Collections.unmodifiableMap(renaming), getPackageRenaming());
+ }
+
+ private Map<String, String> getPackageRenaming() {
+ ImmutableMap.Builder<String, String> packageRenaming = ImmutableMap.builder();
+ for (Entry<String, Namespace> entry : states.entrySet()) {
+ String originalPackageName = entry.getKey();
+ String minifiedPackageName = entry.getValue().getPackageName();
+ if (!minifiedPackageName.equals(originalPackageName)) {
+ packageRenaming.put(originalPackageName, minifiedPackageName);
+ }
+ }
+ return packageRenaming.build();
}
private void renameDanglingTypes(DexClass clazz) {
@@ -406,6 +431,7 @@
private class Namespace {
+ private final String packageName;
private final char[] packagePrefix;
private int typeCounter = 1;
private int packageCounter = 1;
@@ -417,6 +443,7 @@
}
Namespace(String packageName, String separator) {
+ this.packageName = packageName;
this.packagePrefix = ("L" + packageName
// L or La/b/ (or La/b/C$)
+ (packageName.isEmpty() ? "" : separator))
@@ -425,6 +452,10 @@
this.classDictionaryIterator = classDictionary.iterator();
}
+ public String getPackageName() {
+ return packageName;
+ }
+
private String nextSuggestedNameForClass() {
StringBuilder nextName = new StringBuilder();
if (classDictionaryIterator.hasNext()) {
@@ -466,7 +497,6 @@
usedPackagePrefixes.add(candidate);
return candidate;
}
-
}
private class GenericSignatureRewriter implements GenericSignatureAction<DexType> {
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
index 34fdd9d..f45a3de 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
@@ -14,6 +14,7 @@
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -68,17 +69,17 @@
/** The parameters are forwarded to MappedRange constructor, see explanation there. */
@Override
public void addMappedRange(
- Range obfuscatedRange,
+ Range minifiedRange,
MemberNaming.MethodSignature originalSignature,
Object originalRange,
- String obfuscatedName) {
+ String renamedName) {
mappedRangesByName
- .computeIfAbsent(obfuscatedName, k -> new ArrayList<>())
- .add(new MappedRange(obfuscatedRange, originalSignature, originalRange, obfuscatedName));
+ .computeIfAbsent(renamedName, k -> new ArrayList<>())
+ .add(new MappedRange(minifiedRange, originalSignature, originalRange, renamedName));
}
}
- /** List of MappedRanges that belong to the same obfuscated name. */
+ /** List of MappedRanges that belong to the same renamed name. */
public static class MappedRangesOfName {
private final List<MappedRange> mappedRanges;
@@ -93,14 +94,14 @@
public MappedRange firstRangeForLine(int line) {
MappedRange bestRange = null;
for (MappedRange range : mappedRanges) {
- if (range.obfuscatedRange == null) {
+ if (range.minifiedRange == null) {
if (bestRange == null) {
// This is an "a() -> b" mapping (no concrete line numbers), remember this if there'll
// be no better one.
bestRange = range;
}
- } else if (range.obfuscatedRange.contains(line)) {
- // Concrete obfuscated range found ("x:y:a()[:u[:v]] -> b")
+ } else if (range.minifiedRange.contains(line)) {
+ // Concrete minified range found ("x:y:a()[:u[:v]] -> b")
return range;
}
}
@@ -108,25 +109,25 @@
}
/**
- * Search for a MappedRange where the obfuscated range contains the specified {@code line} and
- * return that and the subsequent MappedRanges with the same obfuscated range. Return general
+ * Search for a MappedRange where the minified range contains the specified {@code line} and
+ * return that and the subsequent MappedRanges with the same minified range. Return general
* MappedRange ("a() -> b") if no concrete mapping found or empty list if nothing found.
*/
public List<MappedRange> allRangesForLine(int line) {
MappedRange noLineRange = null;
for (int i = 0; i < mappedRanges.size(); ++i) {
MappedRange rangeI = mappedRanges.get(i);
- if (rangeI.obfuscatedRange == null) {
+ if (rangeI.minifiedRange == null) {
if (noLineRange == null) {
// This is an "a() -> b" mapping (no concrete line numbers), remember this if there'll
// be no better one.
noLineRange = rangeI;
}
- } else if (rangeI.obfuscatedRange.contains(line)) {
- // Concrete obfuscated range found ("x:y:a()[:u[:v]] -> b")
+ } else if (rangeI.minifiedRange.contains(line)) {
+ // Concrete minified range found ("x:y:a()[:u[:v]] -> b")
int j = i + 1;
for (; j < mappedRanges.size(); ++j) {
- if (!Objects.equals(mappedRanges.get(j).obfuscatedRange, rangeI.obfuscatedRange)) {
+ if (!Objects.equals(mappedRanges.get(j).minifiedRange, rangeI.minifiedRange)) {
break;
}
}
@@ -165,27 +166,27 @@
/**
* Mapping from the renamed signature to the naming information for a member.
- * <p>
- * A renamed signature is a signature where the member's name has been obfuscated but not the type
+ *
+ * <p>A renamed signature is a signature where the member's name has been renamed but not the type
* information.
- **/
+ */
private final ImmutableMap<MethodSignature, MemberNaming> methodMembers;
private final ImmutableMap<FieldSignature, MemberNaming> fieldMembers;
- /** Map of obfuscated name -> MappedRangesOfName */
- public final Map<String, MappedRangesOfName> mappedRangesByName;
+ /** Map of renamed name -> MappedRangesOfName */
+ public final Map<String, MappedRangesOfName> mappedRangesByRenamedName;
private ClassNamingForNameMapper(
String renamedName,
String originalName,
Map<MethodSignature, MemberNaming> methodMembers,
Map<FieldSignature, MemberNaming> fieldMembers,
- Map<String, MappedRangesOfName> mappedRangesByName) {
+ Map<String, MappedRangesOfName> mappedRangesByRenamedName) {
this.renamedName = renamedName;
this.originalName = originalName;
this.methodMembers = ImmutableMap.copyOf(methodMembers);
this.fieldMembers = ImmutableMap.copyOf(fieldMembers);
- this.mappedRangesByName = mappedRangesByName;
+ this.mappedRangesByRenamedName = mappedRangesByRenamedName;
}
@Override
@@ -273,16 +274,13 @@
});
// Sort MappedRanges by sequence number to restore construction order (original Proguard-map
- // input)
- List<MappedRange> rangeList = new ArrayList<>();
- for (MappedRangesOfName ranges : mappedRangesByName.values()) {
- rangeList.addAll(ranges.mappedRanges);
+ // input).
+ List<MappedRange> mappedRangesSorted = new ArrayList<>();
+ for (MappedRangesOfName ranges : mappedRangesByRenamedName.values()) {
+ mappedRangesSorted.addAll(ranges.mappedRanges);
}
- rangeList.sort(
- (lhs, rhs) -> {
- return lhs.sequenceNumber - rhs.sequenceNumber;
- });
- for (MappedRange range : rangeList) {
+ mappedRangesSorted.sort(Comparator.comparingInt(range -> range.sequenceNumber));
+ for (MappedRange range : mappedRangesSorted) {
writer.append(" ").append(range.toString()).append('\n');
}
}
@@ -313,7 +311,7 @@
&& renamedName.equals(that.renamedName)
&& methodMembers.equals(that.methodMembers)
&& fieldMembers.equals(that.fieldMembers)
- && mappedRangesByName.equals(that.mappedRangesByName);
+ && mappedRangesByRenamedName.equals(that.mappedRangesByRenamedName);
}
@Override
@@ -322,27 +320,27 @@
result = 31 * result + renamedName.hashCode();
result = 31 * result + methodMembers.hashCode();
result = 31 * result + fieldMembers.hashCode();
- result = 31 * result + mappedRangesByName.hashCode();
+ result = 31 * result + mappedRangesByRenamedName.hashCode();
return result;
}
/**
- * MappedRange describes an (original line numbers, signature) <-> (obfuscated line numbers)
+ * MappedRange describes an (original line numbers, signature) <-> (minified line numbers)
* mapping. It can describe 3 different things:
*
* <p>1. The method is renamed. The original source lines are preserved. The corresponding
* Proguard-map syntax is "a(...) -> b"
*
- * <p>2. The source lines of a method in the original range are renumbered to the obfuscated
- * range. In this case the {@link MappedRange#originalRange} is either a {@code Range} or null,
+ * <p>2. The source lines of a method in the original range are renumbered to the minified range.
+ * In this case the {@link MappedRange#originalRange} is either a {@code Range} or null,
* indicating that the original range is unknown or is the same as the {@link
- * MappedRange#obfuscatedRange}. The corresponding Proguard-map syntax is "x:y:a(...) -> b" or
+ * MappedRange#minifiedRange}. The corresponding Proguard-map syntax is "x:y:a(...) -> b" or
* "x:y:a(...):u:v -> b"
*
* <p>3. The source line of a method is the inlining caller of the previous {@code MappedRange}.
* In this case the {@link MappedRange@originalRange} is either an {@code int} or null, indicating
- * that the original source line is unknown, or may be identical to a line of the obfuscated
- * range. The corresponding Proguard-map syntax is "x:y:a(...) -> b" or "x:y:a(...):u -> b"
+ * that the original source line is unknown, or may be identical to a line of the minified range.
+ * The corresponding Proguard-map syntax is "x:y:a(...) -> b" or "x:y:a(...):u -> b"
*/
public static class MappedRange {
@@ -352,10 +350,10 @@
return nextSequenceNumber++;
}
- public final Range obfuscatedRange; // Can be null, if so then originalRange must also be null.
+ public final Range minifiedRange; // Can be null, if so then originalRange must also be null.
public final MethodSignature signature;
public final Object originalRange; // null, Integer or Range.
- public final String obfuscatedName;
+ public final String renamedName;
/**
* The sole purpose of {@link #sequenceNumber} is to preserve the order of members read from a
@@ -364,52 +362,49 @@
private final int sequenceNumber = getNextSequenceNumber();
private MappedRange(
- Range obfuscatedRange,
- MethodSignature signature,
- Object originalRange,
- String obfuscatedName) {
+ Range minifiedRange, MethodSignature signature, Object originalRange, String renamedName) {
- assert obfuscatedRange != null || originalRange == null;
+ assert minifiedRange != null || originalRange == null;
assert originalRange == null
|| originalRange instanceof Integer
|| originalRange instanceof Range;
- this.obfuscatedRange = obfuscatedRange;
+ this.minifiedRange = minifiedRange;
this.signature = signature;
this.originalRange = originalRange;
- this.obfuscatedName = obfuscatedName;
+ this.renamedName = renamedName;
}
- public int originalLineFromObfuscated(int obfuscatedLineNumber) {
- if (obfuscatedRange == null) {
+ public int getOriginalLineNumber(int lineNumberAfterMinification) {
+ if (minifiedRange == null) {
// General mapping without concrete line numbers: "a() -> b"
- return obfuscatedLineNumber;
+ return lineNumberAfterMinification;
}
- assert obfuscatedRange.contains(obfuscatedLineNumber);
+ assert minifiedRange.contains(lineNumberAfterMinification);
if (originalRange == null) {
// Concrete identity mapping: "x:y:a() -> b"
- return obfuscatedLineNumber;
+ return lineNumberAfterMinification;
} else if (originalRange instanceof Integer) {
// Inlinee: "x:y:a():u -> b"
return (int) originalRange;
} else {
// "x:y:a():u:v -> b"
assert originalRange instanceof Range;
- return ((Range) originalRange).from + obfuscatedLineNumber - obfuscatedRange.from;
+ return ((Range) originalRange).from + lineNumberAfterMinification - minifiedRange.from;
}
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
- if (obfuscatedRange != null) {
- builder.append(obfuscatedRange).append(':');
+ if (minifiedRange != null) {
+ builder.append(minifiedRange).append(':');
}
builder.append(signature);
if (originalRange != null) {
builder.append(":").append(originalRange);
}
- builder.append(" -> ").append(obfuscatedName);
+ builder.append(" -> ").append(renamedName);
return builder.toString();
}
@@ -426,19 +421,19 @@
MappedRange that = (MappedRange) o;
- return Objects.equals(obfuscatedRange, that.obfuscatedRange)
+ return Objects.equals(minifiedRange, that.minifiedRange)
&& Objects.equals(originalRange, that.originalRange)
&& signature.equals(that.signature)
- && obfuscatedName.equals(that.obfuscatedName);
+ && renamedName.equals(that.renamedName);
}
@Override
public int hashCode() {
// sequenceNumber is intentionally omitted from hashCode since it's not used in equality test.
- int result = Objects.hashCode(obfuscatedRange);
+ int result = Objects.hashCode(minifiedRange);
result = 31 * result + Objects.hashCode(originalRange);
result = 31 * result + signature.hashCode();
- result = 31 * result + obfuscatedName.hashCode();
+ result = 31 * result + renamedName.hashCode();
return result;
}
}
diff --git a/src/main/java/com/android/tools/r8/naming/ClassRenamingMapper.java b/src/main/java/com/android/tools/r8/naming/ClassRenamingMapper.java
index 6b283f0..71cbc4f 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassRenamingMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassRenamingMapper.java
@@ -1,3 +1,7 @@
+// 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.naming;
import com.google.common.collect.BiMap;
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java b/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
index 2fd66d0..8714b28 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
@@ -5,9 +5,12 @@
import static com.android.tools.r8.utils.DescriptorUtils.descriptorToJavaType;
+import com.android.tools.r8.cf.code.CfConstString;
+import com.android.tools.r8.cf.code.CfInstruction;
import com.android.tools.r8.code.ConstString;
import com.android.tools.r8.code.ConstStringJumbo;
import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.Code;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.DexEncodedField;
@@ -23,20 +26,18 @@
import com.android.tools.r8.graph.DexValue.DexValueString;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.shaking.ProguardClassFilter;
+import it.unimi.dsi.fastutil.objects.Object2BooleanMap;
import java.util.Map;
-import java.util.Set;
class IdentifierMinifier {
private final AppInfoWithLiveness appInfo;
private final ProguardClassFilter adaptClassStrings;
private final NamingLens lens;
- private final Set<DexItem> identifierNameStrings;
+ private final Object2BooleanMap<DexItem> identifierNameStrings;
IdentifierMinifier(
- AppInfoWithLiveness appInfo,
- ProguardClassFilter adaptClassStrings,
- NamingLens lens) {
+ AppInfoWithLiveness appInfo, ProguardClassFilter adaptClassStrings, NamingLens lens) {
this.appInfo = appInfo;
this.adaptClassStrings = adaptClassStrings;
this.lens = lens;
@@ -86,17 +87,29 @@
if (code == null) {
return;
}
- assert code.isDexCode();
- DexCode dexCode = code.asDexCode();
- for (Instruction instr : dexCode.instructions) {
- if (instr instanceof ConstString) {
- ConstString cnst = (ConstString) instr;
- DexString dexString = cnst.getString();
- cnst.BBBB = getRenamedStringLiteral(dexString);
- } else if (instr instanceof ConstStringJumbo) {
- ConstStringJumbo cnst = (ConstStringJumbo) instr;
- DexString dexString = cnst.getString();
- cnst.BBBBBBBB = getRenamedStringLiteral(dexString);
+ if (code.isDexCode()) {
+ DexCode dexCode = code.asDexCode();
+ for (Instruction instr : dexCode.instructions) {
+ if (instr instanceof ConstString) {
+ ConstString cnst = (ConstString) instr;
+ DexString dexString = cnst.getString();
+ cnst.BBBB = getRenamedStringLiteral(dexString);
+ } else if (instr instanceof ConstStringJumbo) {
+ ConstStringJumbo cnst = (ConstStringJumbo) instr;
+ DexString dexString = cnst.getString();
+ cnst.BBBBBBBB = getRenamedStringLiteral(dexString);
+ }
+ }
+ } else {
+ assert code.isCfCode();
+ CfCode cfCode = code.asCfCode();
+
+ for (CfInstruction instr : cfCode.getInstructions()) {
+ if (instr instanceof CfConstString) {
+ CfConstString cnst = (CfConstString) instr;
+ DexString dexString = cnst.getString();
+ cnst.setString(getRenamedStringLiteral(dexString));
+ }
}
}
}
@@ -153,19 +166,32 @@
if (code == null) {
return;
}
- assert code.isDexCode();
- DexCode dexCode = code.asDexCode();
- for (Instruction instr : dexCode.instructions) {
- if (instr instanceof ConstString
- && ((ConstString) instr).getString() instanceof DexItemBasedString) {
- ConstString cnst = (ConstString) instr;
- DexItemBasedString itemBasedString = (DexItemBasedString) cnst.getString();
- cnst.BBBB = materialize(itemBasedString);
- } else if (instr instanceof ConstStringJumbo
- && ((ConstStringJumbo) instr).getString() instanceof DexItemBasedString) {
- ConstStringJumbo cnst = (ConstStringJumbo) instr;
- DexItemBasedString itemBasedString = (DexItemBasedString) cnst.getString();
- cnst.BBBBBBBB = materialize(itemBasedString);
+ if (code.isDexCode()) {
+ DexCode dexCode = code.asDexCode();
+ for (Instruction instr : dexCode.instructions) {
+ if (instr instanceof ConstString
+ && ((ConstString) instr).getString() instanceof DexItemBasedString) {
+ ConstString cnst = (ConstString) instr;
+ DexItemBasedString itemBasedString = (DexItemBasedString) cnst.getString();
+ cnst.BBBB = materialize(itemBasedString);
+ } else if (instr instanceof ConstStringJumbo
+ && ((ConstStringJumbo) instr).getString() instanceof DexItemBasedString) {
+ ConstStringJumbo cnst = (ConstStringJumbo) instr;
+ DexItemBasedString itemBasedString = (DexItemBasedString) cnst.getString();
+ cnst.BBBBBBBB = materialize(itemBasedString);
+ }
+ }
+ } else {
+ assert code.isCfCode();
+ CfCode cfCode = code.asCfCode();
+
+ for (CfInstruction instr : cfCode.getInstructions()) {
+ if (instr instanceof CfConstString
+ && ((CfConstString) instr).getString() instanceof DexItemBasedString) {
+ CfConstString cnst = (CfConstString) instr;
+ DexItemBasedString itemBasedString = (DexItemBasedString) cnst.getString();
+ cnst.setString(materialize(itemBasedString));
+ }
}
}
}
@@ -184,5 +210,4 @@
return lens.lookupName((DexField) itemBasedString.basedOn);
}
}
-
}
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
index b0f4dc0..830391a 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
@@ -35,17 +35,17 @@
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
import com.google.common.collect.Streams;
+import it.unimi.dsi.fastutil.objects.Object2BooleanMap;
import java.util.Arrays;
import java.util.List;
import java.util.ListIterator;
import java.util.Objects;
-import java.util.Set;
import java.util.stream.Collectors;
public class IdentifierNameStringMarker {
private final AppInfo appInfo;
private final DexItemFactory dexItemFactory;
- private final Set<DexItem> identifierNameStrings;
+ private final Object2BooleanMap<DexItem> identifierNameStrings;
private final InternalOptions options;
public IdentifierNameStringMarker(AppInfoWithLiveness appInfo, InternalOptions options) {
@@ -63,7 +63,7 @@
}
private void decoupleIdentifierNameStringInField(DexEncodedField encodedField) {
- if (!identifierNameStrings.contains(encodedField.field)) {
+ if (!identifierNameStrings.containsKey(encodedField.field)) {
return;
}
if (!encodedField.accessFlags.isStatic()) {
@@ -102,22 +102,27 @@
if (instruction.isStaticPut() || instruction.isInstancePut()) {
FieldInstruction fieldPut = instruction.asFieldInstruction();
DexField field = fieldPut.getField();
- if (!identifierNameStrings.contains(field)) {
+ if (!identifierNameStrings.containsKey(field)) {
continue;
}
+ boolean isExplicitRule = identifierNameStrings.getBoolean(field);
Value in = instruction.isStaticPut()
? instruction.asStaticPut().inValue()
: instruction.asInstancePut().value();
if (!in.isConstString()) {
- warnUndeterminedIdentifierIfNecessary(
- appInfo, options, field, originHolder, instruction, null);
+ if (isExplicitRule) {
+ warnUndeterminedIdentifierIfNecessary(
+ appInfo, options, field, originHolder, instruction, null);
+ }
continue;
}
DexString original = in.getConstInstruction().asConstString().getValue();
DexItemBasedString itemBasedString = inferMemberOrTypeFromNameString(appInfo, original);
if (itemBasedString == null) {
- warnUndeterminedIdentifierIfNecessary(
- appInfo, options, field, originHolder, instruction, original);
+ if (isExplicitRule) {
+ warnUndeterminedIdentifierIfNecessary(
+ appInfo, options, field, originHolder, instruction, original);
+ }
continue;
}
// Move the cursor back to $fieldPut
@@ -163,16 +168,19 @@
} else if (instruction.isInvokeMethod()) {
InvokeMethod invoke = instruction.asInvokeMethod();
DexMethod invokedMethod = invoke.getInvokedMethod();
- if (!identifierNameStrings.contains(invokedMethod)) {
+ if (!identifierNameStrings.containsKey(invokedMethod)) {
continue;
}
+ boolean isExplicitRule = identifierNameStrings.getBoolean(invokedMethod);
List<Value> ins = invoke.arguments();
Value[] changes = new Value [ins.size()];
if (isReflectionMethod(dexItemFactory, invokedMethod)) {
DexItemBasedString itemBasedString = identifyIdentiferNameString(appInfo, invoke);
if (itemBasedString == null) {
- warnUndeterminedIdentifierIfNecessary(
- appInfo, options, invokedMethod, originHolder, instruction, null);
+ if (isExplicitRule) {
+ warnUndeterminedIdentifierIfNecessary(
+ appInfo, options, invokedMethod, originHolder, instruction, null);
+ }
continue;
}
DexType returnType = invoke.getReturnType();
@@ -217,16 +225,20 @@
for (int i = 0; i < ins.size(); i++) {
Value in = ins.get(i);
if (!in.isConstString()) {
- warnUndeterminedIdentifierIfNecessary(
- appInfo, options, invokedMethod, originHolder, instruction, null);
+ if (isExplicitRule) {
+ warnUndeterminedIdentifierIfNecessary(
+ appInfo, options, invokedMethod, originHolder, instruction, null);
+ }
continue;
}
DexString original = in.getConstInstruction().asConstString().getValue();
DexItemBasedString itemBasedString =
inferMemberOrTypeFromNameString(appInfo, original);
if (itemBasedString == null) {
- warnUndeterminedIdentifierIfNecessary(
- appInfo, options, invokedMethod, originHolder, instruction, original);
+ if (isExplicitRule) {
+ warnUndeterminedIdentifierIfNecessary(
+ appInfo, options, invokedMethod, originHolder, instruction, original);
+ }
continue;
}
// Move the cursor back to $invoke
diff --git a/src/main/java/com/android/tools/r8/naming/MemberNaming.java b/src/main/java/com/android/tools/r8/naming/MemberNaming.java
index c766c37..094dcaf 100644
--- a/src/main/java/com/android/tools/r8/naming/MemberNaming.java
+++ b/src/main/java/com/android/tools/r8/naming/MemberNaming.java
@@ -134,7 +134,12 @@
}
public static FieldSignature fromDexField(DexField field) {
- return new FieldSignature(field.name.toSourceString(),
+ return fromDexField(field, false);
+ }
+
+ public static FieldSignature fromDexField(DexField field, boolean withQualifiedName) {
+ return new FieldSignature(
+ withQualifiedName ? field.qualifiedName() : field.name.toSourceString(),
field.type.toSourceString());
}
diff --git a/src/main/java/com/android/tools/r8/naming/Minifier.java b/src/main/java/com/android/tools/r8/naming/Minifier.java
index 382ac92..10aa8af 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -12,6 +12,7 @@
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.naming.ClassNameMinifier.ClassRenaming;
import com.android.tools.r8.naming.MethodNameMinifier.MethodRenaming;
import com.android.tools.r8.optimize.MemberRebindingAnalysis;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
@@ -42,8 +43,8 @@
public NamingLens run(Timing timing) {
assert options.enableMinification;
timing.begin("MinifyClasses");
- Map<DexType, DexString> classRenaming =
- new ClassNameMinifier(appInfo, rootSet, options).computeRenaming(timing);
+ ClassNameMinifier classNameMinifier = new ClassNameMinifier(appInfo, rootSet, options);
+ ClassRenaming classRenaming = classNameMinifier.computeRenaming(timing);
timing.end();
timing.begin("MinifyMethods");
MethodRenaming methodRenaming =
@@ -53,7 +54,12 @@
Map<DexField, DexString> fieldRenaming =
new FieldNameMinifier(appInfo, rootSet, options).computeRenaming(timing);
timing.end();
- NamingLens lens = new MinifiedRenaming(classRenaming, methodRenaming, fieldRenaming, appInfo);
+ NamingLens lens =
+ new MinifiedRenaming(
+ classRenaming,
+ methodRenaming,
+ fieldRenaming,
+ appInfo);
timing.begin("MinifyIdentifiers");
new IdentifierMinifier(appInfo, options.proguardConfiguration.getAdaptClassStrings(), lens)
.run();
@@ -64,21 +70,28 @@
private static class MinifiedRenaming extends NamingLens {
private final AppInfo appInfo;
+ private final Map<String, String> packageRenaming;
private final Map<DexItem, DexString> renaming = new IdentityHashMap<>();
private MinifiedRenaming(
- Map<DexType, DexString> classRenaming,
+ ClassRenaming classRenaming,
MethodRenaming methodRenaming,
Map<DexField, DexString> fieldRenaming,
AppInfo appInfo) {
this.appInfo = appInfo;
- renaming.putAll(classRenaming);
+ this.packageRenaming = classRenaming.packageRenaming;
+ renaming.putAll(classRenaming.classRenaming);
renaming.putAll(methodRenaming.renaming);
renaming.putAll(methodRenaming.callSiteRenaming);
renaming.putAll(fieldRenaming);
}
@Override
+ public String lookupPackageName(String packageName) {
+ return packageRenaming.getOrDefault(packageName, packageName);
+ }
+
+ @Override
public DexString lookupDescriptor(DexType type) {
return renaming.getOrDefault(type, type.descriptor);
}
diff --git a/src/main/java/com/android/tools/r8/naming/NamingLens.java b/src/main/java/com/android/tools/r8/naming/NamingLens.java
index f1f13b3..47459c0 100644
--- a/src/main/java/com/android/tools/r8/naming/NamingLens.java
+++ b/src/main/java/com/android/tools/r8/naming/NamingLens.java
@@ -30,6 +30,8 @@
*/
public abstract class NamingLens {
+ public abstract String lookupPackageName(String packageName);
+
public abstract DexString lookupDescriptor(DexType type);
public abstract String lookupSimpleName(DexType inner, DexString innerName);
@@ -99,6 +101,11 @@
}
@Override
+ public String lookupPackageName(String packageName) {
+ return packageName;
+ }
+
+ @Override
void forAllRenamedTypes(Consumer<DexType> consumer) {
// Intentionally left empty.
}
diff --git a/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java b/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java
index 492bcd0..46fdc82 100644
--- a/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.optimize;
+import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
@@ -88,6 +89,28 @@
}
@Override
+ public DexField getOriginalFieldSignature(DexField field) {
+ return previousLense.getOriginalFieldSignature(field);
+ }
+
+ @Override
+ public DexMethod getOriginalMethodSignature(DexMethod method) {
+ // TODO(b/79143143): implement this when re-enable bridge analysis.
+ throw new Unimplemented("BridgeLense.getOriginalMethodSignature() not implemented");
+ }
+
+ @Override
+ public DexField getRenamedFieldSignature(DexField originalField) {
+ return previousLense.getRenamedFieldSignature(originalField);
+ }
+
+ @Override
+ public DexMethod getRenamedMethodSignature(DexMethod originalMethod) {
+ // TODO(b/79143143): implement this when re-enabling bridge analysis.
+ throw new Unimplemented("BridgeLense.getRenamedMethodSignature() not implemented");
+ }
+
+ @Override
public DexType lookupType(DexType type) {
return previousLense.lookupType(type);
}
diff --git a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
index 73ebf73..50571f3 100644
--- a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
@@ -11,23 +11,16 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.ir.optimize.MethodPoolCollection;
import com.android.tools.r8.optimize.PublicizerLense.PublicizedLenseBuilder;
import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
import com.android.tools.r8.utils.MethodSignatureEquivalence;
-import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
import com.google.common.base.Equivalence;
-import com.google.common.base.Equivalence.Wrapper;
-import java.util.ArrayList;
-import java.util.HashSet;
import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Future;
public final class ClassAndMemberPublicizer {
private final DexApplication application;
@@ -36,11 +29,12 @@
private final PublicizedLenseBuilder lenseBuilder;
private final Equivalence<DexMethod> equivalence = MethodSignatureEquivalence.get();
- private final Map<DexClass, MethodPool> methodPools = new ConcurrentHashMap<>();
+ private final MethodPoolCollection methodPoolCollection;
private ClassAndMemberPublicizer(DexApplication application, AppView appView, RootSet rootSet) {
this.application = application;
this.appView = appView;
+ this.methodPoolCollection = new MethodPoolCollection(application);
this.rootSet = rootSet;
lenseBuilder = PublicizerLense.createBuilder();
}
@@ -64,15 +58,7 @@
private GraphLense run(ExecutorService executorService, Timing timing)
throws ExecutionException {
// Phase 1: Collect methods to check if private instance methods don't have conflicts.
- timing.begin("Phase 1: collectMethods");
- try {
- List<Future<?>> futures = new ArrayList<>();
- application.classes().forEach(clazz ->
- futures.add(executorService.submit(computeMethodPoolPerClass(clazz))));
- ThreadUtils.awaitFutures(futures);
- } finally {
- timing.end();
- }
+ methodPoolCollection.buildAll(executorService, timing);
// Phase 2: Visit classes and promote class/member to public if possible.
timing.begin("Phase 2: promoteToPublic");
@@ -83,35 +69,6 @@
return lenseBuilder.build(appView);
}
- private Runnable computeMethodPoolPerClass(DexClass clazz) {
- return () -> {
- MethodPool methodPool = methodPools.computeIfAbsent(clazz, k -> new MethodPool());
- clazz.forEachMethod(encodedMethod -> {
- // We will add private instance methods when we promote them.
- if (!encodedMethod.isPrivateMethod() || encodedMethod.isStaticMethod()) {
- methodPool.seen(equivalence.wrap(encodedMethod.method));
- }
- });
- if (clazz.superType != null) {
- DexClass superClazz = application.definitionFor(clazz.superType);
- if (superClazz != null) {
- MethodPool superPool = methodPools.computeIfAbsent(superClazz, k -> new MethodPool());
- superPool.linkSubtype(methodPool);
- methodPool.linkSupertype(superPool);
- }
- }
- if (clazz.isInterface()) {
- clazz.type.forAllImplementsSubtypes(implementer -> {
- DexClass subClazz = application.definitionFor(implementer);
- if (subClazz != null) {
- MethodPool childPool = methodPools.computeIfAbsent(subClazz, k -> new MethodPool());
- childPool.linkInterface(methodPool);
- }
- });
- }
- };
- }
-
private void publicizeType(DexType type) {
DexClass clazz = application.definitionFor(type);
if (clazz != null && clazz.isProgramClass()) {
@@ -149,7 +106,8 @@
assert accessFlags.isPrivate();
if (appView.getDexItemFactory().isConstructor(encodedMethod.method)) {
- // TODO(b/72211928)
+ accessFlags.unsetPrivate();
+ accessFlags.setPublic();
return false;
}
@@ -166,9 +124,8 @@
return false;
}
- MethodPool methodPool = methodPools.get(holder);
- Wrapper<DexMethod> key = equivalence.wrap(encodedMethod.method);
- if (methodPool.hasSeen(key)) {
+ boolean wasSeen = methodPoolCollection.markIfNotSeen(holder, encodedMethod.method);
+ if (wasSeen) {
// We can't do anything further because even renaming is not allowed due to the keep rule.
if (rootSet.noObfuscation.contains(encodedMethod)) {
return false;
@@ -176,7 +133,6 @@
// TODO(b/111118390): Renaming will enable more private instance methods to be publicized.
return false;
}
- methodPool.seen(key);
lenseBuilder.add(encodedMethod.method);
accessFlags.unsetPrivate();
accessFlags.setFinal();
@@ -196,51 +152,4 @@
accessFlags.setPublic();
return false;
}
-
- // Per-class collection of method signatures, which will be used to determine if a certain method
- // can be publicized or not.
- static class MethodPool {
- private MethodPool superType;
- private final Set<MethodPool> interfaces = new HashSet<>();
- private final Set<MethodPool> subTypes = new HashSet<>();
- private final Set<Wrapper<DexMethod>> methodPool = new HashSet<>();
-
- MethodPool() {
- }
-
- synchronized void linkSupertype(MethodPool superType) {
- assert this.superType == null;
- this.superType = superType;
- }
-
- synchronized void linkSubtype(MethodPool subType) {
- boolean added = subTypes.add(subType);
- assert added;
- }
-
- synchronized void linkInterface(MethodPool itf) {
- boolean added = interfaces.add(itf);
- assert added;
- }
-
- synchronized void seen(Wrapper<DexMethod> method) {
- boolean added = methodPool.add(method);
- assert added;
- }
-
- boolean hasSeen(Wrapper<DexMethod> method) {
- return hasSeenUpwardRecursive(method) || hasSeenDownwardRecursive(method);
- }
-
- private boolean hasSeenUpwardRecursive(Wrapper<DexMethod> method) {
- return methodPool.contains(method)
- || (superType != null && superType.hasSeenUpwardRecursive(method))
- || interfaces.stream().anyMatch(itf -> itf.hasSeenUpwardRecursive(method));
- }
-
- private boolean hasSeenDownwardRecursive(Wrapper<DexMethod> method) {
- return methodPool.contains(method)
- || subTypes.stream().anyMatch(subType -> subType.hasSeenDownwardRecursive(method));
- }
- }
}
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
index fe098cc..c1d7403 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -12,7 +12,7 @@
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLense;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
@@ -208,14 +208,14 @@
if (holder == null) {
return false;
}
- Constraint classVisibility =
- Constraint.deriveConstraint(context, holderType, holder.accessFlags, appInfo);
- if (classVisibility == Constraint.NEVER) {
+ ConstraintWithTarget classVisibility =
+ ConstraintWithTarget.deriveConstraint(context, holderType, holder.accessFlags, appInfo);
+ if (classVisibility == ConstraintWithTarget.NEVER) {
return false;
}
- Constraint fieldVisibility =
- Constraint.deriveConstraint(context, holderType, field.accessFlags, appInfo);
- return fieldVisibility != Constraint.NEVER;
+ ConstraintWithTarget fieldVisibility =
+ ConstraintWithTarget.deriveConstraint(context, holderType, field.accessFlags, appInfo);
+ return fieldVisibility != ConstraintWithTarget.NEVER;
}
private Map<DexField, Set<DexEncodedMethod>> mergeFieldAccessContexts(
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingLense.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingLense.java
index 4ee0cee..98a0da4 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingLense.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingLense.java
@@ -38,7 +38,8 @@
Map<DexMethod, DexMethod> methodMap,
Map<DexField, DexField> fieldMap,
GraphLense previousLense) {
- super(ImmutableMap.of(), methodMap, fieldMap, previousLense, appInfo.dexItemFactory);
+ super(
+ ImmutableMap.of(), methodMap, fieldMap, null, null, previousLense, appInfo.dexItemFactory);
this.appInfo = appInfo;
}
diff --git a/src/main/java/com/android/tools/r8/optimize/PublicizerLense.java b/src/main/java/com/android/tools/r8/optimize/PublicizerLense.java
index 7a9c08f..ad7effd 100644
--- a/src/main/java/com/android/tools/r8/optimize/PublicizerLense.java
+++ b/src/main/java/com/android/tools/r8/optimize/PublicizerLense.java
@@ -25,6 +25,8 @@
ImmutableMap.of(),
ImmutableMap.of(),
ImmutableMap.of(),
+ null,
+ null,
appView.getGraphLense(),
appView.getAppInfo().dexItemFactory);
this.appView = appView;
diff --git a/src/main/java/com/android/tools/r8/position/TextPosition.java b/src/main/java/com/android/tools/r8/position/TextPosition.java
index 0dd4090..08d13bc 100644
--- a/src/main/java/com/android/tools/r8/position/TextPosition.java
+++ b/src/main/java/com/android/tools/r8/position/TextPosition.java
@@ -54,12 +54,12 @@
@Override
public String toString() {
- return "Offset: " + offset + ", Line: " + line + ", column: " + column;
+ return "offset: " + offset + ", line: " + line + ", column: " + column;
}
@Override
public String getDescription() {
- return "Line: " + line + (column != UNKNOWN_COLUMN ? ", column: " + column: "");
+ return "line " + line + (column != UNKNOWN_COLUMN ? (", column " + column) : "");
}
@Override
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 6a12b60..27d3d3b 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -5,8 +5,6 @@
import static com.android.tools.r8.naming.IdentifierNameStringUtils.identifyIdentiferNameString;
import static com.android.tools.r8.naming.IdentifierNameStringUtils.isReflectionMethod;
-import static com.android.tools.r8.naming.IdentifierNameStringUtils.warnUndeterminedIdentifierIfNecessary;
-import static com.android.tools.r8.shaking.ProguardConfigurationUtils.buildIdentifierNameStringRule;
import com.android.tools.r8.Diagnostic;
import com.android.tools.r8.dex.IndexedItemCollection;
@@ -58,6 +56,8 @@
import com.google.common.collect.Sets;
import com.google.common.collect.Sets.SetView;
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import it.unimi.dsi.fastutil.objects.Object2BooleanArrayMap;
+import it.unimi.dsi.fastutil.objects.Object2BooleanMap;
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
import java.util.ArrayDeque;
import java.util.Collection;
@@ -94,6 +94,7 @@
private boolean tracingMainDex = false;
private final AppInfoWithSubtyping appInfo;
+ private final GraphLense graphLense;
private final InternalOptions options;
private RootSet rootSet;
@@ -186,10 +187,9 @@
private final Queue<Action> proguardCompatibilityWorkList = Queues.newArrayDeque();
/**
- * A set of methods that need code inspection for Proguard compatibility rules.
+ * A set of methods that need code inspection for Java reflection in use.
*/
- private final Set<DexEncodedMethod> pendingProguardReflectiveCompatibility =
- Sets.newLinkedHashSet();
+ private final Set<DexEncodedMethod> pendingReflectiveUses = Sets.newLinkedHashSet();
/**
* A cache for DexMethod that have been marked reachable.
@@ -218,15 +218,23 @@
*/
private final ProguardConfiguration.Builder compatibility;
- public Enqueuer(AppInfoWithSubtyping appInfo, InternalOptions options,
+ public Enqueuer(
+ AppInfoWithSubtyping appInfo,
+ GraphLense graphLense,
+ InternalOptions options,
boolean forceProguardCompatibility) {
- this(appInfo, options, forceProguardCompatibility, null, null);
+ this(appInfo, graphLense, options, forceProguardCompatibility, null, null);
}
- public Enqueuer(AppInfoWithSubtyping appInfo, InternalOptions options,
+ public Enqueuer(
+ AppInfoWithSubtyping appInfo,
+ GraphLense graphLense,
+ InternalOptions options,
boolean forceProguardCompatibility,
- ProguardConfiguration.Builder compatibility, ProtoLiteExtension protoLiteExtension) {
+ ProguardConfiguration.Builder compatibility,
+ ProtoLiteExtension protoLiteExtension) {
this.appInfo = appInfo;
+ this.graphLense = graphLense;
this.compatibility = compatibility;
this.options = options;
this.protoLiteExtension = protoLiteExtension;
@@ -305,13 +313,10 @@
boolean registerInvokeVirtual(DexMethod method, KeepReason keepReason) {
if (appInfo.dexItemFactory.classMethods.isReflectiveMemberLookup(method)) {
- if (forceProguardCompatibility) {
- // TODO(b/76181966): whether or not add this rule in normal mode.
- if (identifierNameStrings.add(method) && compatibility != null) {
- compatibility.addRule(buildIdentifierNameStringRule(method));
- }
- pendingProguardReflectiveCompatibility.add(currentMethod);
- }
+ // Implicitly add -identifiernamestring rule for the Java reflection in use.
+ identifierNameStrings.add(method);
+ // Revisit the current method to implicitly add -keep rule for items with reflective access.
+ pendingReflectiveUses.add(currentMethod);
}
if (!registerItemWithTarget(virtualInvokes, method)) {
return false;
@@ -347,13 +352,10 @@
boolean registerInvokeStatic(DexMethod method, KeepReason keepReason) {
if (method == appInfo.dexItemFactory.classMethods.forName
|| appInfo.dexItemFactory.atomicFieldUpdaterMethods.isFieldUpdater(method)) {
- if (forceProguardCompatibility) {
- // TODO(b/76181966): whether or not add this rule in normal mode.
- if (identifierNameStrings.add(method) && compatibility != null) {
- compatibility.addRule(buildIdentifierNameStringRule(method));
- }
- pendingProguardReflectiveCompatibility.add(currentMethod);
- }
+ // Implicitly add -identifiernamestring rule for the Java reflection in use.
+ identifierNameStrings.add(method);
+ // Revisit the current method to implicitly add -keep rule for items with reflective access.
+ pendingReflectiveUses.add(currentMethod);
}
if (!registerItemWithTarget(staticInvokes, method)) {
return false;
@@ -1270,15 +1272,15 @@
}
// Continue fix-point processing while there are additional work items to ensure
- // Proguard compatibility.
+ // items that are passed to Java reflections are traced.
if (proguardCompatibilityWorkList.isEmpty()
- && pendingProguardReflectiveCompatibility.isEmpty()) {
+ && pendingReflectiveUses.isEmpty()) {
break;
}
- pendingProguardReflectiveCompatibility.forEach(this::handleProguardReflectiveBehavior);
+ pendingReflectiveUses.forEach(this::handleReflectiveBehavior);
workList.addAll(proguardCompatibilityWorkList);
proguardCompatibilityWorkList.clear();
- pendingProguardReflectiveCompatibility.clear();
+ pendingReflectiveUses.clear();
}
if (Log.ENABLED) {
Set<DexEncodedMethod> allLive = Sets.newIdentityHashSet();
@@ -1475,15 +1477,15 @@
Action.markMethodLive(method, KeepReason.dueToProguardCompatibilityKeepRule(rule)));
}
- private void handleProguardReflectiveBehavior(DexEncodedMethod method) {
+ private void handleReflectiveBehavior(DexEncodedMethod method) {
DexType originHolder = method.method.holder;
Origin origin = appInfo.originFor(originHolder);
- IRCode code = method.buildIR(appInfo, options, origin);
+ IRCode code = method.buildIR(appInfo, graphLense, options, origin);
code.instructionIterator().forEachRemaining(instr ->
- handleProguardReflectiveBehavior(instr, originHolder));
+ handleReflectiveBehavior(instr, originHolder));
}
- private void handleProguardReflectiveBehavior(Instruction instruction, DexType originHolder) {
+ private void handleReflectiveBehavior(Instruction instruction, DexType originHolder) {
if (!instruction.isInvokeMethod()) {
return;
}
@@ -1494,8 +1496,6 @@
}
DexItemBasedString itemBasedString = identifyIdentiferNameString(appInfo, invoke);
if (itemBasedString == null) {
- warnUndeterminedIdentifierIfNecessary(
- appInfo, options, invokedMethod, originHolder, instruction, null);
return;
}
if (itemBasedString.basedOn instanceof DexType) {
@@ -1672,19 +1672,21 @@
/**
* All methods that should be inlined if possible due to a configuration directive.
*/
- public final Set<DexItem> alwaysInline;
+ public final Set<DexMethod> alwaysInline;
/**
* All methods that *must* be inlined due to a configuration directive (testing only).
*/
- public final Set<DexItem> forceInline;
+ public final Set<DexMethod> forceInline;
/**
* All methods that *must* never be inlined due to a configuration directive (testing only).
*/
- public final Set<DexItem> neverInline;
+ public final Set<DexMethod> neverInline;
/**
* All items with -identifiernamestring rule.
+ * Bound boolean value indicates the rule is explicitly specified by users (<code>true</code>)
+ * or not, i.e., implicitly added by R8 (<code>false</code>).
*/
- public final Set<DexItem> identifierNameStrings;
+ public final Object2BooleanMap<DexItem> identifierNameStrings;
/**
* Set of fields that have been identified as proto-lite fields by the
* {@link ProtoLiteExtension}.
@@ -1741,8 +1743,8 @@
this.alwaysInline = enqueuer.rootSet.alwaysInline;
this.forceInline = enqueuer.rootSet.forceInline;
this.neverInline = enqueuer.rootSet.neverInline;
- this.identifierNameStrings =
- Sets.union(enqueuer.rootSet.identifierNameStrings, enqueuer.identifierNameStrings);
+ this.identifierNameStrings = joinIdentifierNameStrings(
+ enqueuer.rootSet.identifierNameStrings, enqueuer.identifierNameStrings);
this.protoLiteFields = enqueuer.protoLiteFields;
this.prunedTypes = Collections.emptySet();
this.switchMaps = Collections.emptyMap();
@@ -1825,10 +1827,12 @@
this.noSideEffects = previous.noSideEffects;
assert lense.assertNotModified(previous.assumedValues.keySet());
this.assumedValues = previous.assumedValues;
- assert lense.assertNotModified(previous.alwaysInline);
+ assert lense.assertNotModified(
+ previous.alwaysInline.stream().map(this::definitionFor).filter(Objects::nonNull)
+ .collect(Collectors.toList()));
this.alwaysInline = previous.alwaysInline;
- this.forceInline = previous.forceInline;
- this.neverInline = previous.neverInline;
+ this.forceInline = rewriteMethodsWithRenamedSignature(previous.forceInline, lense);
+ this.neverInline = rewriteMethodsWithRenamedSignature(previous.neverInline, lense);
this.identifierNameStrings =
rewriteMixedItemsConservatively(previous.identifierNameStrings, lense);
// Switchmap classes should never be affected by renaming.
@@ -1913,6 +1917,18 @@
.collect(ImmutableSortedSet.toImmutableSortedSet(PresortedComparable::slowCompare));
}
+ private Object2BooleanMap<DexItem> joinIdentifierNameStrings(
+ Set<DexItem> explicit, Set<DexItem> implicit) {
+ Object2BooleanMap<DexItem> result = new Object2BooleanArrayMap<>();
+ for (DexItem e : explicit) {
+ result.putIfAbsent(e, true);
+ }
+ for (DexItem i : implicit) {
+ result.putIfAbsent(i, false);
+ }
+ return result;
+ }
+
private <T extends PresortedComparable<T>> SortedSet<T> toSortedDescriptorSet(
Set<? extends KeyedDexItem<T>> set) {
ImmutableSortedSet.Builder<T> builder =
@@ -1940,6 +1956,16 @@
return builder.build();
}
+ private static ImmutableSortedSet<DexMethod> rewriteMethodsWithRenamedSignature(
+ Set<DexMethod> methods, GraphLense lense) {
+ ImmutableSortedSet.Builder<DexMethod> builder =
+ new ImmutableSortedSet.Builder<>(PresortedComparable::slowCompare);
+ for (DexMethod method : methods) {
+ builder.add(lense.getRenamedMethodSignature(method));
+ }
+ return builder.build();
+ }
+
private static ImmutableSortedSet<DexMethod> rewriteMethodsConservatively(
Set<DexMethod> original, GraphLense lense) {
ImmutableSortedSet.Builder<DexMethod> builder =
@@ -2022,6 +2048,32 @@
return builder.build();
}
+ private static Object2BooleanMap<DexItem> rewriteMixedItemsConservatively(
+ Object2BooleanMap<DexItem> original, GraphLense lense) {
+ Object2BooleanMap<DexItem> result = new Object2BooleanArrayMap<>();
+ for (Object2BooleanMap.Entry<DexItem> entry : original.object2BooleanEntrySet()) {
+ DexItem item = entry.getKey();
+ // TODO(b/67934123) There should be a common interface to perform the dispatch.
+ if (item instanceof DexType) {
+ result.put(lense.lookupType((DexType) item), entry.getBooleanValue());
+ } else if (item instanceof DexMethod) {
+ DexMethod method = (DexMethod) item;
+ if (lense.isContextFreeForMethod(method)) {
+ result.put(lense.lookupMethod(method), entry.getBooleanValue());
+ } else {
+ for (DexMethod candidate: lense.lookupMethodInAllContexts(method)) {
+ result.put(candidate, entry.getBooleanValue());
+ }
+ }
+ } else if (item instanceof DexField) {
+ result.put(lense.lookupField((DexField) item), entry.getBooleanValue());
+ } else {
+ throw new Unreachable();
+ }
+ }
+ return result;
+ }
+
private static <T> Set<T> mergeSets(Collection<T> first, Collection<T> second) {
ImmutableSet.Builder<T> builder = ImmutableSet.builder();
builder.addAll(first);
diff --git a/src/main/java/com/android/tools/r8/shaking/InlineRule.java b/src/main/java/com/android/tools/r8/shaking/InlineRule.java
index 565856c..f263bd6 100644
--- a/src/main/java/com/android/tools/r8/shaking/InlineRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/InlineRule.java
@@ -4,6 +4,8 @@
package com.android.tools.r8.shaking;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
import java.util.List;
public class InlineRule extends ProguardConfigurationRule {
@@ -12,20 +14,27 @@
ALWAYS, FORCE, NEVER
}
- public static class Builder extends ProguardConfigurationRule.Builder {
+ public static class Builder extends ProguardConfigurationRule.Builder<InlineRule, Builder> {
private Builder() {
+ super();
}
Type type;
+ @Override
+ public Builder self() {
+ return this;
+ }
+
public Builder setType(Type type) {
this.type = type;
return this;
}
+ @Override
public InlineRule build() {
- return new InlineRule(classAnnotation, classAccessFlags,
+ return new InlineRule(origin, getPosition(), source, classAnnotation, classAccessFlags,
negatedClassAccessFlags, classTypeNegated, classType, classNames, inheritanceAnnotation,
inheritanceClassName, inheritanceIsExtends, memberRules, type);
}
@@ -34,6 +43,9 @@
private final Type type;
private InlineRule(
+ Origin origin,
+ Position position,
+ String source,
ProguardTypeMatcher classAnnotation,
ProguardAccessFlags classAccessFlags,
ProguardAccessFlags negatedClassAccessFlags,
@@ -45,13 +57,14 @@
boolean inheritanceIsExtends,
List<ProguardMemberRule> memberRules,
Type type) {
- super(classAnnotation, classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType,
- classNames, inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
+ super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
+ classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
+ inheritanceIsExtends, memberRules);
this.type = type;
}
- public static InlineRule.Builder builder() {
- return new InlineRule.Builder();
+ public static Builder builder() {
+ return new Builder();
}
public Type getType() {
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardAssumeNoSideEffectRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardAssumeNoSideEffectRule.java
index 39d0335..4c1f9ef 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardAssumeNoSideEffectRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardAssumeNoSideEffectRule.java
@@ -3,22 +3,36 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
import java.util.List;
public class ProguardAssumeNoSideEffectRule extends ProguardConfigurationRule {
- public static class Builder extends ProguardClassSpecification.Builder {
+ public static class Builder
+ extends ProguardConfigurationRule.Builder<ProguardAssumeNoSideEffectRule, Builder> {
- private Builder() {}
+ private Builder() {
+ super();
+ }
+ @Override
+ public Builder self() {
+ return this;
+ }
+
+ @Override
public ProguardAssumeNoSideEffectRule build() {
- return new ProguardAssumeNoSideEffectRule(classAnnotation, classAccessFlags,
- negatedClassAccessFlags, classTypeNegated, classType, classNames, inheritanceAnnotation,
- inheritanceClassName, inheritanceIsExtends, memberRules);
+ return new ProguardAssumeNoSideEffectRule(origin, getPosition(), source, classAnnotation,
+ classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType, classNames,
+ inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
}
}
private ProguardAssumeNoSideEffectRule(
+ Origin origin,
+ Position position,
+ String source,
ProguardTypeMatcher classAnnotation,
ProguardAccessFlags classAccessFlags,
ProguardAccessFlags negatedClassAccessFlags,
@@ -29,8 +43,9 @@
ProguardTypeMatcher inheritanceClassName,
boolean inheritanceIsExtends,
List<ProguardMemberRule> memberRules) {
- super(classAnnotation, classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType,
- classNames, inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
+ super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
+ classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
+ inheritanceIsExtends, memberRules);
}
/**
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardAssumeValuesRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardAssumeValuesRule.java
index 0dafb11..8b8f4dd 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardAssumeValuesRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardAssumeValuesRule.java
@@ -3,21 +3,36 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
import java.util.List;
public class ProguardAssumeValuesRule extends ProguardConfigurationRule {
- public static class Builder extends ProguardClassSpecification.Builder {
- private Builder() {}
+ public static class Builder
+ extends ProguardConfigurationRule.Builder<ProguardAssumeValuesRule, Builder> {
+ private Builder() {
+ super();
+ }
+
+ @Override
+ public Builder self() {
+ return this;
+ }
+
+ @Override
public ProguardAssumeValuesRule build() {
- return new ProguardAssumeValuesRule(classAnnotation, classAccessFlags, negatedClassAccessFlags,
- classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
- inheritanceIsExtends, memberRules);
+ return new ProguardAssumeValuesRule(origin, getPosition(), source, classAnnotation,
+ classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType, classNames,
+ inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
}
}
private ProguardAssumeValuesRule(
+ Origin origin,
+ Position position,
+ String source,
ProguardTypeMatcher classAnnotation,
ProguardAccessFlags classAccessFlags,
ProguardAccessFlags negatedClassAccessFlags,
@@ -28,8 +43,9 @@
ProguardTypeMatcher inheritanceClassName,
boolean inheritanceIsExtends,
List<ProguardMemberRule> memberRules) {
- super(classAnnotation, classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType,
- classNames, inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
+ super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
+ classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
+ inheritanceIsExtends, memberRules);
}
/**
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardCheckDiscardRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardCheckDiscardRule.java
index 2461732..664cb25 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardCheckDiscardRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardCheckDiscardRule.java
@@ -3,23 +3,36 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
import java.util.List;
public class ProguardCheckDiscardRule extends ProguardConfigurationRule {
- public static class Builder extends ProguardConfigurationRule.Builder {
+ public static class Builder extends
+ ProguardConfigurationRule.Builder<ProguardCheckDiscardRule, Builder> {
private Builder() {
+ super();
}
+ @Override
+ public Builder self() {
+ return this;
+ }
+
+ @Override
public ProguardCheckDiscardRule build() {
- return new ProguardCheckDiscardRule(classAnnotation, classAccessFlags,
- negatedClassAccessFlags, classTypeNegated, classType, classNames, inheritanceAnnotation,
- inheritanceClassName, inheritanceIsExtends, memberRules);
+ return new ProguardCheckDiscardRule(origin, getPosition(), source, classAnnotation,
+ classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType, classNames,
+ inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
}
}
private ProguardCheckDiscardRule(
+ Origin origin,
+ Position position,
+ String source,
ProguardTypeMatcher classAnnotation,
ProguardAccessFlags classAccessFlags,
ProguardAccessFlags negatedClassAccessFlags,
@@ -30,8 +43,9 @@
ProguardTypeMatcher inheritanceClassName,
boolean inheritanceIsExtends,
List<ProguardMemberRule> memberRules) {
- super(classAnnotation, classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType,
- classNames, inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
+ super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
+ classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
+ inheritanceIsExtends, memberRules);
}
public static Builder builder() {
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java b/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java
index 8de361e..5d610a8 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java
@@ -3,6 +3,10 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+import com.android.tools.r8.position.TextPosition;
+import com.android.tools.r8.position.TextRange;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableList;
import java.util.LinkedList;
@@ -11,8 +15,13 @@
public abstract class ProguardClassSpecification {
- public static class Builder {
+ public abstract static class
+ Builder<C extends ProguardClassSpecification, B extends Builder<C, B>> {
+ protected Origin origin;
+ protected Position start;
+ protected Position end;
+ protected String source;
protected ProguardTypeMatcher classAnnotation;
protected ProguardAccessFlags classAccessFlags = new ProguardAccessFlags();
protected ProguardAccessFlags negatedClassAccessFlags = new ProguardAccessFlags();
@@ -25,6 +34,46 @@
protected List<ProguardMemberRule> memberRules = new LinkedList<>();
protected Builder() {
+ this(Origin.unknown(), Position.UNKNOWN);
+ }
+
+ protected Builder(Origin origin, Position start) {
+ this.origin = origin;
+ this.start = start;
+ }
+
+ public abstract C build();
+
+ public abstract B self();
+
+ public B setOrigin(Origin origin) {
+ this.origin = origin;
+ return self();
+ }
+
+ public B setStart(Position start) {
+ this.start = start;
+ return self();
+ }
+
+ public B setEnd(Position end) {
+ this.end = end;
+ return self();
+ }
+
+ public B setSource(String source) {
+ this.source = source;
+ return self();
+ }
+
+ public Position getPosition() {
+ if (start == null) {
+ return Position.UNKNOWN;
+ }
+ if (end == null || !((start instanceof TextPosition) && (end instanceof TextPosition))) {
+ return start;
+ }
+ return new TextRange((TextPosition) start, (TextPosition) end);
}
public List<ProguardMemberRule> getMemberRules() {
@@ -117,6 +166,9 @@
}
}
+ private final Origin origin;
+ private final Position position;
+ private final String source;
private final ProguardTypeMatcher classAnnotation;
private final ProguardAccessFlags classAccessFlags;
private final ProguardAccessFlags negatedClassAccessFlags;
@@ -129,6 +181,9 @@
private final List<ProguardMemberRule> memberRules;
protected ProguardClassSpecification(
+ Origin origin,
+ Position position,
+ String source,
ProguardTypeMatcher classAnnotation,
ProguardAccessFlags classAccessFlags,
ProguardAccessFlags negatedClassAccessFlags,
@@ -139,6 +194,9 @@
ProguardTypeMatcher inheritanceClassName,
boolean inheritanceIsExtends,
List<ProguardMemberRule> memberRules) {
+ this.origin = origin;
+ this.position = position;
+ this.source =source;
this.classAnnotation = classAnnotation;
this.classAccessFlags = classAccessFlags;
this.negatedClassAccessFlags = negatedClassAccessFlags;
@@ -152,6 +210,18 @@
this.memberRules = memberRules;
}
+ public Origin getOrigin() {
+ return origin;
+ }
+
+ public Position getPosition() {
+ return position;
+ }
+
+ public String getSource() {
+ return source;
+ }
+
public List<ProguardMemberRule> getMemberRules() {
return memberRules;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
index 54b90b2..599d444 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -55,9 +55,10 @@
private Origin keepParameterNamesOptionOrigin;
private Position keepParameterNamesOptionPosition;
private final ProguardClassFilter.Builder adaptClassStrings = ProguardClassFilter.builder();
- private final ProguardPathFilter.Builder adaptResourceFilenames = ProguardPathFilter.builder();
- private final ProguardPathFilter.Builder adaptResourceFilecontents =
- ProguardPathFilter.builder();
+ private final ProguardPathFilter.Builder adaptResourceFilenames =
+ ProguardPathFilter.builder().disable();
+ private final ProguardPathFilter.Builder adaptResourceFileContents =
+ ProguardPathFilter.builder().disable();
private final ProguardPathFilter.Builder keepDirectories = ProguardPathFilter.builder();
private boolean forceProguardCompatibility = false;
private boolean overloadAggressively;
@@ -231,12 +232,20 @@
adaptClassStrings.addPattern(pattern);
}
+ public void enableAdaptResourceFilenames() {
+ adaptResourceFilenames.enable();
+ }
+
public void addAdaptResourceFilenames(ProguardPathList pattern) {
adaptResourceFilenames.addPattern(pattern);
}
- public void addAdaptResourceFilecontents(ProguardPathList pattern) {
- adaptResourceFilecontents.addPattern(pattern);
+ public void enableAdaptResourceFileContents() {
+ adaptResourceFileContents.enable();
+ }
+
+ public void addAdaptResourceFileContents(ProguardPathList pattern) {
+ adaptResourceFileContents.addPattern(pattern);
}
public void addKeepDirectories(ProguardPathList pattern) {
@@ -288,7 +297,7 @@
keepParameterNames,
adaptClassStrings.build(),
adaptResourceFilenames.build(),
- adaptResourceFilecontents.build(),
+ adaptResourceFileContents.build(),
keepDirectories.build());
reporter.failIfPendingErrors();
@@ -357,7 +366,7 @@
private final boolean keepParameterNames;
private final ProguardClassFilter adaptClassStrings;
private final ProguardPathFilter adaptResourceFilenames;
- private final ProguardPathFilter adaptResourceFilecontents;
+ private final ProguardPathFilter adaptResourceFileContents;
private final ProguardPathFilter keepDirectories;
private ProguardConfiguration(
@@ -395,7 +404,7 @@
boolean keepParameterNames,
ProguardClassFilter adaptClassStrings,
ProguardPathFilter adaptResourceFilenames,
- ProguardPathFilter adaptResourceFilecontents,
+ ProguardPathFilter adaptResourceFileContents,
ProguardPathFilter keepDirectories) {
this.parsedConfiguration = parsedConfiguration;
this.dexItemFactory = factory;
@@ -431,7 +440,7 @@
this.keepParameterNames = keepParameterNames;
this.adaptClassStrings = adaptClassStrings;
this.adaptResourceFilenames = adaptResourceFilenames;
- this.adaptResourceFilecontents = adaptResourceFilecontents;
+ this.adaptResourceFileContents = adaptResourceFileContents;
this.keepDirectories = keepDirectories;
}
@@ -575,8 +584,8 @@
return adaptResourceFilenames;
}
- public ProguardPathFilter getAdaptResourceFilecontents() {
- return adaptResourceFilecontents;
+ public ProguardPathFilter getAdaptResourceFileContents() {
+ return adaptResourceFileContents;
}
public ProguardPathFilter getKeepDirectories() {
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index 5b22456..d1a0b3d 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -228,7 +228,7 @@
} else if (acceptString("keepparameternames")) {
configurationBuilder.setKeepParameterNames(true, origin, getPosition(optionStart));
} else if (acceptString("checkdiscard")) {
- ProguardCheckDiscardRule rule = parseCheckDiscardRule();
+ ProguardCheckDiscardRule rule = parseCheckDiscardRule(optionStart);
configurationBuilder.addRule(rule);
} else if (acceptString("keepdirectories")) {
// TODO(74279367): Report an error until it's fully supported.
@@ -237,10 +237,10 @@
}
parsePathFilter(configurationBuilder::addKeepDirectories);
} else if (acceptString("keep")) {
- ProguardKeepRule rule = parseKeepRule();
+ ProguardKeepRule rule = parseKeepRule(optionStart);
configurationBuilder.addRule(rule);
} else if (acceptString("whyareyoukeeping")) {
- ProguardWhyAreYouKeepingRule rule = parseWhyAreYouKeepingRule();
+ ProguardWhyAreYouKeepingRule rule = parseWhyAreYouKeepingRule(optionStart);
configurationBuilder.addRule(rule);
} else if (acceptString("dontoptimize")) {
configurationBuilder.disableOptimization();
@@ -316,10 +316,10 @@
} else if (acceptString("applymapping")) {
configurationBuilder.setApplyMappingFile(parseFileName());
} else if (acceptString("assumenosideeffects")) {
- ProguardAssumeNoSideEffectRule rule = parseAssumeNoSideEffectsRule();
+ ProguardAssumeNoSideEffectRule rule = parseAssumeNoSideEffectsRule(optionStart);
configurationBuilder.addRule(rule);
} else if (acceptString("assumevalues")) {
- ProguardAssumeValuesRule rule = parseAssumeValuesRule();
+ ProguardAssumeValuesRule rule = parseAssumeValuesRule(optionStart);
configurationBuilder.addRule(rule);
} else if (acceptString("include")) {
// Collect the parsed configuration until the include.
@@ -348,35 +348,29 @@
} else if (acceptString("packageobfuscationdictionary")) {
configurationBuilder.setPackageObfuscationDictionary(parseFileName());
} else if (acceptString("alwaysinline")) {
- InlineRule rule = parseInlineRule(Type.ALWAYS);
+ InlineRule rule = parseInlineRule(Type.ALWAYS, optionStart);
configurationBuilder.addRule(rule);
} else if (allowTestOptions && acceptString("forceinline")) {
- InlineRule rule = parseInlineRule(Type.FORCE);
+ InlineRule rule = parseInlineRule(Type.FORCE, optionStart);
configurationBuilder.addRule(rule);
// Insert a matching -checkdiscard rule to ensure force inlining happens.
ProguardCheckDiscardRule ruled = rule.asProguardCheckDiscardRule();
configurationBuilder.addRule(ruled);
} else if (allowTestOptions && acceptString("neverinline")) {
- InlineRule rule = parseInlineRule(Type.NEVER);
+ InlineRule rule = parseInlineRule(Type.NEVER, optionStart);
configurationBuilder.addRule(rule);
} else if (acceptString("useuniqueclassmembernames")) {
configurationBuilder.setUseUniqueClassMemberNames(true);
} else if (acceptString("adaptclassstrings")) {
parseClassFilter(configurationBuilder::addAdaptClassStringsPattern);
} else if (acceptString("adaptresourcefilenames")) {
- // TODO(76377381): Report an error until it's fully supported.
- if (failOnPartiallyImplementedOptions) {
- failPartiallyImplementedOption("-adaptresourcefilenames", optionStart);
- }
+ configurationBuilder.enableAdaptResourceFilenames();
parsePathFilter(configurationBuilder::addAdaptResourceFilenames);
} else if (acceptString("adaptresourcefilecontents")) {
- // TODO(36847655): Report an error until it's fully supported.
- if (failOnPartiallyImplementedOptions) {
- failPartiallyImplementedOption("-adaptresourcefilecontents", optionStart);
- }
- parsePathFilter(configurationBuilder::addAdaptResourceFilecontents);
+ configurationBuilder.enableAdaptResourceFileContents();
+ parsePathFilter(configurationBuilder::addAdaptResourceFileContents);
} else if (acceptString("identifiernamestring")) {
- configurationBuilder.addRule(parseIdentifierNameStringRule());
+ configurationBuilder.addRule(parseIdentifierNameStringRule(optionStart));
} else if (acceptString("if")) {
configurationBuilder.addRule(parseIfRule(optionStart));
} else {
@@ -545,9 +539,11 @@
}
}
- private ProguardKeepRule parseKeepRule()
+ private ProguardKeepRule parseKeepRule(Position start)
throws ProguardRuleParserException {
- ProguardKeepRule.Builder keepRuleBuilder = ProguardKeepRule.builder();
+ ProguardKeepRule.Builder keepRuleBuilder = ProguardKeepRule.builder()
+ .setOrigin(origin)
+ .setStart(start);
parseRuleTypeAndModifiers(keepRuleBuilder);
parseClassSpec(keepRuleBuilder, false);
if (keepRuleBuilder.getMemberRules().isEmpty()) {
@@ -560,13 +556,21 @@
defaultRuleBuilder.setArguments(Collections.emptyList());
keepRuleBuilder.getMemberRules().add(defaultRuleBuilder.build());
}
+ Position end = getPosition();
+ keepRuleBuilder.setSource(getSourceSnippet(contents, start, end));
+ keepRuleBuilder.setEnd(end);
return keepRuleBuilder.build();
}
- private ProguardWhyAreYouKeepingRule parseWhyAreYouKeepingRule()
+ private ProguardWhyAreYouKeepingRule parseWhyAreYouKeepingRule(Position start)
throws ProguardRuleParserException {
- ProguardWhyAreYouKeepingRule.Builder keepRuleBuilder = ProguardWhyAreYouKeepingRule.builder();
+ ProguardWhyAreYouKeepingRule.Builder keepRuleBuilder = ProguardWhyAreYouKeepingRule.builder()
+ .setOrigin(origin)
+ .setStart(start);
parseClassSpec(keepRuleBuilder, false);
+ Position end = getPosition();
+ keepRuleBuilder.setSource(getSourceSnippet(contents, start, end));
+ keepRuleBuilder.setEnd(end);
return keepRuleBuilder.build();
}
@@ -577,37 +581,56 @@
return keepRuleBuilder.build();
}
- private ProguardCheckDiscardRule parseCheckDiscardRule()
+ private ProguardCheckDiscardRule parseCheckDiscardRule(Position start)
throws ProguardRuleParserException {
- ProguardCheckDiscardRule.Builder keepRuleBuilder = ProguardCheckDiscardRule.builder();
+ ProguardCheckDiscardRule.Builder keepRuleBuilder = ProguardCheckDiscardRule.builder()
+ .setOrigin(origin)
+ .setStart(start);
parseClassSpec(keepRuleBuilder, false);
+ Position end = getPosition();
+ keepRuleBuilder.setSource(getSourceSnippet(contents, start, end));
+ keepRuleBuilder.setEnd(end);
return keepRuleBuilder.build();
}
- private InlineRule parseInlineRule(InlineRule.Type type)
+ private InlineRule parseInlineRule(InlineRule.Type type, Position start)
throws ProguardRuleParserException {
- InlineRule.Builder keepRuleBuilder = InlineRule.builder().setType(type);
+ InlineRule.Builder keepRuleBuilder = InlineRule.builder()
+ .setOrigin(origin)
+ .setStart(start)
+ .setType(type);
parseClassSpec(keepRuleBuilder, false);
+ Position end = getPosition();
+ keepRuleBuilder.setSource(getSourceSnippet(contents, start, end));
+ keepRuleBuilder.setEnd(end);
return keepRuleBuilder.build();
}
- private ProguardIdentifierNameStringRule parseIdentifierNameStringRule()
+ private ProguardIdentifierNameStringRule parseIdentifierNameStringRule(Position start)
throws ProguardRuleParserException {
ProguardIdentifierNameStringRule.Builder keepRuleBuilder =
- ProguardIdentifierNameStringRule.builder();
+ ProguardIdentifierNameStringRule.builder()
+ .setOrigin(origin)
+ .setStart(start);
parseClassSpec(keepRuleBuilder, false);
+ Position end = getPosition();
+ keepRuleBuilder.setSource(getSourceSnippet(contents, start, end));
+ keepRuleBuilder.setEnd(end);
return keepRuleBuilder.build();
}
private ProguardIfRule parseIfRule(TextPosition optionStart)
throws ProguardRuleParserException {
- ProguardIfRule.Builder ifRuleBuilder = ProguardIfRule.builder();
+ ProguardIfRule.Builder ifRuleBuilder = ProguardIfRule.builder()
+ .setOrigin(origin)
+ .setStart(optionStart);
parseClassSpec(ifRuleBuilder, false);
// Required a subsequent keep rule.
skipWhitespace();
+ Position keepStart = getPosition();
if (acceptString("-keep")) {
- ProguardKeepRule subsequentRule = parseKeepRule();
+ ProguardKeepRule subsequentRule = parseKeepRule(keepStart);
ifRuleBuilder.setSubsequentRule(subsequentRule);
ProguardIfRule ifRule = ifRuleBuilder.build();
verifyAndLinkBackReferences(ifRule.getWildcards());
@@ -636,8 +659,11 @@
}
}
- private void parseClassSpec(
- ProguardConfigurationRule.Builder builder, boolean allowValueSpecification)
+ private
+ <C extends ProguardClassSpecification, B extends ProguardClassSpecification.Builder<C, B>>
+ void parseClassSpec(
+ ProguardClassSpecification.Builder<C, B> builder,
+ boolean allowValueSpecification)
throws ProguardRuleParserException {
parseClassFlagsAndAnnotations(builder);
parseClassType(builder);
@@ -646,8 +672,7 @@
parseMemberRules(builder, allowValueSpecification);
}
- private void parseRuleTypeAndModifiers(ProguardKeepRule.Builder builder)
- throws ProguardRuleParserException {
+ private void parseRuleTypeAndModifiers(ProguardKeepRule.Builder builder) {
if (acceptString("names")) {
builder.setType(ProguardKeepRuleType.KEEP);
builder.getModifiersBuilder().setAllowsShrinking(true);
@@ -800,12 +825,15 @@
ClassOrType.CLASS, dexItemFactory));
}
- private void parseMemberRules(ProguardClassSpecification.Builder classSpecificationBuilder,
+ private
+ <C extends ProguardClassSpecification, B extends ProguardClassSpecification.Builder<C, B>>
+ void parseMemberRules(
+ ProguardClassSpecification.Builder<C, B> classSpecificationBuilder,
boolean allowValueSpecification)
throws ProguardRuleParserException {
skipWhitespace();
if (!eof() && acceptChar('{')) {
- ProguardMemberRule rule = null;
+ ProguardMemberRule rule;
while ((rule = parseMemberRule(allowValueSpecification)) != null) {
classSpecificationBuilder.getMemberRules().add(rule);
}
@@ -1086,16 +1114,27 @@
return fileFilter;
}
- private ProguardAssumeNoSideEffectRule parseAssumeNoSideEffectsRule()
+ private ProguardAssumeNoSideEffectRule parseAssumeNoSideEffectsRule(Position start)
throws ProguardRuleParserException {
- ProguardAssumeNoSideEffectRule.Builder builder = ProguardAssumeNoSideEffectRule.builder();
+ ProguardAssumeNoSideEffectRule.Builder builder = ProguardAssumeNoSideEffectRule.builder()
+ .setOrigin(origin)
+ .setStart(start);
parseClassSpec(builder, true);
+ Position end = getPosition();
+ builder.setSource(getSourceSnippet(contents, start, end));
+ builder.setEnd(end);
return builder.build();
}
- private ProguardAssumeValuesRule parseAssumeValuesRule() throws ProguardRuleParserException {
- ProguardAssumeValuesRule.Builder builder = ProguardAssumeValuesRule.builder();
+ private ProguardAssumeValuesRule parseAssumeValuesRule(Position start)
+ throws ProguardRuleParserException {
+ ProguardAssumeValuesRule.Builder builder = ProguardAssumeValuesRule.builder()
+ .setOrigin(origin)
+ .setStart(start);
parseClassSpec(builder, true);
+ Position end = getPosition();
+ builder.setSource(getSourceSnippet(contents, start, end));
+ builder.setEnd(end);
return builder.build();
}
@@ -1223,6 +1262,7 @@
private IdentifierPatternWithWildcards acceptIdentifierWithBackreference(IdentifierType kind) {
ImmutableList.Builder<ProguardWildcard> wildcardsCollector = ImmutableList.builder();
StringBuilder currentAsterisks = null;
+ int asteriskCount = 0;
StringBuilder currentBackreference = null;
skipWhitespace();
int start = position;
@@ -1264,19 +1304,35 @@
}
} else if (currentAsterisks != null) {
if (current == '*') {
+ // only '*', '**', and '***' are allowed.
+ // E.g., '****' should be regarded as two separate wildcards (e.g., '***' and '*')
+ if (asteriskCount >= 3) {
+ wildcardsCollector.add(new ProguardWildcard.Pattern(currentAsterisks.toString()));
+ currentAsterisks = new StringBuilder();
+ asteriskCount = 0;
+ }
currentAsterisks.append((char) current);
+ asteriskCount++;
end += Character.charCount(current);
continue;
} else {
wildcardsCollector.add(new ProguardWildcard.Pattern(currentAsterisks.toString()));
currentAsterisks = null;
+ asteriskCount = 0;
}
}
// From now on, neither in asterisk collecting state nor back reference collecting state.
assert currentAsterisks == null && currentBackreference == null;
if (current == '*') {
- currentAsterisks = new StringBuilder();
- currentAsterisks.append((char) current);
+ if (kind == IdentifierType.CLASS_NAME) {
+ // '**' and '***' are only allowed in type name.
+ currentAsterisks = new StringBuilder();
+ currentAsterisks.append((char) current);
+ asteriskCount = 1;
+ } else {
+ // For member names, regard '**' or '***' as separate single-asterisk wildcards.
+ wildcardsCollector.add(new ProguardWildcard.Pattern(String.valueOf((char) current)));
+ }
end += Character.charCount(current);
} else if (current == '?' || current == '%') {
wildcardsCollector.add(new ProguardWildcard.Pattern(String.valueOf((char) current)));
@@ -1549,7 +1605,24 @@
private int getColumn() {
return position - lineStartPosition + 1 /* column starts at 1 */;
}
- }
+
+ private String getSourceSnippet(String source, Position start, Position end) {
+ return start instanceof TextPosition && end instanceof TextPosition
+ ? getTextSourceSnippet(source, (TextPosition) start, (TextPosition) end)
+ : null;
+ }
+ }
+
+ private String getTextSourceSnippet(String source, TextPosition start, TextPosition end) {
+ long length = end.getOffset() - start.getOffset();
+ if (start.getOffset() < 0 || end.getOffset() < 0
+ || start.getOffset() >= source.length() || end.getOffset() > source.length()
+ || length <= 0) {
+ return null;
+ } else {
+ return source.substring((int) start.getOffset(), (int) end.getOffset());
+ }
+ }
static class IdentifierPatternWithWildcards {
final String pattern;
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
index e4c5691..f7282ff 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
@@ -3,6 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.Iterables;
import java.util.Collections;
@@ -11,6 +13,9 @@
public abstract class ProguardConfigurationRule extends ProguardClassSpecification {
ProguardConfigurationRule(
+ Origin origin,
+ Position position,
+ String source,
ProguardTypeMatcher classAnnotation,
ProguardAccessFlags classAccessFlags,
ProguardAccessFlags negatedClassAccessFlags,
@@ -21,8 +26,9 @@
ProguardTypeMatcher inheritanceClassName,
boolean inheritanceIsExtends,
List<ProguardMemberRule> memberRules) {
- super(classAnnotation, classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType,
- classNames, inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
+ super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
+ classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
+ inheritanceIsExtends, memberRules);
}
abstract String typeString();
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardIdentifierNameStringRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardIdentifierNameStringRule.java
index 07ffd85..8ff7670 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardIdentifierNameStringRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardIdentifierNameStringRule.java
@@ -3,22 +3,35 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
import java.util.List;
public class ProguardIdentifierNameStringRule extends ProguardConfigurationRule {
- public static class Builder extends ProguardConfigurationRule.Builder {
+ public static class Builder
+ extends ProguardConfigurationRule.Builder<ProguardIdentifierNameStringRule, Builder> {
private Builder() {
+ super();
}
+ @Override
+ public Builder self() {
+ return this;
+ }
+
+ @Override
public ProguardIdentifierNameStringRule build() {
- return new ProguardIdentifierNameStringRule(classAnnotation, classAccessFlags,
- negatedClassAccessFlags, classTypeNegated, classType, classNames, inheritanceAnnotation,
- inheritanceClassName, inheritanceIsExtends, memberRules);
+ return new ProguardIdentifierNameStringRule(origin, getPosition(), source, classAnnotation,
+ classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType, classNames,
+ inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
}
}
private ProguardIdentifierNameStringRule(
+ Origin origin,
+ Position position,
+ String source,
ProguardTypeMatcher classAnnotation,
ProguardAccessFlags classAccessFlags,
ProguardAccessFlags negatedClassAccessFlags,
@@ -29,8 +42,9 @@
ProguardTypeMatcher inheritanceClassName,
boolean inheritanceIsExtends,
List<ProguardMemberRule> memberRules) {
- super(classAnnotation, classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType,
- classNames, inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
+ super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
+ classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
+ inheritanceIsExtends, memberRules);
}
public static Builder builder() {
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
index 47f26ed..54a3f2f 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
@@ -3,18 +3,29 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
import com.google.common.collect.Iterables;
import java.util.List;
import java.util.stream.Collectors;
-public class ProguardIfRule extends ProguardKeepRule {
+public class ProguardIfRule extends ProguardKeepRuleBase {
final ProguardKeepRule subsequentRule;
- public static class Builder extends ProguardKeepRule.Builder {
+ public static class Builder extends ProguardKeepRuleBase.Builder<ProguardIfRule, Builder> {
ProguardKeepRule subsequentRule = null;
+ protected Builder() {
+ super();
+ }
+
+ @Override
+ public Builder self() {
+ return this;
+ }
+
public void setSubsequentRule(ProguardKeepRule rule) {
subsequentRule = rule;
}
@@ -22,13 +33,17 @@
@Override
public ProguardIfRule build() {
assert subsequentRule != null : "Option -if without a subsequent rule.";
- return new ProguardIfRule(classAnnotation, classAccessFlags,
+ return new ProguardIfRule(origin, getPosition(), source, classAnnotation, classAccessFlags,
negatedClassAccessFlags, classTypeNegated, classType, classNames, inheritanceAnnotation,
inheritanceClassName, inheritanceIsExtends, memberRules, subsequentRule);
}
}
- private ProguardIfRule(ProguardTypeMatcher classAnnotation,
+ private ProguardIfRule(
+ Origin origin,
+ Position position,
+ String source,
+ ProguardTypeMatcher classAnnotation,
ProguardAccessFlags classAccessFlags,
ProguardAccessFlags negatedClassAccessFlags, boolean classTypeNegated,
ProguardClassType classType, ProguardClassNameList classNames,
@@ -36,8 +51,9 @@
ProguardTypeMatcher inheritanceClassName, boolean inheritanceIsExtends,
List<ProguardMemberRule> memberRules,
ProguardKeepRule subsequentRule) {
- super(classAnnotation, classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType,
- classNames, inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules,
+ super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
+ classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
+ inheritanceIsExtends, memberRules,
ProguardKeepRuleType.CONDITIONAL, ProguardKeepRuleModifiers.builder().build());
this.subsequentRule = subsequentRule;
}
@@ -51,9 +67,11 @@
return Iterables.concat(super.getWildcards(), subsequentRule.getWildcards());
}
- @Override
protected ProguardIfRule materialize() {
return new ProguardIfRule(
+ Origin.unknown(),
+ Position.UNKNOWN,
+ null,
getClassAnnotation(),
getClassAccessFlags(),
getNegatedClassAccessFlags(),
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardKeepPackageNamesRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardKeepPackageNamesRule.java
index 66fdd50..767ecf7 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardKeepPackageNamesRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardKeepPackageNamesRule.java
@@ -3,23 +3,36 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
import java.util.List;
public class ProguardKeepPackageNamesRule extends ProguardConfigurationRule {
- public static class Builder extends ProguardConfigurationRule.Builder {
+ public static class Builder
+ extends ProguardConfigurationRule.Builder<ProguardKeepPackageNamesRule, Builder> {
private Builder() {
+ super();
}
+ @Override
+ public Builder self() {
+ return this;
+ }
+
+ @Override
public ProguardKeepPackageNamesRule build() {
- return new ProguardKeepPackageNamesRule(classAnnotation, classAccessFlags,
- negatedClassAccessFlags, classTypeNegated, classType, classNames, inheritanceAnnotation,
- inheritanceClassName, inheritanceIsExtends, memberRules);
+ return new ProguardKeepPackageNamesRule(origin, getPosition(), source, classAnnotation,
+ classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType, classNames,
+ inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
}
}
private ProguardKeepPackageNamesRule(
+ Origin origin,
+ Position position,
+ String source,
ProguardTypeMatcher classAnnotation,
ProguardAccessFlags classAccessFlags,
ProguardAccessFlags negatedClassAccessFlags,
@@ -30,12 +43,13 @@
ProguardTypeMatcher inheritanceClassName,
boolean inheritanceIsExtends,
List<ProguardMemberRule> memberRules) {
- super(classAnnotation, classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType,
- classNames, inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
+ super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
+ classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
+ inheritanceIsExtends, memberRules);
}
- public static ProguardKeepPackageNamesRule.Builder builder() {
- return new ProguardKeepPackageNamesRule.Builder();
+ public static Builder builder() {
+ return new Builder();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRule.java
index 6651dae..3c1fb2d 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRule.java
@@ -3,39 +3,37 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
-public class ProguardKeepRule extends ProguardConfigurationRule {
+public class ProguardKeepRule extends ProguardKeepRuleBase {
- public static class Builder extends ProguardClassSpecification.Builder {
+ public static class Builder extends ProguardKeepRuleBase.Builder<ProguardKeepRule, Builder> {
- private ProguardKeepRuleType type;
- private final ProguardKeepRuleModifiers.Builder modifiersBuilder =
- ProguardKeepRuleModifiers.builder();
-
- protected Builder() {}
-
- public void setType(ProguardKeepRuleType type) {
- this.type = type;
+ protected Builder() {
+ super();
}
- public ProguardKeepRuleModifiers.Builder getModifiersBuilder() {
- return modifiersBuilder;
+ @Override
+ public Builder self() {
+ return this;
}
+ @Override
public ProguardKeepRule build() {
- return new ProguardKeepRule(classAnnotation, classAccessFlags, negatedClassAccessFlags,
- classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
- inheritanceIsExtends, memberRules, type, modifiersBuilder.build());
+ return new ProguardKeepRule(origin, getPosition(), source, classAnnotation, classAccessFlags,
+ negatedClassAccessFlags, classTypeNegated, classType, classNames, inheritanceAnnotation,
+ inheritanceClassName, inheritanceIsExtends, memberRules, type, modifiersBuilder.build());
}
}
- private final ProguardKeepRuleType type;
- private final ProguardKeepRuleModifiers modifiers;
-
protected ProguardKeepRule(
+ Origin origin,
+ Position position,
+ String source,
ProguardTypeMatcher classAnnotation,
ProguardAccessFlags classAccessFlags,
ProguardAccessFlags negatedClassAccessFlags,
@@ -48,10 +46,9 @@
List<ProguardMemberRule> memberRules,
ProguardKeepRuleType type,
ProguardKeepRuleModifiers modifiers) {
- super(classAnnotation, classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType,
- classNames, inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
- this.type = type;
- this.modifiers = modifiers;
+ super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
+ classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
+ inheritanceIsExtends, memberRules, type, modifiers);
}
/**
@@ -61,16 +58,11 @@
return new Builder();
}
- public ProguardKeepRuleType getType() {
- return type;
- }
-
- public ProguardKeepRuleModifiers getModifiers() {
- return modifiers;
- }
-
protected ProguardKeepRule materialize() {
return new ProguardKeepRule(
+ Origin.unknown(),
+ Position.UNKNOWN,
+ null,
getClassAnnotation(),
getClassAccessFlags(),
getNegatedClassAccessFlags(),
@@ -93,25 +85,9 @@
return false;
}
ProguardKeepRule that = (ProguardKeepRule) o;
-
- if (type != that.type) {
- return false;
- }
- if (!modifiers.equals(that.modifiers)) {
- return false;
- }
return super.equals(that);
}
- @Override
- public int hashCode() {
- // Used multiplier 3 to avoid too much overflow when computing hashCode.
- int result = type.hashCode();
- result = 3 * result + modifiers.hashCode();
- result = 3 * result + super.hashCode();
- return result;
- }
-
static void appendNonEmpty(StringBuilder builder, String pre, Object item, String post) {
if (item == null) {
return;
@@ -128,16 +104,6 @@
}
}
- @Override
- String typeString() {
- return type.toString();
- }
-
- @Override
- String modifierString() {
- return modifiers.toString();
- }
-
public static ProguardKeepRule defaultKeepAllRule(
Consumer<ProguardKeepRuleModifiers.Builder> modifiers) {
ProguardKeepRule.Builder builder = ProguardKeepRule.builder();
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleBase.java b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleBase.java
new file mode 100644
index 0000000..f67634e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleBase.java
@@ -0,0 +1,116 @@
+// Copyright (c) 2016, 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;
+
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+import java.util.List;
+
+public class ProguardKeepRuleBase extends ProguardConfigurationRule {
+
+ public static abstract class Builder<C extends ProguardKeepRuleBase, B extends Builder<C, B>>
+ extends ProguardConfigurationRule.Builder<C, B> {
+
+ protected ProguardKeepRuleType type;
+ protected final ProguardKeepRuleModifiers.Builder modifiersBuilder =
+ ProguardKeepRuleModifiers.builder();
+
+ protected Builder() {
+ super();
+ }
+
+ public void setType(ProguardKeepRuleType type) {
+ this.type = type;
+ }
+
+ public ProguardKeepRuleModifiers.Builder getModifiersBuilder() {
+ return modifiersBuilder;
+ }
+ }
+
+ private final ProguardKeepRuleType type;
+ private final ProguardKeepRuleModifiers modifiers;
+
+ protected ProguardKeepRuleBase(
+ Origin origin,
+ Position position,
+ String source,
+ ProguardTypeMatcher classAnnotation,
+ ProguardAccessFlags classAccessFlags,
+ ProguardAccessFlags negatedClassAccessFlags,
+ boolean classTypeNegated,
+ ProguardClassType classType,
+ ProguardClassNameList classNames,
+ ProguardTypeMatcher inheritanceAnnotation,
+ ProguardTypeMatcher inheritanceClassName,
+ boolean inheritanceIsExtends,
+ List<ProguardMemberRule> memberRules,
+ ProguardKeepRuleType type,
+ ProguardKeepRuleModifiers modifiers) {
+ super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
+ classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
+ inheritanceIsExtends, memberRules);
+ this.type = type;
+ this.modifiers = modifiers;
+ }
+
+ public ProguardKeepRuleType getType() {
+ return type;
+ }
+
+ public ProguardKeepRuleModifiers getModifiers() {
+ return modifiers;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof ProguardKeepRuleBase)) {
+ return false;
+ }
+ ProguardKeepRuleBase that = (ProguardKeepRuleBase) o;
+
+ if (type != that.type) {
+ return false;
+ }
+ if (!modifiers.equals(that.modifiers)) {
+ return false;
+ }
+ return super.equals(that);
+ }
+
+ @Override
+ public int hashCode() {
+ // Used multiplier 3 to avoid too much overflow when computing hashCode.
+ int result = type.hashCode();
+ result = 3 * result + modifiers.hashCode();
+ result = 3 * result + super.hashCode();
+ return result;
+ }
+
+ static void appendNonEmpty(StringBuilder builder, String pre, Object item, String post) {
+ if (item == null) {
+ return;
+ }
+ String text = item.toString();
+ if (!text.isEmpty()) {
+ if (pre != null) {
+ builder.append(pre);
+ }
+ builder.append(text);
+ if (post != null) {
+ builder.append(post);
+ }
+ }
+ }
+
+ @Override
+ String typeString() {
+ return type.toString();
+ }
+
+ @Override
+ String modifierString() {
+ return modifiers.toString();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardPathFilter.java b/src/main/java/com/android/tools/r8/shaking/ProguardPathFilter.java
index cb2e117..b3771ef 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardPathFilter.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardPathFilter.java
@@ -7,6 +7,7 @@
import com.google.common.collect.ImmutableList;
public class ProguardPathFilter {
+ private final boolean enabled;
private final ImmutableList<ProguardPathList> patterns;
public static Builder builder() {
@@ -14,6 +15,7 @@
}
public static class Builder {
+ private boolean enabled = true;
private final ImmutableList.Builder<ProguardPathList> patterns = ImmutableList.builder();
private Builder() {
@@ -24,23 +26,41 @@
return this;
}
+ public Builder disable() {
+ enabled = false;
+ return this;
+ }
+
+ public Builder enable() {
+ enabled = true;
+ return this;
+ }
+
ProguardPathFilter build() {
- return new ProguardPathFilter(patterns.build());
+ return new ProguardPathFilter(patterns.build(), enabled);
}
}
- private ProguardPathFilter(ImmutableList<ProguardPathList> patterns) {
+ private ProguardPathFilter(ImmutableList<ProguardPathList> patterns, boolean enabled) {
+ this.enabled = enabled;
if (patterns.isEmpty()) {
this.patterns = ImmutableList.of(ProguardPathList.emptyList());
} else {
+ assert enabled;
this.patterns = patterns;
}
}
+ public boolean isEnabled() {
+ return enabled;
+ }
+
public boolean matches(String path) {
- for (ProguardPathList pattern : patterns) {
- if (pattern.matches(path)) {
- return true;
+ if (enabled) {
+ for (ProguardPathList pattern : patterns) {
+ if (pattern.matches(path)) {
+ return true;
+ }
}
}
return false;
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardWhyAreYouKeepingRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardWhyAreYouKeepingRule.java
index 73b5538..87200fb 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardWhyAreYouKeepingRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardWhyAreYouKeepingRule.java
@@ -3,23 +3,36 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
import java.util.List;
public class ProguardWhyAreYouKeepingRule extends ProguardConfigurationRule {
- public static class Builder extends ProguardConfigurationRule.Builder {
+ public static class Builder
+ extends ProguardConfigurationRule.Builder<ProguardWhyAreYouKeepingRule, Builder> {
private Builder() {
+ super();
}
+ @Override
+ public Builder self() {
+ return this;
+ }
+
+ @Override
public ProguardWhyAreYouKeepingRule build() {
- return new ProguardWhyAreYouKeepingRule(classAnnotation, classAccessFlags,
- negatedClassAccessFlags, classTypeNegated, classType, classNames, inheritanceAnnotation,
- inheritanceClassName, inheritanceIsExtends, memberRules);
+ return new ProguardWhyAreYouKeepingRule(origin, getPosition(), source, classAnnotation,
+ classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType, classNames,
+ inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
}
}
private ProguardWhyAreYouKeepingRule(
+ Origin origin,
+ Position position,
+ String source,
ProguardTypeMatcher classAnnotation,
ProguardAccessFlags classAccessFlags,
ProguardAccessFlags negatedClassAccessFlags,
@@ -30,12 +43,13 @@
ProguardTypeMatcher inheritanceClassName,
boolean inheritanceIsExtends,
List<ProguardMemberRule> memberRules) {
- super(classAnnotation, classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType,
- classNames, inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
+ super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
+ classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
+ inheritanceIsExtends, memberRules);
}
- public static ProguardWhyAreYouKeepingRule.Builder builder() {
- return new ProguardWhyAreYouKeepingRule.Builder();
+ public static Builder builder() {
+ return new Builder();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index c95cfe9..473c990 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -59,9 +59,9 @@
private final Set<ProguardConfigurationRule> rulesThatUseExtendsOrImplementsWrong =
Sets.newIdentityHashSet();
private final Set<DexItem> checkDiscarded = Sets.newIdentityHashSet();
- private final Set<DexItem> alwaysInline = Sets.newIdentityHashSet();
- private final Set<DexItem> forceInline = Sets.newIdentityHashSet();
- private final Set<DexItem> neverInline = Sets.newIdentityHashSet();
+ private final Set<DexMethod> alwaysInline = Sets.newIdentityHashSet();
+ private final Set<DexMethod> forceInline = Sets.newIdentityHashSet();
+ private final Set<DexMethod> neverInline = Sets.newIdentityHashSet();
private final Map<DexItem, Map<DexItem, ProguardKeepRule>> dependentNoShrinking =
new IdentityHashMap<>();
private final Map<DexItem, ProguardMemberRule> noSideEffects = new IdentityHashMap<>();
@@ -727,13 +727,19 @@
} else if (context instanceof InlineRule) {
switch (((InlineRule) context).getType()) {
case ALWAYS:
- alwaysInline.add(item);
+ if (item instanceof DexEncodedMethod) {
+ alwaysInline.add(((DexEncodedMethod) item).method);
+ }
break;
case FORCE:
- forceInline.add(item);
+ if (item instanceof DexEncodedMethod) {
+ forceInline.add(((DexEncodedMethod) item).method);
+ }
break;
case NEVER:
- neverInline.add(item);
+ if (item instanceof DexEncodedMethod) {
+ neverInline.add(((DexEncodedMethod) item).method);
+ }
break;
default:
throw new Unreachable();
@@ -755,9 +761,9 @@
public final Set<DexItem> reasonAsked;
public final Set<DexItem> keepPackageName;
public final Set<DexItem> checkDiscarded;
- public final Set<DexItem> alwaysInline;
- public final Set<DexItem> forceInline;
- public final Set<DexItem> neverInline;
+ public final Set<DexMethod> alwaysInline;
+ public final Set<DexMethod> forceInline;
+ public final Set<DexMethod> neverInline;
public final Map<DexItem, ProguardMemberRule> noSideEffects;
public final Map<DexItem, ProguardMemberRule> assumedValues;
private final Map<DexItem, Map<DexItem, ProguardKeepRule>> dependentNoShrinking;
@@ -792,9 +798,9 @@
Set<DexItem> reasonAsked,
Set<DexItem> keepPackageName,
Set<DexItem> checkDiscarded,
- Set<DexItem> alwaysInline,
- Set<DexItem> forceInline,
- Set<DexItem> neverInline,
+ Set<DexMethod> alwaysInline,
+ Set<DexMethod> forceInline,
+ Set<DexMethod> neverInline,
Map<DexItem, ProguardMemberRule> noSideEffects,
Map<DexItem, ProguardMemberRule> assumedValues,
Map<DexItem, Map<DexItem, ProguardKeepRule>> dependentNoShrinking,
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 4d072c4..8dec498 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfo.ResolutionResult;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.Code;
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.DexTypeList;
import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.graph.GraphLense.Builder;
+import com.android.tools.r8.graph.GraphLense.GraphLenseLookupResult;
import com.android.tools.r8.graph.JarCode;
import com.android.tools.r8.graph.KeyedDexItem;
import com.android.tools.r8.graph.MethodAccessFlags;
@@ -29,7 +31,9 @@
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.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
+import com.android.tools.r8.ir.optimize.MethodPoolCollection;
+import com.android.tools.r8.ir.optimize.MethodPoolCollection.MethodPool;
import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode;
import com.android.tools.r8.ir.synthetic.SynthesizedCode;
import com.android.tools.r8.logging.Log;
@@ -57,8 +61,14 @@
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.tree.MethodNode;
/**
* Merges Supertypes with a single implementation into their single subtype.
@@ -150,7 +160,9 @@
private final DexApplication application;
private final AppInfoWithLiveness appInfo;
+ private final ExecutorService executorService;
private final GraphLense graphLense;
+ private final MethodPoolCollection methodPoolCollection;
private final Timing timing;
private Collection<DexMethod> invokes;
@@ -170,11 +182,16 @@
private final VerticalClassMergerGraphLense.Builder renamedMembersLense;
public VerticalClassMerger(
- DexApplication application, AppView<AppInfoWithLiveness> appView, Timing timing) {
+ DexApplication application,
+ AppView<AppInfoWithLiveness> appView,
+ ExecutorService executorService,
+ Timing timing) {
this.application = application;
this.appInfo = appView.getAppInfo();
+ this.executorService = executorService;
this.graphLense = appView.getGraphLense();
- this.renamedMembersLense = VerticalClassMergerGraphLense.builder(appInfo);
+ this.methodPoolCollection = new MethodPoolCollection(application);
+ this.renamedMembersLense = new VerticalClassMergerGraphLense.Builder();
this.timing = timing;
Iterable<DexProgramClass> classes = application.classesWithDeterministicOrder();
@@ -237,7 +254,7 @@
}
}
- private void extractPinnedItems(Iterable<DexItem> items, AbortReason reason) {
+ private <T extends DexItem> void extractPinnedItems(Iterable<T> items, AbortReason reason) {
for (DexItem item : items) {
if (item instanceof DexType || item instanceof DexClass) {
DexType type = item instanceof DexType ? (DexType) item : ((DexClass) item).type;
@@ -520,14 +537,20 @@
}
}
- public GraphLense run() {
+ public GraphLense run() throws ExecutionException {
timing.begin("merge");
GraphLense mergingGraphLense = mergeClasses(graphLense);
timing.end();
timing.begin("fixup");
GraphLense result = new TreeFixer().fixupTypeReferences(mergingGraphLense);
timing.end();
- assert result.assertNotModified(appInfo.alwaysInline);
+ assert result.assertNotModified(
+ appInfo
+ .alwaysInline
+ .stream()
+ .map(appInfo::definitionFor)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList()));
assert result.assertNotModified(appInfo.noSideEffects.keySet());
// TODO(christofferqa): Enable this assert.
// assert result.assertNotModified(appInfo.pinnedItems);
@@ -561,7 +584,7 @@
}
}
- private GraphLense mergeClasses(GraphLense graphLense) {
+ private GraphLense mergeClasses(GraphLense graphLense) throws ExecutionException {
Deque<DexProgramClass> worklist = new ArrayDeque<>();
Set<DexProgramClass> seenBefore = new HashSet<>();
@@ -636,7 +659,7 @@
if (Log.ENABLED) {
Log.debug(getClass(), "Merged %d classes.", numberOfMerges);
}
- return renamedMembersLense.build(graphLense, mergedClasses, application.dexItemFactory);
+ return renamedMembersLense.build(graphLense, mergedClasses, appInfo);
}
private boolean methodResolutionMayChange(DexClass source, DexClass target) {
@@ -718,7 +741,7 @@
private final DexClass source;
private final DexClass target;
private final VerticalClassMergerGraphLense.Builder deferredRenamings =
- VerticalClassMergerGraphLense.builder(appInfo);
+ new VerticalClassMergerGraphLense.Builder();
private boolean abortMerge = false;
private ClassMerger(DexClass source, DexClass target) {
@@ -726,7 +749,7 @@
this.target = target;
}
- public boolean merge() {
+ public boolean merge() throws ExecutionException {
// Merge the class [clazz] into [targetClass] by adding all methods to
// targetClass that are not currently contained.
// Step 1: Merge methods
@@ -758,6 +781,7 @@
directMethod.isClassInitializer() ? Rename.NEVER : Rename.IF_NEEDED);
add(directMethods, resultingDirectMethod, MethodSignatureEquivalence.get());
deferredRenamings.map(directMethod.method, resultingDirectMethod.method);
+ deferredRenamings.recordMove(directMethod.method, resultingDirectMethod.method);
if (!directMethod.isStaticMethod()) {
blockRedirectionOfSuperCalls(resultingDirectMethod.method);
@@ -787,17 +811,43 @@
DexEncodedMethod resultingVirtualMethod =
renameMethod(virtualMethod, availableMethodSignatures, Rename.NEVER);
deferredRenamings.map(virtualMethod.method, resultingVirtualMethod.method);
+ deferredRenamings.recordMove(virtualMethod.method, resultingVirtualMethod.method);
add(virtualMethods, resultingVirtualMethod, MethodSignatureEquivalence.get());
continue;
}
}
- // This virtual method could be called directly from a sub class via an invoke-super
- // instruction. Therefore, we translate this virtual method into a direct method, such that
- // relevant invoke-super instructions can be rewritten into invoke-direct instructions.
- DexEncodedMethod resultingDirectMethod =
- renameMethod(virtualMethod, availableMethodSignatures, Rename.ALWAYS);
- makePrivate(resultingDirectMethod);
+ DexEncodedMethod resultingDirectMethod;
+ if (source.accessFlags.isInterface()) {
+ // Moving a default interface method into its subtype. This method could be hit directly
+ // via an invoke-super instruction from any of the transitive subtypes of this interface,
+ // due to the way invoke-super works on default interface methods. In order to be able
+ // to hit this method directly after the merge, we need to make it public, and find a
+ // method name that does not collide with one in the hierarchy of this class.
+ MethodPool methodPoolForTarget =
+ methodPoolCollection.buildForHierarchy(target, executorService, timing);
+ resultingDirectMethod =
+ renameMethod(
+ virtualMethod,
+ method ->
+ availableMethodSignatures.test(method)
+ && !methodPoolForTarget.hasSeen(
+ MethodSignatureEquivalence.get().wrap(method)),
+ Rename.ALWAYS,
+ getStaticProto(virtualMethod.method.holder, virtualMethod.method.proto));
+ makeStatic(resultingDirectMethod);
+
+ // Update method pool collection now that we are adding a new public method.
+ methodPoolForTarget.seen(resultingDirectMethod.method);
+ } else {
+ // This virtual method could be called directly from a sub class via an invoke-super in-
+ // struction. Therefore, we translate this virtual method into a direct method, such that
+ // relevant invoke-super instructions can be rewritten into invoke-direct instructions.
+ resultingDirectMethod =
+ renameMethod(virtualMethod, availableMethodSignatures, Rename.ALWAYS);
+ makePrivate(resultingDirectMethod);
+ }
+
add(directMethods, resultingDirectMethod, MethodSignatureEquivalence.get());
// Record that invoke-super instructions in the target class should be redirected to the
@@ -811,11 +861,12 @@
// Note that this method is added independently of whether it will actually be used. If
// it turns out that the method is never used, it will be removed by the final round
// of tree shaking.
- shadowedBy = buildBridgeMethod(virtualMethod, resultingDirectMethod.method);
+ shadowedBy = buildBridgeMethod(virtualMethod, resultingDirectMethod);
add(virtualMethods, shadowedBy, MethodSignatureEquivalence.get());
}
deferredRenamings.map(virtualMethod.method, shadowedBy.method);
+ deferredRenamings.recordMove(virtualMethod.method, resultingDirectMethod.method);
}
if (abortMerge) {
@@ -903,7 +954,8 @@
// rewrite any invocations on the form "invoke-super J.m()" to "invoke-direct C.m$I()",
// if I has a supertype J. This is due to the fact that invoke-super instructions that
// resolve to a method on an interface never hit an implementation below that interface.
- deferredRenamings.mapVirtualMethodToDirectInType(oldTarget, newTarget, target.type);
+ deferredRenamings.mapVirtualMethodToDirectInType(
+ oldTarget, new GraphLenseLookupResult(newTarget, Type.STATIC), target.type);
} else {
// 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
@@ -922,7 +974,7 @@
|| appInfo.lookupSuperTarget(signatureInHolder, holder.type) != null;
if (resolutionSucceeds) {
deferredRenamings.mapVirtualMethodToDirectInType(
- signatureInHolder, newTarget, target.type);
+ signatureInHolder, new GraphLenseLookupResult(newTarget, Type.DIRECT), target.type);
} else {
break;
}
@@ -944,7 +996,9 @@
|| appInfo.lookupSuperTarget(signatureInHolder, holder.type) != null;
if (resolutionSucceededBeforeMerge) {
deferredRenamings.mapVirtualMethodToDirectInType(
- signatureInType, newTarget, target.type);
+ signatureInType,
+ new GraphLenseLookupResult(newTarget, Type.DIRECT),
+ target.type);
}
}
}
@@ -974,23 +1028,38 @@
}
private DexEncodedMethod buildBridgeMethod(
- DexEncodedMethod method, DexMethod invocationTarget) {
+ DexEncodedMethod method, DexEncodedMethod invocationTarget) {
DexType holder = target.type;
- DexProto proto = invocationTarget.proto;
+ DexProto proto = method.method.proto;
DexString name = method.method.name;
MethodAccessFlags accessFlags = method.accessFlags.copy();
accessFlags.setBridge();
accessFlags.setSynthetic();
accessFlags.unsetAbstract();
- DexEncodedMethod bridge = new DexEncodedMethod(
- application.dexItemFactory.createMethod(holder, proto, name),
- accessFlags,
- DexAnnotationSet.empty(),
- ParameterAnnotationsList.empty(),
- new SynthesizedCode(
- new ForwardMethodSourceCode(holder, proto, holder, invocationTarget, Type.DIRECT),
- registry -> registry.registerInvokeDirect(invocationTarget)),
- method.hasClassFileVersion() ? method.getClassFileVersion() : -1);
+ SynthesizedCode code;
+ if (invocationTarget.isPrivateMethod()) {
+ assert !invocationTarget.isStaticMethod();
+ code =
+ new SynthesizedCode(
+ new ForwardMethodSourceCode(
+ holder, proto, holder, invocationTarget.method, Type.DIRECT),
+ registry -> registry.registerInvokeDirect(invocationTarget.method));
+ } else {
+ assert invocationTarget.isStaticMethod();
+ code =
+ new SynthesizedCode(
+ new ForwardMethodSourceCode(
+ holder, proto, null, invocationTarget.method, Type.STATIC),
+ registry -> registry.registerInvokeStatic(invocationTarget.method));
+ }
+ DexEncodedMethod bridge =
+ new DexEncodedMethod(
+ application.dexItemFactory.createMethod(holder, proto, name),
+ accessFlags,
+ DexAnnotationSet.empty(),
+ ParameterAnnotationsList.empty(),
+ code,
+ method.hasClassFileVersion() ? method.getClassFileVersion() : -1);
if (method.getOptimizationInfo().isPublicized()) {
// The bridge is now the public method serving the role of the original method, and should
// reflect that this method was publicized.
@@ -1097,6 +1166,7 @@
DexEncodedMethod result = method.toTypeSubstitutedMethod(newSignature);
result.markForceInline();
deferredRenamings.map(method.method, result.method);
+ deferredRenamings.recordMove(method.method, result.method);
// Renamed constructors turn into ordinary private functions. They can be private, as
// they are only references from their direct subclass, which they were merged into.
result.accessFlags.unsetConstructor();
@@ -1106,6 +1176,14 @@
private DexEncodedMethod renameMethod(
DexEncodedMethod method, Predicate<DexMethod> availableMethodSignatures, Rename strategy) {
+ return renameMethod(method, availableMethodSignatures, strategy, method.method.proto);
+ }
+
+ private DexEncodedMethod renameMethod(
+ DexEncodedMethod method,
+ Predicate<DexMethod> availableMethodSignatures,
+ Rename strategy,
+ DexProto newProto) {
// We cannot handle renaming static initializers yet and constructors should have been
// renamed already.
assert !method.accessFlags.isConstructor() || strategy == Rename.NEVER;
@@ -1115,8 +1193,7 @@
DexMethod newSignature;
switch (strategy) {
case IF_NEEDED:
- newSignature =
- application.dexItemFactory.createMethod(target.type, method.method.proto, oldName);
+ newSignature = application.dexItemFactory.createMethod(target.type, newProto, oldName);
if (availableMethodSignatures.test(newSignature)) {
break;
}
@@ -1126,15 +1203,13 @@
int count = 1;
do {
DexString newName = getFreshName(oldName.toSourceString(), count, oldHolder);
- newSignature =
- application.dexItemFactory.createMethod(target.type, method.method.proto, newName);
+ newSignature = application.dexItemFactory.createMethod(target.type, newProto, newName);
count++;
} while (!availableMethodSignatures.test(newSignature));
break;
case NEVER:
- newSignature =
- application.dexItemFactory.createMethod(target.type, method.method.proto, oldName);
+ newSignature = application.dexItemFactory.createMethod(target.type, newProto, oldName);
assert availableMethodSignatures.test(newSignature);
break;
@@ -1173,6 +1248,24 @@
method.accessFlags.setPrivate();
}
+ private static void makeStatic(DexEncodedMethod method) {
+ method.accessFlags.setStatic();
+
+ Code code = method.getCode();
+ if (code.isJarCode()) {
+ MethodNode node = code.asJarCode().getNode();
+ node.access |= Opcodes.ACC_STATIC;
+ node.desc = method.method.proto.toDescriptorString();
+ }
+ }
+
+ private DexProto getStaticProto(DexType receiverType, DexProto proto) {
+ DexType[] parameterTypes = new DexType[proto.parameters.size() + 1];
+ parameterTypes[0] = receiverType;
+ System.arraycopy(proto.parameters.values, 0, parameterTypes, 1, proto.parameters.size());
+ return appInfo.dexItemFactory.createProto(proto.returnType, parameterTypes);
+ }
+
private class TreeFixer {
private final Builder lense = GraphLense.builder();
@@ -1405,13 +1498,13 @@
// that we always return true here in these cases.
if (method.getCode().isJarCode()) {
JarCode jarCode = method.getCode().asJarCode();
- Constraint constraint =
+ ConstraintWithTarget constraint =
jarCode.computeInliningConstraint(
method,
appInfo,
new SingleTypeMapperGraphLense(method.method.holder, invocationContext),
invocationContext);
- return constraint == Constraint.NEVER;
+ return constraint == ConstraintWithTarget.NEVER;
}
// TODO(christofferqa): For non-jar code we currently cannot guarantee that markForceInline()
// will succeed.
@@ -1429,6 +1522,26 @@
}
@Override
+ public DexField getOriginalFieldSignature(DexField field) {
+ throw new Unreachable();
+ }
+
+ @Override
+ public DexMethod getOriginalMethodSignature(DexMethod method) {
+ throw new Unreachable();
+ }
+
+ @Override
+ public DexField getRenamedFieldSignature(DexField originalField) {
+ throw new Unreachable();
+ }
+
+ @Override
+ public DexMethod getRenamedMethodSignature(DexMethod originalMethod) {
+ throw new Unreachable();
+ }
+
+ @Override
public DexType lookupType(DexType type) {
return type == source ? target : mergedClasses.getOrDefault(type, type);
}
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
index fa10215..4e09a9e 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
@@ -14,6 +14,9 @@
import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
import com.android.tools.r8.ir.code.Invoke.Type;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
+import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import java.util.HashMap;
@@ -48,42 +51,49 @@
private final AppInfo appInfo;
private final Set<DexMethod> mergedMethods;
- private final Map<DexType, Map<DexMethod, DexMethod>> contextualVirtualToDirectMethodMaps;
+ private final Map<DexType, Map<DexMethod, GraphLenseLookupResult>>
+ contextualVirtualToDirectMethodMaps;
public VerticalClassMergerGraphLense(
AppInfo appInfo,
Map<DexField, DexField> fieldMap,
Map<DexMethod, DexMethod> methodMap,
Set<DexMethod> mergedMethods,
- Map<DexType, Map<DexMethod, DexMethod>> contextualVirtualToDirectMethodMaps,
+ Map<DexType, Map<DexMethod, GraphLenseLookupResult>> contextualVirtualToDirectMethodMaps,
+ BiMap<DexField, DexField> originalFieldSignatures,
+ BiMap<DexMethod, DexMethod> originalMethodSignatures,
GraphLense previousLense) {
- super(ImmutableMap.of(), methodMap, fieldMap, previousLense, appInfo.dexItemFactory);
+ super(
+ ImmutableMap.of(),
+ methodMap,
+ fieldMap,
+ originalFieldSignatures,
+ originalMethodSignatures,
+ previousLense,
+ appInfo.dexItemFactory);
this.appInfo = appInfo;
this.mergedMethods = mergedMethods;
this.contextualVirtualToDirectMethodMaps = contextualVirtualToDirectMethodMaps;
}
- public static Builder builder(AppInfo appInfo) {
- return new Builder(appInfo);
- }
-
@Override
public GraphLenseLookupResult lookupMethod(
DexMethod method, DexEncodedMethod context, Type type) {
assert isContextFreeForMethod(method) || (context != null && type != null);
GraphLenseLookupResult previous = previousLense.lookupMethod(method, context, type);
if (previous.getType() == Type.SUPER && !mergedMethods.contains(context.method)) {
- Map<DexMethod, DexMethod> virtualToDirectMethodMap =
+ Map<DexMethod, GraphLenseLookupResult> virtualToDirectMethodMap =
contextualVirtualToDirectMethodMaps.get(context.method.holder);
if (virtualToDirectMethodMap != null) {
- DexMethod directMethod = virtualToDirectMethodMap.get(previous.getMethod());
- if (directMethod != null) {
+ GraphLenseLookupResult lookup = virtualToDirectMethodMap.get(previous.getMethod());
+ if (lookup != null) {
// If the super class A of the enclosing class B (i.e., context.method.holder)
// has been merged into B during vertical class merging, and this invoke-super instruction
// was resolving to a method in A, then the target method has been changed to a direct
// method and moved into B, so that we need to use an invoke-direct instruction instead of
- // invoke-super.
- return new GraphLenseLookupResult(directMethod, Type.DIRECT);
+ // invoke-super (or invoke-static, if the method was originally a default interface
+ // method).
+ return lookup;
}
}
}
@@ -102,11 +112,11 @@
ImmutableSet.Builder<DexMethod> builder = ImmutableSet.builder();
for (DexMethod previous : previousLense.lookupMethodInAllContexts(method)) {
builder.add(methodMap.getOrDefault(previous, previous));
- for (Map<DexMethod, DexMethod> virtualToDirectMethodMap :
+ for (Map<DexMethod, GraphLenseLookupResult> virtualToDirectMethodMap :
contextualVirtualToDirectMethodMaps.values()) {
- DexMethod directMethod = virtualToDirectMethodMap.get(previous);
- if (directMethod != null) {
- builder.add(directMethod);
+ GraphLenseLookupResult lookup = virtualToDirectMethodMap.get(previous);
+ if (lookup != null) {
+ builder.add(lookup.getMethod());
}
}
}
@@ -124,7 +134,7 @@
return false;
}
DexMethod previous = previousLense.lookupMethod(method);
- for (Map<DexMethod, DexMethod> virtualToDirectMethodMap :
+ for (Map<DexMethod, GraphLenseLookupResult> virtualToDirectMethodMap :
contextualVirtualToDirectMethodMaps.values()) {
if (virtualToDirectMethodMap.containsKey(previous)) {
return false;
@@ -134,62 +144,109 @@
}
public static class Builder {
- private final AppInfo appInfo;
- protected final Map<DexField, DexField> fieldMap = new HashMap<>();
+ protected final BiMap<DexField, DexField> fieldMap = HashBiMap.create();
protected final Map<DexMethod, DexMethod> methodMap = new HashMap<>();
private final ImmutableSet.Builder<DexMethod> mergedMethodsBuilder = ImmutableSet.builder();
- private final Map<DexType, Map<DexMethod, DexMethod>> contextualVirtualToDirectMethodMaps =
- new HashMap<>();
+ private final Map<DexType, Map<DexMethod, GraphLenseLookupResult>>
+ contextualVirtualToDirectMethodMaps = new HashMap<>();
- private Builder(AppInfo appInfo) {
- this.appInfo = appInfo;
- }
+ private final Map<DexMethod, DexMethod> originalMethodSignatures = HashBiMap.create();
public GraphLense build(
- GraphLense previousLense,
- Map<DexType, DexType> mergedClasses,
- DexItemFactory dexItemFactory) {
+ GraphLense previousLense, Map<DexType, DexType> mergedClasses, AppInfo appInfo) {
if (fieldMap.isEmpty()
&& methodMap.isEmpty()
&& contextualVirtualToDirectMethodMaps.isEmpty()) {
return previousLense;
}
+ Map<DexProto, DexProto> cache = new HashMap<>();
+ BiMap<DexField, DexField> originalFieldSignatures = fieldMap.inverse();
return new VerticalClassMergerGraphLense(
appInfo,
fieldMap,
methodMap,
getMergedMethodSignaturesAfterClassMerging(
- mergedMethodsBuilder.build(), mergedClasses, dexItemFactory),
+ mergedMethodsBuilder.build(), mergedClasses, appInfo.dexItemFactory, cache),
contextualVirtualToDirectMethodMaps,
+ getOriginalFieldSignaturesAfterClassMerging(
+ originalFieldSignatures, mergedClasses, appInfo.dexItemFactory),
+ getOriginalMethodSignaturesAfterClassMerging(
+ originalMethodSignatures, mergedClasses, appInfo.dexItemFactory, cache),
previousLense);
}
// After we have recorded that a method "a.b.c.Foo;->m(A, B, C)V" was merged into another class,
// it could be that the class B was merged into its subclass B'. In that case we update the
// signature to "a.b.c.Foo;->m(A, B', C)V".
- private Set<DexMethod> getMergedMethodSignaturesAfterClassMerging(
+ private static Set<DexMethod> getMergedMethodSignaturesAfterClassMerging(
Set<DexMethod> mergedMethods,
Map<DexType, DexType> mergedClasses,
- DexItemFactory dexItemFactory) {
+ DexItemFactory dexItemFactory,
+ Map<DexProto, DexProto> cache) {
ImmutableSet.Builder<DexMethod> result = ImmutableSet.builder();
- Map<DexProto, DexProto> cache = new HashMap<>();
for (DexMethod signature : mergedMethods) {
- DexType newHolder = mergedClasses.getOrDefault(signature.holder, signature.holder);
- DexProto newProto =
- dexItemFactory.applyClassMappingToProto(
- signature.proto, type -> mergedClasses.getOrDefault(type, type), cache);
- if (signature.holder.equals(newHolder) && signature.proto.equals(newProto)) {
- result.add(signature);
- } else {
- result.add(dexItemFactory.createMethod(newHolder, newProto, signature.name));
- }
+ result.add(
+ getMethodSignatureAfterClassMerging(signature, mergedClasses, dexItemFactory, cache));
}
return result.build();
}
+ private static BiMap<DexField, DexField> getOriginalFieldSignaturesAfterClassMerging(
+ Map<DexField, DexField> originalFieldSignatures,
+ Map<DexType, DexType> mergedClasses,
+ DexItemFactory dexItemFactory) {
+ ImmutableBiMap.Builder<DexField, DexField> result = ImmutableBiMap.builder();
+ for (Map.Entry<DexField, DexField> entry : originalFieldSignatures.entrySet()) {
+ result.put(
+ getFieldSignatureAfterClassMerging(entry.getKey(), mergedClasses, dexItemFactory),
+ entry.getValue());
+ }
+ return result.build();
+ }
+
+ private static BiMap<DexMethod, DexMethod> getOriginalMethodSignaturesAfterClassMerging(
+ Map<DexMethod, DexMethod> originalMethodSignatures,
+ Map<DexType, DexType> mergedClasses,
+ DexItemFactory dexItemFactory,
+ Map<DexProto, DexProto> cache) {
+ ImmutableBiMap.Builder<DexMethod, DexMethod> result = ImmutableBiMap.builder();
+ for (Map.Entry<DexMethod, DexMethod> entry : originalMethodSignatures.entrySet()) {
+ result.put(
+ getMethodSignatureAfterClassMerging(
+ entry.getKey(), mergedClasses, dexItemFactory, cache),
+ entry.getValue());
+ }
+ return result.build();
+ }
+
+ private static DexField getFieldSignatureAfterClassMerging(
+ DexField signature, Map<DexType, DexType> mergedClasses, DexItemFactory dexItemFactory) {
+ DexType newClass = mergedClasses.getOrDefault(signature.clazz, signature.clazz);
+ DexType newType = mergedClasses.getOrDefault(signature.type, signature.type);
+ if (signature.clazz.equals(newClass) && signature.type.equals(newType)) {
+ return signature;
+ }
+ return dexItemFactory.createField(newClass, newType, signature.name);
+ }
+
+ private static DexMethod getMethodSignatureAfterClassMerging(
+ DexMethod signature,
+ Map<DexType, DexType> mergedClasses,
+ DexItemFactory dexItemFactory,
+ Map<DexProto, DexProto> cache) {
+ DexType newHolder = mergedClasses.getOrDefault(signature.holder, signature.holder);
+ DexProto newProto =
+ dexItemFactory.applyClassMappingToProto(
+ signature.proto, type -> mergedClasses.getOrDefault(type, type), cache);
+ if (signature.holder.equals(newHolder) && signature.proto.equals(newProto)) {
+ return signature;
+ }
+ return dexItemFactory.createMethod(newHolder, newProto, signature.name);
+ }
+
public boolean hasMappingForSignatureInContext(DexType context, DexMethod signature) {
- Map<DexMethod, DexMethod> virtualToDirectMethodMap =
+ Map<DexMethod, GraphLenseLookupResult> virtualToDirectMethodMap =
contextualVirtualToDirectMethodMaps.get(context);
if (virtualToDirectMethodMap != null) {
return virtualToDirectMethodMap.containsKey(signature);
@@ -209,8 +266,13 @@
methodMap.put(from, to);
}
- public void mapVirtualMethodToDirectInType(DexMethod from, DexMethod to, DexType type) {
- Map<DexMethod, DexMethod> virtualToDirectMethodMap =
+ public void recordMove(DexMethod from, DexMethod to) {
+ originalMethodSignatures.put(to, from);
+ }
+
+ public void mapVirtualMethodToDirectInType(
+ DexMethod from, GraphLenseLookupResult to, DexType type) {
+ Map<DexMethod, GraphLenseLookupResult> virtualToDirectMethodMap =
contextualVirtualToDirectMethodMaps.computeIfAbsent(type, key -> new HashMap<>());
virtualToDirectMethodMap.put(from, to);
}
@@ -219,9 +281,12 @@
fieldMap.putAll(builder.fieldMap);
methodMap.putAll(builder.methodMap);
mergedMethodsBuilder.addAll(builder.mergedMethodsBuilder.build());
+ originalMethodSignatures.putAll(builder.originalMethodSignatures);
for (DexType context : builder.contextualVirtualToDirectMethodMaps.keySet()) {
- Map<DexMethod, DexMethod> current = contextualVirtualToDirectMethodMaps.get(context);
- Map<DexMethod, DexMethod> other = builder.contextualVirtualToDirectMethodMaps.get(context);
+ Map<DexMethod, GraphLenseLookupResult> current =
+ contextualVirtualToDirectMethodMaps.get(context);
+ Map<DexMethod, GraphLenseLookupResult> other =
+ builder.contextualVirtualToDirectMethodMaps.get(context);
if (current != null) {
current.putAll(other);
} else {
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java b/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
index 127debe..135e961 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
@@ -9,6 +9,7 @@
* Android API level description
*/
public enum AndroidApiLevel {
+ Q(29), // Speculative, this can change.
P(28),
O_MR1(27),
O(26),
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index 1b026c0..4dff93c 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -10,6 +10,9 @@
import com.android.tools.r8.ArchiveClassFileProvider;
import com.android.tools.r8.ClassFileConsumer;
import com.android.tools.r8.ClassFileResourceProvider;
+import com.android.tools.r8.DataEntryResource;
+import com.android.tools.r8.DataResourceProvider;
+import com.android.tools.r8.DataResourceProvider.Visitor;
import com.android.tools.r8.DexFilePerClassFileConsumer;
import com.android.tools.r8.DexIndexedConsumer;
import com.android.tools.r8.DirectoryClassFileProvider;
@@ -267,6 +270,7 @@
private final List<ProgramResourceProvider> programResourceProviders = new ArrayList<>();
private final List<ProgramResource> programResources = new ArrayList<>();
+ private final List<DataEntryResource> dataResources = new ArrayList<>();
private final Map<ProgramResource, String> programResourcesMainDescriptor = new HashMap<>();
private final List<ClassFileResourceProvider> classpathResourceProviders = new ArrayList<>();
private final List<ClassFileResourceProvider> libraryResourceProviders = new ArrayList<>();
@@ -464,6 +468,12 @@
return this;
}
+ /** Add resource data. */
+ public Builder addDataResource(DataEntryResource dataResource) {
+ addDataResources(dataResource);
+ return this;
+ }
+
/**
* Set proguard-map output data.
*
@@ -529,17 +539,34 @@
* Build final AndroidApp.
*/
public AndroidApp build() {
- if (!programResources.isEmpty()) {
+ if (!programResources.isEmpty() || !dataResources.isEmpty()) {
// If there are individual program resources move them to a dedicated provider.
- final List<ProgramResource> resources = ImmutableList.copyOf(programResources);
+ final List<ProgramResource> finalProgramResources = ImmutableList.copyOf(programResources);
+ final List<DataEntryResource> finalDataResources = ImmutableList.copyOf(dataResources);
programResourceProviders.add(
new ProgramResourceProvider() {
@Override
- public Collection<ProgramResource> getProgramResources() throws ResourceException {
- return resources;
+ public Collection<ProgramResource> getProgramResources() {
+ return finalProgramResources;
+ }
+
+ @Override
+ public DataResourceProvider getDataResourceProvider() {
+ if (!finalDataResources.isEmpty()) {
+ return new DataResourceProvider() {
+ @Override
+ public void accept(Visitor visitor) {
+ for (DataEntryResource dataResource : finalDataResources) {
+ visitor.visit(dataResource);
+ }
+ }
+ };
+ }
+ return null;
}
});
programResources.clear();
+ dataResources.clear();
}
return new AndroidApp(
ImmutableList.copyOf(programResourceProviders),
@@ -560,9 +587,7 @@
} else if (isClassFile(file)) {
addProgramResources(ProgramResource.fromFile(Kind.CF, file));
} else if (isArchive(file)) {
- ArchiveResourceProvider archiveResourceProvider = new ArchiveResourceProvider(
- FilteredClassPath.unfiltered(file), ignoreDexInArchive);
- addProgramResourceProvider(archiveResourceProvider);
+ addProgramResourceProvider(ArchiveResourceProvider.fromArchive(file, ignoreDexInArchive));
} else {
throw new CompilationError("Unsupported source file type", new PathOrigin(file));
}
@@ -576,6 +601,14 @@
programResources.addAll(resources);
}
+ private void addDataResources(DataEntryResource... resources) {
+ addDataResources(Arrays.asList(resources));
+ }
+
+ private void addDataResources(Collection<DataEntryResource> resources) {
+ this.dataResources.addAll(resources);
+ }
+
private void addClasspathOrLibraryProvider(
Path file, List<ClassFileResourceProvider> providerList) throws IOException {
if (!Files.exists(file)) {
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 a611105..e92d0a2 100644
--- a/src/main/java/com/android/tools/r8/utils/ArchiveResourceProvider.java
+++ b/src/main/java/com/android/tools/r8/utils/ArchiveResourceProvider.java
@@ -21,6 +21,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -36,6 +37,10 @@
private final FilteredClassPath archive;
private final boolean ignoreDexInArchive;
+ public static ArchiveResourceProvider fromArchive(Path archive, boolean ignoreDexInArchive) {
+ return new ArchiveResourceProvider(FilteredClassPath.unfiltered(archive), ignoreDexInArchive);
+ }
+
ArchiveResourceProvider(FilteredClassPath archive, boolean ignoreDexInArchive) {
assert isArchive(archive.getPath());
origin = new PathOrigin(archive.getPath());
diff --git a/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java b/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java
index 31dc439..d177b36 100644
--- a/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java
+++ b/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java
@@ -9,12 +9,16 @@
import com.android.tools.r8.dexsplitter.DexSplitter.FeatureJar;
import com.android.tools.r8.origin.PathOrigin;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.regex.Pattern;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
/**
* Provides a mappings of classes to modules. The structure of the input file is as follows:
@@ -43,6 +47,7 @@
public final class FeatureClassMapping {
HashMap<String, String> parsedRules = new HashMap<>(); // Already parsed rules.
+ HashMap<String, String> parseNonClassRules = new HashMap<>();
boolean usesOnlyExactMappings = true;
HashSet<FeaturePredicate> mappings = new HashSet<>();
@@ -122,6 +127,19 @@
String javaType = DescriptorUtils.descriptorToJavaType(javaDescriptor);
mapping.addMapping(javaType, featureJar.getOutputName());
}
+ try (ZipFile zipfile = new ZipFile(jarPath.toString(), StandardCharsets.UTF_8)) {
+ Enumeration<? extends ZipEntry> entries = zipfile.entries();
+ while (entries.hasMoreElements()) {
+ ZipEntry entry = entries.nextElement();
+ String name = entry.getName();
+ if (!ZipUtils.isClassFile(name)) {
+ mapping.addNonClassMapping(name, featureJar.getOutputName());
+ }
+ }
+ } catch (IOException e) {
+ reporter.error(new ExceptionDiagnostic(e, new JarFileOrigin(jarPath)));
+ throw new AbortException();
+ }
}
assert mapping.usesOnlyExactMappings;
return mapping;
@@ -135,6 +153,11 @@
addRule(clazz, feature, 0);
}
+ public void addNonClassMapping(String name, String feature) {
+ // If a non-class file is present in multiple features put the resource in the base.
+ parseNonClassRules.put(name, parseNonClassRules.containsKey(name) ? baseName : feature);
+ }
+
FeatureClassMapping(List<String> lines) throws FeatureMappingException {
for (int i = 0; i < lines.size(); i++) {
String line = lines.get(i);
@@ -161,6 +184,10 @@
}
}
+ public String featureForNonClass(String nonClass) {
+ return parseNonClassRules.getOrDefault(nonClass, baseName);
+ }
+
private void parseAndAdd(String line, int lineNumber) throws FeatureMappingException {
if (line.startsWith(COMMENT)) {
return; // Ignore comments
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index eb8ee0e..d3c9e6d 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -84,6 +84,7 @@
enableNonNullTracking = false;
enableInlining = false;
enableClassInlining = false;
+ enableClassStaticizer = false;
enableSwitchMapRemoval = false;
outline.enabled = false;
enableValuePropagation = false;
@@ -102,6 +103,7 @@
public boolean enableInlining =
!Version.isDev() || System.getProperty("com.android.tools.r8.disableinlining") == null;
public boolean enableClassInlining = true;
+ public boolean enableClassStaticizer = true;
public int classInliningInstructionLimit = 50;
public int inliningInstructionLimit = 5;
public boolean enableSwitchMapRemoval = true;
@@ -447,7 +449,9 @@
public boolean placeExceptionalBlocksLast = false;
public boolean dontCreateMarkerInD8 = false;
public boolean forceJumboStringProcessing = false;
+ public boolean nondeterministicCycleElimination = false;
public Set<Inliner.Reason> validInliningReasons = null;
+ public boolean suppressExperimentalCfBackendWarning = false;
}
public boolean canUseInvokePolymorphicOnVarHandle() {
@@ -535,7 +539,7 @@
//
// See b/69364976 and b/77996377.
public boolean canHaveBoundsCheckEliminationBug() {
- return minApiLevel <= AndroidApiLevel.L.getLevel();
+ return minApiLevel < AndroidApiLevel.M.getLevel();
}
// MediaTek JIT compilers for KitKat phones did not implement the not
@@ -629,7 +633,7 @@
//
// See b/78493232 and b/80118070.
public boolean canHaveArtStringNewInitBug() {
- return minApiLevel <= AndroidApiLevel.P.getLevel();
+ return minApiLevel < AndroidApiLevel.Q.getLevel();
}
// Dalvik tracing JIT may perform invalid optimizations when int/float values are converted to
@@ -637,6 +641,6 @@
//
// See b/77496850.
public boolean canHaveNumberConversionRegisterAllocationBug() {
- return minApiLevel <= AndroidApiLevel.K.getLevel();
+ return minApiLevel < AndroidApiLevel.L.getLevel();
}
}
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index 7d7baf8..66490b2 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -20,6 +20,7 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.naming.ClassNaming;
@@ -35,6 +36,7 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.function.Function;
import java.util.function.Supplier;
public class LineNumberOptimizer {
@@ -202,8 +204,10 @@
}
public static ClassNameMapper run(
- DexApplication application, NamingLens namingLens, boolean identityMapping) {
- IdentityHashMap<DexString, List<DexProgramClass>> classesOfFiles = new IdentityHashMap<>();
+ DexApplication application,
+ GraphLense graphLense,
+ NamingLens namingLens,
+ boolean identityMapping) {
ClassNameMapper.Builder classNameMapperBuilder = ClassNameMapper.builder();
// Collect which files contain which classes that need to have their line numbers optimized.
for (DexProgramClass clazz : application.classes()) {
@@ -213,8 +217,8 @@
continue;
}
- IdentityHashMap<DexString, List<DexEncodedMethod>> methodsByName =
- groupMethodsByName(namingLens, clazz);
+ IdentityHashMap<DexString, List<DexEncodedMethod>> methodsByRenamedName =
+ groupMethodsByRenamedName(namingLens, clazz);
// At this point we don't know if we really need to add this class to the builder.
// It depends on whether any methods/fields are renamed or some methods contain positions.
@@ -231,10 +235,13 @@
addClassToClassNaming(clazz, renamedClassName, onDemandClassNamingBuilder);
// First transfer renamed fields to classNamingBuilder.
- addFieldsToClassNaming(namingLens, clazz, onDemandClassNamingBuilder);
+ addFieldsToClassNaming(graphLense, namingLens, clazz, onDemandClassNamingBuilder);
- // Then process the methods.
- for (List<DexEncodedMethod> methods : methodsByName.values()) {
+ // Then process the methods, ordered by renamed name.
+ List<DexString> renamedMethodNames = new ArrayList<>(methodsByRenamedName.keySet());
+ renamedMethodNames.sort(DexString::slowCompareTo);
+ for (DexString methodName : renamedMethodNames) {
+ List<DexEncodedMethod> methods = methodsByRenamedName.get(methodName);
if (methods.size() > 1) {
// If there are multiple methods with the same name (overloaded) then sort them for
// deterministic behaviour: the algorithm will assign new line numbers in this order.
@@ -259,7 +266,9 @@
}
}
- MethodSignature originalSignature = MethodSignature.fromDexMethod(method.method);
+ DexMethod originalMethod = graphLense.getOriginalMethodSignature(method.method);
+ MethodSignature originalSignature =
+ MethodSignature.fromDexMethod(originalMethod, originalMethod.holder != clazz.type);
DexString obfuscatedNameDexString = namingLens.lookupName(method.method);
String obfuscatedName = obfuscatedNameDexString.toString();
@@ -267,7 +276,8 @@
// Add simple "a() -> b" mapping if we won't have any other with concrete line numbers
if (mappedPositions.isEmpty()) {
// But only if it's been renamed.
- if (obfuscatedNameDexString != method.method.name) {
+ if (obfuscatedNameDexString != originalMethod.name
+ || originalMethod.holder != clazz.type) {
onDemandClassNamingBuilder
.get()
.addMappedRange(null, originalSignature, null, obfuscatedName);
@@ -276,7 +286,16 @@
}
Map<DexMethod, MethodSignature> signatures = new IdentityHashMap<>();
- signatures.put(method.method, originalSignature);
+ signatures.put(originalMethod, originalSignature);
+ Function<DexMethod, MethodSignature> getOriginalMethodSignature =
+ m -> {
+ DexMethod original = graphLense.getOriginalMethodSignature(m);
+ return signatures.computeIfAbsent(
+ original,
+ key ->
+ MethodSignature.fromDexMethod(
+ original, original.holder != clazz.getType()));
+ };
MemberNaming memberNaming = new MemberNaming(originalSignature, obfuscatedName);
onDemandClassNamingBuilder.get().addMemberEntry(memberNaming);
@@ -307,23 +326,14 @@
ClassNaming.Builder classNamingBuilder = onDemandClassNamingBuilder.get();
classNamingBuilder.addMappedRange(
obfuscatedRange,
- signatures.computeIfAbsent(
- firstPosition.method,
- m ->
- MethodSignature.fromDexMethod(
- m, firstPosition.method.holder != clazz.getType())),
+ getOriginalMethodSignature.apply(firstPosition.method),
originalRange,
obfuscatedName);
Position caller = firstPosition.caller;
while (caller != null) {
- Position finalCaller = caller;
classNamingBuilder.addMappedRange(
obfuscatedRange,
- signatures.computeIfAbsent(
- caller.method,
- m ->
- MethodSignature.fromDexMethod(
- m, finalCaller.method.holder != clazz.getType())),
+ getOriginalMethodSignature.apply(caller.method),
Math.max(caller.line, 0), // Prevent against "no-position".
obfuscatedName);
caller = caller.callerPosition;
@@ -379,42 +389,37 @@
}
}
- private static void addFieldsToClassNaming(NamingLens namingLens, DexProgramClass clazz,
+ private static void addFieldsToClassNaming(
+ GraphLense graphLense,
+ NamingLens namingLens,
+ DexProgramClass clazz,
Supplier<Builder> onDemandClassNamingBuilder) {
clazz.forEachField(
dexEncodedField -> {
DexField dexField = dexEncodedField.field;
+ DexField originalField = graphLense.getOriginalFieldSignature(dexField);
DexString renamedName = namingLens.lookupName(dexField);
- if (renamedName != dexField.name) {
- FieldSignature signature =
- new FieldSignature(dexField.name.toString(), dexField.type.toString());
- MemberNaming memberNaming = new MemberNaming(signature, renamedName.toString());
+ if (renamedName != originalField.name || originalField.clazz != clazz.type) {
+ FieldSignature originalSignature =
+ FieldSignature.fromDexField(originalField, originalField.clazz != clazz.type);
+ MemberNaming memberNaming = new MemberNaming(originalSignature, renamedName.toString());
onDemandClassNamingBuilder.get().addMemberEntry(memberNaming);
}
});
}
- private static IdentityHashMap<DexString, List<DexEncodedMethod>> groupMethodsByName(
+ private static IdentityHashMap<DexString, List<DexEncodedMethod>> groupMethodsByRenamedName(
NamingLens namingLens, DexProgramClass clazz) {
- IdentityHashMap<DexString, List<DexEncodedMethod>> methodsByName =
+ IdentityHashMap<DexString, List<DexEncodedMethod>> methodsByRenamedName =
new IdentityHashMap<>(clazz.directMethods().length + clazz.virtualMethods().length);
- clazz.forEachMethod(
- method -> {
- // Add method only if renamed or contains positions.
- if (namingLens.lookupName(method.method) != method.method.name
- || doesContainPositions(method)) {
- methodsByName.compute(
- method.method.name,
- (name, methods) -> {
- if (methods == null) {
- methods = new ArrayList<>();
- }
- methods.add(method);
- return methods;
- });
- }
- });
- return methodsByName;
+ for (DexEncodedMethod method : clazz.methods()) {
+ // Add method only if renamed or contains positions.
+ DexString renamedName = namingLens.lookupName(method.method);
+ if (renamedName != method.method.name || doesContainPositions(method)) {
+ methodsByRenamedName.computeIfAbsent(renamedName, key -> new ArrayList<>()).add(method);
+ }
+ }
+ return methodsByRenamedName;
}
private static boolean doesContainPositions(DexEncodedMethod method) {
diff --git a/src/test/examples/adaptresourcefilenames/A.java b/src/test/examples/adaptresourcefilenames/A.java
new file mode 100644
index 0000000..c83e8c5
--- /dev/null
+++ b/src/test/examples/adaptresourcefilenames/A.java
@@ -0,0 +1,12 @@
+// 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 adaptresourcefilenames;
+
+public class A {
+
+ public void method() {
+ System.out.println("In A.method()");
+ }
+}
diff --git a/src/test/examples/adaptresourcefilenames/B.java b/src/test/examples/adaptresourcefilenames/B.java
new file mode 100644
index 0000000..79725cb
--- /dev/null
+++ b/src/test/examples/adaptresourcefilenames/B.java
@@ -0,0 +1,23 @@
+// 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 adaptresourcefilenames;
+
+public class B extends A {
+
+ private Inner inner = new Inner();
+
+ public static class Inner {
+
+ public void method() {
+ System.out.println("In Inner.method()");
+ }
+ }
+
+ public void method() {
+ System.out.println("In B.method()");
+ super.method();
+ inner.method();
+ }
+}
diff --git a/src/test/examples/adaptresourcefilenames/TestClass.java b/src/test/examples/adaptresourcefilenames/TestClass.java
new file mode 100644
index 0000000..a5e96e5
--- /dev/null
+++ b/src/test/examples/adaptresourcefilenames/TestClass.java
@@ -0,0 +1,17 @@
+// 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 adaptresourcefilenames;
+
+import adaptresourcefilenames.pkg.C;
+import adaptresourcefilenames.pkg.innerpkg.D;
+
+public class TestClass {
+
+ public static void main(String[] args) {
+ new B().method();
+ new C().method();
+ new D().method();
+ }
+}
diff --git a/src/test/examples/adaptresourcefilenames/pkg/C.java b/src/test/examples/adaptresourcefilenames/pkg/C.java
new file mode 100644
index 0000000..93d4446
--- /dev/null
+++ b/src/test/examples/adaptresourcefilenames/pkg/C.java
@@ -0,0 +1,12 @@
+// 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 adaptresourcefilenames.pkg;
+
+public class C {
+
+ public void method() {
+ System.out.println("In C.method()");
+ }
+}
diff --git a/src/test/examples/adaptresourcefilenames/pkg/innerpkg/D.java b/src/test/examples/adaptresourcefilenames/pkg/innerpkg/D.java
new file mode 100644
index 0000000..2107547
--- /dev/null
+++ b/src/test/examples/adaptresourcefilenames/pkg/innerpkg/D.java
@@ -0,0 +1,12 @@
+// 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 adaptresourcefilenames.pkg.innerpkg;
+
+public class D {
+
+ public void method() {
+ System.out.println("In D.method()");
+ }
+}
diff --git a/src/test/examples/assumevalues6/Assumevalues.java b/src/test/examples/assumevalues6/Assumevalues.java
new file mode 100644
index 0000000..b3ad1de
--- /dev/null
+++ b/src/test/examples/assumevalues6/Assumevalues.java
@@ -0,0 +1,45 @@
+// 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 assumevalues6;
+
+public class Assumevalues {
+ public static int field = 0;
+ public static int field2 = 2;
+ public static int field3 = 2;
+
+ public static void main(String[] args) {
+ if (0 > field) {
+ System.out.println("NOPE1");
+ }
+ if (field < 0) {
+ System.out.println("NOPE2");
+ }
+ if (field3 == 0) {
+ System.out.println("NOPE3");
+ }
+ if (field3 != 0) {
+ System.out.println("YUP1");
+ }
+ if (2 < field) {
+ System.out.println("NOPE4");
+ }
+ if (field > 2) {
+ System.out.println("NOPE5");
+ }
+ if (field2 < field) {
+ System.out.println("NOPE6");
+ }
+ if (field > field2) {
+ System.out.println("NOPE7");
+ }
+ if (field <= field2) {
+ System.out.println("YUP2");
+ }
+ if (field2 >= field) {
+ System.out.println("YUP3");
+ }
+ System.out.println("OK");
+ }
+}
diff --git a/src/test/examples/assumevalues6/keep-rules.txt b/src/test/examples/assumevalues6/keep-rules.txt
new file mode 100644
index 0000000..4910a9d
--- /dev/null
+++ b/src/test/examples/assumevalues6/keep-rules.txt
@@ -0,0 +1,16 @@
+# 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.
+
+# Keep the application entry point. Get rid of everything that is not
+# reachable from there.
+-keep class assumevalues6.Assumevalues {
+ void main(...);
+}
+
+# Mark some fields with value ranges.
+-assumevalues public class assumevalues6.Assumevalues {
+ int field return 0..2;
+ int field2 return 2..4;
+ int field3 return 2..2;
+}
diff --git a/src/test/examples/classmerging/CallGraphCycleTest.java b/src/test/examples/classmerging/CallGraphCycleTest.java
new file mode 100644
index 0000000..40347d6
--- /dev/null
+++ b/src/test/examples/classmerging/CallGraphCycleTest.java
@@ -0,0 +1,30 @@
+// 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 classmerging;
+
+public class CallGraphCycleTest {
+
+ public static void main(String[] args) {
+ new B(true);
+ }
+
+ public static class A {
+
+ public A(boolean instantiateB) {
+ if (instantiateB) {
+ new B(false);
+ }
+ System.out.println("A(" + instantiateB + ")");
+ }
+ }
+
+ public static class B extends A {
+
+ public B(boolean instantiateBinA) {
+ super(instantiateBinA);
+ System.out.println("B(" + instantiateBinA + ")");
+ }
+ }
+}
diff --git a/src/test/examples/classmerging/ProguardFieldMapTest.java b/src/test/examples/classmerging/ProguardFieldMapTest.java
new file mode 100644
index 0000000..0cbf8a1
--- /dev/null
+++ b/src/test/examples/classmerging/ProguardFieldMapTest.java
@@ -0,0 +1,25 @@
+// 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 classmerging;
+
+public class ProguardFieldMapTest {
+
+ public static void main(String[] args) {
+ B b = new B();
+ b.test();
+ }
+
+ public static class A {
+
+ public String f = "A.f";
+ }
+
+ public static class B extends A {
+
+ public void test() {
+ System.out.println(f);
+ }
+ }
+}
diff --git a/src/test/examples/classmerging/ProguardMethodMapTest.java b/src/test/examples/classmerging/ProguardMethodMapTest.java
new file mode 100644
index 0000000..9ce0ad7
--- /dev/null
+++ b/src/test/examples/classmerging/ProguardMethodMapTest.java
@@ -0,0 +1,29 @@
+// 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 classmerging;
+
+public class ProguardMethodMapTest {
+
+ public static void main(String[] args) {
+ B b = new B();
+ b.method();
+ }
+
+ public static class A {
+
+ public void method() {
+ System.out.println("In A.method()");
+ }
+ }
+
+ public static class B extends A {
+
+ @Override
+ public void method() {
+ System.out.println("In B.method()");
+ super.method();
+ }
+ }
+}
diff --git a/src/test/examples/classmerging/keep-rules.txt b/src/test/examples/classmerging/keep-rules.txt
index 4ba014e..3a70504 100644
--- a/src/test/examples/classmerging/keep-rules.txt
+++ b/src/test/examples/classmerging/keep-rules.txt
@@ -7,6 +7,9 @@
-keep public class classmerging.Test {
public static void main(...);
}
+-keep public class classmerging.CallGraphCycleTest {
+ public static void main(...);
+}
-keep public class classmerging.ClassWithNativeMethodTest {
public static void main(...);
}
@@ -34,6 +37,12 @@
-keep public class classmerging.PinnedParameterTypesTest$TestClass {
public static void method(...);
}
+-keep public class classmerging.ProguardFieldMapTest {
+ public static void main(...);
+}
+-keep public class classmerging.ProguardMethodMapTest {
+ public static void main(...);
+}
-keep public class classmerging.SimpleInterfaceAccessTest {
public static void main(...);
}
diff --git a/src/test/examples/identifiernamestring/keep-rules-3.txt b/src/test/examples/identifiernamestring/keep-rules-3.txt
index aa23772..1c69314 100644
--- a/src/test/examples/identifiernamestring/keep-rules-3.txt
+++ b/src/test/examples/identifiernamestring/keep-rules-3.txt
@@ -11,7 +11,7 @@
-dontshrink
--identifiernamestring class * {
+-identifiernamestring class **.R {
static java.lang.reflect.Field *(java.lang.Class, java.lang.String);
static java.lang.reflect.Method *(java.lang.Class, java.lang.String, java.lang.Class[]);
}
diff --git a/src/test/examples/inlining/A.java b/src/test/examples/inlining/A.java
index dd857c8..a7aa4d2 100644
--- a/src/test/examples/inlining/A.java
+++ b/src/test/examples/inlining/A.java
@@ -6,15 +6,21 @@
class A {
int a;
+ int b;
A(int a) {
this.a = a;
+ this.b = a + 1;
}
int a() {
return a;
}
+ int b() {
+ return b;
+ }
+
int cannotInline(int v) {
// Cannot inline due to recursion.
if (v > 0) {
diff --git a/src/test/examples/inlining/Nullability.java b/src/test/examples/inlining/Nullability.java
index 74747d0..95422f0 100644
--- a/src/test/examples/inlining/Nullability.java
+++ b/src/test/examples/inlining/Nullability.java
@@ -43,8 +43,8 @@
}
int conditionalOperator(A a) {
- // a is not null when a.a() is invoked.
- return a != null ? a.a() : -1;
+ // a is not null when a.b() is invoked.
+ return a != null ? a.b() : -1;
}
int notInlinableOnThrow(Throwable t) throws Throwable {
@@ -97,8 +97,8 @@
// instructions with side effects.
if (a != null && result != 0) {
// Thus, the invocation below is the first instruction with side effect.
- // Also, a is not null here, hence a.a() is inlinable.
- result *= a.a();
+ // Also, a is not null here, hence a.b() is inlinable.
+ result *= a.b();
}
return result;
}
diff --git a/src/test/examples/shaking1/print-mapping-cf.ref b/src/test/examples/shaking1/print-mapping-cf.ref
new file mode 100644
index 0000000..9bf8013
--- /dev/null
+++ b/src/test/examples/shaking1/print-mapping-cf.ref
@@ -0,0 +1,7 @@
+shaking1.Shaking -> shaking1.Shaking:
+shaking1.Used -> a.a:
+ java.lang.String name -> a
+ 1:1:java.lang.String method():17:17 -> a
+ 1:1:void main(java.lang.String[]):8:8 -> main
+ 1:2:void <init>(java.lang.String):12:13 -> <init>
+ 1:1:java.lang.String aMethodThatIsNotUsedButKept():21:21 -> aMethodThatIsNotUsedButKept
diff --git a/src/test/examplesAndroidN/dexsplitsample/TextFile.txt b/src/test/examplesAndroidN/dexsplitsample/TextFile.txt
new file mode 100644
index 0000000..0bad44c
--- /dev/null
+++ b/src/test/examplesAndroidN/dexsplitsample/TextFile.txt
@@ -0,0 +1,9 @@
+// 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.
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore
+et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
+aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
+cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
+culpa qui officia deserunt mollit anim id est laborum.
diff --git a/src/test/java/com/android/tools/r8/CfFrontendExamplesTest.java b/src/test/java/com/android/tools/r8/CfFrontendExamplesTest.java
index 4f749b5..70e8445 100644
--- a/src/test/java/com/android/tools/r8/CfFrontendExamplesTest.java
+++ b/src/test/java/com/android/tools/r8/CfFrontendExamplesTest.java
@@ -303,6 +303,7 @@
R8Command command =
R8Command.builder()
.addProgramFiles(inputJar)
+ .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
.setMode(CompilationMode.DEBUG)
.setOutput(outputJar, OutputMode.ClassFile)
.build();
diff --git a/src/test/java/com/android/tools/r8/R8CFExamplesTests.java b/src/test/java/com/android/tools/r8/R8CFExamplesTests.java
index 377bfc0..2dbfb37 100644
--- a/src/test/java/com/android/tools/r8/R8CFExamplesTests.java
+++ b/src/test/java/com/android/tools/r8/R8CFExamplesTests.java
@@ -101,7 +101,7 @@
Path outputJar = temp.getRoot().toPath().resolve(outputName);
ToolHelper.runR8(
R8Command.builder()
- .addLibraryFiles(Paths.get(ToolHelper.JAVA_8_RUNTIME))
+ .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
.setMode(mode)
.addProgramFiles(inputJar)
.setOutput(outputJar, OutputMode.ClassFile)
diff --git a/src/test/java/com/android/tools/r8/R8CFRunExamplesJava9Test.java b/src/test/java/com/android/tools/r8/R8CFRunExamplesJava9Test.java
index b3950b4..f0e758d 100644
--- a/src/test/java/com/android/tools/r8/R8CFRunExamplesJava9Test.java
+++ b/src/test/java/com/android/tools/r8/R8CFRunExamplesJava9Test.java
@@ -8,8 +8,7 @@
import static org.junit.Assert.assertEquals;
import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.nio.file.Path;
@@ -37,7 +36,7 @@
for (UnaryOperator<R8Command.Builder> transformation : builderTransformations) {
builder = transformation.apply(builder);
}
- builder.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P));
+ builder.addLibraryFiles(ToolHelper.getJava8RuntimeJar());
R8Command command =
builder.addProgramFiles(inputFile).setOutput(out, OutputMode.ClassFile).build();
ToolHelper.runR8(command, this::combinedOptionConsumer);
@@ -62,8 +61,8 @@
}
if (!dexInspectorChecks.isEmpty()) {
- DexInspector inspector = new DexInspector(out);
- for (Consumer<DexInspector> check : dexInspectorChecks) {
+ CodeInspector inspector = new CodeInspector(out);
+ for (Consumer<CodeInspector> check : dexInspectorChecks) {
check.accept(inspector);
}
}
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index 7328c6e..180a5f8 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -12,18 +12,17 @@
import com.android.tools.r8.ToolHelper.DexVm;
import com.android.tools.r8.ToolHelper.DexVm.Kind;
import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.dex.Marker.Tool;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.ProguardRuleParserException;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.ArtErrorParser;
import com.android.tools.r8.utils.ArtErrorParser.ArtErrorInfo;
-import com.android.tools.r8.utils.DexInspector;
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.TestDescriptionWatcher;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
@@ -2060,10 +2059,10 @@
private void failWithDexDiff(File originalFile, File processedFile)
throws IOException, ExecutionException {
- DexInspector inspectOriginal =
- new DexInspector(originalFile.toPath().toAbsolutePath());
- DexInspector inspectProcessed =
- new DexInspector(processedFile.toPath().toAbsolutePath());
+ CodeInspector inspectOriginal =
+ new CodeInspector(originalFile.toPath().toAbsolutePath());
+ CodeInspector inspectProcessed =
+ new CodeInspector(processedFile.toPath().toAbsolutePath());
StringBuilder builderOriginal = new StringBuilder();
StringBuilder builderProcessed = new StringBuilder();
inspectOriginal.forAllClasses((clazz) -> builderOriginal.append(clazz.dumpMethods()));
@@ -2078,10 +2077,10 @@
List<ComparisonFailure> errors;
try {
// Parse all the verification errors.
- DexInspector processed = new DexInspector(processedFile.toPath());
- DexInspector original = DEX_COMPARE_WITH_DEX_REFERENCE_ON_FAILURE
- ? new DexInspector(referenceFile.toPath())
- : new DexInspector(inputFiles.stream().map(Paths::get).collect(Collectors.toList()));
+ CodeInspector processed = new CodeInspector(processedFile.toPath());
+ CodeInspector original = DEX_COMPARE_WITH_DEX_REFERENCE_ON_FAILURE
+ ? new CodeInspector(referenceFile.toPath())
+ : new CodeInspector(inputFiles.stream().map(Paths::get).collect(Collectors.toList()));
List<ArtErrorInfo> errorInfo = ArtErrorParser.parse(verificationError.getMessage());
errors = ListUtils.map(errorInfo, (error) ->
new ComparisonFailure(
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index f20335b..71f4559 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -4,12 +4,16 @@
package com.android.tools.r8;
+import static org.junit.Assert.assertEquals;
+
import com.android.tools.r8.ToolHelper.DexVm;
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.VmTestRunner.IgnoreIfVmOlderThan;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.OffOrAuto;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
@@ -94,6 +98,7 @@
.withOptionConsumer(opts -> opts.enableClassInlining = false)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 179, "lambdadesugaring"))
.run();
test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
@@ -101,6 +106,7 @@
.withOptionConsumer(opts -> opts.enableClassInlining = true)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 23, "lambdadesugaring"))
.run();
}
@@ -112,6 +118,7 @@
.withOptionConsumer(opts -> opts.enableClassInlining = false)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 179, "lambdadesugaring"))
.run();
test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
@@ -119,6 +126,7 @@
.withOptionConsumer(opts -> opts.enableClassInlining = true)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 23, "lambdadesugaring"))
.run();
}
@@ -131,6 +139,7 @@
.withOptionConsumer(opts -> opts.enableClassInlining = false)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 40, "lambdadesugaringnplus"))
.run();
test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
@@ -139,6 +148,7 @@
.withOptionConsumer(opts -> opts.enableClassInlining = true)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 5, "lambdadesugaringnplus"))
.run();
}
@@ -151,6 +161,7 @@
.withOptionConsumer(opts -> opts.enableClassInlining = false)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 40, "lambdadesugaringnplus"))
.run();
test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
@@ -159,9 +170,21 @@
.withOptionConsumer(opts -> opts.enableClassInlining = true)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 5, "lambdadesugaringnplus"))
.run();
}
+ private void checkLambdaCount(CodeInspector inspector, int expectedCount, String prefix) {
+ int count = 0;
+ for (FoundClassSubject clazz : inspector.allClasses()) {
+ if (clazz.isSynthesizedJavaLambdaClass() &&
+ clazz.getOriginalName().startsWith(prefix)) {
+ count++;
+ }
+ }
+ assertEquals(expectedCount, count);
+ }
+
class R8TestRunner extends TestRunner<R8TestRunner> {
R8TestRunner(String testName, String packageName, String mainClass) {
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java b/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
index 3d3b4e7..008c6b9 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
@@ -161,11 +161,15 @@
break;
}
case R8: {
- R8Command command =
- addInputFile(R8Command.builder())
- .setOutput(getOutputFile(), outputMode)
- .setMode(mode)
- .build();
+ R8Command command =
+ addInputFile(R8Command.builder())
+ .addLibraryFiles(
+ output == Output.CF
+ ? ToolHelper.getJava8RuntimeJar()
+ : ToolHelper.getDefaultAndroidJar())
+ .setOutput(getOutputFile(), outputMode)
+ .setMode(mode)
+ .build();
ExceptionUtils.withR8CompilationHandler(
command.getReporter(),
() ->
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
index b0876c9..f638575 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
@@ -14,14 +14,14 @@
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.FoundClassSubject;
-import com.android.tools.r8.utils.DexInspector.FoundMethodSubject;
-import com.android.tools.r8.utils.DexInspector.InstructionSubject;
-import com.android.tools.r8.utils.DexInspector.InvokeInstructionSubject;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.OffOrAuto;
import com.android.tools.r8.utils.TestDescriptionWatcher;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.InvokeInstructionSubject;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.ByteStreams;
@@ -49,7 +49,6 @@
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;
-import org.junit.rules.TestWatcher;
public abstract class RunExamplesAndroidOTest
<B extends BaseCommand.Builder<? extends BaseCommand, B>> {
@@ -63,7 +62,7 @@
AndroidApiLevel androidJarVersion = null;
final List<Consumer<InternalOptions>> optionConsumers = new ArrayList<>();
- final List<Consumer<DexInspector>> dexInspectorChecks = new ArrayList<>();
+ final List<Consumer<CodeInspector>> dexInspectorChecks = new ArrayList<>();
final List<UnaryOperator<B>> builderTransformations = new ArrayList<>();
TestRunner(String testName, String packageName, String mainClass) {
@@ -74,7 +73,7 @@
abstract C self();
- C withDexCheck(Consumer<DexInspector> check) {
+ C withDexCheck(Consumer<CodeInspector> check) {
dexInspectorChecks.add(check);
return self();
}
@@ -156,8 +155,8 @@
}
if (!dexInspectorChecks.isEmpty()) {
- DexInspector inspector = new DexInspector(out);
- for (Consumer<DexInspector> check : dexInspectorChecks) {
+ CodeInspector inspector = new CodeInspector(out);
+ for (Consumer<CodeInspector> check : dexInspectorChecks) {
check.accept(inspector);
}
}
@@ -496,10 +495,10 @@
mainDexClasses);
// Collect main dex types.
- DexInspector fullInspector = getMainDexInspector(fullDexes);
- DexInspector indexedIntermediateInspector =
+ CodeInspector fullInspector = getMainDexInspector(fullDexes);
+ CodeInspector indexedIntermediateInspector =
getMainDexInspector(dexesThroughIndexedIntermediate);
- DexInspector filePerInputClassIntermediateInspector =
+ CodeInspector filePerInputClassIntermediateInspector =
getMainDexInspector(dexesThroughFilePerInputClassIntermediate);
Collection<String> fullMainClasses = new HashSet<>();
fullInspector.forAllClasses(
@@ -579,12 +578,12 @@
}
}
- protected DexInspector getMainDexInspector(Path zip)
+ protected CodeInspector getMainDexInspector(Path zip)
throws ZipException, IOException, ExecutionException {
try (ZipFile zipFile = new ZipFile(zip.toFile(), StandardCharsets.UTF_8)) {
try (InputStream in =
zipFile.getInputStream(zipFile.getEntry(ToolHelper.DEFAULT_DEX_FILENAME))) {
- return new DexInspector(
+ return new CodeInspector(
AndroidApp.builder()
.addDexProgramData(ByteStreams.toByteArray(in), Origin.unknown())
.build());
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java
index 4701ac3..7f15eca 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java
@@ -13,13 +13,13 @@
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.FoundClassSubject;
-import com.android.tools.r8.utils.DexInspector.FoundMethodSubject;
-import com.android.tools.r8.utils.DexInspector.InstructionSubject;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.OffOrAuto;
import com.android.tools.r8.utils.TestDescriptionWatcher;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.ByteStreams;
@@ -57,7 +57,7 @@
Integer androidJarVersion = null;
final List<Consumer<InternalOptions>> optionConsumers = new ArrayList<>();
- final List<Consumer<DexInspector>> dexInspectorChecks = new ArrayList<>();
+ final List<Consumer<CodeInspector>> dexInspectorChecks = new ArrayList<>();
final List<UnaryOperator<B>> builderTransformations = new ArrayList<>();
TestRunner(String testName, String packageName, String mainClass) {
@@ -68,7 +68,7 @@
abstract C self();
- C withDexCheck(Consumer<DexInspector> check) {
+ C withDexCheck(Consumer<CodeInspector> check) {
dexInspectorChecks.add(check);
return self();
}
@@ -150,8 +150,8 @@
}
if (!dexInspectorChecks.isEmpty()) {
- DexInspector inspector = new DexInspector(out);
- for (Consumer<DexInspector> check : dexInspectorChecks) {
+ CodeInspector inspector = new CodeInspector(out);
+ for (Consumer<CodeInspector> check : dexInspectorChecks) {
check.accept(inspector);
}
}
@@ -255,12 +255,12 @@
}
}
- protected DexInspector getMainDexInspector(Path zip)
+ protected CodeInspector getMainDexInspector(Path zip)
throws ZipException, IOException, ExecutionException {
try (ZipFile zipFile = new ZipFile(zip.toFile(), StandardCharsets.UTF_8)) {
try (InputStream in =
zipFile.getInputStream(zipFile.getEntry(ToolHelper.DEFAULT_DEX_FILENAME))) {
- return new DexInspector(
+ return new CodeInspector(
AndroidApp.builder()
.addDexProgramData(ByteStreams.toByteArray(in), Origin.unknown())
.build());
diff --git a/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java b/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java
index 60ebf39..f149cba 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java
@@ -4,9 +4,9 @@
package com.android.tools.r8;
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
import static com.android.tools.r8.utils.FileUtils.ZIP_EXTENSION;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
@@ -14,15 +14,15 @@
import com.android.tools.r8.ToolHelper.DexVm;
import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.FoundClassSubject;
-import com.android.tools.r8.utils.DexInspector.FoundMethodSubject;
-import com.android.tools.r8.utils.DexInspector.InstructionSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.OffOrAuto;
import com.android.tools.r8.utils.TestDescriptionWatcher;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import java.io.IOException;
@@ -54,7 +54,7 @@
Integer androidJarVersion = null;
final List<Consumer<InternalOptions>> optionConsumers = new ArrayList<>();
- final List<Consumer<DexInspector>> dexInspectorChecks = new ArrayList<>();
+ final List<Consumer<CodeInspector>> dexInspectorChecks = new ArrayList<>();
final List<UnaryOperator<B>> builderTransformations = new ArrayList<>();
TestRunner(String testName, String packageName, String mainClass) {
@@ -65,7 +65,7 @@
abstract C self();
- C withDexCheck(Consumer<DexInspector> check) {
+ C withDexCheck(Consumer<CodeInspector> check) {
dexInspectorChecks.add(check);
return self();
}
@@ -147,8 +147,8 @@
}
if (!dexInspectorChecks.isEmpty()) {
- DexInspector inspector = new DexInspector(out);
- for (Consumer<DexInspector> check : dexInspectorChecks) {
+ CodeInspector inspector = new CodeInspector(out);
+ for (Consumer<CodeInspector> check : dexInspectorChecks) {
check.accept(inspector);
}
}
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 8adddfd..d6abe44 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -7,6 +7,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import com.android.tools.r8.DataResourceProvider.Visitor;
import com.android.tools.r8.ToolHelper.DexVm;
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.code.Instruction;
@@ -20,14 +21,14 @@
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.AndroidAppConsumers;
import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.PreloadedClassFileProvider;
import com.android.tools.r8.utils.TestDescriptionWatcher;
import com.android.tools.r8.utils.ZipUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
@@ -40,6 +41,7 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -54,6 +56,11 @@
public class TestBase {
+ protected enum Backend {
+ CF,
+ DEX
+ };
+
// Actually running Proguard should only be during development.
private static final boolean RUN_PROGUARD = System.getProperty("run_proguard") != null;
// Actually running r8.jar in a forked process.
@@ -180,17 +187,25 @@
* Create a temporary JAR file containing the specified test classes.
*/
protected Path jarTestClasses(Class... classes) throws IOException {
+ return jarTestClasses(Arrays.asList(classes), null);
+ }
+
+ /** Create a temporary JAR file containing the specified test classes and data resources. */
+ protected Path jarTestClasses(Iterable<Class> classes, List<DataEntryResource> dataResources)
+ throws IOException {
Path jar = File.createTempFile("junit", ".jar", temp.getRoot()).toPath();
try (JarOutputStream out = new JarOutputStream(new FileOutputStream(jar.toFile()))) {
addTestClassesToJar(out, classes);
+ if (dataResources != null) {
+ addDataResourcesToJar(out, dataResources);
+ }
}
return jar;
}
- /**
- * Create a temporary JAR file containing the specified test classes.
- */
- protected void addTestClassesToJar(JarOutputStream out, Class... classes) throws IOException {
+ /** Create a temporary JAR file containing the specified test classes. */
+ protected void addTestClassesToJar(JarOutputStream out, Iterable<Class> classes)
+ throws IOException {
for (Class clazz : classes) {
try (FileInputStream in =
new FileInputStream(ToolHelper.getClassFileForTestClass(clazz).toFile())) {
@@ -201,6 +216,67 @@
}
}
+ /** Create a temporary JAR file containing the specified data resources. */
+ protected void addDataResourcesToJar(JarOutputStream out, List<DataEntryResource> dataResources)
+ throws IOException {
+ try {
+ for (DataEntryResource dataResource : dataResources) {
+ out.putNextEntry(new ZipEntry(dataResource.getName()));
+ ByteStreams.copy(dataResource.getByteStream(), out);
+ out.closeEntry();
+ }
+ } catch (ResourceException e) {
+ throw new IOException("Resource error", e);
+ }
+ }
+
+ /**
+ * Creates a new, temporary JAR that contains all the entries from the given JAR as well as the
+ * specified data resources. The given JAR is left unchanged.
+ */
+ protected Path addDataResourcesToExistingJar(
+ Path existingJar, List<DataEntryResource> dataResources) throws IOException {
+ Path newJar = File.createTempFile("app", FileUtils.JAR_EXTENSION, temp.getRoot()).toPath();
+ try (JarOutputStream out = new JarOutputStream(new FileOutputStream(newJar.toFile()))) {
+ ArchiveProgramResourceProvider.fromArchive(existingJar)
+ .readArchive(
+ (entry, stream) -> {
+ out.putNextEntry(new ZipEntry(entry.getEntryName()));
+ ByteStreams.copy(stream, out);
+ out.closeEntry();
+ });
+ addDataResourcesToJar(out, dataResources);
+ }
+ return newJar;
+ }
+
+ /** Returns a list containing all the data resources in the given app. */
+ public static List<DataEntryResource> getDataResources(AndroidApp app) throws ResourceException {
+ List<DataEntryResource> dataResources = new ArrayList<>();
+ for (ProgramResourceProvider programResourceProvider : app.getProgramResourceProviders()) {
+ dataResources.addAll(getDataResources(programResourceProvider.getDataResourceProvider()));
+ }
+ return dataResources;
+ }
+
+ public static List<DataEntryResource> getDataResources(DataResourceProvider dataResourceProvider)
+ throws ResourceException {
+ List<DataEntryResource> dataResources = new ArrayList<>();
+ if (dataResourceProvider != null) {
+ dataResourceProvider.accept(
+ new Visitor() {
+ @Override
+ public void visit(DataDirectoryResource directory) {}
+
+ @Override
+ public void visit(DataEntryResource file) {
+ dataResources.add(file);
+ }
+ });
+ }
+ return dataResources;
+ }
+
/**
* Create a temporary JAR file containing all test classes in a package.
*/
@@ -528,6 +604,25 @@
return result.stdout;
}
+ /** Run application on Java with the specified main class and provided arguments. */
+ protected String runOnJava(AndroidApp app, Class mainClass, String... args) throws IOException {
+ return runOnJava(app, mainClass, Arrays.asList(args));
+ }
+
+ /** Run application on Java with the specified main class and provided arguments. */
+ protected String runOnJava(AndroidApp app, Class mainClass, List<String> args)
+ throws IOException {
+ return runOnJava(app, mainClass.getCanonicalName(), args);
+ }
+
+ /** Run application on Java with the specified main class and provided arguments. */
+ protected String runOnJava(AndroidApp app, String mainClass, List<String> args)
+ throws IOException {
+ Path out = File.createTempFile("junit", ".zip", temp.getRoot()).toPath();
+ app.writeToZip(out, OutputMode.ClassFile);
+ return ToolHelper.runJava(out, mainClass).stdout;
+ }
+
protected ProcessResult runOnJavaRaw(String main, byte[]... classes) throws IOException {
return runOnJavaRaw(main, Arrays.asList(classes));
}
@@ -575,7 +670,7 @@
}
protected DexEncodedMethod getMethod(
- DexInspector inspector,
+ CodeInspector inspector,
String className,
String returnType,
String methodName,
@@ -594,7 +689,7 @@
String methodName,
List<String> parameters) {
try {
- DexInspector inspector = new DexInspector(application);
+ CodeInspector inspector = new CodeInspector(application);
return getMethod(inspector, className, returnType, methodName, parameters);
} catch (Exception e) {
return null;
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 2667ce2..2af67a4 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.shaking.FilteredClassPath;
import com.android.tools.r8.shaking.ProguardConfiguration;
@@ -73,11 +74,13 @@
public static final String LIBS_DIR = BUILD_DIR + "libs/";
public static final String TESTS_DIR = "src/test/";
public static final String EXAMPLES_DIR = TESTS_DIR + "examples/";
+ public static final String EXAMPLES_ANDROID_N_DIR = TESTS_DIR + "examplesAndroidN/";
public static final String EXAMPLES_ANDROID_O_DIR = TESTS_DIR + "examplesAndroidO/";
public static final String EXAMPLES_ANDROID_P_DIR = TESTS_DIR + "examplesAndroidP/";
public static final String EXAMPLES_KOTLIN_DIR = TESTS_DIR + "examplesKotlin/";
public static final String TESTS_BUILD_DIR = BUILD_DIR + "test/";
public static final String EXAMPLES_BUILD_DIR = TESTS_BUILD_DIR + "examples/";
+ public static final String EXAMPLES_CF_DIR = EXAMPLES_BUILD_DIR + "classes/";
public static final String EXAMPLES_KOTLIN_BUILD_DIR = TESTS_BUILD_DIR + "examplesKotlin/";
public static final String EXAMPLES_KOTLIN_RESOURCE_DIR =
TESTS_BUILD_DIR + "kotlinR8TestResources/";
@@ -474,8 +477,9 @@
public static byte[] getClassAsBytes(Class clazz) throws IOException {
String s = clazz.getSimpleName() + ".class";
Class outer = clazz.getEnclosingClass();
- if (outer != null) {
+ while (outer != null) {
s = outer.getSimpleName() + '$' + s;
+ outer = outer.getEnclosingClass();
}
return ByteStreams.toByteArray(clazz.getResourceAsStream(s));
}
@@ -532,6 +536,10 @@
return getArtDir(version) + "/" + binary;
}
+ public static Path getJava8RuntimeJar() {
+ return Paths.get(JAVA_8_RUNTIME);
+ }
+
public static Path getDefaultAndroidJar() {
return getAndroidJar(AndroidApiLevel.getDefault());
}
@@ -746,8 +754,11 @@
}
public static List<Path> getClassFilesForTestPackage(Package pkg) throws IOException {
- Path dir = ToolHelper.getPackageDirectoryForTestPackage(pkg);
- return Files.walk(dir)
+ return getClassFilesForTestDirectory(ToolHelper.getPackageDirectoryForTestPackage(pkg));
+ }
+
+ public static List<Path> getClassFilesForTestDirectory(Path directory) throws IOException {
+ return Files.walk(directory)
.filter(path -> path.toString().endsWith(".class"))
.collect(Collectors.toList());
}
@@ -816,6 +827,13 @@
.setProguardMapConsumer(StringConsumer.emptyConsumer());
}
+ public static R8Command.Builder prepareR8CommandBuilder(
+ AndroidApp app, ProgramConsumer programConsumer, DiagnosticsHandler diagnosticsHandler) {
+ return R8Command.builder(app, diagnosticsHandler)
+ .setProgramConsumer(programConsumer)
+ .setProguardMapConsumer(StringConsumer.emptyConsumer());
+ }
+
public static AndroidApp runR8(AndroidApp app) throws IOException {
return runR8WithProgramConsumer(app, DexIndexedConsumer.emptyConsumer());
}
@@ -1603,6 +1621,7 @@
Executors.newSingleThreadExecutor(),
application,
null,
+ GraphLense.getIdentityLense(),
NamingLens.getIdentityLens(),
null,
options,
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/AccessRelaxationTestBase.java b/src/test/java/com/android/tools/r8/accessrelaxation/AccessRelaxationTestBase.java
new file mode 100644
index 0000000..80541e8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/AccessRelaxationTestBase.java
@@ -0,0 +1,74 @@
+// 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.accessrelaxation;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPublic;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.VmTestRunner;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.runner.RunWith;
+
+@RunWith(VmTestRunner.class)
+abstract class AccessRelaxationTestBase extends TestBase {
+
+ static R8Command.Builder loadProgramFiles(Iterable<Class> classes) {
+ R8Command.Builder builder = R8Command.builder();
+ for (Class clazz : classes) {
+ builder.addProgramFiles(ToolHelper.getClassFileForTestClass(clazz));
+ }
+ builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+ builder.setMinApiLevel(ToolHelper.getMinApiLevelForDexVm().getLevel());
+ return builder;
+ }
+
+ static R8Command.Builder loadProgramFiles(Package p, Class... classes) throws Exception {
+ R8Command.Builder builder = R8Command.builder();
+ builder.addProgramFiles(ToolHelper.getClassFilesForTestPackage(p));
+ for (Class clazz : classes) {
+ builder.addProgramFiles(ToolHelper.getClassFileForTestClass(clazz));
+ }
+ builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+ builder.setMinApiLevel(ToolHelper.getMinApiLevelForDexVm().getLevel());
+ return builder;
+ }
+
+ void compareJvmAndArt(AndroidApp app, Class mainClass) throws Exception {
+ // Run on Jvm.
+ String jvmOutput = runOnJava(mainClass);
+
+ // Run on Art to check generated code against verifier.
+ String artOutput = runOnArt(app, mainClass);
+
+ String adjustedArtOutput = artOutput.replace(
+ "java.lang.IncompatibleClassChangeError", "java.lang.IllegalAccessError");
+ assertEquals(jvmOutput, adjustedArtOutput);
+ }
+
+ static void assertPublic(CodeInspector codeInspector, Class clazz, MethodSignature signature) {
+ ClassSubject classSubject = codeInspector.clazz(clazz);
+ assertThat(classSubject, isPresent());
+ MethodSubject methodSubject = classSubject.method(signature);
+ assertThat(methodSubject, isPublic());
+ }
+
+ static void assertNotPublic(CodeInspector codeInspector, Class clazz, MethodSignature signature) {
+ ClassSubject classSubject = codeInspector.clazz(clazz);
+ assertThat(classSubject, isPresent());
+ MethodSubject methodSubject = classSubject.method(signature);
+ assertThat(methodSubject, not(isPublic()));
+ }
+
+}
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/ConstructorRelaxationTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/ConstructorRelaxationTest.java
new file mode 100644
index 0000000..d90ae45
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/ConstructorRelaxationTest.java
@@ -0,0 +1,198 @@
+// 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.accessrelaxation;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import java.util.List;
+import org.junit.Test;
+
+class L1 {
+ private final String x;
+
+ private L1(String x) {
+ this.x = x;
+ }
+
+ private L1() {
+ this("private_x");
+ }
+
+ static L1 create() {
+ return new L1();
+ }
+
+ L1(int i) {
+ this(String.valueOf(i));
+ }
+
+ @Override
+ public String toString() {
+ return x;
+ }
+}
+
+class L2_1 extends L1 {
+ private String y;
+
+ private L2_1() {
+ this(21);
+ this.y = "private_L2_1_y";
+ }
+
+ L2_1(int i) {
+ super(i);
+ this.y = "L2_1_y";
+ }
+
+ private L2_1(String y) {
+ this(21);
+ this.y = y;
+ }
+
+ static L2_1 create(String y) {
+ return new L2_1(y);
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + "_" + y;
+ }
+}
+
+class L2_2 extends L1 {
+ private String y;
+
+ private L2_2(int i) {
+ super(i);
+ this.y = "private_L2_2_y";
+ }
+
+ L2_2(String y) {
+ this(22);
+ this.y = y;
+ }
+
+ static L2_1 create() {
+ return new L2_1(22);
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + "_" + y;
+ }
+}
+
+class L3_1 extends L2_1 {
+ private final String z;
+
+ private L3_1(int i) {
+ this(String.valueOf(i));
+ }
+
+ private L3_1(String z) {
+ super(31);
+ this.z = z;
+ }
+
+ static L3_1 create(int i) {
+ return new L3_1(i);
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + "_" + z;
+ }
+}
+
+class L3_2 extends L2_2 {
+ private String z;
+
+ private L3_2() {
+ super("private_L3_2_y");
+ this.z = "private_L3_2_z";
+ }
+
+ private L3_2(int i) {
+ super(String.valueOf(i));
+ this.z = "private_L3_2_z" + "_" + i;
+ }
+
+ L3_2(String z) {
+ this(32);
+ this.z = z;
+ }
+
+ static L3_2 create(String z) {
+ return new L3_2(z);
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + "_" + z;
+ }
+}
+
+class CtorTestMain {
+ public static void main(String[] args) {
+ System.out.println(L1.create());
+ System.out.println(L2_1.create("main_y"));
+ System.out.println(L2_2.create());
+ System.out.println(L3_1.create(41));
+ System.out.println(L3_2.create("main_z"));
+ }
+}
+
+public final class ConstructorRelaxationTest extends AccessRelaxationTestBase {
+ private static final String INIT= "<init>";
+ private static final List<Class> CLASSES =
+ ImmutableList.of(L1.class, L2_1.class, L2_2.class, L3_1.class, L3_2.class);
+
+ @Test
+ public void test() throws Exception {
+ Class mainClass = CtorTestMain.class;
+ R8Command.Builder builder =
+ loadProgramFiles(Iterables.concat(CLASSES, ImmutableList.of(mainClass)));
+ builder.addProguardConfiguration(
+ ImmutableList.of(
+ "-keep class " + mainClass.getCanonicalName() + "{",
+ " public static void main(java.lang.String[]);",
+ "}",
+ "",
+ "-keep class *.L* {",
+ " <init>(...);",
+ "}",
+ "",
+ "-dontobfuscate",
+ "-allowaccessmodification"
+ ),
+ Origin.unknown());
+
+ AndroidApp app = ToolHelper.runR8(builder.build(), options -> {
+ options.enableInlining = false;
+ options.enableClassMerging = false;
+ });
+ compareJvmAndArt(app, mainClass);
+
+ CodeInspector codeInspector = new CodeInspector(app);
+ for (Class clazz : CLASSES) {
+ ClassSubject classSubject = codeInspector.clazz(clazz);
+ assertThat(classSubject, isPresent());
+ classSubject.getDexClass().forEachMethod(m -> {
+ assertTrue(!m.isInstanceInitializer() || m.isPublicMethod());
+ });
+ }
+ }
+
+}
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/InvokeTypeConversionTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/InvokeTypeConversionTest.java
index 1f97c11..787568b 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/InvokeTypeConversionTest.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/InvokeTypeConversionTest.java
@@ -3,7 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.accessrelaxation;
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -24,8 +24,8 @@
import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
import com.android.tools.r8.smali.SmaliTestBase;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.util.List;
import java.util.function.Consumer;
@@ -62,7 +62,7 @@
private void run(
SmaliBuilder builder,
String expectedException,
- Consumer<DexInspector> inspectorConsumer) throws Exception {
+ Consumer<CodeInspector> inspectorConsumer) throws Exception {
AndroidApp app = buildApplication(builder);
List<String> pgConfigs = ImmutableList.of(
keepMainProguardConfiguration(CLASS_NAME),
@@ -82,7 +82,7 @@
assertEquals(1, artResult.exitCode);
assertThat(artResult.stderr, containsString(expectedException));
}
- DexInspector inspector = new DexInspector(processedApp);
+ CodeInspector inspector = new CodeInspector(processedApp);
inspectorConsumer.accept(inspector);
}
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/AccessRelaxationTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/NonConstructorRelaxationTest.java
similarity index 61%
rename from src/test/java/com/android/tools/r8/accessrelaxation/AccessRelaxationTest.java
rename to src/test/java/com/android/tools/r8/accessrelaxation/NonConstructorRelaxationTest.java
index 53a8c31..5b641dc 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/AccessRelaxationTest.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/NonConstructorRelaxationTest.java
@@ -4,17 +4,9 @@
package com.android.tools.r8.accessrelaxation;
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPublic;
-import static org.hamcrest.CoreMatchers.not;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertThat;
-import com.android.tools.r8.DexIndexedConsumer;
import com.android.tools.r8.R8Command;
-import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.VmTestRunner;
import com.android.tools.r8.accessrelaxation.privateinstance.Base;
import com.android.tools.r8.accessrelaxation.privateinstance.Sub1;
import com.android.tools.r8.accessrelaxation.privateinstance.Sub2;
@@ -26,56 +18,13 @@
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import org.junit.Test;
-import org.junit.runner.RunWith;
-@RunWith(VmTestRunner.class)
-public class AccessRelaxationTest extends TestBase {
+public final class NonConstructorRelaxationTest extends AccessRelaxationTestBase {
private static final String STRING = "java.lang.String";
- private static R8Command.Builder loadProgramFiles(Package p, Class... classes) throws Exception {
- R8Command.Builder builder = R8Command.builder();
- builder.addProgramFiles(ToolHelper.getClassFilesForTestPackage(p));
- for (Class clazz : classes) {
- builder.addProgramFiles(ToolHelper.getClassFileForTestClass(clazz));
- }
- builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
- builder.setMinApiLevel(ToolHelper.getMinApiLevelForDexVm().getLevel());
- return builder;
- }
-
- private void compareJvmAndArt(AndroidApp app, Class mainClass) throws Exception {
- // Run on Jvm.
- String jvmOutput = runOnJava(mainClass);
-
- // Run on Art to check generated code against verifier.
- String artOutput = runOnArt(app, mainClass);
-
- String adjustedArtOutput = artOutput.replace(
- "java.lang.IncompatibleClassChangeError", "java.lang.IllegalAccessError");
- assertEquals(jvmOutput, adjustedArtOutput);
- }
-
- private static void assertPublic(
- DexInspector dexInspector, Class clazz, MethodSignature signature) {
- ClassSubject classSubject = dexInspector.clazz(clazz);
- assertThat(classSubject, isPresent());
- MethodSubject methodSubject = classSubject.method(signature);
- assertThat(methodSubject, isPublic());
- }
-
- private static void assertNotPublic(
- DexInspector dexInspector, Class clazz, MethodSignature signature) {
- ClassSubject classSubject = dexInspector.clazz(clazz);
- assertThat(classSubject, isPresent());
- MethodSubject methodSubject = classSubject.method(signature);
- assertThat(methodSubject, not(isPublic()));
- }
-
@Test
public void testStaticMethodRelaxation() throws Exception {
Class mainClass = C.class;
@@ -113,20 +62,20 @@
AndroidApp app = ToolHelper.runR8(builder.build());
compareJvmAndArt(app, mainClass);
- DexInspector dexInspector = new DexInspector(app);
- assertPublic(dexInspector, A.class,
+ CodeInspector codeInspector = new CodeInspector(app);
+ assertPublic(codeInspector, A.class,
new MethodSignature("baz", STRING, ImmutableList.of()));
- assertPublic(dexInspector, A.class,
+ assertPublic(codeInspector, A.class,
new MethodSignature("bar", STRING, ImmutableList.of()));
- assertPublic(dexInspector, A.class,
+ assertPublic(codeInspector, A.class,
new MethodSignature("bar", STRING, ImmutableList.of("int")));
- assertPublic(dexInspector, A.class,
+ assertPublic(codeInspector, A.class,
new MethodSignature("blah", STRING, ImmutableList.of("int")));
- assertPublic(dexInspector, B.class,
+ assertPublic(codeInspector, B.class,
new MethodSignature("blah", STRING, ImmutableList.of("int")));
- assertPublic(dexInspector, BB.class,
+ assertPublic(codeInspector, BB.class,
new MethodSignature("blah", STRING, ImmutableList.of("int")));
}
@@ -161,24 +110,24 @@
AndroidApp app = ToolHelper.runR8(builder.build());
compareJvmAndArt(app, mainClass);
- DexInspector dexInspector = new DexInspector(app);
- assertPublic(dexInspector, Base.class,
+ CodeInspector codeInspector = new CodeInspector(app);
+ assertPublic(codeInspector, Base.class,
new MethodSignature("foo", STRING, ImmutableList.of()));
// Base#foo?() can't be publicized due to Itf<1>#foo<1>().
- assertNotPublic(dexInspector, Base.class,
+ assertNotPublic(codeInspector, Base.class,
new MethodSignature("foo1", STRING, ImmutableList.of()));
- assertNotPublic(dexInspector, Base.class,
+ assertNotPublic(codeInspector, Base.class,
new MethodSignature("foo2", STRING, ImmutableList.of()));
// Sub?#bar1(int) can be publicized as they don't bother each other.
- assertPublic(dexInspector, Sub1.class,
+ assertPublic(codeInspector, Sub1.class,
new MethodSignature("bar1", STRING, ImmutableList.of("int")));
- assertPublic(dexInspector, Sub2.class,
+ assertPublic(codeInspector, Sub2.class,
new MethodSignature("bar1", STRING, ImmutableList.of("int")));
// Sub2#bar2(int) is unique throughout the hierarchy, hence publicized.
- assertPublic(dexInspector, Sub2.class,
+ assertPublic(codeInspector, Sub2.class,
new MethodSignature("bar2", STRING, ImmutableList.of("int")));
}
}
diff --git a/src/test/java/com/android/tools/r8/bisect/BisectTest.java b/src/test/java/com/android/tools/r8/bisect/BisectTest.java
index 82a4a94..5252f08 100644
--- a/src/test/java/com/android/tools/r8/bisect/BisectTest.java
+++ b/src/test/java/com/android/tools/r8/bisect/BisectTest.java
@@ -14,9 +14,9 @@
import com.android.tools.r8.smali.SmaliBuilder;
import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -81,7 +81,7 @@
}
private Result command(DexApplication application) {
- DexInspector inspector = new DexInspector(application);
+ CodeInspector inspector = new CodeInspector(application);
if (inspector
.clazz(ERRONEOUS_CLASS)
.method(erroneousMethodSignature.returnType,
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java b/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
index cc8633e4..37d3914 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
@@ -3,7 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.bridgeremoval;
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
@@ -16,9 +16,9 @@
import com.android.tools.r8.jasmin.JasminBuilder;
import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import java.nio.file.Path;
import org.junit.Test;
@@ -129,7 +129,7 @@
AndroidApp processedApp = runAndVerifyOnJvmAndArt(jasminBuilder, mainClassName, proguardConfig);
- DexInspector inspector = new DexInspector(processedApp);
+ CodeInspector inspector = new CodeInspector(processedApp);
ClassSubject absSubject = inspector.clazz(absCls.name);
assertThat(absSubject, isPresent());
ClassSubject cls1Subject = inspector.clazz(cls1.name);
@@ -236,7 +236,7 @@
AndroidApp processedApp = runAndVerifyOnJvmAndArt(jasminBuilder, mainClassName, proguardConfig);
- DexInspector inspector = new DexInspector(processedApp);
+ CodeInspector inspector = new CodeInspector(processedApp);
ClassSubject baseSubject = inspector.clazz(baseCls.name);
assertThat(baseSubject, isPresent());
ClassSubject cls1Subject = inspector.clazz(cls1.name);
@@ -332,7 +332,7 @@
AndroidApp processedApp = runAndVerifyOnJvmAndArt(jasminBuilder, mainClassName, proguardConfig);
- DexInspector inspector = new DexInspector(processedApp);
+ CodeInspector inspector = new CodeInspector(processedApp);
ClassSubject baseSubject = inspector.clazz(baseCls.name);
assertThat(baseSubject, isPresent());
ClassSubject subSubject = inspector.clazz(subCls.name);
@@ -408,7 +408,7 @@
String proguardConfig = keepMainProguardConfiguration(mainClass.name, false, false);
AndroidApp processedApp = runAndVerifyOnJvmAndArt(jasminBuilder, mainClassName, proguardConfig);
- DexInspector inspector = new DexInspector(processedApp);
+ CodeInspector inspector = new CodeInspector(processedApp);
ClassSubject baseSubject = inspector.clazz(cls.name);
assertThat(baseSubject, isPresent());
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/EmptyBridgeTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/EmptyBridgeTest.java
index 160ad59..9c525b5 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/EmptyBridgeTest.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/EmptyBridgeTest.java
@@ -3,16 +3,16 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.bridgeremoval;
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.junit.Assert.assertThat;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.jasmin.JasminBuilder;
import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import org.junit.Test;
@@ -53,7 +53,7 @@
+ "-keep class " + absClassName + "{ *; }";
AndroidApp processedApp = compileWithR8(jasminBuilder.build(), proguardConfig);
- DexInspector inspector = new DexInspector(processedApp);
+ CodeInspector inspector = new CodeInspector(processedApp);
ClassSubject classSubject = inspector.clazz(absClassName);
assertThat(classSubject, isPresent());
MethodSubject methodSubject = classSubject.method("void", "emptyBridge", ImmutableList.of());
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java
index 60df7d2..c19b308 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java
@@ -4,7 +4,7 @@
package com.android.tools.r8.bridgeremoval;
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
@@ -17,9 +17,9 @@
import com.android.tools.r8.jasmin.JasminBuilder;
import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import java.lang.reflect.Method;
import java.nio.file.Path;
@@ -34,7 +34,7 @@
Outer.class,
Main.class);
String proguardConfig = keepMainProguardConfiguration(Main.class, true, obfuscate);
- DexInspector inspector = new DexInspector(compileWithR8(classes, proguardConfig));
+ CodeInspector inspector = new CodeInspector(compileWithR8(classes, proguardConfig));
List<Method> removedMethods = ImmutableList.of(
Outer.SubClass.class.getMethod("method"),
@@ -116,7 +116,7 @@
String artResult = runOnArt(optimizedApp, mainClassName);
assertEquals(javaResult.stdout, artResult);
- DexInspector inspector = new DexInspector(optimizedApp);
+ CodeInspector inspector = new CodeInspector(optimizedApp);
ClassSubject classSubject = inspector.clazz(superClass.name);
assertThat(classSubject, isPresent());
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/KeepNonVisibilityBridgeMethodsTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/KeepNonVisibilityBridgeMethodsTest.java
index 55ff3a2..4535b6f 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/KeepNonVisibilityBridgeMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/KeepNonVisibilityBridgeMethodsTest.java
@@ -7,8 +7,8 @@
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.TestBase;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import java.lang.reflect.Method;
import java.util.List;
@@ -32,7 +32,7 @@
SimpleObservableList.class,
Main.class);
String proguardConfig = keepMainAllowAccessModification(Main.class, obfuscate);
- DexInspector inspector = new DexInspector(compileWithR8(classes, proguardConfig));
+ CodeInspector inspector = new CodeInspector(compileWithR8(classes, proguardConfig));
// The bridge for registerObserver cannot be removed.
Method registerObserver =
diff --git a/src/test/java/com/android/tools/r8/cf/AlwaysNullGetItemTest.java b/src/test/java/com/android/tools/r8/cf/AlwaysNullGetItemTest.java
index 8a0a3b7..72d6fa4 100644
--- a/src/test/java/com/android/tools/r8/cf/AlwaysNullGetItemTest.java
+++ b/src/test/java/com/android/tools/r8/cf/AlwaysNullGetItemTest.java
@@ -4,9 +4,6 @@
package com.android.tools.r8.cf;
-import java.util.*;
-import org.objectweb.asm.*;
-
public class AlwaysNullGetItemTest {
public static void main(String[] args) {
try {
diff --git a/src/test/java/com/android/tools/r8/cf/BootstrapTest.java b/src/test/java/com/android/tools/r8/cf/BootstrapTest.java
index 7fdad0d..90b64900 100644
--- a/src/test/java/com/android/tools/r8/cf/BootstrapTest.java
+++ b/src/test/java/com/android/tools/r8/cf/BootstrapTest.java
@@ -102,7 +102,7 @@
ToolHelper.runR8(
R8Command.builder()
.setMode(mode)
- .addLibraryFiles(Paths.get(ToolHelper.JAVA_8_RUNTIME))
+ .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
.setProgramConsumer(new ClassFileConsumer.ArchiveConsumer(outputJar, true))
.addProgramFiles(inputJar)
.addProguardConfigurationFiles(pgConfigFile)
diff --git a/src/test/java/com/android/tools/r8/cf/IdenticalCatchHandlerTest.java b/src/test/java/com/android/tools/r8/cf/IdenticalCatchHandlerTest.java
index fd696bc..450e6d0 100644
--- a/src/test/java/com/android/tools/r8/cf/IdenticalCatchHandlerTest.java
+++ b/src/test/java/com/android/tools/r8/cf/IdenticalCatchHandlerTest.java
@@ -19,11 +19,10 @@
import com.android.tools.r8.graph.DexCode.Try;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.Sets;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
-import java.nio.file.Paths;
import java.util.Set;
import org.junit.Test;
@@ -52,7 +51,7 @@
AndroidApp inputApp =
AndroidApp.builder()
.addClassProgramData(inputBytes, Origin.unknown())
- .addLibraryFiles(Paths.get(ToolHelper.JAVA_8_RUNTIME))
+ .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
.build();
assertEquals(2, countCatchHandlers(inputApp));
AndroidApp outputDexApp = ToolHelper.runR8(inputApp);
@@ -64,7 +63,7 @@
}
private int countCatchHandlers(AndroidApp inputApp) throws Exception {
- DexInspector inspector = new DexInspector(inputApp, o -> o.enableCfFrontend = true);
+ CodeInspector inspector = new CodeInspector(inputApp, o -> o.enableCfFrontend = true);
DexClass dexClass = inspector.clazz(TestClass.class).getDexClass();
Code code = dexClass.virtualMethods()[0].getCode();
if (code.isCfCode()) {
diff --git a/src/test/java/com/android/tools/r8/cf/LambdaTestRunner.java b/src/test/java/com/android/tools/r8/cf/LambdaTestRunner.java
index 6c9d7b6..7db1d61 100644
--- a/src/test/java/com/android/tools/r8/cf/LambdaTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/LambdaTestRunner.java
@@ -18,9 +18,8 @@
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.AndroidAppConsumers;
-import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import java.nio.file.Path;
-import java.nio.file.Paths;
import java.util.Collections;
import java.util.List;
import org.junit.Assert;
@@ -52,7 +51,7 @@
R8.run(
R8Command.builder()
.setMode(CompilationMode.DEBUG)
- .addLibraryFiles(Paths.get(ToolHelper.JAVA_8_RUNTIME))
+ .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
.setProgramConsumer(appBuilder.wrapClassFileConsumer(new ArchiveConsumer(outPath)))
.addClassProgramData(inputClass, Origin.unknown())
.build());
@@ -70,7 +69,7 @@
private static CfInvokeDynamic findFirstInMethod(AndroidApp app) throws Exception {
String returnType = "void";
- DexInspector inspector = new DexInspector(app, o -> o.enableCfFrontend = true);
+ CodeInspector inspector = new CodeInspector(app, o -> o.enableCfFrontend = true);
List<String> args = Collections.singletonList(String[].class.getTypeName());
DexEncodedMethod method = inspector.clazz(CLASS).method(returnType, METHOD, args).getMethod();
CfCode code = method.getCode().asCfCode();
diff --git a/src/test/java/com/android/tools/r8/cf/MethodHandleDump.java b/src/test/java/com/android/tools/r8/cf/MethodHandleDump.java
index a213a91..9adf616 100644
--- a/src/test/java/com/android/tools/r8/cf/MethodHandleDump.java
+++ b/src/test/java/com/android/tools/r8/cf/MethodHandleDump.java
@@ -6,7 +6,13 @@
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
-import org.objectweb.asm.*;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
// The method MethodHandleDump.transform() translates methods in MethodHandleTest that look like
// MethodType.methodType(TYPES)
diff --git a/src/test/java/com/android/tools/r8/cf/NestedExceptionTestRunner.java b/src/test/java/com/android/tools/r8/cf/NestedExceptionTestRunner.java
index c0a5455..a60eda9 100644
--- a/src/test/java/com/android/tools/r8/cf/NestedExceptionTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/NestedExceptionTestRunner.java
@@ -29,7 +29,7 @@
R8Command.builder()
.setMode(CompilationMode.DEBUG)
.addClassProgramData(ToolHelper.getClassAsBytes(CLASS), Origin.unknown())
- .addLibraryFiles(ToolHelper.getAndroidJar(ToolHelper.getMinApiLevelForDexVm()))
+ .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
.setProgramConsumer(sink.wrapClassFileConsumer(null))
.build());
Path out = temp.newFolder().toPath().resolve("test.jar");
diff --git a/src/test/java/com/android/tools/r8/cf/SynchronizedNoopTestRunner.java b/src/test/java/com/android/tools/r8/cf/SynchronizedNoopTestRunner.java
index 7565dee..e4f9249 100644
--- a/src/test/java/com/android/tools/r8/cf/SynchronizedNoopTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/SynchronizedNoopTestRunner.java
@@ -15,7 +15,7 @@
import com.android.tools.r8.graph.JarCode;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidAppConsumers;
-import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import java.util.ArrayList;
import java.util.Collections;
import org.junit.Test;
@@ -37,7 +37,7 @@
.addLibraryFiles(ToolHelper.getAndroidJar(ToolHelper.getMinApiLevelForDexVm()))
.setProgramConsumer(a.wrapClassFileConsumer(null))
.build());
- DexInspector inspector = new DexInspector(a.build());
+ CodeInspector inspector = new CodeInspector(a.build());
DexEncodedMethod method =
inspector.clazz(CLASS).method("void", "noop", Collections.emptyList()).getMethod();
ArrayList<AbstractInsnNode> insns = new ArrayList<>();
diff --git a/src/test/java/com/android/tools/r8/cf/UninitializedInFrameDump.java b/src/test/java/com/android/tools/r8/cf/UninitializedInFrameDump.java
index bac896e..4f3f615 100644
--- a/src/test/java/com/android/tools/r8/cf/UninitializedInFrameDump.java
+++ b/src/test/java/com/android/tools/r8/cf/UninitializedInFrameDump.java
@@ -4,8 +4,12 @@
package com.android.tools.r8.cf;
-import java.util.*;
-import org.objectweb.asm.*;
+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;
public class UninitializedInFrameDump implements Opcodes {
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 b6489b0..6322840 100644
--- a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
@@ -3,9 +3,11 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.classmerging;
+import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX;
import static com.android.tools.r8.smali.SmaliBuilder.buildCode;
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
@@ -14,6 +16,7 @@
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.OutputMode;
import com.android.tools.r8.R8Command;
+import com.android.tools.r8.StringConsumer.FileConsumer;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.DexVm.Version;
@@ -21,6 +24,10 @@
import com.android.tools.r8.VmTestRunner.IgnoreForRangeOfVmVersions;
import com.android.tools.r8.code.Instruction;
import com.android.tools.r8.code.MoveException;
+import com.android.tools.r8.debug.DebugTestBase;
+import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.Command;
+import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.DebuggeeState;
+import com.android.tools.r8.debug.DexDebugTestConfig;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.ir.optimize.Inliner.Reason;
import com.android.tools.r8.jasmin.JasminBuilder;
@@ -29,13 +36,16 @@
import com.android.tools.r8.smali.SmaliBuilder;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.FoundClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.base.Joiner;
+import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
+import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -70,6 +80,7 @@
options.enableClassMerging = true;
options.enableClassInlining = false;
options.enableMinification = false;
+ options.testing.nondeterministicCycleElimination = true;
}
private void runR8(Path proguardConfig, Consumer<InternalOptions> optionsConsumer)
@@ -82,11 +93,11 @@
.setDisableMinification(true)
.build(),
optionsConsumer);
- inspector = new DexInspector(
+ inspector = new CodeInspector(
Paths.get(temp.getRoot().getCanonicalPath()).resolve("classes.dex"));
}
- private DexInspector inspector;
+ private CodeInspector inspector;
private final List<String> CAN_BE_MERGED = ImmutableList.of(
"classmerging.GenericInterface",
@@ -96,7 +107,7 @@
);
@Test
- public void testClassesHaveBeenMerged() throws Exception {
+ public void testClassesHaveBeenMerged() throws Throwable {
runR8(EXAMPLE_KEEP, this::configure);
// GenericInterface should be merged into GenericInterfaceImpl.
for (String candidate : CAN_BE_MERGED) {
@@ -108,15 +119,38 @@
}
@Test
- public void testClassesHaveNotBeenMerged() throws Exception {
+ public void testClassesHaveNotBeenMerged() throws Throwable {
runR8(DONT_OPTIMIZE, null);
for (String candidate : CAN_BE_MERGED) {
assertTrue(inspector.clazz(candidate).isPresent());
}
}
+ // This test has a cycle in the call graph consisting of the methods A.<init> and B.<init>.
+ // When nondeterministicCycleElimination is enabled, we shuffle the nodes in the call graph
+ // before the cycle elimination. Therefore, it is nondeterministic if the cycle detector will
+ // detect the cycle upon the call edge A.<init> to B.<init>, or B.<init> to A.<init>. In order
+ // increase the likelihood that this test encounters both orderings, the test is repeated 5 times.
+ // Assuming that the chance of observing one of the two orderings is 50%, this test therefore has
+ // approximately 3% chance of succeeding although there is an issue.
@Test
- public void testConflictInGeneratedName() throws Exception {
+ public void testCallGraphCycle() throws Throwable {
+ String main = "classmerging.CallGraphCycleTest";
+ Path[] programFiles =
+ new Path[] {
+ CF_DIR.resolve("CallGraphCycleTest.class"),
+ CF_DIR.resolve("CallGraphCycleTest$A.class"),
+ CF_DIR.resolve("CallGraphCycleTest$B.class")
+ };
+ Set<String> preservedClassNames =
+ ImmutableSet.of("classmerging.CallGraphCycleTest", "classmerging.CallGraphCycleTest$B");
+ for (int i = 0; i < 5; i++) {
+ runTest(main, programFiles, preservedClassNames::contains);
+ }
+ }
+
+ @Test
+ public void testConflictInGeneratedName() throws Throwable {
String main = "classmerging.ConflictInGeneratedNameTest";
Path[] programFiles =
new Path[] {
@@ -128,7 +162,7 @@
ImmutableSet.of(
"classmerging.ConflictInGeneratedNameTest",
"classmerging.ConflictInGeneratedNameTest$B");
- DexInspector inspector =
+ CodeInspector inspector =
runTestOnInput(
main,
readProgramFiles(programFiles),
@@ -138,7 +172,9 @@
configure(options);
// Avoid that direct methods in B get inlined.
options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
- });
+ },
+ // Disable debug testing since the test has a method with "$classmerging$" in the name.
+ null);
ClassSubject clazzSubject = inspector.clazz("classmerging.ConflictInGeneratedNameTest$B");
assertThat(clazzSubject, isPresent());
@@ -180,14 +216,14 @@
}
@Test
- public void testConflictWasDetected() throws Exception {
+ public void testConflictWasDetected() throws Throwable {
runR8(EXAMPLE_KEEP, this::configure);
assertTrue(inspector.clazz("classmerging.ConflictingInterface").isPresent());
assertTrue(inspector.clazz("classmerging.ConflictingInterfaceImpl").isPresent());
}
@Test
- public void testFieldCollision() throws Exception {
+ public void testFieldCollision() throws Throwable {
String main = "classmerging.FieldCollisionTest";
Path[] programFiles =
new Path[] {
@@ -203,7 +239,7 @@
}
@Test
- public void testLambdaRewriting() throws Exception {
+ public void testLambdaRewriting() throws Throwable {
String main = "classmerging.LambdaRewritingTest";
Path[] programFiles =
new Path[] {
@@ -226,7 +262,7 @@
}
@Test
- public void testMethodCollision() throws Exception {
+ public void testMethodCollision() throws Throwable {
String main = "classmerging.MethodCollisionTest";
Path[] programFiles =
new Path[] {
@@ -251,7 +287,7 @@
}
@Test
- public void testNestedDefaultInterfaceMethodsTest() throws Exception {
+ public void testNestedDefaultInterfaceMethodsTest() throws Throwable {
String main = "classmerging.NestedDefaultInterfaceMethodsTest";
Path[] programFiles =
new Path[] {
@@ -270,7 +306,7 @@
}
@Test
- public void testNestedDefaultInterfaceMethodsWithCDumpTest() throws Exception {
+ public void testNestedDefaultInterfaceMethodsWithCDumpTest() throws Throwable {
String main = "classmerging.NestedDefaultInterfaceMethodsTest";
Path[] programFiles =
new Path[] {
@@ -295,7 +331,7 @@
}
@Test
- public void testPinnedParameterTypes() throws Exception {
+ public void testPinnedParameterTypes() throws Throwable {
String main = "classmerging.PinnedParameterTypesTest";
Path[] programFiles =
new Path[] {
@@ -318,7 +354,210 @@
}
@Test
- public void testSuperCallWasDetected() throws Exception {
+ public void testProguardFieldMap() throws Throwable {
+ String main = "classmerging.ProguardFieldMapTest";
+ Path[] programFiles =
+ new Path[] {
+ CF_DIR.resolve("ProguardFieldMapTest.class"),
+ CF_DIR.resolve("ProguardFieldMapTest$A.class"),
+ CF_DIR.resolve("ProguardFieldMapTest$B.class")
+ };
+
+ // Try without vertical class merging, to check that output is as expected.
+ String expectedProguardMapWithoutClassMerging =
+ Joiner.on(System.lineSeparator())
+ .join(
+ "classmerging.ProguardFieldMapTest -> classmerging.ProguardFieldMapTest:",
+ " 1:2:void main(java.lang.String[]):10:11 -> main",
+ "classmerging.ProguardFieldMapTest$A -> classmerging.ProguardFieldMapTest$A:",
+ " 1:1:void <init>():14:14 -> <init>",
+ " 2:2:void <init>():16:16 -> <init>",
+ "classmerging.ProguardFieldMapTest$B -> classmerging.ProguardFieldMapTest$B:",
+ " 1:1:void <init>():19:19 -> <init>",
+ " 1:1:void test():22:22 -> test");
+ runTestOnInput(
+ main,
+ readProgramFiles(programFiles),
+ Predicates.alwaysTrue(),
+ getProguardConfig(EXAMPLE_KEEP),
+ options -> {
+ configure(options);
+ options.enableClassMerging = false;
+ options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
+ options.proguardMapConsumer =
+ (proguardMap, handler) ->
+ assertThat(proguardMap, containsString(expectedProguardMapWithoutClassMerging));
+ });
+
+ // Try with vertical class merging.
+ String expectedProguardMapWithClassMerging =
+ Joiner.on(System.lineSeparator())
+ .join(
+ "classmerging.ProguardFieldMapTest -> classmerging.ProguardFieldMapTest:",
+ " 1:2:void main(java.lang.String[]):10:11 -> main",
+ "classmerging.ProguardFieldMapTest$B -> classmerging.ProguardFieldMapTest$B:",
+ " java.lang.String classmerging.ProguardFieldMapTest$A.f -> f",
+ " 1:1:void classmerging.ProguardFieldMapTest$A.<init>():14:14 -> <init>",
+ " 1:1:void <init>():19 -> <init>",
+ " 2:2:void classmerging.ProguardFieldMapTest$A.<init>():16:16 -> <init>",
+ " 2:2:void <init>():19 -> <init>",
+ " 1:1:void test():22:22 -> test");
+ Set<String> preservedClassNames =
+ ImmutableSet.of("classmerging.ProguardFieldMapTest", "classmerging.ProguardFieldMapTest$B");
+ runTestOnInput(
+ main,
+ readProgramFiles(programFiles),
+ preservedClassNames::contains,
+ getProguardConfig(EXAMPLE_KEEP),
+ options -> {
+ configure(options);
+ options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
+ options.proguardMapConsumer =
+ (proguardMap, handler) ->
+ assertThat(proguardMap, containsString(expectedProguardMapWithClassMerging));
+ });
+ }
+
+ @Test
+ public void testProguardMethodMap() throws Throwable {
+ String main = "classmerging.ProguardMethodMapTest";
+ Path[] programFiles =
+ new Path[] {
+ CF_DIR.resolve("ProguardMethodMapTest.class"),
+ CF_DIR.resolve("ProguardMethodMapTest$A.class"),
+ CF_DIR.resolve("ProguardMethodMapTest$B.class")
+ };
+
+ // Try without vertical class merging, to check that output is as expected.
+ String expectedProguardMapWithoutClassMerging =
+ Joiner.on(System.lineSeparator())
+ .join(
+ "classmerging.ProguardMethodMapTest -> classmerging.ProguardMethodMapTest:",
+ " 1:2:void main(java.lang.String[]):10:11 -> main",
+ "classmerging.ProguardMethodMapTest$A -> classmerging.ProguardMethodMapTest$A:",
+ " 1:1:void <init>():14:14 -> <init>",
+ " 1:1:void method():17:17 -> method",
+ "classmerging.ProguardMethodMapTest$B -> classmerging.ProguardMethodMapTest$B:",
+ " 1:1:void <init>():21:21 -> <init>",
+ " 1:2:void method():25:26 -> method");
+ runTestOnInput(
+ main,
+ readProgramFiles(programFiles),
+ Predicates.alwaysTrue(),
+ getProguardConfig(EXAMPLE_KEEP),
+ options -> {
+ configure(options);
+ options.enableClassMerging = false;
+ options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
+ options.proguardMapConsumer =
+ (proguardMap, handler) ->
+ assertThat(proguardMap, containsString(expectedProguardMapWithoutClassMerging));
+ });
+
+ // Try with vertical class merging.
+ String expectedProguardMapWithClassMerging =
+ Joiner.on(System.lineSeparator())
+ .join(
+ "classmerging.ProguardMethodMapTest -> classmerging.ProguardMethodMapTest:",
+ " 1:2:void main(java.lang.String[]):10:11 -> main",
+ "classmerging.ProguardMethodMapTest$B -> classmerging.ProguardMethodMapTest$B:",
+ // TODO(christofferqa): Should this be "...<init>():14 -> <init>" to reflect that
+ // A.<init> has been inlined into B.<init>?
+ " 1:1:void classmerging.ProguardMethodMapTest$A.<init>():14:14 -> <init>",
+ // TODO(christofferqa): Should this be " ...<init>():21:21 -> <init>"?
+ " 1:1:void <init>():21 -> <init>",
+ " 1:2:void method():25:26 -> method",
+ " 1:1:void classmerging.ProguardMethodMapTest$A.method():17:17 -> "
+ + "method$classmerging$ProguardMethodMapTest$A");
+ Set<String> preservedClassNames =
+ ImmutableSet.of(
+ "classmerging.ProguardMethodMapTest", "classmerging.ProguardMethodMapTest$B");
+ runTestOnInput(
+ main,
+ readProgramFiles(programFiles),
+ preservedClassNames::contains,
+ getProguardConfig(EXAMPLE_KEEP),
+ options -> {
+ configure(options);
+ options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
+ options.proguardMapConsumer =
+ (proguardMap, handler) ->
+ assertThat(proguardMap, containsString(expectedProguardMapWithClassMerging));
+ });
+ }
+
+ @Test
+ public void testProguardMethodMapAfterInlining() throws Throwable {
+ String main = "classmerging.ProguardMethodMapTest";
+ Path[] programFiles =
+ new Path[] {
+ CF_DIR.resolve("ProguardMethodMapTest.class"),
+ CF_DIR.resolve("ProguardMethodMapTest$A.class"),
+ CF_DIR.resolve("ProguardMethodMapTest$B.class")
+ };
+
+ // Try without vertical class merging, to check that output is as expected.
+ String expectedProguardMapWithoutClassMerging =
+ Joiner.on(System.lineSeparator())
+ .join(
+ "classmerging.ProguardMethodMapTest -> classmerging.ProguardMethodMapTest:",
+ " 1:2:void main(java.lang.String[]):10:11 -> main",
+ "classmerging.ProguardMethodMapTest$A -> classmerging.ProguardMethodMapTest$A:",
+ " 1:1:void <init>():14:14 -> <init>",
+ "classmerging.ProguardMethodMapTest$B -> classmerging.ProguardMethodMapTest$B:",
+ " 1:1:void <init>():21:21 -> <init>",
+ " 1:1:void method():25:25 -> method",
+ " 2:2:void classmerging.ProguardMethodMapTest$A.method():17:17 -> method",
+ " 2:2:void method():26 -> method");
+ runTestOnInput(
+ main,
+ readProgramFiles(programFiles),
+ Predicates.alwaysTrue(),
+ getProguardConfig(
+ EXAMPLE_KEEP,
+ "-forceinline class classmerging.ProguardMethodMapTest$A { public void method(); }"),
+ options -> {
+ configure(options);
+ options.enableClassMerging = false;
+ options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
+ options.proguardMapConsumer =
+ (proguardMap, handler) ->
+ assertThat(proguardMap, containsString(expectedProguardMapWithoutClassMerging));
+ });
+
+ // Try with vertical class merging.
+ String expectedProguardMapWithClassMerging =
+ Joiner.on(System.lineSeparator())
+ .join(
+ "classmerging.ProguardMethodMapTest -> classmerging.ProguardMethodMapTest:",
+ " 1:2:void main(java.lang.String[]):10:11 -> main",
+ "classmerging.ProguardMethodMapTest$B -> classmerging.ProguardMethodMapTest$B:",
+ " 1:1:void classmerging.ProguardMethodMapTest$A.<init>():14:14 -> <init>",
+ " 1:1:void <init>():21 -> <init>",
+ " 1:1:void method():25:25 -> method",
+ " 2:2:void classmerging.ProguardMethodMapTest$A.method():17:17 -> method",
+ " 2:2:void method():26 -> method");
+ Set<String> preservedClassNames =
+ ImmutableSet.of(
+ "classmerging.ProguardMethodMapTest", "classmerging.ProguardMethodMapTest$B");
+ runTestOnInput(
+ main,
+ readProgramFiles(programFiles),
+ preservedClassNames::contains,
+ getProguardConfig(
+ EXAMPLE_KEEP,
+ "-forceinline class classmerging.ProguardMethodMapTest$A { public void method(); }"),
+ options -> {
+ configure(options);
+ options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
+ options.proguardMapConsumer =
+ (proguardMap, handler) ->
+ assertThat(proguardMap, containsString(expectedProguardMapWithClassMerging));
+ });
+ }
+
+ @Test
+ public void testSuperCallWasDetected() throws Throwable {
String main = "classmerging.SuperCallRewritingTest";
Path[] programFiles =
new Path[] {
@@ -342,7 +581,7 @@
// targets SubClassThatReferencesSuperMethod.referencedMethod. When running without class
// merging, R8 should not rewrite the invoke-super instruction into invoke-direct.
@Test
- public void testSuperCallNotRewrittenToDirect() throws Exception {
+ public void testSuperCallNotRewrittenToDirect() throws Throwable {
String main = "classmerging.SuperCallRewritingTest";
Path[] programFiles =
new Path[] {
@@ -422,7 +661,7 @@
// }
@Test
@IgnoreForRangeOfVmVersions(from = Version.V5_1_1, to = Version.V6_0_1)
- public void testSuperCallToMergedClassIsRewritten() throws Exception {
+ public void testSuperCallToMergedClassIsRewritten() throws Throwable {
String main = "classmerging.SuperCallToMergedClassIsRewrittenTest";
Set<String> preservedClassNames =
ImmutableSet.of(
@@ -545,7 +784,7 @@
}
@Test
- public void testConflictingInterfaceSignatures() throws Exception {
+ public void testConflictingInterfaceSignatures() throws Throwable {
String main = "classmerging.ConflictingInterfaceSignaturesTest";
Path[] programFiles =
new Path[] {
@@ -564,7 +803,7 @@
// If an exception class A is merged into another exception class B, then all exception tables
// should be updated, and class A should be removed entirely.
@Test
- public void testExceptionTables() throws Exception {
+ public void testExceptionTables() throws Throwable {
String main = "classmerging.ExceptionTest";
Path[] programFiles =
new Path[] {
@@ -579,7 +818,7 @@
"classmerging.ExceptionTest",
"classmerging.ExceptionTest$ExceptionB",
"classmerging.ExceptionTest$Exception2");
- DexInspector inspector = runTest(main, programFiles, preservedClassNames::contains);
+ CodeInspector inspector = runTest(main, programFiles, preservedClassNames::contains);
ClassSubject mainClass = inspector.clazz(main);
assertThat(mainClass, isPresent());
@@ -600,7 +839,7 @@
}
@Test
- public void testMergeDefaultMethodIntoClass() throws Exception {
+ public void testMergeDefaultMethodIntoClass() throws Throwable {
String main = "classmerging.MergeDefaultMethodIntoClassTest";
Path[] programFiles =
new Path[] {
@@ -621,7 +860,7 @@
// Sanity check that there is actually an invoke-interface instruction in the input. We need
// to make sure that this invoke-interface instruction is translated to invoke-virtual after
// the classes A and B are merged.
- DexInspector inputInspector = new DexInspector(app);
+ CodeInspector inputInspector = new CodeInspector(app);
ClassSubject clazz = inputInspector.clazz("classmerging.MergeDefaultMethodIntoClassTest");
assertThat(clazz, isPresent());
MethodSubject method = clazz.method("void", "main", ImmutableList.of("java.lang.String[]"));
@@ -634,7 +873,7 @@
}
@Test
- public void testNativeMethod() throws Exception {
+ public void testNativeMethod() throws Throwable {
String main = "classmerging.ClassWithNativeMethodTest";
Path[] programFiles =
new Path[] {
@@ -652,7 +891,7 @@
}
@Test
- public void testNoIllegalClassAccess() throws Exception {
+ public void testNoIllegalClassAccess() throws Throwable {
String main = "classmerging.SimpleInterfaceAccessTest";
Path[] programFiles =
new Path[] {
@@ -677,7 +916,7 @@
}
@Test
- public void testNoIllegalClassAccessWithAccessModifications() throws Exception {
+ public void testNoIllegalClassAccessWithAccessModifications() throws Throwable {
// If access modifications are allowed then SimpleInterface should be merged into
// SimpleInterfaceImpl.
String main = "classmerging.SimpleInterfaceAccessTest";
@@ -712,7 +951,7 @@
// into an invoke-direct instruction after it gets merged into class C. We should add a test that
// checks that this works with and without inlining of the method B.m().
@Test
- public void testRewritePinnedMethod() throws Exception {
+ public void testRewritePinnedMethod() throws Throwable {
String main = "classmerging.RewritePinnedMethodTest";
Path[] programFiles =
new Path[] {
@@ -735,7 +974,7 @@
}
@Test
- public void testTemplateMethodPattern() throws Exception {
+ public void testTemplateMethodPattern() throws Throwable {
String main = "classmerging.TemplateMethodTest";
Path[] programFiles =
new Path[] {
@@ -749,37 +988,66 @@
runTest(main, programFiles, preservedClassNames::contains);
}
- private DexInspector runTest(
- String main, Path[] programFiles, Predicate<String> preservedClassNames) throws Exception {
+ private CodeInspector runTest(
+ String main, Path[] programFiles, Predicate<String> preservedClassNames) throws Throwable {
return runTest(main, programFiles, preservedClassNames, getProguardConfig(EXAMPLE_KEEP));
}
- private DexInspector runTest(
+ private CodeInspector runTest(
String main,
Path[] programFiles,
Predicate<String> preservedClassNames,
String proguardConfig)
- throws Exception {
+ throws Throwable {
return runTestOnInput(
main, readProgramFiles(programFiles), preservedClassNames, proguardConfig);
}
- private DexInspector runTestOnInput(
+ private CodeInspector runTestOnInput(
String main, AndroidApp input, Predicate<String> preservedClassNames, String proguardConfig)
- throws Exception {
+ throws Throwable {
return runTestOnInput(main, input, preservedClassNames, proguardConfig, this::configure);
}
- private DexInspector runTestOnInput(
+ private CodeInspector runTestOnInput(
String main,
AndroidApp input,
Predicate<String> preservedClassNames,
String proguardConfig,
Consumer<InternalOptions> optionsConsumer)
- throws Exception {
- AndroidApp output = compileWithR8(input, proguardConfig, optionsConsumer);
- DexInspector inputInspector = new DexInspector(input);
- DexInspector outputInspector = new DexInspector(output);
+ throws Throwable {
+ return runTestOnInput(
+ main,
+ input,
+ preservedClassNames,
+ proguardConfig,
+ optionsConsumer,
+ new VerticalClassMergerDebugTest(main));
+ }
+
+ private CodeInspector runTestOnInput(
+ String main,
+ AndroidApp input,
+ Predicate<String> preservedClassNames,
+ String proguardConfig,
+ Consumer<InternalOptions> optionsConsumer,
+ VerticalClassMergerDebugTest debugTestRunner)
+ throws Throwable {
+ Path proguardMapPath = File.createTempFile("mapping", ".txt", temp.getRoot()).toPath();
+ R8Command.Builder commandBuilder =
+ ToolHelper.prepareR8CommandBuilder(input)
+ .addProguardConfiguration(ImmutableList.of(proguardConfig), Origin.unknown());
+ ToolHelper.allowTestProguardOptions(commandBuilder);
+ AndroidApp output =
+ ToolHelper.runR8(
+ commandBuilder.build(),
+ options -> {
+ optionsConsumer.accept(options);
+ options.proguardMapConsumer =
+ new FileConsumer(proguardMapPath, options.proguardMapConsumer);
+ });
+ CodeInspector inputInspector = new CodeInspector(input);
+ CodeInspector outputInspector = new CodeInspector(output);
// Check that all classes in [preservedClassNames] are in fact preserved.
for (FoundClassSubject classSubject : inputInspector.allClasses()) {
String className = classSubject.getOriginalName();
@@ -791,6 +1059,11 @@
}
// Check that the R8-generated code produces the same result as D8-generated code.
assertEquals(runOnArt(compileWithD8(input), main), runOnArt(output, main));
+ // Check that we never come across a method that has a name with "$classmerging$" in it during
+ // debugging.
+ if (debugTestRunner != null) {
+ debugTestRunner.test(output, proguardMapPath);
+ }
return outputInspector;
}
@@ -806,4 +1079,82 @@
}
return builder.toString();
}
+
+ private class VerticalClassMergerDebugTest extends DebugTestBase {
+
+ private final String main;
+ private DebugTestRunner runner = null;
+
+ public VerticalClassMergerDebugTest(String main) {
+ this.main = main;
+ }
+
+ public void test(AndroidApp app, Path proguardMapPath) throws Throwable {
+ Path appPath =
+ File.createTempFile("app", ".zip", ClassMergingTest.this.temp.getRoot()).toPath();
+ app.writeToZip(appPath, OutputMode.DexIndexed);
+
+ DexDebugTestConfig config = new DexDebugTestConfig(appPath);
+ config.allowUnprocessedCommands();
+ config.setProguardMap(proguardMapPath);
+
+ this.runner =
+ getDebugTestRunner(
+ config, main, breakpoint(main, "main"), run(), stepIntoUntilNoLongerInApp());
+ this.runner.runBare();
+ }
+
+ private void checkState(DebuggeeState state) {
+ // If a class pkg.A is merged into pkg.B, and a method pkg.A.m() needs to be renamed, then
+ // it will be renamed to pkg.B.m$pkg$A(). Since all tests are in the package "classmerging",
+ // we check that no methods in the debugging state (i.e., after the Proguard map has been
+ // applied) contain "$classmerging$.
+ String qualifiedMethodSignature =
+ state.getClassSignature() + "->" + state.getMethodName() + state.getMethodSignature();
+ boolean holderIsCompanionClass = state.getClassName().endsWith(COMPANION_CLASS_NAME_SUFFIX);
+ if (!holderIsCompanionClass) {
+ assertThat(qualifiedMethodSignature, not(containsString("$classmerging$")));
+ }
+ }
+
+ // Keeps stepping in until it is no longer in a class from the classmerging package.
+ // Then starts stepping out until it is again in the classmerging package.
+ private Command stepIntoUntilNoLongerInApp() {
+ return stepUntil(
+ StepKind.INTO,
+ StepLevel.INSTRUCTION,
+ state -> {
+ if (state.getClassSignature().contains("classmerging")) {
+ checkState(state);
+
+ // Continue stepping into.
+ return false;
+ }
+
+ // Stop stepping into.
+ runner.enqueueCommandFirst(stepOutUntilInApp());
+ return true;
+ });
+ }
+
+ // Keeps stepping out until it is in a class from the classmerging package.
+ // Then starts stepping in until it is no longer in the classmerging package.
+ private Command stepOutUntilInApp() {
+ return stepUntil(
+ StepKind.OUT,
+ StepLevel.INSTRUCTION,
+ state -> {
+ if (state.getClassSignature().contains("classmerging")) {
+ checkState(state);
+
+ // Stop stepping out.
+ runner.enqueueCommandFirst(stepIntoUntilNoLongerInApp());
+ return true;
+ }
+
+ // Continue stepping out.
+ return false;
+ });
+ }
+ }
}
diff --git a/src/test/java/com/android/tools/r8/code/NativeMethodWithCodeTest.java b/src/test/java/com/android/tools/r8/code/NativeMethodWithCodeTest.java
index 83e711c..5b4f1d4 100644
--- a/src/test/java/com/android/tools/r8/code/NativeMethodWithCodeTest.java
+++ b/src/test/java/com/android/tools/r8/code/NativeMethodWithCodeTest.java
@@ -3,7 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.code;
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
@@ -17,9 +17,9 @@
import com.android.tools.r8.jasmin.JasminBuilder;
import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.nio.file.Path;
@@ -102,7 +102,7 @@
private MethodSubject getNativeMethod(String mainClassName, AndroidApp processedApp)
throws IOException, ExecutionException {
- DexInspector inspector = new DexInspector(processedApp);
+ CodeInspector inspector = new CodeInspector(processedApp);
ClassSubject mainSubject = inspector.clazz(mainClassName);
return mainSubject.method("void", "n1", ImmutableList.of("java.lang.String"));
}
diff --git a/src/test/java/com/android/tools/r8/compatproguard/AtomicFieldUpdaterTest.java b/src/test/java/com/android/tools/r8/compatproguard/AtomicFieldUpdaterTest.java
index 502fa95..2521e02 100644
--- a/src/test/java/com/android/tools/r8/compatproguard/AtomicFieldUpdaterTest.java
+++ b/src/test/java/com/android/tools/r8/compatproguard/AtomicFieldUpdaterTest.java
@@ -12,9 +12,9 @@
import com.android.tools.r8.code.ReturnVoid;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.smali.SmaliBuilder;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import java.util.List;
import org.junit.Test;
@@ -44,11 +44,11 @@
keepMainProguardConfiguration(CLASS_NAME),
"-dontshrink",
"-dontoptimize");
- DexInspector inspector = runCompatProguard(builder, pgConfigs);
+ CodeInspector inspector = runCompatProguard(builder, pgConfigs);
ClassSubject clazz = inspector.clazz(CLASS_NAME);
assertTrue(clazz.isPresent());
- MethodSubject method = clazz.method(DexInspector.MAIN);
+ MethodSubject method = clazz.method(CodeInspector.MAIN);
assertTrue(method.isPresent());
DexCode code = method.getMethod().getCode().asDexCode();
@@ -80,11 +80,11 @@
keepMainProguardConfiguration(CLASS_NAME),
"-dontshrink",
"-dontoptimize");
- DexInspector inspector = runCompatProguard(builder, pgConfigs);
+ CodeInspector inspector = runCompatProguard(builder, pgConfigs);
ClassSubject clazz = inspector.clazz(CLASS_NAME);
assertTrue(clazz.isPresent());
- MethodSubject method = clazz.method(DexInspector.MAIN);
+ MethodSubject method = clazz.method(CodeInspector.MAIN);
assertTrue(method.isPresent());
DexCode code = method.getMethod().getCode().asDexCode();
@@ -117,11 +117,11 @@
keepMainProguardConfiguration(CLASS_NAME),
"-dontshrink",
"-dontoptimize");
- DexInspector inspector = runCompatProguard(builder, pgConfigs);
+ CodeInspector inspector = runCompatProguard(builder, pgConfigs);
ClassSubject clazz = inspector.clazz(CLASS_NAME);
assertTrue(clazz.isPresent());
- MethodSubject method = clazz.method(DexInspector.MAIN);
+ MethodSubject method = clazz.method(CodeInspector.MAIN);
assertTrue(method.isPresent());
DexCode code = method.getMethod().getCode().asDexCode();
diff --git a/src/test/java/com/android/tools/r8/compatproguard/CompatProguardSmaliTestBase.java b/src/test/java/com/android/tools/r8/compatproguard/CompatProguardSmaliTestBase.java
index 178337a..ea450e4 100644
--- a/src/test/java/com/android/tools/r8/compatproguard/CompatProguardSmaliTestBase.java
+++ b/src/test/java/com/android/tools/r8/compatproguard/CompatProguardSmaliTestBase.java
@@ -10,12 +10,12 @@
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.smali.SmaliBuilder;
import com.android.tools.r8.smali.SmaliTestBase;
-import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import java.nio.file.Path;
import java.util.List;
class CompatProguardSmaliTestBase extends SmaliTestBase {
- DexInspector runCompatProguard(SmaliBuilder builder, List<String> proguardConfigurations)
+ CodeInspector runCompatProguard(SmaliBuilder builder, List<String> proguardConfigurations)
throws Exception {
Path dexOutputDir = temp.newFolder().toPath();
R8Command.Builder commandBuilder =
@@ -23,6 +23,6 @@
.setOutput(dexOutputDir, OutputMode.DexIndexed)
.addProguardConfiguration(proguardConfigurations, Origin.unknown());
ToolHelper.getAppBuilder(commandBuilder).addDexProgramData(builder.compile(), Origin.unknown());
- return new DexInspector(ToolHelper.runR8(commandBuilder.build()));
+ return new CodeInspector(ToolHelper.runR8(commandBuilder.build()));
}
}
diff --git a/src/test/java/com/android/tools/r8/compatproguard/ForNameTest.java b/src/test/java/com/android/tools/r8/compatproguard/ForNameTest.java
index 5ef5830..6aa8805 100644
--- a/src/test/java/com/android/tools/r8/compatproguard/ForNameTest.java
+++ b/src/test/java/com/android/tools/r8/compatproguard/ForNameTest.java
@@ -12,9 +12,9 @@
import com.android.tools.r8.code.ReturnVoid;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.smali.SmaliBuilder;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import java.util.List;
import org.junit.Test;
@@ -39,11 +39,11 @@
keepMainProguardConfiguration(CLASS_NAME),
"-dontshrink",
"-dontoptimize");
- DexInspector inspector = runCompatProguard(builder, pgConfigs);
+ CodeInspector inspector = runCompatProguard(builder, pgConfigs);
ClassSubject clazz = inspector.clazz(CLASS_NAME);
assertTrue(clazz.isPresent());
- MethodSubject method = clazz.method(DexInspector.MAIN);
+ MethodSubject method = clazz.method(CodeInspector.MAIN);
assertTrue(method.isPresent());
DexCode code = method.getMethod().getCode().asDexCode();
@@ -70,11 +70,11 @@
"-dontshrink",
"-dontoptimize",
"-dontobfuscate");
- DexInspector inspector = runCompatProguard(builder, pgConfigs);
+ CodeInspector inspector = runCompatProguard(builder, pgConfigs);
ClassSubject clazz = inspector.clazz(CLASS_NAME);
assertTrue(clazz.isPresent());
- MethodSubject method = clazz.method(DexInspector.MAIN);
+ MethodSubject method = clazz.method(CodeInspector.MAIN);
assertTrue(method.isPresent());
DexCode code = method.getMethod().getCode().asDexCode();
diff --git a/src/test/java/com/android/tools/r8/compatproguard/GetMembersTest.java b/src/test/java/com/android/tools/r8/compatproguard/GetMembersTest.java
index 665919b..5771019 100644
--- a/src/test/java/com/android/tools/r8/compatproguard/GetMembersTest.java
+++ b/src/test/java/com/android/tools/r8/compatproguard/GetMembersTest.java
@@ -15,9 +15,9 @@
import com.android.tools.r8.code.ReturnVoid;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.smali.SmaliBuilder;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import java.util.List;
import org.junit.Test;
@@ -45,11 +45,11 @@
keepMainProguardConfiguration(CLASS_NAME),
"-dontshrink",
"-dontoptimize");
- DexInspector inspector = runCompatProguard(builder, pgConfigs);
+ CodeInspector inspector = runCompatProguard(builder, pgConfigs);
ClassSubject clazz = inspector.clazz(CLASS_NAME);
assertTrue(clazz.isPresent());
- MethodSubject method = clazz.method(DexInspector.MAIN);
+ MethodSubject method = clazz.method(CodeInspector.MAIN);
assertTrue(method.isPresent());
DexCode code = method.getMethod().getCode().asDexCode();
@@ -85,11 +85,11 @@
keepMainProguardConfiguration(CLASS_NAME),
"-dontshrink",
"-dontoptimize");
- DexInspector inspector = runCompatProguard(builder, pgConfigs);
+ CodeInspector inspector = runCompatProguard(builder, pgConfigs);
ClassSubject clazz = inspector.clazz(CLASS_NAME);
assertTrue(clazz.isPresent());
- MethodSubject method = clazz.method(DexInspector.MAIN);
+ MethodSubject method = clazz.method(CodeInspector.MAIN);
assertTrue(method.isPresent());
DexCode code = method.getMethod().getCode().asDexCode();
diff --git a/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTestDump.java b/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTestDump.java
index 5cd652d..8d79472 100644
--- a/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTestDump.java
+++ b/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTestDump.java
@@ -1,3 +1,7 @@
+// 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.debug;
import org.objectweb.asm.AnnotationVisitor;
diff --git a/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTestRunner.java b/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTestRunner.java
index 7866e1b..bcc70c6 100644
--- a/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTestRunner.java
@@ -36,6 +36,7 @@
R8Command.builder()
.addClassProgramData(ToolHelper.getClassAsBytes(CLASS), Origin.unknown())
.setMode(CompilationMode.DEBUG)
+ .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
.setOutput(cfOut, OutputMode.ClassFile)
.build(),
optionsConsumer);
diff --git a/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java b/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java
index f010357..29b0e17 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java
@@ -55,7 +55,9 @@
config = new DexDebugTestConfig(outjar);
} else {
assert (runtimeKind == RuntimeKind.CF);
- builder.setOutput(outjar, OutputMode.ClassFile);
+ builder
+ .setOutput(outjar, OutputMode.ClassFile)
+ .addLibraryFiles(ToolHelper.getJava8RuntimeJar());
config = new CfDebugTestConfig(outjar);
}
diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
index 5707812..e9ef5ed 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -137,6 +137,18 @@
private void runInternal(
DebugTestConfig config, String debuggeeClass, List<JUnit3Wrapper.Command> commands)
throws Throwable {
+ getDebugTestRunner(config, debuggeeClass, commands).runBare();
+ }
+
+ protected DebugTestRunner getDebugTestRunner(
+ DebugTestConfig config, String debuggeeClass, JUnit3Wrapper.Command... commands)
+ throws Throwable {
+ return getDebugTestRunner(config, debuggeeClass, Arrays.asList(commands));
+ }
+
+ protected DebugTestRunner getDebugTestRunner(
+ DebugTestConfig config, String debuggeeClass, List<JUnit3Wrapper.Command> commands)
+ throws Throwable {
// Skip test due to unsupported runtime.
Assume.assumeTrue("Skipping test " + testName.getMethodName() + " because ART is not supported",
ToolHelper.artSupported());
@@ -152,7 +164,7 @@
? null
: ClassNameMapper.mapperFromFile(config.getProguardMap());
- new JUnit3Wrapper(config, debuggeeClass, commands, classNameMapper).runBare();
+ return new JUnit3Wrapper(config, debuggeeClass, commands, classNameMapper);
}
/**
@@ -495,8 +507,14 @@
return t -> inspector.accept(t.debuggeeState.getLocalValues().get(localName));
}
+ protected interface DebugTestRunner {
+ void enqueueCommandFirst(JUnit3Wrapper.Command command);
+
+ void runBare() throws Throwable;
+ }
+
@Ignore("Prevents Gradle from running the wrapper as a test.")
- static class JUnit3Wrapper extends JDWPTestCase {
+ public static class JUnit3Wrapper extends JDWPTestCase implements DebugTestRunner {
private final String debuggeeClassName;
@@ -630,7 +648,7 @@
return null;
}
ClassNamingForNameMapper.MappedRangesOfName ranges =
- classNaming.mappedRangesByName.get(obfuscatedMethodName);
+ classNaming.mappedRangesByRenamedName.get(obfuscatedMethodName);
if (ranges == null) {
return null;
}
@@ -683,7 +701,7 @@
if (range == null) {
return obfuscatedLineNumber;
}
- return range.originalLineFromObfuscated(obfuscatedLineNumber);
+ return range.getOriginalLineNumber(obfuscatedLineNumber);
}
@Override
@@ -694,7 +712,7 @@
return null;
}
ClassNamingForNameMapper.MappedRangesOfName ranges =
- classNaming.mappedRangesByName.get(obfuscatedMethodName);
+ classNaming.mappedRangesByRenamedName.get(obfuscatedMethodName);
if (ranges == null) {
return null;
}
@@ -706,8 +724,7 @@
for (MappedRange range : mappedRanges) {
lines.add(
new SignatureAndLine(
- range.signature.toString(),
- range.originalLineFromObfuscated(obfuscatedLineNumber)));
+ range.signature.toString(), range.getOriginalLineNumber(obfuscatedLineNumber)));
}
return lines;
}
@@ -801,8 +818,10 @@
// Continue stepping until mainLoopStep exits with false.
}
- assertTrue(
- "All commands have NOT been processed for config: " + config, commandsQueue.isEmpty());
+ if (config.mustProcessAllCommands()) {
+ assertTrue(
+ "All commands have NOT been processed for config: " + config, commandsQueue.isEmpty());
+ }
logWriter.println("Finish loop");
}
diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestConfig.java b/src/test/java/com/android/tools/r8/debug/DebugTestConfig.java
index 556e17f..519b580 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestConfig.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestConfig.java
@@ -17,6 +17,7 @@
DEX
}
+ private boolean mustProcessAllCommands = true;
private List<Path> paths = new ArrayList<>();
private Path proguardMap = null;
@@ -47,6 +48,14 @@
return this;
}
+ public boolean mustProcessAllCommands() {
+ return mustProcessAllCommands;
+ }
+
+ public void allowUnprocessedCommands() {
+ mustProcessAllCommands = false;
+ }
+
/** Proguard map that the debuggee has been translated according to, null if not present. */
public Path getProguardMap() {
return proguardMap;
diff --git a/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java b/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java
index 6de20c6..6dccf0b 100644
--- a/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java
+++ b/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java
@@ -51,6 +51,7 @@
ToolHelper.runR8(
R8Command.builder()
.addProgramFiles(input)
+ .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
.setMode(CompilationMode.DEBUG)
.setOutput(output, OutputMode.ClassFile)
.build(),
diff --git a/src/test/java/com/android/tools/r8/debug/IincDebugTestDump.java b/src/test/java/com/android/tools/r8/debug/IincDebugTestDump.java
index 8ff3d92..670a3bc 100644
--- a/src/test/java/com/android/tools/r8/debug/IincDebugTestDump.java
+++ b/src/test/java/com/android/tools/r8/debug/IincDebugTestDump.java
@@ -4,7 +4,10 @@
package com.android.tools.r8.debug;
-import org.objectweb.asm.*;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
public class IincDebugTestDump implements Opcodes {
diff --git a/src/test/java/com/android/tools/r8/debug/IincDebugTestRunner.java b/src/test/java/com/android/tools/r8/debug/IincDebugTestRunner.java
index bc390b9..a275e6b 100644
--- a/src/test/java/com/android/tools/r8/debug/IincDebugTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debug/IincDebugTestRunner.java
@@ -17,7 +17,6 @@
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.DebuggeeState;
import java.nio.file.Path;
-import java.nio.file.Paths;
import java.util.stream.Stream;
import org.junit.Assume;
import org.junit.Test;
@@ -115,7 +114,7 @@
.setProgramConsumer(consumer)
.addProgramFiles(inputJar);
if ((consumer instanceof ClassFileConsumer)) {
- builder.addLibraryFiles(Paths.get(ToolHelper.JAVA_8_RUNTIME));
+ builder.addLibraryFiles(ToolHelper.getJava8RuntimeJar());
} else {
builder.addLibraryFiles(ToolHelper.getAndroidJar(ToolHelper.getMinApiLevelForDexVm()));
}
diff --git a/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java b/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java
index 6e1c05f..5cbe03d 100644
--- a/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java
+++ b/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java
@@ -60,7 +60,9 @@
DebugTestConfig config = null;
if (runtimeKind == RuntimeKind.CF) {
- builder.setOutput(outjar, OutputMode.ClassFile);
+ builder
+ .setOutput(outjar, OutputMode.ClassFile)
+ .addLibraryFiles(ToolHelper.getJava8RuntimeJar());
config = new CfDebugTestConfig(outjar);
} else {
assert (runtimeKind == RuntimeKind.DEX);
diff --git a/src/test/java/com/android/tools/r8/debug/LocalsLiveAtBlockEntryDebugTest.java b/src/test/java/com/android/tools/r8/debug/LocalsLiveAtBlockEntryDebugTest.java
index 36cdad0..09a46e5 100644
--- a/src/test/java/com/android/tools/r8/debug/LocalsLiveAtBlockEntryDebugTest.java
+++ b/src/test/java/com/android/tools/r8/debug/LocalsLiveAtBlockEntryDebugTest.java
@@ -7,9 +7,9 @@
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.jasmin.JasminBuilder;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import java.nio.file.Path;
import java.util.Collections;
@@ -53,8 +53,8 @@
}
private void runTest(DebugTestConfig config) throws Throwable {
- DexInspector inspector =
- new DexInspector(
+ CodeInspector inspector =
+ new CodeInspector(
(config instanceof CfDebugTestConfig)
? Collections.singletonList(config.getPaths().get(1).resolve(className + ".class"))
: config.getPaths());
diff --git a/src/test/java/com/android/tools/r8/debug/MinificationTest.java b/src/test/java/com/android/tools/r8/debug/MinificationTest.java
index 2f995db..1b51cd8 100644
--- a/src/test/java/com/android/tools/r8/debug/MinificationTest.java
+++ b/src/test/java/com/android/tools/r8/debug/MinificationTest.java
@@ -15,9 +15,9 @@
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.nio.file.Path;
import java.util.Collection;
@@ -80,7 +80,6 @@
proguardConfigurations = builder.build();
}
- AndroidApiLevel minSdk = ToolHelper.getMinApiLevelForDexVm();
Path outputPath = temp.getRoot().toPath().resolve("classes.zip");
Path proguardMap = writeProguardMap ? temp.getRoot().toPath().resolve("proguard.map") : null;
OutputMode outputMode =
@@ -89,10 +88,12 @@
R8Command.builder()
.addProgramFiles(DEBUGGEE_JAR)
.setOutput(outputPath, outputMode)
- .setMode(CompilationMode.DEBUG)
- .addLibraryFiles(ToolHelper.getAndroidJar(minSdk));
- if (runtimeKind != RuntimeKind.CF) {
- builder.setMinApiLevel(minSdk.getLevel());
+ .setMode(CompilationMode.DEBUG);
+ if (runtimeKind == RuntimeKind.DEX) {
+ AndroidApiLevel minSdk = ToolHelper.getMinApiLevelForDexVm();
+ builder.setMinApiLevel(minSdk.getLevel()).addLibraryFiles(ToolHelper.getAndroidJar(minSdk));
+ } else if (runtimeKind == RuntimeKind.CF) {
+ builder.addLibraryFiles(ToolHelper.getJava8RuntimeJar());
}
if (proguardMap != null) {
builder.setProguardMapOutputPath(proguardMap);
@@ -187,7 +188,7 @@
throws Throwable {
Path proguardMap = config.getProguardMap();
String mappingFile = proguardMap == null ? null : proguardMap.toString();
- DexInspector inspector = new DexInspector(config.getPaths(), mappingFile);
+ CodeInspector inspector = new CodeInspector(config.getPaths(), mappingFile);
ClassSubject clazz = inspector.clazz(className);
assertTrue(clazz.isPresent());
if (method != null) {
diff --git a/src/test/java/com/android/tools/r8/debug/NopDebugTestRunner.java b/src/test/java/com/android/tools/r8/debug/NopDebugTestRunner.java
index 3ea4017..813a873 100644
--- a/src/test/java/com/android/tools/r8/debug/NopDebugTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debug/NopDebugTestRunner.java
@@ -11,7 +11,6 @@
import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.DebuggeeState;
import com.android.tools.r8.utils.DescriptorUtils;
import java.nio.file.Path;
-import java.nio.file.Paths;
import java.util.stream.Stream;
import org.junit.Assume;
import org.junit.Test;
@@ -47,7 +46,7 @@
Path outputJar = temp.getRoot().toPath().resolve(outputName);
ToolHelper.runR8(
R8Command.builder()
- .addLibraryFiles(Paths.get(ToolHelper.JAVA_8_RUNTIME))
+ .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
.setMode(CompilationMode.DEBUG)
.addProgramFiles(inputJar)
.setOutput(outputJar, OutputMode.ClassFile)
diff --git a/src/test/java/com/android/tools/r8/debug/R8CfDebugTestResourcesConfig.java b/src/test/java/com/android/tools/r8/debug/R8CfDebugTestResourcesConfig.java
index e4a88f0..67b7c1a 100644
--- a/src/test/java/com/android/tools/r8/debug/R8CfDebugTestResourcesConfig.java
+++ b/src/test/java/com/android/tools/r8/debug/R8CfDebugTestResourcesConfig.java
@@ -8,7 +8,6 @@
import com.android.tools.r8.R8;
import com.android.tools.r8.R8Command;
import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.AndroidAppConsumers;
import java.nio.file.Path;
@@ -21,14 +20,13 @@
private static synchronized AndroidApp getCompiledResources() throws Throwable {
if (compiledResources == null) {
- AndroidApiLevel minApi = ToolHelper.getMinApiLevelForDexVm();
AndroidAppConsumers sink = new AndroidAppConsumers();
R8.run(
R8Command.builder()
.setMode(CompilationMode.DEBUG)
.addProgramFiles(DebugTestBase.DEBUGGEE_JAR)
.setProgramConsumer(sink.wrapClassFileConsumer(null))
- .addLibraryFiles(ToolHelper.getAndroidJar(minApi))
+ .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
.build());
compiledResources = sink.build();
}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/DebugInfoInspector.java b/src/test/java/com/android/tools/r8/debuginfo/DebugInfoInspector.java
index 4ce3b5a..3ccf46d 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/DebugInfoInspector.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/DebugInfoInspector.java
@@ -19,8 +19,8 @@
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.DexInspector;
import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
@@ -49,13 +49,13 @@
}
}
- public DebugInfoInspector(DexInspector inspector, String clazz, MethodSignature method) {
+ public DebugInfoInspector(CodeInspector inspector, String clazz, MethodSignature method) {
this(inspector.clazz(clazz).method(method).getMethod(), inspector.getFactory());
}
public DebugInfoInspector(AndroidApp app, String clazz, MethodSignature method)
throws IOException, ExecutionException {
- this(new DexInspector(app), clazz, method);
+ this(new CodeInspector(app), clazz, method);
}
public boolean hasLocalsInfo() {
diff --git a/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestRunner.java
index c67cf32..ce84f51 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestRunner.java
@@ -40,33 +40,49 @@
@RunWith(Parameterized.class)
public class InliningWithoutPositionsTestRunner {
+ enum Backend {
+ CF,
+ DEX
+ }
+
private static final String TEST_CLASS = "InliningWithoutPositionsTestSource";
private static final String TEST_PACKAGE = "com.android.tools.r8.debuginfo";
@ClassRule public static TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+ private final Backend backend;
private final boolean mainPos;
private final boolean foo1Pos;
private final boolean barPos;
private final boolean foo2Pos;
private final Location throwLocation;
- @Parameters(name = "main/foo1/bar/foo2 positions: {0}/{1}/{2}/{3}, throwLocation: {4}")
+ @Parameters(name = "{0}: main/foo1/bar/foo2 positions: {1}/{2}/{3}/{4}, throwLocation: {5}")
public static Collection<Object[]> data() {
List<Object[]> testCases = new ArrayList<>();
+ for (Backend backend : Backend.values()) {
for (int i = 0; i < 16; ++i) {
for (Location throwLocation : Location.values()) {
if (throwLocation != Location.MAIN) {
- testCases.add(
- new Object[] {(i & 1) != 0, (i & 2) != 0, (i & 4) != 0, (i & 8) != 0, throwLocation});
+ testCases.add(
+ new Object[] {
+ backend, (i & 1) != 0, (i & 2) != 0, (i & 4) != 0, (i & 8) != 0, throwLocation
+ });
}
}
+ }
}
return testCases;
}
public InliningWithoutPositionsTestRunner(
- boolean mainPos, boolean foo1Pos, boolean barPos, boolean foo2Pos, Location throwLocation) {
+ Backend backend,
+ boolean mainPos,
+ boolean foo1Pos,
+ boolean barPos,
+ boolean foo2Pos,
+ Location throwLocation) {
+ this.backend = backend;
this.mainPos = mainPos;
this.foo1Pos = foo1Pos;
this.barPos = barPos;
@@ -79,33 +95,45 @@
// See InliningWithoutPositionsTestSourceDump for the code compiled here.
Path testClassDir = temp.newFolder(TEST_PACKAGE.split(".")).toPath();
Path testClassPath = testClassDir.resolve(TEST_CLASS + ".class");
- Path outputDexPath = temp.newFolder().toPath();
+ Path outputPath = temp.newFolder().toPath();
Files.write(
testClassPath,
InliningWithoutPositionsTestSourceDump.dump(
mainPos, foo1Pos, barPos, foo2Pos, throwLocation));
- AndroidApiLevel minSdk = ToolHelper.getMinApiLevelForDexVm();
Path proguardMapPath = testClassDir.resolve("proguard.map");
- ToolHelper.runR8(
+ R8Command.Builder builder =
R8Command.builder()
.addProgramFiles(testClassPath)
- .setMinApiLevel(minSdk.getLevel())
- .addLibraryFiles(ToolHelper.getAndroidJar(minSdk))
- .setOutput(outputDexPath, OutputMode.DexIndexed)
.setMode(CompilationMode.RELEASE)
- .setProguardMapOutputPath(proguardMapPath)
- .build(),
- options -> options.inliningInstructionLimit = 20);
+ .setProguardMapOutputPath(proguardMapPath);
+ if (backend == Backend.DEX) {
+ AndroidApiLevel minSdk = ToolHelper.getMinApiLevelForDexVm();
+ builder
+ .setMinApiLevel(minSdk.getLevel())
+ .addLibraryFiles(ToolHelper.getAndroidJar(minSdk))
+ .setOutput(outputPath, OutputMode.DexIndexed);
+ } else {
+ assert (backend == Backend.CF);
+ builder
+ .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
+ .setOutput(outputPath, OutputMode.ClassFile);
+ }
- ArtCommandBuilder artCommandBuilder = new ArtCommandBuilder();
- artCommandBuilder.appendClasspath(outputDexPath.resolve("classes.dex").toString());
- artCommandBuilder.setMainClass(TEST_PACKAGE + "." + TEST_CLASS);
+ ToolHelper.runR8(builder.build(), options -> options.inliningInstructionLimit = 40);
- ProcessResult result = ToolHelper.runArtRaw(artCommandBuilder);
+ ProcessResult result;
+ if (backend == Backend.DEX) {
+ ArtCommandBuilder artCommandBuilder = new ArtCommandBuilder();
+ artCommandBuilder.appendClasspath(outputPath.resolve("classes.dex").toString());
+ artCommandBuilder.setMainClass(TEST_PACKAGE + "." + TEST_CLASS);
+ result = ToolHelper.runArtRaw(artCommandBuilder);
+ } else {
+ result = ToolHelper.runJava(outputPath, TEST_PACKAGE + "." + TEST_CLASS);
+ }
assertNotEquals(result.exitCode, 0);
// Verify stack trace.
@@ -157,7 +185,7 @@
ClassNamingForNameMapper classNaming = mapper.getClassNaming(TEST_PACKAGE + "." + TEST_CLASS);
assertNotNull(classNaming);
- MappedRangesOfName rangesForMain = classNaming.mappedRangesByName.get("main");
+ MappedRangesOfName rangesForMain = classNaming.mappedRangesByRenamedName.get("main");
assertNotNull(rangesForMain);
List<MappedRange> frames = rangesForMain.allRangesForLine(expectedLineNumber);
diff --git a/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestSourceDump.java b/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestSourceDump.java
index 53d6df3..d0ddf00 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestSourceDump.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestSourceDump.java
@@ -4,8 +4,12 @@
package com.android.tools.r8.debuginfo;
-import java.util.*;
-import org.objectweb.asm.*;
+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;
public class InliningWithoutPositionsTestSourceDump implements Opcodes {
@@ -61,8 +65,7 @@
}
*/
public static byte[] dump(
- boolean mainPos, boolean foo1Pos, boolean barPos, boolean foo2Pos, Location throwLocation)
- throws Exception {
+ boolean mainPos, boolean foo1Pos, boolean barPos, boolean foo2Pos, Location throwLocation) {
ClassWriter cw = new ClassWriter(0);
FieldVisitor fv;
MethodVisitor mv;
diff --git a/src/test/java/com/android/tools/r8/debuginfo/KotlinDebugInfoTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/KotlinDebugInfoTestRunner.java
index 103c07f..80279d5 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/KotlinDebugInfoTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/KotlinDebugInfoTestRunner.java
@@ -15,7 +15,6 @@
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.ProcessResult;
import java.nio.file.Path;
-import java.nio.file.Paths;
import org.junit.Test;
public class KotlinDebugInfoTestRunner extends TestBase {
@@ -74,7 +73,7 @@
.setProgramConsumer(consumer)
.addProgramFiles(inputJar);
if ((consumer instanceof ClassFileConsumer)) {
- builder.addLibraryFiles(Paths.get(ToolHelper.JAVA_8_RUNTIME));
+ builder.addLibraryFiles(ToolHelper.getJava8RuntimeJar());
} else {
builder.addLibraryFiles(ToolHelper.getAndroidJar(ToolHelper.getMinApiLevelForDexVm()));
}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/KotlinReflectionDump.java b/src/test/java/com/android/tools/r8/debuginfo/KotlinReflectionDump.java
index f8bddbb..ac2a8ff 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/KotlinReflectionDump.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/KotlinReflectionDump.java
@@ -3,7 +3,12 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.debuginfo;
-import org.objectweb.asm.*;
+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;
public class KotlinReflectionDump implements Opcodes {
diff --git a/src/test/java/com/android/tools/r8/debuginfo/KotlinRingBufferDump.java b/src/test/java/com/android/tools/r8/debuginfo/KotlinRingBufferDump.java
index 11b981a..4811648 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/KotlinRingBufferDump.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/KotlinRingBufferDump.java
@@ -3,7 +3,12 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.debuginfo;
-import org.objectweb.asm.*;
+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;
public class KotlinRingBufferDump implements Opcodes {
diff --git a/src/test/java/com/android/tools/r8/debuginfo/PreamblePositionTestSourceDump.java b/src/test/java/com/android/tools/r8/debuginfo/PreamblePositionTestSourceDump.java
index 5ed2a4e..7067295 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/PreamblePositionTestSourceDump.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/PreamblePositionTestSourceDump.java
@@ -4,8 +4,12 @@
package com.android.tools.r8.debuginfo;
-import java.util.*;
-import org.objectweb.asm.*;
+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 from the source code below, line numbers removed, except for the false branch,
diff --git a/src/test/java/com/android/tools/r8/dex/JumboStringProcessing.java b/src/test/java/com/android/tools/r8/dex/JumboStringProcessing.java
index d452699..3b1e30a 100644
--- a/src/test/java/com/android/tools/r8/dex/JumboStringProcessing.java
+++ b/src/test/java/com/android/tools/r8/dex/JumboStringProcessing.java
@@ -26,7 +26,7 @@
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import com.google.common.io.Files;
import java.nio.file.Path;
@@ -127,7 +127,7 @@
AndroidApp application = AndroidApp.builder()
.addDexProgramData(Files.toByteArray(originalDexFile.toFile()), Origin.unknown())
.build();
- DexInspector inspector = new DexInspector(application);
+ CodeInspector inspector = new CodeInspector(application);
DexEncodedMethod method = getMethod(
inspector,
"android.databinding.DataBinderMapperImpl",
diff --git a/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java b/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
index ead9175..3b12cc4 100644
--- a/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
+++ b/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
@@ -22,6 +22,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DexTypeList;
import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.ParameterAnnotationsList;
import com.android.tools.r8.naming.NamingLens;
@@ -135,7 +136,14 @@
options.programConsumer = consumer;
ApplicationWriter writer =
new ApplicationWriter(
- application, options, null, null, NamingLens.getIdentityLens(), null, null);
+ application,
+ options,
+ null,
+ null,
+ GraphLense.getIdentityLense(),
+ NamingLens.getIdentityLens(),
+ null,
+ null);
ExecutorService executorService = ThreadUtils.getExecutorService(options);
writer.write(executorService);
List<Set<String>> generatedDescriptors = consumer.getDescriptors();
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java
index a0b2515..5848396 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java
@@ -20,9 +20,9 @@
import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
import com.android.tools.r8.dex.Marker;
import com.android.tools.r8.dexsplitter.DexSplitter.Options;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
import com.android.tools.r8.utils.FeatureClassMapping.FeatureMappingException;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import java.io.FileNotFoundException;
@@ -32,6 +32,7 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.zip.ZipEntry;
@@ -50,6 +51,8 @@
private static final String CLASS3_INNER_CLASS = CLASS_DIR + "/Class3$InnerClass.class";
private static final String CLASS4_CLASS = CLASS_DIR + "/Class4.class";
private static final String CLASS4_LAMBDA_INTERFACE = CLASS_DIR + "/Class4$LambdaInterface.class";
+ private static final String TEXT_FILE =
+ ToolHelper.EXAMPLES_ANDROID_N_DIR + "dexsplitsample/TextFile.txt";
@Rule public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
@@ -222,8 +225,7 @@
@Test
public void splitFilesFromJar()
- throws IOException, CompilationFailedException, FeatureMappingException, ResourceException,
- ExecutionException {
+ throws IOException, CompilationFailedException, FeatureMappingException {
splitFromJars(true, true);
splitFromJars(false, true);
splitFromJars(true, false);
@@ -304,7 +306,6 @@
validateUnobfuscatedOutput(base, feature);
}
-
@Test
public void splitFilesObfuscation()
throws CompilationFailedException, IOException, ExecutionException {
@@ -363,9 +364,109 @@
// Ensure that the Class1 is actually in the correct split. Note that Class2 would have been
// shaken away.
- DexInspector inspector = new DexInspector(base, proguardMap.toString());
+ CodeInspector inspector = new CodeInspector(base, proguardMap.toString());
ClassSubject subject = inspector.clazz("dexsplitsample.Class1");
assertTrue(subject.isPresent());
assertTrue(subject.isRenamed());
}
+
+ @Test
+ public void splitNonClassFiles()
+ throws CompilationFailedException, IOException, FeatureMappingException {
+ Path inputZip = temp.getRoot().toPath().resolve("input-zip-with-non-class-files.jar");
+ ZipOutputStream inputZipStream = new ZipOutputStream(Files.newOutputStream(inputZip));
+ String name = "dexsplitsample/TextFile.txt";
+ inputZipStream.putNextEntry(new ZipEntry(name));
+ byte[] fileBytes = Files.readAllBytes(Paths.get(TEXT_FILE));
+ inputZipStream.write(fileBytes);
+ inputZipStream.closeEntry();
+ name = "dexsplitsample/TextFile2.txt";
+ inputZipStream.putNextEntry(new ZipEntry(name));
+ inputZipStream.write(fileBytes);
+ inputZipStream.write(fileBytes);
+ inputZipStream.closeEntry();
+ inputZipStream.close();
+ Path output = temp.newFolder().toPath().resolve("output");
+ Files.createDirectory(output);
+ Path baseJar = temp.getRoot().toPath().resolve("base.jar");
+ Path featureJar = temp.getRoot().toPath().resolve("feature1.jar");
+ ZipOutputStream baseStream = new ZipOutputStream(Files.newOutputStream(baseJar));
+ name = "dexsplitsample/TextFile.txt";
+ baseStream.putNextEntry(new ZipEntry(name));
+ baseStream.write(fileBytes);
+ baseStream.closeEntry();
+ baseStream.close();
+ ZipOutputStream featureStream = new ZipOutputStream(Files.newOutputStream(featureJar));
+ name = "dexsplitsample/TextFile2.txt";
+ featureStream.putNextEntry(new ZipEntry(name));
+ featureStream.write(fileBytes);
+ featureStream.write(fileBytes);
+ featureStream.closeEntry();
+ featureStream.close();
+ Options options = new Options();
+ options.addInputArchive(inputZip.toString());
+ options.setOutput(output.toString());
+ options.addFeatureJar(baseJar.toString());
+ options.addFeatureJar(featureJar.toString());
+ options.setSplitNonClassResources(true);
+ DexSplitter.run(options);
+ Path baseDir = output.resolve("base");
+ Path featureDir = output.resolve("feature1");
+ byte[] contents = fileBytes;
+ byte[] contents2 = new byte[contents.length * 2];
+ System.arraycopy(contents, 0, contents2, 0, contents.length);
+ System.arraycopy(contents, 0, contents2, contents.length, contents.length);
+ Path baseTextFile = baseDir.resolve("dexsplitsample/TextFile.txt");
+ Path featureTextFile = featureDir.resolve("dexsplitsample/TextFile2.txt");
+ assert Files.exists(baseTextFile);
+ assert Files.exists(featureTextFile);
+ assert Arrays.equals(Files.readAllBytes(baseTextFile), contents);
+ assert Arrays.equals(Files.readAllBytes(featureTextFile), contents2);
+ }
+
+ @Test
+ public void splitDuplicateNonClassFiles()
+ throws IOException, CompilationFailedException, FeatureMappingException {
+ Path inputZip = temp.getRoot().toPath().resolve("input-zip-with-non-class-files.jar");
+ ZipOutputStream inputZipStream = new ZipOutputStream(Files.newOutputStream(inputZip));
+ String name = "dexsplitsample/TextFile.txt";
+ inputZipStream.putNextEntry(new ZipEntry(name));
+ byte[] fileBytes = Files.readAllBytes(Paths.get(TEXT_FILE));
+ inputZipStream.write(fileBytes);
+ inputZipStream.closeEntry();
+ inputZipStream.close();
+ Path output = temp.newFolder().toPath().resolve("output");
+ Files.createDirectory(output);
+ Path featureJar = temp.getRoot().toPath().resolve("feature1.jar");
+ Path feature2Jar = temp.getRoot().toPath().resolve("feature2.jar");
+ ZipOutputStream featureStream = new ZipOutputStream(Files.newOutputStream(featureJar));
+ name = "dexsplitsample/TextFile.txt";
+ featureStream.putNextEntry(new ZipEntry(name));
+ featureStream.write(fileBytes);
+ featureStream.closeEntry();
+ featureStream.close();
+ ZipOutputStream feature2Stream = new ZipOutputStream(Files.newOutputStream(feature2Jar));
+ name = "dexsplitsample/TextFile.txt";
+ feature2Stream.putNextEntry(new ZipEntry(name));
+ feature2Stream.write(fileBytes);
+ feature2Stream.closeEntry();
+ feature2Stream.close();
+ Options options = new Options();
+ options.addInputArchive(inputZip.toString());
+ options.setOutput(output.toString());
+ options.addFeatureJar(feature2Jar.toString());
+ options.addFeatureJar(featureJar.toString());
+ options.setSplitNonClassResources(true);
+ DexSplitter.run(options);
+ Path baseDir = output.resolve("base");
+ Path feature1Dir = output.resolve("feature1");
+ Path feature2Dir = output.resolve("feature2");
+ Path baseTextFile = baseDir.resolve("dexsplitsample/TextFile.txt");
+ Path feature1TextFile = feature1Dir.resolve("dexsplitsample/TextFile2.txt");
+ Path feature2TextFile = feature2Dir.resolve("dexsplitsample/TextFile2.txt");
+ assert !Files.exists(feature1TextFile);
+ assert !Files.exists(feature2TextFile);
+ assert Files.exists(baseTextFile);
+ assert Arrays.equals(Files.readAllBytes(baseTextFile), fileBytes);
+ }
}
diff --git a/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java b/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
index 6877617..39b5ff8 100644
--- a/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
@@ -23,7 +23,7 @@
import com.android.tools.r8.smali.SmaliBuilder;
import com.android.tools.r8.smali.SmaliTestBase;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.nio.file.Path;
import java.util.Collections;
@@ -77,7 +77,7 @@
AndroidApp application = buildApplication(builder);
AppInfo appInfo = getAppInfo(application);
- DexInspector inspector = new DexInspector(appInfo.app);
+ CodeInspector inspector = new CodeInspector(appInfo.app);
DexEncodedMethod method = getMethod(inspector, DEFAULT_CLASS_NAME, "int", "x",
ImmutableList.of());
assertNull(appInfo.lookupVirtualTarget(method.method.holder, method.method));
@@ -147,7 +147,7 @@
AndroidApp application = buildApplication(builder);
AppInfo appInfo = getAppInfo(application);
- DexInspector inspector = new DexInspector(appInfo.app);
+ CodeInspector inspector = new CodeInspector(appInfo.app);
DexMethod methodXOnTestSuper =
getMethod(inspector, "TestSuper", "int", "x", ImmutableList.of()).method;
diff --git a/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java b/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
index 39c8d28..2ec38b4 100644
--- a/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
+++ b/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
@@ -28,12 +28,12 @@
import com.android.tools.r8.utils.ArtErrorParser;
import com.android.tools.r8.utils.ArtErrorParser.ArtErrorInfo;
import com.android.tools.r8.utils.ArtErrorParser.ArtErrorParserException;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.FoundClassSubject;
-import com.android.tools.r8.utils.DexInspector.FoundFieldSubject;
-import com.android.tools.r8.utils.DexInspector.FoundMethodSubject;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import com.android.tools.r8.utils.codeinspector.FoundFieldSubject;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
import com.google.common.io.ByteStreams;
import com.google.common.io.Closer;
import java.io.File;
@@ -132,8 +132,8 @@
if (referenceApk == null) {
throw e;
}
- DexInspector theirs = new DexInspector(Paths.get(referenceApk));
- DexInspector ours = new DexInspector(out);
+ CodeInspector theirs = new CodeInspector(Paths.get(referenceApk));
+ CodeInspector ours = new CodeInspector(out);
List<ArtErrorInfo> errors;
try {
errors = ArtErrorParser.parse(e.getMessage());
@@ -194,8 +194,8 @@
public void assertIdenticalApplicationsUpToCode(
AndroidApp app1, AndroidApp app2, boolean allowNewClassesInApp2)
throws IOException, ExecutionException {
- DexInspector inspect1 = new DexInspector(app1);
- DexInspector inspect2 = new DexInspector(app2);
+ CodeInspector inspect1 = new CodeInspector(app1);
+ CodeInspector inspect2 = new CodeInspector(app2);
class Pair<T> {
private T first;
@@ -213,7 +213,7 @@
// Collect all classes from both inspectors, indexed by finalDescriptor.
Map<String, Pair<FoundClassSubject>> allClasses = new HashMap<>();
- BiConsumer<DexInspector, Boolean> collectClasses =
+ BiConsumer<CodeInspector, Boolean> collectClasses =
(inspector, selectFirst) -> {
inspector.forAllClasses(
clazz -> {
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java
index abc7a73..6ae151e 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java
@@ -3,13 +3,14 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.internal;
+import static junit.framework.TestCase.assertEquals;
+
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.DexIndexedConsumer;
import com.android.tools.r8.OutputMode;
import com.android.tools.r8.R8Command;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.shaking.ProguardRuleParserException;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
import com.beust.jcommander.internal.Lists;
@@ -20,52 +21,60 @@
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
-import java.util.concurrent.ExecutionException;
import org.junit.Test;
public class R8GMSCoreDeterministicTest extends GMSCoreCompilationTestBase {
- public Set<DexEncodedMethod> shuffle(Set<DexEncodedMethod> methods) {
+ private static class CompilationResult {
+ AndroidApp app;
+ String proguardMap;
+ }
+
+ private static Set<DexEncodedMethod> shuffle(Set<DexEncodedMethod> methods) {
List<DexEncodedMethod> toShuffle = Lists.newArrayList(methods);
Collections.shuffle(toShuffle);
return new LinkedHashSet<>(toShuffle);
}
- private AndroidApp doRun()
- throws IOException, ProguardRuleParserException, ExecutionException,
- CompilationFailedException {
+ private CompilationResult doRun() throws IOException, CompilationFailedException {
R8Command command =
R8Command.builder()
.addProgramFiles(Paths.get(GMSCORE_V7_DIR, GMSCORE_APK))
.setProgramConsumer(DexIndexedConsumer.emptyConsumer())
.setMinApiLevel(AndroidApiLevel.L.getLevel())
.build();
- return ToolHelper.runR8(
- command,
- options -> {
- // For this test just do random shuffle.
- options.testing.irOrdering = this::shuffle;
- // Only use one thread to process to process in the order decided by the callback.
- options.numberOfThreads = 1;
- // Ignore the missing classes.
- options.ignoreMissingClasses = true;
- });
+ CompilationResult result = new CompilationResult();
+ result.app =
+ ToolHelper.runR8(
+ command,
+ options -> {
+ // For this test just do random shuffle.
+ options.testing.irOrdering = R8GMSCoreDeterministicTest::shuffle;
+ // Only use one thread to process to process in the order decided by the callback.
+ options.numberOfThreads = 1;
+ // Ignore the missing classes.
+ options.ignoreMissingClasses = true;
+ // Store the generated Proguard map.
+ options.proguardMapConsumer =
+ (proguardMap, handler) -> result.proguardMap = proguardMap;
+ });
+ return result;
}
@Test
public void deterministic() throws Exception {
-
// Run two independent compilations.
- AndroidApp app1 = doRun();
- AndroidApp app2 = doRun();
+ CompilationResult result1 = doRun();
+ CompilationResult result2 = doRun();
// Check that the generated bytecode runs through the dex2oat verifier with no errors.
Path combinedInput = temp.getRoot().toPath().resolve("all.jar");
Path oatFile = temp.getRoot().toPath().resolve("all.oat");
- app1.writeToZip(combinedInput, OutputMode.DexIndexed);
+ result1.app.writeToZip(combinedInput, OutputMode.DexIndexed);
ToolHelper.runDex2Oat(combinedInput, oatFile);
// Verify that the result of the two compilations was the same.
- assertIdenticalApplications(app1, app2);
+ assertIdenticalApplications(result1.app, result2.app);
+ assertEquals(result1.proguardMap, result2.proguardMap);
}
}
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10DeployJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10DeployJarVerificationTest.java
index 6adf680..d881185 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10DeployJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10DeployJarVerificationTest.java
@@ -4,6 +4,8 @@
package com.android.tools.r8.internal;
+import static org.junit.Assert.assertEquals;
+
import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.R8RunArtTestsTest.CompilerUnderTest;
import com.android.tools.r8.utils.AndroidApp;
@@ -11,17 +13,33 @@
public class R8GMSCoreV10DeployJarVerificationTest extends GMSCoreDeployJarVerificationTest {
+ private String proguardMap1 = null;
+ private String proguardMap2 = null;
+
@Test
public void buildFromDeployJar() throws Exception {
// TODO(tamaskenez): set hasReference = true when we have the noshrink file for V10
- AndroidApp app1 = buildFromDeployJar(
- CompilerUnderTest.R8, CompilationMode.RELEASE,
- GMSCoreCompilationTestBase.GMSCORE_V10_DIR, false);
- AndroidApp app2 = buildFromDeployJar(
- CompilerUnderTest.R8, CompilationMode.RELEASE,
- GMSCoreCompilationTestBase.GMSCORE_V10_DIR, false);
+ AndroidApp app1 =
+ buildFromDeployJar(
+ CompilerUnderTest.R8,
+ CompilationMode.RELEASE,
+ GMSCoreCompilationTestBase.GMSCORE_V10_DIR,
+ false,
+ options ->
+ options.proguardMapConsumer =
+ (proguardMap, handler) -> this.proguardMap1 = proguardMap);
+ AndroidApp app2 =
+ buildFromDeployJar(
+ CompilerUnderTest.R8,
+ CompilationMode.RELEASE,
+ GMSCoreCompilationTestBase.GMSCORE_V10_DIR,
+ false,
+ options ->
+ options.proguardMapConsumer =
+ (proguardMap, handler) -> this.proguardMap2 = proguardMap);
// Verify that the result of the two compilations was the same.
assertIdenticalApplications(app1, app2);
+ assertEquals(proguardMap1, proguardMap2);
}
}
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10TreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10TreeShakeJarVerificationTest.java
index ae470c6..4374d0e 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10TreeShakeJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10TreeShakeJarVerificationTest.java
@@ -3,6 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.internal;
+import static org.junit.Assert.assertEquals;
+
import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.utils.AndroidApp;
import org.junit.Test;
@@ -10,15 +12,33 @@
public class R8GMSCoreV10TreeShakeJarVerificationTest
extends R8GMSCoreTreeShakeJarVerificationTest {
+ private String proguardMap1 = null;
+ private String proguardMap2 = null;
+
@Test
public void buildAndTreeShakeFromDeployJar() throws Exception {
// TODO(tamaskenez): set hasReference = true when we have the noshrink file for V10
- AndroidApp app1 = buildAndTreeShakeFromDeployJar(
- CompilationMode.RELEASE, GMSCORE_V10_DIR, false, GMSCORE_V10_MAX_SIZE, null);
- AndroidApp app2 = buildAndTreeShakeFromDeployJar(
- CompilationMode.RELEASE, GMSCORE_V10_DIR, false, GMSCORE_V10_MAX_SIZE, null);
+ AndroidApp app1 =
+ buildAndTreeShakeFromDeployJar(
+ CompilationMode.RELEASE,
+ GMSCORE_V10_DIR,
+ false,
+ GMSCORE_V10_MAX_SIZE,
+ options ->
+ options.proguardMapConsumer =
+ (proguardMap, handler) -> this.proguardMap1 = proguardMap);
+ AndroidApp app2 =
+ buildAndTreeShakeFromDeployJar(
+ CompilationMode.RELEASE,
+ GMSCORE_V10_DIR,
+ false,
+ GMSCORE_V10_MAX_SIZE,
+ options ->
+ options.proguardMapConsumer =
+ (proguardMap, handler) -> this.proguardMap2 = proguardMap);
// Verify that the result of the two compilations was the same.
assertIdenticalApplications(app1, app2);
+ assertEquals(proguardMap1, proguardMap2);
}
}
diff --git a/src/test/java/com/android/tools/r8/invalid/DuplicateDefinitionsTest.java b/src/test/java/com/android/tools/r8/invalid/DuplicateDefinitionsTest.java
new file mode 100644
index 0000000..68daeb8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/invalid/DuplicateDefinitionsTest.java
@@ -0,0 +1,100 @@
+// 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.invalid;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.jasmin.JasminBuilder;
+import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
+import com.android.tools.r8.jasmin.JasminTestBase;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.nio.charset.Charset;
+import org.junit.Test;
+
+public class DuplicateDefinitionsTest extends JasminTestBase {
+
+ @Test
+ public void testDuplicateMethods() throws Exception {
+ JasminBuilder jasminBuilder = new JasminBuilder();
+ ClassBuilder classBuilder = jasminBuilder.addClass("C");
+ classBuilder.addMainMethod(".limit locals 1", ".limit stack 0", "return");
+ classBuilder.addMainMethod(".limit locals 1", ".limit stack 0", "return");
+ classBuilder.addVirtualMethod("method", "V", ".limit locals 1", ".limit stack 0", "return");
+ classBuilder.addVirtualMethod("method", "V", ".limit locals 1", ".limit stack 0", "return");
+
+ // Run D8 and intercept warnings.
+ PrintStream stderr = System.err;
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ System.setErr(new PrintStream(baos));
+
+ AndroidApp app = compileWithD8(jasminBuilder.build());
+
+ String output = new String(baos.toByteArray(), Charset.defaultCharset());
+ System.setOut(stderr);
+
+ // Check that warnings were emitted.
+ assertThat(
+ output,
+ containsString(
+ "Ignoring an implementation of the method `void C.main(java.lang.String[])` because "
+ + "it has multiple definitions"));
+ assertThat(
+ output,
+ containsString(
+ "Ignoring an implementation of the method `void C.method()` because "
+ + "it has multiple definitions"));
+
+ CodeInspector inspector = new CodeInspector(app);
+ ClassSubject clazz = inspector.clazz("C");
+ assertThat(clazz, isPresent());
+
+ // There are two direct methods, but only because one is <init>.
+ assertEquals(2, clazz.getDexClass().directMethods().length);
+ assertThat(clazz.method("void", "<init>", ImmutableList.of()), isPresent());
+
+ // There is only one virtual method.
+ assertEquals(1, clazz.getDexClass().virtualMethods().length);
+ }
+
+ @Test
+ public void testDuplicateFields() throws Exception {
+ JasminBuilder jasminBuilder = new JasminBuilder();
+ ClassBuilder classBuilder = jasminBuilder.addClass("C");
+ classBuilder.addField("public", "fld", "LC;", null);
+ classBuilder.addField("public", "fld", "LC;", null);
+ classBuilder.addStaticField("staticFld", "LC;", null);
+ classBuilder.addStaticField("staticFld", "LC;", null);
+
+ // Run D8 and intercept warnings.
+ PrintStream stderr = System.err;
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ System.setErr(new PrintStream(baos));
+
+ AndroidApp app = compileWithD8(jasminBuilder.build());
+
+ String output = new String(baos.toByteArray(), Charset.defaultCharset());
+ System.setOut(stderr);
+
+ // Check that warnings were emitted.
+ assertThat(output, containsString("Field `C C.fld` has multiple definitions"));
+ assertThat(output, containsString("Field `C C.staticFld` has multiple definitions"));
+
+ CodeInspector inspector = new CodeInspector(app);
+ ClassSubject clazz = inspector.clazz("C");
+ assertThat(clazz, isPresent());
+
+ // Redundant fields have been removed.
+ assertEquals(1, clazz.getDexClass().instanceFields().length);
+ assertEquals(1, clazz.getDexClass().staticFields().length);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java b/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
index d4ab6c6..b66e6e0 100644
--- a/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
+++ b/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
@@ -20,9 +20,9 @@
import com.android.tools.r8.smali.SmaliTestBase;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.AndroidAppConsumers;
-import com.android.tools.r8.utils.DexInspector;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import java.io.IOException;
import java.util.List;
import java.util.ListIterator;
@@ -61,7 +61,7 @@
String returnType,
String methodName,
List<String> parameters) {
- DexInspector inspector = new DexInspector(application);
+ CodeInspector inspector = new CodeInspector(application);
return getMethod(inspector, className, returnType, methodName, parameters);
}
diff --git a/src/test/java/com/android/tools/r8/ir/PhiDefinitionsTestDump.java b/src/test/java/com/android/tools/r8/ir/PhiDefinitionsTestDump.java
index 5a93117..0094ad1 100644
--- a/src/test/java/com/android/tools/r8/ir/PhiDefinitionsTestDump.java
+++ b/src/test/java/com/android/tools/r8/ir/PhiDefinitionsTestDump.java
@@ -3,7 +3,12 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir;
-import org.objectweb.asm.*;
+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;
public class PhiDefinitionsTestDump implements Opcodes {
diff --git a/src/test/java/com/android/tools/r8/ir/PhiDefinitionsTestRunner.java b/src/test/java/com/android/tools/r8/ir/PhiDefinitionsTestRunner.java
index 4f2bdeb..ba43a10 100644
--- a/src/test/java/com/android/tools/r8/ir/PhiDefinitionsTestRunner.java
+++ b/src/test/java/com/android/tools/r8/ir/PhiDefinitionsTestRunner.java
@@ -17,7 +17,6 @@
import com.android.tools.r8.utils.DescriptorUtils;
import java.io.IOException;
import java.nio.file.Path;
-import java.nio.file.Paths;
import org.junit.Test;
public class PhiDefinitionsTestRunner extends TestBase {
@@ -111,7 +110,7 @@
.setProgramConsumer(consumer)
.addProgramFiles(inputJar);
if (consumer instanceof ClassFileConsumer) {
- builder.addLibraryFiles(Paths.get(ToolHelper.JAVA_8_RUNTIME));
+ builder.addLibraryFiles(ToolHelper.getJava8RuntimeJar());
} else {
builder.addLibraryFiles(ToolHelper.getAndroidJar(ToolHelper.getMinApiLevelForDexVm()));
}
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
index 16785f6..a329513 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.ir.code.Argument;
import com.android.tools.r8.ir.code.ArrayGet;
import com.android.tools.r8.ir.code.IRCode;
@@ -25,18 +26,18 @@
import com.android.tools.r8.ir.code.NewInstance;
import com.android.tools.r8.ir.code.NonNull;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.NonNullTracker;
import com.android.tools.r8.ir.optimize.nonnull.FieldAccessTest;
import com.android.tools.r8.ir.optimize.nonnull.NonNullAfterArrayAccess;
import com.android.tools.r8.ir.optimize.nonnull.NonNullAfterFieldAccess;
import com.android.tools.r8.ir.optimize.nonnull.NonNullAfterInvoke;
-import com.android.tools.r8.ir.optimize.NonNullTracker;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.DexInspector;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableMap;
import java.util.Map;
import java.util.function.BiConsumer;
@@ -56,9 +57,10 @@
new ApplicationReader(app, TEST_OPTIONS, new Timing("NullabilityTest.appReader"))
.read().toDirect();
AppInfo appInfo = new AppInfo(dexApplication);
- DexInspector dexInspector = new DexInspector(appInfo.app);
- DexEncodedMethod foo = dexInspector.clazz(mainClass.getName()).method(signature).getMethod();
- IRCode irCode = foo.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
+ CodeInspector codeInspector = new CodeInspector(appInfo.app);
+ DexEncodedMethod foo = codeInspector.clazz(mainClass.getName()).method(signature).getMethod();
+ IRCode irCode =
+ foo.buildIR(appInfo, GraphLense.getIdentityLense(), TEST_OPTIONS, Origin.unknown());
NonNullTracker nonNullTracker = new NonNullTracker();
nonNullTracker.addNonNull(irCode);
TypeAnalysis analysis = new TypeAnalysis(appInfo, foo, irCode);
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeAnalysisTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeAnalysisTest.java
index aefe665..9c1214c 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeAnalysisTest.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.ir.code.ArrayLength;
import com.android.tools.r8.ir.code.CheckCast;
import com.android.tools.r8.ir.code.ConstString;
@@ -30,10 +31,10 @@
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.smali.SmaliTestBase;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.Smali;
import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import java.nio.charset.StandardCharsets;
@@ -116,13 +117,14 @@
// Simple one path with a lot of arithmetic operations.
private static void arithmetic(AppInfo appInfo) {
- DexInspector inspector = new DexInspector(appInfo.app);
+ CodeInspector inspector = new CodeInspector(appInfo.app);
DexEncodedMethod subtract =
inspector.clazz("Test")
.method(
new MethodSignature("subtractConstants8bitRegisters", "int", ImmutableList.of()))
.getMethod();
- IRCode irCode = subtract.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
+ IRCode irCode =
+ subtract.buildIR(appInfo, GraphLense.getIdentityLense(), TEST_OPTIONS, Origin.unknown());
TypeAnalysis analysis = new TypeAnalysis(appInfo, subtract, irCode);
analysis.forEach((v, l) -> {
assertEither(l, PRIMITIVE, NULL, TOP);
@@ -131,12 +133,13 @@
// A couple branches, along with some recursive calls.
private static void fibonacci(AppInfo appInfo) {
- DexInspector inspector = new DexInspector(appInfo.app);
+ CodeInspector inspector = new CodeInspector(appInfo.app);
DexEncodedMethod fib =
inspector.clazz("Test")
.method(new MethodSignature("fibonacci", "int", ImmutableList.of("int")))
.getMethod();
- IRCode irCode = fib.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
+ IRCode irCode =
+ fib.buildIR(appInfo, GraphLense.getIdentityLense(), TEST_OPTIONS, Origin.unknown());
TypeAnalysis analysis = new TypeAnalysis(appInfo, fib, irCode);
analysis.forEach((v, l) -> {
assertEither(l, PRIMITIVE, NULL);
@@ -145,12 +148,13 @@
// fill-array-data
private static void fillArrayData(AppInfo appInfo) {
- DexInspector inspector = new DexInspector(appInfo.app);
+ CodeInspector inspector = new CodeInspector(appInfo.app);
DexEncodedMethod test1 =
inspector.clazz("Test")
.method(new MethodSignature("test1", "int[]", ImmutableList.of()))
.getMethod();
- IRCode irCode = test1.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
+ IRCode irCode =
+ test1.buildIR(appInfo, GraphLense.getIdentityLense(), TEST_OPTIONS, Origin.unknown());
TypeAnalysis analysis = new TypeAnalysis(appInfo, test1, irCode);
Value array = null;
InstructionIterator iterator = irCode.instructionIterator();
@@ -176,12 +180,13 @@
// filled-new-array
private static void filledNewArray(AppInfo appInfo) {
- DexInspector inspector = new DexInspector(appInfo.app);
+ CodeInspector inspector = new CodeInspector(appInfo.app);
DexEncodedMethod test4 =
inspector.clazz("Test")
.method(new MethodSignature("test4", "int[]", ImmutableList.of()))
.getMethod();
- IRCode irCode = test4.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
+ IRCode irCode =
+ test4.buildIR(appInfo, GraphLense.getIdentityLense(), TEST_OPTIONS, Origin.unknown());
TypeAnalysis analysis = new TypeAnalysis(appInfo, test4, irCode);
Value array = null;
InstructionIterator iterator = irCode.instructionIterator();
@@ -207,12 +212,13 @@
// Make sure the analysis does not hang.
private static void infiniteLoop(AppInfo appInfo) {
- DexInspector inspector = new DexInspector(appInfo.app);
+ CodeInspector inspector = new CodeInspector(appInfo.app);
DexEncodedMethod loop2 =
inspector.clazz("Test")
.method(new MethodSignature("loop2", "void", ImmutableList.of()))
.getMethod();
- IRCode irCode = loop2.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
+ IRCode irCode =
+ loop2.buildIR(appInfo, GraphLense.getIdentityLense(), TEST_OPTIONS, Origin.unknown());
TypeAnalysis analysis = new TypeAnalysis(appInfo, loop2, irCode);
analysis.forEach((v, l) -> {
if (l.isClassTypeLatticeElement()) {
@@ -226,12 +232,13 @@
// move-exception
private static void tryCatch(AppInfo appInfo) {
- DexInspector inspector = new DexInspector(appInfo.app);
+ CodeInspector inspector = new CodeInspector(appInfo.app);
DexEncodedMethod test2 =
inspector.clazz("Test")
.method(new MethodSignature("test2_throw", "int", ImmutableList.of()))
.getMethod();
- IRCode irCode = test2.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
+ IRCode irCode =
+ test2.buildIR(appInfo, GraphLense.getIdentityLense(), TEST_OPTIONS, Origin.unknown());
TypeAnalysis analysis = new TypeAnalysis(appInfo, test2, irCode);
analysis.forEach((v, l) -> {
if (l.isClassTypeLatticeElement()) {
@@ -244,7 +251,7 @@
// One very complicated example.
private static void typeConfusion(AppInfo appInfo) {
- DexInspector inspector = new DexInspector(appInfo.app);
+ CodeInspector inspector = new CodeInspector(appInfo.app);
DexEncodedMethod method =
inspector.clazz("TestObject")
.method(
@@ -257,14 +264,15 @@
ConstString.class, new ClassTypeLatticeElement(appInfo.dexItemFactory.stringType, false),
CheckCast.class, new ClassTypeLatticeElement(test, true),
NewInstance.class, new ClassTypeLatticeElement(test, false));
- IRCode irCode = method.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
+ IRCode irCode =
+ method.buildIR(appInfo, GraphLense.getIdentityLense(), TEST_OPTIONS, Origin.unknown());
TypeAnalysis analysis = new TypeAnalysis(appInfo, method, irCode);
analysis.forEach((v, l) -> verifyTypeEnvironment(expectedLattices, v, l));
}
// One more complicated example.
private static void typeConfusion5(AppInfo appInfo) {
- DexInspector inspector = new DexInspector(appInfo.app);
+ CodeInspector inspector = new CodeInspector(appInfo.app);
DexEncodedMethod method =
inspector.clazz("TestObject")
.method(
@@ -275,7 +283,8 @@
ConstString.class, new ClassTypeLatticeElement(appInfo.dexItemFactory.stringType, false),
InstanceOf.class, PRIMITIVE,
StaticGet.class, new ClassTypeLatticeElement(test, true));
- IRCode irCode = method.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
+ IRCode irCode =
+ method.buildIR(appInfo, GraphLense.getIdentityLense(), TEST_OPTIONS, Origin.unknown());
TypeAnalysis analysis = new TypeAnalysis(appInfo, method, irCode);
analysis.forEach((v, l) -> verifyTypeEnvironment(expectedLattices, v, l));
}
diff --git a/src/test/java/com/android/tools/r8/ir/callgraph/CycleEliminationTest.java b/src/test/java/com/android/tools/r8/ir/callgraph/CycleEliminationTest.java
new file mode 100644
index 0000000..56699e3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/callgraph/CycleEliminationTest.java
@@ -0,0 +1,214 @@
+// 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.ir.callgraph;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.ir.conversion.CallGraph.CycleEliminator;
+import com.android.tools.r8.ir.conversion.CallGraph.Node;
+import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.function.BooleanSupplier;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+public class CycleEliminationTest extends TestBase {
+
+ private static class Configuration {
+
+ final Collection<Node> nodes;
+ final Set<Node> forceInline;
+ final BooleanSupplier test;
+
+ Configuration(Collection<Node> nodes, Set<Node> forceInline, BooleanSupplier test) {
+ this.nodes = nodes;
+ this.forceInline = forceInline;
+ this.test = test;
+ }
+ }
+
+ private DexItemFactory dexItemFactory = new DexItemFactory();
+
+ @Rule public final ExpectedException exception = ExpectedException.none();
+
+ @Test
+ public void testSimpleCycle() {
+ Node method = createNode("n1");
+ Node forceInlinedMethod = createForceInlinedNode("n2");
+
+ Iterable<Collection<Node>> orderings =
+ ImmutableList.of(
+ ImmutableList.of(method, forceInlinedMethod),
+ ImmutableList.of(forceInlinedMethod, method));
+
+ for (Collection<Node> nodes : orderings) {
+ // Create a cycle between the two nodes.
+ method.addCallee(forceInlinedMethod);
+ forceInlinedMethod.addCallee(method);
+
+ // Check that the cycle eliminator finds the cycle.
+ CycleEliminator cycleEliminator = new CycleEliminator(nodes, new InternalOptions());
+ assertEquals(1, cycleEliminator.breakCycles());
+
+ // The edge from method to forceInlinedMethod should be removed to ensure that force inlining
+ // will work.
+ assertTrue(forceInlinedMethod.isLeaf());
+
+ // Check that the cycle eliminator agrees that there are no more cycles left.
+ assertEquals(0, cycleEliminator.breakCycles());
+ }
+ }
+
+ @Test
+ public void testSimpleCycleWithCyclicForceInlining() {
+ Node method = createForceInlinedNode("n1");
+ Node forceInlinedMethod = createForceInlinedNode("n2");
+
+ // Create a cycle between the two nodes.
+ method.addCallee(forceInlinedMethod);
+ forceInlinedMethod.addCallee(method);
+
+ CycleEliminator cycleEliminator =
+ new CycleEliminator(ImmutableList.of(method, forceInlinedMethod), new InternalOptions());
+
+ exception.expect(CompilationError.class);
+ exception.expectMessage(CycleEliminator.CYCLIC_FORCE_INLINING_MESSAGE);
+
+ // Should throw because force inlining will fail.
+ cycleEliminator.breakCycles();
+ }
+
+ @Test
+ public void testGraphWithNestedCycles() {
+ Node n1 = createNode("n1");
+ Node n2 = createNode("n2");
+ Node n3 = createNode("n3");
+
+ BooleanSupplier canInlineN1 =
+ () -> {
+ // The node n1 should be force inlined into n2 and n3, so these edges must be kept.
+ assertTrue(n2.hasCallee(n1));
+ assertTrue(n3.hasCallee(n1));
+ // Furthermore, the edge from n1 to n2 must be removed.
+ assertFalse(n1.hasCallee(n2));
+ return true;
+ };
+
+ BooleanSupplier canInlineN3 =
+ () -> {
+ // The node n3 should be force inlined into n2, so this edge must be kept.
+ assertTrue(n2.hasCallee(n3));
+ // Furthermore, one of the edges n1 -> n2 and n3 -> n1 must be removed.
+ assertFalse(n1.hasCallee(n2) && n3.hasCallee(n1));
+ return true;
+ };
+
+ BooleanSupplier canInlineN1N3 =
+ () -> {
+ // The edge n1 -> n2 must be removed.
+ assertFalse(n1.hasCallee(n2));
+ // Check that both can be force inlined.
+ return canInlineN1.getAsBoolean() && canInlineN3.getAsBoolean();
+ };
+
+ List<Collection<Node>> orderings =
+ ImmutableList.of(
+ ImmutableList.of(n1, n2, n3),
+ ImmutableList.of(n1, n3, n2),
+ ImmutableList.of(n2, n1, n3),
+ ImmutableList.of(n2, n3, n1),
+ ImmutableList.of(n3, n1, n2),
+ ImmutableList.of(n3, n2, n1));
+
+ List<Configuration> configurations = new ArrayList<>();
+ // All orderings, where no methods are marked as force inline.
+ orderings
+ .stream()
+ .map(ordering -> new Configuration(ordering, ImmutableSet.of(), null))
+ .forEach(configurations::add);
+ // All orderings, where n1 is marked as force inline
+ // (the configuration where n2 is marked as force inline is symmetric).
+ orderings
+ .stream()
+ .map(ordering -> new Configuration(ordering, ImmutableSet.of(n1), canInlineN1))
+ .forEach(configurations::add);
+ // All orderings, where n3 is marked as force inline.
+ orderings
+ .stream()
+ .map(ordering -> new Configuration(ordering, ImmutableSet.of(n3), canInlineN3))
+ .forEach(configurations::add);
+ // All orderings, where n1 and n3 are marked as force inline.
+ orderings
+ .stream()
+ .map(ordering -> new Configuration(ordering, ImmutableSet.of(n1, n3), canInlineN1N3))
+ .forEach(configurations::add);
+
+ for (Configuration configuration : configurations) {
+ // Create a cycle between the three nodes.
+ n1.addCallee(n2);
+ n2.addCallee(n3);
+ n3.addCallee(n1);
+
+ // Create a cycle in the graph between node n1 and n2.
+ n2.addCallee(n1);
+
+ for (Node node : configuration.nodes) {
+ if (configuration.forceInline.contains(node)) {
+ node.method.markForceInline();
+ } else {
+ node.method.unsetForceInline();
+ }
+ }
+
+ // Check that the cycle eliminator finds the cycles.
+ CycleEliminator cycleEliminator =
+ new CycleEliminator(configuration.nodes, new InternalOptions());
+ int numberOfCycles = cycleEliminator.breakCycles();
+ if (numberOfCycles == 1) {
+ // If only one cycle was removed, then it must be the edge from n1 -> n2 that was removed.
+ assertTrue(n1.isLeaf());
+ } else {
+ // Check that the cycle eliminator found both cycles.
+ assertEquals(2, numberOfCycles);
+ }
+
+ // Check that the cycle eliminator agrees that there are no more cycles left.
+ assertEquals(0, cycleEliminator.breakCycles());
+
+ // Check that force inlining is guaranteed to succeed.
+ if (configuration.test != null) {
+ assertTrue(configuration.test.getAsBoolean());
+ }
+ }
+ }
+
+ private Node createNode(String methodName) {
+ DexMethod signature =
+ dexItemFactory.createMethod(
+ dexItemFactory.objectType,
+ dexItemFactory.createProto(dexItemFactory.voidType),
+ methodName);
+ return new Node(new DexEncodedMethod(signature, null, null, null, null));
+ }
+
+ private Node createForceInlinedNode(String methodName) {
+ Node node = createNode(methodName);
+ node.method.markForceInline();
+ return node;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/CovariantReturnTypeAnnotationTransformerTest.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/CovariantReturnTypeAnnotationTransformerTest.java
index 1f4d2ba..f1f2842 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/annotations/CovariantReturnTypeAnnotationTransformerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/CovariantReturnTypeAnnotationTransformerTest.java
@@ -4,14 +4,16 @@
package com.android.tools.r8.ir.desugar.annotations;
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertThat;
import com.android.tools.r8.AsmTestBase;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import java.util.Collections;
import org.junit.Assert;
import org.junit.Test;
@@ -153,47 +155,47 @@
}
private void checkPresenceOfSyntheticMethods(AndroidApp output) throws Exception {
- DexInspector inspector = new DexInspector(output);
+ CodeInspector inspector = new CodeInspector(output);
// Get classes A, B, and C.
- DexInspector.ClassSubject clazzA = inspector.clazz(A.class.getCanonicalName());
+ ClassSubject clazzA = inspector.clazz(A.class.getCanonicalName());
assertThat(clazzA, isPresent());
- DexInspector.ClassSubject clazzB = inspector.clazz(B.class.getCanonicalName());
+ ClassSubject clazzB = inspector.clazz(B.class.getCanonicalName());
assertThat(clazzB, isPresent());
- DexInspector.ClassSubject clazzC = inspector.clazz(C.class.getCanonicalName());
+ ClassSubject clazzC = inspector.clazz(C.class.getCanonicalName());
assertThat(clazzC, isPresent());
// Check that the original methods are there, and that they are not synthetic.
- DexInspector.MethodSubject methodA =
+ MethodSubject methodA =
clazzB.method(A.class.getCanonicalName(), "method", Collections.emptyList());
assertThat(methodA, isPresent());
Assert.assertTrue(!methodA.getMethod().isSyntheticMethod());
- DexInspector.MethodSubject methodB =
+ MethodSubject methodB =
clazzB.method(A.class.getCanonicalName(), "method", Collections.emptyList());
assertThat(methodB, isPresent());
Assert.assertTrue(!methodB.getMethod().isSyntheticMethod());
- DexInspector.MethodSubject methodC =
+ MethodSubject methodC =
clazzC.method(A.class.getCanonicalName(), "method", Collections.emptyList());
assertThat(methodC, isPresent());
Assert.assertTrue(!methodC.getMethod().isSyntheticMethod());
// Check that a synthetic method has been added to class B.
- DexInspector.MethodSubject methodB2 =
+ MethodSubject methodB2 =
clazzB.method(B.class.getCanonicalName(), "method", Collections.emptyList());
assertThat(methodB2, isPresent());
Assert.assertTrue(methodB2.getMethod().isSyntheticMethod());
// Check that two synthetic methods have been added to class C.
- DexInspector.MethodSubject methodC2 =
+ MethodSubject methodC2 =
clazzC.method(B.class.getCanonicalName(), "method", Collections.emptyList());
assertThat(methodC2, isPresent());
Assert.assertTrue(methodC2.getMethod().isSyntheticMethod());
- DexInspector.MethodSubject methodC3 =
+ MethodSubject methodC3 =
clazzC.method(C.class.getCanonicalName(), "method", Collections.emptyList());
assertThat(methodC3, isPresent());
Assert.assertTrue(methodC3.getMethod().isSyntheticMethod());
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/CDump.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/CDump.java
index bc06250..30cf603 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/CDump.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/CDump.java
@@ -4,7 +4,9 @@
package com.android.tools.r8.ir.desugar.annotations.version2;
-import static com.android.tools.r8.ir.desugar.annotations.CovariantReturnTypeAnnotationTransformerTest.*;
+import static com.android.tools.r8.ir.desugar.annotations.CovariantReturnTypeAnnotationTransformerTest.CRTS_SIMPLE_NAME;
+import static com.android.tools.r8.ir.desugar.annotations.CovariantReturnTypeAnnotationTransformerTest.CRT_NAME;
+import static com.android.tools.r8.ir.desugar.annotations.CovariantReturnTypeAnnotationTransformerTest.PACKAGE_NAME;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassWriter;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/B87341268.java b/src/test/java/com/android/tools/r8/ir/optimize/B87341268.java
index 3f7bcfb..ac3952d 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/B87341268.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/B87341268.java
@@ -3,20 +3,20 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.optimize;
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.junit.Assert.assertThat;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import org.junit.Test;
public class B87341268 extends TestBase {
@Test
public void test() throws Exception {
AndroidApp app = compileWithD8(readClasses(TestClass.class));
- DexInspector inspector = new DexInspector(app);
+ CodeInspector inspector = new CodeInspector(app);
ClassSubject clazz = inspector.clazz(TestClass.class);
assertThat(clazz, isPresent());
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ConstraintWithTargetTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ConstraintWithTargetTest.java
new file mode 100644
index 0000000..33994b1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/ConstraintWithTargetTest.java
@@ -0,0 +1,144 @@
+// 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.ir.optimize;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Timing;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class ConstraintWithTargetTest {
+ private static DexItemFactory factory;
+ private static AppInfoWithSubtyping appInfo;
+
+ @BeforeClass
+ public static void makeAppInfo() throws Exception {
+ InternalOptions options = new InternalOptions();
+ DexApplication application =
+ new ApplicationReader(
+ AndroidApp.builder()
+ .addLibraryFiles(ToolHelper.getDefaultAndroidJar())
+ .build(),
+ options,
+ new Timing(ConstraintWithTargetTest.class.getName()))
+ .read()
+ .toDirect();
+ factory = options.itemFactory;
+ appInfo = new AppInfoWithSubtyping(application);
+ }
+
+ private ConstraintWithTarget never() {
+ return ConstraintWithTarget.NEVER;
+ }
+
+ private ConstraintWithTarget always() {
+ return ConstraintWithTarget.ALWAYS;
+ }
+
+ private ConstraintWithTarget element(Constraint constraint, DexType type) {
+ return new ConstraintWithTarget(constraint, type);
+ }
+
+ private ConstraintWithTarget meet(ConstraintWithTarget e1, ConstraintWithTarget e2) {
+ return ConstraintWithTarget.meet(e1, e2, appInfo);
+ }
+
+ @Test
+ public void meetNeverIsNever() {
+ assertEquals(never(),
+ meet(never(), always()));
+ assertEquals(never(),
+ meet(always(), never()));
+ assertEquals(never(),
+ meet(never(), element(Constraint.SAMECLASS, factory.objectType)));
+ }
+
+ @Test
+ public void meetAlwaysIsUnit() {
+ ConstraintWithTarget o = element(Constraint.SUBCLASS, factory.objectType);
+ assertEquals(o,
+ meet(always(), o));
+ assertEquals(o,
+ meet(o, always()));
+ }
+
+ @Test
+ public void withSameTarget() {
+ DexType s = factory.createType("Ljava/lang/String;");
+ ConstraintWithTarget c0 = element(Constraint.SAMECLASS, s);
+ ConstraintWithTarget c1 = element(Constraint.PACKAGE, s);
+ ConstraintWithTarget c2 = element(Constraint.SUBCLASS, s);
+ assertEquals(c0,
+ meet(c1, c0));
+ assertEquals(c0,
+ meet(c0, c2));
+ assertEquals(c1,
+ meet(c2, c1));
+ }
+
+ @Test
+ public void withDifferentTarget() {
+ DexType s = factory.createType("Ljava/lang/String;");
+ DexType b = factory.createType("Ljava/lang/StringBuilder;");
+ ConstraintWithTarget c1 = element(Constraint.SAMECLASS, s);
+ ConstraintWithTarget c2 = element(Constraint.SAMECLASS, b);
+ assertEquals(never(),
+ meet(c1, c2));
+
+ ConstraintWithTarget c0 = element(Constraint.PACKAGE, factory.objectType);
+ assertEquals(c1,
+ meet(c0, c1));
+ assertEquals(c2,
+ meet(c0, c2));
+
+ c0 = element(Constraint.SUBCLASS, factory.objectType);
+ assertEquals(c1,
+ meet(c0, c1));
+ assertEquals(c2,
+ meet(c0, c2));
+
+ c1 = element(Constraint.PACKAGE, s);
+ c2 = element(Constraint.PACKAGE, b);
+ assertEquals(c1,
+ meet(c0, c1));
+ assertEquals(c2,
+ meet(c0, c2));
+ assertEquals(c1,
+ meet(c1, c2));
+ assertEquals(c2,
+ meet(c2, c1));
+
+ DexType t = factory.createType("Ljava/lang/reflect/Type;");
+ DexType c = factory.createType("Ljava/lang/Class;");
+ c1 = element(Constraint.SUBCLASS, t);
+ c2 = element(Constraint.SUBCLASS, c);
+ assertEquals(c2,
+ meet(c1, c2));
+ assertEquals(c2,
+ meet(c2, c1));
+ }
+
+
+ @Test
+ public void b111080693() {
+ ConstraintWithTarget c1 =
+ element(Constraint.SUBCLASS, factory.createType("Ljava/lang/Class;"));
+ ConstraintWithTarget c2 =
+ element(Constraint.PACKAGE, factory.createType("Ljava/lang/reflect/Type;"));
+ assertEquals(never(),
+ meet(c1, c2));
+ }
+
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/MemberValuePropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/MemberValuePropagationTest.java
index a5c7093..0eac6bb 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/MemberValuePropagationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/MemberValuePropagationTest.java
@@ -18,9 +18,9 @@
import com.android.tools.r8.code.SputObject;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -43,7 +43,7 @@
@Test
public void testWriteOnlyField_putObject_gone() throws Exception {
Path processedApp = runR8(EXAMPLE_KEEP);
- DexInspector inspector = new DexInspector(processedApp);
+ CodeInspector inspector = new CodeInspector(processedApp);
ClassSubject clazz = inspector.clazz(WRITE_ONLY_FIELD + ".WriteOnlyCls");
clazz.forAllMethods(
methodSubject -> {
@@ -71,7 +71,7 @@
@Test
public void testWriteOnlyField_dontoptimize() throws Exception {
Path processedApp = runR8(DONT_OPTIMIZE);
- DexInspector inspector = new DexInspector(processedApp);
+ CodeInspector inspector = new CodeInspector(processedApp);
ClassSubject clazz = inspector.clazz(WRITE_ONLY_FIELD + ".WriteOnlyCls");
clazz.forAllMethods(
methodSubject -> {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
index 7c90976..b1647cc 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.InstancePut;
import com.android.tools.r8.ir.code.Instruction;
@@ -26,9 +27,9 @@
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import java.util.function.Consumer;
import org.junit.Test;
@@ -46,9 +47,10 @@
new ApplicationReader(app, TEST_OPTIONS, new Timing("NonNullMarkerTest.appReader"))
.read().toDirect();
AppInfo appInfo = new AppInfo(dexApplication);
- DexInspector dexInspector = new DexInspector(appInfo.app);
- DexEncodedMethod foo = dexInspector.clazz(testClass.getName()).method(signature).getMethod();
- IRCode irCode = foo.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
+ CodeInspector codeInspector = new CodeInspector(appInfo.app);
+ DexEncodedMethod foo = codeInspector.clazz(testClass.getName()).method(signature).getMethod();
+ IRCode irCode =
+ foo.buildIR(appInfo, GraphLense.getIdentityLense(), TEST_OPTIONS, Origin.unknown());
checkCountOfNonNull(irCode, 0);
NonNullTracker nonNullTracker = new NonNullTracker();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
index 5dc7f23..dd7756b 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
@@ -27,11 +27,12 @@
import com.android.tools.r8.code.Throw;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.nio.file.Files;
@@ -55,17 +56,26 @@
private static final String DEFAULT_DEX_FILENAME = "classes.dex";
private static final String DEFAULT_MAP_FILENAME = "proguard.map";
- @Parameters(name = "{0}")
+ @Parameters(name = "{0}, minification={1}, allowaccessmodification={2}")
public static Collection<Object[]> data() {
- return Arrays.asList(new Object[][]{{"Inlining"}});
+ return Arrays.asList(new Object[][]{
+ {"Inlining", false, false},
+ {"Inlining", false, true},
+ {"Inlining", true, false},
+ {"Inlining", true, true}
+ });
}
private final String name;
private final String keepRulesFile;
+ private final boolean minification;
+ private final boolean allowAccessModification;
- public R8InliningTest(String name) {
+ public R8InliningTest(String name, boolean minification, boolean allowAccessModification) {
this.name = name.toLowerCase();
this.keepRulesFile = ToolHelper.EXAMPLES_DIR + this.name + "/keep-rules.txt";
+ this.minification = minification;
+ this.allowAccessModification = allowAccessModification;
}
private Path getInputFile() {
@@ -81,7 +91,7 @@
}
private String getGeneratedProguardMap() throws IOException {
- Path mapFile = Paths.get(temp.getRoot().getCanonicalPath(), DEFAULT_MAP_FILENAME);
+ Path mapFile = temp.getRoot().toPath().resolve(DEFAULT_MAP_FILENAME);
if (Files.exists(mapFile)) {
return mapFile.toAbsolutePath().toString();
}
@@ -94,19 +104,23 @@
@Before
public void generateR8Version() throws Exception {
Path out = temp.getRoot().toPath();
- R8Command command =
+ Path mapFile = out.resolve(DEFAULT_MAP_FILENAME);
+ R8Command.Builder commandBuilder =
R8Command.builder()
.addProgramFiles(getInputFile())
.setMinApiLevel(AndroidApiLevel.M.getLevel())
.setOutput(out, OutputMode.DexIndexed)
- .addProguardConfigurationFiles(Paths.get(keepRulesFile))
- .build();
- // TODO(62048823): Enable minification.
- ToolHelper.runR8(command, o -> {
- o.enableMinification = false;
+ .setProguardMapOutputPath(mapFile)
+ .addProguardConfigurationFiles(Paths.get(keepRulesFile));
+ if (allowAccessModification) {
+ commandBuilder.addProguardConfiguration(
+ ImmutableList.of("-allowaccessmodification"), Origin.unknown());
+ }
+ ToolHelper.runR8(commandBuilder.build(), o -> {
+ o.enableMinification = minification;
});
- String artOutput = ToolHelper.runArtNoVerificationErrors(out + "/classes.dex",
- "inlining.Inlining");
+ String artOutput =
+ ToolHelper.runArtNoVerificationErrors(out + "/classes.dex", "inlining.Inlining");
// Compare result with Java to make sure we have the same behavior.
ProcessResult javaResult = ToolHelper.runJava(getInputFile(), "inlining.Inlining");
@@ -131,14 +145,14 @@
private void dump(Path path, String title) throws Throwable {
System.out.println(title + ":");
- DexInspector inspector = new DexInspector(path.toAbsolutePath());
+ CodeInspector inspector = new CodeInspector(path.toAbsolutePath());
inspector.clazz("inlining.Inlining").forAllMethods(m -> dump(m.getMethod()));
System.out.println(title + " size: " + Files.size(path));
}
@Test
public void checkNoInvokes() throws Throwable {
- DexInspector inspector = new DexInspector(getGeneratedDexFile().toAbsolutePath(),
+ CodeInspector inspector = new CodeInspector(getGeneratedDexFile().toAbsolutePath(),
getGeneratedProguardMap());
ClassSubject clazz = inspector.clazz("inlining.Inlining");
@@ -175,21 +189,25 @@
@Test
public void invokeOnNullableReceiver() throws Exception {
- DexInspector inspector =
- new DexInspector(getGeneratedDexFile().toAbsolutePath(), getGeneratedProguardMap());
+ CodeInspector inspector =
+ new CodeInspector(getGeneratedDexFile().toAbsolutePath(), getGeneratedProguardMap());
ClassSubject clazz = inspector.clazz("inlining.Nullability");
MethodSubject m = clazz.method("int", "inlinable", ImmutableList.of("inlining.A"));
- assertTrue(m.isPresent());
- DexCode code = m.getMethod().getCode().asDexCode();
- checkInstructions(
- code,
- ImmutableList.of(
- Iget.class,
- // TODO(b/70572176): below two could be replaced with Iget via inlining
- InvokeVirtual.class,
- MoveResult.class,
- AddInt2Addr.class,
- Return.class));
+ DexCode code;
+ if (allowAccessModification) {
+ assertFalse(m.isPresent());
+ } else {
+ assertTrue(m.isPresent());
+ code = m.getMethod().getCode().asDexCode();
+ checkInstructions(
+ code,
+ ImmutableList.of(
+ Iget.class,
+ InvokeVirtual.class,
+ MoveResult.class,
+ AddInt2Addr.class,
+ Return.class));
+ }
m = clazz.method("int", "notInlinable", ImmutableList.of("inlining.A"));
assertTrue(m.isPresent());
@@ -239,8 +257,8 @@
@Test
public void invokeOnNonNullReceiver() throws Exception {
- DexInspector inspector =
- new DexInspector(getGeneratedDexFile().toAbsolutePath(), getGeneratedProguardMap());
+ CodeInspector inspector =
+ new CodeInspector(getGeneratedDexFile().toAbsolutePath(), getGeneratedProguardMap());
ClassSubject clazz = inspector.clazz("inlining.Nullability");
MethodSubject m = clazz.method("int", "conditionalOperator", ImmutableList.of("inlining.A"));
assertTrue(m.isPresent());
@@ -249,10 +267,7 @@
code,
ImmutableList.of(
IfEqz.class,
- // TODO(b/70794661): below two could be replaced with Iget via inlining if access
- // modification is allowed.
- InvokeVirtual.class,
- MoveResult.class,
+ Iget.class,
Goto.class,
Const4.class,
Return.class));
@@ -273,9 +288,7 @@
builder.add(Const4.class);
builder.add(IfEqz.class);
builder.add(IfEqz.class);
- // TODO(b/70794661): below two could be replaced with Iget via inlining
- builder.add(InvokeVirtual.class);
- builder.add(MoveResult.class);
+ builder.add(Iget.class);
builder.add(MulInt2Addr.class);
builder.add(Return.class);
builder.add(PackedSwitchPayload.class);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/SimplifyIfNotNullTest.java b/src/test/java/com/android/tools/r8/ir/optimize/SimplifyIfNotNullTest.java
index 525e21b..ad0ae87 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/SimplifyIfNotNullTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/SimplifyIfNotNullTest.java
@@ -17,7 +17,7 @@
import com.android.tools.r8.ir.optimize.nonnull.NonNullAfterInvoke;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.util.Arrays;
import java.util.List;
@@ -32,10 +32,10 @@
AndroidApp app = buildAndroidApp(ToolHelper.getClassAsBytes(testClass));
AndroidApp r8Result = compileWithR8(app,
"-keep class " + testClass.getCanonicalName() + " { *; }");
- DexInspector dexInspector = new DexInspector(r8Result);
+ CodeInspector codeInspector = new CodeInspector(r8Result);
for (MethodSignature signature : signatures) {
DexEncodedMethod method =
- dexInspector.clazz(testClass.getName()).method(signature).getMethod();
+ codeInspector.clazz(testClass.getName()).method(signature).getMethod();
long count = Arrays.stream(method.getCode().asDexCode().instructions)
.filter(SimplifyIfNotNullTest::isIf).count();
assertEquals(0, count);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
index 3239191..793da3c 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
@@ -29,6 +29,7 @@
import com.android.tools.r8.ir.optimize.classinliner.code.C;
import com.android.tools.r8.ir.optimize.classinliner.code.CodeTestClass;
import com.android.tools.r8.ir.optimize.classinliner.invalidroot.InvalidRootsTestClass;
+import com.android.tools.r8.ir.optimize.classinliner.lambdas.LambdasTestClass;
import com.android.tools.r8.ir.optimize.classinliner.trivial.ClassWithFinal;
import com.android.tools.r8.ir.optimize.classinliner.trivial.CycleReferenceAB;
import com.android.tools.r8.ir.optimize.classinliner.trivial.CycleReferenceBA;
@@ -44,9 +45,9 @@
import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.Sets;
import java.nio.file.Path;
import java.util.Collections;
@@ -80,7 +81,7 @@
String artOutput = runOnArt(app, TrivialTestClass.class);
assertEquals(javaOutput, artOutput);
- DexInspector inspector = new DexInspector(app);
+ CodeInspector inspector = new CodeInspector(app);
ClassSubject clazz = inspector.clazz(TrivialTestClass.class);
assertEquals(
@@ -152,7 +153,7 @@
String artOutput = runOnArt(app, BuildersTestClass.class);
assertEquals(javaOutput, artOutput);
- DexInspector inspector = new DexInspector(app);
+ CodeInspector inspector = new CodeInspector(app);
ClassSubject clazz = inspector.clazz(BuildersTestClass.class);
assertEquals(
@@ -237,7 +238,7 @@
String artOutput = runOnArt(app, CodeTestClass.class);
assertEquals(javaOutput, artOutput);
- DexInspector inspector = new DexInspector(app);
+ CodeInspector inspector = new CodeInspector(app);
ClassSubject clazz = inspector.clazz(C.class);
assertEquals(
@@ -273,7 +274,7 @@
String artOutput = runOnArt(app, InvalidRootsTestClass.class);
assertEquals(javaOutput, artOutput);
- DexInspector inspector = new DexInspector(app);
+ CodeInspector inspector = new CodeInspector(app);
ClassSubject clazz = inspector.clazz(InvalidRootsTestClass.class);
assertEquals(
@@ -301,6 +302,37 @@
assertFalse(inspector.clazz(InvalidRootsTestClass.B.class).isPresent());
}
+ @Test
+ public void testDesugaredLambdas() throws Exception {
+ byte[][] classes = {
+ ToolHelper.getClassAsBytes(LambdasTestClass.class),
+ ToolHelper.getClassAsBytes(LambdasTestClass.Iface.class),
+ ToolHelper.getClassAsBytes(LambdasTestClass.IfaceUtil.class),
+ };
+ AndroidApp app = runR8(buildAndroidApp(classes), LambdasTestClass.class);
+
+ String javaOutput = runOnJava(LambdasTestClass.class);
+ String artOutput = runOnArt(app, LambdasTestClass.class);
+ assertEquals(javaOutput, artOutput);
+
+ CodeInspector inspector = new CodeInspector(app);
+ ClassSubject clazz = inspector.clazz(LambdasTestClass.class);
+
+ assertEquals(
+ Sets.newHashSet(
+ "java.lang.StringBuilder"),
+ collectTypes(clazz, "testStatelessLambda", "void"));
+
+ assertEquals(
+ Sets.newHashSet(
+ "java.lang.StringBuilder"),
+ collectTypes(clazz, "testStatefulLambda", "void", "java.lang.String", "java.lang.String"));
+
+ assertEquals(0,
+ inspector.allClasses().stream()
+ .filter(ClassSubject::isSynthesizedJavaLambdaClass).count());
+ }
+
private Set<String> collectTypes(
ClassSubject clazz, String methodName, String retValue, String... params) {
return Stream.concat(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/lambdas/LambdasTestClass.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/lambdas/LambdasTestClass.java
new file mode 100644
index 0000000..130f895
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/lambdas/LambdasTestClass.java
@@ -0,0 +1,54 @@
+// 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.ir.optimize.classinliner.lambdas;
+
+public class LambdasTestClass {
+ private static int ID = 0;
+
+ private static int nextInt() {
+ return ID++;
+ }
+
+ private static String next() {
+ return Integer.toString(nextInt());
+ }
+
+ public interface Iface {
+ String foo();
+ }
+
+ public static class IfaceUtil {
+ public static void act(Iface iface) {
+ System.out.println("" + next() + "> " + iface.foo());
+ }
+ }
+
+ public static void main(String[] args) {
+ LambdasTestClass test = new LambdasTestClass();
+ test.testStatelessLambda();
+ test.testStatefulLambda(next(), next());
+ }
+
+ public static String exact() {
+ return next();
+ }
+
+ public static String almost(String... s) {
+ return next();
+ }
+
+ private synchronized void testStatelessLambda() {
+ IfaceUtil.act(() -> next());
+ IfaceUtil.act(LambdasTestClass::next);
+ IfaceUtil.act(LambdasTestClass::exact);
+ IfaceUtil.act(LambdasTestClass::almost);
+ }
+
+ private synchronized void testStatefulLambda(String a, String b) {
+ IfaceUtil.act(() -> a);
+ IfaceUtil.act(() -> a + b);
+ IfaceUtil.act((a + b)::toLowerCase);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeInterfaceToInvokeVirtualTest.java b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeInterfaceToInvokeVirtualTest.java
index 71dae86..c5d6261 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeInterfaceToInvokeVirtualTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeInterfaceToInvokeVirtualTest.java
@@ -24,8 +24,8 @@
import com.android.tools.r8.ir.optimize.devirtualize.invokeinterface.Main;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.nio.file.Path;
import org.junit.Test;
@@ -67,9 +67,9 @@
Path out = temp.getRoot().toPath();
AndroidApp processedApp = runR8(originalApp, Main.class, out);
- DexInspector dexInspector = new DexInspector(processedApp);
- ClassSubject clazz = dexInspector.clazz(main);
- DexEncodedMethod m = clazz.method(DexInspector.MAIN).getMethod();
+ CodeInspector codeInspector = new CodeInspector(processedApp);
+ ClassSubject clazz = codeInspector.clazz(main);
+ DexEncodedMethod m = clazz.method(CodeInspector.MAIN).getMethod();
DexCode code = m.getCode().asDexCode();
long numOfInvokeInterface = filterInstructionKind(code, InvokeInterface.class).count();
// List#add, List#get
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlinerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlinerTest.java
index adb87e6..5cdaac8 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlinerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlinerTest.java
@@ -21,10 +21,10 @@
import com.android.tools.r8.ir.optimize.inliner.interfaces.InterfaceTargetsTestClass.IfaceNoImpl;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import java.nio.file.Path;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -57,7 +57,7 @@
String artOutput = runOnArt(app, InterfaceTargetsTestClass.class);
assertEquals(javaOutput, artOutput);
- DexInspector inspector = new DexInspector(app);
+ CodeInspector inspector = new CodeInspector(app);
ClassSubject clazz = inspector.clazz(InterfaceTargetsTestClass.class);
assertFalse(getMethodSubject(clazz,
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/b111893131/B111893131.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/b111893131/B111893131.java
new file mode 100644
index 0000000..860a913
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/b111893131/B111893131.java
@@ -0,0 +1,114 @@
+// 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.ir.optimize.outliner.b111893131;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.DexIndexedConsumer;
+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.VmTestRunner;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.code.InvokeVirtual;
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+class TestClass {
+ public interface Act {
+ // Need both builder and arg to create code snippets for outline candidates.
+ String get(StringBuilder builder, String arg);
+ }
+
+ public static void main(String[] args) {
+ System.out.println(test(new TestClass("OK").toAct(), new StringBuilder(), "1"));
+ }
+
+ // Need to pass Act and call #get to create private instance lambda$
+ private static String test(Act act, StringBuilder builder, String arg) {
+ // Outline candidate
+ builder.append(arg).append(arg).append(arg);
+ act.get(builder, "#");
+ return builder.toString();
+ }
+
+ private final String foo;
+
+ TestClass(String foo) {
+ this.foo = foo;
+ }
+
+ private Act toAct() {
+ return (builder, arg) -> {
+ // Outline candidate
+ builder.append(arg).append(arg).append(arg);
+ return foo;
+ };
+ }
+}
+
+@RunWith(VmTestRunner.class)
+public class B111893131 extends TestBase {
+
+ @Test
+ public void test() throws Exception {
+ String javaResult = runOnJava(TestClass.class);
+
+ R8Command.Builder builder = R8Command.builder();
+ builder.addProgramFiles(ToolHelper.getClassFileForTestClass(TestClass.class));
+ builder.addProgramFiles(ToolHelper.getClassFileForTestClass(TestClass.Act.class));
+ builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+ builder.setMinApiLevel(ToolHelper.getMinApiLevelForDexVm().getLevel());
+ String config = keepMainProguardConfiguration(TestClass.class);
+ builder.addProguardConfiguration(ImmutableList.of(config), Origin.unknown());
+ AndroidApp app = ToolHelper.runR8(builder.build(), options -> {
+ // To trigger outliner, set # of expected outline candidate as threshold.
+ options.outline.threshold = 2;
+ options.enableInlining = false;
+ options.enableMinification = false;
+ });
+ ProcessResult result = runOnArtRaw(app, TestClass.class);
+ assertEquals(0, result.exitCode);
+ assertEquals(javaResult, result.stdout);
+
+ CodeInspector inspector = new CodeInspector(app);
+ ClassSubject classSubject = inspector.clazz(TestClass.class);
+ assertThat(classSubject, isPresent());
+ DexClass clazz = classSubject.getDexClass();
+ clazz.forEachMethod(encodedMethod -> {
+ Code code = encodedMethod.getCode();
+ assertTrue(code.isDexCode());
+ DexCode dexCode = code.asDexCode();
+ // TODO(b/111893131): all outline candidate should be replaced with a call to outlined code.
+ verifyAbsenceOfStringBuilderAppend(dexCode.instructions);
+ });
+ }
+
+ private void verifyAbsenceOfStringBuilderAppend(Instruction[] instructions) {
+ for (Instruction instr : instructions) {
+ if (instr instanceof InvokeVirtual) {
+ InvokeVirtual invokeVirtual = (InvokeVirtual) instr;
+ DexMethod invokedMethod = invokeVirtual.getMethod();
+ if (invokedMethod.getHolder().getName().endsWith("StringBuilder")) {
+ assertNotEquals("append", invokedMethod.name.toString());
+ }
+ }
+ }
+ }
+
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
new file mode 100644
index 0000000..9be118c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
@@ -0,0 +1,272 @@
+// 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.ir.optimize.staticizer;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.VmTestRunner;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.code.InvokeDirect;
+import com.android.tools.r8.code.InvokeStatic;
+import com.android.tools.r8.code.InvokeVirtual;
+import com.android.tools.r8.code.SgetObject;
+import com.android.tools.r8.code.SputObject;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.optimize.staticizer.movetohost.CandidateConflictField;
+import com.android.tools.r8.ir.optimize.staticizer.movetohost.CandidateConflictMethod;
+import com.android.tools.r8.ir.optimize.staticizer.movetohost.CandidateOk;
+import com.android.tools.r8.ir.optimize.staticizer.movetohost.CandidateOkSideEffects;
+import com.android.tools.r8.ir.optimize.staticizer.movetohost.HostConflictField;
+import com.android.tools.r8.ir.optimize.staticizer.movetohost.HostConflictMethod;
+import com.android.tools.r8.ir.optimize.staticizer.movetohost.HostOk;
+import com.android.tools.r8.ir.optimize.staticizer.movetohost.HostOkSideEffects;
+import com.android.tools.r8.ir.optimize.staticizer.movetohost.MoveToHostTestClass;
+import com.android.tools.r8.ir.optimize.staticizer.trivial.Simple;
+import com.android.tools.r8.ir.optimize.staticizer.trivial.SimpleWithGetter;
+import com.android.tools.r8.ir.optimize.staticizer.trivial.SimpleWithParams;
+import com.android.tools.r8.ir.optimize.staticizer.trivial.SimpleWithSideEffects;
+import com.android.tools.r8.ir.optimize.staticizer.trivial.TrivialTestClass;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Streams;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(VmTestRunner.class)
+public class ClassStaticizerTest extends TestBase {
+ @Test
+ public void testTrivial() throws Exception {
+ byte[][] classes = {
+ ToolHelper.getClassAsBytes(TrivialTestClass.class),
+ ToolHelper.getClassAsBytes(Simple.class),
+ ToolHelper.getClassAsBytes(SimpleWithSideEffects.class),
+ ToolHelper.getClassAsBytes(SimpleWithParams.class),
+ ToolHelper.getClassAsBytes(SimpleWithGetter.class),
+ };
+ AndroidApp app = runR8(buildAndroidApp(classes), TrivialTestClass.class);
+
+ String javaOutput = runOnJava(TrivialTestClass.class);
+ String artOutput = runOnArt(app, TrivialTestClass.class);
+ assertEquals(javaOutput, artOutput);
+
+ CodeInspector inspector = new CodeInspector(app);
+ ClassSubject clazz = inspector.clazz(TrivialTestClass.class);
+
+ assertEquals(
+ Lists.newArrayList(
+ "STATIC: String trivial.Simple.bar(String)",
+ "STATIC: String trivial.Simple.foo()",
+ "STATIC: String trivial.TrivialTestClass.next()"),
+ references(clazz, "testSimple", "void"));
+
+ assertTrue(instanceMethods(inspector.clazz(Simple.class)).isEmpty());
+
+ assertEquals(
+ Lists.newArrayList(
+ "STATIC: String trivial.SimpleWithParams.bar(String)",
+ "STATIC: String trivial.SimpleWithParams.foo()",
+ "STATIC: String trivial.TrivialTestClass.next()"),
+ references(clazz, "testSimpleWithParams", "void"));
+
+ assertTrue(instanceMethods(inspector.clazz(SimpleWithParams.class)).isEmpty());
+
+ assertEquals(
+ Lists.newArrayList(
+ "STATIC: String trivial.SimpleWithSideEffects.bar(String)",
+ "STATIC: String trivial.SimpleWithSideEffects.foo()",
+ "STATIC: String trivial.TrivialTestClass.next()",
+ "trivial.SimpleWithSideEffects trivial.SimpleWithSideEffects.INSTANCE",
+ "trivial.SimpleWithSideEffects trivial.SimpleWithSideEffects.INSTANCE"),
+ references(clazz, "testSimpleWithSideEffects", "void"));
+
+ assertTrue(instanceMethods(inspector.clazz(SimpleWithSideEffects.class)).isEmpty());
+
+ // TODO(b/111832046): add support for singleton instance getters.
+ assertEquals(
+ Lists.newArrayList(
+ "STATIC: String trivial.TrivialTestClass.next()",
+ "VIRTUAL: String trivial.SimpleWithGetter.bar(String)",
+ "VIRTUAL: String trivial.SimpleWithGetter.foo()",
+ "trivial.SimpleWithGetter trivial.SimpleWithGetter.INSTANCE",
+ "trivial.SimpleWithGetter trivial.SimpleWithGetter.INSTANCE"),
+ references(clazz, "testSimpleWithGetter", "void"));
+
+ assertFalse(instanceMethods(inspector.clazz(SimpleWithGetter.class)).isEmpty());
+ }
+
+ @Test
+ public void testMoveToHost() throws Exception {
+ byte[][] classes = {
+ ToolHelper.getClassAsBytes(MoveToHostTestClass.class),
+ ToolHelper.getClassAsBytes(HostOk.class),
+ ToolHelper.getClassAsBytes(CandidateOk.class),
+ ToolHelper.getClassAsBytes(HostOkSideEffects.class),
+ ToolHelper.getClassAsBytes(CandidateOkSideEffects.class),
+ ToolHelper.getClassAsBytes(HostConflictMethod.class),
+ ToolHelper.getClassAsBytes(CandidateConflictMethod.class),
+ ToolHelper.getClassAsBytes(HostConflictField.class),
+ ToolHelper.getClassAsBytes(CandidateConflictField.class),
+ };
+ AndroidApp app = runR8(buildAndroidApp(classes), MoveToHostTestClass.class);
+
+ String javaOutput = runOnJava(MoveToHostTestClass.class);
+ String artOutput = runOnArt(app, MoveToHostTestClass.class);
+ assertEquals(javaOutput, artOutput);
+
+ CodeInspector inspector = new CodeInspector(app);
+ ClassSubject clazz = inspector.clazz(MoveToHostTestClass.class);
+
+ assertEquals(
+ Lists.newArrayList(
+ "STATIC: String movetohost.HostOk.bar(String)",
+ "STATIC: String movetohost.HostOk.foo()",
+ "STATIC: String movetohost.MoveToHostTestClass.next()",
+ "STATIC: String movetohost.MoveToHostTestClass.next()",
+ "STATIC: void movetohost.HostOk.blah(String)"),
+ references(clazz, "testOk", "void"));
+
+ assertFalse(inspector.clazz(CandidateOk.class).isPresent());
+
+ assertEquals(
+ Lists.newArrayList(
+ "STATIC: String movetohost.HostOkSideEffects.bar(String)",
+ "STATIC: String movetohost.HostOkSideEffects.foo()",
+ "STATIC: String movetohost.MoveToHostTestClass.next()",
+ "movetohost.HostOkSideEffects movetohost.HostOkSideEffects.INSTANCE",
+ "movetohost.HostOkSideEffects movetohost.HostOkSideEffects.INSTANCE"),
+ references(clazz, "testOkSideEffects", "void"));
+
+ assertFalse(inspector.clazz(CandidateOkSideEffects.class).isPresent());
+
+ assertEquals(
+ Lists.newArrayList(
+ "DIRECT: void movetohost.HostConflictMethod.<init>()",
+ "STATIC: String movetohost.CandidateConflictMethod.bar(String)",
+ "STATIC: String movetohost.CandidateConflictMethod.foo()",
+ "STATIC: String movetohost.MoveToHostTestClass.next()",
+ "STATIC: String movetohost.MoveToHostTestClass.next()",
+ "VIRTUAL: String movetohost.HostConflictMethod.bar(String)"),
+ references(clazz, "testConflictMethod", "void"));
+
+ assertTrue(inspector.clazz(CandidateConflictMethod.class).isPresent());
+
+ assertEquals(
+ Lists.newArrayList(
+ "DIRECT: void movetohost.HostConflictField.<init>()",
+ "STATIC: String movetohost.CandidateConflictField.bar(String)",
+ "STATIC: String movetohost.CandidateConflictField.foo()",
+ "STATIC: String movetohost.MoveToHostTestClass.next()",
+ "String movetohost.CandidateConflictField.field"),
+ references(clazz, "testConflictField", "void"));
+
+ assertTrue(inspector.clazz(CandidateConflictMethod.class).isPresent());
+ }
+
+ private List<String> instanceMethods(ClassSubject clazz) {
+ assertNotNull(clazz);
+ assertTrue(clazz.isPresent());
+ return Streams.stream(clazz.getDexClass().methods())
+ .filter(method -> !method.isStaticMethod())
+ .map(method -> method.method.toSourceString())
+ .sorted()
+ .collect(Collectors.toList());
+ }
+
+ private List<String> references(
+ ClassSubject clazz, String methodName, String retValue, String... params) {
+ assertNotNull(clazz);
+ assertTrue(clazz.isPresent());
+
+ MethodSignature signature = new MethodSignature(methodName, retValue, params);
+ DexCode code = clazz.method(signature).getMethod().getCode().asDexCode();
+ return Streams.concat(
+ filterInstructionKind(code, SgetObject.class)
+ .map(Instruction::getField)
+ .filter(fld -> isTypeOfInterest(fld.clazz))
+ .map(DexField::toSourceString),
+ filterInstructionKind(code, SputObject.class)
+ .map(Instruction::getField)
+ .filter(fld -> isTypeOfInterest(fld.clazz))
+ .map(DexField::toSourceString),
+ filterInstructionKind(code, InvokeStatic.class)
+ .map(insn -> (InvokeStatic) insn)
+ .map(InvokeStatic::getMethod)
+ .filter(method -> isTypeOfInterest(method.holder))
+ .map(method -> "STATIC: " + method.toSourceString()),
+ filterInstructionKind(code, InvokeVirtual.class)
+ .map(insn -> (InvokeVirtual) insn)
+ .map(InvokeVirtual::getMethod)
+ .filter(method -> isTypeOfInterest(method.holder))
+ .map(method -> "VIRTUAL: " + method.toSourceString()),
+ filterInstructionKind(code, InvokeDirect.class)
+ .map(insn -> (InvokeDirect) insn)
+ .map(InvokeDirect::getMethod)
+ .filter(method -> isTypeOfInterest(method.holder))
+ .map(method -> "DIRECT: " + method.toSourceString()))
+ .map(txt -> txt.replace("java.lang.", ""))
+ .map(txt -> txt.replace("com.android.tools.r8.ir.optimize.staticizer.", ""))
+ .sorted()
+ .collect(Collectors.toList());
+ }
+
+ private boolean isTypeOfInterest(DexType type) {
+ return type.toSourceString().startsWith("com.android.tools.r8.ir.optimize.staticizer");
+ }
+
+ private AndroidApp runR8(AndroidApp app, Class mainClass) throws Exception {
+ AndroidApp compiled =
+ compileWithR8(app, getProguardConfig(mainClass.getCanonicalName()), this::configure);
+
+ // Materialize file for execution.
+ Path generatedDexFile = temp.getRoot().toPath().resolve("classes.jar");
+ compiled.writeToZip(generatedDexFile, OutputMode.DexIndexed);
+
+ // Run with ART.
+ String artOutput = ToolHelper.runArtNoVerificationErrors(
+ generatedDexFile.toString(), mainClass.getCanonicalName());
+
+ // Compare with Java.
+ ProcessResult javaResult = ToolHelper.runJava(
+ ToolHelper.getClassPathForTests(), mainClass.getCanonicalName());
+
+ if (javaResult.exitCode != 0) {
+ System.out.println(javaResult.stdout);
+ System.err.println(javaResult.stderr);
+ fail("JVM failed for: " + mainClass);
+ }
+ assertEquals("JVM and ART output differ", javaResult.stdout, artOutput);
+
+ return compiled;
+ }
+
+ private String getProguardConfig(String main) {
+ return keepMainProguardConfiguration(main)
+ + System.lineSeparator()
+ + "-dontobfuscate"
+ + System.lineSeparator()
+ + "-allowaccessmodification";
+ }
+
+ private void configure(InternalOptions options) {
+ options.enableClassInlining = false;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateConflictField.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateConflictField.java
new file mode 100644
index 0000000..f2108b0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateConflictField.java
@@ -0,0 +1,21 @@
+// 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.ir.optimize.staticizer.movetohost;
+
+public class CandidateConflictField {
+ public static String field;
+
+ public String foo() {
+ synchronized ("") {
+ return bar("CandidateConflictMethod::foo()");
+ }
+ }
+
+ public String bar(String other) {
+ synchronized ("") {
+ return "CandidateConflictMethod::bar(" + other + ")";
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateConflictMethod.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateConflictMethod.java
new file mode 100644
index 0000000..40d0576
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateConflictMethod.java
@@ -0,0 +1,19 @@
+// 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.ir.optimize.staticizer.movetohost;
+
+public class CandidateConflictMethod {
+ public String foo() {
+ synchronized ("") {
+ return bar("CandidateConflictMethod::foo()");
+ }
+ }
+
+ public String bar(String other) {
+ synchronized ("") {
+ return "CandidateConflictMethod::bar(" + other + ")";
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateOk.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateOk.java
new file mode 100644
index 0000000..daf5647
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateOk.java
@@ -0,0 +1,25 @@
+// 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.ir.optimize.staticizer.movetohost;
+
+public class CandidateOk {
+ public String foo() {
+ synchronized ("") {
+ return bar("CandidateOk::foo()");
+ }
+ }
+
+ public String bar(String other) {
+ synchronized ("") {
+ return "CandidateOk::bar(" + other + ")";
+ }
+ }
+
+ public void blah(String other) {
+ synchronized ("") {
+ System.out.println("CandidateOk::blah(" + other + ")");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateOkSideEffects.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateOkSideEffects.java
new file mode 100644
index 0000000..2077056
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateOkSideEffects.java
@@ -0,0 +1,19 @@
+// 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.ir.optimize.staticizer.movetohost;
+
+public class CandidateOkSideEffects {
+ public String foo() {
+ synchronized ("") {
+ return bar("CandidateOkSideEffects::foo()");
+ }
+ }
+
+ public String bar(String other) {
+ synchronized ("") {
+ return "CandidateOkSideEffects::bar(" + other + ")";
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/HostConflictField.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/HostConflictField.java
new file mode 100644
index 0000000..7166b81
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/HostConflictField.java
@@ -0,0 +1,11 @@
+// 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.ir.optimize.staticizer.movetohost;
+
+public class HostConflictField {
+ static CandidateConflictField INSTANCE = new CandidateConflictField();
+
+ public String field;
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/HostConflictMethod.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/HostConflictMethod.java
new file mode 100644
index 0000000..ad86b35
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/HostConflictMethod.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.ir.optimize.staticizer.movetohost;
+
+public class HostConflictMethod {
+ static CandidateConflictMethod INSTANCE = new CandidateConflictMethod();
+
+ public String bar(String other) {
+ synchronized ("") {
+ return "HostConflictMethod::bar(" + other + ")";
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/HostOk.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/HostOk.java
new file mode 100644
index 0000000..8ecd7ac
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/HostOk.java
@@ -0,0 +1,9 @@
+// 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.ir.optimize.staticizer.movetohost;
+
+public class HostOk {
+ static CandidateOk INSTANCE = new CandidateOk();
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/HostOkSideEffects.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/HostOkSideEffects.java
new file mode 100644
index 0000000..1f6ec7c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/HostOkSideEffects.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.ir.optimize.staticizer.movetohost;
+
+public class HostOkSideEffects {
+ static CandidateOkSideEffects INSTANCE = new CandidateOkSideEffects();
+
+ static {
+ System.out.println("Inside HostOkSideEffects::<clinit>()");
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/MoveToHostTestClass.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/MoveToHostTestClass.java
new file mode 100644
index 0000000..3f31839
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/MoveToHostTestClass.java
@@ -0,0 +1,46 @@
+// 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.ir.optimize.staticizer.movetohost;
+
+public class MoveToHostTestClass {
+ private static int ID = 0;
+
+ private static String next() {
+ return Integer.toString(ID++);
+ }
+
+ public static void main(String[] args) {
+ MoveToHostTestClass test = new MoveToHostTestClass();
+ test.testOk();
+ test.testOkSideEffects();
+ test.testConflictMethod();
+ test.testConflictField();
+ }
+
+ private synchronized void testOk() {
+ System.out.println(HostOk.INSTANCE.foo());
+ System.out.println(HostOk.INSTANCE.bar(next()));
+ HostOk.INSTANCE.blah(next());
+ }
+
+ private synchronized void testOkSideEffects() {
+ System.out.println(HostOkSideEffects.INSTANCE.foo());
+ System.out.println(HostOkSideEffects.INSTANCE.bar(next()));
+ }
+
+ private synchronized void testConflictMethod() {
+ System.out.println(new HostConflictMethod().bar(next()));
+ System.out.println(HostConflictMethod.INSTANCE.foo());
+ System.out.println(HostConflictMethod.INSTANCE.bar(next()));
+ }
+
+ private synchronized void testConflictField() {
+ System.out.println(new HostConflictField().field);
+ System.out.println(CandidateConflictField.field);
+ System.out.println(HostConflictField.INSTANCE.foo());
+ System.out.println(HostConflictField.INSTANCE.bar(next()));
+ }
+}
+
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/Simple.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/Simple.java
new file mode 100644
index 0000000..8df079c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/Simple.java
@@ -0,0 +1,21 @@
+// 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.ir.optimize.staticizer.trivial;
+
+public class Simple {
+ static Simple INSTANCE = new Simple();
+
+ String foo() {
+ synchronized ("") {
+ return bar("Simple::foo()");
+ }
+ }
+
+ String bar(String other) {
+ synchronized ("") {
+ return "Simple::bar(" + other + ")";
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithGetter.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithGetter.java
new file mode 100644
index 0000000..9ff20c3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithGetter.java
@@ -0,0 +1,25 @@
+// 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.ir.optimize.staticizer.trivial;
+
+public class SimpleWithGetter {
+ private static SimpleWithGetter INSTANCE = new SimpleWithGetter();
+
+ static SimpleWithGetter getInstance() {
+ return INSTANCE;
+ }
+
+ String foo() {
+ synchronized ("") {
+ return bar("Simple::foo()");
+ }
+ }
+
+ String bar(String other) {
+ synchronized ("") {
+ return "Simple::bar(" + other + ")";
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithParams.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithParams.java
new file mode 100644
index 0000000..a71f1f5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithParams.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.ir.optimize.staticizer.trivial;
+
+public class SimpleWithParams {
+ static SimpleWithParams INSTANCE = new SimpleWithParams(123);
+
+ SimpleWithParams(int i) {
+ }
+
+ String foo() {
+ synchronized ("") {
+ return bar("SimpleWithParams::foo()");
+ }
+ }
+
+ String bar(String other) {
+ synchronized ("") {
+ return "SimpleWithParams::bar(" + other + ")";
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithSideEffects.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithSideEffects.java
new file mode 100644
index 0000000..5ba97dc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithSideEffects.java
@@ -0,0 +1,25 @@
+// 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.ir.optimize.staticizer.trivial;
+
+public class SimpleWithSideEffects {
+ static SimpleWithSideEffects INSTANCE = new SimpleWithSideEffects();
+
+ static {
+ System.out.println("SimpleWithSideEffects::<clinit>()");
+ }
+
+ String foo() {
+ synchronized ("") {
+ return bar("SimpleWithSideEffects::foo()");
+ }
+ }
+
+ String bar(String other) {
+ synchronized ("") {
+ return "SimpleWithSideEffects::bar(" + other + ")";
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/TrivialTestClass.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/TrivialTestClass.java
new file mode 100644
index 0000000..2dad273
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/TrivialTestClass.java
@@ -0,0 +1,42 @@
+// 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.ir.optimize.staticizer.trivial;
+
+public class TrivialTestClass {
+ private static int ID = 0;
+
+ private static String next() {
+ return Integer.toString(ID++);
+ }
+
+ public static void main(String[] args) {
+ TrivialTestClass test = new TrivialTestClass();
+ test.testSimple();
+ test.testSimpleWithSideEffects();
+ test.testSimpleWithParams();
+ test.testSimpleWithGetter();
+ }
+
+ private synchronized void testSimple() {
+ System.out.println(Simple.INSTANCE.foo());
+ System.out.println(Simple.INSTANCE.bar(next()));
+ }
+
+ private synchronized void testSimpleWithSideEffects() {
+ System.out.println(SimpleWithSideEffects.INSTANCE.foo());
+ System.out.println(SimpleWithSideEffects.INSTANCE.bar(next()));
+ }
+
+ private synchronized void testSimpleWithParams() {
+ System.out.println(SimpleWithParams.INSTANCE.foo());
+ System.out.println(SimpleWithParams.INSTANCE.bar(next()));
+ }
+
+ private synchronized void testSimpleWithGetter() {
+ System.out.println(SimpleWithGetter.getInstance().foo());
+ System.out.println(SimpleWithGetter.getInstance().bar(next()));
+ }
+}
+
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/B77240639.java b/src/test/java/com/android/tools/r8/ir/regalloc/B77240639.java
index a143b65..2c66eb1 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/B77240639.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/B77240639.java
@@ -3,7 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.regalloc;
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertThat;
@@ -11,8 +11,8 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import java.util.List;
import java.util.Map;
import org.junit.Test;
@@ -21,7 +21,7 @@
@Test
public void test1() throws Exception {
AndroidApp app = compileWithD8(readClasses(TestClass.class));
- DexInspector inspector = new DexInspector(app);
+ CodeInspector inspector = new CodeInspector(app);
ClassSubject clazz = inspector.clazz(TestClass.class);
assertThat(clazz, isPresent());
}
@@ -29,7 +29,7 @@
@Test
public void test2() throws Exception {
AndroidApp app = compileWithD8(readClasses(OtherTestClass.class));
- DexInspector inspector = new DexInspector(app);
+ CodeInspector inspector = new CodeInspector(app);
ClassSubject clazz = inspector.clazz(OtherTestClass.class);
assertThat(clazz, isPresent());
ToolHelper.ProcessResult d8Result = runOnArtRaw(app, OtherTestClass.class.getCanonicalName());
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/B79405526.java b/src/test/java/com/android/tools/r8/ir/regalloc/B79405526.java
index 08058e5..4065493 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/B79405526.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/B79405526.java
@@ -4,20 +4,21 @@
package com.android.tools.r8.ir.regalloc;
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.junit.Assert.assertThat;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import org.junit.Test;
public class B79405526 extends TestBase {
@Test
public void test() throws Exception {
AndroidApp app = compileWithD8(readClasses(TestClass.class));
- DexInspector inspector = new DexInspector(app);
- DexInspector.ClassSubject clazz = inspector.clazz(TestClass.class);
+ CodeInspector inspector = new CodeInspector(app);
+ ClassSubject clazz = inspector.clazz(TestClass.class);
assertThat(clazz, isPresent());
// Throws if a method in TestClass does not verify.
runOnArt(app, TestClass.class.getName());
diff --git a/src/test/java/com/android/tools/r8/jar/UnicodeSetRegression/UnicodeSetRegressionTest.java b/src/test/java/com/android/tools/r8/jar/UnicodeSetRegression/UnicodeSetRegressionTest.java
index 16fed9e..6f8d39c 100644
--- a/src/test/java/com/android/tools/r8/jar/UnicodeSetRegression/UnicodeSetRegressionTest.java
+++ b/src/test/java/com/android/tools/r8/jar/UnicodeSetRegression/UnicodeSetRegressionTest.java
@@ -11,7 +11,7 @@
import com.android.tools.r8.utils.ArtErrorParser;
import com.android.tools.r8.utils.ArtErrorParser.ArtErrorInfo;
import com.android.tools.r8.utils.ArtErrorParser.ArtErrorParserException;
-import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -60,8 +60,8 @@
} catch (AssertionError e) {
AndroidApp fromDexApp =
ToolHelper.runR8(dexFromDX(), options -> options.ignoreMissingClasses = true);
- DexInspector fromDex = new DexInspector(fromDexApp);
- DexInspector fromJar = new DexInspector(result);
+ CodeInspector fromDex = new CodeInspector(fromDexApp);
+ CodeInspector fromJar = new CodeInspector(result);
List<ArtErrorInfo> errors;
try {
errors = ArtErrorParser.parse(e.getMessage());
diff --git a/src/test/java/com/android/tools/r8/jasmin/AnnotationCompanionClassTest.java b/src/test/java/com/android/tools/r8/jasmin/AnnotationCompanionClassTest.java
index 033183c..7e90163 100644
--- a/src/test/java/com/android/tools/r8/jasmin/AnnotationCompanionClassTest.java
+++ b/src/test/java/com/android/tools/r8/jasmin/AnnotationCompanionClassTest.java
@@ -7,7 +7,7 @@
import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import org.junit.Test;
@@ -33,9 +33,9 @@
JasminBuilder builder = buildClass();
AndroidApp androidApp = compileWithD8(builder);
- DexInspector dexInspector = new DexInspector(androidApp);
+ CodeInspector codeInspector = new CodeInspector(androidApp);
assertFalse(
- dexInspector
+ codeInspector
.clazz("LMyAnnotation" + InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX + ";")
.isAnnotation());
}
diff --git a/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java b/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java
index e572bfa..a6505c8 100644
--- a/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java
+++ b/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java
@@ -12,9 +12,9 @@
import com.android.tools.r8.jasmin.JasminBuilder.ClassFileVersion;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import org.junit.Test;
@@ -101,7 +101,7 @@
AndroidApp jasminApp = builder.build();
AndroidApp d8App = ToolHelper.runD8(jasminApp);
- DexInspector inspector = new DexInspector(d8App);
+ CodeInspector inspector = new CodeInspector(d8App);
ClassSubject classSubject = inspector.clazz("Test");
MethodSubject methodSubject = classSubject.method(foo);
DexCode code = methodSubject.getMethod().getCode().asDexCode();
diff --git a/src/test/java/com/android/tools/r8/jasmin/JumboStringTests.java b/src/test/java/com/android/tools/r8/jasmin/JumboStringTests.java
index 27d8d8b..40928ee 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JumboStringTests.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JumboStringTests.java
@@ -10,7 +10,7 @@
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.LinkedHashMap;
@@ -91,7 +91,7 @@
AndroidApp d8App = ToolHelper.runD8(jasminApp);
assertEquals(expected, runOnArt(d8App, clazz.name));
- DexInspector inspector = new DexInspector(d8App);
+ CodeInspector inspector = new CodeInspector(d8App);
for (Entry<String, MethodSignature> entry : classes.entrySet()) {
DebugInfoInspector info = new DebugInfoInspector(inspector, entry.getKey(), entry.getValue());
info.checkStartLine(1);
diff --git a/src/test/java/com/android/tools/r8/jasmin/JumpSubroutineTests.java b/src/test/java/com/android/tools/r8/jasmin/JumpSubroutineTests.java
index ec18fac..fd68772 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JumpSubroutineTests.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JumpSubroutineTests.java
@@ -16,7 +16,6 @@
import com.android.tools.r8.utils.AndroidApp;
import com.google.common.collect.ImmutableList;
import java.nio.file.Path;
-import java.nio.file.Paths;
import org.junit.Test;
public class JumpSubroutineTests extends JasminTestBase {
@@ -40,7 +39,7 @@
R8Command.builder()
.addProgramFiles(inputJar)
.setOutput(outputJar, OutputMode.ClassFile)
- .addLibraryFiles(Paths.get(ToolHelper.JAVA_8_RUNTIME))
+ .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
.build(),
options -> options.enableCfFrontend = true);
ProcessResult processResult = ToolHelper.runJava(outputJar, main);
diff --git a/src/test/java/com/android/tools/r8/jsr45/JSR45Tests.java b/src/test/java/com/android/tools/r8/jsr45/JSR45Tests.java
index 7b74453..4919227 100644
--- a/src/test/java/com/android/tools/r8/jsr45/JSR45Tests.java
+++ b/src/test/java/com/android/tools/r8/jsr45/JSR45Tests.java
@@ -14,9 +14,9 @@
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.AndroidAppConsumers;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.AnnotationSubject;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.AnnotationSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Files;
@@ -132,9 +132,9 @@
compileWithR8(INPUT_PATH, outputPath, SHRINK_NO_KEEP_CONFIG);
- DexInspector dexInspector =
- new DexInspector(outputPath.resolve("classes.dex"), getGeneratedProguardMap());
- ClassSubject classSubject = dexInspector.clazz("HelloKt");
+ CodeInspector codeInspector =
+ new CodeInspector(outputPath.resolve("classes.dex"), getGeneratedProguardMap());
+ ClassSubject classSubject = codeInspector.clazz("HelloKt");
AnnotationSubject annotationSubject =
classSubject.annotation("dalvik.annotation.SourceDebugExtension");
Assert.assertFalse(annotationSubject.isPresent());
@@ -147,8 +147,8 @@
new ReadSourceDebugExtensionAttribute(Opcodes.ASM6, null);
classReader.accept(sourceDebugExtensionReader, 0);
- DexInspector dexInspector = new DexInspector(androidApp);
- ClassSubject classSubject = dexInspector.clazz("HelloKt");
+ CodeInspector codeInspector = new CodeInspector(androidApp);
+ ClassSubject classSubject = codeInspector.clazz("HelloKt");
AnnotationSubject annotationSubject =
classSubject.annotation("dalvik.annotation.SourceDebugExtension");
diff --git a/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java b/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
index fd78cb7..4405565 100644
--- a/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
+++ b/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
@@ -21,12 +21,12 @@
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.FieldSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -112,7 +112,7 @@
}));
}
- protected ClassSubject checkClassIsKept(DexInspector inspector, String className) {
+ protected ClassSubject checkClassIsKept(CodeInspector inspector, String className) {
checkClassExistsInInput(className);
ClassSubject classSubject = inspector.clazz(className);
assertNotNull(classSubject);
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
index dc2774a..67afdd7 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
@@ -17,8 +17,8 @@
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
@@ -55,7 +55,7 @@
clazz.interfaces.size() == 1;
}
- private static Predicate<DexType> createLambdaCheck(DexInspector inspector) {
+ private static Predicate<DexType> createLambdaCheck(CodeInspector inspector) {
Set<DexType> lambdaClasses = inspector.allClasses().stream()
.filter(clazz -> isLambda(clazz.getDexClass()))
.map(clazz -> clazz.getDexClass().type)
@@ -67,7 +67,7 @@
public void testJStyleLambdas() throws Exception {
final String mainClassName = "class_inliner_lambda_j_style.MainKt";
runTest("class_inliner_lambda_j_style", mainClassName, false, (app) -> {
- DexInspector inspector = new DexInspector(app);
+ CodeInspector inspector = new CodeInspector(app);
assertTrue(
inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateful$1").isPresent());
assertTrue(
@@ -77,7 +77,7 @@
});
runTest("class_inliner_lambda_j_style", mainClassName, true, (app) -> {
- DexInspector inspector = new DexInspector(app);
+ CodeInspector inspector = new CodeInspector(app);
Predicate<DexType> lambdaCheck = createLambdaCheck(inspector);
ClassSubject clazz = inspector.clazz(mainClassName);
@@ -112,7 +112,7 @@
public void testKStyleLambdas() throws Exception {
final String mainClassName = "class_inliner_lambda_k_style.MainKt";
runTest("class_inliner_lambda_k_style", mainClassName, false, (app) -> {
- DexInspector inspector = new DexInspector(app);
+ CodeInspector inspector = new CodeInspector(app);
assertTrue(inspector.clazz(
"class_inliner_lambda_k_style.MainKt$testKotlinSequencesStateless$1").isPresent());
assertTrue(inspector.clazz(
@@ -135,7 +135,7 @@
});
runTest("class_inliner_lambda_k_style", mainClassName, true, (app) -> {
- DexInspector inspector = new DexInspector(app);
+ CodeInspector inspector = new CodeInspector(app);
Predicate<DexType> lambdaCheck = createLambdaCheck(inspector);
ClassSubject clazz = inspector.clazz(mainClassName);
@@ -186,7 +186,7 @@
public void testDataClass() throws Exception {
final String mainClassName = "class_inliner_data_class.MainKt";
runTest("class_inliner_data_class", mainClassName, true, (app) -> {
- DexInspector inspector = new DexInspector(app);
+ CodeInspector inspector = new CodeInspector(app);
ClassSubject clazz = inspector.clazz(mainClassName);
assertTrue(collectAccessedTypes(
type -> !type.toSourceString().startsWith("java."),
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java
new file mode 100644
index 0000000..c19b80e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java
@@ -0,0 +1,72 @@
+// 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.junit.Test;
+import org.junit.runners.Parameterized.Parameters;
+
+public class KotlinClassStaticizerTest extends AbstractR8KotlinTestBase {
+ @Parameters(name = "allowAccessModification: {0} target: {1}")
+ public static Collection<Object[]> data() {
+ ImmutableList.Builder<Object[]> builder = new ImmutableList.Builder<>();
+ for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) {
+ builder.add(new Object[]{Boolean.TRUE, targetVersion});
+ }
+ return builder.build();
+ }
+
+ @Test
+ public void testCompanionAndRegularObjects() throws Exception {
+ final String mainClassName = "class_staticizer.MainKt";
+
+ // Without class staticizer.
+ runTest("class_staticizer", mainClassName, false, (app) -> {
+ CodeInspector inspector = new CodeInspector(app);
+ assertTrue(inspector.clazz("class_staticizer.Regular$Companion").isPresent());
+ assertTrue(inspector.clazz("class_staticizer.Derived$Companion").isPresent());
+
+ ClassSubject utilClass = inspector.clazz("class_staticizer.Util");
+ assertTrue(utilClass.isPresent());
+ AtomicInteger nonStaticMethodCount = new AtomicInteger();
+ utilClass.forAllMethods(method -> {
+ if (!method.isStatic()) {
+ nonStaticMethodCount.incrementAndGet();
+ }
+ });
+ assertEquals(4, nonStaticMethodCount.get());
+ });
+
+ // With class staticizer.
+ runTest("class_staticizer", mainClassName, true, (app) -> {
+ CodeInspector inspector = new CodeInspector(app);
+ assertFalse(inspector.clazz("class_staticizer.Regular$Companion").isPresent());
+ assertFalse(inspector.clazz("class_staticizer.Derived$Companion").isPresent());
+
+ ClassSubject utilClass = inspector.clazz("class_staticizer.Util");
+ assertTrue(utilClass.isPresent());
+ utilClass.forAllMethods(method -> assertTrue(method.isStatic()));
+ });
+ }
+
+ protected void runTest(String folder, String mainClass,
+ boolean enabled, AndroidAppInspector inspector) throws Exception {
+ runTest(
+ folder, mainClass, null,
+ options -> {
+ options.enableClassInlining = false;
+ options.enableClassStaticizer = enabled;
+ }, inspector);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java
index 062095c..e43e94c 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java
@@ -13,8 +13,8 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.optimize.lambda.CaptureSignature;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.Lists;
import java.io.IOException;
import java.util.ArrayList;
@@ -139,21 +139,21 @@
}
static class Verifier {
- final DexInspector dexInspector;
+ final CodeInspector codeInspector;
final List<DexClass> lambdas = new ArrayList<>();
final List<DexClass> groups = new ArrayList<>();
Verifier(AndroidApp app) throws IOException, ExecutionException {
- this(new DexInspector(app));
+ this(new CodeInspector(app));
}
- Verifier(DexInspector dexInspector) {
- this.dexInspector = dexInspector;
+ Verifier(CodeInspector codeInspector) {
+ this.codeInspector = codeInspector;
initGroupsAndLambdas();
}
private void initGroupsAndLambdas() {
- dexInspector.forAllClasses(clazz -> {
+ codeInspector.forAllClasses(clazz -> {
DexClass dexClass = clazz.getDexClass();
if (isLambdaOrGroup(dexClass)) {
if (isLambdaGroupClass(dexClass)) {
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinxMetadataExtensionsServiceTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinxMetadataExtensionsServiceTest.java
index 2beb777..87b9487 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinxMetadataExtensionsServiceTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinxMetadataExtensionsServiceTest.java
@@ -19,8 +19,8 @@
import com.android.tools.r8.kotlin.KotlinLambdaMergingTest.Lambda;
import com.android.tools.r8.kotlin.KotlinLambdaMergingTest.Verifier;
import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.DexInspector;
import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -60,7 +60,7 @@
"No MetadataExtensions instances found in the classpath")));
assertTrue(Files.exists(output));
- DexInspector inspector = new DexInspector(output);
+ CodeInspector inspector = new CodeInspector(output);
Verifier verifier = new Verifier(inspector);
String pkg = "lambdas_kstyle_trivial";
verifier.assertLambdaGroups(
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
index 7425082..a3aa5e1 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
@@ -16,11 +16,13 @@
import com.android.tools.r8.kotlin.TestKotlinClass.Visibility;
import com.android.tools.r8.naming.MemberNaming;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.FieldSubject;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
import java.nio.file.Path;
import java.util.Collections;
+import java.util.function.Consumer;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
@@ -59,14 +61,17 @@
.addProperty("property", JAVA_LANG_STRING, Visibility.PRIVATE)
.addProperty("indirectPropertyGetter", JAVA_LANG_STRING, Visibility.PRIVATE);
+ private Consumer<InternalOptions> disableClassStaticizer =
+ opts -> opts.enableClassStaticizer = false;
+
@Test
public void testCompanionProperty_primitivePropertyIsAlwaysInlined() throws Exception {
final TestKotlinCompanionClass testedClass = COMPANION_PROPERTY_CLASS;
String mainClass = addMainToClasspath("properties.CompanionPropertiesKt",
"companionProperties_usePrimitiveProp");
- runTest(PROPERTIES_PACKAGE_NAME, mainClass, (app) -> {
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject outerClass = checkClassIsKept(dexInspector, testedClass.getOuterClassName());
+ runTest(PROPERTIES_PACKAGE_NAME, mainClass, disableClassStaticizer, (app) -> {
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject outerClass = checkClassIsKept(codeInspector, testedClass.getOuterClassName());
String propertyName = "primitiveProp";
FieldSubject fieldSubject = checkFieldIsKept(outerClass, "int", propertyName);
assertTrue(fieldSubject.getField().accessFlags.isStatic());
@@ -93,9 +98,9 @@
final TestKotlinCompanionClass testedClass = COMPANION_PROPERTY_CLASS;
String mainClass = addMainToClasspath("properties.CompanionPropertiesKt",
"companionProperties_usePrivateProp");
- runTest(PROPERTIES_PACKAGE_NAME, mainClass, (app) -> {
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject outerClass = checkClassIsKept(dexInspector, testedClass.getOuterClassName());
+ runTest(PROPERTIES_PACKAGE_NAME, mainClass, disableClassStaticizer, (app) -> {
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject outerClass = checkClassIsKept(codeInspector, testedClass.getOuterClassName());
String propertyName = "privateProp";
FieldSubject fieldSubject = checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
assertTrue(fieldSubject.getField().accessFlags.isStatic());
@@ -123,9 +128,9 @@
final TestKotlinCompanionClass testedClass = COMPANION_PROPERTY_CLASS;
String mainClass = addMainToClasspath("properties.CompanionPropertiesKt",
"companionProperties_useInternalProp");
- runTest(PROPERTIES_PACKAGE_NAME, mainClass, (app) -> {
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject outerClass = checkClassIsKept(dexInspector, testedClass.getOuterClassName());
+ runTest(PROPERTIES_PACKAGE_NAME, mainClass, disableClassStaticizer, (app) -> {
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject outerClass = checkClassIsKept(codeInspector, testedClass.getOuterClassName());
String propertyName = "internalProp";
FieldSubject fieldSubject = checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
assertTrue(fieldSubject.getField().accessFlags.isStatic());
@@ -152,9 +157,9 @@
final TestKotlinCompanionClass testedClass = COMPANION_PROPERTY_CLASS;
String mainClass = addMainToClasspath("properties.CompanionPropertiesKt",
"companionProperties_usePublicProp");
- runTest(PROPERTIES_PACKAGE_NAME, mainClass, (app) -> {
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject outerClass = checkClassIsKept(dexInspector, testedClass.getOuterClassName());
+ runTest(PROPERTIES_PACKAGE_NAME, mainClass, disableClassStaticizer, (app) -> {
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject outerClass = checkClassIsKept(codeInspector, testedClass.getOuterClassName());
String propertyName = "publicProp";
FieldSubject fieldSubject = checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
assertTrue(fieldSubject.getField().accessFlags.isStatic());
@@ -181,9 +186,9 @@
final TestKotlinCompanionClass testedClass = COMPANION_LATE_INIT_PROPERTY_CLASS;
String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt",
"companionLateInitProperties_usePrivateLateInitProp");
- runTest(PROPERTIES_PACKAGE_NAME, mainClass, (app) -> {
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject outerClass = checkClassIsKept(dexInspector, testedClass.getOuterClassName());
+ runTest(PROPERTIES_PACKAGE_NAME, mainClass, disableClassStaticizer, (app) -> {
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject outerClass = checkClassIsKept(codeInspector, testedClass.getOuterClassName());
String propertyName = "privateLateInitProp";
FieldSubject fieldSubject = checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
assertTrue(fieldSubject.getField().accessFlags.isStatic());
@@ -210,8 +215,8 @@
String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt",
"companionLateInitProperties_useInternalLateInitProp");
runTest(PROPERTIES_PACKAGE_NAME, mainClass, (app) -> {
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject outerClass = checkClassIsKept(dexInspector, testedClass.getOuterClassName());
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject outerClass = checkClassIsKept(codeInspector, testedClass.getOuterClassName());
String propertyName = "internalLateInitProp";
FieldSubject fieldSubject = checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
assertTrue(fieldSubject.getField().accessFlags.isStatic());
@@ -234,8 +239,8 @@
String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt",
"companionLateInitProperties_usePublicLateInitProp");
runTest(PROPERTIES_PACKAGE_NAME, mainClass, (app) -> {
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject outerClass = checkClassIsKept(dexInspector, testedClass.getOuterClassName());
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject outerClass = checkClassIsKept(codeInspector, testedClass.getOuterClassName());
String propertyName = "publicLateInitProp";
FieldSubject fieldSubject = checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
assertTrue(fieldSubject.getField().accessFlags.isStatic());
@@ -257,10 +262,10 @@
TestKotlinCompanionClass testedClass = ACCESSOR_COMPANION_PROPERTY_CLASS;
String mainClass = addMainToClasspath("accessors.AccessorKt",
"accessor_accessPropertyFromCompanionClass");
- runTest("accessors", mainClass, (app) -> {
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject outerClass = checkClassIsKept(dexInspector, testedClass.getOuterClassName());
- ClassSubject companionClass = checkClassIsKept(dexInspector, testedClass.getClassName());
+ runTest("accessors", mainClass, disableClassStaticizer, (app) -> {
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject outerClass = checkClassIsKept(codeInspector, testedClass.getOuterClassName());
+ ClassSubject companionClass = checkClassIsKept(codeInspector, testedClass.getClassName());
String propertyName = "property";
FieldSubject fieldSubject = checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
assertTrue(fieldSubject.getField().accessFlags.isStatic());
@@ -288,9 +293,9 @@
String mainClass = addMainToClasspath("accessors.AccessorKt",
"accessor_accessPropertyFromOuterClass");
runTest("accessors", mainClass, (app) -> {
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject outerClass = checkClassIsKept(dexInspector, testedClass.getOuterClassName());
- ClassSubject companionClass = checkClassIsKept(dexInspector, testedClass.getClassName());
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject outerClass = checkClassIsKept(codeInspector, testedClass.getOuterClassName());
+ ClassSubject companionClass = checkClassIsKept(codeInspector, testedClass.getClassName());
String propertyName = "property";
FieldSubject fieldSubject = checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
assertTrue(fieldSubject.getField().accessFlags.isStatic());
@@ -318,8 +323,8 @@
String mainClass = addMainToClasspath(testedClass.className + "Kt",
"noUseOfPropertyAccessorFromInnerClass");
runTest("accessors", mainClass, (app) -> {
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject classSubject = checkClassIsKept(dexInspector, testedClass.getClassName());
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject classSubject = checkClassIsKept(codeInspector, testedClass.getClassName());
for (String propertyName : testedClass.properties.keySet()) {
MemberNaming.MethodSignature getterAccessor =
@@ -340,8 +345,8 @@
String mainClass = addMainToClasspath(testedClass.className + "Kt",
"usePrivatePropertyAccessorFromInnerClass");
runTest("accessors", mainClass, (app) -> {
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject classSubject = checkClassIsKept(dexInspector, testedClass.getClassName());
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject classSubject = checkClassIsKept(codeInspector, testedClass.getClassName());
String propertyName = "privateProp";
FieldSubject fieldSubject = checkFieldIsKept(classSubject, JAVA_LANG_STRING,
@@ -371,8 +376,8 @@
String mainClass = addMainToClasspath(testedClass.className + "Kt",
"usePrivateLateInitPropertyAccessorFromInnerClass");
runTest("accessors", mainClass, (app) -> {
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject classSubject = checkClassIsKept(dexInspector, testedClass.getClassName());
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject classSubject = checkClassIsKept(codeInspector, testedClass.getClassName());
String propertyName = "privateLateInitProp";
FieldSubject fieldSubject = checkFieldIsKept(classSubject, JAVA_LANG_STRING,
@@ -402,8 +407,8 @@
String mainClass = addMainToClasspath(testedClass.className + "Kt",
"noUseOfPropertyAccessorFromLambda");
runTest("accessors", mainClass, (app) -> {
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject classSubject = checkClassIsKept(dexInspector, testedClass.getClassName());
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject classSubject = checkClassIsKept(codeInspector, testedClass.getClassName());
String propertyName = "property";
MemberNaming.MethodSignature getterAccessor =
@@ -423,8 +428,8 @@
String mainClass = addMainToClasspath(testedClass.className + "Kt",
"usePropertyAccessorFromLambda");
runTest("accessors", mainClass, (app) -> {
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject classSubject = checkClassIsKept(dexInspector, testedClass.getClassName());
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject classSubject = checkClassIsKept(codeInspector, testedClass.getClassName());
String propertyName = "property";
FieldSubject fieldSubject = checkFieldIsKept(classSubject, JAVA_LANG_STRING, propertyName);
assertFalse(fieldSubject.getField().accessFlags.isStatic());
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
index 73d1e50..58eae89 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
@@ -7,10 +7,10 @@
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.kotlin.TestKotlinClass.Visibility;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import java.util.Collections;
import java.util.function.Consumer;
import org.junit.Test;
@@ -44,8 +44,8 @@
new MethodSignature("testDataClassGetters", "void", Collections.emptyList());
final String extraRules = keepClassMethod(mainClassName, testMethodSignature);
runTest("dataclass", mainClassName, extraRules, disableClassInliner, (app) -> {
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject dataClass = checkClassIsKept(dexInspector, TEST_DATA_CLASS.getClassName());
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject dataClass = checkClassIsKept(codeInspector, TEST_DATA_CLASS.getClassName());
// Getters should be removed after inlining, which is possible only if access is relaxed.
final boolean areGetterPresent = !allowAccessModification;
@@ -60,7 +60,7 @@
checkMethodIsRemoved(dataClass, COPY_METHOD);
checkMethodIsRemoved(dataClass, COPY_DEFAULT_METHOD);
- ClassSubject classSubject = checkClassIsKept(dexInspector, mainClassName);
+ ClassSubject classSubject = checkClassIsKept(codeInspector, mainClassName);
MethodSubject testMethod = checkMethodIsKept(classSubject, testMethodSignature);
DexCode dexCode = getDexCode(testMethod);
if (allowAccessModification) {
@@ -79,8 +79,8 @@
new MethodSignature("testAllDataClassComponentFunctions", "void", Collections.emptyList());
final String extraRules = keepClassMethod(mainClassName, testMethodSignature);
runTest("dataclass", mainClassName, extraRules, disableClassInliner, (app) -> {
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject dataClass = checkClassIsKept(dexInspector, TEST_DATA_CLASS.getClassName());
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject dataClass = checkClassIsKept(codeInspector, TEST_DATA_CLASS.getClassName());
// ComponentN functions should be removed after inlining, which is possible only if access
// is relaxed.
@@ -96,7 +96,7 @@
checkMethodIsRemoved(dataClass, COPY_METHOD);
checkMethodIsRemoved(dataClass, COPY_DEFAULT_METHOD);
- ClassSubject classSubject = checkClassIsKept(dexInspector, mainClassName);
+ ClassSubject classSubject = checkClassIsKept(codeInspector, mainClassName);
MethodSubject testMethod = checkMethodIsKept(classSubject, testMethodSignature);
DexCode dexCode = getDexCode(testMethod);
if (allowAccessModification) {
@@ -114,8 +114,8 @@
new MethodSignature("testSomeDataClassComponentFunctions", "void", Collections.emptyList());
final String extraRules = keepClassMethod(mainClassName, testMethodSignature);
runTest("dataclass", mainClassName, extraRules, disableClassInliner, (app) -> {
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject dataClass = checkClassIsKept(dexInspector, TEST_DATA_CLASS.getClassName());
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject dataClass = checkClassIsKept(codeInspector, TEST_DATA_CLASS.getClassName());
boolean component2IsPresent = !allowAccessModification;
checkMethodisKeptOrRemoved(dataClass, COMPONENT2_METHOD, component2IsPresent);
@@ -131,7 +131,7 @@
checkMethodIsRemoved(dataClass, COPY_METHOD);
checkMethodIsRemoved(dataClass, COPY_DEFAULT_METHOD);
- ClassSubject classSubject = checkClassIsKept(dexInspector, mainClassName);
+ ClassSubject classSubject = checkClassIsKept(codeInspector, mainClassName);
MethodSubject testMethod = checkMethodIsKept(classSubject, testMethodSignature);
DexCode dexCode = getDexCode(testMethod);
if (allowAccessModification) {
@@ -149,8 +149,8 @@
new MethodSignature("testDataClassCopy", "void", Collections.emptyList());
final String extraRules = keepClassMethod(mainClassName, testMethodSignature);
runTest("dataclass", mainClassName, extraRules, disableClassInliner, (app) -> {
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject dataClass = checkClassIsKept(dexInspector, TEST_DATA_CLASS.getClassName());
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject dataClass = checkClassIsKept(codeInspector, TEST_DATA_CLASS.getClassName());
checkMethodIsRemoved(dataClass, COPY_METHOD);
checkMethodIsRemoved(dataClass, COPY_DEFAULT_METHOD);
@@ -164,8 +164,8 @@
new MethodSignature("testDataClassCopyWithDefault", "void", Collections.emptyList());
final String extraRules = keepClassMethod(mainClassName, testMethodSignature);
runTest("dataclass", mainClassName, extraRules, disableClassInliner, (app) -> {
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject dataClass = checkClassIsKept(dexInspector, TEST_DATA_CLASS.getClassName());
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject dataClass = checkClassIsKept(codeInspector, TEST_DATA_CLASS.getClassName());
checkMethodIsRemoved(dataClass, COPY_DEFAULT_METHOD);
});
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinIntrinsicsTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinIntrinsicsTest.java
index 021e045..c90df04 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinIntrinsicsTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinIntrinsicsTest.java
@@ -5,8 +5,8 @@
package com.android.tools.r8.kotlin;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import java.util.Collections;
@@ -27,9 +27,9 @@
"java.lang.String", Lists.newArrayList("java.lang.String", "java.lang.String")));
runTest("intrinsics", "intrinsics.IntrinsicsKt", extraRules, (app) -> {
- DexInspector dexInspector = new DexInspector(app);
+ CodeInspector codeInspector = new CodeInspector(app);
ClassSubject intrinsicsClass = checkClassIsKept(
- dexInspector, KOTLIN_INTRINSICS_CLASS.getClassName());
+ codeInspector, KOTLIN_INTRINSICS_CLASS.getClassName());
checkMethodsPresence(intrinsicsClass,
ImmutableMap.<MethodSignature, Boolean>builder()
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 dc9b642..08fcebe 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
@@ -10,10 +10,10 @@
import com.android.tools.r8.kotlin.TestKotlinClass.Visibility;
import com.android.tools.r8.naming.MemberNaming;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.FieldSubject;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
import java.util.Map.Entry;
import java.util.function.Consumer;
import org.junit.Test;
@@ -83,18 +83,19 @@
.addProperty("internalLateInitProp", JAVA_LANG_STRING, Visibility.INTERNAL)
.addProperty("publicLateInitProp", JAVA_LANG_STRING, Visibility.PUBLIC);
- private Consumer<InternalOptions> disableClassInliningAndMerging = o -> {
+ private Consumer<InternalOptions> disableAggressiveClassOptimizations = o -> {
o.enableClassInlining = false;
o.enableClassMerging = false;
+ o.enableClassStaticizer = false;
};
@Test
public void testMutableProperty_getterAndSetterAreRemoveIfNotUsed() throws Exception {
String mainClass = addMainToClasspath("properties/MutablePropertyKt",
"mutableProperty_noUseOfProperties");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject classSubject = checkClassIsKept(dexInspector,
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject classSubject = checkClassIsKept(codeInspector,
MUTABLE_PROPERTY_CLASS.getClassName());
for (Entry<String, KotlinProperty> property : MUTABLE_PROPERTY_CLASS.properties.entrySet()) {
MethodSignature getter = MUTABLE_PROPERTY_CLASS.getGetterForProperty(property.getKey());
@@ -115,9 +116,9 @@
public void testMutableProperty_privateIsAlwaysInlined() throws Exception {
String mainClass = addMainToClasspath("properties/MutablePropertyKt",
"mutableProperty_usePrivateProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject classSubject = checkClassIsKept(dexInspector,
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject classSubject = checkClassIsKept(codeInspector,
MUTABLE_PROPERTY_CLASS.getClassName());
String propertyName = "privateProp";
FieldSubject fieldSubject = checkFieldIsKept(classSubject, JAVA_LANG_STRING, propertyName);
@@ -137,9 +138,9 @@
public void testMutableProperty_protectedIsAlwaysInlined() throws Exception {
String mainClass = addMainToClasspath("properties/MutablePropertyKt",
"mutableProperty_useProtectedProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject classSubject = checkClassIsKept(dexInspector,
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject classSubject = checkClassIsKept(codeInspector,
MUTABLE_PROPERTY_CLASS.getClassName());
String propertyName = "protectedProp";
FieldSubject fieldSubject = checkFieldIsKept(classSubject, JAVA_LANG_STRING, propertyName);
@@ -160,9 +161,9 @@
public void testMutableProperty_internalIsAlwaysInlined() throws Exception {
String mainClass = addMainToClasspath("properties/MutablePropertyKt",
"mutableProperty_useInternalProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject classSubject = checkClassIsKept(dexInspector,
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject classSubject = checkClassIsKept(codeInspector,
MUTABLE_PROPERTY_CLASS.getClassName());
String propertyName = "internalProp";
FieldSubject fieldSubject = checkFieldIsKept(classSubject, JAVA_LANG_STRING, propertyName);
@@ -183,9 +184,9 @@
public void testMutableProperty_publicIsAlwaysInlined() throws Exception {
String mainClass = addMainToClasspath("properties/MutablePropertyKt",
"mutableProperty_usePublicProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject classSubject = checkClassIsKept(dexInspector,
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject classSubject = checkClassIsKept(codeInspector,
MUTABLE_PROPERTY_CLASS.getClassName());
String propertyName = "publicProp";
FieldSubject fieldSubject = checkFieldIsKept(classSubject, JAVA_LANG_STRING, propertyName);
@@ -206,9 +207,9 @@
public void testMutableProperty_primitivePropertyIsAlwaysInlined() throws Exception {
String mainClass = addMainToClasspath("properties/MutablePropertyKt",
"mutableProperty_usePrimitiveProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject classSubject = checkClassIsKept(dexInspector,
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject classSubject = checkClassIsKept(codeInspector,
MUTABLE_PROPERTY_CLASS.getClassName());
String propertyName = "primitiveProp";
FieldSubject fieldSubject = checkFieldIsKept(classSubject, "int", propertyName);
@@ -231,11 +232,12 @@
public void testLateInitProperty_getterAndSetterAreRemoveIfNotUsed() throws Exception {
String mainClass = addMainToClasspath("properties/LateInitPropertyKt",
"lateInitProperty_noUseOfProperties");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject classSubject = checkClassIsKept(dexInspector,
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject classSubject = checkClassIsKept(codeInspector,
LATE_INIT_PROPERTY_CLASS.getClassName());
- for (Entry<String, KotlinProperty> property : LATE_INIT_PROPERTY_CLASS.properties.entrySet()) {
+ for (Entry<String, KotlinProperty> property : LATE_INIT_PROPERTY_CLASS.properties
+ .entrySet()) {
MethodSignature getter = LATE_INIT_PROPERTY_CLASS.getGetterForProperty(property.getKey());
MethodSignature setter = LATE_INIT_PROPERTY_CLASS.getSetterForProperty(property.getKey());
if (property.getValue().getVisibility() == Visibility.PRIVATE) {
@@ -255,9 +257,9 @@
public void testLateInitProperty_privateIsAlwaysInlined() throws Exception {
String mainClass = addMainToClasspath(
"properties/LateInitPropertyKt", "lateInitProperty_usePrivateLateInitProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject classSubject = checkClassIsKept(dexInspector,
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject classSubject = checkClassIsKept(codeInspector,
LATE_INIT_PROPERTY_CLASS.getClassName());
String propertyName = "privateLateInitProp";
FieldSubject fieldSubject = classSubject.field(JAVA_LANG_STRING, propertyName);
@@ -278,9 +280,9 @@
public void testLateInitProperty_protectedIsAlwaysInlined() throws Exception {
String mainClass = addMainToClasspath("properties/LateInitPropertyKt",
"lateInitProperty_useProtectedLateInitProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject classSubject = checkClassIsKept(dexInspector,
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject classSubject = checkClassIsKept(codeInspector,
LATE_INIT_PROPERTY_CLASS.getClassName());
String propertyName = "protectedLateInitProp";
FieldSubject fieldSubject = classSubject.field(JAVA_LANG_STRING, propertyName);
@@ -299,9 +301,9 @@
public void testLateInitProperty_internalIsAlwaysInlined() throws Exception {
String mainClass = addMainToClasspath(
"properties/LateInitPropertyKt", "lateInitProperty_useInternalLateInitProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject classSubject = checkClassIsKept(dexInspector,
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject classSubject = checkClassIsKept(codeInspector,
LATE_INIT_PROPERTY_CLASS.getClassName());
String propertyName = "internalLateInitProp";
FieldSubject fieldSubject = classSubject.field(JAVA_LANG_STRING, propertyName);
@@ -318,9 +320,9 @@
public void testLateInitProperty_publicIsAlwaysInlined() throws Exception {
String mainClass = addMainToClasspath(
"properties/LateInitPropertyKt", "lateInitProperty_usePublicLateInitProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject classSubject = checkClassIsKept(dexInspector,
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject classSubject = checkClassIsKept(codeInspector,
LATE_INIT_PROPERTY_CLASS.getClassName());
String propertyName = "publicLateInitProp";
FieldSubject fieldSubject = classSubject.field(JAVA_LANG_STRING, propertyName);
@@ -337,9 +339,9 @@
public void testUserDefinedProperty_getterAndSetterAreRemoveIfNotUsed() throws Exception {
String mainClass = addMainToClasspath(
"properties/UserDefinedPropertyKt", "userDefinedProperty_noUseOfProperties");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject classSubject = checkClassIsKept(dexInspector,
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject classSubject = checkClassIsKept(codeInspector,
USER_DEFINED_PROPERTY_CLASS.getClassName());
for (String propertyName : USER_DEFINED_PROPERTY_CLASS.properties.keySet()) {
checkMethodIsRemoved(classSubject,
@@ -354,9 +356,9 @@
public void testUserDefinedProperty_publicIsAlwaysInlined() throws Exception {
String mainClass = addMainToClasspath(
"properties/UserDefinedPropertyKt", "userDefinedProperty_useProperties");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject classSubject = checkClassIsKept(dexInspector,
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject classSubject = checkClassIsKept(codeInspector,
USER_DEFINED_PROPERTY_CLASS.getClassName());
String propertyName = "durationInSeconds";
// The 'wrapper' property is not assigned to a backing field, it only relies on the wrapped
@@ -380,11 +382,11 @@
public void testCompanionProperty_primitivePropertyCannotBeInlined() throws Exception {
String mainClass = addMainToClasspath(
"properties.CompanionPropertiesKt", "companionProperties_usePrimitiveProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject outerClass = checkClassIsKept(dexInspector,
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject outerClass = checkClassIsKept(codeInspector,
"properties.CompanionProperties");
- ClassSubject companionClass = checkClassIsKept(dexInspector,
+ ClassSubject companionClass = checkClassIsKept(codeInspector,
COMPANION_PROPERTY_CLASS.getClassName());
String propertyName = "primitiveProp";
FieldSubject fieldSubject = checkFieldIsKept(outerClass, "int", propertyName);
@@ -411,11 +413,11 @@
public void testCompanionProperty_privatePropertyIsAlwaysInlined() throws Exception {
String mainClass = addMainToClasspath(
"properties.CompanionPropertiesKt", "companionProperties_usePrivateProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject outerClass = checkClassIsKept(dexInspector,
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject outerClass = checkClassIsKept(codeInspector,
"properties.CompanionProperties");
- ClassSubject companionClass = checkClassIsKept(dexInspector,
+ ClassSubject companionClass = checkClassIsKept(codeInspector,
COMPANION_PROPERTY_CLASS.getClassName());
String propertyName = "privateProp";
FieldSubject fieldSubject = checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
@@ -445,11 +447,11 @@
public void testCompanionProperty_internalPropertyCannotBeInlined() throws Exception {
String mainClass = addMainToClasspath(
"properties.CompanionPropertiesKt", "companionProperties_useInternalProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject outerClass = checkClassIsKept(dexInspector,
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject outerClass = checkClassIsKept(codeInspector,
"properties.CompanionProperties");
- ClassSubject companionClass = checkClassIsKept(dexInspector,
+ ClassSubject companionClass = checkClassIsKept(codeInspector,
COMPANION_PROPERTY_CLASS.getClassName());
String propertyName = "internalProp";
FieldSubject fieldSubject = checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
@@ -476,11 +478,11 @@
public void testCompanionProperty_publicPropertyCannotBeInlined() throws Exception {
String mainClass = addMainToClasspath(
"properties.CompanionPropertiesKt", "companionProperties_usePublicProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject outerClass = checkClassIsKept(dexInspector,
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject outerClass = checkClassIsKept(codeInspector,
"properties.CompanionProperties");
- ClassSubject companionClass = checkClassIsKept(dexInspector,
+ ClassSubject companionClass = checkClassIsKept(codeInspector,
COMPANION_PROPERTY_CLASS.getClassName());
String propertyName = "publicProp";
FieldSubject fieldSubject = checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
@@ -508,10 +510,10 @@
final TestKotlinCompanionClass testedClass = COMPANION_LATE_INIT_PROPERTY_CLASS;
String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt",
"companionLateInitProperties_usePrivateLateInitProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject outerClass = checkClassIsKept(dexInspector, testedClass.getOuterClassName());
- ClassSubject companionClass = checkClassIsKept(dexInspector, testedClass.getClassName());
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject outerClass = checkClassIsKept(codeInspector, testedClass.getOuterClassName());
+ ClassSubject companionClass = checkClassIsKept(codeInspector, testedClass.getClassName());
String propertyName = "privateLateInitProp";
FieldSubject fieldSubject = checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
assertTrue(fieldSubject.getField().accessFlags.isStatic());
@@ -539,10 +541,10 @@
final TestKotlinCompanionClass testedClass = COMPANION_LATE_INIT_PROPERTY_CLASS;
String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt",
"companionLateInitProperties_useInternalLateInitProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject outerClass = checkClassIsKept(dexInspector, testedClass.getOuterClassName());
- ClassSubject companionClass = checkClassIsKept(dexInspector, testedClass.getClassName());
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject outerClass = checkClassIsKept(codeInspector, testedClass.getOuterClassName());
+ ClassSubject companionClass = checkClassIsKept(codeInspector, testedClass.getClassName());
String propertyName = "internalLateInitProp";
FieldSubject fieldSubject = checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
assertTrue(fieldSubject.getField().accessFlags.isStatic());
@@ -563,10 +565,10 @@
final TestKotlinCompanionClass testedClass = COMPANION_LATE_INIT_PROPERTY_CLASS;
String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt",
"companionLateInitProperties_usePublicLateInitProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject outerClass = checkClassIsKept(dexInspector, testedClass.getOuterClassName());
- ClassSubject companionClass = checkClassIsKept(dexInspector, testedClass.getClassName());
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject outerClass = checkClassIsKept(codeInspector, testedClass.getOuterClassName());
+ ClassSubject companionClass = checkClassIsKept(codeInspector, testedClass.getClassName());
String propertyName = "publicLateInitProp";
FieldSubject fieldSubject = checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
assertTrue(fieldSubject.getField().accessFlags.isStatic());
@@ -587,9 +589,9 @@
final TestKotlinClass testedClass = OBJECT_PROPERTY_CLASS;
String mainClass = addMainToClasspath(
"properties.ObjectPropertiesKt", "objectProperties_usePrimitiveProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject objectClass = checkClassIsKept(dexInspector, testedClass.getClassName());
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject objectClass = checkClassIsKept(codeInspector, testedClass.getClassName());
String propertyName = "primitiveProp";
FieldSubject fieldSubject = checkFieldIsKept(objectClass, "int", propertyName);
assertTrue(fieldSubject.getField().accessFlags.isStatic());
@@ -614,9 +616,9 @@
final TestKotlinClass testedClass = OBJECT_PROPERTY_CLASS;
String mainClass = addMainToClasspath(
"properties.ObjectPropertiesKt", "objectProperties_usePrivateProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject objectClass = checkClassIsKept(dexInspector, testedClass.getClassName());
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject objectClass = checkClassIsKept(codeInspector, testedClass.getClassName());
String propertyName = "privateProp";
FieldSubject fieldSubject = checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName);
assertTrue(fieldSubject.getField().accessFlags.isStatic());
@@ -641,9 +643,9 @@
final TestKotlinClass testedClass = OBJECT_PROPERTY_CLASS;
String mainClass = addMainToClasspath(
"properties.ObjectPropertiesKt", "objectProperties_useInternalProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject objectClass = checkClassIsKept(dexInspector, testedClass.getClassName());
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject objectClass = checkClassIsKept(codeInspector, testedClass.getClassName());
String propertyName = "internalProp";
FieldSubject fieldSubject = checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName);
assertTrue(fieldSubject.getField().accessFlags.isStatic());
@@ -668,9 +670,9 @@
final TestKotlinClass testedClass = OBJECT_PROPERTY_CLASS;
String mainClass = addMainToClasspath(
"properties.ObjectPropertiesKt", "objectProperties_usePublicProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject objectClass = checkClassIsKept(dexInspector, testedClass.getClassName());
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject objectClass = checkClassIsKept(codeInspector, testedClass.getClassName());
String propertyName = "publicProp";
FieldSubject fieldSubject = checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName);
assertTrue(fieldSubject.getField().accessFlags.isStatic());
@@ -695,9 +697,9 @@
final TestKotlinClass testedClass = OBJECT_PROPERTY_CLASS;
String mainClass = addMainToClasspath(
"properties.ObjectPropertiesKt", "objectProperties_useLateInitPrivateProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject objectClass = checkClassIsKept(dexInspector, testedClass.getClassName());
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject objectClass = checkClassIsKept(codeInspector, testedClass.getClassName());
String propertyName = "privateLateInitProp";
FieldSubject fieldSubject = checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName);
assertTrue(fieldSubject.getField().accessFlags.isStatic());
@@ -722,9 +724,9 @@
final TestKotlinClass testedClass = OBJECT_PROPERTY_CLASS;
String mainClass = addMainToClasspath(
"properties.ObjectPropertiesKt", "objectProperties_useLateInitInternalProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject objectClass = checkClassIsKept(dexInspector, testedClass.getClassName());
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject objectClass = checkClassIsKept(codeInspector, testedClass.getClassName());
String propertyName = "internalLateInitProp";
FieldSubject fieldSubject = checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName);
assertTrue(fieldSubject.getField().accessFlags.isStatic());
@@ -745,9 +747,9 @@
final TestKotlinClass testedClass = OBJECT_PROPERTY_CLASS;
String mainClass = addMainToClasspath(
"properties.ObjectPropertiesKt", "objectProperties_useLateInitPublicProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject objectClass = checkClassIsKept(dexInspector, testedClass.getClassName());
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject objectClass = checkClassIsKept(codeInspector, testedClass.getClassName());
String propertyName = "publicLateInitProp";
FieldSubject fieldSubject = checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName);
assertTrue(fieldSubject.getField().accessFlags.isStatic());
@@ -768,9 +770,9 @@
final TestKotlinClass testedClass = FILE_PROPERTY_CLASS;
String mainClass = addMainToClasspath(
"properties.FilePropertiesKt", "fileProperties_usePrimitiveProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject objectClass = checkClassIsKept(dexInspector, testedClass.getClassName());
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject objectClass = checkClassIsKept(codeInspector, testedClass.getClassName());
String propertyName = "primitiveProp";
FieldSubject fieldSubject = checkFieldIsKept(objectClass, "int", propertyName);
assertTrue(fieldSubject.getField().accessFlags.isStatic());
@@ -795,9 +797,9 @@
final TestKotlinClass testedClass = FILE_PROPERTY_CLASS;
String mainClass = addMainToClasspath(
"properties.FilePropertiesKt", "fileProperties_usePrivateProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject objectClass = checkClassIsKept(dexInspector, testedClass.getClassName());
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject objectClass = checkClassIsKept(codeInspector, testedClass.getClassName());
String propertyName = "privateProp";
FieldSubject fieldSubject = checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName);
assertTrue(fieldSubject.getField().accessFlags.isStatic());
@@ -821,9 +823,9 @@
final TestKotlinClass testedClass = FILE_PROPERTY_CLASS;
String mainClass = addMainToClasspath(
"properties.FilePropertiesKt", "fileProperties_useInternalProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject objectClass = checkClassIsKept(dexInspector, testedClass.getClassName());
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject objectClass = checkClassIsKept(codeInspector, testedClass.getClassName());
String propertyName = "internalProp";
FieldSubject fieldSubject = checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName);
assertTrue(fieldSubject.getField().accessFlags.isStatic());
@@ -847,9 +849,9 @@
final TestKotlinClass testedClass = FILE_PROPERTY_CLASS;
String mainClass = addMainToClasspath(
"properties.FilePropertiesKt", "fileProperties_usePublicProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject objectClass = checkClassIsKept(dexInspector, testedClass.getClassName());
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject objectClass = checkClassIsKept(codeInspector, testedClass.getClassName());
String propertyName = "publicProp";
FieldSubject fieldSubject = checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName);
assertTrue(fieldSubject.getField().accessFlags.isStatic());
@@ -874,9 +876,9 @@
final TestKotlinClass testedClass = FILE_PROPERTY_CLASS;
String mainClass = addMainToClasspath(
"properties.FilePropertiesKt", "fileProperties_useLateInitPrivateProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject fileClass = checkClassIsKept(dexInspector, testedClass.getClassName());
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject fileClass = checkClassIsKept(codeInspector, testedClass.getClassName());
String propertyName = "privateLateInitProp";
FieldSubject fieldSubject = checkFieldIsKept(fileClass, JAVA_LANG_STRING, propertyName);
assertTrue(fieldSubject.getField().accessFlags.isStatic());
@@ -900,9 +902,9 @@
final TestKotlinClass testedClass = FILE_PROPERTY_CLASS;
String mainClass = addMainToClasspath(
"properties.FilePropertiesKt", "fileProperties_useLateInitInternalProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject objectClass = checkClassIsKept(dexInspector, testedClass.getClassName());
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject objectClass = checkClassIsKept(codeInspector, testedClass.getClassName());
String propertyName = "internalLateInitProp";
FieldSubject fieldSubject = checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName);
assertTrue(fieldSubject.getField().accessFlags.isStatic());
@@ -924,9 +926,9 @@
final TestKotlinClass testedClass = FILE_PROPERTY_CLASS;
String mainClass = addMainToClasspath(
"properties.FilePropertiesKt", "fileProperties_useLateInitPublicProp");
- runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject objectClass = checkClassIsKept(dexInspector, testedClass.getClassName());
+ runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject objectClass = checkClassIsKept(codeInspector, testedClass.getClassName());
String propertyName = "publicLateInitProp";
FieldSubject fieldSubject = checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName);
assertTrue(fieldSubject.getField().accessFlags.isStatic());
diff --git a/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java b/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java
index 5a30cfd..12fdaf9 100644
--- a/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java
@@ -10,9 +10,9 @@
import com.android.tools.r8.code.Instruction;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import java.util.Arrays;
import org.junit.Test;
@@ -35,8 +35,8 @@
final String mainClassName = ex1.getClassName();
final String extraRules = keepAllMembers(mainClassName);
runTest(FOLDER, mainClassName, extraRules, app -> {
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject clazz = checkClassIsKept(dexInspector, ex1.getClassName());
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject clazz = checkClassIsKept(codeInspector, ex1.getClassName());
MethodSubject testMethod = checkMethodIsKept(clazz, testMethodSignature);
DexCode dexCode = getDexCode(testMethod);
@@ -61,8 +61,8 @@
final String mainClassName = ex2.getClassName();
final String extraRules = keepAllMembers(mainClassName);
runTest(FOLDER, mainClassName, extraRules, app -> {
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject clazz = checkClassIsKept(dexInspector, ex2.getClassName());
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject clazz = checkClassIsKept(codeInspector, ex2.getClassName());
MethodSubject testMethod = checkMethodIsKept(clazz, testMethodSignature);
DexCode dexCode = getDexCode(testMethod);
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index 101bf06..d56c0e7 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -38,6 +38,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DexTypeList;
import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.ParameterAnnotationsList;
import com.android.tools.r8.ir.code.CatchHandlers;
@@ -59,8 +60,6 @@
import com.android.tools.r8.utils.AndroidAppConsumers;
import com.android.tools.r8.utils.DefaultDiagnosticsHandler;
import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.FoundClassSubject;
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ListUtils;
@@ -68,6 +67,8 @@
import com.android.tools.r8.utils.Reporter;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import java.io.IOException;
@@ -484,7 +485,7 @@
.forEach(
p -> {
try {
- DexInspector i = new DexInspector(AndroidApp.builder().addProgramFiles(p).build());
+ CodeInspector i = new CodeInspector(AndroidApp.builder().addProgramFiles(p).build());
assertFalse("Found " + clazz + " in file " + p, i.clazz(clazz).isPresent());
} catch (IOException | ExecutionException e) {
e.printStackTrace();
@@ -519,7 +520,7 @@
throws IOException, ExecutionException, ProguardRuleParserException,
CompilationFailedException {
AndroidApp originalApp = AndroidApp.builder().addProgramFiles(app).build();
- DexInspector originalInspector = new DexInspector(originalApp);
+ CodeInspector originalInspector = new CodeInspector(originalApp);
for (String clazz : mainDex) {
assertTrue("Class " + clazz + " does not exist in input",
originalInspector.clazz(clazz).isPresent());
@@ -583,8 +584,8 @@
assertTrue("Output run only produced one dex file.",
1 < Files.list(outDir).filter(FileUtils::isDexFile).count());
}
- DexInspector inspector =
- new DexInspector(
+ CodeInspector inspector =
+ new CodeInspector(
AndroidApp.builder().addProgramFiles(outDir.resolve("classes.dex")).build());
for (String clazz : mainDex) {
if (!inspector.clazz(clazz).isPresent()) {
@@ -650,7 +651,8 @@
DexAnnotationSet.empty(),
ParameterAnnotationsList.empty(),
code);
- IRCode ir = code.buildIR(method, null, options, Origin.unknown());
+ IRCode ir =
+ code.buildIR(method, null, GraphLense.getIdentityLense(), options, Origin.unknown());
RegisterAllocator allocator = new LinearScanRegisterAllocator(ir, options);
method.setCode(ir, allocator, options);
directMethods[i] = method;
@@ -676,7 +678,14 @@
DirectMappedDexApplication application = builder.build().toDirect();
ApplicationWriter writer =
new ApplicationWriter(
- application, options, null, null, NamingLens.getIdentityLens(), null, null);
+ application,
+ options,
+ null,
+ null,
+ GraphLense.getIdentityLense(),
+ NamingLens.getIdentityLens(),
+ null,
+ null);
ExecutorService executor = ThreadUtils.getExecutorService(options);
AndroidAppConsumers compatSink = new AndroidAppConsumers(options);
try {
@@ -771,7 +780,7 @@
}
@Override
- public Position getDebugPositionAtOffset(int offset) {
+ public Position getCanonicalDebugPositionAtOffset(int offset) {
throw new Unreachable();
}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/b72312389/B72312389.java b/src/test/java/com/android/tools/r8/maindexlist/b72312389/B72312389.java
index 0f1359e..c1741c9 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/b72312389/B72312389.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/b72312389/B72312389.java
@@ -23,7 +23,7 @@
import com.android.tools.r8.VmTestRunner;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.nio.file.Paths;
import java.util.ArrayList;
@@ -84,7 +84,7 @@
.setMainDexListConsumer(
(string, handler) -> mainDexList.content = string)
.build();
- DexInspector inspector = new DexInspector(ToolHelper.runR8(command));
+ CodeInspector inspector = new CodeInspector(ToolHelper.runR8(command));
assertTrue(inspector.clazz("instrumentationtest.InstrumentationTest").isPresent());
assertTrue(mainDexList.content.contains("junit/framework/TestCase.class"));
// TODO(72794301): Two copies of this message is a bit over the top.
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/CompositionalLenseTest.java b/src/test/java/com/android/tools/r8/memberrebinding/CompositionalLenseTest.java
index aba14cf..6c6e69d 100644
--- a/src/test/java/com/android/tools/r8/memberrebinding/CompositionalLenseTest.java
+++ b/src/test/java/com/android/tools/r8/memberrebinding/CompositionalLenseTest.java
@@ -3,7 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.memberrebinding;
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
@@ -11,15 +11,15 @@
import com.android.tools.r8.R8Command;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.code.InvokeVirtual;
+import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import java.nio.file.Path;
import java.util.List;
@@ -74,10 +74,10 @@
options.enableInlining = false;
options.enableClassMerging = false;
});
- DexInspector dexInspector = new DexInspector(processedApp);
- ClassSubject classSubject = dexInspector.clazz(TestMain.class);
+ CodeInspector codeInspector = new CodeInspector(processedApp);
+ ClassSubject classSubject = codeInspector.clazz(TestMain.class);
assertThat(classSubject, isPresent());
- MethodSubject methodSubject = classSubject.method(DexInspector.MAIN);
+ MethodSubject methodSubject = classSubject.method(CodeInspector.MAIN);
assertThat(methodSubject, isPresent());
DexCode dexCode = methodSubject.getMethod().getCode().asDexCode();
assertTrue(dexCode.instructions[2] instanceof InvokeVirtual);
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java
index 46a439d..80379e0 100644
--- a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java
+++ b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java
@@ -11,12 +11,12 @@
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.FieldAccessInstructionSubject;
-import com.android.tools.r8.utils.DexInspector.InstructionSubject;
-import com.android.tools.r8.utils.DexInspector.InvokeInstructionSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
import com.android.tools.r8.utils.TestDescriptionWatcher;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldAccessInstructionSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.InvokeInstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.nio.file.Path;
@@ -55,8 +55,8 @@
private final Frontend kind;
private final Path originalDex;
private final Path programFile;
- private final Consumer<DexInspector> inspection;
- private final Consumer<DexInspector> originalInspection;
+ private final Consumer<CodeInspector> inspection;
+ private final Consumer<CodeInspector> originalInspection;
private final int minApiLevel;
@Rule
@@ -101,9 +101,9 @@
return !invoke.holder().is("java.io.PrintStream");
}
- private static void inspectOriginalMain(DexInspector inspector) {
+ private static void inspectOriginalMain(CodeInspector inspector) {
MethodSubject main = inspector.clazz("memberrebinding.Memberrebinding")
- .method(DexInspector.MAIN);
+ .method(CodeInspector.MAIN);
Iterator<InvokeInstructionSubject> iterator =
main.iterateInstructions(MemberRebindingTest::coolInvokes);
assertTrue(iterator.next().holder().is("memberrebinding.ClassAtBottomOfChain"));
@@ -129,9 +129,9 @@
assertFalse(iterator.hasNext());
}
- private static void inspectMain(DexInspector inspector) {
+ private static void inspectMain(CodeInspector inspector) {
MethodSubject main = inspector.clazz("memberrebinding.Memberrebinding")
- .method(DexInspector.MAIN);
+ .method(CodeInspector.MAIN);
Iterator<InvokeInstructionSubject> iterator =
main.iterateInstructions(MemberRebindingTest::coolInvokes);
assertTrue(iterator.next().holder().is("memberrebinding.ClassAtBottomOfChain"));
@@ -160,9 +160,9 @@
assertFalse(iterator.hasNext());
}
- private static void inspectOriginalMain2(DexInspector inspector) {
+ private static void inspectOriginalMain2(CodeInspector inspector) {
MethodSubject main = inspector.clazz("memberrebinding2.Memberrebinding")
- .method(DexInspector.MAIN);
+ .method(CodeInspector.MAIN);
Iterator<FieldAccessInstructionSubject> iterator =
main.iterateInstructions(InstructionSubject::isFieldAccess);
// Run through instance put, static put, instance get and instance get.
@@ -176,9 +176,9 @@
assertFalse(iterator.hasNext());
}
- private static void inspectMain2(DexInspector inspector) {
+ private static void inspectMain2(CodeInspector inspector) {
MethodSubject main = inspector.clazz("memberrebinding2.Memberrebinding")
- .method(DexInspector.MAIN);
+ .method(CodeInspector.MAIN);
Iterator<FieldAccessInstructionSubject> iterator =
main.iterateInstructions(InstructionSubject::isFieldAccess);
// Run through instance put, static put, instance get and instance get.
@@ -195,7 +195,7 @@
public static MethodSignature TEST =
new MethodSignature("test", "void", new String[]{});
- private static void inspectOriginal3(DexInspector inspector) {
+ private static void inspectOriginal3(CodeInspector inspector) {
MethodSubject main = inspector.clazz("memberrebinding3.Memberrebinding").method(TEST);
Iterator<InvokeInstructionSubject> iterator =
main.iterateInstructions(InstructionSubject::isInvoke);
@@ -205,7 +205,7 @@
assertFalse(iterator.hasNext());
}
- private static void inspect3(DexInspector inspector) {
+ private static void inspect3(CodeInspector inspector) {
MethodSubject main = inspector.clazz("memberrebinding3.Memberrebinding").method(TEST);
Iterator<InvokeInstructionSubject> iterator =
main.iterateInstructions(InstructionSubject::isInvoke);
@@ -215,7 +215,7 @@
assertFalse(iterator.hasNext());
}
- private static void inspectOriginal4(DexInspector inspector) {
+ private static void inspectOriginal4(CodeInspector inspector) {
MethodSubject main = inspector.clazz("memberrebinding4.Memberrebinding").method(TEST);
Iterator<InvokeInstructionSubject> iterator =
main.iterateInstructions(InstructionSubject::isInvoke);
@@ -224,7 +224,7 @@
assertFalse(iterator.hasNext());
}
- private static void inspect4(DexInspector inspector) {
+ private static void inspect4(CodeInspector inspector) {
MethodSubject main = inspector.clazz("memberrebinding4.Memberrebinding").method(TEST);
Iterator<InvokeInstructionSubject> iterator =
main.iterateInstructions(InstructionSubject::isInvoke);
@@ -243,14 +243,14 @@
final String name;
final Frontend kind;
final AndroidVersion version;
- final Consumer<DexInspector> originalInspection;
- final Consumer<DexInspector> processedInspection;
+ final Consumer<CodeInspector> originalInspection;
+ final Consumer<CodeInspector> processedInspection;
private TestConfiguration(String name,
Frontend kind,
AndroidVersion version,
- Consumer<DexInspector> originalInspection,
- Consumer<DexInspector> processedInspection) {
+ Consumer<CodeInspector> originalInspection,
+ Consumer<CodeInspector> processedInspection) {
this.name = name;
this.kind = kind;
this.version = version;
@@ -261,8 +261,8 @@
public static void add(ImmutableList.Builder<TestConfiguration> builder,
String name,
AndroidVersion version,
- Consumer<DexInspector> originalInspection,
- Consumer<DexInspector> processedInspection) {
+ Consumer<CodeInspector> originalInspection,
+ Consumer<CodeInspector> processedInspection) {
if (version == AndroidVersion.PRE_N) {
builder.add(new TestConfiguration(name, Frontend.DEX, version, originalInspection,
processedInspection));
@@ -330,11 +330,11 @@
Path processed = Paths.get(out, "classes.dex");
if (kind == Frontend.DEX) {
- DexInspector inspector = new DexInspector(originalDex);
+ CodeInspector inspector = new CodeInspector(originalDex);
originalInspection.accept(inspector);
}
- DexInspector inspector = new DexInspector(processed);
+ CodeInspector inspector = new CodeInspector(processed);
inspection.accept(inspector);
// We don't run Art, as the test R8RunExamplesTest already does that.
diff --git a/src/test/java/com/android/tools/r8/movestringconstants/MoveStringConstantsTest.java b/src/test/java/com/android/tools/r8/movestringconstants/MoveStringConstantsTest.java
index 3429c2a..d7d0538 100644
--- a/src/test/java/com/android/tools/r8/movestringconstants/MoveStringConstantsTest.java
+++ b/src/test/java/com/android/tools/r8/movestringconstants/MoveStringConstantsTest.java
@@ -6,6 +6,7 @@
import static org.junit.Assert.assertTrue;
+import com.android.tools.r8.ClassFileConsumer;
import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.DexIndexedConsumer;
import com.android.tools.r8.R8Command;
@@ -13,22 +14,46 @@
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.InstructionSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.CfInstructionSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject.JumboStringMode;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.Iterator;
import java.util.function.Consumer;
import java.util.function.Predicate;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+@RunWith(Parameterized.class)
public class MoveStringConstantsTest extends TestBase {
- private void runTest(Consumer<DexInspector> inspection) throws Exception {
+
+ private Backend backend;
+
+ @Parameters(name = "Backend: {0}")
+ public static Collection<Backend> data() {
+ return Arrays.asList(Backend.values());
+ }
+
+ private void runTest(Consumer<CodeInspector> inspection) throws Exception {
R8Command.Builder builder = R8Command.builder();
builder.addProgramFiles(ToolHelper.getClassFileForTestClass(TestClass.class));
builder.addProgramFiles(ToolHelper.getClassFileForTestClass(Utils.class));
- builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+ builder.addLibraryFiles(
+ backend == Backend.DEX
+ ? ToolHelper.getDefaultAndroidJar()
+ : ToolHelper.getJava8RuntimeJar());
+ assert (backend == Backend.CF || backend == Backend.DEX);
+ builder.setProgramConsumer(
+ backend == Backend.DEX
+ ? DexIndexedConsumer.emptyConsumer()
+ : ClassFileConsumer.emptyConsumer());
builder.setMode(CompilationMode.RELEASE);
builder.addProguardConfiguration(
ImmutableList.of(
@@ -37,14 +62,35 @@
"-allowaccessmodification"
),
Origin.unknown());
- AndroidApp app = ToolHelper.runR8(builder.build());
- inspection.accept(new DexInspector(app));
+ AndroidApp app =
+ ToolHelper.runR8(
+ builder.build(),
+ options -> {
+ // This test relies on that TestClass.java/Utils.check will be inlined.
+ // Its size must fit into the inlining instruction limit. For CF, the default
+ // setting (5) is just too small.
+ options.inliningInstructionLimit = 10;
+ });
+ inspection.accept(
+ new CodeInspector(
+ app,
+ options -> {
+ options.enableCfFrontend = true;
+ }));
- // Run on Art to check generated code against verifier.
- runOnArt(app, TestClass.class);
+ if (backend == Backend.DEX) {
+ // Run on Art to check generated code against verifier.
+ runOnArt(app, TestClass.class);
+ } else {
+ runOnJava(app, TestClass.class);
+ }
}
- private void validate(DexInspector inspector) {
+ public MoveStringConstantsTest(Backend backend) {
+ this.backend = backend;
+ }
+
+ private void validate(CodeInspector inspector) {
ClassSubject clazz = inspector.clazz(TestClass.class);
assertTrue(clazz.isPresent());
@@ -53,37 +99,41 @@
clazz.method("void", "foo", ImmutableList.of(
"java.lang.String", "java.lang.String", "java.lang.String", "java.lang.String"));
assertTrue(methodThrowToBeInlined.isPresent());
- validateSequence(methodThrowToBeInlined.iterateInstructions(),
+ assert (backend == Backend.DEX || backend == Backend.CF);
+ Predicate<InstructionSubject> nullCheck =
+ backend == Backend.DEX
+ ? InstructionSubject::isIfEqz
+ : insn -> ((CfInstructionSubject) insn).isIfNull();
+ validateSequence(
+ methodThrowToBeInlined.iterateInstructions(),
// 'if' with "foo#1" is flipped.
- InstructionSubject::isIfEqz,
+ nullCheck,
// 'if' with "foo#2" is removed along with the constant.
// 'if' with "foo#3" is removed so now we have unconditional call.
- insn -> insn.isConstString("StringConstants::foo#3"),
+ insn -> insn.isConstString("StringConstants::foo#3", JumboStringMode.DISALLOW),
InstructionSubject::isInvokeStatic,
InstructionSubject::isThrow,
// 'if's with "foo#4" and "foo#5" are flipped, but their throwing branches
// are not moved to the end of the code (area for improvement?).
- insn -> insn.isConstString("StringConstants::foo#4"),
- InstructionSubject::isIfEqz, // Flipped if
+ insn -> insn.isConstString("StringConstants::foo#4", JumboStringMode.DISALLOW),
+ nullCheck, // Flipped if
InstructionSubject::isGoto, // Jump around throwing branch.
InstructionSubject::isInvokeStatic, // Throwing branch.
InstructionSubject::isThrow,
-
- insn -> insn.isConstString("StringConstants::foo#5"),
- InstructionSubject::isIfEqz, // Flipped if
+ insn -> insn.isConstString("StringConstants::foo#5", JumboStringMode.DISALLOW),
+ nullCheck, // Flipped if
InstructionSubject::isReturnVoid, // Final return statement.
InstructionSubject::isInvokeStatic, // Throwing branch.
InstructionSubject::isThrow,
// After 'if' with "foo#1" flipped, always throwing branch
// moved here along with the constant.
- insn -> insn.isConstString("StringConstants::foo#1"),
+ insn -> insn.isConstString("StringConstants::foo#1", JumboStringMode.DISALLOW),
InstructionSubject::isInvokeStatic,
- InstructionSubject::isThrow
- );
+ InstructionSubject::isThrow);
}
@SafeVarargs
diff --git a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java
new file mode 100644
index 0000000..2f59f6d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java
@@ -0,0 +1,406 @@
+// 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.naming;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DataDirectoryResource;
+import com.android.tools.r8.DataEntryResource;
+import com.android.tools.r8.DataResourceConsumer;
+import com.android.tools.r8.DataResourceProvider.Visitor;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.ArchiveResourceProvider;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import com.google.common.io.ByteStreams;
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class AdaptResourceFileContentsTest extends ProguardCompatibilityTestBase {
+
+ private Backend backend;
+
+ @Parameterized.Parameters(name = "Backend: {0}")
+ public static Collection<Backend> data() {
+ return Arrays.asList(Backend.values());
+ }
+
+ public AdaptResourceFileContentsTest(Backend backend) {
+ this.backend = backend;
+ }
+
+ protected static class CustomDataResourceConsumer implements DataResourceConsumer {
+
+ private final Map<String, ImmutableList<String>> resources = new HashMap<>();
+
+ @Override
+ public void accept(DataDirectoryResource directory, DiagnosticsHandler diagnosticsHandler) {
+ throw new Unreachable();
+ }
+
+ @Override
+ public void accept(DataEntryResource file, DiagnosticsHandler diagnosticsHandler) {
+ assertFalse(resources.containsKey(file.getName()));
+ try {
+ byte[] bytes = ByteStreams.toByteArray(file.getByteStream());
+ String contents = new String(bytes, Charset.defaultCharset());
+ resources.put(file.getName(), ImmutableList.copyOf(contents.split(System.lineSeparator())));
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void finished(DiagnosticsHandler handler) {}
+
+ public ImmutableList<String> get(String name) {
+ return resources.get(name);
+ }
+
+ public int size() {
+ return resources.size();
+ }
+ }
+
+ private static final ImmutableList<String> originalAllChangedResource =
+ ImmutableList.of(
+ "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$A",
+ "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$A<java.lang.String>",
+ "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$A<"
+ + "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$A>",
+ "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B",
+ // Test property values are rewritten.
+ "property=com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$A",
+ // Test XML content is rewritten.
+ "<tag>com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$A</tag>",
+ "<tag attr=\"com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$A\"></tag>",
+ // Test single-quote literals are rewritten.
+ "'com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$A'");
+
+ private static final ImmutableList<String> originalAllPresentResource =
+ ImmutableList.of(
+ "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass",
+ "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$A",
+ "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B");
+
+ private static final ImmutableList<String> originalAllUnchangedResource =
+ ImmutableList.of(
+ "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass",
+ // Test there is no renaming for the method on A.
+ "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$A.method",
+ "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$A.method()",
+ // Test there is no renaming for the methods on B.
+ "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B.method",
+ "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B.method()",
+ // Test various prefixes.
+ "42com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B",
+ "WithIdentifierPrefixcom.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B",
+ "-com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B",
+ "WithDashPrefix-com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B",
+ "$com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B",
+ "WithDollarPrefix$com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B",
+ ".com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B",
+ "WithDotPrefix.com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B",
+ // Test various suffixes.
+ "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B42",
+ "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$BWithIdentifierSuffix",
+ "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B-",
+ "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B-WithDashSuffix",
+ "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B$",
+ "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B$WithDollarSuffix",
+ "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B.",
+ "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B.WithDotSuffix");
+
+ private static String getProguardConfig(
+ boolean enableAdaptResourceFileContents, String adaptResourceFileContentsPathFilter) {
+ String adaptResourceFileContentsRule;
+ if (enableAdaptResourceFileContents) {
+ adaptResourceFileContentsRule = "-adaptresourcefilecontents";
+ if (adaptResourceFileContentsPathFilter != null) {
+ adaptResourceFileContentsRule += " " + adaptResourceFileContentsPathFilter;
+ }
+ } else {
+ adaptResourceFileContentsRule = "";
+ }
+ return String.join(
+ System.lineSeparator(),
+ adaptResourceFileContentsRule,
+ "-keep class " + AdaptResourceFileContentsTestClass.class.getName() + " {",
+ " public static void main(...);",
+ "}");
+ }
+
+ private static String getProguardConfigWithNeverInline(
+ boolean enableAdaptResourceFileContents, String adaptResourceFileContentsPathFilter) {
+ return String.join(
+ System.lineSeparator(),
+ getProguardConfig(enableAdaptResourceFileContents, adaptResourceFileContentsPathFilter),
+ "-neverinline class com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B {",
+ " public void method();",
+ "}");
+ }
+
+ @Test
+ public void testEnabled() throws Exception {
+ CustomDataResourceConsumer dataResourceConsumer = new CustomDataResourceConsumer();
+ AndroidApp out =
+ compileWithR8(getProguardConfigWithNeverInline(true, null), dataResourceConsumer);
+
+ // Check that the data resources have changed as expected.
+ checkAllAreChanged(
+ dataResourceConsumer.get("resource-all-changed.md"), originalAllChangedResource);
+ checkAllAreChanged(
+ dataResourceConsumer.get("resource-all-changed.txt"), originalAllChangedResource);
+
+ // Check that the new names are consistent with the actual application code.
+ checkAllArePresent(
+ dataResourceConsumer.get("resource-all-present.txt"), new CodeInspector(out));
+
+ // Check that the data resources have not changed unexpectedly.
+ checkAllAreUnchanged(
+ dataResourceConsumer.get("resource-all-changed.class"), originalAllChangedResource);
+ checkAllAreUnchanged(
+ dataResourceConsumer.get("resource-all-changed.cLaSs"), originalAllChangedResource);
+ checkAllAreUnchanged(
+ dataResourceConsumer.get("resource-all-unchanged.txt"), originalAllUnchangedResource);
+ }
+
+ @Test
+ public void testProguardBehavior() throws Exception {
+ Path proguardedJar =
+ File.createTempFile("proguarded", FileUtils.JAR_EXTENSION, temp.getRoot()).toPath();
+ runProguard6Raw(
+ proguardedJar,
+ ImmutableList.of(
+ AdaptResourceFileContentsTestClass.class,
+ AdaptResourceFileContentsTestClass.A.class,
+ AdaptResourceFileContentsTestClass.B.class),
+ getProguardConfig(true, null),
+ null,
+ getDataResources()
+ .stream()
+ .filter(x -> !x.getName().toLowerCase().endsWith(FileUtils.CLASS_EXTENSION))
+ .collect(Collectors.toList()));
+
+ // Visit each of the resources in the jar and check that their contents are as expected.
+ Set<String> filenames = new HashSet<>();
+ ArchiveResourceProvider.fromArchive(proguardedJar, true)
+ .accept(
+ new Visitor() {
+ @Override
+ public void visit(DataDirectoryResource directory) {}
+
+ @Override
+ public void visit(DataEntryResource file) {
+ try {
+ byte[] bytes = ByteStreams.toByteArray(file.getByteStream());
+ List<String> lines =
+ Arrays.asList(
+ new String(bytes, Charset.defaultCharset())
+ .split(System.lineSeparator()));
+ if (file.getName().endsWith("resource-all-changed.md")) {
+ checkAllAreChanged(lines, originalAllChangedResource);
+ } else if (file.getName().endsWith("resource-all-changed.txt")) {
+ checkAllAreChanged(lines, originalAllChangedResource);
+ } else if (file.getName().endsWith("resource-all-present.txt")) {
+ checkAllArePresent(lines, new CodeInspector(readJar(proguardedJar)));
+ } else if (file.getName().endsWith("resource-all-unchanged.txt")) {
+ checkAllAreUnchanged(lines, originalAllUnchangedResource);
+ }
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+
+ // Record that the jar contains a resource with this name.
+ filenames.add(file.getName());
+ }
+ });
+
+ // Check that the jar contains the four expected resources, and nothing else.
+ assertEquals(4, filenames.size());
+ assertTrue(filenames.stream().anyMatch(x -> x.endsWith("resource-all-changed.md")));
+ assertTrue(filenames.stream().anyMatch(x -> x.endsWith("resource-all-changed.txt")));
+ assertTrue(filenames.stream().anyMatch(x -> x.endsWith("resource-all-present.txt")));
+ assertTrue(filenames.stream().anyMatch(x -> x.endsWith("resource-all-unchanged.txt")));
+ }
+
+ @Test
+ public void testEnabledWithFilter() throws Exception {
+ CustomDataResourceConsumer dataResourceConsumer = new CustomDataResourceConsumer();
+ compileWithR8(getProguardConfigWithNeverInline(true, "*.md"), dataResourceConsumer);
+
+ // Check that the file matching the filter has changed as expected.
+ checkAllAreChanged(
+ dataResourceConsumer.get("resource-all-changed.md"), originalAllChangedResource);
+
+ // Check that all the other data resources are unchanged.
+ checkAllAreUnchanged(
+ dataResourceConsumer.get("resource-all-changed.class"), originalAllChangedResource);
+ checkAllAreUnchanged(
+ dataResourceConsumer.get("resource-all-changed.cLaSs"), originalAllChangedResource);
+ checkAllAreUnchanged(
+ dataResourceConsumer.get("resource-all-changed.txt"), originalAllChangedResource);
+ checkAllAreUnchanged(
+ dataResourceConsumer.get("resource-all-present.txt"), originalAllPresentResource);
+ checkAllAreUnchanged(
+ dataResourceConsumer.get("resource-all-unchanged.txt"), originalAllUnchangedResource);
+ }
+
+ @Test
+ public void testDisabled() throws Exception {
+ CustomDataResourceConsumer dataResourceConsumer = new CustomDataResourceConsumer();
+ compileWithR8(getProguardConfigWithNeverInline(false, null), dataResourceConsumer);
+
+ // Check that all data resources are unchanged.
+ checkAllAreUnchanged(
+ dataResourceConsumer.get("resource-all-changed.class"), originalAllChangedResource);
+ checkAllAreUnchanged(
+ dataResourceConsumer.get("resource-all-changed.cLaSs"), originalAllChangedResource);
+ checkAllAreUnchanged(
+ dataResourceConsumer.get("resource-all-changed.md"), originalAllChangedResource);
+ checkAllAreUnchanged(
+ dataResourceConsumer.get("resource-all-changed.txt"), originalAllChangedResource);
+ checkAllAreUnchanged(
+ dataResourceConsumer.get("resource-all-present.txt"), originalAllPresentResource);
+ checkAllAreUnchanged(
+ dataResourceConsumer.get("resource-all-unchanged.txt"), originalAllUnchangedResource);
+ }
+
+ private static void checkAllAreChanged(List<String> adaptedLines, List<String> originalLines) {
+ assertEquals(adaptedLines.size(), originalLines.size());
+ for (int i = 0; i < originalLines.size(); i++) {
+ assertNotEquals(originalLines.get(i), adaptedLines.get(i));
+ }
+ }
+
+ private static void checkAllArePresent(List<String> lines, CodeInspector inspector) {
+ for (String line : lines) {
+ assertThat(inspector.clazz(line), isPresent());
+ }
+ }
+
+ private static void checkAllAreUnchanged(List<String> adaptedLines, List<String> originalLines) {
+ assertEquals(adaptedLines.size(), originalLines.size());
+ for (int i = 0; i < originalLines.size(); i++) {
+ assertEquals(originalLines.get(i), adaptedLines.get(i));
+ }
+ }
+
+ private AndroidApp compileWithR8(String proguardConfig, DataResourceConsumer dataResourceConsumer)
+ throws CompilationFailedException, IOException {
+ assert backend == Backend.DEX || backend == Backend.CF;
+ R8Command command =
+ ToolHelper.allowTestProguardOptions(
+ ToolHelper.prepareR8CommandBuilder(
+ getAndroidApp(),
+ backend == Backend.DEX
+ ? DexIndexedConsumer.emptyConsumer()
+ : ClassFileConsumer.emptyConsumer())
+ .addProguardConfiguration(ImmutableList.of(proguardConfig), Origin.unknown()))
+ .addLibraryFiles(
+ backend == Backend.DEX
+ ? ToolHelper.getDefaultAndroidJar()
+ : ToolHelper.getJava8RuntimeJar())
+ .build();
+ return ToolHelper.runR8(
+ command,
+ options -> {
+ // TODO(christofferqa): Class inliner should respect -neverinline.
+ options.enableClassInlining = false;
+ options.enableClassMerging = true;
+ options.dataResourceConsumer = dataResourceConsumer;
+ });
+ }
+
+ private AndroidApp getAndroidApp() throws IOException {
+ AndroidApp.Builder builder = AndroidApp.builder();
+ builder.addProgramFiles(
+ ToolHelper.getClassFileForTestClass(AdaptResourceFileContentsTestClass.class),
+ ToolHelper.getClassFileForTestClass(AdaptResourceFileContentsTestClass.A.class),
+ ToolHelper.getClassFileForTestClass(AdaptResourceFileContentsTestClass.B.class));
+ getDataResources().forEach(builder::addDataResource);
+ return builder.build();
+ }
+
+ private List<DataEntryResource> getDataResources() {
+ return ImmutableList.of(
+ DataEntryResource.fromBytes(
+ String.join(System.lineSeparator(), originalAllChangedResource).getBytes(),
+ "resource-all-changed.class",
+ Origin.unknown()),
+ DataEntryResource.fromBytes(
+ String.join(System.lineSeparator(), originalAllChangedResource).getBytes(),
+ "resource-all-changed.cLaSs",
+ Origin.unknown()),
+ DataEntryResource.fromBytes(
+ String.join(System.lineSeparator(), originalAllChangedResource).getBytes(),
+ "resource-all-changed.md",
+ Origin.unknown()),
+ DataEntryResource.fromBytes(
+ String.join(System.lineSeparator(), originalAllChangedResource).getBytes(),
+ "resource-all-changed.txt",
+ Origin.unknown()),
+ DataEntryResource.fromBytes(
+ String.join(System.lineSeparator(), originalAllPresentResource).getBytes(),
+ "resource-all-present.txt",
+ Origin.unknown()),
+ DataEntryResource.fromBytes(
+ String.join(System.lineSeparator(), originalAllUnchangedResource).getBytes(),
+ "resource-all-unchanged.txt",
+ Origin.unknown()));
+ }
+}
+
+class AdaptResourceFileContentsTestClass {
+
+ public static void main(String[] args) {
+ B obj = new B();
+ obj.method();
+ }
+
+ static class A {
+
+ public void method() {
+ System.out.println("In A.method()");
+ }
+ }
+
+ static class B extends A {
+
+ public void method() {
+ System.out.println("In B.method()");
+ super.method();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java
new file mode 100644
index 0000000..51201df
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java
@@ -0,0 +1,534 @@
+// 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.naming;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DataDirectoryResource;
+import com.android.tools.r8.DataEntryResource;
+import com.android.tools.r8.DataResourceConsumer;
+import com.android.tools.r8.DataResourceProvider.Visitor;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.StringConsumer;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.naming.AdaptResourceFileContentsTest.CustomDataResourceConsumer;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.ArchiveResourceProvider;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.KeepingDiagnosticHandler;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class AdaptResourceFileNamesTest extends ProguardCompatibilityTestBase {
+
+ private Backend backend;
+
+ @Parameterized.Parameters(name = "Backend: {0}")
+ public static Collection<Backend> data() {
+ return Arrays.asList(Backend.values());
+ }
+
+ public AdaptResourceFileNamesTest(Backend backend) {
+ this.backend = backend;
+ }
+
+ private static final Path CF_DIR =
+ Paths.get(ToolHelper.EXAMPLES_CF_DIR).resolve("adaptresourcefilenames");
+ private static final Path TEST_JAR =
+ Paths.get(ToolHelper.EXAMPLES_BUILD_DIR)
+ .resolve("adaptresourcefilenames" + FileUtils.JAR_EXTENSION);
+
+ private KeepingDiagnosticHandler diagnosticsHandler;
+ private ClassNameMapper mapper = null;
+
+ @Before
+ public void reset() {
+ diagnosticsHandler = new KeepingDiagnosticHandler();
+ mapper = null;
+ }
+
+ private static String getProguardConfig(
+ boolean enableAdaptResourceFileNames, String adaptResourceFileNamesPathFilter) {
+ String adaptResourceFilenamesRule;
+ if (enableAdaptResourceFileNames) {
+ adaptResourceFilenamesRule = "-adaptresourcefilenames";
+ if (adaptResourceFileNamesPathFilter != null) {
+ adaptResourceFilenamesRule += " " + adaptResourceFileNamesPathFilter;
+ }
+ } else {
+ adaptResourceFilenamesRule = "";
+ }
+ return String.join(
+ System.lineSeparator(),
+ adaptResourceFilenamesRule,
+ "-keep class " + adaptresourcefilenames.TestClass.class.getName() + " {",
+ " public static void main(...);",
+ "}");
+ }
+
+ private static String getProguardConfigWithNeverInline(
+ boolean enableAdaptResourceFileNames, String adaptResourceFileNamesPathFilter) {
+ return String.join(
+ System.lineSeparator(),
+ getProguardConfig(enableAdaptResourceFileNames, adaptResourceFileNamesPathFilter),
+ "-neverinline class " + adaptresourcefilenames.A.class.getName() + " {",
+ " public void method();",
+ "}",
+ "-neverinline class " + adaptresourcefilenames.B.Inner.class.getName() + " {",
+ " public void method();",
+ "}");
+ }
+
+ @Test
+ public void testEnabled() throws Exception {
+ CustomDataResourceConsumer dataResourceConsumer = new CustomDataResourceConsumer();
+ compileWithR8(
+ getProguardConfigWithNeverInline(true, null), dataResourceConsumer, this::checkR8Renamings);
+ // Check that the generated resources have the expected names.
+ for (DataEntryResource dataResource : getOriginalDataResources()) {
+ assertNotNull(
+ "Resource not renamed as expected: " + dataResource.getName(),
+ dataResourceConsumer.get(getExpectedRenamingFor(dataResource.getName(), mapper)));
+ }
+ }
+
+ @Test
+ public void testEnabledWithFilter() throws Exception {
+ CustomDataResourceConsumer dataResourceConsumer = new CustomDataResourceConsumer();
+ compileWithR8(
+ getProguardConfigWithNeverInline(true, "**.md"),
+ dataResourceConsumer,
+ this::checkR8Renamings);
+ // Check that the generated resources have the expected names.
+ Map<String, String> expectedRenamings =
+ ImmutableMap.of("adaptresourcefilenames/B.md", "adaptresourcefilenames/b.md");
+ for (DataEntryResource dataResource : getOriginalDataResources()) {
+ assertNotNull(
+ "Resource not renamed as expected: " + dataResource.getName(),
+ dataResourceConsumer.get(
+ expectedRenamings.getOrDefault(dataResource.getName(), dataResource.getName())));
+ }
+ }
+
+ @Test
+ public void testDisabled() throws Exception {
+ CustomDataResourceConsumer dataResourceConsumer = new CustomDataResourceConsumer();
+ compileWithR8(getProguardConfigWithNeverInline(false, null), dataResourceConsumer);
+ // Check that none of the resources were renamed.
+ for (DataEntryResource dataResource : getOriginalDataResources()) {
+ assertNotNull(
+ "Resource not renamed as expected: " + dataResource.getName(),
+ dataResourceConsumer.get(dataResource.getName()));
+ }
+ }
+
+ @Test
+ public void testCollisionBehavior() throws Exception {
+ CustomDataResourceConsumer dataResourceConsumer = new CustomDataResourceConsumer();
+ compileWithR8(
+ getProguardConfigWithNeverInline(true, null),
+ dataResourceConsumer,
+ this::checkR8Renamings,
+ ImmutableList.<DataEntryResource>builder()
+ .addAll(getOriginalDataResources())
+ .add(
+ DataEntryResource.fromBytes(
+ new byte[0], "adaptresourcefilenames/b.txt", Origin.unknown()))
+ .build());
+ assertEquals(1, diagnosticsHandler.warnings.size());
+ assertThat(
+ diagnosticsHandler.warnings.get(0).getDiagnosticMessage(),
+ containsString("Resource 'adaptresourcefilenames/b.txt' already exists."));
+ assertEquals(getOriginalDataResources().size(), dataResourceConsumer.size());
+ }
+
+ @Test
+ public void testProguardBehavior() throws Exception {
+ Path inputJar = addDataResourcesToExistingJar(TEST_JAR, getOriginalDataResources());
+ Path proguardedJar =
+ File.createTempFile("proguarded", FileUtils.JAR_EXTENSION, temp.getRoot()).toPath();
+ Path proguardMapFile = File.createTempFile("mapping", ".txt", temp.getRoot()).toPath();
+ runProguard6Raw(proguardedJar, inputJar, getProguardConfig(true, null), proguardMapFile);
+ // Extract the names of the generated resources.
+ Set<String> filenames = new HashSet<>();
+ ArchiveResourceProvider.fromArchive(proguardedJar, true)
+ .accept(
+ new Visitor() {
+ @Override
+ public void visit(DataDirectoryResource directory) {}
+
+ @Override
+ public void visit(DataEntryResource file) {
+ filenames.add(file.getName());
+ }
+ });
+ // Check that the generated resources have the expected names.
+ ClassNameMapper mapper = ClassNameMapper.mapperFromFile(proguardMapFile);
+ for (DataEntryResource dataResource : getOriginalDataResources()) {
+ String expectedName = getExpectedRenamingFor(dataResource.getName(), mapper);
+ assertTrue(
+ "Resource not renamed to '" + expectedName + "' as expected: " + dataResource.getName(),
+ filenames.contains(expectedName));
+ }
+ }
+
+ @Test
+ public void testProguardCollisionBehavior() throws Exception {
+ List<DataEntryResource> originalDataResources = getOriginalDataResources();
+ Path inputJar =
+ addDataResourcesToExistingJar(
+ TEST_JAR,
+ ImmutableList.<DataEntryResource>builder()
+ .addAll(originalDataResources)
+ .add(
+ DataEntryResource.fromBytes(
+ new byte[0], "adaptresourcefilenames/b.txt", Origin.unknown()))
+ .build());
+ Path proguardedJar =
+ File.createTempFile("proguarded", FileUtils.JAR_EXTENSION, temp.getRoot()).toPath();
+ runProguard6Raw(
+ proguardedJar,
+ inputJar,
+ getProguardConfig(true, null),
+ null,
+ processResult -> {
+ assertEquals(0, processResult.exitCode);
+ assertThat(
+ processResult.stderr,
+ containsString(
+ "Warning: can't write resource [adaptresourcefilenames/b.txt] "
+ + "(Duplicate jar entry [adaptresourcefilenames/b.txt])"));
+ });
+ assertEquals(
+ originalDataResources.size(),
+ getDataResources(ArchiveResourceProvider.fromArchive(proguardedJar, true))
+ .stream()
+ .filter(dataResource -> !dataResource.getName().equals("META-INF/MANIFEST.MF"))
+ .count());
+ }
+
+ private AndroidApp compileWithR8(String proguardConfig, DataResourceConsumer dataResourceConsumer)
+ throws CompilationFailedException, IOException {
+ return compileWithR8(proguardConfig, dataResourceConsumer, null);
+ }
+
+ private AndroidApp compileWithR8(
+ String proguardConfig,
+ DataResourceConsumer dataResourceConsumer,
+ StringConsumer proguardMapConsumer)
+ throws CompilationFailedException, IOException {
+ return compileWithR8(
+ proguardConfig, dataResourceConsumer, proguardMapConsumer, getOriginalDataResources());
+ }
+
+ private AndroidApp compileWithR8(
+ String proguardConfig,
+ DataResourceConsumer dataResourceConsumer,
+ StringConsumer proguardMapConsumer,
+ List<DataEntryResource> dataResources)
+ throws CompilationFailedException, IOException {
+ assert backend == Backend.DEX || backend == Backend.CF;
+ R8Command command =
+ ToolHelper.allowTestProguardOptions(
+ ToolHelper.prepareR8CommandBuilder(
+ getAndroidApp(dataResources),
+ backend == Backend.DEX
+ ? DexIndexedConsumer.emptyConsumer()
+ : ClassFileConsumer.emptyConsumer(),
+ diagnosticsHandler)
+ .addProguardConfiguration(ImmutableList.of(proguardConfig), Origin.unknown()))
+ .addLibraryFiles(
+ backend == Backend.DEX
+ ? ToolHelper.getDefaultAndroidJar()
+ : ToolHelper.getJava8RuntimeJar())
+ .build();
+ return ToolHelper.runR8(
+ command,
+ options -> {
+ // TODO(christofferqa): Class inliner should respect -neverinline.
+ options.enableClassInlining = false;
+ options.enableClassMerging = true;
+ options.dataResourceConsumer = dataResourceConsumer;
+ options.proguardMapConsumer = proguardMapConsumer;
+ options.testing.suppressExperimentalCfBackendWarning = true;
+ });
+ }
+
+ private void checkR8Renamings(String proguardMap, DiagnosticsHandler handler) {
+ try {
+ // Check that the renamings are as expected. These exact renamings are not important as
+ // such, but the test expectations rely on them.
+ mapper = ClassNameMapper.mapperFromString(proguardMap);
+ assertEquals(
+ "adaptresourcefilenames.TestClass",
+ mapper.deobfuscateClassName("adaptresourcefilenames.TestClass"));
+ assertEquals(
+ "adaptresourcefilenames.B", mapper.deobfuscateClassName("adaptresourcefilenames.b"));
+ assertEquals(
+ "adaptresourcefilenames.B$Inner",
+ mapper.deobfuscateClassName("adaptresourcefilenames.a"));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private AndroidApp getAndroidApp(List<DataEntryResource> dataResources) throws IOException {
+ AndroidApp.Builder builder = AndroidApp.builder();
+ builder.addProgramFiles(ToolHelper.getClassFilesForTestDirectory(CF_DIR));
+ dataResources.forEach(builder::addDataResource);
+ return builder.build();
+ }
+
+ private static List<DataEntryResource> getOriginalDataResources() {
+ List<String> filenames =
+ ImmutableList.of(
+ // Filename with simple name in root directory.
+ "TestClass",
+ "B",
+ // Filename with qualified name in root directory.
+ "adaptresourcefilenames.TestClass",
+ "adaptresourcefilenames.B",
+ // Filename with qualified directory name in root directory.
+ "adaptresourcefilenames/TestClass",
+ "adaptresourcefilenames/B",
+ // Filename with simple name in sub directory.
+ "foo/bar/baz/TestClass",
+ "foo/bar/baz/B",
+ // Filename with qualified name in sub directory.
+ "foo/bar/baz/adaptresourcefiles.TestClass",
+ "foo/bar/baz/adaptresourcefiles.B",
+ // Filename with qualified directory name in sub directory.
+ "foo/bar/baz/adaptresourcefilenames/TestClass",
+ "foo/bar/baz/adaptresourcefilenames/B",
+ //
+ // SUFFIX VARIANTS:
+ //
+ // Filename with simple name and extension in root directory.
+ "TestClass.txt",
+ "B.txt",
+ // Filename with qualified name and extension in root directory.
+ "adaptresourcefilenames.TestClass.txt",
+ "adaptresourcefilenames.B.txt",
+ // Filename with qualified directory name and extension in root directory.
+ "adaptresourcefilenames/TestClass.txt",
+ "adaptresourcefilenames/B.txt",
+ // Filename with simple name and extension in sub directory.
+ "foo/bar/baz/TestClass.txt",
+ "foo/bar/baz/B.txt",
+ // Filename with qualified name and extension in sub directory.
+ "foo/bar/baz/adaptresourcefiles.TestClass.txt",
+ "foo/bar/baz/adaptresourcefiles.B.txt",
+ // Filename with qualified directory name and extension in sub directory.
+ "foo/bar/baz/adaptresourcefilenames/TestClass.txt",
+ "foo/bar/baz/adaptresourcefilenames/B.txt",
+ // Filename with other extension (used to test filtering).
+ "adaptresourcefilenames/TestClass.md",
+ "adaptresourcefilenames/B.md",
+ // Filename with dot suffix only.
+ "adaptresourcefilenames/TestClass.",
+ "adaptresourcefilenames/B.",
+ // Filename with dot suffix and extension.
+ "adaptresourcefilenames/TestClass.suffix.txt",
+ "adaptresourcefilenames/B.suffix.txt",
+ // Filename with dash suffix and extension.
+ "adaptresourcefilenames/TestClass-suffix.txt",
+ "adaptresourcefilenames/B-suffix.txt",
+ // Filename with dollar suffix and extension.
+ "adaptresourcefilenames/TestClass$suffix.txt",
+ "adaptresourcefilenames/B$suffix.txt",
+ // Filename with dollar suffix matching inner class and extension.
+ "adaptresourcefilenames/TestClass$Inner.txt",
+ "adaptresourcefilenames/B$Inner.txt",
+ // Filename with underscore suffix and extension.
+ "adaptresourcefilenames/TestClass_suffix.txt",
+ "adaptresourcefilenames/B_suffix.txt",
+ // Filename with whitespace suffix and extension.
+ "adaptresourcefilenames/TestClass suffix.txt",
+ "adaptresourcefilenames/B suffix.txt",
+ // Filename with identifier suffix and extension.
+ "adaptresourcefilenames/TestClasssuffix.txt",
+ "adaptresourcefilenames/Bsuffix.txt",
+ // Filename with numeric suffix and extension.
+ "adaptresourcefilenames/TestClass42.txt",
+ "adaptresourcefilenames/B42.txt",
+ //
+ // PREFIX VARIANTS:
+ //
+ // Filename with dot prefix and extension.
+ "adaptresourcefilenames/prefix.TestClass.txt",
+ "adaptresourcefilenames/prefix.B.txt",
+ // Filename with dash prefix and extension.
+ "adaptresourcefilenames/prefix-TestClass.txt",
+ "adaptresourcefilenames/prefix-B.txt",
+ // Filename with dollar prefix and extension.
+ "adaptresourcefilenames/prefix$TestClass.txt",
+ "adaptresourcefilenames/prefix$B.txt",
+ // Filename with identifier prefix and extension.
+ "adaptresourcefilenames/prefixTestClass.txt",
+ "adaptresourcefilenames/prefixB.txt",
+ // Filename with numeric prefix and extension.
+ "adaptresourcefilenames/42TestClass.txt",
+ "adaptresourcefilenames/42B.txt",
+ //
+ // PACKAGE RENAMING TESTS:
+ //
+ // Filename that matches a type, but only the directory should be renamed.
+ "adaptresourcefilenames/pkg/C",
+ // Filename that matches a type that should be renamed.
+ "adaptresourcefilenames/pkg/C.txt",
+ // Filename that does not match a type, but where the directory should be renamed.
+ "adaptresourcefilenames/pkg/file.txt",
+ // Filename that does not match a type, but where a directory-prefix should be renamed.
+ "adaptresourcefilenames/pkg/directory/file.txt",
+ // Filename that matches a type, but only the directory should be renamed.
+ "adaptresourcefilenames/pkg/innerpkg/D",
+ // Filename that matches a type that should be renamed.
+ "adaptresourcefilenames/pkg/innerpkg/D.txt",
+ // Filename that does not match a type, but where the directory should be renamed.
+ "adaptresourcefilenames/pkg/innerpkg/file.txt",
+ // Filename that does not match a type, but where a directory-prefix should be renamed.
+ "adaptresourcefilenames/pkg/innerpkg/directory/file.txt"
+ );
+ return filenames
+ .stream()
+ .map(filename -> DataEntryResource.fromBytes(new byte[0], filename, Origin.unknown()))
+ .collect(Collectors.toList());
+ }
+
+ private static String getExpectedRenamingFor(String filename, ClassNameMapper mapper) {
+ String typeName = null;
+ String suffix = null;
+ switch (filename) {
+ // Filename with dot only.
+ case "adaptresourcefilenames/B.":
+ typeName = "adaptresourcefilenames.B";
+ suffix = ".";
+ break;
+ // Filename with extension.
+ case "adaptresourcefilenames/B.txt":
+ typeName = "adaptresourcefilenames.B";
+ suffix = ".txt";
+ break;
+ // Filename with other extension (used to test filtering).
+ case "adaptresourcefilenames/B.md":
+ typeName = "adaptresourcefilenames.B";
+ suffix = ".md";
+ break;
+ // Filename with dot suffix and extension.
+ case "adaptresourcefilenames/B.suffix.txt":
+ typeName = "adaptresourcefilenames.B";
+ suffix = ".suffix.txt";
+ break;
+ // Filename with dash suffix and extension.
+ case "adaptresourcefilenames/B-suffix.txt":
+ typeName = "adaptresourcefilenames.B";
+ suffix = "-suffix.txt";
+ break;
+ // Filename with dollar suffix and extension.
+ case "adaptresourcefilenames/B$suffix.txt":
+ typeName = "adaptresourcefilenames.B";
+ suffix = "$suffix.txt";
+ break;
+ // Filename with dollar suffix matching inner class and extension.
+ case "adaptresourcefilenames/B$Inner.txt":
+ typeName = "adaptresourcefilenames.B$Inner";
+ suffix = ".txt";
+ break;
+ // Filename with underscore suffix and extension.
+ case "adaptresourcefilenames/B_suffix.txt":
+ typeName = "adaptresourcefilenames.B";
+ suffix = "_suffix.txt";
+ break;
+ // Filename with whitespace suffix and extension.
+ case "adaptresourcefilenames/B suffix.txt":
+ typeName = "adaptresourcefilenames.B";
+ suffix = " suffix.txt";
+ break;
+ //
+ // PACKAGE RENAMING TESTS
+ //
+ case "adaptresourcefilenames/pkg/C.txt":
+ typeName = "adaptresourcefilenames.pkg.C";
+ suffix = ".txt";
+ break;
+ case "adaptresourcefilenames/pkg/innerpkg/D.txt":
+ typeName = "adaptresourcefilenames.pkg.innerpkg.D";
+ suffix = ".txt";
+ break;
+ }
+ if (typeName != null) {
+ String renamedName = mapper.getObfuscatedToOriginalMapping().inverse().get(typeName);
+ assertNotNull(renamedName);
+ assertNotEquals(typeName, renamedName);
+ return renamedName.replace('.', '/') + suffix;
+ }
+ // Renamings for files in directories that match packages that have been renamed,
+ // but where the filename itself should not be renamed.
+ String samePackageAsType = null;
+ switch (filename) {
+ case "adaptresourcefilenames/pkg/C":
+ samePackageAsType = "adaptresourcefilenames.pkg.C";
+ suffix = "C";
+ break;
+ case "adaptresourcefilenames/pkg/file.txt":
+ samePackageAsType = "adaptresourcefilenames.pkg.C";
+ suffix = "file.txt";
+ break;
+ case "adaptresourcefilenames/pkg/directory/file.txt":
+ samePackageAsType = "adaptresourcefilenames.pkg.C";
+ suffix = "directory/file.txt";
+ break;
+ case "adaptresourcefilenames/pkg/innerpkg/D":
+ samePackageAsType = "adaptresourcefilenames.pkg.innerpkg.D";
+ suffix = "D";
+ break;
+ case "adaptresourcefilenames/pkg/innerpkg/file.txt":
+ samePackageAsType = "adaptresourcefilenames.pkg.innerpkg.D";
+ suffix = "file.txt";
+ break;
+ case "adaptresourcefilenames/pkg/innerpkg/directory/file.txt":
+ samePackageAsType = "adaptresourcefilenames.pkg.innerpkg.D";
+ suffix = "directory/file.txt";
+ break;
+ }
+ if (samePackageAsType != null) {
+ String renamedName = mapper.getObfuscatedToOriginalMapping().inverse().get(samePackageAsType);
+ assertNotNull(renamedName);
+ assertNotEquals(samePackageAsType, renamedName);
+ if (renamedName.contains(".")) {
+ String renamedPackageName = renamedName.substring(0, renamedName.lastIndexOf('.'));
+ return renamedPackageName.replace('.', '/') + "/" + suffix;
+ }
+ }
+ return filename;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/ApplyMappingTest.java b/src/test/java/com/android/tools/r8/naming/ApplyMappingTest.java
index bf6c895..aaecd58 100644
--- a/src/test/java/com/android/tools/r8/naming/ApplyMappingTest.java
+++ b/src/test/java/com/android/tools/r8/naming/ApplyMappingTest.java
@@ -6,6 +6,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -15,25 +16,30 @@
import com.android.tools.r8.StringConsumer;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.code.Instruction;
-import com.android.tools.r8.code.NewInstance;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.shaking.ProguardRuleParserException;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.InstructionSubject;
-import com.android.tools.r8.utils.DexInspector.InvokeInstructionSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.InvokeInstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.NewInstanceInstructionSubject;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.Iterator;
import java.util.concurrent.ExecutionException;
import org.junit.Before;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+@RunWith(Parameterized.class)
public class ApplyMappingTest extends TestBase {
private static final String MAPPING = "test-mapping.txt";
@@ -52,9 +58,20 @@
private Path out;
+ private Backend backend;
+
+ @Parameters(name = "Backend: {0}")
+ public static Collection<Backend> data() {
+ return Arrays.asList(Backend.values());
+ }
+
+ public ApplyMappingTest(Backend backend) {
+ this.backend = backend;
+ }
+
@Before
public void setup() throws IOException {
- out = temp.newFolder("outdex").toPath();
+ out = temp.newFolder("out").toPath();
}
@Test
@@ -97,8 +114,8 @@
pgConfig -> pgConfig.setApplyMappingFile(proguardMap))
.build());
- DexInspector inspector = new DexInspector(instrApp);
- MethodSubject main = inspector.clazz("applymapping044.Main").method(DexInspector.MAIN);
+ CodeInspector inspector = createDexInspector(instrApp);
+ MethodSubject main = inspector.clazz("applymapping044.Main").method(CodeInspector.MAIN);
Iterator<InvokeInstructionSubject> iterator =
main.iterateInstructions(InstructionSubject::isInvoke);
// B#m()
@@ -152,8 +169,8 @@
.build());
// Make sure the given proguard map is indeed applied.
- DexInspector inspector = new DexInspector(outputApp);
- MethodSubject main = inspector.clazz("applymapping044.Main").method(DexInspector.MAIN);
+ CodeInspector inspector = createDexInspector(outputApp);
+ MethodSubject main = inspector.clazz("applymapping044.Main").method(CodeInspector.MAIN);
Iterator<InvokeInstructionSubject> iterator =
main.iterateInstructions(InstructionSubject::isInvoke);
// B#m() -> y#n()
@@ -194,6 +211,15 @@
assertEquals("p", original_f.invokedMethod().name.toString());
}
+ private static CodeInspector createDexInspector(AndroidApp outputApp)
+ throws IOException, ExecutionException {
+ return new CodeInspector(
+ outputApp,
+ o -> {
+ o.enableCfFrontend = true;
+ });
+ }
+
@Test
public void test_naming001_rule105() throws Exception {
// keep rules to reserve D and E, along with a proguard map.
@@ -210,8 +236,8 @@
.build());
// Make sure the given proguard map is indeed applied.
- DexInspector inspector = new DexInspector(outputApp);
- MethodSubject main = inspector.clazz("naming001.D").method(DexInspector.MAIN);
+ CodeInspector inspector = createDexInspector(outputApp);
+ MethodSubject main = inspector.clazz("naming001.D").method(CodeInspector.MAIN);
Iterator<InvokeInstructionSubject> iterator =
main.iterateInstructions(InstructionSubject::isInvoke);
// mapping-105 simply includes: naming001.D#keep -> peek
@@ -242,13 +268,20 @@
.build());
// Make sure the given proguard map is indeed applied.
- DexInspector inspector = new DexInspector(outputApp);
- MethodSubject main = inspector.clazz("naming001.D").method(DexInspector.MAIN);
+ CodeInspector inspector = createDexInspector(outputApp);
+ MethodSubject main = inspector.clazz("naming001.D").method(CodeInspector.MAIN);
+ Iterator<InstructionSubject> iterator = main.iterateInstructions();
// naming001.E is renamed to a.a, so first instruction must be: new-instance La/a;
- Instruction[] instructions = main.getMethod().getCode().asDexCode().instructions;
- assertTrue(instructions[0] instanceof NewInstance);
- NewInstance newInstance = (NewInstance) instructions[0];
+ NewInstanceInstructionSubject newInstance = null;
+ while (iterator.hasNext()) {
+ InstructionSubject instruction = iterator.next();
+ if (instruction.isNewInstance()) {
+ newInstance = (NewInstanceInstructionSubject) instruction;
+ break;
+ }
+ }
+ assertNotNull(newInstance);
assertEquals( "La/a;", newInstance.getType().toSmaliString());
}
@@ -267,26 +300,35 @@
private R8Command.Builder getCommandForInstrumentation(
Path out, Path flag, Path mainApp, Path instrApp) throws IOException {
return R8Command.builder()
- .addLibraryFiles(ToolHelper.getDefaultAndroidJar(), mainApp)
+ .addLibraryFiles(
+ backend == Backend.DEX
+ ? ToolHelper.getDefaultAndroidJar()
+ : ToolHelper.getJava8RuntimeJar(),
+ mainApp)
.addProgramFiles(instrApp)
- .setOutput(out, OutputMode.DexIndexed)
+ .setOutput(out, backend == Backend.DEX ? OutputMode.DexIndexed : OutputMode.ClassFile)
.addProguardConfigurationFiles(flag);
}
private R8Command.Builder getCommandForApps(Path out, Path flag, Path... jars)
throws IOException {
return R8Command.builder()
- .addLibraryFiles(ToolHelper.getDefaultAndroidJar())
+ .addLibraryFiles(
+ backend == Backend.DEX
+ ? ToolHelper.getDefaultAndroidJar()
+ : ToolHelper.getJava8RuntimeJar())
.addProgramFiles(jars)
- .setOutput(out, OutputMode.DexIndexed)
+ .setOutput(out, backend == Backend.DEX ? OutputMode.DexIndexed : OutputMode.ClassFile)
.addProguardConfigurationFiles(flag);
}
private static AndroidApp runR8(R8Command command)
throws ProguardRuleParserException, ExecutionException, IOException {
- return ToolHelper.runR8(command, options -> {
- // Disable inlining to make this test not depend on inlining decisions.
- options.enableInlining = false;
- });
+ return ToolHelper.runR8(
+ command,
+ options -> {
+ // Disable inlining to make this test not depend on inlining decisions.
+ options.enableInlining = false;
+ });
}
}
diff --git a/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java b/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
index f27c88a..73cdaaf 100644
--- a/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
+++ b/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
@@ -6,28 +6,29 @@
import static com.android.tools.r8.utils.DescriptorUtils.descriptorToJavaType;
import static com.android.tools.r8.utils.DescriptorUtils.isValidJavaType;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
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.code.ConstString;
-import com.android.tools.r8.code.ConstStringJumbo;
-import com.android.tools.r8.code.Instruction;
-import com.android.tools.r8.graph.Code;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexValue.DexValueString;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ConstStringInstructionSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject.JumboStringMode;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
+import com.google.common.collect.Streams;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
@@ -37,9 +38,7 @@
import java.util.function.Consumer;
import java.util.stream.Stream;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
@@ -49,15 +48,20 @@
private final String appFileName;
private final List<String> keepRulesFiles;
- private final Consumer<DexInspector> inspection;
+ private final Consumer<CodeInspector> inspection;
+ private final Backend backend;
public IdentifierMinifierTest(
+ Backend backend,
String test,
List<String> keepRulesFiles,
- Consumer<DexInspector> inspection) {
- this.appFileName = ToolHelper.EXAMPLES_BUILD_DIR + test + "/classes.dex";
+ Consumer<CodeInspector> inspection) {
+ assert backend == Backend.DEX || backend == Backend.CF;
+ this.appFileName =
+ ToolHelper.EXAMPLES_BUILD_DIR + test + (backend == Backend.DEX ? "/classes.dex" : ".jar");
this.keepRulesFiles = keepRulesFiles;
this.inspection = inspection;
+ this.backend = backend;
}
private AndroidApp processedApp;
@@ -67,25 +71,34 @@
Path out = temp.getRoot().toPath();
R8Command.Builder builder =
ToolHelper.addProguardConfigurationConsumer(
- R8Command.builder(),
- pgConfig -> {
- pgConfig.setPrintMapping(true);
- pgConfig.setPrintMappingFile(out.resolve(ToolHelper.DEFAULT_PROGUARD_MAP_FILE));
- })
- .setOutput(out, OutputMode.DexIndexed)
- .addProguardConfigurationFiles(ListUtils.map(keepRulesFiles, Paths::get))
- .addLibraryFiles(ToolHelper.getDefaultAndroidJar());
+ R8Command.builder(),
+ pgConfig -> {
+ pgConfig.setPrintMapping(true);
+ pgConfig.setPrintMappingFile(out.resolve(ToolHelper.DEFAULT_PROGUARD_MAP_FILE));
+ })
+ .setOutput(out, backend == Backend.DEX ? OutputMode.DexIndexed : OutputMode.ClassFile)
+ .addProguardConfigurationFiles(ListUtils.map(keepRulesFiles, Paths::get));
+ if (backend == Backend.DEX) {
+ builder.addLibraryFiles(ToolHelper.getDefaultAndroidJar());
+ } else if (backend == Backend.CF) {
+ builder.addLibraryFiles(ToolHelper.getJava8RuntimeJar());
+ }
ToolHelper.getAppBuilder(builder).addProgramFiles(Paths.get(appFileName));
processedApp = ToolHelper.runR8(builder.build(), o -> o.debug = false);
}
@Test
public void identiferMinifierTest() throws Exception {
- DexInspector dexInspector = new DexInspector(processedApp);
- inspection.accept(dexInspector);
+ CodeInspector codeInspector =
+ new CodeInspector(
+ processedApp,
+ options -> {
+ options.enableCfFrontend = true;
+ });
+ inspection.accept(codeInspector);
}
- @Parameters(name = "test: {0} keep: {1}")
+ @Parameters(name = "[{0}] test: {1} keep: {2}")
public static Collection<Object[]> data() {
List<String> tests = Arrays.asList(
"adaptclassstrings",
@@ -94,7 +107,7 @@
"getmembers",
"identifiernamestring");
- Map<String, Consumer<DexInspector>> inspections = new HashMap<>();
+ Map<String, Consumer<CodeInspector>> inspections = new HashMap<>();
inspections.put("adaptclassstrings:keep-rules-1.txt", IdentifierMinifierTest::test1_rule1);
inspections.put("adaptclassstrings:keep-rules-2.txt", IdentifierMinifierTest::test1_rule2);
inspections.put(
@@ -105,25 +118,38 @@
inspections.put("identifiernamestring:keep-rules-2.txt", IdentifierMinifierTest::test2_rule2);
inspections.put("identifiernamestring:keep-rules-3.txt", IdentifierMinifierTest::test2_rule3);
- return NamingTestBase.createTests(tests, inspections);
+ Collection<Object[]> parameters = NamingTestBase.createTests(tests, inspections);
+
+ // Duplicate parameters for each backend.
+ List<Object[]> parametersWithBackend = new ArrayList<>();
+ for (Backend backend : Backend.values()) {
+ for (Object[] row : parameters) {
+ Object[] newRow = new Object[row.length + 1];
+ newRow[0] = backend;
+ System.arraycopy(row, 0, newRow, 1, row.length);
+ parametersWithBackend.add(newRow);
+ }
+ }
+
+ return parametersWithBackend;
}
// Without -adaptclassstrings
- private static void test1_rule1(DexInspector inspector) {
+ private static void test1_rule1(CodeInspector inspector) {
ClassSubject mainClass = inspector.clazz("adaptclassstrings.Main");
- MethodSubject main = mainClass.method(DexInspector.MAIN);
- Code mainCode = main.getMethod().getCode();
- verifyPresenceOfConstString(mainCode.asDexCode().instructions);
- int renamedYetFoundIdentifierCount =
- countRenamedClassIdentifier(inspector, mainCode.asDexCode().instructions);
- assertEquals(0, renamedYetFoundIdentifierCount);
+ MethodSubject main = mainClass.method(CodeInspector.MAIN);
+ assertTrue(main instanceof FoundMethodSubject);
+ FoundMethodSubject foundMain = (FoundMethodSubject) main;
+ verifyPresenceOfConstString(foundMain);
+ int renamedYetFoundIdentifierCount = countRenamedClassIdentifier(inspector, foundMain);
+ assertEquals(4, renamedYetFoundIdentifierCount);
ClassSubject aClass = inspector.clazz("adaptclassstrings.A");
MethodSubject bar = aClass.method("void", "bar", ImmutableList.of());
- Code barCode = bar.getMethod().getCode();
- verifyPresenceOfConstString(barCode.asDexCode().instructions);
- renamedYetFoundIdentifierCount =
- countRenamedClassIdentifier(inspector, barCode.asDexCode().instructions);
+ assertTrue(bar instanceof FoundMethodSubject);
+ FoundMethodSubject foundBar = (FoundMethodSubject) bar;
+ verifyPresenceOfConstString(foundBar);
+ renamedYetFoundIdentifierCount = countRenamedClassIdentifier(inspector, foundBar);
assertEquals(0, renamedYetFoundIdentifierCount);
renamedYetFoundIdentifierCount =
@@ -132,21 +158,21 @@
}
// With -adaptclassstrings *.*A
- private static void test1_rule2(DexInspector inspector) {
+ private static void test1_rule2(CodeInspector inspector) {
ClassSubject mainClass = inspector.clazz("adaptclassstrings.Main");
- MethodSubject main = mainClass.method(DexInspector.MAIN);
- Code mainCode = main.getMethod().getCode();
- verifyPresenceOfConstString(mainCode.asDexCode().instructions);
- int renamedYetFoundIdentifierCount =
- countRenamedClassIdentifier(inspector, mainCode.asDexCode().instructions);
- assertEquals(0, renamedYetFoundIdentifierCount);
+ MethodSubject main = mainClass.method(CodeInspector.MAIN);
+ assertTrue(main instanceof FoundMethodSubject);
+ FoundMethodSubject foundMain = (FoundMethodSubject) main;
+ verifyPresenceOfConstString(foundMain);
+ int renamedYetFoundIdentifierCount = countRenamedClassIdentifier(inspector, foundMain);
+ assertEquals(4, renamedYetFoundIdentifierCount);
ClassSubject aClass = inspector.clazz("adaptclassstrings.A");
MethodSubject bar = aClass.method("void", "bar", ImmutableList.of());
- Code barCode = bar.getMethod().getCode();
- verifyPresenceOfConstString(barCode.asDexCode().instructions);
- renamedYetFoundIdentifierCount =
- countRenamedClassIdentifier(inspector, barCode.asDexCode().instructions);
+ assertTrue(bar instanceof FoundMethodSubject);
+ FoundMethodSubject foundBar = (FoundMethodSubject) bar;
+ verifyPresenceOfConstString(foundBar);
+ renamedYetFoundIdentifierCount = countRenamedClassIdentifier(inspector, foundBar);
assertEquals(1, renamedYetFoundIdentifierCount);
renamedYetFoundIdentifierCount =
@@ -154,64 +180,66 @@
assertEquals(1, renamedYetFoundIdentifierCount);
}
- private static void test_atomicfieldupdater(DexInspector inspector) {
+ private static void test_atomicfieldupdater(CodeInspector inspector) {
ClassSubject mainClass = inspector.clazz("atomicfieldupdater.Main");
- MethodSubject main = mainClass.method(DexInspector.MAIN);
- Code mainCode = main.getMethod().getCode();
- verifyPresenceOfConstString(mainCode.asDexCode().instructions);
+ MethodSubject main = mainClass.method(CodeInspector.MAIN);
+ assertTrue(main instanceof FoundMethodSubject);
+ FoundMethodSubject foundMain = (FoundMethodSubject) main;
+ verifyPresenceOfConstString(foundMain);
ClassSubject a = inspector.clazz("atomicfieldupdater.A");
- Set<Instruction> constStringInstructions =
- getRenamedMemberIdentifierConstStrings(a, mainCode.asDexCode().instructions);
+ Set<InstructionSubject> constStringInstructions =
+ getRenamedMemberIdentifierConstStrings(a, foundMain);
assertEquals(3, constStringInstructions.size());
}
- private static void test_forname(DexInspector inspector) {
+ private static void test_forname(CodeInspector inspector) {
ClassSubject mainClass = inspector.clazz("forname.Main");
- MethodSubject main = mainClass.method(DexInspector.MAIN);
- Code mainCode = main.getMethod().getCode();
- verifyPresenceOfConstString(mainCode.asDexCode().instructions);
- int renamedYetFoundIdentifierCount =
- countRenamedClassIdentifier(inspector, mainCode.asDexCode().instructions);
+ MethodSubject main = mainClass.method(CodeInspector.MAIN);
+ assertTrue(main instanceof FoundMethodSubject);
+ FoundMethodSubject foundMain = (FoundMethodSubject) main;
+ verifyPresenceOfConstString(foundMain);
+ int renamedYetFoundIdentifierCount = countRenamedClassIdentifier(inspector, foundMain);
assertEquals(1, renamedYetFoundIdentifierCount);
}
- private static void test_getmembers(DexInspector inspector) {
+ private static void test_getmembers(CodeInspector inspector) {
ClassSubject mainClass = inspector.clazz("getmembers.Main");
- MethodSubject main = mainClass.method(DexInspector.MAIN);
- Code mainCode = main.getMethod().getCode();
- verifyPresenceOfConstString(mainCode.asDexCode().instructions);
+ MethodSubject main = mainClass.method(CodeInspector.MAIN);
+ assertTrue(main instanceof FoundMethodSubject);
+ FoundMethodSubject foundMain = (FoundMethodSubject) main;
+ verifyPresenceOfConstString(foundMain);
ClassSubject a = inspector.clazz("getmembers.A");
- Set<Instruction> constStringInstructions =
- getRenamedMemberIdentifierConstStrings(a, mainCode.asDexCode().instructions);
+ Set<InstructionSubject> constStringInstructions =
+ getRenamedMemberIdentifierConstStrings(a, foundMain);
assertEquals(2, constStringInstructions.size());
ClassSubject b = inspector.clazz("getmembers.B");
MethodSubject inliner = b.method("java.lang.String", "inliner", ImmutableList.of());
- Code inlinerCode = inliner.getMethod().getCode();
- constStringInstructions =
- getRenamedMemberIdentifierConstStrings(a, inlinerCode.asDexCode().instructions);
+ assertTrue(inliner instanceof FoundMethodSubject);
+ FoundMethodSubject foundInliner = (FoundMethodSubject) inliner;
+ constStringInstructions = getRenamedMemberIdentifierConstStrings(a, foundInliner);
assertEquals(1, constStringInstructions.size());
}
// Without -identifiernamestring
- private static void test2_rule1(DexInspector inspector) {
+ private static void test2_rule1(CodeInspector inspector) {
ClassSubject mainClass = inspector.clazz("identifiernamestring.Main");
- MethodSubject main = mainClass.method(DexInspector.MAIN);
- Code mainCode = main.getMethod().getCode();
- verifyPresenceOfConstString(mainCode.asDexCode().instructions);
- int renamedYetFoundIdentifierCount =
- countRenamedClassIdentifier(inspector, mainCode.asDexCode().instructions);
- assertEquals(0, renamedYetFoundIdentifierCount);
+ MethodSubject main = mainClass.method(CodeInspector.MAIN);
+ assertTrue(main instanceof FoundMethodSubject);
+ FoundMethodSubject foundMain = (FoundMethodSubject) main;
+ verifyPresenceOfConstString(foundMain);
+ int renamedYetFoundIdentifierCount = countRenamedClassIdentifier(inspector, foundMain);
+ assertEquals(1, renamedYetFoundIdentifierCount);
ClassSubject aClass = inspector.clazz("identifiernamestring.A");
MethodSubject aInit =
aClass.method("void", "<init>", ImmutableList.of());
- Code initCode = aInit.getMethod().getCode();
- verifyPresenceOfConstString(initCode.asDexCode().instructions);
- renamedYetFoundIdentifierCount =
- countRenamedClassIdentifier(inspector, initCode.asDexCode().instructions);
+ assertTrue(aInit instanceof FoundMethodSubject);
+ FoundMethodSubject foundAInit = (FoundMethodSubject) aInit;
+ verifyPresenceOfConstString(foundAInit);
+ renamedYetFoundIdentifierCount = countRenamedClassIdentifier(inspector, foundAInit);
assertEquals(0, renamedYetFoundIdentifierCount);
renamedYetFoundIdentifierCount =
@@ -220,24 +248,22 @@
}
// With -identifiernamestring for annotations and name-based filters
- private static void test2_rule2(DexInspector inspector) {
+ private static void test2_rule2(CodeInspector inspector) {
ClassSubject mainClass = inspector.clazz("identifiernamestring.Main");
- MethodSubject main = mainClass.method(DexInspector.MAIN);
- assertTrue(main.isPresent());
- Code mainCode = main.getMethod().getCode();
- assertTrue(mainCode.isDexCode());
- verifyPresenceOfConstString(mainCode.asDexCode().instructions);
- int renamedYetFoundIdentifierCount =
- countRenamedClassIdentifier(inspector, mainCode.asDexCode().instructions);
- assertEquals(1, renamedYetFoundIdentifierCount);
+ MethodSubject main = mainClass.method(CodeInspector.MAIN);
+ assertTrue(main instanceof FoundMethodSubject);
+ FoundMethodSubject foundMain = (FoundMethodSubject) main;
+ verifyPresenceOfConstString(foundMain);
+ int renamedYetFoundIdentifierCount = countRenamedClassIdentifier(inspector, foundMain);
+ assertEquals(2, renamedYetFoundIdentifierCount);
ClassSubject aClass = inspector.clazz("identifiernamestring.A");
MethodSubject aInit =
aClass.method("void", "<init>", ImmutableList.of());
- Code initCode = aInit.getMethod().getCode();
- verifyPresenceOfConstString(initCode.asDexCode().instructions);
- renamedYetFoundIdentifierCount =
- countRenamedClassIdentifier(inspector, initCode.asDexCode().instructions);
+ assertTrue(aInit instanceof FoundMethodSubject);
+ FoundMethodSubject foundAInit = (FoundMethodSubject) aInit;
+ verifyPresenceOfConstString(foundAInit);
+ renamedYetFoundIdentifierCount = countRenamedClassIdentifier(inspector, foundAInit);
assertEquals(1, renamedYetFoundIdentifierCount);
renamedYetFoundIdentifierCount =
@@ -245,63 +271,55 @@
assertEquals(2, renamedYetFoundIdentifierCount);
}
- // With -identifiernamestring for reflective methods
- private static void test2_rule3(DexInspector inspector) {
+ // With -identifiernamestring for reflective methods in testing class R.
+ private static void test2_rule3(CodeInspector inspector) {
ClassSubject mainClass = inspector.clazz("identifiernamestring.Main");
- MethodSubject main = mainClass.method(DexInspector.MAIN);
- assertTrue(main.isPresent());
- Code mainCode = main.getMethod().getCode();
- assertTrue(mainCode.isDexCode());
- verifyPresenceOfConstString(mainCode.asDexCode().instructions);
+ MethodSubject main = mainClass.method(CodeInspector.MAIN);
+ assertTrue(main instanceof FoundMethodSubject);
+ FoundMethodSubject foundMain = (FoundMethodSubject) main;
+ verifyPresenceOfConstString(foundMain);
ClassSubject b = inspector.clazz("identifiernamestring.B");
- Set<Instruction> constStringInstructions =
- getRenamedMemberIdentifierConstStrings(b, mainCode.asDexCode().instructions);
+ Set<InstructionSubject> constStringInstructions =
+ getRenamedMemberIdentifierConstStrings(b, foundMain);
assertEquals(2, constStringInstructions.size());
}
- private static void verifyPresenceOfConstString(Instruction[] instructions) {
- boolean presence =
- Arrays.stream(instructions)
- .anyMatch(instr -> instr instanceof ConstString || instr instanceof ConstStringJumbo);
- assertTrue(presence);
+ private static void verifyPresenceOfConstString(FoundMethodSubject method) {
+ assertTrue(
+ method
+ .iterateInstructions(instruction -> instruction.isConstString(JumboStringMode.ALLOW))
+ .hasNext());
}
- private static String retrieveString(Instruction instr) {
- if (instr instanceof ConstString) {
- ConstString cnst = (ConstString) instr;
- return cnst.getString().toString();
- } else if (instr instanceof ConstStringJumbo) {
- ConstStringJumbo cnst = (ConstStringJumbo) instr;
- return cnst.getString().toString();
- }
- return null;
- }
-
- private static Stream<Instruction> getConstStringInstructions(Instruction[] instructions) {
- return Arrays.stream(instructions)
- .filter(instr -> instr instanceof ConstString || instr instanceof ConstStringJumbo);
+ private static Stream<InstructionSubject> getConstStringInstructions(FoundMethodSubject method) {
+ return Streams.stream(method.iterateInstructions())
+ .filter(instr -> instr.isConstString(JumboStringMode.ALLOW));
}
private static int countRenamedClassIdentifier(
- DexInspector inspector, Instruction[] instructions) {
- return getConstStringInstructions(instructions)
- .reduce(0, (cnt, instr) -> {
- String cnstString = retrieveString(instr);
- assertNotNull(cnstString);
- if (isValidJavaType(cnstString)) {
- ClassSubject classSubject = inspector.clazz(cnstString);
- if (classSubject.isRenamed()
- && descriptorToJavaType(classSubject.getFinalDescriptor()).equals(cnstString)) {
- return cnt + 1;
- }
- }
- return cnt;
- }, Integer::sum);
+ CodeInspector inspector, FoundMethodSubject method) {
+ return getConstStringInstructions(method)
+ .reduce(
+ 0,
+ (cnt, instr) -> {
+ assert (instr instanceof ConstStringInstructionSubject);
+ String cnstString =
+ ((ConstStringInstructionSubject) instr).getString().toSourceString();
+ if (isValidJavaType(cnstString)) {
+ ClassSubject classSubject = inspector.clazz(cnstString);
+ if (classSubject.isRenamed()
+ && descriptorToJavaType(classSubject.getFinalDescriptor()).equals(cnstString)) {
+ return cnt + 1;
+ }
+ }
+ return cnt;
+ },
+ Integer::sum);
}
private static int countRenamedClassIdentifier(
- DexInspector inspector, DexEncodedField[] fields) {
+ CodeInspector inspector, DexEncodedField[] fields) {
return Arrays.stream(fields)
.filter(encodedField -> encodedField.getStaticValue() instanceof DexValueString)
.reduce(0, (cnt, encodedField) -> {
@@ -318,22 +336,27 @@
}, Integer::sum);
}
- private static Set<Instruction> getRenamedMemberIdentifierConstStrings(
- ClassSubject clazz, Instruction[] instructions) {
- Set<Instruction> result = Sets.newIdentityHashSet();
- getConstStringInstructions(instructions).forEach(instr -> {
- String cnstString = retrieveString(instr);
- clazz.forAllMethods(foundMethodSubject -> {
- if (foundMethodSubject.getFinalSignature().name.equals(cnstString)) {
- result.add(instr);
- }
- });
- clazz.forAllFields(foundFieldSubject -> {
- if (foundFieldSubject.getFinalSignature().name.equals(cnstString)) {
- result.add(instr);
- }
- });
- });
+ private static Set<InstructionSubject> getRenamedMemberIdentifierConstStrings(
+ ClassSubject clazz, FoundMethodSubject method) {
+ Set<InstructionSubject> result = Sets.newIdentityHashSet();
+ getConstStringInstructions(method)
+ .forEach(
+ instr -> {
+ String cnstString =
+ ((ConstStringInstructionSubject) instr).getString().toSourceString();
+ clazz.forAllMethods(
+ foundMethodSubject -> {
+ if (foundMethodSubject.getFinalSignature().name.equals(cnstString)) {
+ result.add(instr);
+ }
+ });
+ clazz.forAllFields(
+ foundFieldSubject -> {
+ if (foundFieldSubject.getFinalSignature().name.equals(cnstString)) {
+ result.add(instr);
+ }
+ });
+ });
return result;
}
diff --git a/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java b/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java
index 982e935..28884cc 100644
--- a/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java
+++ b/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java
@@ -26,9 +26,9 @@
import com.android.tools.r8.smali.SmaliBuilder;
import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
import com.android.tools.r8.smali.SmaliTestBase;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.FieldSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
import com.google.common.collect.ImmutableList;
import java.util.List;
import org.junit.Test;
@@ -52,7 +52,7 @@
"-identifiernamestring class " + CLASS_NAME + " { java.lang.String aClassName; }",
"-keep class " + CLASS_NAME,
"-dontoptimize");
- DexInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
+ CodeInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
ClassSubject clazz = inspector.clazz(CLASS_NAME);
assertTrue(clazz.isPresent());
@@ -84,7 +84,7 @@
"-identifiernamestring class " + CLASS_NAME + " { java.lang.String aClassName; }",
"-keep class " + CLASS_NAME,
"-dontoptimize");
- DexInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
+ CodeInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
ClassSubject clazz = inspector.clazz(CLASS_NAME);
assertTrue(clazz.isPresent());
@@ -126,7 +126,7 @@
"-keep class " + CLASS_NAME,
"-keep,allowobfuscation class " + BOO,
"-dontoptimize");
- DexInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
+ CodeInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
ClassSubject clazz = inspector.clazz(CLASS_NAME);
assertTrue(clazz.isPresent());
@@ -163,7 +163,7 @@
"-identifiernamestring class " + CLASS_NAME + " { static java.lang.String sClassName; }",
"-keep class " + CLASS_NAME,
"-dontoptimize");
- DexInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
+ CodeInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
ClassSubject clazz = inspector.clazz(CLASS_NAME);
assertTrue(clazz.isPresent());
@@ -194,7 +194,7 @@
"-identifiernamestring class " + CLASS_NAME + " { static java.lang.String sClassName; }",
"-keep class " + CLASS_NAME,
"-dontoptimize");
- DexInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
+ CodeInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
ClassSubject clazz = inspector.clazz(CLASS_NAME);
assertTrue(clazz.isPresent());
@@ -232,7 +232,7 @@
"-keep class " + CLASS_NAME,
"-keep,allowobfuscation class " + BOO,
"-dontoptimize");
- DexInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
+ CodeInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
ClassSubject clazz = inspector.clazz(CLASS_NAME);
assertTrue(clazz.isPresent());
@@ -262,7 +262,7 @@
"-identifiernamestring class " + CLASS_NAME + " { static java.lang.String sClassName; }",
"-keep class " + CLASS_NAME + " { static java.lang.String sClassName; }",
"-dontshrink");
- DexInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
+ CodeInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
ClassSubject clazz = inspector.clazz(CLASS_NAME);
assertTrue(clazz.isPresent());
@@ -284,7 +284,7 @@
"-keep class " + CLASS_NAME + " { static java.lang.String sClassName; }",
"-keep,allowobfuscation class " + BOO,
"-dontshrink");
- DexInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
+ CodeInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
ClassSubject clazz = inspector.clazz(CLASS_NAME);
assertTrue(clazz.isPresent());
@@ -308,7 +308,7 @@
"-keep class " + CLASS_NAME + " { static java.lang.String sFieldName; }",
"-keep,allowobfuscation class " + BOO + " { <fields>; }",
"-dontshrink");
- DexInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
+ CodeInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
ClassSubject clazz = inspector.clazz(CLASS_NAME);
assertTrue(clazz.isPresent());
@@ -332,7 +332,7 @@
"-keep class " + CLASS_NAME + " { static java.lang.String sMethodName; }",
"-keep,allowobfuscation class " + BOO + " { <methods>; }",
"-dontshrink");
- DexInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
+ CodeInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
ClassSubject clazz = inspector.clazz(CLASS_NAME);
assertTrue(clazz.isPresent());
@@ -362,7 +362,7 @@
List<String> pgConfigs = ImmutableList.of(
"-identifiernamestring class " + CLASS_NAME + " { static void foo(...); }",
"-keep class " + CLASS_NAME);
- DexInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
+ CodeInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
ClassSubject clazz = inspector.clazz(CLASS_NAME);
assertTrue(clazz.isPresent());
@@ -404,7 +404,7 @@
List<String> pgConfigs = ImmutableList.of(
"-identifiernamestring class " + CLASS_NAME + " { static void foo(...); }",
"-keep class " + CLASS_NAME);
- DexInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
+ CodeInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
ClassSubject clazz = inspector.clazz(CLASS_NAME);
assertTrue(clazz.isPresent());
@@ -450,7 +450,7 @@
"-identifiernamestring class " + CLASS_NAME + " { static void foo(...); }",
"-keep class " + CLASS_NAME,
"-keep,allowobfuscation class " + BOO);
- DexInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
+ CodeInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
ClassSubject clazz = inspector.clazz(CLASS_NAME);
assertTrue(clazz.isPresent());
@@ -504,7 +504,7 @@
+ "}",
"-keep class " + CLASS_NAME,
"-keep class R { *; }");
- DexInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
+ CodeInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
ClassSubject clazz = inspector.clazz(CLASS_NAME);
assertTrue(clazz.isPresent());
@@ -554,7 +554,7 @@
+ "}",
"-keep class " + CLASS_NAME,
"-keep,allowobfuscation class R { *; }");
- DexInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
+ CodeInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
ClassSubject clazz = inspector.clazz(CLASS_NAME);
assertTrue(clazz.isPresent());
@@ -611,7 +611,7 @@
+ "}",
"-keep class " + CLASS_NAME,
"-keep class R { *; }");
- DexInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
+ CodeInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
ClassSubject clazz = inspector.clazz(CLASS_NAME);
assertTrue(clazz.isPresent());
@@ -672,7 +672,7 @@
+ "}",
"-keep class " + CLASS_NAME,
"-keep,allowobfuscation class R { *; }");
- DexInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
+ CodeInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
ClassSubject clazz = inspector.clazz(CLASS_NAME);
assertTrue(clazz.isPresent());
@@ -696,8 +696,8 @@
assertNotEquals("foo", constString.getString().toString());
}
- private DexInspector getInspectorAfterRunR8(
+ private CodeInspector getInspectorAfterRunR8(
SmaliBuilder builder, List<String> proguardConfigurations) throws Exception {
- return new DexInspector(runR8(builder, proguardConfigurations));
+ return new CodeInspector(runR8(builder, proguardConfigurations));
}
}
diff --git a/src/test/java/com/android/tools/r8/naming/LambdaRenamingTestRunner.java b/src/test/java/com/android/tools/r8/naming/LambdaRenamingTestRunner.java
index c7c74e9..02bca3a 100644
--- a/src/test/java/com/android/tools/r8/naming/LambdaRenamingTestRunner.java
+++ b/src/test/java/com/android/tools/r8/naming/LambdaRenamingTestRunner.java
@@ -20,7 +20,6 @@
import com.android.tools.r8.utils.FileUtils;
import java.io.IOException;
import java.nio.file.Path;
-import java.nio.file.Paths;
import org.junit.Before;
import org.junit.Test;
@@ -129,7 +128,7 @@
Path outPg = temp.getRoot().toPath().resolve(outName);
ProcessResult proguardResult =
ToolHelper.runProguard6Raw(
- inputJar, outPg, Paths.get(ToolHelper.JAVA_8_RUNTIME), pgConfig, null);
+ inputJar, outPg, ToolHelper.getJava8RuntimeJar(), pgConfig, null);
System.out.println(proguardResult.stdout);
if (proguardResult.exitCode != 0) {
System.out.println(proguardResult.stderr);
diff --git a/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java b/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java
index b9ff7ea..a5bbdf6 100644
--- a/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java
@@ -4,10 +4,9 @@
package com.android.tools.r8.naming;
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
-import static com.android.tools.r8.utils.DexInspectorMatchers.isRenamed;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
import static org.hamcrest.CoreMatchers.not;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
@@ -21,6 +20,7 @@
import static org.objectweb.asm.Opcodes.RETURN;
import static org.objectweb.asm.Opcodes.V1_8;
+import com.android.tools.r8.ClassFileConsumer;
import com.android.tools.r8.DexIndexedConsumer;
import com.android.tools.r8.DiagnosticsChecker;
import com.android.tools.r8.R8Command;
@@ -29,15 +29,21 @@
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.graph.invokesuper.Consumer;
import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
+import java.util.Arrays;
+import java.util.Collection;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
+@RunWith(Parameterized.class)
public class MinifierClassSignatureTest extends TestBase {
/*
@@ -62,6 +68,16 @@
String outerSignature = "<T:Ljava/lang/Object;>Ljava/lang/Object;";
String extendsInnerSignature = "LOuter<TT;>.Inner;";
String extendsInnerInnerSignature = "LOuter<TT;>.Inner.InnerInner;";
+ Backend backend;
+
+ @Parameters(name = "Backend: {0}")
+ public static Collection<Backend> data() {
+ return Arrays.asList(Backend.values());
+ }
+
+ public MinifierClassSignatureTest(Backend backend) {
+ this.backend = backend;
+ }
private byte[] dumpSimple(String classSignature) throws Exception {
@@ -287,29 +303,39 @@
public void runTest(
ImmutableMap<String, String> signatures,
Consumer<DiagnosticsChecker> diagnostics,
- Consumer<DexInspector> inspect)
+ Consumer<CodeInspector> inspect)
throws Exception {
DiagnosticsChecker checker = new DiagnosticsChecker();
- DexInspector inspector = new DexInspector(
- ToolHelper.runR8(R8Command.builder(checker)
- .addClassProgramData(dumpSimple(signatures.get("Simple")), Origin.unknown())
- .addClassProgramData(dumpBase(signatures.get("Base")), Origin.unknown())
- .addClassProgramData(dumpOuter(signatures.get("Outer")), Origin.unknown())
- .addClassProgramData(dumpInner(signatures.get("Outer$Inner")), Origin.unknown())
- .addClassProgramData(
- dumpExtendsInner(signatures.get("Outer$ExtendsInner")), Origin.unknown())
- .addClassProgramData(
- dumpInnerInner(signatures.get("Outer$Inner$InnerInner")), Origin.unknown())
- .addClassProgramData(
- dumpExtendsInnerInner(
- signatures.get("Outer$Inner$ExtendsInnerInner")), Origin.unknown())
- .addProguardConfiguration(ImmutableList.of(
- "-keepattributes InnerClasses,EnclosingMethod,Signature",
- "-keep,allowobfuscation class **"
- ), Origin.unknown())
- .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
- .setProguardMapConsumer(StringConsumer.emptyConsumer())
- .build()));
+ assert (backend == Backend.CF || backend == Backend.DEX);
+ CodeInspector inspector =
+ new CodeInspector(
+ ToolHelper.runR8(
+ R8Command.builder(checker)
+ .addClassProgramData(dumpSimple(signatures.get("Simple")), Origin.unknown())
+ .addClassProgramData(dumpBase(signatures.get("Base")), Origin.unknown())
+ .addClassProgramData(dumpOuter(signatures.get("Outer")), Origin.unknown())
+ .addClassProgramData(dumpInner(signatures.get("Outer$Inner")), Origin.unknown())
+ .addClassProgramData(
+ dumpExtendsInner(signatures.get("Outer$ExtendsInner")), Origin.unknown())
+ .addClassProgramData(
+ dumpInnerInner(signatures.get("Outer$Inner$InnerInner")), Origin.unknown())
+ .addClassProgramData(
+ dumpExtendsInnerInner(signatures.get("Outer$Inner$ExtendsInnerInner")),
+ Origin.unknown())
+ .addProguardConfiguration(
+ ImmutableList.of(
+ "-keepattributes InnerClasses,EnclosingMethod,Signature",
+ "-keep,allowobfuscation class **"),
+ Origin.unknown())
+ .setProgramConsumer(
+ backend == Backend.DEX
+ ? DexIndexedConsumer.emptyConsumer()
+ : ClassFileConsumer.emptyConsumer())
+ .setProguardMapConsumer(StringConsumer.emptyConsumer())
+ .build(),
+ options -> {
+ options.testing.suppressExperimentalCfBackendWarning = true;
+ }));
// All classes are kept, and renamed.
assertThat(inspector.clazz("Simple"), isRenamed());
assertThat(inspector.clazz("Base"), isRenamed());
@@ -350,7 +376,7 @@
private void testSingleClass(String name, String signature,
Consumer<DiagnosticsChecker> diagnostics,
- Consumer<DexInspector> inspector)
+ Consumer<CodeInspector> inspector)
throws Exception {
ImmutableMap<String, String> signatures = ImmutableMap.of(name, signature);
runTest(signatures, diagnostics, inspector);
@@ -364,7 +390,7 @@
assertEquals(0, checker.warnings.size());
}
- private void noInspection(DexInspector inspector) {
+ private void noInspection(CodeInspector inspector) {
}
private void noSignatureAttribute(ClassSubject clazz) {
diff --git a/src/test/java/com/android/tools/r8/naming/MinifierFieldSignatureTest.java b/src/test/java/com/android/tools/r8/naming/MinifierFieldSignatureTest.java
index e48ce56..c470661 100644
--- a/src/test/java/com/android/tools/r8/naming/MinifierFieldSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/naming/MinifierFieldSignatureTest.java
@@ -4,8 +4,8 @@
package com.android.tools.r8.naming;
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
-import static com.android.tools.r8.utils.DexInspectorMatchers.isRenamed;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
@@ -21,6 +21,7 @@
import static org.objectweb.asm.Opcodes.RETURN;
import static org.objectweb.asm.Opcodes.V1_8;
+import com.android.tools.r8.ClassFileConsumer;
import com.android.tools.r8.DexIndexedConsumer;
import com.android.tools.r8.DiagnosticsChecker;
import com.android.tools.r8.R8Command;
@@ -29,18 +30,23 @@
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.graph.invokesuper.Consumer;
import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.FieldSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.Map;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
-
+@RunWith(Parameterized.class)
public class MinifierFieldSignatureTest extends TestBase {
/*
@@ -59,6 +65,16 @@
private String anArrayOfXSignature = "[TX;";
private String aFieldsOfXSignature = "LFields<TX;>;";
private String aFieldsOfXInnerSignature = "LFields<TX;>.Inner;";
+ private Backend backend;
+
+ @Parameters(name = "Backend: {0}")
+ public static Collection<Backend> data() {
+ return Arrays.asList(Backend.values());
+ }
+
+ public MinifierFieldSignatureTest(Backend backend) {
+ this.backend = backend;
+ }
public byte[] dumpFields(Map<String, String> signatures) throws Exception {
@@ -143,17 +159,17 @@
return cw.toByteArray();
}
- private FieldSubject lookupAnX(DexInspector inspector) {
+ private FieldSubject lookupAnX(CodeInspector inspector) {
ClassSubject clazz = inspector.clazz("Fields");
return clazz.field("java.lang.String", "anX");
}
- private FieldSubject lookupAnArrayOfX(DexInspector inspector) {
+ private FieldSubject lookupAnArrayOfX(CodeInspector inspector) {
ClassSubject clazz = inspector.clazz("Fields");
return clazz.field("java.lang.String[]", "anArrayOfX");
}
- private FieldSubject lookupAFieldsOfX(DexInspector inspector) {
+ private FieldSubject lookupAFieldsOfX(CodeInspector inspector) {
ClassSubject clazz = inspector.clazz("Fields");
return clazz.field("Fields", "aFieldsOfX");
}
@@ -161,20 +177,30 @@
public void runTest(
ImmutableMap<String, String> signatures,
Consumer<DiagnosticsChecker> diagnostics,
- Consumer<DexInspector> inspect)
+ Consumer<CodeInspector> inspect)
throws Exception {
DiagnosticsChecker checker = new DiagnosticsChecker();
- DexInspector inspector = new DexInspector(
- ToolHelper.runR8(R8Command.builder(checker)
- .addClassProgramData(dumpFields(signatures), Origin.unknown())
- .addClassProgramData(dumpInner(), Origin.unknown())
- .addProguardConfiguration(ImmutableList.of(
- "-keepattributes InnerClasses,EnclosingMethod,Signature",
- "-keep,allowobfuscation class ** { *; }"
- ), Origin.unknown())
- .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
- .setProguardMapConsumer(StringConsumer.emptyConsumer())
- .build()));
+ assert (backend == Backend.CF || backend == Backend.DEX);
+ CodeInspector inspector =
+ new CodeInspector(
+ ToolHelper.runR8(
+ R8Command.builder(checker)
+ .addClassProgramData(dumpFields(signatures), Origin.unknown())
+ .addClassProgramData(dumpInner(), Origin.unknown())
+ .addProguardConfiguration(
+ ImmutableList.of(
+ "-keepattributes InnerClasses,EnclosingMethod,Signature",
+ "-keep,allowobfuscation class ** { *; }"),
+ Origin.unknown())
+ .setProgramConsumer(
+ backend == Backend.DEX
+ ? DexIndexedConsumer.emptyConsumer()
+ : ClassFileConsumer.emptyConsumer())
+ .setProguardMapConsumer(StringConsumer.emptyConsumer())
+ .build(),
+ options -> {
+ options.testing.suppressExperimentalCfBackendWarning = true;
+ }));
// All classes are kept, and renamed.
ClassSubject clazz = inspector.clazz("Fields");
assertThat(clazz, isRenamed());
@@ -216,7 +242,7 @@
private void testSingleField(String name, String signature,
Consumer<DiagnosticsChecker> diagnostics,
- Consumer<DexInspector> inspector)
+ Consumer<CodeInspector> inspector)
throws Exception {
ImmutableMap<String, String> signatures = ImmutableMap.of(name, signature);
runTest(signatures, diagnostics, inspector);
@@ -230,7 +256,7 @@
assertEquals(0, checker.warnings.size());
}
- private void noInspection(DexInspector inspector) {
+ private void noInspection(CodeInspector inspector) {
}
private void noSignatureAttribute(FieldSubject field) {
diff --git a/src/test/java/com/android/tools/r8/naming/MinifierMethodSignatureTest.java b/src/test/java/com/android/tools/r8/naming/MinifierMethodSignatureTest.java
index 41750ae..aa5307d 100644
--- a/src/test/java/com/android/tools/r8/naming/MinifierMethodSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/naming/MinifierMethodSignatureTest.java
@@ -4,8 +4,8 @@
package com.android.tools.r8.naming;
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
-import static com.android.tools.r8.utils.DexInspectorMatchers.isRenamed;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
@@ -24,6 +24,7 @@
import static org.objectweb.asm.Opcodes.RETURN;
import static org.objectweb.asm.Opcodes.V1_8;
+import com.android.tools.r8.ClassFileConsumer;
import com.android.tools.r8.DexIndexedConsumer;
import com.android.tools.r8.DiagnosticsChecker;
import com.android.tools.r8.R8Command;
@@ -32,17 +33,23 @@
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.graph.invokesuper.Consumer;
import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.Map;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
+@RunWith(Parameterized.class)
public class MinifierMethodSignatureTest extends TestBase {
/*
@@ -61,6 +68,16 @@
private String parameterizedReturnSignature = "()LMethods<TX;>.Inner;";
private String parameterizedArgumentsSignature = "(TX;LMethods<TX;>.Inner;)V";
private String parametrizedThrowsSignature = "()V^TX;";
+ Backend backend;
+
+ @Parameters(name = "Backend: {0}")
+ public static Collection<Backend> data() {
+ return Arrays.asList(Backend.values());
+ }
+
+ public MinifierMethodSignatureTest(Backend backend) {
+ this.backend = backend;
+ }
private byte[] dumpMethods(Map<String, String> signatures) throws Exception {
@@ -161,19 +178,19 @@
return cw.toByteArray();
}
- private MethodSubject lookupGeneric(DexInspector inspector) {
+ private MethodSubject lookupGeneric(CodeInspector inspector) {
ClassSubject clazz = inspector.clazz("Methods");
return clazz.method(
"java.lang.Throwable", "generic", ImmutableList.of("java.lang.Throwable", "Methods$Inner"));
}
- private MethodSubject lookupParameterizedReturn(DexInspector inspector) {
+ private MethodSubject lookupParameterizedReturn(CodeInspector inspector) {
ClassSubject clazz = inspector.clazz("Methods");
return clazz.method(
"Methods$Inner", "parameterizedReturn", ImmutableList.of());
}
- private MethodSubject lookupParameterizedArguments(DexInspector inspector) {
+ private MethodSubject lookupParameterizedArguments(CodeInspector inspector) {
ClassSubject clazz = inspector.clazz("Methods");
return clazz.method(
"void", "parameterizedArguments", ImmutableList.of("java.lang.Throwable", "Methods$Inner"));
@@ -182,20 +199,30 @@
public void runTest(
ImmutableMap<String, String> signatures,
Consumer<DiagnosticsChecker> diagnostics,
- Consumer<DexInspector> inspect)
+ Consumer<CodeInspector> inspect)
throws Exception {
DiagnosticsChecker checker = new DiagnosticsChecker();
- DexInspector inspector = new DexInspector(
- ToolHelper.runR8(R8Command.builder(checker)
- .addClassProgramData(dumpMethods(signatures), Origin.unknown())
- .addClassProgramData(dumpInner(), Origin.unknown())
- .addProguardConfiguration(ImmutableList.of(
- "-keepattributes InnerClasses,EnclosingMethod,Signature",
- "-keep,allowobfuscation class ** { *; }"
- ), Origin.unknown())
- .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
- .setProguardMapConsumer(StringConsumer.emptyConsumer())
- .build()));
+ assert (backend == Backend.CF || backend == Backend.DEX);
+ CodeInspector inspector =
+ new CodeInspector(
+ ToolHelper.runR8(
+ R8Command.builder(checker)
+ .addClassProgramData(dumpMethods(signatures), Origin.unknown())
+ .addClassProgramData(dumpInner(), Origin.unknown())
+ .addProguardConfiguration(
+ ImmutableList.of(
+ "-keepattributes InnerClasses,EnclosingMethod,Signature",
+ "-keep,allowobfuscation class ** { *; }"),
+ Origin.unknown())
+ .setProgramConsumer(
+ backend == Backend.DEX
+ ? DexIndexedConsumer.emptyConsumer()
+ : ClassFileConsumer.emptyConsumer())
+ .setProguardMapConsumer(StringConsumer.emptyConsumer())
+ .build(),
+ options -> {
+ options.testing.suppressExperimentalCfBackendWarning = true;
+ }));
// All classes are kept, and renamed.
ClassSubject clazz = inspector.clazz("Methods");
assertThat(clazz, isRenamed());
@@ -236,7 +263,7 @@
private void testSingleMethod(String name, String signature,
Consumer<DiagnosticsChecker> diagnostics,
- Consumer<DexInspector> inspector)
+ Consumer<CodeInspector> inspector)
throws Exception {
ImmutableMap<String, String> signatures = ImmutableMap.of(name, signature);
runTest(signatures, diagnostics, inspector);
@@ -250,7 +277,7 @@
assertEquals(0, checker.warnings.size());
}
- private void noInspection(DexInspector inspector) {
+ private void noInspection(CodeInspector inspector) {
}
private void noSignatureAttribute(MethodSubject method) {
diff --git a/src/test/java/com/android/tools/r8/naming/NamingTestBase.java b/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
index 5cc07a4..ed95627 100644
--- a/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
+++ b/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
@@ -84,7 +84,9 @@
new RootSetBuilder(appInfo, program, configuration.getRules(), options).run(executor);
}
- Enqueuer enqueuer = new Enqueuer(appInfo, options, options.forceProguardCompatibility);
+ Enqueuer enqueuer =
+ new Enqueuer(
+ appInfo, GraphLense.getIdentityLense(), options, options.forceProguardCompatibility);
appInfo = enqueuer.traceApplication(rootSet, executor, timing);
return new Minifier(appInfo.withLiveness(), rootSet, options).run(timing);
}
diff --git a/src/test/java/com/android/tools/r8/naming/WarnReflectiveAccessTest.java b/src/test/java/com/android/tools/r8/naming/WarnReflectiveAccessTest.java
index ddd0aa2..49ba186 100644
--- a/src/test/java/com/android/tools/r8/naming/WarnReflectiveAccessTest.java
+++ b/src/test/java/com/android/tools/r8/naming/WarnReflectiveAccessTest.java
@@ -3,12 +3,13 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.naming;
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
import com.android.tools.r8.DiagnosticsChecker;
import com.android.tools.r8.OutputMode;
@@ -19,10 +20,10 @@
import com.android.tools.r8.VmTestRunner;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
import com.android.tools.r8.utils.KeepingDiagnosticHandler;
import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.lang.reflect.Method;
import java.nio.file.Path;
@@ -72,15 +73,17 @@
byte[][] classes,
Class main,
Path out,
+ boolean explicitRule,
boolean enableMinification,
boolean forceProguardCompatibility)
throws Exception {
String minificationModifier = enableMinification ? ",allowobfuscation " : " ";
- String reflectionRules = forceProguardCompatibility ? ""
- : "-identifiernamestring class java.lang.Class {\n"
+ String reflectionRules = explicitRule
+ ? "-identifiernamestring class java.lang.Class {\n"
+ "static java.lang.Class forName(java.lang.String);\n"
+ "java.lang.reflect.Method getDeclaredMethod(java.lang.String, java.lang.Class[]);\n"
- + "}\n";
+ + "}\n"
+ : "";
R8Command.Builder commandBuilder =
R8Command.builder(reporter)
.addProguardConfiguration(
@@ -102,7 +105,8 @@
});
}
- private void reflectionWithBuildter(
+ private void reflectionWithBuilder(
+ boolean explicitRule,
boolean enableMinification,
boolean forceProguardCompatibility) throws Exception {
byte[][] classes = {
@@ -110,11 +114,11 @@
};
Path out = temp.getRoot().toPath();
AndroidApp processedApp = runR8(classes, WarnReflectiveAccessTestMain.class, out,
- enableMinification, forceProguardCompatibility);
+ explicitRule, enableMinification, forceProguardCompatibility);
String main = WarnReflectiveAccessTestMain.class.getCanonicalName();
- DexInspector dexInspector = new DexInspector(processedApp);
- ClassSubject mainSubject = dexInspector.clazz(main);
+ CodeInspector codeInspector = new CodeInspector(processedApp);
+ ClassSubject mainSubject = codeInspector.clazz(main);
assertThat(mainSubject, isPresent());
ProcessResult javaOutput = runOnJavaRaw(main, classes);
@@ -133,35 +137,59 @@
}
@Test
- public void test_minification_forceProguardCompatibility() throws Exception {
- reflectionWithBuildter(true, true);
+ public void test_explicit_minification_forceProguardCompatibility() throws Exception {
+ reflectionWithBuilder(true, true, true);
assertFalse(handler.warnings.isEmpty());
- DiagnosticsChecker.checkDiagnostic(handler.warnings.get(0), (Path) null, 54, 1,
- "Cannot determine", "getDeclaredMethod", "resolution failure");
- }
-
- @Test
- public void test_noMinification_forceProguardCompatibility() throws Exception {
- reflectionWithBuildter(false, true);
- assertFalse(handler.warnings.isEmpty());
- DiagnosticsChecker.checkDiagnostic(handler.warnings.get(0), (Path) null, 54, 1,
- "Cannot determine", "getDeclaredMethod", "resolution failure");
- }
-
- @Test
- public void test_minification_R8() throws Exception {
- reflectionWithBuildter(true, false);
- assertFalse(handler.warnings.isEmpty());
- DiagnosticsChecker.checkDiagnostic(handler.warnings.get(0), (Path) null, 54, 1,
+ DiagnosticsChecker.checkDiagnostic(handler.warnings.get(0), (Path) null, 55, 1,
"Cannot determine", "getDeclaredMethod", "-identifiernamestring", "resolution failure");
}
@Test
- public void test_noMinification_R8() throws Exception {
- reflectionWithBuildter(false, false);
+ public void test_explicit_noMinification_forceProguardCompatibility() throws Exception {
+ reflectionWithBuilder(true, false, true);
assertFalse(handler.warnings.isEmpty());
- DiagnosticsChecker.checkDiagnostic(handler.warnings.get(0), (Path) null, 54, 1,
+ DiagnosticsChecker.checkDiagnostic(handler.warnings.get(0), (Path) null, 55, 1,
"Cannot determine", "getDeclaredMethod", "-identifiernamestring", "resolution failure");
}
+ @Test
+ public void test_explicit_minification_R8() throws Exception {
+ reflectionWithBuilder(true, true, false);
+ assertFalse(handler.warnings.isEmpty());
+ DiagnosticsChecker.checkDiagnostic(handler.warnings.get(0), (Path) null, 55, 1,
+ "Cannot determine", "getDeclaredMethod", "-identifiernamestring", "resolution failure");
+ }
+
+ @Test
+ public void test_explicit_noMinification_R8() throws Exception {
+ reflectionWithBuilder(true, false, false);
+ assertFalse(handler.warnings.isEmpty());
+ DiagnosticsChecker.checkDiagnostic(handler.warnings.get(0), (Path) null, 55, 1,
+ "Cannot determine", "getDeclaredMethod", "-identifiernamestring", "resolution failure");
+ }
+
+ @Test
+ public void test_implicit_minification_forceProguardCompatibility() throws Exception {
+ reflectionWithBuilder(false, true, true);
+ assertTrue(handler.warnings.isEmpty());
+ }
+
+ @Test
+ public void test_implicit_noMinification_forceProguardCompatibility() throws Exception {
+ reflectionWithBuilder(false, false, true);
+ assertTrue(handler.warnings.isEmpty());
+ }
+
+ @Test
+ public void test_implicit_minification_R8() throws Exception {
+ reflectionWithBuilder(false, true, false);
+ assertTrue(handler.warnings.isEmpty());
+ }
+
+ @Test
+ public void test_implicit_noMinification_R8() throws Exception {
+ reflectionWithBuilder(false, false, false);
+ assertTrue(handler.warnings.isEmpty());
+ }
+
}
diff --git a/src/test/java/com/android/tools/r8/naming/b72391662/B72391662.java b/src/test/java/com/android/tools/r8/naming/b72391662/B72391662.java
index ec27895..9eeb2f7 100644
--- a/src/test/java/com/android/tools/r8/naming/b72391662/B72391662.java
+++ b/src/test/java/com/android/tools/r8/naming/b72391662/B72391662.java
@@ -4,7 +4,7 @@
package com.android.tools.r8.naming.b72391662;
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -15,11 +15,11 @@
import com.android.tools.r8.VmTestRunner.IgnoreIfVmOlderThan;
import com.android.tools.r8.naming.b72391662.subpackage.OtherPackageSuper;
import com.android.tools.r8.naming.b72391662.subpackage.OtherPackageTestClass;
-import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatabilityTestBase;
+import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import java.util.List;
@@ -27,7 +27,7 @@
import org.junit.runner.RunWith;
@RunWith(VmTestRunner.class)
-public class B72391662 extends ProguardCompatabilityTestBase {
+public class B72391662 extends ProguardCompatibilityTestBase {
private static final List<Class> CLASSES = ImmutableList.of(
TestMain.class, Interface.class, Super.class, TestClass.class,
@@ -57,12 +57,12 @@
"-dontwarn java.lang.invoke.*"
);
- AndroidApp app = runShrinkerRaw(shrinker, CLASSES, config);
+ AndroidApp app = runShrinker(shrinker, CLASSES, config);
assertEquals("123451234567\nABC\n", runOnArt(app, mainClass.getCanonicalName()));
- DexInspector dexInspector =
- isR8(shrinker) ? new DexInspector(app) : new DexInspector(app, proguardMap);
- ClassSubject testClass = dexInspector.clazz(TestClass.class);
+ CodeInspector codeInspector =
+ isR8(shrinker) ? new CodeInspector(app) : new CodeInspector(app, proguardMap);
+ ClassSubject testClass = codeInspector.clazz(TestClass.class);
assertThat(testClass, isPresent());
// Test the totally unused method.
@@ -147,12 +147,12 @@
"-dontwarn java.lang.invoke.*"
);
- AndroidApp app = runShrinkerRaw(shrinker, CLASSES, config);
+ AndroidApp app = runShrinker(shrinker, CLASSES, config);
assertEquals("123451234567\nABC\n", runOnArt(app, mainClass.getCanonicalName()));
- DexInspector dexInspector =
- isR8(shrinker) ? new DexInspector(app) : new DexInspector(app, proguardMap);
- ClassSubject testClass = dexInspector.clazz(TestClass.class);
+ CodeInspector codeInspector =
+ isR8(shrinker) ? new CodeInspector(app) : new CodeInspector(app, proguardMap);
+ ClassSubject testClass = codeInspector.clazz(TestClass.class);
assertThat(testClass, isPresent());
// Test the totally unused method.
@@ -244,12 +244,12 @@
));
}
- AndroidApp app = runShrinkerRaw(shrinker, CLASSES, config);
+ AndroidApp app = runShrinker(shrinker, CLASSES, config);
assertEquals("123451234567\nABC\n", runOnArt(app, mainClass.getCanonicalName()));
- DexInspector dexInspector =
- isR8(shrinker) ? new DexInspector(app) : new DexInspector(app, proguardMap);
- ClassSubject testClass = dexInspector.clazz(TestClass.class);
+ CodeInspector codeInspector =
+ isR8(shrinker) ? new CodeInspector(app) : new CodeInspector(app, proguardMap);
+ ClassSubject testClass = codeInspector.clazz(TestClass.class);
assertThat(testClass, isPresent());
// Test the totally unused method.
diff --git a/src/test/java/com/android/tools/r8/naming/b80083341/B80083341.java b/src/test/java/com/android/tools/r8/naming/b80083341/B80083341.java
index 008e4ff..9791142 100644
--- a/src/test/java/com/android/tools/r8/naming/b80083341/B80083341.java
+++ b/src/test/java/com/android/tools/r8/naming/b80083341/B80083341.java
@@ -4,7 +4,7 @@
package com.android.tools.r8.naming.b80083341;
import static com.android.tools.r8.utils.DescriptorUtils.getClassNameFromDescriptor;
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
@@ -12,8 +12,8 @@
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.VmTestRunner;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.util.List;
import org.junit.Test;
@@ -40,7 +40,7 @@
PackagePrivateClass.class, PackagePrivateClass.Itf.class, PackagePrivateClass.Impl.class
));
AndroidApp processedApp = compileWithR8(app, String.join(System.lineSeparator(), config));
- DexInspector inspector = new DexInspector(processedApp);
+ CodeInspector inspector = new CodeInspector(processedApp);
ClassSubject mainSubject = inspector.clazz(mainClass);
assertThat(mainSubject, isPresent());
diff --git a/src/test/java/com/android/tools/r8/naming/overloadaggressively/OverloadAggressivelyTest.java b/src/test/java/com/android/tools/r8/naming/overloadaggressively/OverloadAggressivelyTest.java
index a9eb622..f289674 100644
--- a/src/test/java/com/android/tools/r8/naming/overloadaggressively/OverloadAggressivelyTest.java
+++ b/src/test/java/com/android/tools/r8/naming/overloadaggressively/OverloadAggressivelyTest.java
@@ -18,8 +18,8 @@
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.nio.file.Path;
import org.junit.Test;
@@ -60,8 +60,8 @@
Path out = temp.getRoot().toPath();
AndroidApp processedApp = runR8(originalApp, FieldUpdater.class, out, overloadaggressively);
- DexInspector dexInspector = new DexInspector(processedApp);
- ClassSubject a = dexInspector.clazz(A.class.getCanonicalName());
+ CodeInspector codeInspector = new CodeInspector(processedApp);
+ ClassSubject a = codeInspector.clazz(A.class.getCanonicalName());
DexEncodedField f1 = a.field("int", "f1").getField();
assertNotNull(f1);
DexEncodedField f2 = a.field("java.lang.Object", "f2").getField();
@@ -112,8 +112,8 @@
Path out = temp.getRoot().toPath();
AndroidApp processedApp = runR8(originalApp, FieldResolution.class, out, overloadaggressively);
- DexInspector dexInspector = new DexInspector(processedApp);
- ClassSubject a = dexInspector.clazz(A.class.getCanonicalName());
+ CodeInspector codeInspector = new CodeInspector(processedApp);
+ ClassSubject a = codeInspector.clazz(A.class.getCanonicalName());
DexEncodedField f1 = a.field("int", "f1").getField();
assertNotNull(f1);
DexEncodedField f3 = a.field(B.class.getCanonicalName(), "f3").getField();
@@ -157,8 +157,8 @@
Path out = temp.getRoot().toPath();
AndroidApp processedApp = runR8(originalApp, MethodResolution.class, out, overloadaggressively);
- DexInspector dexInspector = new DexInspector(processedApp);
- ClassSubject b = dexInspector.clazz(B.class.getCanonicalName());
+ CodeInspector codeInspector = new CodeInspector(processedApp);
+ ClassSubject b = codeInspector.clazz(B.class.getCanonicalName());
DexEncodedMethod m1 =
b.method("int", "getF1", ImmutableList.of()).getMethod();
assertNotNull(m1);
diff --git a/src/test/java/com/android/tools/r8/naming/overloadaggressively/ValidNameConflictTest.java b/src/test/java/com/android/tools/r8/naming/overloadaggressively/ValidNameConflictTest.java
index 3e5ba7c..efead2d 100644
--- a/src/test/java/com/android/tools/r8/naming/overloadaggressively/ValidNameConflictTest.java
+++ b/src/test/java/com/android/tools/r8/naming/overloadaggressively/ValidNameConflictTest.java
@@ -14,10 +14,10 @@
import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
import com.android.tools.r8.jasmin.JasminTestBase;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.FieldSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import java.util.List;
@@ -94,8 +94,8 @@
"-dontshrink");
AndroidApp app = compileWithR8(builder, pgConfigs, null);
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject clazz = dexInspector.clazz(CLASS_NAME);
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject clazz = codeInspector.clazz(CLASS_NAME);
assertTrue(clazz.isPresent());
FieldSubject f1 = clazz.field("java.lang.String", "same");
assertTrue(f1.isPresent());
@@ -122,8 +122,8 @@
"-dontshrink");
AndroidApp app = compileWithR8(builder, pgConfigs, null);
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject clazz = dexInspector.clazz(CLASS_NAME);
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject clazz = codeInspector.clazz(CLASS_NAME);
assertTrue(clazz.isPresent());
FieldSubject f1 = clazz.field("java.lang.String", "same");
assertTrue(f1.isPresent());
@@ -152,8 +152,8 @@
"-dontshrink");
AndroidApp app = compileWithR8(builder, pgConfigs, null);
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject clazz = dexInspector.clazz(CLASS_NAME);
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject clazz = codeInspector.clazz(CLASS_NAME);
assertTrue(clazz.isPresent());
FieldSubject f1 = clazz.field("java.lang.String", "same");
assertTrue(f1.isPresent());
@@ -179,8 +179,8 @@
"-dontshrink");
AndroidApp app = compileWithR8(builder, pgConfigs, null);
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject clazz = dexInspector.clazz(CLASS_NAME);
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject clazz = codeInspector.clazz(CLASS_NAME);
assertTrue(clazz.isPresent());
FieldSubject f1 = clazz.field("java.lang.String", "same");
assertTrue(f1.isPresent());
@@ -207,8 +207,8 @@
"-dontshrink");
AndroidApp app = compileWithR8(builder, pgConfigs, null);
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject clazz = dexInspector.clazz(CLASS_NAME);
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject clazz = codeInspector.clazz(CLASS_NAME);
assertTrue(clazz.isPresent());
FieldSubject f1 = clazz.field("java.lang.String", "same");
assertTrue(f1.isPresent());
@@ -263,8 +263,8 @@
"-dontshrink");
AndroidApp app = compileWithR8(builder, pgConfigs, null);
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject clazz = dexInspector.clazz(ANOTHER_CLASS);
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject clazz = codeInspector.clazz(ANOTHER_CLASS);
assertTrue(clazz.isPresent());
MethodSubject m1 = clazz.method("java.lang.String", "same", ImmutableList.of());
assertTrue(m1.isPresent());
@@ -291,8 +291,8 @@
"-dontshrink");
AndroidApp app = compileWithR8(builder, pgConfigs, null);
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject clazz = dexInspector.clazz(ANOTHER_CLASS);
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject clazz = codeInspector.clazz(ANOTHER_CLASS);
assertTrue(clazz.isPresent());
MethodSubject m1 = clazz.method("java.lang.String", "same", ImmutableList.of());
assertTrue(m1.isPresent());
@@ -321,8 +321,8 @@
"-dontshrink");
AndroidApp app = compileWithR8(builder, pgConfigs, null);
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject clazz = dexInspector.clazz(ANOTHER_CLASS);
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject clazz = codeInspector.clazz(ANOTHER_CLASS);
assertTrue(clazz.isPresent());
MethodSubject m1 = clazz.method("java.lang.String", "same", ImmutableList.of());
assertTrue(m1.isPresent());
@@ -348,8 +348,8 @@
"-dontshrink");
AndroidApp app = compileWithR8(builder, pgConfigs, null);
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject clazz = dexInspector.clazz(ANOTHER_CLASS);
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject clazz = codeInspector.clazz(ANOTHER_CLASS);
assertTrue(clazz.isPresent());
MethodSubject m1 = clazz.method("java.lang.String", "same", ImmutableList.of());
assertTrue(m1.isPresent());
@@ -376,8 +376,8 @@
"-dontshrink");
AndroidApp app = compileWithR8(builder, pgConfigs, null);
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject clazz = dexInspector.clazz(ANOTHER_CLASS);
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject clazz = codeInspector.clazz(ANOTHER_CLASS);
assertTrue(clazz.isPresent());
MethodSubject m1 = clazz.method("java.lang.String", "same", ImmutableList.of());
assertTrue(m1.isPresent());
@@ -445,8 +445,8 @@
"-dontshrink");
AndroidApp app = compileWithR8(builder, pgConfigs, null);
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject sup = dexInspector.clazz(SUPER_CLASS);
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject sup = codeInspector.clazz(SUPER_CLASS);
assertTrue(sup.isPresent());
MethodSubject m1 = sup.method("java.lang.String", "same", ImmutableList.of());
assertTrue(m1.isPresent());
@@ -456,7 +456,7 @@
assertFalse(m2.isRenamed());
assertEquals(m1.getFinalName(), m2.getFinalName());
- ClassSubject sub = dexInspector.clazz(ANOTHER_CLASS);
+ ClassSubject sub = codeInspector.clazz(ANOTHER_CLASS);
assertTrue(sub.isPresent());
MethodSubject subM1 = sub.method("java.lang.String", "same", ImmutableList.of());
assertTrue(subM1.isPresent());
@@ -487,8 +487,8 @@
"-dontshrink");
AndroidApp app = compileWithR8(builder, pgConfigs, null);
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject sup = dexInspector.clazz(SUPER_CLASS);
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject sup = codeInspector.clazz(SUPER_CLASS);
assertTrue(sup.isPresent());
MethodSubject m1 = sup.method("java.lang.String", "same", ImmutableList.of());
assertTrue(m1.isPresent());
@@ -498,7 +498,7 @@
assertTrue(m2.isRenamed());
assertEquals(m1.getFinalName(), m2.getFinalName());
- ClassSubject sub = dexInspector.clazz(ANOTHER_CLASS);
+ ClassSubject sub = codeInspector.clazz(ANOTHER_CLASS);
assertTrue(sub.isPresent());
MethodSubject subM1 = sub.method("java.lang.String", "same", ImmutableList.of());
assertTrue(subM1.isPresent());
@@ -531,8 +531,8 @@
"-dontshrink");
AndroidApp app = compileWithR8(builder, pgConfigs, null);
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject sup = dexInspector.clazz(SUPER_CLASS);
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject sup = codeInspector.clazz(SUPER_CLASS);
assertTrue(sup.isPresent());
MethodSubject m1 = sup.method("java.lang.String", "same", ImmutableList.of());
assertTrue(m1.isPresent());
@@ -542,7 +542,7 @@
assertTrue(m2.isRenamed());
assertEquals(m1.getFinalName(), m2.getFinalName());
- ClassSubject sub = dexInspector.clazz(ANOTHER_CLASS);
+ ClassSubject sub = codeInspector.clazz(ANOTHER_CLASS);
assertTrue(sub.isPresent());
MethodSubject subM1 = sub.method("java.lang.String", "same", ImmutableList.of());
assertTrue(subM1.isPresent());
@@ -572,8 +572,8 @@
"-dontshrink");
AndroidApp app = compileWithR8(builder, pgConfigs, null);
- DexInspector dexInspector = new DexInspector(app);
- ClassSubject sup = dexInspector.clazz(SUPER_CLASS);
+ CodeInspector codeInspector = new CodeInspector(app);
+ ClassSubject sup = codeInspector.clazz(SUPER_CLASS);
assertTrue(sup.isPresent());
MethodSubject m1 = sup.method("java.lang.String", "same", ImmutableList.of());
assertTrue(m1.isPresent());
@@ -583,7 +583,7 @@
assertTrue(m2.isRenamed());
assertNotEquals(m1.getFinalName(), m2.getFinalName());
- ClassSubject sub = dexInspector.clazz(ANOTHER_CLASS);
+ ClassSubject sub = codeInspector.clazz(ANOTHER_CLASS);
assertTrue(sub.isPresent());
MethodSubject subM1 = sub.method("java.lang.String", "same", ImmutableList.of());
assertTrue(subM1.isPresent());
@@ -614,9 +614,9 @@
"-dontshrink");
AndroidApp app = compileWithR8(builder, pgConfigs, null);
- DexInspector dexInspector = new DexInspector(app);
+ CodeInspector codeInspector = new CodeInspector(app);
- ClassSubject sup = dexInspector.clazz(SUPER_CLASS);
+ ClassSubject sup = codeInspector.clazz(SUPER_CLASS);
assertTrue(sup.isPresent());
MethodSubject m1 = sup.method("java.lang.String", "same", ImmutableList.of());
assertTrue(m1.isPresent());
@@ -626,7 +626,7 @@
assertTrue(m2.isRenamed());
assertEquals(m1.getFinalName(), m2.getFinalName());
- ClassSubject sub = dexInspector.clazz(ANOTHER_CLASS);
+ ClassSubject sub = codeInspector.clazz(ANOTHER_CLASS);
assertTrue(sub.isPresent());
MethodSubject subM1 = sub.method("java.lang.String", "same", ImmutableList.of());
assertTrue(subM1.isPresent());
diff --git a/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java b/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java
index c929eaf..73dcdb3 100644
--- a/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java
+++ b/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java
@@ -7,30 +7,61 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import com.android.tools.r8.ClassFileConsumer;
import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.DexIndexedConsumer;
import com.android.tools.r8.R8Command;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.InstructionSubject;
-import com.android.tools.r8.utils.DexInspector.InvokeInstructionSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.CfInstructionSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.DexInstructionSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject.JumboStringMode;
+import com.android.tools.r8.utils.codeinspector.InvokeInstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.Iterator;
import java.util.function.BiConsumer;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+@RunWith(Parameterized.class)
public class NeverReturnsNormallyTest extends TestBase {
+
+ private Backend backend;
+
+ @Parameters(name = "Backend: {0}")
+ public static Collection<Backend> data() {
+ return Arrays.asList(Backend.values());
+ }
+
+ public NeverReturnsNormallyTest(Backend backend) {
+ this.backend = backend;
+ }
+
private void runTest(
- BiConsumer<DexInspector, CompilationMode> inspection,
+ BiConsumer<CodeInspector, CompilationMode> inspection,
boolean enableClassInliner, CompilationMode mode) throws Exception {
R8Command.Builder builder = R8Command.builder();
builder.addProgramFiles(ToolHelper.getClassFileForTestClass(TestClass.class));
- builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+ assert (backend == Backend.DEX || backend == Backend.CF);
+ builder.setProgramConsumer(
+ backend == Backend.DEX
+ ? DexIndexedConsumer.emptyConsumer()
+ : ClassFileConsumer.emptyConsumer());
+ builder.addLibraryFiles(
+ backend == Backend.DEX
+ ? ToolHelper.getDefaultAndroidJar()
+ : ToolHelper.getJava8RuntimeJar());
builder.setMode(mode);
builder.addProguardConfiguration(
ImmutableList.of(
@@ -46,18 +77,28 @@
"}",
"",
"-dontobfuscate",
- "-allowaccessmodification"
- ),
+ "-allowaccessmodification"),
Origin.unknown());
- AndroidApp app = ToolHelper.runR8(builder.build(),
- opts -> opts.enableClassInlining = enableClassInliner);
- inspection.accept(new DexInspector(app), mode);
+ AndroidApp app =
+ ToolHelper.runR8(builder.build(), opts -> opts.enableClassInlining = enableClassInliner);
+ inspection.accept(
+ new CodeInspector(
+ app,
+ options -> {
+ options.enableCfFrontend = true;
+ }),
+ mode);
- // Run on Art to check generated code against verifier.
- runOnArt(app, TestClass.class);
+ if (backend == Backend.DEX) {
+ // Run on Art to check generated code against verifier.
+ runOnArt(app, TestClass.class);
+ } else {
+ runOnJava(app, TestClass.class);
+ }
}
- private void validate(DexInspector inspector, CompilationMode mode) {
+ private void validate(CodeInspector inspector, CompilationMode mode) {
+ assert (backend == Backend.DEX || backend == Backend.CF);
ClassSubject clazz = inspector.clazz(TestClass.class);
assertTrue(clazz.isPresent());
@@ -77,37 +118,34 @@
assertTrue(methodThrowToBeInlined.isPresent());
Iterator<InstructionSubject> instructions = methodThrowToBeInlined.iterateInstructions();
// Call, followed by throw null.
- assertTrue(nextInstruction(instructions).isConstString());
- InstructionSubject insn = nextInstruction(instructions);
+ InstructionSubject insn = nextInstructionSkippingCfPositionAndLabel(instructions);
+ assertTrue(insn != null && insn.isConstString(JumboStringMode.ALLOW));
+ insn = nextInstruction(instructions);
assertTrue(insn.isInvoke());
assertTrue(((InvokeInstructionSubject) insn)
.invokedMethod().name.toString().equals("throwNpe"));
- assertTrue(nextInstruction(instructions).isConst4());
- assertTrue(nextInstruction(instructions).isThrow());
- assertFalse(instructions.hasNext());
+ verifyTrailingPattern(instructions);
// Check the instruction used for testInlinedIntoVoidMethod
MethodSubject methodTestInlinedIntoVoidMethod =
clazz.method("void", "testInlinedIntoVoidMethod", ImmutableList.of());
assertTrue(methodTestInlinedIntoVoidMethod.isPresent());
instructions = methodTestInlinedIntoVoidMethod.iterateInstructions();
+ insn = nextInstructionSkippingCfPositionAndLabel(instructions);
if (mode == CompilationMode.DEBUG) {
// Not inlined call to throwToBeInlined.
- insn = nextInstruction(instructions);
assertTrue(insn.isInvoke());
assertTrue(((InvokeInstructionSubject) insn)
.invokedMethod().name.toString().equals("throwToBeInlined"));
} else {
// Inlined code from throwToBeInlined.
- assertTrue(nextInstruction(instructions).isConstString());
+ assertTrue(insn.isConstString(JumboStringMode.ALLOW));
insn = nextInstruction(instructions);
assertTrue(insn.isInvoke());
assertTrue(((InvokeInstructionSubject) insn)
.invokedMethod().name.toString().equals("throwNpe"));
}
- assertTrue(nextInstruction(instructions).isConst4());
- assertTrue(nextInstruction(instructions).isThrow());
- assertFalse(instructions.hasNext());
+ verifyTrailingPattern(instructions);
// Check the instruction used for testInlinedIntoVoidMethod
MethodSubject methodOuterTrivial =
@@ -115,13 +153,11 @@
assertTrue(methodOuterTrivial.isPresent());
instructions = methodOuterTrivial.iterateInstructions();
// Call, followed by [nop, goto]
- insn = nextInstruction(instructions);
+ insn = nextInstructionSkippingCfPositionAndLabel(instructions);
assertTrue(insn.isInvoke());
assertTrue(((InvokeInstructionSubject) insn)
.invokedMethod().name.toString().equals("innerNotReachable"));
- assertTrue(nextInstruction(instructions).isConst4());
- assertTrue(nextInstruction(instructions).isThrow());
- assertFalse(instructions.hasNext());
+ verifyTrailingPattern(instructions);
}
private InstructionSubject nextInstruction(Iterator<InstructionSubject> instructions) {
@@ -129,6 +165,39 @@
return instructions.next();
}
+ private InstructionSubject nextInstructionSkippingCfPositionAndLabel(
+ Iterator<InstructionSubject> instructions) {
+ InstructionSubject insn = null;
+ while (instructions.hasNext()) {
+ insn = instructions.next();
+ if (!(insn instanceof CfInstructionSubject)) {
+ break;
+ }
+ CfInstructionSubject cfInsn = (CfInstructionSubject) insn;
+ if (!cfInsn.isLabel() && !cfInsn.isPosition()) {
+ break;
+ }
+ }
+ return insn;
+ }
+
+ private void verifyTrailingPattern(Iterator<InstructionSubject> instructions) {
+ InstructionSubject insn = nextInstruction(instructions);
+ if (backend == Backend.DEX) {
+ assertTrue(
+ insn instanceof DexInstructionSubject && ((DexInstructionSubject) insn).isConst4());
+ } else {
+ assertTrue(insn instanceof CfInstructionSubject);
+ assertTrue(((CfInstructionSubject) insn).isStackInstruction(Opcode.Pop));
+ assertTrue(instructions.hasNext());
+ insn = instructions.next();
+ assertTrue(insn instanceof CfInstructionSubject);
+ assertTrue(((CfInstructionSubject) insn).isConstNull());
+ }
+ assertTrue(nextInstruction(instructions).isThrow());
+ assertFalse(instructions.hasNext());
+ }
+
@Test
public void test() throws Exception {
runTest(this::validate, true, CompilationMode.DEBUG);
diff --git a/src/test/java/com/android/tools/r8/regress/B76025099.java b/src/test/java/com/android/tools/r8/regress/B76025099.java
index 0a0e861..c7a093f 100644
--- a/src/test/java/com/android/tools/r8/regress/B76025099.java
+++ b/src/test/java/com/android/tools/r8/regress/B76025099.java
@@ -3,7 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.regress;
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
@@ -19,10 +19,10 @@
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import java.io.File;
import java.nio.file.Files;
@@ -104,7 +104,7 @@
private void verifyFieldAccess(AndroidApp processedApp, ProcessResult jvmOutput)
throws Exception {
- DexInspector inspector = new DexInspector(processedApp);
+ CodeInspector inspector = new CodeInspector(processedApp);
ClassSubject impl = inspector.clazz(Impl.class);
assertThat(impl, isPresent());
MethodSubject init = impl.init(ImmutableList.of("java.lang.String"));
diff --git a/src/test/java/com/android/tools/r8/regress/Regress37740372.java b/src/test/java/com/android/tools/r8/regress/Regress37740372.java
index 64df6ed..e6ac9f8 100644
--- a/src/test/java/com/android/tools/r8/regress/Regress37740372.java
+++ b/src/test/java/com/android/tools/r8/regress/Regress37740372.java
@@ -18,8 +18,8 @@
import com.android.tools.r8.smali.SmaliTestBase;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.AndroidAppConsumers;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import java.util.Base64;
import java.util.Set;
import org.junit.Test;
@@ -129,7 +129,7 @@
}
private void checkApplicationOnlyHasJavaLangObject(AndroidApp app) throws Throwable {
- DexInspector inspector = new DexInspector(app);
+ CodeInspector inspector = new CodeInspector(app);
inspector.forAllClasses(this::assertIsJavaLangObjet);
}
diff --git a/src/test/java/com/android/tools/r8/regress/b111080693/B111080693.java b/src/test/java/com/android/tools/r8/regress/b111080693/B111080693.java
new file mode 100644
index 0000000..f079016
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b111080693/B111080693.java
@@ -0,0 +1,49 @@
+// 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.regress.b111080693;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.DexIndexedConsumer;
+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.VmTestRunner;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.regress.b111080693.a.Observable;
+import com.android.tools.r8.regress.b111080693.b.RecyclerView;
+import com.android.tools.r8.utils.AndroidApp;
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(VmTestRunner.class)
+public class B111080693 extends TestBase {
+ @Test
+ public void test() throws Exception {
+ R8Command.Builder builder = R8Command.builder();
+ builder.addProgramFiles(
+ ToolHelper.getClassFilesForTestPackage(Observable.class.getPackage()));
+ builder.addProgramFiles(
+ ToolHelper.getClassFilesForTestPackage(RecyclerView.class.getPackage()));
+ builder.addProgramFiles(ToolHelper.getClassFileForTestClass(TestMain.class));
+ builder.addProgramFiles(ToolHelper.getClassFileForTestClass(TestMain.TestAdapter.class));
+ builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+ builder.setMinApiLevel(ToolHelper.getMinApiLevelForDexVm().getLevel());
+ String config = keepMainProguardConfiguration(TestMain.class);
+ builder.addProguardConfiguration(
+ ImmutableList.of(config,
+ "-keepattributes Signature, InnerClasses, EnclosingMethod, *Annotation*"),
+ Origin.unknown());
+ AndroidApp app = ToolHelper.runR8(builder.build());
+ ProcessResult result = runOnArtRaw(app, TestMain.class);
+ assertEquals(0, result.exitCode);
+ assertThat(result.stderr, not(containsString("IllegalAccessError")));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b111080693/TestMain.java b/src/test/java/com/android/tools/r8/regress/b111080693/TestMain.java
new file mode 100644
index 0000000..8979250
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b111080693/TestMain.java
@@ -0,0 +1,22 @@
+// 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.regress.b111080693;
+
+import com.android.tools.r8.regress.b111080693.b.RecyclerView;
+import com.android.tools.r8.regress.b111080693.b.RecyclerView.Adapter;
+
+public class TestMain {
+ static final class TestAdapter extends Adapter {
+ TestAdapter() {
+ }
+ }
+
+ public static void main(String[] args) {
+ TestAdapter adapter = new TestAdapter();
+ RecyclerView view = new RecyclerView();
+ view.setAdapter(adapter);
+ adapter.notifyDataSetChanged();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b111080693/a/Observable.java b/src/test/java/com/android/tools/r8/regress/b111080693/a/Observable.java
new file mode 100644
index 0000000..c7a4858
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b111080693/a/Observable.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.regress.b111080693.a;
+
+import java.util.ArrayList;
+
+public abstract class Observable<T> {
+ /**
+ * The list of observers. An observer can be in the list at most
+ * once and will never be null.
+ */
+ protected final ArrayList<T> mObservers = new ArrayList<T>();
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b111080693/b/RecyclerView.java b/src/test/java/com/android/tools/r8/regress/b111080693/b/RecyclerView.java
new file mode 100644
index 0000000..06347fe
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b111080693/b/RecyclerView.java
@@ -0,0 +1,78 @@
+// 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.regress.b111080693.b;
+
+import com.android.tools.r8.regress.b111080693.a.Observable;
+
+public class RecyclerView {
+
+ final State mState = new State();
+
+ public static class State {
+ boolean mStructureChanged = false;
+ }
+
+ public abstract static class AdapterDataObserver {
+ public void onChanged() {
+ // Do nothing
+ }
+ }
+
+ private class RecyclerViewDataObserver extends AdapterDataObserver {
+ RecyclerViewDataObserver() {
+ }
+
+ @Override
+ public void onChanged() {
+ // This is the single target of AdapterDataObserver#onChanged(), and could be inlined to
+ // AdapterDataObservable#notifyChanged() as long as this preserves null check of the receiver.
+ // To do so, access the enclosing class' member to use the receiver.
+ mState.mStructureChanged = true;
+ }
+ }
+
+ static class AdapterDataObservable extends Observable<AdapterDataObserver> {
+ public void registerObserver(AdapterDataObserver observer) {
+ mObservers.add(observer);
+ }
+ public void notifyChanged() {
+ for (int i = mObservers.size() - 1; i >= 0; i--) {
+ // The single target, RecyclerViewDataObserver#onChange is inlined, along with check-cast:
+ // AdapterDataObserver observer_i = mObservers.get(i);
+ // RecyclerViewDataObserver casted_obs = (RecyclerViewDataObserver) observer_i;
+ // // inlining RecyclerViewDataObserver#onChanged():
+ mObservers.get(i).onChanged();
+ }
+ }
+ }
+
+ public abstract static class Adapter {
+ private final AdapterDataObservable mObservable = new AdapterDataObservable();
+
+ public void registerAdapterDataObserver(AdapterDataObserver observer) {
+ mObservable.registerObserver(observer);
+ }
+
+ public final void notifyDataSetChanged() {
+ // Single callee, AdapterDataObservable#notifyChanged(), could be inlined, but should not.
+ // Accessing AdapterDataObservable.mObservers, which is a protected field in Observable,
+ // results in an illegal access error.
+ //
+ // Without the above inlining, the inlining constraint for the target here is SUBCLASS due to
+ // that protected field, and thus decline to inline because the holder, Adapter, is not a
+ // subtype of the target holder, AdapterDataObservable.
+ // However, after the above inlining, check-cast to RecyclerViewDataObserver overrides that
+ // condition to PACKAGE, which accidentally allows the target to be inlined here.
+ mObservable.notifyChanged();
+ }
+ }
+
+ private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver();
+
+ public void setAdapter(Adapter adapter) {
+ adapter.registerAdapterDataObserver(mObserver);
+ }
+
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b111250398/B111250398.java b/src/test/java/com/android/tools/r8/regress/b111250398/B111250398.java
index 90fba5f..91d8460 100644
--- a/src/test/java/com/android/tools/r8/regress/b111250398/B111250398.java
+++ b/src/test/java/com/android/tools/r8/regress/b111250398/B111250398.java
@@ -4,7 +4,7 @@
package com.android.tools.r8.regress.b111250398;
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
@@ -15,11 +15,11 @@
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.FieldSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import java.util.Arrays;
import org.junit.Test;
@@ -91,6 +91,16 @@
t = f;
}
+ public void mfWithMonitor() {
+ t = f;
+ synchronized (this) {
+ t = f;
+ t = f;
+ }
+ t = f;
+ t = f;
+ }
+
public void msf() {
t = sf;
t = sf;
@@ -226,11 +236,13 @@
.count();
}
- private void check(DexInspector inspector, int mfOnBGets, int msfOnBGets) {
+ private void check(CodeInspector inspector, int mfOnBGets, int msfOnBGets) {
ClassSubject classA = inspector.clazz(A.class);
assertThat(classA, isPresent());
MethodSubject mfOnA = classA.method("void", "mf", ImmutableList.of());
assertThat(mfOnA, isPresent());
+ MethodSubject mfWithMonitorOnA = classA.method("void", "mfWithMonitor", ImmutableList.of());
+ assertThat(mfWithMonitorOnA, isPresent());
MethodSubject msfOnA = classA.method("void", "msf", ImmutableList.of());
assertThat(msfOnA, isPresent());
MethodSubject mvOnA = classA.method("void", "mv", ImmutableList.of());
@@ -264,6 +276,13 @@
// compilation (R8) will eliminate field loads on non-volatile fields.
assertEquals(1, countIget(mfOnA.getMethod().getCode().asDexCode(), fOnA.getField().field));
assertEquals(1, countSget(msfOnA.getMethod().getCode().asDexCode(), sfOnA.getField().field));
+ // TODO(111380066). This could be 2 in stead of 4, but right now the optimization tracks the
+ // combined set of fields for all successors, and for synchronized code all blocks have
+ // exceptional edges for ensuring monitor exit causing the active load to be invalidated for
+ // both normal and exceptional successors.
+ assertEquals(4,
+ countIget(mfWithMonitorOnA.getMethod().getCode().asDexCode(), fOnA.getField().field));
+
// For fields on other class both separate compilation (D8) and whole program
// compilation (R8) will differ in the eliminated field loads of non-volatile fields.
assertEquals(mfOnBGets,
@@ -274,22 +293,20 @@
@Test
public void testSeparateCompilation() throws Exception {
- DexInspector inspector =
- new DexInspector(compileWithD8(readClasses(A.class, B.class), this::releaseMode));
+ CodeInspector inspector =
+ new CodeInspector(compileWithD8(readClasses(A.class, B.class), this::releaseMode));
check(inspector, 5, 5);
}
@Test
public void testWholeProgram() throws Exception {
- DexInspector inspector =
- new DexInspector(compileWithR8(readClasses(A.class, B.class), this::releaseMode));
- // The reason for getting two Igets in B.mf is that the first Iget inserts a NonNull
- // instruction which creates a new value for the remaining Igets.
- check(inspector, 2, 1);
+ CodeInspector inspector =
+ new CodeInspector(compileWithR8(readClasses(A.class, B.class), this::releaseMode));
+ check(inspector, 1, 1);
}
private void checkMixed(AndroidApp app) throws Exception{
- DexInspector inspector = new DexInspector(app);
+ CodeInspector inspector = new CodeInspector(app);
ClassSubject classC = inspector.clazz(C.class);
assertThat(classC, isPresent());
MethodSubject totalDays = classC.method("int", "totalDays", ImmutableList.of());
@@ -324,7 +341,7 @@
}
private void checkDaggerSingleProviderGet(AndroidApp app) throws Exception {
- DexInspector inspector = new DexInspector(app);
+ CodeInspector inspector = new CodeInspector(app);
MethodSubject get =
inspector.clazz(SingleCheck.class).method("java.lang.Object", "get", ImmutableList.of());
assertThat(get, isPresent());
diff --git a/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java b/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java
index a185f4c..911ceb7 100644
--- a/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java
+++ b/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java
@@ -7,19 +7,35 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import com.android.tools.r8.ClassFileConsumer;
import com.android.tools.r8.DexIndexedConsumer;
import com.android.tools.r8.R8Command;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.FoundClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
import com.google.common.collect.ImmutableList;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.List;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+@RunWith(Parameterized.class)
public class Regress69825683Test extends TestBase {
+ private Backend backend;
+
+ @Parameterized.Parameters(name = "Backend: {0}")
+ public static Collection<Backend> data() {
+ return Arrays.asList(Backend.values());
+ }
+
+ public Regress69825683Test(Backend backend) {
+ this.backend = backend;
+ }
@Test
public void outerConstructsInner() throws Exception {
@@ -32,9 +48,18 @@
"}",
"-dontobfuscate"),
Origin.unknown());
- builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+ if (backend == Backend.DEX) {
+ builder
+ .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+ .addLibraryFiles(ToolHelper.getDefaultAndroidJar());
+ } else {
+ assert backend == Backend.CF;
+ builder
+ .setProgramConsumer(ClassFileConsumer.emptyConsumer())
+ .addLibraryFiles(ToolHelper.getJava8RuntimeJar());
+ }
AndroidApp app = ToolHelper.runR8(builder.build(), o -> o.enableClassInlining = false);
- DexInspector inspector = new DexInspector(app);
+ CodeInspector inspector = new CodeInspector(app, o -> o.enableCfFrontend = true);
List<FoundClassSubject> classes = inspector.allClasses();
// Check that the synthetic class is still present.
@@ -51,7 +76,9 @@
String innerName = innerClass.getCanonicalName();
int index = innerName.lastIndexOf('.');
innerName = innerName.substring(0, index) + "$" + innerName.substring(index + 1);
- assertTrue(runOnArt(app, mainClass).startsWith(innerName));
+ assertTrue(
+ (backend == Backend.DEX ? runOnArt(app, mainClass) : runOnJava(app, mainClass))
+ .startsWith(innerName));
}
@Test
@@ -65,9 +92,18 @@
"}",
"-dontobfuscate"),
Origin.unknown());
- builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+ if (backend == Backend.DEX) {
+ builder
+ .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+ .addLibraryFiles(ToolHelper.getDefaultAndroidJar());
+ } else {
+ assert backend == Backend.CF;
+ builder
+ .setProgramConsumer(ClassFileConsumer.emptyConsumer())
+ .addLibraryFiles(ToolHelper.getJava8RuntimeJar());
+ }
AndroidApp app = ToolHelper.runR8(builder.build(), o -> o.enableClassInlining = false);
- DexInspector inspector = new DexInspector(app);
+ CodeInspector inspector = new CodeInspector(app, o -> o.enableCfFrontend = true);
List<FoundClassSubject> classes = inspector.allClasses();
// Check that the synthetic class is still present.
@@ -79,6 +115,8 @@
.count());
// Run code to check that the constructor with synthetic class as argument is present.
- assertTrue(runOnArt(app, mainClass).startsWith(mainClass.getCanonicalName()));
+ assertTrue(
+ (backend == Backend.DEX ? runOnArt(app, mainClass) : runOnJava(app, mainClass))
+ .startsWith(mainClass.getCanonicalName()));
}
}
diff --git a/src/test/java/com/android/tools/r8/regress/b71604169/Regress71604169Test.java b/src/test/java/com/android/tools/r8/regress/b71604169/Regress71604169Test.java
index f40ecfd..cc356c3 100644
--- a/src/test/java/com/android/tools/r8/regress/b71604169/Regress71604169Test.java
+++ b/src/test/java/com/android/tools/r8/regress/b71604169/Regress71604169Test.java
@@ -6,15 +6,32 @@
import static junit.framework.TestCase.assertEquals;
+import com.android.tools.r8.ClassFileConsumer;
import com.android.tools.r8.DexIndexedConsumer;
import com.android.tools.r8.R8Command;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.origin.Origin;
import com.google.common.collect.ImmutableList;
+import java.util.Arrays;
+import java.util.Collection;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+@RunWith(Parameterized.class)
public class Regress71604169Test extends TestBase {
+ private Backend backend;
+
+ @Parameterized.Parameters(name = "Backend: {0}")
+ public static Collection<Backend> data() {
+ return Arrays.asList(Backend.values());
+ }
+
+ public Regress71604169Test(Backend backend) {
+ this.backend = backend;
+ }
+
@Test
public void test() throws Exception {
R8Command.Builder builder = R8Command.builder();
@@ -28,7 +45,20 @@
builder.addProguardConfiguration(
ImmutableList.of(keepMainProguardConfiguration(mainClass, true, false)), Origin.unknown());
- builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
- assertEquals("Hello, world!", runOnArt(ToolHelper.runR8(builder.build()), mainClass));
+ if (backend == Backend.DEX) {
+ builder
+ .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+ .addLibraryFiles(ToolHelper.getDefaultAndroidJar());
+ } else {
+ assert backend == Backend.CF;
+ builder
+ .setProgramConsumer(ClassFileConsumer.emptyConsumer())
+ .addLibraryFiles(ToolHelper.getJava8RuntimeJar());
+ }
+ assertEquals(
+ "Hello, world!",
+ backend == Backend.DEX
+ ? runOnArt(ToolHelper.runR8(builder.build()), mainClass)
+ : runOnJava(ToolHelper.runR8(builder.build()), mainClass));
}
}
diff --git a/src/test/java/com/android/tools/r8/regress/b77496850/B77496850.java b/src/test/java/com/android/tools/r8/regress/b77496850/B77496850.java
index 03614b9..a052d86 100644
--- a/src/test/java/com/android/tools/r8/regress/b77496850/B77496850.java
+++ b/src/test/java/com/android/tools/r8/regress/b77496850/B77496850.java
@@ -15,9 +15,9 @@
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import org.junit.Test;
@@ -431,7 +431,7 @@
private void checkPathParserMethods(AndroidApp app, Class testClass, int a, int b)
throws Exception {
- DexInspector inspector = new DexInspector(app);
+ CodeInspector inspector = new CodeInspector(app);
DexItemFactory factory = inspector.getFactory();
ClassSubject clazz = inspector.clazz(testClass);
MethodSubject drawArc = clazz.method(
@@ -484,7 +484,7 @@
assert compiler == Tool.R8;
app = compileWithR8(app, "-keep class * { *; }", o -> o.minApiLevel = apiLevel.getLevel());
}
- DexInspector inspector = new DexInspector(app);
+ CodeInspector inspector = new CodeInspector(app);
DexItemFactory factory = inspector.getFactory();
ClassSubject clazz = inspector.clazz(testClass);
MethodSubject arcToBezier = clazz.method(
diff --git a/src/test/java/com/android/tools/r8/resolution/B77944861.java b/src/test/java/com/android/tools/r8/resolution/B77944861.java
index aa5552a..2118b44 100644
--- a/src/test/java/com/android/tools/r8/resolution/B77944861.java
+++ b/src/test/java/com/android/tools/r8/resolution/B77944861.java
@@ -3,7 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.resolution;
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
@@ -19,10 +19,10 @@
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -64,8 +64,8 @@
ProcessResult jvmOutput = ToolHelper.runJava(ImmutableList.of(jarPath), mainName);
assertEquals(0, jvmOutput.exitCode);
AndroidApp processedApp = runR8(readJar(jarPath), SomeView.class, out);
- DexInspector dexInspector = new DexInspector(processedApp);
- ClassSubject view = dexInspector.clazz("regress_77944861.SomeView");
+ CodeInspector codeInspector = new CodeInspector(processedApp);
+ ClassSubject view = codeInspector.clazz("regress_77944861.SomeView");
assertThat(view, isPresent());
String className = "regress_77944861.inner.TopLevelPolicy$MobileIconState";
MethodSubject initView = view.method("java.lang.String", "get", ImmutableList.of(className));
diff --git a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
index 22cab2a..23c8933 100644
--- a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.resolution.singletarget.Main;
import com.android.tools.r8.resolution.singletarget.one.AbstractSubClass;
import com.android.tools.r8.resolution.singletarget.one.AbstractTopClass;
@@ -107,8 +108,13 @@
ExecutorService executor = Executors.newSingleThreadExecutor();
RootSet rootSet = new RootSetBuilder(appInfoWithSubtyping, application,
buildKeepRuleForClass(Main.class, application.dexItemFactory), options).run(executor);
- appInfo = new Enqueuer(appInfoWithSubtyping, options, options.forceProguardCompatibility)
- .traceApplication(rootSet, executor, timing);
+ appInfo =
+ new Enqueuer(
+ appInfoWithSubtyping,
+ GraphLense.getIdentityLense(),
+ options,
+ options.forceProguardCompatibility)
+ .traceApplication(rootSet, executor, timing);
// We do not run the tree pruner to ensure that the hierarchy is as designed and not modified
// due to liveness.
}
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/ChromuimAssertionHookMock.java b/src/test/java/com/android/tools/r8/rewrite/assertions/ChromuimAssertionHookMock.java
new file mode 100644
index 0000000..772ef09
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/ChromuimAssertionHookMock.java
@@ -0,0 +1,11 @@
+// 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.rewrite.assertions;
+
+public class ChromuimAssertionHookMock {
+ public static void assertFailureHandler(AssertionError assertion) {
+ System.out.println("Got AssertionError " + assertion);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/ClassWithAssertions.java b/src/test/java/com/android/tools/r8/rewrite/assertions/ClassWithAssertions.java
index cc56790..49fe85e 100644
--- a/src/test/java/com/android/tools/r8/rewrite/assertions/ClassWithAssertions.java
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/ClassWithAssertions.java
@@ -16,7 +16,9 @@
}
int getX() {
+ System.out.println("1");
assert condition();
+ System.out.println("2");
return x;
}
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java b/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java
index 3c724c0..fff9ac3 100644
--- a/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java
@@ -4,17 +4,139 @@
package com.android.tools.r8.rewrite.assertions;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.DexIndexedConsumer;
+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.dex.Constants;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.function.Consumer;
+import java.util.function.Function;
import org.junit.Test;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+// This ASM class visitor has been adapted from
+// https://chromium.googlesource.com/chromium/src/+/164e81fcd0828b40f5496e9025349ea728cde7f5/build/android/bytecode/java/org/chromium/bytecode/AssertionEnablerClassAdapter.java
+// See b/110887293.
+
+/**
+ * An ClassVisitor for replacing Java ASSERT statements with a function by modifying Java bytecode.
+ *
+ * We do this in two steps, first step is to enable assert.
+ * Following bytecode is generated for each class with ASSERT statements:
+ * 0: ldc #8 // class CLASSNAME
+ * 2: invokevirtual #9 // Method java/lang/Class.desiredAssertionStatus:()Z
+ * 5: ifne 12
+ * 8: iconst_1
+ * 9: goto 13
+ * 12: iconst_0
+ * 13: putstatic #2 // Field $assertionsDisabled:Z
+ * Replaces line #13 to the following:
+ * 13: pop
+ * Consequently, $assertionsDisabled is assigned the default value FALSE.
+ * This is done in the first if statement in overridden visitFieldInsn. We do this per per-assert.
+ *
+ * Second step is to replace assert statement with a function:
+ * The followed instructions are generated by a java assert statement:
+ * getstatic #3 // Field $assertionsDisabled:Z
+ * ifne 118 // Jump to instruction as if assertion if not enabled
+ * ...
+ * ifne 19
+ * new #4 // class java/lang/AssertionError
+ * dup
+ * ldc #5 // String (don't have this line if no assert message given)
+ * invokespecial #6 // Method java/lang/AssertionError.
+ * athrow
+ * Replace athrow with:
+ * invokestatic #7 // Method org/chromium/base/JavaExceptionReporter.assertFailureHandler
+ * goto 118
+ * JavaExceptionReporter.assertFailureHandler is a function that handles the AssertionError,
+ * 118 is the instruction to execute as if assertion if not enabled.
+ */
+class AssertionEnablerClassAdapter extends ClassVisitor {
+ AssertionEnablerClassAdapter(ClassVisitor visitor) {
+ super(Opcodes.ASM6, visitor);
+ }
+
+ @Override
+ public MethodVisitor visitMethod(final int access, final String name, String desc,
+ String signature, String[] exceptions) {
+ return new RewriteAssertMethodVisitor(
+ Opcodes.ASM5, super.visitMethod(access, name, desc, signature, exceptions));
+ }
+
+ static class RewriteAssertMethodVisitor extends MethodVisitor {
+ static final String ASSERTION_DISABLED_NAME = "$assertionsDisabled";
+ static final String INSERT_INSTRUCTION_NAME = "assertFailureHandler";
+ static final String INSERT_INSTRUCTION_DESC =
+ Type.getMethodDescriptor(Type.VOID_TYPE, Type.getObjectType("java/lang/AssertionError"));
+ static final boolean INSERT_INSTRUCTION_ITF = false;
+
+ boolean mStartLoadingAssert;
+ Label mGotoLabel;
+
+ public RewriteAssertMethodVisitor(int api, MethodVisitor mv) {
+ super(api, mv);
+ }
+
+ @Override
+ public void visitFieldInsn(int opcode, String owner, String name, String desc) {
+ if (opcode == Opcodes.PUTSTATIC && name.equals(ASSERTION_DISABLED_NAME)) {
+ super.visitInsn(Opcodes.POP); // enable assert
+ } else if (opcode == Opcodes.GETSTATIC && name.equals(ASSERTION_DISABLED_NAME)) {
+ mStartLoadingAssert = true;
+ super.visitFieldInsn(opcode, owner, name, desc);
+ } else {
+ super.visitFieldInsn(opcode, owner, name, desc);
+ }
+ }
+
+ @Override
+ public void visitJumpInsn(int opcode, Label label) {
+ if (mStartLoadingAssert && opcode == Opcodes.IFNE && mGotoLabel == null) {
+ mGotoLabel = label;
+ }
+ super.visitJumpInsn(opcode, label);
+ }
+
+ @Override
+ public void visitInsn(int opcode) {
+ if (!mStartLoadingAssert || opcode != Opcodes.ATHROW) {
+ super.visitInsn(opcode);
+ } else {
+ super.visitMethodInsn(
+ Opcodes.INVOKESTATIC,
+ ChromuimAssertionHookMock.class.getCanonicalName().replace('.', '/'),
+ INSERT_INSTRUCTION_NAME,
+ INSERT_INSTRUCTION_DESC,
+ INSERT_INSTRUCTION_ITF);
+ super.visitJumpInsn(Opcodes.GOTO, mGotoLabel);
+ mStartLoadingAssert = false;
+ mGotoLabel = null;
+ }
+ }
+ }
+}
public class RemoveAssertionsTest extends TestBase {
@@ -27,7 +149,7 @@
ImmutableList.of(testClass),
keepMainProguardConfiguration(testClass, true, false),
options -> options.enableInlining = false);
- DexInspector x = new DexInspector(app);
+ CodeInspector x = new CodeInspector(app);
ClassSubject clazz = x.clazz(ClassWithAssertions.class);
assertTrue(clazz.isPresent());
@@ -38,4 +160,76 @@
clazz.method(new MethodSignature(Constants.CLASS_INITIALIZER_NAME, "void", new String[]{}));
assertTrue(!clinit.isPresent());
}
+
+ private Path buildTestToCf(Consumer<InternalOptions> consumer) throws Exception {
+ Path outputJar = temp.getRoot().toPath().resolve("output.jar");
+ R8Command command =
+ ToolHelper.prepareR8CommandBuilder(readClasses(ClassWithAssertions.class))
+ .setMode(CompilationMode.DEBUG)
+ .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
+ .setOutput(outputJar, OutputMode.ClassFile)
+ .build();
+ ToolHelper.runR8(command, consumer);
+ return outputJar;
+ }
+
+ @Test
+ public void testCfOutput() throws Exception {
+ String main = ClassWithAssertions.class.getCanonicalName();
+ ProcessResult result;
+ // Assertion is hit.
+ result = ToolHelper.runJava(buildTestToCf(options -> {}), "-ea", main, "0");
+ assertEquals(1, result.exitCode);
+ assertEquals("1\n".replace("\n", System.lineSeparator()), result.stdout);
+ // Assertion is not hit.
+ result = ToolHelper.runJava(buildTestToCf(options -> {}), "-ea", main, "1");
+ assertEquals(0, result.exitCode);
+ assertEquals("1\n2\n".replace("\n", System.lineSeparator()), result.stdout);
+ // Assertion is hit, but removed.
+ result = ToolHelper.runJava(
+ buildTestToCf(
+ options -> options.disableAssertions = true), "-ea", main, "0");
+ assertEquals(0, result.exitCode);
+ assertEquals("1\n2\n".replace("\n", System.lineSeparator()), result.stdout);
+ }
+
+ private byte[] identity(byte[] classBytes) {
+ return classBytes;
+ }
+
+ private byte[] chromiumAssertionEnabler(byte[] classBytes) {
+ ClassWriter writer = new ClassWriter(0);
+ new ClassReader(classBytes).accept(new AssertionEnablerClassAdapter(writer), 0);
+ return writer.toByteArray();
+ }
+
+ private AndroidApp runRegress110887293(Function<byte[], byte[]> rewriter) throws Exception {
+ return ToolHelper.runR8(
+ R8Command.builder()
+ .addClassProgramData(
+ rewriter.apply(ToolHelper.getClassAsBytes(ClassWithAssertions.class)),
+ Origin.unknown())
+ .addClassProgramData(
+ ToolHelper.getClassAsBytes(ChromuimAssertionHookMock.class), Origin.unknown())
+ .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+ .setMode(CompilationMode.DEBUG)
+ .build());
+ }
+
+ @Test
+ public void regress110887293() throws Exception {
+ AndroidApp app;
+ // Assertions removed for default assertion code.
+ app = runRegress110887293(this::identity);
+ assertEquals("1\n2\n", runOnArt(app, ClassWithAssertions.class.getCanonicalName(), "0"));
+ assertEquals("1\n2\n", runOnArt(app, ClassWithAssertions.class.getCanonicalName(), "1"));
+ // Assertions not removed when default assertion code is not present.
+ app = runRegress110887293(this::chromiumAssertionEnabler);
+ assertEquals(
+ "1\nGot AssertionError java.lang.AssertionError\n2\n".replace("\n", System.lineSeparator()),
+ runOnArt(app, ClassWithAssertions.class.getCanonicalName(), "0"));
+ assertEquals(
+ "1\n2\n".replace("\n", System.lineSeparator()),
+ runOnArt(app, ClassWithAssertions.class.getCanonicalName(), "1"));
+ }
}
diff --git a/src/test/java/com/android/tools/r8/rewrite/longcompare/LongCompare.java b/src/test/java/com/android/tools/r8/rewrite/longcompare/LongCompare.java
index e0d9ecf..16b7922 100644
--- a/src/test/java/com/android/tools/r8/rewrite/longcompare/LongCompare.java
+++ b/src/test/java/com/android/tools/r8/rewrite/longcompare/LongCompare.java
@@ -9,12 +9,12 @@
import com.android.tools.r8.OutputMode;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.InstructionSubject;
-import com.android.tools.r8.utils.DexInspector.InvokeInstructionSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.InvokeInstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -65,8 +65,8 @@
Path dexPath = outputPath.resolve("classes.dex");
- DexInspector dexInspector = new DexInspector(dexPath);
- ClassSubject classSubject = dexInspector.clazz("rewrite.LongCompare");
+ CodeInspector codeInspector = new CodeInspector(dexPath);
+ ClassSubject classSubject = codeInspector.clazz("rewrite.LongCompare");
MethodSubject methodSubject = classSubject
.method("int", "simpleCompare", Arrays.asList("long", "long"));
// Check that exception handler is removed since it is no longer needed.
diff --git a/src/test/java/com/android/tools/r8/rewrite/longcompare/RequireNonNullRewriteTest.java b/src/test/java/com/android/tools/r8/rewrite/longcompare/RequireNonNullRewriteTest.java
index a50a667..278bc8b 100644
--- a/src/test/java/com/android/tools/r8/rewrite/longcompare/RequireNonNullRewriteTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/longcompare/RequireNonNullRewriteTest.java
@@ -11,11 +11,11 @@
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.InstructionSubject;
-import com.android.tools.r8.utils.DexInspector.InvokeInstructionSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.InvokeInstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -75,8 +75,8 @@
Path dexPath = outputPath.resolve("classes.dex");
- DexInspector dexInspector = new DexInspector(dexPath);
- ClassSubject classSubject = dexInspector.clazz("rewrite.RequireNonNull");
+ CodeInspector codeInspector = new CodeInspector(dexPath);
+ ClassSubject classSubject = codeInspector.clazz("rewrite.RequireNonNull");
MethodSubject methodSubject = classSubject
.method("java.lang.Object", "nonnullRemove", Collections.emptyList());
diff --git a/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java b/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java
index 087b5f8..d3784ee 100644
--- a/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java
@@ -25,9 +25,9 @@
import com.android.tools.r8.smali.SmaliBuilder;
import com.android.tools.r8.smali.SmaliTestBase;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import org.junit.Test;
public class StaticValuesTest extends SmaliTestBase {
@@ -94,7 +94,7 @@
AndroidApp originalApplication = buildApplication(builder);
AndroidApp processedApplication = processApplication(originalApplication);
- DexInspector inspector = new DexInspector(processedApplication);
+ CodeInspector inspector = new CodeInspector(processedApplication);
// Test is running without tree-shaking, so the empty <clinit> is not removed.
assertTrue(
inspector.clazz("Test").clinit().getMethod().getCode().asDexCode().isEmptyVoidMethod());
@@ -178,7 +178,7 @@
AndroidApp originalApplication = buildApplication(builder);
AndroidApp processedApplication = processApplication(originalApplication);
- DexInspector inspector = new DexInspector(processedApplication);
+ CodeInspector inspector = new CodeInspector(processedApplication);
MethodSubject clinit = inspector.clazz("Test").clinit();
// Nothing changed in the class initializer.
assertEquals(5, clinit.getMethod().getCode().asDexCode().instructions.length);
@@ -219,7 +219,7 @@
AndroidApp originalApplication = buildApplication(builder);
AndroidApp processedApplication = processApplication(originalApplication);
- DexInspector inspector = new DexInspector(processedApplication);
+ CodeInspector inspector = new CodeInspector(processedApplication);
// Test is running without tree-shaking, so the empty <clinit> is not removed.
assertTrue(
inspector.clazz("Test").clinit().getMethod().getCode().asDexCode().isEmptyVoidMethod());
@@ -261,7 +261,7 @@
AndroidApp originalApplication = buildApplication(builder);
AndroidApp processedApplication = processApplication(originalApplication);
- DexInspector inspector = new DexInspector(processedApplication);
+ CodeInspector inspector = new CodeInspector(processedApplication);
// Test is running without tree-shaking, so the empty <clinit> is not removed.
assertTrue(
inspector.clazz("Test").clinit().getMethod().getCode().asDexCode().isEmptyVoidMethod());
@@ -311,7 +311,7 @@
AndroidApp originalApplication = buildApplication(builder);
AndroidApp processedApplication = processApplication(originalApplication);
- DexInspector inspector = new DexInspector(processedApplication);
+ CodeInspector inspector = new CodeInspector(processedApplication);
// Test is running without tree-shaking, so the empty <clinit> is not removed.
assertTrue(
inspector.clazz("Test").clinit().getMethod().getCode().asDexCode().isEmptyVoidMethod());
@@ -384,7 +384,7 @@
AndroidApp originalApplication = buildApplication(builder);
AndroidApp processedApplication = processApplication(originalApplication);
- DexInspector inspector = new DexInspector(processedApplication);
+ CodeInspector inspector = new CodeInspector(processedApplication);
assertTrue(inspector.clazz("Test").clinit().isPresent());
DexValue value;
@@ -471,7 +471,7 @@
AndroidApp originalApplication = buildApplication(builder);
AndroidApp processedApplication = processApplication(originalApplication);
- DexInspector inspector = new DexInspector(processedApplication);
+ CodeInspector inspector = new CodeInspector(processedApplication);
assertTrue(inspector.clazz(className).isPresent());
// Test is running without tree-shaking, so the empty <clinit> is not removed.
assertTrue(
@@ -520,7 +520,7 @@
AndroidApp originalApplication = buildApplication(builder);
AndroidApp processedApplication = processApplication(originalApplication);
- DexInspector inspector = new DexInspector(processedApplication);
+ CodeInspector inspector = new CodeInspector(processedApplication);
assertTrue(inspector.clazz(className).isPresent());
assertTrue(inspector.clazz(className).clinit().isPresent());
@@ -553,7 +553,7 @@
AndroidApp originalApplication = buildApplication(builder);
AndroidApp processedApplication = processApplication(originalApplication);
- DexInspector inspector = new DexInspector(processedApplication);
+ CodeInspector inspector = new CodeInspector(processedApplication);
MethodSubject clinit = inspector.clazz("Test").clinit();
// Nothing changed in the class initializer.
assertEquals(3, clinit.getMethod().getCode().asDexCode().instructions.length);
diff --git a/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingJarTest.java b/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingJarTest.java
index ae88c12..bffcfc5 100644
--- a/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingJarTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingJarTest.java
@@ -18,9 +18,9 @@
import com.android.tools.r8.jasmin.JasminTestBase;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import java.util.List;
import java.util.stream.Collectors;
@@ -282,7 +282,7 @@
appBuilder.addProgramFiles(ToolHelper.getClassFileForTestClass(CheckSwitchInTestClass.class));
AndroidApp app = compileWithR8(appBuilder.build());
- DexInspector inspector = new DexInspector(app);
+ CodeInspector inspector = new CodeInspector(app);
MethodSubject method = inspector.clazz("Test").method("int", "test", ImmutableList.of("int"));
DexCode code = method.getMethod().getCode().asDexCode();
diff --git a/src/test/java/com/android/tools/r8/rewrite/switchmaps/RewriteSwitchMapsTest.java b/src/test/java/com/android/tools/r8/rewrite/switchmaps/RewriteSwitchMapsTest.java
index 38247bd..b2bcb4e 100644
--- a/src/test/java/com/android/tools/r8/rewrite/switchmaps/RewriteSwitchMapsTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/switchmaps/RewriteSwitchMapsTest.java
@@ -10,7 +10,7 @@
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.nio.file.Paths;
import java.util.List;
@@ -45,7 +45,7 @@
.setProgramConsumer(DexIndexedConsumer.emptyConsumer())
.build();
AndroidApp result = ToolHelper.runR8(command);
- DexInspector inspector = new DexInspector(result);
+ CodeInspector inspector = new CodeInspector(result);
Assert.assertFalse(inspector.clazz(SWITCHMAP_CLASS_NAME).isPresent());
}
}
diff --git a/src/test/java/com/android/tools/r8/shaking/AsterisksTest.java b/src/test/java/com/android/tools/r8/shaking/AsterisksTest.java
new file mode 100644
index 0000000..d28770a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/AsterisksTest.java
@@ -0,0 +1,176 @@
+// 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;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+class B111974287 {
+ B111974287 self;
+ B111974287[] clones;
+
+ B111974287() {
+ self = this;
+ clones = new B111974287[1];
+ clones[0] = self;
+ }
+
+ B111974287 fooX() {
+ System.out.println("fooX");
+ return self;
+ }
+
+ B111974287 fooYY() {
+ System.out.println("fooYY");
+ return self;
+ }
+
+ B111974287 fooZZZ() {
+ System.out.println("fooZZZ");
+ return self;
+ }
+}
+
+@RunWith(Parameterized.class)
+public class AsterisksTest extends ProguardCompatibilityTestBase {
+ private final static List<Class> CLASSES = ImmutableList.of(B111974287.class);
+ private final Shrinker shrinker;
+
+ public AsterisksTest(Shrinker shrinker) {
+ this.shrinker = shrinker;
+ }
+
+ @Parameters(name = "shrinker: {0}")
+ public static Collection<Object> data() {
+ return ImmutableList.of(Shrinker.PROGUARD6, Shrinker.R8, Shrinker.R8_CF);
+ }
+
+ @Test
+ public void doubleAsterisksInField() throws Exception {
+ List<String> config = ImmutableList.of(
+ "-keep class **." + B111974287.class.getSimpleName() + "{",
+ " ** **;",
+ "}"
+ );
+ CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+ ClassSubject classSubject = codeInspector.clazz(B111974287.class);
+ assertThat(classSubject, isPresent());
+ assertThat(classSubject, not(isRenamed()));
+ FieldSubject fieldSubject = classSubject.field(B111974287.class.getTypeName(), "self");
+ assertThat(fieldSubject, isPresent());
+ assertThat(fieldSubject, not(isRenamed()));
+ fieldSubject = classSubject.field(B111974287.class.getTypeName() + "[]", "clones");
+ // TODO(b/111974287): Proguard6 kept and renamed the field with array type.
+ if (shrinker == Shrinker.PROGUARD6) {
+ return;
+ }
+ assertThat(fieldSubject, not(isPresent()));
+ }
+
+ @Test
+ public void doubleAsterisksInMethod() throws Exception {
+ List<String> config = ImmutableList.of(
+ "-keep class **." + B111974287.class.getSimpleName() + "{",
+ " ** foo**(...);",
+ "}"
+ );
+ CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+ ClassSubject classSubject = codeInspector.clazz(B111974287.class);
+ assertThat(classSubject, isPresent());
+ assertThat(classSubject, not(isRenamed()));
+ DexClass clazz = classSubject.getDexClass();
+ assertEquals(3, clazz.virtualMethods().length);
+ for (DexEncodedMethod encodedMethod : clazz.virtualMethods()) {
+ assertTrue(encodedMethod.method.name.toString().startsWith("foo"));
+ MethodSubject methodSubject =
+ classSubject.method(MethodSignature.fromDexMethod(encodedMethod.method));
+ assertThat(methodSubject, isPresent());
+ assertThat(methodSubject, not(isRenamed()));
+ }
+ }
+
+ @Test
+ public void tripleAsterisksInField() throws Exception {
+ List<String> config = ImmutableList.of(
+ "-keep class **." + B111974287.class.getSimpleName() + "{",
+ " *** ***;",
+ "}"
+ );
+ CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+ ClassSubject classSubject = codeInspector.clazz(B111974287.class);
+ assertThat(classSubject, isPresent());
+ assertThat(classSubject, not(isRenamed()));
+ FieldSubject fieldSubject = classSubject.field(B111974287.class.getTypeName(), "self");
+ assertThat(fieldSubject, isPresent());
+ assertThat(fieldSubject, not(isRenamed()));
+ fieldSubject = classSubject.field(B111974287.class.getTypeName() + "[]", "clones");
+ assertThat(fieldSubject, isPresent());
+ assertThat(fieldSubject, not(isRenamed()));
+ }
+
+ @Test
+ public void tripleAsterisksInMethod() throws Exception {
+ List<String> config = ImmutableList.of(
+ "-keep class **." + B111974287.class.getSimpleName() + "{",
+ " *** foo***(...);",
+ "}"
+ );
+ CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+ ClassSubject classSubject = codeInspector.clazz(B111974287.class);
+ assertThat(classSubject, isPresent());
+ assertThat(classSubject, not(isRenamed()));
+ DexClass clazz = classSubject.getDexClass();
+ assertEquals(3, clazz.virtualMethods().length);
+ for (DexEncodedMethod encodedMethod : clazz.virtualMethods()) {
+ assertTrue(encodedMethod.method.name.toString().startsWith("foo"));
+ MethodSubject methodSubject =
+ classSubject.method(MethodSignature.fromDexMethod(encodedMethod.method));
+ assertThat(methodSubject, isPresent());
+ assertThat(methodSubject, not(isRenamed()));
+ }
+ }
+
+ @Test
+ public void quadrupleAsterisksInType() throws Exception {
+ List<String> config = ImmutableList.of(
+ "-keep class **** {",
+ " **** foo***(...);",
+ "}"
+ );
+ CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+ ClassSubject classSubject = codeInspector.clazz(B111974287.class);
+ assertThat(classSubject, isPresent());
+ assertThat(classSubject, not(isRenamed()));
+ DexClass clazz = classSubject.getDexClass();
+ assertEquals(3, clazz.virtualMethods().length);
+ for (DexEncodedMethod encodedMethod : clazz.virtualMethods()) {
+ assertTrue(encodedMethod.method.name.toString().startsWith("foo"));
+ MethodSubject methodSubject =
+ classSubject.method(MethodSignature.fromDexMethod(encodedMethod.method));
+ assertThat(methodSubject, isPresent());
+ assertThat(methodSubject, not(isRenamed()));
+ }
+ }
+
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/B112290098.java b/src/test/java/com/android/tools/r8/shaking/B112290098.java
new file mode 100644
index 0000000..429853c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/B112290098.java
@@ -0,0 +1,59 @@
+// 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;
+
+import static junit.framework.TestCase.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.AndroidApp;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class B112290098 extends TestBase {
+
+ @Ignore("b/112290098")
+ @Test
+ public void test() throws Exception {
+ String mainClass = TestClass.class.getName();
+ AndroidApp input = readClasses(TestClass.class, C.class);
+ AndroidApp output =
+ compileWithR8(
+ input,
+ String.join(
+ System.lineSeparator(),
+ "-keep public class " + mainClass + " {",
+ " public static void main(...);",
+ "}"));
+ assertEquals(runOnArt(compileWithD8(input), mainClass), runOnArt(output, mainClass));
+ }
+
+ public static class TestClass {
+
+ public static void main(String[] args) {
+ // Instantiation that will be removed as a result of class inlining.
+ new C();
+
+ C obj = null;
+ try {
+ // After inlining this will lead to an iget instruction.
+ obj.getField();
+ } catch (NullPointerException e) {
+ System.out.println("Caught NullPointerException");
+ }
+ }
+ }
+
+ public static class C {
+
+ // In the second round of tree shaking, C is no longer instantiated, but we should still
+ // keep this field to avoid a NoSuchFieldError instead of a NullPointerException at the
+ // obj.getField() invocation.
+ public int field = 42;
+
+ public int getField() {
+ return field;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ClassKindTest.java b/src/test/java/com/android/tools/r8/shaking/ClassKindTest.java
index 3475871..89cbbbe 100644
--- a/src/test/java/com/android/tools/r8/shaking/ClassKindTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ClassKindTest.java
@@ -9,7 +9,7 @@
import com.android.tools.r8.shaking.classkinds.Enum;
import com.android.tools.r8.shaking.classkinds.Interface;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import java.util.Collection;
@@ -59,7 +59,7 @@
Assert.assertTrue(classes.isEmpty());
return;
}
- DexInspector inspector = new DexInspector(app);
+ CodeInspector inspector = new CodeInspector(app);
HashSet<java.lang.Class<?>> expected = Sets.newHashSet(classes);
CLASSES_TO_INCLUDE.forEach(c -> {
Assert.assertEquals(expected.contains(c), inspector.clazz(c).isPresent());
diff --git a/src/test/java/com/android/tools/r8/shaking/FieldTypeTest.java b/src/test/java/com/android/tools/r8/shaking/FieldTypeTest.java
index 8f00f8d..2b4865d 100644
--- a/src/test/java/com/android/tools/r8/shaking/FieldTypeTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/FieldTypeTest.java
@@ -3,7 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking;
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
@@ -17,8 +17,8 @@
import com.android.tools.r8.naming.MemberNaming.FieldSignature;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.nio.file.Path;
import org.junit.Test;
@@ -126,7 +126,7 @@
assertThat(artResult.stdout, containsString(impl2.name));
assertEquals(-1, artResult.stderr.indexOf("DoFieldPut"));
- DexInspector inspector = new DexInspector(processedApp);
+ CodeInspector inspector = new CodeInspector(processedApp);
ClassSubject itf1Subject = inspector.clazz(itf1.name);
assertThat(itf1Subject, isPresent());
}
diff --git a/src/test/java/com/android/tools/r8/shaking/InstantiatedLambdasTestRunner.java b/src/test/java/com/android/tools/r8/shaking/InstantiatedLambdasTestRunner.java
index 474af4d..12ef651 100644
--- a/src/test/java/com/android/tools/r8/shaking/InstantiatedLambdasTestRunner.java
+++ b/src/test/java/com/android/tools/r8/shaking/InstantiatedLambdasTestRunner.java
@@ -19,7 +19,6 @@
import com.android.tools.r8.utils.FileUtils;
import java.io.IOException;
import java.nio.file.Path;
-import java.nio.file.Paths;
import org.junit.Before;
import org.junit.Test;
@@ -122,7 +121,7 @@
Path outPg = temp.getRoot().toPath().resolve(outName);
ProcessResult proguardResult =
ToolHelper.runProguard6Raw(
- inputJar, outPg, Paths.get(ToolHelper.JAVA_8_RUNTIME), pgConfig, null);
+ inputJar, outPg, ToolHelper.getJava8RuntimeJar(), pgConfig, null);
System.out.println(proguardResult.stdout);
if (proguardResult.exitCode != 0) {
System.out.println(proguardResult.stderr);
diff --git a/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesTest.java b/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesTest.java
index 6ecdb09..dba2caa 100644
--- a/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesTest.java
@@ -4,7 +4,7 @@
package com.android.tools.r8.shaking;
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -14,6 +14,7 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import com.android.tools.r8.ClassFileConsumer;
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.DataResourceProvider;
import com.android.tools.r8.DexIndexedConsumer;
@@ -31,7 +32,7 @@
import com.android.tools.r8.origin.PathOrigin;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import com.google.common.io.ByteStreams;
import com.google.common.io.CharSource;
@@ -39,12 +40,16 @@
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.jar.JarOutputStream;
import java.util.zip.ZipEntry;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
class A {
private static String buildClassName(String className) {
@@ -65,7 +70,20 @@
}
+@RunWith(Parameterized.class)
public class LibraryProvidedProguardRulesTest extends TestBase {
+
+ private Backend backend;
+
+ @Parameters(name = "Backend: {0}")
+ public static Collection<Backend> data() {
+ return Arrays.asList(Backend.values());
+ }
+
+ public LibraryProvidedProguardRulesTest(Backend backend) {
+ this.backend = backend;
+ }
+
private void addTextJarEntry(JarOutputStream out, String name, String content) throws Exception {
out.putNextEntry(new ZipEntry(name));
ByteStreams.copy(
@@ -76,7 +94,7 @@
private AndroidApp runTest(List<String> rules, DiagnosticsHandler handler) throws Exception {
Path jar = temp.newFile("test.jar").toPath();
try (JarOutputStream out = new JarOutputStream(new FileOutputStream(jar.toFile()))) {
- addTestClassesToJar(out, A.class, B.class);
+ addTestClassesToJar(out, ImmutableList.of(A.class, B.class));
for (int i = 0; i < rules.size(); i++) {
String name = "META-INF/proguard/jar" + (i == 0 ? "" : i) + ".rules";
addTextJarEntry(out, name, rules.get(i));
@@ -84,11 +102,19 @@
}
try {
- R8Command command = (handler != null ? R8Command.builder(handler) : R8Command.builder())
- .addProgramFiles(jar)
- .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
- .build();
- return ToolHelper.runR8(command);
+ R8Command.Builder builder =
+ (handler != null ? R8Command.builder(handler) : R8Command.builder()).addProgramFiles(jar);
+ if (backend == Backend.DEX) {
+ builder
+ .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+ .addLibraryFiles(ToolHelper.getDefaultAndroidJar());
+ } else {
+ assert backend == Backend.CF;
+ builder
+ .setProgramConsumer(ClassFileConsumer.emptyConsumer())
+ .addLibraryFiles(ToolHelper.getJava8RuntimeJar());
+ }
+ return ToolHelper.runR8(builder.build());
} catch (CompilationFailedException e) {
assertNotNull(handler);
return null;
@@ -103,7 +129,7 @@
@Test
public void keepOnlyA() throws Exception {
AndroidApp app = runTest("-keep class " + A.class.getTypeName() +" {}", null);
- DexInspector inspector = new DexInspector(app);
+ CodeInspector inspector = new CodeInspector(app);
assertThat(inspector.clazz(A.class), isPresent());
assertThat(inspector.clazz(B.class), not(isPresent()));
}
@@ -111,7 +137,7 @@
@Test
public void keepOnlyB() throws Exception {
AndroidApp app = runTest("-keep class **B {}", null);
- DexInspector inspector = new DexInspector(app);
+ CodeInspector inspector = new CodeInspector(app);
assertThat(inspector.clazz(A.class), not(isPresent()));
assertThat(inspector.clazz(B.class), isPresent());
}
@@ -119,7 +145,7 @@
@Test
public void keepBoth() throws Exception {
AndroidApp app = runTest("-keep class ** {}", null);
- DexInspector inspector = new DexInspector(app);
+ CodeInspector inspector = new CodeInspector(app);
assertThat(inspector.clazz(A.class), isPresent());
assertThat(inspector.clazz(B.class), isPresent());
}
@@ -127,7 +153,7 @@
@Test
public void multipleFiles() throws Exception {
AndroidApp app = runTest(ImmutableList.of("-keep class **A {}", "-keep class **B {}"), null);
- DexInspector inspector = new DexInspector(app);
+ CodeInspector inspector = new CodeInspector(app);
assertThat(inspector.clazz(A.class), isPresent());
assertThat(inspector.clazz(B.class), isPresent());
}
@@ -186,10 +212,19 @@
public void throwingDataResourceProvider() throws Exception {
DiagnosticsChecker checker = new DiagnosticsChecker();
try {
- R8Command command = R8Command.builder(checker)
- .addProgramResourceProvider(new TestProvider())
- .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
- .build();
+ R8Command.Builder builder =
+ R8Command.builder(checker).addProgramResourceProvider(new TestProvider());
+ if (backend == Backend.DEX) {
+ builder
+ .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+ .addLibraryFiles(ToolHelper.getDefaultAndroidJar());
+ } else {
+ assert backend == Backend.CF;
+ builder
+ .setProgramConsumer(ClassFileConsumer.emptyConsumer())
+ .addLibraryFiles(ToolHelper.getJava8RuntimeJar());
+ }
+ builder.build();
fail("Should not succeed");
} catch (CompilationFailedException e) {
DiagnosticsChecker.checkDiagnostic(
diff --git a/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java b/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
index bce2e8b..3b5a830 100644
--- a/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
@@ -41,8 +41,14 @@
@RunWith(Parameterized.class)
public class PrintUsageTest {
+ private enum Backend {
+ CF,
+ DEX
+ }
+
private static final String PRINT_USAGE_FILE_SUFFIX = "-print-usage.txt";
+ private final Backend backend;
private final String test;
private final String programFile;
private final List<String> keepRulesFiles;
@@ -52,9 +58,11 @@
public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
public PrintUsageTest(
+ Backend backend,
String test,
List<String> keepRulesFiles,
Consumer<PrintUsageInspector> inspection) {
+ this.backend = backend;
this.test = test;
this.programFile = ToolHelper.EXAMPLES_BUILD_DIR + test + ".jar";
this.keepRulesFiles = keepRulesFiles;
@@ -64,22 +72,29 @@
@Before
public void runR8andGetPrintUsage() throws Exception {
Path out = temp.getRoot().toPath();
- R8Command command =
+ R8Command.Builder builder =
ToolHelper.addProguardConfigurationConsumer(
R8Command.builder(),
pgConfig -> {
pgConfig.setPrintUsage(true);
pgConfig.setPrintUsageFile(out.resolve(test + PRINT_USAGE_FILE_SUFFIX));
})
- .setOutput(out, OutputMode.DexIndexed)
.addProgramFiles(Paths.get(programFile))
- .addProguardConfigurationFiles(ListUtils.map(keepRulesFiles, Paths::get))
- .addLibraryFiles(ToolHelper.getDefaultAndroidJar())
- .build();
- ToolHelper.runR8(command, options -> {
- // Disable inlining to make this test not depend on inlining decisions.
- options.enableInlining = false;
- });
+ .addProguardConfigurationFiles(ListUtils.map(keepRulesFiles, Paths::get));
+
+ if (backend == Backend.DEX) {
+ builder
+ .setOutput(out, OutputMode.DexIndexed)
+ .addLibraryFiles(ToolHelper.getDefaultAndroidJar());
+ } else {
+ builder.setOutput(out, OutputMode.ClassFile).addLibraryFiles(ToolHelper.getJava8RuntimeJar());
+ }
+ ToolHelper.runR8(
+ builder.build(),
+ options -> {
+ // Disable inlining to make this test not depend on inlining decisions.
+ options.enableInlining = false;
+ });
}
@Test
@@ -106,6 +121,7 @@
inspections.put("shaking12:keep-rules-printusage.txt", PrintUsageTest::inspectShaking12);
List<Object[]> testCases = new ArrayList<>();
+ for (Backend backend : Backend.values()) {
Set<String> usedInspections = new HashSet<>();
for (String test : tests) {
File[] keepFiles = new File(ToolHelper.EXAMPLES_DIR + test)
@@ -115,11 +131,13 @@
Consumer<PrintUsageInspector> inspection =
getTestOptionalParameter(inspections, usedInspections, test, keepName);
if (inspection != null) {
- testCases.add(new Object[]{test, ImmutableList.of(keepFile.getPath()), inspection});
+ testCases.add(
+ new Object[] {backend, test, ImmutableList.of(keepFile.getPath()), inspection});
}
}
}
- assert usedInspections.size() == inspections.size();
+ assert usedInspections.size() == inspections.size();
+ }
return testCases;
}
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
index 1379b95..f79e01f 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -27,11 +27,11 @@
import com.android.tools.r8.shaking.ProguardConfigurationParser.IdentifierPatternWithWildcards;
import com.android.tools.r8.utils.AbortException;
import com.android.tools.r8.utils.DefaultDiagnosticsHandler;
-import com.android.tools.r8.utils.DexInspector;
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.InternalOptions.PackageObfuscationMode;
import com.android.tools.r8.utils.KeepingDiagnosticHandler;
import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.io.File;
import java.io.IOException;
@@ -41,6 +41,7 @@
import java.util.Iterator;
import java.util.List;
import java.util.function.Function;
+import java.util.stream.Collectors;
import org.junit.Before;
import org.junit.Test;
@@ -1233,7 +1234,7 @@
"-keepdirectories"
));
checkFileFilterMatchAnything(config.getAdaptResourceFilenames());
- checkFileFilterMatchAnything(config.getAdaptResourceFilecontents());
+ checkFileFilterMatchAnything(config.getAdaptResourceFileContents());
checkFileFilterMatchAnything(config.getKeepDirectories());
}
@@ -1246,7 +1247,7 @@
"-adaptresourcefilecontents"
));
checkFileFilterMatchAnything(config.getAdaptResourceFilenames());
- checkFileFilterMatchAnything(config.getAdaptResourceFilecontents());
+ checkFileFilterMatchAnything(config.getAdaptResourceFileContents());
checkFileFilterMatchAnything(config.getKeepDirectories());
}
@@ -1259,7 +1260,7 @@
"-adaptresourcefilenames"
));
checkFileFilterMatchAnything(config.getAdaptResourceFilenames());
- checkFileFilterMatchAnything(config.getAdaptResourceFilecontents());
+ checkFileFilterMatchAnything(config.getAdaptResourceFileContents());
checkFileFilterMatchAnything(config.getKeepDirectories());
}
@@ -1281,7 +1282,7 @@
"-keepdirectories " + FILE_FILTER_SINGLE
));
checkFileFilterSingle(config.getAdaptResourceFilenames());
- checkFileFilterSingle(config.getAdaptResourceFilecontents());
+ checkFileFilterSingle(config.getAdaptResourceFileContents());
checkFileFilterSingle(config.getKeepDirectories());
}
@@ -1314,7 +1315,7 @@
"-keepdirectories " + FILE_FILTER_MULTIPLE
));
checkFileFilterMultiple(config.getAdaptResourceFilenames());
- checkFileFilterMultiple(config.getAdaptResourceFilecontents());
+ checkFileFilterMultiple(config.getAdaptResourceFileContents());
checkFileFilterMultiple(config.getKeepDirectories());
}
@@ -1807,12 +1808,70 @@
}
@Test
- public void parse_pariallyImplemented_notSupported() {
+ public void parse_partiallyImplemented_notSupported() {
testNotSupported("-keepdirectories");
- testNotSupported("-adaptresourcefilenames");
- testNotSupported("-adaptresourcefilecontents");
}
+ private void checkRulesSourceSnippet(List<String> sourceRules) {
+ checkRulesSourceSnippet(sourceRules, sourceRules, false);
+ }
+
+ private void checkRulesSourceSnippet(
+ List<String> sourceRules, List<String> expected, boolean trim) {
+ reset();
+ parser.parse(createConfigurationForTesting(sourceRules));
+ verifyParserEndsCleanly();
+ List<ProguardConfigurationRule> rules = parser.getConfig().getRules();
+ assertEquals(expected.size(), rules.size());
+ for (int i = 0; i < expected.size(); i++) {
+ assertEquals(trim ? expected.get(i).trim() : expected.get(i), rules.get(i).getSource());
+ }
+ }
+
+ @Test
+ public void accurateSourceSnippet() {
+ String rule1 = String.join(System.lineSeparator(), ImmutableList.of("-keep class A { *; }"));
+ String rule2 =
+ String.join(System.lineSeparator(), ImmutableList.of("-keep class A ", "{ *; ", "}"));
+ String rule3 =
+ String.join(
+ System.lineSeparator(), ImmutableList.of("-checkdiscard class A ", "{ *; ", "}"));
+
+ checkRulesSourceSnippet(ImmutableList.of(rule1));
+ checkRulesSourceSnippet(ImmutableList.of(rule2));
+ checkRulesSourceSnippet(ImmutableList.of(rule3));
+ checkRulesSourceSnippet(ImmutableList.of(rule1, rule2, rule3));
+ }
+
+ @Test
+ public void accurateSourceSnippet_withWhitespace() {
+ Iterable<String> nonEmptyWhiteSpace =
+ whiteSpace.stream().filter(space -> !space.equals("")).collect(Collectors.toList());
+ for (String space : nonEmptyWhiteSpace) {
+ String rule1 =
+ String.join(System.lineSeparator(), ImmutableList.of(" -keep class A { *; } "))
+ .replaceAll(" {2}", space);
+ String rule2 =
+ String.join(
+ System.lineSeparator(), ImmutableList.of("-keep class A ", "{ *; ", "}", ""))
+ .replaceAll(" {2}", space);
+
+ checkRulesSourceSnippet(ImmutableList.of(rule1), ImmutableList.of(rule1), true);
+ checkRulesSourceSnippet(
+ ImmutableList.of("#Test comment ", "", rule1), ImmutableList.of(rule1), true);
+ checkRulesSourceSnippet(
+ ImmutableList.of("#Test comment ", "", rule1, "", "#Test comment ", ""),
+ ImmutableList.of(rule1),
+ true);
+ checkRulesSourceSnippet(ImmutableList.of(rule2), ImmutableList.of(rule2), true);
+ checkRulesSourceSnippet(ImmutableList.of(rule1, rule2), ImmutableList.of(rule1, rule2), true);
+ checkRulesSourceSnippet(
+ ImmutableList.of(
+ "#Test comment ", "", rule1, " ", "#Test comment ", "", rule2, "#Test comment ", ""),
+ ImmutableList.of(rule1, rule2),
+ true);
+ }
+ }
private ProguardConfiguration parseAndVerifyParserEndsCleanly(List<String> config) {
parser.parse(createConfigurationForTesting(config));
@@ -1844,7 +1903,7 @@
ImmutableList.of(proguardConfig, additionalProguardConfig),
null);
assertEquals(0, result.exitCode);
- DexInspector proguardInspector = new DexInspector(readJar(proguardedJar));
+ CodeInspector proguardInspector = new CodeInspector(readJar(proguardedJar));
assertEquals(1, proguardInspector.allClasses().size());
}
}
@@ -1867,7 +1926,7 @@
ImmutableList.of(proguardConfig, additionalProguardConfig),
null);
assertEquals(0, result.exitCode);
- DexInspector proguardInspector = new DexInspector(readJar(proguardedJar));
+ CodeInspector proguardInspector = new CodeInspector(readJar(proguardedJar));
assertEquals(1, proguardInspector.allClasses().size());
}
}
diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
index cba8686..ad67919 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
@@ -14,12 +14,15 @@
import com.android.tools.r8.R8Command;
import com.android.tools.r8.ToolHelper;
import java.io.BufferedReader;
+import java.io.IOException;
import java.io.PrintStream;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.junit.Assert;
@@ -27,8 +30,28 @@
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+@RunWith(Parameterized.class)
public class TreeShakingSpecificTest {
+ enum Backend {
+ DEX,
+ CF
+ }
+
+ private Backend backend;
+
+ @Parameters(name = "Backend: {0}")
+ public static Collection<Backend> data() {
+ return Arrays.asList(Backend.values());
+ }
+
+ public TreeShakingSpecificTest(Backend backend) {
+ this.backend = backend;
+ }
+
private static final String VALID_PROGUARD_DIR = "src/test/proguard/valid/";
@Rule
@@ -37,18 +60,28 @@
@Rule
public ExpectedException thrown = ExpectedException.none();
+ private void finishBuild(R8Command.Builder builder, Path out, String test) throws IOException {
+ Path input;
+ if (backend == Backend.DEX) {
+ builder.setOutput(out, OutputMode.DexIndexed);
+ input = Paths.get(EXAMPLES_BUILD_DIR, test, "classes.dex");
+ } else {
+ builder.setOutput(out, OutputMode.ClassFile);
+ input = Paths.get(EXAMPLES_BUILD_DIR, test + ".jar");
+ }
+ ToolHelper.getAppBuilder(builder).addProgramFiles(input);
+ }
+
@Test
public void testIgnoreWarnings() throws Exception {
// Generate R8 processed version without library option.
Path out = temp.getRoot().toPath();
String test = "shaking2";
- Path originalDex = Paths.get(EXAMPLES_BUILD_DIR, test, "classes.dex");
Path keepRules = Paths.get(EXAMPLES_DIR, test, "keep-rules.txt");
Path ignoreWarnings = Paths.get(VALID_PROGUARD_DIR, "ignorewarnings.flags");
R8Command.Builder builder = R8Command.builder()
- .setOutput(out, OutputMode.DexIndexed)
.addProguardConfigurationFiles(keepRules, ignoreWarnings);
- ToolHelper.getAppBuilder(builder).addProgramFiles(originalDex);
+ finishBuild(builder, out, test);
R8.run(builder.build());
}
@@ -57,7 +90,6 @@
// Generate R8 processed version without library option.
Path out = temp.getRoot().toPath();
String test = "shaking2";
- Path originalDex = Paths.get(EXAMPLES_BUILD_DIR, test, "classes.dex");
Path keepRules = Paths.get(EXAMPLES_DIR, test, "keep-rules.txt");
DiagnosticsHandler handler = new DiagnosticsHandler() {
@Override
@@ -68,9 +100,8 @@
}
};
R8Command.Builder builder = R8Command.builder(handler)
- .setOutput(out, OutputMode.DexIndexed)
.addProguardConfigurationFiles(keepRules);
- ToolHelper.getAppBuilder(builder).addProgramFiles(originalDex);
+ finishBuild(builder, out, test);
R8.run(builder.build());
}
@@ -79,7 +110,6 @@
// Generate R8 processed version without library option.
String test = "shaking1";
Path out = temp.getRoot().toPath();
- Path originalDex = Paths.get(EXAMPLES_BUILD_DIR, test, "classes.dex");
Path keepRules = Paths.get(EXAMPLES_DIR, test, "keep-rules.txt");
// Create a flags file in temp dir requesting dump of the mapping.
@@ -90,10 +120,15 @@
}
R8Command.Builder builder = R8Command.builder()
- .setOutput(out, OutputMode.DexIndexed)
.addProguardConfigurationFiles(keepRules, printMapping);
- ToolHelper.getAppBuilder(builder).addProgramFiles(originalDex);
// Turn off inlining, as we want the mapping that is printed to be stable.
+ finishBuild(builder, out, test);
+ if (backend == Backend.DEX) {
+ builder.addLibraryFiles(ToolHelper.getDefaultAndroidJar());
+ } else {
+ assert backend == Backend.CF;
+ builder.addLibraryFiles(ToolHelper.getJava8RuntimeJar());
+ }
ToolHelper.runR8(builder.build(), options -> options.enableInlining = false);
Path outputmapping = out.resolve("mapping.txt");
@@ -102,8 +137,15 @@
Stream.of(new String(Files.readAllBytes(outputmapping), StandardCharsets.UTF_8).split("\n"))
.filter(line -> !line.startsWith("#"))
.collect(Collectors.joining("\n"));
- String refMapping = new String(Files.readAllBytes(
- Paths.get(EXAMPLES_DIR, "shaking1", "print-mapping.ref")), StandardCharsets.UTF_8);
+ // For the CF backend we treat ConstString/LDC as a (potentially always) throwing instruction,
+ // as opposed to the DEX backend where it's throwing only if its string is ill-formed.
+ // When ConstString is throwing we preserve its position which makes it show up in the
+ // the output Proguard map. That's why the reference CF map is different from the DEX one.
+ String mapping_ref = backend == Backend.CF ? "print-mapping-cf.ref" : "print-mapping.ref";
+ String refMapping =
+ new String(
+ Files.readAllBytes(Paths.get(EXAMPLES_DIR, "shaking1", mapping_ref)),
+ StandardCharsets.UTF_8);
Assert.assertEquals(sorted(refMapping), sorted(actualMapping));
}
diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
index b6a1ac6..9eb7815 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
@@ -14,13 +14,13 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.naming.MemberNaming.FieldSignature;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.FoundFieldSubject;
-import com.android.tools.r8.utils.DexInspector.FoundMethodSubject;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.TestDescriptionWatcher;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundFieldSubject;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
import com.google.common.collect.ImmutableList;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -137,7 +137,7 @@
});
}
- protected static void checkSameStructure(DexInspector ref, DexInspector inspector) {
+ protected static void checkSameStructure(CodeInspector ref, CodeInspector inspector) {
ref.forAllClasses(refClazz -> checkSameStructure(refClazz,
inspector.clazz(refClazz.getDexClass().toSourceString())));
}
@@ -167,18 +167,18 @@
}
protected void runTest(
- Consumer<DexInspector> inspection,
+ Consumer<CodeInspector> inspection,
BiConsumer<String, String> outputComparator,
- BiConsumer<DexInspector, DexInspector> dexComparator,
+ BiConsumer<CodeInspector, CodeInspector> dexComparator,
List<String> keepRulesFiles)
throws Exception {
runTest(inspection, outputComparator, dexComparator, keepRulesFiles, null);
}
protected void runTest(
- Consumer<DexInspector> inspection,
+ Consumer<CodeInspector> inspection,
BiConsumer<String, String> outputComparator,
- BiConsumer<DexInspector, DexInspector> dexComparator,
+ BiConsumer<CodeInspector, CodeInspector> dexComparator,
List<String> keepRulesFiles,
Consumer<InternalOptions> optionsConsumer)
throws Exception {
@@ -193,7 +193,7 @@
if (backend == Backend.CF) {
jarLibraries =
ImmutableList.of(
- Paths.get(ToolHelper.JAVA_8_RUNTIME),
+ ToolHelper.getJava8RuntimeJar(),
Paths.get(ToolHelper.EXAMPLES_BUILD_DIR + "shakinglib.jar"));
} else {
jarLibraries =
@@ -218,8 +218,8 @@
Assert.assertEquals(resultInput.toString(), resultOutput.toString());
}
if (inspection != null) {
- DexInspector inspector =
- new DexInspector(
+ CodeInspector inspector =
+ new CodeInspector(
out,
minify.isMinify()
? proguardMap.toString()
@@ -249,8 +249,8 @@
}
if (dexComparator != null) {
- DexInspector ref = new DexInspector(Paths.get(originalDex));
- DexInspector inspector = new DexInspector(out,
+ CodeInspector ref = new CodeInspector(Paths.get(originalDex));
+ CodeInspector inspector = new CodeInspector(out,
minify.isMinify() ? proguardMap.toString()
: null);
dexComparator.accept(ref, inspector);
@@ -263,7 +263,7 @@
}
if (inspection != null) {
- DexInspector inspector = new DexInspector(out,
+ CodeInspector inspector = new CodeInspector(out,
minify.isMinify() ? proguardMap.toString()
: null);
inspection.accept(inspector);
diff --git a/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java b/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java
index 46feb00..d753632 100644
--- a/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java
@@ -7,6 +7,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import com.android.tools.r8.ClassFileConsumer;
import com.android.tools.r8.DexIndexedConsumer;
import com.android.tools.r8.R8Command;
import com.android.tools.r8.TestBase;
@@ -15,22 +16,47 @@
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.List;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+@RunWith(Parameterized.class)
public class DefaultMethodsTest extends TestBase {
- private void runTest(List<String> additionalKeepRules, Consumer<DexInspector> inspection)
+
+ private Backend backend;
+
+ @Parameterized.Parameters(name = "Backend: {0}")
+ public static Collection<Backend> data() {
+ return Arrays.asList(Backend.values());
+ }
+
+ public DefaultMethodsTest(Backend backend) {
+ this.backend = backend;
+ }
+
+ private void runTest(List<String> additionalKeepRules, Consumer<CodeInspector> inspection)
throws Exception {
R8Command.Builder builder = R8Command.builder();
builder.addProgramFiles(ToolHelper.getClassFileForTestClass(InterfaceWithDefaultMethods.class));
builder.addProgramFiles(ToolHelper.getClassFileForTestClass(ClassImplementingInterface.class));
builder.addProgramFiles(ToolHelper.getClassFileForTestClass(TestClass.class));
- builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
- builder.setMinApiLevel(AndroidApiLevel.O.getLevel());
+ if (backend == Backend.DEX) {
+ builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+ int apiLevel = AndroidApiLevel.O.getLevel();
+ builder.setMinApiLevel(apiLevel);
+ builder.addLibraryFiles(ToolHelper.getAndroidJar(apiLevel));
+ } else {
+ assert backend == Backend.CF;
+ builder.setProgramConsumer(ClassFileConsumer.emptyConsumer());
+ builder.addLibraryFiles(ToolHelper.getJava8RuntimeJar());
+ }
// Always keep main in the test class, so the output never becomes empty.
builder.addProguardConfiguration(ImmutableList.of(
"-keep class " + TestClass.class.getCanonicalName() + "{",
@@ -40,20 +66,20 @@
Origin.unknown());
builder.addProguardConfiguration(additionalKeepRules, Origin.unknown());
AndroidApp app = ToolHelper.runR8(builder.build(), o -> o.enableClassInlining = false);
- inspection.accept(new DexInspector(app));
+ inspection.accept(new CodeInspector(app));
}
- private void interfaceNotKept(DexInspector inspector) {
+ private void interfaceNotKept(CodeInspector inspector) {
assertFalse(inspector.clazz(InterfaceWithDefaultMethods.class).isPresent());
}
- private void defaultMethodNotKept(DexInspector inspector) {
+ private void defaultMethodNotKept(CodeInspector inspector) {
ClassSubject clazz = inspector.clazz(InterfaceWithDefaultMethods.class);
assertTrue(clazz.isPresent());
assertFalse(clazz.method("int", "method", ImmutableList.of()).isPresent());
}
- private void defaultMethodKept(DexInspector inspector) {
+ private void defaultMethodKept(CodeInspector inspector) {
ClassSubject clazz = inspector.clazz(InterfaceWithDefaultMethods.class);
assertTrue(clazz.isPresent());
MethodSubject method = clazz.method("int", "method", ImmutableList.of());
@@ -61,7 +87,7 @@
assertFalse(method.isAbstract());
}
- private void defaultMethodAbstract(DexInspector inspector) {
+ private void defaultMethodAbstract(CodeInspector inspector) {
ClassSubject clazz = inspector.clazz(InterfaceWithDefaultMethods.class);
assertTrue(clazz.isPresent());
MethodSubject method = clazz.method("int", "method", ImmutableList.of());
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
index 7fcb318..ba0d468 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/InliningClassVersionTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/InliningClassVersionTest.java
@@ -17,7 +17,6 @@
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;
@@ -147,7 +146,7 @@
ToolHelper.runR8(
R8Command.builder()
.addProgramFiles(inputJar)
- .addLibraryFiles(Paths.get(ToolHelper.JAVA_8_RUNTIME))
+ .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
.addProguardConfiguration(keepRule, Origin.unknown())
.setOutput(outputJar, OutputMode.ClassFile)
.build());
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking11Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking11Test.java
index 54bec20..f65c1d4 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking11Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking11Test.java
@@ -5,7 +5,7 @@
import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
-import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collection;
@@ -53,24 +53,24 @@
ImmutableList.of("src/test/examples/shaking11/keep-rules-keep-method.txt"));
}
- private static void shaking11OnlyOneClassKept(DexInspector dexInspector) {
- Assert.assertFalse(dexInspector.clazz("shaking11.Subclass").isPresent());
- Assert.assertTrue(dexInspector.clazz("shaking11.SubclassWithMethod").isPresent());
+ private static void shaking11OnlyOneClassKept(CodeInspector codeInspector) {
+ Assert.assertFalse(codeInspector.clazz("shaking11.Subclass").isPresent());
+ Assert.assertTrue(codeInspector.clazz("shaking11.SubclassWithMethod").isPresent());
}
- private static void shaking11BothMethodsKept(DexInspector dexInspector) {
+ private static void shaking11BothMethodsKept(CodeInspector codeInspector) {
Assert.assertFalse(
- dexInspector
+ codeInspector
.clazz("shaking11.Subclass")
.method("void", "aMethod", Collections.emptyList())
.isPresent());
Assert.assertTrue(
- dexInspector
+ codeInspector
.clazz("shaking11.SuperClass")
.method("void", "aMethod", Collections.emptyList())
.isPresent());
Assert.assertTrue(
- dexInspector
+ codeInspector
.clazz("shaking11.SubclassWithMethod")
.method("void", "aMethod", Collections.emptyList())
.isPresent());
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking12Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking12Test.java
index 7bbde47..710635d 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking12Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking12Test.java
@@ -5,8 +5,8 @@
import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collection;
@@ -54,7 +54,7 @@
ImmutableList.of("src/test/examples/shaking12/keep-rules-printusage.txt"));
}
- private static void shaking12OnlyInstantiatedClassesHaveConstructors(DexInspector inspector) {
+ private static void shaking12OnlyInstantiatedClassesHaveConstructors(CodeInspector inspector) {
ClassSubject animalClass = inspector.clazz("shaking12.AnimalClass");
Assert.assertTrue(animalClass.isPresent());
Assert.assertFalse(animalClass.method("void", "<init>", Collections.emptyList()).isPresent());
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking13Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking13Test.java
index 4729897..2157cd3 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking13Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking13Test.java
@@ -5,11 +5,11 @@
import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.FieldAccessInstructionSubject;
-import com.android.tools.r8.utils.DexInspector.InstructionSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldAccessInstructionSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collection;
@@ -49,7 +49,7 @@
ImmutableList.of("src/test/examples/shaking13/keep-rules.txt"));
}
- private static void shaking13EnsureFieldWritesCorrect(DexInspector inspector) {
+ private static void shaking13EnsureFieldWritesCorrect(CodeInspector inspector) {
ClassSubject mainClass = inspector.clazz("shaking13.Shaking");
MethodSubject testMethod = mainClass.method("void", "fieldTest", Collections.emptyList());
Assert.assertTrue(testMethod.isPresent());
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking14Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking14Test.java
index 6891708..820afc2 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking14Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking14Test.java
@@ -5,8 +5,8 @@
import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collection;
@@ -44,7 +44,7 @@
ImmutableList.of("src/test/examples/shaking14/keep-rules.txt"));
}
- private static void shaking14EnsureRightStaticMethodsLive(DexInspector inspector) {
+ private static void shaking14EnsureRightStaticMethodsLive(CodeInspector inspector) {
ClassSubject superclass = inspector.clazz("shaking14.Superclass");
Assert.assertFalse(superclass.method("int", "aMethod", ImmutableList.of("int")).isPresent());
Assert.assertFalse(
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking15Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking15Test.java
index 98a7a28..1dd353e 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking15Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking15Test.java
@@ -5,10 +5,10 @@
import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.FieldSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collection;
@@ -48,7 +48,7 @@
ImmutableList.of("src/test/examples/shaking15/keep-rules.txt"));
}
- private static void shaking15testDictionary(DexInspector inspector) {
+ private static void shaking15testDictionary(CodeInspector inspector) {
inspector.forAllClasses((clazz) -> checkClassAndMemberInDictionary(clazz));
}
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking17Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking17Test.java
index be0f529..fac36d6 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking17Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking17Test.java
@@ -5,8 +5,8 @@
import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collection;
@@ -45,7 +45,7 @@
ImmutableList.of("src/test/examples/shaking17/keep-rules.txt"));
}
- private static void abstractMethodRemains(DexInspector inspector) {
+ private static void abstractMethodRemains(CodeInspector inspector) {
ClassSubject programClass = inspector.clazz("shaking17.AbstractProgramClass");
Assert.assertTrue(programClass.isPresent());
Assert.assertTrue(
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking18Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking18Test.java
index 97d9bfc..b691046 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking18Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking18Test.java
@@ -5,7 +5,7 @@
import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
-import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collection;
@@ -43,7 +43,7 @@
ImmutableList.of("src/test/examples/shaking18/keep-rules.txt"));
}
- private static void unusedRemoved(DexInspector inspector) {
+ private static void unusedRemoved(CodeInspector inspector) {
// TODO(b/80455722): Change to assertFalse when tree-shaking detects this case.
Assert.assertTrue(
"DerivedUnused should be removed", inspector.clazz("shaking18.DerivedUnused").isPresent());
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking19Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking19Test.java
index e5be477..f30bcac 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking19Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking19Test.java
@@ -3,15 +3,15 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking.examples;
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertThat;
import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collection;
@@ -52,7 +52,7 @@
opt -> opt.enableClassMerging = false);
}
- private static void unusedRemoved(DexInspector inspector) {
+ private static void unusedRemoved(CodeInspector inspector) {
ClassSubject clazz = inspector.clazz("shaking19.Shaking$A");
assertThat(clazz, isPresent());
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking1Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking1Test.java
index ad2aea7..17cfb7f 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking1Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking1Test.java
@@ -6,8 +6,8 @@
import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collection;
@@ -165,7 +165,7 @@
"src/test/proguard/valid/empty.flags"));
}
- private static void shaking1IsCorrectlyRepackaged(DexInspector inspector) {
+ private static void shaking1IsCorrectlyRepackaged(CodeInspector inspector) {
inspector.forAllClasses(
clazz -> {
String descriptor = clazz.getFinalDescriptor();
@@ -176,7 +176,7 @@
});
}
- private static void shaking1HasNoClassUnused(DexInspector inspector) {
+ private static void shaking1HasNoClassUnused(CodeInspector inspector) {
Assert.assertFalse(inspector.clazz("shaking1.Unused").isPresent());
ClassSubject used = inspector.clazz("shaking1.Used");
Assert.assertTrue(used.isPresent());
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking2Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking2Test.java
index 39dd4ab..5f87781 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking2Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking2Test.java
@@ -5,8 +5,8 @@
import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collection;
@@ -60,7 +60,7 @@
null, null, null, ImmutableList.of("src/test/examples/shaking2/keep-rules-printusage.txt"));
}
- private static void shaking2SuperClassIsAbstract(DexInspector inspector) {
+ private static void shaking2SuperClassIsAbstract(CodeInspector inspector) {
ClassSubject clazz = inspector.clazz("shaking2.SuperClass");
Assert.assertTrue(clazz.isAbstract());
Assert.assertTrue(clazz.method("void", "virtualMethod", Collections.emptyList()).isAbstract());
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking3Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking3Test.java
index 0aa431a..6ed0099 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking3Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking3Test.java
@@ -5,8 +5,8 @@
import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collection;
@@ -89,12 +89,12 @@
ImmutableList.of("src/test/examples/shaking3/keep-no-abstract-classes.txt"));
}
- private static void shaking3HasNoPrivateClass(DexInspector inspector) {
+ private static void shaking3HasNoPrivateClass(CodeInspector inspector) {
Assert.assertTrue(inspector.clazz("shaking3.B").isPresent());
Assert.assertFalse(inspector.clazz("shaking3.AnAbstractClass").isPresent());
}
- private static void shaking3HasNoClassB(DexInspector inspector) {
+ private static void shaking3HasNoClassB(CodeInspector inspector) {
Assert.assertFalse(inspector.clazz("shaking3.B").isPresent());
ClassSubject classA = inspector.clazz("shaking3.A");
Assert.assertTrue(classA.isPresent());
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking5Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking5Test.java
index f7b0b47..7484255 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking5Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking5Test.java
@@ -5,7 +5,7 @@
import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
-import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collection;
@@ -44,7 +44,7 @@
ImmutableList.of("src/test/examples/shaking5/keep-rules.txt"));
}
- private static void shaking5Inspection(DexInspector inspector) {
+ private static void shaking5Inspection(CodeInspector inspector) {
Assert.assertFalse(
inspector
.clazz("shaking5.Superclass")
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking6Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking6Test.java
index f93ccd2..f5733cc 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking6Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking6Test.java
@@ -5,8 +5,8 @@
import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collection;
@@ -72,7 +72,7 @@
ImmutableList.of("src/test/examples/shaking6/keep-public.txt"));
}
- private static void hasNoPublicMethodsButPrivate(DexInspector inspector) {
+ private static void hasNoPublicMethodsButPrivate(CodeInspector inspector) {
inspector.forAllClasses(
clazz ->
clazz.forAllMethods(
@@ -88,7 +88,7 @@
.isPresent());
}
- private static void hasOnlyIntJustAMethod(DexInspector inspector) {
+ private static void hasOnlyIntJustAMethod(CodeInspector inspector) {
Assert.assertFalse(
inspector
.clazz("shaking6.Superclass")
@@ -105,7 +105,7 @@
subclass.method("int", "justAMethod", Collections.singletonList("double")).isPresent());
}
- private static void hasNoPrivateJustAMethod(DexInspector inspector) {
+ private static void hasNoPrivateJustAMethod(CodeInspector inspector) {
Assert.assertFalse(
inspector
.clazz("shaking6.Superclass")
@@ -122,7 +122,7 @@
subclass.method("int", "justAMethod", Collections.singletonList("double")).isPresent());
}
- private static void hasNoPrivateMethods(DexInspector inspector) {
+ private static void hasNoPrivateMethods(CodeInspector inspector) {
inspector.forAllClasses(
clazz ->
clazz.forAllMethods(
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking7Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking7Test.java
index 7762040..9d2faf3 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking7Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking7Test.java
@@ -5,8 +5,8 @@
import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collection;
@@ -71,7 +71,7 @@
ImmutableList.of("src/test/examples/shaking7/keep-public-theIntField-fields.txt"));
}
- private static void shaking7HasOnlyDoubleFields(DexInspector inspector) {
+ private static void shaking7HasOnlyDoubleFields(CodeInspector inspector) {
inspector.forAllClasses(
clazz -> {
clazz.forAllFields(
@@ -86,7 +86,7 @@
Assert.assertFalse(inspector.clazz("shaking7.Liar").field("int", "theDoubleField").isPresent());
}
- private static void shaking7HasOnlyPublicFieldsNamedTheIntField(DexInspector inspector) {
+ private static void shaking7HasOnlyPublicFieldsNamedTheIntField(CodeInspector inspector) {
inspector.forAllClasses(
clazz -> {
clazz.forAllFields(
@@ -104,7 +104,7 @@
Assert.assertTrue(liar.field("double", "theIntField").isPresent());
}
- private static void shaking7HasOnlyPublicFields(DexInspector inspector) {
+ private static void shaking7HasOnlyPublicFields(CodeInspector inspector) {
inspector.forAllClasses(
clazz -> {
clazz.forAllFields(
@@ -120,7 +120,7 @@
Assert.assertTrue(inspector.clazz("shaking7.Liar").field("int", "theDoubleField").isPresent());
}
- private static void shaking7HasOnlyPublicFieldsNamedTheDoubleField(DexInspector inspector) {
+ private static void shaking7HasOnlyPublicFieldsNamedTheDoubleField(CodeInspector inspector) {
inspector.forAllClasses(
clazz -> {
clazz.forAllFields(
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking8Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking8Test.java
index afa3003..b957c64 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking8Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking8Test.java
@@ -5,8 +5,8 @@
import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collection;
@@ -50,7 +50,7 @@
null, null, null, ImmutableList.of("src/test/examples/shaking8/keep-rules-printusage.txt"));
}
- private static void shaking8ThingClassIsAbstractAndEmpty(DexInspector inspector) {
+ private static void shaking8ThingClassIsAbstractAndEmpty(CodeInspector inspector) {
ClassSubject clazz = inspector.clazz("shaking8.Thing");
Assert.assertTrue(clazz.isAbstract());
clazz.forAllMethods((method) -> Assert.fail());
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking9Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking9Test.java
index e3c8b8a..e64abe1 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking9Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking9Test.java
@@ -5,8 +5,8 @@
import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collection;
@@ -50,7 +50,7 @@
null, null, null, ImmutableList.of("src/test/examples/shaking9/keep-rules-printusage.txt"));
}
- private static void shaking9OnlySuperMethodsKept(DexInspector inspector) {
+ private static void shaking9OnlySuperMethodsKept(CodeInspector inspector) {
ClassSubject superclass = inspector.clazz("shaking9.Superclass");
Assert.assertTrue(superclass.isAbstract());
Assert.assertTrue(superclass.method("void", "aMethod", ImmutableList.of()).isPresent());
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAnnotationremovalTest.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAnnotationremovalTest.java
index a235df5..d7d45f0 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAnnotationremovalTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAnnotationremovalTest.java
@@ -5,8 +5,8 @@
import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collection;
@@ -68,7 +68,7 @@
});
}
- private static void annotationRemovalHasNoInnerClassAnnotations(DexInspector inspector) {
+ private static void annotationRemovalHasNoInnerClassAnnotations(CodeInspector inspector) {
ClassSubject outer = inspector.clazz("annotationremoval.OuterClass");
Assert.assertTrue(outer.isPresent());
Assert.assertTrue(outer.getDexClass().getInnerClasses().isEmpty());
@@ -86,7 +86,7 @@
Assert.assertTrue(local.getDexClass().getInnerClasses().isEmpty());
}
- private static void annotationRemovalHasAllInnerClassAnnotations(DexInspector inspector) {
+ private static void annotationRemovalHasAllInnerClassAnnotations(CodeInspector inspector) {
ClassSubject outer = inspector.clazz("annotationremoval.OuterClass");
Assert.assertTrue(outer.isPresent());
Assert.assertFalse(outer.getDexClass().getInnerClasses().isEmpty());
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues6Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues6Test.java
new file mode 100644
index 0000000..e093f00
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues6Test.java
@@ -0,0 +1,69 @@
+// 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 com.android.tools.r8.TestBase.MinifyMode;
+import com.android.tools.r8.shaking.TreeShakingTest;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.ConstStringInstructionSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject.JumboStringMode;
+import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class TreeShakingAssumevalues6Test extends TreeShakingTest {
+
+ @Parameters(name = "mode:{0}-{1} minify:{2}")
+ public static Collection<Object[]> data() {
+ List<Object[]> parameters = new ArrayList<>();
+ for (MinifyMode minify : MinifyMode.values()) {
+ parameters.add(new Object[] {Frontend.JAR, Backend.CF, minify});
+ parameters.add(new Object[] {Frontend.JAR, Backend.DEX, minify});
+ parameters.add(new Object[] {Frontend.DEX, Backend.DEX, minify});
+ }
+ return parameters;
+ }
+
+ public TreeShakingAssumevalues6Test(Frontend frontend, Backend backend, MinifyMode minify) {
+ super("examples/assumevalues6", "assumevalues6.Assumevalues", frontend, backend, minify);
+ }
+
+ @Test
+ public void test() throws Exception {
+ runTest(
+ getBackend() == Backend.DEX ? TreeShakingAssumevalues6Test::assumevalues6CheckCode : null,
+ TreeShakingAssumevalues6Test::assumevalues6CheckOutput,
+ null,
+ ImmutableList.of("src/test/examples/assumevalues6/keep-rules.txt"));
+ }
+
+ private static void assumevalues6CheckCode(CodeInspector inspector) {
+ inspector.forAllClasses(c -> {
+ c.forAllMethods(m -> {
+ if (m.getFinalName().equals("main")) {
+ m.iterateInstructions().forEachRemaining(i -> {
+ if (i.isConstString(JumboStringMode.ALLOW)) {
+ ConstStringInstructionSubject str = (ConstStringInstructionSubject) i;
+ assert !str.getString().toASCIIString().contains("NOPE");
+ }
+ });
+ }
+ });
+ });
+ }
+
+ private static void assumevalues6CheckOutput(String output1, String output2) {
+ String expected = StringUtils.lines("YUP1", "YUP2", "YUP3", "OK");
+ Assert.assertEquals(expected, output1);
+ Assert.assertEquals(expected, output2);
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingEnumprotoTest.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingEnumprotoTest.java
index 7570990..7f211db 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingEnumprotoTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingEnumprotoTest.java
@@ -5,8 +5,8 @@
import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collection;
@@ -44,7 +44,7 @@
ImmutableList.of("src/test/examples/enumproto/keep-rules.txt"));
}
- private static void enumprotoUnusedFieldsAreGone(DexInspector inspector) {
+ private static void enumprotoUnusedFieldsAreGone(CodeInspector inspector) {
ClassSubject protoClass = inspector.clazz("enumproto.GeneratedEnumProto$Enum");
Assert.assertTrue(protoClass.isPresent());
Assert.assertFalse(protoClass.field("int", "id_").isPresent());
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingNestedproto1Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingNestedproto1Test.java
index 2ff5dfc..16b5f59 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingNestedproto1Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingNestedproto1Test.java
@@ -5,8 +5,8 @@
import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collection;
@@ -44,7 +44,7 @@
ImmutableList.of("src/test/examples/nestedproto1/keep-rules.txt"));
}
- private static void nestedproto1UnusedFieldsAreGone(DexInspector inspector) {
+ private static void nestedproto1UnusedFieldsAreGone(CodeInspector inspector) {
ClassSubject protoClass = inspector.clazz("nestedproto1.GeneratedNestedProto$Outer");
Assert.assertTrue(protoClass.isPresent());
Assert.assertFalse(protoClass.field("int", "id_").isPresent());
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingNestedproto2Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingNestedproto2Test.java
index cb61962..a679810 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingNestedproto2Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingNestedproto2Test.java
@@ -5,8 +5,8 @@
import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collection;
@@ -44,7 +44,7 @@
ImmutableList.of("src/test/examples/nestedproto2/keep-rules.txt"));
}
- private static void nestedproto2UnusedFieldsAreGone(DexInspector inspector) {
+ private static void nestedproto2UnusedFieldsAreGone(CodeInspector inspector) {
ClassSubject protoClass = inspector.clazz("nestedproto2.GeneratedNestedProto$Outer");
Assert.assertTrue(protoClass.isPresent());
Assert.assertTrue(protoClass.field("int", "id_").isPresent());
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingOneofprotoTest.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingOneofprotoTest.java
index 44f5f89..e38258c 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingOneofprotoTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingOneofprotoTest.java
@@ -5,8 +5,8 @@
import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collection;
@@ -44,7 +44,7 @@
ImmutableList.of("src/test/examples/oneofproto/keep-rules.txt"));
}
- private static void oneofprotoUnusedFieldsAreGone(DexInspector inspector) {
+ private static void oneofprotoUnusedFieldsAreGone(CodeInspector inspector) {
ClassSubject protoClass = inspector.clazz("oneofproto.GeneratedOneOfProto$Oneof");
Assert.assertTrue(protoClass.isPresent());
Assert.assertFalse(protoClass.field("int", "id_").isPresent());
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingRepeatedprotoTest.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingRepeatedprotoTest.java
index bd27c7f..923cf34 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingRepeatedprotoTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingRepeatedprotoTest.java
@@ -5,8 +5,8 @@
import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collection;
@@ -44,7 +44,7 @@
ImmutableList.of("src/test/examples/repeatedproto/keep-rules.txt"));
}
- private static void repeatedUnusedFieldsAreGone(DexInspector inspector) {
+ private static void repeatedUnusedFieldsAreGone(CodeInspector inspector) {
ClassSubject protoClass = inspector.clazz("repeatedproto.GeneratedRepeatedProto$Repeated");
Assert.assertTrue(protoClass.isPresent());
Assert.assertFalse(protoClass.field("int", "id_").isPresent());
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingSimpleproto1Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingSimpleproto1Test.java
index 8c3f4a4..64e3839 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingSimpleproto1Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingSimpleproto1Test.java
@@ -5,8 +5,8 @@
import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collection;
@@ -44,7 +44,7 @@
ImmutableList.of("src/test/examples/simpleproto1/keep-rules.txt"));
}
- private static void simpleproto1UnusedFieldIsGone(DexInspector inspector) {
+ private static void simpleproto1UnusedFieldIsGone(CodeInspector inspector) {
ClassSubject protoClass = inspector.clazz("simpleproto1.GeneratedSimpleProto$Simple");
Assert.assertTrue(protoClass.isPresent());
Assert.assertFalse(protoClass.field("boolean", "other_").isPresent());
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingSimpleproto2Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingSimpleproto2Test.java
index 30a9131..5304dae 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingSimpleproto2Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingSimpleproto2Test.java
@@ -5,8 +5,8 @@
import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collection;
@@ -44,7 +44,7 @@
ImmutableList.of("src/test/examples/simpleproto2/keep-rules.txt"));
}
- private static void simpleproto2UnusedFieldsAreGone(DexInspector inspector) {
+ private static void simpleproto2UnusedFieldsAreGone(CodeInspector inspector) {
ClassSubject protoClass = inspector.clazz("simpleproto2.GeneratedSimpleProto$Simple");
Assert.assertTrue(protoClass.isPresent());
Assert.assertFalse(protoClass.field("int", "id_").isPresent());
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
index 642045c..9cf0a85 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
@@ -23,7 +23,6 @@
import com.android.tools.r8.shaking.ProguardConfiguration;
import com.android.tools.r8.shaking.ProguardConfigurationParser;
import com.android.tools.r8.shaking.ProguardConfigurationRule;
-import com.android.tools.r8.shaking.ProguardIdentifierNameStringRule;
import com.android.tools.r8.shaking.ProguardKeepAttributes;
import com.android.tools.r8.shaking.ProguardKeepRule;
import com.android.tools.r8.shaking.ProguardKeepRuleType;
@@ -36,12 +35,12 @@
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DefaultDiagnosticsHandler;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.FieldSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import it.unimi.dsi.fastutil.objects.Object2BooleanArrayMap;
@@ -56,7 +55,7 @@
private void test(Class mainClass, Class mentionedClass, boolean forceProguardCompatibility)
throws Exception {
String proguardConfig = keepMainProguardConfiguration(mainClass, true, false);
- DexInspector inspector = new DexInspector(
+ CodeInspector inspector = new CodeInspector(
compileWithR8(
ImmutableList.of(mainClass, mentionedClass),
proguardConfig,
@@ -103,7 +102,7 @@
}
builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
- DexInspector inspector = new DexInspector(ToolHelper.runR8(builder.build()));
+ CodeInspector inspector = new CodeInspector(ToolHelper.runR8(builder.build()));
assertTrue(inspector.clazz(mainClass.getCanonicalName()).isPresent());
ClassSubject clazz = inspector.clazz(getJavacGeneratedClassName(mentionedClassWithAnnotations));
assertTrue(clazz.isPresent());
@@ -138,7 +137,7 @@
builder.setProguardCompatibilityRulesOutput(proguardCompatibilityRules);
builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
- DexInspector inspector = new DexInspector(ToolHelper.runR8(builder.build()));
+ CodeInspector inspector = new CodeInspector(ToolHelper.runR8(builder.build()));
ClassSubject clazz = inspector.clazz(getJavacGeneratedClassName(testClass));
assertTrue(clazz.isPresent());
assertEquals(forceProguardCompatibility && hasDefaultConstructor,
@@ -195,7 +194,7 @@
builder.addProguardConfiguration(proguardConfig, Origin.unknown());
builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
- DexInspector inspector = new DexInspector(ToolHelper.runR8(builder.build()));
+ CodeInspector inspector = new CodeInspector(ToolHelper.runR8(builder.build()));
assertTrue(inspector.clazz(getJavacGeneratedClassName(mainClass)).isPresent());
ClassSubject clazz = inspector.clazz(getJavacGeneratedClassName(instantiatedClass));
assertEquals(containsCheckCast, clazz.isPresent());
@@ -210,7 +209,7 @@
FileUtils.writeTextFile(proguardConfigFile, proguardConfig);
ToolHelper.runProguard(jarTestClasses(ImmutableList.of(mainClass, instantiatedClass)),
proguardedJar, proguardConfigFile, null);
- DexInspector proguardInspector = new DexInspector(readJar(proguardedJar));
+ CodeInspector proguardInspector = new CodeInspector(readJar(proguardedJar));
assertTrue(proguardInspector.clazz(mainClass).isPresent());
assertEquals(
containsCheckCast, proguardInspector.clazz(instantiatedClass).isPresent());
@@ -257,11 +256,11 @@
}
builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
- DexInspector inspector = new DexInspector(ToolHelper.runR8(builder.build()));
+ CodeInspector inspector = new CodeInspector(ToolHelper.runR8(builder.build()));
assertTrue(inspector.clazz(getJavacGeneratedClassName(mainClass)).isPresent());
forNameClasses.forEach(clazz -> {
ClassSubject subject = inspector.clazz(getJavacGeneratedClassName(clazz));
- assertEquals(forceProguardCompatibility, subject.isPresent());
+ assertTrue(subject.isPresent());
assertEquals(subject.isPresent() && allowObfuscation, subject.isRenamed());
});
@@ -273,8 +272,10 @@
ProguardConfiguration configuration = parser.getConfigRawForTesting();
if (forceProguardCompatibility) {
List<ProguardConfigurationRule> rules = configuration.getRules();
- assertEquals(3, rules.size());
- Iterables.filter(rules, ProguardKeepRule.class).forEach(rule -> {
+ assertEquals(2, rules.size());
+ for (ProguardConfigurationRule r : rules) {
+ assertTrue(r instanceof ProguardKeepRule);
+ ProguardKeepRule rule = (ProguardKeepRule) r;
assertEquals(ProguardKeepRuleType.KEEP, rule.getType());
assertTrue(rule.getModifiers().allowsObfuscation);
assertTrue(rule.getModifiers().allowsOptimization);
@@ -292,18 +293,7 @@
assertEquals(1, memberRules.size());
assertEquals(ProguardMemberType.INIT, memberRules.iterator().next().getRuleType());
}
- });
- Iterables.filter(rules, ProguardIdentifierNameStringRule.class).forEach(rule -> {
- List<ProguardMemberRule> memberRules = rule.getMemberRules();
- ProguardClassNameList classNames = rule.getClassNames();
- assertEquals(1, classNames.size());
- DexType type = classNames.asSpecificDexTypes().get(0);
- assertEquals("java.lang.Class", type.toSourceString());
- assertEquals(1, memberRules.size());
- ProguardMemberRule memberRule = memberRules.iterator().next();
- assertEquals(ProguardMemberType.METHOD, memberRule.getRuleType());
- assertEquals("forName", memberRule.getName().toString());
- });
+ }
} else {
assertEquals(0, configuration.getRules().size());
}
@@ -316,7 +306,7 @@
ToolHelper.runProguard(jarTestClasses(
ImmutableList.of(mainClass, forNameClass1, forNameClass2)),
proguardedJar, proguardConfigFile, proguardMapFile);
- DexInspector proguardedInspector = new DexInspector(readJar(proguardedJar), proguardMapFile);
+ CodeInspector proguardedInspector = new CodeInspector(readJar(proguardedJar), proguardMapFile);
assertEquals(3, proguardedInspector.allClasses().size());
assertTrue(proguardedInspector.clazz(mainClass).isPresent());
for (Class clazz : ImmutableList.of(forNameClass1, forNameClass2)) {
@@ -360,18 +350,18 @@
}
builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
- DexInspector inspector = new DexInspector(ToolHelper.runR8(builder.build()));
+ CodeInspector inspector = new CodeInspector(ToolHelper.runR8(builder.build()));
assertTrue(inspector.clazz(getJavacGeneratedClassName(mainClass)).isPresent());
ClassSubject classSubject = inspector.clazz(getJavacGeneratedClassName(withMemberClass));
// Due to the direct usage of .class
assertTrue(classSubject.isPresent());
assertEquals(allowObfuscation, classSubject.isRenamed());
FieldSubject foo = classSubject.field("java.lang.String", "foo");
- assertEquals(forceProguardCompatibility, foo.isPresent());
+ assertTrue(foo.isPresent());
assertEquals(foo.isPresent() && allowObfuscation, foo.isRenamed());
MethodSubject bar =
classSubject.method("java.lang.String", "bar", ImmutableList.of("java.lang.String"));
- assertEquals(forceProguardCompatibility, bar.isPresent());
+ assertTrue(bar.isPresent());
assertEquals(bar.isPresent() && allowObfuscation, bar.isRenamed());
// Check the Proguard compatibility rules generated.
@@ -382,8 +372,10 @@
ProguardConfiguration configuration = parser.getConfigRawForTesting();
if (forceProguardCompatibility) {
List<ProguardConfigurationRule> rules = configuration.getRules();
- assertEquals(4, rules.size());
- Iterables.filter(rules, ProguardKeepRule.class).forEach(rule -> {
+ assertEquals(2, rules.size());
+ for (ProguardConfigurationRule r : rules) {
+ assertTrue(r instanceof ProguardKeepRule);
+ ProguardKeepRule rule = (ProguardKeepRule) r;
assertEquals(ProguardKeepRuleType.KEEP_CLASS_MEMBERS, rule.getType());
assertTrue(rule.getModifiers().allowsObfuscation);
assertTrue(rule.getModifiers().allowsOptimization);
@@ -400,18 +392,7 @@
assertEquals(ProguardMemberType.METHOD, memberRule.getRuleType());
assertTrue(memberRule.getName().matches("bar"));
}
- });
- Iterables.filter(rules, ProguardIdentifierNameStringRule.class).forEach(rule -> {
- List<ProguardMemberRule> memberRules = rule.getMemberRules();
- ProguardClassNameList classNames = rule.getClassNames();
- assertEquals(1, classNames.size());
- DexType type = classNames.asSpecificDexTypes().get(0);
- assertEquals("java.lang.Class", type.toSourceString());
- assertEquals(1, memberRules.size());
- ProguardMemberRule memberRule = memberRules.iterator().next();
- assertEquals(ProguardMemberType.METHOD, memberRule.getRuleType());
- assertTrue(memberRule.getName().toString().startsWith("getDeclared"));
- });
+ }
} else {
assertEquals(0, configuration.getRules().size());
}
@@ -424,7 +405,7 @@
ToolHelper.runProguard(jarTestClasses(
ImmutableList.of(mainClass, withMemberClass)),
proguardedJar, proguardConfigFile, proguardMapFile);
- DexInspector proguardedInspector = new DexInspector(readJar(proguardedJar), proguardMapFile);
+ CodeInspector proguardedInspector = new CodeInspector(readJar(proguardedJar), proguardMapFile);
assertEquals(2, proguardedInspector.allClasses().size());
assertTrue(proguardedInspector.clazz(mainClass).isPresent());
classSubject = proguardedInspector.clazz(withMemberClass);
@@ -473,20 +454,20 @@
}
builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
- DexInspector inspector = new DexInspector(ToolHelper.runR8(builder.build()));
+ CodeInspector inspector = new CodeInspector(ToolHelper.runR8(builder.build()));
assertTrue(inspector.clazz(getJavacGeneratedClassName(mainClass)).isPresent());
ClassSubject classSubject = inspector.clazz(getJavacGeneratedClassName(withVolatileFields));
// Due to the direct usage of .class
assertTrue(classSubject.isPresent());
assertEquals(allowObfuscation, classSubject.isRenamed());
FieldSubject f = classSubject.field("int", "intField");
- assertEquals(forceProguardCompatibility, f.isPresent());
+ assertTrue(f.isPresent());
assertEquals(f.isPresent() && allowObfuscation, f.isRenamed());
f = classSubject.field("long", "longField");
- assertEquals(forceProguardCompatibility, f.isPresent());
+ assertTrue(f.isPresent());
assertEquals(f.isPresent() && allowObfuscation, f.isRenamed());
f = classSubject.field("java.lang.Object", "objField");
- assertEquals(forceProguardCompatibility, f.isPresent());
+ assertTrue(f.isPresent());
assertEquals(f.isPresent() && allowObfuscation, f.isRenamed());
// Check the Proguard compatibility rules generated.
@@ -497,9 +478,11 @@
ProguardConfiguration configuration = parser.getConfigRawForTesting();
if (forceProguardCompatibility) {
List<ProguardConfigurationRule> rules = configuration.getRules();
- assertEquals(6, rules.size());
+ assertEquals(3, rules.size());
Object2BooleanMap<String> keptFields = new Object2BooleanArrayMap<>();
- Iterables.filter(rules, ProguardKeepRule.class).forEach(rule -> {
+ for (ProguardConfigurationRule r : rules) {
+ assertTrue(r instanceof ProguardKeepRule);
+ ProguardKeepRule rule = (ProguardKeepRule) r;
assertEquals(ProguardKeepRuleType.KEEP_CLASS_MEMBERS, rule.getType());
assertTrue(rule.getModifiers().allowsObfuscation);
assertTrue(rule.getModifiers().allowsOptimization);
@@ -512,23 +495,11 @@
ProguardMemberRule memberRule = memberRules.iterator().next();
assertEquals(ProguardMemberType.FIELD, memberRule.getRuleType());
keptFields.put(memberRule.getName().toString(), true);
- });
+ }
assertEquals(3, keptFields.size());
assertTrue(keptFields.containsKey("intField"));
assertTrue(keptFields.containsKey("longField"));
assertTrue(keptFields.containsKey("objField"));
- Iterables.filter(rules, ProguardIdentifierNameStringRule.class).forEach(rule -> {
- List<ProguardMemberRule> memberRules = rule.getMemberRules();
- ProguardClassNameList classNames = rule.getClassNames();
- assertEquals(1, classNames.size());
- DexType type = classNames.asSpecificDexTypes().get(0);
- assertTrue(type.toSourceString().startsWith("java.util.concurrent.atomic.Atomic"));
- assertTrue(type.toSourceString().endsWith("FieldUpdater"));
- assertEquals(1, memberRules.size());
- ProguardMemberRule memberRule = memberRules.iterator().next();
- assertEquals(ProguardMemberType.METHOD, memberRule.getRuleType());
- assertEquals("newUpdater", memberRule.getName().toString());
- });
} else {
assertEquals(0, configuration.getRules().size());
}
@@ -541,7 +512,7 @@
ToolHelper.runProguard(jarTestClasses(
ImmutableList.of(mainClass, withVolatileFields)),
proguardedJar, proguardConfigFile, proguardMapFile);
- DexInspector proguardedInspector = new DexInspector(readJar(proguardedJar), proguardMapFile);
+ CodeInspector proguardedInspector = new CodeInspector(readJar(proguardedJar), proguardMapFile);
assertEquals(2, proguardedInspector.allClasses().size());
assertTrue(proguardedInspector.clazz(mainClass).isPresent());
classSubject = proguardedInspector.clazz(withVolatileFields);
@@ -604,7 +575,7 @@
assertTrue(!forceProguardCompatibility && (!innerClasses || !enclosingMethod));
return;
}
- DexInspector inspector = new DexInspector(app);
+ CodeInspector inspector = new CodeInspector(app);
assertTrue(inspector.clazz(getJavacGeneratedClassName(mainClass)).isPresent());
assertEquals(innerClasses || enclosingMethod ? "1" : "0", runOnArt(app, mainClass));
@@ -639,7 +610,7 @@
private void runKeepDefaultMethodsTest(
List<String> additionalKeepRules,
- Consumer<DexInspector> inspection,
+ Consumer<CodeInspector> inspection,
Consumer<ProguardConfiguration> compatInspection) throws Exception {
Class mainClass = TestClass.class;
CompatProguardCommandBuilder builder = new CompatProguardCommandBuilder();
@@ -657,7 +628,7 @@
Path proguardCompatibilityRules = temp.newFile().toPath();
builder.setProguardCompatibilityRulesOutput(proguardCompatibilityRules);
AndroidApp app = ToolHelper.runR8(builder.build(), o -> o.enableClassInlining = false);
- inspection.accept(new DexInspector(app));
+ inspection.accept(new CodeInspector(app));
// Check the Proguard compatibility configuration generated.
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(),
@@ -671,7 +642,7 @@
assertEquals(0, configuration.getRules().size());
}
- private void defaultMethodKept(DexInspector inspector) {
+ private void defaultMethodKept(CodeInspector inspector) {
ClassSubject clazz = inspector.clazz(InterfaceWithDefaultMethods.class);
assertTrue(clazz.isPresent());
MethodSubject method = clazz.method("int", "method", ImmutableList.of());
@@ -695,7 +666,7 @@
assertEquals(0, memberRule.getArguments().size());
}
- private void defaultMethod2Kept(DexInspector inspector) {
+ private void defaultMethod2Kept(CodeInspector inspector) {
ClassSubject clazz = inspector.clazz(InterfaceWithDefaultMethods.class);
assertTrue(clazz.isPresent());
MethodSubject method =
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatabilityTestBase.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatabilityTestBase.java
deleted file mode 100644
index e3bffc6..0000000
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatabilityTestBase.java
+++ /dev/null
@@ -1,228 +0,0 @@
-// 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.forceproguardcompatibility;
-
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
-import static org.hamcrest.CoreMatchers.not;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.fail;
-
-import com.android.tools.r8.CompatProguardCommandBuilder;
-import com.android.tools.r8.DexIndexedConsumer;
-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.AndroidApiLevel;
-import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.FileUtils;
-import com.android.tools.r8.utils.InternalOptions;
-import com.google.common.collect.ImmutableList;
-import java.io.File;
-import java.nio.file.Path;
-import java.util.List;
-import java.util.function.Consumer;
-
-public class ProguardCompatabilityTestBase extends TestBase {
-
- protected Path proguardMap;
-
- public enum Shrinker {
- PROGUARD5,
- PROGUARD6,
- PROGUARD6_THEN_D8,
- R8_COMPAT,
- R8
- }
-
- protected static boolean isR8(Shrinker shrinker) {
- return shrinker == Shrinker.R8_COMPAT || shrinker == Shrinker.R8;
- }
-
- protected AndroidApp runShrinkerRaw(
- Shrinker mode, List<Class> programClasses, Iterable<String> proguadConfigs) throws Exception {
- return runShrinkerRaw(
- mode, programClasses, String.join(System.lineSeparator(), proguadConfigs));
- }
-
- protected AndroidApp runShrinkerRaw(
- Shrinker mode, List<Class> programClasses, String proguardConfig) throws Exception {
- proguardMap = File.createTempFile("proguard", ".map", temp.getRoot()).toPath();
- switch (mode) {
- case PROGUARD5:
- return runProguard5Raw(programClasses, proguardConfig, proguardMap);
- case PROGUARD6:
- return runProguard6Raw(programClasses, proguardConfig, proguardMap);
- case PROGUARD6_THEN_D8:
- return runProguard6AndD8Raw(programClasses, proguardConfig, proguardMap);
- case R8_COMPAT:
- return runR8CompatRaw(programClasses, proguardConfig);
- case R8:
- return runR8Raw(programClasses, proguardConfig);
- }
- throw new IllegalArgumentException("Unknown shrinker: " + mode);
- }
-
- protected DexInspector runShrinker(
- Shrinker mode, List<Class> programClasses, List<String> proguadConfigs) throws Exception {
- return runShrinker(mode, programClasses, String.join(System.lineSeparator(), proguadConfigs));
- }
-
- protected DexInspector runShrinker(
- Shrinker mode, List<Class> programClasses, String proguardConfig) throws Exception {
- switch (mode) {
- case PROGUARD5:
- return runProguard5(programClasses, proguardConfig);
- case PROGUARD6:
- return runProguard6(programClasses, proguardConfig);
- case PROGUARD6_THEN_D8:
- return runProguard6AndD8(programClasses, proguardConfig);
- case R8_COMPAT:
- return runR8Compat(programClasses, proguardConfig);
- case R8:
- return runR8(programClasses, proguardConfig);
- }
- throw new IllegalArgumentException("Unknown shrinker: " + mode);
- }
-
- protected AndroidApp runR8Raw(List<Class> programClasses, String proguardConfig)
- throws Exception {
- return runR8Raw(programClasses, proguardConfig, null);
- }
-
- protected AndroidApp runR8Raw(
- List<Class> programClasses, String proguardConfig, Consumer<InternalOptions> configure)
- throws Exception {
- AndroidApp app = readClassesAndAndriodJar(programClasses);
- R8Command.Builder builder = ToolHelper.prepareR8CommandBuilder(app);
- ToolHelper.allowTestProguardOptions(builder);
- builder.addProguardConfiguration(ImmutableList.of(proguardConfig), Origin.unknown());
- return ToolHelper.runR8(builder.build(), configure);
- }
-
- protected DexInspector runR8(List<Class> programClasses, String proguardConfig) throws Exception {
- return new DexInspector(runR8Raw(programClasses, proguardConfig));
- }
-
- protected AndroidApp runR8CompatRaw(
- List<Class> programClasses, String proguardConfig) throws Exception {
- CompatProguardCommandBuilder builder = new CompatProguardCommandBuilder(true);
- ToolHelper.allowTestProguardOptions(builder);
- builder.addProguardConfiguration(ImmutableList.of(proguardConfig), Origin.unknown());
- programClasses.forEach(
- clazz -> builder.addProgramFiles(ToolHelper.getClassFileForTestClass(clazz)));
- builder.addLibraryFiles(ToolHelper.getAndroidJar(ToolHelper.getMinApiLevelForDexVm()));
- builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
- return ToolHelper.runR8(builder.build());
- }
-
- protected DexInspector runR8Compat(
- List<Class> programClasses, String proguardConfig) throws Exception {
- return new DexInspector(runR8CompatRaw(programClasses, proguardConfig));
- }
-
- protected DexInspector runR8CompatKeepingMain(Class mainClass, List<Class> programClasses)
- throws Exception {
- return runR8Compat(programClasses, keepMainProguardConfiguration(mainClass));
- }
-
- protected AndroidApp runProguard5Raw(
- List<Class> programClasses, String proguardConfig, Path proguardMap) throws Exception {
- Path proguardedJar =
- File.createTempFile("proguarded", FileUtils.JAR_EXTENSION, temp.getRoot()).toPath();
- Path proguardConfigFile = File.createTempFile("proguard", ".config", temp.getRoot()).toPath();
- FileUtils.writeTextFile(proguardConfigFile, proguardConfig);
- ProcessResult result = ToolHelper.runProguardRaw(
- jarTestClasses(programClasses),
- proguardedJar,
- ToolHelper.getAndroidJar(AndroidApiLevel.N),
- proguardConfigFile,
- proguardMap);
- if (result.exitCode != 0) {
- fail("Proguard failed, exit code " + result.exitCode + ", stderr:\n" + result.stderr);
- }
- return readJar(proguardedJar);
- }
-
- protected DexInspector runProguard5(
- List<Class> programClasses, String proguardConfig) throws Exception {
- proguardMap = File.createTempFile("proguard", ".map", temp.getRoot()).toPath();
- return new DexInspector(
- runProguard5Raw(programClasses, proguardConfig, proguardMap), proguardMap);
- }
-
- protected AndroidApp runProguard6Raw(
- List<Class> programClasses, String proguardConfig, Path proguardMap) throws Exception {
- Path proguardedJar =
- File.createTempFile("proguarded", FileUtils.JAR_EXTENSION, temp.getRoot()).toPath();
- Path proguardConfigFile = File.createTempFile("proguard", ".config", temp.getRoot()).toPath();
- FileUtils.writeTextFile(proguardConfigFile, proguardConfig);
- ProcessResult result = ToolHelper.runProguard6Raw(
- jarTestClasses(programClasses),
- proguardedJar,
- ToolHelper.getAndroidJar(AndroidApiLevel.N),
- proguardConfigFile,
- proguardMap);
- if (result.exitCode != 0) {
- fail("Proguard failed, exit code " + result.exitCode + ", stderr:\n" + result.stderr);
- }
- return readJar(proguardedJar);
- }
-
- protected DexInspector runProguard6(
- List<Class> programClasses, String proguardConfig) throws Exception {
- proguardMap = File.createTempFile("proguard", ".map", temp.getRoot()).toPath();
- return new DexInspector(
- runProguard6Raw(programClasses, proguardConfig, proguardMap), proguardMap);
- }
-
- protected AndroidApp runProguard6AndD8Raw(
- List<Class> programClasses, String proguardConfig, Path proguardMap) throws Exception {
- Path proguardedJar =
- File.createTempFile("proguarded", FileUtils.JAR_EXTENSION, temp.getRoot()).toPath();
- Path proguardConfigFile = File.createTempFile("proguard", ".config", temp.getRoot()).toPath();
- FileUtils.writeTextFile(proguardConfigFile, proguardConfig);
- ProcessResult result = ToolHelper.runProguard6Raw(
- jarTestClasses(programClasses),
- proguardedJar,
- ToolHelper.getAndroidJar(AndroidApiLevel.N),
- proguardConfigFile,
- proguardMap);
- if (result.exitCode != 0) {
- fail("Proguard failed, exit code " + result.exitCode + ", stderr:\n" + result.stderr);
- }
- return ToolHelper.runD8(readJar(proguardedJar));
- }
-
- protected DexInspector runProguard6AndD8(
- List<Class> programClasses, String proguardConfig) throws Exception {
- proguardMap = File.createTempFile("proguard", ".map", temp.getRoot()).toPath();
- return new DexInspector(
- runProguard6AndD8Raw(programClasses, proguardConfig, proguardMap), proguardMap);
- }
-
- protected DexInspector runProguardKeepingMain(Class mainClass, List<Class> programClasses)
- throws Exception {
- return runProguard6AndD8(programClasses, keepMainProguardConfiguration(mainClass));
- }
-
- protected void verifyClassesPresent(
- DexInspector dexInspector, Class<?>... classesOfInterest) {
- for (Class klass : classesOfInterest) {
- ClassSubject c = dexInspector.clazz(klass);
- assertThat(c, isPresent());
- }
- }
-
- protected void verifyClassesAbsent(
- DexInspector dexInspector, Class<?>... classesOfInterest) {
- for (Class klass : classesOfInterest) {
- ClassSubject c = dexInspector.clazz(klass);
- assertThat(c, not(isPresent()));
- }
- }
-}
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatibilityTestBase.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatibilityTestBase.java
new file mode 100644
index 0000000..24f64c9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatibilityTestBase.java
@@ -0,0 +1,297 @@
+// 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.forceproguardcompatibility;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.CompatProguardCommandBuilder;
+import com.android.tools.r8.DataEntryResource;
+import com.android.tools.r8.DexIndexedConsumer;
+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.AndroidApiLevel;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.io.File;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.function.Consumer;
+
+public class ProguardCompatibilityTestBase extends TestBase {
+
+ protected Path proguardMap;
+
+ public enum Shrinker {
+ PROGUARD5,
+ PROGUARD6,
+ PROGUARD6_THEN_D8,
+ R8_COMPAT,
+ R8_COMPAT_CF,
+ R8,
+ R8_CF
+ }
+
+ protected static boolean isR8(Shrinker shrinker) {
+ return shrinker == Shrinker.R8_COMPAT
+ || shrinker == Shrinker.R8_COMPAT_CF
+ || shrinker == Shrinker.R8
+ || shrinker == Shrinker.R8_CF;
+ }
+
+ protected AndroidApp runShrinker(
+ Shrinker mode, List<Class> programClasses, Iterable<String> proguadConfigs) throws Exception {
+ return runShrinker(mode, programClasses, String.join(System.lineSeparator(), proguadConfigs));
+ }
+
+ protected AndroidApp runShrinker(Shrinker mode, List<Class> programClasses, String proguardConfig)
+ throws Exception {
+ proguardMap = File.createTempFile("proguard", ".map", temp.getRoot()).toPath();
+ switch (mode) {
+ case PROGUARD5:
+ return runProguard5(programClasses, proguardConfig, proguardMap);
+ case PROGUARD6:
+ return runProguard6(programClasses, proguardConfig, proguardMap);
+ case PROGUARD6_THEN_D8:
+ return runProguard6AndD8(programClasses, proguardConfig, proguardMap);
+ case R8_COMPAT:
+ return runR8Compat(programClasses, proguardConfig, Backend.DEX);
+ case R8_COMPAT_CF:
+ return runR8Compat(programClasses, proguardConfig, Backend.CF);
+ case R8:
+ return runR8(programClasses, proguardConfig, Backend.DEX);
+ case R8_CF:
+ return runR8(programClasses, proguardConfig, Backend.CF);
+ }
+ throw new IllegalArgumentException("Unknown shrinker: " + mode);
+ }
+
+ protected CodeInspector inspectAfterShrinking(
+ Shrinker mode, List<Class> programClasses, List<String> proguadConfigs) throws Exception {
+ return inspectAfterShrinking(
+ mode, programClasses, String.join(System.lineSeparator(), proguadConfigs));
+ }
+
+ protected CodeInspector inspectAfterShrinking(
+ Shrinker mode, List<Class> programClasses, String proguardConfig) throws Exception {
+ switch (mode) {
+ case PROGUARD5:
+ return inspectProguard5Result(programClasses, proguardConfig);
+ case PROGUARD6:
+ return inspectProguard6Result(programClasses, proguardConfig);
+ case PROGUARD6_THEN_D8:
+ return inspectProguard6AndD8Result(programClasses, proguardConfig);
+ case R8_COMPAT:
+ return inspectR8CompatResult(programClasses, proguardConfig, Backend.DEX);
+ case R8_COMPAT_CF:
+ return inspectR8CompatResult(programClasses, proguardConfig, Backend.CF);
+ case R8:
+ return inspectR8Result(programClasses, proguardConfig, Backend.DEX);
+ case R8_CF:
+ return inspectR8Result(programClasses, proguardConfig, Backend.CF);
+ }
+ throw new IllegalArgumentException("Unknown shrinker: " + mode);
+ }
+
+ protected AndroidApp runR8(List<Class> programClasses, String proguardConfig, Backend backend)
+ throws Exception {
+ return runR8(programClasses, proguardConfig, null, backend);
+ }
+
+ protected AndroidApp runR8(
+ List<Class> programClasses,
+ String proguardConfig,
+ Consumer<InternalOptions> configure,
+ Backend backend)
+ throws Exception {
+ assert backend == Backend.DEX || backend == Backend.CF;
+ AndroidApp app = readClassesAndAndriodJar(programClasses);
+ R8Command.Builder builder =
+ ToolHelper.prepareR8CommandBuilder(
+ app,
+ backend == Backend.DEX
+ ? DexIndexedConsumer.emptyConsumer()
+ : ClassFileConsumer.emptyConsumer())
+ .addLibraryFiles(
+ backend == Backend.DEX
+ ? ToolHelper.getDefaultAndroidJar()
+ : ToolHelper.getJava8RuntimeJar());
+ ToolHelper.allowTestProguardOptions(builder);
+ builder.addProguardConfiguration(ImmutableList.of(proguardConfig), Origin.unknown());
+ return ToolHelper.runR8(builder.build(), configure);
+ }
+
+ protected CodeInspector inspectR8Result(
+ List<Class> programClasses, String proguardConfig, Backend backend) throws Exception {
+ return new CodeInspector(runR8(programClasses, proguardConfig, backend));
+ }
+
+ protected AndroidApp runR8Compat(
+ List<Class> programClasses, String proguardConfig, Backend backend) throws Exception {
+ CompatProguardCommandBuilder builder = new CompatProguardCommandBuilder(true);
+ ToolHelper.allowTestProguardOptions(builder);
+ builder.addProguardConfiguration(ImmutableList.of(proguardConfig), Origin.unknown());
+ programClasses.forEach(
+ clazz -> builder.addProgramFiles(ToolHelper.getClassFileForTestClass(clazz)));
+ if (backend == Backend.DEX) {
+ builder.addLibraryFiles(ToolHelper.getAndroidJar(ToolHelper.getMinApiLevelForDexVm()));
+ builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+ } else {
+ assert backend == Backend.CF;
+ builder.addLibraryFiles(ToolHelper.getJava8RuntimeJar());
+ builder.setProgramConsumer(ClassFileConsumer.emptyConsumer());
+ }
+ return ToolHelper.runR8(builder.build());
+ }
+
+ protected CodeInspector inspectR8CompatResult(
+ List<Class> programClasses, String proguardConfig, Backend backend) throws Exception {
+ return new CodeInspector(runR8Compat(programClasses, proguardConfig, backend));
+ }
+
+ protected AndroidApp runProguard5(
+ List<Class> programClasses, String proguardConfig, Path proguardMap) throws Exception {
+ Path proguardedJar =
+ File.createTempFile("proguarded", FileUtils.JAR_EXTENSION, temp.getRoot()).toPath();
+ Path proguardConfigFile = File.createTempFile("proguard", ".config", temp.getRoot()).toPath();
+ FileUtils.writeTextFile(proguardConfigFile, proguardConfig);
+ ProcessResult result = ToolHelper.runProguardRaw(
+ jarTestClasses(programClasses),
+ proguardedJar,
+ ToolHelper.getAndroidJar(AndroidApiLevel.N),
+ proguardConfigFile,
+ proguardMap);
+ if (result.exitCode != 0) {
+ fail("Proguard failed, exit code " + result.exitCode + ", stderr:\n" + result.stderr);
+ }
+ return readJar(proguardedJar);
+ }
+
+ protected CodeInspector inspectProguard5Result(List<Class> programClasses, String proguardConfig)
+ throws Exception {
+ proguardMap = File.createTempFile("proguard", ".map", temp.getRoot()).toPath();
+ return new CodeInspector(
+ runProguard5(programClasses, proguardConfig, proguardMap), proguardMap);
+ }
+
+ protected ProcessResult runProguard6Raw(
+ Path destination,
+ List<Class> programClasses,
+ String proguardConfig,
+ Path proguardMap,
+ List<DataEntryResource> dataResources)
+ throws Exception {
+ return runProguard6Raw(
+ destination,
+ jarTestClasses(programClasses, dataResources),
+ proguardConfig,
+ proguardMap,
+ null);
+ }
+
+ protected ProcessResult runProguard6Raw(
+ Path destination, Path jar, String proguardConfig, Path proguardMap) throws Exception {
+ return runProguard6Raw(destination, jar, proguardConfig, proguardMap, null);
+ }
+
+ protected ProcessResult runProguard6Raw(
+ Path destination,
+ Path jar,
+ String proguardConfig,
+ Path proguardMap,
+ Consumer<ProcessResult> processResultConsumer)
+ throws Exception {
+ Path proguardConfigFile = File.createTempFile("proguard", ".config", temp.getRoot()).toPath();
+ FileUtils.writeTextFile(proguardConfigFile, proguardConfig);
+ ProcessResult result =
+ ToolHelper.runProguard6Raw(
+ jar,
+ destination,
+ ToolHelper.getAndroidJar(AndroidApiLevel.N),
+ proguardConfigFile,
+ proguardMap);
+ if (result.exitCode != 0) {
+ fail("Proguard failed, exit code " + result.exitCode + ", stderr:\n" + result.stderr);
+ }
+ if (processResultConsumer != null) {
+ processResultConsumer.accept(result);
+ }
+ return result;
+ }
+
+ protected AndroidApp runProguard6(
+ List<Class> programClasses, String proguardConfig, Path proguardMap) throws Exception {
+ return runProguard6(programClasses, proguardConfig, proguardMap, null);
+ }
+
+ protected AndroidApp runProguard6(
+ List<Class> programClasses,
+ String proguardConfig,
+ Path proguardMap,
+ List<DataEntryResource> dataResources)
+ throws Exception {
+ Path proguardedJar =
+ File.createTempFile("proguarded", FileUtils.JAR_EXTENSION, temp.getRoot()).toPath();
+ runProguard6Raw(proguardedJar, programClasses, proguardConfig, proguardMap, dataResources);
+ return readJar(proguardedJar);
+ }
+
+ protected CodeInspector inspectProguard6Result(List<Class> programClasses, String proguardConfig)
+ throws Exception {
+ proguardMap = File.createTempFile("proguard", ".map", temp.getRoot()).toPath();
+ return new CodeInspector(
+ runProguard6(programClasses, proguardConfig, proguardMap), proguardMap);
+ }
+
+ protected AndroidApp runProguard6AndD8(
+ List<Class> programClasses, String proguardConfig, Path proguardMap) throws Exception {
+ Path proguardedJar =
+ File.createTempFile("proguarded", FileUtils.JAR_EXTENSION, temp.getRoot()).toPath();
+ Path proguardConfigFile = File.createTempFile("proguard", ".config", temp.getRoot()).toPath();
+ FileUtils.writeTextFile(proguardConfigFile, proguardConfig);
+ ProcessResult result = ToolHelper.runProguard6Raw(
+ jarTestClasses(programClasses),
+ proguardedJar,
+ ToolHelper.getAndroidJar(AndroidApiLevel.N),
+ proguardConfigFile,
+ proguardMap);
+ if (result.exitCode != 0) {
+ fail("Proguard failed, exit code " + result.exitCode + ", stderr:\n" + result.stderr);
+ }
+ return ToolHelper.runD8(readJar(proguardedJar));
+ }
+
+ protected CodeInspector inspectProguard6AndD8Result(
+ List<Class> programClasses, String proguardConfig) throws Exception {
+ proguardMap = File.createTempFile("proguard", ".map", temp.getRoot()).toPath();
+ return new CodeInspector(
+ runProguard6AndD8(programClasses, proguardConfig, proguardMap), proguardMap);
+ }
+
+ protected void verifyClassesPresent(
+ CodeInspector codeInspector, Class<?>... classesOfInterest) {
+ for (Class klass : classesOfInterest) {
+ ClassSubject c = codeInspector.clazz(klass);
+ assertThat(c, isPresent());
+ }
+ }
+
+ protected void verifyClassesAbsent(
+ CodeInspector codeInspector, Class<?>... classesOfInterest) {
+ for (Class klass : classesOfInterest) {
+ ClassSubject c = codeInspector.clazz(klass);
+ assertThat(c, not(isPresent()));
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ImplicitlyKeptDefaultConstructorTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ImplicitlyKeptDefaultConstructorTest.java
index d8615e0..1f5ea77 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ImplicitlyKeptDefaultConstructorTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ImplicitlyKeptDefaultConstructorTest.java
@@ -4,19 +4,23 @@
package com.android.tools.r8.shaking.forceproguardcompatibility.defaultctor;
-import static com.android.tools.r8.utils.DexInspectorMatchers.hasDefaultConstructor;
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.hasDefaultConstructor;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.core.IsNot.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
-import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatabilityTestBase;
+import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
import com.android.tools.r8.smali.ConstantFoldingTest.TriConsumer;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.List;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
class SuperClass {
@@ -90,7 +94,19 @@
}
}
-public class ImplicitlyKeptDefaultConstructorTest extends ProguardCompatabilityTestBase {
+@RunWith(Parameterized.class)
+public class ImplicitlyKeptDefaultConstructorTest extends ProguardCompatibilityTestBase {
+
+ private Backend backend;
+
+ @Parameterized.Parameters(name = "Backend: {0}")
+ public static Collection<Backend> data() {
+ return Arrays.asList(Backend.values());
+ }
+
+ public ImplicitlyKeptDefaultConstructorTest(Backend backend) {
+ this.backend = backend;
+ }
private void checkPresentWithDefaultConstructor(ClassSubject clazz) {
assertThat(clazz, isPresent());
@@ -103,14 +119,14 @@
}
private void checkAllClassesPresentWithDefaultConstructor(
- Class mainClass, List<Class> programClasses, DexInspector inspector) {
+ Class mainClass, List<Class> programClasses, CodeInspector inspector) {
assert programClasses.contains(mainClass);
assertEquals(programClasses.size(), inspector.allClasses().size());
inspector.forAllClasses(this::checkPresentWithDefaultConstructor);
}
private void checkAllClassesPresentOnlyMainWithDefaultConstructor(
- Class mainClass, List<Class> programClasses, DexInspector inspector) {
+ Class mainClass, List<Class> programClasses, CodeInspector inspector) {
assert programClasses.contains(mainClass);
assertEquals(programClasses.size(), inspector.allClasses().size());
checkPresentWithDefaultConstructor(inspector.clazz(mainClass));
@@ -121,7 +137,7 @@
}
private void checkOnlyMainPresent(
- Class mainClass, List<Class> programClasses, DexInspector inspector) {
+ Class mainClass, List<Class> programClasses, CodeInspector inspector) {
assert programClasses.contains(mainClass);
assertEquals(1, inspector.allClasses().size());
inspector.forAllClasses(this::checkPresentWithDefaultConstructor);
@@ -129,22 +145,22 @@
private void runTest(
Class mainClass, List<Class> programClasses, String proguardConfiguration,
- TriConsumer<Class, List<Class>, DexInspector> r8Checker,
- TriConsumer<Class, List<Class>, DexInspector> proguardChecker) throws Exception {
- DexInspector inspector = runR8Compat(programClasses, proguardConfiguration);
+ TriConsumer<Class, List<Class>, CodeInspector> r8Checker,
+ TriConsumer<Class, List<Class>, CodeInspector> proguardChecker) throws Exception {
+ CodeInspector inspector = inspectR8CompatResult(programClasses, proguardConfiguration, backend);
r8Checker.accept(mainClass, programClasses, inspector);
if (isRunProguard()) {
- inspector = runProguard6(programClasses, proguardConfiguration);
+ inspector = inspectProguard6Result(programClasses, proguardConfiguration);
proguardChecker.accept(mainClass, programClasses, inspector);
- inspector = runProguard6AndD8(programClasses, proguardConfiguration);
+ inspector = inspectProguard6AndD8Result(programClasses, proguardConfiguration);
proguardChecker.accept(mainClass, programClasses, inspector);
}
}
private void runTest(
Class mainClass, List<Class> programClasses, String proguardConfiguration,
- TriConsumer<Class, List<Class>, DexInspector> checker) throws Exception {
+ TriConsumer<Class, List<Class>, CodeInspector> checker) throws Exception {
runTest(mainClass, programClasses, proguardConfiguration, checker, checker);
}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
index 65d1763..23aad75 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
@@ -3,17 +3,17 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking.ifrule;
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
-import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatabilityTestBase;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import java.util.Collection;
import java.util.List;
@@ -23,7 +23,7 @@
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
-public class IfOnAccessModifierTest extends ProguardCompatabilityTestBase {
+public class IfOnAccessModifierTest extends ProguardCompatibilityTestBase {
private final static List<Class> CLASSES = ImmutableList.of(
ClassForIf.class, ClassForSubsequent.class,
MainForAccessModifierTest.class);
@@ -59,8 +59,8 @@
" public <methods>;",
"}"
);
- DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
- ClassSubject classSubject = dexInspector.clazz(ClassForIf.class);
+ CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+ ClassSubject classSubject = codeInspector.clazz(ClassForIf.class);
assertThat(classSubject, isPresent());
MethodSubject methodSubject = classSubject.method(publicMethod);
assertThat(methodSubject, not(isPresent()));
@@ -68,7 +68,7 @@
assertThat(methodSubject, isPresent());
assertTrue(methodSubject.getMethod().accessFlags.isPublic());
- classSubject = dexInspector.clazz(ClassForSubsequent.class);
+ classSubject = codeInspector.clazz(ClassForSubsequent.class);
if (isR8(shrinker)) {
// TODO(b/72109068): ClassForIf#nonPublicMethod becomes public, and -if rule is not applied
// at the 2nd tree shaking.
@@ -98,8 +98,8 @@
" !public <methods>;",
"}"
);
- DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
- ClassSubject classSubject = dexInspector.clazz(ClassForIf.class);
+ CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+ ClassSubject classSubject = codeInspector.clazz(ClassForIf.class);
assertThat(classSubject, isPresent());
MethodSubject methodSubject = classSubject.method(publicMethod);
assertThat(methodSubject, not(isPresent()));
@@ -107,7 +107,7 @@
assertThat(methodSubject, isPresent());
assertTrue(methodSubject.getMethod().accessFlags.isPublic());
- classSubject = dexInspector.clazz(ClassForSubsequent.class);
+ classSubject = codeInspector.clazz(ClassForSubsequent.class);
if (isR8(shrinker)) {
// TODO(b/72109068): ClassForIf#nonPublicMethod becomes public, and -if rule is not applied
// at the 2nd tree shaking.
@@ -138,15 +138,15 @@
" public <methods>;",
"}"
);
- DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
- ClassSubject classSubject = dexInspector.clazz(ClassForIf.class);
+ CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+ ClassSubject classSubject = codeInspector.clazz(ClassForIf.class);
assertThat(classSubject, isPresent());
MethodSubject methodSubject = classSubject.method(publicMethod);
assertThat(methodSubject, isPresent());
methodSubject = classSubject.method(nonPublicMethod);
assertThat(methodSubject, not(isPresent()));
- classSubject = dexInspector.clazz(ClassForSubsequent.class);
+ classSubject = codeInspector.clazz(ClassForSubsequent.class);
assertThat(classSubject, isPresent());
methodSubject = classSubject.method(publicMethod);
assertThat(methodSubject, isPresent());
@@ -170,15 +170,15 @@
" !public <methods>;",
"}"
);
- DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
- ClassSubject classSubject = dexInspector.clazz(ClassForIf.class);
+ CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+ ClassSubject classSubject = codeInspector.clazz(ClassForIf.class);
assertThat(classSubject, isPresent());
MethodSubject methodSubject = classSubject.method(publicMethod);
assertThat(methodSubject, isPresent());
methodSubject = classSubject.method(nonPublicMethod);
assertThat(methodSubject, not(isPresent()));
- classSubject = dexInspector.clazz(ClassForSubsequent.class);
+ classSubject = codeInspector.clazz(ClassForSubsequent.class);
assertThat(classSubject, isPresent());
methodSubject = classSubject.method(publicMethod);
assertThat(methodSubject, not(isPresent()));
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAnnotationTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAnnotationTest.java
index 4d0281f..20363c5 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAnnotationTest.java
@@ -3,8 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking.ifrule;
-import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatabilityTestBase;
-import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.util.Collection;
import java.util.List;
@@ -14,7 +14,7 @@
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
-public class IfOnAnnotationTest extends ProguardCompatabilityTestBase {
+public class IfOnAnnotationTest extends ProguardCompatibilityTestBase {
private final static List<Class> CLASSES = ImmutableList.of(
UsedAnnotation.class, UnusedAnnotation.class,
UsedAnnotationDependent.class, UnusedAnnotationDependent.class,
@@ -50,10 +50,10 @@
"-keep class **.UnusedAnnotation*"
);
- DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
- verifyClassesAbsent(dexInspector,
+ CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+ verifyClassesAbsent(codeInspector,
UnusedAnnotation.class, UnusedAnnotationDependent.class);
- verifyClassesPresent(dexInspector,
+ verifyClassesPresent(codeInspector,
UsedAnnotation.class, UsedAnnotationDependent.class);
}
@@ -76,10 +76,10 @@
"-keep class <1>.Unused<2>*"
);
- DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
- verifyClassesAbsent(dexInspector,
+ CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+ verifyClassesAbsent(codeInspector,
UnusedAnnotation.class, UnusedAnnotationDependent.class);
- verifyClassesPresent(dexInspector,
+ verifyClassesPresent(codeInspector,
UsedAnnotation.class, UsedAnnotationDependent.class);
}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnClassTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnClassTest.java
index 76c1023..906788a 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnClassTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnClassTest.java
@@ -3,18 +3,18 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking.ifrule;
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
-import static com.android.tools.r8.utils.DexInspectorMatchers.isNotRenamed;
-import static com.android.tools.r8.utils.DexInspectorMatchers.isRenamed;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isNotRenamed;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
-import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatabilityTestBase;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.FieldSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collection;
@@ -25,7 +25,7 @@
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
-public class IfOnClassTest extends ProguardCompatabilityTestBase {
+public class IfOnClassTest extends ProguardCompatibilityTestBase {
private final static List<Class> CLASSES = ImmutableList.of(
EmptyMainClassForIfOnClassTests.class,
Precondition.class, DependentUser.class, Dependent.class);
@@ -60,21 +60,21 @@
}
@Override
- protected DexInspector runR8(
- List<Class> programClasses, String proguardConfig) throws Exception {
- return super.runR8(programClasses, adaptConfiguration(proguardConfig));
+ protected CodeInspector inspectR8Result(
+ List<Class> programClasses, String proguardConfig, Backend backend) throws Exception {
+ return super.inspectR8Result(programClasses, adaptConfiguration(proguardConfig), backend);
}
@Override
- protected DexInspector runProguard5(
- List<Class> programClasses, String proguardConfig) throws Exception {
- return super.runProguard5(programClasses, adaptConfiguration(proguardConfig));
+ protected CodeInspector inspectProguard5Result(List<Class> programClasses, String proguardConfig)
+ throws Exception {
+ return super.inspectProguard5Result(programClasses, adaptConfiguration(proguardConfig));
}
@Override
- protected DexInspector runProguard6(
- List<Class> programClasses, String proguardConfig) throws Exception {
- return super.runProguard6(programClasses, adaptConfiguration(proguardConfig));
+ protected CodeInspector inspectProguard6Result(List<Class> programClasses, String proguardConfig)
+ throws Exception {
+ return super.inspectProguard6Result(programClasses, adaptConfiguration(proguardConfig));
}
@Test
@@ -86,17 +86,17 @@
"-keep,allowobfuscation class **.Dependent"
);
- DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
+ CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
if (!keepPrecondition) {
// TODO(b/73708139): Proguard6 kept Dependent (w/o any members), which is not necessary.
if (shrinker == Shrinker.PROGUARD6) {
return;
}
- assertEquals(1, dexInspector.allClasses().size());
+ assertEquals(1, codeInspector.allClasses().size());
return;
}
- ClassSubject clazz = dexInspector.clazz(DependentUser.class);
+ ClassSubject clazz = codeInspector.clazz(DependentUser.class);
assertThat(clazz, isRenamed());
// Members of DependentUser are not used anywhere.
MethodSubject m = clazz.method("void", "callFoo", ImmutableList.of());
@@ -105,7 +105,7 @@
assertThat(f, not(isPresent()));
// Although DependentUser#callFoo is shrinked, Dependent is kept via -if.
- clazz = dexInspector.clazz(Dependent.class);
+ clazz = codeInspector.clazz(Dependent.class);
assertThat(clazz, isRenamed());
// But, its members are gone.
m = clazz.method("java.lang.String", "foo", ImmutableList.of());
@@ -123,17 +123,17 @@
"-keep,allowobfuscation class <1>.<2>"
);
- DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
+ CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
if (!keepPrecondition) {
// TODO(b/73708139): Proguard6 kept Dependent (w/o any members), which is not necessary.
if (shrinker == Shrinker.PROGUARD6) {
return;
}
- assertEquals(1, dexInspector.allClasses().size());
+ assertEquals(1, codeInspector.allClasses().size());
return;
}
- ClassSubject clazz = dexInspector.clazz(DependentUser.class);
+ ClassSubject clazz = codeInspector.clazz(DependentUser.class);
assertThat(clazz, isRenamed());
// Members of DependentUser are not used anywhere.
MethodSubject m = clazz.method("void", "callFoo", ImmutableList.of());
@@ -142,7 +142,7 @@
assertThat(f, not(isPresent()));
// Although DependentUser#callFoo is shrinked, Dependent is kept via -if.
- clazz = dexInspector.clazz(Dependent.class);
+ clazz = codeInspector.clazz(Dependent.class);
assertThat(clazz, isRenamed());
// But, its members are gone.
m = clazz.method("java.lang.String", "foo", ImmutableList.of());
@@ -160,13 +160,13 @@
"}"
);
- DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
+ CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
if (!keepPrecondition) {
- assertEquals(1, dexInspector.allClasses().size());
+ assertEquals(1, codeInspector.allClasses().size());
return;
}
- ClassSubject clazz = dexInspector.clazz(DependentUser.class);
+ ClassSubject clazz = codeInspector.clazz(DependentUser.class);
assertThat(clazz, isRenamed());
MethodSubject m = clazz.method("void", "callFoo", ImmutableList.of());
assertThat(m, isRenamed());
@@ -174,7 +174,7 @@
assertThat(f, not(isPresent()));
// Dependent is kept due to DependentUser#callFoo, but renamed.
- clazz = dexInspector.clazz(Dependent.class);
+ clazz = codeInspector.clazz(Dependent.class);
assertThat(clazz, isRenamed());
m = clazz.method("java.lang.String", "foo", ImmutableList.of());
assertThat(m, isRenamed());
@@ -196,17 +196,17 @@
"-keep,allowobfuscation class **.*User"
);
- DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
+ CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
if (!keepPrecondition) {
// TODO(b/73708139): Proguard6 kept DependentUser (w/o any members), which is not necessary.
if (shrinker == Shrinker.PROGUARD6) {
return;
}
- assertEquals(1, dexInspector.allClasses().size());
+ assertEquals(1, codeInspector.allClasses().size());
return;
}
- ClassSubject clazz = dexInspector.clazz(DependentUser.class);
+ ClassSubject clazz = codeInspector.clazz(DependentUser.class);
assertThat(clazz, isRenamed());
MethodSubject m = clazz.method("void", "callFoo", ImmutableList.of());
assertThat(m, isRenamed());
@@ -214,7 +214,7 @@
assertThat(f, not(isPresent()));
// Dependent is kept due to DependentUser#callFoo, but renamed.
- clazz = dexInspector.clazz(Dependent.class);
+ clazz = codeInspector.clazz(Dependent.class);
assertThat(clazz, isRenamed());
m = clazz.method("java.lang.String", "foo", ImmutableList.of());
assertThat(m, isRenamed());
@@ -236,17 +236,17 @@
"-keep,allowobfuscation class <1>.<2>User"
);
- DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
+ CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
if (!keepPrecondition) {
// TODO(b/73708139): Proguard6 kept DependentUser (w/o any members), which is not necessary.
if (shrinker == Shrinker.PROGUARD6) {
return;
}
- assertEquals(1, dexInspector.allClasses().size());
+ assertEquals(1, codeInspector.allClasses().size());
return;
}
- ClassSubject clazz = dexInspector.clazz(DependentUser.class);
+ ClassSubject clazz = codeInspector.clazz(DependentUser.class);
assertThat(clazz, isRenamed());
MethodSubject m = clazz.method("void", "callFoo", ImmutableList.of());
assertThat(m, isRenamed());
@@ -254,7 +254,7 @@
assertThat(f, not(isPresent()));
// Dependent is kept due to DependentUser#callFoo, but renamed.
- clazz = dexInspector.clazz(Dependent.class);
+ clazz = codeInspector.clazz(Dependent.class);
assertThat(clazz, isRenamed());
m = clazz.method("java.lang.String", "foo", ImmutableList.of());
assertThat(m, isRenamed());
@@ -274,9 +274,9 @@
"-keepnames class **.Dependent"
);
- DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
+ CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
- ClassSubject clazz = dexInspector.clazz(Dependent.class);
+ ClassSubject clazz = codeInspector.clazz(Dependent.class);
// Only class name is not renamed, if triggered.
assertThat(clazz, keepPrecondition ? isNotRenamed() : isRenamed());
MethodSubject m = clazz.method("java.lang.String", "foo", ImmutableList.of());
@@ -299,9 +299,9 @@
"}"
);
- DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
+ CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
- ClassSubject clazz = dexInspector.clazz(Dependent.class);
+ ClassSubject clazz = codeInspector.clazz(Dependent.class);
// Class name is not renamed, if triggered.
assertThat(clazz, keepPrecondition ? isNotRenamed() : isRenamed());
MethodSubject m = clazz.method("java.lang.String", "foo", ImmutableList.of());
@@ -325,9 +325,9 @@
"}"
);
- DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
+ CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
- ClassSubject clazz = dexInspector.clazz(Dependent.class);
+ ClassSubject clazz = codeInspector.clazz(Dependent.class);
assertThat(clazz, isRenamed());
MethodSubject m = clazz.method("java.lang.String", "foo", ImmutableList.of());
// Only method name is not renamed, if triggered.
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnFieldTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnFieldTest.java
index 7480e2e..79c132b 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnFieldTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnFieldTest.java
@@ -3,8 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking.ifrule;
-import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatabilityTestBase;
-import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collection;
@@ -15,7 +15,7 @@
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
-public class IfOnFieldTest extends ProguardCompatabilityTestBase {
+public class IfOnFieldTest extends ProguardCompatibilityTestBase {
private final static List<Class> CLASSES = ImmutableList.of(
D.class, D1.class, D2.class,
R.class, R1.class, R2.class,
@@ -42,15 +42,15 @@
}
@Override
- protected DexInspector runR8(
- List<Class> programClasses, String proguardConfig) throws Exception {
- return super.runR8(programClasses, adaptConfiguration(proguardConfig));
+ protected CodeInspector inspectR8Result(
+ List<Class> programClasses, String proguardConfig, Backend backend) throws Exception {
+ return super.inspectR8Result(programClasses, adaptConfiguration(proguardConfig), backend);
}
@Override
- protected DexInspector runProguard6(
- List<Class> programClasses, String proguardConfig) throws Exception {
- return super.runProguard6(programClasses, adaptConfiguration(proguardConfig));
+ protected CodeInspector inspectProguard6Result(List<Class> programClasses, String proguardConfig)
+ throws Exception {
+ return super.inspectProguard6Result(programClasses, adaptConfiguration(proguardConfig));
}
@Test
@@ -77,10 +77,10 @@
"-keep class **.D"
);
- DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
- verifyClassesAbsent(dexInspector,
+ CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+ verifyClassesAbsent(codeInspector,
R1.class, R2.class, D.class, D2.class);
- verifyClassesPresent(dexInspector,
+ verifyClassesPresent(codeInspector,
R.class, D1.class);
}
@@ -96,10 +96,10 @@
"-keep class **.D<2>"
);
- DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
- verifyClassesAbsent(dexInspector,
+ CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+ verifyClassesAbsent(codeInspector,
R1.class, R2.class, D.class, D2.class);
- verifyClassesPresent(dexInspector,
+ verifyClassesPresent(codeInspector,
R.class, D1.class);
}
@@ -119,10 +119,10 @@
"-keep class **.D2"
);
- DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
- verifyClassesAbsent(dexInspector,
+ CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+ verifyClassesAbsent(codeInspector,
R.class, D.class, R1.class, D1.class);
- verifyClassesPresent(dexInspector,
+ verifyClassesPresent(codeInspector,
R2.class, D2.class);
}
@@ -138,10 +138,10 @@
"-keep class **.D<2>"
);
- DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
- verifyClassesAbsent(dexInspector,
+ CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+ verifyClassesAbsent(codeInspector,
R.class, D.class, R1.class, D1.class);
- verifyClassesPresent(dexInspector,
+ verifyClassesPresent(codeInspector,
R2.class, D2.class);
}
@@ -158,10 +158,10 @@
"-keep class **$*D"
);
- DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
- verifyClassesAbsent(dexInspector,
+ CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+ verifyClassesAbsent(codeInspector,
R.class, D.class, R1.class, D1.class, R2.class, D2.class);
- verifyClassesPresent(dexInspector,
+ verifyClassesPresent(codeInspector,
MainWithInner.InnerR.class, MainWithInner.InnerD.class);
}
@@ -178,10 +178,10 @@
"-keep class <1>$<2>D"
);
- DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
- verifyClassesAbsent(dexInspector,
+ CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+ verifyClassesAbsent(codeInspector,
R.class, D.class, R1.class, D1.class, R2.class, D2.class);
- verifyClassesPresent(dexInspector,
+ verifyClassesPresent(codeInspector,
MainWithInner.InnerR.class, MainWithInner.InnerD.class);
}
@@ -202,9 +202,9 @@
"}",
"-keep class **.D2");
- DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
- verifyClassesAbsent(dexInspector, D2.class);
- verifyClassesPresent(dexInspector,
+ CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+ verifyClassesAbsent(codeInspector, D2.class);
+ verifyClassesPresent(codeInspector,
I.class, Impl.class, D1.class);
}
@@ -225,9 +225,9 @@
"}",
"-keep class <2>.D2");
- DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
- verifyClassesAbsent(dexInspector, D2.class);
- verifyClassesPresent(dexInspector,
+ CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+ verifyClassesAbsent(codeInspector, D2.class);
+ verifyClassesPresent(codeInspector,
I.class, Impl.class, D1.class);
}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/inlining/IfRuleWithInlining.java b/src/test/java/com/android/tools/r8/shaking/ifrule/inlining/IfRuleWithInlining.java
index 22f9ef6..fb9ce61 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/inlining/IfRuleWithInlining.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/inlining/IfRuleWithInlining.java
@@ -4,15 +4,15 @@
package com.android.tools.r8.shaking.ifrule.inlining;
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatabilityTestBase;
+import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.util.Collection;
import java.util.List;
@@ -43,7 +43,7 @@
}
@RunWith(Parameterized.class)
-public class IfRuleWithInlining extends ProguardCompatabilityTestBase {
+public class IfRuleWithInlining extends ProguardCompatibilityTestBase {
private final static List<Class> CLASSES = ImmutableList.of(
A.class, D.class, Main.class);
@@ -65,7 +65,7 @@
}
private void check(AndroidApp app) throws Exception {
- DexInspector inspector = new DexInspector(app);
+ CodeInspector inspector = new CodeInspector(app);
ClassSubject clazzA = inspector.clazz(A.class);
assertThat(clazzA, isPresent());
// A.a might be inlined.
@@ -90,6 +90,6 @@
"-dontobfuscate"
);
- check(runShrinkerRaw(shrinker, CLASSES, config));
+ check(runShrinker(shrinker, CLASSES, config));
}
}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/IfRuleWithVerticalClassMerging.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/IfRuleWithVerticalClassMerging.java
index b484d73..1cec850 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/IfRuleWithVerticalClassMerging.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/IfRuleWithVerticalClassMerging.java
@@ -4,16 +4,16 @@
package com.android.tools.r8.shaking.ifrule.verticalclassmerging;
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatabilityTestBase;
+import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.util.Collection;
import java.util.List;
@@ -58,7 +58,7 @@
}
@RunWith(Parameterized.class)
-public class IfRuleWithVerticalClassMerging extends ProguardCompatabilityTestBase {
+public class IfRuleWithVerticalClassMerging extends ProguardCompatibilityTestBase {
private final static List<Class> CLASSES = ImmutableList.of(
A.class, B.class, C.class, D.class, Main.class);
@@ -84,13 +84,13 @@
}
@Override
- protected AndroidApp runR8Raw(
- List<Class> programClasses, String proguardConfig) throws Exception {
- return super.runR8Raw(programClasses, proguardConfig, this::configure);
+ protected AndroidApp runR8(List<Class> programClasses, String proguardConfig, Backend backend)
+ throws Exception {
+ return super.runR8(programClasses, proguardConfig, this::configure, backend);
}
private void check(AndroidApp app) throws Exception {
- DexInspector inspector = new DexInspector(app);
+ CodeInspector inspector = new CodeInspector(app);
ClassSubject clazzA = inspector.clazz(A.class);
assertEquals(!enableClassMerging, clazzA.isPresent());
ClassSubject clazzB = inspector.clazz(B.class);
@@ -119,7 +119,7 @@
"-dontobfuscate"
);
- check(runShrinkerRaw(shrinker, CLASSES, config));
+ check(runShrinker(shrinker, CLASSES, config));
}
@Test
@@ -135,7 +135,7 @@
"-dontobfuscate"
);
- check(runShrinkerRaw(shrinker, CLASSES, config));
+ check(runShrinker(shrinker, CLASSES, config));
}
@Test
@@ -151,6 +151,6 @@
"-dontobfuscate"
);
- check(runShrinkerRaw(shrinker, CLASSES, config));
+ check(runShrinker(shrinker, CLASSES, config));
}
}
diff --git a/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/IncludeDescriptorClassesTest.java b/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/IncludeDescriptorClassesTest.java
index cae3b06..bc94fa6 100644
--- a/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/IncludeDescriptorClassesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/IncludeDescriptorClassesTest.java
@@ -9,7 +9,7 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.nio.file.Path;
import java.util.ArrayList;
@@ -18,10 +18,10 @@
public class IncludeDescriptorClassesTest extends TestBase {
private class Result {
- final DexInspector inspector;
- final DexInspector proguardedInspector;
+ final CodeInspector inspector;
+ final CodeInspector proguardedInspector;
- Result(DexInspector inspector, DexInspector proguardedInspector) {
+ Result(CodeInspector inspector, CodeInspector proguardedInspector) {
this.inspector = inspector;
this.proguardedInspector = proguardedInspector;
}
@@ -62,15 +62,15 @@
List<Class> classes = new ArrayList<>(applicationClasses);
classes.add(mainClass);
- DexInspector inspector = new DexInspector(compileWithR8(classes, proguardConfig));
+ CodeInspector inspector = new CodeInspector(compileWithR8(classes, proguardConfig));
- DexInspector proguardedInspector = null;
+ CodeInspector proguardedInspector = null;
// Actually running Proguard should only be during development.
if (isRunProguard()) {
Path proguardedJar = temp.newFolder().toPath().resolve("proguarded.jar");
Path proguardedMap = temp.newFolder().toPath().resolve("proguarded.map");
ToolHelper.runProguard(jarTestClasses(classes), proguardedJar, proguardConfig, proguardedMap);
- proguardedInspector = new DexInspector(readJar(proguardedJar), proguardedMap);
+ proguardedInspector = new CodeInspector(readJar(proguardedJar), proguardedMap);
}
return new Result(inspector, proguardedInspector);
diff --git a/src/test/java/com/android/tools/r8/shaking/keepclassmembers/KeepClassMembersTest.java b/src/test/java/com/android/tools/r8/shaking/keepclassmembers/KeepClassMembersTest.java
index 12bcdfd..cff0675 100644
--- a/src/test/java/com/android/tools/r8/shaking/keepclassmembers/KeepClassMembersTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/keepclassmembers/KeepClassMembersTest.java
@@ -4,24 +4,24 @@
package com.android.tools.r8.shaking.keepclassmembers;
-import static com.android.tools.r8.utils.DexInspectorMatchers.isAbstract;
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbstract;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.core.IsNot.not;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
-import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatabilityTestBase;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.FieldSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import org.junit.Test;
-public class KeepClassMembersTest extends ProguardCompatabilityTestBase {
+public class KeepClassMembersTest extends ProguardCompatibilityTestBase {
- private void check(DexInspector inspector, Class mainClass, Class<?> staticClass,
+ private void check(CodeInspector inspector, Class mainClass, Class<?> staticClass,
boolean forceProguardCompatibility, boolean fromProguard) {
assertTrue(inspector.clazz(mainClass).isPresent());
ClassSubject staticClassSubject = inspector.clazz(staticClass);
@@ -70,14 +70,14 @@
"}",
"-dontoptimize", "-dontobfuscate"
));
- DexInspector inspector;
- inspector = new DexInspector(
+ CodeInspector inspector;
+ inspector = new CodeInspector(
compileWithR8(ImmutableList.of(mainClass, staticClass), proguardConfig,
options -> options.forceProguardCompatibility = forceProguardCompatibility));
check(inspector, mainClass, staticClass, forceProguardCompatibility, false);
if (isRunProguard()) {
- inspector = runProguard6(ImmutableList.of(mainClass, staticClass), proguardConfig);
+ inspector = inspectProguard6Result(ImmutableList.of(mainClass, staticClass), proguardConfig);
check(inspector, mainClass, staticClass, true, true);
}
}
diff --git a/src/test/java/com/android/tools/r8/shaking/proxy/ProxiesTest.java b/src/test/java/com/android/tools/r8/shaking/proxy/ProxiesTest.java
index 8e39b67..b3e6367 100644
--- a/src/test/java/com/android/tools/r8/shaking/proxy/ProxiesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/proxy/ProxiesTest.java
@@ -6,14 +6,11 @@
import static org.junit.Assert.assertEquals;
+import com.android.tools.r8.ClassFileConsumer;
import com.android.tools.r8.DexIndexedConsumer;
import com.android.tools.r8.R8Command;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.code.Instruction;
-import com.android.tools.r8.code.InvokeInterface;
-import com.android.tools.r8.code.InvokeVirtual;
-import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.invokesuper.Consumer;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.origin.Origin;
@@ -23,14 +20,35 @@
import com.android.tools.r8.shaking.proxy.testclasses.SubInterface;
import com.android.tools.r8.shaking.proxy.testclasses.TestClass;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.InvokeInstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Streams;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.List;
+import java.util.function.Predicate;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+@RunWith(Parameterized.class)
public class ProxiesTest extends TestBase {
+ private Backend backend;
- private void runTest(List<String> additionalKeepRules, Consumer<DexInspector> inspection,
+ @Parameterized.Parameters(name = "Backend: {0}")
+ public static Collection<Backend> data() {
+ return Arrays.asList(Backend.values());
+ }
+
+ public ProxiesTest(Backend backend) {
+ this.backend = backend;
+ }
+
+ private void runTest(List<String> additionalKeepRules, Consumer<CodeInspector> inspection,
String expectedResult)
throws Exception {
Class mainClass = Main.class;
@@ -49,52 +67,91 @@
Origin.unknown()
);
builder.addProguardConfiguration(additionalKeepRules, Origin.unknown());
- builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+ if (backend == Backend.DEX) {
+ builder
+ .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+ .addLibraryFiles(ToolHelper.getDefaultAndroidJar());
+ } else {
+ assert backend == Backend.CF;
+ builder
+ .setProgramConsumer(ClassFileConsumer.emptyConsumer())
+ .addLibraryFiles(ToolHelper.getJava8RuntimeJar());
+ }
AndroidApp app = ToolHelper.runR8(builder.build(), o -> o.enableDevirtualization = false);
- inspection.accept(new DexInspector(app));
- assertEquals(expectedResult, runOnArt(app, mainClass));
+ inspection.accept(new CodeInspector(app, o -> o.enableCfFrontend = true));
+ String result = backend == Backend.DEX ? runOnArt(app, mainClass) : runOnJava(app, mainClass);
+ if (ToolHelper.isWindows()) {
+ result = result.replace(System.lineSeparator(), "\n");
+ }
+ assertEquals(expectedResult, result);
}
- private int countInstructionInX(DexInspector inspector, Class<? extends Instruction> invoke) {
+ private int countInstructionInX(CodeInspector inspector, Predicate<InstructionSubject> invoke) {
MethodSignature signatureForX =
new MethodSignature("x", "void", ImmutableList.of(BaseInterface.class.getCanonicalName()));
- DexCode x = inspector.clazz(Main.class).method(signatureForX).getMethod().getCode().asDexCode();
- return (int) filterInstructionKind(x, invoke).count();
+ MethodSubject method = inspector.clazz(Main.class).method(signatureForX);
+ assert method instanceof FoundMethodSubject;
+ FoundMethodSubject foundMethod = (FoundMethodSubject) method;
+ return (int) Streams.stream(foundMethod.iterateInstructions(invoke)).count();
}
- private int countInstructionInY(DexInspector inspector, Class<? extends Instruction> invoke) {
+ private int countInstructionInY(CodeInspector inspector, Predicate<InstructionSubject> invoke) {
MethodSignature signatureForY =
new MethodSignature("y", "void", ImmutableList.of(SubInterface.class.getCanonicalName()));
- DexCode y = inspector.clazz(Main.class).method(signatureForY).getMethod().getCode().asDexCode();
- return (int) filterInstructionKind(y, invoke)
- .filter(instruction -> instruction.getMethod().qualifiedName().endsWith("method"))
- .count();
+ MethodSubject method = inspector.clazz(Main.class).method(signatureForY);
+ assert method instanceof FoundMethodSubject;
+ FoundMethodSubject foundMethod = (FoundMethodSubject) method;
+ return (int)
+ Streams.stream(foundMethod.iterateInstructions(invoke))
+ .filter(
+ instruction -> {
+ InvokeInstructionSubject invokeInstruction =
+ (InvokeInstructionSubject) instruction;
+ return invokeInstruction.invokedMethod().qualifiedName().endsWith("method");
+ })
+ .count();
}
- private int countInstructionInZ(DexInspector inspector, Class<? extends Instruction> invoke) {
+ private int countInstructionInZ(CodeInspector inspector, Predicate<InstructionSubject> invoke) {
MethodSignature signatureForZ =
new MethodSignature("z", "void", ImmutableList.of(TestClass.class.getCanonicalName()));
- DexCode z = inspector.clazz(Main.class).method(signatureForZ).getMethod().getCode().asDexCode();
- return (int) filterInstructionKind(z, invoke)
- .filter(instruction -> instruction.getMethod().qualifiedName().endsWith("method"))
- .count();
+ MethodSubject method = inspector.clazz(Main.class).method(signatureForZ);
+ assert method instanceof FoundMethodSubject;
+ FoundMethodSubject foundMethod = (FoundMethodSubject) method;
+ return (int)
+ Streams.stream(foundMethod.iterateInstructions(invoke))
+ .filter(
+ instruction -> {
+ InvokeInstructionSubject invokeInstruction =
+ (InvokeInstructionSubject) instruction;
+ return invokeInstruction.invokedMethod().qualifiedName().endsWith("method");
+ })
+ .count();
}
private int countInstructionInZSubClass(
- DexInspector inspector, Class<? extends Instruction> invoke) {
+ CodeInspector inspector, Predicate<InstructionSubject> invoke) {
MethodSignature signatureForZ =
new MethodSignature("z", "void", ImmutableList.of(SubClass.class.getCanonicalName()));
- DexCode z = inspector.clazz(Main.class).method(signatureForZ).getMethod().getCode().asDexCode();
- return (int) filterInstructionKind(z, invoke)
- .filter(instruction -> instruction.getMethod().qualifiedName().endsWith("method"))
- .count();
+ MethodSubject method = inspector.clazz(Main.class).method(signatureForZ);
+ assert method instanceof FoundMethodSubject;
+ FoundMethodSubject foundMethod = (FoundMethodSubject) method;
+ return (int)
+ Streams.stream(foundMethod.iterateInstructions(invoke))
+ .filter(
+ instruction -> {
+ InvokeInstructionSubject invokeInstruction =
+ (InvokeInstructionSubject) instruction;
+ return invokeInstruction.invokedMethod().qualifiedName().endsWith("method");
+ })
+ .count();
}
- private void noInterfaceKept(DexInspector inspector) {
+ private void noInterfaceKept(CodeInspector inspector) {
// Indirectly assert that method is inlined into x, y and z.
- assertEquals(1, countInstructionInX(inspector, InvokeInterface.class));
- assertEquals(1, countInstructionInY(inspector, InvokeInterface.class));
- assertEquals(1, countInstructionInZ(inspector, InvokeVirtual.class));
+ assertEquals(1, countInstructionInX(inspector, InstructionSubject::isInvokeInterface));
+ assertEquals(1, countInstructionInY(inspector, InstructionSubject::isInvokeInterface));
+ assertEquals(1, countInstructionInZ(inspector, InstructionSubject::isInvokeVirtual));
}
@Test
@@ -104,13 +161,13 @@
"TestClass 1\nTestClass 1\nTestClass 1\nProxy\nEXCEPTION\n");
}
- private void baseInterfaceKept(DexInspector inspector) {
+ private void baseInterfaceKept(CodeInspector inspector) {
// Indirectly assert that method is not inlined into x.
- assertEquals(3, countInstructionInX(inspector, InvokeInterface.class));
+ assertEquals(3, countInstructionInX(inspector, InstructionSubject::isInvokeInterface));
// Indirectly assert that method is inlined into y and z.
- assertEquals(1, countInstructionInY(inspector, InvokeInterface.class));
- assertEquals(1, countInstructionInZ(inspector, InvokeVirtual.class));
- assertEquals(1, countInstructionInZSubClass(inspector, InvokeVirtual.class));
+ assertEquals(1, countInstructionInY(inspector, InstructionSubject::isInvokeInterface));
+ assertEquals(1, countInstructionInZ(inspector, InstructionSubject::isInvokeVirtual));
+ assertEquals(1, countInstructionInZSubClass(inspector, InstructionSubject::isInvokeVirtual));
}
@Test
@@ -124,13 +181,13 @@
"TestClass 2\nTestClass 2\nTestClass 2\nProxy\nEXCEPTION\n");
}
- private void subInterfaceKept(DexInspector inspector) {
+ private void subInterfaceKept(CodeInspector inspector) {
// Indirectly assert that method is not inlined into x or y.
- assertEquals(3, countInstructionInX(inspector, InvokeInterface.class));
- assertEquals(3, countInstructionInY(inspector, InvokeInterface.class));
+ assertEquals(3, countInstructionInX(inspector, InstructionSubject::isInvokeInterface));
+ assertEquals(3, countInstructionInY(inspector, InstructionSubject::isInvokeInterface));
// Indirectly assert that method is inlined into z.
- assertEquals(1, countInstructionInZ(inspector, InvokeVirtual.class));
- assertEquals(1, countInstructionInZSubClass(inspector, InvokeVirtual.class));
+ assertEquals(1, countInstructionInZ(inspector, InstructionSubject::isInvokeVirtual));
+ assertEquals(1, countInstructionInZSubClass(inspector, InstructionSubject::isInvokeVirtual));
}
@Test
@@ -146,12 +203,12 @@
"TestClass 4\nTestClass 4\nTestClass 4\nSUCCESS\n");
}
- private void classKept(DexInspector inspector) {
+ private void classKept(CodeInspector inspector) {
// Indirectly assert that method is not inlined into x, y or z.
- assertEquals(3, countInstructionInX(inspector, InvokeInterface.class));
- assertEquals(3, countInstructionInY(inspector, InvokeInterface.class));
- assertEquals(3, countInstructionInZ(inspector, InvokeVirtual.class));
- assertEquals(3, countInstructionInZSubClass(inspector, InvokeVirtual.class));
+ assertEquals(3, countInstructionInX(inspector, InstructionSubject::isInvokeInterface));
+ assertEquals(3, countInstructionInY(inspector, InstructionSubject::isInvokeInterface));
+ assertEquals(3, countInstructionInZ(inspector, InstructionSubject::isInvokeVirtual));
+ assertEquals(3, countInstructionInZSubClass(inspector, InstructionSubject::isInvokeVirtual));
}
@Test
diff --git a/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java b/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java
index e4be101..d12f41a 100644
--- a/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java
@@ -7,34 +7,66 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking.testrules;
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
-import static org.junit.Assert.assertThat;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.ProgramConsumer;
import com.android.tools.r8.R8Command;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.List;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+@RunWith(Parameterized.class)
public class ForceInlineTest extends TestBase {
+ private Backend backend;
- private DexInspector runTest(List<String> proguardConfiguration) throws Exception {
+ @Parameterized.Parameters(name = "Backend: {0}")
+ public static Collection<Backend> data() {
+ return Arrays.asList(Backend.values());
+ }
+
+ public ForceInlineTest(Backend backend) {
+ this.backend = backend;
+ }
+
+ private CodeInspector runTest(List<String> proguardConfiguration) throws Exception {
+ ProgramConsumer programConsumer;
+ Path library;
+ if (backend == Backend.DEX) {
+ programConsumer = DexIndexedConsumer.emptyConsumer();
+ library = ToolHelper.getDefaultAndroidJar();
+ } else {
+ assert backend == Backend.CF;
+ programConsumer = ClassFileConsumer.emptyConsumer();
+ library = ToolHelper.getJava8RuntimeJar();
+ }
R8Command.Builder builder =
- ToolHelper.prepareR8CommandBuilder(readClasses(Main.class, A.class, B.class, C.class));
+ ToolHelper.prepareR8CommandBuilder(
+ readClasses(Main.class, A.class, B.class, C.class), programConsumer)
+ .addLibraryFiles(library);
ToolHelper.allowTestProguardOptions(builder);
builder.addProguardConfiguration(proguardConfiguration, Origin.unknown());
- return new DexInspector(ToolHelper.runR8(builder.build()));
+ return new CodeInspector(ToolHelper.runR8(builder.build(), o -> o.enableCfFrontend = true));
}
@Test
public void testDefaultInlining() throws Exception {
- DexInspector inspector = runTest(ImmutableList.of(
+ CodeInspector inspector = runTest(ImmutableList.of(
"-keep class **.Main { *; }",
"-dontobfuscate"
));
@@ -60,7 +92,7 @@
@Test
public void testNeverInline() throws Exception {
- DexInspector inspector = runTest(ImmutableList.of(
+ CodeInspector inspector = runTest(ImmutableList.of(
"-neverinline class **.A { method(); }",
"-neverinline class **.B { method(); }",
"-keep class **.Main { *; }",
@@ -85,7 +117,7 @@
@Test
public void testForceInline() throws Exception {
- DexInspector inspector = runTest(ImmutableList.of(
+ CodeInspector inspector = runTest(ImmutableList.of(
"-forceinline class **.A { int m(int, int); }",
"-forceinline class **.B { int m(int, int); }",
"-keep class **.Main { *; }",
@@ -107,7 +139,7 @@
@Test
public void testForceInlineFails() throws Exception {
try {
- DexInspector inspector = runTest(ImmutableList.of(
+ CodeInspector inspector = runTest(ImmutableList.of(
"-forceinline class **.A { int x(); }",
"-keep class **.Main { *; }",
"-dontobfuscate"
diff --git a/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java b/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java
index 6b90d88..00d385f 100644
--- a/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java
+++ b/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java
@@ -7,6 +7,7 @@
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Phi;
@@ -68,7 +69,9 @@
DexEncodedMethod method = getMethod(originalApplication, methodSig);
// Get the IR pre-optimization.
- IRCode code = method.buildIR(null, new InternalOptions(), Origin.unknown());
+ IRCode code =
+ method.buildIR(
+ null, GraphLense.getIdentityLense(), new InternalOptions(), Origin.unknown());
// Find the exit block and assert that the value is a phi merging the exceptional edge
// with the normal edge.
diff --git a/src/test/java/com/android/tools/r8/smali/ConstantFoldingTest.java b/src/test/java/com/android/tools/r8/smali/ConstantFoldingTest.java
index 3381473..ce1fd33 100644
--- a/src/test/java/com/android/tools/r8/smali/ConstantFoldingTest.java
+++ b/src/test/java/com/android/tools/r8/smali/ConstantFoldingTest.java
@@ -20,8 +20,8 @@
import com.android.tools.r8.ir.code.SingleConstant;
import com.android.tools.r8.ir.code.WideConstant;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collections;
@@ -54,7 +54,7 @@
public void run() throws Exception {
AndroidApp processdApplication = processApplication(buildApplication(builder));
assertEquals(1, getNumberOfProgramClasses(processdApplication));
- DexInspector inspector = new DexInspector(processdApplication);
+ CodeInspector inspector = new CodeInspector(processdApplication);
ClassSubject clazz = inspector.clazz(DEFAULT_CLASS_NAME);
clazz.forAllMethods(method -> {
int index = Integer.parseInt(method.getMethod().method.name.toString().substring(1));
diff --git a/src/test/java/com/android/tools/r8/smali/OutlineTest.java b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
index 371c4d1..1479add 100644
--- a/src/test/java/com/android/tools/r8/smali/OutlineTest.java
+++ b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
@@ -32,11 +32,11 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Arrays;
@@ -59,7 +59,7 @@
}
DexEncodedMethod getInvokedMethod(DexApplication application, InvokeStatic invoke) {
- DexInspector inspector = new DexInspector(application);
+ CodeInspector inspector = new CodeInspector(application);
ClassSubject clazz = inspector.clazz(invoke.getMethod().holder.toSourceString());
assertTrue(clazz.isPresent());
DexMethod invokedMethod = invoke.getMethod();
@@ -843,7 +843,7 @@
assertEquals(2, getNumberOfProgramClasses(processedApplication));
// Check that three outlining methods was created.
- DexInspector inspector = new DexInspector(processedApplication);
+ CodeInspector inspector = new CodeInspector(processedApplication);
ClassSubject clazz = inspector.clazz(OutlineOptions.CLASS_NAME);
assertTrue(clazz.isPresent());
assertEquals(3, clazz.getDexClass().directMethods().length);
diff --git a/src/test/java/com/android/tools/r8/smali/RemoveWriteOfUnusedFieldsTest.java b/src/test/java/com/android/tools/r8/smali/RemoveWriteOfUnusedFieldsTest.java
index 86c4242..0261af6 100644
--- a/src/test/java/com/android/tools/r8/smali/RemoveWriteOfUnusedFieldsTest.java
+++ b/src/test/java/com/android/tools/r8/smali/RemoveWriteOfUnusedFieldsTest.java
@@ -11,8 +11,8 @@
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import java.nio.file.Path;
import org.junit.Test;
@@ -74,7 +74,7 @@
keepMainProguardConfiguration("Test"),
options -> options.enableInlining = false);
- DexInspector inspector = new DexInspector(app);
+ CodeInspector inspector = new CodeInspector(app);
MethodSubject method = inspector.clazz("Test").method("void", "test", ImmutableList.of());
DexCode code = method.getMethod().getCode().asDexCode();
assertTrue(code.isEmptyVoidMethod());
@@ -142,7 +142,7 @@
keepMainProguardConfiguration("Test"),
options -> options.enableInlining = false);
- DexInspector inspector = new DexInspector(app);
+ CodeInspector inspector = new CodeInspector(app);
MethodSubject method = inspector.clazz("Test").method("void", "test", ImmutableList.of());
DexCode code = method.getMethod().getCode().asDexCode();
assertTrue(code.isEmptyVoidMethod());
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliBuildTest.java b/src/test/java/com/android/tools/r8/smali/SmaliBuildTest.java
index b6db233..8d780b4 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliBuildTest.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliBuildTest.java
@@ -9,8 +9,8 @@
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.origin.EmbeddedOrigin;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
import org.junit.Test;
@@ -19,7 +19,7 @@
private void checkJavaLangString(AndroidApp application, boolean present) {
try {
- DexInspector inspector = new DexInspector(application);
+ CodeInspector inspector = new CodeInspector(application);
ClassSubject clazz = inspector.clazz("java.lang.String");
assertEquals(present, clazz.isPresent());
} catch (IOException | ExecutionException e) {
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
index 6c4b884..b12c0b9 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
@@ -23,10 +23,10 @@
import com.android.tools.r8.shaking.ProguardConfiguration;
import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Collection;
@@ -112,7 +112,7 @@
}
protected DexClass getClass(DexApplication application, String className) {
- DexInspector inspector = new DexInspector(application);
+ CodeInspector inspector = new CodeInspector(application);
ClassSubject clazz = inspector.clazz(className);
assertTrue(clazz.isPresent());
return clazz.getDexClass();
@@ -124,7 +124,7 @@
protected DexClass getClass(Path appPath, String className) {
try {
- DexInspector inspector = new DexInspector(appPath);
+ CodeInspector inspector = new CodeInspector(appPath);
ClassSubject clazz = inspector.clazz(className);
assertTrue(clazz.isPresent());
return clazz.getDexClass();
@@ -135,14 +135,14 @@
protected DexEncodedMethod getMethod(Path appPath, MethodSignature signature) {
try {
- DexInspector inspector = new DexInspector(appPath);
+ CodeInspector inspector = new CodeInspector(appPath);
return getMethod(inspector, signature);
} catch (IOException | ExecutionException e) {
throw new RuntimeException(e);
}
}
- protected DexEncodedMethod getMethod(DexInspector inspector, MethodSignature signature) {
+ protected DexEncodedMethod getMethod(CodeInspector inspector, MethodSignature signature) {
return getMethod(
inspector, signature.clazz, signature.returnType, signature.name, signature.parameterTypes);
}
diff --git a/src/test/java/com/android/tools/r8/utils/ArtErrorParser.java b/src/test/java/com/android/tools/r8/utils/ArtErrorParser.java
index 406c63f..5f00fc9 100644
--- a/src/test/java/com/android/tools/r8/utils/ArtErrorParser.java
+++ b/src/test/java/com/android/tools/r8/utils/ArtErrorParser.java
@@ -4,8 +4,9 @@
package com.android.tools.r8.utils;
import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import java.util.ArrayList;
import java.util.List;
@@ -71,7 +72,7 @@
public static abstract class ArtErrorInfo {
public abstract int consumedLines();
public abstract String getMessage();
- public abstract String dump(DexInspector inspector, boolean markLocation);
+ public abstract String dump(CodeInspector inspector, boolean markLocation);
}
private static class ArtMethodError extends ArtErrorInfo {
@@ -116,7 +117,7 @@
}
@Override
- public String dump(DexInspector inspector, boolean markLocation) {
+ public String dump(CodeInspector inspector, boolean markLocation) {
ClassSubject clazz = inspector.clazz(className);
MethodSubject method = clazz.method(methodReturn, methodName, methodFormals);
DexEncodedMethod dex = method.getMethod();
diff --git a/src/test/java/com/android/tools/r8/utils/DexInspector.java b/src/test/java/com/android/tools/r8/utils/DexInspector.java
deleted file mode 100644
index eba3be8..0000000
--- a/src/test/java/com/android/tools/r8/utils/DexInspector.java
+++ /dev/null
@@ -1,1664 +0,0 @@
-// Copyright (c) 2016, 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.utils;
-
-import static org.junit.Assert.assertTrue;
-
-import com.android.tools.r8.StringResource;
-import com.android.tools.r8.code.Const4;
-import com.android.tools.r8.code.ConstString;
-import com.android.tools.r8.code.Goto;
-import com.android.tools.r8.code.IfEqz;
-import com.android.tools.r8.code.IfNez;
-import com.android.tools.r8.code.Iget;
-import com.android.tools.r8.code.IgetBoolean;
-import com.android.tools.r8.code.IgetByte;
-import com.android.tools.r8.code.IgetChar;
-import com.android.tools.r8.code.IgetObject;
-import com.android.tools.r8.code.IgetShort;
-import com.android.tools.r8.code.IgetWide;
-import com.android.tools.r8.code.Instruction;
-import com.android.tools.r8.code.InvokeDirect;
-import com.android.tools.r8.code.InvokeDirectRange;
-import com.android.tools.r8.code.InvokeInterface;
-import com.android.tools.r8.code.InvokeInterfaceRange;
-import com.android.tools.r8.code.InvokeStatic;
-import com.android.tools.r8.code.InvokeStaticRange;
-import com.android.tools.r8.code.InvokeSuper;
-import com.android.tools.r8.code.InvokeSuperRange;
-import com.android.tools.r8.code.InvokeVirtual;
-import com.android.tools.r8.code.InvokeVirtualRange;
-import com.android.tools.r8.code.Iput;
-import com.android.tools.r8.code.IputBoolean;
-import com.android.tools.r8.code.IputByte;
-import com.android.tools.r8.code.IputChar;
-import com.android.tools.r8.code.IputObject;
-import com.android.tools.r8.code.IputShort;
-import com.android.tools.r8.code.IputWide;
-import com.android.tools.r8.code.Nop;
-import com.android.tools.r8.code.ReturnVoid;
-import com.android.tools.r8.code.Sget;
-import com.android.tools.r8.code.SgetBoolean;
-import com.android.tools.r8.code.SgetByte;
-import com.android.tools.r8.code.SgetChar;
-import com.android.tools.r8.code.SgetObject;
-import com.android.tools.r8.code.SgetShort;
-import com.android.tools.r8.code.SgetWide;
-import com.android.tools.r8.code.Sput;
-import com.android.tools.r8.code.SputBoolean;
-import com.android.tools.r8.code.SputByte;
-import com.android.tools.r8.code.SputChar;
-import com.android.tools.r8.code.SputObject;
-import com.android.tools.r8.code.SputShort;
-import com.android.tools.r8.code.SputWide;
-import com.android.tools.r8.code.Throw;
-import com.android.tools.r8.dex.ApplicationReader;
-import com.android.tools.r8.graph.DexAnnotation;
-import com.android.tools.r8.graph.DexAnnotationElement;
-import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexCode;
-import com.android.tools.r8.graph.DexEncodedAnnotation;
-import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexProto;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexValue;
-import com.android.tools.r8.graph.DexValue.DexValueArray;
-import com.android.tools.r8.graph.DexValue.DexValueString;
-import com.android.tools.r8.graph.InnerClassAttribute;
-import com.android.tools.r8.naming.ClassNameMapper;
-import com.android.tools.r8.naming.ClassNamingForNameMapper;
-import com.android.tools.r8.naming.MemberNaming;
-import com.android.tools.r8.naming.MemberNaming.FieldSignature;
-import com.android.tools.r8.naming.MemberNaming.MethodSignature;
-import com.android.tools.r8.naming.MemberNaming.Signature;
-import com.android.tools.r8.naming.signature.GenericSignatureAction;
-import com.android.tools.r8.naming.signature.GenericSignatureParser;
-import com.android.tools.r8.smali.SmaliBuilder;
-import com.google.common.collect.BiMap;
-import com.google.common.collect.ImmutableList;
-import java.io.IOException;
-import java.lang.reflect.Method;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-import java.util.NoSuchElementException;
-import java.util.concurrent.ExecutionException;
-import java.util.function.BiConsumer;
-import java.util.function.BiFunction;
-import java.util.function.Consumer;
-import java.util.function.Function;
-import java.util.function.Predicate;
-
-public class DexInspector {
-
- private final DexApplication application;
- private final DexItemFactory dexItemFactory;
- private final ClassNameMapper mapping;
- private final BiMap<String, String> originalToObfuscatedMapping;
-
- private final InstructionSubjectFactory factory = new InstructionSubjectFactory();
-
- public static MethodSignature MAIN =
- new MethodSignature("main", "void", new String[]{"java.lang.String[]"});
-
- public DexInspector(Path file, String mappingFile) throws IOException, ExecutionException {
- this(Collections.singletonList(file), mappingFile);
- }
-
- public DexInspector(Path file) throws IOException, ExecutionException {
- this(Collections.singletonList(file), null);
- }
-
- public DexInspector(List<Path> files) throws IOException, ExecutionException {
- this(files, null);
- }
-
- public DexInspector(List<Path> files, String mappingFile)
- throws IOException, ExecutionException {
- if (mappingFile != null) {
- this.mapping = ClassNameMapper.mapperFromFile(Paths.get(mappingFile));
- originalToObfuscatedMapping = this.mapping.getObfuscatedToOriginalMapping().inverse();
- } else {
- this.mapping = null;
- originalToObfuscatedMapping = null;
- }
- Timing timing = new Timing("DexInspector");
- InternalOptions options = new InternalOptions();
- dexItemFactory = options.itemFactory;
- AndroidApp input = AndroidApp.builder().addProgramFiles(files).build();
- application = new ApplicationReader(input, options, timing).read();
- }
-
- public DexInspector(AndroidApp app) throws IOException, ExecutionException {
- this(
- new ApplicationReader(app, new InternalOptions(), new Timing("DexInspector"))
- .read(app.getProguardMapOutputData()));
- }
-
- public DexInspector(AndroidApp app, Consumer<InternalOptions> optionsConsumer)
- throws IOException, ExecutionException {
- this(
- new ApplicationReader(app, runOptionsConsumer(optionsConsumer), new Timing("DexInspector"))
- .read(app.getProguardMapOutputData()));
- }
-
- private static InternalOptions runOptionsConsumer(Consumer<InternalOptions> optionsConsumer) {
- InternalOptions internalOptions = new InternalOptions();
- optionsConsumer.accept(internalOptions);
- return internalOptions;
- }
-
- public DexInspector(AndroidApp app, Path proguardMap) throws IOException, ExecutionException {
- this(
- new ApplicationReader(app, new InternalOptions(), new Timing("DexInspector"))
- .read(StringResource.fromFile(proguardMap)));
- }
-
- public DexInspector(DexApplication application) {
- dexItemFactory = application.dexItemFactory;
- this.application = application;
- this.mapping = application.getProguardMap();
- originalToObfuscatedMapping =
- mapping == null ? null : mapping.getObfuscatedToOriginalMapping().inverse();
- }
-
- public DexItemFactory getFactory() {
- return dexItemFactory;
- }
-
- private DexType toDexType(String string) {
- return dexItemFactory.createType(DescriptorUtils.javaTypeToDescriptor(string));
- }
-
- private DexType toDexTypeIgnorePrimitives(String string) {
- return dexItemFactory.createType(DescriptorUtils.javaTypeToDescriptorIgnorePrimitives(string));
- }
-
- private static <S, T extends Subject> void forAll(S[] items,
- BiFunction<S, FoundClassSubject, ? extends T> constructor,
- FoundClassSubject clazz,
- Consumer<T> consumer) {
- for (S item : items) {
- consumer.accept(constructor.apply(item, clazz));
- }
- }
-
- private static <S, T extends Subject> void forAll(Iterable<S> items, Function<S, T> constructor,
- Consumer<T> consumer) {
- for (S item : items) {
- consumer.accept(constructor.apply(item));
- }
- }
-
- DexAnnotation findAnnotation(String name, DexAnnotationSet annotations) {
- for (DexAnnotation annotation : annotations.annotations) {
- DexType type = annotation.annotation.type;
- String original = mapping == null ? type.toSourceString() : mapping.originalNameOf(type);
- if (original.equals(name)) {
- return annotation;
- }
- }
- return null;
- }
-
- public String getFinalSignatureAttribute(DexAnnotationSet annotations) {
- DexAnnotation annotation =
- findAnnotation("dalvik.annotation.Signature", annotations);
- if (annotation == null) {
- return null;
- }
- assert annotation.annotation.elements.length == 1;
- DexAnnotationElement element = annotation.annotation.elements[0];
- assert element.value instanceof DexValueArray;
- StringBuilder builder = new StringBuilder();
- DexValueArray valueArray = (DexValueArray) element.value;
- for (DexValue value : valueArray.getValues()) {
- assertTrue(value instanceof DexValueString);
- DexValueString s = (DexValueString) value;
- builder.append(s.getValue());
- }
- return builder.toString();
- }
-
- public String getOriginalSignatureAttribute(
- DexAnnotationSet annotations, BiConsumer<GenericSignatureParser, String> parse) {
- String finalSignature = getFinalSignatureAttribute(annotations);
- if (finalSignature == null || mapping == null) {
- return finalSignature;
- }
-
- GenericSignatureGenerater rewriter = new GenericSignatureGenerater();
- GenericSignatureParser<String> parser = new GenericSignatureParser<>(rewriter);
- parse.accept(parser, finalSignature);
- return rewriter.getSignature();
- }
-
-
- public ClassSubject clazz(Class clazz) {
- return clazz(clazz.getTypeName());
- }
-
- /**
- * Lookup a class by name. This allows both original and obfuscated names.
- */
- public ClassSubject clazz(String name) {
- ClassNamingForNameMapper naming = null;
- if (mapping != null) {
- String obfuscated = originalToObfuscatedMapping.get(name);
- if (obfuscated != null) {
- naming = mapping.getClassNaming(obfuscated);
- name = obfuscated;
- } else {
- // Figure out if the name is an already obfuscated name.
- String original = originalToObfuscatedMapping.inverse().get(name);
- if (original != null) {
- naming = mapping.getClassNaming(name);
- }
- }
- }
- DexClass clazz = application.definitionFor(toDexTypeIgnorePrimitives(name));
- if (clazz == null) {
- return new AbsentClassSubject();
- }
- return new FoundClassSubject(clazz, naming);
- }
-
- public void forAllClasses(Consumer<FoundClassSubject> inspection) {
- forAll(application.classes(), cls -> {
- ClassSubject subject = clazz(cls.type.toSourceString());
- assert subject.isPresent();
- return (FoundClassSubject) subject;
- }, inspection);
- }
-
- public List<FoundClassSubject> allClasses() {
- ImmutableList.Builder<FoundClassSubject> builder = ImmutableList.builder();
- forAllClasses(builder::add);
- return builder.build();
- }
-
- public MethodSubject method(Method method) {
- ClassSubject clazz = clazz(method.getDeclaringClass());
- if (!clazz.isPresent()) {
- return new AbsentMethodSubject();
- }
- return clazz.method(method);
- }
-
- private String getObfuscatedTypeName(String originalTypeName) {
- String obfuscatedType = null;
- if (mapping != null) {
- obfuscatedType = originalToObfuscatedMapping.get(originalTypeName);
- }
- obfuscatedType = obfuscatedType == null ? originalTypeName : obfuscatedType;
- return obfuscatedType;
- }
-
- public abstract class Subject {
-
- public abstract boolean isPresent();
- public abstract boolean isRenamed();
- }
-
- public abstract class AnnotationSubject extends Subject {
-
- public abstract DexEncodedAnnotation getAnnotation();
- }
-
- public class FoundAnnotationSubject extends AnnotationSubject {
-
- private final DexAnnotation annotation;
-
- private FoundAnnotationSubject(DexAnnotation annotation) {
- this.annotation = annotation;
- }
-
- @Override
- public boolean isPresent() {
- return true;
- }
-
- @Override
- public boolean isRenamed() {
- return false;
- }
-
- @Override
- public DexEncodedAnnotation getAnnotation() {
- return annotation.annotation;
- }
- }
-
- public class AbsentAnnotationSubject extends AnnotationSubject {
-
- @Override
- public boolean isPresent() {
- return false;
- }
-
- @Override
- public boolean isRenamed() {
- return false;
- }
-
- @Override
- public DexEncodedAnnotation getAnnotation() {
- throw new UnsupportedOperationException();
- }
- }
-
-
- public abstract class ClassSubject extends Subject {
-
- public abstract void forAllMethods(Consumer<FoundMethodSubject> inspection);
-
- public MethodSubject method(Method method) {
- List<String> parameters = new ArrayList<>();
- for (Class<?> parameterType : method.getParameterTypes()) {
- parameters.add(parameterType.getTypeName());
- }
- return method(method.getReturnType().getTypeName(), method.getName(), parameters);
- }
-
- public abstract MethodSubject method(String returnType, String name, List<String> parameters);
-
- public MethodSubject clinit() {
- return method("void", "<clinit>", ImmutableList.of());
- }
-
- public MethodSubject init(List<String> parameters) {
- return method("void", "<init>", parameters);
- }
-
- public MethodSubject method(MethodSignature signature) {
- return method(signature.type, signature.name, ImmutableList.copyOf(signature.parameters));
- }
-
- public MethodSubject method(SmaliBuilder.MethodSignature signature) {
- return method(
- signature.returnType, signature.name, ImmutableList.copyOf(signature.parameterTypes));
- }
-
- public abstract void forAllFields(Consumer<FoundFieldSubject> inspection);
-
- public abstract FieldSubject field(String type, String name);
-
- public abstract boolean isAbstract();
-
- public abstract boolean isAnnotation();
-
- public String dumpMethods() {
- StringBuilder dump = new StringBuilder();
- forAllMethods((FoundMethodSubject method) ->
- dump.append(method.getMethod().toString())
- .append(method.getMethod().codeToString()));
- return dump.toString();
- }
-
- public abstract DexClass getDexClass();
-
- public abstract AnnotationSubject annotation(String name);
-
- public abstract String getOriginalName();
-
- public abstract String getOriginalDescriptor();
-
- public abstract String getFinalName();
-
- public abstract String getFinalDescriptor();
-
- public abstract boolean isMemberClass();
-
- public abstract boolean isLocalClass();
-
- public abstract boolean isAnonymousClass();
-
- public abstract String getOriginalSignatureAttribute();
-
- public abstract String getFinalSignatureAttribute();
- }
-
- private class AbsentClassSubject extends ClassSubject {
-
- @Override
- public boolean isPresent() {
- return false;
- }
-
- @Override
- public void forAllMethods(Consumer<FoundMethodSubject> inspection) {
- }
-
- @Override
- public MethodSubject method(String returnType, String name, List<String> parameters) {
- return new AbsentMethodSubject();
- }
-
- @Override
- public void forAllFields(Consumer<FoundFieldSubject> inspection) {
- }
-
- @Override
- public FieldSubject field(String type, String name) {
- return new AbsentFieldSubject();
- }
-
- @Override
- public boolean isAbstract() {
- return false;
- }
-
- @Override
- public boolean isAnnotation() {
- return false;
- }
-
- @Override
- public DexClass getDexClass() {
- return null;
- }
-
- @Override
- public AnnotationSubject annotation(String name) {
- return new AbsentAnnotationSubject();
- }
-
- @Override
- public String getOriginalName() {
- return null;
- }
-
- @Override
- public String getOriginalDescriptor() {
- return null;
- }
-
- @Override
- public String getFinalName() {
- return null;
- }
-
- @Override
- public String getFinalDescriptor() {
- return null;
- }
-
- @Override
- public boolean isRenamed() {
- return false;
- }
-
- @Override
- public boolean isMemberClass() {
- return false;
- }
-
- @Override
- public boolean isLocalClass() {
- return false;
- }
-
- @Override
- public boolean isAnonymousClass() {
- return false;
- }
-
- @Override
- public String getOriginalSignatureAttribute() {
- return null;
- }
-
- @Override
- public String getFinalSignatureAttribute() {
- return null;
- }
- }
-
- public class FoundClassSubject extends ClassSubject {
-
- private final DexClass dexClass;
- private final ClassNamingForNameMapper naming;
-
- private FoundClassSubject(DexClass dexClass, ClassNamingForNameMapper naming) {
- this.dexClass = dexClass;
- this.naming = naming;
- }
-
- @Override
- public boolean isPresent() {
- return true;
- }
-
- @Override
- public void forAllMethods(Consumer<FoundMethodSubject> inspection) {
- forAll(dexClass.directMethods(), FoundMethodSubject::new, this, inspection);
- forAll(dexClass.virtualMethods(), FoundMethodSubject::new, this, inspection);
- }
-
- @Override
- public MethodSubject method(String returnType, String name, List<String> parameters) {
- DexType[] parameterTypes = new DexType[parameters.size()];
- for (int i = 0; i < parameters.size(); i++) {
- parameterTypes[i] = toDexType(getObfuscatedTypeName(parameters.get(i)));
- }
- DexProto proto = dexItemFactory.createProto(toDexType(getObfuscatedTypeName(returnType)),
- parameterTypes);
- if (naming != null) {
- String[] parameterStrings = new String[parameterTypes.length];
- Signature signature = new MethodSignature(name, returnType,
- parameters.toArray(parameterStrings));
- MemberNaming methodNaming = naming.lookupByOriginalSignature(signature);
- if (methodNaming != null) {
- name = methodNaming.getRenamedName();
- }
- }
- DexMethod dexMethod =
- dexItemFactory.createMethod(dexClass.type, proto, dexItemFactory.createString(name));
- DexEncodedMethod encoded = findMethod(dexClass.directMethods(), dexMethod);
- if (encoded == null) {
- encoded = findMethod(dexClass.virtualMethods(), dexMethod);
- }
- return encoded == null ? new AbsentMethodSubject() : new FoundMethodSubject(encoded, this);
- }
-
- private DexEncodedMethod findMethod(DexEncodedMethod[] methods, DexMethod dexMethod) {
- for (DexEncodedMethod method : methods) {
- if (method.method.equals(dexMethod)) {
- return method;
- }
- }
- return null;
- }
-
- @Override
- public void forAllFields(Consumer<FoundFieldSubject> inspection) {
- forAll(dexClass.staticFields(), FoundFieldSubject::new, this, inspection);
- forAll(dexClass.instanceFields(), FoundFieldSubject::new, this, inspection);
- }
-
- @Override
- public FieldSubject field(String type, String name) {
- String obfuscatedType = getObfuscatedTypeName(type);
- MemberNaming fieldNaming = null;
- if (naming != null) {
- fieldNaming = naming.lookupByOriginalSignature(
- new FieldSignature(name, type));
- }
- String obfuscatedName = fieldNaming == null ? name : fieldNaming.getRenamedName();
-
- DexField field = dexItemFactory.createField(dexClass.type,
- toDexType(obfuscatedType), dexItemFactory.createString(obfuscatedName));
- DexEncodedField encoded = findField(dexClass.staticFields(), field);
- if (encoded == null) {
- encoded = findField(dexClass.instanceFields(), field);
- }
- return encoded == null ? new AbsentFieldSubject() : new FoundFieldSubject(encoded, this);
- }
-
- @Override
- public boolean isAbstract() {
- return dexClass.accessFlags.isAbstract();
- }
-
- @Override
- public boolean isAnnotation() {
- return dexClass.accessFlags.isAnnotation();
- }
-
- private DexEncodedField findField(DexEncodedField[] fields, DexField dexField) {
- for (DexEncodedField field : fields) {
- if (field.field.equals(dexField)) {
- return field;
- }
- }
- return null;
- }
-
- @Override
- public DexClass getDexClass() {
- return dexClass;
- }
-
- @Override
- public AnnotationSubject annotation(String name) {
- // Ensure we don't check for annotations represented as attributes.
- assert !name.endsWith("EnclosingClass")
- && !name.endsWith("EnclosingMethod")
- && !name.endsWith("InnerClass");
- DexAnnotation annotation = findAnnotation(name, dexClass.annotations);
- return annotation == null
- ? new AbsentAnnotationSubject()
- : new FoundAnnotationSubject(annotation);
- }
-
- @Override
- public String getOriginalName() {
- if (naming != null) {
- return naming.originalName;
- } else {
- return getFinalName();
- }
- }
-
- @Override
- public String getOriginalDescriptor() {
- if (naming != null) {
- return DescriptorUtils.javaTypeToDescriptor(naming.originalName);
- } else {
- return getFinalDescriptor();
- }
- }
-
- @Override
- public String getFinalName() {
- return DescriptorUtils.descriptorToJavaType(getFinalDescriptor());
- }
-
- @Override
- public String getFinalDescriptor() {
- return dexClass.type.descriptor.toString();
- }
-
- @Override
- public boolean isRenamed() {
- return naming != null && !getFinalDescriptor().equals(getOriginalDescriptor());
- }
-
- private InnerClassAttribute getInnerClassAttribute() {
- for (InnerClassAttribute innerClassAttribute : dexClass.getInnerClasses()) {
- if (dexClass.type == innerClassAttribute.getInner()) {
- return innerClassAttribute;
- }
- }
- return null;
- }
-
- @Override
- public boolean isLocalClass() {
- InnerClassAttribute innerClass = getInnerClassAttribute();
- return innerClass != null
- && innerClass.isNamed()
- && dexClass.getEnclosingMethod() != null;
- }
-
- @Override
- public boolean isMemberClass() {
- InnerClassAttribute innerClass = getInnerClassAttribute();
- return innerClass != null
- && innerClass.getOuter() != null
- && innerClass.isNamed()
- && dexClass.getEnclosingMethod() == null;
- }
-
- @Override
- public boolean isAnonymousClass() {
- InnerClassAttribute innerClass = getInnerClassAttribute();
- return innerClass != null
- && innerClass.isAnonymous()
- && dexClass.getEnclosingMethod() != null;
- }
-
- @Override
- public String getOriginalSignatureAttribute() {
- return DexInspector.this.getOriginalSignatureAttribute(
- dexClass.annotations, GenericSignatureParser::parseClassSignature);
- }
-
- @Override
- public String getFinalSignatureAttribute() {
- return DexInspector.this.getFinalSignatureAttribute(dexClass.annotations);
- }
-
- @Override
- public String toString() {
- return dexClass.toSourceString();
- }
- }
-
- public abstract class MemberSubject extends Subject {
-
- public abstract boolean isPublic();
-
- public abstract boolean isStatic();
-
- public abstract boolean isFinal();
-
- public abstract Signature getOriginalSignature();
-
- public abstract Signature getFinalSignature();
-
- public String getOriginalName() {
- Signature originalSignature = getOriginalSignature();
- return originalSignature == null ? null : originalSignature.name;
- }
-
- public String getFinalName() {
- Signature finalSignature = getFinalSignature();
- return finalSignature == null ? null : finalSignature.name;
- }
- }
-
- public abstract class MethodSubject extends MemberSubject {
-
- public abstract boolean isAbstract();
-
- public abstract boolean isBridge();
-
- public abstract boolean isInstanceInitializer();
-
- public abstract boolean isClassInitializer();
-
- public abstract String getOriginalSignatureAttribute();
-
- public abstract String getFinalSignatureAttribute();
-
- public abstract DexEncodedMethod getMethod();
-
- public Iterator<InstructionSubject> iterateInstructions() {
- return null;
- }
-
- public <T extends InstructionSubject> Iterator<T> iterateInstructions(
- Predicate<InstructionSubject> filter) {
- return null;
- }
- }
-
- public class AbsentMethodSubject extends MethodSubject {
-
- @Override
- public boolean isPresent() {
- return false;
- }
-
- @Override
- public boolean isRenamed() {
- return false;
- }
-
- @Override
- public boolean isPublic() {
- return false;
- }
-
- @Override
- public boolean isStatic() {
- return false;
- }
-
- @Override
- public boolean isFinal() {
- return false;
- }
-
- @Override
- public boolean isAbstract() {
- return false;
- }
-
- @Override
- public boolean isBridge() {
- return false;
- }
-
- @Override
- public boolean isInstanceInitializer() {
- return false;
- }
-
- @Override
- public boolean isClassInitializer() {
- return false;
- }
-
- @Override
- public DexEncodedMethod getMethod() {
- return null;
- }
-
- @Override
- public Signature getOriginalSignature() {
- return null;
- }
-
- @Override
- public Signature getFinalSignature() {
- return null;
- }
-
- @Override
- public String getOriginalSignatureAttribute() {
- return null;
- }
-
- @Override
- public String getFinalSignatureAttribute() {
- return null;
- }
- }
-
- public class FoundMethodSubject extends MethodSubject {
-
- private final FoundClassSubject clazz;
- private final DexEncodedMethod dexMethod;
-
- public FoundMethodSubject(DexEncodedMethod encoded, FoundClassSubject clazz) {
- this.clazz = clazz;
- this.dexMethod = encoded;
- }
-
- @Override
- public boolean isPresent() {
- return true;
- }
-
- @Override
- public boolean isRenamed() {
- return clazz.naming != null && !getFinalSignature().name.equals(getOriginalSignature().name);
- }
-
- @Override
- public boolean isPublic() {
- return dexMethod.accessFlags.isPublic();
- }
-
- @Override
- public boolean isStatic() {
- return dexMethod.accessFlags.isStatic();
- }
-
- @Override
- public boolean isFinal() {
- return dexMethod.accessFlags.isFinal();
- }
-
- @Override
- public boolean isAbstract() {
- return dexMethod.accessFlags.isAbstract();
- }
-
- @Override
- public boolean isBridge() {
- return dexMethod.accessFlags.isBridge();
- }
-
- @Override
- public boolean isInstanceInitializer() {
- return dexMethod.isInstanceInitializer();
- }
-
- @Override
- public boolean isClassInitializer() {
- return dexMethod.isClassInitializer();
- }
-
- @Override
- public DexEncodedMethod getMethod() {
- return dexMethod;
- }
-
- @Override
- public MethodSignature getOriginalSignature() {
- MethodSignature signature = getFinalSignature();
- if (clazz.naming == null) {
- return signature;
- }
-
- // Map the parameters and return type to original names. This is needed as the in the
- // Proguard map the names on the left side are the original names. E.g.
- //
- // X -> a
- // X method(X) -> a
- //
- // whereas the final signature is for X.a is "a (a)"
- String[] OriginalParameters = new String[signature.parameters.length];
- for (int i = 0; i < OriginalParameters.length; i++) {
- String obfuscated = signature.parameters[i];
- String original = originalToObfuscatedMapping.inverse().get(obfuscated);
- OriginalParameters[i] = original != null ? original : obfuscated;
- }
- String obfuscatedReturnType = signature.type;
- String originalReturnType = originalToObfuscatedMapping.inverse().get(obfuscatedReturnType);
- String returnType = originalReturnType != null ? originalReturnType : obfuscatedReturnType;
-
- MethodSignature lookupSignature =
- new MethodSignature(signature.name, returnType, OriginalParameters);
-
- MemberNaming memberNaming = clazz.naming.lookup(lookupSignature);
- return memberNaming != null
- ? (MethodSignature) memberNaming.getOriginalSignature()
- : signature;
- }
-
- @Override
- public MethodSignature getFinalSignature() {
- return MemberNaming.MethodSignature.fromDexMethod(dexMethod.method);
- }
-
- @Override
- public String getOriginalSignatureAttribute() {
- return DexInspector.this.getOriginalSignatureAttribute(
- dexMethod.annotations, GenericSignatureParser::parseMethodSignature);
- }
-
- @Override
- public String getFinalSignatureAttribute() {
- return DexInspector.this.getFinalSignatureAttribute(dexMethod.annotations);
- }
-
- @Override
- public Iterator<InstructionSubject> iterateInstructions() {
- return new InstructionIterator(this);
- }
-
- @Override
- public <T extends InstructionSubject> Iterator<T> iterateInstructions(
- Predicate<InstructionSubject> filter) {
- return new FilteredInstructionIterator<>(this, filter);
- }
-
- @Override
- public String toString() {
- return dexMethod.toSourceString();
- }
- }
-
- public abstract class FieldSubject extends MemberSubject {
- public abstract boolean hasExplicitStaticValue();
-
- public abstract DexEncodedField getField();
-
- public abstract DexValue getStaticValue();
-
- public abstract boolean isRenamed();
-
- public abstract String getOriginalSignatureAttribute();
-
- public abstract String getFinalSignatureAttribute();
- }
-
- public class AbsentFieldSubject extends FieldSubject {
-
- @Override
- public boolean isPublic() {
- return false;
- }
-
- @Override
- public boolean isStatic() {
- return false;
- }
-
- @Override
- public boolean isFinal() {
- return false;
- }
-
- @Override
- public boolean isPresent() {
- return false;
- }
-
- @Override
- public boolean isRenamed() {
- return false;
- }
-
- @Override
- public Signature getOriginalSignature() {
- return null;
- }
-
- @Override
- public Signature getFinalSignature() {
- return null;
- }
-
- @Override
- public boolean hasExplicitStaticValue() {
- return false;
- }
-
- @Override
- public DexValue getStaticValue() {
- return null;
- }
-
- @Override
- public DexEncodedField getField() {
- return null;
- }
-
- @Override
- public String getOriginalSignatureAttribute() {
- return null;
- }
-
- @Override
- public String getFinalSignatureAttribute() {
- return null;
- }
- }
-
- public class FoundFieldSubject extends FieldSubject {
-
- private final FoundClassSubject clazz;
- private final DexEncodedField dexField;
-
- public FoundFieldSubject(DexEncodedField dexField, FoundClassSubject clazz) {
- this.clazz = clazz;
- this.dexField = dexField;
- }
-
- @Override
- public boolean isPublic() {
- return dexField.accessFlags.isPublic();
- }
-
- @Override
- public boolean isStatic() {
- return dexField.accessFlags.isStatic();
- }
-
- @Override
- public boolean isFinal() {
- return dexField.accessFlags.isFinal();
- }
-
- @Override
- public boolean isPresent() {
- return true;
- }
-
- @Override
- public boolean isRenamed() {
- return clazz.naming != null && !getFinalSignature().name.equals(getOriginalSignature().name);
- }
-
-
- public TypeSubject type() {
- return new TypeSubject(dexField.field.type);
- }
-
- @Override
- public FieldSignature getOriginalSignature() {
- FieldSignature signature = getFinalSignature();
- if (clazz.naming == null) {
- return signature;
- }
-
- // Map the type to the original name. This is needed as the in the Proguard map the
- // names on the left side are the original names. E.g.
- //
- // X -> a
- // X field -> a
- //
- // whereas the final signature is for X.a is "a a"
- String obfuscatedType = signature.type;
- String originalType = originalToObfuscatedMapping.inverse().get(obfuscatedType);
- String fieldType = originalType != null ? originalType : obfuscatedType;
-
- FieldSignature lookupSignature = new FieldSignature(signature.name, fieldType);
-
- MemberNaming memberNaming = clazz.naming.lookup(lookupSignature);
- return memberNaming != null
- ? (FieldSignature) memberNaming.getOriginalSignature()
- : signature;
- }
-
- @Override
- public FieldSignature getFinalSignature() {
- return MemberNaming.FieldSignature.fromDexField(dexField.field);
- }
-
- @Override
- public boolean hasExplicitStaticValue() {
- return isStatic() && dexField.hasExplicitStaticValue();
- }
-
- @Override
- public DexValue getStaticValue() {
- return dexField.getStaticValue();
- }
-
- @Override
- public DexEncodedField getField() {
- return dexField;
- }
-
- @Override
- public String getOriginalSignatureAttribute() {
- return DexInspector.this.getOriginalSignatureAttribute(
- dexField.annotations, GenericSignatureParser::parseFieldSignature);
- }
-
- @Override
- public String getFinalSignatureAttribute() {
- return DexInspector.this.getFinalSignatureAttribute(dexField.annotations);
- }
-
- @Override
- public String toString() {
- return dexField.toSourceString();
- }
- }
-
- public class TypeSubject extends Subject {
-
- private final DexType dexType;
-
- TypeSubject(DexType dexType) {
- this.dexType = dexType;
- }
-
- @Override
- public boolean isPresent() {
- return true;
- }
-
- @Override
- public boolean isRenamed() {
- return false;
- }
-
- public boolean is(String type) {
- return dexType.equals(toDexType(type));
- }
-
- public String toString() {
- return dexType.toSourceString();
- }
- }
-
- private class InstructionSubjectFactory {
-
- InstructionSubject create(Instruction instruction) {
- if (isInvoke(instruction)) {
- return new InvokeInstructionSubject(this, instruction);
- } else if (isFieldAccess(instruction)) {
- return new FieldAccessInstructionSubject(this, instruction);
- } else {
- return new InstructionSubject(this, instruction);
- }
- }
-
- boolean isInvoke(Instruction instruction) {
- return isInvokeVirtual(instruction)
- || isInvokeInterface(instruction)
- || isInvokeDirect(instruction)
- || isInvokeSuper(instruction)
- || isInvokeStatic(instruction);
- }
-
- boolean isInvokeVirtual(Instruction instruction) {
- return instruction instanceof InvokeVirtual || instruction instanceof InvokeVirtualRange;
- }
-
- boolean isInvokeInterface(Instruction instruction) {
- return instruction instanceof InvokeInterface || instruction instanceof InvokeInterfaceRange;
- }
-
- boolean isInvokeDirect(Instruction instruction) {
- return instruction instanceof InvokeDirect || instruction instanceof InvokeDirectRange;
- }
-
- boolean isInvokeSuper(Instruction instruction) {
- return instruction instanceof InvokeSuper || instruction instanceof InvokeSuperRange;
- }
-
- boolean isInvokeStatic(Instruction instruction) {
- return instruction instanceof InvokeStatic || instruction instanceof InvokeStaticRange;
- }
-
- boolean isNop(Instruction instruction) {
- return instruction instanceof Nop;
- }
-
- boolean isGoto(Instruction instruction) {
- return instruction instanceof Goto;
- }
-
- boolean isReturnVoid(Instruction instruction) {
- return instruction instanceof ReturnVoid;
- }
-
- boolean isConst4(Instruction instruction) {
- return instruction instanceof Const4;
- }
-
- boolean isThrow(Instruction instruction) {
- return instruction instanceof Throw;
- }
-
- boolean isConstString(Instruction instruction) {
- return instruction instanceof ConstString;
- }
-
- boolean isConstString(Instruction instruction, String value) {
- return instruction instanceof ConstString
- && ((ConstString) instruction).BBBB.toSourceString().equals(value);
- }
-
- boolean isIfNez(Instruction instruction) {
- return instruction instanceof IfNez;
- }
-
- boolean isIfEqz(Instruction instruction) {
- return instruction instanceof IfEqz;
- }
-
- boolean isFieldAccess(Instruction instruction) {
- return isInstanceGet(instruction)
- || isInstancePut(instruction)
- || isStaticGet(instruction)
- || isStaticSet(instruction);
- }
-
- boolean isInstanceGet(Instruction instruction) {
- return instruction instanceof Iget
- || instruction instanceof IgetBoolean
- || instruction instanceof IgetByte
- || instruction instanceof IgetShort
- || instruction instanceof IgetChar
- || instruction instanceof IgetWide
- || instruction instanceof IgetObject;
- }
-
- boolean isInstancePut(Instruction instruction) {
- return instruction instanceof Iput
- || instruction instanceof IputBoolean
- || instruction instanceof IputByte
- || instruction instanceof IputShort
- || instruction instanceof IputChar
- || instruction instanceof IputWide
- || instruction instanceof IputObject;
- }
-
- boolean isStaticGet(Instruction instruction) {
- return instruction instanceof Sget
- || instruction instanceof SgetBoolean
- || instruction instanceof SgetByte
- || instruction instanceof SgetShort
- || instruction instanceof SgetChar
- || instruction instanceof SgetWide
- || instruction instanceof SgetObject;
- }
-
- boolean isStaticSet(Instruction instruction) {
- return instruction instanceof Sput
- || instruction instanceof SputBoolean
- || instruction instanceof SputByte
- || instruction instanceof SputShort
- || instruction instanceof SputChar
- || instruction instanceof SputWide
- || instruction instanceof SputObject;
- }
- }
-
- public class InstructionSubject {
-
- protected final InstructionSubjectFactory factory;
- protected final Instruction instruction;
-
- protected InstructionSubject(InstructionSubjectFactory factory, Instruction instruction) {
- this.factory = factory;
- this.instruction = instruction;
- }
-
- public boolean isInvoke() {
- return factory.isInvoke(instruction);
- }
-
- public boolean isFieldAccess() {
- return factory.isFieldAccess(instruction);
- }
-
- public boolean isInvokeVirtual() {
- return factory.isInvokeVirtual(instruction);
- }
-
- public boolean isInvokeInterface() {
- return factory.isInvokeInterface(instruction);
- }
-
- public boolean isInvokeDirect() {
- return factory.isInvokeDirect(instruction);
- }
-
- public boolean isInvokeSuper() {
- return factory.isInvokeSuper(instruction);
- }
-
- public boolean isInvokeStatic() {
- return factory.isInvokeStatic(instruction);
- }
-
- boolean isFieldAccess(Instruction instruction) {
- return factory.isFieldAccess(instruction);
- }
-
- public boolean isNop() {
- return factory.isNop(instruction);
- }
-
- public boolean isConstString() {
- return factory.isConstString(instruction);
- }
-
- public boolean isConstString(String value) {
- return factory.isConstString(instruction, value);
- }
-
- public boolean isGoto() {
- return factory.isGoto(instruction);
- }
-
- public boolean isIfNez() {
- return factory.isIfNez(instruction);
- }
-
- public boolean isIfEqz() {
- return factory.isIfEqz(instruction);
- }
-
- public boolean isReturnVoid() {
- return factory.isReturnVoid(instruction);
- }
-
- public boolean isConst4() {
- return factory.isConst4(instruction);
- }
-
- public boolean isThrow() {
- return factory.isThrow(instruction);
- }
- }
-
- public class InvokeInstructionSubject extends InstructionSubject {
-
- InvokeInstructionSubject(InstructionSubjectFactory factory, Instruction instruction) {
- super(factory, instruction);
- assert isInvoke();
- }
-
- public TypeSubject holder() {
- return new TypeSubject(invokedMethod().getHolder());
- }
-
- public DexMethod invokedMethod() {
- if (instruction instanceof InvokeVirtual) {
- return ((InvokeVirtual) instruction).getMethod();
- }
- if (instruction instanceof InvokeVirtualRange) {
- return ((InvokeVirtualRange) instruction).getMethod();
- }
- if (instruction instanceof InvokeInterface) {
- return ((InvokeInterface) instruction).getMethod();
- }
- if (instruction instanceof InvokeInterfaceRange) {
- return ((InvokeInterfaceRange) instruction).getMethod();
- }
- if (instruction instanceof InvokeDirect) {
- return ((InvokeDirect) instruction).getMethod();
- }
- if (instruction instanceof InvokeDirectRange) {
- return ((InvokeDirectRange) instruction).getMethod();
- }
- if (instruction instanceof InvokeSuper) {
- return ((InvokeSuper) instruction).getMethod();
- }
- if (instruction instanceof InvokeSuperRange) {
- return ((InvokeSuperRange) instruction).getMethod();
- }
- if (instruction instanceof InvokeDirect) {
- return ((InvokeDirect) instruction).getMethod();
- }
- if (instruction instanceof InvokeDirectRange) {
- return ((InvokeDirectRange) instruction).getMethod();
- }
- if (instruction instanceof InvokeStatic) {
- return ((InvokeStatic) instruction).getMethod();
- }
- if (instruction instanceof InvokeStaticRange) {
- return ((InvokeStaticRange) instruction).getMethod();
- }
- assert false;
- return null;
- }
- }
-
- public class FieldAccessInstructionSubject extends InstructionSubject {
-
- FieldAccessInstructionSubject(InstructionSubjectFactory factory, Instruction instruction) {
- super(factory, instruction);
- assert isFieldAccess();
- }
-
- public TypeSubject holder() {
- return new TypeSubject(accessedField().getHolder());
- }
-
- public DexField accessedField() {
- if (instruction instanceof Iget) {
- return ((Iget) instruction).getField();
- }
- if (instruction instanceof IgetBoolean) {
- return ((IgetBoolean) instruction).getField();
- }
- if (instruction instanceof IgetByte) {
- return ((IgetByte) instruction).getField();
- }
- if (instruction instanceof IgetShort) {
- return ((IgetShort) instruction).getField();
- }
- if (instruction instanceof IgetChar) {
- return ((IgetChar) instruction).getField();
- }
- if (instruction instanceof IgetWide) {
- return ((IgetWide) instruction).getField();
- }
- if (instruction instanceof IgetObject) {
- return ((IgetObject) instruction).getField();
- }
- if (instruction instanceof Iput) {
- return ((Iput) instruction).getField();
- }
- if (instruction instanceof IputBoolean) {
- return ((IputBoolean) instruction).getField();
- }
- if (instruction instanceof IputByte) {
- return ((IputByte) instruction).getField();
- }
- if (instruction instanceof IputShort) {
- return ((IputShort) instruction).getField();
- }
- if (instruction instanceof IputChar) {
- return ((IputChar) instruction).getField();
- }
- if (instruction instanceof IputWide) {
- return ((IputWide) instruction).getField();
- }
- if (instruction instanceof IputObject) {
- return ((IputObject) instruction).getField();
- }
- if (instruction instanceof Sget) {
- return ((Sget) instruction).getField();
- }
- if (instruction instanceof SgetBoolean) {
- return ((SgetBoolean) instruction).getField();
- }
- if (instruction instanceof SgetByte) {
- return ((SgetByte) instruction).getField();
- }
- if (instruction instanceof SgetShort) {
- return ((SgetShort) instruction).getField();
- }
- if (instruction instanceof SgetChar) {
- return ((SgetChar) instruction).getField();
- }
- if (instruction instanceof SgetWide) {
- return ((SgetWide) instruction).getField();
- }
- if (instruction instanceof SgetObject) {
- return ((SgetObject) instruction).getField();
- }
- if (instruction instanceof Sput) {
- return ((Sput) instruction).getField();
- }
- if (instruction instanceof SputBoolean) {
- return ((SputBoolean) instruction).getField();
- }
- if (instruction instanceof SputByte) {
- return ((SputByte) instruction).getField();
- }
- if (instruction instanceof SputShort) {
- return ((SputShort) instruction).getField();
- }
- if (instruction instanceof SputChar) {
- return ((SputChar) instruction).getField();
- }
- if (instruction instanceof SputWide) {
- return ((SputWide) instruction).getField();
- }
- if (instruction instanceof SputObject) {
- return ((SputObject) instruction).getField();
- }
- assert false;
- return null;
- }
- }
-
- private class InstructionIterator implements Iterator<InstructionSubject> {
-
- private final DexCode code;
- private int index;
-
- InstructionIterator(MethodSubject method) {
- assert method.isPresent();
- this.code = method.getMethod().getCode().asDexCode();
- this.index = 0;
- }
-
- @Override
- public boolean hasNext() {
- return index < code.instructions.length;
- }
-
- @Override
- public InstructionSubject next() {
- if (index == code.instructions.length) {
- throw new NoSuchElementException();
- }
- return factory.create(code.instructions[index++]);
- }
- }
-
- private class FilteredInstructionIterator<T extends InstructionSubject> implements Iterator<T> {
-
- private final InstructionIterator iterator;
- private final Predicate<InstructionSubject> predicate;
- private InstructionSubject pendingNext = null;
-
- FilteredInstructionIterator(MethodSubject method, Predicate<InstructionSubject> predicate) {
- this.iterator = new InstructionIterator(method);
- this.predicate = predicate;
- hasNext();
- }
-
- @Override
- public boolean hasNext() {
- if (pendingNext == null) {
- while (iterator.hasNext()) {
- pendingNext = iterator.next();
- if (predicate.test(pendingNext)) {
- break;
- }
- pendingNext = null;
- }
- }
- return pendingNext != null;
- }
-
- @Override
- public T next() {
- hasNext();
- if (pendingNext == null) {
- throw new NoSuchElementException();
- }
- // We cannot tell if the provided predicate will only match instruction subjects of type T.
- @SuppressWarnings("unchecked")
- T result = (T) pendingNext;
- pendingNext = null;
- return result;
- }
- }
-
- // Build the generic signature using the current mapping if any.
- class GenericSignatureGenerater implements GenericSignatureAction<String> {
-
- private StringBuilder signature;
-
- public String getSignature() {
- return signature.toString();
- }
-
- @Override
- public void parsedSymbol(char symbol) {
- signature.append(symbol);
- }
-
- @Override
- public void parsedIdentifier(String identifier) {
- signature.append(identifier);
- }
-
- @Override
- public String parsedTypeName(String name) {
- String type = name;
- if (originalToObfuscatedMapping != null) {
- String original = originalToObfuscatedMapping.inverse().get(name);
- type = original != null ? original : name;
- }
- signature.append(type);
- return type;
- }
-
- @Override
- public String parsedInnerTypeName(String enclosingType, String name) {
- String type;
- if (originalToObfuscatedMapping != null) {
- // The enclosingType has already been mapped if a mapping is present.
- String minifiedEnclosing = originalToObfuscatedMapping.get(enclosingType);
- type = originalToObfuscatedMapping.inverse().get(minifiedEnclosing + "$" + name);
- if (type != null) {
- assert type.startsWith(enclosingType + "$");
- name = type.substring(enclosingType.length() + 1);
- }
- } else {
- type = enclosingType + "$" + name;
- }
- signature.append(name);
- return type;
- }
-
- @Override
- public void start() {
- signature = new StringBuilder();
- }
-
- @Override
- public void stop() {
- // nothing to do
- }
- }
-}
diff --git a/src/test/java/com/android/tools/r8/utils/Smali.java b/src/test/java/com/android/tools/r8/utils/Smali.java
index 68de752..ac46dfc 100644
--- a/src/test/java/com/android/tools/r8/utils/Smali.java
+++ b/src/test/java/com/android/tools/r8/utils/Smali.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.dex.ApplicationReader;
import com.android.tools.r8.dex.ApplicationWriter;
import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.origin.Origin;
import com.google.common.collect.ImmutableList;
@@ -110,7 +111,14 @@
app, options, new Timing("smali")).read(executor);
ApplicationWriter writer =
new ApplicationWriter(
- dexApp, options, null, null, NamingLens.getIdentityLens(), null, null);
+ dexApp,
+ options,
+ null,
+ null,
+ GraphLense.getIdentityLense(),
+ NamingLens.getIdentityLens(),
+ null,
+ null);
writer.write(executor);
return consumer.contents;
} finally {
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentAnnotationSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentAnnotationSubject.java
new file mode 100644
index 0000000..a82d9b0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentAnnotationSubject.java
@@ -0,0 +1,25 @@
+// 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.utils.codeinspector;
+
+import com.android.tools.r8.graph.DexEncodedAnnotation;
+
+public class AbsentAnnotationSubject extends AnnotationSubject {
+
+ @Override
+ public boolean isPresent() {
+ return false;
+ }
+
+ @Override
+ public boolean isRenamed() {
+ return false;
+ }
+
+ @Override
+ public DexEncodedAnnotation getAnnotation() {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
new file mode 100644
index 0000000..167bc8d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
@@ -0,0 +1,108 @@
+// 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.utils.codeinspector;
+
+import com.android.tools.r8.graph.DexClass;
+import java.util.List;
+import java.util.function.Consumer;
+
+public class AbsentClassSubject extends ClassSubject {
+
+ @Override
+ public boolean isPresent() {
+ return false;
+ }
+
+ @Override
+ public void forAllMethods(Consumer<FoundMethodSubject> inspection) {}
+
+ @Override
+ public MethodSubject method(String returnType, String name, List<String> parameters) {
+ return new AbsentMethodSubject();
+ }
+
+ @Override
+ public void forAllFields(Consumer<FoundFieldSubject> inspection) {}
+
+ @Override
+ public FieldSubject field(String type, String name) {
+ return new AbsentFieldSubject();
+ }
+
+ @Override
+ public boolean isAbstract() {
+ return false;
+ }
+
+ @Override
+ public boolean isAnnotation() {
+ return false;
+ }
+
+ @Override
+ public DexClass getDexClass() {
+ return null;
+ }
+
+ @Override
+ public AnnotationSubject annotation(String name) {
+ return new AbsentAnnotationSubject();
+ }
+
+ @Override
+ public String getOriginalName() {
+ return null;
+ }
+
+ @Override
+ public String getOriginalDescriptor() {
+ return null;
+ }
+
+ @Override
+ public String getFinalName() {
+ return null;
+ }
+
+ @Override
+ public String getFinalDescriptor() {
+ return null;
+ }
+
+ @Override
+ public boolean isRenamed() {
+ return false;
+ }
+
+ @Override
+ public boolean isMemberClass() {
+ return false;
+ }
+
+ @Override
+ public boolean isLocalClass() {
+ return false;
+ }
+
+ @Override
+ public boolean isAnonymousClass() {
+ return false;
+ }
+
+ @Override
+ public boolean isSynthesizedJavaLambdaClass() {
+ return false;
+ }
+
+ @Override
+ public String getOriginalSignatureAttribute() {
+ return null;
+ }
+
+ @Override
+ public String getFinalSignatureAttribute() {
+ return null;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentFieldSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentFieldSubject.java
new file mode 100644
index 0000000..d4409d1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentFieldSubject.java
@@ -0,0 +1,72 @@
+// 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.utils.codeinspector;
+
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.naming.MemberNaming.Signature;
+
+public class AbsentFieldSubject extends FieldSubject {
+
+ @Override
+ public boolean isPublic() {
+ return false;
+ }
+
+ @Override
+ public boolean isStatic() {
+ return false;
+ }
+
+ @Override
+ public boolean isFinal() {
+ return false;
+ }
+
+ @Override
+ public boolean isPresent() {
+ return false;
+ }
+
+ @Override
+ public boolean isRenamed() {
+ return false;
+ }
+
+ @Override
+ public Signature getOriginalSignature() {
+ return null;
+ }
+
+ @Override
+ public Signature getFinalSignature() {
+ return null;
+ }
+
+ @Override
+ public boolean hasExplicitStaticValue() {
+ return false;
+ }
+
+ @Override
+ public DexValue getStaticValue() {
+ return null;
+ }
+
+ @Override
+ public DexEncodedField getField() {
+ return null;
+ }
+
+ @Override
+ public String getOriginalSignatureAttribute() {
+ return null;
+ }
+
+ @Override
+ public String getFinalSignatureAttribute() {
+ return null;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java
new file mode 100644
index 0000000..1396be9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java
@@ -0,0 +1,81 @@
+// 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.utils.codeinspector;
+
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.naming.MemberNaming.Signature;
+
+public class AbsentMethodSubject extends MethodSubject {
+
+ @Override
+ public boolean isPresent() {
+ return false;
+ }
+
+ @Override
+ public boolean isRenamed() {
+ return false;
+ }
+
+ @Override
+ public boolean isPublic() {
+ return false;
+ }
+
+ @Override
+ public boolean isStatic() {
+ return false;
+ }
+
+ @Override
+ public boolean isFinal() {
+ return false;
+ }
+
+ @Override
+ public boolean isAbstract() {
+ return false;
+ }
+
+ @Override
+ public boolean isBridge() {
+ return false;
+ }
+
+ @Override
+ public boolean isInstanceInitializer() {
+ return false;
+ }
+
+ @Override
+ public boolean isClassInitializer() {
+ return false;
+ }
+
+ @Override
+ public DexEncodedMethod getMethod() {
+ return null;
+ }
+
+ @Override
+ public Signature getOriginalSignature() {
+ return null;
+ }
+
+ @Override
+ public Signature getFinalSignature() {
+ return null;
+ }
+
+ @Override
+ public String getOriginalSignatureAttribute() {
+ return null;
+ }
+
+ @Override
+ public String getFinalSignatureAttribute() {
+ return null;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AnnotationSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AnnotationSubject.java
new file mode 100644
index 0000000..4bffcca
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AnnotationSubject.java
@@ -0,0 +1,12 @@
+// 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.utils.codeinspector;
+
+import com.android.tools.r8.graph.DexEncodedAnnotation;
+
+public abstract class AnnotationSubject extends Subject {
+
+ public abstract DexEncodedAnnotation getAnnotation();
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionIterator.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionIterator.java
new file mode 100644
index 0000000..0a3263a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionIterator.java
@@ -0,0 +1,33 @@
+// 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.utils.codeinspector;
+
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.graph.Code;
+import java.util.Iterator;
+
+class CfInstructionIterator implements InstructionIterator {
+
+ private final CodeInspector codeInspector;
+ private final Iterator<CfInstruction> iterator;
+
+ CfInstructionIterator(CodeInspector codeInspector, MethodSubject method) {
+ this.codeInspector = codeInspector;
+ assert method.isPresent();
+ Code code = method.getMethod().getCode();
+ assert code != null && code.isCfCode();
+ iterator = code.asCfCode().getInstructions().iterator();
+ }
+
+ @Override
+ public boolean hasNext() {
+ return iterator.hasNext();
+ }
+
+ @Override
+ public InstructionSubject next() {
+ return codeInspector.createInstructionSubject(iterator.next());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
new file mode 100644
index 0000000..fa00432
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
@@ -0,0 +1,134 @@
+// 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.utils.codeinspector;
+
+import com.android.tools.r8.cf.code.CfConstNull;
+import com.android.tools.r8.cf.code.CfConstString;
+import com.android.tools.r8.cf.code.CfFieldInstruction;
+import com.android.tools.r8.cf.code.CfGoto;
+import com.android.tools.r8.cf.code.CfIf;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfInvokeDynamic;
+import com.android.tools.r8.cf.code.CfLabel;
+import com.android.tools.r8.cf.code.CfNew;
+import com.android.tools.r8.cf.code.CfNop;
+import com.android.tools.r8.cf.code.CfPosition;
+import com.android.tools.r8.cf.code.CfReturnVoid;
+import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfThrow;
+import org.objectweb.asm.Opcodes;
+
+public class CfInstructionSubject implements InstructionSubject {
+ protected final CfInstruction instruction;
+
+ public CfInstructionSubject(CfInstruction instruction) {
+ this.instruction = instruction;
+ }
+
+ @Override
+ public boolean isFieldAccess() {
+ return instruction instanceof CfFieldInstruction;
+ }
+
+ @Override
+ public boolean isInvokeVirtual() {
+ return instruction instanceof CfInvoke
+ && ((CfInvoke) instruction).getOpcode() == Opcodes.INVOKEVIRTUAL;
+ }
+
+ @Override
+ public boolean isInvokeInterface() {
+ return instruction instanceof CfInvoke
+ && ((CfInvoke) instruction).getOpcode() == Opcodes.INVOKEINTERFACE;
+ }
+
+ @Override
+ public boolean isInvokeStatic() {
+ return instruction instanceof CfInvoke
+ && ((CfInvoke) instruction).getOpcode() == Opcodes.INVOKESTATIC;
+ }
+
+ @Override
+ public boolean isNop() {
+ return instruction instanceof CfNop;
+ }
+
+ @Override
+ public boolean isConstString(JumboStringMode jumboStringMode) {
+ return instruction instanceof CfConstString;
+ }
+
+ @Override
+ public boolean isConstString(String value, JumboStringMode jumboStringMode) {
+ return isConstString(jumboStringMode)
+ && ((CfConstString) instruction).getString().toSourceString().equals(value);
+ }
+
+ @Override
+ public boolean isGoto() {
+ return instruction instanceof CfGoto;
+ }
+
+ @Override
+ public boolean isIfNez() {
+ return instruction instanceof CfIf && ((CfIf) instruction).getOpcode() == Opcodes.IFNE;
+ }
+
+ @Override
+ public boolean isIfEqz() {
+ return instruction instanceof CfIf && ((CfIf) instruction).getOpcode() == Opcodes.IFEQ;
+ }
+
+ @Override
+ public boolean isReturnVoid() {
+ return instruction instanceof CfReturnVoid;
+ }
+
+ @Override
+ public boolean isThrow() {
+ return instruction instanceof CfThrow;
+ }
+
+ @Override
+ public boolean isInvoke() {
+ return instruction instanceof CfInvoke || instruction instanceof CfInvokeDynamic;
+ }
+
+ @Override
+ public boolean isNewInstance() {
+ return instruction instanceof CfNew;
+ }
+
+ public boolean isInvokeSpecial() {
+ return instruction instanceof CfInvoke
+ && ((CfInvoke) instruction).getOpcode() == Opcodes.INVOKESPECIAL;
+ }
+
+ public boolean isInvokeDynamic() {
+ return instruction instanceof CfInvokeDynamic;
+ }
+
+ public boolean isLabel() {
+ return instruction instanceof CfLabel;
+ }
+
+ public boolean isPosition() {
+ return instruction instanceof CfPosition;
+ }
+
+ public boolean isStackInstruction(CfStackInstruction.Opcode opcode) {
+ return instruction instanceof CfStackInstruction
+ && ((CfStackInstruction) instruction).getOpcode() == opcode;
+ }
+
+ public boolean isConstNull() {
+ return instruction instanceof CfConstNull;
+ }
+
+ public boolean isIfNull() {
+ return instruction instanceof CfIf && ((CfIf) instruction).getOpcode() == Opcodes.IFNULL;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
new file mode 100644
index 0000000..bbb6213
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
@@ -0,0 +1,86 @@
+// 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.utils.codeinspector;
+
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.smali.SmaliBuilder;
+import com.google.common.collect.ImmutableList;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+public abstract class ClassSubject extends Subject {
+
+ public abstract void forAllMethods(Consumer<FoundMethodSubject> inspection);
+
+ public MethodSubject method(Method method) {
+ List<String> parameters = new ArrayList<>();
+ for (Class<?> parameterType : method.getParameterTypes()) {
+ parameters.add(parameterType.getTypeName());
+ }
+ return method(method.getReturnType().getTypeName(), method.getName(), parameters);
+ }
+
+ public abstract MethodSubject method(String returnType, String name, List<String> parameters);
+
+ public MethodSubject clinit() {
+ return method("void", "<clinit>", ImmutableList.of());
+ }
+
+ public MethodSubject init(List<String> parameters) {
+ return method("void", "<init>", parameters);
+ }
+
+ public MethodSubject method(MethodSignature signature) {
+ return method(signature.type, signature.name, ImmutableList.copyOf(signature.parameters));
+ }
+
+ public MethodSubject method(SmaliBuilder.MethodSignature signature) {
+ return method(
+ signature.returnType, signature.name, ImmutableList.copyOf(signature.parameterTypes));
+ }
+
+ public abstract void forAllFields(Consumer<FoundFieldSubject> inspection);
+
+ public abstract FieldSubject field(String type, String name);
+
+ public abstract boolean isAbstract();
+
+ public abstract boolean isAnnotation();
+
+ public String dumpMethods() {
+ StringBuilder dump = new StringBuilder();
+ forAllMethods(
+ (FoundMethodSubject method) ->
+ dump.append(method.getMethod().toString()).append(method.getMethod().codeToString()));
+ return dump.toString();
+ }
+
+ public abstract DexClass getDexClass();
+
+ public abstract AnnotationSubject annotation(String name);
+
+ public abstract String getOriginalName();
+
+ public abstract String getOriginalDescriptor();
+
+ public abstract String getFinalName();
+
+ public abstract String getFinalDescriptor();
+
+ public abstract boolean isMemberClass();
+
+ public abstract boolean isLocalClass();
+
+ public abstract boolean isAnonymousClass();
+
+ public abstract boolean isSynthesizedJavaLambdaClass();
+
+ public abstract String getOriginalSignatureAttribute();
+
+ public abstract String getFinalSignatureAttribute();
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
new file mode 100644
index 0000000..ecc78c6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -0,0 +1,350 @@
+// Copyright (c) 2016, 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.utils.codeinspector;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.StringResource;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexAnnotationElement;
+import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexItemFactory;
+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.DexValueString;
+import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.naming.ClassNamingForNameMapper;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.naming.signature.GenericSignatureAction;
+import com.android.tools.r8.naming.signature.GenericSignatureParser;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject.JumboStringMode;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+public class CodeInspector {
+
+ private final DexApplication application;
+ final DexItemFactory dexItemFactory;
+ private final ClassNameMapper mapping;
+ final BiMap<String, String> originalToObfuscatedMapping;
+
+ public static MethodSignature MAIN =
+ new MethodSignature("main", "void", new String[] {"java.lang.String[]"});
+
+ public CodeInspector(Path file, String mappingFile) throws IOException, ExecutionException {
+ this(Collections.singletonList(file), mappingFile);
+ }
+
+ public CodeInspector(Path file) throws IOException, ExecutionException {
+ this(Collections.singletonList(file), null);
+ }
+
+ public CodeInspector(List<Path> files) throws IOException, ExecutionException {
+ this(files, null);
+ }
+
+ public CodeInspector(List<Path> files, String mappingFile) throws IOException, ExecutionException {
+ if (mappingFile != null) {
+ this.mapping = ClassNameMapper.mapperFromFile(Paths.get(mappingFile));
+ originalToObfuscatedMapping = this.mapping.getObfuscatedToOriginalMapping().inverse();
+ } else {
+ this.mapping = null;
+ originalToObfuscatedMapping = null;
+ }
+ Timing timing = new Timing("CodeInspector");
+ InternalOptions options = new InternalOptions();
+ dexItemFactory = options.itemFactory;
+ AndroidApp input = AndroidApp.builder().addProgramFiles(files).build();
+ application = new ApplicationReader(input, options, timing).read();
+ }
+
+ public CodeInspector(AndroidApp app) throws IOException, ExecutionException {
+ this(
+ new ApplicationReader(app, new InternalOptions(), new Timing("CodeInspector"))
+ .read(app.getProguardMapOutputData()));
+ }
+
+ public CodeInspector(AndroidApp app, Consumer<InternalOptions> optionsConsumer)
+ throws IOException, ExecutionException {
+ this(
+ new ApplicationReader(app, runOptionsConsumer(optionsConsumer), new Timing("CodeInspector"))
+ .read(app.getProguardMapOutputData()));
+ }
+
+ private static InternalOptions runOptionsConsumer(Consumer<InternalOptions> optionsConsumer) {
+ InternalOptions internalOptions = new InternalOptions();
+ optionsConsumer.accept(internalOptions);
+ return internalOptions;
+ }
+
+ public CodeInspector(AndroidApp app, Path proguardMap) throws IOException, ExecutionException {
+ this(
+ new ApplicationReader(app, new InternalOptions(), new Timing("CodeInspector"))
+ .read(StringResource.fromFile(proguardMap)));
+ }
+
+ public CodeInspector(DexApplication application) {
+ dexItemFactory = application.dexItemFactory;
+ this.application = application;
+ this.mapping = application.getProguardMap();
+ originalToObfuscatedMapping =
+ mapping == null ? null : mapping.getObfuscatedToOriginalMapping().inverse();
+ }
+
+ public DexItemFactory getFactory() {
+ return dexItemFactory;
+ }
+
+ DexType toDexType(String string) {
+ return dexItemFactory.createType(DescriptorUtils.javaTypeToDescriptor(string));
+ }
+
+ private DexType toDexTypeIgnorePrimitives(String string) {
+ return dexItemFactory.createType(DescriptorUtils.javaTypeToDescriptorIgnorePrimitives(string));
+ }
+
+ static <S, T extends Subject> void forAll(
+ S[] items,
+ BiFunction<S, FoundClassSubject, ? extends T> constructor,
+ FoundClassSubject clazz,
+ Consumer<T> consumer) {
+ for (S item : items) {
+ consumer.accept(constructor.apply(item, clazz));
+ }
+ }
+
+ private static <S, T extends Subject> void forAll(
+ Iterable<S> items, Function<S, T> constructor, Consumer<T> consumer) {
+ for (S item : items) {
+ consumer.accept(constructor.apply(item));
+ }
+ }
+
+ DexAnnotation findAnnotation(String name, DexAnnotationSet annotations) {
+ for (DexAnnotation annotation : annotations.annotations) {
+ DexType type = annotation.annotation.type;
+ String original = mapping == null ? type.toSourceString() : mapping.originalNameOf(type);
+ if (original.equals(name)) {
+ return annotation;
+ }
+ }
+ return null;
+ }
+
+ public String getFinalSignatureAttribute(DexAnnotationSet annotations) {
+ DexAnnotation annotation = findAnnotation("dalvik.annotation.Signature", annotations);
+ if (annotation == null) {
+ return null;
+ }
+ assert annotation.annotation.elements.length == 1;
+ DexAnnotationElement element = annotation.annotation.elements[0];
+ assert element.value instanceof DexValueArray;
+ StringBuilder builder = new StringBuilder();
+ DexValueArray valueArray = (DexValueArray) element.value;
+ for (DexValue value : valueArray.getValues()) {
+ assertTrue(value instanceof DexValueString);
+ DexValueString s = (DexValueString) value;
+ builder.append(s.getValue());
+ }
+ return builder.toString();
+ }
+
+ public String getOriginalSignatureAttribute(
+ DexAnnotationSet annotations, BiConsumer<GenericSignatureParser, String> parse) {
+ String finalSignature = getFinalSignatureAttribute(annotations);
+ if (finalSignature == null || mapping == null) {
+ return finalSignature;
+ }
+
+ GenericSignatureGenerator rewriter = new GenericSignatureGenerator();
+ GenericSignatureParser<String> parser = new GenericSignatureParser<>(rewriter);
+ parse.accept(parser, finalSignature);
+ return rewriter.getSignature();
+ }
+
+ public ClassSubject clazz(Class clazz) {
+ return clazz(clazz.getTypeName());
+ }
+
+ /** Lookup a class by name. This allows both original and obfuscated names. */
+ public ClassSubject clazz(String name) {
+ ClassNamingForNameMapper naming = null;
+ if (mapping != null) {
+ String obfuscated = originalToObfuscatedMapping.get(name);
+ if (obfuscated != null) {
+ naming = mapping.getClassNaming(obfuscated);
+ name = obfuscated;
+ } else {
+ // Figure out if the name is an already obfuscated name.
+ String original = originalToObfuscatedMapping.inverse().get(name);
+ if (original != null) {
+ naming = mapping.getClassNaming(name);
+ }
+ }
+ }
+ DexClass clazz = application.definitionFor(toDexTypeIgnorePrimitives(name));
+ if (clazz == null) {
+ return new AbsentClassSubject();
+ }
+ return new FoundClassSubject(this, clazz, naming);
+ }
+
+ public void forAllClasses(Consumer<FoundClassSubject> inspection) {
+ forAll(
+ application.classes(),
+ cls -> {
+ ClassSubject subject = clazz(cls.type.toSourceString());
+ assert subject.isPresent();
+ return (FoundClassSubject) subject;
+ },
+ inspection);
+ }
+
+ public List<FoundClassSubject> allClasses() {
+ ImmutableList.Builder<FoundClassSubject> builder = ImmutableList.builder();
+ forAllClasses(builder::add);
+ return builder.build();
+ }
+
+ public MethodSubject method(Method method) {
+ ClassSubject clazz = clazz(method.getDeclaringClass());
+ if (!clazz.isPresent()) {
+ return new AbsentMethodSubject();
+ }
+ return clazz.method(method);
+ }
+
+ String getObfuscatedTypeName(String originalTypeName) {
+ String obfuscatedType = null;
+ if (mapping != null) {
+ obfuscatedType = originalToObfuscatedMapping.get(originalTypeName);
+ }
+ obfuscatedType = obfuscatedType == null ? originalTypeName : obfuscatedType;
+ return obfuscatedType;
+ }
+
+ InstructionSubject createInstructionSubject(Instruction instruction) {
+ DexInstructionSubject dexInst = new DexInstructionSubject(instruction);
+ if (dexInst.isInvoke()) {
+ return new InvokeDexInstructionSubject(this, instruction);
+ } else if (dexInst.isFieldAccess()) {
+ return new FieldAccessDexInstructionSubject(this, instruction);
+ } else if (dexInst.isNewInstance()) {
+ return new NewInstanceDexInstructionSubject(instruction);
+ } else if (dexInst.isConstString(JumboStringMode.ALLOW)) {
+ return new ConstStringDexInstructionSubject(instruction);
+ } else {
+ return dexInst;
+ }
+ }
+
+ InstructionSubject createInstructionSubject(CfInstruction instruction) {
+ CfInstructionSubject cfInst = new CfInstructionSubject(instruction);
+ if (cfInst.isInvoke()) {
+ return new InvokeCfInstructionSubject(this, instruction);
+ } else if (cfInst.isFieldAccess()) {
+ return new FieldAccessCfInstructionSubject(this, instruction);
+ } else if (cfInst.isNewInstance()) {
+ return new NewInstanceCfInstructionSubject(instruction);
+ } else if (cfInst.isConstString(JumboStringMode.ALLOW)) {
+ return new ConstStringCfInstructionSubject(instruction);
+ } else {
+ return cfInst;
+ }
+ }
+
+ InstructionIterator createInstructionIterator(MethodSubject method) {
+ Code code = method.getMethod().getCode();
+ assert code != null;
+ if (code.isDexCode()) {
+ return new DexInstructionIterator(this, method);
+ } else if (code.isCfCode()) {
+ return new CfInstructionIterator(this, method);
+ } else {
+ throw new Unimplemented("InstructionIterator is implemented for DexCode and CfCode only.");
+ }
+ }
+
+ // Build the generic signature using the current mapping if any.
+ class GenericSignatureGenerator implements GenericSignatureAction<String> {
+
+ private StringBuilder signature;
+
+ public String getSignature() {
+ return signature.toString();
+ }
+
+ @Override
+ public void parsedSymbol(char symbol) {
+ signature.append(symbol);
+ }
+
+ @Override
+ public void parsedIdentifier(String identifier) {
+ signature.append(identifier);
+ }
+
+ @Override
+ public String parsedTypeName(String name) {
+ String type = name;
+ if (originalToObfuscatedMapping != null) {
+ String original = originalToObfuscatedMapping.inverse().get(name);
+ type = original != null ? original : name;
+ }
+ signature.append(type);
+ return type;
+ }
+
+ @Override
+ public String parsedInnerTypeName(String enclosingType, String name) {
+ String type;
+ if (originalToObfuscatedMapping != null) {
+ // The enclosingType has already been mapped if a mapping is present.
+ String minifiedEnclosing = originalToObfuscatedMapping.get(enclosingType);
+ type = originalToObfuscatedMapping.inverse().get(minifiedEnclosing + "$" + name);
+ if (type != null) {
+ assert type.startsWith(enclosingType + "$");
+ name = type.substring(enclosingType.length() + 1);
+ }
+ } else {
+ type = enclosingType + "$" + name;
+ }
+ signature.append(name);
+ return type;
+ }
+
+ @Override
+ public void start() {
+ signature = new StringBuilder();
+ }
+
+ @Override
+ public void stop() {
+ // nothing to do
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ConstStringCfInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ConstStringCfInstructionSubject.java
new file mode 100644
index 0000000..da545e4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ConstStringCfInstructionSubject.java
@@ -0,0 +1,22 @@
+// 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.utils.codeinspector;
+
+import com.android.tools.r8.cf.code.CfConstString;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.graph.DexString;
+
+public class ConstStringCfInstructionSubject extends CfInstructionSubject
+ implements ConstStringInstructionSubject {
+ public ConstStringCfInstructionSubject(CfInstruction instruction) {
+ super(instruction);
+ assert isConstString(JumboStringMode.ALLOW);
+ }
+
+ @Override
+ public DexString getString() {
+ return ((CfConstString) instruction).getString();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ConstStringDexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ConstStringDexInstructionSubject.java
new file mode 100644
index 0000000..cb69144
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ConstStringDexInstructionSubject.java
@@ -0,0 +1,28 @@
+// 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.utils.codeinspector;
+
+import com.android.tools.r8.code.ConstString;
+import com.android.tools.r8.code.ConstStringJumbo;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.graph.DexString;
+
+public class ConstStringDexInstructionSubject extends DexInstructionSubject
+ implements ConstStringInstructionSubject {
+ public ConstStringDexInstructionSubject(Instruction instruction) {
+ super(instruction);
+ assert isConstString(JumboStringMode.ALLOW);
+ }
+
+ @Override
+ public DexString getString() {
+ if (instruction instanceof ConstString) {
+ return ((ConstString) instruction).getString();
+ } else {
+ assert (instruction instanceof ConstStringJumbo);
+ return ((ConstStringJumbo) instruction).getString();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ConstStringInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ConstStringInstructionSubject.java
new file mode 100644
index 0000000..5914be7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ConstStringInstructionSubject.java
@@ -0,0 +1,11 @@
+// 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.utils.codeinspector;
+
+import com.android.tools.r8.graph.DexString;
+
+public interface ConstStringInstructionSubject extends InstructionSubject {
+ DexString getString();
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionIterator.java b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionIterator.java
new file mode 100644
index 0000000..8ae5609
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionIterator.java
@@ -0,0 +1,38 @@
+// 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.utils.codeinspector;
+
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexCode;
+import java.util.NoSuchElementException;
+
+class DexInstructionIterator implements InstructionIterator {
+
+ private final CodeInspector codeInspector;
+ private final DexCode code;
+ private int index;
+
+ DexInstructionIterator(CodeInspector codeInspector, MethodSubject method) {
+ this.codeInspector = codeInspector;
+ assert method.isPresent();
+ Code code = method.getMethod().getCode();
+ assert code != null && code.isDexCode();
+ this.code = code.asDexCode();
+ this.index = 0;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return index < code.instructions.length;
+ }
+
+ @Override
+ public InstructionSubject next() {
+ if (index == code.instructions.length) {
+ throw new NoSuchElementException();
+ }
+ return codeInspector.createInstructionSubject(code.instructions[index++]);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
new file mode 100644
index 0000000..9c6d21c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
@@ -0,0 +1,195 @@
+// 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.utils.codeinspector;
+
+import com.android.tools.r8.code.Const4;
+import com.android.tools.r8.code.ConstString;
+import com.android.tools.r8.code.ConstStringJumbo;
+import com.android.tools.r8.code.Goto;
+import com.android.tools.r8.code.IfEqz;
+import com.android.tools.r8.code.IfNez;
+import com.android.tools.r8.code.Iget;
+import com.android.tools.r8.code.IgetBoolean;
+import com.android.tools.r8.code.IgetByte;
+import com.android.tools.r8.code.IgetChar;
+import com.android.tools.r8.code.IgetObject;
+import com.android.tools.r8.code.IgetShort;
+import com.android.tools.r8.code.IgetWide;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.code.InvokeDirect;
+import com.android.tools.r8.code.InvokeDirectRange;
+import com.android.tools.r8.code.InvokeInterface;
+import com.android.tools.r8.code.InvokeInterfaceRange;
+import com.android.tools.r8.code.InvokeStatic;
+import com.android.tools.r8.code.InvokeStaticRange;
+import com.android.tools.r8.code.InvokeSuper;
+import com.android.tools.r8.code.InvokeSuperRange;
+import com.android.tools.r8.code.InvokeVirtual;
+import com.android.tools.r8.code.InvokeVirtualRange;
+import com.android.tools.r8.code.Iput;
+import com.android.tools.r8.code.IputBoolean;
+import com.android.tools.r8.code.IputByte;
+import com.android.tools.r8.code.IputChar;
+import com.android.tools.r8.code.IputObject;
+import com.android.tools.r8.code.IputShort;
+import com.android.tools.r8.code.IputWide;
+import com.android.tools.r8.code.NewInstance;
+import com.android.tools.r8.code.Nop;
+import com.android.tools.r8.code.ReturnVoid;
+import com.android.tools.r8.code.Sget;
+import com.android.tools.r8.code.SgetBoolean;
+import com.android.tools.r8.code.SgetByte;
+import com.android.tools.r8.code.SgetChar;
+import com.android.tools.r8.code.SgetObject;
+import com.android.tools.r8.code.SgetShort;
+import com.android.tools.r8.code.SgetWide;
+import com.android.tools.r8.code.Sput;
+import com.android.tools.r8.code.SputBoolean;
+import com.android.tools.r8.code.SputByte;
+import com.android.tools.r8.code.SputChar;
+import com.android.tools.r8.code.SputObject;
+import com.android.tools.r8.code.SputShort;
+import com.android.tools.r8.code.SputWide;
+import com.android.tools.r8.code.Throw;
+
+public class DexInstructionSubject implements InstructionSubject {
+ protected final Instruction instruction;
+
+ public DexInstructionSubject(Instruction instruction) {
+ this.instruction = instruction;
+ }
+
+ @Override
+ public boolean isFieldAccess() {
+ return isInstanceGet() || isInstancePut() || isStaticGet() || isStaticSet();
+ }
+
+ @Override
+ public boolean isInvokeVirtual() {
+ return instruction instanceof InvokeVirtual || instruction instanceof InvokeVirtualRange;
+ }
+
+ @Override
+ public boolean isInvokeInterface() {
+ return instruction instanceof InvokeInterface || instruction instanceof InvokeInterfaceRange;
+ }
+
+ @Override
+ public boolean isInvokeStatic() {
+ return instruction instanceof InvokeStatic || instruction instanceof InvokeStaticRange;
+ }
+
+ @Override
+ public boolean isNop() {
+ return instruction instanceof Nop;
+ }
+
+ @Override
+ public boolean isConstString(JumboStringMode jumboStringMode) {
+ return instruction instanceof ConstString
+ || (jumboStringMode == JumboStringMode.ALLOW && instruction instanceof ConstStringJumbo);
+ }
+
+ @Override
+ public boolean isConstString(String value, JumboStringMode jumboStringMode) {
+ return (instruction instanceof ConstString
+ && ((ConstString) instruction).BBBB.toSourceString().equals(value))
+ || (jumboStringMode == JumboStringMode.ALLOW
+ && instruction instanceof ConstStringJumbo
+ && ((ConstStringJumbo) instruction).BBBBBBBB.toSourceString().equals(value));
+ }
+
+ @Override
+ public boolean isGoto() {
+
+ return instruction instanceof Goto;
+ }
+
+ @Override
+ public boolean isIfNez() {
+ return instruction instanceof IfNez;
+ }
+
+ @Override
+ public boolean isIfEqz() {
+ return instruction instanceof IfEqz;
+ }
+
+ @Override
+ public boolean isReturnVoid() {
+ return instruction instanceof ReturnVoid;
+ }
+
+ @Override
+ public boolean isThrow() {
+ return instruction instanceof Throw;
+ }
+
+ @Override
+ public boolean isInvoke() {
+ return isInvokeVirtual()
+ || isInvokeInterface()
+ || isInvokeDirect()
+ || isInvokeSuper()
+ || isInvokeStatic();
+ }
+
+ @Override
+ public boolean isNewInstance() {
+ return instruction instanceof NewInstance;
+ }
+
+ public boolean isInvokeSuper() {
+ return instruction instanceof InvokeSuper || instruction instanceof InvokeSuperRange;
+ }
+
+ public boolean isInvokeDirect() {
+ return instruction instanceof InvokeDirect || instruction instanceof InvokeDirectRange;
+ }
+
+ public boolean isConst4() {
+ return instruction instanceof Const4;
+ }
+
+ public boolean isInstanceGet() {
+ return instruction instanceof Iget
+ || instruction instanceof IgetBoolean
+ || instruction instanceof IgetByte
+ || instruction instanceof IgetShort
+ || instruction instanceof IgetChar
+ || instruction instanceof IgetWide
+ || instruction instanceof IgetObject;
+ }
+
+ public boolean isInstancePut() {
+ return instruction instanceof Iput
+ || instruction instanceof IputBoolean
+ || instruction instanceof IputByte
+ || instruction instanceof IputShort
+ || instruction instanceof IputChar
+ || instruction instanceof IputWide
+ || instruction instanceof IputObject;
+ }
+
+ public boolean isStaticGet() {
+ return instruction instanceof Sget
+ || instruction instanceof SgetBoolean
+ || instruction instanceof SgetByte
+ || instruction instanceof SgetShort
+ || instruction instanceof SgetChar
+ || instruction instanceof SgetWide
+ || instruction instanceof SgetObject;
+ }
+
+ public boolean isStaticSet() {
+ return instruction instanceof Sput
+ || instruction instanceof SputBoolean
+ || instruction instanceof SputByte
+ || instruction instanceof SputShort
+ || instruction instanceof SputChar
+ || instruction instanceof SputWide
+ || instruction instanceof SputObject;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FieldAccessCfInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FieldAccessCfInstructionSubject.java
new file mode 100644
index 0000000..e9ccfd3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FieldAccessCfInstructionSubject.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.utils.codeinspector;
+
+import com.android.tools.r8.cf.code.CfFieldInstruction;
+import com.android.tools.r8.cf.code.CfInstruction;
+
+public class FieldAccessCfInstructionSubject extends CfInstructionSubject
+ implements FieldAccessInstructionSubject {
+ private final CodeInspector codeInspector;
+
+ public FieldAccessCfInstructionSubject(CodeInspector codeInspector, CfInstruction instruction) {
+ super(instruction);
+ this.codeInspector = codeInspector;
+ assert isFieldAccess();
+ }
+
+ @Override
+ public TypeSubject holder() {
+ return new TypeSubject(codeInspector, ((CfFieldInstruction) instruction).getField().getHolder());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FieldAccessDexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FieldAccessDexInstructionSubject.java
new file mode 100644
index 0000000..b9e7fd2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FieldAccessDexInstructionSubject.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.utils.codeinspector;
+
+import com.android.tools.r8.code.Instruction;
+
+public class FieldAccessDexInstructionSubject extends DexInstructionSubject
+ implements FieldAccessInstructionSubject {
+
+ private final CodeInspector codeInspector;
+
+ public FieldAccessDexInstructionSubject(CodeInspector codeInspector, Instruction instruction) {
+ super(instruction);
+ this.codeInspector = codeInspector;
+ assert isFieldAccess();
+ }
+
+ @Override
+ public TypeSubject holder() {
+ return new TypeSubject(codeInspector, instruction.getField().getHolder());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FieldAccessInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FieldAccessInstructionSubject.java
new file mode 100644
index 0000000..355f3a4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FieldAccessInstructionSubject.java
@@ -0,0 +1,9 @@
+// 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.utils.codeinspector;
+
+public interface FieldAccessInstructionSubject extends InstructionSubject {
+ TypeSubject holder();
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FieldSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FieldSubject.java
new file mode 100644
index 0000000..f9a669a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FieldSubject.java
@@ -0,0 +1,22 @@
+// 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.utils.codeinspector;
+
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexValue;
+
+public abstract class FieldSubject extends MemberSubject {
+ public abstract boolean hasExplicitStaticValue();
+
+ public abstract DexEncodedField getField();
+
+ public abstract DexValue getStaticValue();
+
+ public abstract boolean isRenamed();
+
+ public abstract String getOriginalSignatureAttribute();
+
+ public abstract String getFinalSignatureAttribute();
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FilteredInstructionIterator.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FilteredInstructionIterator.java
new file mode 100644
index 0000000..870953e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FilteredInstructionIterator.java
@@ -0,0 +1,50 @@
+// 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.utils.codeinspector;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import java.util.function.Predicate;
+
+class FilteredInstructionIterator<T extends InstructionSubject> implements Iterator<T> {
+
+ private final InstructionIterator iterator;
+ private final Predicate<InstructionSubject> predicate;
+ private InstructionSubject pendingNext = null;
+
+ FilteredInstructionIterator(
+ CodeInspector codeInspector, MethodSubject method, Predicate<InstructionSubject> predicate) {
+ this.iterator = codeInspector.createInstructionIterator(method);
+ this.predicate = predicate;
+ hasNext();
+ }
+
+ @Override
+ public boolean hasNext() {
+ if (pendingNext == null) {
+ while (iterator.hasNext()) {
+ pendingNext = iterator.next();
+ if (predicate.test(pendingNext)) {
+ break;
+ }
+ pendingNext = null;
+ }
+ }
+ return pendingNext != null;
+ }
+
+ @Override
+ public T next() {
+ hasNext();
+ if (pendingNext == null) {
+ throw new NoSuchElementException();
+ }
+ // We cannot tell if the provided predicate will only match instruction subjects of type T.
+ @SuppressWarnings("unchecked")
+ T result = (T) pendingNext;
+ pendingNext = null;
+ return result;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundAnnotationSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundAnnotationSubject.java
new file mode 100644
index 0000000..546ef59
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundAnnotationSubject.java
@@ -0,0 +1,32 @@
+// 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.utils.codeinspector;
+
+import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexEncodedAnnotation;
+
+public class FoundAnnotationSubject extends AnnotationSubject {
+
+ private final DexAnnotation annotation;
+
+ public FoundAnnotationSubject(DexAnnotation annotation) {
+ this.annotation = annotation;
+ }
+
+ @Override
+ public boolean isPresent() {
+ return true;
+ }
+
+ @Override
+ public boolean isRenamed() {
+ return false;
+ }
+
+ @Override
+ public DexEncodedAnnotation getAnnotation() {
+ return annotation.annotation;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
new file mode 100644
index 0000000..9a0661f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
@@ -0,0 +1,253 @@
+// 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.utils.codeinspector;
+
+import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.InnerClassAttribute;
+import com.android.tools.r8.naming.ClassNamingForNameMapper;
+import com.android.tools.r8.naming.MemberNaming;
+import com.android.tools.r8.naming.MemberNaming.FieldSignature;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.naming.MemberNaming.Signature;
+import com.android.tools.r8.naming.signature.GenericSignatureParser;
+import com.android.tools.r8.utils.DescriptorUtils;
+import java.util.List;
+import java.util.function.Consumer;
+
+public class FoundClassSubject extends ClassSubject {
+
+ private final CodeInspector codeInspector;
+ private final DexClass dexClass;
+ final ClassNamingForNameMapper naming;
+
+ FoundClassSubject(CodeInspector codeInspector, DexClass dexClass, ClassNamingForNameMapper naming) {
+ this.codeInspector = codeInspector;
+ this.dexClass = dexClass;
+ this.naming = naming;
+ }
+
+ @Override
+ public boolean isPresent() {
+ return true;
+ }
+
+ @Override
+ public void forAllMethods(Consumer<FoundMethodSubject> inspection) {
+ CodeInspector.forAll(
+ dexClass.directMethods(),
+ (encoded, clazz) -> new FoundMethodSubject(codeInspector, encoded, clazz),
+ this,
+ inspection);
+ CodeInspector.forAll(
+ dexClass.virtualMethods(),
+ (encoded, clazz) -> new FoundMethodSubject(codeInspector, encoded, clazz),
+ this,
+ inspection);
+ }
+
+ @Override
+ public MethodSubject method(String returnType, String name, List<String> parameters) {
+ DexType[] parameterTypes = new DexType[parameters.size()];
+ for (int i = 0; i < parameters.size(); i++) {
+ parameterTypes[i] =
+ codeInspector.toDexType(codeInspector.getObfuscatedTypeName(parameters.get(i)));
+ }
+ DexProto proto =
+ codeInspector.dexItemFactory.createProto(
+ codeInspector.toDexType(codeInspector.getObfuscatedTypeName(returnType)), parameterTypes);
+ if (naming != null) {
+ String[] parameterStrings = new String[parameterTypes.length];
+ Signature signature =
+ new MethodSignature(name, returnType, parameters.toArray(parameterStrings));
+ MemberNaming methodNaming = naming.lookupByOriginalSignature(signature);
+ if (methodNaming != null) {
+ name = methodNaming.getRenamedName();
+ }
+ }
+ DexMethod dexMethod =
+ codeInspector.dexItemFactory.createMethod(
+ dexClass.type, proto, codeInspector.dexItemFactory.createString(name));
+ DexEncodedMethod encoded = findMethod(dexClass.directMethods(), dexMethod);
+ if (encoded == null) {
+ encoded = findMethod(dexClass.virtualMethods(), dexMethod);
+ }
+ return encoded == null
+ ? new AbsentMethodSubject()
+ : new FoundMethodSubject(codeInspector, encoded, this);
+ }
+
+ private DexEncodedMethod findMethod(DexEncodedMethod[] methods, DexMethod dexMethod) {
+ for (DexEncodedMethod method : methods) {
+ if (method.method.equals(dexMethod)) {
+ return method;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void forAllFields(Consumer<FoundFieldSubject> inspection) {
+ CodeInspector.forAll(
+ dexClass.staticFields(),
+ (dexField, clazz) -> new FoundFieldSubject(codeInspector, dexField, clazz),
+ this,
+ inspection);
+ CodeInspector.forAll(
+ dexClass.instanceFields(),
+ (dexField, clazz) -> new FoundFieldSubject(codeInspector, dexField, clazz),
+ this,
+ inspection);
+ }
+
+ @Override
+ public FieldSubject field(String type, String name) {
+ String obfuscatedType = codeInspector.getObfuscatedTypeName(type);
+ MemberNaming fieldNaming = null;
+ if (naming != null) {
+ fieldNaming = naming.lookupByOriginalSignature(new FieldSignature(name, type));
+ }
+ String obfuscatedName = fieldNaming == null ? name : fieldNaming.getRenamedName();
+
+ DexField field =
+ codeInspector.dexItemFactory.createField(
+ dexClass.type,
+ codeInspector.toDexType(obfuscatedType),
+ codeInspector.dexItemFactory.createString(obfuscatedName));
+ DexEncodedField encoded = findField(dexClass.staticFields(), field);
+ if (encoded == null) {
+ encoded = findField(dexClass.instanceFields(), field);
+ }
+ return encoded == null
+ ? new AbsentFieldSubject()
+ : new FoundFieldSubject(codeInspector, encoded, this);
+ }
+
+ @Override
+ public boolean isAbstract() {
+ return dexClass.accessFlags.isAbstract();
+ }
+
+ @Override
+ public boolean isAnnotation() {
+ return dexClass.accessFlags.isAnnotation();
+ }
+
+ private DexEncodedField findField(DexEncodedField[] fields, DexField dexField) {
+ for (DexEncodedField field : fields) {
+ if (field.field.equals(dexField)) {
+ return field;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public DexClass getDexClass() {
+ return dexClass;
+ }
+
+ @Override
+ public AnnotationSubject annotation(String name) {
+ // Ensure we don't check for annotations represented as attributes.
+ assert !name.endsWith("EnclosingClass")
+ && !name.endsWith("EnclosingMethod")
+ && !name.endsWith("InnerClass");
+ DexAnnotation annotation = codeInspector.findAnnotation(name, dexClass.annotations);
+ return annotation == null
+ ? new AbsentAnnotationSubject()
+ : new FoundAnnotationSubject(annotation);
+ }
+
+ @Override
+ public String getOriginalName() {
+ if (naming != null) {
+ return naming.originalName;
+ } else {
+ return getFinalName();
+ }
+ }
+
+ @Override
+ public String getOriginalDescriptor() {
+ if (naming != null) {
+ return DescriptorUtils.javaTypeToDescriptor(naming.originalName);
+ } else {
+ return getFinalDescriptor();
+ }
+ }
+
+ @Override
+ public String getFinalName() {
+ return DescriptorUtils.descriptorToJavaType(getFinalDescriptor());
+ }
+
+ @Override
+ public String getFinalDescriptor() {
+ return dexClass.type.descriptor.toString();
+ }
+
+ @Override
+ public boolean isRenamed() {
+ return naming != null && !getFinalDescriptor().equals(getOriginalDescriptor());
+ }
+
+ private InnerClassAttribute getInnerClassAttribute() {
+ for (InnerClassAttribute innerClassAttribute : dexClass.getInnerClasses()) {
+ if (dexClass.type == innerClassAttribute.getInner()) {
+ return innerClassAttribute;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public boolean isLocalClass() {
+ InnerClassAttribute innerClass = getInnerClassAttribute();
+ return innerClass != null && innerClass.isNamed() && dexClass.getEnclosingMethod() != null;
+ }
+
+ @Override
+ public boolean isMemberClass() {
+ InnerClassAttribute innerClass = getInnerClassAttribute();
+ return innerClass != null
+ && innerClass.getOuter() != null
+ && innerClass.isNamed()
+ && dexClass.getEnclosingMethod() == null;
+ }
+
+ @Override
+ public boolean isAnonymousClass() {
+ InnerClassAttribute innerClass = getInnerClassAttribute();
+ return innerClass != null && innerClass.isAnonymous() && dexClass.getEnclosingMethod() != null;
+ }
+
+ @Override
+ public boolean isSynthesizedJavaLambdaClass() {
+ return dexClass.type.getName().contains("$Lambda$");
+ }
+
+ @Override
+ public String getOriginalSignatureAttribute() {
+ return codeInspector.getOriginalSignatureAttribute(
+ dexClass.annotations, GenericSignatureParser::parseClassSignature);
+ }
+
+ @Override
+ public String getFinalSignatureAttribute() {
+ return codeInspector.getFinalSignatureAttribute(dexClass.annotations);
+ }
+
+ @Override
+ public String toString() {
+ return dexClass.toSourceString();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
new file mode 100644
index 0000000..ae42b14
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
@@ -0,0 +1,114 @@
+// 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.utils.codeinspector;
+
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.naming.MemberNaming;
+import com.android.tools.r8.naming.MemberNaming.FieldSignature;
+import com.android.tools.r8.naming.signature.GenericSignatureParser;
+
+public class FoundFieldSubject extends FieldSubject {
+
+ private final CodeInspector codeInspector;
+ private final FoundClassSubject clazz;
+ private final DexEncodedField dexField;
+
+ public FoundFieldSubject(
+ CodeInspector codeInspector, DexEncodedField dexField, FoundClassSubject clazz) {
+ this.codeInspector = codeInspector;
+ this.clazz = clazz;
+ this.dexField = dexField;
+ }
+
+ @Override
+ public boolean isPublic() {
+ return dexField.accessFlags.isPublic();
+ }
+
+ @Override
+ public boolean isStatic() {
+ return dexField.accessFlags.isStatic();
+ }
+
+ @Override
+ public boolean isFinal() {
+ return dexField.accessFlags.isFinal();
+ }
+
+ @Override
+ public boolean isPresent() {
+ return true;
+ }
+
+ @Override
+ public boolean isRenamed() {
+ return clazz.naming != null && !getFinalSignature().name.equals(getOriginalSignature().name);
+ }
+
+ public TypeSubject type() {
+ return new TypeSubject(codeInspector, dexField.field.type);
+ }
+
+ @Override
+ public FieldSignature getOriginalSignature() {
+ FieldSignature signature = getFinalSignature();
+ if (clazz.naming == null) {
+ return signature;
+ }
+
+ // Map the type to the original name. This is needed as the in the Proguard map the
+ // names on the left side are the original names. E.g.
+ //
+ // X -> a
+ // X field -> a
+ //
+ // whereas the final signature is for X.a is "a a"
+ String obfuscatedType = signature.type;
+ String originalType = codeInspector.originalToObfuscatedMapping.inverse().get(obfuscatedType);
+ String fieldType = originalType != null ? originalType : obfuscatedType;
+
+ FieldSignature lookupSignature = new FieldSignature(signature.name, fieldType);
+
+ MemberNaming memberNaming = clazz.naming.lookup(lookupSignature);
+ return memberNaming != null ? (FieldSignature) memberNaming.getOriginalSignature() : signature;
+ }
+
+ @Override
+ public FieldSignature getFinalSignature() {
+ return FieldSignature.fromDexField(dexField.field);
+ }
+
+ @Override
+ public boolean hasExplicitStaticValue() {
+ return isStatic() && dexField.hasExplicitStaticValue();
+ }
+
+ @Override
+ public DexValue getStaticValue() {
+ return dexField.getStaticValue();
+ }
+
+ @Override
+ public DexEncodedField getField() {
+ return dexField;
+ }
+
+ @Override
+ public String getOriginalSignatureAttribute() {
+ return codeInspector.getOriginalSignatureAttribute(
+ dexField.annotations, GenericSignatureParser::parseFieldSignature);
+ }
+
+ @Override
+ public String getFinalSignatureAttribute() {
+ return codeInspector.getFinalSignatureAttribute(dexField.annotations);
+ }
+
+ @Override
+ public String toString() {
+ return dexField.toSourceString();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
new file mode 100644
index 0000000..3fe8186
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
@@ -0,0 +1,140 @@
+// 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.utils.codeinspector;
+
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.naming.MemberNaming;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.naming.signature.GenericSignatureParser;
+import java.util.Iterator;
+import java.util.function.Predicate;
+
+public class FoundMethodSubject extends MethodSubject {
+
+ private final CodeInspector codeInspector;
+ private final FoundClassSubject clazz;
+ private final DexEncodedMethod dexMethod;
+
+ public FoundMethodSubject(
+ CodeInspector codeInspector, DexEncodedMethod encoded, FoundClassSubject clazz) {
+ this.codeInspector = codeInspector;
+ this.clazz = clazz;
+ this.dexMethod = encoded;
+ }
+
+ @Override
+ public boolean isPresent() {
+ return true;
+ }
+
+ @Override
+ public boolean isRenamed() {
+ return clazz.naming != null && !getFinalSignature().name.equals(getOriginalSignature().name);
+ }
+
+ @Override
+ public boolean isPublic() {
+ return dexMethod.accessFlags.isPublic();
+ }
+
+ @Override
+ public boolean isStatic() {
+ return dexMethod.accessFlags.isStatic();
+ }
+
+ @Override
+ public boolean isFinal() {
+ return dexMethod.accessFlags.isFinal();
+ }
+
+ @Override
+ public boolean isAbstract() {
+ return dexMethod.accessFlags.isAbstract();
+ }
+
+ @Override
+ public boolean isBridge() {
+ return dexMethod.accessFlags.isBridge();
+ }
+
+ @Override
+ public boolean isInstanceInitializer() {
+ return dexMethod.isInstanceInitializer();
+ }
+
+ @Override
+ public boolean isClassInitializer() {
+ return dexMethod.isClassInitializer();
+ }
+
+ @Override
+ public DexEncodedMethod getMethod() {
+ return dexMethod;
+ }
+
+ @Override
+ public MethodSignature getOriginalSignature() {
+ MethodSignature signature = getFinalSignature();
+ if (clazz.naming == null) {
+ return signature;
+ }
+
+ // Map the parameters and return type to original names. This is needed as the in the
+ // Proguard map the names on the left side are the original names. E.g.
+ //
+ // X -> a
+ // X method(X) -> a
+ //
+ // whereas the final signature is for X.a is "a (a)"
+ String[] OriginalParameters = new String[signature.parameters.length];
+ for (int i = 0; i < OriginalParameters.length; i++) {
+ String obfuscated = signature.parameters[i];
+ String original = codeInspector.originalToObfuscatedMapping.inverse().get(obfuscated);
+ OriginalParameters[i] = original != null ? original : obfuscated;
+ }
+ String obfuscatedReturnType = signature.type;
+ String originalReturnType =
+ codeInspector.originalToObfuscatedMapping.inverse().get(obfuscatedReturnType);
+ String returnType = originalReturnType != null ? originalReturnType : obfuscatedReturnType;
+
+ MethodSignature lookupSignature =
+ new MethodSignature(signature.name, returnType, OriginalParameters);
+
+ MemberNaming memberNaming = clazz.naming.lookup(lookupSignature);
+ return memberNaming != null ? (MethodSignature) memberNaming.getOriginalSignature() : signature;
+ }
+
+ @Override
+ public MethodSignature getFinalSignature() {
+ return MethodSignature.fromDexMethod(dexMethod.method);
+ }
+
+ @Override
+ public String getOriginalSignatureAttribute() {
+ return codeInspector.getOriginalSignatureAttribute(
+ dexMethod.annotations, GenericSignatureParser::parseMethodSignature);
+ }
+
+ @Override
+ public String getFinalSignatureAttribute() {
+ return codeInspector.getFinalSignatureAttribute(dexMethod.annotations);
+ }
+
+ @Override
+ public Iterator<InstructionSubject> iterateInstructions() {
+ return codeInspector.createInstructionIterator(this);
+ }
+
+ @Override
+ public <T extends InstructionSubject> Iterator<T> iterateInstructions(
+ Predicate<InstructionSubject> filter) {
+ return new FilteredInstructionIterator<>(codeInspector, this, filter);
+ }
+
+ @Override
+ public String toString() {
+ return dexMethod.toSourceString();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionIterator.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionIterator.java
new file mode 100644
index 0000000..4685730
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionIterator.java
@@ -0,0 +1,9 @@
+// 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.utils.codeinspector;
+
+import java.util.Iterator;
+
+interface InstructionIterator extends Iterator<InstructionSubject> {}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
new file mode 100644
index 0000000..6c1f4be
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
@@ -0,0 +1,41 @@
+// 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.utils.codeinspector;
+
+public interface InstructionSubject {
+
+ enum JumboStringMode {
+ ALLOW,
+ DISALLOW
+ };
+
+ boolean isFieldAccess();
+
+ boolean isInvokeVirtual();
+
+ boolean isInvokeInterface();
+
+ boolean isInvokeStatic();
+
+ boolean isNop();
+
+ boolean isConstString(JumboStringMode jumboStringMode);
+
+ boolean isConstString(String value, JumboStringMode jumboStringMode);
+
+ boolean isGoto();
+
+ boolean isIfNez();
+
+ boolean isIfEqz();
+
+ boolean isReturnVoid();
+
+ boolean isThrow();
+
+ boolean isInvoke();
+
+ boolean isNewInstance();
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InvokeCfInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InvokeCfInstructionSubject.java
new file mode 100644
index 0000000..b307112
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InvokeCfInstructionSubject.java
@@ -0,0 +1,35 @@
+// 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.utils.codeinspector;
+
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.graph.DexMethod;
+
+public class InvokeCfInstructionSubject extends CfInstructionSubject
+ implements InvokeInstructionSubject {
+ private final CodeInspector codeInspector;
+
+ public InvokeCfInstructionSubject(CodeInspector codeInspector, CfInstruction instruction) {
+ super(instruction);
+ assert isInvoke();
+ this.codeInspector = codeInspector;
+ }
+
+ @Override
+ public TypeSubject holder() {
+ return new TypeSubject(codeInspector, invokedMethod().getHolder());
+ }
+
+ @Override
+ public DexMethod invokedMethod() {
+ if (isInvokeDynamic()) {
+ throw new Unimplemented(
+ "invokeMethod is not implemented for the INVOKEDYNAMIC CF instruction.");
+ }
+ return ((CfInvoke) instruction).getMethod();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InvokeDexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InvokeDexInstructionSubject.java
new file mode 100644
index 0000000..7b5d41c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InvokeDexInstructionSubject.java
@@ -0,0 +1,30 @@
+// 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.utils.codeinspector;
+
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.graph.DexMethod;
+
+public class InvokeDexInstructionSubject extends DexInstructionSubject
+ implements InvokeInstructionSubject {
+
+ private final CodeInspector codeInspector;
+
+ public InvokeDexInstructionSubject(CodeInspector codeInspector, Instruction instruction) {
+ super(instruction);
+ this.codeInspector = codeInspector;
+ assert isInvoke();
+ }
+
+ @Override
+ public TypeSubject holder() {
+ return new TypeSubject(codeInspector, invokedMethod().getHolder());
+ }
+
+ @Override
+ public DexMethod invokedMethod() {
+ return instruction.getMethod();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InvokeInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InvokeInstructionSubject.java
new file mode 100644
index 0000000..b2f99dc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InvokeInstructionSubject.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.utils.codeinspector;
+
+import com.android.tools.r8.graph.DexMethod;
+
+public interface InvokeInstructionSubject extends InstructionSubject {
+ TypeSubject holder();
+
+ DexMethod invokedMethod();
+}
diff --git a/src/test/java/com/android/tools/r8/utils/DexInspectorMatchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
similarity index 93%
rename from src/test/java/com/android/tools/r8/utils/DexInspectorMatchers.java
rename to src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
index 1986bbc..3b718a4 100644
--- a/src/test/java/com/android/tools/r8/utils/DexInspectorMatchers.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
@@ -2,18 +2,14 @@
// 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.utils;
+package com.android.tools.r8.utils.codeinspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.FieldSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
-import com.android.tools.r8.utils.DexInspector.Subject;
import com.google.common.collect.ImmutableList;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
-public class DexInspectorMatchers {
+public class Matchers {
private static String type(Subject subject) {
String type = "<unknown subject type>";
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java
new file mode 100644
index 0000000..9e90fde
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java
@@ -0,0 +1,30 @@
+// 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.utils.codeinspector;
+
+import com.android.tools.r8.naming.MemberNaming.Signature;
+
+public abstract class MemberSubject extends Subject {
+
+ public abstract boolean isPublic();
+
+ public abstract boolean isStatic();
+
+ public abstract boolean isFinal();
+
+ public abstract Signature getOriginalSignature();
+
+ public abstract Signature getFinalSignature();
+
+ public String getOriginalName() {
+ Signature originalSignature = getOriginalSignature();
+ return originalSignature == null ? null : originalSignature.name;
+ }
+
+ public String getFinalName() {
+ Signature finalSignature = getFinalSignature();
+ return finalSignature == null ? null : finalSignature.name;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
new file mode 100644
index 0000000..6eaaf5f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
@@ -0,0 +1,35 @@
+// 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.utils.codeinspector;
+
+import com.android.tools.r8.graph.DexEncodedMethod;
+import java.util.Iterator;
+import java.util.function.Predicate;
+
+public abstract class MethodSubject extends MemberSubject {
+
+ public abstract boolean isAbstract();
+
+ public abstract boolean isBridge();
+
+ public abstract boolean isInstanceInitializer();
+
+ public abstract boolean isClassInitializer();
+
+ public abstract String getOriginalSignatureAttribute();
+
+ public abstract String getFinalSignatureAttribute();
+
+ public abstract DexEncodedMethod getMethod();
+
+ public Iterator<InstructionSubject> iterateInstructions() {
+ return null;
+ }
+
+ public <T extends InstructionSubject> Iterator<T> iterateInstructions(
+ Predicate<InstructionSubject> filter) {
+ return null;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/NewInstanceCfInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/NewInstanceCfInstructionSubject.java
new file mode 100644
index 0000000..d3b0a93
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/NewInstanceCfInstructionSubject.java
@@ -0,0 +1,21 @@
+// 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.utils.codeinspector;
+
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfNew;
+import com.android.tools.r8.graph.DexType;
+
+public class NewInstanceCfInstructionSubject extends CfInstructionSubject
+ implements NewInstanceInstructionSubject {
+ public NewInstanceCfInstructionSubject(CfInstruction instruction) {
+ super(instruction);
+ }
+
+ @Override
+ public DexType getType() {
+ return ((CfNew) instruction).getType();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/NewInstanceDexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/NewInstanceDexInstructionSubject.java
new file mode 100644
index 0000000..a396468
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/NewInstanceDexInstructionSubject.java
@@ -0,0 +1,21 @@
+// 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.utils.codeinspector;
+
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.code.NewInstance;
+import com.android.tools.r8.graph.DexType;
+
+public class NewInstanceDexInstructionSubject extends DexInstructionSubject
+ implements NewInstanceInstructionSubject {
+ public NewInstanceDexInstructionSubject(Instruction instruction) {
+ super(instruction);
+ }
+
+ @Override
+ public DexType getType() {
+ return ((NewInstance) instruction).getType();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/NewInstanceInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/NewInstanceInstructionSubject.java
new file mode 100644
index 0000000..2a0f192
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/NewInstanceInstructionSubject.java
@@ -0,0 +1,11 @@
+// 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.utils.codeinspector;
+
+import com.android.tools.r8.graph.DexType;
+
+public interface NewInstanceInstructionSubject extends InstructionSubject {
+ DexType getType();
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/Subject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/Subject.java
new file mode 100644
index 0000000..d93e19a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/Subject.java
@@ -0,0 +1,12 @@
+// 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.utils.codeinspector;
+
+public abstract class Subject {
+
+ public abstract boolean isPresent();
+
+ public abstract boolean isRenamed();
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/TypeSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/TypeSubject.java
new file mode 100644
index 0000000..ba2230a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/TypeSubject.java
@@ -0,0 +1,36 @@
+// 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.utils.codeinspector;
+
+import com.android.tools.r8.graph.DexType;
+
+public class TypeSubject extends Subject {
+
+ private final CodeInspector codeInspector;
+ private final DexType dexType;
+
+ TypeSubject(CodeInspector codeInspector, DexType dexType) {
+ this.codeInspector = codeInspector;
+ this.dexType = dexType;
+ }
+
+ @Override
+ public boolean isPresent() {
+ return true;
+ }
+
+ @Override
+ public boolean isRenamed() {
+ return false;
+ }
+
+ public boolean is(String type) {
+ return dexType.equals(codeInspector.toDexType(type));
+ }
+
+ public String toString() {
+ return dexType.toSourceString();
+ }
+}
diff --git a/src/test/kotlinR8TestResources/class_staticizer/main.kt b/src/test/kotlinR8TestResources/class_staticizer/main.kt
new file mode 100644
index 0000000..01915c4
--- /dev/null
+++ b/src/test/kotlinR8TestResources/class_staticizer/main.kt
@@ -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 class_staticizer
+
+private var COUNT = 0
+
+fun next() = "${COUNT++}".padStart(3, '0')
+
+fun main(args: Array<String>) {
+ println(Regular.foo)
+ println(Regular.bar)
+ println(Regular.blah(next()))
+ println(Derived.foo)
+ println(Derived.bar)
+ println(Derived.blah(next()))
+ println(Util.foo)
+ println(Util.bar)
+ println(Util.blah(next()))
+}
+
+open class Regular {
+ companion object {
+ var foo: String = "Regular::CC::foo[${next()}]"
+ var bar: String = blah(next())
+ fun blah(p: String) = "Regular::CC::blah($p)[${next()}]"
+ }
+}
+
+open class Derived : Regular() {
+ companion object {
+ var foo: String = "Derived::CC::foo[${next()}]"
+ var bar: String = blah(next())
+ fun blah(p: String) = "Derived::CC::blah($p)[${next()}]"
+ }
+}
+
+object Util {
+ var foo: String = "Util::foo[${next()}]"
+ var bar: String = Regular.blah(next()) + Derived.blah(next())
+ fun blah(p: String) = "Util::blah($p)[${next()}]"
+}
diff --git a/third_party/gmail/gmail_android_170604.16.tar.gz.sha1 b/third_party/gmail/gmail_android_170604.16.tar.gz.sha1
index 9d9985c..f57ba90 100644
--- a/third_party/gmail/gmail_android_170604.16.tar.gz.sha1
+++ b/third_party/gmail/gmail_android_170604.16.tar.gz.sha1
@@ -1 +1 @@
-a6d49ef4fb2094672a6f6be039c971727cc9fd34
\ No newline at end of file
+161c569821a5c9b4cb8e99de764f3449191af084
\ No newline at end of file
diff --git a/third_party/gmscore/gmscore_v10.tar.gz.sha1 b/third_party/gmscore/gmscore_v10.tar.gz.sha1
index 535f285..15cab81 100644
--- a/third_party/gmscore/gmscore_v10.tar.gz.sha1
+++ b/third_party/gmscore/gmscore_v10.tar.gz.sha1
@@ -1 +1 @@
-fd2bc157ba2d61a19804107df47e0f87926b210d
\ No newline at end of file
+43838ee1687ff48e866396dfd4b99415662fbea6
\ No newline at end of file
diff --git a/third_party/gmscore/gmscore_v9.tar.gz.sha1 b/third_party/gmscore/gmscore_v9.tar.gz.sha1
index 0ff5779..5983b5b 100644
--- a/third_party/gmscore/gmscore_v9.tar.gz.sha1
+++ b/third_party/gmscore/gmscore_v9.tar.gz.sha1
@@ -1 +1 @@
-4bfdee0d2287b061164f984dfa4ad6ec8617effa
\ No newline at end of file
+0066065faeb293c5a850d3319f2cb8a48d1e760d
\ No newline at end of file
diff --git a/third_party/youtube/youtube.android_12.22.tar.gz.sha1 b/third_party/youtube/youtube.android_12.22.tar.gz.sha1
index 056ff59..8f6813c 100644
--- a/third_party/youtube/youtube.android_12.22.tar.gz.sha1
+++ b/third_party/youtube/youtube.android_12.22.tar.gz.sha1
@@ -1 +1 @@
-57b5c53a80ba010d1faef7da1b643f8c72b3e4e8
\ No newline at end of file
+73c4880898d734064815d0426d8fe84ee6d075b4
\ No newline at end of file