mirror of
https://github.com/Guardsquare/proguard.git
synced 2026-03-13 09:50:34 +08:00
Support a wider char range in class specifications
This commit is contained in:
@@ -39,8 +39,6 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
|
||||
/**
|
||||
* This class parses ProGuard configurations. Configurations can be read from an
|
||||
@@ -51,6 +49,8 @@ import java.util.function.BiFunction;
|
||||
*/
|
||||
public class ConfigurationParser implements AutoCloseable
|
||||
{
|
||||
private final boolean useDalvikVerification = System.getProperty("proguard.use.dalvik.identifier.verification") != null;
|
||||
|
||||
private final WordReader reader;
|
||||
private final Properties properties;
|
||||
|
||||
@@ -1955,7 +1955,7 @@ public class ConfigurationParser implements AutoCloseable
|
||||
private void checkJavaIdentifier(String expectedDescription, String identifier, boolean allowGenerics)
|
||||
throws ParseException
|
||||
{
|
||||
if (!isJavaIdentifier(identifier))
|
||||
if (!isValidIdentifier(identifier))
|
||||
{
|
||||
throw new ParseException("Expecting " + expectedDescription +
|
||||
" before " + reader.locationDescription());
|
||||
@@ -1968,6 +1968,10 @@ public class ConfigurationParser implements AutoCloseable
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isValidIdentifier(String word)
|
||||
{
|
||||
return useDalvikVerification ? isDexIdentifier(word) : isJavaIdentifier(word);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given word is a valid Java identifier.
|
||||
@@ -2002,6 +2006,43 @@ public class ConfigurationParser implements AutoCloseable
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given word is a valid DEX identifier. Special wildcard characters for
|
||||
* ProGuard class specifiction syntaxs are accepted. The list of valid identifier can be
|
||||
* found at https://source.android.com/docs/core/runtime/dex-format#simplename
|
||||
*/
|
||||
private boolean isDexIdentifier(String word) {
|
||||
if (word.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int[] codePoints = word.codePoints().toArray();
|
||||
|
||||
for (int index = 0; index < codePoints.length; index++) {
|
||||
int c = codePoints[index];
|
||||
|
||||
boolean isLetterOrNumber = Character.isLetterOrDigit(c);
|
||||
boolean isValidSymbol = c == '$' || c == '-' || c == '_';
|
||||
boolean isWithinSupportedUnicodeRanges =
|
||||
(c >= 0x00a1 && c <= 0x1fff)
|
||||
|| (c >= 0x2010 && c <= 0x2027)
|
||||
|| (c >= 0x2030 && c <= 0xd7ff)
|
||||
|| (c >= 0xe000 && c <= 0xffef)
|
||||
|| (c >= 0x10000 && c <= 0x10ffff);
|
||||
boolean isProGuardSymbols =
|
||||
c == '.' || c == '[' || c == ']' || c == '<' || c == '>' || c == '-' || c == '!'
|
||||
|| c == '*' || c == '?' || c == '%';
|
||||
|
||||
if (!(isLetterOrNumber
|
||||
|| isValidSymbol
|
||||
|| isWithinSupportedUnicodeRanges
|
||||
|| isProGuardSymbols)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given word contains angle brackets around
|
||||
|
||||
@@ -9,9 +9,13 @@ package proguard
|
||||
|
||||
import io.kotest.assertions.throwables.shouldThrow
|
||||
import io.kotest.core.spec.style.FreeSpec
|
||||
import io.kotest.extensions.system.withSystemProperty
|
||||
import io.kotest.matchers.collections.shouldHaveSize
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.shouldNotBe
|
||||
import io.kotest.matchers.string.shouldContain
|
||||
import io.mockk.every
|
||||
import io.mockk.mockkObject
|
||||
import proguard.classfile.AccessConstants.PUBLIC
|
||||
import testutils.asConfiguration
|
||||
import java.io.ByteArrayOutputStream
|
||||
@@ -39,6 +43,9 @@ class ConfigurationParserTest : FreeSpec({
|
||||
return configuration
|
||||
}
|
||||
|
||||
fun parseRulesAsArguments(rules: String) =
|
||||
rules.split(' ', '\n').toTypedArray()
|
||||
|
||||
"Keep rule tests" - {
|
||||
"Keep rule with <fields> wildcard should be valid" {
|
||||
parseConfiguration("-keep class * { <fields>; }")
|
||||
@@ -428,4 +435,73 @@ class ConfigurationParserTest : FreeSpec({
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"Class specification with unicode identifiers" - {
|
||||
"Given some -keep rules with class specifications containing supported characters for DEX file" - {
|
||||
val rules = """
|
||||
-keep class uu.☱ { *; }
|
||||
-keep class uu.o { ** ☱; }
|
||||
-keep class uu.o { *** ☱(); }
|
||||
-keep class uu.o1
|
||||
-keep class uu.o${"$"}o
|
||||
-keep class uu.o-o
|
||||
-keep class uu.o_o
|
||||
-keep class ‐ { <methods>; }
|
||||
-keep class ‧ { <fields>; }
|
||||
-keep class ‰
|
||||
-keep class * { ** (...); }
|
||||
-keep class { *; }
|
||||
-keep class { int[] foo; }
|
||||
-if class **𐀀 { ** 𐀀*(); }
|
||||
-keep class <1> { ** 𐀀*(); }
|
||||
-keep class * extends
|
||||
-keep class ** implements ☱
|
||||
""".trimIndent()
|
||||
|
||||
val reader = ArgumentWordReader(parseRulesAsArguments(rules), null)
|
||||
mockkObject(reader)
|
||||
every { reader.locationDescription() } returns "dummyOrigin"
|
||||
|
||||
"When the given rules are parsed" - {
|
||||
withSystemProperty("proguard.use.dalvik.identifier.verification", "true") {
|
||||
val configuration = parseConfiguration(reader)
|
||||
"Then 'keep' should contain 16 keep class specifications" {
|
||||
configuration.keep shouldHaveSize 16
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"Given some -keep rules with class specifications containing unsupported identifier for DEX file" - {
|
||||
val rules = """
|
||||
-keep class uu.${String(Character.toChars(0x00a1 - 1))} { *; }
|
||||
-keep class uu.o { ** ${String(Character.toChars(0x1fff + 1))}; }
|
||||
-keep class uu.o { *** ${String(Character.toChars(0x2010 - 1))}(); }
|
||||
-keep class ${String(Character.toChars(0x2027 + 1))} { <methods>; }
|
||||
-keep class ${String(Character.toChars(0x2030 - 1))} { <fields>; }
|
||||
-keep class ${String(Character.toChars(0xd7ff + 1))}
|
||||
-keep class * { ** ${String(Character.toChars(0xe000 - 1))}(...); }
|
||||
-keep class ${String(Character.toChars(0xffef + 1))} { *; }
|
||||
-keep class ${String(Character.toChars(0x10000 - 1))} { int[] foo; }
|
||||
-keep class * extends !
|
||||
-keep class ** implements @
|
||||
-keep class #
|
||||
-keep class %
|
||||
-keep class ^
|
||||
-keep class &
|
||||
-keep class ;
|
||||
-keep class ,
|
||||
""".trimIndent()
|
||||
|
||||
val reader = ArgumentWordReader(parseRulesAsArguments(rules), null)
|
||||
mockkObject(reader)
|
||||
every { reader.locationDescription() } returns "dummyOrigin"
|
||||
|
||||
"When the given rule is parsed, then a ParseException should be thrown" - {
|
||||
withSystemProperty("proguard.use.dalvik.identifier.verification", "true") {
|
||||
shouldThrow<ParseException> { parseConfiguration(reader) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user