Skip to content

Commit

Permalink
Support parsing of wildcard * when used as a field type or method r…
Browse files Browse the repository at this point in the history
…eturn type in class specifications.
  • Loading branch information
mrjameshamilton committed Oct 11, 2023
1 parent 58eae9e commit bfdfa02
Show file tree
Hide file tree
Showing 7 changed files with 356 additions and 48 deletions.
6 changes: 3 additions & 3 deletions base/src/main/java/proguard/ClassSpecification.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ public class ClassSpecification implements Cloneable
public final String extendsAnnotationType;
public final String extendsClassName;

public final List attributeNames = null;
public List fieldSpecifications;
public List methodSpecifications;
public final List<String> attributeNames = null;
public List<MemberSpecification> fieldSpecifications;
public List<MemberSpecification> methodSpecifications;


/**
Expand Down
61 changes: 35 additions & 26 deletions base/src/main/java/proguard/ConfigurationParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -1104,12 +1104,25 @@ private void parseMemberSpecificationArguments(String externalClassN
// Parse the class member type and name part.

// Did we get a special wildcard?
if (ConfigurationConstants.ANY_CLASS_MEMBER_KEYWORD.equals(nextWord) ||
ConfigurationConstants.ANY_FIELD_KEYWORD .equals(nextWord) ||
ConfigurationConstants.ANY_METHOD_KEYWORD .equals(nextWord))
boolean isStar = ConfigurationConstants.ANY_CLASS_MEMBER_KEYWORD.equals(nextWord);
boolean isFields = ConfigurationConstants.ANY_FIELD_KEYWORD.equals(nextWord);
boolean isMethods = ConfigurationConstants.ANY_METHOD_KEYWORD.equals(nextWord);
boolean isFieldsOrMethods = isFields || isMethods;

String type = nextWord;
String typeLocation = reader.locationDescription();

// Try to read the class member name; we need to do this now so that we can check the nextWord
// to see if we're parsing a wildcard type.
readNextWord("class member name", false, false, false);

// Is it a wildcard star (short for all members) or is a type wildcard?
boolean isReallyStar = isStar && ConfigurationConstants.SEPARATOR_KEYWORD.equals(nextWord);

if (isFieldsOrMethods || isReallyStar)
{
// Act according to the type of wildcard.
if (ConfigurationConstants.ANY_CLASS_MEMBER_KEYWORD.equals(nextWord))
if (isStar)
{
checkFieldAccessFlags(requiredSetMemberAccessFlags,
requiredUnsetMemberAccessFlags);
Expand All @@ -1129,10 +1142,10 @@ private void parseMemberSpecificationArguments(String externalClassN
null,
null));
}
else if (ConfigurationConstants.ANY_FIELD_KEYWORD.equals(nextWord))
else if (isFields)
{
checkFieldAccessFlags(requiredSetMemberAccessFlags,
requiredUnsetMemberAccessFlags);
requiredUnsetMemberAccessFlags);

classSpecification.addField(
new MemberSpecification(requiredSetMemberAccessFlags,
Expand All @@ -1141,7 +1154,7 @@ else if (ConfigurationConstants.ANY_FIELD_KEYWORD.equals(nextWord))
null,
null));
}
else if (ConfigurationConstants.ANY_METHOD_KEYWORD.equals(nextWord))
else if (isMethods)
{
checkMethodAccessFlags(requiredSetMemberAccessFlags,
requiredUnsetMemberAccessFlags);
Expand All @@ -1154,9 +1167,6 @@ else if (ConfigurationConstants.ANY_METHOD_KEYWORD.equals(nextWord))
null));
}

// We still have to read the closing separator.
readNextWord("separator '" + ConfigurationConstants.SEPARATOR_KEYWORD + "'");

if (!ConfigurationConstants.SEPARATOR_KEYWORD.equals(nextWord))
{
throw new ParseException("Expecting separator '" + ConfigurationConstants.SEPARATOR_KEYWORD +
Expand All @@ -1165,13 +1175,8 @@ else if (ConfigurationConstants.ANY_METHOD_KEYWORD.equals(nextWord))
}
else
{
// Make sure we have a proper type.
checkJavaIdentifier("java type");
String type = nextWord;
String typeLocation = reader.locationDescription();

readNextWord("class member name");
String name = nextWord;
checkJavaIdentifier("java type", type, true);

// Did we get just one word before the opening parenthesis?
if (ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD.equals(name))
Expand All @@ -1195,7 +1200,7 @@ else if (ConfigurationConstants.ANY_METHOD_KEYWORD.equals(nextWord))
{
// It's not a constructor.
// Make sure we have a proper name.
checkJavaIdentifier("class member name");
checkNextWordIsJavaIdentifier("class member name");

// Read the opening parenthesis or the separating
// semi-colon.
Expand Down Expand Up @@ -1613,7 +1618,7 @@ else if (nextWord.equals(ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD))
{
if (checkJavaIdentifiers)
{
checkJavaIdentifier("java type", allowGenerics);
checkNextWordIsJavaIdentifier("java type", allowGenerics);
}

if (replaceSystemProperties)
Expand Down Expand Up @@ -1880,30 +1885,34 @@ private boolean configurationEnd(boolean expectingAtCharacter)
* Checks whether the given word is a valid Java identifier and throws
* a ParseException if it isn't. Wildcard characters are accepted.
*/
private void checkJavaIdentifier(String expectedDescription)
private void checkNextWordIsJavaIdentifier(String expectedDescription)
throws ParseException
{
checkJavaIdentifier(expectedDescription, true);
checkNextWordIsJavaIdentifier(expectedDescription, true);
}

