Add initial support for -addconfigurationdebugging
Bug: 73707846
Change-Id: Icc7b82e1fdb69ee7dae074f4e9f2618fd77b992c
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 036c874..458972d 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -659,7 +659,13 @@
// Perform minification.
NamingLens namingLens;
- if (options.getProguardConfiguration().hasApplyMappingFile()) {
+ if (options.configurationDebugging) {
+ if (options.getProguardConfiguration().hasApplyMappingFile() || options.isMinifying()) {
+ options.reporter.info(new StringDiagnostic(
+ "Build is not being obfuscated due to the use of -addconfigurationdebugging"));
+ }
+ namingLens = NamingLens.getIdentityLens();
+ } else if (options.getProguardConfiguration().hasApplyMappingFile()) {
SeedMapper seedMapper =
SeedMapper.seedMapperFromFile(
options.reporter, options.getProguardConfiguration().getApplyMappingFile());
@@ -697,7 +703,7 @@
return;
}
- // Remove unneeeded visibility bridges that have been inserted for member rebinding.
+ // Remove unneeded visibility bridges that have been inserted for member rebinding.
// This can only be done if we have AppInfoWithLiveness.
if (appView.appInfo().hasLiveness()) {
ImmutableSet.Builder<DexMethod> unneededVisibilityBridgeMethods = ImmutableSet.builder();
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 6fede89..6dafe55 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -10,11 +10,17 @@
import static com.android.tools.r8.graph.DexEncodedMethod.CompilationState.PROCESSED_NOT_INLINING_CANDIDATE;
import com.android.tools.r8.cf.code.CfConstNull;
+import com.android.tools.r8.cf.code.CfConstString;
import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfNew;
+import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
+import com.android.tools.r8.cf.code.CfStore;
import com.android.tools.r8.cf.code.CfThrow;
import com.android.tools.r8.code.Const;
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.code.InvokeDirect;
import com.android.tools.r8.code.InvokeStatic;
@@ -47,6 +53,7 @@
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import java.util.Arrays;
import java.util.BitSet;
@@ -54,8 +61,10 @@
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
+import org.objectweb.asm.Opcodes;
public class DexEncodedMethod extends KeyedDexItem<DexMethod> implements ResolutionResult {
+ public static final String CONFIGURATION_DEBUGGING_PREFIX = "Shaking error: Missing method in ";
/**
* Encodes the processing state of a method.
@@ -525,16 +534,11 @@
/**
* Generates a {@link DexCode} object for the given instructions.
- * <p>
- * As the code object is produced outside of the normal compilation cycle, it has to use {@link
- * ConstStringJumbo} to reference string constants. Hence, code produced form these templates
- * might incur a size overhead.
*/
private DexCode generateCodeFromTemplate(
int numberOfRegisters, int outRegisters, Instruction... instructions) {
int offset = 0;
for (Instruction instruction : instructions) {
- assert !(instruction instanceof ConstString);
instruction.setOffset(offset);
offset += instruction.getSize();
}
@@ -591,47 +595,93 @@
return builder.build();
}
- public DexEncodedMethod toMethodThatLogsError(DexItemFactory itemFactory) {
+ public DexEncodedMethod toMethodThatLogsError(AppView<?> appView) {
+ if (appView.options().isGeneratingDex()) {
+ return toMethodThatLogsErrorDexCode(appView.dexItemFactory());
+ } else {
+ return toMethodThatLogsErrorCfCode(appView.dexItemFactory());
+ }
+ }
+
+ private DexEncodedMethod toMethodThatLogsErrorDexCode(DexItemFactory itemFactory) {
checkIfObsolete();
Signature signature = MethodSignature.fromDexMethod(method);
- // TODO(herhut): Construct this out of parts to enable reuse, maybe even using descriptors.
DexString message = itemFactory.createString(
- "Shaking error: Missing method in " + method.holder.toSourceString() + ": "
- + signature);
- DexString tag = itemFactory.createString("TOIGHTNESS");
+ CONFIGURATION_DEBUGGING_PREFIX + method.holder.toSourceString() + ": " + signature);
+ DexString tag = itemFactory.createString("[R8]");
DexType[] args = {itemFactory.stringType, itemFactory.stringType};
DexProto proto = itemFactory.createProto(itemFactory.intType, args);
- DexMethod logMethod = itemFactory
- .createMethod(itemFactory.createType("Landroid/util/Log;"), proto,
- itemFactory.createString("e"));
+ DexMethod logMethod =
+ itemFactory.createMethod(
+ itemFactory.createType("Landroid/util/Log;"), proto, itemFactory.createString("e"));
DexType exceptionType = itemFactory.createType("Ljava/lang/RuntimeException;");
- DexMethod exceptionInitMethod = itemFactory
- .createMethod(exceptionType, itemFactory.createProto(itemFactory.voidType,
- itemFactory.stringType),
+ DexMethod exceptionInitMethod =
+ itemFactory.createMethod(
+ exceptionType,
+ itemFactory.createProto(itemFactory.voidType, itemFactory.stringType),
itemFactory.constructorMethodName);
- DexCode code;
- if (isInstanceInitializer()) {
- // The Java VM Spec requires that a constructor calls an initializer from the super class
- // or another constructor from the current class. For simplicity we do the latter by just
- // calling ourself. This is ok, as the constructor always throws before the recursive call.
- code = generateCodeFromTemplate(3, 2, new ConstStringJumbo(0, tag),
- new ConstStringJumbo(1, message),
- new InvokeStatic(2, logMethod, 0, 1, 0, 0, 0),
- new NewInstance(0, exceptionType),
- new InvokeDirect(2, exceptionInitMethod, 0, 1, 0, 0, 0),
- new Throw(0),
- new InvokeDirect(1, method, 2, 0, 0, 0, 0));
+ DexCode code =
+ generateCodeFromTemplate(2, 2,
+ new ConstString(0, tag),
+ new ConstString(1, message),
+ new InvokeStatic(2, logMethod, 0, 1, 0, 0, 0),
+ new NewInstance(0, exceptionType),
+ new InvokeDirect(2, exceptionInitMethod, 0, 1, 0, 0, 0),
+ new Throw(0));
+ Builder builder = builder(this);
+ builder.setCode(code);
+ setObsolete();
+ return builder.build();
+ }
- } else {
- // These methods might not get registered for jumbo string processing, therefore we always
- // use the jumbo string encoding for the const string instruction.
- code = generateCodeFromTemplate(2, 2, new ConstStringJumbo(0, tag),
- new ConstStringJumbo(1, message),
- new InvokeStatic(2, logMethod, 0, 1, 0, 0, 0),
- new NewInstance(0, exceptionType),
- new InvokeDirect(2, exceptionInitMethod, 0, 1, 0, 0, 0),
- new Throw(0));
+ private DexEncodedMethod toMethodThatLogsErrorCfCode(DexItemFactory itemFactory) {
+ checkIfObsolete();
+ Signature signature = MethodSignature.fromDexMethod(method);
+ DexString message = itemFactory.createString(
+ CONFIGURATION_DEBUGGING_PREFIX + method.holder.toSourceString() + ": " + signature);
+ DexString tag = itemFactory.createString("[R8]");
+ DexType logger = itemFactory.createType("Ljava/util/logging/Logger;");
+ DexMethod getLogger =
+ itemFactory.createMethod(
+ logger,
+ itemFactory.createProto(logger, itemFactory.stringType),
+ itemFactory.createString("getLogger"));
+ DexMethod severe =
+ itemFactory.createMethod(
+ logger,
+ itemFactory.createProto(itemFactory.voidType, itemFactory.stringType),
+ itemFactory.createString("severe"));
+ DexType exceptionType = itemFactory.createType("Ljava/lang/RuntimeException;");
+ DexMethod exceptionInitMethod =
+ itemFactory.createMethod(
+ exceptionType,
+ itemFactory.createProto(itemFactory.voidType, itemFactory.stringType),
+ itemFactory.constructorMethodName);
+ int locals = method.proto.parameters.size() + 1;
+ if (!isStaticMember()) {
+ // Consider `this` pointer
+ locals++;
}
+ ImmutableList.Builder<CfInstruction> instructionBuilder = ImmutableList.builder();
+ instructionBuilder
+ .add(new CfConstString(tag))
+ .add(new CfInvoke(Opcodes.INVOKESTATIC, getLogger, false))
+ .add(new CfStore(ValueType.OBJECT, locals - 1))
+ .add(new CfLoad(ValueType.OBJECT, locals - 1))
+ .add(new CfConstString(message))
+ .add(new CfInvoke(Opcodes.INVOKEVIRTUAL, severe, false))
+ .add(new CfNew(exceptionType))
+ .add(new CfStackInstruction(Opcode.Dup))
+ .add(new CfConstString(message))
+ .add(new CfInvoke(Opcodes.INVOKESPECIAL, exceptionInitMethod, false))
+ .add(new CfThrow());
+ CfCode code = new CfCode(
+ method,
+ 3,
+ locals,
+ instructionBuilder.build(),
+ Collections.emptyList(),
+ Collections.emptyList());
Builder builder = builder(this);
builder.setCode(code);
setObsolete();
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 360a11a..d46a976 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -68,6 +68,7 @@
private boolean forceProguardCompatibility = false;
private boolean overloadAggressively;
private boolean keepRuleSynthesisForRecompilation = false;
+ private boolean configurationDebugging = false;
private Builder(DexItemFactory dexItemFactory, Reporter reporter) {
this.dexItemFactory = dexItemFactory;
@@ -270,6 +271,10 @@
this.keepRuleSynthesisForRecompilation = true;
}
+ public void setConfigurationDebugging(boolean configurationDebugging) {
+ this.configurationDebugging = configurationDebugging;
+ }
+
/**
* This synthesizes a set of keep rules that are necessary in order to be able to successfully
* recompile the generated dex files with the same keep rules.
@@ -326,7 +331,8 @@
adaptClassStrings.build(),
adaptResourceFilenames.build(),
adaptResourceFileContents.build(),
- keepDirectories.build());
+ keepDirectories.build(),
+ configurationDebugging);
reporter.failIfPendingErrors();
@@ -393,6 +399,7 @@
private final ProguardPathFilter adaptResourceFilenames;
private final ProguardPathFilter adaptResourceFileContents;
private final ProguardPathFilter keepDirectories;
+ private final boolean configurationDebugging;
private ProguardConfiguration(
String parsedConfiguration,
@@ -430,7 +437,8 @@
ProguardClassFilter adaptClassStrings,
ProguardPathFilter adaptResourceFilenames,
ProguardPathFilter adaptResourceFileContents,
- ProguardPathFilter keepDirectories) {
+ ProguardPathFilter keepDirectories,
+ boolean configurationDebugging) {
this.parsedConfiguration = parsedConfiguration;
this.dexItemFactory = factory;
this.injars = ImmutableList.copyOf(injars);
@@ -467,6 +475,7 @@
this.adaptResourceFilenames = adaptResourceFilenames;
this.adaptResourceFileContents = adaptResourceFileContents;
this.keepDirectories = keepDirectories;
+ this.configurationDebugging = configurationDebugging;
}
/**
@@ -625,6 +634,10 @@
return seedFile;
}
+ public boolean isConfigurationDebugging() {
+ return configurationDebugging;
+ }
+
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
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 15534bd..c816928 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -87,9 +87,7 @@
"dump");
private static final List<String> WARNED_FLAG_OPTIONS =
- ImmutableList.of(
- // TODO(b/73707846): add support -addconfigurationdebugging
- "addconfigurationdebugging", "useuniqueclassmembernames");
+ ImmutableList.of("useuniqueclassmembernames");
private static final List<String> WARNED_CLASS_DESCRIPTOR_OPTIONS = ImmutableList.of(
// TODO(b/73708157): add support -assumenoexternalsideeffects <class_spec>
@@ -398,6 +396,8 @@
configurationBuilder.addRule(parseIdentifierNameStringRule(optionStart));
} else if (acceptString("if")) {
configurationBuilder.addRule(parseIfRule(optionStart));
+ } else if (acceptString("addconfigurationdebugging")) {
+ configurationBuilder.setConfigurationDebugging(true);
} else {
String unknownOption = acceptString();
String devMessage = "";
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index ba63b85..5b7b182 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -17,7 +17,6 @@
import com.android.tools.r8.graph.PresortedComparable;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.StringDiagnostic;
import com.google.common.base.Predicates;
import com.google.common.collect.Sets;
import java.util.ArrayList;
@@ -47,14 +46,6 @@
public DexApplication run() {
application.timing.begin("Pruning application...");
- InternalOptions options = appView.options();
- if (options.debugKeepRules && options.isShrinking()) {
- options.reporter.info(
- new StringDiagnostic(
- "Debugging keep rules on a minified build might yield broken builds, as "
- + "minification also depends on the used keep rules. We recommend using "
- + "--skip-minification."));
- }
DexApplication result;
try {
result = removeUnused(application).appendDeadCode(usagePrinter.toStringContent()).build();
@@ -74,6 +65,11 @@
InternalOptions options = appView.options();
List<DexProgramClass> newClasses = new ArrayList<>();
for (DexProgramClass clazz : classes) {
+ if (options.configurationDebugging) {
+ newClasses.add(clazz);
+ pruneMembersAndAttributes(clazz);
+ continue;
+ }
if (!appInfo.liveTypes.contains(clazz.type)) {
// The class is completely unused and we can remove it.
if (Log.ENABLED) {
@@ -84,8 +80,7 @@
} else {
newClasses.add(clazz);
if (!appInfo.instantiatedTypes.contains(clazz.type)
- && !options.forceProguardCompatibility
- && (!options.debugKeepRules || !clazz.hasDefaultInitializer())) {
+ && !options.forceProguardCompatibility) {
// The class is only needed as a type but never instantiated. Make it abstract to reflect
// this.
if (clazz.accessFlags.isFinal()) {
@@ -98,46 +93,49 @@
}
clazz.accessFlags.setAbstract();
}
- // The class is used and must be kept. Remove the unused fields and methods from
- // the class.
- usagePrinter.visiting(clazz);
- DexEncodedMethod[] reachableDirectMethods = reachableMethods(clazz.directMethods(), clazz);
- if (reachableDirectMethods != null) {
- clazz.setDirectMethods(reachableDirectMethods);
- }
- DexEncodedMethod[] reachableVirtualMethods =
- reachableMethods(clazz.virtualMethods(), clazz);
- if (reachableVirtualMethods != null) {
- clazz.setVirtualMethods(reachableVirtualMethods);
- }
- DexEncodedField[] reachableInstanceFields = reachableFields(clazz.instanceFields());
- if (reachableInstanceFields != null) {
- clazz.setInstanceFields(reachableInstanceFields);
- }
- DexEncodedField[] reachableStaticFields = reachableFields(clazz.staticFields());
- if (reachableStaticFields != null) {
- clazz.setStaticFields(reachableStaticFields);
- }
- // If the class is a local class, it'll become an ordinary class by renaming.
- // Invalidate its inner-class / enclosing-method attributes early.
- if (appView.options().isMinifying()
- && appView.rootSet().mayBeMinified(clazz.type, appView)
- && clazz.isLocalClass()) {
- assert clazz.getEnclosingMethod() != null;
- assert clazz.getInnerClassAttributeForThisClass() != null;
- clazz.removeEnclosingMethod(Predicates.alwaysTrue());
- InnerClassAttribute innerClassAttribute =
- clazz.getInnerClassAttributeForThisClass();
- clazz.removeInnerClasses(attr -> attr == innerClassAttribute);
- }
- clazz.removeInnerClasses(this::isAttributeReferencingPrunedType);
- clazz.removeEnclosingMethod(this::isAttributeReferencingPrunedItem);
- usagePrinter.visited();
+ // The class is used and must be kept. Remove the unused fields and methods from the class.
+ pruneMembersAndAttributes(clazz);
}
}
return newClasses;
}
+ private void pruneMembersAndAttributes(DexProgramClass clazz) {
+ usagePrinter.visiting(clazz);
+ DexEncodedMethod[] reachableDirectMethods = reachableMethods(clazz.directMethods(), clazz);
+ if (reachableDirectMethods != null) {
+ clazz.setDirectMethods(reachableDirectMethods);
+ }
+ DexEncodedMethod[] reachableVirtualMethods =
+ reachableMethods(clazz.virtualMethods(), clazz);
+ if (reachableVirtualMethods != null) {
+ clazz.setVirtualMethods(reachableVirtualMethods);
+ }
+ DexEncodedField[] reachableInstanceFields = reachableFields(clazz.instanceFields());
+ if (reachableInstanceFields != null) {
+ clazz.setInstanceFields(reachableInstanceFields);
+ }
+ DexEncodedField[] reachableStaticFields = reachableFields(clazz.staticFields());
+ if (reachableStaticFields != null) {
+ clazz.setStaticFields(reachableStaticFields);
+ }
+ // If the class is a local class, it'll become an ordinary class by renaming.
+ // Invalidate its inner-class / enclosing-method attributes early.
+ if (appView.options().isMinifying()
+ && appView.rootSet().mayBeMinified(clazz.type, appView)
+ && clazz.isLocalClass()) {
+ assert clazz.getEnclosingMethod() != null;
+ assert clazz.getInnerClassAttributeForThisClass() != null;
+ clazz.removeEnclosingMethod(Predicates.alwaysTrue());
+ InnerClassAttribute innerClassAttribute =
+ clazz.getInnerClassAttributeForThisClass();
+ clazz.removeInnerClasses(attr -> attr == innerClassAttribute);
+ }
+ clazz.removeInnerClasses(this::isAttributeReferencingPrunedType);
+ clazz.removeEnclosingMethod(this::isAttributeReferencingPrunedItem);
+ usagePrinter.visited();
+ }
+
private boolean isAttributeReferencingPrunedItem(EnclosingMethodAttribute attr) {
AppInfoWithLiveness appInfo = appView.appInfo();
return
@@ -166,11 +164,6 @@
return -1;
}
- private boolean isDefaultConstructor(DexEncodedMethod method) {
- return method.isInstanceInitializer()
- && method.method.proto.parameters.isEmpty();
- }
-
private DexEncodedMethod[] reachableMethods(List<DexEncodedMethod> methods, DexClass clazz) {
AppInfoWithLiveness appInfo = appView.appInfo();
InternalOptions options = appView.options();
@@ -187,12 +180,12 @@
DexEncodedMethod method = methods.get(i);
if (appInfo.liveMethods.contains(method.getKey())) {
reachableMethods.add(method);
- } else if (options.debugKeepRules && isDefaultConstructor(method)) {
+ } else if (options.configurationDebugging) {
// Keep the method but rewrite its body, if it has one.
reachableMethods.add(
method.shouldNotHaveCode() && !method.hasCode()
? method
- : method.toMethodThatLogsError(application.dexItemFactory));
+ : method.toMethodThatLogsError(appView));
} else if (appInfo.targetedMethods.contains(method.getKey())) {
// If the method is already abstract, and doesn't have code, let it be.
if (method.shouldNotHaveCode() && !method.hasCode()) {
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 c00f327..d0538c0 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -103,6 +103,7 @@
if (!proguardConfiguration.isOptimizing()) {
disableAllOptimizations();
}
+ configurationDebugging = proguardConfiguration.isConfigurationDebugging();
}
void disableAllOptimizations() {
@@ -303,7 +304,7 @@
// EXPERIMENTAL flag to get behaviour as close to Proguard as possible.
public boolean forceProguardCompatibility = false;
public boolean disableAssertions = true;
- public boolean debugKeepRules = false;
+ public boolean configurationDebugging = false;
// Read input classes into CfCode format (instead of JarCode).
public boolean enableCfFrontend = false;
// Don't convert Code objects to IRCode.
diff --git a/src/test/java/com/android/tools/r8/TestRunResult.java b/src/test/java/com/android/tools/r8/TestRunResult.java
index edd1492..2e1f053 100644
--- a/src/test/java/com/android/tools/r8/TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/TestRunResult.java
@@ -3,10 +3,10 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8;
+import static org.hamcrest.MatcherAssert.assertThat;
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 com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.utils.AndroidApp;
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 63bed21..001e3201 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -2032,8 +2032,8 @@
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(proguardConfig);
- checkDiagnostics(handler.warnings, proguardConfig, 1, 1,
- "Ignoring", "-addconfigurationdebugging");
+ verifyParserEndsCleanly();
+ assertTrue(parser.getConfig().isConfigurationDebugging());
}
@Test
diff --git a/src/test/java/com/android/tools/r8/shaking/addconfigurationdebugging/ConfigurationDebuggingTest.java b/src/test/java/com/android/tools/r8/shaking/addconfigurationdebugging/ConfigurationDebuggingTest.java
new file mode 100644
index 0000000..7cb0d57
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/addconfigurationdebugging/ConfigurationDebuggingTest.java
@@ -0,0 +1,182 @@
+// Copyright (c) 2019, 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.addconfigurationdebugging;
+
+import static com.android.tools.r8.graph.DexEncodedMethod.CONFIGURATION_DEBUGGING_PREFIX;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+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.MethodSubject;
+import java.nio.file.Path;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+class BaseClass {
+ Object field;
+ BaseClass(Object usedArg) {
+ field = usedArg;
+ }
+}
+
+class UninstantiatedClass extends BaseClass {
+ UninstantiatedClass() {
+ super(null);
+ System.out.println("UninstantiatedClass#<init>");
+ }
+
+ UninstantiatedClass(String arg) {
+ super(arg);
+ System.out.println("UninstantiatedClass#<init>(String)");
+ }
+}
+
+class TestClass {
+ BaseClass b;
+
+ TestClass() {
+ b = new BaseClass(this);
+ System.out.println(b);
+ }
+
+ void foo(int i, long l) {
+ System.out.println("void TestClass#foo(IJ)");
+ }
+
+ static void bar(TestClass arg) {
+ System.out.println("void TestClass#bar(TestClass)");
+ }
+}
+
+class Caller {
+ public static void main(String[] args) {
+ try {
+ new UninstantiatedClass();
+ } catch (RuntimeException e) {
+ }
+ try {
+ new UninstantiatedClass("aaarrrrrrhhhhhh");
+ } catch (RuntimeException e) {
+ }
+
+ TestClass instance = new TestClass();
+ try {
+ instance.foo(4, 2L);
+ } catch (RuntimeException e) {
+ }
+ try {
+ TestClass.bar(instance);
+ } catch (RuntimeException e) {
+ }
+
+ throw new RuntimeException("Reaching the end");
+ }
+}
+
+@RunWith(Parameterized.class)
+public class ConfigurationDebuggingTest extends TestBase {
+ private static final String PACKAGE_NAME =
+ ConfigurationDebuggingTest.class.getPackage().getName();
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().build();
+ }
+
+ public ConfigurationDebuggingTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ Path firstRunArchive =
+ testForR8(parameters.getBackend())
+ .addProgramClasses(BaseClass.class, UninstantiatedClass.class, TestClass.class)
+ .addKeepRules("-addconfigurationdebugging")
+ .addKeepRules("-keep class **.TestClass { <init>(); }")
+ .noMinification()
+ .setMinApi(parameters.getRuntime())
+ .compile()
+ .inspect(this::inspect)
+ .writeToZip();
+
+ R8FullTestBuilder builder =
+ testForR8(parameters.getBackend())
+ .addLibraryClasses(BaseClass.class, UninstantiatedClass.class, TestClass.class)
+ .addLibraryFiles(ToolHelper.getDefaultAndroidJar())
+ .addProgramClasses(Caller.class)
+ .addKeepMainRule(Caller.class)
+ .setMinApi(parameters.getRuntime());
+ R8TestRunResult result =
+ builder
+ .compile()
+ .addRunClasspathFiles(firstRunArchive)
+ .run(parameters.getRuntime(), Caller.class);
+ // TODO(b/117302947): Dex runtime should be able to find that framework class.
+ if (parameters.isDexRuntime()) {
+ result.assertFailureWithErrorThatMatches(containsString("NoClassDefFoundError"));
+ result.assertFailureWithErrorThatMatches(containsString("android.util.Log"));
+ return;
+ }
+ result
+ .assertFailureWithErrorThatMatches(
+ containsString(createExpectedMessage(UninstantiatedClass.class)))
+ .assertFailureWithErrorThatMatches(containsString("void <init>()"))
+ .assertFailureWithErrorThatMatches(containsString("void <init>(java.lang.String)"))
+ .assertFailureWithErrorThatMatches(
+ containsString(createExpectedMessage(TestClass.class)))
+ .assertFailureWithErrorThatMatches(containsString("void foo(int,long)"))
+ .assertFailureWithErrorThatMatches(
+ containsString("void bar(" + PACKAGE_NAME + ".TestClass" +")"))
+ .assertFailureWithErrorThatMatches(containsString("Reaching the end"));
+ }
+
+ private String createExpectedMessage(Class<?> clazz) {
+ return CONFIGURATION_DEBUGGING_PREFIX + clazz.getName();
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject baseClass = inspector.clazz(BaseClass.class);
+ assertThat(baseClass, isPresent());
+ baseClass.allMethods().forEach(methodSubject -> {
+ assertTrue(methodSubject.isInstanceInitializer());
+ assertFalse(hasThrow(methodSubject));
+ });
+
+ ClassSubject uninstantiatedClass = inspector.clazz(UninstantiatedClass.class);
+ assertThat(uninstantiatedClass, isPresent());
+ uninstantiatedClass.allMethods().forEach(methodSubject -> {
+ assertTrue(methodSubject.isInstanceInitializer());
+ assertTrue(hasThrow(methodSubject));
+ });
+
+ ClassSubject testClass = inspector.clazz(TestClass.class);
+ assertThat(testClass, isPresent());
+ MethodSubject foo = testClass.uniqueMethodWithName("foo");
+ assertThat(foo, isPresent());
+ assertTrue(hasThrow(foo));
+ MethodSubject bar = testClass.uniqueMethodWithName("bar");
+ assertThat(bar, isPresent());
+ assertTrue(hasThrow(bar));
+ }
+
+ private static boolean hasThrow(MethodSubject methodSubject) {
+ return methodSubject.iterateInstructions(InstructionSubject::isThrow).hasNext();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/smali/JumboStringTest.java b/src/test/java/com/android/tools/r8/smali/JumboStringTest.java
index 0395177..20fda04 100644
--- a/src/test/java/com/android/tools/r8/smali/JumboStringTest.java
+++ b/src/test/java/com/android/tools/r8/smali/JumboStringTest.java
@@ -4,46 +4,70 @@
package com.android.tools.r8.smali;
-import static org.junit.Assert.assertEquals;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
-import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
-import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.Pair;
import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
+import org.junit.BeforeClass;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+@RunWith(Parameterized.class)
public class JumboStringTest extends SmaliTestBase {
+ private static Pair<StringBuilder, StringBuilder> builders;
+ private final TestParameters parameters;
- @Test
- public void test() throws Exception {
+ @BeforeClass
+ public static void createBuilders() {
StringBuilder builder = new StringBuilder();
StringBuilder expectedBuilder = new StringBuilder();
- builder.append(StringUtils.lines(" new-instance v0, Ljava/lang/StringBuilder;"));
- builder.append(StringUtils.lines(" invoke-direct { v0 }, Ljava/lang/StringBuilder;"
- + "-><init>()V"));
+ builder.append(" new-instance v0, Ljava/lang/StringBuilder;");
+ builder.append(" invoke-direct { v0 }, Ljava/lang/StringBuilder;-><init>()V");
for (int i = 0; i <= 0xffff + 2; i++) {
String prefixed = StringUtils.zeroPrefix(i, 5);
expectedBuilder.append(prefixed);
expectedBuilder.append(StringUtils.lines(""));
- builder.append(StringUtils.lines(" const-string v1, \"" + prefixed + "\\n\""));
+ builder.append(" const-string v1, \"" + prefixed + "\\n\"");
builder.append(
- StringUtils.lines(" invoke-virtual { v0, v1 }, Ljava/lang/StringBuilder;"
- + "->append(Ljava/lang/String;)Ljava/lang/StringBuilder;"));
+ " invoke-virtual { v0, v1 }, Ljava/lang/StringBuilder;"
+ + "->append(Ljava/lang/String;)Ljava/lang/StringBuilder;");
}
builder.append(
- StringUtils.lines(" invoke-virtual { v0 }, Ljava/lang/StringBuilder;"
- + "->toString()Ljava/lang/String;"));
+ " invoke-virtual { v0 }, Ljava/lang/StringBuilder;"
+ + "->toString()Ljava/lang/String;");
builder.append(StringUtils.lines(" move-result-object v0"));
- builder.append(StringUtils.lines(" return-object v0"));
+ builder.append(StringUtils.lines(" return-object v0"));
+ builders = new Pair<>(builder, expectedBuilder);
+ }
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDexRuntimes().build();
+ }
+
+ public JumboStringTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
SmaliBuilder smaliBuilder = new SmaliBuilder(DEFAULT_CLASS_NAME);
- MethodSignature signature = smaliBuilder.addStaticMethod(
+ smaliBuilder.addStaticMethod(
"java.lang.String",
DEFAULT_METHOD_NAME,
ImmutableList.of(),
2,
- builder.toString()
+ builders.getFirst().toString()
);
smaliBuilder.addMainMethod(
@@ -55,10 +79,67 @@
" return-void"
);
- AndroidApp originalApplication = buildApplication(smaliBuilder);
- AndroidApp processedApplication = processApplication(originalApplication);
- String result = runArt(processedApplication);
+ testForR8(parameters.getBackend())
+ .addProgramDexFileData(smaliBuilder.compile())
+ .addKeepMainRule(DEFAULT_CLASS_NAME)
+ .setMinApi(parameters.getRuntime())
+ .run(parameters.getRuntime(), DEFAULT_CLASS_NAME)
+ .assertSuccessWithOutput(builders.getSecond().toString())
+ .inspect(inspector -> {
+ ClassSubject main = inspector.clazz(DEFAULT_CLASS_NAME);
+ assertThat(main, isPresent());
+ MethodSubject method = main.uniqueMethodWithName(DEFAULT_METHOD_NAME);
+ assertThat(method, isPresent());
+ assertTrue(method.streamInstructions().anyMatch(InstructionSubject::isJumboString));
+ });
+ }
- assertEquals(expectedBuilder.toString(), result);
+ @Test
+ public void test_addconfigurationdebugging() throws Exception {
+ SmaliBuilder smaliBuilder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+
+ smaliBuilder.addStaticMethod(
+ "java.lang.String",
+ DEFAULT_METHOD_NAME,
+ ImmutableList.of(),
+ 2,
+ builders.getFirst().toString()
+ );
+
+ // Intentionally dead code, but will be kept due to -addconfigurationdebugging.
+ smaliBuilder.addStaticMethod(
+ "java.lang.String",
+ DEFAULT_METHOD_NAME + "2",
+ ImmutableList.of(),
+ 2,
+ builders.getFirst().toString()
+ );
+
+ smaliBuilder.addMainMethod(
+ 2,
+ " sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+ " invoke-static {}, LTest;->method()Ljava/lang/String;",
+ " move-result-object v1",
+ " invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->print(Ljava/lang/String;)V",
+ " return-void"
+ );
+
+ testForR8(parameters.getBackend())
+ .addProgramDexFileData(smaliBuilder.compile())
+ .addKeepMainRule(DEFAULT_CLASS_NAME)
+ .addKeepRules("-addconfigurationdebugging")
+ .setMinApi(parameters.getRuntime())
+ .run(parameters.getRuntime(), DEFAULT_CLASS_NAME)
+ .assertSuccessWithOutput(builders.getSecond().toString())
+ .inspect(inspector -> {
+ ClassSubject main = inspector.clazz(DEFAULT_CLASS_NAME);
+ assertThat(main, isPresent());
+ MethodSubject method = main.uniqueMethodWithName(DEFAULT_METHOD_NAME);
+ assertThat(method, isPresent());
+ assertTrue(method.streamInstructions().anyMatch(InstructionSubject::isJumboString));
+ MethodSubject method2 = main.uniqueMethodWithName(DEFAULT_METHOD_NAME + "2");
+ assertThat(method2, isPresent());
+ assertTrue(method2.streamInstructions().anyMatch(InstructionSubject::isJumboString));
+ });
}
}
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
index 1223b61..b9f72c4 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
@@ -143,6 +143,11 @@
}
@Override
+ public boolean isJumboString() {
+ return false;
+ }
+
+ @Override
public String getConstString() {
if (instruction instanceof CfConstString) {
return ((CfConstString) instruction).getString().toSourceString();
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
index c195dc0..466f68f 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
@@ -259,6 +259,11 @@
}
@Override
+ public boolean isJumboString() {
+ return instruction instanceof ConstStringJumbo;
+ }
+
+ @Override
public String getConstString() {
if (instruction instanceof ConstString) {
return ((ConstString) instruction).BBBB.toSourceString();
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
index 43ef5d4..0c5a4a6 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
@@ -48,6 +48,8 @@
boolean isConstString(String value, JumboStringMode jumboStringMode);
+ boolean isJumboString();
+
String getConstString();
boolean isConstClass();