mirror of
https://github.com/Guardsquare/proguard.git
synced 2026-03-13 09:50:34 +08:00
Compare commits
66 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8f67c1977b | ||
|
|
0303116af9 | ||
|
|
8425e86946 | ||
|
|
164e3d8f4d | ||
|
|
6776d6a3aa | ||
|
|
e1e2689c72 | ||
|
|
14dcc28ba2 | ||
|
|
0c51b82946 | ||
|
|
68dcc9880e | ||
|
|
7a76843f0c | ||
|
|
d2170bad63 | ||
|
|
6dfd878ebb | ||
|
|
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 |
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.2'
|
||||
classpath 'com.guardsquare:proguard-gradle:7.8.2'
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -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,22 +3,9 @@ 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) {
|
||||
publications.named(project.name) {
|
||||
pom {
|
||||
description = 'Java annotations to configure ProGuard, the free shrinker, optimizer, obfuscator, and preverifier for Java bytecode'
|
||||
}
|
||||
|
||||
@@ -10,26 +10,12 @@ 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) {
|
||||
def fatJar = tasks.register("fatJar", ShadowJar) {
|
||||
destinationDirectory.set(file("$rootDir/lib"))
|
||||
archiveFileName.set('proguard-ant.jar')
|
||||
from sourceSets.main.output
|
||||
@@ -46,7 +32,7 @@ assemble.dependsOn fatJar
|
||||
|
||||
afterEvaluate {
|
||||
publishing {
|
||||
publications.getByName(project.name) {
|
||||
publications.named(project.name) {
|
||||
pom {
|
||||
description = 'Ant plugin for ProGuard, the free shrinker, optimizer, obfuscator, and preverifier for Java bytecode'
|
||||
}
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
|
||||
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 {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile) {
|
||||
kotlinOptions {
|
||||
jvmTarget = "${target}"
|
||||
}
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
jvmTarget.set(JvmTarget.fromTarget(project.findProperty("target")))
|
||||
}
|
||||
}
|
||||
|
||||
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:${proguardCoreVersion}")) {
|
||||
exclude group: 'com.guardsquare', module: 'proguard-core'
|
||||
@@ -50,30 +50,31 @@ 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()
|
||||
}
|
||||
|
||||
task testAllJavaVersions() { testAllTask ->
|
||||
def testAllJavaVersion = tasks.register("testAllJavaVersions"){ testAllTask ->
|
||||
dependsOn(test) // the usual test runs on Java 8
|
||||
}
|
||||
|
||||
javaVersionsForTest.each {version ->
|
||||
task("testJava$version", type: Test) {
|
||||
useJUnitPlatform()
|
||||
ignoreFailures = true
|
||||
javaVersionsForTest.each {version ->
|
||||
def testJavaVersion = tasks.register("testJava$version", Test) {
|
||||
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)
|
||||
|
||||
javaLauncher = javaToolchains.launcherFor {
|
||||
languageVersion = JavaLanguageVersion.of(version)
|
||||
}
|
||||
javaLauncher = javaToolchains.launcherFor {
|
||||
languageVersion = JavaLanguageVersion.of(version)
|
||||
}
|
||||
}
|
||||
testAllJavaVersion.configure { it.dependsOn(testJavaVersion) }
|
||||
|
||||
}
|
||||
|
||||
jacocoTestReport {
|
||||
@@ -86,15 +87,17 @@ 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"
|
||||
}
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
publishing {
|
||||
publications.getByName(project.name) {
|
||||
publications.named(project.name) {
|
||||
pom {
|
||||
description = 'ProGuard is a free shrinker, optimizer, obfuscator, and preverifier for Java bytecode'
|
||||
}
|
||||
|
||||
@@ -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 = "**";
|
||||
|
||||
|
||||
@@ -39,8 +39,6 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
|
||||
/**
|
||||
* This class parses ProGuard configurations. Configurations can be read from an
|
||||
@@ -51,6 +49,8 @@ import java.util.function.BiFunction;
|
||||
*/
|
||||
public class ConfigurationParser implements AutoCloseable
|
||||
{
|
||||
private final boolean useDalvikVerification = System.getProperty("proguard.use.dalvik.identifier.verification") != null;
|
||||
|
||||
private final WordReader reader;
|
||||
private final Properties properties;
|
||||
|
||||
@@ -260,6 +260,7 @@ 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
|
||||
{
|
||||
if (unknownOptionHandler != null) {
|
||||
@@ -1954,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());
|
||||
@@ -1967,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.
|
||||
@@ -2001,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
|
||||
@@ -2087,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
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package proguard.classfile.attribute;
|
||||
|
||||
public enum ProGuardOrigin implements LineOrigin
|
||||
{
|
||||
INLINED
|
||||
}
|
||||
@@ -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,31 @@ 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.getterMetadata != null &&
|
||||
kotlinPropertyMetadata.getterMetadata.referencedMethod != null)
|
||||
{
|
||||
kotlinPropertyMetadata.referencedGetterMethod.accept(clazz, annotationCounter.reset());
|
||||
kotlinPropertyMetadata.getterFlags.common.hasAnnotations = annotationCounter.getCount() > 0;
|
||||
kotlinPropertyMetadata.getterMetadata.referencedMethod.accept(clazz, annotationCounter.reset());
|
||||
kotlinPropertyMetadata.getterMetadata.hasAnnotations = annotationCounter.getCount() > 0;
|
||||
}
|
||||
|
||||
if (kotlinPropertyMetadata.flags.hasSetter && kotlinPropertyMetadata.referencedSetterMethod != null)
|
||||
if (kotlinPropertyMetadata.flags.isVar &&
|
||||
kotlinPropertyMetadata.setterMetadata != null &&
|
||||
kotlinPropertyMetadata.setterMetadata.referencedMethod != null)
|
||||
{
|
||||
kotlinPropertyMetadata.referencedSetterMethod.accept(clazz, annotationCounter.reset());
|
||||
kotlinPropertyMetadata.setterFlags.common.hasAnnotations = annotationCounter.getCount() > 0;
|
||||
kotlinPropertyMetadata.setterMetadata.referencedMethod.accept(clazz, annotationCounter.reset());
|
||||
kotlinPropertyMetadata.setterMetadata.hasAnnotations = annotationCounter.getCount() > 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,7 +160,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 +175,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 +195,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.
|
||||
@@ -237,10 +240,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;
|
||||
}
|
||||
}
|
||||
@@ -256,12 +259,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;
|
||||
}
|
||||
}
|
||||
@@ -278,10 +281,11 @@ implements KotlinMetadataVisitor,
|
||||
kotlinPropertyMetadata,
|
||||
this);
|
||||
|
||||
if (kotlinValueParameterMetadata.flags.common.hasAnnotations)
|
||||
if (kotlinValueParameterMetadata.flags.hasAnnotations &&
|
||||
kotlinPropertyMetadata.setterMetadata != null)
|
||||
{
|
||||
kotlinPropertyMetadata.referencedSetterMethod.accept(clazz, annotationCounter.reset());
|
||||
kotlinValueParameterMetadata.flags.common.hasAnnotations =
|
||||
kotlinPropertyMetadata.setterMetadata.referencedMethod.accept(clazz, annotationCounter.reset());
|
||||
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.getterMetadata.referencedMethod,
|
||||
kotlinPropertyMetadata.setterMetadata != null ? kotlinPropertyMetadata.setterMetadata.referencedMethod : null)
|
||||
.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,
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,11 +64,13 @@ implements KotlinMetadataVisitor,
|
||||
if ((kotlinPropertyMetadata.referencedBackingField != null &&
|
||||
(kotlinPropertyMetadata.referencedBackingField.getProcessingFlags() & ProcessingFlags.DONT_OBFUSCATE) != 0) ||
|
||||
|
||||
(kotlinPropertyMetadata.referencedGetterMethod != null &&
|
||||
(kotlinPropertyMetadata.referencedGetterMethod.getProcessingFlags() & ProcessingFlags.DONT_OBFUSCATE) != 0) ||
|
||||
(kotlinPropertyMetadata.getterMetadata != null &&
|
||||
kotlinPropertyMetadata.getterMetadata.referencedMethod != null &&
|
||||
(kotlinPropertyMetadata.getterMetadata.referencedMethod.getProcessingFlags() & ProcessingFlags.DONT_OBFUSCATE) != 0) ||
|
||||
|
||||
(kotlinPropertyMetadata.referencedSetterMethod != null &&
|
||||
(kotlinPropertyMetadata.referencedSetterMethod.getProcessingFlags() & ProcessingFlags.DONT_OBFUSCATE) != 0))
|
||||
(kotlinPropertyMetadata.setterMetadata != null &&
|
||||
kotlinPropertyMetadata.setterMetadata.referencedMethod != null &&
|
||||
(kotlinPropertyMetadata.setterMetadata.referencedMethod.getProcessingFlags() & ProcessingFlags.DONT_OBFUSCATE) != 0))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -114,14 +114,14 @@ implements KotlinMetadataVisitor,
|
||||
KotlinPropertyMetadata kotlinPropertyMetadata)
|
||||
{
|
||||
keepParameterInfo = false;
|
||||
if (kotlinPropertyMetadata.referencedSetterMethod != null)
|
||||
if (kotlinPropertyMetadata.setterMetadata != null && kotlinPropertyMetadata.setterMetadata.referencedMethod != null)
|
||||
{
|
||||
kotlinPropertyMetadata.referencedSetterMethod.accept(clazz, this);
|
||||
kotlinPropertyMetadata.setterMetadata.referencedMethod.accept(clazz, this);
|
||||
}
|
||||
|
||||
if (keepParameterInfo)
|
||||
{
|
||||
kotlinPropertyMetadata.setterParametersAccept(clazz, kotlinDeclarationContainerMetadata, this);
|
||||
kotlinPropertyMetadata.setterParameterAccept(clazz, kotlinDeclarationContainerMetadata, this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -936,7 +936,11 @@ public class Optimizer implements Pass
|
||||
new AllMethodVisitor(
|
||||
new AllAttributeVisitor(
|
||||
new DebugAttributeVisitor("Filling out fields, method parameters, and return values in synthetic classes",
|
||||
new PartialEvaluator(detailedValueFactory, storingInvocationUnit, false))))));
|
||||
PartialEvaluator.Builder.create()
|
||||
.setValueFactory(detailedValueFactory)
|
||||
.setInvocationUnit(storingInvocationUnit)
|
||||
.setEvaluateAllCode(false)
|
||||
.build())))));
|
||||
|
||||
// Evaluate non-synthetic classes. We may need to evaluate all
|
||||
// casts, to account for downcasts when specializing descriptors.
|
||||
@@ -959,10 +963,14 @@ public class Optimizer implements Pass
|
||||
new AllMethodVisitor(
|
||||
new AllAttributeVisitor(
|
||||
new DebugAttributeVisitor("Filling out fields, method parameters, and return values",
|
||||
new PartialEvaluator(valueFactory, storingInvocationUnit,
|
||||
fieldSpecializationType ||
|
||||
methodSpecializationParametertype ||
|
||||
methodSpecializationReturntype)))));
|
||||
PartialEvaluator.Builder.create()
|
||||
.setValueFactory(valueFactory)
|
||||
.setInvocationUnit(storingInvocationUnit)
|
||||
.setEvaluateAllCode(
|
||||
fieldSpecializationType ||
|
||||
methodSpecializationParametertype ||
|
||||
methodSpecializationReturntype)
|
||||
.build()))));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1056,7 +1064,11 @@ public class Optimizer implements Pass
|
||||
new ClassAccessFilter(AccessConstants.SYNTHETIC, 0,
|
||||
new AllMethodVisitor(
|
||||
new AllAttributeVisitor(
|
||||
new PartialEvaluator(valueFactory, loadingInvocationUnit, false)))));
|
||||
PartialEvaluator.Builder.create()
|
||||
.setValueFactory(valueFactory)
|
||||
.setInvocationUnit(loadingInvocationUnit)
|
||||
.setEvaluateAllCode(false)
|
||||
.build()))));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1083,7 +1095,11 @@ public class Optimizer implements Pass
|
||||
new DebugAttributeVisitor("Simplifying code",
|
||||
new OptimizationCodeAttributeFilter(
|
||||
new EvaluationSimplifier(
|
||||
new PartialEvaluator(valueFactory, loadingInvocationUnit, false),
|
||||
PartialEvaluator.Builder.create()
|
||||
.setValueFactory(valueFactory)
|
||||
.setInvocationUnit(loadingInvocationUnit)
|
||||
.setEvaluateAllCode(false)
|
||||
.build(),
|
||||
codeSimplificationAdvancedCounter,
|
||||
configuration.optimizeConservatively)))));
|
||||
}
|
||||
@@ -1125,11 +1141,14 @@ public class Optimizer implements Pass
|
||||
new OptimizationCodeAttributeFilter(
|
||||
new EvaluationShrinker(
|
||||
new InstructionUsageMarker(
|
||||
new PartialEvaluator(referenceTracingValueFactory,
|
||||
new ParameterTracingInvocationUnit(loadingInvocationUnit),
|
||||
!codeSimplificationAdvanced,
|
||||
referenceTracingValueFactory),
|
||||
PartialEvaluator.Builder.create()
|
||||
.setValueFactory(referenceTracingValueFactory)
|
||||
.setInvocationUnit(new ParameterTracingInvocationUnit(loadingInvocationUnit))
|
||||
.setEvaluateAllCode(!codeSimplificationAdvanced)
|
||||
.setExtraInstructionVisitor(referenceTracingValueFactory)
|
||||
.build(),
|
||||
true, configuration.optimizeConservatively), true, deletedCounter, addedCounter)))));
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -52,10 +52,12 @@ implements InfluenceFixpointVisitor.MemberVisitorFactory
|
||||
ReferenceTracingValueFactory referenceTracingValueFactory1 =
|
||||
new ReferenceTracingValueFactory(new TypedReferenceValueFactory());
|
||||
PartialEvaluator partialEvaluator =
|
||||
new PartialEvaluator(referenceTracingValueFactory1,
|
||||
new ParameterTracingInvocationUnit(new BasicInvocationUnit(referenceTracingValueFactory1)),
|
||||
false,
|
||||
referenceTracingValueFactory1);
|
||||
PartialEvaluator.Builder.create()
|
||||
.setValueFactory(referenceTracingValueFactory1)
|
||||
.setInvocationUnit(new ParameterTracingInvocationUnit(new BasicInvocationUnit(referenceTracingValueFactory1)))
|
||||
.setEvaluateAllCode(false)
|
||||
.setExtraInstructionVisitor(referenceTracingValueFactory1)
|
||||
.build();
|
||||
InstructionUsageMarker instructionUsageMarker =
|
||||
new InstructionUsageMarker(partialEvaluator, false, false);
|
||||
|
||||
|
||||
@@ -114,7 +114,7 @@ implements AttributeVisitor,
|
||||
*/
|
||||
public EvaluationShrinker()
|
||||
{
|
||||
this(new PartialEvaluator(), true, false, null, null);
|
||||
this(PartialEvaluator.Builder.create().build(), true, false, null, null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -48,8 +48,9 @@ public class EvaluationSimplifier
|
||||
implements AttributeVisitor,
|
||||
InstructionVisitor
|
||||
{
|
||||
private static final int POS_ZERO_FLOAT_BITS = Float.floatToIntBits(0.0f);
|
||||
private static final long POS_ZERO_DOUBLE_BITS = Double.doubleToLongBits(0.0);
|
||||
private static final boolean ENABLE_LOWER_SLOT_REPLACEMENT = System.getProperty("optimization.enable.slot.replacement") != null;
|
||||
private static final int POS_ZERO_FLOAT_BITS = Float.floatToIntBits(0.0f);
|
||||
private static final long POS_ZERO_DOUBLE_BITS = Double.doubleToLongBits(0.0);
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(EvaluationSimplifier.class);
|
||||
|
||||
@@ -58,7 +59,7 @@ implements AttributeVisitor,
|
||||
|
||||
private final PartialEvaluator partialEvaluator;
|
||||
private final SideEffectInstructionChecker sideEffectInstructionChecker;
|
||||
private final CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor(true, true);
|
||||
private final CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor(true, true);
|
||||
|
||||
|
||||
/**
|
||||
@@ -69,7 +70,7 @@ implements AttributeVisitor,
|
||||
*/
|
||||
public EvaluationSimplifier(boolean predictNullPointerExceptions)
|
||||
{
|
||||
this(new PartialEvaluator(), null, predictNullPointerExceptions);
|
||||
this(PartialEvaluator.Builder.create().build(), null, predictNullPointerExceptions);
|
||||
}
|
||||
|
||||
|
||||
@@ -692,7 +693,7 @@ implements AttributeVisitor,
|
||||
replaceInstruction(clazz, offset, instruction, replacementInstruction);
|
||||
}
|
||||
}
|
||||
else if (pushedValue.isSpecific())
|
||||
else if (ENABLE_LOWER_SLOT_REPLACEMENT && pushedValue.isSpecific())
|
||||
{
|
||||
// Load an equivalent lower-numbered variable instead, if any.
|
||||
TracedVariables variables = partialEvaluator.getVariablesBefore(offset);
|
||||
@@ -762,7 +763,7 @@ implements AttributeVisitor,
|
||||
replaceInstruction(clazz, offset, instruction, replacementInstruction);
|
||||
}
|
||||
}
|
||||
else if (pushedValue.isSpecific())
|
||||
else if (ENABLE_LOWER_SLOT_REPLACEMENT && pushedValue.isSpecific())
|
||||
{
|
||||
// Load an equivalent lower-numbered variable instead, if any.
|
||||
TracedVariables variables = partialEvaluator.getVariablesBefore(offset);
|
||||
@@ -836,7 +837,7 @@ implements AttributeVisitor,
|
||||
replaceInstruction(clazz, offset, instruction, replacementInstruction);
|
||||
}
|
||||
}
|
||||
else if (pushedValue.isSpecific())
|
||||
else if (ENABLE_LOWER_SLOT_REPLACEMENT && pushedValue.isSpecific())
|
||||
{
|
||||
// Load an equivalent lower-numbered variable instead, if any.
|
||||
TracedVariables variables = partialEvaluator.getVariablesBefore(offset);
|
||||
@@ -906,7 +907,7 @@ implements AttributeVisitor,
|
||||
replaceInstruction(clazz, offset, instruction, replacementInstruction);
|
||||
}
|
||||
}
|
||||
else if (pushedValue.isSpecific())
|
||||
else if (ENABLE_LOWER_SLOT_REPLACEMENT && pushedValue.isSpecific())
|
||||
{
|
||||
// Load an equivalent lower-numbered variable instead, if any.
|
||||
TracedVariables variables = partialEvaluator.getVariablesBefore(offset);
|
||||
|
||||
@@ -91,7 +91,7 @@ implements AttributeVisitor
|
||||
*/
|
||||
public InstructionUsageMarker(boolean markExternalSideEffects)
|
||||
{
|
||||
this(new PartialEvaluator(), true, true, markExternalSideEffects);
|
||||
this(PartialEvaluator.Builder.create().build(), true, true, markExternalSideEffects);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -129,9 +129,10 @@ implements AttributeVisitor
|
||||
this.ensureSafetyForVerifier = ensureSafetyForVerifier;
|
||||
this.markExternalSideEffects = markExternalSideEffects;
|
||||
this.sideEffectInstructionChecker = new SideEffectInstructionChecker(true, true, markExternalSideEffects);
|
||||
if (ensureSafetyForVerifier)
|
||||
{
|
||||
this.simplePartialEvaluator = new PartialEvaluator(new TypedReferenceValueFactory());
|
||||
if (ensureSafetyForVerifier) {
|
||||
this.simplePartialEvaluator = PartialEvaluator.Builder.create()
|
||||
.setValueFactory(new TypedReferenceValueFactory())
|
||||
.build();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -71,7 +71,7 @@ implements ClassVisitor,
|
||||
*/
|
||||
public SimpleEnumUseChecker()
|
||||
{
|
||||
this(new PartialEvaluator(new TypedReferenceValueFactory()));
|
||||
this(PartialEvaluator.Builder.create().setValueFactory(new TypedReferenceValueFactory()).build());
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ implements AttributeVisitor,
|
||||
*/
|
||||
public SimpleEnumUseSimplifier()
|
||||
{
|
||||
this(new PartialEvaluator(new TypedReferenceValueFactory()), null);
|
||||
this(PartialEvaluator.Builder.create().setValueFactory(new TypedReferenceValueFactory()).build(), null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -60,11 +60,13 @@ implements InstructionVisitor
|
||||
private final InstructionSequenceMatcher registerTypeAdapterFactoryMatcher;
|
||||
private final InstructionSequenceMatcher serializeSpecialFloatingPointValuesMatcher;
|
||||
private final TypedReferenceValueFactory valueFactory =
|
||||
new TypedReferenceValueFactory();
|
||||
new TypedReferenceValueFactory();
|
||||
private final PartialEvaluator partialEvaluator =
|
||||
new PartialEvaluator(valueFactory,
|
||||
new BasicInvocationUnit(new TypedReferenceValueFactory()),
|
||||
true);
|
||||
PartialEvaluator.Builder.create()
|
||||
.setValueFactory(valueFactory)
|
||||
.setInvocationUnit(new BasicInvocationUnit(valueFactory))
|
||||
.setEvaluateAllCode(true)
|
||||
.build();
|
||||
private final AttributeVisitor lazyPartialEvaluator =
|
||||
new AttributeNameFilter(Attribute.CODE,
|
||||
new SingleTimeAttributeVisitor(
|
||||
|
||||
@@ -49,11 +49,13 @@ implements MemberVisitor,
|
||||
|
||||
private final CodeAttributeEditor codeAttributeEditor;
|
||||
private final TypedReferenceValueFactory valueFactory =
|
||||
new TypedReferenceValueFactory();
|
||||
new TypedReferenceValueFactory();
|
||||
private final PartialEvaluator partialEvaluator =
|
||||
new PartialEvaluator(valueFactory,
|
||||
new BasicInvocationUnit(new TypedReferenceValueFactory()),
|
||||
true);
|
||||
PartialEvaluator.Builder.create()
|
||||
.setValueFactory(valueFactory)
|
||||
.setInvocationUnit(new BasicInvocationUnit(valueFactory))
|
||||
.setEvaluateAllCode(true)
|
||||
.build();
|
||||
private final AttributeVisitor lazyPartialEvaluator =
|
||||
new AttributeNameFilter(Attribute.CODE,
|
||||
new SingleTimeAttributeVisitor(
|
||||
|
||||
@@ -51,12 +51,14 @@ implements InstructionVisitor
|
||||
private final ClassVisitor domainClassVisitor;
|
||||
private final WarningPrinter warningPrinter;
|
||||
private final FromJsonInvocationMatcher[] fromJsonInvocationMatchers;
|
||||
private final TypedReferenceValueFactory valueFactory =
|
||||
new TypedReferenceValueFactory();
|
||||
private final PartialEvaluator partialEvaluator =
|
||||
new PartialEvaluator(valueFactory,
|
||||
new BasicInvocationUnit(new TypedReferenceValueFactory()),
|
||||
true);
|
||||
private final TypedReferenceValueFactory valueFactory =
|
||||
new TypedReferenceValueFactory();
|
||||
private final PartialEvaluator partialEvaluator =
|
||||
PartialEvaluator.Builder.create()
|
||||
.setValueFactory(valueFactory)
|
||||
.setInvocationUnit(new BasicInvocationUnit(valueFactory))
|
||||
.setEvaluateAllCode(true)
|
||||
.build();
|
||||
private final AttributeVisitor lazyPartialEvaluator =
|
||||
new AttributeNameFilter(Attribute.CODE,
|
||||
new SingleTimeAttributeVisitor(
|
||||
|
||||
@@ -53,11 +53,13 @@ implements InstructionVisitor
|
||||
private final WarningPrinter warningPrinter;
|
||||
private final ToJsonInvocationMatcher[] toJsonInvocationMatchers;
|
||||
private final TypedReferenceValueFactory valueFactory =
|
||||
new TypedReferenceValueFactory();
|
||||
new TypedReferenceValueFactory();
|
||||
private final PartialEvaluator partialEvaluator =
|
||||
new PartialEvaluator(valueFactory,
|
||||
new BasicInvocationUnit(new TypedReferenceValueFactory()),
|
||||
true);
|
||||
PartialEvaluator.Builder.create()
|
||||
.setValueFactory(valueFactory)
|
||||
.setInvocationUnit(new BasicInvocationUnit(valueFactory))
|
||||
.setEvaluateAllCode(true)
|
||||
.build();
|
||||
private final AttributeVisitor lazyPartialEvaluator =
|
||||
new AttributeNameFilter(Attribute.CODE,
|
||||
new SingleTimeAttributeVisitor(partialEvaluator));
|
||||
|
||||
@@ -77,10 +77,12 @@ implements AttributeVisitor,
|
||||
*/
|
||||
public EscapingClassMarker(ReferenceTracingValueFactory tracingValueFactory)
|
||||
{
|
||||
this(new PartialEvaluator(tracingValueFactory,
|
||||
new ReferenceTracingInvocationUnit(new BasicInvocationUnit(tracingValueFactory)),
|
||||
true,
|
||||
tracingValueFactory),
|
||||
this(PartialEvaluator.Builder.create()
|
||||
.setValueFactory(tracingValueFactory)
|
||||
.setInvocationUnit(new ReferenceTracingInvocationUnit(new BasicInvocationUnit(tracingValueFactory)))
|
||||
.setEvaluateAllCode(true)
|
||||
.setExtraInstructionVisitor(tracingValueFactory)
|
||||
.build(),
|
||||
true);
|
||||
}
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@ implements MemberVisitor,
|
||||
*/
|
||||
public ParameterEscapeMarker(ValueFactory valueFactory, MemberVisitor extraMemberVisitor)
|
||||
{
|
||||
this(valueFactory, new ReferenceTracingValueFactory(valueFactory), extraMemberVisitor
|
||||
this(new ReferenceTracingValueFactory(valueFactory), extraMemberVisitor
|
||||
);
|
||||
}
|
||||
|
||||
@@ -111,14 +111,15 @@ implements MemberVisitor,
|
||||
/**
|
||||
* Creates a new ParameterEscapeMarker.
|
||||
*/
|
||||
public ParameterEscapeMarker(ValueFactory valueFactory,
|
||||
ReferenceTracingValueFactory tracingValueFactory,
|
||||
public ParameterEscapeMarker(ReferenceTracingValueFactory tracingValueFactory,
|
||||
MemberVisitor extraMemberVisitor)
|
||||
{
|
||||
this(new PartialEvaluator(tracingValueFactory,
|
||||
new ParameterTracingInvocationUnit(new BasicInvocationUnit(tracingValueFactory)),
|
||||
true,
|
||||
tracingValueFactory), true, extraMemberVisitor
|
||||
this(PartialEvaluator.Builder.create()
|
||||
.setValueFactory(tracingValueFactory)
|
||||
.setInvocationUnit(new ParameterTracingInvocationUnit(new BasicInvocationUnit(tracingValueFactory)))
|
||||
.setEvaluateAllCode(true)
|
||||
.setExtraInstructionVisitor(tracingValueFactory)
|
||||
.build(), true, extraMemberVisitor
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -56,13 +56,14 @@ implements ClassPoolVisitor,
|
||||
private final ClassVisitor parameterEscapedMarker =
|
||||
new AllMethodVisitor(
|
||||
new AllAttributeVisitor(this));
|
||||
private final ValueFactory valueFactory = new BasicValueFactory();
|
||||
private final ReferenceTracingValueFactory tracingValueFactory = new ReferenceTracingValueFactory(valueFactory);
|
||||
private final ReferenceTracingValueFactory tracingValueFactory = new ReferenceTracingValueFactory(new BasicValueFactory());
|
||||
private final PartialEvaluator partialEvaluator =
|
||||
new PartialEvaluator(tracingValueFactory,
|
||||
new ParameterTracingInvocationUnit(new BasicInvocationUnit(tracingValueFactory)),
|
||||
true,
|
||||
tracingValueFactory);
|
||||
PartialEvaluator.Builder.create()
|
||||
.setValueFactory(tracingValueFactory)
|
||||
.setInvocationUnit(new ParameterTracingInvocationUnit(new BasicInvocationUnit(tracingValueFactory)))
|
||||
.setEvaluateAllCode(true)
|
||||
.setExtraInstructionVisitor(tracingValueFactory)
|
||||
.build();
|
||||
private final ReferenceEscapeChecker referenceEscapeChecker = new ReferenceEscapeChecker(partialEvaluator, false);
|
||||
|
||||
// Parameters and values for visitor methods.
|
||||
|
||||
@@ -50,7 +50,7 @@ implements MemberVisitor,
|
||||
private final boolean markThisParameter;
|
||||
private final boolean markAllParameters;
|
||||
private final boolean analyzeCode;
|
||||
private final PartialEvaluator partialEvaluator = new PartialEvaluator();
|
||||
private final PartialEvaluator partialEvaluator = PartialEvaluator.Builder.create().build();
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@@ -86,10 +86,13 @@ implements AttributeVisitor,
|
||||
*/
|
||||
private ReferenceEscapeChecker(ReferenceTracingValueFactory referenceTracingValueFactory)
|
||||
{
|
||||
this(new PartialEvaluator(referenceTracingValueFactory,
|
||||
new ParameterTracingInvocationUnit(new BasicInvocationUnit(referenceTracingValueFactory)),
|
||||
true,
|
||||
referenceTracingValueFactory),
|
||||
this(
|
||||
PartialEvaluator.Builder.create()
|
||||
.setValueFactory(referenceTracingValueFactory)
|
||||
.setInvocationUnit(new ParameterTracingInvocationUnit(new BasicInvocationUnit(referenceTracingValueFactory)))
|
||||
.setEvaluateAllCode(true)
|
||||
.setExtraInstructionVisitor(referenceTracingValueFactory)
|
||||
.build(),
|
||||
true);
|
||||
}
|
||||
|
||||
|
||||
@@ -108,8 +108,8 @@ public class KotlinContextReceiverUsageMarker implements
|
||||
public void visitAnyProperty(Clazz clazz, KotlinDeclarationContainerMetadata kotlinDeclarationContainerMetadata, KotlinPropertyMetadata kotlinPropertyMetadata)
|
||||
{
|
||||
markContextReceiverParameters(kotlinPropertyMetadata.contextReceivers,
|
||||
kotlinPropertyMetadata.referencedGetterMethod,
|
||||
kotlinPropertyMetadata.referencedSetterMethod);
|
||||
kotlinPropertyMetadata.getterMetadata.referencedMethod,
|
||||
kotlinPropertyMetadata.setterMetadata != null ? kotlinPropertyMetadata.setterMetadata.referencedMethod : null);
|
||||
}
|
||||
|
||||
private void markContextReceiverParameters(List<KotlinTypeMetadata> contextReceivers, Method...methods)
|
||||
|
||||
@@ -29,18 +29,19 @@ import proguard.classfile.ProgramClass;
|
||||
import proguard.classfile.ProgramMethod;
|
||||
import proguard.classfile.attribute.Attribute;
|
||||
import proguard.classfile.attribute.CodeAttribute;
|
||||
import proguard.classfile.attribute.ExtendedLineNumberInfo;
|
||||
import proguard.classfile.attribute.LineNumberInfo;
|
||||
import proguard.classfile.attribute.LineNumberInfoBlock;
|
||||
import proguard.classfile.attribute.LineNumberTableAttribute;
|
||||
import proguard.classfile.attribute.StructuredLineNumberInfo;
|
||||
import proguard.classfile.attribute.visitor.AllAttributeVisitor;
|
||||
import proguard.classfile.attribute.visitor.AllLineNumberInfoVisitor;
|
||||
import proguard.classfile.attribute.visitor.AttributeVisitor;
|
||||
import proguard.classfile.attribute.visitor.LineNumberInfoVisitor;
|
||||
import proguard.classfile.attribute.visitor.LineNumberRangeFinder;
|
||||
import proguard.classfile.visitor.ClassVisitor;
|
||||
import proguard.classfile.visitor.MemberVisitor;
|
||||
import proguard.pass.Pass;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Stack;
|
||||
|
||||
/**
|
||||
@@ -56,8 +57,7 @@ public class LineNumberLinearizer
|
||||
implements Pass,
|
||||
ClassVisitor,
|
||||
MemberVisitor,
|
||||
AttributeVisitor,
|
||||
LineNumberInfoVisitor
|
||||
AttributeVisitor
|
||||
{
|
||||
private static final Logger logger = LogManager.getLogger(LineNumberLinearizer.class);
|
||||
|
||||
@@ -76,16 +76,15 @@ implements Pass,
|
||||
* optimizations like method inlining and class merging.
|
||||
*/
|
||||
@Override
|
||||
public void execute(AppView appView)
|
||||
{
|
||||
public void execute(AppView appView) {
|
||||
appView.programClassPool.classesAccept(this);
|
||||
}
|
||||
|
||||
// Implementations for ClassVisitor.
|
||||
|
||||
@Override
|
||||
public void visitAnyClass(Clazz clazz) { }
|
||||
|
||||
public void visitAnyClass(Clazz clazz) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitProgramClass(ProgramClass programClass)
|
||||
@@ -109,26 +108,26 @@ implements Pass,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Implementations for MemberVisitor.
|
||||
|
||||
@Override
|
||||
public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
|
||||
{
|
||||
programMethod.attributesAccept(programClass, this);
|
||||
}
|
||||
|
||||
|
||||
// Implementations for AttributeVisitor.
|
||||
|
||||
@Override
|
||||
public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
|
||||
|
||||
|
||||
@Override
|
||||
public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
|
||||
{
|
||||
codeAttribute.attributesAccept(clazz, method, this);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void visitLineNumberTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberTableAttribute lineNumberTableAttribute)
|
||||
{
|
||||
logger.debug("LineNumberLinearizer [{}.{}{}]:",
|
||||
@@ -138,112 +137,142 @@ implements Pass,
|
||||
);
|
||||
|
||||
enclosingLineNumbers.clear();
|
||||
previousLineNumberInfo = null;
|
||||
|
||||
// Process all line numbers.
|
||||
lineNumberTableAttribute.lineNumbersAccept(clazz, method, codeAttribute, this);
|
||||
}
|
||||
// Figure out which lines need linearizing. Only freshly inlined blocks need to be linearized.
|
||||
|
||||
LineNumberInfo[] infos = lineNumberTableAttribute.lineNumberTable;
|
||||
int lineNumberTableLength = lineNumberTableAttribute.u2lineNumberTableLength;
|
||||
boolean[] inlinedBlock = new boolean[lineNumberTableLength];
|
||||
|
||||
// Implementations for LineNumberInfoVisitor.
|
||||
|
||||
public void visitLineNumberInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberInfo lineNumberInfo)
|
||||
{
|
||||
String source = lineNumberInfo.getSource();
|
||||
|
||||
String debugMessage = String.format(" [%s] line %s%s",
|
||||
lineNumberInfo.u2startPC,
|
||||
lineNumberInfo.u2lineNumber,
|
||||
source == null ? "" : " [" + source + "]"
|
||||
);
|
||||
|
||||
// Is it an inlined line number?
|
||||
if (source != null)
|
||||
int currentDepth = 0;
|
||||
for (int i = 0; i < lineNumberTableLength; i++)
|
||||
{
|
||||
ExtendedLineNumberInfo extendedLineNumberInfo =
|
||||
(ExtendedLineNumberInfo)lineNumberInfo;
|
||||
|
||||
int lineNumber = extendedLineNumberInfo.u2lineNumber;
|
||||
|
||||
// Are we entering or exiting a new inlined block?
|
||||
if (previousLineNumberInfo == null ||
|
||||
!source.equals(previousLineNumberInfo.getSource()))
|
||||
LineNumberInfo currentInfo = infos[i];
|
||||
if (currentInfo.u2lineNumber == MethodInliner.INLINED_METHOD_START_LINE_NUMBER)
|
||||
{
|
||||
currentDepth++;
|
||||
}
|
||||
|
||||
inlinedBlock[i] = currentDepth > 0;
|
||||
|
||||
if (currentInfo.u2lineNumber == MethodInliner.INLINED_METHOD_END_LINE_NUMBER)
|
||||
{
|
||||
currentDepth--;
|
||||
}
|
||||
}
|
||||
|
||||
// Linearize the line numbers.
|
||||
|
||||
LineNumberInfo previousLineNumberInfo = null;
|
||||
for (int i = 0; i < lineNumberTableLength; i++)
|
||||
{
|
||||
LineNumberInfo lineNumberInfo = infos[i];
|
||||
String source = lineNumberInfo.getSource();
|
||||
|
||||
logger.debug(" [{}] line {}{}", lineNumberInfo.u2startPC, lineNumberInfo.u2lineNumber, source == null ? "" : " [" + source + "]");
|
||||
|
||||
// Is it an inlined line number?
|
||||
if (source != null && inlinedBlock[i])
|
||||
{
|
||||
int lineNumber = lineNumberInfo.u2lineNumber;
|
||||
|
||||
// Are we entering a new inlined block?
|
||||
if (lineNumber != MethodInliner.INLINED_METHOD_END_LINE_NUMBER)
|
||||
if (lineNumber == MethodInliner.INLINED_METHOD_START_LINE_NUMBER)
|
||||
{
|
||||
// Remember information about the inlined block.
|
||||
enclosingLineNumbers.push(previousLineNumberInfo != null ?
|
||||
new MyLineNumberBlock(currentLineNumberShift,
|
||||
previousLineNumberInfo.u2lineNumber,
|
||||
previousLineNumberInfo.getSource()) :
|
||||
new MyLineNumberBlock(0, 0, null));
|
||||
enclosingLineNumbers.push(
|
||||
previousLineNumberInfo != null
|
||||
? new MyLineNumberBlock(
|
||||
currentLineNumberShift,
|
||||
previousLineNumberInfo.u2lineNumber,
|
||||
previousLineNumberInfo.getSource() != null
|
||||
? previousLineNumberInfo.getBlock()
|
||||
: null)
|
||||
: new MyLineNumberBlock(0, 0, null));
|
||||
|
||||
// Parse the end line number from the source string,
|
||||
// so we know how large a block this will be.
|
||||
// Parse the end line number from the source string, so we know how large a block this
|
||||
// will be.
|
||||
int separatorIndex1 = source.indexOf(':');
|
||||
int separatorIndex2 = source.indexOf(':', separatorIndex1 + 1);
|
||||
|
||||
int startLineNumber = Integer.parseInt(source.substring(separatorIndex1 + 1, separatorIndex2));
|
||||
int endLineNumber = Integer.parseInt(source.substring(separatorIndex2 + 1));
|
||||
int startLineNumber =
|
||||
Integer.parseInt(source.substring(separatorIndex1 + 1, separatorIndex2));
|
||||
int endLineNumber = Integer.parseInt(source.substring(separatorIndex2 + 1));
|
||||
|
||||
// Start shifting, if necessary, so the block ends up beyond
|
||||
// the highest used line number. We're striving for rounded
|
||||
// shifts, unless we've reached a given limit, to avoid
|
||||
// running out of line numbers too quickly.
|
||||
// TODO: this matches a quirk in the old behavior where the opening line is always :0:0
|
||||
// this is a bug that probably causes overlapping line numbers but for now we will match
|
||||
// this behavior so we can directly compare old and new mappings.
|
||||
startLineNumber = 0;
|
||||
endLineNumber = 0;
|
||||
|
||||
// Start shifting, if necessary, so the block ends up beyond the highest used line number.
|
||||
// We're striving for rounded shifts, unless we've reached a given limit, to avoid running
|
||||
// out of line numbers too quickly.
|
||||
currentLineNumberShift =
|
||||
highestUsedLineNumber > SHIFT_ROUNDING_LIMIT ?
|
||||
highestUsedLineNumber - startLineNumber + 1 :
|
||||
startLineNumber > highestUsedLineNumber ? 0 :
|
||||
(highestUsedLineNumber - startLineNumber + SHIFT_ROUNDING)
|
||||
/ SHIFT_ROUNDING * SHIFT_ROUNDING;
|
||||
highestUsedLineNumber > SHIFT_ROUNDING_LIMIT
|
||||
? highestUsedLineNumber - startLineNumber + 1
|
||||
: startLineNumber > highestUsedLineNumber
|
||||
? 0
|
||||
: (highestUsedLineNumber - startLineNumber + SHIFT_ROUNDING)
|
||||
/ SHIFT_ROUNDING
|
||||
* SHIFT_ROUNDING;
|
||||
|
||||
highestUsedLineNumber = endLineNumber + currentLineNumberShift;
|
||||
|
||||
debugMessage += String.format(" (enter with shift %s)", currentLineNumberShift);
|
||||
|
||||
logger.debug(" (enter with shift {})", currentLineNumberShift);
|
||||
}
|
||||
|
||||
// Are we exiting an inlined block?
|
||||
else if (lineNumber == MethodInliner.INLINED_METHOD_END_LINE_NUMBER)
|
||||
{
|
||||
// TODO: There appear to be cases where the stack is empty at this point, so we've added a
|
||||
// check.
|
||||
if (enclosingLineNumbers.isEmpty())
|
||||
{
|
||||
logger.debug("Problem linearizing line numbers for optimized code ({}.{})", clazz.getName(), method.getName(clazz));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Pop information about the enclosing line number.
|
||||
MyLineNumberBlock lineNumberBlock = enclosingLineNumbers.pop();
|
||||
|
||||
// Set this end of the block to the line at which it was inlined.
|
||||
lineNumberInfo =
|
||||
lineNumberBlock.enclosingSource != null
|
||||
? lineNumberBlock.enclosingSource.line(
|
||||
lineNumberInfo.u2startPC, lineNumberBlock.enclosingLineNumber)
|
||||
: new LineNumberInfo(
|
||||
lineNumberInfo.u2startPC, lineNumberBlock.enclosingLineNumber);
|
||||
infos[i] = lineNumberInfo;
|
||||
|
||||
// Reset the shift to the shift of the block.
|
||||
currentLineNumberShift = lineNumberBlock.lineNumberShift;
|
||||
|
||||
logger.debug(" (exit to shift {})", currentLineNumberShift);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.debug(" (apply shift {})", currentLineNumberShift);
|
||||
|
||||
|
||||
// Apply the shift.
|
||||
lineNumberInfo.u2lineNumber += currentLineNumberShift;
|
||||
}
|
||||
|
||||
// TODO: There appear to be cases where the stack is empty at this point, so we've added a check.
|
||||
else if (enclosingLineNumbers.isEmpty())
|
||||
{
|
||||
debugMessage += String.format("Problem linearizing line numbers for optimized code %s.%s)", clazz.getName(), method.getName(clazz));
|
||||
logger.debug(debugMessage);
|
||||
debugMessage = "";
|
||||
}
|
||||
|
||||
// Are we exiting an inlined block?
|
||||
else
|
||||
{
|
||||
// Pop information about the enclosing line number.
|
||||
MyLineNumberBlock lineNumberBlock = enclosingLineNumbers.pop();
|
||||
|
||||
// Set this end of the block to the line at which it was
|
||||
// inlined.
|
||||
extendedLineNumberInfo.u2lineNumber = lineNumberBlock.enclosingLineNumber;
|
||||
extendedLineNumberInfo.source = lineNumberBlock.enclosingSource;
|
||||
|
||||
// Reset the shift to the shift of the block.
|
||||
currentLineNumberShift = lineNumberBlock.lineNumberShift;
|
||||
|
||||
debugMessage += String.format(" (exit to shift %s)", currentLineNumberShift);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
debugMessage += String.format(" (apply shift %s)", currentLineNumberShift);
|
||||
|
||||
// Apply the shift.
|
||||
lineNumberInfo.u2lineNumber += currentLineNumberShift;
|
||||
}
|
||||
previousLineNumberInfo = lineNumberInfo;
|
||||
|
||||
logger.debug(" -> line {}", lineNumberInfo.u2lineNumber);
|
||||
}
|
||||
|
||||
previousLineNumberInfo = lineNumberInfo;
|
||||
|
||||
debugMessage += String.format(" -> line %s", lineNumberInfo.u2lineNumber);
|
||||
logger.debug(debugMessage);
|
||||
lineNumberTableAttribute.lineNumberTable =
|
||||
Arrays.stream(infos, 0, lineNumberTableLength)
|
||||
.filter(info -> info.u2lineNumber != MethodInliner.INLINED_METHOD_START_LINE_NUMBER)
|
||||
.toArray(LineNumberInfo[]::new);
|
||||
lineNumberTableAttribute.u2lineNumberTableLength =
|
||||
lineNumberTableAttribute.lineNumberTable.length;
|
||||
}
|
||||
|
||||
|
||||
@@ -253,14 +282,13 @@ implements Pass,
|
||||
*/
|
||||
private static class MyLineNumberBlock
|
||||
{
|
||||
public final int lineNumberShift;
|
||||
public final int enclosingLineNumber;
|
||||
public final String enclosingSource;
|
||||
public final int lineNumberShift;
|
||||
public final int enclosingLineNumber;
|
||||
public final LineNumberInfoBlock enclosingSource;
|
||||
|
||||
public MyLineNumberBlock(int lineNumberShift,
|
||||
int enclosingLineNumber,
|
||||
String enclosingSource)
|
||||
{
|
||||
public MyLineNumberBlock(int lineNumberShift,
|
||||
int enclosingLineNumber,
|
||||
LineNumberInfoBlock enclosingSource) {
|
||||
this.lineNumberShift = lineNumberShift;
|
||||
this.enclosingLineNumber = enclosingLineNumber;
|
||||
this.enclosingSource = enclosingSource;
|
||||
|
||||
@@ -40,6 +40,8 @@ import proguard.util.ProcessingFlags;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
import java.util.Stack;
|
||||
|
||||
/**
|
||||
@@ -70,8 +72,9 @@ implements AttributeVisitor,
|
||||
protected static final int MAXIMUM_RESULTING_CODE_LENGTH_JME = Integer.parseInt(System.getProperty("maximum.resulting.code.length", "2000"));
|
||||
protected static final int MAXIMUM_RESULTING_CODE_LENGTH_JVM = 65535;
|
||||
|
||||
static final int METHOD_DUMMY_START_LINE_NUMBER = 0;
|
||||
static final int INLINED_METHOD_END_LINE_NUMBER = -1;
|
||||
static final int METHOD_DUMMY_START_LINE_NUMBER = 0;
|
||||
public static final int INLINED_METHOD_END_LINE_NUMBER = -1;
|
||||
public static final int INLINED_METHOD_START_LINE_NUMBER = -2;
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(MethodInliner.class);
|
||||
|
||||
@@ -94,23 +97,23 @@ implements AttributeVisitor,
|
||||
new MethodInvocationMarker());
|
||||
private final StackSizeComputer stackSizeComputer = new StackSizeComputer();
|
||||
|
||||
private ProgramClass targetClass;
|
||||
private ProgramMethod targetMethod;
|
||||
private ConstantAdder constantAdder;
|
||||
private ExceptionInfoAdder exceptionInfoAdder;
|
||||
private int estimatedResultingCodeLength;
|
||||
private boolean inlining;
|
||||
private Stack inliningMethods = new Stack();
|
||||
private boolean emptyInvokingStack;
|
||||
private boolean coveredByCatchAllHandler;
|
||||
private int exceptionInfoCount;
|
||||
private int uninitializedObjectCount;
|
||||
private int variableOffset;
|
||||
private boolean inlined;
|
||||
private boolean inlinedAny;
|
||||
private boolean copiedLineNumbers;
|
||||
private String source;
|
||||
private int minimumLineNumberIndex;
|
||||
private ProgramClass targetClass;
|
||||
private ProgramMethod targetMethod;
|
||||
private ConstantAdder constantAdder;
|
||||
private ExceptionInfoAdder exceptionInfoAdder;
|
||||
private int estimatedResultingCodeLength;
|
||||
private boolean inlining;
|
||||
private Stack inliningMethods = new Stack();
|
||||
private boolean emptyInvokingStack;
|
||||
private boolean coveredByCatchAllHandler;
|
||||
private int exceptionInfoCount;
|
||||
private int uninitializedObjectCount;
|
||||
private int variableOffset;
|
||||
private boolean inlined;
|
||||
private boolean inlinedAny;
|
||||
private boolean copiedLineNumbers;
|
||||
private final Deque<LineNumberInfoBlock> sourceBlock = new ArrayDeque<>();
|
||||
private int minimumLineNumberIndex;
|
||||
|
||||
|
||||
/**
|
||||
@@ -322,15 +325,6 @@ implements AttributeVisitor,
|
||||
|
||||
public void visitLineNumberTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberTableAttribute lineNumberTableAttribute)
|
||||
{
|
||||
// Remember the source if we're inlining a method.
|
||||
source = inlining ?
|
||||
clazz.getName() + '.' +
|
||||
method.getName(clazz) +
|
||||
method.getDescriptor(clazz) + ':' +
|
||||
lineNumberTableAttribute.getLowestLineNumber() + ':' +
|
||||
lineNumberTableAttribute.getHighestLineNumber() :
|
||||
null;
|
||||
|
||||
// Insert all line numbers, possibly partly before previously inserted
|
||||
// line numbers.
|
||||
lineNumberTableAttribute.lineNumbersAccept(clazz, method, codeAttribute, this);
|
||||
@@ -452,39 +446,61 @@ implements AttributeVisitor,
|
||||
|
||||
codeAttribute.attributesAccept(clazz, method, this);
|
||||
|
||||
// Add a marker at the start of the method. The LineNumberLinearizer relies on this to detect
|
||||
// inlined blocks.
|
||||
if (inlining)
|
||||
{
|
||||
LineNumberTableAttribute lineNumberTableAttribute =
|
||||
(LineNumberTableAttribute) codeAttribute.getAttribute(clazz, Attribute.LINE_NUMBER_TABLE);
|
||||
int lowest = 0;
|
||||
int highest = 0;
|
||||
if (lineNumberTableAttribute != null)
|
||||
{
|
||||
lowest = lineNumberTableAttribute.getLowestLineNumber();
|
||||
highest = lineNumberTableAttribute.getHighestLineNumber();
|
||||
}
|
||||
|
||||
StructuredLineNumberInfo.Block block =
|
||||
new StructuredLineNumberInfo.Block(
|
||||
ProGuardOrigin.INLINED,
|
||||
clazz.getName() + '.' + method.getName(clazz) + method.getDescriptor(clazz),
|
||||
lowest,
|
||||
highest);
|
||||
sourceBlock.push(block);
|
||||
|
||||
// Insert a start marker.
|
||||
LineNumberInfo startLineNumberInfo = block.line(0, INLINED_METHOD_START_LINE_NUMBER);
|
||||
minimumLineNumberIndex =
|
||||
codeAttributeComposer.insertLineNumber(minimumLineNumberIndex, startLineNumberInfo) + 1;
|
||||
}
|
||||
|
||||
codeAttribute.attributesAccept(clazz, method, this);
|
||||
|
||||
// Make sure we at least have some entry at the start of the method.
|
||||
if (!copiedLineNumbers)
|
||||
{
|
||||
String source = inlining ?
|
||||
clazz.getName() + '.' +
|
||||
method.getName(clazz) +
|
||||
method.getDescriptor(clazz) +
|
||||
":0:0" :
|
||||
null;
|
||||
|
||||
LineNumberInfo line =
|
||||
inlining
|
||||
? sourceBlock.peekLast().line(0, METHOD_DUMMY_START_LINE_NUMBER)
|
||||
: new LineNumberInfo(0, METHOD_DUMMY_START_LINE_NUMBER);
|
||||
minimumLineNumberIndex =
|
||||
codeAttributeComposer.insertLineNumber(minimumLineNumberIndex,
|
||||
new ExtendedLineNumberInfo(0,
|
||||
METHOD_DUMMY_START_LINE_NUMBER,
|
||||
source)) + 1;
|
||||
codeAttributeComposer.insertLineNumber(minimumLineNumberIndex, line) + 1;
|
||||
}
|
||||
|
||||
// Add a marker at the end of an inlined method.
|
||||
// The marker will be corrected in LineNumberLinearizer,
|
||||
// so it points to the line of the enclosing method.
|
||||
// Add a marker at the end of an inlined method. The marker will be corrected in
|
||||
// LineNumberLinearizer, so it points to the line of the enclosing method.
|
||||
if (inlining)
|
||||
{
|
||||
String source =
|
||||
clazz.getName() + '.' +
|
||||
method.getName(clazz) +
|
||||
method.getDescriptor(clazz) +
|
||||
":0:0";
|
||||
clazz.getName() + '.' + method.getName(clazz) + method.getDescriptor(clazz) + ":0:0";
|
||||
|
||||
minimumLineNumberIndex =
|
||||
codeAttributeComposer.insertLineNumber(minimumLineNumberIndex,
|
||||
new ExtendedLineNumberInfo(codeAttribute.u4codeLength,
|
||||
INLINED_METHOD_END_LINE_NUMBER,
|
||||
source)) + 1;
|
||||
codeAttributeComposer.insertLineNumber(
|
||||
minimumLineNumberIndex,
|
||||
sourceBlock
|
||||
.pop()
|
||||
.line(codeAttribute.u4codeLength, INLINED_METHOD_END_LINE_NUMBER))
|
||||
+ 1;
|
||||
}
|
||||
|
||||
codeAttributeComposer.endCodeFragment();
|
||||
@@ -826,19 +842,30 @@ implements AttributeVisitor,
|
||||
{
|
||||
try
|
||||
{
|
||||
String newSource = lineNumberInfo.getSource() != null ?
|
||||
lineNumberInfo.getSource() :
|
||||
source;
|
||||
LineNumberInfoBlock block = sourceBlock.peekLast();
|
||||
boolean newSource = block != null;
|
||||
boolean preserveSource = lineNumberInfo.getSource() != null;
|
||||
|
||||
LineNumberInfo newLineNumberInfo = newSource != null ?
|
||||
new ExtendedLineNumberInfo(lineNumberInfo.u2startPC,
|
||||
lineNumberInfo.u2lineNumber,
|
||||
newSource) :
|
||||
new LineNumberInfo(lineNumberInfo.u2startPC,
|
||||
lineNumberInfo.u2lineNumber);
|
||||
LineNumberInfo newLineNumberInfo;
|
||||
if (preserveSource)
|
||||
{
|
||||
newLineNumberInfo =
|
||||
((StructuredLineNumberInfo) lineNumberInfo)
|
||||
.getBlock(ProGuardOrigin.INLINED)
|
||||
.line(lineNumberInfo.u2startPC, lineNumberInfo.u2lineNumber);
|
||||
}
|
||||
else if (newSource)
|
||||
{
|
||||
newLineNumberInfo = block.line(lineNumberInfo.u2startPC, lineNumberInfo.u2lineNumber);
|
||||
}
|
||||
else
|
||||
{
|
||||
newLineNumberInfo =
|
||||
new LineNumberInfo(lineNumberInfo.u2startPC, lineNumberInfo.u2lineNumber);
|
||||
}
|
||||
|
||||
minimumLineNumberIndex =
|
||||
codeAttributeComposer.insertLineNumber(minimumLineNumberIndex, newLineNumberInfo) + 1;
|
||||
codeAttributeComposer.insertLineNumber(minimumLineNumberIndex, newLineNumberInfo) + 1;
|
||||
}
|
||||
catch (IllegalArgumentException e)
|
||||
{
|
||||
|
||||
@@ -1869,11 +1869,13 @@ implements ClassVisitor,
|
||||
kotlinPropertyMetadata.referencedBackingField != null &&
|
||||
isUsed(kotlinPropertyMetadata.referencedBackingField);
|
||||
boolean getterUsed =
|
||||
kotlinPropertyMetadata.referencedGetterMethod != null &&
|
||||
isUsed(kotlinPropertyMetadata.referencedGetterMethod);
|
||||
kotlinPropertyMetadata.getterMetadata != null &&
|
||||
kotlinPropertyMetadata.getterMetadata.referencedMethod != null &&
|
||||
isUsed(kotlinPropertyMetadata.getterMetadata.referencedMethod);
|
||||
boolean setterUsed =
|
||||
kotlinPropertyMetadata.referencedSetterMethod != null &&
|
||||
isUsed(kotlinPropertyMetadata.referencedSetterMethod);
|
||||
kotlinPropertyMetadata.setterMetadata != null &&
|
||||
kotlinPropertyMetadata.setterMetadata.referencedMethod != null &&
|
||||
isUsed(kotlinPropertyMetadata.setterMetadata.referencedMethod);
|
||||
|
||||
if (backingFieldUsed || getterUsed || setterUsed)
|
||||
{
|
||||
@@ -1886,9 +1888,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())
|
||||
@@ -1913,7 +1916,7 @@ implements ClassVisitor,
|
||||
kotlinPropertyMetadata.receiverTypeAccept( clazz, kotlinDeclarationContainerMetadata, this);
|
||||
kotlinPropertyMetadata.contextReceiverTypesAccept(clazz, kotlinDeclarationContainerMetadata, this);
|
||||
kotlinPropertyMetadata.typeParametersAccept( clazz, kotlinDeclarationContainerMetadata, this);
|
||||
kotlinPropertyMetadata.setterParametersAccept( clazz, kotlinDeclarationContainerMetadata, this);
|
||||
kotlinPropertyMetadata.setterParameterAccept( clazz, kotlinDeclarationContainerMetadata, this);
|
||||
kotlinPropertyMetadata.typeAccept( clazz, kotlinDeclarationContainerMetadata, this);
|
||||
kotlinPropertyMetadata.versionRequirementAccept( clazz, kotlinDeclarationContainerMetadata, this);
|
||||
}
|
||||
@@ -1971,23 +1974,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 +2071,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);
|
||||
|
||||
@@ -26,6 +26,7 @@ import proguard.classfile.kotlin.KotlinClassKindMetadata;
|
||||
import proguard.classfile.kotlin.KotlinConstants;
|
||||
import proguard.classfile.kotlin.KotlinConstructorMetadata;
|
||||
import proguard.classfile.kotlin.KotlinDeclarationContainerMetadata;
|
||||
import proguard.classfile.kotlin.KotlinEnumEntryMetadata;
|
||||
import proguard.classfile.kotlin.KotlinFileFacadeKindMetadata;
|
||||
import proguard.classfile.kotlin.KotlinFunctionMetadata;
|
||||
import proguard.classfile.kotlin.KotlinMetadata;
|
||||
@@ -38,6 +39,7 @@ import proguard.classfile.kotlin.KotlinTypeMetadata;
|
||||
import proguard.classfile.kotlin.KotlinTypeParameterMetadata;
|
||||
import proguard.classfile.kotlin.KotlinValueParameterMetadata;
|
||||
import proguard.classfile.kotlin.KotlinVersionRequirementMetadata;
|
||||
import proguard.classfile.kotlin.flags.KotlinPropertyAccessorMetadata;
|
||||
import proguard.classfile.kotlin.visitor.AllTypeVisitor;
|
||||
import proguard.classfile.kotlin.visitor.KotlinConstructorVisitor;
|
||||
import proguard.classfile.kotlin.visitor.KotlinFunctionVisitor;
|
||||
@@ -110,8 +112,7 @@ implements KotlinMetadataVisitor,
|
||||
|
||||
shrinkMetadataArray(kotlinClassKindMetadata.constructors);
|
||||
|
||||
shrinkArray(kotlinClassKindMetadata.enumEntryNames,
|
||||
kotlinClassKindMetadata.referencedEnumEntries);
|
||||
shrinkEnumEntries(kotlinClassKindMetadata.enumEntries);
|
||||
|
||||
shrinkArray(kotlinClassKindMetadata.nestedClassNames,
|
||||
kotlinClassKindMetadata.referencedNestedClasses);
|
||||
@@ -167,7 +168,7 @@ implements KotlinMetadataVisitor,
|
||||
{
|
||||
kotlinPropertyMetadata.versionRequirementAccept( clazz, kotlinDeclarationContainerMetadata, this);
|
||||
kotlinPropertyMetadata.typeAccept( clazz, kotlinDeclarationContainerMetadata, this);
|
||||
kotlinPropertyMetadata.setterParametersAccept( clazz, kotlinDeclarationContainerMetadata, this);
|
||||
kotlinPropertyMetadata.setterParameterAccept( clazz, kotlinDeclarationContainerMetadata, this);
|
||||
kotlinPropertyMetadata.receiverTypeAccept( clazz, kotlinDeclarationContainerMetadata, this);
|
||||
kotlinPropertyMetadata.contextReceiverTypesAccept(clazz, kotlinDeclarationContainerMetadata, this);
|
||||
kotlinPropertyMetadata.typeParametersAccept( clazz, kotlinDeclarationContainerMetadata, this);
|
||||
@@ -179,21 +180,18 @@ implements KotlinMetadataVisitor,
|
||||
kotlinPropertyMetadata.referencedBackingField = null;
|
||||
}
|
||||
|
||||
if (shouldShrinkMetadata(kotlinPropertyMetadata.getterSignature,
|
||||
kotlinPropertyMetadata.referencedGetterMethod))
|
||||
if (shouldShrinkMetadata(kotlinPropertyMetadata.getterMetadata))
|
||||
{
|
||||
kotlinPropertyMetadata.getterSignature = null;
|
||||
kotlinPropertyMetadata.referencedGetterMethod = null;
|
||||
kotlinPropertyMetadata.flags.hasGetter = false;
|
||||
kotlinPropertyMetadata.getterMetadata.signature = null;
|
||||
kotlinPropertyMetadata.getterMetadata.referencedMethod = null;
|
||||
}
|
||||
|
||||
if (shouldShrinkMetadata(kotlinPropertyMetadata.setterSignature,
|
||||
kotlinPropertyMetadata.referencedSetterMethod))
|
||||
if (shouldShrinkMetadata(kotlinPropertyMetadata.setterMetadata))
|
||||
{
|
||||
kotlinPropertyMetadata.setterSignature = null;
|
||||
kotlinPropertyMetadata.referencedSetterMethod = null;
|
||||
kotlinPropertyMetadata.flags.hasSetter = false;
|
||||
kotlinPropertyMetadata.setterParameters.clear();
|
||||
kotlinPropertyMetadata.setterMetadata.signature = null;
|
||||
kotlinPropertyMetadata.setterMetadata.referencedMethod = null;
|
||||
kotlinPropertyMetadata.flags.isVar = false;
|
||||
kotlinPropertyMetadata.setterParameter = null;
|
||||
}
|
||||
|
||||
kotlinPropertyMetadata.versionRequirementAccept(clazz,
|
||||
@@ -206,7 +204,7 @@ implements KotlinMetadataVisitor,
|
||||
kotlinPropertyMetadata.syntheticMethodForAnnotations = null;
|
||||
kotlinPropertyMetadata.referencedSyntheticMethodForAnnotations = null;
|
||||
kotlinPropertyMetadata.referencedSyntheticMethodClass = null;
|
||||
kotlinPropertyMetadata.flags.common.hasAnnotations = false;
|
||||
kotlinPropertyMetadata.annotations.clear();
|
||||
}
|
||||
|
||||
if (kotlinPropertyMetadata.syntheticMethodForDelegate != null &&
|
||||
@@ -220,8 +218,10 @@ implements KotlinMetadataVisitor,
|
||||
// Fix inconsistencies that were introduced as
|
||||
// a result of shrinking
|
||||
if (kotlinPropertyMetadata.referencedBackingField != null &&
|
||||
kotlinPropertyMetadata.getterSignature == null &&
|
||||
kotlinPropertyMetadata.setterSignature == null &&
|
||||
(kotlinPropertyMetadata.setterMetadata == null ||
|
||||
kotlinPropertyMetadata.setterMetadata.signature == null) &&
|
||||
(kotlinPropertyMetadata.getterMetadata == null ||
|
||||
kotlinPropertyMetadata.getterMetadata.signature == null) &&
|
||||
(kotlinPropertyMetadata.referencedBackingField.getAccessFlags() & AccessConstants.PRIVATE) != 0 &&
|
||||
!kotlinPropertyMetadata.flags.visibility.isPrivate)
|
||||
{
|
||||
@@ -399,6 +399,11 @@ implements KotlinMetadataVisitor,
|
||||
!usageMarker.isUsed(jvmElement);
|
||||
}
|
||||
|
||||
private boolean shouldShrinkMetadata(KotlinPropertyAccessorMetadata kotlinPropertyAccessorMetadata) {
|
||||
return kotlinPropertyAccessorMetadata != null && !usageMarker.isUsed(kotlinPropertyAccessorMetadata.referencedMethod);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Shrinks elements and their corresponding referenced element, based on
|
||||
@@ -412,10 +417,13 @@ implements KotlinMetadataVisitor,
|
||||
shrinkArray(usageMarker, elements, referencedJavaElements);
|
||||
}
|
||||
|
||||
private void shrinkEnumEntries(List<KotlinEnumEntryMetadata> enumEntries) {
|
||||
enumEntries.removeIf(usageMarker::isUsed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shrinks elements and their corresponding referenced element, based on
|
||||
* markings on the referenced element.
|
||||
*
|
||||
* List is modified - must be a modifiable list!
|
||||
*/
|
||||
static void shrinkArray(SimpleUsageMarker usageMarker,
|
||||
|
||||
@@ -106,6 +106,10 @@ public class UsageMarker
|
||||
classUsageMarker))
|
||||
));
|
||||
|
||||
// Mark interfaces that have to be kept. This must be before the NestUsageMarker call right after,
|
||||
// see https://github.com/Guardsquare/proguard/issues/501.
|
||||
programClassPool.classesAccept(new InterfaceUsageMarker(classUsageMarker));
|
||||
|
||||
// Mark the elements of Kotlin metadata that need to be kept.
|
||||
if (configuration.keepKotlinMetadata)
|
||||
{
|
||||
@@ -114,7 +118,6 @@ public class UsageMarker
|
||||
new ReferencedKotlinMetadataVisitor(
|
||||
classUsageMarker));
|
||||
}
|
||||
|
||||
// Mark the inner class and annotation information that has to be kept.
|
||||
programClassPool.classesAccept(
|
||||
new UsedClassFilter(simpleUsageMarker,
|
||||
@@ -126,7 +129,8 @@ public class UsageMarker
|
||||
new LocalVariableTypeUsageMarker(classUsageMarker)
|
||||
))));
|
||||
|
||||
// Mark interfaces that have to be kept.
|
||||
// Second Interface Usage marking, this is necessary for marking interface constants that are not directly referenced
|
||||
// (e.g. interfaces only referenced through annotations). See https://github.com/Guardsquare/proguard/issues/508.
|
||||
programClassPool.classesAccept(new InterfaceUsageMarker(classUsageMarker));
|
||||
|
||||
if (configuration.keepKotlinMetadata)
|
||||
|
||||
@@ -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,22 +29,22 @@ class ConfigurationWriterTest : FreeSpec({
|
||||
|
||||
"Keep rules tests" - {
|
||||
"Keep class constructor should be kept" {
|
||||
val rules = "-keep class * {$EOL <init>();$EOL}"
|
||||
val rules = "-keep class * {$eol <init>();$eol}"
|
||||
val out = printConfiguration(rules)
|
||||
out shouldBe rules
|
||||
}
|
||||
|
||||
"Keep class initializer should be kept" {
|
||||
val rules = "-keep class * {$EOL <clinit>();$EOL}"
|
||||
val rules = "-keep class * {$eol <clinit>();$eol}"
|
||||
val out = printConfiguration(rules)
|
||||
val expected = "-keep class * {$EOL void <clinit>();$EOL}"
|
||||
val expected = "-keep class * {$eol void <clinit>();$eol}"
|
||||
out shouldBe expected
|
||||
}
|
||||
|
||||
"Keep class initializer should respect allowobfuscation flag" {
|
||||
val rules = "-keep,allowobfuscation class ** extends com.example.A {$EOL <clinit>();$EOL}"
|
||||
val rules = "-keep,allowobfuscation class ** extends com.example.A {$eol <clinit>();$eol}"
|
||||
val out = printConfiguration(rules)
|
||||
val expected = "-keep,allowobfuscation class ** extends com.example.A {$EOL void <clinit>();$EOL}"
|
||||
val expected = "-keep,allowobfuscation class ** extends com.example.A {$eol void <clinit>();$eol}"
|
||||
out shouldBe expected
|
||||
}
|
||||
}
|
||||
@@ -54,11 +55,11 @@ class ConfigurationWriterTest : FreeSpec({
|
||||
}
|
||||
|
||||
"Comments should not be quoted" {
|
||||
printConfiguration("# comment$EOL-keep class **") shouldBe "# comment$EOL-keep class **"
|
||||
printConfiguration("# comment$eol-keep class **") shouldBe "# comment$eol-keep class **"
|
||||
}
|
||||
|
||||
"Hash characters in comments should not be quoted" {
|
||||
printConfiguration("# #comment$EOL-keep class **") shouldBe "# #comment$EOL-keep class **"
|
||||
printConfiguration("# #comment$eol-keep class **") shouldBe "# #comment$eol-keep class **"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,26 +9,27 @@ import testutils.RequiresJavaVersion
|
||||
@RequiresJavaVersion(19, 19)
|
||||
class Java19RecordPatternTest : FreeSpec({
|
||||
"Given a class with Java record pattern" - {
|
||||
val (programClassPool, _) = ClassPoolBuilder.fromSource(
|
||||
JavaSource(
|
||||
"Test.java",
|
||||
"""
|
||||
public class Test {
|
||||
public static void main(String[] args) {
|
||||
printPoint(new Point(1, 2));
|
||||
}
|
||||
|
||||
private static void printPoint(Object o) {
|
||||
if (o instanceof (Point(int x, int y) p)) {
|
||||
System.out.println(p + " = " + (x + y));
|
||||
val (programClassPool, _) =
|
||||
ClassPoolBuilder.fromSource(
|
||||
JavaSource(
|
||||
"Test.java",
|
||||
"""
|
||||
public class Test {
|
||||
public static void main(String[] args) {
|
||||
printPoint(new Point(1, 2));
|
||||
}
|
||||
|
||||
private static void printPoint(Object o) {
|
||||
if (o instanceof (Point(int x, int y) p)) {
|
||||
System.out.println(p + " = " + (x + y));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
record Point(int x, int y) {}
|
||||
""".trimIndent()
|
||||
),
|
||||
javacArguments = listOf("--enable-preview", "--release", "19")
|
||||
)
|
||||
record Point(int x, int y) {}
|
||||
""".trimIndent(),
|
||||
),
|
||||
javacArguments = listOf("--enable-preview", "--release", "19"),
|
||||
)
|
||||
|
||||
"Then ProGuard should parse the class correctly" {
|
||||
programClassPool.getClass("Test") shouldNotBe null
|
||||
|
||||
@@ -9,26 +9,27 @@ import testutils.RequiresJavaVersion
|
||||
@RequiresJavaVersion(20, 20)
|
||||
class Java20RecordPatternTest : FreeSpec({
|
||||
"Given a class with Java record pattern" - {
|
||||
val (programClassPool, _) = ClassPoolBuilder.fromSource(
|
||||
JavaSource(
|
||||
"Test.java",
|
||||
"""
|
||||
public class Test {
|
||||
public static void main(String[] args) {
|
||||
printPoint(new Point(1, 2));
|
||||
}
|
||||
|
||||
private static void printPoint(Object o) {
|
||||
if (o instanceof Point(int x, int y)) {
|
||||
System.out.println(x + y);
|
||||
val (programClassPool, _) =
|
||||
ClassPoolBuilder.fromSource(
|
||||
JavaSource(
|
||||
"Test.java",
|
||||
"""
|
||||
public class Test {
|
||||
public static void main(String[] args) {
|
||||
printPoint(new Point(1, 2));
|
||||
}
|
||||
|
||||
private static void printPoint(Object o) {
|
||||
if (o instanceof Point(int x, int y)) {
|
||||
System.out.println(x + y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
record Point(int x, int y) {}
|
||||
""".trimIndent()
|
||||
),
|
||||
javacArguments = listOf("--enable-preview", "--release", "20")
|
||||
)
|
||||
record Point(int x, int y) {}
|
||||
""".trimIndent(),
|
||||
),
|
||||
javacArguments = listOf("--enable-preview", "--release", "20"),
|
||||
)
|
||||
|
||||
"Then ProGuard should parse the class correctly" {
|
||||
programClassPool.getClass("Test") shouldNotBe null
|
||||
|
||||
@@ -27,22 +27,23 @@ import testutils.shouldNotHaveFlag
|
||||
class MarkerTest : FreeSpec({
|
||||
|
||||
"Given a Kotlin inline class" - {
|
||||
val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource(
|
||||
KotlinSource(
|
||||
"Test.kt",
|
||||
"""
|
||||
@JvmInline
|
||||
value class Password(val s: String)
|
||||
val (programClassPool, libraryClassPool) =
|
||||
ClassPoolBuilder.fromSource(
|
||||
KotlinSource(
|
||||
"Test.kt",
|
||||
"""
|
||||
@JvmInline
|
||||
value class Password(val s: String)
|
||||
|
||||
// The underlying JVM method descriptor will reference the
|
||||
// underlying property of `Password`, rather than `Password` itself.
|
||||
// The underlying JVM method descriptor will reference the
|
||||
// underlying property of `Password`, rather than `Password` itself.
|
||||
|
||||
fun login(password: Password) {
|
||||
println(password)
|
||||
}
|
||||
""".trimIndent()
|
||||
fun login(password: Password) {
|
||||
println(password)
|
||||
}
|
||||
""".trimIndent(),
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
beforeEach {
|
||||
programClassPool.classesAccept {
|
||||
@@ -51,7 +52,8 @@ class MarkerTest : FreeSpec({
|
||||
}
|
||||
|
||||
"Then when using includedescriptorclasses modifier" - {
|
||||
val config = """
|
||||
val config =
|
||||
"""
|
||||
-keep,includedescriptorclasses class TestKt {
|
||||
<methods>;
|
||||
}
|
||||
@@ -71,7 +73,8 @@ class MarkerTest : FreeSpec({
|
||||
}
|
||||
|
||||
"Then when not using includedescriptorclasses modifier" - {
|
||||
val config = """
|
||||
val config =
|
||||
"""
|
||||
-keep class TestKt {
|
||||
<methods>;
|
||||
}
|
||||
@@ -92,25 +95,27 @@ class MarkerTest : FreeSpec({
|
||||
}
|
||||
|
||||
"Given a Kotlin interface with default method implementation in the interface itself" - {
|
||||
val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource(
|
||||
KotlinSource(
|
||||
"Test.kt",
|
||||
"""
|
||||
interface Test {
|
||||
fun foo() {
|
||||
TODO()
|
||||
val (programClassPool, libraryClassPool) =
|
||||
ClassPoolBuilder.fromSource(
|
||||
KotlinSource(
|
||||
"Test.kt",
|
||||
"""
|
||||
interface Test {
|
||||
fun foo() {
|
||||
TODO()
|
||||
}
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
),
|
||||
kotlincArguments = listOf("-Xjvm-default=all")
|
||||
)
|
||||
""".trimIndent(),
|
||||
),
|
||||
kotlincArguments = listOf("-Xjvm-default=all"),
|
||||
)
|
||||
|
||||
// Run the asserter to ensure any metadata that isn't initialized correctly is thrown away
|
||||
KotlinMetadataVerifier(Configuration()).execute(AppView(programClassPool, libraryClassPool))
|
||||
|
||||
"Then when marking" - {
|
||||
val config = """
|
||||
val config =
|
||||
"""
|
||||
-keep class Test {
|
||||
<methods>;
|
||||
}
|
||||
@@ -134,25 +139,27 @@ class MarkerTest : FreeSpec({
|
||||
}
|
||||
|
||||
"Given a Kotlin interface with default method implementation in compatibility mode" - {
|
||||
val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource(
|
||||
KotlinSource(
|
||||
"Test.kt",
|
||||
"""
|
||||
interface Test {
|
||||
fun foo() {
|
||||
TODO()
|
||||
val (programClassPool, libraryClassPool) =
|
||||
ClassPoolBuilder.fromSource(
|
||||
KotlinSource(
|
||||
"Test.kt",
|
||||
"""
|
||||
interface Test {
|
||||
fun foo() {
|
||||
TODO()
|
||||
}
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
),
|
||||
kotlincArguments = listOf("-Xjvm-default=all-compatibility")
|
||||
)
|
||||
""".trimIndent(),
|
||||
),
|
||||
kotlincArguments = listOf("-Xjvm-default=all-compatibility"),
|
||||
)
|
||||
|
||||
// Run the asserter to ensure any metadata that isn't initialized correctly is thrown away
|
||||
KotlinMetadataVerifier(Configuration()).execute(AppView(programClassPool, libraryClassPool))
|
||||
|
||||
"Then when marking" - {
|
||||
val config = """
|
||||
val config =
|
||||
"""
|
||||
-keep class Test {
|
||||
<methods>;
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
@@ -44,29 +44,33 @@ class MemberDescriptorSpecializerTest : FreeSpec({
|
||||
programClassPool.classesAccept(AllMemberVisitor(ProgramMemberOptimizationInfoSetter()))
|
||||
|
||||
// Create the optimization as in Optimizer
|
||||
val fillingOutValuesClassVisitor = ClassVisitorFactory {
|
||||
val valueFactory: ValueFactory = ParticularValueFactory()
|
||||
val storingInvocationUnit: InvocationUnit = StoringInvocationUnit(
|
||||
valueFactory,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
)
|
||||
ClassAccessFilter(
|
||||
0, AccessConstants.SYNTHETIC,
|
||||
AllMethodVisitor(
|
||||
AllAttributeVisitor(
|
||||
DebugAttributeVisitor(
|
||||
"Filling out fields, method parameters, and return values",
|
||||
PartialEvaluator(
|
||||
valueFactory, storingInvocationUnit,
|
||||
true
|
||||
)
|
||||
)
|
||||
val fillingOutValuesClassVisitor =
|
||||
ClassVisitorFactory {
|
||||
val valueFactory: ValueFactory = ParticularValueFactory()
|
||||
val storingInvocationUnit: InvocationUnit =
|
||||
StoringInvocationUnit(
|
||||
valueFactory,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
)
|
||||
ClassAccessFilter(
|
||||
0,
|
||||
AccessConstants.SYNTHETIC,
|
||||
AllMethodVisitor(
|
||||
AllAttributeVisitor(
|
||||
DebugAttributeVisitor(
|
||||
"Filling out fields, method parameters, and return values",
|
||||
PartialEvaluator(
|
||||
valueFactory,
|
||||
storingInvocationUnit,
|
||||
true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
programClassPool.classesAccept(fillingOutValuesClassVisitor.createClassVisitor())
|
||||
|
||||
@@ -80,33 +84,34 @@ class MemberDescriptorSpecializerTest : FreeSpec({
|
||||
true,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)
|
||||
)
|
||||
)
|
||||
null,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
"Given a method with a more general program class pool parameter type than its use" - {
|
||||
val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource(
|
||||
JavaSource(
|
||||
"Test.java",
|
||||
"""
|
||||
public class Test {
|
||||
public static void main(String[] args) {
|
||||
foo(new Foo());
|
||||
val (programClassPool, libraryClassPool) =
|
||||
ClassPoolBuilder.fromSource(
|
||||
JavaSource(
|
||||
"Test.java",
|
||||
"""
|
||||
public class Test {
|
||||
public static void main(String[] args) {
|
||||
foo(new Foo());
|
||||
}
|
||||
public static void foo(Bar foo) {
|
||||
System.out.println(foo);
|
||||
}
|
||||
}
|
||||
public static void foo(Bar foo) {
|
||||
System.out.println(foo);
|
||||
}
|
||||
}
|
||||
|
||||
class Bar { }
|
||||
|
||||
class Foo extends Bar { }
|
||||
""".trimIndent()
|
||||
|
||||
class Bar { }
|
||||
|
||||
class Foo extends Bar { }
|
||||
""".trimIndent(),
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
"When specializing the member descriptors" - {
|
||||
specializeMemberDescriptors(programClassPool, libraryClassPool)
|
||||
@@ -119,12 +124,15 @@ class MemberDescriptorSpecializerTest : FreeSpec({
|
||||
MemberNameFilter(
|
||||
"foo*",
|
||||
object : MemberVisitor {
|
||||
override fun visitAnyMember(clazz: Clazz, member: Member) {
|
||||
override fun visitAnyMember(
|
||||
clazz: Clazz,
|
||||
member: Member,
|
||||
) {
|
||||
memberDescriptor = member.getDescriptor(clazz)
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
memberDescriptor shouldBe "(LFoo;)V"
|
||||
}
|
||||
@@ -132,24 +140,25 @@ class MemberDescriptorSpecializerTest : FreeSpec({
|
||||
}
|
||||
|
||||
"Given a field with a more general program class pool parameter type than its use" - {
|
||||
val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource(
|
||||
JavaSource(
|
||||
"Test.java",
|
||||
"""
|
||||
public class Test {
|
||||
static Bar myField = null;
|
||||
|
||||
public static void main(String[] args) {
|
||||
myField = new Foo();
|
||||
val (programClassPool, libraryClassPool) =
|
||||
ClassPoolBuilder.fromSource(
|
||||
JavaSource(
|
||||
"Test.java",
|
||||
"""
|
||||
public class Test {
|
||||
static Bar myField = null;
|
||||
|
||||
public static void main(String[] args) {
|
||||
myField = new Foo();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Bar { }
|
||||
|
||||
class Foo extends Bar { }
|
||||
""".trimIndent()
|
||||
|
||||
class Bar { }
|
||||
|
||||
class Foo extends Bar { }
|
||||
""".trimIndent(),
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
"When specializing the member descriptors" - {
|
||||
specializeMemberDescriptors(programClassPool, libraryClassPool)
|
||||
@@ -162,12 +171,15 @@ class MemberDescriptorSpecializerTest : FreeSpec({
|
||||
MemberNameFilter(
|
||||
"myField*",
|
||||
object : MemberVisitor {
|
||||
override fun visitAnyMember(clazz: Clazz, member: Member) {
|
||||
override fun visitAnyMember(
|
||||
clazz: Clazz,
|
||||
member: Member,
|
||||
) {
|
||||
memberDescriptor = member.getDescriptor(clazz)
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
memberDescriptor shouldBe "LFoo;"
|
||||
}
|
||||
@@ -175,20 +187,21 @@ class MemberDescriptorSpecializerTest : FreeSpec({
|
||||
}
|
||||
|
||||
"Given a field with a more general library class pool parameter type than its use" - {
|
||||
val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource(
|
||||
JavaSource(
|
||||
"Test.java",
|
||||
"""
|
||||
public class Test {
|
||||
static java.lang.Object myField = null;
|
||||
|
||||
public static void main(String[] args) {
|
||||
myField = new java.lang.StringBuffer();
|
||||
val (programClassPool, libraryClassPool) =
|
||||
ClassPoolBuilder.fromSource(
|
||||
JavaSource(
|
||||
"Test.java",
|
||||
"""
|
||||
public class Test {
|
||||
static java.lang.Object myField = null;
|
||||
|
||||
public static void main(String[] args) {
|
||||
myField = new java.lang.StringBuffer();
|
||||
}
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
""".trimIndent(),
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
"When specializing the member descriptors" - {
|
||||
specializeMemberDescriptors(programClassPool, libraryClassPool)
|
||||
@@ -201,12 +214,15 @@ class MemberDescriptorSpecializerTest : FreeSpec({
|
||||
MemberNameFilter(
|
||||
"myField*",
|
||||
object : MemberVisitor {
|
||||
override fun visitAnyMember(clazz: Clazz, member: Member) {
|
||||
override fun visitAnyMember(
|
||||
clazz: Clazz,
|
||||
member: Member,
|
||||
) {
|
||||
memberDescriptor = member.getDescriptor(clazz)
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
// Library classes are not marked as available by default. Therefore, they are not specialized.
|
||||
memberDescriptor shouldBe "Ljava/lang/Object;"
|
||||
|
||||
@@ -9,14 +9,15 @@ import proguard.testutils.JavaSource
|
||||
|
||||
class SimpleEnumClassCheckerTest : FreeSpec({
|
||||
"Given a enum class without instance methods" - {
|
||||
val (programClassPool, _) = ClassPoolBuilder.fromSource(
|
||||
JavaSource(
|
||||
"Enum.java",
|
||||
"""
|
||||
public enum Enum { FOO, BAR }
|
||||
""".trimIndent()
|
||||
val (programClassPool, _) =
|
||||
ClassPoolBuilder.fromSource(
|
||||
JavaSource(
|
||||
"Enum.java",
|
||||
"""
|
||||
public enum Enum { FOO, BAR }
|
||||
""".trimIndent(),
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
"Then when checked with SimpleEnumClassChecker" - {
|
||||
val enumClass = programClassPool.getClass("Enum")
|
||||
@@ -30,19 +31,20 @@ class SimpleEnumClassCheckerTest : FreeSpec({
|
||||
}
|
||||
|
||||
"Given a enum class with a public instance method" - {
|
||||
val (programClassPool, _) = ClassPoolBuilder.fromSource(
|
||||
JavaSource(
|
||||
"Enum.java",
|
||||
"""
|
||||
public enum Enum {
|
||||
FOO, BAR;
|
||||
public void foo() {
|
||||
|
||||
val (programClassPool, _) =
|
||||
ClassPoolBuilder.fromSource(
|
||||
JavaSource(
|
||||
"Enum.java",
|
||||
"""
|
||||
public enum Enum {
|
||||
FOO, BAR;
|
||||
public void foo() {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
""".trimIndent(),
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
"Then when checked with SimpleEnumClassChecker" - {
|
||||
val enumClass = programClassPool.getClass("Enum")
|
||||
@@ -56,19 +58,20 @@ class SimpleEnumClassCheckerTest : FreeSpec({
|
||||
}
|
||||
|
||||
"Given a enum class with a private instance method" - {
|
||||
val (programClassPool, _) = ClassPoolBuilder.fromSource(
|
||||
JavaSource(
|
||||
"Enum.java",
|
||||
"""
|
||||
public enum Enum {
|
||||
FOO, BAR;
|
||||
private void foo() {
|
||||
|
||||
val (programClassPool, _) =
|
||||
ClassPoolBuilder.fromSource(
|
||||
JavaSource(
|
||||
"Enum.java",
|
||||
"""
|
||||
public enum Enum {
|
||||
FOO, BAR;
|
||||
private void foo() {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
""".trimIndent(),
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
"Then when checked with SimpleEnumClassChecker" - {
|
||||
val enumClass = programClassPool.getClass("Enum")
|
||||
|
||||
@@ -22,18 +22,19 @@ import proguard.testutils.JavaSource
|
||||
|
||||
class MarkedAnnotationDeleterTest : FreeSpec({
|
||||
"Given a class with two annotations on its field" - {
|
||||
val (programClassPool, _) = ClassPoolBuilder.fromSource(
|
||||
JavaSource("Ann1.java", "public @interface Ann1 {}"),
|
||||
JavaSource("Ann2.java", "public @interface Ann2 {}"),
|
||||
JavaSource(
|
||||
"A.java",
|
||||
"""class A {
|
||||
val (programClassPool, _) =
|
||||
ClassPoolBuilder.fromSource(
|
||||
JavaSource("Ann1.java", "public @interface Ann1 {}"),
|
||||
JavaSource("Ann2.java", "public @interface Ann2 {}"),
|
||||
JavaSource(
|
||||
"A.java",
|
||||
"""class A {
|
||||
@Ann1
|
||||
@Ann2
|
||||
public int a;
|
||||
}"""
|
||||
}""",
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
val classA = programClassPool.getClass("A")
|
||||
|
||||
@@ -43,15 +44,15 @@ class MarkedAnnotationDeleterTest : FreeSpec({
|
||||
classA.fieldsAccept(
|
||||
AllAttributeVisitor(
|
||||
AllAnnotationVisitor(
|
||||
ProcessingInfoSetter(mark)
|
||||
)
|
||||
)
|
||||
ProcessingInfoSetter(mark),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
classA.fieldsAccept(
|
||||
AllAttributeVisitor(
|
||||
MarkedAnnotationDeleter(mark)
|
||||
)
|
||||
MarkedAnnotationDeleter(mark),
|
||||
),
|
||||
)
|
||||
|
||||
"Then no annotations should remain" {
|
||||
@@ -59,13 +60,17 @@ class MarkedAnnotationDeleterTest : FreeSpec({
|
||||
|
||||
classA.fieldsAccept(
|
||||
AllAttributeVisitor(
|
||||
AllAnnotationVisitor(object : AnnotationVisitor {
|
||||
override fun visitAnnotation(clazz: Clazz?, annotation: Annotation?) {
|
||||
count += 1
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
AllAnnotationVisitor(
|
||||
object : AnnotationVisitor {
|
||||
override fun visitAnnotation(
|
||||
clazz: Clazz?,
|
||||
annotation: Annotation?,
|
||||
) {
|
||||
count += 1
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
count shouldBe 0
|
||||
@@ -74,24 +79,25 @@ class MarkedAnnotationDeleterTest : FreeSpec({
|
||||
}
|
||||
|
||||
"Given a class with two A and B annotations on its field" - {
|
||||
val (programClassPool, _) = ClassPoolBuilder.fromSource(
|
||||
JavaSource("AnnA1.java", "public @interface AnnA1 {}"),
|
||||
JavaSource("AnnA2.java", "public @interface AnnA2 {}"),
|
||||
JavaSource("AnnB1.java", "public @interface AnnB1 {}"),
|
||||
JavaSource("AnnB2.java", "public @interface AnnB2 {}"),
|
||||
JavaSource("AnnB3.java", "public @interface AnnB3 {}"),
|
||||
JavaSource(
|
||||
"A.java",
|
||||
"""class A {
|
||||
val (programClassPool, _) =
|
||||
ClassPoolBuilder.fromSource(
|
||||
JavaSource("AnnA1.java", "public @interface AnnA1 {}"),
|
||||
JavaSource("AnnA2.java", "public @interface AnnA2 {}"),
|
||||
JavaSource("AnnB1.java", "public @interface AnnB1 {}"),
|
||||
JavaSource("AnnB2.java", "public @interface AnnB2 {}"),
|
||||
JavaSource("AnnB3.java", "public @interface AnnB3 {}"),
|
||||
JavaSource(
|
||||
"A.java",
|
||||
"""class A {
|
||||
@AnnB1
|
||||
@AnnA1
|
||||
@AnnB2
|
||||
@AnnA2
|
||||
@AnnB3
|
||||
public int a;
|
||||
}"""
|
||||
}""",
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
val classA = programClassPool.getClass("A")
|
||||
|
||||
@@ -103,16 +109,16 @@ class MarkedAnnotationDeleterTest : FreeSpec({
|
||||
AllAnnotationVisitor(
|
||||
AnnotationTypeFilter(
|
||||
"LAnnA*;",
|
||||
ProcessingInfoSetter(mark)
|
||||
)
|
||||
)
|
||||
)
|
||||
ProcessingInfoSetter(mark),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
classA.fieldsAccept(
|
||||
AllAttributeVisitor(
|
||||
MarkedAnnotationDeleter(mark)
|
||||
)
|
||||
MarkedAnnotationDeleter(mark),
|
||||
),
|
||||
)
|
||||
|
||||
"Then only the B annotations should remain" {
|
||||
@@ -120,13 +126,17 @@ class MarkedAnnotationDeleterTest : FreeSpec({
|
||||
|
||||
classA.fieldsAccept(
|
||||
AllAttributeVisitor(
|
||||
AllAnnotationVisitor(object : AnnotationVisitor {
|
||||
override fun visitAnnotation(clazz: Clazz, annotation: Annotation) {
|
||||
list.add(annotation.getType(clazz))
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
AllAnnotationVisitor(
|
||||
object : AnnotationVisitor {
|
||||
override fun visitAnnotation(
|
||||
clazz: Clazz,
|
||||
annotation: Annotation,
|
||||
) {
|
||||
list.add(annotation.getType(clazz))
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
list shouldContainExactly listOf("LAnnB1;", "LAnnB2;", "LAnnB3;")
|
||||
@@ -135,16 +145,17 @@ class MarkedAnnotationDeleterTest : FreeSpec({
|
||||
}
|
||||
|
||||
"Given a class with two annotations on its method parameter" - {
|
||||
val (programClassPool, _) = ClassPoolBuilder.fromSource(
|
||||
JavaSource("Ann1.java", "public @interface Ann1 {}"),
|
||||
JavaSource("Ann2.java", "public @interface Ann2 {}"),
|
||||
JavaSource(
|
||||
"A.java",
|
||||
"""class A {
|
||||
val (programClassPool, _) =
|
||||
ClassPoolBuilder.fromSource(
|
||||
JavaSource("Ann1.java", "public @interface Ann1 {}"),
|
||||
JavaSource("Ann2.java", "public @interface Ann2 {}"),
|
||||
JavaSource(
|
||||
"A.java",
|
||||
"""class A {
|
||||
public void a(@Ann1 @Ann2 int x) {}
|
||||
}"""
|
||||
}""",
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
val classA = programClassPool.getClass("A")
|
||||
|
||||
@@ -154,15 +165,15 @@ class MarkedAnnotationDeleterTest : FreeSpec({
|
||||
classA.methodsAccept(
|
||||
AllAttributeVisitor(
|
||||
AllAnnotationVisitor(
|
||||
ProcessingInfoSetter(mark)
|
||||
)
|
||||
)
|
||||
ProcessingInfoSetter(mark),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
classA.methodsAccept(
|
||||
AllAttributeVisitor(
|
||||
MarkedAnnotationDeleter(mark)
|
||||
)
|
||||
MarkedAnnotationDeleter(mark),
|
||||
),
|
||||
)
|
||||
|
||||
"Then no annotations should remain" {
|
||||
@@ -170,13 +181,17 @@ class MarkedAnnotationDeleterTest : FreeSpec({
|
||||
|
||||
classA.methodsAccept(
|
||||
AllAttributeVisitor(
|
||||
AllAnnotationVisitor(object : AnnotationVisitor {
|
||||
override fun visitAnnotation(clazz: Clazz?, annotation: Annotation?) {
|
||||
count += 1
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
AllAnnotationVisitor(
|
||||
object : AnnotationVisitor {
|
||||
override fun visitAnnotation(
|
||||
clazz: Clazz?,
|
||||
annotation: Annotation?,
|
||||
) {
|
||||
count += 1
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
count shouldBe 0
|
||||
@@ -185,19 +200,20 @@ class MarkedAnnotationDeleterTest : FreeSpec({
|
||||
}
|
||||
|
||||
"Given a class with two A and B annotations on its method parameter" - {
|
||||
val (programClassPool, _) = ClassPoolBuilder.fromSource(
|
||||
JavaSource("AnnA1.java", "public @interface AnnA1 {}"),
|
||||
JavaSource("AnnA2.java", "public @interface AnnA2 {}"),
|
||||
JavaSource("AnnB1.java", "public @interface AnnB1 {}"),
|
||||
JavaSource("AnnB2.java", "public @interface AnnB2 {}"),
|
||||
JavaSource("AnnB3.java", "public @interface AnnB3 {}"),
|
||||
JavaSource(
|
||||
"A.java",
|
||||
"""class A {
|
||||
val (programClassPool, _) =
|
||||
ClassPoolBuilder.fromSource(
|
||||
JavaSource("AnnA1.java", "public @interface AnnA1 {}"),
|
||||
JavaSource("AnnA2.java", "public @interface AnnA2 {}"),
|
||||
JavaSource("AnnB1.java", "public @interface AnnB1 {}"),
|
||||
JavaSource("AnnB2.java", "public @interface AnnB2 {}"),
|
||||
JavaSource("AnnB3.java", "public @interface AnnB3 {}"),
|
||||
JavaSource(
|
||||
"A.java",
|
||||
"""class A {
|
||||
public void a(@AnnB1 @AnnA1 @AnnB2 @AnnA2 @AnnB3 int x) {}
|
||||
}"""
|
||||
}""",
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
val classA = programClassPool.getClass("A")
|
||||
|
||||
@@ -209,16 +225,16 @@ class MarkedAnnotationDeleterTest : FreeSpec({
|
||||
AllAnnotationVisitor(
|
||||
AnnotationTypeFilter(
|
||||
"LAnnA*;",
|
||||
ProcessingInfoSetter(mark)
|
||||
)
|
||||
)
|
||||
)
|
||||
ProcessingInfoSetter(mark),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
classA.methodsAccept(
|
||||
AllAttributeVisitor(
|
||||
MarkedAnnotationDeleter(mark)
|
||||
)
|
||||
MarkedAnnotationDeleter(mark),
|
||||
),
|
||||
)
|
||||
|
||||
"Then only the B annotations should remain" {
|
||||
@@ -226,13 +242,17 @@ class MarkedAnnotationDeleterTest : FreeSpec({
|
||||
|
||||
classA.methodsAccept(
|
||||
AllAttributeVisitor(
|
||||
AllAnnotationVisitor(object : AnnotationVisitor {
|
||||
override fun visitAnnotation(clazz: Clazz, annotation: Annotation) {
|
||||
list.add(annotation.getType(clazz))
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
AllAnnotationVisitor(
|
||||
object : AnnotationVisitor {
|
||||
override fun visitAnnotation(
|
||||
clazz: Clazz,
|
||||
annotation: Annotation,
|
||||
) {
|
||||
list.add(annotation.getType(clazz))
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
list shouldContainExactly listOf("LAnnB1;", "LAnnB2;", "LAnnB3;")
|
||||
|
||||
@@ -22,14 +22,15 @@ import proguard.testutils.JavaSource
|
||||
|
||||
class TypeArgumentFinderTest : FreeSpec({
|
||||
"Given an aload instruction with TypeToken" - {
|
||||
val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource(
|
||||
JavaSource(
|
||||
"TypeToken.java",
|
||||
"""
|
||||
val (programClassPool, libraryClassPool) =
|
||||
ClassPoolBuilder.fromSource(
|
||||
JavaSource(
|
||||
"TypeToken.java",
|
||||
"""
|
||||
package com.google.gson.reflect;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
|
||||
public class TypeToken<T> {
|
||||
Type type;
|
||||
|
||||
@@ -37,11 +38,11 @@ class TypeArgumentFinderTest : FreeSpec({
|
||||
return type;
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
),
|
||||
JavaSource(
|
||||
"A.java",
|
||||
"""
|
||||
""".trimIndent(),
|
||||
),
|
||||
JavaSource(
|
||||
"A.java",
|
||||
"""
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
@@ -51,9 +52,9 @@ class TypeArgumentFinderTest : FreeSpec({
|
||||
System.out.println(x);
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
""".trimIndent(),
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
"When retrieving the return type with TypeArgumentFinder" - {
|
||||
val partialEvaluator = PartialEvaluator()
|
||||
@@ -63,9 +64,9 @@ class TypeArgumentFinderTest : FreeSpec({
|
||||
AllMethodVisitor(
|
||||
MemberNameFilter(
|
||||
"a",
|
||||
AllAttributeVisitor(partialEvaluator)
|
||||
)
|
||||
)
|
||||
AllAttributeVisitor(partialEvaluator),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
val typeArgumentFinder = TypeArgumentFinder(programClassPool, libraryClassPool, partialEvaluator)
|
||||
@@ -83,14 +84,14 @@ class TypeArgumentFinderTest : FreeSpec({
|
||||
Instruction.OP_ALOAD_0.toInt(),
|
||||
Instruction.OP_ALOAD_1.toInt(),
|
||||
Instruction.OP_ALOAD_2.toInt(),
|
||||
Instruction.OP_ALOAD_3.toInt()
|
||||
Instruction.OP_ALOAD_3.toInt(),
|
||||
),
|
||||
typeArgumentFinder
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
typeArgumentFinder,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"Then we obtain the return type java.lang.String" {
|
||||
@@ -100,14 +101,15 @@ class TypeArgumentFinderTest : FreeSpec({
|
||||
}
|
||||
|
||||
"Given an invokevirtual instruction with TypeToken" - {
|
||||
val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource(
|
||||
JavaSource(
|
||||
"TypeToken.java",
|
||||
"""
|
||||
val (programClassPool, libraryClassPool) =
|
||||
ClassPoolBuilder.fromSource(
|
||||
JavaSource(
|
||||
"TypeToken.java",
|
||||
"""
|
||||
package com.google.gson.reflect;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
|
||||
public class TypeToken<T> {
|
||||
Type type;
|
||||
|
||||
@@ -115,11 +117,11 @@ class TypeArgumentFinderTest : FreeSpec({
|
||||
return type;
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
),
|
||||
JavaSource(
|
||||
"A.java",
|
||||
"""
|
||||
""".trimIndent(),
|
||||
),
|
||||
JavaSource(
|
||||
"A.java",
|
||||
"""
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
@@ -128,9 +130,9 @@ class TypeArgumentFinderTest : FreeSpec({
|
||||
new TypeToken<String>() {}.getType();
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
""".trimIndent(),
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
"When retrieving the return type with TypeArgumentFinder" - {
|
||||
val typeArgumentFinder = TypeArgumentFinder(programClassPool, libraryClassPool, null)
|
||||
@@ -144,12 +146,12 @@ class TypeArgumentFinderTest : FreeSpec({
|
||||
AllInstructionVisitor(
|
||||
InstructionOpCodeFilter(
|
||||
intArrayOf(Instruction.OP_INVOKEVIRTUAL.toInt()),
|
||||
typeArgumentFinder
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
typeArgumentFinder,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"Then we obtain null (Note: this case is not implemented like for aload instructions)" {
|
||||
@@ -159,10 +161,11 @@ class TypeArgumentFinderTest : FreeSpec({
|
||||
}
|
||||
|
||||
"Given an ldc instruction" - {
|
||||
val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource(
|
||||
JavaSource(
|
||||
"Z.java",
|
||||
"""
|
||||
val (programClassPool, libraryClassPool) =
|
||||
ClassPoolBuilder.fromSource(
|
||||
JavaSource(
|
||||
"Z.java",
|
||||
"""
|
||||
package a.b;
|
||||
|
||||
public class Z {
|
||||
@@ -172,9 +175,9 @@ class TypeArgumentFinderTest : FreeSpec({
|
||||
|
||||
public void x(Class<?> c) {}
|
||||
}
|
||||
""".trimIndent()
|
||||
""".trimIndent(),
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
"When retrieving the return type with TypeArgumentFinder" - {
|
||||
val typeArgumentFinder = TypeArgumentFinder(programClassPool, libraryClassPool, null)
|
||||
@@ -188,12 +191,12 @@ class TypeArgumentFinderTest : FreeSpec({
|
||||
AllInstructionVisitor(
|
||||
InstructionOpCodeFilter(
|
||||
intArrayOf(Instruction.OP_LDC.toInt()),
|
||||
typeArgumentFinder
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
typeArgumentFinder,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"Then we obtain the return type a.b.Z" {
|
||||
@@ -203,19 +206,20 @@ class TypeArgumentFinderTest : FreeSpec({
|
||||
}
|
||||
|
||||
"Given an aload instruction" - {
|
||||
val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource(
|
||||
JavaSource(
|
||||
"A.java",
|
||||
"""
|
||||
val (programClassPool, libraryClassPool) =
|
||||
ClassPoolBuilder.fromSource(
|
||||
JavaSource(
|
||||
"A.java",
|
||||
"""
|
||||
public class A {
|
||||
public void a() {
|
||||
String x = "text";
|
||||
System.out.println(x);
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
""".trimIndent(),
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
"When retrieving the return type with TypeArgumentFinder" - {
|
||||
val partialEvaluator = PartialEvaluator()
|
||||
@@ -225,9 +229,9 @@ class TypeArgumentFinderTest : FreeSpec({
|
||||
AllMethodVisitor(
|
||||
MemberNameFilter(
|
||||
"a",
|
||||
AllAttributeVisitor(partialEvaluator)
|
||||
)
|
||||
)
|
||||
AllAttributeVisitor(partialEvaluator),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
val typeArgumentFinder = TypeArgumentFinder(programClassPool, libraryClassPool, partialEvaluator)
|
||||
@@ -245,14 +249,14 @@ class TypeArgumentFinderTest : FreeSpec({
|
||||
Instruction.OP_ALOAD_0.toInt(),
|
||||
Instruction.OP_ALOAD_1.toInt(),
|
||||
Instruction.OP_ALOAD_2.toInt(),
|
||||
Instruction.OP_ALOAD_3.toInt()
|
||||
Instruction.OP_ALOAD_3.toInt(),
|
||||
),
|
||||
typeArgumentFinder
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
typeArgumentFinder,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
// This is not an intended use case of the TypeArgumentFinder
|
||||
@@ -264,10 +268,11 @@ class TypeArgumentFinderTest : FreeSpec({
|
||||
}
|
||||
|
||||
"Given a new instruction" - {
|
||||
val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource(
|
||||
JavaSource(
|
||||
"Z.java",
|
||||
"""
|
||||
val (programClassPool, libraryClassPool) =
|
||||
ClassPoolBuilder.fromSource(
|
||||
JavaSource(
|
||||
"Z.java",
|
||||
"""
|
||||
package a.b;
|
||||
|
||||
public class Z {
|
||||
@@ -275,9 +280,9 @@ class TypeArgumentFinderTest : FreeSpec({
|
||||
new Z();
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
""".trimIndent(),
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
"When retrieving the return type with TypeArgumentFinder" - {
|
||||
val typeArgumentFinder = TypeArgumentFinder(programClassPool, libraryClassPool, null)
|
||||
@@ -291,12 +296,12 @@ class TypeArgumentFinderTest : FreeSpec({
|
||||
AllInstructionVisitor(
|
||||
InstructionOpCodeFilter(
|
||||
intArrayOf(Instruction.OP_NEW.toInt()),
|
||||
typeArgumentFinder
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
typeArgumentFinder,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"Then we obtain the return type a.b.Z" {
|
||||
|
||||
@@ -13,19 +13,20 @@ import proguard.testutils.JavaSource
|
||||
class InstantiationClassMarkerTest : FreeSpec({
|
||||
"A class should be marked as instantiated" - {
|
||||
"when it is instantiated with a `new` instruction" {
|
||||
val (classPool, _) = ClassPoolBuilder.fromSource(
|
||||
JavaSource(
|
||||
"Main.java",
|
||||
"""
|
||||
class A { }
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
System.out.println(new A());
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
val (classPool, _) =
|
||||
ClassPoolBuilder.fromSource(
|
||||
JavaSource(
|
||||
"Main.java",
|
||||
"""
|
||||
class A { }
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
System.out.println(new A());
|
||||
}
|
||||
}
|
||||
""".trimIndent(),
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
val classA = classPool.getClass("A")
|
||||
|
||||
@@ -36,30 +37,31 @@ class InstantiationClassMarkerTest : FreeSpec({
|
||||
AllMemberVisitor(
|
||||
AllAttributeVisitor(
|
||||
AllInstructionVisitor(
|
||||
InstantiationClassMarker()
|
||||
)
|
||||
)
|
||||
)
|
||||
InstantiationClassMarker(),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
getProgramClassOptimizationInfo(classA).isInstantiated shouldBe true
|
||||
}
|
||||
|
||||
"when one of its subclasses is instantiated with a `new` instruction" {
|
||||
val (classPool, _) = ClassPoolBuilder.fromSource(
|
||||
JavaSource(
|
||||
"Main.java",
|
||||
"""
|
||||
class A { }
|
||||
class B extends A { }
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
System.out.println(new B());
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
val (classPool, _) =
|
||||
ClassPoolBuilder.fromSource(
|
||||
JavaSource(
|
||||
"Main.java",
|
||||
"""
|
||||
class A { }
|
||||
class B extends A { }
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
System.out.println(new B());
|
||||
}
|
||||
}
|
||||
""".trimIndent(),
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
val classA = classPool.getClass("A")
|
||||
val classB = classPool.getClass("B")
|
||||
@@ -71,10 +73,10 @@ class InstantiationClassMarkerTest : FreeSpec({
|
||||
AllMemberVisitor(
|
||||
AllAttributeVisitor(
|
||||
AllInstructionVisitor(
|
||||
InstantiationClassMarker()
|
||||
)
|
||||
)
|
||||
)
|
||||
InstantiationClassMarker(),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
getProgramClassOptimizationInfo(classA).isInstantiated shouldBe true
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
package proguard.optimize.peephole
|
||||
|
||||
import io.kotest.core.spec.style.BehaviorSpec
|
||||
import io.kotest.matchers.nulls.shouldBeNull
|
||||
import io.kotest.matchers.shouldBe
|
||||
import proguard.classfile.AccessConstants
|
||||
import proguard.classfile.ClassConstants
|
||||
import proguard.classfile.ProgramMethod
|
||||
import proguard.classfile.VersionConstants
|
||||
import proguard.classfile.attribute.Attribute
|
||||
import proguard.classfile.attribute.Attribute.LINE_NUMBER_TABLE
|
||||
import proguard.classfile.attribute.LineNumberInfo
|
||||
import proguard.classfile.attribute.LineNumberTableAttribute
|
||||
import proguard.classfile.attribute.ProGuardOrigin
|
||||
import proguard.classfile.attribute.StructuredLineNumberInfo
|
||||
import proguard.classfile.editor.AttributesEditor
|
||||
import proguard.classfile.editor.ClassBuilder
|
||||
import proguard.testutils.CodeAttributeFinder
|
||||
|
||||
class InlinedMethodLineNumberLinearizerTest : BehaviorSpec({
|
||||
|
||||
Given("A method with two levels of inlined line numbers") {
|
||||
val clazzBuilder = ClassBuilder(VersionConstants.CLASS_VERSION_18, AccessConstants.PUBLIC, "A", ClassConstants.NAME_JAVA_LANG_OBJECT)
|
||||
|
||||
val clazz = clazzBuilder.programClass
|
||||
val method = clazzBuilder.addAndReturnMethod(
|
||||
AccessConstants.PUBLIC or AccessConstants.STATIC,
|
||||
"a",
|
||||
"()V",
|
||||
10,
|
||||
) {
|
||||
it.return_()
|
||||
} as ProgramMethod
|
||||
|
||||
val codeAttribute = CodeAttributeFinder.findCodeAttribute(method)!!
|
||||
|
||||
val blockB = StructuredLineNumberInfo.Block(ProGuardOrigin.INLINED, "B.b()V", 0, 0)
|
||||
val blockC = StructuredLineNumberInfo.Block(ProGuardOrigin.INLINED, "C.c()V", 0, 0)
|
||||
|
||||
val lineNumbers = arrayOf(
|
||||
LineNumberInfo(0, 1),
|
||||
blockB.line(1, MethodInliner.INLINED_METHOD_START_LINE_NUMBER),
|
||||
blockB.line(2, 11),
|
||||
blockC.line(3, MethodInliner.INLINED_METHOD_START_LINE_NUMBER),
|
||||
blockC.line(4, 21),
|
||||
blockC.line(5, MethodInliner.INLINED_METHOD_END_LINE_NUMBER),
|
||||
blockB.line(6, 12),
|
||||
blockB.line(7, MethodInliner.INLINED_METHOD_END_LINE_NUMBER),
|
||||
LineNumberInfo(8, 2)
|
||||
)
|
||||
|
||||
val lineNumberTableAttribute = LineNumberTableAttribute(clazzBuilder.constantPoolEditor.addUtf8Constant(LINE_NUMBER_TABLE), lineNumbers.size, lineNumbers)
|
||||
AttributesEditor(clazz, method, codeAttribute, true).addAttribute(lineNumberTableAttribute)
|
||||
|
||||
When("Linearizing the line numbers") {
|
||||
clazz.accept(LineNumberLinearizer())
|
||||
|
||||
Then("The line number table should look correct") {
|
||||
val lineNumberTableAttribute = codeAttribute.getAttribute(clazz, Attribute.LINE_NUMBER_TABLE) as LineNumberTableAttribute
|
||||
val table = lineNumberTableAttribute.lineNumberTable
|
||||
table[0].u2lineNumber shouldBe 1
|
||||
table[0].source.shouldBeNull()
|
||||
|
||||
table[1].u2lineNumber.mod(LineNumberLinearizer.SHIFT_ROUNDING) shouldBe 11
|
||||
table[1].source shouldBe "B.b()V:0:0"
|
||||
|
||||
table[2].u2lineNumber.mod(LineNumberLinearizer.SHIFT_ROUNDING) shouldBe 21
|
||||
table[2].source shouldBe "C.c()V:0:0"
|
||||
|
||||
table[3].u2lineNumber.mod(LineNumberLinearizer.SHIFT_ROUNDING) shouldBe 11
|
||||
table[3].source shouldBe "B.b()V:0:0"
|
||||
|
||||
table[4].u2lineNumber.mod(LineNumberLinearizer.SHIFT_ROUNDING) shouldBe 12
|
||||
table[4].source shouldBe "B.b()V:0:0"
|
||||
|
||||
table[5].u2lineNumber shouldBe 1
|
||||
table[5].source.shouldBeNull()
|
||||
|
||||
table[6].u2lineNumber shouldBe 2
|
||||
table[6].source.shouldBeNull()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
218
base/src/test/kotlin/proguard/shrink/UsageMarkerTest.kt
Normal file
218
base/src/test/kotlin/proguard/shrink/UsageMarkerTest.kt
Normal file
@@ -0,0 +1,218 @@
|
||||
package proguard.shrink
|
||||
|
||||
import io.kotest.core.spec.style.BehaviorSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import proguard.Configuration
|
||||
import proguard.classfile.ClassPool
|
||||
import proguard.classfile.Clazz
|
||||
import proguard.classfile.attribute.Attribute
|
||||
import proguard.classfile.attribute.PermittedSubclassesAttribute
|
||||
import proguard.classfile.attribute.visitor.AllAttributeVisitor
|
||||
import proguard.classfile.attribute.visitor.AttributeVisitor
|
||||
import proguard.classfile.constant.ClassConstant
|
||||
import proguard.classfile.constant.Constant
|
||||
import proguard.classfile.constant.visitor.ConstantVisitor
|
||||
import proguard.classfile.visitor.AllMemberVisitor
|
||||
import proguard.classfile.visitor.MultiClassVisitor
|
||||
import proguard.resources.file.ResourceFilePool
|
||||
import proguard.testutils.ClassPoolBuilder
|
||||
import proguard.testutils.JavaSource
|
||||
import proguard.testutils.RequiresJavaVersion
|
||||
import proguard.util.ProcessingFlagSetter
|
||||
import proguard.util.ProcessingFlags.DONT_SHRINK
|
||||
|
||||
class UsageMarkerTest : BehaviorSpec({
|
||||
Given("A class pool with interfaces only referenced through annotations") {
|
||||
|
||||
val (programClassPool, _) = ClassPoolBuilder.fromSource(
|
||||
JavaSource("MyInterface.java","""
|
||||
interface MyInterface {}
|
||||
""".trimIndent()),
|
||||
JavaSource("MyAnnotation.java","""
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Retention;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface MyAnnotation {
|
||||
Class<?> value();
|
||||
}
|
||||
""".trimIndent()),
|
||||
JavaSource("MyImpl.java","""
|
||||
class MyImpl implements MyInterface {}
|
||||
""".trimIndent()),
|
||||
JavaSource("InterfaceTest.java", """
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
class InterfaceTest {
|
||||
|
||||
@MyAnnotation(MyImpl.class)
|
||||
String s;
|
||||
|
||||
public static void main(String... args) throws Exception {
|
||||
Field f = InterfaceTest.class.getDeclaredField("s");
|
||||
MyAnnotation annotation = f.getAnnotation(MyAnnotation.class);
|
||||
Object obj = annotation.value().getDeclaredConstructor().newInstance();
|
||||
if (obj instanceof MyInterface) {
|
||||
System.out.println("success");
|
||||
} else {
|
||||
throw new Exception(obj.getClass() + " does not implement " + MyInterface.class);
|
||||
}
|
||||
}
|
||||
}
|
||||
""".trimIndent())
|
||||
)
|
||||
val implClass = programClassPool.getClass("MyImpl")
|
||||
val main = programClassPool.getClass("InterfaceTest")
|
||||
main.accept(
|
||||
MultiClassVisitor(ProcessingFlagSetter(DONT_SHRINK), AllMemberVisitor(
|
||||
ProcessingFlagSetter(DONT_SHRINK)
|
||||
)))
|
||||
When("marking") {
|
||||
val simpleUsageMarker = SimpleUsageMarker()
|
||||
UsageMarker(Configuration()).mark(programClassPool,ClassPool(), ResourceFilePool(),simpleUsageMarker)
|
||||
Then("The interface class should be marked as used.") {
|
||||
val used = object : ConstantVisitor {
|
||||
var used = false
|
||||
override fun visitAnyConstant(
|
||||
clazz: Clazz?,
|
||||
constant: Constant?
|
||||
) {
|
||||
used = used or simpleUsageMarker.isUsed(constant)
|
||||
}
|
||||
}
|
||||
implClass.interfaceConstantsAccept(used)
|
||||
used.used shouldBe true
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
@RequiresJavaVersion(15)
|
||||
class Java15UsageMarkerTest : BehaviorSpec({
|
||||
// Regression test for https://github.com/Guardsquare/proguard/issues/501
|
||||
Given("A class pool containing a sealed interface extending another sealed interface, and final classes implementing both") {
|
||||
val (programClassPool, _) = ClassPoolBuilder.fromSource(
|
||||
JavaSource("sample/Animal.java","""
|
||||
package sample;
|
||||
public sealed interface Animal permits Fish, Mammal {
|
||||
static Animal ofType(String type) {
|
||||
if (Cat.TYPE.matches(type)) {
|
||||
return new Cat();
|
||||
} else if (Dog.TYPE.matches(type)) {
|
||||
return new Dog();
|
||||
} else if (Fish.TYPE.matches(type)) {
|
||||
return new Fish();
|
||||
}
|
||||
throw new IllegalArgumentException("Wrong animal type: " + type);
|
||||
}
|
||||
}
|
||||
""".trimIndent()),
|
||||
JavaSource("sample/AnimalType.java","""
|
||||
package sample;
|
||||
|
||||
public enum AnimalType {
|
||||
|
||||
CAT("CAT"),
|
||||
DOG("DOG"),
|
||||
FISH("FISH");
|
||||
|
||||
|
||||
private final String typeString;
|
||||
|
||||
AnimalType(String typeString){
|
||||
this.typeString = typeString;
|
||||
}
|
||||
|
||||
|
||||
public boolean matches(String typeString) {
|
||||
return this.typeString.equalsIgnoreCase(typeString);
|
||||
}
|
||||
|
||||
}
|
||||
""".trimIndent()),
|
||||
JavaSource("sample/Mammal.java", """
|
||||
package sample;
|
||||
public sealed interface Mammal extends Animal permits Cat, Dog {
|
||||
}
|
||||
|
||||
""".trimIndent()),
|
||||
JavaSource("sample/Cat.java","""
|
||||
package sample;
|
||||
public final class Cat implements Mammal {
|
||||
public static final AnimalType TYPE = AnimalType.CAT;
|
||||
}
|
||||
|
||||
""".trimIndent()),
|
||||
JavaSource("sample/Dog.java","""
|
||||
package sample;
|
||||
public final class Dog implements Mammal {
|
||||
public static final AnimalType TYPE = AnimalType.DOG;
|
||||
}
|
||||
""".trimIndent()),
|
||||
JavaSource("sample/Fish.java","""
|
||||
package sample;
|
||||
public final class Fish implements Animal {
|
||||
public static final AnimalType TYPE = AnimalType.FISH;
|
||||
}
|
||||
""".trimIndent()),
|
||||
JavaSource("sample/Main.java","""
|
||||
package sample;
|
||||
public class Main {
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("Trying to create animal");
|
||||
Animal animal = Animal.ofType("fish");
|
||||
System.out.println("Successfully created animal");
|
||||
}
|
||||
}
|
||||
|
||||
""".trimIndent()
|
||||
), javacArguments = listOf("--enable-preview", "--release", "15"),
|
||||
)
|
||||
val animal = programClassPool.getClass("sample/Animal")
|
||||
|
||||
val main = programClassPool.getClass("sample/Main")
|
||||
main.accept(
|
||||
MultiClassVisitor(ProcessingFlagSetter(DONT_SHRINK), AllMemberVisitor(
|
||||
ProcessingFlagSetter(DONT_SHRINK)
|
||||
)))
|
||||
When("marking") {
|
||||
val simpleUsageMarker = SimpleUsageMarker()
|
||||
UsageMarker(Configuration()).mark(programClassPool,ClassPool(), ResourceFilePool(),simpleUsageMarker)
|
||||
Then("The Animal class permitted subclasses constants should all be marked as used") {
|
||||
var visited = false
|
||||
animal.accept(AllAttributeVisitor(object : AttributeVisitor, ConstantVisitor {
|
||||
override fun visitAnyAttribute(
|
||||
clazz: Clazz,
|
||||
attribute: Attribute
|
||||
) {
|
||||
}
|
||||
|
||||
override fun visitPermittedSubclassesAttribute(
|
||||
clazz: Clazz,
|
||||
permittedSubclassesAttribute: PermittedSubclassesAttribute
|
||||
) {
|
||||
permittedSubclassesAttribute.permittedSubclassConstantsAccept(clazz,this)
|
||||
}
|
||||
|
||||
override fun visitAnyConstant(
|
||||
clazz: Clazz,
|
||||
constant: Constant
|
||||
) {
|
||||
}
|
||||
|
||||
override fun visitClassConstant(
|
||||
clazz: Clazz,
|
||||
classConstant: ClassConstant
|
||||
) {
|
||||
visited = true
|
||||
simpleUsageMarker.isUsed(classConstant) shouldBe true
|
||||
}
|
||||
}))
|
||||
visited shouldBe true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -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" {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user