Warn users of unidentified reflection while minification is allowed.
Bug: 72858955
Change-Id: I6bc8ce2d78659e6a707e9189f7539ea00832847d
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 3288aa9..fffbc44 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
@@ -149,7 +149,8 @@
this.protoLiteRewriter =
options.forceProguardCompatibility ? null : new ProtoLitePruner(appInfo.withLiveness());
if (!appInfo.withLiveness().identifierNameStrings.isEmpty() && options.enableMinification) {
- this.identifierNameStringMarker = new IdentifierNameStringMarker(appInfo.withLiveness());
+ this.identifierNameStringMarker =
+ new IdentifierNameStringMarker(appInfo.withLiveness(), options);
} else {
this.identifierNameStringMarker = null;
}
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 57e8514..581ac8a 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
@@ -6,6 +6,7 @@
import static com.android.tools.r8.naming.IdentifierNameStringUtils.identifyIdentiferNameString;
import static com.android.tools.r8.naming.IdentifierNameStringUtils.inferMemberOrTypeFromNameString;
import static com.android.tools.r8.naming.IdentifierNameStringUtils.isReflectionMethod;
+import static com.android.tools.r8.naming.IdentifierNameStringUtils.warnUndeterminedIdentifier;
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.DexEncodedField;
@@ -31,7 +32,9 @@
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.StaticPut;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.utils.InternalOptions;
import com.google.common.collect.Streams;
import java.util.Arrays;
import java.util.List;
@@ -44,12 +47,14 @@
private final AppInfo appInfo;
private final DexItemFactory dexItemFactory;
private final Set<DexItem> identifierNameStrings;
+ private final InternalOptions options;
- public IdentifierNameStringMarker(AppInfoWithLiveness appInfo) {
+ public IdentifierNameStringMarker(AppInfoWithLiveness appInfo, InternalOptions options) {
this.appInfo = appInfo;
this.dexItemFactory = appInfo.dexItemFactory;
// Note that this info is only available at AppInfoWithLiveness.
this.identifierNameStrings = appInfo.identifierNameStrings;
+ this.options = options;
}
public void decoupleIdentifierNameStringsInFields() {
@@ -77,6 +82,7 @@
}
public void decoupleIdentifierNameStringsInMethod(DexEncodedMethod encodedMethod, IRCode code) {
+ Origin origin = appInfo.originFor(code.method.method.getHolder());
ListIterator<BasicBlock> blocks = code.listIterator();
while (blocks.hasNext()) {
BasicBlock block = blocks.next();
@@ -104,11 +110,17 @@
? instruction.asStaticPut().inValue()
: instruction.asInstancePut().value();
if (!in.isConstString()) {
+ if (options.proguardConfiguration.isObfuscating()) {
+ warnUndeterminedIdentifier(options.reporter, field, origin, instruction, null);
+ }
continue;
}
DexString original = in.getConstInstruction().asConstString().getValue();
DexItemBasedString itemBasedString = inferMemberOrTypeFromNameString(appInfo, original);
if (itemBasedString == null) {
+ if (options.proguardConfiguration.isObfuscating()) {
+ warnUndeterminedIdentifier(options.reporter, field, origin, instruction, original);
+ }
continue;
}
// Move the cursor back to $fieldPut
@@ -162,6 +174,10 @@
if (isReflectionMethod(dexItemFactory, invokedMethod)) {
DexItemBasedString itemBasedString = identifyIdentiferNameString(appInfo, invoke);
if (itemBasedString == null) {
+ if (options.proguardConfiguration.isObfuscating()) {
+ warnUndeterminedIdentifier(
+ options.reporter, invokedMethod, origin, instruction, null);
+ }
continue;
}
DexType returnType = invoke.getReturnType();
@@ -206,12 +222,20 @@
for (int i = 0; i < ins.size(); i++) {
Value in = ins.get(i);
if (!in.isConstString()) {
+ if (options.proguardConfiguration.isObfuscating()) {
+ warnUndeterminedIdentifier(
+ options.reporter, invokedMethod, origin, instruction, null);
+ }
continue;
}
DexString original = in.getConstInstruction().asConstString().getValue();
DexItemBasedString itemBasedString =
inferMemberOrTypeFromNameString(appInfo, original);
if (itemBasedString == null) {
+ if (options.proguardConfiguration.isObfuscating()) {
+ warnUndeterminedIdentifier(
+ options.reporter, invokedMethod, origin, instruction, original);
+ }
continue;
}
// Move the cursor back to $invoke
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
index 4c64cf6..4f66be2 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
@@ -9,6 +9,7 @@
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.DexItem;
import com.android.tools.r8.graph.DexItemBasedString;
import com.android.tools.r8.graph.DexItemFactory;
@@ -23,6 +24,10 @@
import com.android.tools.r8.ir.code.InstructionIterator;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.TextPosition;
+import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.StringDiagnostic;
import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
import java.util.List;
import java.util.Map;
@@ -366,4 +371,23 @@
}
return new DexTypeList(types);
}
+
+ public static void warnUndeterminedIdentifier(
+ Reporter reporter,
+ DexItem member,
+ Origin origin,
+ Instruction instruction,
+ DexString original) {
+ assert member instanceof DexField || member instanceof DexMethod;
+ String kind = member instanceof DexField ? "field" : "method";
+ String originalMessage = original == null ? "what identifier string flows to "
+ : "what '" + original.toString() + "' refers to, which flows to ";
+ reporter.warning(new StringDiagnostic(
+ "Cannot determine " + originalMessage + member.toSourceString()
+ + " that is specified in -identifiernamestring rules."
+ + " Thus, not all identifier strings flowing to that " + kind
+ + " are renamed, which can cause resolution failures at runtime.",
+ origin,
+ new TextPosition(0L, instruction.getPosition().line, 1)));
+ }
}
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 baacb1e..69e0221 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -5,6 +5,7 @@
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.warnUndeterminedIdentifier;
import static com.android.tools.r8.shaking.ProguardConfigurationUtils.buildIdentifierNameStringRule;
import com.android.tools.r8.Diagnostic;
@@ -39,6 +40,7 @@
import com.android.tools.r8.ir.code.Invoke.Type;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.logging.Log;
+import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.RootSetBuilder.ConsequentRootSet;
import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
import com.android.tools.r8.shaking.protolite.ProtoLiteExtension;
@@ -1357,11 +1359,13 @@
}
private void handleProguardReflectiveBehavior(DexEncodedMethod method) {
- IRCode code = method.buildIR(appInfo, options, appInfo.originFor(method.method.holder));
- code.instructionIterator().forEachRemaining(this::handleProguardReflectiveBehavior);
+ Origin origin = appInfo.originFor(method.method.holder);
+ IRCode code = method.buildIR(appInfo, options, origin);
+ code.instructionIterator().forEachRemaining(instr ->
+ handleProguardReflectiveBehavior(instr, origin));
}
- private void handleProguardReflectiveBehavior(Instruction instruction) {
+ private void handleProguardReflectiveBehavior(Instruction instruction, Origin origin) {
if (!instruction.isInvokeMethod()) {
return;
}
@@ -1372,6 +1376,9 @@
}
DexItemBasedString itemBasedString = identifyIdentiferNameString(appInfo, invoke);
if (itemBasedString == null) {
+ if (options.proguardConfiguration.isObfuscating()) {
+ warnUndeterminedIdentifier(options.reporter, invokedMethod, origin, instruction, null);
+ }
return;
}
if (itemBasedString.basedOn instanceof DexType) {
diff --git a/src/test/java/com/android/tools/r8/naming/WarnReflectiveAccessTest.java b/src/test/java/com/android/tools/r8/naming/WarnReflectiveAccessTest.java
new file mode 100644
index 0000000..ddd0aa2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/WarnReflectiveAccessTest.java
@@ -0,0 +1,167 @@
+// 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.DexInspectorMatchers.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 com.android.tools.r8.DiagnosticsChecker;
+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.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.google.common.collect.ImmutableList;
+import java.lang.reflect.Method;
+import java.nio.file.Path;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+class WarnReflectiveAccessTestMain {
+ int counter = 0;
+
+ WarnReflectiveAccessTestMain() {
+ }
+
+ public void foo() {
+ System.out.println("TestMain::foo(" + counter++ + ")");
+ }
+
+ public int boo() {
+ System.out.println("TestMain::boo(" + counter + ")");
+ return counter;
+ }
+
+ public static void main(String[] args) throws Exception {
+ WarnReflectiveAccessTestMain instance = new WarnReflectiveAccessTestMain();
+
+ StringBuilder builder = new StringBuilder();
+ builder.append("f");
+ builder.append("o").append("o");
+ Method foo = instance.getClass().getDeclaredMethod(builder.toString());
+ foo.invoke(instance);
+ }
+}
+
+@RunWith(VmTestRunner.class)
+public class WarnReflectiveAccessTest extends TestBase {
+
+ private KeepingDiagnosticHandler handler;
+ private Reporter reporter;
+
+ @Before
+ public void reset() {
+ handler = new KeepingDiagnosticHandler();
+ reporter = new Reporter(handler);
+ }
+
+ private AndroidApp runR8(
+ byte[][] classes,
+ Class main,
+ Path out,
+ boolean enableMinification,
+ boolean forceProguardCompatibility)
+ throws Exception {
+ String minificationModifier = enableMinification ? ",allowobfuscation " : " ";
+ String reflectionRules = forceProguardCompatibility ? ""
+ : "-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";
+ R8Command.Builder commandBuilder =
+ R8Command.builder(reporter)
+ .addProguardConfiguration(
+ ImmutableList.of(
+ "-keep" + minificationModifier + "class " + main.getCanonicalName() + " {"
+ + " <methods>;"
+ + "}",
+ "-printmapping",
+ reflectionRules),
+ Origin.unknown())
+ .setOutput(out, OutputMode.DexIndexed);
+ for (byte[] clazz : classes) {
+ commandBuilder.addClassProgramData(clazz, Origin.unknown());
+ }
+ commandBuilder.addLibraryFiles(ToolHelper.getDefaultAndroidJar());
+ return ToolHelper.runR8(commandBuilder.build(), o -> {
+ o.enableInlining = false;
+ o.forceProguardCompatibility = forceProguardCompatibility;
+ });
+ }
+
+ private void reflectionWithBuildter(
+ boolean enableMinification,
+ boolean forceProguardCompatibility) throws Exception {
+ byte[][] classes = {
+ ToolHelper.getClassAsBytes(WarnReflectiveAccessTestMain.class)
+ };
+ Path out = temp.getRoot().toPath();
+ AndroidApp processedApp = runR8(classes, WarnReflectiveAccessTestMain.class, out,
+ enableMinification, forceProguardCompatibility);
+
+ String main = WarnReflectiveAccessTestMain.class.getCanonicalName();
+ DexInspector dexInspector = new DexInspector(processedApp);
+ ClassSubject mainSubject = dexInspector.clazz(main);
+ assertThat(mainSubject, isPresent());
+
+ ProcessResult javaOutput = runOnJavaRaw(main, classes);
+ assertEquals(0, javaOutput.exitCode);
+ assertThat(javaOutput.stdout, containsString("TestMain::foo"));
+
+ ProcessResult artOutput = runOnArtRaw(processedApp,
+ enableMinification ? mainSubject.getFinalName() : main);
+ if (enableMinification) {
+ assertNotEquals(0, artOutput.exitCode);
+ assertThat(artOutput.stderr, containsString("NoSuchMethodError"));
+ } else {
+ assertEquals(0, artOutput.exitCode);
+ assertThat(artOutput.stdout, containsString("TestMain::foo"));
+ }
+ }
+
+ @Test
+ public void test_minification_forceProguardCompatibility() throws Exception {
+ reflectionWithBuildter(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,
+ "Cannot determine", "getDeclaredMethod", "-identifiernamestring", "resolution failure");
+ }
+
+ @Test
+ public void test_noMinification_R8() throws Exception {
+ reflectionWithBuildter(false, false);
+ assertFalse(handler.warnings.isEmpty());
+ DiagnosticsChecker.checkDiagnostic(handler.warnings.get(0), (Path) null, 54, 1,
+ "Cannot determine", "getDeclaredMethod", "-identifiernamestring", "resolution failure");
+ }
+
+}
diff --git a/src/test/java/com/android/tools/r8/utils/DexInspector.java b/src/test/java/com/android/tools/r8/utils/DexInspector.java
index 0b8de12..eba3be8 100644
--- a/src/test/java/com/android/tools/r8/utils/DexInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/DexInspector.java
@@ -413,6 +413,8 @@
public abstract String getOriginalDescriptor();
+ public abstract String getFinalName();
+
public abstract String getFinalDescriptor();
public abstract boolean isMemberClass();
@@ -482,6 +484,11 @@
}
@Override
+ public String getFinalName() {
+ return null;
+ }
+
+ @Override
public String getFinalDescriptor() {
return null;
}
@@ -639,7 +646,7 @@
if (naming != null) {
return naming.originalName;
} else {
- return DescriptorUtils.descriptorToJavaType(getFinalDescriptor());
+ return getFinalName();
}
}
@@ -653,6 +660,11 @@
}
@Override
+ public String getFinalName() {
+ return DescriptorUtils.descriptorToJavaType(getFinalDescriptor());
+ }
+
+ @Override
public String getFinalDescriptor() {
return dexClass.type.descriptor.toString();
}