Compare commits

...

26 Commits
v7.5 ... v7.7

Author SHA1 Message Date
James Hamilton
ef6a8352bd Update ProGuardCORE version for Java 24 support (#470)
Update ProGuardCORE version for Java 24 support
2025-03-24 13:28:56 +01:00
niccolo.piazzesi
e225e56a8d Add link to the maven distribution and proguard release in the retrace page. 2025-03-24 11:25:13 +01:00
Ruben Pieters
4288cce536 Bump proguardCore version to include MethodLinker changes.
Includes the change from this proguard-core PR: https://github.com/Guardsquare/proguard-core/pull/133 .

Verified on the jar from the reproducing project: https://github.com/LlamaLad7/slow-proguard-example .
Takes ~45s before, ~10s after.
2025-03-19 12:04:53 +01:00
James Hamilton
3456cf330e Move source files to standard locations (#464) 2025-02-20 12:36:09 +01:00
niccolo.piazzesi
fbcf41fd67 Remove bad import 2024-12-13 14:26:54 +01:00
niccolo.piazzesi
bacde1cede Limit size of strings to 65535 bytes 2024-12-13 11:31:50 +01:00
Thomas Vochten
430a04502d Bump version to 7.6.2 2024-12-12 14:11:44 +01:00
Thomas Vochten
08adfa5552 Bump version to 7.6.1 in the manual 2024-12-12 12:13:01 +01:00
Thomas Vochten
89b1e55ea2 Add release notes for version 7.6.1, bump ProGuardCORE version 2024-12-12 11:27:45 +01:00
Bengt Verscheure
f4c4a13a90 Remove Guardsquare community link. 2024-12-10 15:53:19 +01:00
Thomas Vochten
c1eafc7b6b Log ACD rules for parameterless constructors 2024-12-06 12:47:35 +01:00
Thomas Vochten
73860de626 Discard empty Kotlin metadata 2024-12-06 12:47:35 +01:00
Thomas Vochten
ff66baaced Remove references to encryption 2024-12-04 12:30:55 +01:00
Thomas Vochten
174d3f4155 Upgrade Gradle, upgrade dependencies, bump version to 7.6.1 2024-11-29 08:49:38 +01:00
Thomas Vochten
f5352fece7 Clean up Kotlin verification in ProGuard.java 2024-11-27 09:36:51 +01:00
Bengt Verscheure
844f3d76be Remove variable push replacements optimization 2024-11-20 09:27:50 +01:00
niccolo.piazzesi
7b6712e840 Replace all PartialEvaluator constructor calls with builder calls 2024-10-25 16:19:41 +02:00
James Hamilton
dd4b8bde06 Update version to 7.6.0 2024-10-02 17:42:30 +02:00
James Hamilton
b3deed8286 Update versions for version 7.6 (#440) 2024-09-27 14:43:19 +02:00
alonalbert
8903bfb23f Separate Multiple Frames With a Newline (#433)
When an obfuscated frame resolves to multiple clear frames, separate them with a newline. Closes #432
2024-09-19 15:37:06 +02:00
Jelle De Coninck
03d7effdd2 Improve DictionaryNameFactory performance 2024-09-13 15:02:53 +02:00
James Hamilton
c2146ae315 Update ProGuardCORE version (#429) 2024-08-26 09:23:20 +02:00
James Hamilton
4e643b4f60 Update ProGuardCORE version (#421) 2024-07-18 12:48:54 +02:00
daphnis.chevreton
ee3deb69fa Support a wider char range in class specifications 2024-07-02 10:48:14 +02:00
daphnis.chevreton
6075d17bee Fix gradlew.bat newline chars 2024-07-02 10:48:10 +02:00
James Hamilton
3a9b11bb3c Bump version to 7.5.1 2024-05-29 18:07:33 +02:00
194 changed files with 3778 additions and 3079 deletions

View File

@@ -81,8 +81,7 @@ number of facade classes that construct and run these chains. Notably:
- `Initializer` initializes links between the code and resources, to traverse
the data structures more easily.
- `Marker` marks code and resources to be kept, encrypted, etc., based on the
configuration.
- `Marker` marks code and resources to be kept etc. based on the configuration.
- `Backporter` backports code to older versions of Java.
@@ -103,8 +102,8 @@ At a high level, the flow of data inside ProGuard is as follows:
- Traverse the input data, to parse any useful data structures.
- Process the data structures in a number of steps (mainly shrinking, string
encryption, optimization, obfuscation, class encryption).
- Process the data structures in a number of steps (mainly shrinking, optimization,
obfuscation).
- Traverse the input data again, this time to write to the output, by copying,
transforming, replacing, or removing data entries. The transformations can

View File

@@ -60,7 +60,6 @@ bytecode:
The resulting applications and libraries are smaller and faster.
## ❓ Getting Help
If you have **usage or general questions** please ask them in the <a href="https://community.guardsquare.com/?utm_source=github&utm_medium=site-link&utm_campaign=github-community">**Guardsquare Community**.</a>
Please use <a href="https://github.com/guardsquare/proguard/issues">**the issue tracker**</a> to report actual **bugs 🐛, crashes**, etc.
<br />
<br />
@@ -109,7 +108,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.guardsquare:proguard-gradle:7.5.0'
classpath 'com.guardsquare:proguard-gradle:7.7.0'
}
}
```
@@ -193,7 +192,7 @@ guide](CONTRIBUTING.md) if you would like to contribute.
## 📝 License
Copyright (c) 2002-2023 [Guardsquare NV](https://www.guardsquare.com/).
Copyright (c) 2002-2025 [Guardsquare NV](https://www.guardsquare.com/).
ProGuard is released under the [GNU General Public License, version
2](LICENSE), with [exceptions granted to a number of
projects](docs/md/manual/license/gplexception.md).

View File

@@ -3,19 +3,6 @@ plugins {
id 'maven-publish'
}
sourceSets.main {
java {
srcDirs = ['src']
}
resources {
srcDirs = ['src']
include '**/*.properties'
include '**/*.gif'
include '**/*.png'
include '**/*.pro'
}
}
afterEvaluate {
publishing {
publications.getByName(project.name) {

View File

@@ -10,23 +10,9 @@ repositories {
mavenCentral()
}
sourceSets.main {
java {
srcDirs = ['src']
include '**/*.java'
}
resources {
srcDirs = ['src']
include '**/*.properties'
include '**/*.gif'
include '**/*.png'
include '**/*.pro'
}
}
dependencies {
implementation project(':base')
implementation 'org.apache.ant:ant:1.9.7'
implementation 'org.apache.ant:ant:1.10.15'
}
task fatJar(type: ShadowJar) {

View File

@@ -2,10 +2,10 @@ plugins {
id 'java-library'
id 'java-test-fixtures'
id 'maven-publish'
id "org.jetbrains.kotlin.jvm" version "$kotlinVersion"
id 'com.adarshr.test-logger' version '3.0.0'
id "org.jetbrains.kotlin.jvm"
id 'com.adarshr.test-logger' version '4.0.0'
id 'jacoco'
id "org.jlleitschuh.gradle.ktlint" version '10.2.1'
id "org.jlleitschuh.gradle.ktlint" version '12.1.2'
}
repositories {
@@ -21,17 +21,17 @@ tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile) {
dependencies {
api "com.guardsquare:proguard-core:${proguardCoreVersion}"
implementation "com.google.code.gson:gson:${gsonVersion}"
implementation 'org.apache.logging.log4j:log4j-api:2.19.0'
implementation 'org.apache.logging.log4j:log4j-core:2.19.0'
implementation 'org.apache.logging.log4j:log4j-api:2.24.2'
implementation 'org.apache.logging.log4j:log4j-core:2.24.2'
implementation 'org.json:json:20231013'
testImplementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
testImplementation "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"
testImplementation 'dev.zacsweers.kctfork:core:0.5.0-alpha07'
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.9.0' // for kotest framework
testImplementation 'io.kotest:kotest-assertions-core-jvm:5.9.0' // for kotest core jvm assertions
testImplementation 'io.kotest:kotest-property-jvm:5.9.0' // for kotest property test
testImplementation 'io.mockk:mockk:1.13.11' // for mocking
testImplementation 'dev.zacsweers.kctfork:core:0.6.0'
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.9.1' // for kotest framework
testImplementation 'io.kotest:kotest-assertions-core-jvm:5.9.1' // for kotest core jvm assertions
testImplementation 'io.kotest:kotest-property-jvm:5.9.1' // for kotest property test
testImplementation 'io.mockk:mockk:1.13.13' // for mocking
testImplementation(testFixtures("com.guardsquare:proguard-core:${proguardCoreVersion}")) {
exclude group: 'com.guardsquare', module: 'proguard-core'
@@ -49,7 +49,7 @@ jar {
// Early access automatic downloads are not yet supported:
// https://github.com/gradle/gradle/issues/14814
// But it will work if e.g. Java N-ea is pre-installed
def javaVersionsForTest = 9..22
def javaVersionsForTest = 9..23
test {
useJUnitPlatform()
@@ -64,7 +64,7 @@ task testAllJavaVersions() { testAllTask ->
ignoreFailures = true
// The version of bytebuddy used by mockk only supports Java 22 experimentally so far
if (version >= 22) systemProperty 'net.bytebuddy.experimental', true
// if (version >= 22) systemProperty 'net.bytebuddy.experimental', true
testAllTask.dependsOn(it)

View File

@@ -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

View File

@@ -31,6 +31,7 @@ import proguard.configuration.InitialStateInfo;
import proguard.evaluation.IncompleteClassHierarchyException;
import proguard.logging.Logging;
import proguard.mark.Marker;
import proguard.normalize.StringNormalizer;
import proguard.obfuscate.NameObfuscationReferenceFixer;
import proguard.obfuscate.ObfuscationPreparation;
import proguard.obfuscate.Obfuscator;
@@ -53,7 +54,6 @@ import proguard.util.kotlin.KotlinUnsupportedVersionChecker;
import proguard.util.kotlin.asserter.KotlinMetadataVerifier;
import java.io.IOException;
import java.io.PrintWriter;
/**
* Tool for shrinking, optimizing, obfuscating, and preverifying Java classes.
@@ -220,6 +220,7 @@ public class ProGuard
configuration.obfuscate)
{
expandPrimitiveArrayConstants();
normalizeStrings();
}
if (configuration.targetClassVersion != 0)
@@ -270,6 +271,11 @@ public class ProGuard
}
}
private void normalizeStrings() throws Exception {
passRunner.run(new StringNormalizer(),appView);
}
/**
* Checks the GPL.
@@ -352,11 +358,7 @@ public class ProGuard
}
passRunner.run(new Initializer(configuration), appView);
if (configuration.keepKotlinMetadata &&
configuration.enableKotlinAsserter)
{
passRunner.run(new KotlinMetadataVerifier(configuration), appView);
}
verifyKotlinMetadata();
}
@@ -442,11 +444,7 @@ public class ProGuard
// Perform the actual shrinking.
passRunner.run(new Shrinker(configuration, afterOptimizer), appView);
if (configuration.keepKotlinMetadata &&
configuration.enableKotlinAsserter)
{
passRunner.run(new KotlinMetadataVerifier(configuration), appView);
}
verifyKotlinMetadata();
}
@@ -510,11 +508,7 @@ public class ProGuard
// Fix the Kotlin modules so the filename matches and the class names match.
passRunner.run(new NameObfuscationReferenceFixer(configuration), appView);
if (configuration.keepKotlinMetadata &&
configuration.enableKotlinAsserter)
{
passRunner.run(new KotlinMetadataVerifier(configuration), appView);
}
verifyKotlinMetadata();
}
@@ -526,6 +520,13 @@ public class ProGuard
passRunner.run(new KotlinMetadataAdapter(), appView);
}
private void verifyKotlinMetadata() throws Exception {
if (configuration.keepKotlinMetadata &&
configuration.enableKotlinAsserter)
{
passRunner.run(new KotlinMetadataVerifier(configuration), appView);
}
}
/**
* Expands primitive array constants back to traditional primitive array

View File

@@ -363,7 +363,7 @@ public class ConfigurationLogger implements Runnable
constructorParameters,
false);
if (constructorInfo != null && !isKept(constructorInfo) && (constructorParameters == null || constructorParameters.length > 0)) {
if (constructorInfo != null && !isKept(constructorInfo)) {
String signature = signatureString(INIT, constructorParameters, true);
if (shouldLog(reflectedClass, sMissingMethods, signature))
{

View File

@@ -93,7 +93,7 @@ implements DataEntryReader
if (clazz == null)
{
// The class is no longer in the classpool or encrypted class pool so it must have been shrunk.
// The class is no longer in the classpool, so it must have been shrunk.
// obfuscated name (original name, because it was removed from class pool so not obfuscated)
dataOutputStream.writeUTF(ClassUtil.externalClassName(className));
dataOutputStream.writeUTF(ClassUtil.externalClassName(initialStateInfo.getSuperClassName(className)));

View File

@@ -0,0 +1,21 @@
package proguard.normalize;
import proguard.AppView;
import proguard.classfile.visitor.ParallelAllClassVisitor;
import proguard.pass.Pass;
/**
* Ensures all strings are at most 65535 bytes in length, when encoded as modified UTF-8.
*
* @see LargeStringSplitter
*/
public class StringNormalizer implements Pass {
@Override
public void execute(AppView appView) throws Exception {
appView.programClassPool.accept(
new ParallelAllClassVisitor(
() -> new LargeStringSplitter(appView.programClassPool, appView.libraryClassPool)));
}
}

View File

@@ -20,267 +20,229 @@
*/
package proguard.obfuscate;
import java.io.*;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Reader;
import java.net.URL;
import java.util.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
/**
* This <code>NameFactory</code> generates names that are read from a
* specified input file.
* Comments (everything starting with '#' on a single line) are ignored.
* This <code>NameFactory</code> generates names that are read from a specified input file. Comments
* (everything starting with '#' on a single line) are ignored.
*
* @author Eric Lafortune
*/
public class DictionaryNameFactory implements NameFactory
{
private static final char COMMENT_CHARACTER = '#';
public class DictionaryNameFactory implements NameFactory {
private static final char COMMENT_CHARACTER = '#';
private final List<String> names;
private Set<String> nameSet;
private final NameFactory nameFactory;
private int index = 0;
private final List names;
private final NameFactory nameFactory;
/**
* Creates a new <code>DictionaryNameFactory</code>.
*
* @param url The URL from which the names can be read.
* @param nameFactory The name factory from which names will be retrieved if the list of read
* names has been exhausted.
*/
public DictionaryNameFactory(URL url, NameFactory nameFactory) throws IOException {
this(url, true, nameFactory);
}
private int index = 0;
/**
* Creates a new <code>DictionaryNameFactory</code>.
*
* @param url The URL from which the names can be read.
* @param validJavaIdentifiers Specifies whether the produced names should be valid Java
* identifiers.
* @param nameFactory The name factory from which names will be retrieved if the list of read
* names has been exhausted.
*/
public DictionaryNameFactory(URL url, boolean validJavaIdentifiers, NameFactory nameFactory)
throws IOException {
this(
new BufferedReader(new InputStreamReader(url.openStream(), StandardCharsets.UTF_8)),
validJavaIdentifiers,
nameFactory);
}
/**
* Creates a new <code>DictionaryNameFactory</code>.
*
* @param file The file from which the names can be read.
* @param nameFactory The name factory from which names will be retrieved if the list of read
* names has been exhausted.
*/
public DictionaryNameFactory(File file, NameFactory nameFactory) throws IOException {
this(file, true, nameFactory);
}
/**
* Creates a new <code>DictionaryNameFactory</code>.
* @param url the URL from which the names can be read.
* @param nameFactory the name factory from which names will be retrieved
* if the list of read names has been exhausted.
*/
public DictionaryNameFactory(URL url,
NameFactory nameFactory) throws IOException
{
this(url, true, nameFactory);
}
/**
* Creates a new <code>DictionaryNameFactory</code>.
*
* @param file The file from which the names can be read.
* @param validJavaIdentifiers Specifies whether the produced names should be valid Java
* identifiers.
* @param nameFactory The name factory from which names will be retrieved if the list of read
* names has been exhausted.
*/
public DictionaryNameFactory(File file, boolean validJavaIdentifiers, NameFactory nameFactory)
throws IOException {
this(
new BufferedReader(
new InputStreamReader(Files.newInputStream(file.toPath()), StandardCharsets.UTF_8)),
validJavaIdentifiers,
nameFactory);
}
/**
* Creates a new <code>DictionaryNameFactory</code>.
*
* @param reader The reader from which the names can be read. The reader is closed at the end.
* @param nameFactory The name factory from which names will be retrieved if the list of read
* names has been exhausted.
*/
public DictionaryNameFactory(Reader reader, NameFactory nameFactory) throws IOException {
this(reader, true, nameFactory);
}
/**
* Creates a new <code>DictionaryNameFactory</code>.
* @param url the URL from which the names can be read.
* @param validJavaIdentifiers specifies whether the produced names should
* be valid Java identifiers.
* @param nameFactory the name factory from which names will be
* retrieved if the list of read names has been
* exhausted.
*/
public DictionaryNameFactory(URL url,
boolean validJavaIdentifiers,
NameFactory nameFactory) throws IOException
{
this (new BufferedReader(
new InputStreamReader(
url.openStream(), "UTF-8")),
validJavaIdentifiers,
nameFactory);
}
/**
* Creates a new <code>DictionaryNameFactory</code>.
*
* @param reader The reader from which the names can be read. The reader is closed at the end.
* @param validJavaIdentifiers Specifies whether the produced names should be valid Java
* identifiers.
* @param nameFactory The name factory from which names will be retrieved if the list of read
* names has been exhausted.
*/
public DictionaryNameFactory(Reader reader, boolean validJavaIdentifiers, NameFactory nameFactory)
throws IOException {
this.nameSet = readDictionary(reader, validJavaIdentifiers);
this.nameFactory = nameFactory;
this.names = new ArrayList<>(this.nameSet);
}
private static Set<String> readDictionary(Reader reader, boolean validJavaIdentifiers)
throws IOException {
try {
Set<String> names = new LinkedHashSet<>();
StringBuilder builder = new StringBuilder();
/**
* Creates a new <code>DictionaryNameFactory</code>.
* @param file the file from which the names can be read.
* @param nameFactory the name factory from which names will be retrieved
* if the list of read names has been exhausted.
*/
public DictionaryNameFactory(File file,
NameFactory nameFactory) throws IOException
{
this(file, true, nameFactory);
}
while (true) {
// Read the next character.
int c = reader.read();
// Is it a valid identifier character?
if (c != -1
&& (validJavaIdentifiers
? (builder.length() == 0
? Character.isJavaIdentifierStart((char) c)
: Character.isJavaIdentifierPart((char) c))
: (c != '\n' && c != '\r' && c != COMMENT_CHARACTER))) {
// Append it to the current identifier.
builder.append((char) c);
} else {
// Did we collect a new identifier?
if (builder.length() > 0) {
// Add the completed name to the list of names, if it's
// not in it yet.
String name = builder.toString();
names.add(name);
/**
* Creates a new <code>DictionaryNameFactory</code>.
* @param file the file from which the names can be read.
* @param validJavaIdentifiers specifies whether the produced names should
* be valid Java identifiers.
* @param nameFactory the name factory from which names will be
* retrieved if the list of read names has been
* exhausted.
*/
public DictionaryNameFactory(File file,
boolean validJavaIdentifiers,
NameFactory nameFactory) throws IOException
{
this (new BufferedReader(
new InputStreamReader(
new FileInputStream(file), "UTF-8")),
validJavaIdentifiers,
nameFactory);
}
// Clear the builder.
builder.setLength(0);
}
// Is this the beginning of a comment line?
if (c == COMMENT_CHARACTER) {
// Skip all characters till the end of the line.
do {
c = reader.read();
} while (c != -1 && c != '\n' && c != '\r');
}
/**
* Creates a new <code>DictionaryNameFactory</code>.
* @param reader the reader from which the names can be read. The
* reader is closed at the end.
* @param nameFactory the name factory from which names will be retrieved
* if the list of read names has been exhausted.
*/
public DictionaryNameFactory(Reader reader,
NameFactory nameFactory) throws IOException
{
this(reader, true, nameFactory);
}
/**
* Creates a new <code>DictionaryNameFactory</code>.
* @param reader the reader from which the names can be read.
* The reader is closed at the end.
* @param validJavaIdentifiers specifies whether the produced names should
* be valid Java identifiers.
* @param nameFactory the name factory from which names will be
* retrieved if the list of read names has been
* exhausted.
*/
public DictionaryNameFactory(Reader reader,
boolean validJavaIdentifiers,
NameFactory nameFactory) throws IOException
{
this.names = new ArrayList();
this.nameFactory = nameFactory;
try
{
StringBuffer buffer = new StringBuffer();
while (true)
{
// Read the next character.
int c = reader.read();
// Is it a valid identifier character?
if (c != -1 &&
(validJavaIdentifiers ?
(buffer.length() == 0 ?
Character.isJavaIdentifierStart((char)c) :
Character.isJavaIdentifierPart((char)c)) :
(c != '\n' &&
c != '\r' &&
c != COMMENT_CHARACTER)))
{
// Append it to the current identifier.
buffer.append((char)c);
}
else
{
// Did we collect a new identifier?
if (buffer.length() > 0)
{
// Add the completed name to the list of names, if it's
// not in it yet.
String name = buffer.toString();
if (!names.contains(name))
{
names.add(name);
}
// Clear the buffer.
buffer.setLength(0);
}
// Is this the beginning of a comment line?
if (c == COMMENT_CHARACTER)
{
// Skip all characters till the end of the line.
do
{
c = reader.read();
}
while (c != -1 &&
c != '\n' &&
c != '\r');
}
// Is this the end of the file?
if (c == -1)
{
// Just return.
return;
}
}
}
}
finally
{
reader.close();
// Is this the end of the file?
if (c == -1) {
// Just return.
return names;
}
}
}
} finally {
reader.close();
}
}
/**
* Creates a new <code>DictionaryNameFactory</code>.
*
* @param dictionaryNameFactory The dictionary name factory whose dictionary will be used.
* @param nameFactory The name factory from which names will be retrieved if the list of read
* names has been exhausted.
*/
public DictionaryNameFactory(
DictionaryNameFactory dictionaryNameFactory, NameFactory nameFactory) {
this.names = dictionaryNameFactory.names;
this.nameFactory = nameFactory;
}
// Implementations for NameFactory.
public void reset() {
index = 0;
nameFactory.reset();
}
public String nextName() {
String name;
// Do we still have names?
if (index < names.size()) {
// Return the next name.
name = names.get(index++);
} else {
if (nameSet == null) {
nameSet = new HashSet<>(names);
}
// Return the next different name from the other name factory.
do {
name = nameFactory.nextName();
} while (nameSet.contains(name));
}
return name;
}
/**
* Creates a new <code>DictionaryNameFactory</code>.
* @param dictionaryNameFactory the dictionary name factory whose dictionary
* will be used.
* @param nameFactory the name factory from which names will be
* retrieved if the list of read names has been
* exhausted.
*/
public DictionaryNameFactory(DictionaryNameFactory dictionaryNameFactory,
NameFactory nameFactory)
{
this.names = dictionaryNameFactory.names;
this.nameFactory = nameFactory;
}
// Implementations for NameFactory.
public void reset()
{
index = 0;
nameFactory.reset();
}
public String nextName()
{
String name;
// Do we still have names?
if (index < names.size())
{
// Return the next name.
name = (String)names.get(index++);
}
else
{
// Return the next different name from the other name factory.
do
{
name = nameFactory.nextName();
}
while (names.contains(name));
}
return name;
}
public static void main(String[] args)
{
try
{
DictionaryNameFactory factory =
new DictionaryNameFactory(new File(args[0]), new SimpleNameFactory());
// For debugging, we're always using UTF-8 instead of the default
// character encoding, even for writing to the standard output.
PrintWriter out =
new PrintWriter(new OutputStreamWriter(System.out, "UTF-8"));
for (int counter = 0; counter < 50; counter++)
{
out.println("[" + factory.nextName() + "]");
}
out.flush();
}
catch (IOException ex)
{
ex.printStackTrace();
}
public static void main(String[] args) {
try {
DictionaryNameFactory factory =
new DictionaryNameFactory(new File(args[0]), new SimpleNameFactory());
// For debugging, we're always using UTF-8 instead of the default
// character encoding, even for writing to the standard output.
PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out, StandardCharsets.UTF_8));
for (int counter = 0; counter < 50; counter++) {
out.println("[" + factory.nextName() + "]");
}
out.flush();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}

View File

@@ -21,14 +21,13 @@
package proguard.obfuscate;
/**
* This interfaces provides methods to generate unique sequences of names.
* The names must be valid Java identifiers.
* This interfaces provides methods to generate unique sequences of names. The names must be valid
* Java identifiers.
*
* @author Eric Lafortune
*/
public interface NameFactory
{
public void reset();
public interface NameFactory {
void reset();
public String nextName();
String nextName();
}

View File

@@ -20,41 +20,33 @@
*/
package proguard.obfuscate;
/**
* NameFactory that prepends the names of the wrapped NameFactory with
* a fixed prefix.
* NameFactory that prepends the names of the wrapped NameFactory with a fixed prefix.
*
* @author Johan Leys
*/
public class PrefixingNameFactory implements NameFactory
{
private final NameFactory delegateNameFactory;
private final String prefix;
public class PrefixingNameFactory implements NameFactory {
private final NameFactory delegateNameFactory;
private final String prefix;
/**
* Creates a new PrefixingNameFactory.
*
* @param delegateNameFactory The wrapped NameFactory.
* @param prefix The prefix to add to all generated names.
*/
public PrefixingNameFactory(NameFactory delegateNameFactory, String prefix) {
this.delegateNameFactory = delegateNameFactory;
this.prefix = prefix;
}
/**
* Creates a new PrefixingNameFactory.
* @param delegateNameFactory the wrapped NameFactory.
* @param prefix the prefix to add to all generated names.
*/
public PrefixingNameFactory(NameFactory delegateNameFactory,
String prefix)
{
this.delegateNameFactory = delegateNameFactory;
this.prefix = prefix;
}
// Implementations for NameFactory.
public String nextName() {
return prefix + delegateNameFactory.nextName();
}
// Implementations for NameFactory.
public String nextName()
{
return prefix + delegateNameFactory.nextName();
}
public void reset()
{
delegateNameFactory.reset();
}
public void reset() {
delegateNameFactory.reset();
}
}

View File

@@ -23,126 +23,94 @@ package proguard.obfuscate;
import java.util.Arrays;
/**
* This <code>NameFactory</code> generates unique short names, using mixed-case
* characters or lower-case characters only.
* This <code>NameFactory</code> generates unique short names, using mixed-case characters or
* lower-case characters only.
*
* @author Eric Lafortune
*/
public class SimpleNameFactory implements NameFactory
{
private static final int CHARACTER_COUNT = 26;
public class SimpleNameFactory implements NameFactory {
private static final int CHARACTER_COUNT = 26;
/**
+ * Array of windows reserved names.
+ * This array does not include COM{digit} or LPT{digit} as {@link SimpleNameFactory} does not generate digits.
+ * This array must be sorted in ascending order as we're using {@link Arrays#binarySearch(Object[], Object)} on it.
+ */
private static final String[] reservedNames = new String[] {"AUX", "CON", "NUL", "PRN"};
/**
* + * Array of windows reserved names. + * This array does not include COM{digit} or LPT{digit}
* as {@link SimpleNameFactory} does not generate digits. + * This array must be sorted in
* ascending order as we're using {@link Arrays#binarySearch(Object[], Object)} on it. +
*/
private static final String[] reservedNames = new String[] {"AUX", "CON", "NUL", "PRN"};
private final boolean generateMixedCaseNames;
private int index = 0;
private final boolean generateMixedCaseNames;
private int index = 0;
/**
* Creates a new <code>SimpleNameFactory</code> that generates mixed-case names.
*/
public SimpleNameFactory()
{
this(true);
/** Creates a new <code>SimpleNameFactory</code> that generates mixed-case names. */
public SimpleNameFactory() {
this(true);
}
/**
* Creates a new <code>SimpleNameFactory</code>.
*
* @param generateMixedCaseNames A flag to indicate whether the generated names will be
* mixed-case, or lower-case only.
*/
public SimpleNameFactory(boolean generateMixedCaseNames) {
this.generateMixedCaseNames = generateMixedCaseNames;
}
// Implementations for NameFactory.
public void reset() {
index = 0;
}
public String nextName() {
return name(index++);
}
/** Returns the name at the given index. */
private String name(int index) {
// Create a new name for this index
return newName(index);
}
/** Creates and returns the name at the given index. */
private String newName(int index) {
// If we're allowed to generate mixed-case names, we can use twice as many characters.
int totalCharacterCount = generateMixedCaseNames ? 2 * CHARACTER_COUNT : CHARACTER_COUNT;
int baseIndex = index / totalCharacterCount;
int offset = index % totalCharacterCount;
char newChar = charAt(offset);
String newName = baseIndex == 0 ? String.valueOf(newChar) : (name(baseIndex - 1) + newChar);
if (Arrays.binarySearch(reservedNames, newName.toUpperCase()) >= 0) {
newName += newChar;
}
return newName;
}
/**
* Returns the character with the given index, between 0 and the number of acceptable characters.
*/
private char charAt(int index) {
return (char) ((index < CHARACTER_COUNT ? 'a' : 'A' - CHARACTER_COUNT) + index);
}
/**
* Creates a new <code>SimpleNameFactory</code>.
* @param generateMixedCaseNames a flag to indicate whether the generated
* names will be mixed-case, or lower-case only.
*/
public SimpleNameFactory(boolean generateMixedCaseNames)
{
this.generateMixedCaseNames = generateMixedCaseNames;
}
// Implementations for NameFactory.
public void reset()
{
index = 0;
}
public String nextName()
{
return name(index++);
}
/**
* Returns the name at the given index.
*/
private String name(int index)
{
// Create a new name for this index
return newName(index);
}
/**
* Creates and returns the name at the given index.
*/
private String newName(int index)
{
// If we're allowed to generate mixed-case names, we can use twice as
// many characters.
int totalCharacterCount = generateMixedCaseNames ?
2 * CHARACTER_COUNT :
CHARACTER_COUNT;
int baseIndex = index / totalCharacterCount;
int offset = index % totalCharacterCount;
char newChar = charAt(offset);
String newName = baseIndex == 0 ?
new String(new char[] { newChar }) :
(name(baseIndex-1) + newChar);
if (Arrays.binarySearch(reservedNames, newName.toUpperCase()) >= 0)
{
newName += newChar;
}
return newName;
}
/**
* Returns the character with the given index, between 0 and the number of
* acceptable characters.
*/
private char charAt(int index)
{
return (char)((index < CHARACTER_COUNT ? 'a' - 0 :
'A' - CHARACTER_COUNT) + index);
}
public static void main(String[] args)
{
System.out.println("Some mixed-case names:");
printNameSamples(new SimpleNameFactory(true), 60);
System.out.println("Some lower-case names:");
printNameSamples(new SimpleNameFactory(false), 60);
System.out.println("Some more mixed-case names:");
printNameSamples(new SimpleNameFactory(true), 80);
System.out.println("Some more lower-case names:");
printNameSamples(new SimpleNameFactory(false), 80);
}
private static void printNameSamples(SimpleNameFactory factory, int count)
{
for (int counter = 0; counter < count; counter++)
{
System.out.println(" ["+factory.nextName()+"]");
}
public static void main(String[] args) {
System.out.println("Some mixed-case names:");
printNameSamples(new SimpleNameFactory(true), 60);
System.out.println("Some lower-case names:");
printNameSamples(new SimpleNameFactory(false), 60);
System.out.println("Some more mixed-case names:");
printNameSamples(new SimpleNameFactory(true), 80);
System.out.println("Some more lower-case names:");
printNameSamples(new SimpleNameFactory(false), 80);
}
private static void printNameSamples(SimpleNameFactory factory, int count) {
for (int counter = 0; counter < count; counter++) {
System.out.println(" [" + factory.nextName() + "]");
}
}
}

View File

@@ -21,63 +21,46 @@
package proguard.obfuscate;
/**
* This <code>NameFactory</code> generates names that are special, by appending
* a suffix.
* This <code>NameFactory</code> generates names that are special, by appending a suffix.
*
* @author Eric Lafortune
*/
public class SpecialNameFactory implements NameFactory
{
private static final char SPECIAL_SUFFIX = '_';
public class SpecialNameFactory implements NameFactory {
private static final char SPECIAL_SUFFIX = '_';
private final NameFactory nameFactory;
private final NameFactory nameFactory;
/**
* Creates a new <code>SpecialNameFactory</code>.
*
* @param nameFactory The name factory from which original names will be retrieved.
*/
public SpecialNameFactory(NameFactory nameFactory) {
this.nameFactory = nameFactory;
}
// Implementations for NameFactory.
/**
* Creates a new <code>SpecialNameFactory</code>.
* @param nameFactory the name factory from which original names will be
* retrieved.
*/
public SpecialNameFactory(NameFactory nameFactory)
{
this.nameFactory = nameFactory;
}
// Implementations for NameFactory.
public void reset()
{
nameFactory.reset();
}
public String nextName()
{
return nameFactory.nextName() + SPECIAL_SUFFIX;
}
// Small utility methods.
/**
* Returns whether the given name is special.
*/
static boolean isSpecialName(String name)
{
return name != null &&
name.charAt(name.length()-1) == SPECIAL_SUFFIX;
}
public static void main(String[] args)
{
SpecialNameFactory factory = new SpecialNameFactory(new SimpleNameFactory());
for (int counter = 0; counter < 50; counter++)
{
System.out.println("["+factory.nextName()+"]");
}
public void reset() {
nameFactory.reset();
}
public String nextName() {
return nameFactory.nextName() + SPECIAL_SUFFIX;
}
// Small utility methods.
/** Returns whether the given name is special. */
static boolean isSpecialName(String name) {
return name != null && name.charAt(name.length() - 1) == SPECIAL_SUFFIX;
}
public static void main(String[] args) {
SpecialNameFactory factory = new SpecialNameFactory(new SimpleNameFactory());
for (int counter = 0; counter < 50; counter++) {
System.out.println("[" + factory.nextName() + "]");
}
}
}

View File

@@ -23,71 +23,56 @@ package proguard.obfuscate;
import proguard.classfile.Clazz;
/**
* NameFactory which only generates names that don't exist yet as members
* on the class for which it is created.
* NameFactory which only generates names that don't exist yet as members on the class for which it
* is created.
*
* @author Johan Leys
*/
public class UniqueMemberNameFactory implements NameFactory
{
private static final String INJECTED_MEMBER_PREFIX = "$$";
public class UniqueMemberNameFactory implements NameFactory {
private static final String INJECTED_MEMBER_PREFIX = "$$";
private final NameFactory delegateNameFactory;
private final Clazz clazz;
private final NameFactory delegateNameFactory;
private final Clazz clazz;
/**
* Utility for creating a new NameFactory that can generate names for injected members: the
* generated names are unique within the given class, and don't clash with non-injected members of
* its super classes.
*
* @param clazz The class for which to generate a NameFactory.
* @return The new NameFactory instance.
*/
public static UniqueMemberNameFactory newInjectedMemberNameFactory(Clazz clazz) {
return new UniqueMemberNameFactory(
new PrefixingNameFactory(new SimpleNameFactory(), INJECTED_MEMBER_PREFIX), clazz);
}
/**
* Utility for creating a new NameFactory that can generate names for injected
* members: the generated names are unique within the given class, and don't
* clash with non-injected members of its super classes.
*
* @param clazz the class for which to generate a NameFactory.
* @return the new NameFactory instance.
*/
public static UniqueMemberNameFactory newInjectedMemberNameFactory(Clazz clazz)
{
return new UniqueMemberNameFactory(
new PrefixingNameFactory(
new SimpleNameFactory(), INJECTED_MEMBER_PREFIX), clazz);
}
/**
* Creates a new UniqueMemberNameFactory.
*
* @param delegateNameFactory The delegate NameFactory, used for generating new candidate names.
* @param clazz The class in which to check for existing member names.
*/
public UniqueMemberNameFactory(NameFactory delegateNameFactory, Clazz clazz) {
this.delegateNameFactory = delegateNameFactory;
this.clazz = clazz;
}
// Implementations for NameFactory.
/**
* Creates a new UniqueMemberNameFactory.
* @param delegateNameFactory the delegate NameFactory, used for generating
* new candidate names.
* @param clazz the class in which to check for existing
* member names.
*/
public UniqueMemberNameFactory(NameFactory delegateNameFactory,
Clazz clazz)
{
this.delegateNameFactory = delegateNameFactory;
this.clazz = clazz;
}
public String nextName() {
String name;
// Check if the name doesn't exist yet. We don't have additional
// descriptor information, so we can only search on the name.
do {
name = delegateNameFactory.nextName();
} while (clazz.findField(name, null) != null || clazz.findMethod(name, null) != null);
// Implementations for NameFactory.
return name;
}
public String nextName()
{
String name;
// Check if the name doesn't exist yet. We don't have additional
// descriptor information, so we can only search on the name.
do
{
name = delegateNameFactory.nextName();
}
while (clazz.findField(name, null) != null ||
clazz.findMethod(name, null) != null);
return name;
}
public void reset()
{
delegateNameFactory.reset();
}
}
public void reset() {
delegateNameFactory.reset();
}
}

View File

@@ -936,7 +936,11 @@ public class Optimizer implements Pass
new AllMethodVisitor(
new AllAttributeVisitor(
new DebugAttributeVisitor("Filling out fields, method parameters, and return values in synthetic classes",
new PartialEvaluator(detailedValueFactory, storingInvocationUnit, false))))));
PartialEvaluator.Builder.create()
.setValueFactory(detailedValueFactory)
.setInvocationUnit(storingInvocationUnit)
.setEvaluateAllCode(false)
.build())))));
// Evaluate non-synthetic classes. We may need to evaluate all
// casts, to account for downcasts when specializing descriptors.
@@ -959,10 +963,14 @@ public class Optimizer implements Pass
new AllMethodVisitor(
new AllAttributeVisitor(
new DebugAttributeVisitor("Filling out fields, method parameters, and return values",
new PartialEvaluator(valueFactory, storingInvocationUnit,
fieldSpecializationType ||
methodSpecializationParametertype ||
methodSpecializationReturntype)))));
PartialEvaluator.Builder.create()
.setValueFactory(valueFactory)
.setInvocationUnit(storingInvocationUnit)
.setEvaluateAllCode(
fieldSpecializationType ||
methodSpecializationParametertype ||
methodSpecializationReturntype)
.build()))));
}
};
@@ -1056,7 +1064,11 @@ public class Optimizer implements Pass
new ClassAccessFilter(AccessConstants.SYNTHETIC, 0,
new AllMethodVisitor(
new AllAttributeVisitor(
new PartialEvaluator(valueFactory, loadingInvocationUnit, false)))));
PartialEvaluator.Builder.create()
.setValueFactory(valueFactory)
.setInvocationUnit(loadingInvocationUnit)
.setEvaluateAllCode(false)
.build()))));
}
}
@@ -1083,7 +1095,11 @@ public class Optimizer implements Pass
new DebugAttributeVisitor("Simplifying code",
new OptimizationCodeAttributeFilter(
new EvaluationSimplifier(
new PartialEvaluator(valueFactory, loadingInvocationUnit, false),
PartialEvaluator.Builder.create()
.setValueFactory(valueFactory)
.setInvocationUnit(loadingInvocationUnit)
.setEvaluateAllCode(false)
.build(),
codeSimplificationAdvancedCounter,
configuration.optimizeConservatively)))));
}
@@ -1125,11 +1141,14 @@ public class Optimizer implements Pass
new OptimizationCodeAttributeFilter(
new EvaluationShrinker(
new InstructionUsageMarker(
new PartialEvaluator(referenceTracingValueFactory,
new ParameterTracingInvocationUnit(loadingInvocationUnit),
!codeSimplificationAdvanced,
referenceTracingValueFactory),
PartialEvaluator.Builder.create()
.setValueFactory(referenceTracingValueFactory)
.setInvocationUnit(new ParameterTracingInvocationUnit(loadingInvocationUnit))
.setEvaluateAllCode(!codeSimplificationAdvanced)
.setExtraInstructionVisitor(referenceTracingValueFactory)
.build(),
true, configuration.optimizeConservatively), true, deletedCounter, addedCounter)))));
}
};

View File

@@ -52,10 +52,12 @@ implements InfluenceFixpointVisitor.MemberVisitorFactory
ReferenceTracingValueFactory referenceTracingValueFactory1 =
new ReferenceTracingValueFactory(new TypedReferenceValueFactory());
PartialEvaluator partialEvaluator =
new PartialEvaluator(referenceTracingValueFactory1,
new ParameterTracingInvocationUnit(new BasicInvocationUnit(referenceTracingValueFactory1)),
false,
referenceTracingValueFactory1);
PartialEvaluator.Builder.create()
.setValueFactory(referenceTracingValueFactory1)
.setInvocationUnit(new ParameterTracingInvocationUnit(new BasicInvocationUnit(referenceTracingValueFactory1)))
.setEvaluateAllCode(false)
.setExtraInstructionVisitor(referenceTracingValueFactory1)
.build();
InstructionUsageMarker instructionUsageMarker =
new InstructionUsageMarker(partialEvaluator, false, false);

View File

@@ -114,7 +114,7 @@ implements AttributeVisitor,
*/
public EvaluationShrinker()
{
this(new PartialEvaluator(), true, false, null, null);
this(PartialEvaluator.Builder.create().build(), true, false, null, null);
}

View File

@@ -48,8 +48,9 @@ public class EvaluationSimplifier
implements AttributeVisitor,
InstructionVisitor
{
private static final int POS_ZERO_FLOAT_BITS = Float.floatToIntBits(0.0f);
private static final long POS_ZERO_DOUBLE_BITS = Double.doubleToLongBits(0.0);
private static final boolean ENABLE_LOWER_SLOT_REPLACEMENT = System.getProperty("optimization.enable.slot.replacement") != null;
private static final int POS_ZERO_FLOAT_BITS = Float.floatToIntBits(0.0f);
private static final long POS_ZERO_DOUBLE_BITS = Double.doubleToLongBits(0.0);
private static final Logger logger = LogManager.getLogger(EvaluationSimplifier.class);
@@ -58,7 +59,7 @@ implements AttributeVisitor,
private final PartialEvaluator partialEvaluator;
private final SideEffectInstructionChecker sideEffectInstructionChecker;
private final CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor(true, true);
private final CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor(true, true);
/**
@@ -69,7 +70,7 @@ implements AttributeVisitor,
*/
public EvaluationSimplifier(boolean predictNullPointerExceptions)
{
this(new PartialEvaluator(), null, predictNullPointerExceptions);
this(PartialEvaluator.Builder.create().build(), null, predictNullPointerExceptions);
}
@@ -692,7 +693,7 @@ implements AttributeVisitor,
replaceInstruction(clazz, offset, instruction, replacementInstruction);
}
}
else if (pushedValue.isSpecific())
else if (ENABLE_LOWER_SLOT_REPLACEMENT && pushedValue.isSpecific())
{
// Load an equivalent lower-numbered variable instead, if any.
TracedVariables variables = partialEvaluator.getVariablesBefore(offset);
@@ -762,7 +763,7 @@ implements AttributeVisitor,
replaceInstruction(clazz, offset, instruction, replacementInstruction);
}
}
else if (pushedValue.isSpecific())
else if (ENABLE_LOWER_SLOT_REPLACEMENT && pushedValue.isSpecific())
{
// Load an equivalent lower-numbered variable instead, if any.
TracedVariables variables = partialEvaluator.getVariablesBefore(offset);
@@ -836,7 +837,7 @@ implements AttributeVisitor,
replaceInstruction(clazz, offset, instruction, replacementInstruction);
}
}
else if (pushedValue.isSpecific())
else if (ENABLE_LOWER_SLOT_REPLACEMENT && pushedValue.isSpecific())
{
// Load an equivalent lower-numbered variable instead, if any.
TracedVariables variables = partialEvaluator.getVariablesBefore(offset);
@@ -906,7 +907,7 @@ implements AttributeVisitor,
replaceInstruction(clazz, offset, instruction, replacementInstruction);
}
}
else if (pushedValue.isSpecific())
else if (ENABLE_LOWER_SLOT_REPLACEMENT && pushedValue.isSpecific())
{
// Load an equivalent lower-numbered variable instead, if any.
TracedVariables variables = partialEvaluator.getVariablesBefore(offset);

View File

@@ -91,7 +91,7 @@ implements AttributeVisitor
*/
public InstructionUsageMarker(boolean markExternalSideEffects)
{
this(new PartialEvaluator(), true, true, markExternalSideEffects);
this(PartialEvaluator.Builder.create().build(), true, true, markExternalSideEffects);
}
/**
@@ -129,9 +129,10 @@ implements AttributeVisitor
this.ensureSafetyForVerifier = ensureSafetyForVerifier;
this.markExternalSideEffects = markExternalSideEffects;
this.sideEffectInstructionChecker = new SideEffectInstructionChecker(true, true, markExternalSideEffects);
if (ensureSafetyForVerifier)
{
this.simplePartialEvaluator = new PartialEvaluator(new TypedReferenceValueFactory());
if (ensureSafetyForVerifier) {
this.simplePartialEvaluator = PartialEvaluator.Builder.create()
.setValueFactory(new TypedReferenceValueFactory())
.build();
}
else
{

View File

@@ -71,7 +71,7 @@ implements ClassVisitor,
*/
public SimpleEnumUseChecker()
{
this(new PartialEvaluator(new TypedReferenceValueFactory()));
this(PartialEvaluator.Builder.create().setValueFactory(new TypedReferenceValueFactory()).build());
}

View File

@@ -72,7 +72,7 @@ implements AttributeVisitor,
*/
public SimpleEnumUseSimplifier()
{
this(new PartialEvaluator(new TypedReferenceValueFactory()), null);
this(PartialEvaluator.Builder.create().setValueFactory(new TypedReferenceValueFactory()).build(), null);
}

View File

@@ -60,11 +60,13 @@ implements InstructionVisitor
private final InstructionSequenceMatcher registerTypeAdapterFactoryMatcher;
private final InstructionSequenceMatcher serializeSpecialFloatingPointValuesMatcher;
private final TypedReferenceValueFactory valueFactory =
new TypedReferenceValueFactory();
new TypedReferenceValueFactory();
private final PartialEvaluator partialEvaluator =
new PartialEvaluator(valueFactory,
new BasicInvocationUnit(new TypedReferenceValueFactory()),
true);
PartialEvaluator.Builder.create()
.setValueFactory(valueFactory)
.setInvocationUnit(new BasicInvocationUnit(valueFactory))
.setEvaluateAllCode(true)
.build();
private final AttributeVisitor lazyPartialEvaluator =
new AttributeNameFilter(Attribute.CODE,
new SingleTimeAttributeVisitor(

View File

@@ -49,11 +49,13 @@ implements MemberVisitor,
private final CodeAttributeEditor codeAttributeEditor;
private final TypedReferenceValueFactory valueFactory =
new TypedReferenceValueFactory();
new TypedReferenceValueFactory();
private final PartialEvaluator partialEvaluator =
new PartialEvaluator(valueFactory,
new BasicInvocationUnit(new TypedReferenceValueFactory()),
true);
PartialEvaluator.Builder.create()
.setValueFactory(valueFactory)
.setInvocationUnit(new BasicInvocationUnit(valueFactory))
.setEvaluateAllCode(true)
.build();
private final AttributeVisitor lazyPartialEvaluator =
new AttributeNameFilter(Attribute.CODE,
new SingleTimeAttributeVisitor(

View File

@@ -51,12 +51,14 @@ implements InstructionVisitor
private final ClassVisitor domainClassVisitor;
private final WarningPrinter warningPrinter;
private final FromJsonInvocationMatcher[] fromJsonInvocationMatchers;
private final TypedReferenceValueFactory valueFactory =
new TypedReferenceValueFactory();
private final PartialEvaluator partialEvaluator =
new PartialEvaluator(valueFactory,
new BasicInvocationUnit(new TypedReferenceValueFactory()),
true);
private final TypedReferenceValueFactory valueFactory =
new TypedReferenceValueFactory();
private final PartialEvaluator partialEvaluator =
PartialEvaluator.Builder.create()
.setValueFactory(valueFactory)
.setInvocationUnit(new BasicInvocationUnit(valueFactory))
.setEvaluateAllCode(true)
.build();
private final AttributeVisitor lazyPartialEvaluator =
new AttributeNameFilter(Attribute.CODE,
new SingleTimeAttributeVisitor(

View File

@@ -53,11 +53,13 @@ implements InstructionVisitor
private final WarningPrinter warningPrinter;
private final ToJsonInvocationMatcher[] toJsonInvocationMatchers;
private final TypedReferenceValueFactory valueFactory =
new TypedReferenceValueFactory();
new TypedReferenceValueFactory();
private final PartialEvaluator partialEvaluator =
new PartialEvaluator(valueFactory,
new BasicInvocationUnit(new TypedReferenceValueFactory()),
true);
PartialEvaluator.Builder.create()
.setValueFactory(valueFactory)
.setInvocationUnit(new BasicInvocationUnit(valueFactory))
.setEvaluateAllCode(true)
.build();
private final AttributeVisitor lazyPartialEvaluator =
new AttributeNameFilter(Attribute.CODE,
new SingleTimeAttributeVisitor(partialEvaluator));

View File

@@ -77,10 +77,12 @@ implements AttributeVisitor,
*/
public EscapingClassMarker(ReferenceTracingValueFactory tracingValueFactory)
{
this(new PartialEvaluator(tracingValueFactory,
new ReferenceTracingInvocationUnit(new BasicInvocationUnit(tracingValueFactory)),
true,
tracingValueFactory),
this(PartialEvaluator.Builder.create()
.setValueFactory(tracingValueFactory)
.setInvocationUnit(new ReferenceTracingInvocationUnit(new BasicInvocationUnit(tracingValueFactory)))
.setEvaluateAllCode(true)
.setExtraInstructionVisitor(tracingValueFactory)
.build(),
true);
}

View File

@@ -103,7 +103,7 @@ implements MemberVisitor,
*/
public ParameterEscapeMarker(ValueFactory valueFactory, MemberVisitor extraMemberVisitor)
{
this(valueFactory, new ReferenceTracingValueFactory(valueFactory), extraMemberVisitor
this(new ReferenceTracingValueFactory(valueFactory), extraMemberVisitor
);
}
@@ -111,14 +111,15 @@ implements MemberVisitor,
/**
* Creates a new ParameterEscapeMarker.
*/
public ParameterEscapeMarker(ValueFactory valueFactory,
ReferenceTracingValueFactory tracingValueFactory,
public ParameterEscapeMarker(ReferenceTracingValueFactory tracingValueFactory,
MemberVisitor extraMemberVisitor)
{
this(new PartialEvaluator(tracingValueFactory,
new ParameterTracingInvocationUnit(new BasicInvocationUnit(tracingValueFactory)),
true,
tracingValueFactory), true, extraMemberVisitor
this(PartialEvaluator.Builder.create()
.setValueFactory(tracingValueFactory)
.setInvocationUnit(new ParameterTracingInvocationUnit(new BasicInvocationUnit(tracingValueFactory)))
.setEvaluateAllCode(true)
.setExtraInstructionVisitor(tracingValueFactory)
.build(), true, extraMemberVisitor
);
}

View File

@@ -56,13 +56,14 @@ implements ClassPoolVisitor,
private final ClassVisitor parameterEscapedMarker =
new AllMethodVisitor(
new AllAttributeVisitor(this));
private final ValueFactory valueFactory = new BasicValueFactory();
private final ReferenceTracingValueFactory tracingValueFactory = new ReferenceTracingValueFactory(valueFactory);
private final ReferenceTracingValueFactory tracingValueFactory = new ReferenceTracingValueFactory(new BasicValueFactory());
private final PartialEvaluator partialEvaluator =
new PartialEvaluator(tracingValueFactory,
new ParameterTracingInvocationUnit(new BasicInvocationUnit(tracingValueFactory)),
true,
tracingValueFactory);
PartialEvaluator.Builder.create()
.setValueFactory(tracingValueFactory)
.setInvocationUnit(new ParameterTracingInvocationUnit(new BasicInvocationUnit(tracingValueFactory)))
.setEvaluateAllCode(true)
.setExtraInstructionVisitor(tracingValueFactory)
.build();
private final ReferenceEscapeChecker referenceEscapeChecker = new ReferenceEscapeChecker(partialEvaluator, false);
// Parameters and values for visitor methods.

View File

@@ -50,7 +50,7 @@ implements MemberVisitor,
private final boolean markThisParameter;
private final boolean markAllParameters;
private final boolean analyzeCode;
private final PartialEvaluator partialEvaluator = new PartialEvaluator();
private final PartialEvaluator partialEvaluator = PartialEvaluator.Builder.create().build();
/**

View File

@@ -86,10 +86,13 @@ implements AttributeVisitor,
*/
private ReferenceEscapeChecker(ReferenceTracingValueFactory referenceTracingValueFactory)
{
this(new PartialEvaluator(referenceTracingValueFactory,
new ParameterTracingInvocationUnit(new BasicInvocationUnit(referenceTracingValueFactory)),
true,
referenceTracingValueFactory),
this(
PartialEvaluator.Builder.create()
.setValueFactory(referenceTracingValueFactory)
.setInvocationUnit(new ParameterTracingInvocationUnit(new BasicInvocationUnit(referenceTracingValueFactory)))
.setEvaluateAllCode(true)
.setExtraInstructionVisitor(referenceTracingValueFactory)
.build(),
true);
}

View File

@@ -66,13 +66,20 @@ public class KotlinUnsupportedVersionChecker implements Pass
@Override
public void visitUnsupportedKotlinMetadata(Clazz clazz, UnsupportedKotlinMetadata kotlinMetadata)
{
if (kotlinMetadata.mv == null || kotlinMetadata.mv.length < 3 ||
!isSupportedMetadataVersion(new KotlinMetadataVersion(kotlinMetadata.mv)))
if (kotlinMetadata.mv != null
&& (kotlinMetadata.mv.length == 2 || kotlinMetadata.mv.length == 3)
&& !isSupportedMetadataVersion(new KotlinMetadataVersion(kotlinMetadata.mv)))
{
throw new RuntimeException(
"Unsupported Kotlin metadata version found on class '" + clazz.getName() + "'." +
System.lineSeparator() +
"Kotlin versions up to " + LATEST_STABLE_SUPPORTED + " are supported.");
"Unsupported Kotlin metadata version "
+ new KotlinMetadataVersion(kotlinMetadata.mv)
+ " found on class '"
+ clazz.getName()
+ "'."
+ System.lineSeparator()
+ "Kotlin versions up to "
+ LATEST_STABLE_SUPPORTED
+ " are supported.");
}
else
{

View File

@@ -35,20 +35,23 @@ class AfterInitConfigurationVerifierTest : FreeSpec({
"Given a configuration with target specified" - {
val configuration = """
val configuration =
"""
-verbose
-target 6
""".trimIndent().asConfiguration()
""".trimIndent().asConfiguration()
"It should throw an exception if program class pool contains a class with class file version > jdk 11" {
val view = AppView()
view.programClassPool.addClass(FakeClass(CLASS_VERSION_12))
val exception = shouldThrow<RuntimeException> {
AfterInitConfigurationVerifier(configuration).execute(view)
}
val exception =
shouldThrow<RuntimeException> {
AfterInitConfigurationVerifier(configuration).execute(view)
}
exception.message shouldContain "-target can only be used with class file versions <= 55 (Java 11)."
exception.message shouldContain "The input classes contain version 56 class files which cannot be backported to target version (50)."
exception.message shouldContain "The input classes contain version 56 class files which cannot be backported " +
"to target version (50)."
}
"It should not throw an exception if program class pool contains classes with class file version = jdk 11" {
@@ -72,9 +75,10 @@ class AfterInitConfigurationVerifierTest : FreeSpec({
"Given a configuration with a target specified and classes above Java 11" - {
val configuration = """
val configuration =
"""
-target 17
""".trimIndent().asConfiguration()
""".trimIndent().asConfiguration()
"It should not throw an exception if -target is set but all the classes are already at the targeted version (no backport needed)" {
val view = AppView()
@@ -93,33 +97,38 @@ class AfterInitConfigurationVerifierTest : FreeSpec({
addClass(FakeClass(CLASS_VERSION_17))
}
val output = getLogOutputOf {
AfterInitConfigurationVerifier(configuration).execute(view)
}
val output =
getLogOutputOf {
AfterInitConfigurationVerifier(configuration).execute(view)
}
output shouldContain "-target is deprecated when using class file above"
}
"It should throw an exception if -target is set and the version of one of the classes version is different from the target version" {
val view = AppView()
with(view.programClassPool) {
addClass(FakeClass(CLASS_VERSION_18))
addClass(FakeClass(CLASS_VERSION_17))
}
"It should throw an exception if -target is set and the version of one of the classes version is different from" +
"the target version" {
val view = AppView()
with(view.programClassPool) {
addClass(FakeClass(CLASS_VERSION_18))
addClass(FakeClass(CLASS_VERSION_17))
}
val exception = shouldThrow<RuntimeException> {
AfterInitConfigurationVerifier(configuration).execute(view)
}
val exception =
shouldThrow<RuntimeException> {
AfterInitConfigurationVerifier(configuration).execute(view)
}
exception.message shouldContain "-target can only be used with class file versions <= 55 (Java 11)."
exception.message shouldContain "The input classes contain version 62 class files which cannot be backported to target version (61)."
}
exception.message shouldContain "-target can only be used with class file versions <= 55 (Java 11)."
exception.message shouldContain "The input classes contain version 62 class files which cannot be backported " +
"to target version (61)."
}
}
"Given a configuration with no target specified" - {
val configuration = """
val configuration =
"""
-verbose
""".trimIndent().asConfiguration()
""".trimIndent().asConfiguration()
"It should not throw an exception if program class pool contains classes with class file version > jdk 11" {
val view = AppView()

View File

@@ -20,14 +20,15 @@ import proguard.testutils.ClassPoolBuilder
import proguard.testutils.JavaSource
class ClassMergerTest : FreeSpec({
val classBPools = ClassPoolBuilder.fromSource(
JavaSource(
"B.java",
"""
public class B {}
""".trimIndent()
val classBPools =
ClassPoolBuilder.fromSource(
JavaSource(
"B.java",
"""
public class B {}
""".trimIndent(),
),
)
)
val classB = classBPools.programClassPool.getClass("B") as ProgramClass
classB.accept(ClassVersionSetter(CLASS_VERSION_1_8))
@@ -35,18 +36,19 @@ class ClassMergerTest : FreeSpec({
classBPools.programClassPool.classesAccept(ProcessingInfoSetter(ProgramClassOptimizationInfo()))
"Given a non-nested class" - {
val classAPools = ClassPoolBuilder.fromSource(
AssemblerSource(
"A.jbc",
"""
version 1.8;
public class A extends java.lang.Object [
SourceFile "A.java";
] {
}
""".trimIndent()
val classAPools =
ClassPoolBuilder.fromSource(
AssemblerSource(
"A.jbc",
"""
version 1.8;
public class A extends java.lang.Object [
SourceFile "A.java";
] {
}
""".trimIndent(),
),
)
)
val classA = classAPools.programClassPool.getClass("A") as ProgramClass
"the check should indicate so" {
@@ -60,19 +62,20 @@ class ClassMergerTest : FreeSpec({
}
"Given a nested class" - {
val classAPools = ClassPoolBuilder.fromSource(
AssemblerSource(
"A.jbc",
"""
version 1.8;
public class A extends java.lang.Object [
NestHost java.lang.Class;
SourceFile "A.java";
] {
}
""".trimIndent()
val classAPools =
ClassPoolBuilder.fromSource(
AssemblerSource(
"A.jbc",
"""
version 1.8;
public class A extends java.lang.Object [
NestHost java.lang.Class;
SourceFile "A.java";
] {
}
""".trimIndent(),
),
)
)
val classA = classAPools.programClassPool.getClass("A") as ProgramClass
@@ -87,26 +90,27 @@ class ClassMergerTest : FreeSpec({
}
"Given a class with no native methods" - {
val targetPools = ClassPoolBuilder.fromSource(
JavaSource(
"Target.java",
"""
public class Target {
private boolean value;
public Target(boolean value)
{
this.value = value;
}
public void setValue(boolean value)
{
this.value = value;
}
}
""".trimIndent()
val targetPools =
ClassPoolBuilder.fromSource(
JavaSource(
"Target.java",
"""
public class Target {
private boolean value;
public Target(boolean value)
{
this.value = value;
}
public void setValue(boolean value)
{
this.value = value;
}
}
""".trimIndent(),
),
)
)
targetPools.libraryClassPool.classesAccept(ProcessingInfoSetter(ClassOptimizationInfo()))
targetPools.programClassPool.classesAccept(ProcessingInfoSetter(ProgramClassOptimizationInfo()))
@@ -116,21 +120,22 @@ class ClassMergerTest : FreeSpec({
val merger = ClassMerger(target, true, true, true)
"When merging in a class with no native methods" - {
val sourcePools = ClassPoolBuilder.fromSource(
JavaSource(
"Source.java",
"""
public class Source {
private String name;
public String getName()
{
return name;
}
}
""".trimIndent()
val sourcePools =
ClassPoolBuilder.fromSource(
JavaSource(
"Source.java",
"""
public class Source {
private String name;
public String getName()
{
return name;
}
}
""".trimIndent(),
),
)
)
"Then it should be possible to merge the classes" {
sourcePools.libraryClassPool.classesAccept(ProcessingInfoSetter(ClassOptimizationInfo()))
@@ -140,23 +145,24 @@ class ClassMergerTest : FreeSpec({
}
"When merging in a class with native methods" - {
val sourcePools = ClassPoolBuilder.fromSource(
JavaSource(
"Source.java",
"""
public class Source {
private String name;
private native void method();
public String getName()
{
return name;
}
}
""".trimIndent()
val sourcePools =
ClassPoolBuilder.fromSource(
JavaSource(
"Source.java",
"""
public class Source {
private String name;
private native void method();
public String getName()
{
return name;
}
}
""".trimIndent(),
),
)
)
"Then it should not be possible to merge the classes" {
sourcePools.libraryClassPool.classesAccept(ProcessingInfoSetter(ClassOptimizationInfo()))
@@ -168,28 +174,29 @@ class ClassMergerTest : FreeSpec({
"Given a class with native methods" - {
// Same as above but checking if merging in a class with native methods is still possible.
val targetPools = ClassPoolBuilder.fromSource(
JavaSource(
"Target.java",
"""
public class Target {
private boolean value;
public static native void foo();
public Target(boolean value)
{
this.value = value;
}
public void setValue(boolean value)
{
this.value = value;
}
}
""".trimIndent()
val targetPools =
ClassPoolBuilder.fromSource(
JavaSource(
"Target.java",
"""
public class Target {
private boolean value;
public static native void foo();
public Target(boolean value)
{
this.value = value;
}
public void setValue(boolean value)
{
this.value = value;
}
}
""".trimIndent(),
),
)
)
targetPools.libraryClassPool.classesAccept(ProcessingInfoSetter(ClassOptimizationInfo()))
targetPools.programClassPool.classesAccept(ProcessingInfoSetter(ProgramClassOptimizationInfo()))
@@ -199,21 +206,22 @@ class ClassMergerTest : FreeSpec({
val merger = ClassMerger(target, true, true, true)
"When merging in a class with no native methods" - {
val sourcePools = ClassPoolBuilder.fromSource(
JavaSource(
"Source.java",
"""
public class Source {
private String name;
public String getName()
{
return name;
}
}
""".trimIndent()
val sourcePools =
ClassPoolBuilder.fromSource(
JavaSource(
"Source.java",
"""
public class Source {
private String name;
public String getName()
{
return name;
}
}
""".trimIndent(),
),
)
)
"Then it should be possible to merge the classes" {
sourcePools.libraryClassPool.classesAccept(ProcessingInfoSetter(ClassOptimizationInfo()))
@@ -223,23 +231,24 @@ class ClassMergerTest : FreeSpec({
}
"When merging in a class with native methods" - {
val sourcePools = ClassPoolBuilder.fromSource(
JavaSource(
"Source.java",
"""
public class Source {
private String name;
private native void method();
public String getName()
{
return name;
}
}
""".trimIndent()
val sourcePools =
ClassPoolBuilder.fromSource(
JavaSource(
"Source.java",
"""
public class Source {
private String name;
private native void method();
public String getName()
{
return name;
}
}
""".trimIndent(),
),
)
)
"Then it should not be possible to merge the classes" {
sourcePools.libraryClassPool.classesAccept(ProcessingInfoSetter(ClassOptimizationInfo()))

View File

@@ -22,15 +22,17 @@ import testutils.asConfiguration
class ClassSpecificationVisitorFactoryTest : FreeSpec({
"Given a class specification with an extending class" - {
val (programClassPool, _) = ClassPoolBuilder.fromSource(
JavaSource("Foo.java", "class Foo extends Bar3 { }"),
JavaSource("Bar3.java", "class Bar3 { }")
)
val (programClassPool, _) =
ClassPoolBuilder.fromSource(
JavaSource("Foo.java", "class Foo extends Bar3 { }"),
JavaSource("Bar3.java", "class Bar3 { }"),
)
val spec = "-keep class F** extends Bar* { public *; }".asConfiguration().keep.first()
val classVisitor = spyk<ClassVisitor>()
val visitor = KeepClassSpecificationVisitorFactory(true, false, false).createClassPoolVisitor(
spec, classVisitor, null, null, null
)
val visitor =
KeepClassSpecificationVisitorFactory(true, false, false).createClassPoolVisitor(
spec, classVisitor, null, null, null,
)
"Then the visitor should visit the sub-class" {
programClassPool.accept(visitor)
verify(exactly = 1) {
@@ -40,15 +42,17 @@ class ClassSpecificationVisitorFactoryTest : FreeSpec({
}
"Given a class specification with an implementing class" - {
val (programClassPool, _) = ClassPoolBuilder.fromSource(
JavaSource("Foo.java", "class Foo implements Bar3 { }"),
JavaSource("Bar3.java", "interface Bar3 { }")
)
val (programClassPool, _) =
ClassPoolBuilder.fromSource(
JavaSource("Foo.java", "class Foo implements Bar3 { }"),
JavaSource("Bar3.java", "interface Bar3 { }"),
)
val spec = "-keep class F** implements Bar* { public *; }".asConfiguration().keep.first()
val classVisitor = spyk<ClassVisitor>()
val visitor = KeepClassSpecificationVisitorFactory(true, false, false).createClassPoolVisitor(
spec, classVisitor, null, null, null
)
val visitor =
KeepClassSpecificationVisitorFactory(true, false, false).createClassPoolVisitor(
spec, classVisitor, null, null, null,
)
"Then the visitor should visit the implementing class" {
programClassPool.accept(visitor)
verify(exactly = 1) {
@@ -58,15 +62,17 @@ class ClassSpecificationVisitorFactoryTest : FreeSpec({
}
"Given a class specification with a negation" - {
val (programClassPool, _) = ClassPoolBuilder.fromSource(
JavaSource("Foo.java", "class Foo implements Bar3 { }"),
JavaSource("Bar3.java", "interface Bar3 { }")
)
val (programClassPool, _) =
ClassPoolBuilder.fromSource(
JavaSource("Foo.java", "class Foo implements Bar3 { }"),
JavaSource("Bar3.java", "interface Bar3 { }"),
)
val spec = "-keep !interface B** { public *; }".asConfiguration().keep.first()
val classVisitor = spyk<ClassVisitor>()
val visitor = KeepClassSpecificationVisitorFactory(true, false, false).createClassPoolVisitor(
spec, classVisitor, null, null, null
)
val visitor =
KeepClassSpecificationVisitorFactory(true, false, false).createClassPoolVisitor(
spec, classVisitor, null, null, null,
)
"Then the visitor should not visit the interface of which the name starts with 'B'" {
programClassPool.accept(visitor)
verify(exactly = 0) {
@@ -76,15 +82,17 @@ class ClassSpecificationVisitorFactoryTest : FreeSpec({
}
"Given a conditional class specification containing a wildcard" - {
val (programClassPool, _) = ClassPoolBuilder.fromSource(
JavaSource("FooBar.java", "class FooBar extends Bar { }"),
JavaSource("Bar.java", "class Bar { }")
)
val (programClassPool, _) =
ClassPoolBuilder.fromSource(
JavaSource("FooBar.java", "class FooBar extends Bar { }"),
JavaSource("Bar.java", "class Bar { }"),
)
val spec = "-if class * -keep class <1> { public *; }".asConfiguration().keep.first()
val classVisitor = spyk<ClassVisitor>()
val visitor = KeepClassSpecificationVisitorFactory(true, false, false).createClassPoolVisitor(
spec, classVisitor, null, null, null
)
val visitor =
KeepClassSpecificationVisitorFactory(true, false, false).createClassPoolVisitor(
spec, classVisitor, null, null, null,
)
"Then the visitor should visit the matched class" {
programClassPool.accept(visitor)
verify(exactly = 1) {
@@ -94,16 +102,18 @@ class ClassSpecificationVisitorFactoryTest : FreeSpec({
}
"Given a class specification extending an annotation class" - {
val (programClassPool, _) = ClassPoolBuilder.fromSource(
JavaSource("Annotation.java", "@interface Annotation { }"),
JavaSource("FooBar.java", "class FooBar extends Bar { }"),
JavaSource("Bar.java", "@Annotation class Bar { }")
)
val (programClassPool, _) =
ClassPoolBuilder.fromSource(
JavaSource("Annotation.java", "@interface Annotation { }"),
JavaSource("FooBar.java", "class FooBar extends Bar { }"),
JavaSource("Bar.java", "@Annotation class Bar { }"),
)
val spec = "-keep class * extends @Annotation *".asConfiguration().keep.first()
val classVisitor = spyk<ClassVisitor>()
val visitor = KeepClassSpecificationVisitorFactory(true, false, false).createClassPoolVisitor(
spec, classVisitor, null, null, null
)
val visitor =
KeepClassSpecificationVisitorFactory(true, false, false).createClassPoolVisitor(
spec, classVisitor, null, null, null,
)
"Then the visitor should visit the matched class" {
programClassPool.accept(visitor)
verify(exactly = 1) {
@@ -113,28 +123,32 @@ class ClassSpecificationVisitorFactoryTest : FreeSpec({
}
"Given a class with fields of different types" - {
fun createFieldVisitor(config: String, fieldVisitor: MemberVisitor): ClassPoolVisitor =
fun createFieldVisitor(
config: String,
fieldVisitor: MemberVisitor,
): ClassPoolVisitor =
KeepClassSpecificationVisitorFactory(true, false, false).createClassPoolVisitor(
config.asConfiguration().keep.first(), null, fieldVisitor, null, null
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()
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()) {
@@ -166,28 +180,32 @@ class ClassSpecificationVisitorFactoryTest : FreeSpec({
}
"Given a class with methods of different types" - {
fun createMethodVisitor(config: String, methodVisitor: MemberVisitor): ClassPoolVisitor =
fun createMethodVisitor(
config: String,
methodVisitor: MemberVisitor,
): ClassPoolVisitor =
KeepClassSpecificationVisitorFactory(true, false, false).createClassPoolVisitor(
config.asConfiguration().keep.first(), null, null, methodVisitor, null
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()
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()) {

View File

@@ -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,8 @@ 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>; }")
@@ -144,12 +150,12 @@ class ConfigurationParserTest : FreeSpec({
"""-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."
customOutputStream.toString() shouldContain "Warning: The R8 option -alwaysinline is currently not " +
"supported by ProGuard.\nThis option will have no effect on the optimized artifact."
System.setOut(savedPrintStream)
}
}
@@ -184,12 +190,12 @@ class ConfigurationParserTest : FreeSpec({
"""-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."
customOutputStream.toString() shouldContain "Warning: The R8 option -identifiernamestring is currently " +
"not supported by ProGuard.\nThis option will have no effect on the optimized artifact."
System.setOut(savedPrintStream)
}
}
@@ -222,8 +228,8 @@ class ConfigurationParserTest : FreeSpec({
parseConfiguration("-maximumremovedandroidloglevel 1")
"The option prints out a warning" {
customOutputStream.toString() shouldContain "Warning: The R8 option -maximumremovedandroidloglevel is currently not supported by ProGuard.\n" +
"This option will have no effect on the optimized artifact."
customOutputStream.toString() shouldContain "Warning: The R8 option -maximumremovedandroidloglevel is " +
"currently not supported by ProGuard.\nThis option will have no effect on the optimized artifact."
System.setOut(savedPrintStream)
}
}
@@ -235,15 +241,15 @@ class ConfigurationParserTest : FreeSpec({
parseConfiguration(
"""
-maximumremovedandroidloglevel 1 @org.chromium.build.annotations.DoNotStripLogs class ** {
<methods>;
}
-maximumremovedandroidloglevel 1 @org.chromium.build.annotations.DoNotStripLogs class ** {
<methods>;
}
""".trimIndent(),
)
"The option prints out a warning" {
customOutputStream.toString() shouldContain "Warning: The R8 option -maximumremovedandroidloglevel is currently not supported by ProGuard.\n" +
"This option will have no effect on the optimized artifact."
customOutputStream.toString() shouldContain "Warning: The R8 option -maximumremovedandroidloglevel is " +
"currently not supported by ProGuard.\nThis option will have no effect on the optimized artifact."
System.setOut(savedPrintStream)
}
}
@@ -253,7 +259,7 @@ class ConfigurationParserTest : FreeSpec({
class TestConfig(
val configOption: String,
classSpecificationConfig: String,
private val classSpecificationGetter: Configuration.() -> List<ClassSpecification>?
private val classSpecificationGetter: Configuration.() -> List<ClassSpecification>?,
) {
private val configuration: Configuration by lazy {
"$configOption $classSpecificationConfig".asConfiguration()
@@ -261,14 +267,15 @@ class ConfigurationParserTest : FreeSpec({
val classSpecifications: List<ClassSpecification>? get() = classSpecificationGetter.invoke(configuration)
}
fun generateTestCases(clSpec: String): List<TestConfig> = listOf(
TestConfig("-keep", clSpec) { keep },
TestConfig("-assumenosideeffects", clSpec) { assumeNoSideEffects },
TestConfig("-assumenoexternalsideeffects", clSpec) { assumeNoExternalSideEffects },
TestConfig("-assumenoescapingparameters", clSpec) { assumeNoEscapingParameters },
TestConfig("-assumenoexternalreturnvalues", clSpec) { assumeNoExternalReturnValues },
TestConfig("-assumevalues", clSpec) { assumeValues },
)
fun generateTestCases(clSpec: String): List<TestConfig> =
listOf(
TestConfig("-keep", clSpec) { keep },
TestConfig("-assumenosideeffects", clSpec) { assumeNoSideEffects },
TestConfig("-assumenoexternalsideeffects", clSpec) { assumeNoExternalSideEffects },
TestConfig("-assumenoescapingparameters", clSpec) { assumeNoEscapingParameters },
TestConfig("-assumenoexternalreturnvalues", clSpec) { assumeNoExternalReturnValues },
TestConfig("-assumevalues", clSpec) { assumeValues },
)
"Test wildcard matches all methods and fields" {
val testConfigurations = generateTestCases("class Foo { *; }") + generateTestCases("class Foo { <fields>; <methods>; }")
@@ -428,4 +435,75 @@ 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) }
}
}
}
}
})

View File

@@ -10,7 +10,8 @@ import java.io.StringWriter
* Test printing of the configuration (-printconfiguration option).
*/
class ConfigurationWriterTest : FreeSpec({
val EOL = System.lineSeparator()
val eol = System.lineSeparator()
fun printConfiguration(rules: String): String {
val out = StringWriter()
val configuration = Configuration()
@@ -28,22 +29,22 @@ class ConfigurationWriterTest : FreeSpec({
"Keep rules tests" - {
"Keep class constructor should be kept" {
val rules = "-keep class * {$EOL <init>();$EOL}"
val rules = "-keep class * {$eol <init>();$eol}"
val out = printConfiguration(rules)
out shouldBe rules
}
"Keep class initializer should be kept" {
val rules = "-keep class * {$EOL <clinit>();$EOL}"
val rules = "-keep class * {$eol <clinit>();$eol}"
val out = printConfiguration(rules)
val expected = "-keep class * {$EOL void <clinit>();$EOL}"
val expected = "-keep class * {$eol void <clinit>();$eol}"
out shouldBe expected
}
"Keep class initializer should respect allowobfuscation flag" {
val rules = "-keep,allowobfuscation class ** extends com.example.A {$EOL <clinit>();$EOL}"
val rules = "-keep,allowobfuscation class ** extends com.example.A {$eol <clinit>();$eol}"
val out = printConfiguration(rules)
val expected = "-keep,allowobfuscation class ** extends com.example.A {$EOL void <clinit>();$EOL}"
val expected = "-keep,allowobfuscation class ** extends com.example.A {$eol void <clinit>();$eol}"
out shouldBe expected
}
}
@@ -54,11 +55,11 @@ class ConfigurationWriterTest : FreeSpec({
}
"Comments should not be quoted" {
printConfiguration("# comment$EOL-keep class **") shouldBe "# comment$EOL-keep class **"
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 **"
printConfiguration("# #comment$eol-keep class **") shouldBe "# #comment$eol-keep class **"
}
}

View File

@@ -9,26 +9,27 @@ import testutils.RequiresJavaVersion
@RequiresJavaVersion(19, 19)
class Java19RecordPatternTest : FreeSpec({
"Given a class with Java record pattern" - {
val (programClassPool, _) = ClassPoolBuilder.fromSource(
JavaSource(
"Test.java",
"""
public class Test {
public static void main(String[] args) {
printPoint(new Point(1, 2));
}
private static void printPoint(Object o) {
if (o instanceof (Point(int x, int y) p)) {
System.out.println(p + " = " + (x + y));
val (programClassPool, _) =
ClassPoolBuilder.fromSource(
JavaSource(
"Test.java",
"""
public class Test {
public static void main(String[] args) {
printPoint(new Point(1, 2));
}
private static void printPoint(Object o) {
if (o instanceof (Point(int x, int y) p)) {
System.out.println(p + " = " + (x + y));
}
}
}
}
record Point(int x, int y) {}
""".trimIndent()
),
javacArguments = listOf("--enable-preview", "--release", "19")
)
record Point(int x, int y) {}
""".trimIndent(),
),
javacArguments = listOf("--enable-preview", "--release", "19"),
)
"Then ProGuard should parse the class correctly" {
programClassPool.getClass("Test") shouldNotBe null

View File

@@ -9,26 +9,27 @@ import testutils.RequiresJavaVersion
@RequiresJavaVersion(20, 20)
class Java20RecordPatternTest : FreeSpec({
"Given a class with Java record pattern" - {
val (programClassPool, _) = ClassPoolBuilder.fromSource(
JavaSource(
"Test.java",
"""
public class Test {
public static void main(String[] args) {
printPoint(new Point(1, 2));
}
private static void printPoint(Object o) {
if (o instanceof Point(int x, int y)) {
System.out.println(x + y);
val (programClassPool, _) =
ClassPoolBuilder.fromSource(
JavaSource(
"Test.java",
"""
public class Test {
public static void main(String[] args) {
printPoint(new Point(1, 2));
}
private static void printPoint(Object o) {
if (o instanceof Point(int x, int y)) {
System.out.println(x + y);
}
}
}
}
record Point(int x, int y) {}
""".trimIndent()
),
javacArguments = listOf("--enable-preview", "--release", "20")
)
record Point(int x, int y) {}
""".trimIndent(),
),
javacArguments = listOf("--enable-preview", "--release", "20"),
)
"Then ProGuard should parse the class correctly" {
programClassPool.getClass("Test") shouldNotBe null

View File

@@ -27,22 +27,23 @@ import testutils.shouldNotHaveFlag
class MarkerTest : FreeSpec({
"Given a Kotlin inline class" - {
val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource(
KotlinSource(
"Test.kt",
"""
@JvmInline
value class Password(val s: String)
val (programClassPool, libraryClassPool) =
ClassPoolBuilder.fromSource(
KotlinSource(
"Test.kt",
"""
@JvmInline
value class Password(val s: String)
// The underlying JVM method descriptor will reference the
// underlying property of `Password`, rather than `Password` itself.
// The underlying JVM method descriptor will reference the
// underlying property of `Password`, rather than `Password` itself.
fun login(password: Password) {
println(password)
}
""".trimIndent()
fun login(password: Password) {
println(password)
}
""".trimIndent(),
),
)
)
beforeEach {
programClassPool.classesAccept {
@@ -51,7 +52,8 @@ class MarkerTest : FreeSpec({
}
"Then when using includedescriptorclasses modifier" - {
val config = """
val config =
"""
-keep,includedescriptorclasses class TestKt {
<methods>;
}
@@ -71,7 +73,8 @@ class MarkerTest : FreeSpec({
}
"Then when not using includedescriptorclasses modifier" - {
val config = """
val config =
"""
-keep class TestKt {
<methods>;
}
@@ -92,25 +95,27 @@ class MarkerTest : FreeSpec({
}
"Given a Kotlin interface with default method implementation in the interface itself" - {
val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource(
KotlinSource(
"Test.kt",
"""
interface Test {
fun foo() {
TODO()
val (programClassPool, libraryClassPool) =
ClassPoolBuilder.fromSource(
KotlinSource(
"Test.kt",
"""
interface Test {
fun foo() {
TODO()
}
}
}
""".trimIndent()
),
kotlincArguments = listOf("-Xjvm-default=all")
)
""".trimIndent(),
),
kotlincArguments = listOf("-Xjvm-default=all"),
)
// Run the asserter to ensure any metadata that isn't initialized correctly is thrown away
KotlinMetadataVerifier(Configuration()).execute(AppView(programClassPool, libraryClassPool))
"Then when marking" - {
val config = """
val config =
"""
-keep class Test {
<methods>;
}
@@ -134,25 +139,27 @@ class MarkerTest : FreeSpec({
}
"Given a Kotlin interface with default method implementation in compatibility mode" - {
val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource(
KotlinSource(
"Test.kt",
"""
interface Test {
fun foo() {
TODO()
val (programClassPool, libraryClassPool) =
ClassPoolBuilder.fromSource(
KotlinSource(
"Test.kt",
"""
interface Test {
fun foo() {
TODO()
}
}
}
""".trimIndent()
),
kotlincArguments = listOf("-Xjvm-default=all-compatibility")
)
""".trimIndent(),
),
kotlincArguments = listOf("-Xjvm-default=all-compatibility"),
)
// Run the asserter to ensure any metadata that isn't initialized correctly is thrown away
KotlinMetadataVerifier(Configuration()).execute(AppView(programClassPool, libraryClassPool))
"Then when marking" - {
val config = """
val config =
"""
-keep class Test {
<methods>;
}

View File

@@ -25,7 +25,11 @@ import io.kotest.matchers.collections.shouldNotBeIn
class SimpleNameFactoryTest : StringSpec({
val reservedNames = listOf("AUX", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "CON", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9", "NUL", "PRN")
val reservedNames =
listOf(
"AUX", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "CON", "LPT1", "LPT2",
"LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9", "NUL", "PRN",
)
"Should not generate WINDOWS reserved names with mixed case enabled" {

View File

@@ -53,15 +53,16 @@ import proguard.testutils.KotlinSource
class KotlinCallableReferenceFixerTest : FreeSpec({
"Given a function callable reference" - {
val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource(
KotlinSource(
"Test.kt",
"""
fun original() = "bar"
fun ref() = ::original
""".trimIndent()
val (programClassPool, libraryClassPool) =
ClassPoolBuilder.fromSource(
KotlinSource(
"Test.kt",
"""
fun original() = "bar"
fun ref() = ::original
""".trimIndent(),
),
)
)
programClassPool.classesAccept(
MultiClassVisitor(
@@ -69,15 +70,15 @@ class KotlinCallableReferenceFixerTest : FreeSpec({
"TestKt",
ClassRenamer(
{ "Obfuscated" },
{ clazz, member -> if (member.getName(clazz) == "original") "obfuscated" else member.getName(clazz) }
)
{ clazz, member -> if (member.getName(clazz) == "original") "obfuscated" else member.getName(clazz) },
),
),
createFixer(programClassPool, libraryClassPool)
)
createFixer(programClassPool, libraryClassPool),
),
)
val callableRefInfoVisitor = spyk<CallableReferenceInfoVisitor>()
val ownerVisitor = spyk< KotlinMetadataVisitor>()
val ownerVisitor = spyk<KotlinMetadataVisitor>()
val testVisitor = createVisitor(callableRefInfoVisitor, ownerVisitor)
programClassPool.classesAccept("TestKt\$ref\$1", testVisitor)
@@ -89,7 +90,7 @@ class KotlinCallableReferenceFixerTest : FreeSpec({
it.name shouldBe "obfuscated"
it.signature shouldBe "obfuscated()Ljava/lang/String;"
it.owner shouldNotBe null
}
},
)
}
@@ -98,7 +99,7 @@ class KotlinCallableReferenceFixerTest : FreeSpec({
withArg {
it.name shouldBe "Obfuscated"
},
ofType(KotlinFileFacadeKindMetadata::class)
ofType(KotlinFileFacadeKindMetadata::class),
)
}
}
@@ -108,26 +109,30 @@ class KotlinCallableReferenceFixerTest : FreeSpec({
"TestKt\$ref\$1",
AllConstantVisitor(
object : ConstantVisitor {
override fun visitAnyConstant(clazz: Clazz, constant: Constant) {
override fun visitAnyConstant(
clazz: Clazz,
constant: Constant,
) {
constant.toString() shouldNotContainIgnoringCase "original"
}
}
)
},
),
)
}
}
"Given a non-optimized function callable reference" - {
val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource(
KotlinSource(
"Test.kt",
"""
fun original() = "bar"
fun ref() = ::original
""".trimIndent()
),
kotlincArguments = listOf("-Xno-optimized-callable-references")
)
val (programClassPool, libraryClassPool) =
ClassPoolBuilder.fromSource(
KotlinSource(
"Test.kt",
"""
fun original() = "bar"
fun ref() = ::original
""".trimIndent(),
),
kotlincArguments = listOf("-Xno-optimized-callable-references"),
)
programClassPool.classesAccept(
MultiClassVisitor(
@@ -135,15 +140,15 @@ class KotlinCallableReferenceFixerTest : FreeSpec({
"TestKt",
ClassRenamer(
{ "Obfuscated" },
{ clazz, member -> if (member.getName(clazz) == "original") "obfuscated" else member.getName(clazz) }
)
{ clazz, member -> if (member.getName(clazz) == "original") "obfuscated" else member.getName(clazz) },
),
),
createFixer(programClassPool, libraryClassPool)
)
createFixer(programClassPool, libraryClassPool),
),
)
val callableRefInfoVisitor = spyk<CallableReferenceInfoVisitor>()
val ownerVisitor = spyk< KotlinMetadataVisitor>()
val ownerVisitor = spyk<KotlinMetadataVisitor>()
val testVisitor = createVisitor(callableRefInfoVisitor, ownerVisitor)
programClassPool.classesAccept("TestKt\$ref\$1", testVisitor)
@@ -155,7 +160,7 @@ class KotlinCallableReferenceFixerTest : FreeSpec({
it.name shouldBe "obfuscated"
it.signature shouldBe "obfuscated()Ljava/lang/String;"
it.owner shouldNotBe null
}
},
)
}
@@ -164,7 +169,7 @@ class KotlinCallableReferenceFixerTest : FreeSpec({
withArg {
it.name shouldBe "Obfuscated"
},
ofType(KotlinFileFacadeKindMetadata::class)
ofType(KotlinFileFacadeKindMetadata::class),
)
}
}
@@ -174,28 +179,32 @@ class KotlinCallableReferenceFixerTest : FreeSpec({
"TestKt\$ref\$1",
AllConstantVisitor(
object : ConstantVisitor {
override fun visitAnyConstant(clazz: Clazz, constant: Constant) {
override fun visitAnyConstant(
clazz: Clazz,
constant: Constant,
) {
constant.toString() shouldNotContainIgnoringCase "original"
}
}
)
},
),
)
}
}
"Given a property callable reference" - {
val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource(
KotlinSource(
"Test.kt",
"""
class Foo {
var original = "bar"
}
val (programClassPool, libraryClassPool) =
ClassPoolBuilder.fromSource(
KotlinSource(
"Test.kt",
"""
class Foo {
var original = "bar"
}
fun ref() = Foo()::original
""".trimIndent()
fun ref() = Foo()::original
""".trimIndent(),
),
)
)
programClassPool.classesAccept(
MultiClassVisitor(
@@ -211,22 +220,22 @@ class KotlinCallableReferenceFixerTest : FreeSpec({
member.getName(clazz) == "setOriginal" -> "setObfuscated"
else -> member.getName(clazz)
}
}
},
),
ReferencedKotlinMetadataVisitor(
AllPropertyVisitor
{ _, _, property ->
if (property.name == "original") property.name = "obfuscated"
}
)
)
{ _, _, property ->
if (property.name == "original") property.name = "obfuscated"
},
),
),
),
createFixer(programClassPool, libraryClassPool)
)
createFixer(programClassPool, libraryClassPool),
),
)
val callableRefInfoVisitor = spyk<CallableReferenceInfoVisitor>()
val ownerVisitor = spyk< KotlinMetadataVisitor>()
val ownerVisitor = spyk<KotlinMetadataVisitor>()
val testVisitor = createVisitor(callableRefInfoVisitor, ownerVisitor)
programClassPool.classesAccept("TestKt\$ref\$1", testVisitor)
@@ -238,7 +247,7 @@ class KotlinCallableReferenceFixerTest : FreeSpec({
it.name shouldBe "obfuscated"
it.signature shouldBe "getObfuscated()Ljava/lang/String;"
it.owner shouldNotBe null
}
},
)
}
@@ -247,7 +256,7 @@ class KotlinCallableReferenceFixerTest : FreeSpec({
withArg {
it.name shouldBe "Obfuscated"
},
ofType(KotlinClassKindMetadata::class)
ofType(KotlinClassKindMetadata::class),
)
}
}
@@ -257,33 +266,37 @@ class KotlinCallableReferenceFixerTest : FreeSpec({
"TestKt\$ref\$1",
AllConstantVisitor(
object : ConstantVisitor {
override fun visitAnyConstant(clazz: Clazz, constant: Constant) {
override fun visitAnyConstant(
clazz: Clazz,
constant: Constant,
) {
constant.toString() shouldNotContainIgnoringCase "original"
}
}
)
},
),
)
}
}
"Given a Java method callable reference" - {
val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource(
KotlinSource(
"Test.kt",
"""
val javaClassInstance = Original()
fun ref() = javaClassInstance::original
""".trimIndent()
),
JavaSource(
"Original.java",
"""
public class Original {
public String original() { return "bar"; }
}
""".trimIndent()
val (programClassPool, libraryClassPool) =
ClassPoolBuilder.fromSource(
KotlinSource(
"Test.kt",
"""
val javaClassInstance = Original()
fun ref() = javaClassInstance::original
""".trimIndent(),
),
JavaSource(
"Original.java",
"""
public class Original {
public String original() { return "bar"; }
}
""".trimIndent(),
),
)
)
programClassPool.classesAccept(
MultiClassVisitor(
@@ -291,15 +304,15 @@ class KotlinCallableReferenceFixerTest : FreeSpec({
"Original",
ClassRenamer(
{ "Obfuscated" },
{ clazz, member -> if (member.getName(clazz) == "original") "obfuscated" else member.getName(clazz) }
)
{ clazz, member -> if (member.getName(clazz) == "original") "obfuscated" else member.getName(clazz) },
),
),
createFixer(programClassPool, libraryClassPool)
)
createFixer(programClassPool, libraryClassPool),
),
)
val callableRefInfoVisitor = spyk<CallableReferenceInfoVisitor>()
val ownerVisitor = spyk< KotlinMetadataVisitor>()
val ownerVisitor = spyk<KotlinMetadataVisitor>()
val testVisitor = createVisitor(callableRefInfoVisitor, ownerVisitor)
programClassPool.classesAccept("TestKt\$ref\$1", testVisitor)
@@ -311,7 +324,7 @@ class KotlinCallableReferenceFixerTest : FreeSpec({
it.name shouldBe "obfuscated"
it.signature shouldBe "obfuscated()Ljava/lang/String;"
it.owner shouldBe null
}
},
)
}
@@ -320,7 +333,7 @@ class KotlinCallableReferenceFixerTest : FreeSpec({
withArg {
it.name shouldBe "Obfuscated"
},
ofType(KotlinMetadata::class)
ofType(KotlinMetadata::class),
)
}
}
@@ -330,33 +343,37 @@ class KotlinCallableReferenceFixerTest : FreeSpec({
"TestKt\$ref\$1",
AllConstantVisitor(
object : ConstantVisitor {
override fun visitAnyConstant(clazz: Clazz, constant: Constant) {
override fun visitAnyConstant(
clazz: Clazz,
constant: Constant,
) {
constant.toString() shouldNotContainIgnoringCase "original"
}
}
)
},
),
)
}
}
"Given a Java field callable reference" - {
val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource(
KotlinSource(
"Test.kt",
"""
val javaClassInstance = Original()
fun ref() = javaClassInstance::original
""".trimIndent()
),
JavaSource(
"Original.java",
"""
public class Original {
public String original = "bar";
}
""".trimIndent()
val (programClassPool, libraryClassPool) =
ClassPoolBuilder.fromSource(
KotlinSource(
"Test.kt",
"""
val javaClassInstance = Original()
fun ref() = javaClassInstance::original
""".trimIndent(),
),
JavaSource(
"Original.java",
"""
public class Original {
public String original = "bar";
}
""".trimIndent(),
),
)
)
programClassPool.classesAccept(
MultiClassVisitor(
@@ -364,15 +381,15 @@ class KotlinCallableReferenceFixerTest : FreeSpec({
"Original",
ClassRenamer(
{ "Obfuscated" },
{ clazz, member -> if (member.getName(clazz) == "original") "obfuscated" else member.getName(clazz) }
)
{ clazz, member -> if (member.getName(clazz) == "original") "obfuscated" else member.getName(clazz) },
),
),
createFixer(programClassPool, libraryClassPool)
)
createFixer(programClassPool, libraryClassPool),
),
)
val callableRefInfoVisitor = spyk<CallableReferenceInfoVisitor>()
val ownerVisitor = spyk< KotlinMetadataVisitor>()
val ownerVisitor = spyk<KotlinMetadataVisitor>()
val testVisitor = createVisitor(callableRefInfoVisitor, ownerVisitor)
programClassPool.classesAccept("TestKt\$ref\$1", testVisitor)
@@ -384,7 +401,7 @@ class KotlinCallableReferenceFixerTest : FreeSpec({
it.name shouldBe "obfuscated"
it.signature shouldBe "getObfuscated()Ljava/lang/String;"
it.owner shouldBe null
}
},
)
}
@@ -393,7 +410,7 @@ class KotlinCallableReferenceFixerTest : FreeSpec({
withArg {
it.name shouldBe "Obfuscated"
},
ofType(KotlinMetadata::class)
ofType(KotlinMetadata::class),
)
}
}
@@ -403,29 +420,45 @@ class KotlinCallableReferenceFixerTest : FreeSpec({
"TestKt\$ref\$1",
AllConstantVisitor(
object : ConstantVisitor {
override fun visitAnyConstant(clazz: Clazz, constant: Constant) {
override fun visitAnyConstant(
clazz: Clazz,
constant: Constant,
) {
constant.toString() shouldNotContainIgnoringCase "original"
}
}
)
},
),
)
}
}
})
private fun createVisitor(callableRefInfoVisitor: CallableReferenceInfoVisitor, ownerVisitor: KotlinMetadataVisitor) = ReferencedKotlinMetadataVisitor(
private fun createVisitor(
callableRefInfoVisitor: CallableReferenceInfoVisitor,
ownerVisitor: KotlinMetadataVisitor,
) = ReferencedKotlinMetadataVisitor(
object : KotlinMetadataVisitor {
override fun visitAnyKotlinMetadata(clazz: Clazz, kotlinMetadata: KotlinMetadata) {}
override fun visitKotlinSyntheticClassMetadata(clazz: Clazz, classMetadata: KotlinSyntheticClassKindMetadata) {
override fun visitAnyKotlinMetadata(
clazz: Clazz,
kotlinMetadata: KotlinMetadata,
) {}
override fun visitKotlinSyntheticClassMetadata(
clazz: Clazz,
classMetadata: KotlinSyntheticClassKindMetadata,
) {
classMetadata.callableReferenceInfoAccept(callableRefInfoVisitor)
classMetadata.callableReferenceInfoAccept { it.ownerAccept(ownerVisitor) }
}
}
},
)
private fun createFixer(programClassPool: ClassPool, libraryClassPool: ClassPool) = MultiClassVisitor(
private fun createFixer(
programClassPool: ClassPool,
libraryClassPool: ClassPool,
) = MultiClassVisitor(
MemberReferenceFixer(false),
ClassReferenceFixer(true),
ReferencedKotlinMetadataVisitor(KotlinCallableReferenceFixer(programClassPool, libraryClassPool)),
ConstantPoolShrinker()
ConstantPoolShrinker(),
)

View File

@@ -33,22 +33,24 @@ import proguard.util.ProcessingFlags
class KotlinIntrinsicsReplacementSequencesTest : FreeSpec({
"Given a class with a lateinit variable" - {
val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource(
KotlinSource(
"Test.kt",
"""
lateinit var lateVar : String
val (programClassPool, libraryClassPool) =
ClassPoolBuilder.fromSource(
KotlinSource(
"Test.kt",
"""
lateinit var lateVar : String
""",
),
)
)
val builder = InstructionSequenceBuilder(programClassPool, libraryClassPool)
val intrinsicsSequence = builder.ldc_(X)
.invokestatic(
KOTLIN_INTRINSICS_CLASS,
"throwUninitializedPropertyAccessException",
"(Ljava/lang/String;)V"
).instructions()
val intrinsicsSequence =
builder.ldc_(X)
.invokestatic(
KOTLIN_INTRINSICS_CLASS,
"throwUninitializedPropertyAccessException",
"(Ljava/lang/String;)V",
).instructions()
val constants = builder.constants()
val intrinsicsSequenceMatcher = InstructionSequenceMatcher(constants, intrinsicsSequence)
@@ -60,35 +62,43 @@ class KotlinIntrinsicsReplacementSequencesTest : FreeSpec({
true,
AllInstructionVisitor(
object : InstructionVisitor {
override fun visitAnyInstruction(clazz: Clazz, method: Method, codeAttribute: CodeAttribute, offset: Int, instruction: Instruction) {
override fun visitAnyInstruction(
clazz: Clazz,
method: Method,
codeAttribute: CodeAttribute,
offset: Int,
instruction: Instruction,
) {
instruction.accept(clazz, method, codeAttribute, offset, intrinsicsSequenceMatcher)
if (intrinsicsSequenceMatcher.isMatching) hasMatched = true
}
}
)
)
},
),
),
)
hasMatched shouldBe true
}
}
"Given a class with a lateinit variable without the DONT_OBFUSCATE flag" - {
val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource(
KotlinSource(
"Test.kt",
"""
lateinit var lateVar : String
val (programClassPool, libraryClassPool) =
ClassPoolBuilder.fromSource(
KotlinSource(
"Test.kt",
"""
lateinit var lateVar : String
""",
),
)
)
val builder = InstructionSequenceBuilder(programClassPool, libraryClassPool)
val intrinsicsSequence = builder.ldc_(X)
.invokestatic(
KOTLIN_INTRINSICS_CLASS,
"throwUninitializedPropertyAccessException",
"(Ljava/lang/String;)V"
).instructions()
val intrinsicsSequence =
builder.ldc_(X)
.invokestatic(
KOTLIN_INTRINSICS_CLASS,
"throwUninitializedPropertyAccessException",
"(Ljava/lang/String;)V",
).instructions()
val constants = builder.constants()
val intrinsicsSequenceMatcher = InstructionSequenceMatcher(constants, intrinsicsSequence)
@@ -102,24 +112,33 @@ class KotlinIntrinsicsReplacementSequencesTest : FreeSpec({
true,
AllInstructionVisitor(
object : InstructionVisitor {
override fun visitAnyInstruction(clazz: Clazz, method: Method, codeAttribute: CodeAttribute, offset: Int, instruction: Instruction) {
override fun visitAnyInstruction(
clazz: Clazz,
method: Method,
codeAttribute: CodeAttribute,
offset: Int,
instruction: Instruction,
) {
instruction.accept(clazz, method, codeAttribute, offset, intrinsicsSequenceMatcher)
if (intrinsicsSequenceMatcher.isMatching) {
var constantIndex = intrinsicsSequenceMatcher.matchedConstantIndex(X)
clazz.constantPoolEntryAccept(
constantIndex,
object : ConstantVisitor {
override fun visitStringConstant(clazz: Clazz, stringConstant: StringConstant) {
override fun visitStringConstant(
clazz: Clazz,
stringConstant: StringConstant,
) {
matchedConstant = stringConstant.getString(clazz)
}
}
},
)
}
}
}
)
)
)
},
),
),
),
)
matchedConstant shouldBe ""
@@ -127,22 +146,24 @@ class KotlinIntrinsicsReplacementSequencesTest : FreeSpec({
}
"Given a class with a lateinit variable and the DONT_OBFUSCATE flag" - {
val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource(
KotlinSource(
"Test.kt",
"""
lateinit var lateVar : String
val (programClassPool, libraryClassPool) =
ClassPoolBuilder.fromSource(
KotlinSource(
"Test.kt",
"""
lateinit var lateVar : String
""",
),
)
)
val builder = InstructionSequenceBuilder(programClassPool, libraryClassPool)
val intrinsicsSequence = builder.ldc_(X)
.invokestatic(
KOTLIN_INTRINSICS_CLASS,
"throwUninitializedPropertyAccessException",
"(Ljava/lang/String;)V"
).instructions()
val intrinsicsSequence =
builder.ldc_(X)
.invokestatic(
KOTLIN_INTRINSICS_CLASS,
"throwUninitializedPropertyAccessException",
"(Ljava/lang/String;)V",
).instructions()
val constants = builder.constants()
val intrinsicsSequenceMatcher = InstructionSequenceMatcher(constants, intrinsicsSequence)
@@ -158,24 +179,33 @@ class KotlinIntrinsicsReplacementSequencesTest : FreeSpec({
true,
AllInstructionVisitor(
object : InstructionVisitor {
override fun visitAnyInstruction(clazz: Clazz, method: Method, codeAttribute: CodeAttribute, offset: Int, instruction: Instruction) {
override fun visitAnyInstruction(
clazz: Clazz,
method: Method,
codeAttribute: CodeAttribute,
offset: Int,
instruction: Instruction,
) {
instruction.accept(clazz, method, codeAttribute, offset, intrinsicsSequenceMatcher)
if (intrinsicsSequenceMatcher.isMatching) {
var constantIndex = intrinsicsSequenceMatcher.matchedConstantIndex(X)
clazz.constantPoolEntryAccept(
constantIndex,
object : ConstantVisitor {
override fun visitStringConstant(clazz: Clazz, stringConstant: StringConstant) {
override fun visitStringConstant(
clazz: Clazz,
stringConstant: StringConstant,
) {
matchedConstant = stringConstant.getString(clazz)
}
}
},
)
}
}
}
)
)
)
},
),
),
),
)
matchedConstant shouldBe "lateVar"

View File

@@ -48,19 +48,20 @@ class KotlinValueParameterNameShrinkerTest : FreeSpec({
// InstancePerTest so the names are reset before every test
isolationMode = IsolationMode.InstancePerTest
val (programClassPool, _) = ClassPoolBuilder.fromSource(
KotlinSource(
"Test.kt",
"""
@Suppress("UNUSED_PARAMETER")
class Foo(param1: String, param2: String, param3: String) {
var property: String = "foo"
set(param1) { }
fun foo(param1: String, param2: String, param3: String) {}
}
""".trimIndent()
val (programClassPool, _) =
ClassPoolBuilder.fromSource(
KotlinSource(
"Test.kt",
"""
@Suppress("UNUSED_PARAMETER")
class Foo(param1: String, param2: String, param3: String) {
var property: String = "foo"
set(param1) { }
fun foo(param1: String, param2: String, param3: String) {}
}
""".trimIndent(),
),
)
)
"Given a class with value parameters" - {
val clazz = programClassPool.getClass("Foo")
@@ -77,9 +78,9 @@ class KotlinValueParameterNameShrinkerTest : FreeSpec({
clazz.kotlinMetadataAccept(
AllConstructorVisitor(
AllValueParameterVisitor(
valueParameterVisitor
)
)
valueParameterVisitor,
),
),
)
val valueParameters = mutableListOf<ValueParameter>()
@@ -89,15 +90,16 @@ class KotlinValueParameterNameShrinkerTest : FreeSpec({
clazz,
ofType(KotlinClassKindMetadata::class),
ofType(KotlinConstructorMetadata::class),
capture(valueParameters)
capture(valueParameters),
)
}
valueParameters shouldExistInOrder listOf<(ValueParameter) -> Boolean>(
{ it.parameterName == "param1" },
{ it.parameterName == "param2" },
{ it.parameterName == "param3" }
)
valueParameters shouldExistInOrder
listOf<(ValueParameter) -> Boolean>(
{ it.parameterName == "param1" },
{ it.parameterName == "param2" },
{ it.parameterName == "param3" },
)
}
}
@@ -113,9 +115,9 @@ class KotlinValueParameterNameShrinkerTest : FreeSpec({
clazz.kotlinMetadataAccept(
AllConstructorVisitor(
AllValueParameterVisitor(
valueParameterVisitor
)
)
valueParameterVisitor,
),
),
)
val valueParameters = mutableListOf<ValueParameter>()
@@ -125,15 +127,16 @@ class KotlinValueParameterNameShrinkerTest : FreeSpec({
clazz,
ofType(KotlinClassKindMetadata::class),
ofType(KotlinConstructorMetadata::class),
capture(valueParameters)
capture(valueParameters),
)
}
valueParameters shouldExistInOrder listOf<(ValueParameter) -> Boolean>(
{ it.parameterName == "p0" },
{ it.parameterName == "p1" },
{ it.parameterName == "p2" }
)
valueParameters shouldExistInOrder
listOf<(ValueParameter) -> Boolean>(
{ it.parameterName == "p0" },
{ it.parameterName == "p1" },
{ it.parameterName == "p2" },
)
}
}
@@ -152,9 +155,9 @@ class KotlinValueParameterNameShrinkerTest : FreeSpec({
clazz.kotlinMetadataAccept(
AllConstructorVisitor(
AllValueParameterVisitor(
valueParameterVisitor
)
)
valueParameterVisitor,
),
),
)
verify(exactly = 3) {
@@ -162,15 +165,16 @@ class KotlinValueParameterNameShrinkerTest : FreeSpec({
clazz,
ofType(KotlinClassKindMetadata::class),
ofType(KotlinConstructorMetadata::class),
capture(valueParameters)
capture(valueParameters),
)
}
valueParameters shouldExistInOrder listOf<(ValueParameter) -> Boolean>(
{ it.parameterName == "p0" },
{ it.parameterName == "param2" },
{ it.parameterName == "p1" }
)
valueParameters shouldExistInOrder
listOf<(ValueParameter) -> Boolean>(
{ it.parameterName == "p0" },
{ it.parameterName == "param2" },
{ it.parameterName == "p1" },
)
}
}
}
@@ -190,9 +194,9 @@ class KotlinValueParameterNameShrinkerTest : FreeSpec({
clazz.kotlinMetadataAccept(
AllFunctionVisitor(
AllValueParameterVisitor(
valueParameterVisitor
)
)
valueParameterVisitor,
),
),
)
val valueParameters = mutableListOf<ValueParameter>()
@@ -202,15 +206,16 @@ class KotlinValueParameterNameShrinkerTest : FreeSpec({
clazz,
ofType(KotlinClassKindMetadata::class),
ofType(KotlinFunctionMetadata::class),
capture(valueParameters)
capture(valueParameters),
)
}
valueParameters shouldExistInOrder listOf<(ValueParameter) -> Boolean>(
{ it.parameterName == "param1" },
{ it.parameterName == "param2" },
{ it.parameterName == "param3" }
)
valueParameters shouldExistInOrder
listOf<(ValueParameter) -> Boolean>(
{ it.parameterName == "param1" },
{ it.parameterName == "param2" },
{ it.parameterName == "param3" },
)
}
}
@@ -226,9 +231,9 @@ class KotlinValueParameterNameShrinkerTest : FreeSpec({
clazz.kotlinMetadataAccept(
AllFunctionVisitor(
AllValueParameterVisitor(
valueParameterVisitor
)
)
valueParameterVisitor,
),
),
)
val valueParameters = mutableListOf<ValueParameter>()
@@ -238,15 +243,16 @@ class KotlinValueParameterNameShrinkerTest : FreeSpec({
clazz,
ofType(KotlinClassKindMetadata::class),
ofType(KotlinFunctionMetadata::class),
capture(valueParameters)
capture(valueParameters),
)
}
valueParameters shouldExistInOrder listOf<(ValueParameter) -> Boolean>(
{ it.parameterName == "p0" },
{ it.parameterName == "p1" },
{ it.parameterName == "p2" }
)
valueParameters shouldExistInOrder
listOf<(ValueParameter) -> Boolean>(
{ it.parameterName == "p0" },
{ it.parameterName == "p1" },
{ it.parameterName == "p2" },
)
}
}
@@ -265,9 +271,9 @@ class KotlinValueParameterNameShrinkerTest : FreeSpec({
clazz.kotlinMetadataAccept(
AllFunctionVisitor(
AllValueParameterVisitor(
valueParameterVisitor
)
)
valueParameterVisitor,
),
),
)
verify(exactly = 3) {
@@ -275,15 +281,16 @@ class KotlinValueParameterNameShrinkerTest : FreeSpec({
clazz,
ofType(KotlinClassKindMetadata::class),
ofType(KotlinFunctionMetadata::class),
capture(valueParameters)
capture(valueParameters),
)
}
valueParameters shouldExistInOrder listOf<(ValueParameter) -> Boolean>(
{ it.parameterName == "p0" },
{ it.parameterName == "param2" },
{ it.parameterName == "p1" }
)
valueParameters shouldExistInOrder
listOf<(ValueParameter) -> Boolean>(
{ it.parameterName == "p0" },
{ it.parameterName == "param2" },
{ it.parameterName == "p1" },
)
}
}
}
@@ -303,9 +310,9 @@ class KotlinValueParameterNameShrinkerTest : FreeSpec({
clazz.kotlinMetadataAccept(
AllPropertyVisitor(
AllValueParameterVisitor(
valueParameterVisitor
)
)
valueParameterVisitor,
),
),
)
verify {
@@ -313,7 +320,7 @@ class KotlinValueParameterNameShrinkerTest : FreeSpec({
clazz,
ofType(KotlinClassKindMetadata::class),
ofType(KotlinPropertyMetadata::class),
withArg { it.parameterName shouldBe "param1" }
withArg { it.parameterName shouldBe "param1" },
)
}
}
@@ -331,9 +338,9 @@ class KotlinValueParameterNameShrinkerTest : FreeSpec({
clazz.kotlinMetadataAccept(
AllPropertyVisitor(
AllValueParameterVisitor(
valueParameterVisitor
)
)
valueParameterVisitor,
),
),
)
verify {
@@ -341,7 +348,7 @@ class KotlinValueParameterNameShrinkerTest : FreeSpec({
clazz,
ofType(KotlinClassKindMetadata::class),
ofType(KotlinPropertyMetadata::class),
withArg { it.parameterName shouldBe "p0" }
withArg { it.parameterName shouldBe "p0" },
)
}
}

View File

@@ -29,37 +29,39 @@ class KotlinContextReceiverParameterShrinkingTest : StringSpec({
// with existing optimizations.
"Given a Kotlin function with ContextReceivers" {
val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource(
KotlinSource(
"Test.kt",
"""
class Unused {
fun info(message: String) {
println(message)
val (programClassPool, libraryClassPool) =
ClassPoolBuilder.fromSource(
KotlinSource(
"Test.kt",
"""
class Unused {
fun info(message: String) {
println(message)
}
}
class Used {
fun used(message: String) { }
}
}
class Used {
fun used(message: String) { }
}
context(Used, Unused)
fun String.foo() {
used(this)
}
fun main() {
with (Unused()) { with (Used()) { "test".foo() } }
}
""".trimIndent()
),
kotlincArguments = listOf(
"-Xcontext-receivers",
// Disable generation of instrinsics null checks
// In real world use-cases, -assumenosideeffects might be used
"-Xno-param-assertions"
context(Used, Unused)
fun String.foo() {
used(this)
}
fun main() {
with (Unused()) { with (Used()) { "test".foo() } }
}
""".trimIndent(),
),
kotlincArguments =
listOf(
"-Xcontext-receivers",
// Disable generation of instrinsics null checks
// In real world use-cases, -assumenosideeffects might be used
"-Xno-param-assertions",
),
)
)
optimize(programClassPool, libraryClassPool)
@@ -72,38 +74,40 @@ class KotlinContextReceiverParameterShrinkingTest : StringSpec({
}
"Given a Kotlin function with default parameters with ContextReceivers" {
val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource(
KotlinSource(
"Test.kt",
"""
class Unused {
fun info(message: String) {
println(message)
val (programClassPool, libraryClassPool) =
ClassPoolBuilder.fromSource(
KotlinSource(
"Test.kt",
"""
class Unused {
fun info(message: String) {
println(message)
}
}
class Used {
fun bar(message: String) { }
}
}
class Used {
fun bar(message: String) { }
}
context(Used, Unused)
fun String.foo(message: String = "test") {
println(this) // use the String receiver
bar(message)
}
context(Used, Unused)
fun String.foo(message: String = "test") {
println(this) // use the String receiver
bar(message)
}
fun main() {
with (Unused()) { with (Used()) { "test".foo() } }
}
""".trimIndent()
),
kotlincArguments = listOf(
"-Xcontext-receivers",
// Disable generation of instrinsics null checks
// In real world use-cases, -assumenosideeffects might be used
"-Xno-param-assertions"
fun main() {
with (Unused()) { with (Used()) { "test".foo() } }
}
""".trimIndent(),
),
kotlincArguments =
listOf(
"-Xcontext-receivers",
// Disable generation of instrinsics null checks
// In real world use-cases, -assumenosideeffects might be used
"-Xno-param-assertions",
),
)
)
optimize(programClassPool, libraryClassPool)
@@ -117,40 +121,42 @@ class KotlinContextReceiverParameterShrinkingTest : StringSpec({
}
"Given a Kotlin property with ContextReceivers used in the getter" {
val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource(
KotlinSource(
"Test.kt",
"""
class Unused {
fun info(message: String) {
println(message)
val (programClassPool, libraryClassPool) =
ClassPoolBuilder.fromSource(
KotlinSource(
"Test.kt",
"""
class Unused {
fun info(message: String) {
println(message)
}
}
class Used {
fun barfo(message: String) { }
}
}
class Used {
fun barfo(message: String) { }
}
context(Used, Unused)
val String.property: String
get() {
println(this)
barfo("bar")
return "test"
}
fun main() {
with (Unused()) { with (Used()) { "test".property } }
}
""".trimIndent()
),
kotlincArguments = listOf(
"-Xcontext-receivers",
// Disable generation of instrinsics null checks
// In real world use-cases, -assumenosideeffects might be used
"-Xno-param-assertions"
context(Used, Unused)
val String.property: String
get() {
println(this)
barfo("bar")
return "test"
}
fun main() {
with (Unused()) { with (Used()) { "test".property } }
}
""".trimIndent(),
),
kotlincArguments =
listOf(
"-Xcontext-receivers",
// Disable generation of instrinsics null checks
// In real world use-cases, -assumenosideeffects might be used
"-Xno-param-assertions",
),
)
)
optimize(programClassPool, libraryClassPool)
@@ -163,44 +169,46 @@ class KotlinContextReceiverParameterShrinkingTest : StringSpec({
}
"Given a Kotlin property with a single ContextReceivers used both in the getter and setter" {
val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource(
KotlinSource(
"Test.kt",
"""
class Used2 {
fun used2(message: String) {
println(message)
val (programClassPool, libraryClassPool) =
ClassPoolBuilder.fromSource(
KotlinSource(
"Test.kt",
"""
class Used2 {
fun used2(message: String) {
println(message)
}
}
class Used {
fun used(message: String) { }
}
}
class Used {
fun used(message: String) { }
}
context(Used, Used2)
var String.property: String
set(value) {
used(value)
used2(value)
context(Used, Used2)
var String.property: String
set(value) {
used(value)
used2(value)
}
get() {
used("bar")
used2(this)
return "test"
}
fun main() {
with (Used2()) { with (Used()) { "test".property } }
}
get() {
used("bar")
used2(this)
return "test"
}
fun main() {
with (Used2()) { with (Used()) { "test".property } }
}
""".trimIndent()
),
kotlincArguments = listOf(
"-Xcontext-receivers",
// Disable generation of instrinsics null checks
// In real world use-cases, -assumenosideeffects might be used
"-Xno-param-assertions"
""".trimIndent(),
),
kotlincArguments =
listOf(
"-Xcontext-receivers",
// Disable generation of instrinsics null checks
// In real world use-cases, -assumenosideeffects might be used
"-Xno-param-assertions",
),
)
)
optimize(programClassPool, libraryClassPool)
@@ -213,41 +221,43 @@ class KotlinContextReceiverParameterShrinkingTest : StringSpec({
}
"Given a Kotlin property with a single ContextReceivers used in only the setter" {
val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource(
KotlinSource(
"Test.kt",
"""
class Used2 {
fun used2(message: String) {
println(message)
val (programClassPool, libraryClassPool) =
ClassPoolBuilder.fromSource(
KotlinSource(
"Test.kt",
"""
class Used2 {
fun used2(message: String) {
println(message)
}
}
class Used {
fun used(message: String) { }
}
}
class Used {
fun used(message: String) { }
}
context(Used, Used2)
var String.property: String
set(value) {
used2(value)
context(Used, Used2)
var String.property: String
set(value) {
used2(value)
}
get() {
return "test"
}
fun main() {
with (Used2()) { with (Used()) { "test".property } }
}
get() {
return "test"
}
fun main() {
with (Used2()) { with (Used()) { "test".property } }
}
""".trimIndent()
),
kotlincArguments = listOf(
"-Xcontext-receivers",
// Disable generation of instrinsics null checks
// In real world use-cases, -assumenosideeffects might be used
"-Xno-param-assertions"
""".trimIndent(),
),
kotlincArguments =
listOf(
"-Xcontext-receivers",
// Disable generation of instrinsics null checks
// In real world use-cases, -assumenosideeffects might be used
"-Xno-param-assertions",
),
)
)
optimize(programClassPool, libraryClassPool)
@@ -258,40 +268,42 @@ class KotlinContextReceiverParameterShrinkingTest : StringSpec({
verifyParameters(testKt, "get*,set*", p1 = "LUsed;", p2 = "LUsed2;")
}
"Given a Kotlin property with ContextReceivers used in only the getter but also containing a setter" {
val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource(
KotlinSource(
"Test.kt",
"""
class Unused {
fun info(message: String) {
println(message)
val (programClassPool, libraryClassPool) =
ClassPoolBuilder.fromSource(
KotlinSource(
"Test.kt",
"""
class Unused {
fun info(message: String) {
println(message)
}
}
class Used {
fun used(message: String) { }
}
}
class Used {
fun used(message: String) { }
}
context(Used, Unused)
var String.property: String
set(value) { }
get() {
used(this)
return "test"
}
fun main() {
with (Unused()) { with (Used()) { "test".property } }
}
""".trimIndent()
),
kotlincArguments = listOf(
"-Xcontext-receivers",
// Disable generation of instrinsics null checks
// In real world use-cases, -assumenosideeffects might be used
"-Xno-param-assertions"
context(Used, Unused)
var String.property: String
set(value) { }
get() {
used(this)
return "test"
}
fun main() {
with (Unused()) { with (Used()) { "test".property } }
}
""".trimIndent(),
),
kotlincArguments =
listOf(
"-Xcontext-receivers",
// Disable generation of instrinsics null checks
// In real world use-cases, -assumenosideeffects might be used
"-Xno-param-assertions",
),
)
)
optimize(programClassPool, libraryClassPool)
@@ -303,30 +315,32 @@ class KotlinContextReceiverParameterShrinkingTest : StringSpec({
}
"Given a Kotlin property with ContextReceivers with no getter or setter" {
val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource(
KotlinSource(
"Test.kt",
"""
class Unused1
class Unused2
val (programClassPool, libraryClassPool) =
ClassPoolBuilder.fromSource(
KotlinSource(
"Test.kt",
"""
class Unused1
class Unused2
class ClassWithProperty {
context(Unused1, Unused2)
val property: String by lazy { "foo" }
}
fun main() {
with (Unused2()) { with (Unused1()) { ClassWithProperty().property } }
}
""".trimIndent()
),
kotlincArguments = listOf(
"-Xcontext-receivers",
// Disable generation of instrinsics null checks
// In real world use-cases, -assumenosideeffects might be used
"-Xno-param-assertions"
class ClassWithProperty {
context(Unused1, Unused2)
val property: String by lazy { "foo" }
}
fun main() {
with (Unused2()) { with (Unused1()) { ClassWithProperty().property } }
}
""".trimIndent(),
),
kotlincArguments =
listOf(
"-Xcontext-receivers",
// Disable generation of instrinsics null checks
// In real world use-cases, -assumenosideeffects might be used
"-Xno-param-assertions",
),
)
)
val classWithProperty = programClassPool.getClass("ClassWithProperty")
@@ -339,39 +353,41 @@ class KotlinContextReceiverParameterShrinkingTest : StringSpec({
}
"Given a Kotlin class with ContextReceivers" {
val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource(
KotlinSource(
"Test.kt",
"""
class Unused {
fun info(message: String) {
println(message)
val (programClassPool, libraryClassPool) =
ClassPoolBuilder.fromSource(
KotlinSource(
"Test.kt",
"""
class Unused {
fun info(message: String) {
println(message)
}
}
class Used {
fun barfo(message: String) { }
}
}
class Used {
fun barfo(message: String) { }
}
context(Used, Unused)
class MyClass {
fun foo() {
barfo("test")
context(Used, Unused)
class MyClass {
fun foo() {
barfo("test")
}
}
}
fun main() {
with (Unused()) { with (Used()) { MyClass().foo() } }
}
""".trimIndent()
),
kotlincArguments = listOf(
"-Xcontext-receivers",
// Disable generation of instrinsics null checks
// In real world use-cases, -assumenosideeffects might be used
"-Xno-param-assertions"
fun main() {
with (Unused()) { with (Used()) { MyClass().foo() } }
}
""".trimIndent(),
),
kotlincArguments =
listOf(
"-Xcontext-receivers",
// Disable generation of instrinsics null checks
// In real world use-cases, -assumenosideeffects might be used
"-Xno-param-assertions",
),
)
)
optimize(programClassPool, libraryClassPool)
@@ -384,7 +400,12 @@ class KotlinContextReceiverParameterShrinkingTest : StringSpec({
}
})
private fun verifyParameters(testKt: Clazz, s: String, p1: String = "LUsed;", p2: String = "LUnused;") {
private fun verifyParameters(
testKt: Clazz,
s: String,
p1: String = "LUsed;",
p2: String = "LUnused;",
) {
val parameterVisitor = spyk<ParameterVisitor>()
testKt.methodsAccept(MemberNameFilter(s, AllParameterVisitor(false, parameterVisitor)))
verify {
@@ -398,7 +419,7 @@ private fun verifyParameters(testKt: Clazz, s: String, p1: String = "LUsed;", p2
withArg {
it shouldBe p1
},
ofType<Clazz>()
ofType<Clazz>(),
)
parameterVisitor.visitParameter(
@@ -411,13 +432,17 @@ private fun verifyParameters(testKt: Clazz, s: String, p1: String = "LUsed;", p2
withArg {
it shouldBe p2
},
ofType<Clazz>()
ofType<Clazz>(),
)
}
}
private fun optimize(programClassPool: ClassPool, libraryClassPool: ClassPool) {
val config = """
private fun optimize(
programClassPool: ClassPool,
libraryClassPool: ClassPool,
) {
val config =
"""
-keep,allowoptimization,allowshrinking class TestKt,ClassWithProperty,MyClass { *; }
-optimizations **
""".asConfiguration()

View File

@@ -44,29 +44,33 @@ class MemberDescriptorSpecializerTest : FreeSpec({
programClassPool.classesAccept(AllMemberVisitor(ProgramMemberOptimizationInfoSetter()))
// Create the optimization as in Optimizer
val fillingOutValuesClassVisitor = ClassVisitorFactory {
val valueFactory: ValueFactory = ParticularValueFactory()
val storingInvocationUnit: InvocationUnit = StoringInvocationUnit(
valueFactory,
true,
true,
true
)
ClassAccessFilter(
0, AccessConstants.SYNTHETIC,
AllMethodVisitor(
AllAttributeVisitor(
DebugAttributeVisitor(
"Filling out fields, method parameters, and return values",
PartialEvaluator(
valueFactory, storingInvocationUnit,
true
)
)
val fillingOutValuesClassVisitor =
ClassVisitorFactory {
val valueFactory: ValueFactory = ParticularValueFactory()
val storingInvocationUnit: InvocationUnit =
StoringInvocationUnit(
valueFactory,
true,
true,
true,
)
ClassAccessFilter(
0,
AccessConstants.SYNTHETIC,
AllMethodVisitor(
AllAttributeVisitor(
DebugAttributeVisitor(
"Filling out fields, method parameters, and return values",
PartialEvaluator(
valueFactory,
storingInvocationUnit,
true,
),
),
),
),
)
)
}
}
programClassPool.classesAccept(fillingOutValuesClassVisitor.createClassVisitor())
@@ -80,33 +84,34 @@ class MemberDescriptorSpecializerTest : FreeSpec({
true,
null,
null,
null
)
)
)
null,
),
),
),
)
}
"Given a method with a more general program class pool parameter type than its use" - {
val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource(
JavaSource(
"Test.java",
"""
public class Test {
public static void main(String[] args) {
foo(new Foo());
val (programClassPool, libraryClassPool) =
ClassPoolBuilder.fromSource(
JavaSource(
"Test.java",
"""
public class Test {
public static void main(String[] args) {
foo(new Foo());
}
public static void foo(Bar foo) {
System.out.println(foo);
}
}
public static void foo(Bar foo) {
System.out.println(foo);
}
}
class Bar { }
class Foo extends Bar { }
""".trimIndent()
class Bar { }
class Foo extends Bar { }
""".trimIndent(),
),
)
)
"When specializing the member descriptors" - {
specializeMemberDescriptors(programClassPool, libraryClassPool)
@@ -119,12 +124,15 @@ class MemberDescriptorSpecializerTest : FreeSpec({
MemberNameFilter(
"foo*",
object : MemberVisitor {
override fun visitAnyMember(clazz: Clazz, member: Member) {
override fun visitAnyMember(
clazz: Clazz,
member: Member,
) {
memberDescriptor = member.getDescriptor(clazz)
}
}
)
)
},
),
),
)
memberDescriptor shouldBe "(LFoo;)V"
}
@@ -132,24 +140,25 @@ class MemberDescriptorSpecializerTest : FreeSpec({
}
"Given a field with a more general program class pool parameter type than its use" - {
val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource(
JavaSource(
"Test.java",
"""
public class Test {
static Bar myField = null;
public static void main(String[] args) {
myField = new Foo();
val (programClassPool, libraryClassPool) =
ClassPoolBuilder.fromSource(
JavaSource(
"Test.java",
"""
public class Test {
static Bar myField = null;
public static void main(String[] args) {
myField = new Foo();
}
}
}
class Bar { }
class Foo extends Bar { }
""".trimIndent()
class Bar { }
class Foo extends Bar { }
""".trimIndent(),
),
)
)
"When specializing the member descriptors" - {
specializeMemberDescriptors(programClassPool, libraryClassPool)
@@ -162,12 +171,15 @@ class MemberDescriptorSpecializerTest : FreeSpec({
MemberNameFilter(
"myField*",
object : MemberVisitor {
override fun visitAnyMember(clazz: Clazz, member: Member) {
override fun visitAnyMember(
clazz: Clazz,
member: Member,
) {
memberDescriptor = member.getDescriptor(clazz)
}
}
)
)
},
),
),
)
memberDescriptor shouldBe "LFoo;"
}
@@ -175,20 +187,21 @@ class MemberDescriptorSpecializerTest : FreeSpec({
}
"Given a field with a more general library class pool parameter type than its use" - {
val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource(
JavaSource(
"Test.java",
"""
public class Test {
static java.lang.Object myField = null;
public static void main(String[] args) {
myField = new java.lang.StringBuffer();
val (programClassPool, libraryClassPool) =
ClassPoolBuilder.fromSource(
JavaSource(
"Test.java",
"""
public class Test {
static java.lang.Object myField = null;
public static void main(String[] args) {
myField = new java.lang.StringBuffer();
}
}
}
""".trimIndent()
""".trimIndent(),
),
)
)
"When specializing the member descriptors" - {
specializeMemberDescriptors(programClassPool, libraryClassPool)
@@ -201,12 +214,15 @@ class MemberDescriptorSpecializerTest : FreeSpec({
MemberNameFilter(
"myField*",
object : MemberVisitor {
override fun visitAnyMember(clazz: Clazz, member: Member) {
override fun visitAnyMember(
clazz: Clazz,
member: Member,
) {
memberDescriptor = member.getDescriptor(clazz)
}
}
)
)
},
),
),
)
// Library classes are not marked as available by default. Therefore, they are not specialized.
memberDescriptor shouldBe "Ljava/lang/Object;"

View File

@@ -9,14 +9,15 @@ import proguard.testutils.JavaSource
class SimpleEnumClassCheckerTest : FreeSpec({
"Given a enum class without instance methods" - {
val (programClassPool, _) = ClassPoolBuilder.fromSource(
JavaSource(
"Enum.java",
"""
public enum Enum { FOO, BAR }
""".trimIndent()
val (programClassPool, _) =
ClassPoolBuilder.fromSource(
JavaSource(
"Enum.java",
"""
public enum Enum { FOO, BAR }
""".trimIndent(),
),
)
)
"Then when checked with SimpleEnumClassChecker" - {
val enumClass = programClassPool.getClass("Enum")
@@ -30,19 +31,20 @@ class SimpleEnumClassCheckerTest : FreeSpec({
}
"Given a enum class with a public instance method" - {
val (programClassPool, _) = ClassPoolBuilder.fromSource(
JavaSource(
"Enum.java",
"""
public enum Enum {
FOO, BAR;
public void foo() {
val (programClassPool, _) =
ClassPoolBuilder.fromSource(
JavaSource(
"Enum.java",
"""
public enum Enum {
FOO, BAR;
public void foo() {
}
}
}
""".trimIndent()
""".trimIndent(),
),
)
)
"Then when checked with SimpleEnumClassChecker" - {
val enumClass = programClassPool.getClass("Enum")
@@ -56,19 +58,20 @@ class SimpleEnumClassCheckerTest : FreeSpec({
}
"Given a enum class with a private instance method" - {
val (programClassPool, _) = ClassPoolBuilder.fromSource(
JavaSource(
"Enum.java",
"""
public enum Enum {
FOO, BAR;
private void foo() {
val (programClassPool, _) =
ClassPoolBuilder.fromSource(
JavaSource(
"Enum.java",
"""
public enum Enum {
FOO, BAR;
private void foo() {
}
}
}
""".trimIndent()
""".trimIndent(),
),
)
)
"Then when checked with SimpleEnumClassChecker" - {
val enumClass = programClassPool.getClass("Enum")

View File

@@ -22,18 +22,19 @@ import proguard.testutils.JavaSource
class MarkedAnnotationDeleterTest : FreeSpec({
"Given a class with two annotations on its field" - {
val (programClassPool, _) = ClassPoolBuilder.fromSource(
JavaSource("Ann1.java", "public @interface Ann1 {}"),
JavaSource("Ann2.java", "public @interface Ann2 {}"),
JavaSource(
"A.java",
"""class A {
val (programClassPool, _) =
ClassPoolBuilder.fromSource(
JavaSource("Ann1.java", "public @interface Ann1 {}"),
JavaSource("Ann2.java", "public @interface Ann2 {}"),
JavaSource(
"A.java",
"""class A {
@Ann1
@Ann2
public int a;
}"""
}""",
),
)
)
val classA = programClassPool.getClass("A")
@@ -43,15 +44,15 @@ class MarkedAnnotationDeleterTest : FreeSpec({
classA.fieldsAccept(
AllAttributeVisitor(
AllAnnotationVisitor(
ProcessingInfoSetter(mark)
)
)
ProcessingInfoSetter(mark),
),
),
)
classA.fieldsAccept(
AllAttributeVisitor(
MarkedAnnotationDeleter(mark)
)
MarkedAnnotationDeleter(mark),
),
)
"Then no annotations should remain" {
@@ -59,13 +60,17 @@ class MarkedAnnotationDeleterTest : FreeSpec({
classA.fieldsAccept(
AllAttributeVisitor(
AllAnnotationVisitor(object : AnnotationVisitor {
override fun visitAnnotation(clazz: Clazz?, annotation: Annotation?) {
count += 1
}
}
)
)
AllAnnotationVisitor(
object : AnnotationVisitor {
override fun visitAnnotation(
clazz: Clazz?,
annotation: Annotation?,
) {
count += 1
}
},
),
),
)
count shouldBe 0
@@ -74,24 +79,25 @@ class MarkedAnnotationDeleterTest : FreeSpec({
}
"Given a class with two A and B annotations on its field" - {
val (programClassPool, _) = ClassPoolBuilder.fromSource(
JavaSource("AnnA1.java", "public @interface AnnA1 {}"),
JavaSource("AnnA2.java", "public @interface AnnA2 {}"),
JavaSource("AnnB1.java", "public @interface AnnB1 {}"),
JavaSource("AnnB2.java", "public @interface AnnB2 {}"),
JavaSource("AnnB3.java", "public @interface AnnB3 {}"),
JavaSource(
"A.java",
"""class A {
val (programClassPool, _) =
ClassPoolBuilder.fromSource(
JavaSource("AnnA1.java", "public @interface AnnA1 {}"),
JavaSource("AnnA2.java", "public @interface AnnA2 {}"),
JavaSource("AnnB1.java", "public @interface AnnB1 {}"),
JavaSource("AnnB2.java", "public @interface AnnB2 {}"),
JavaSource("AnnB3.java", "public @interface AnnB3 {}"),
JavaSource(
"A.java",
"""class A {
@AnnB1
@AnnA1
@AnnB2
@AnnA2
@AnnB3
public int a;
}"""
}""",
),
)
)
val classA = programClassPool.getClass("A")
@@ -103,16 +109,16 @@ class MarkedAnnotationDeleterTest : FreeSpec({
AllAnnotationVisitor(
AnnotationTypeFilter(
"LAnnA*;",
ProcessingInfoSetter(mark)
)
)
)
ProcessingInfoSetter(mark),
),
),
),
)
classA.fieldsAccept(
AllAttributeVisitor(
MarkedAnnotationDeleter(mark)
)
MarkedAnnotationDeleter(mark),
),
)
"Then only the B annotations should remain" {
@@ -120,13 +126,17 @@ class MarkedAnnotationDeleterTest : FreeSpec({
classA.fieldsAccept(
AllAttributeVisitor(
AllAnnotationVisitor(object : AnnotationVisitor {
override fun visitAnnotation(clazz: Clazz, annotation: Annotation) {
list.add(annotation.getType(clazz))
}
}
)
)
AllAnnotationVisitor(
object : AnnotationVisitor {
override fun visitAnnotation(
clazz: Clazz,
annotation: Annotation,
) {
list.add(annotation.getType(clazz))
}
},
),
),
)
list shouldContainExactly listOf("LAnnB1;", "LAnnB2;", "LAnnB3;")
@@ -135,16 +145,17 @@ class MarkedAnnotationDeleterTest : FreeSpec({
}
"Given a class with two annotations on its method parameter" - {
val (programClassPool, _) = ClassPoolBuilder.fromSource(
JavaSource("Ann1.java", "public @interface Ann1 {}"),
JavaSource("Ann2.java", "public @interface Ann2 {}"),
JavaSource(
"A.java",
"""class A {
val (programClassPool, _) =
ClassPoolBuilder.fromSource(
JavaSource("Ann1.java", "public @interface Ann1 {}"),
JavaSource("Ann2.java", "public @interface Ann2 {}"),
JavaSource(
"A.java",
"""class A {
public void a(@Ann1 @Ann2 int x) {}
}"""
}""",
),
)
)
val classA = programClassPool.getClass("A")
@@ -154,15 +165,15 @@ class MarkedAnnotationDeleterTest : FreeSpec({
classA.methodsAccept(
AllAttributeVisitor(
AllAnnotationVisitor(
ProcessingInfoSetter(mark)
)
)
ProcessingInfoSetter(mark),
),
),
)
classA.methodsAccept(
AllAttributeVisitor(
MarkedAnnotationDeleter(mark)
)
MarkedAnnotationDeleter(mark),
),
)
"Then no annotations should remain" {
@@ -170,13 +181,17 @@ class MarkedAnnotationDeleterTest : FreeSpec({
classA.methodsAccept(
AllAttributeVisitor(
AllAnnotationVisitor(object : AnnotationVisitor {
override fun visitAnnotation(clazz: Clazz?, annotation: Annotation?) {
count += 1
}
}
)
)
AllAnnotationVisitor(
object : AnnotationVisitor {
override fun visitAnnotation(
clazz: Clazz?,
annotation: Annotation?,
) {
count += 1
}
},
),
),
)
count shouldBe 0
@@ -185,19 +200,20 @@ class MarkedAnnotationDeleterTest : FreeSpec({
}
"Given a class with two A and B annotations on its method parameter" - {
val (programClassPool, _) = ClassPoolBuilder.fromSource(
JavaSource("AnnA1.java", "public @interface AnnA1 {}"),
JavaSource("AnnA2.java", "public @interface AnnA2 {}"),
JavaSource("AnnB1.java", "public @interface AnnB1 {}"),
JavaSource("AnnB2.java", "public @interface AnnB2 {}"),
JavaSource("AnnB3.java", "public @interface AnnB3 {}"),
JavaSource(
"A.java",
"""class A {
val (programClassPool, _) =
ClassPoolBuilder.fromSource(
JavaSource("AnnA1.java", "public @interface AnnA1 {}"),
JavaSource("AnnA2.java", "public @interface AnnA2 {}"),
JavaSource("AnnB1.java", "public @interface AnnB1 {}"),
JavaSource("AnnB2.java", "public @interface AnnB2 {}"),
JavaSource("AnnB3.java", "public @interface AnnB3 {}"),
JavaSource(
"A.java",
"""class A {
public void a(@AnnB1 @AnnA1 @AnnB2 @AnnA2 @AnnB3 int x) {}
}"""
}""",
),
)
)
val classA = programClassPool.getClass("A")
@@ -209,16 +225,16 @@ class MarkedAnnotationDeleterTest : FreeSpec({
AllAnnotationVisitor(
AnnotationTypeFilter(
"LAnnA*;",
ProcessingInfoSetter(mark)
)
)
)
ProcessingInfoSetter(mark),
),
),
),
)
classA.methodsAccept(
AllAttributeVisitor(
MarkedAnnotationDeleter(mark)
)
MarkedAnnotationDeleter(mark),
),
)
"Then only the B annotations should remain" {
@@ -226,13 +242,17 @@ class MarkedAnnotationDeleterTest : FreeSpec({
classA.methodsAccept(
AllAttributeVisitor(
AllAnnotationVisitor(object : AnnotationVisitor {
override fun visitAnnotation(clazz: Clazz, annotation: Annotation) {
list.add(annotation.getType(clazz))
}
}
)
)
AllAnnotationVisitor(
object : AnnotationVisitor {
override fun visitAnnotation(
clazz: Clazz,
annotation: Annotation,
) {
list.add(annotation.getType(clazz))
}
},
),
),
)
list shouldContainExactly listOf("LAnnB1;", "LAnnB2;", "LAnnB3;")

View File

@@ -22,14 +22,15 @@ import proguard.testutils.JavaSource
class TypeArgumentFinderTest : FreeSpec({
"Given an aload instruction with TypeToken" - {
val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource(
JavaSource(
"TypeToken.java",
"""
val (programClassPool, libraryClassPool) =
ClassPoolBuilder.fromSource(
JavaSource(
"TypeToken.java",
"""
package com.google.gson.reflect;
import java.lang.reflect.Type;
public class TypeToken<T> {
Type type;
@@ -37,11 +38,11 @@ class TypeArgumentFinderTest : FreeSpec({
return type;
}
}
""".trimIndent()
),
JavaSource(
"A.java",
"""
""".trimIndent(),
),
JavaSource(
"A.java",
"""
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
@@ -51,9 +52,9 @@ class TypeArgumentFinderTest : FreeSpec({
System.out.println(x);
}
}
""".trimIndent()
""".trimIndent(),
),
)
)
"When retrieving the return type with TypeArgumentFinder" - {
val partialEvaluator = PartialEvaluator()
@@ -63,9 +64,9 @@ class TypeArgumentFinderTest : FreeSpec({
AllMethodVisitor(
MemberNameFilter(
"a",
AllAttributeVisitor(partialEvaluator)
)
)
AllAttributeVisitor(partialEvaluator),
),
),
)
val typeArgumentFinder = TypeArgumentFinder(programClassPool, libraryClassPool, partialEvaluator)
@@ -83,14 +84,14 @@ class TypeArgumentFinderTest : FreeSpec({
Instruction.OP_ALOAD_0.toInt(),
Instruction.OP_ALOAD_1.toInt(),
Instruction.OP_ALOAD_2.toInt(),
Instruction.OP_ALOAD_3.toInt()
Instruction.OP_ALOAD_3.toInt(),
),
typeArgumentFinder
)
)
)
)
)
typeArgumentFinder,
),
),
),
),
),
)
"Then we obtain the return type java.lang.String" {
@@ -100,14 +101,15 @@ class TypeArgumentFinderTest : FreeSpec({
}
"Given an invokevirtual instruction with TypeToken" - {
val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource(
JavaSource(
"TypeToken.java",
"""
val (programClassPool, libraryClassPool) =
ClassPoolBuilder.fromSource(
JavaSource(
"TypeToken.java",
"""
package com.google.gson.reflect;
import java.lang.reflect.Type;
public class TypeToken<T> {
Type type;
@@ -115,11 +117,11 @@ class TypeArgumentFinderTest : FreeSpec({
return type;
}
}
""".trimIndent()
),
JavaSource(
"A.java",
"""
""".trimIndent(),
),
JavaSource(
"A.java",
"""
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
@@ -128,9 +130,9 @@ class TypeArgumentFinderTest : FreeSpec({
new TypeToken<String>() {}.getType();
}
}
""".trimIndent()
""".trimIndent(),
),
)
)
"When retrieving the return type with TypeArgumentFinder" - {
val typeArgumentFinder = TypeArgumentFinder(programClassPool, libraryClassPool, null)
@@ -144,12 +146,12 @@ class TypeArgumentFinderTest : FreeSpec({
AllInstructionVisitor(
InstructionOpCodeFilter(
intArrayOf(Instruction.OP_INVOKEVIRTUAL.toInt()),
typeArgumentFinder
)
)
)
)
)
typeArgumentFinder,
),
),
),
),
),
)
"Then we obtain null (Note: this case is not implemented like for aload instructions)" {
@@ -159,10 +161,11 @@ class TypeArgumentFinderTest : FreeSpec({
}
"Given an ldc instruction" - {
val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource(
JavaSource(
"Z.java",
"""
val (programClassPool, libraryClassPool) =
ClassPoolBuilder.fromSource(
JavaSource(
"Z.java",
"""
package a.b;
public class Z {
@@ -172,9 +175,9 @@ class TypeArgumentFinderTest : FreeSpec({
public void x(Class<?> c) {}
}
""".trimIndent()
""".trimIndent(),
),
)
)
"When retrieving the return type with TypeArgumentFinder" - {
val typeArgumentFinder = TypeArgumentFinder(programClassPool, libraryClassPool, null)
@@ -188,12 +191,12 @@ class TypeArgumentFinderTest : FreeSpec({
AllInstructionVisitor(
InstructionOpCodeFilter(
intArrayOf(Instruction.OP_LDC.toInt()),
typeArgumentFinder
)
)
)
)
)
typeArgumentFinder,
),
),
),
),
),
)
"Then we obtain the return type a.b.Z" {
@@ -203,19 +206,20 @@ class TypeArgumentFinderTest : FreeSpec({
}
"Given an aload instruction" - {
val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource(
JavaSource(
"A.java",
"""
val (programClassPool, libraryClassPool) =
ClassPoolBuilder.fromSource(
JavaSource(
"A.java",
"""
public class A {
public void a() {
String x = "text";
System.out.println(x);
}
}
""".trimIndent()
""".trimIndent(),
),
)
)
"When retrieving the return type with TypeArgumentFinder" - {
val partialEvaluator = PartialEvaluator()
@@ -225,9 +229,9 @@ class TypeArgumentFinderTest : FreeSpec({
AllMethodVisitor(
MemberNameFilter(
"a",
AllAttributeVisitor(partialEvaluator)
)
)
AllAttributeVisitor(partialEvaluator),
),
),
)
val typeArgumentFinder = TypeArgumentFinder(programClassPool, libraryClassPool, partialEvaluator)
@@ -245,14 +249,14 @@ class TypeArgumentFinderTest : FreeSpec({
Instruction.OP_ALOAD_0.toInt(),
Instruction.OP_ALOAD_1.toInt(),
Instruction.OP_ALOAD_2.toInt(),
Instruction.OP_ALOAD_3.toInt()
Instruction.OP_ALOAD_3.toInt(),
),
typeArgumentFinder
)
)
)
)
)
typeArgumentFinder,
),
),
),
),
),
)
// This is not an intended use case of the TypeArgumentFinder
@@ -264,10 +268,11 @@ class TypeArgumentFinderTest : FreeSpec({
}
"Given a new instruction" - {
val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource(
JavaSource(
"Z.java",
"""
val (programClassPool, libraryClassPool) =
ClassPoolBuilder.fromSource(
JavaSource(
"Z.java",
"""
package a.b;
public class Z {
@@ -275,9 +280,9 @@ class TypeArgumentFinderTest : FreeSpec({
new Z();
}
}
""".trimIndent()
""".trimIndent(),
),
)
)
"When retrieving the return type with TypeArgumentFinder" - {
val typeArgumentFinder = TypeArgumentFinder(programClassPool, libraryClassPool, null)
@@ -291,12 +296,12 @@ class TypeArgumentFinderTest : FreeSpec({
AllInstructionVisitor(
InstructionOpCodeFilter(
intArrayOf(Instruction.OP_NEW.toInt()),
typeArgumentFinder
)
)
)
)
)
typeArgumentFinder,
),
),
),
),
),
)
"Then we obtain the return type a.b.Z" {

View File

@@ -13,19 +13,20 @@ import proguard.testutils.JavaSource
class InstantiationClassMarkerTest : FreeSpec({
"A class should be marked as instantiated" - {
"when it is instantiated with a `new` instruction" {
val (classPool, _) = ClassPoolBuilder.fromSource(
JavaSource(
"Main.java",
"""
class A { }
public class Main {
public static void main(String[] args) {
System.out.println(new A());
}
}
""".trimIndent()
val (classPool, _) =
ClassPoolBuilder.fromSource(
JavaSource(
"Main.java",
"""
class A { }
public class Main {
public static void main(String[] args) {
System.out.println(new A());
}
}
""".trimIndent(),
),
)
)
val classA = classPool.getClass("A")
@@ -36,30 +37,31 @@ class InstantiationClassMarkerTest : FreeSpec({
AllMemberVisitor(
AllAttributeVisitor(
AllInstructionVisitor(
InstantiationClassMarker()
)
)
)
InstantiationClassMarker(),
),
),
),
)
getProgramClassOptimizationInfo(classA).isInstantiated shouldBe true
}
"when one of its subclasses is instantiated with a `new` instruction" {
val (classPool, _) = ClassPoolBuilder.fromSource(
JavaSource(
"Main.java",
"""
class A { }
class B extends A { }
public class Main {
public static void main(String[] args) {
System.out.println(new B());
}
}
""".trimIndent()
val (classPool, _) =
ClassPoolBuilder.fromSource(
JavaSource(
"Main.java",
"""
class A { }
class B extends A { }
public class Main {
public static void main(String[] args) {
System.out.println(new B());
}
}
""".trimIndent(),
),
)
)
val classA = classPool.getClass("A")
val classB = classPool.getClass("B")
@@ -71,10 +73,10 @@ class InstantiationClassMarkerTest : FreeSpec({
AllMemberVisitor(
AllAttributeVisitor(
AllInstructionVisitor(
InstantiationClassMarker()
)
)
)
InstantiationClassMarker(),
),
),
),
)
getProgramClassOptimizationInfo(classA).isInstantiated shouldBe true

View File

@@ -23,10 +23,11 @@ class MethodInlinerJava9Test : FreeSpec({
isolationMode = IsolationMode.InstancePerTest
"Given a method calling a private method in the same interface" - {
val (programClassPool, _) = ClassPoolBuilder.fromSource(
JavaSource(
"Foo.java",
"""interface Foo {
val (programClassPool, _) =
ClassPoolBuilder.fromSource(
JavaSource(
"Foo.java",
"""interface Foo {
default void f1() {
f2();
}
@@ -37,8 +38,8 @@ class MethodInlinerJava9Test : FreeSpec({
System.out.println(sb.toString());
}
}""",
),
)
)
val clazz = programClassPool.getClass("Foo") as ProgramClass
val method = clazz.findMethod("f1", "()V") as ProgramMethod
@@ -47,27 +48,33 @@ class MethodInlinerJava9Test : FreeSpec({
val lengthBefore = codeAttr.u4codeLength
// Initialize optimization info (used when inlining).
val optimizationInfoInitializer: ClassVisitor = MultiClassVisitor(
ProgramClassOptimizationInfoSetter(),
AllMethodVisitor(
ProgramMemberOptimizationInfoSetter()
val optimizationInfoInitializer: ClassVisitor =
MultiClassVisitor(
ProgramClassOptimizationInfoSetter(),
AllMethodVisitor(
ProgramMemberOptimizationInfoSetter(),
),
)
)
programClassPool.classesAccept(optimizationInfoInitializer)
// Create a mock method inliner which always returns true.
val methodInliner = object : MethodInliner(false, true, true) {
override fun shouldInline(clazz: Clazz?, method: Method?, codeAttribute: CodeAttribute?): Boolean = true
}
val methodInliner =
object : MethodInliner(false, true, true) {
override fun shouldInline(
clazz: Clazz?,
method: Method?,
codeAttribute: CodeAttribute?,
): Boolean = true
}
"Then the interface method is inlined" {
programClassPool.classesAccept(
AllMethodVisitor(
AllAttributeVisitor(
methodInliner
)
)
methodInliner,
),
),
)
val lengthAfter = codeAttr.u4codeLength

View File

@@ -33,10 +33,11 @@ class MethodInlinerTest : FreeSpec({
isolationMode = IsolationMode.InstancePerTest
"Given two simple functions, one calling the other" - {
val (programClassPool, _) = ClassPoolBuilder.fromSource(
JavaSource(
"Foo.java",
"""class Foo {
val (programClassPool, _) =
ClassPoolBuilder.fromSource(
JavaSource(
"Foo.java",
"""class Foo {
static int f1() {
return f2() + 1;
}
@@ -44,9 +45,9 @@ class MethodInlinerTest : FreeSpec({
static int f2() {
return 1;
}
}"""
}""",
),
)
)
// Sanity check how the instructions look before.
val instructionsBefore = printProgramMethodInstructions(programClassPool, "Foo", "f1", "()I")
@@ -59,26 +60,32 @@ class MethodInlinerTest : FreeSpec({
"When calling the method inliner, specifying that we should always inline" - {
// Initialize optimization info (used when inlining).
val optimizationInfoInitializer: ClassVisitor = MultiClassVisitor(
ProgramClassOptimizationInfoSetter(),
AllMethodVisitor(
ProgramMemberOptimizationInfoSetter()
val optimizationInfoInitializer: ClassVisitor =
MultiClassVisitor(
ProgramClassOptimizationInfoSetter(),
AllMethodVisitor(
ProgramMemberOptimizationInfoSetter(),
),
)
)
programClassPool.classesAccept(optimizationInfoInitializer)
// Create a mock method inliner which always returns true.
val methodInliner = object : MethodInliner(false, true, true) {
override fun shouldInline(clazz: Clazz, method: Method?, codeAttribute: CodeAttribute?): Boolean = true
}
val methodInliner =
object : MethodInliner(false, true, true) {
override fun shouldInline(
clazz: Clazz,
method: Method?,
codeAttribute: CodeAttribute?,
): Boolean = true
}
programClassPool.classesAccept(
AllMethodVisitor(
AllAttributeVisitor(
methodInliner
)
)
methodInliner,
),
),
)
"Then the called function is inlined as expected" {
@@ -94,26 +101,32 @@ class MethodInlinerTest : FreeSpec({
"When calling the method inliner, specifying that we should never inline" - {
// Initialize optimization info (used when inlining).
val optimizationInfoInitializer: ClassVisitor = MultiClassVisitor(
ProgramClassOptimizationInfoSetter(),
AllMethodVisitor(
ProgramMemberOptimizationInfoSetter()
val optimizationInfoInitializer: ClassVisitor =
MultiClassVisitor(
ProgramClassOptimizationInfoSetter(),
AllMethodVisitor(
ProgramMemberOptimizationInfoSetter(),
),
)
)
programClassPool.classesAccept(optimizationInfoInitializer)
// Create a mock method inliner which always returns true.
val methodInliner = object : MethodInliner(false, true, true) {
override fun shouldInline(clazz: Clazz?, method: Method?, codeAttribute: CodeAttribute?): Boolean = false
}
val methodInliner =
object : MethodInliner(false, true, true) {
override fun shouldInline(
clazz: Clazz?,
method: Method?,
codeAttribute: CodeAttribute?,
): Boolean = false
}
programClassPool.classesAccept(
AllMethodVisitor(
AllAttributeVisitor(
methodInliner
)
)
methodInliner,
),
),
)
"Then the called function is not inlined" {
@@ -131,22 +144,23 @@ class MethodInlinerTest : FreeSpec({
"Given a function calling a big function" - {
val lotsOfPrints = (1..3000).joinToString("\n") { "System.out.println(\"${it}\");" }
val (programClassPool, _) = ClassPoolBuilder.fromSource(
JavaSource(
"Foo.java",
"""class Foo {
val (programClassPool, _) =
ClassPoolBuilder.fromSource(
JavaSource(
"Foo.java",
"""class Foo {
static void f1() {
f2();
}
static void f2() {
""" +
lotsOfPrints +
"""
lotsOfPrints +
"""
}
}"""
}""",
),
)
)
val clazz = programClassPool.getClass("Foo") as ProgramClass
val method = clazz.findMethod("f1", "()V") as ProgramMethod
@@ -156,27 +170,33 @@ class MethodInlinerTest : FreeSpec({
"When using the default maximum resulting code length parameter" - {
// Initialize optimization info (used when inlining).
val optimizationInfoInitializer: ClassVisitor = MultiClassVisitor(
ProgramClassOptimizationInfoSetter(),
AllMethodVisitor(
ProgramMemberOptimizationInfoSetter()
val optimizationInfoInitializer: ClassVisitor =
MultiClassVisitor(
ProgramClassOptimizationInfoSetter(),
AllMethodVisitor(
ProgramMemberOptimizationInfoSetter(),
),
)
)
programClassPool.classesAccept(optimizationInfoInitializer)
// Create a mock method inliner which always returns true.
val methodInliner = object : MethodInliner(false, true, true) {
override fun shouldInline(clazz: Clazz?, method: Method?, codeAttribute: CodeAttribute?): Boolean = true
}
val methodInliner =
object : MethodInliner(false, true, true) {
override fun shouldInline(
clazz: Clazz?,
method: Method?,
codeAttribute: CodeAttribute?,
): Boolean = true
}
"Then the large method is not inlined" {
programClassPool.classesAccept(
AllMethodVisitor(
AllAttributeVisitor(
methodInliner
)
)
methodInliner,
),
),
)
val lengthAfter = codeAttr.u4codeLength
@@ -187,26 +207,32 @@ class MethodInlinerTest : FreeSpec({
"When using the maximum resulting code length parameter" - {
// Initialize optimization info (used when inlining).
val optimizationInfoInitializer: ClassVisitor = MultiClassVisitor(
ProgramClassOptimizationInfoSetter(),
AllMethodVisitor(
ProgramMemberOptimizationInfoSetter()
val optimizationInfoInitializer: ClassVisitor =
MultiClassVisitor(
ProgramClassOptimizationInfoSetter(),
AllMethodVisitor(
ProgramMemberOptimizationInfoSetter(),
),
)
)
programClassPool.classesAccept(optimizationInfoInitializer)
// Create a mock method inliner with the maximum limit
val methodInliner = object : MethodInliner(false, true, MAXIMUM_RESULTING_CODE_LENGTH_JVM, true, true, null) {
override fun shouldInline(clazz: Clazz?, method: Method?, codeAttribute: CodeAttribute?): Boolean = true
}
val methodInliner =
object : MethodInliner(false, true, MAXIMUM_RESULTING_CODE_LENGTH_JVM, true, true, null) {
override fun shouldInline(
clazz: Clazz?,
method: Method?,
codeAttribute: CodeAttribute?,
): Boolean = true
}
programClassPool.classesAccept(
AllMethodVisitor(
AllAttributeVisitor(
methodInliner
)
)
methodInliner,
),
),
)
"Then the large method is inlined" {
@@ -218,10 +244,11 @@ class MethodInlinerTest : FreeSpec({
}
"Given a method initializing a library class and calling a method with backwards branching " - {
val (programClassPool, _) = ClassPoolBuilder.fromSource(
JavaSource(
"Foo.java",
"""class Foo {
val (programClassPool, _) =
ClassPoolBuilder.fromSource(
JavaSource(
"Foo.java",
"""class Foo {
static void f1() {
StringBuilder sb = new StringBuilder();
sb.append(System.currentTimeMillis());
@@ -235,9 +262,9 @@ class MethodInlinerTest : FreeSpec({
System.out.println(i);
}
}
}"""
}""",
),
)
)
val clazz = programClassPool.getClass("Foo") as ProgramClass
val method = clazz.findMethod("f1", "()V") as ProgramMethod
@@ -248,32 +275,38 @@ class MethodInlinerTest : FreeSpec({
"When inlining the method call" - {
// Initialize optimization info (used when inlining).
// Make sure the backwards branching info is set correctly.
val optimizationInfoInitializer: ClassVisitor = MultiClassVisitor(
ProgramClassOptimizationInfoSetter(),
AllMethodVisitor(
MultiMemberVisitor(
ProgramMemberOptimizationInfoSetter(),
AllAttributeVisitor(
AllInstructionVisitor(
BackwardBranchMarker()
)
)
)
val optimizationInfoInitializer: ClassVisitor =
MultiClassVisitor(
ProgramClassOptimizationInfoSetter(),
AllMethodVisitor(
MultiMemberVisitor(
ProgramMemberOptimizationInfoSetter(),
AllAttributeVisitor(
AllInstructionVisitor(
BackwardBranchMarker(),
),
),
),
),
)
)
programClassPool.classesAccept(optimizationInfoInitializer)
// Create a mock method inliner which always returns true.
val methodInliner = object : MethodInliner(false, true, true) {
override fun shouldInline(clazz: Clazz?, method: Method?, codeAttribute: CodeAttribute?): Boolean = true
}
val methodInliner =
object : MethodInliner(false, true, true) {
override fun shouldInline(
clazz: Clazz?,
method: Method?,
codeAttribute: CodeAttribute?,
): Boolean = true
}
"Then the method is inlined" {
programClassPool.classesAccept(
AllMethodVisitor(
AllAttributeVisitor(methodInliner)
)
AllAttributeVisitor(methodInliner),
),
)
val lengthAfter = codeAttr.u4codeLength
@@ -284,10 +317,11 @@ class MethodInlinerTest : FreeSpec({
}
"Given a method calling another non-private method in an interface" - {
val (programClassPool, _) = ClassPoolBuilder.fromSource(
JavaSource(
"Foo.java",
"""interface Foo {
val (programClassPool, _) =
ClassPoolBuilder.fromSource(
JavaSource(
"Foo.java",
"""interface Foo {
default void f1() {
f2();
}
@@ -297,9 +331,9 @@ class MethodInlinerTest : FreeSpec({
sb.append(System.currentTimeMillis());
System.out.println(sb.toString());
}
}"""
}""",
),
)
)
val clazz = programClassPool.getClass("Foo") as ProgramClass
val method = clazz.findMethod("f1", "()V") as ProgramMethod
@@ -308,27 +342,33 @@ class MethodInlinerTest : FreeSpec({
val lengthBefore = codeAttr.u4codeLength
// Initialize optimization info (used when inlining).
val optimizationInfoInitializer: ClassVisitor = MultiClassVisitor(
ProgramClassOptimizationInfoSetter(),
AllMethodVisitor(
ProgramMemberOptimizationInfoSetter()
val optimizationInfoInitializer: ClassVisitor =
MultiClassVisitor(
ProgramClassOptimizationInfoSetter(),
AllMethodVisitor(
ProgramMemberOptimizationInfoSetter(),
),
)
)
programClassPool.classesAccept(optimizationInfoInitializer)
// Create a mock method inliner which always returns true.
val methodInliner = object : MethodInliner(false, true, true) {
override fun shouldInline(clazz: Clazz?, method: Method?, codeAttribute: CodeAttribute?): Boolean = true
}
val methodInliner =
object : MethodInliner(false, true, true) {
override fun shouldInline(
clazz: Clazz?,
method: Method?,
codeAttribute: CodeAttribute?,
): Boolean = true
}
"Then the interface method is not inlined" {
programClassPool.classesAccept(
AllMethodVisitor(
AllAttributeVisitor(
methodInliner
)
)
methodInliner,
),
),
)
val lengthAfter = codeAttr.u4codeLength
@@ -342,7 +382,7 @@ private fun printProgramMethodInstructions(
classPool: ClassPool,
className: String,
methodName: String,
methodDescriptor: String
methodDescriptor: String,
): List<String> {
val output = ByteArrayOutputStream()
val pw = PrintWriter(output)
@@ -352,10 +392,13 @@ private fun printProgramMethodInstructions(
methodName,
methodDescriptor,
object : MemberVisitor {
override fun visitProgramMethod(programClass: ProgramClass?, programMethod: ProgramMethod?) {
override fun visitProgramMethod(
programClass: ProgramClass?,
programMethod: ProgramMethod?,
) {
programMethod?.accept(programClass, AllAttributeVisitor(AllInstructionVisitor(ClassPrinter(pw))))
}
}
},
)
}

View File

@@ -29,45 +29,47 @@ import proguard.util.ProcessingFlagSetter
import proguard.util.ProcessingFlags.DONT_SHRINK
import proguard.util.kotlin.asserter.KotlinMetadataVerifier
private fun beMarkedWith(simpleUsageMarker: SimpleUsageMarker) = object : Matcher<Processable> {
override fun test(value: Processable) =
MatcherResult(
simpleUsageMarker.isUsed(value),
{ "$value should be marked" },
{ "$value should not be used" }
)
}
private fun beMarkedWith(simpleUsageMarker: SimpleUsageMarker) =
object : Matcher<Processable> {
override fun test(value: Processable) =
MatcherResult(
simpleUsageMarker.isUsed(value),
{ "$value should be marked" },
{ "$value should not be used" },
)
}
class ClassUsageMarkerTest : StringSpec({
"Class Usage Marking should mark methods invoked in the method body" {
val (classPool, _) = ClassPoolBuilder.fromSource(
JavaSource(
"A.java",
"""
public class A {
public void method1() {
this.method2();
}
public void method2() {
this.method2();
}
public void method3() {
B.method4();
}
}
""".trimIndent()
),
JavaSource(
"B.java",
"""
public class B {
public static void method4() {
new A().method2();
}
}
""".trimIndent()
val (classPool, _) =
ClassPoolBuilder.fromSource(
JavaSource(
"A.java",
"""
public class A {
public void method1() {
this.method2();
}
public void method2() {
this.method2();
}
public void method3() {
B.method4();
}
}
""".trimIndent(),
),
JavaSource(
"B.java",
"""
public class B {
public static void method4() {
new A().method2();
}
}
""".trimIndent(),
),
)
)
val classA = classPool.getClass("A")
val method1 = classA.findMethod("method1", null)
@@ -104,29 +106,30 @@ class ClassUsageMarkerTest : StringSpec({
}
"The comparable interface should induce additional marking" {
val (classPool, _) = ClassPoolBuilder.fromSource(
JavaSource(
"Application.java",
"""
public class Application {
Other attribute;
public Application() {
attribute = new Other();
}
}
""".trimIndent()
),
JavaSource(
"Other.java",
"""
public class Other implements Comparable<Other> {
public void foo() {}
public void bar() {}
public int compareTo(Other o) { foo(); return 0; }
}
""".trimIndent()
val (classPool, _) =
ClassPoolBuilder.fromSource(
JavaSource(
"Application.java",
"""
public class Application {
Other attribute;
public Application() {
attribute = new Other();
}
}
""".trimIndent(),
),
JavaSource(
"Other.java",
"""
public class Other implements Comparable<Other> {
public void foo() {}
public void bar() {}
public int compareTo(Other o) { foo(); return 0; }
}
""".trimIndent(),
),
)
)
val classUsageMarker = ClassUsageMarker()
val applicationClazz = classPool.getClass("Application")
@@ -146,24 +149,25 @@ class ClassUsageMarkerTest : StringSpec({
}
"Using an enum as default value in an annotation field should mark the enum value as used" {
val (programClassPool, _) = ClassPoolBuilder.fromSource(
JavaSource(
"A.java",
"""
public @interface A {
B b() default B.Y;
}
""".trimIndent()
),
JavaSource(
"B.java",
"""
public enum B {
X, Y, Z
}
""".trimIndent()
val (programClassPool, _) =
ClassPoolBuilder.fromSource(
JavaSource(
"A.java",
"""
public @interface A {
B b() default B.Y;
}
""".trimIndent(),
),
JavaSource(
"B.java",
"""
public enum B {
X, Y, Z
}
""".trimIndent(),
),
)
)
// Make sure the field references in enum fields are updated.
programClassPool.classesAccept(
@@ -171,17 +175,17 @@ class ClassUsageMarkerTest : StringSpec({
true,
AllElementValueVisitor(
true,
EnumFieldReferenceInitializer()
)
)
EnumFieldReferenceInitializer(),
),
),
)
// Visit attributes to mark usage processing info.
programClassPool.getClass("A").accept(
AllAttributeVisitor(
true,
ClassUsageMarker()
)
ClassUsageMarker(),
),
)
val bClass = programClassPool.getClass("B")
@@ -195,19 +199,20 @@ class ClassUsageMarkerTest : StringSpec({
}
"Given a Kotlin interface with default method implementation in compatibility mode" {
val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource(
KotlinSource(
"Test.kt",
"""
interface Test {
fun foo() {
TODO()
}
}
""".trimIndent()
),
kotlincArguments = listOf("-Xjvm-default=all")
)
val (programClassPool, libraryClassPool) =
ClassPoolBuilder.fromSource(
KotlinSource(
"Test.kt",
"""
interface Test {
fun foo() {
TODO()
}
}
""".trimIndent(),
),
kotlincArguments = listOf("-Xjvm-default=all"),
)
// Run the asserter to ensure any metadata that isn't initialized correctly is thrown away
KotlinMetadataVerifier(Configuration()).execute(AppView(programClassPool, libraryClassPool))
@@ -219,23 +224,27 @@ class ClassUsageMarkerTest : StringSpec({
}
}
"Given a Kotlin interface with default method implementation" {
val (programClassPool, _) = ClassPoolBuilder.fromSource(
KotlinSource(
"Interface.kt",
"""
package test;
interface Interface {
fun foo() : Int {
return 42;
}
}
""".trimIndent(),
),
)
val (programClassPool, _) =
ClassPoolBuilder.fromSource(
KotlinSource(
"Interface.kt",
"""
package test;
interface Interface {
fun foo() : Int {
return 42;
}
}
""".trimIndent(),
),
)
// Necessary to force marking methods that are not actually used and have not been processed by the Marker.
class CustomMarker(var marker: SimpleUsageMarker) : MemberVisitor {
override fun visitAnyMember(clazz: Clazz, member: Member) {
override fun visitAnyMember(
clazz: Clazz,
member: Member,
) {
marker.markAsUsed(member)
}
}
@@ -247,7 +256,9 @@ class ClassUsageMarkerTest : StringSpec({
programClassPool.classesAccept(classUsageMarker)
// Mark the default implementation as used.
programClassPool.accept(NamedClassVisitor(NamedMethodVisitor("foo", null, CustomMarker(usageMarker)), "test/Interface\$DefaultImpls"))
programClassPool.accept(
NamedClassVisitor(NamedMethodVisitor("foo", null, CustomMarker(usageMarker)), "test/Interface\$DefaultImpls"),
)
// Process Kotlin metadata: this should cause the interface method to be kept as well.
programClassPool.classesAccept(ReferencedKotlinMetadataVisitor(classUsageMarker))

View File

@@ -17,10 +17,11 @@ import proguard.testutils.ClassPoolBuilder
class ProguardAssemblerTest : FreeSpec({
"Given Java bytecode" - {
val (programClassPool, _) = ClassPoolBuilder.fromSource(
AssemblerSource(
"A.jbc",
"""
val (programClassPool, _) =
ClassPoolBuilder.fromSource(
AssemblerSource(
"A.jbc",
"""
version 1.8;
public class A extends java.lang.Object [
SourceFile "A.java";
@@ -43,9 +44,9 @@ class ProguardAssemblerTest : FreeSpec({
}
}
"""
""",
),
)
)
"When the ClassPool object is created" - {
programClassPool.shouldNotBeNull()
"Then the count and name of the methods should match the bytecode" {

View File

@@ -30,9 +30,11 @@ val currentJavaVersion: Int by lazy {
return@lazy version.toInt()
}
fun isJava9OrLater(): Boolean =
SourceVersion.latestSupported() > SourceVersion.RELEASE_8
fun isJava9OrLater(): Boolean = SourceVersion.latestSupported() > SourceVersion.RELEASE_8
fun getCurrentJavaHome(): File =
if (isJava9OrLater()) File(System.getProperty("java.home"))
else File(System.getProperty("java.home")).parentFile
if (isJava9OrLater()) {
File(System.getProperty("java.home"))
} else {
File(System.getProperty("java.home")).parentFile
}

View File

@@ -16,23 +16,26 @@ import proguard.util.ProcessingFlags.DONT_OPTIMIZE
import proguard.util.ProcessingFlags.DONT_PROCESS_KOTLIN_MODULE
import proguard.util.ProcessingFlags.DONT_SHRINK
fun hasFlag(flag: Int) = object : Matcher<Int> {
override fun test(value: Int): MatcherResult =
MatcherResult(
(value and flag) != 0,
{ "Flag ${flag.asProcessingFlagString} should be set" },
{ "Flag ${flag.asProcessingFlagString} should not be set" }
)
}
fun hasFlag(flag: Int) =
object : Matcher<Int> {
override fun test(value: Int): MatcherResult =
MatcherResult(
(value and flag) != 0,
{ "Flag ${flag.asProcessingFlagString} should be set" },
{ "Flag ${flag.asProcessingFlagString} should not be set" },
)
}
infix fun Int.shouldHaveFlag(flag: Int) = this should hasFlag(flag)
infix fun Int.shouldNotHaveFlag(flag: Int) = this shouldNot hasFlag(flag)
val Int.asProcessingFlagString: String
get() = when (this) {
DONT_OBFUSCATE -> "DONT_OBFUSCATE"
DONT_SHRINK -> "DONT_SHRINK"
DONT_OPTIMIZE -> "DONT_OPTIMIZE"
DONT_PROCESS_KOTLIN_MODULE -> "DONT_PROCESS_KOTLIN_MODULE"
else -> this.toString()
}
get() =
when (this) {
DONT_OBFUSCATE -> "DONT_OBFUSCATE"
DONT_SHRINK -> "DONT_SHRINK"
DONT_OPTIMIZE -> "DONT_OPTIMIZE"
DONT_PROCESS_KOTLIN_MODULE -> "DONT_PROCESS_KOTLIN_MODULE"
else -> this.toString()
}

View File

@@ -24,10 +24,15 @@ object TestConfig : AbstractProjectConfig() {
}
class RequiresJavaVersionAnnotationFilter : SpecFilter {
override fun filter(kclass: KClass<*>): SpecFilterResult = if (with(kclass.findAnnotation<RequiresJavaVersion>()) {
(this == null || (currentJavaVersion >= this.from && currentJavaVersion <= this.to))
}
) Include else Exclude("Required Java version is not in range.")
override fun filter(kclass: KClass<*>): SpecFilterResult =
if (with(kclass.findAnnotation<RequiresJavaVersion>()) {
(this == null || (currentJavaVersion >= this.from && currentJavaVersion <= this.to))
}
) {
Include
} else {
Exclude("Required Java version is not in range.")
}
}
@Target(AnnotationTarget.CLASS)

View File

@@ -2,6 +2,7 @@ plugins {
id 'distribution'
id 'io.github.gradle-nexus.publish-plugin'
id 'signing'
id "org.jetbrains.kotlin.jvm" version "$kotlinVersion" apply false
}
allprojects {

View File

@@ -16,11 +16,8 @@ ProGuard is currently hosted on GitHub:
[source code](https://github.com/Guardsquare/proguard) yourself and create
[pull requests](https://github.com/Guardsquare/proguard/pulls).
!!! tip
The [***Guardsquare Community***](https://community.guardsquare.com/) is the place to be for all your ProGuard-related questions and feedback.
You may also find answers on
[Stack Overflow](http://stackoverflow.com/questions/tagged/proguard).
- You may also find answers on
[Stack Overflow](http://stackoverflow.com/questions/tagged/proguard).
ProGuard used to be hosted on Sourceforge:

View File

@@ -1,4 +1,4 @@
Welcome to the manual for **ProGuard** version 7.5 ([what's new?](releasenotes.md)).
Welcome to the manual for **ProGuard** version 7.7 ([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,40 @@
## Version 7.7
### Java support
- Add support for Java 24. (#458)
### Bugfixes
- Prevent `IllegalArgumentException` when strings longer than 65535 bytes are present in the application (#267).
- Prevent `StackOverflowException` when processing a pattern match switch (#444).
### Improved
- Improve processing time in apps where a large number of linked methods are present.
## Version 7.6.1
### Bugfixes
- Fix backporting default interface method parameter annotations. (#451)
- Prevent `Value in slot <n> is empty` exception during processing time by no longer applying lower slot replacement by default. It can be enabled again with the `optimization.enable.slot.replacement` system property.
## Version 7.6
### Java support
- Add support for Java 23. (#387)
### Improved
- Improve obfuscation dictionary name performance with large dictionaries. (#413)
### Bugfixes
- Prevent unknown enum value for `KmVersionRequirementVersionKind` exception when processing code compiled with an outdated Kotlin version.
- ReTrace: Fix separation of multiple frames with a newline. (#432)
## Version 7.5.0
### Kotlin support

View File

@@ -9,7 +9,7 @@ have been replaced by short meaningless strings. Source file names and
line numbers are missing altogether. While this may be intentional, it
can also be inconvenient when debugging problems.
<img src="PG_ReTrace.png" alt="ReTrace deobfuscation workflow" style="display: block; margin-left: auto; margin-right: auto;" />
<img src="./PG_ReTrace.png" alt="ReTrace deobfuscation workflow" style="display: block; margin-left: auto; margin-right: auto;" />
ReTrace can read an obfuscated stack trace and restore it to what it
would look like without obfuscation. The restoration is based on the
@@ -18,9 +18,10 @@ file links the original class names and class member names to their
obfuscated names.
## Usage {: #usage }
[The Retrace jar is published individually in Maven](https://mvnrepository.com/artifact/com.guardsquare/proguard-retrace/latest), you can also find it in the `lib` directory of the [ProGuard
distribution](https://github.com/Guardsquare/proguard/releases/latest).
You can find the ReTrace jar in the `lib` directory of the ProGuard
distribution. To run ReTrace, just type:
To run ReTrace, just type:
`java -jar retrace.jar `\[*options...*\] *mapping\_file*
\[*stacktrace\_file*\]

View File

@@ -2,9 +2,6 @@ While preparing a configuration for processing your code, you may bump
into a few problems. The following sections discuss some common issues
and solutions:
!!! tip
Whenever you don't find the solution for your issue on this page, the [***Guardsquare Community***](https://community.guardsquare.com/) might be the place to check for an answer or post a question.
## Problems while processing {: #processing}

View File

@@ -7,7 +7,7 @@ buildscript {
google()
}
dependencies {
classpath 'com.guardsquare:proguard-gradle:7.5.0'
classpath 'com.guardsquare:proguard-gradle:7.7.0'
}
}
@@ -40,7 +40,7 @@ compileTestKotlin {
}
application {
mainClassName = 'AppKt'
mainClass = 'AppKt'
}
ext.baseCoordinates = "${project.name}-${project.version}"

View File

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

View File

Binary file not shown.

View File

@@ -1,5 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
@@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -80,13 +82,12 @@ do
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -133,22 +134,29 @@ location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@@ -193,11 +201,15 @@ if "$cygwin" || "$msys" ; then
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
@@ -205,6 +217,12 @@ set -- \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.

View File

@@ -13,8 +13,10 @@
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%" == "" @echo off
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@@ -25,7 +27,8 @@
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@@ -40,13 +43,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
@@ -56,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
@@ -75,13 +78,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal

View File

@@ -5,7 +5,7 @@ buildscript {
google()
}
dependencies {
classpath("com.guardsquare:proguard-gradle:7.5.0")
classpath("com.guardsquare:proguard-gradle:7.7.0")
}
}
@@ -23,13 +23,13 @@ dependencies {
}
application {
mainClassName = "gradlekotlindsl.App"
mainClass = "gradlekotlindsl.App"
}
tasks.withType<Jar> {
manifest {
attributes["Main-Class"] = application.mainClassName
attributes["Main-Class"] = application.mainClass
}
}

View File

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

View File

@@ -3,9 +3,9 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
plugins {
id 'com.github.johnrengelman.shadow'
id 'java-gradle-plugin'
id 'org.jetbrains.kotlin.jvm' version "$kotlinVersion"
id 'org.jetbrains.kotlin.jvm'
id 'maven-publish'
id 'org.jlleitschuh.gradle.ktlint' version '9.2.1'
id 'org.jlleitschuh.gradle.ktlint' version '12.1.2'
}
repositories {
@@ -43,22 +43,22 @@ dependencies {
exclude module: 'proguard-gradle'
exclude module: 'proguard-base'
}
implementation 'com.github.zafarkhaja:java-semver:0.9.0'
implementation 'com.github.zafarkhaja:java-semver:0.10.2'
testCompileOnly ("com.android.tools.build:gradle:$agpVersion") {
exclude module: 'proguard-gradle'
exclude module: 'proguard-base'
}
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.9.0' // for kotest framework
testImplementation 'io.kotest:kotest-assertions-core-jvm:5.9.0' // for kotest core jvm assertions
testImplementation 'io.kotest:kotest-property-jvm:5.9.0' // for kotest property test
testImplementation 'io.mockk:mockk:1.13.11' // for mocking
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.9.1' // for kotest framework
testImplementation 'io.kotest:kotest-assertions-core-jvm:5.9.1' // for kotest core jvm assertions
testImplementation 'io.kotest:kotest-property-jvm:5.9.1' // for kotest property test
testImplementation 'io.mockk:mockk:1.13.13' // for mocking
testImplementation "commons-io:commons-io:2.8.0"
testImplementation "commons-io:commons-io:2.18.0"
fatJar project(":base")
// This library is used to parse the android gradle plugin version at runtime.
fatJar 'com.github.zafarkhaja:java-semver:0.9.0'
fatJar 'com.github.zafarkhaja:java-semver:0.10.2'
}
test {

View File

@@ -29,39 +29,42 @@ import proguard.gradle.plugin.android.AndroidPlugin
import proguard.gradle.plugin.android.agpVersion
class ProGuardPlugin : Plugin<Project> {
override fun apply(project: Project) {
val androidExtension: BaseExtension? = project.extensions.findByName("android") as BaseExtension?
val javaExtension = project.extensions.findByName("java")
val javaErrMessage = """For Java projects, you can manually declare a ProGuardTask instead of applying the plugin:
val javaErrMessage =
"""For Java projects, you can manually declare a ProGuardTask instead of applying the plugin:
|
| task myProguardTask(type: proguard.gradle.ProGuardTask) {
| // ...
| }""".trimMargin()
| }
""".trimMargin()
when {
androidExtension != null -> {
if (agpVersion.majorVersion < 4) {
throw GradleException(
"""The ProGuard plugin only supports Android plugin 4 and higher.
"""The ProGuard plugin only supports Android plugin 4 and higher.
|For Android plugin version 3 and lower, you can use ProGuard through the Android plugin integration.
|Please refer to the manual for further details: https://www.guardsquare.com/manual/setup/gradleplugin
""".trimMargin())
""".trimMargin(),
)
}
AndroidPlugin(androidExtension).apply(project)
}
javaExtension != null -> {
throw GradleException(
"""The ProGuard plugin requires the Android plugin to function properly.
"""The ProGuard plugin requires the Android plugin to function properly.
|$javaErrMessage
""".trimMargin())
""".trimMargin(),
)
}
else -> {
throw GradleException(
"""For Android applications or libraries 'com.android.application' or 'com.android.library' is required, respectively.
|$javaErrMessage
""".trimMargin())
""".trimMargin(),
)
}
}
}

View File

@@ -29,7 +29,6 @@ import com.android.build.gradle.api.BaseVariant
import com.android.build.gradle.internal.res.LinkApplicationAndroidResourcesTask
import com.android.build.gradle.internal.tasks.factory.dependsOn
import com.github.zafarkhaja.semver.Version
import java.io.File
import org.gradle.api.GradleException
import org.gradle.api.Plugin
import org.gradle.api.Project
@@ -48,45 +47,50 @@ import proguard.gradle.plugin.android.tasks.ConsumerRuleFilterEntry
import proguard.gradle.plugin.android.tasks.PrepareProguardConfigDirectoryTask
import proguard.gradle.plugin.android.transforms.AndroidConsumerRulesTransform
import proguard.gradle.plugin.android.transforms.ArchiveConsumerRulesTransform
import java.io.File
class AndroidPlugin(private val androidExtension: BaseExtension) : Plugin<Project> {
override fun apply(project: Project) {
val collectConsumerRulesTask = project.tasks.register(COLLECT_CONSUMER_RULES_TASK_NAME)
registerDependencyTransforms(project)
val proguardBlock = project.extensions.create<ProGuardAndroidExtension>("proguard", ProGuardAndroidExtension::class.java, project)
val projectType = when (androidExtension) {
is AppExtension -> ANDROID_APPLICATION
is LibraryExtension -> ANDROID_LIBRARY
else -> throw GradleException("The ProGuard Gradle plugin can only be used on Android application and library projects")
}
val projectType =
when (androidExtension) {
is AppExtension -> ANDROID_APPLICATION
is LibraryExtension -> ANDROID_LIBRARY
else -> throw GradleException("The ProGuard Gradle plugin can only be used on Android application and library projects")
}
configureAapt(project)
warnOldProguardVersion(project)
androidExtension.registerTransform(
ProGuardTransform(project, proguardBlock, projectType, androidExtension),
collectConsumerRulesTask)
ProGuardTransform(project, proguardBlock, projectType, androidExtension),
collectConsumerRulesTask,
)
project.afterEvaluate {
if (proguardBlock.configurations.isEmpty())
if (proguardBlock.configurations.isEmpty()) {
throw GradleException("There are no configured variants in the 'proguard' block")
}
val matchedConfigurations = mutableListOf<VariantConfiguration>()
when (androidExtension) {
is AppExtension -> androidExtension.applicationVariants.all { applicationVariant ->
setupVariant(proguardBlock, applicationVariant, collectConsumerRulesTask, project)?.let {
matchedConfigurations.add(it)
is AppExtension ->
androidExtension.applicationVariants.all { applicationVariant ->
setupVariant(proguardBlock, applicationVariant, collectConsumerRulesTask, project)?.let {
matchedConfigurations.add(it)
}
}
}
is LibraryExtension -> androidExtension.libraryVariants.all { libraryVariant ->
setupVariant(proguardBlock, libraryVariant, collectConsumerRulesTask, project)?.let {
matchedConfigurations.add(it)
is LibraryExtension ->
androidExtension.libraryVariants.all { libraryVariant ->
setupVariant(proguardBlock, libraryVariant, collectConsumerRulesTask, project)?.let {
matchedConfigurations.add(it)
}
}
}
}
proguardBlock.configurations.forEach {
@@ -94,9 +98,17 @@ class AndroidPlugin(private val androidExtension: BaseExtension) : Plugin<Projec
}
(proguardBlock.configurations - matchedConfigurations).apply {
if (isNotEmpty()) when (size) {
1 -> throw GradleException("The configured variant '${first().name}' does not exist")
else -> throw GradleException("The configured variants ${joinToString(separator = "', '", prefix = "'", postfix = "'") { it.name }} do not exist")
if (isNotEmpty()) {
when (size) {
1 -> throw GradleException("The configured variant '${first().name}' does not exist")
else -> throw GradleException(
"The configured variants ${joinToString(
separator = "', '",
prefix = "'",
postfix = "'",
) { it.name }} do not exist",
)
}
}
}
}
@@ -108,9 +120,11 @@ class AndroidPlugin(private val androidExtension: BaseExtension) : Plugin<Projec
it.dependsOn(createDirectoryTask)
}
if (!androidExtension.aaptAdditionalParameters.contains("--proguard")) {
androidExtension.aaptAdditionalParameters.addAll(listOf(
androidExtension.aaptAdditionalParameters.addAll(
listOf(
"--proguard",
project.buildDir.resolve("intermediates/proguard/configs/aapt_rules.pro").absolutePath)
project.buildDir.resolve("intermediates/proguard/configs/aapt_rules.pro").absolutePath,
),
)
}
@@ -119,18 +133,26 @@ class AndroidPlugin(private val androidExtension: BaseExtension) : Plugin<Projec
}
}
private fun setupVariant(proguardBlock: ProGuardAndroidExtension, variant: BaseVariant, collectConsumerRulesTask: TaskProvider<Task>, project: Project): VariantConfiguration? {
private fun setupVariant(
proguardBlock: ProGuardAndroidExtension,
variant: BaseVariant,
collectConsumerRulesTask: TaskProvider<Task>,
project: Project,
): VariantConfiguration? {
val matchingConfiguration = proguardBlock.configurations.findVariantConfiguration(variant.name)
if (matchingConfiguration != null) {
verifyNotMinified(variant)
disableAaptOutputCaching(project, variant)
collectConsumerRulesTask.dependsOn(createCollectConsumerRulesTask(
collectConsumerRulesTask.dependsOn(
createCollectConsumerRulesTask(
project,
variant,
createConsumerRulesConfiguration(project, variant),
matchingConfiguration.consumerRuleFilter,
project.buildDir.resolve("intermediates/proguard/configs")))
project.buildDir.resolve("intermediates/proguard/configs"),
),
)
}
return matchingConfiguration
}
@@ -140,9 +162,8 @@ class AndroidPlugin(private val androidExtension: BaseExtension) : Plugin<Projec
variant: BaseVariant,
inputConfiguration: Configuration,
consumerRuleFilter: MutableList<String>,
outputDir: File
outputDir: File,
): TaskProvider<CollectConsumerRulesTask> {
fun parseConsumerRuleFilter(consumerRuleFilter: List<String>) =
consumerRuleFilter.map { filter ->
val splits = filter.split(':')
@@ -160,7 +181,10 @@ class AndroidPlugin(private val androidExtension: BaseExtension) : Plugin<Projec
}
}
private fun createConsumerRulesConfiguration(project: Project, variant: BaseVariant): Configuration =
private fun createConsumerRulesConfiguration(
project: Project,
variant: BaseVariant,
): Configuration =
project.configurations.create("${variant.name}ProGuardConsumerRulesArtifacts") {
it.isCanBeResolved = true
it.isCanBeConsumed = false
@@ -172,7 +196,10 @@ class AndroidPlugin(private val androidExtension: BaseExtension) : Plugin<Projec
it.attributes.attribute(ATTRIBUTE_ARTIFACT_TYPE, ARTIFACT_TYPE_CONSUMER_RULES)
}
private fun checkConfigurationFile(project: Project, files: List<ProGuardConfiguration>) {
private fun checkConfigurationFile(
project: Project,
files: List<ProGuardConfiguration>,
) {
files.filterIsInstance<UserProGuardConfiguration>().forEach {
val file = project.file(it.path)
if (!file.exists()) throw GradleException("ProGuard configuration file ${file.absolutePath} was set but does not exist.")
@@ -182,11 +209,16 @@ class AndroidPlugin(private val androidExtension: BaseExtension) : Plugin<Projec
private fun verifyNotMinified(variant: BaseVariant) {
if (variant.buildType.isMinifyEnabled) {
throw GradleException(
"The option 'minifyEnabled' is set to 'true' for variant '${variant.name}', but should be 'false' for variants processed by ProGuard")
"The option 'minifyEnabled' is set to 'true' for variant '${variant.name}', but should be 'false' " +
"for variants processed by ProGuard",
)
}
}
private fun copyConfigurationAttributes(destConfiguration: Configuration, srcConfiguration: Configuration) {
private fun copyConfigurationAttributes(
destConfiguration: Configuration,
srcConfiguration: Configuration,
) {
srcConfiguration.attributes.keySet().forEach { attribute ->
val attributeValue = srcConfiguration.attributes.getAttribute(attribute)
destConfiguration.attributes.attribute(attribute as Attribute<Any>, attributeValue)
@@ -209,8 +241,12 @@ class AndroidPlugin(private val androidExtension: BaseExtension) : Plugin<Projec
}
// TODO: improve loading AAPT rules so that we don't rely on this
private fun disableAaptOutputCaching(project: Project, variant: BaseVariant) {
val cachingEnabled = project.hasProperty("org.gradle.caching") &&
private fun disableAaptOutputCaching(
project: Project,
variant: BaseVariant,
) {
val cachingEnabled =
project.hasProperty("org.gradle.caching") &&
(project.findProperty("org.gradle.caching") as String).toBoolean()
if (cachingEnabled) {
@@ -271,7 +307,7 @@ buildscript {
enum class AndroidProjectType {
ANDROID_APPLICATION,
ANDROID_LIBRARY;
ANDROID_LIBRARY,
}
fun Iterable<VariantConfiguration>.findVariantConfiguration(variant: VariantInfo) =
@@ -280,8 +316,7 @@ fun Iterable<VariantConfiguration>.findVariantConfiguration(variant: VariantInfo
fun Iterable<VariantConfiguration>.findVariantConfiguration(variantName: String) =
find { it.name == variantName } ?: find { variantName.endsWith(it.name.capitalize()) }
fun Iterable<VariantConfiguration>.hasVariantConfiguration(variantName: String) =
this.findVariantConfiguration(variantName) != null
fun Iterable<VariantConfiguration>.hasVariantConfiguration(variantName: String) = this.findVariantConfiguration(variantName) != null
val agpVersion: Version = Version.valueOf(com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION)

View File

@@ -40,7 +40,6 @@ import com.android.build.api.transform.TransformInvocation
import com.android.build.api.transform.TransformOutputProvider
import com.android.build.api.variant.VariantInfo
import com.android.build.gradle.BaseExtension
import java.io.File
import org.gradle.api.Project
import proguard.gradle.ProGuardTask
import proguard.gradle.plugin.android.AndroidPlugin.Companion.COLLECT_CONSUMER_RULES_TASK_NAME
@@ -48,17 +47,18 @@ import proguard.gradle.plugin.android.AndroidProjectType.ANDROID_APPLICATION
import proguard.gradle.plugin.android.AndroidProjectType.ANDROID_LIBRARY
import proguard.gradle.plugin.android.dsl.ProGuardAndroidExtension
import proguard.gradle.plugin.android.dsl.UserProGuardConfiguration
import java.io.File
class ProGuardTransform(
private val project: Project,
private val proguardBlock: ProGuardAndroidExtension,
private val projectType: AndroidProjectType,
private val androidExtension: BaseExtension
private val androidExtension: BaseExtension,
) : Transform() {
override fun transform(transformInvocation: TransformInvocation) {
val variantName: String = transformInvocation.context.variantName
val variantBlock = proguardBlock.configurations.findVariantConfiguration(variantName)
val variantBlock =
proguardBlock.configurations.findVariantConfiguration(variantName)
?: throw RuntimeException("Invalid configuration: $variantName")
val proguardTask = project.tasks.create("proguardTask${variantName.capitalize()}", ProGuardTask::class.java)
@@ -67,9 +67,11 @@ class ProGuardTransform(
proguardTask.outjars(it.second)
}
proguardTask.extraJar(transformInvocation
proguardTask.extraJar(
transformInvocation
.outputProvider
.getContentLocation("extra.jar", setOf(CLASSES, RESOURCES), mutableSetOf(PROJECT), JAR))
.getContentLocation("extra.jar", setOf(CLASSES, RESOURCES), mutableSetOf(PROJECT), JAR),
)
proguardTask.libraryjars(createLibraryJars(transformInvocation.referencedInputs))
@@ -80,7 +82,10 @@ class ProGuardTransform(
if (aaptRulesFile != null && File(aaptRulesFile).exists()) {
proguardTask.configuration(aaptRulesFile)
} else {
project.logger.warn("AAPT rules file not found: you may need to apply some extra keep rules for classes referenced from resources in your own ProGuard configuration.")
project.logger.warn(
"AAPT rules file not found: you may need to apply some extra keep rules for classes referenced from " +
"resources in your own ProGuard configuration.",
)
}
val mappingDir = project.buildDir.resolve("outputs/proguard/$variantName/mapping")
@@ -98,10 +103,10 @@ class ProGuardTransform(
override fun getInputTypes(): Set<DefaultContentType> = setOf(CLASSES, RESOURCES)
override fun getScopes(): MutableSet<in Scope> =
when (projectType) {
ANDROID_APPLICATION -> mutableSetOf(PROJECT, SUB_PROJECTS, EXTERNAL_LIBRARIES)
ANDROID_LIBRARY -> mutableSetOf(PROJECT)
}
when (projectType) {
ANDROID_APPLICATION -> mutableSetOf(PROJECT, SUB_PROJECTS, EXTERNAL_LIBRARIES)
ANDROID_LIBRARY -> mutableSetOf(PROJECT)
}
override fun getReferencedScopes(): MutableSet<in Scope> =
when (projectType) {
@@ -112,10 +117,10 @@ class ProGuardTransform(
override fun isIncremental(): Boolean = false
override fun applyToVariant(variant: VariantInfo?): Boolean =
variant?.let { proguardBlock.configurations.findVariantConfiguration(it) } != null
variant?.let { proguardBlock.configurations.findVariantConfiguration(it) } != null
override fun getSecondaryFiles(): MutableCollection<SecondaryFile> =
proguardBlock
proguardBlock
.configurations
.flatMap { it.configurations }
.filterIsInstance<UserProGuardConfiguration>()
@@ -126,13 +131,16 @@ class ProGuardTransform(
private fun createIOEntries(
inputs: Collection<TransformInput>,
outputProvider: TransformOutputProvider
outputProvider: TransformOutputProvider,
): List<ProGuardIOEntry> {
fun createEntry(input: QualifiedContent, format: Format): ProGuardIOEntry {
fun createEntry(
input: QualifiedContent,
format: Format,
): ProGuardIOEntry {
return ProGuardIOEntry(
input.file,
outputProvider.getContentLocation(input.name, input.contentTypes, input.scopes, format).canonicalFile)
input.file,
outputProvider.getContentLocation(input.name, input.contentTypes, input.scopes, format).canonicalFile,
)
}
return inputs.flatMap { input ->
@@ -143,13 +151,14 @@ class ProGuardTransform(
private fun createLibraryJars(inputs: Collection<TransformInput>): List<File> =
inputs.flatMap { input -> input.directoryInputs.map { it.file } + input.jarInputs.map { it.file } } +
listOf(androidExtension.sdkDirectory.resolve("platforms/${androidExtension.compileSdkVersion}/android.jar")) +
listOf(androidExtension.sdkDirectory.resolve("platforms/${androidExtension.compileSdkVersion}/android.jar")) +
androidExtension.libraryRequests.map {
androidExtension.sdkDirectory.resolve("platforms/${androidExtension.compileSdkVersion}/optional/${it.name}.jar")
}
androidExtension.libraryRequests.map {
androidExtension.sdkDirectory.resolve("platforms/${androidExtension.compileSdkVersion}/optional/${it.name}.jar")
}
private fun getAaptRulesFile() = androidExtension.aaptAdditionalParameters
private fun getAaptRulesFile() =
androidExtension.aaptAdditionalParameters
.zipWithNext { cmd, param -> if (cmd == "--proguard") param else null }
.filterNotNull()
.firstOrNull { File(it).exists() }

View File

@@ -25,13 +25,13 @@ import proguard.gradle.ProGuardTask.DEFAULT_CONFIG_RESOURCE_PREFIX
sealed class ProGuardConfiguration(val filename: String) {
open val path: String = filename
override fun toString(): String = filename
}
class UserProGuardConfiguration(filename: String) : ProGuardConfiguration(filename)
class DefaultProGuardConfiguration private constructor(filename: String) : ProGuardConfiguration(filename) {
override val path: String
get() = "$DEFAULT_CONFIG_RESOURCE_PREFIX/$filename"
@@ -45,12 +45,14 @@ class DefaultProGuardConfiguration private constructor(filename: String) : ProGu
ANDROID_DEBUG.filename -> ANDROID_DEBUG
ANDROID_RELEASE.filename -> ANDROID_RELEASE
ANDROID_RELEASE_OPTIMIZE.filename -> ANDROID_RELEASE_OPTIMIZE
else -> throw IllegalArgumentException("""
The default ProGuard configuration '$filename' is invalid.
else -> throw IllegalArgumentException(
"""
The default ProGuard configuration '$filename' is invalid.
Choose from:
$ANDROID_DEBUG, $ANDROID_RELEASE, $ANDROID_RELEASE_OPTIMIZE
""".trimIndent())
Choose from:
$ANDROID_DEBUG, $ANDROID_RELEASE, $ANDROID_RELEASE_OPTIMIZE
""".trimIndent(),
)
}
}
}

Some files were not shown because too many files have changed in this diff Show More