Compare commits

...

12 Commits
v7.4 ... v7.4.1

Author SHA1 Message Date
James Hamilton
f6b82b1478 Update example versions to 7.4.1 2023-11-22 11:22:08 +01:00
Bengt Verscheure
789777ded5 Add support for <clinit> in ConfigurationParser 2023-11-17 13:40:46 +01:00
Dimitrios Anyfantakis
e76e47953f Close file handle in ConfigurationWriter 2023-11-03 12:28:07 +01:00
Fergal Whyte
836253f1da Use system-specific line separators in ConfigurationWriterTest 2023-10-26 13:51:26 +02:00
Fergal Whyte
f92fc632b1 Parse alwaysinline and identifiernamestring rules 2023-10-25 17:50:40 +02:00
Fergal Whyte
f5f04cbec5 Fix printing of hash characters in ConfigurationWriter 2023-10-25 17:50:40 +02:00
Fergal Whyte
ce2c8a8b5d Add ConfigurationWriterTest 2023-10-25 17:50:25 +02:00
Fergal Whyte
0032aa037c Prevent NullPointerException when parsing annotations 2023-10-25 17:44:19 +02:00
Fergal Whyte
0070bc9e80 Fix incorrectly configured unit tests 2023-10-25 17:44:11 +02:00
Sebastian Ratz
6f3610bd7b Fix inadvertent closing of System.out when printing configuration (#366)
Co-authored-by: Blend Hamiti <blend.hamiti@guardsquare.com>
2023-10-25 12:15:16 +02:00
James Hamilton
813616d095 Bump version number to 7.4.1 2023-10-25 11:07:22 +02:00
James Hamilton
04123e8f9b Update version number in home.md 2023-10-18 12:01:03 +02:00
15 changed files with 367 additions and 34 deletions

View File

@@ -109,7 +109,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.guardsquare:proguard-gradle:7.4.0'
classpath 'com.guardsquare:proguard-gradle:7.4.1'
}
}
```

View File

@@ -116,6 +116,9 @@ public class ConfigurationConstants
public static final String DONT_PROCESS_KOTLIN_METADATA = "-dontprocesskotlinmetadata";
public static final String OPTIMIZE_AGGRESSIVELY = "-optimizeaggressively";
public static final String ALWAYS_INLINE = "-alwaysinline";
public static final String IDENTIFIER_NAME_STRING = "-identifiernamestring";
public static final String ANY_FILE_KEYWORD = "**";
public static final String ANY_ATTRIBUTE_KEYWORD = "*";

View File

@@ -230,6 +230,8 @@ public class ConfigurationParser implements AutoCloseable
else if (ConfigurationConstants.DUMP_OPTION .startsWith(nextWord)) configuration.dump = parseOptionalFile();
else if (ConfigurationConstants.ADD_CONFIGURATION_DEBUGGING_OPTION .startsWith(nextWord)) configuration.addConfigurationDebugging = parseNoArgument(true);
else if (ConfigurationConstants.OPTIMIZE_AGGRESSIVELY .startsWith(nextWord)) configuration.optimizeConservatively = parseNoArgument(false);
else if (ConfigurationConstants.ALWAYS_INLINE .startsWith(nextWord)) parseUnsupportedR8Rules(ConfigurationConstants.ALWAYS_INLINE, true);
else if (ConfigurationConstants.IDENTIFIER_NAME_STRING .startsWith(nextWord)) parseUnsupportedR8Rules(ConfigurationConstants.IDENTIFIER_NAME_STRING, true);
else
{
throw new ParseException("Unknown option " + reader.locationDescription());
@@ -850,7 +852,7 @@ public class ConfigurationParser implements AutoCloseable
int requiredUnsetClassAccessFlags = 0;
// Parse the class annotations and access modifiers until the class keyword.
while (!ConfigurationConstants.CLASS_KEYWORD.equals(nextWord))
while (!ConfigurationConstants.CLASS_KEYWORD.equals(nextWord) && !configurationEnd(true))
{
// Strip the negating sign, if any.
boolean negated =
@@ -1181,24 +1183,29 @@ public class ConfigurationParser implements AutoCloseable
// Did we get just one word before the opening parenthesis?
if (ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD.equals(name))
{
// This must be a constructor then.
// Make sure the type is a proper constructor name.
if (!(type.equals(ClassConstants.METHOD_NAME_INIT) ||
type.equals(externalClassName) ||
type.equals(ClassUtil.externalShortClassName(externalClassName))))
// This must be an initializer then.
// Make sure the type is a proper initializer name.
if (ClassUtil.isInitializer(type))
{
name = type; // This is either `<init>` or `<clinit>`.
type = JavaTypeConstants.VOID;
}
else if (type.equals(externalClassName) ||
type.equals(ClassUtil.externalShortClassName(externalClassName)))
{
name = ClassConstants.METHOD_NAME_INIT;
type = JavaTypeConstants.VOID;
}
else
{
throw new ParseException("Expecting type and name " +
"instead of just '" + type +
"' before " + reader.locationDescription());
}
// Assign the fixed constructor type and name.
type = JavaTypeConstants.VOID;
name = ClassConstants.METHOD_NAME_INIT;
}
else
{
// It's not a constructor.
// It's not an initializer.
// Make sure we have a proper name.
checkNextWordIsJavaIdentifier("class member name");
@@ -1209,7 +1216,7 @@ public class ConfigurationParser implements AutoCloseable
}
// Check if the type actually contains the use of generics.
// Can not do it right away as we also support "<init>" as a type (see case above).
// Can not do it right away as we also support "<init>" and "<clinit>" as a type (see case above).
if (containsGenerics(type))
{
throw new ParseException("Generics are not allowed (erased) for java type" + typeLocation);
@@ -1290,6 +1297,14 @@ public class ConfigurationParser implements AutoCloseable
"' before " + reader.locationDescription());
}
// Class initializers are not supposed to have any parameters.
if (ClassConstants.METHOD_NAME_CLINIT.equals(name) &&
ClassUtil.internalMethodParameterCount(descriptor) > 0)
{
throw new ParseException("Not expecting method parameters with initializer '" + ClassConstants.METHOD_NAME_CLINIT +
"' before " + reader.locationDescription());
}
// Read the separator after the closing parenthesis.
readNextWord("separator '" + ConfigurationConstants.SEPARATOR_KEYWORD + "'");
@@ -2027,6 +2042,21 @@ public class ConfigurationParser implements AutoCloseable
}
private void parseUnsupportedR8Rules(String option, boolean parseClassSpecification) throws IOException, ParseException
{
readNextWord();
if (parseClassSpecification)
{
parseClassSpecificationArguments();
}
System.out.println("Warning: The R8 option " + option + " is currently not supported by ProGuard.\n" +
"This option will have no effect on the optimized artifact.");
}
/**
* A main method for testing configuration parsing.
*/

View File

@@ -50,7 +50,8 @@ public class ConfigurationWriter implements AutoCloseable
private final PrintWriter writer;
private File baseDir;
private File configurationFile;
private String baseDirName;
/**
@@ -60,7 +61,11 @@ public class ConfigurationWriter implements AutoCloseable
{
this(PrintWriterUtil.createPrintWriterOut(configurationFile));
baseDir = configurationFile.getParentFile();
this.configurationFile = configurationFile;
if (configurationFile.getParentFile() != null)
{
baseDirName = configurationFile.getParentFile().getAbsolutePath() + File.separator;
}
}
@@ -79,7 +84,7 @@ public class ConfigurationWriter implements AutoCloseable
@Override
public void close() throws IOException
{
PrintWriterUtil.closePrintWriter(baseDir, writer);
PrintWriterUtil.closePrintWriter(configurationFile, writer);
}
@@ -799,13 +804,9 @@ public class ConfigurationWriter implements AutoCloseable
String fileName = file.getAbsolutePath();
// See if we can convert the file name into a relative file name.
if (baseDir != null)
if (baseDirName != null && fileName.startsWith(baseDirName))
{
String baseDirName = baseDir.getAbsolutePath() + File.separator;
if (fileName.startsWith(baseDirName))
{
fileName = fileName.substring(baseDirName.length());
}
fileName = fileName.substring(baseDirName.length());
}
return quotedString(fileName);
@@ -819,6 +820,7 @@ public class ConfigurationWriter implements AutoCloseable
{
return string.length() == 0 ||
string.indexOf(' ') >= 0 ||
string.indexOf('#') >= 0 ||
string.indexOf('@') >= 0 ||
string.indexOf('{') >= 0 ||
string.indexOf('}') >= 0 ||
@@ -827,7 +829,7 @@ public class ConfigurationWriter implements AutoCloseable
string.indexOf(':') >= 0 ||
string.indexOf(';') >= 0 ||
string.indexOf(',') >= 0 ? ("'" + string + "'") :
( string );
( string );
}

View File

@@ -48,7 +48,6 @@ import proguard.strip.KotlinAnnotationStripper;
import proguard.util.ConstantMatcher;
import proguard.util.ListParser;
import proguard.util.NameParser;
import proguard.util.PrintWriterUtil;
import proguard.util.StringMatcher;
import proguard.util.kotlin.KotlinUnsupportedVersionChecker;
import proguard.util.kotlin.asserter.KotlinMetadataVerifier;
@@ -297,9 +296,7 @@ public class ProGuard
*/
private void printConfiguration() throws IOException
{
PrintWriter pw = PrintWriterUtil.createPrintWriterOut(configuration.printConfiguration);
try (ConfigurationWriter configurationWriter = new ConfigurationWriter(pw))
try (ConfigurationWriter configurationWriter = new ConfigurationWriter(configuration.printConfiguration))
{
configurationWriter.write(configuration);
}

View File

@@ -11,8 +11,11 @@ import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.FreeSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.string.shouldContain
import proguard.classfile.AccessConstants.PUBLIC
import testutils.asConfiguration
import java.io.ByteArrayOutputStream
import java.io.PrintStream
/**
* Some simple testcases to catch special cases when parsing the Configuration.
@@ -61,6 +64,31 @@ class ConfigurationParserTest : FreeSpec({
parseConfiguration("-keep class * { public protected <methods>; }")
}
"Keep rule with ClassName should be valid" {
val configuration = parseConfiguration("-keep class ClassName { ClassName(); }")
val keep = configuration.keep.single().methodSpecifications.single()
keep.name shouldBe "<init>"
keep.descriptor shouldBe "()V"
}
"Keep rule with ClassName and external class com.example.ClassName should be valid" {
val configuration = parseConfiguration("-keep class com.example.ClassName { ClassName(); }")
val keep = configuration.keep.single().methodSpecifications.single()
keep.name shouldBe "<init>"
keep.descriptor shouldBe "()V"
}
"Keep rule with <clinit> should be valid" {
val configuration = parseConfiguration("-keep class ** { <clinit>(); }")
val keep = configuration.keep.single().methodSpecifications.single()
keep.name shouldBe "<clinit>"
keep.descriptor shouldBe "()V"
}
"Keep rule with <clinit> and non-empty argument list should throw ParseException" {
shouldThrow<ParseException> { parseConfiguration("-keep class * { void <clinit>(int) }") }
}
"Keep rule with * member wildcard and return type should be valid" {
parseConfiguration("-keep class * { java.lang.String *; }")
}
@@ -82,6 +110,97 @@ class ConfigurationParserTest : FreeSpec({
}
}
"A ParseException should be thrown with invalid annotation config at the end of the file" - {
// This is a parse error without any further config after it.
val configStr = ("-keep @MyAnnotation @ThisShouldBeInterfaceKeyword")
"Then the option should throw a ParseException" {
shouldThrow<ParseException> {
configStr.asConfiguration()
}
}
}
"Testing -alwaysinline parsing" - {
"Given an empty configuration" - {
val savedPrintStream = System.out
val customOutputStream = ByteArrayOutputStream()
System.setOut(PrintStream(customOutputStream))
parseConfiguration("")
"The option does not print anything" {
customOutputStream.toString() shouldContain ""
System.setOut(savedPrintStream)
}
}
"Given a configuration with -alwaysinline" - {
val savedPrintStream = System.out
val customOutputStream = ByteArrayOutputStream()
System.setOut(PrintStream(customOutputStream))
parseConfiguration(
"""-alwaysinline class * {
@org.chromium.build.annotations.AlwaysInline *;
}
"""
)
"The option prints out a warning" {
customOutputStream.toString() shouldContain "Warning: The R8 option -alwaysinline is currently not supported by ProGuard.\n" +
"This option will have no effect on the optimized artifact."
System.setOut(savedPrintStream)
}
}
"Given a configuration with -alwaysinline with no class specification" - {
"The parsing should throw an exception" {
shouldThrow<ParseException> { parseConfiguration("-alwaysinline") }
}
}
}
"Testing -identifiernamestring parsing" - {
"Given an empty configuration" - {
val savedPrintStream = System.out
val customOutputStream = ByteArrayOutputStream()
System.setOut(PrintStream(customOutputStream))
parseConfiguration("")
"The option does not print anything" {
customOutputStream.toString() shouldContain ""
System.setOut(savedPrintStream)
}
}
"Given a configuration with -identifiernamestring" - {
val savedPrintStream = System.out
val customOutputStream = ByteArrayOutputStream()
System.setOut(PrintStream(customOutputStream))
parseConfiguration(
"""-identifiernamestring class * {
@org.chromium.build.annotations.IdentifierNameString *;
}
"""
)
"The option prints out a warning" {
customOutputStream.toString() shouldContain "Warning: The R8 option -identifiernamestring is currently not supported by ProGuard.\n" +
"This option will have no effect on the optimized artifact."
System.setOut(savedPrintStream)
}
}
"Given a configuration with -identifiernamestring with no class specification" - {
"The parsing should throw an exception" {
shouldThrow<ParseException> { parseConfiguration("-identifiernamestring") }
}
}
}
"Wildcard type tests" - {
class TestConfig(
val configOption: String,

View File

@@ -0,0 +1,172 @@
package proguard
import io.kotest.core.spec.style.FreeSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.string.shouldContain
import java.io.PrintWriter
import java.io.StringWriter
/**
* Test printing of the configuration (-printconfiguration option).
*/
class ConfigurationWriterTest : FreeSpec({
val EOL = System.lineSeparator()
fun printConfiguration(rules: String): String {
val out = StringWriter()
val configuration = Configuration()
ConfigurationParser(rules, "", null, System.getProperties()).use {
it.parse(configuration)
}
ConfigurationWriter(PrintWriter(out)).use {
it.write(configuration)
}
return out.toString().trim()
}
"Keep rules tests" - {
"Keep class constructor should be kept" {
val rules = """
-keep class * {
<init>();
}
""".trimIndent()
val out = printConfiguration(rules)
out shouldBe rules
}
"Keep class initializer should be kept" {
val rules = """
-keep class * {
<clinit>();
}
""".trimIndent()
val out = printConfiguration(rules)
val expected = """
-keep class * {
void <clinit>();
}
""".trimIndent()
out shouldBe expected
}
"Keep class initializer should respect allowobfuscation flag" {
val rules = """
-keep,allowobfuscation class ** extends com.example.A {
<clinit>();
}
""".trimIndent()
val out = printConfiguration(rules)
val expected = """
-keep,allowobfuscation class ** extends com.example.A {
void <clinit>();
}
""".trimIndent()
out shouldBe expected
}
}
"Hash character handling tests" - {
"Option parameters with hash characters should be quoted" {
printConfiguration("-keystorepassword '#tester'") shouldBe "-keystorepassword '#tester'"
}
"Comments should not be quoted" {
printConfiguration("# comment$EOL-keep class **") shouldBe "# comment$EOL-keep class **"
}
"Hash characters in comments should not be quoted" {
printConfiguration("# #comment$EOL-keep class **") shouldBe "# #comment$EOL-keep class **"
}
}
"Given a -dontnote rule specifying a class name" - {
val rules = "-dontnote com.example.MyClass"
"When the rule is parsed and printed out again" - {
val out = printConfiguration(rules)
"Then the printed rule should be the same as the given rule" {
out shouldBe rules
}
}
}
"Given a -dontnote rule specifying a class name with wildcards" - {
val rules = "-dontnote com.example.**"
"When the rule is parsed and printed out again" - {
val out = printConfiguration(rules)
"Then the printed rule should be the same as the given rule" {
out shouldBe rules
}
}
}
"Given a -addconfigurationdebugging rule" - {
val rules = "-addconfigurationdebugging"
"When the rules are parsed and printed out again" - {
val out = printConfiguration(rules)
"Then the rules should be present in the output" {
out shouldContain rules
}
}
}
"Given an -optimizeaggressively rule" - {
val rules = "-optimizeaggressively"
"When the rule is parsed and printed out again" - {
val out = printConfiguration(rules)
"Then the rule should be present in the output" {
out shouldBe rules
}
}
}
"Given a -alwaysinline rule" - {
val rules = "-alwaysinline class ** {*;}"
"When the rule is parsed and printed out again" - {
val out = printConfiguration(rules)
"Then the rule should be not be present in the output" {
out shouldBe ""
}
}
"When the rule does not exist it shouldn't be printed out" - {
val out = printConfiguration("")
"Then the rule should not be present in the output" {
out shouldBe ""
}
}
}
"Given a -identifiernamestring rule" - {
val rules = "-identifiernamestring class ** {*;}"
"When the rule is parsed and printed out again" - {
val out = printConfiguration(rules)
"Then the rule should be not be present in the output" {
out shouldBe ""
}
}
"When the rule does not exist it shouldn't be printed out" - {
val out = printConfiguration("")
"Then the rule should not be present in the output" {
out shouldBe ""
}
}
}
})

View File

@@ -1,4 +1,4 @@
Welcome to the manual for **ProGuard** version 7.3 ([what's new?](releasenotes.md)).
Welcome to the manual for **ProGuard** version 7.4 ([what's new?](releasenotes.md)).
ProGuard is an open-sourced Java class file shrinker, optimizer, obfuscator, and
preverifier. As a result, ProGuard processed applications and libraries are smaller and faster.

View File

@@ -1,3 +1,13 @@
## Version 7.4.1
### Bugfixes
- Fix inadvertent closing of System.out when printing configuration. (#365)
### Added
- Support for parsing of `<clinit>` methods without specifying the return type in class specifications.
## Version 7.4
### Java support

View File

@@ -7,7 +7,7 @@ buildscript {
google()
}
dependencies {
classpath 'com.guardsquare:proguard-gradle:7.4.0'
classpath 'com.guardsquare:proguard-gradle:7.4.1'
}
}

View File

@@ -7,7 +7,7 @@ buildscript {
google()
}
dependencies {
classpath 'com.guardsquare:proguard-gradle:7.4.0'
classpath 'com.guardsquare:proguard-gradle:7.4.1'
}
}

View File

@@ -7,7 +7,7 @@ buildscript {
google()
}
dependencies {
classpath 'com.guardsquare:proguard-gradle:7.4.0'
classpath 'com.guardsquare:proguard-gradle:7.4.1'
}
}

View File

@@ -82,7 +82,7 @@ class GradlePluginIntegrationTest : FreeSpec({
}
}
"gradle plugin can be configured via #configOption" - {
"gradle plugin can be configured via #configOption" {
include(testConfigOption("proguard"))
include(testConfigOption("proguardWithConfigFile"))
include(testConfigOption("proguardWithGeneratedConfigFile"))

View File

@@ -32,7 +32,7 @@ import testutils.TestPluginClasspath
class ProguardCacheRelocateabilityIntegrationTest : FreeSpec({
"proguard task can be relocated" - {
"proguard task can be relocated" {
val cacheDir = tempdir()
val fixture = File(ProguardCacheRelocateabilityIntegrationTest::class.java.classLoader.getResource("spring-boot").path)

View File

@@ -1,4 +1,4 @@
proguardVersion = 7.4.0
proguardVersion = 7.4.1
# The version of ProGuardCORE that sub-projects are built with
proguardCoreVersion = 9.1.0