Version 1.4.89
Cherry pick: Raise parse error when type name is negated
CL: https://r8-review.googlesource.com/c/r8/+/37421
Bug: 130665986
Change-Id: Ic5f3a449ccf78f6eb64b5114448006b26a82ab5c
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 18c648d..3c1c551 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.4.88";
+ public static final String LABEL = "1.4.89";
private Version() {
}
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 10d3695..4a4a182 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -942,8 +942,9 @@
boolean found = true;
while (found && !eof()) {
found = false;
+ boolean negated = parseNegation();
ProguardAccessFlags flags =
- parseNegation() ? ruleBuilder.getNegatedAccessFlags() : ruleBuilder.getAccessFlags();
+ negated ? ruleBuilder.getNegatedAccessFlags() : ruleBuilder.getAccessFlags();
skipWhitespace();
switch (peekChar()) {
case 'a':
@@ -999,6 +1000,12 @@
default:
// Intentionally left empty.
}
+
+ // Ensure that we do not consume a negation character '!' when the subsequent identifier
+ // does not match a valid access flag name (e.g., "private !int x").
+ if (!found && negated) {
+ unacceptString("!");
+ }
}
}
@@ -1006,6 +1013,11 @@
ProguardMemberRule.Builder ruleBuilder, boolean allowValueSpecification)
throws ProguardRuleParserException {
skipWhitespace();
+ if (!eof() && peekChar() == '!') {
+ throw parseError(
+ "Unexpected character '!': "
+ + "The negation character can only be used to negate access flags");
+ }
if (acceptString("<methods>")) {
ruleBuilder.setRuleType(ProguardMemberType.ALL_METHODS);
} else if (acceptString("<fields>")) {
diff --git a/src/test/java/com/android/tools/r8/proguard/rules/NegatedClassMemberTest.java b/src/test/java/com/android/tools/r8/proguard/rules/NegatedClassMemberTest.java
new file mode 100644
index 0000000..27747f5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/proguard/rules/NegatedClassMemberTest.java
@@ -0,0 +1,107 @@
+// 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.proguard.rules;
+
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class NegatedClassMemberTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ public NegatedClassMemberTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testProguard() {
+ try {
+ testForProguard()
+ .addProgramClasses(
+ NegatedClassMemberTestClassA.class,
+ NegatedClassMemberTestClassB.class,
+ NegatedClassMemberTestClassC.class)
+ .addKeepRules(getKeepRule())
+ .setMinApi(parameters.getRuntime())
+ .compile();
+
+ // For some reason, Proguard fails with "The output jar is empty". One likely explanation is
+ // that Proguard only keeps classes that have a field y with type "!long", which is not a
+ // valid type name, and therefore never matches anything.
+ fail("Expected Proguard to fail with \"The output jar is empty\"");
+ } catch (CompilationFailedException e) {
+ assertThat(e.getMessage(), containsString("The output jar is empty"));
+ }
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ try {
+ testForR8(Backend.DEX)
+ .addProgramClasses(
+ NegatedClassMemberTestClassA.class,
+ NegatedClassMemberTestClassB.class,
+ NegatedClassMemberTestClassC.class)
+ .addKeepRules(getKeepRule())
+ .compile();
+ fail("Expected R8 to fail during parsing of the Proguard configuration file");
+ } catch (CompilationFailedException e) {
+ int expectedOffset = getKeepRule().indexOf("!");
+ int expectedColumn = expectedOffset + 1;
+ assertThat(
+ e.getCause().getMessage(),
+ allOf(
+ containsString(
+ "Error: offset: "
+ + expectedOffset
+ + ", line: 1, column: "
+ + expectedColumn
+ + ", Unexpected character '!': "
+ + "The negation character can only be used to negate access flags"),
+ containsString(
+ StringUtils.joinLines(
+ "-keepclasseswithmembers class ** { long x; !long y; }",
+ " ^"))));
+ }
+ }
+
+ private String getKeepRule() {
+ return "-keepclasseswithmembers class ** { long x; !long y; }";
+ }
+}
+
+class NegatedClassMemberTestClassA {
+
+ long x = System.currentTimeMillis();
+}
+
+class NegatedClassMemberTestClassB {
+
+ long x = System.currentTimeMillis();
+ private long y = System.currentTimeMillis();
+}
+
+class NegatedClassMemberTestClassC {
+
+ long x = System.currentTimeMillis();
+ int y = (int) System.currentTimeMillis();
+}
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 7516576..7805217 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -2464,7 +2464,7 @@
}
@Test
- public void b124181032() throws Exception {
+ public void b124181032() {
// Test spaces and quotes in class name list.
ProguardConfigurationParser parser;
parser = new ProguardConfigurationParser(new DexItemFactory(), reporter);
@@ -2535,4 +2535,22 @@
assertEquals("*", rule.getClassNames().toString());
}
}
+
+ @Test
+ public void negatedMemberTypeTest() throws IOException {
+ Path proguardConfigurationFile = writeTextToTempFile("-keepclassmembers class ** { !int x; }");
+ try {
+ new ProguardConfigurationParser(new DexItemFactory(), reporter)
+ .parse(proguardConfigurationFile);
+ fail("Expected to fail since the type name cannot be negated.");
+ } catch (AbortException e) {
+ checkDiagnostics(
+ handler.errors,
+ proguardConfigurationFile,
+ 1,
+ 30,
+ "Unexpected character '!': "
+ + "The negation character can only be used to negate access flags");
+ }
+ }
}