Compare commits

..

66 Commits

Author SHA1 Message Date
niccolo.piazzesi
8f67c1977b Fix test task dependency 2026-02-12 09:11:03 +01:00
niccolo.piazzesi
0303116af9 Update and clean gradle configuration 2026-02-12 09:07:16 +01:00
niccolo.piazzesi
8425e86946 Update ProguardCORE, address API changes 2026-02-12 09:03:30 +01:00
niccolo.piazzesi
164e3d8f4d Remove java-test-fixtures plugin
The Java-test-fixtures plugin would create incorrect pom files. Since we  don't actually define test fixtures in Proguard (the published jars are empty, you can look older releases), we can just remove it.
2026-01-21 12:03:07 +01:00
niccolo.piazzesi
6776d6a3aa Prepare for 7.8.2 release
Summary: Update all relevant docs to prepare for release containing D56358

Reviewers: dominik.huber, thomas.vochten

Reviewed By: dominik.huber, thomas.vochten

Differential Revision: https://phabricator.guardsquare.com/D57357
2025-12-03 12:32:49 +01:00
niccolo.piazzesi
e1e2689c72 Document support up until java 25 2025-12-01 09:50:36 +01:00
piazzesiNiccolo-GS
14dcc28ba2 Reapply moved InterfaceUsageMarker to fx interface constant marking (#510) 2025-12-01 09:47:13 +01:00
niccolo.piazzesi
0c51b82946 Use latest version in all documentation 2025-10-27 09:11:20 +01:00
piazzesiNiccolo-GS
68dcc9880e Update proguard version 2025-10-27 09:08:23 +01:00
niccolo.piazzesi
7a76843f0c Add release note 2025-10-27 09:03:19 +01:00
Bengt Verscheure
d2170bad63 Use StructuredLineNumberInfo in LineNumberLinearizer 2025-10-24 11:02:26 +02:00
niccolo.piazzesi
6dfd878ebb Run InterfaceUsageMarker before NestUsageMarker to prevent missing marking of permitted subclasses attribute
Closes #501

1. [ClassUsageMarker assumes that the NestUsageMarker will mark the permitted subclasses](869ce156b1/base/src/main/java/proguard/shrink/ClassUsageMarker.java (L1144)).

2. [ NestUsageMarker only marks class constants in the permittedSubclasses attribute if the referenced class is used ](869ce156b1/base/src/main/java/proguard/shrink/NestUsageMarker.java (L100)).

3.  [ If the referenced class is another interface, this is marked by the InterfaceUsageMarker ](869ce156b1/base/src/main/java/proguard/shrink/InterfaceUsageMarker.java (L36)).

4. [However, the interfaceUsageMarker only runs after the NestUsageMarker, hence nothing gets marked if a sealed interface permits another sealed interface](869ce156b1/base/src/main/java/proguard/shrink/UsageMarker.java (L119-130))
2025-10-14 13:43:05 +02:00
niccolo.piazzesi
869ce156b1 Update dependency, update samples, update release notes for 7.8 2025-10-07 13:34:28 +02:00
Fergal Whyte
124b33e473 Bump ProGuard to 7.8.0 and ProGuardCORE to 9.2.0 2025-10-07 13:34:16 +02:00
Robin Lefever
b8a62c8ca8 Bump Nexus publish plugin version 2025-08-25 11:53:54 +02:00
niccolo.piazzesi
886477806c Temporarily downgrade version in examples until release is properly done 2025-08-22 10:47:01 +02:00
Robin Lefever
7fc907d1fb Update ProGuardCORE version to support Kotlin 2.2 2025-08-22 06:48:58 +02:00
piazzesiNiccolo-GS
b10346ba32 Upgrade and cleanup ci configuration (#492) 2025-08-21 09:57:43 +02:00
Thomas Vochten
f2ced20be4 Fix ktlint issue in ClassUsageMarkerTest 2025-08-21 08:28:19 +02:00
huqiuser
35ea6d587f fix NullPointerException for Kotlin typealias of typealias of lambda (#490) 2025-08-20 16:25:40 +02:00
Eric Salemi
eea0ccbe8f Migrate to OSSRH staging API service 2025-08-20 08:18:34 +02:00
Joren Hannes
bee74a9963 Run kotlin linter 2025-08-13 14:56:39 +02:00
Oberon Swings
4b4aa93335 Fix retrace regex mentioned in the manual. 2025-08-13 14:56:37 +02:00
Nolij
4781f5898f Exclude annotation members from aggressive overloading (#453)
* Exclude annotation members from aggressive overloading
2025-08-11 09:15:43 +02:00
Jelle De Coninck
1f9a4a1b94 Remove AppSweep page from manual
Summary: Remove AppSweep page from manual and remove link from toolbar.
2025-06-13 15:52:18 +02:00
Blend Hamiti
40f9222bc3 Add mac finder files to gitignore 2025-05-02 16:28:52 +02:00
James Hamilton
ef6a8352bd Update ProGuardCORE version for Java 24 support (#470)
Update ProGuardCORE version for Java 24 support
2025-03-24 13:28:56 +01:00
niccolo.piazzesi
e225e56a8d Add link to the maven distribution and proguard release in the retrace page. 2025-03-24 11:25:13 +01:00
Ruben Pieters
4288cce536 Bump proguardCore version to include MethodLinker changes.
Includes the change from this proguard-core PR: https://github.com/Guardsquare/proguard-core/pull/133 .

Verified on the jar from the reproducing project: https://github.com/LlamaLad7/slow-proguard-example .
Takes ~45s before, ~10s after.
2025-03-19 12:04:53 +01:00
James Hamilton
3456cf330e Move source files to standard locations (#464) 2025-02-20 12:36:09 +01:00
niccolo.piazzesi
fbcf41fd67 Remove bad import 2024-12-13 14:26:54 +01:00
niccolo.piazzesi
bacde1cede Limit size of strings to 65535 bytes 2024-12-13 11:31:50 +01:00
Thomas Vochten
430a04502d Bump version to 7.6.2 2024-12-12 14:11:44 +01:00
Thomas Vochten
08adfa5552 Bump version to 7.6.1 in the manual 2024-12-12 12:13:01 +01:00
Thomas Vochten
89b1e55ea2 Add release notes for version 7.6.1, bump ProGuardCORE version 2024-12-12 11:27:45 +01:00
Bengt Verscheure
f4c4a13a90 Remove Guardsquare community link. 2024-12-10 15:53:19 +01:00
Thomas Vochten
c1eafc7b6b Log ACD rules for parameterless constructors 2024-12-06 12:47:35 +01:00
Thomas Vochten
73860de626 Discard empty Kotlin metadata 2024-12-06 12:47:35 +01:00
Thomas Vochten
ff66baaced Remove references to encryption 2024-12-04 12:30:55 +01:00
Thomas Vochten
174d3f4155 Upgrade Gradle, upgrade dependencies, bump version to 7.6.1 2024-11-29 08:49:38 +01:00
Thomas Vochten
f5352fece7 Clean up Kotlin verification in ProGuard.java 2024-11-27 09:36:51 +01:00
Bengt Verscheure
844f3d76be Remove variable push replacements optimization 2024-11-20 09:27:50 +01:00
niccolo.piazzesi
7b6712e840 Replace all PartialEvaluator constructor calls with builder calls 2024-10-25 16:19:41 +02:00
James Hamilton
dd4b8bde06 Update version to 7.6.0 2024-10-02 17:42:30 +02:00
James Hamilton
b3deed8286 Update versions for version 7.6 (#440) 2024-09-27 14:43:19 +02:00
alonalbert
8903bfb23f Separate Multiple Frames With a Newline (#433)
When an obfuscated frame resolves to multiple clear frames, separate them with a newline. Closes #432
2024-09-19 15:37:06 +02:00
Jelle De Coninck
03d7effdd2 Improve DictionaryNameFactory performance 2024-09-13 15:02:53 +02:00
James Hamilton
c2146ae315 Update ProGuardCORE version (#429) 2024-08-26 09:23:20 +02:00
James Hamilton
4e643b4f60 Update ProGuardCORE version (#421) 2024-07-18 12:48:54 +02:00
daphnis.chevreton
ee3deb69fa Support a wider char range in class specifications 2024-07-02 10:48:14 +02:00
daphnis.chevreton
6075d17bee Fix gradlew.bat newline chars 2024-07-02 10:48:10 +02:00
James Hamilton
3a9b11bb3c Bump version to 7.5.1 2024-05-29 18:07:33 +02:00
James Hamilton
af475c65b4 Update usage.md (#407) 2024-05-28 16:32:58 +02:00
James Hamilton
8d7ddf898c Update foojay resolver plugin 2024-05-27 11:29:21 +02:00
Ellet
f5f2f06334 Add .kotlin from Kotlin 2.0.0 in .gitignore (#406) 2024-05-27 09:05:12 +02:00
James Hamilton
aa43b9dc21 Update versions for 7.5 (#404)
Updates ProGuardCORE + other required dependencies for Kotlin 2 + Java 22; including running tests with Java 22.
2024-05-23 14:01:48 +02:00
Jelle De Coninck
0d9ceb7451 fix lint violations in ConfigurationParserTest 2024-05-06 11:29:28 +02:00
Jelle De Coninck
1d28c11e36 Synchronize keep flags on getter/setter/backing field for kotlin properties 2024-05-06 11:12:38 +02:00
Jelle De Coninck
b85b2cb201 Bump version to 7.5.0-beta01 and add release note 2024-04-30 16:58:42 +02:00
Jelle De Coninck
0c95982828 integrate kotlin 2 support
Summary:
* Update PGC
* Change use of kotlinx.metadata to kotlin.metadata
* Remove unused `KotlinModuleRewriter` class
2024-04-30 16:58:26 +02:00
James Hamilton
38de2e42b2 Update releasenotes.md 2024-04-19 09:50:00 +02:00
James Hamilton
76b2921738 Update ProGuardCORE version (#398) 2024-04-15 17:11:19 +02:00
niccolo.piazzesi
7483ad32f4 Keep name of Kotlin companion classes when corresponding field is kept and resolve potential name collision in KotlinCompanionEqualizer 2024-03-15 12:46:53 +01:00
niccolo.piazzesi
20c99aa3e8 Parse -maximumremovedandroidloglevel 2024-03-07 10:44:31 +01:00
Bentaii
858bcd0eb5 Fix equals to prevent null values (#388)
Switch frame.getSourceFile().equals(String) around to test String against source file value instead of otherway around to prevent  failures when the value is null.
2024-02-21 08:36:51 +01:00
Vincent Rossetto
1c421bf780 Keep Kotlin interface method when default implementation is used 2024-02-13 11:09:20 +01:00
226 changed files with 4975 additions and 3675 deletions

View File

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

@@ -6,3 +6,5 @@ build
local.properties
/lib/
docs/html
.kotlin
.DS_Store

View File

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

View File

@@ -60,7 +60,6 @@ bytecode:
The resulting applications and libraries are smaller and faster.
## ❓ Getting Help
If you have **usage or general questions** please ask them in the <a href="https://community.guardsquare.com/?utm_source=github&utm_medium=site-link&utm_campaign=github-community">**Guardsquare Community**.</a>
Please use <a href="https://github.com/guardsquare/proguard/issues">**the issue tracker**</a> to report actual **bugs 🐛, crashes**, etc.
<br />
<br />
@@ -109,7 +108,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.guardsquare:proguard-gradle:7.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).

View File

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

View File

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

View File

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

View File

@@ -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 = "**";

View File

@@ -39,8 +39,6 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
/**
* This class parses ProGuard configurations. Configurations can be read from an
@@ -51,6 +49,8 @@ import java.util.function.BiFunction;
*/
public class ConfigurationParser implements AutoCloseable
{
private final boolean useDalvikVerification = System.getProperty("proguard.use.dalvik.identifier.verification") != null;
private final WordReader reader;
private final Properties properties;
@@ -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.");
}

View File

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

View File

@@ -0,0 +1,6 @@
package proguard.classfile.attribute;
public enum ProGuardOrigin implements LineOrigin
{
INLINED
}

View File

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

View File

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

View File

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

View File

@@ -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() {}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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));
}
}
}

View File

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

View File

@@ -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);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -22,15 +22,17 @@ import testutils.asConfiguration
class ClassSpecificationVisitorFactoryTest : FreeSpec({
"Given a class specification with an extending class" - {
val (programClassPool, _) = ClassPoolBuilder.fromSource(
JavaSource("Foo.java", "class Foo extends Bar3 { }"),
JavaSource("Bar3.java", "class Bar3 { }")
)
val (programClassPool, _) =
ClassPoolBuilder.fromSource(
JavaSource("Foo.java", "class Foo extends Bar3 { }"),
JavaSource("Bar3.java", "class Bar3 { }"),
)
val spec = "-keep class F** extends Bar* { public *; }".asConfiguration().keep.first()
val classVisitor = spyk<ClassVisitor>()
val visitor = KeepClassSpecificationVisitorFactory(true, false, false).createClassPoolVisitor(
spec, classVisitor, null, null, null
)
val visitor =
KeepClassSpecificationVisitorFactory(true, false, false).createClassPoolVisitor(
spec, classVisitor, null, null, null,
)
"Then the visitor should visit the sub-class" {
programClassPool.accept(visitor)
verify(exactly = 1) {
@@ -40,15 +42,17 @@ class ClassSpecificationVisitorFactoryTest : FreeSpec({
}
"Given a class specification with an implementing class" - {
val (programClassPool, _) = ClassPoolBuilder.fromSource(
JavaSource("Foo.java", "class Foo implements Bar3 { }"),
JavaSource("Bar3.java", "interface Bar3 { }")
)
val (programClassPool, _) =
ClassPoolBuilder.fromSource(
JavaSource("Foo.java", "class Foo implements Bar3 { }"),
JavaSource("Bar3.java", "interface Bar3 { }"),
)
val spec = "-keep class F** implements Bar* { public *; }".asConfiguration().keep.first()
val classVisitor = spyk<ClassVisitor>()
val visitor = KeepClassSpecificationVisitorFactory(true, false, false).createClassPoolVisitor(
spec, classVisitor, null, null, null
)
val visitor =
KeepClassSpecificationVisitorFactory(true, false, false).createClassPoolVisitor(
spec, classVisitor, null, null, null,
)
"Then the visitor should visit the implementing class" {
programClassPool.accept(visitor)
verify(exactly = 1) {
@@ -58,15 +62,17 @@ class ClassSpecificationVisitorFactoryTest : FreeSpec({
}
"Given a class specification with a negation" - {
val (programClassPool, _) = ClassPoolBuilder.fromSource(
JavaSource("Foo.java", "class Foo implements Bar3 { }"),
JavaSource("Bar3.java", "interface Bar3 { }")
)
val (programClassPool, _) =
ClassPoolBuilder.fromSource(
JavaSource("Foo.java", "class Foo implements Bar3 { }"),
JavaSource("Bar3.java", "interface Bar3 { }"),
)
val spec = "-keep !interface B** { public *; }".asConfiguration().keep.first()
val classVisitor = spyk<ClassVisitor>()
val visitor = KeepClassSpecificationVisitorFactory(true, false, false).createClassPoolVisitor(
spec, classVisitor, null, null, null
)
val visitor =
KeepClassSpecificationVisitorFactory(true, false, false).createClassPoolVisitor(
spec, classVisitor, null, null, null,
)
"Then the visitor should not visit the interface of which the name starts with 'B'" {
programClassPool.accept(visitor)
verify(exactly = 0) {
@@ -76,15 +82,17 @@ class ClassSpecificationVisitorFactoryTest : FreeSpec({
}
"Given a conditional class specification containing a wildcard" - {
val (programClassPool, _) = ClassPoolBuilder.fromSource(
JavaSource("FooBar.java", "class FooBar extends Bar { }"),
JavaSource("Bar.java", "class Bar { }")
)
val (programClassPool, _) =
ClassPoolBuilder.fromSource(
JavaSource("FooBar.java", "class FooBar extends Bar { }"),
JavaSource("Bar.java", "class Bar { }"),
)
val spec = "-if class * -keep class <1> { public *; }".asConfiguration().keep.first()
val classVisitor = spyk<ClassVisitor>()
val visitor = KeepClassSpecificationVisitorFactory(true, false, false).createClassPoolVisitor(
spec, classVisitor, null, null, null
)
val visitor =
KeepClassSpecificationVisitorFactory(true, false, false).createClassPoolVisitor(
spec, classVisitor, null, null, null,
)
"Then the visitor should visit the matched class" {
programClassPool.accept(visitor)
verify(exactly = 1) {
@@ -94,16 +102,18 @@ class ClassSpecificationVisitorFactoryTest : FreeSpec({
}
"Given a class specification extending an annotation class" - {
val (programClassPool, _) = ClassPoolBuilder.fromSource(
JavaSource("Annotation.java", "@interface Annotation { }"),
JavaSource("FooBar.java", "class FooBar extends Bar { }"),
JavaSource("Bar.java", "@Annotation class Bar { }")
)
val (programClassPool, _) =
ClassPoolBuilder.fromSource(
JavaSource("Annotation.java", "@interface Annotation { }"),
JavaSource("FooBar.java", "class FooBar extends Bar { }"),
JavaSource("Bar.java", "@Annotation class Bar { }"),
)
val spec = "-keep class * extends @Annotation *".asConfiguration().keep.first()
val classVisitor = spyk<ClassVisitor>()
val visitor = KeepClassSpecificationVisitorFactory(true, false, false).createClassPoolVisitor(
spec, classVisitor, null, null, null
)
val visitor =
KeepClassSpecificationVisitorFactory(true, false, false).createClassPoolVisitor(
spec, classVisitor, null, null, null,
)
"Then the visitor should visit the matched class" {
programClassPool.accept(visitor)
verify(exactly = 1) {
@@ -113,28 +123,32 @@ class ClassSpecificationVisitorFactoryTest : FreeSpec({
}
"Given a class with fields of different types" - {
fun createFieldVisitor(config: String, fieldVisitor: MemberVisitor): ClassPoolVisitor =
fun createFieldVisitor(
config: String,
fieldVisitor: MemberVisitor,
): ClassPoolVisitor =
KeepClassSpecificationVisitorFactory(true, false, false).createClassPoolVisitor(
config.asConfiguration().keep.first(), null, fieldVisitor, null, null
config.asConfiguration().keep.first(), null, fieldVisitor, null, null,
)
val (programClassPool, _) = ClassPoolBuilder.fromSource(
AssemblerSource(
"ClassWithFields.jbc",
"""
public class ClassWithFields {
java.lang.String myField;
int myField;
long myField;
com.example.ClassInAPackage myField;
java.lang.String[] myField;
int[] myField;
com.example.ClassInAPackage[] myField;
ClassWithFields myField;
}
""".trimIndent()
val (programClassPool, _) =
ClassPoolBuilder.fromSource(
AssemblerSource(
"ClassWithFields.jbc",
"""
public class ClassWithFields {
java.lang.String myField;
int myField;
long myField;
com.example.ClassInAPackage myField;
java.lang.String[] myField;
int[] myField;
com.example.ClassInAPackage[] myField;
ClassWithFields myField;
}
""".trimIndent(),
),
)
)
"Then * should visit any non-primitive, non-array without package separator type fields" {
with(MemberCounter()) {
@@ -166,28 +180,32 @@ class ClassSpecificationVisitorFactoryTest : FreeSpec({
}
"Given a class with methods of different types" - {
fun createMethodVisitor(config: String, methodVisitor: MemberVisitor): ClassPoolVisitor =
fun createMethodVisitor(
config: String,
methodVisitor: MemberVisitor,
): ClassPoolVisitor =
KeepClassSpecificationVisitorFactory(true, false, false).createClassPoolVisitor(
config.asConfiguration().keep.first(), null, null, methodVisitor, null
config.asConfiguration().keep.first(), null, null, methodVisitor, null,
)
val (programClassPool, _) = ClassPoolBuilder.fromSource(
AssemblerSource(
"ClassWithFields.jbc",
"""
public class ClassWithFields {
public void myMethod(java.lang.String);
public void myMethod(int);
public void myMethod(long);
public void myMethod(com.example.ClassInAPackage);
public void myMethod(java.lang.String[]);
public void myMethod(int[]);
public void myMethod(com.example.ClassInAPackage[]);
public void myMethod(ClassWithFields);
}
""".trimIndent()
val (programClassPool, _) =
ClassPoolBuilder.fromSource(
AssemblerSource(
"ClassWithFields.jbc",
"""
public class ClassWithFields {
public void myMethod(java.lang.String);
public void myMethod(int);
public void myMethod(long);
public void myMethod(com.example.ClassInAPackage);
public void myMethod(java.lang.String[]);
public void myMethod(int[]);
public void myMethod(com.example.ClassInAPackage[]);
public void myMethod(ClassWithFields);
}
""".trimIndent(),
),
)
)
"Then * should visit any non-primitive, non-array without package separator type methods" {
with(MemberCounter()) {

View File

@@ -9,9 +9,13 @@ package proguard
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.FreeSpec
import io.kotest.extensions.system.withSystemProperty
import io.kotest.matchers.collections.shouldHaveSize
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.string.shouldContain
import io.mockk.every
import io.mockk.mockkObject
import proguard.classfile.AccessConstants.PUBLIC
import testutils.asConfiguration
import java.io.ByteArrayOutputStream
@@ -39,6 +43,8 @@ class ConfigurationParserTest : FreeSpec({
return configuration
}
fun parseRulesAsArguments(rules: String) = rules.split(' ', '\n').toTypedArray()
"Keep rule tests" - {
"Keep rule with <fields> wildcard should be valid" {
parseConfiguration("-keep class * { <fields>; }")
@@ -144,12 +150,12 @@ class ConfigurationParserTest : FreeSpec({
"""-alwaysinline class * {
@org.chromium.build.annotations.AlwaysInline *;
}
"""
""",
)
"The option prints out a warning" {
customOutputStream.toString() shouldContain "Warning: The R8 option -alwaysinline is currently not supported by ProGuard.\n" +
"This option will have no effect on the optimized artifact."
customOutputStream.toString() shouldContain "Warning: The R8 option -alwaysinline is currently not " +
"supported by ProGuard.\nThis option will have no effect on the optimized artifact."
System.setOut(savedPrintStream)
}
}
@@ -184,12 +190,12 @@ class ConfigurationParserTest : FreeSpec({
"""-identifiernamestring class * {
@org.chromium.build.annotations.IdentifierNameString *;
}
"""
""",
)
"The option prints out a warning" {
customOutputStream.toString() shouldContain "Warning: The R8 option -identifiernamestring is currently not supported by ProGuard.\n" +
"This option will have no effect on the optimized artifact."
customOutputStream.toString() shouldContain "Warning: The R8 option -identifiernamestring is currently " +
"not supported by ProGuard.\nThis option will have no effect on the optimized artifact."
System.setOut(savedPrintStream)
}
}
@@ -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) }
}
}
}
}
})

View File

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

View File

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

View File

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

View File

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

View File

@@ -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",
),
)
}
})

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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()
}
}
}
})

View File

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

View File

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

View File

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

View 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
}
}
}
})

View File

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

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