mirror of
https://github.com/Guardsquare/proguard.git
synced 2026-03-13 09:50:34 +08:00
Compare commits
64 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
869ce156b1 | ||
|
|
124b33e473 | ||
|
|
b8a62c8ca8 | ||
|
|
886477806c | ||
|
|
7fc907d1fb | ||
|
|
b10346ba32 | ||
|
|
f2ced20be4 | ||
|
|
35ea6d587f | ||
|
|
eea0ccbe8f | ||
|
|
bee74a9963 | ||
|
|
4b4aa93335 | ||
|
|
4781f5898f | ||
|
|
1f9a4a1b94 | ||
|
|
40f9222bc3 | ||
|
|
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 | ||
|
|
af475c65b4 | ||
|
|
8d7ddf898c | ||
|
|
f5f2f06334 | ||
|
|
aa43b9dc21 | ||
|
|
0d9ceb7451 | ||
|
|
1d28c11e36 | ||
|
|
b85b2cb201 | ||
|
|
0c95982828 | ||
|
|
38de2e42b2 | ||
|
|
76b2921738 | ||
|
|
7483ad32f4 | ||
|
|
20c99aa3e8 | ||
|
|
858bcd0eb5 | ||
|
|
1c421bf780 | ||
|
|
d4692c3835 | ||
|
|
712fd768ca | ||
|
|
c35913c3f2 | ||
|
|
5a8d50090a | ||
|
|
06c2d12f7a | ||
|
|
a7265a3536 | ||
|
|
7160a9e484 | ||
|
|
12c9c3f23e | ||
|
|
a02100cb93 | ||
|
|
38a0e498b9 |
22
.github/workflows/continuous_integration.yml
vendored
22
.github/workflows/continuous_integration.yml
vendored
@@ -15,19 +15,21 @@ jobs:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: proguard-main
|
||||
- uses: actions/setup-java@v1
|
||||
fetch-depth: 0
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: 8
|
||||
- uses: eskatos/gradle-command-action@v1
|
||||
with:
|
||||
build-root-directory: proguard-main/
|
||||
wrapper-directory: proguard-main/
|
||||
arguments: test :base:testAllJavaVersions :base:jacocoTestReport jar --info
|
||||
- name: Setup gradle
|
||||
uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # version 4.4.2
|
||||
- name: Test
|
||||
run: ./gradlew test :base:testAllJavaVersions :base:jacocoTestReport jar --info
|
||||
- name: Publish Test Report
|
||||
uses: mikepenz/action-junit-report@v3
|
||||
if: always() # always run even if the previous step fails
|
||||
uses: mikepenz/action-junit-report@3585e9575db828022551b4231f165eb59a0e74e3 # version 5.6.2
|
||||
if: success() || failure() # always run even if the previous step fails
|
||||
with:
|
||||
report_paths: '**/build/test-results/test/TEST-*.xml'
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -6,3 +6,5 @@ build
|
||||
local.properties
|
||||
/lib/
|
||||
docs/html
|
||||
.kotlin
|
||||
.DS_Store
|
||||
|
||||
@@ -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.4.1'
|
||||
classpath 'com.guardsquare:proguard-gradle:7.8.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,11 +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 'de.jansauer.printcoverage' version '2.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 {
|
||||
@@ -22,19 +21,19 @@ 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.json:json:20220924'
|
||||
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.2.1'
|
||||
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.5.4' // for kotest framework
|
||||
testImplementation 'io.kotest:kotest-assertions-core-jvm:5.5.4' // for kotest core jvm assertions
|
||||
testImplementation 'io.kotest:kotest-property-jvm:5.5.4' // for kotest property test
|
||||
testImplementation 'io.mockk:mockk:1.13.2' // 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:9.0.8")) {
|
||||
testImplementation(testFixtures("com.guardsquare:proguard-core:${proguardCoreVersion}")) {
|
||||
exclude group: 'com.guardsquare', module: 'proguard-core'
|
||||
}
|
||||
}
|
||||
@@ -50,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..21
|
||||
def javaVersionsForTest = 9..23
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
@@ -64,8 +63,8 @@ task testAllJavaVersions() { testAllTask ->
|
||||
useJUnitPlatform()
|
||||
ignoreFailures = true
|
||||
|
||||
// The version of bytebuddy used by mockk only supports Java 20 experimentally so far
|
||||
if (version >= 20) systemProperty 'net.bytebuddy.experimental', true
|
||||
// The version of bytebuddy used by mockk only supports Java 22 experimentally so far
|
||||
// if (version >= 22) systemProperty 'net.bytebuddy.experimental', true
|
||||
|
||||
testAllTask.dependsOn(it)
|
||||
|
||||
@@ -86,9 +85,11 @@ jacocoTestReport {
|
||||
classDirectories.setFrom(classes)
|
||||
executionData.setFrom project.fileTree(dir: '.', include: '**/build/jacoco/*.exec')
|
||||
reports {
|
||||
xml.enabled true
|
||||
csv.enabled false
|
||||
html.destination file("${buildDir}/reports/coverage")
|
||||
xml.required = true
|
||||
csv.required = false
|
||||
}
|
||||
javaVersionsForTest.each { version ->
|
||||
mustRunAfter "testJava$version"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -118,6 +118,8 @@ public class ConfigurationConstants
|
||||
|
||||
public static final String ALWAYS_INLINE = "-alwaysinline";
|
||||
public static final String IDENTIFIER_NAME_STRING = "-identifiernamestring";
|
||||
public static final String MAXIMUM_REMOVED_ANDROID_LOG_LEVEL = "-maximumremovedandroidloglevel";
|
||||
|
||||
|
||||
public static final String ANY_FILE_KEYWORD = "**";
|
||||
|
||||
|
||||
@@ -20,14 +20,25 @@
|
||||
*/
|
||||
package proguard;
|
||||
|
||||
import proguard.classfile.*;
|
||||
import proguard.classfile.AccessConstants;
|
||||
import proguard.classfile.ClassConstants;
|
||||
import proguard.classfile.JavaAccessConstants;
|
||||
import proguard.classfile.JavaTypeConstants;
|
||||
import proguard.classfile.TypeConstants;
|
||||
import proguard.classfile.util.ClassUtil;
|
||||
import proguard.util.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.util.*;
|
||||
import proguard.util.ListUtil;
|
||||
import proguard.util.StringUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.LineNumberReader;
|
||||
import java.io.StringReader;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
/**
|
||||
* This class parses ProGuard configurations. Configurations can be read from an
|
||||
@@ -38,6 +49,8 @@ import java.util.*;
|
||||
*/
|
||||
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;
|
||||
|
||||
@@ -139,9 +152,24 @@ public class ConfigurationParser implements AutoCloseable
|
||||
* @throws IOException if an IO error occurs while reading a configuration.
|
||||
*/
|
||||
public void parse(Configuration configuration)
|
||||
throws ParseException, IOException {
|
||||
parse(configuration, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses and returns the configuration.
|
||||
*
|
||||
* @param configuration the configuration that is updated as a side-effect.
|
||||
* @param unknownOptionHandler optional handler for unknown options; if null then a {@link ParseException}
|
||||
* is thrown when encountering an unknown option.
|
||||
* @throws ParseException if the any of the configuration settings contains
|
||||
* a syntax error.
|
||||
* @throws IOException if an IO error occurs while reading a configuration.
|
||||
*/
|
||||
public void parse(Configuration configuration, BiConsumer<String, String> unknownOptionHandler)
|
||||
throws ParseException, IOException
|
||||
{
|
||||
while (nextWord != null)
|
||||
parseWord: while (nextWord != null)
|
||||
{
|
||||
lastComments = reader.lastComments();
|
||||
|
||||
@@ -232,9 +260,18 @@ public class ConfigurationParser implements AutoCloseable
|
||||
else if (ConfigurationConstants.OPTIMIZE_AGGRESSIVELY .startsWith(nextWord)) configuration.optimizeConservatively = parseNoArgument(false);
|
||||
else if (ConfigurationConstants.ALWAYS_INLINE .startsWith(nextWord)) parseUnsupportedR8Rules(ConfigurationConstants.ALWAYS_INLINE, true);
|
||||
else if (ConfigurationConstants.IDENTIFIER_NAME_STRING .startsWith(nextWord)) parseUnsupportedR8Rules(ConfigurationConstants.IDENTIFIER_NAME_STRING, true);
|
||||
else if (ConfigurationConstants.MAXIMUM_REMOVED_ANDROID_LOG_LEVEL .equals(nextWord)) parseMaximumRemovedAndroidLogLevel();
|
||||
else
|
||||
{
|
||||
throw new ParseException("Unknown option " + reader.locationDescription());
|
||||
if (unknownOptionHandler != null) {
|
||||
unknownOptionHandler.accept(nextWord, reader.lineLocationDescription());
|
||||
while (nextWord != null) {
|
||||
readNextWord();
|
||||
if (nextWord != null && nextWord.startsWith("-")) {
|
||||
continue parseWord;
|
||||
}
|
||||
}
|
||||
} else throw new ParseException("Unknown option " + reader.locationDescription());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1918,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());
|
||||
@@ -1931,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.
|
||||
@@ -1965,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
|
||||
@@ -2051,9 +2129,22 @@ public class ConfigurationParser implements AutoCloseable
|
||||
parseClassSpecificationArguments();
|
||||
}
|
||||
|
||||
System.out.println("Warning: The R8 option " + option + " is currently not supported by ProGuard.\n" +
|
||||
"This option will have no effect on the optimized artifact.");
|
||||
|
||||
warnUnsupportedR8Option(option);
|
||||
}
|
||||
|
||||
private void parseMaximumRemovedAndroidLogLevel() throws IOException, ParseException {
|
||||
parseIntegerArgument();
|
||||
if (!configurationEnd(true)) {
|
||||
parseClassSpecificationArguments();
|
||||
}
|
||||
|
||||
warnUnsupportedR8Option(ConfigurationConstants.MAXIMUM_REMOVED_ANDROID_LOG_LEVEL);
|
||||
}
|
||||
|
||||
private static void warnUnsupportedR8Option(String option) {
|
||||
System.out.println("Warning: The R8 option " + option + " is currently not supported by ProGuard.\n" +
|
||||
"This option will have no effect on the optimized artifact.");
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -314,6 +314,12 @@ public class Backporter implements Pass
|
||||
appView.programClassPool.classesAccept(new ClassVersionSetter(targetClassVersion));
|
||||
}
|
||||
|
||||
// Backporting may introduce access issues, for example related to nest members/host.
|
||||
if (configuration.allowAccessModification)
|
||||
{
|
||||
appView.programClassPool.classesAccept(new AccessFixer());
|
||||
}
|
||||
|
||||
logger.info(" Number of converted string concatenations: {}", replacedStringConcatCounter.getCount());
|
||||
logger.info(" Number of converted lambda expressions: {}", lambdaExpressionCounter.getCount());
|
||||
logger.info(" Number of converted static interface methods: {}", staticInterfaceMethodCounter.getCount());
|
||||
|
||||
@@ -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))
|
||||
{
|
||||
|
||||
@@ -72,7 +72,7 @@ implements KotlinMetadataVisitor,
|
||||
kotlinClassKindMetadata.inlineClassUnderlyingPropertyTypeAccept(clazz, this);
|
||||
|
||||
kotlinClassKindMetadata.referencedClass.attributesAccept(annotationCounter.reset());
|
||||
kotlinClassKindMetadata.flags.common.hasAnnotations = annotationCounter.getCount() > 0;
|
||||
kotlinClassKindMetadata.flags.hasAnnotations = annotationCounter.getCount() > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -119,28 +119,28 @@ implements KotlinMetadataVisitor,
|
||||
annotationCounter.reset()
|
||||
);
|
||||
|
||||
kotlinPropertyMetadata.flags.common.hasAnnotations = annotationCounter.getCount() > 0;
|
||||
kotlinPropertyMetadata.flags.hasAnnotations = annotationCounter.getCount() > 0;
|
||||
}
|
||||
else if (kotlinPropertyMetadata.referencedBackingField != null)
|
||||
{
|
||||
kotlinPropertyMetadata.referencedBackingField.accept(kotlinPropertyMetadata.referencedBackingFieldClass, annotationCounter);
|
||||
kotlinPropertyMetadata.flags.common.hasAnnotations = annotationCounter.getCount() > 0;
|
||||
kotlinPropertyMetadata.flags.hasAnnotations = annotationCounter.getCount() > 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
kotlinPropertyMetadata.flags.common.hasAnnotations = false;
|
||||
kotlinPropertyMetadata.flags.hasAnnotations = false;
|
||||
}
|
||||
|
||||
if (kotlinPropertyMetadata.flags.hasGetter && kotlinPropertyMetadata.referencedGetterMethod != null)
|
||||
if (kotlinPropertyMetadata.referencedGetterMethod != null)
|
||||
{
|
||||
kotlinPropertyMetadata.referencedGetterMethod.accept(clazz, annotationCounter.reset());
|
||||
kotlinPropertyMetadata.getterFlags.common.hasAnnotations = annotationCounter.getCount() > 0;
|
||||
kotlinPropertyMetadata.getterFlags.hasAnnotations = annotationCounter.getCount() > 0;
|
||||
}
|
||||
|
||||
if (kotlinPropertyMetadata.flags.hasSetter && kotlinPropertyMetadata.referencedSetterMethod != null)
|
||||
if (kotlinPropertyMetadata.flags.isVar && kotlinPropertyMetadata.referencedSetterMethod != null)
|
||||
{
|
||||
kotlinPropertyMetadata.referencedSetterMethod.accept(clazz, annotationCounter.reset());
|
||||
kotlinPropertyMetadata.setterFlags.common.hasAnnotations = annotationCounter.getCount() > 0;
|
||||
kotlinPropertyMetadata.setterFlags.hasAnnotations = annotationCounter.getCount() > 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,7 +157,7 @@ implements KotlinMetadataVisitor,
|
||||
kotlinFunctionMetadata.returnTypeAccept( clazz, kotlinMetadata, this);
|
||||
|
||||
kotlinFunctionMetadata.referencedMethodAccept(annotationCounter.reset());
|
||||
kotlinFunctionMetadata.flags.common.hasAnnotations = annotationCounter.getCount() != 0;
|
||||
kotlinFunctionMetadata.flags.hasAnnotations = annotationCounter.getCount() != 0;
|
||||
}
|
||||
|
||||
// Implementations for KotlinConstructorVisitor.
|
||||
@@ -172,12 +172,12 @@ implements KotlinMetadataVisitor,
|
||||
if (kotlinClassKindMetadata.flags.isAnnotationClass)
|
||||
{
|
||||
//PROBBUG where are the annotations?
|
||||
kotlinConstructorMetadata.flags.common.hasAnnotations = false;
|
||||
kotlinConstructorMetadata.flags.hasAnnotations = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
kotlinConstructorMetadata.referencedMethodAccept(clazz, annotationCounter.reset());
|
||||
kotlinConstructorMetadata.flags.common.hasAnnotations = annotationCounter.getCount() != 0;
|
||||
kotlinConstructorMetadata.flags.hasAnnotations = annotationCounter.getCount() != 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,7 +192,7 @@ implements KotlinMetadataVisitor,
|
||||
kotlinTypeAliasMetadata.expandedTypeAccept( clazz, kotlinDeclarationContainerMetadata, this);
|
||||
kotlinTypeAliasMetadata.versionRequirementAccept(clazz, kotlinDeclarationContainerMetadata, this);
|
||||
|
||||
kotlinTypeAliasMetadata.flags.common.hasAnnotations = !kotlinTypeAliasMetadata.annotations.isEmpty();
|
||||
kotlinTypeAliasMetadata.flags.hasAnnotations = !kotlinTypeAliasMetadata.annotations.isEmpty();
|
||||
}
|
||||
|
||||
// Implementations for KotlinTypeVisitor.
|
||||
@@ -202,8 +202,6 @@ implements KotlinMetadataVisitor,
|
||||
kotlinTypeMetadata.typeArgumentsAccept(clazz, this);
|
||||
kotlinTypeMetadata.upperBoundsAccept( clazz, this);
|
||||
kotlinTypeMetadata.abbreviationAccept( clazz, this);
|
||||
|
||||
kotlinTypeMetadata.flags.common.hasAnnotations = !kotlinTypeMetadata.annotations.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -213,7 +211,6 @@ implements KotlinMetadataVisitor,
|
||||
KotlinTypeMetadata kotlinTypeMetadata)
|
||||
{
|
||||
kotlinFunctionMetadata.referencedMethodAccept(this.annotationCounter.reset());
|
||||
kotlinTypeMetadata.flags.common.hasAnnotations = annotationCounter.getParameterAnnotationCount(0) > 0;
|
||||
}
|
||||
|
||||
// Implementations for KotlinTypeParameterVisitor.
|
||||
@@ -225,8 +222,6 @@ implements KotlinMetadataVisitor,
|
||||
public void visitAnyTypeParameter(Clazz clazz, KotlinTypeParameterMetadata kotlinTypeParameterMetadata)
|
||||
{
|
||||
kotlinTypeParameterMetadata.upperBoundsAccept(clazz, this);
|
||||
|
||||
kotlinTypeParameterMetadata.flags.common.hasAnnotations = !kotlinTypeParameterMetadata.annotations.isEmpty();
|
||||
}
|
||||
|
||||
// Implementations for KotlinValueParameterVisitor.
|
||||
@@ -242,10 +237,10 @@ implements KotlinMetadataVisitor,
|
||||
kotlinFunctionMetadata,
|
||||
this);
|
||||
|
||||
if (kotlinValueParameterMetadata.flags.common.hasAnnotations)
|
||||
if (kotlinValueParameterMetadata.flags.hasAnnotations)
|
||||
{
|
||||
kotlinFunctionMetadata.referencedMethodAccept(annotationCounter.reset());
|
||||
kotlinValueParameterMetadata.flags.common.hasAnnotations =
|
||||
kotlinValueParameterMetadata.flags.hasAnnotations =
|
||||
annotationCounter.getParameterAnnotationCount(kotlinValueParameterMetadata.index) > 0;
|
||||
}
|
||||
}
|
||||
@@ -261,12 +256,12 @@ implements KotlinMetadataVisitor,
|
||||
kotlinConstructorMetadata,
|
||||
this);
|
||||
|
||||
if (kotlinValueParameterMetadata.flags.common.hasAnnotations)
|
||||
if (kotlinValueParameterMetadata.flags.hasAnnotations)
|
||||
{
|
||||
if (!kotlinClassKindMetadata.flags.isAnnotationClass)
|
||||
{
|
||||
kotlinConstructorMetadata.referencedMethodAccept(clazz, annotationCounter.reset());
|
||||
kotlinValueParameterMetadata.flags.common.hasAnnotations =
|
||||
kotlinValueParameterMetadata.flags.hasAnnotations =
|
||||
annotationCounter.getParameterAnnotationCount(kotlinValueParameterMetadata.index) > 0;
|
||||
}
|
||||
}
|
||||
@@ -283,10 +278,10 @@ implements KotlinMetadataVisitor,
|
||||
kotlinPropertyMetadata,
|
||||
this);
|
||||
|
||||
if (kotlinValueParameterMetadata.flags.common.hasAnnotations)
|
||||
if (kotlinValueParameterMetadata.flags.hasAnnotations)
|
||||
{
|
||||
kotlinPropertyMetadata.referencedSetterMethod.accept(clazz, annotationCounter.reset());
|
||||
kotlinValueParameterMetadata.flags.common.hasAnnotations =
|
||||
kotlinValueParameterMetadata.flags.hasAnnotations =
|
||||
annotationCounter.getParameterAnnotationCount(kotlinValueParameterMetadata.index) > 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)));
|
||||
|
||||
@@ -1,168 +0,0 @@
|
||||
/*
|
||||
* ProGuard -- shrinking, optimization, obfuscation, and preverification
|
||||
* of Java bytecode.
|
||||
*
|
||||
* Copyright (c) 2002-2020 Guardsquare NV
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
package proguard.io;
|
||||
|
||||
import kotlinx.metadata.KmAnnotation;
|
||||
import kotlinx.metadata.internal.metadata.jvm.deserialization.JvmMetadataVersion;
|
||||
import kotlinx.metadata.jvm.*;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import proguard.classfile.*;
|
||||
import proguard.classfile.util.ClassUtil;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.*;
|
||||
|
||||
public class KotlinModuleRewriter
|
||||
extends DataEntryCopier
|
||||
{
|
||||
private static final Logger logger = LogManager.getLogger(KotlinModuleRewriter.class);
|
||||
|
||||
private final ClassPool programClassPool;
|
||||
|
||||
|
||||
public KotlinModuleRewriter(ClassPool programClassPool, Charset charset, DataEntryWriter writer)
|
||||
{
|
||||
super(writer);
|
||||
this.programClassPool = programClassPool;
|
||||
}
|
||||
|
||||
|
||||
public void read(DataEntry dataEntry) throws IOException
|
||||
{
|
||||
super.read(dataEntry);
|
||||
}
|
||||
|
||||
|
||||
protected void copyData(InputStream inputStream, OutputStream outputStream) throws IOException
|
||||
{
|
||||
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
|
||||
super.copyData(inputStream, byteStream);
|
||||
byte[] bytes = byteStream.toByteArray();
|
||||
KotlinModuleMetadata kotlinModuleMetadata = KotlinModuleMetadata.read(bytes);
|
||||
KmModule kmModule = kotlinModuleMetadata.toKmModule();
|
||||
|
||||
ModuleTransformer moduleTransformer = new ModuleTransformer();
|
||||
kmModule.accept(moduleTransformer);
|
||||
|
||||
KotlinModuleMetadata.Writer writer = new KotlinModuleMetadata.Writer();
|
||||
moduleTransformer.getResult().accept(writer);
|
||||
byte[] transformedBytes = writer.write(JvmMetadataVersion.INSTANCE.toArray()).getBytes();
|
||||
outputStream.write(transformedBytes);
|
||||
}
|
||||
|
||||
|
||||
private class PackageInformation
|
||||
{
|
||||
private final String fqName;
|
||||
private final List<String> fileFacades = new ArrayList<>();
|
||||
private final Map<String, String> multiFileClassParts = new HashMap<>();
|
||||
|
||||
|
||||
private PackageInformation(String fqName) {this.fqName = fqName;}
|
||||
|
||||
|
||||
public void addToModule(KmModule out)
|
||||
{
|
||||
out.visitPackageParts(fqName, fileFacades, multiFileClassParts);
|
||||
}
|
||||
}
|
||||
|
||||
private class ModuleTransformer extends KmModuleVisitor
|
||||
{
|
||||
private final Map<String, PackageInformation> newModuleInfo = new HashMap<>();
|
||||
|
||||
|
||||
private PackageInformation getPackageInformation(String fqName)
|
||||
{
|
||||
if (newModuleInfo.containsKey(fqName))
|
||||
{
|
||||
return newModuleInfo.get(fqName);
|
||||
}
|
||||
else
|
||||
{
|
||||
PackageInformation packageInformation = new PackageInformation(fqName);
|
||||
newModuleInfo.put(fqName, packageInformation);
|
||||
return packageInformation;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private PackageInformation getPackageInformation(Clazz clazz)
|
||||
{
|
||||
return getPackageInformation(ClassUtil.externalPackageName(ClassUtil.externalClassName(clazz.getName())));
|
||||
}
|
||||
|
||||
|
||||
public void visitPackageParts(String fqName, List<String> fileFacades, Map<String, String> multiFileClassParts)
|
||||
{
|
||||
for (String fileFacade : fileFacades)
|
||||
{
|
||||
Clazz newClass = programClassPool.getClass(fileFacade);
|
||||
|
||||
if (newClass == null)
|
||||
{
|
||||
// This can occur in the case of wrong input modules.
|
||||
// For instance the kotlin.reflect module declares facades which are actually absent.
|
||||
continue;
|
||||
}
|
||||
|
||||
getPackageInformation(newClass).fileFacades.add(newClass.getName());
|
||||
}
|
||||
|
||||
for (Map.Entry<String, String> entry : multiFileClassParts.entrySet())
|
||||
{
|
||||
Clazz keyClass = programClassPool.getClass(entry.getKey());
|
||||
Clazz valueClass = programClassPool.getClass(entry.getValue());
|
||||
|
||||
if (keyClass == null ||
|
||||
valueClass == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
getPackageInformation(keyClass).multiFileClassParts.put(keyClass .getName(),
|
||||
valueClass.getName());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public KmModule getResult()
|
||||
{
|
||||
KmModule out = new KmModule();
|
||||
for (PackageInformation packageInformation : newModuleInfo.values())
|
||||
{
|
||||
packageInformation.addToModule(out);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
public void visitAnnotation(KmAnnotation annotation)
|
||||
{
|
||||
logger.error("Cannot handle annotations yet");
|
||||
}
|
||||
|
||||
|
||||
public void visitEnd() {}
|
||||
}
|
||||
}
|
||||
@@ -39,12 +39,16 @@ import proguard.classfile.kotlin.KotlinConstants;
|
||||
import proguard.classfile.kotlin.KotlinDeclarationContainerMetadata;
|
||||
import proguard.classfile.kotlin.KotlinFunctionMetadata;
|
||||
import proguard.classfile.kotlin.KotlinMetadata;
|
||||
import proguard.classfile.kotlin.KotlinPropertyMetadata;
|
||||
import proguard.classfile.kotlin.KotlinSyntheticClassKindMetadata;
|
||||
import proguard.classfile.kotlin.visitor.KotlinFunctionToDefaultMethodVisitor;
|
||||
import proguard.classfile.kotlin.visitor.KotlinFunctionToMethodVisitor;
|
||||
import proguard.classfile.kotlin.visitor.KotlinFunctionVisitor;
|
||||
import proguard.classfile.kotlin.visitor.KotlinMetadataVisitor;
|
||||
import proguard.classfile.kotlin.visitor.KotlinPropertyVisitor;
|
||||
import proguard.classfile.kotlin.visitor.MemberToKotlinPropertyVisitor;
|
||||
import proguard.classfile.kotlin.visitor.ReferencedKotlinMetadataVisitor;
|
||||
import proguard.classfile.kotlin.visitor.filter.KotlinClassFilter;
|
||||
import proguard.classfile.util.AllParameterVisitor;
|
||||
import proguard.classfile.visitor.AllMemberVisitor;
|
||||
import proguard.classfile.visitor.ClassAccessFilter;
|
||||
@@ -63,14 +67,20 @@ import proguard.classfile.visitor.MultiClassVisitor;
|
||||
import proguard.classfile.visitor.MultiMemberVisitor;
|
||||
import proguard.classfile.visitor.NamedMethodVisitor;
|
||||
import proguard.pass.Pass;
|
||||
import proguard.util.Processable;
|
||||
import proguard.util.ProcessingFlagSetter;
|
||||
import proguard.util.ProcessingFlags;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static proguard.util.ProcessingFlags.DONT_OBFUSCATE;
|
||||
import static proguard.util.ProcessingFlags.DONT_OPTIMIZE;
|
||||
import static proguard.util.ProcessingFlags.DONT_SHRINK;
|
||||
import static proguard.util.ProcessingFlags.DONT_SHRINK_OR_OPTIMIZE_OR_OBFUSCATE;
|
||||
import static proguard.util.ProcessingFlags.INJECTED;
|
||||
|
||||
/**
|
||||
@@ -124,6 +134,34 @@ public class Marker implements Pass
|
||||
new ProcessingFlagSetter(ProcessingFlags.DONT_SHRINK | ProcessingFlags.DONT_OPTIMIZE | ProcessingFlags.DONT_OBFUSCATE))));
|
||||
appView.programClassPool.classesAccept(classVisitor);
|
||||
appView.libraryClassPool.classesAccept(classVisitor);
|
||||
|
||||
// When a property is kept, make sure the getter, setter and backing field all have the same
|
||||
// keep flags.
|
||||
ClassVisitor propertyVisitor =
|
||||
new KotlinClassFilter(
|
||||
new AllMemberVisitor(
|
||||
new MemberToKotlinPropertyVisitor(
|
||||
new KotlinPropertyVisitor() {
|
||||
public void visitAnyProperty(Clazz clazz,
|
||||
KotlinDeclarationContainerMetadata kotlinDeclarationContainerMetadata,
|
||||
KotlinPropertyMetadata kotlinPropertyMetadata) {
|
||||
List<Processable> processables = Stream.of(kotlinPropertyMetadata.referencedBackingField,
|
||||
kotlinPropertyMetadata.referencedGetterMethod,
|
||||
kotlinPropertyMetadata.referencedSetterMethod)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
int flags = 0;
|
||||
for (Processable processable : processables) {
|
||||
flags |= processable.getProcessingFlags();
|
||||
}
|
||||
// Only copy the keep flags.
|
||||
int copiedFlags = flags & DONT_SHRINK_OR_OPTIMIZE_OR_OBFUSCATE;
|
||||
processables.forEach(p -> p.setProcessingFlags(p.getProcessingFlags() | copiedFlags));
|
||||
}
|
||||
})));
|
||||
appView.programClassPool.classesAccept(propertyVisitor);
|
||||
appView.libraryClassPool.classesAccept(propertyVisitor);
|
||||
|
||||
}
|
||||
|
||||
// Mark members that can be safely used for generalization,
|
||||
@@ -234,8 +272,7 @@ public class Marker implements Pass
|
||||
{
|
||||
// Program classes are always available and safe to generalize/specialize from/to.
|
||||
ClassVisitor isClassAvailableMarker =
|
||||
new AllMemberVisitor(
|
||||
new ProcessingFlagSetter(ProcessingFlags.IS_CLASS_AVAILABLE));
|
||||
new ProcessingFlagSetter(ProcessingFlags.IS_CLASS_AVAILABLE);
|
||||
|
||||
programClassPool.classesAccept(isClassAvailableMarker);
|
||||
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +78,8 @@ implements MemberVisitor
|
||||
String descriptor = member.getDescriptor(clazz);
|
||||
|
||||
// Check whether we're allowed to do aggressive overloading
|
||||
if (!allowAggressiveOverloading)
|
||||
// Annotations are always excluded from aggressive overloading due to JVM limitations
|
||||
if (!allowAggressiveOverloading || clazz.extendsOrImplements(ClassConstants.NAME_JAVA_LANG_ANNOTATION_ANNOTATION))
|
||||
{
|
||||
// Trim the return argument from the descriptor if not.
|
||||
// Works for fields and methods alike.
|
||||
|
||||
@@ -109,7 +109,8 @@ public class MemberNameConflictFixer implements MemberVisitor
|
||||
String descriptor = member.getDescriptor(clazz);
|
||||
|
||||
// Check whether we're allowed to overload aggressively.
|
||||
if (!allowAggressiveOverloading)
|
||||
// Annotations are always excluded from aggressive overloading due to JVM limitations
|
||||
if (!allowAggressiveOverloading || clazz.extendsOrImplements(ClassConstants.NAME_JAVA_LANG_ANNOTATION_ANNOTATION))
|
||||
{
|
||||
// Trim the return argument from the descriptor if not.
|
||||
// Works for fields and methods alike.
|
||||
|
||||
@@ -81,7 +81,8 @@ implements MemberVisitor
|
||||
String descriptor = member.getDescriptor(clazz);
|
||||
|
||||
// Check whether we're allowed to overload aggressively.
|
||||
if (!allowAggressiveOverloading)
|
||||
// Annotations are always excluded from aggressive overloading due to JVM limitations
|
||||
if (!allowAggressiveOverloading || clazz.extendsOrImplements(ClassConstants.NAME_JAVA_LANG_ANNOTATION_ANNOTATION))
|
||||
{
|
||||
// Trim the return argument from the descriptor if not.
|
||||
// Works for fields and methods alike.
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,26 +21,33 @@
|
||||
package proguard.obfuscate.kotlin;
|
||||
|
||||
import proguard.classfile.Clazz;
|
||||
import proguard.classfile.LibraryClass;
|
||||
import proguard.classfile.LibraryMember;
|
||||
import proguard.classfile.ProgramClass;
|
||||
import proguard.classfile.ProgramMember;
|
||||
import proguard.classfile.kotlin.KotlinClassKindMetadata;
|
||||
import proguard.classfile.kotlin.KotlinMetadata;
|
||||
import proguard.classfile.kotlin.visitor.KotlinMetadataVisitor;
|
||||
import proguard.classfile.visitor.AllFieldVisitor;
|
||||
import proguard.classfile.visitor.MemberVisitor;
|
||||
|
||||
import static proguard.classfile.util.ClassUtil.internalSimpleClassName;
|
||||
import static proguard.obfuscate.ClassObfuscator.newClassName;
|
||||
import static proguard.obfuscate.ClassObfuscator.setNewClassName;
|
||||
import static proguard.obfuscate.MemberObfuscator.newMemberName;
|
||||
import static proguard.obfuscate.MemberObfuscator.setFixedNewMemberName;
|
||||
import static proguard.util.ProcessingFlags.DONT_OBFUSCATE;
|
||||
|
||||
public class KotlinCompanionEqualizer
|
||||
implements KotlinMetadataVisitor
|
||||
{
|
||||
implements KotlinMetadataVisitor {
|
||||
|
||||
@Override
|
||||
public void visitAnyKotlinMetadata(Clazz clazz, KotlinMetadata kotlinMetadata) {}
|
||||
public void visitAnyKotlinMetadata(Clazz clazz, KotlinMetadata kotlinMetadata) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitKotlinClassMetadata(Clazz clazz, KotlinClassKindMetadata kotlinClassKindMetadata)
|
||||
{
|
||||
if (kotlinClassKindMetadata.companionObjectName != null)
|
||||
{
|
||||
public void visitKotlinClassMetadata(Clazz clazz, KotlinClassKindMetadata kotlinClassKindMetadata) {
|
||||
if (kotlinClassKindMetadata.companionObjectName != null) {
|
||||
String newCompanionClassName = newClassName(kotlinClassKindMetadata.referencedCompanionClass);
|
||||
|
||||
if (newCompanionClassName == null) return;
|
||||
@@ -50,13 +57,51 @@ implements KotlinMetadataVisitor
|
||||
// The Kotlin asserter will check the field name, so will throw the metadata away if
|
||||
// it wasn't named correctly.
|
||||
|
||||
if (newCompanionClassName.contains("$"))
|
||||
{
|
||||
// Set a fixed member name to make sure it gets priority when resolving naming conflicts and collecting
|
||||
// already used member names.
|
||||
setFixedNewMemberName(kotlinClassKindMetadata.referencedCompanionField,
|
||||
internalSimpleClassName(newCompanionClassName));
|
||||
if (newCompanionClassName.contains("$")) {
|
||||
if ((kotlinClassKindMetadata.referencedCompanionField.getProcessingFlags() & DONT_OBFUSCATE) != 0) {
|
||||
// The field is to be kept, so the class name should be kept as well.
|
||||
setNewClassName(kotlinClassKindMetadata.referencedCompanionClass,
|
||||
newClassName(kotlinClassKindMetadata.referencedClass) + "$" + kotlinClassKindMetadata.companionObjectName);
|
||||
} else {
|
||||
final String newCompanionSimpleClassName = internalSimpleClassName(newCompanionClassName);
|
||||
|
||||
// Check whether there is already a field with the same fixed new name in the hierarchy.
|
||||
ConflictingFieldChecker conflictingFieldChecker =
|
||||
new ConflictingFieldChecker(newCompanionSimpleClassName);
|
||||
clazz.hierarchyAccept(
|
||||
false, true, true, false, new AllFieldVisitor(conflictingFieldChecker));
|
||||
|
||||
// There is a field in the hierarchy with the same fixed name. Rename the companion
|
||||
// class and associated field to avoid the class being renamed inconsistently during
|
||||
// conflict resolution and causing runtime crashes.
|
||||
if (conflictingFieldChecker.isConflicting) {
|
||||
newCompanionClassName = newCompanionClassName + "_";
|
||||
setNewClassName(
|
||||
kotlinClassKindMetadata.referencedCompanionClass, newCompanionClassName);
|
||||
}
|
||||
// Set a fixed member name to make sure it gets priority when resolving naming conflicts and collecting
|
||||
// already used member names.
|
||||
setFixedNewMemberName(kotlinClassKindMetadata.referencedCompanionField,
|
||||
internalSimpleClassName(newCompanionClassName));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
private static class ConflictingFieldChecker implements MemberVisitor {
|
||||
|
||||
public boolean isConflicting = false;
|
||||
private String newCompanionSimpleClassName;
|
||||
|
||||
public ConflictingFieldChecker(String newCompanionSimpleClassName) {
|
||||
this.newCompanionSimpleClassName = newCompanionSimpleClassName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitLibraryMember(LibraryClass libraryClass, LibraryMember libraryMember) {}
|
||||
|
||||
@Override
|
||||
public void visitProgramMember(ProgramClass programClass, ProgramMember programMember) {
|
||||
isConflicting |= newCompanionSimpleClassName.equals(newMemberName(programMember));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,7 +141,7 @@ implements MemberVisitor
|
||||
|
||||
if (valueClass != null &&
|
||||
valueClass.extendsOrImplements(ClassUtil.internalClassNameFromClassType(fieldType)) &&
|
||||
(programField.getProcessingFlags() & ProcessingFlags.IS_CLASS_AVAILABLE) != 0)
|
||||
(valueClass.getProcessingFlags() & ProcessingFlags.IS_CLASS_AVAILABLE) != 0)
|
||||
{
|
||||
logger.debug("MemberDescriptorSpecializer [{}.{} {}] -> {}",
|
||||
programClass.getName(),
|
||||
@@ -210,7 +210,7 @@ implements MemberVisitor
|
||||
|
||||
if (valueClass != null &&
|
||||
valueClass.extendsOrImplements(ClassUtil.internalClassNameFromClassType(parameterType)) &&
|
||||
(programMethod.getProcessingFlags() & ProcessingFlags.IS_CLASS_AVAILABLE) != 0)
|
||||
(valueClass.getProcessingFlags() & ProcessingFlags.IS_CLASS_AVAILABLE) != 0)
|
||||
{
|
||||
logger.debug("MemberDescriptorSpecializer [{}.{}{}]: parameter #{}: {} -> {}",
|
||||
programClass.getName(),
|
||||
|
||||
@@ -22,12 +22,22 @@ package proguard.optimize;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import proguard.classfile.*;
|
||||
import proguard.classfile.AccessConstants;
|
||||
import proguard.classfile.Clazz;
|
||||
import proguard.classfile.Field;
|
||||
import proguard.classfile.Member;
|
||||
import proguard.classfile.Method;
|
||||
import proguard.classfile.ProgramClass;
|
||||
import proguard.classfile.attribute.CodeAttribute;
|
||||
import proguard.classfile.constant.*;
|
||||
import proguard.classfile.constant.AnyMethodrefConstant;
|
||||
import proguard.classfile.constant.ClassConstant;
|
||||
import proguard.classfile.constant.FieldrefConstant;
|
||||
import proguard.classfile.constant.RefConstant;
|
||||
import proguard.classfile.constant.visitor.ConstantVisitor;
|
||||
import proguard.classfile.editor.*;
|
||||
import proguard.classfile.instruction.*;
|
||||
import proguard.classfile.editor.CodeAttributeEditor;
|
||||
import proguard.classfile.editor.ConstantPoolEditor;
|
||||
import proguard.classfile.instruction.ConstantInstruction;
|
||||
import proguard.classfile.instruction.Instruction;
|
||||
import proguard.classfile.instruction.visitor.InstructionVisitor;
|
||||
import proguard.classfile.visitor.ClassVisitor;
|
||||
import proguard.util.ProcessingFlags;
|
||||
@@ -200,7 +210,7 @@ implements InstructionVisitor,
|
||||
// DGD-486: Only generalize members which are always available. Partial replacement of a class that is not
|
||||
// available on all platforms may result in a VerifyError at runtime.
|
||||
if (referencedMember != null &&
|
||||
(referencedMember.getProcessingFlags() & ProcessingFlags.IS_CLASS_AVAILABLE) != 0)
|
||||
(clazz.getProcessingFlags() & ProcessingFlags.IS_CLASS_AVAILABLE) != 0)
|
||||
{
|
||||
clazz.constantPoolEntryAccept(refConstant.u2classIndex, this);
|
||||
|
||||
@@ -287,13 +297,14 @@ implements InstructionVisitor,
|
||||
// Otherwise, look in the super class itself.
|
||||
// Only consider public classes and methods, to avoid any
|
||||
// access problems.
|
||||
// Only consider classes that are marked as available.
|
||||
if (generalizedClass == null &&
|
||||
(superClass.getAccessFlags() & AccessConstants.PUBLIC) != 0)
|
||||
(superClass.getAccessFlags() & AccessConstants.PUBLIC) != 0 &&
|
||||
(superClass.getProcessingFlags() & ProcessingFlags.IS_CLASS_AVAILABLE) != 0)
|
||||
{
|
||||
Method method = superClass.findMethod(memberName, memberType);
|
||||
if (method != null &&
|
||||
(method.getAccessFlags() & AccessConstants.PUBLIC) != 0 &&
|
||||
(method.getProcessingFlags() & ProcessingFlags.IS_CLASS_AVAILABLE) != 0)
|
||||
(method.getAccessFlags() & AccessConstants.PUBLIC) != 0)
|
||||
{
|
||||
// Remember the generalized class and class member.
|
||||
generalizedClass = superClass;
|
||||
@@ -308,7 +319,7 @@ implements InstructionVisitor,
|
||||
Field field = clazz.findField(memberName, memberType);
|
||||
|
||||
if (field != null &&
|
||||
(field.getProcessingFlags() & ProcessingFlags.IS_CLASS_AVAILABLE) != 0)
|
||||
(clazz.getProcessingFlags() & ProcessingFlags.IS_CLASS_AVAILABLE) != 0)
|
||||
{
|
||||
// Remember the generalized class and class member.
|
||||
generalizedClass = clazz;
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
/**
|
||||
@@ -124,6 +124,22 @@ implements MemberVisitor,
|
||||
-1L : -2L);
|
||||
}
|
||||
|
||||
if (programMethod.processingInfo instanceof ProgramMethodOptimizationInfo
|
||||
&& parameterSize >= 64) {
|
||||
int parameterSizesCummulative = 0;
|
||||
for (int index = 0; parameterSizesCummulative < 64; index++) {
|
||||
boolean isCategory2 =
|
||||
((ProgramMethodOptimizationInfo) programMethod.processingInfo).getParameterSize(index)
|
||||
== 2;
|
||||
if (parameterSizesCummulative == 63 && isCategory2) {
|
||||
markParameterUsed(programMethod, 63);
|
||||
}
|
||||
parameterSizesCummulative +=
|
||||
((ProgramMethodOptimizationInfo) programMethod.processingInfo)
|
||||
.getParameterSize(index);
|
||||
}
|
||||
}
|
||||
|
||||
// Is it a native method?
|
||||
if ((accessFlags & AccessConstants.NATIVE) != 0)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -1886,9 +1886,10 @@ implements ClassVisitor,
|
||||
markAsUsed(kotlinPropertyMetadata.receiverType);
|
||||
markAsUsed(kotlinPropertyMetadata.typeParameters);
|
||||
markAsUsed(kotlinPropertyMetadata.setterParameters);
|
||||
markAsUsed(kotlinPropertyMetadata.setterParameter);
|
||||
markAsUsed(kotlinPropertyMetadata.type);
|
||||
|
||||
if (kotlinPropertyMetadata.flags.common.hasAnnotations &&
|
||||
if (kotlinPropertyMetadata.flags.hasAnnotations &&
|
||||
kotlinPropertyMetadata.syntheticMethodForAnnotations != null)
|
||||
{
|
||||
// Annotations are placed on a synthetic method (e.g. myProperty$annotations())
|
||||
@@ -1971,23 +1972,28 @@ implements ClassVisitor,
|
||||
{
|
||||
visitAnyFunction(clazz, kotlinDeclarationContainerMetadata, kotlinFunctionMetadata);
|
||||
|
||||
// Non-abstract functions in interfaces should have default implementations, so keep it if the
|
||||
// user kept the original function.
|
||||
if (isUsed(kotlinFunctionMetadata))
|
||||
{
|
||||
if (kotlinDeclarationContainerMetadata.k == KotlinConstants.METADATA_KIND_CLASS &&
|
||||
((KotlinClassKindMetadata)kotlinDeclarationContainerMetadata).flags.isInterface &&
|
||||
!kotlinFunctionMetadata.flags.modality.isAbstract &&
|
||||
(kotlinFunctionMetadata.referencedMethod.getProcessingFlags() & ProcessingFlags.DONT_SHRINK) != 0)
|
||||
{
|
||||
kotlinFunctionMetadata.referencedDefaultImplementationMethodAccept(
|
||||
new MultiMemberVisitor(
|
||||
ClassUsageMarker.this,
|
||||
new MemberToClassVisitor(ClassUsageMarker.this)
|
||||
)
|
||||
);
|
||||
}
|
||||
boolean isInterface =
|
||||
kotlinDeclarationContainerMetadata.k == KotlinConstants.METADATA_KIND_CLASS
|
||||
&& ((KotlinClassKindMetadata) kotlinDeclarationContainerMetadata).flags.isInterface
|
||||
&& !kotlinFunctionMetadata.flags.modality.isAbstract;
|
||||
|
||||
if (isUsed(kotlinFunctionMetadata)
|
||||
&& isInterface
|
||||
&& (kotlinFunctionMetadata.referencedMethod.getProcessingFlags()
|
||||
& ProcessingFlags.DONT_SHRINK)
|
||||
!= 0) {
|
||||
kotlinFunctionMetadata.referencedDefaultImplementationMethodAccept(
|
||||
new MultiMemberVisitor(
|
||||
ClassUsageMarker.this, new MemberToClassVisitor(ClassUsageMarker.this)));
|
||||
}
|
||||
|
||||
// If a default implementation is called directly,
|
||||
// the interface should be marked as used as well.
|
||||
if (kotlinFunctionMetadata.referencedDefaultImplementationMethod != null
|
||||
&& isInterface
|
||||
&& isUsed(kotlinFunctionMetadata.referencedDefaultImplementationMethod)) {
|
||||
kotlinFunctionMetadata.referencedMethodAccept(ClassUsageMarker.this);
|
||||
}
|
||||
}
|
||||
|
||||
// Implementations for KotlinTypeAliasVisitor.
|
||||
@@ -2063,7 +2069,10 @@ implements ClassVisitor,
|
||||
else if (kotlinTypeMetadata.aliasName != null && !isUsed(kotlinTypeMetadata.referencedTypeAlias))
|
||||
{
|
||||
markAsUsed(kotlinTypeMetadata.referencedTypeAlias);
|
||||
kotlinTypeMetadata.referencedTypeAlias.accept(null, null, this);
|
||||
kotlinTypeMetadata.referencedTypeAlias.accept(
|
||||
kotlinTypeMetadata.referencedTypeAlias.referencedDeclarationContainer.ownerReferencedClass,
|
||||
kotlinTypeMetadata.referencedTypeAlias.referencedDeclarationContainer,
|
||||
this);
|
||||
}
|
||||
|
||||
markAsUsed(kotlinTypeMetadata.typeArguments);
|
||||
|
||||
@@ -184,7 +184,6 @@ implements KotlinMetadataVisitor,
|
||||
{
|
||||
kotlinPropertyMetadata.getterSignature = null;
|
||||
kotlinPropertyMetadata.referencedGetterMethod = null;
|
||||
kotlinPropertyMetadata.flags.hasGetter = false;
|
||||
}
|
||||
|
||||
if (shouldShrinkMetadata(kotlinPropertyMetadata.setterSignature,
|
||||
@@ -192,7 +191,8 @@ implements KotlinMetadataVisitor,
|
||||
{
|
||||
kotlinPropertyMetadata.setterSignature = null;
|
||||
kotlinPropertyMetadata.referencedSetterMethod = null;
|
||||
kotlinPropertyMetadata.flags.hasSetter = false;
|
||||
kotlinPropertyMetadata.flags.isVar = false;
|
||||
kotlinPropertyMetadata.setterParameter = null;
|
||||
kotlinPropertyMetadata.setterParameters.clear();
|
||||
}
|
||||
|
||||
@@ -206,7 +206,7 @@ implements KotlinMetadataVisitor,
|
||||
kotlinPropertyMetadata.syntheticMethodForAnnotations = null;
|
||||
kotlinPropertyMetadata.referencedSyntheticMethodForAnnotations = null;
|
||||
kotlinPropertyMetadata.referencedSyntheticMethodClass = null;
|
||||
kotlinPropertyMetadata.flags.common.hasAnnotations = false;
|
||||
kotlinPropertyMetadata.flags.hasAnnotations = false;
|
||||
}
|
||||
|
||||
if (kotlinPropertyMetadata.syntheticMethodForDelegate != null &&
|
||||
|
||||
@@ -30,7 +30,7 @@ import proguard.classfile.util.kotlin.KotlinMetadataInitializer;
|
||||
import proguard.classfile.visitor.ClassVisitor;
|
||||
import proguard.pass.Pass;
|
||||
|
||||
import static proguard.classfile.util.kotlin.KotlinMetadataInitializer.MAX_SUPPORTED_VERSION;
|
||||
import static proguard.classfile.io.kotlin.KotlinMetadataWriter.LATEST_STABLE_SUPPORTED;
|
||||
import static proguard.classfile.util.kotlin.KotlinMetadataInitializer.isSupportedMetadataVersion;
|
||||
|
||||
/**
|
||||
@@ -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 " + MAX_SUPPORTED_VERSION.major + "." + MAX_SUPPORTED_VERSION.minor + " 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)
|
||||
}
|
||||
}
|
||||
@@ -200,12 +206,60 @@ class ConfigurationParserTest : FreeSpec({
|
||||
}
|
||||
}
|
||||
}
|
||||
"Testing -maximumremovedandroidloglevel parsing" - {
|
||||
"Given an empty configuration" - {
|
||||
val savedPrintStream = System.out
|
||||
val customOutputStream = ByteArrayOutputStream()
|
||||
System.setOut(PrintStream(customOutputStream))
|
||||
|
||||
parseConfiguration("")
|
||||
|
||||
"The option does not print anything" {
|
||||
customOutputStream.toString() shouldContain ""
|
||||
System.setOut(savedPrintStream)
|
||||
}
|
||||
}
|
||||
|
||||
"Given a configuration with -maximumremovedandroidloglevel without a class specification" - {
|
||||
val savedPrintStream = System.out
|
||||
val customOutputStream = ByteArrayOutputStream()
|
||||
System.setOut(PrintStream(customOutputStream))
|
||||
|
||||
parseConfiguration("-maximumremovedandroidloglevel 1")
|
||||
|
||||
"The option prints out a warning" {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
"Given a configuration with -maximumremovedandroidloglevel with a class specification" - {
|
||||
val savedPrintStream = System.out
|
||||
val customOutputStream = ByteArrayOutputStream()
|
||||
System.setOut(PrintStream(customOutputStream))
|
||||
|
||||
parseConfiguration(
|
||||
"""
|
||||
-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.\nThis option will have no effect on the optimized artifact."
|
||||
System.setOut(savedPrintStream)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"Wildcard type tests" - {
|
||||
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()
|
||||
@@ -213,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>; }")
|
||||
@@ -380,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,42 +29,22 @@ class ConfigurationWriterTest : FreeSpec({
|
||||
|
||||
"Keep rules tests" - {
|
||||
"Keep class constructor should be kept" {
|
||||
val rules = """
|
||||
-keep class * {
|
||||
<init>();
|
||||
}
|
||||
""".trimIndent()
|
||||
val rules = "-keep class * {$eol <init>();$eol}"
|
||||
val out = printConfiguration(rules)
|
||||
out shouldBe rules
|
||||
}
|
||||
|
||||
"Keep class initializer should be kept" {
|
||||
val rules = """
|
||||
-keep class * {
|
||||
<clinit>();
|
||||
}
|
||||
""".trimIndent()
|
||||
val rules = "-keep class * {$eol <clinit>();$eol}"
|
||||
val out = printConfiguration(rules)
|
||||
val expected = """
|
||||
-keep class * {
|
||||
void <clinit>();
|
||||
}
|
||||
""".trimIndent()
|
||||
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 {
|
||||
<clinit>();
|
||||
}
|
||||
""".trimIndent()
|
||||
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 {
|
||||
void <clinit>();
|
||||
}
|
||||
""".trimIndent()
|
||||
val expected = "-keep,allowobfuscation class ** extends com.example.A {$eol void <clinit>();$eol}"
|
||||
out shouldBe expected
|
||||
}
|
||||
}
|
||||
@@ -74,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>;
|
||||
}
|
||||
|
||||
47
base/src/test/kotlin/proguard/obfuscate/AnnotationTest.kt
Normal file
47
base/src/test/kotlin/proguard/obfuscate/AnnotationTest.kt
Normal file
@@ -0,0 +1,47 @@
|
||||
package proguard.obfuscate
|
||||
|
||||
import io.kotest.core.spec.style.FreeSpec
|
||||
import io.kotest.matchers.equals.shouldBeEqual
|
||||
import proguard.classfile.ProgramClass
|
||||
import proguard.testutils.ClassPoolBuilder
|
||||
import proguard.testutils.JavaSource
|
||||
import java.util.HashMap
|
||||
|
||||
class AnnotationTest : FreeSpec({
|
||||
val (programClassPool, _) =
|
||||
ClassPoolBuilder.fromSource(
|
||||
JavaSource(
|
||||
"Test.java",
|
||||
"""
|
||||
public @interface Test {
|
||||
boolean testBoolean1() default false;
|
||||
boolean testBoolean2() default true;
|
||||
String testString1() default "";
|
||||
}
|
||||
""".trimIndent(),
|
||||
),
|
||||
)
|
||||
val testClass = programClassPool.getClass("Test") as ProgramClass
|
||||
|
||||
"Annotation members should be excluded from aggressive overloading" {
|
||||
val descriptorMap = HashMap<String, Map<String, String>>()
|
||||
|
||||
testClass.methodsAccept(
|
||||
MemberObfuscator(
|
||||
true,
|
||||
SimpleNameFactory(),
|
||||
descriptorMap,
|
||||
),
|
||||
)
|
||||
|
||||
descriptorMap shouldBeEqual
|
||||
mapOf(
|
||||
"()" to
|
||||
mapOf(
|
||||
"a" to "testBoolean1",
|
||||
"b" to "testBoolean2",
|
||||
"c" to "testString1",
|
||||
),
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -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()
|
||||
|
||||
@@ -3,6 +3,7 @@ package proguard.optimize
|
||||
import io.kotest.core.spec.style.FreeSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import proguard.classfile.AccessConstants
|
||||
import proguard.classfile.ClassPool
|
||||
import proguard.classfile.Clazz
|
||||
import proguard.classfile.Member
|
||||
import proguard.classfile.attribute.visitor.AllAttributeVisitor
|
||||
@@ -26,83 +27,95 @@ import proguard.util.ProcessingFlagSetter
|
||||
import proguard.util.ProcessingFlags.IS_CLASS_AVAILABLE
|
||||
|
||||
class MemberDescriptorSpecializerTest : FreeSpec({
|
||||
"Given a method with a more general 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());
|
||||
}
|
||||
public static void foo(Bar foo) {
|
||||
System.out.println(foo);
|
||||
}
|
||||
}
|
||||
|
||||
class Bar { }
|
||||
|
||||
class Foo extends Bar { }
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
|
||||
"When specializing the member descriptors" - {
|
||||
fun specializeMemberDescriptors(
|
||||
programClassPool: ClassPool,
|
||||
libraryClassPool: ClassPool,
|
||||
) {
|
||||
// Mark all program classes as available.
|
||||
programClassPool.classesAccept(ProcessingFlagSetter(IS_CLASS_AVAILABLE))
|
||||
|
||||
// Mark all members as available.
|
||||
programClassPool.classesAccept(AllMemberVisitor(ProcessingFlagSetter(IS_CLASS_AVAILABLE)))
|
||||
// Setup the OptimizationInfo on the classes
|
||||
val keepMarker = KeepMarker()
|
||||
libraryClassPool.classesAccept(keepMarker)
|
||||
libraryClassPool.classesAccept(AllMemberVisitor(keepMarker))
|
||||
|
||||
// Setup the OptimizationInfo on the classes
|
||||
val keepMarker = KeepMarker()
|
||||
libraryClassPool.classesAccept(keepMarker)
|
||||
libraryClassPool.classesAccept(AllMemberVisitor(keepMarker))
|
||||
programClassPool.classesAccept(ProgramClassOptimizationInfoSetter())
|
||||
programClassPool.classesAccept(AllMemberVisitor(ProgramMemberOptimizationInfoSetter()))
|
||||
|
||||
programClassPool.classesAccept(ProgramClassOptimizationInfoSetter())
|
||||
programClassPool.classesAccept(AllMemberVisitor(ProgramMemberOptimizationInfoSetter()))
|
||||
|
||||
// Create the optimization as in Optimizer
|
||||
val fillingOutValuesClassVisitor = ClassVisitorFactory {
|
||||
// Create the optimization as in Optimizer
|
||||
val fillingOutValuesClassVisitor =
|
||||
ClassVisitorFactory {
|
||||
val valueFactory: ValueFactory = ParticularValueFactory()
|
||||
val storingInvocationUnit: InvocationUnit = StoringInvocationUnit(
|
||||
valueFactory,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
)
|
||||
val storingInvocationUnit: InvocationUnit =
|
||||
StoringInvocationUnit(
|
||||
valueFactory,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
)
|
||||
ClassAccessFilter(
|
||||
0, AccessConstants.SYNTHETIC,
|
||||
0,
|
||||
AccessConstants.SYNTHETIC,
|
||||
AllMethodVisitor(
|
||||
AllAttributeVisitor(
|
||||
DebugAttributeVisitor(
|
||||
"Filling out fields, method parameters, and return values",
|
||||
PartialEvaluator(
|
||||
valueFactory, storingInvocationUnit,
|
||||
true
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
valueFactory,
|
||||
storingInvocationUnit,
|
||||
true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
programClassPool.classesAccept(fillingOutValuesClassVisitor.createClassVisitor())
|
||||
programClassPool.classesAccept(fillingOutValuesClassVisitor.createClassVisitor())
|
||||
|
||||
// Specialize class member descriptors, based on partial evaluation.
|
||||
programClassPool.classesAccept(
|
||||
AllMemberVisitor(
|
||||
OptimizationInfoMemberFilter(
|
||||
MemberDescriptorSpecializer(
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)
|
||||
)
|
||||
)
|
||||
// Specialize class member descriptors, based on partial evaluation.
|
||||
programClassPool.classesAccept(
|
||||
AllMemberVisitor(
|
||||
OptimizationInfoMemberFilter(
|
||||
MemberDescriptorSpecializer(
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
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());
|
||||
}
|
||||
public static void foo(Bar foo) {
|
||||
System.out.println(foo);
|
||||
}
|
||||
}
|
||||
|
||||
class Bar { }
|
||||
|
||||
class Foo extends Bar { }
|
||||
""".trimIndent(),
|
||||
),
|
||||
)
|
||||
|
||||
"When specializing the member descriptors" - {
|
||||
specializeMemberDescriptors(programClassPool, libraryClassPool)
|
||||
|
||||
"Then the member descriptor should be correctly specialised" {
|
||||
lateinit var memberDescriptor: String
|
||||
programClassPool.classAccept(
|
||||
@@ -111,15 +124,109 @@ 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"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();
|
||||
}
|
||||
}
|
||||
|
||||
class Bar { }
|
||||
|
||||
class Foo extends Bar { }
|
||||
""".trimIndent(),
|
||||
),
|
||||
)
|
||||
|
||||
"When specializing the member descriptors" - {
|
||||
specializeMemberDescriptors(programClassPool, libraryClassPool)
|
||||
|
||||
"Then the member descriptor should be correctly specialised" {
|
||||
lateinit var memberDescriptor: String
|
||||
programClassPool.classAccept(
|
||||
"Test",
|
||||
AllMemberVisitor(
|
||||
MemberNameFilter(
|
||||
"myField*",
|
||||
object : MemberVisitor {
|
||||
override fun visitAnyMember(
|
||||
clazz: Clazz,
|
||||
member: Member,
|
||||
) {
|
||||
memberDescriptor = member.getDescriptor(clazz)
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
memberDescriptor shouldBe "LFoo;"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"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();
|
||||
}
|
||||
}
|
||||
""".trimIndent(),
|
||||
),
|
||||
)
|
||||
|
||||
"When specializing the member descriptors" - {
|
||||
specializeMemberDescriptors(programClassPool, libraryClassPool)
|
||||
|
||||
"Then the member descriptor should be correctly specialised" {
|
||||
lateinit var memberDescriptor: String
|
||||
programClassPool.classAccept(
|
||||
"Test",
|
||||
AllMemberVisitor(
|
||||
MemberNameFilter(
|
||||
"myField*",
|
||||
object : MemberVisitor {
|
||||
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))))
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -10,11 +10,19 @@ import io.kotest.matchers.shouldNot
|
||||
import io.kotest.matchers.shouldNotBe
|
||||
import proguard.AppView
|
||||
import proguard.Configuration
|
||||
import proguard.classfile.Clazz
|
||||
import proguard.classfile.Member
|
||||
import proguard.classfile.attribute.annotation.visitor.AllElementValueVisitor
|
||||
import proguard.classfile.attribute.visitor.AllAttributeVisitor
|
||||
import proguard.classfile.kotlin.KotlinTypeAliasMetadata
|
||||
import proguard.classfile.kotlin.visitor.AllTypeAliasVisitor
|
||||
import proguard.classfile.kotlin.visitor.ReferencedKotlinMetadataVisitor
|
||||
import proguard.classfile.util.EnumFieldReferenceInitializer
|
||||
import proguard.classfile.visitor.AllMethodVisitor
|
||||
import proguard.classfile.visitor.MemberVisitor
|
||||
import proguard.classfile.visitor.MultiClassVisitor
|
||||
import proguard.classfile.visitor.NamedClassVisitor
|
||||
import proguard.classfile.visitor.NamedMethodVisitor
|
||||
import proguard.testutils.ClassPoolBuilder
|
||||
import proguard.testutils.JavaSource
|
||||
import proguard.testutils.KotlinSource
|
||||
@@ -23,45 +31,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)
|
||||
@@ -98,29 +108,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")
|
||||
@@ -140,24 +151,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(
|
||||
@@ -165,17 +177,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")
|
||||
@@ -189,19 +201,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))
|
||||
@@ -212,4 +225,82 @@ class ClassUsageMarkerTest : StringSpec({
|
||||
programClassPool.classAccept("Test", MultiClassVisitor(classUsageMarker, AllMethodVisitor(classUsageMarker)))
|
||||
}
|
||||
}
|
||||
"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(),
|
||||
),
|
||||
)
|
||||
|
||||
// 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,
|
||||
) {
|
||||
marker.markAsUsed(member)
|
||||
}
|
||||
}
|
||||
|
||||
val usageMarker = SimpleUsageMarker()
|
||||
val classUsageMarker = ClassUsageMarker(usageMarker)
|
||||
|
||||
// Mark the classes as used.
|
||||
programClassPool.classesAccept(classUsageMarker)
|
||||
|
||||
// Mark the default implementation as used.
|
||||
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))
|
||||
|
||||
val fooInterface = programClassPool.getClass("test/Interface").findMethod("foo", null)
|
||||
fooInterface should beMarkedWith(usageMarker)
|
||||
}
|
||||
|
||||
"Given Kotlin `typealias` declarations where one aliases another" {
|
||||
val (programClassPool, _) =
|
||||
ClassPoolBuilder.fromSource(
|
||||
KotlinSource(
|
||||
"KotlinTypeAlias.kt",
|
||||
"""
|
||||
typealias P = () -> Unit
|
||||
typealias A = P
|
||||
""".trimIndent(),
|
||||
),
|
||||
kotlincArguments = listOf("-language-version=1.9"),
|
||||
)
|
||||
|
||||
// Obtain the `typealias` declarations A and P
|
||||
val typeAliasList = mutableListOf<KotlinTypeAliasMetadata>()
|
||||
programClassPool.classesAccept(
|
||||
ReferencedKotlinMetadataVisitor(
|
||||
AllTypeAliasVisitor { _, _, kotlinTypeAliasMetadata ->
|
||||
typeAliasList.add(kotlinTypeAliasMetadata)
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
// Ensure only `typealias` A and `typealias` P are present
|
||||
typeAliasList.map { it.name }.toSet() shouldBe setOf("A", "P")
|
||||
|
||||
// Both `typealias` A and `typealias` P should be marked as used by usageMarker.
|
||||
val usageMarker = SimpleUsageMarker()
|
||||
val classUsageMarker = ClassUsageMarker(usageMarker)
|
||||
programClassPool.classesAccept(classUsageMarker)
|
||||
typeAliasList.forEach {
|
||||
it should beMarkedWith(usageMarker)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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 {
|
||||
@@ -23,6 +24,7 @@ task buildDocumentation(type: Exec) {
|
||||
nexusPublishing {
|
||||
repositories {
|
||||
sonatype {
|
||||
nexusUrl = uri("https://ossrh-staging-api.central.sonatype.com/service/local/")
|
||||
username = findProperty('PROGUARD_STAGING_USERNAME')
|
||||
password = findProperty('PROGUARD_STAGING_PASSWORD')
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
This page lists all available options for ProGuard, grouped logically.
|
||||
|
||||
!!! android R8
|
||||
R8, the default Android shrinker, is compatible with ProGuard keep rules.
|
||||
R8, the default Android shrinker, is compatible with ProGuard keep rules. Both ProGuard and R8 were designed for app optimization, and although they employ minimal obfuscation techniques, they are not security tools and do not harden applications effectively against reverse engineering and tampering.
|
||||
|
||||
[DexGuard](https://hubs.la/Q02yCV0F0) is a protection tool for Android apps that is backward compatible with R8, making it easy to upgrade your R8 configuration with multi-layered security protections to your unprotected mobile application. Learn more in our blog: [Android Security and Obfuscation Realities of R8](https://hubs.la/Q02yCTlt0).
|
||||
|
||||
## Input/Output Options {: #iooptions}
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user