mirror of
https://github.com/Guardsquare/proguard.git
synced 2026-03-13 09:50:34 +08:00
Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef6a8352bd | ||
|
|
e225e56a8d | ||
|
|
4288cce536 | ||
|
|
3456cf330e | ||
|
|
fbcf41fd67 | ||
|
|
bacde1cede | ||
|
|
430a04502d | ||
|
|
08adfa5552 | ||
|
|
89b1e55ea2 | ||
|
|
f4c4a13a90 | ||
|
|
c1eafc7b6b | ||
|
|
73860de626 | ||
|
|
ff66baaced | ||
|
|
174d3f4155 | ||
|
|
f5352fece7 | ||
|
|
844f3d76be | ||
|
|
7b6712e840 | ||
|
|
dd4b8bde06 | ||
|
|
b3deed8286 | ||
|
|
8903bfb23f | ||
|
|
03d7effdd2 | ||
|
|
c2146ae315 | ||
|
|
4e643b4f60 | ||
|
|
ee3deb69fa | ||
|
|
6075d17bee | ||
|
|
3a9b11bb3c |
@@ -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
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
{
|
||||
|
||||
@@ -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)));
|
||||
|
||||
21
base/src/main/java/proguard/normalize/StringNormalizer.java
Normal file
21
base/src/main/java/proguard/normalize/StringNormalizer.java
Normal 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)));
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)))));
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -114,7 +114,7 @@ implements AttributeVisitor,
|
||||
*/
|
||||
public EvaluationShrinker()
|
||||
{
|
||||
this(new PartialEvaluator(), true, false, null, null);
|
||||
this(PartialEvaluator.Builder.create().build(), true, false, null, null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -71,7 +71,7 @@ implements ClassVisitor,
|
||||
*/
|
||||
public SimpleEnumUseChecker()
|
||||
{
|
||||
this(new PartialEvaluator(new TypedReferenceValueFactory()));
|
||||
this(PartialEvaluator.Builder.create().setValueFactory(new TypedReferenceValueFactory()).build());
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ implements AttributeVisitor,
|
||||
*/
|
||||
public SimpleEnumUseSimplifier()
|
||||
{
|
||||
this(new PartialEvaluator(new TypedReferenceValueFactory()), null);
|
||||
this(PartialEvaluator.Builder.create().setValueFactory(new TypedReferenceValueFactory()).build(), null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()))
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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 **"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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" {
|
||||
|
||||
|
||||
@@ -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(),
|
||||
)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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" },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;"
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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;")
|
||||
|
||||
@@ -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" {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))))
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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" {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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*\]
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -7,7 +7,7 @@ buildscript {
|
||||
google()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.guardsquare:proguard-gradle:7.5.0'
|
||||
classpath 'com.guardsquare:proguard-gradle:7.7.0'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
@@ -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
|
||||
|
||||
44
examples/application/gradlew
vendored
44
examples/application/gradlew
vendored
@@ -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.
|
||||
|
||||
37
examples/application/gradlew.bat
vendored
37
examples/application/gradlew.bat
vendored
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ buildscript {
|
||||
google()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.guardsquare:proguard-gradle:7.5.0'
|
||||
classpath 'com.guardsquare:proguard-gradle:7.7.0'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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() }
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user