private void checkNextWordIsJavaIdentifier(String expectedDescription, boolean allowGenerics) throws ParseException
{
checkJavaIdentifier(expectedDescription, nextWord, allowGenerics);
}

/**
* Checks whether the given word is a valid Java identifier and throws
* a ParseException if it isn't. Wildcard characters are accepted.
*/
private void checkJavaIdentifier(String expectedDescription, boolean allowGenerics)
throws ParseException
private void checkJavaIdentifier(String expectedDescription, String identifier, boolean allowGenerics)
throws ParseException
{
if (!isJavaIdentifier(nextWord))
if (!isJavaIdentifier(identifier))
{
throw new ParseException("Expecting " + expectedDescription +
" before " + reader.locationDescription());
" before " + reader.locationDescription());
}

if (!allowGenerics && containsGenerics(nextWord))
if (!allowGenerics && containsGenerics(identifier))
{
throw new ParseException("Generics are not allowed (erased) in " + expectedDescription +
" " + reader.locationDescription());
" " + reader.locationDescription());
}
}

Expand Down
111 changes: 111 additions & 0 deletions base/src/test/kotlin/proguard/ClassSpecificationVisitorFactoryTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,14 @@
package proguard

import io.kotest.core.spec.style.FreeSpec
import io.kotest.matchers.shouldBe
import io.mockk.spyk
import io.mockk.verify
import proguard.classfile.visitor.ClassPoolVisitor
import proguard.classfile.visitor.ClassVisitor
import proguard.classfile.visitor.MemberCounter
import proguard.classfile.visitor.MemberVisitor
import proguard.testutils.AssemblerSource
import proguard.testutils.ClassPoolBuilder
import proguard.testutils.JavaSource
import testutils.asConfiguration
Expand Down Expand Up @@ -106,4 +111,110 @@ class ClassSpecificationVisitorFactoryTest : FreeSpec({
}
}
}

"Given a class with fields of different types" - {
fun createFieldVisitor(config: String, fieldVisitor: MemberVisitor): ClassPoolVisitor =
KeepClassSpecificationVisitorFactory(true, false, false).createClassPoolVisitor(
config.asConfiguration().keep.first(), null, fieldVisitor, null, null
)

val (programClassPool, _) = ClassPoolBuilder.fromSource(
AssemblerSource(
"ClassWithFields.jbc",
"""
public class ClassWithFields {
java.lang.String myField;
int myField;
long myField;
com.example.ClassInAPackage myField;
java.lang.String[] myField;
int[] myField;
com.example.ClassInAPackage[] myField;
ClassWithFields myField;
}
""".trimIndent()
)
)

"Then * should visit any non-primitive, non-array without package separator type fields" {
with(MemberCounter()) {
programClassPool.accept(createFieldVisitor("-keep class ClassWithFields { * myField; }", this))
count shouldBe 1
}
}

"Then ** should visit any non-primitive, non-array separator type fields" {
with(MemberCounter()) {
programClassPool.accept(createFieldVisitor("-keep class ClassWithFields { ** myField; }", this))
count shouldBe 3
}
}

"Then *** should visit all fields" {
with(MemberCounter()) {
programClassPool.accept(createFieldVisitor("-keep class ClassWithFields { *** myField; }", this))
count shouldBe 8
}
}

"Then % should only visit primitive, non-array fields" {
with(MemberCounter()) {
programClassPool.accept(createFieldVisitor("-keep class ClassWithFields { % myField; }", this))
count shouldBe 2
}
}
}

"Given a class with methods of different types" - {
fun createMethodVisitor(config: String, methodVisitor: MemberVisitor): ClassPoolVisitor =
KeepClassSpecificationVisitorFactory(true, false, false).createClassPoolVisitor(
config.asConfiguration().keep.first(), null, null, methodVisitor, null
)

val (programClassPool, _) = ClassPoolBuilder.fromSource(
AssemblerSource(
"ClassWithFields.jbc",
"""
public class ClassWithFields {
public void myMethod(java.lang.String);
public void myMethod(int);
public void myMethod(long);
public void myMethod(com.example.ClassInAPackage);
public void myMethod(java.lang.String[]);
public void myMethod(int[]);
public void myMethod(com.example.ClassInAPackage[]);
public void myMethod(ClassWithFields);
}
""".trimIndent()
)
)

"Then * should visit any non-primitive, non-array without package separator type methods" {
with(MemberCounter()) {
programClassPool.accept(createMethodVisitor("-keep class ClassWithFields { void myMethod(*); }", this))
count shouldBe 1
}
}

"Then ** should visit any non-primitive, non-array separator type methods" {
with(MemberCounter()) {
programClassPool.accept(createMethodVisitor("-keep class ClassWithFields { void myMethod(**); }", this))
count shouldBe 3
}
}

"Then *** should visit all methods" {
with(MemberCounter()) {
programClassPool.accept(createMethodVisitor("-keep class ClassWithFields { void myMethod(***); }", this))
count shouldBe 8
}
}

"Then % should only visit primitive, non-array methods" {
with(MemberCounter()) {
programClassPool.accept(createMethodVisitor("-keep class ClassWithFields { void myMethod(%); }", this))
count shouldBe 2
}
}
}
})
Loading

0 comments on commit bfdfa02

Please sign in to comment.