Compare commits

...

64 Commits
v7.4.1 ... sync

Author SHA1 Message Date
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
James Hamilton
d4692c3835 Update version number in examples 2024-01-31 18:05:50 +01:00
James Hamilton
712fd768ca Add ability to skip to next option if parse error
Allows providing a function to handle the case of an unknown option.

```
    public static void main(String[] args)
    {
        try
        {
            try (ConfigurationParser parser = new ConfigurationParser(new String[]{"-keep class * {}", "-unknownoption", "-whatisthisoption?"}, System.getProperties()))
            {
                parser.parse(new Configuration(), (option, location) -> {
                    System.out.println("Unknown option: " + option + " @ " + location);
                });
            }
            catch (ParseException ex)
            {
                ex.printStackTrace();
            }
        }
        catch (IOException ex)
        {
            ex.printStackTrace();
        }
    }
```

Output:

```
Unknown option: -unknownoption @ argument number 2
Unknown option: -whatisthisoption? @ argument number 3
```
2024-01-22 14:55:53 +01:00
James Hamilton
c35913c3f2 Add release note 2024-01-19 18:41:05 +01:00
Cristian Garcia
5a8d50090a Replace project usages in ProGuardTask by injected Gradle services (#380)
fixes #254
2024-01-19 18:40:33 +01:00
James Hamilton
06c2d12f7a Bump version number 2024-01-16 09:36:28 +01:00
Oberon Swings
a7265a3536 Conservatively mark wide parameter used if it can only be partially marked. 2024-01-08 08:19:01 +01:00
Ruben Pieters
7160a9e484 Fix nullability flag on named type arguments and reified flag on type parameters 2023-12-07 16:49:40 +01:00
Robin Lefever
12c9c3f23e Fix MemberDescriptorSpecializer checking wrong processing flags
This diff also ensures the `IS_CLASS_AVAILABLE` processing flag is set on classes instead of its members.
This requires us to change usages of this flag in some other places as well.
2023-12-01 14:56:44 +01:00
daphnis.chevreton
a02100cb93 Fix potential access issues when backporting
Backporting nest host/members is not supported and can therefore introduce access issue.
Running the access fixer if `-allowaccessmodification` is set solves this issue by setting appropriate visibility.
2023-11-29 16:14:03 +01:00
Bengt Verscheure
38a0e498b9 Use system-specific line separators 2023-11-24 14:49:44 +01:00
218 changed files with 4520 additions and 3472 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.1'
classpath 'com.guardsquare:proguard-gradle:7.8.0'
}
}
```
@@ -193,7 +192,7 @@ guide](CONTRIBUTING.md) if you would like to contribute.
## 📝 License
Copyright (c) 2002-2023 [Guardsquare NV](https://www.guardsquare.com/).
Copyright (c) 2002-2025 [Guardsquare NV](https://www.guardsquare.com/).
ProGuard is released under the [GNU General Public License, version
2](LICENSE), with [exceptions granted to a number of
projects](docs/md/manual/license/gplexception.md).

View File

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

View File

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

View File

@@ -2,11 +2,10 @@ plugins {
id 'java-library'
id 'java-test-fixtures'
id 'maven-publish'
id "org.jetbrains.kotlin.jvm" version "$kotlinVersion"
id 'com.adarshr.test-logger' version '3.0.0'
id 'de.jansauer.printcoverage' version '2.0.0'
id "org.jetbrains.kotlin.jvm"
id 'com.adarshr.test-logger' version '4.0.0'
id 'jacoco'
id "org.jlleitschuh.gradle.ktlint" version '10.2.1'
id "org.jlleitschuh.gradle.ktlint" version '12.1.2'
}
repositories {
@@ -22,19 +21,19 @@ tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile) {
dependencies {
api "com.guardsquare:proguard-core:${proguardCoreVersion}"
implementation "com.google.code.gson:gson:${gsonVersion}"
implementation 'org.apache.logging.log4j:log4j-api:2.19.0'
implementation 'org.apache.logging.log4j:log4j-core:2.19.0'
implementation 'org.json:json:20220924'
implementation 'org.apache.logging.log4j:log4j-api:2.24.2'
implementation 'org.apache.logging.log4j:log4j-core:2.24.2'
implementation 'org.json:json:20231013'
testImplementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
testImplementation "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"
testImplementation 'dev.zacsweers.kctfork:core:0.2.1'
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.5.4' // for kotest framework
testImplementation 'io.kotest:kotest-assertions-core-jvm:5.5.4' // for kotest core jvm assertions
testImplementation 'io.kotest:kotest-property-jvm:5.5.4' // for kotest property test
testImplementation 'io.mockk:mockk:1.13.2' // for mocking
testImplementation 'dev.zacsweers.kctfork:core:0.6.0'
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.9.1' // for kotest framework
testImplementation 'io.kotest:kotest-assertions-core-jvm:5.9.1' // for kotest core jvm assertions
testImplementation 'io.kotest:kotest-property-jvm:5.9.1' // for kotest property test
testImplementation 'io.mockk:mockk:1.13.13' // for mocking
testImplementation(testFixtures("com.guardsquare:proguard-core:9.0.8")) {
testImplementation(testFixtures("com.guardsquare:proguard-core:${proguardCoreVersion}")) {
exclude group: 'com.guardsquare', module: 'proguard-core'
}
}
@@ -50,7 +49,7 @@ jar {
// Early access automatic downloads are not yet supported:
// https://github.com/gradle/gradle/issues/14814
// But it will work if e.g. Java N-ea is pre-installed
def javaVersionsForTest = 9..21
def javaVersionsForTest = 9..23
test {
useJUnitPlatform()
@@ -64,8 +63,8 @@ task testAllJavaVersions() { testAllTask ->
useJUnitPlatform()
ignoreFailures = true
// The version of bytebuddy used by mockk only supports Java 20 experimentally so far
if (version >= 20) systemProperty 'net.bytebuddy.experimental', true
// The version of bytebuddy used by mockk only supports Java 22 experimentally so far
// if (version >= 22) systemProperty 'net.bytebuddy.experimental', true
testAllTask.dependsOn(it)
@@ -86,9 +85,11 @@ jacocoTestReport {
classDirectories.setFrom(classes)
executionData.setFrom project.fileTree(dir: '.', include: '**/build/jacoco/*.exec')
reports {
xml.enabled true
csv.enabled false
html.destination file("${buildDir}/reports/coverage")
xml.required = true
csv.required = false
}
javaVersionsForTest.each { version ->
mustRunAfter "testJava$version"
}
}

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

@@ -20,14 +20,25 @@
*/
package proguard;
import proguard.classfile.*;
import proguard.classfile.AccessConstants;
import proguard.classfile.ClassConstants;
import proguard.classfile.JavaAccessConstants;
import proguard.classfile.JavaTypeConstants;
import proguard.classfile.TypeConstants;
import proguard.classfile.util.ClassUtil;
import proguard.util.*;
import java.io.*;
import java.net.*;
import java.util.*;
import proguard.util.ListUtil;
import proguard.util.StringUtil;
import java.io.File;
import java.io.IOException;
import java.io.LineNumberReader;
import java.io.StringReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.function.BiConsumer;
/**
* This class parses ProGuard configurations. Configurations can be read from an
@@ -38,6 +49,8 @@ import java.util.*;
*/
public class ConfigurationParser implements AutoCloseable
{
private final boolean useDalvikVerification = System.getProperty("proguard.use.dalvik.identifier.verification") != null;
private final WordReader reader;
private final Properties properties;
@@ -139,9 +152,24 @@ public class ConfigurationParser implements AutoCloseable
* @throws IOException if an IO error occurs while reading a configuration.
*/
public void parse(Configuration configuration)
throws ParseException, IOException {
parse(configuration, null);
}
/**
* Parses and returns the configuration.
*
* @param configuration the configuration that is updated as a side-effect.
* @param unknownOptionHandler optional handler for unknown options; if null then a {@link ParseException}
* is thrown when encountering an unknown option.
* @throws ParseException if the any of the configuration settings contains
* a syntax error.
* @throws IOException if an IO error occurs while reading a configuration.
*/
public void parse(Configuration configuration, BiConsumer<String, String> unknownOptionHandler)
throws ParseException, IOException
{
while (nextWord != null)
parseWord: while (nextWord != null)
{
lastComments = reader.lastComments();
@@ -232,9 +260,18 @@ public class ConfigurationParser implements AutoCloseable
else if (ConfigurationConstants.OPTIMIZE_AGGRESSIVELY .startsWith(nextWord)) configuration.optimizeConservatively = parseNoArgument(false);
else if (ConfigurationConstants.ALWAYS_INLINE .startsWith(nextWord)) parseUnsupportedR8Rules(ConfigurationConstants.ALWAYS_INLINE, true);
else if (ConfigurationConstants.IDENTIFIER_NAME_STRING .startsWith(nextWord)) parseUnsupportedR8Rules(ConfigurationConstants.IDENTIFIER_NAME_STRING, true);
else if (ConfigurationConstants.MAXIMUM_REMOVED_ANDROID_LOG_LEVEL .equals(nextWord)) parseMaximumRemovedAndroidLogLevel();
else
{
throw new ParseException("Unknown option " + reader.locationDescription());
if (unknownOptionHandler != null) {
unknownOptionHandler.accept(nextWord, reader.lineLocationDescription());
while (nextWord != null) {
readNextWord();
if (nextWord != null && nextWord.startsWith("-")) {
continue parseWord;
}
}
} else throw new ParseException("Unknown option " + reader.locationDescription());
}
}
}
@@ -1918,7 +1955,7 @@ public class ConfigurationParser implements AutoCloseable
private void checkJavaIdentifier(String expectedDescription, String identifier, boolean allowGenerics)
throws ParseException
{
if (!isJavaIdentifier(identifier))
if (!isValidIdentifier(identifier))
{
throw new ParseException("Expecting " + expectedDescription +
" before " + reader.locationDescription());
@@ -1931,6 +1968,10 @@ public class ConfigurationParser implements AutoCloseable
}
}
private boolean isValidIdentifier(String word)
{
return useDalvikVerification ? isDexIdentifier(word) : isJavaIdentifier(word);
}
/**
* Returns whether the given word is a valid Java identifier.
@@ -1965,6 +2006,43 @@ public class ConfigurationParser implements AutoCloseable
return true;
}
/**
* Returns whether the given word is a valid DEX identifier. Special wildcard characters for
* ProGuard class specifiction syntaxs are accepted. The list of valid identifier can be
* found at https://source.android.com/docs/core/runtime/dex-format#simplename
*/
private boolean isDexIdentifier(String word) {
if (word.isEmpty()) {
return false;
}
int[] codePoints = word.codePoints().toArray();
for (int index = 0; index < codePoints.length; index++) {
int c = codePoints[index];
boolean isLetterOrNumber = Character.isLetterOrDigit(c);
boolean isValidSymbol = c == '$' || c == '-' || c == '_';
boolean isWithinSupportedUnicodeRanges =
(c >= 0x00a1 && c <= 0x1fff)
|| (c >= 0x2010 && c <= 0x2027)
|| (c >= 0x2030 && c <= 0xd7ff)
|| (c >= 0xe000 && c <= 0xffef)
|| (c >= 0x10000 && c <= 0x10ffff);
boolean isProGuardSymbols =
c == '.' || c == '[' || c == ']' || c == '<' || c == '>' || c == '-' || c == '!'
|| c == '*' || c == '?' || c == '%';
if (!(isLetterOrNumber
|| isValidSymbol
|| isWithinSupportedUnicodeRanges
|| isProGuardSymbols)) {
return false;
}
}
return true;
}
/**
* Returns whether the given word contains angle brackets around
@@ -2051,9 +2129,22 @@ public class ConfigurationParser implements AutoCloseable
parseClassSpecificationArguments();
}
System.out.println("Warning: The R8 option " + option + " is currently not supported by ProGuard.\n" +
"This option will have no effect on the optimized artifact.");
warnUnsupportedR8Option(option);
}
private void parseMaximumRemovedAndroidLogLevel() throws IOException, ParseException {
parseIntegerArgument();
if (!configurationEnd(true)) {
parseClassSpecificationArguments();
}
warnUnsupportedR8Option(ConfigurationConstants.MAXIMUM_REMOVED_ANDROID_LOG_LEVEL);
}
private static void warnUnsupportedR8Option(String option) {
System.out.println("Warning: The R8 option " + option + " is currently not supported by ProGuard.\n" +
"This option will have no effect on the optimized artifact.");
}

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

@@ -314,6 +314,12 @@ public class Backporter implements Pass
appView.programClassPool.classesAccept(new ClassVersionSetter(targetClassVersion));
}
// Backporting may introduce access issues, for example related to nest members/host.
if (configuration.allowAccessModification)
{
appView.programClassPool.classesAccept(new AccessFixer());
}
logger.info(" Number of converted string concatenations: {}", replacedStringConcatCounter.getCount());
logger.info(" Number of converted lambda expressions: {}", lambdaExpressionCounter.getCount());
logger.info(" Number of converted static interface methods: {}", staticInterfaceMethodCounter.getCount());

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,28 @@ implements KotlinMetadataVisitor,
annotationCounter.reset()
);
kotlinPropertyMetadata.flags.common.hasAnnotations = annotationCounter.getCount() > 0;
kotlinPropertyMetadata.flags.hasAnnotations = annotationCounter.getCount() > 0;
}
else if (kotlinPropertyMetadata.referencedBackingField != null)
{
kotlinPropertyMetadata.referencedBackingField.accept(kotlinPropertyMetadata.referencedBackingFieldClass, annotationCounter);
kotlinPropertyMetadata.flags.common.hasAnnotations = annotationCounter.getCount() > 0;
kotlinPropertyMetadata.flags.hasAnnotations = annotationCounter.getCount() > 0;
}
else
{
kotlinPropertyMetadata.flags.common.hasAnnotations = false;
kotlinPropertyMetadata.flags.hasAnnotations = false;
}
if (kotlinPropertyMetadata.flags.hasGetter && kotlinPropertyMetadata.referencedGetterMethod != null)
if (kotlinPropertyMetadata.referencedGetterMethod != null)
{
kotlinPropertyMetadata.referencedGetterMethod.accept(clazz, annotationCounter.reset());
kotlinPropertyMetadata.getterFlags.common.hasAnnotations = annotationCounter.getCount() > 0;
kotlinPropertyMetadata.getterFlags.hasAnnotations = annotationCounter.getCount() > 0;
}
if (kotlinPropertyMetadata.flags.hasSetter && kotlinPropertyMetadata.referencedSetterMethod != null)
if (kotlinPropertyMetadata.flags.isVar && kotlinPropertyMetadata.referencedSetterMethod != null)
{
kotlinPropertyMetadata.referencedSetterMethod.accept(clazz, annotationCounter.reset());
kotlinPropertyMetadata.setterFlags.common.hasAnnotations = annotationCounter.getCount() > 0;
kotlinPropertyMetadata.setterFlags.hasAnnotations = annotationCounter.getCount() > 0;
}
}
@@ -157,7 +157,7 @@ implements KotlinMetadataVisitor,
kotlinFunctionMetadata.returnTypeAccept( clazz, kotlinMetadata, this);
kotlinFunctionMetadata.referencedMethodAccept(annotationCounter.reset());
kotlinFunctionMetadata.flags.common.hasAnnotations = annotationCounter.getCount() != 0;
kotlinFunctionMetadata.flags.hasAnnotations = annotationCounter.getCount() != 0;
}
// Implementations for KotlinConstructorVisitor.
@@ -172,12 +172,12 @@ implements KotlinMetadataVisitor,
if (kotlinClassKindMetadata.flags.isAnnotationClass)
{
//PROBBUG where are the annotations?
kotlinConstructorMetadata.flags.common.hasAnnotations = false;
kotlinConstructorMetadata.flags.hasAnnotations = false;
}
else
{
kotlinConstructorMetadata.referencedMethodAccept(clazz, annotationCounter.reset());
kotlinConstructorMetadata.flags.common.hasAnnotations = annotationCounter.getCount() != 0;
kotlinConstructorMetadata.flags.hasAnnotations = annotationCounter.getCount() != 0;
}
}
@@ -192,7 +192,7 @@ implements KotlinMetadataVisitor,
kotlinTypeAliasMetadata.expandedTypeAccept( clazz, kotlinDeclarationContainerMetadata, this);
kotlinTypeAliasMetadata.versionRequirementAccept(clazz, kotlinDeclarationContainerMetadata, this);
kotlinTypeAliasMetadata.flags.common.hasAnnotations = !kotlinTypeAliasMetadata.annotations.isEmpty();
kotlinTypeAliasMetadata.flags.hasAnnotations = !kotlinTypeAliasMetadata.annotations.isEmpty();
}
// Implementations for KotlinTypeVisitor.
@@ -202,8 +202,6 @@ implements KotlinMetadataVisitor,
kotlinTypeMetadata.typeArgumentsAccept(clazz, this);
kotlinTypeMetadata.upperBoundsAccept( clazz, this);
kotlinTypeMetadata.abbreviationAccept( clazz, this);
kotlinTypeMetadata.flags.common.hasAnnotations = !kotlinTypeMetadata.annotations.isEmpty();
}
@Override
@@ -213,7 +211,6 @@ implements KotlinMetadataVisitor,
KotlinTypeMetadata kotlinTypeMetadata)
{
kotlinFunctionMetadata.referencedMethodAccept(this.annotationCounter.reset());
kotlinTypeMetadata.flags.common.hasAnnotations = annotationCounter.getParameterAnnotationCount(0) > 0;
}
// Implementations for KotlinTypeParameterVisitor.
@@ -225,8 +222,6 @@ implements KotlinMetadataVisitor,
public void visitAnyTypeParameter(Clazz clazz, KotlinTypeParameterMetadata kotlinTypeParameterMetadata)
{
kotlinTypeParameterMetadata.upperBoundsAccept(clazz, this);
kotlinTypeParameterMetadata.flags.common.hasAnnotations = !kotlinTypeParameterMetadata.annotations.isEmpty();
}
// Implementations for KotlinValueParameterVisitor.
@@ -242,10 +237,10 @@ implements KotlinMetadataVisitor,
kotlinFunctionMetadata,
this);
if (kotlinValueParameterMetadata.flags.common.hasAnnotations)
if (kotlinValueParameterMetadata.flags.hasAnnotations)
{
kotlinFunctionMetadata.referencedMethodAccept(annotationCounter.reset());
kotlinValueParameterMetadata.flags.common.hasAnnotations =
kotlinValueParameterMetadata.flags.hasAnnotations =
annotationCounter.getParameterAnnotationCount(kotlinValueParameterMetadata.index) > 0;
}
}
@@ -261,12 +256,12 @@ implements KotlinMetadataVisitor,
kotlinConstructorMetadata,
this);
if (kotlinValueParameterMetadata.flags.common.hasAnnotations)
if (kotlinValueParameterMetadata.flags.hasAnnotations)
{
if (!kotlinClassKindMetadata.flags.isAnnotationClass)
{
kotlinConstructorMetadata.referencedMethodAccept(clazz, annotationCounter.reset());
kotlinValueParameterMetadata.flags.common.hasAnnotations =
kotlinValueParameterMetadata.flags.hasAnnotations =
annotationCounter.getParameterAnnotationCount(kotlinValueParameterMetadata.index) > 0;
}
}
@@ -283,10 +278,10 @@ implements KotlinMetadataVisitor,
kotlinPropertyMetadata,
this);
if (kotlinValueParameterMetadata.flags.common.hasAnnotations)
if (kotlinValueParameterMetadata.flags.hasAnnotations)
{
kotlinPropertyMetadata.referencedSetterMethod.accept(clazz, annotationCounter.reset());
kotlinValueParameterMetadata.flags.common.hasAnnotations =
kotlinValueParameterMetadata.flags.hasAnnotations =
annotationCounter.getParameterAnnotationCount(kotlinValueParameterMetadata.index) > 0;
}
}

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.referencedGetterMethod,
kotlinPropertyMetadata.referencedSetterMethod)
.filter(Objects::nonNull)
.collect(Collectors.toList());
int flags = 0;
for (Processable processable : processables) {
flags |= processable.getProcessingFlags();
}
// Only copy the keep flags.
int copiedFlags = flags & DONT_SHRINK_OR_OPTIMIZE_OR_OBFUSCATE;
processables.forEach(p -> p.setProcessingFlags(p.getProcessingFlags() | copiedFlags));
}
})));
appView.programClassPool.classesAccept(propertyVisitor);
appView.libraryClassPool.classesAccept(propertyVisitor);
}
// Mark members that can be safely used for generalization,
@@ -234,8 +272,7 @@ public class Marker implements Pass
{
// Program classes are always available and safe to generalize/specialize from/to.
ClassVisitor isClassAvailableMarker =
new AllMemberVisitor(
new ProcessingFlagSetter(ProcessingFlags.IS_CLASS_AVAILABLE));
new ProcessingFlagSetter(ProcessingFlags.IS_CLASS_AVAILABLE);
programClassPool.classesAccept(isClassAvailableMarker);

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

@@ -141,7 +141,7 @@ implements MemberVisitor
if (valueClass != null &&
valueClass.extendsOrImplements(ClassUtil.internalClassNameFromClassType(fieldType)) &&
(programField.getProcessingFlags() & ProcessingFlags.IS_CLASS_AVAILABLE) != 0)
(valueClass.getProcessingFlags() & ProcessingFlags.IS_CLASS_AVAILABLE) != 0)
{
logger.debug("MemberDescriptorSpecializer [{}.{} {}] -> {}",
programClass.getName(),
@@ -210,7 +210,7 @@ implements MemberVisitor
if (valueClass != null &&
valueClass.extendsOrImplements(ClassUtil.internalClassNameFromClassType(parameterType)) &&
(programMethod.getProcessingFlags() & ProcessingFlags.IS_CLASS_AVAILABLE) != 0)
(valueClass.getProcessingFlags() & ProcessingFlags.IS_CLASS_AVAILABLE) != 0)
{
logger.debug("MemberDescriptorSpecializer [{}.{}{}]: parameter #{}: {} -> {}",
programClass.getName(),

View File

@@ -22,12 +22,22 @@ package proguard.optimize;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import proguard.classfile.*;
import proguard.classfile.AccessConstants;
import proguard.classfile.Clazz;
import proguard.classfile.Field;
import proguard.classfile.Member;
import proguard.classfile.Method;
import proguard.classfile.ProgramClass;
import proguard.classfile.attribute.CodeAttribute;
import proguard.classfile.constant.*;
import proguard.classfile.constant.AnyMethodrefConstant;
import proguard.classfile.constant.ClassConstant;
import proguard.classfile.constant.FieldrefConstant;
import proguard.classfile.constant.RefConstant;
import proguard.classfile.constant.visitor.ConstantVisitor;
import proguard.classfile.editor.*;
import proguard.classfile.instruction.*;
import proguard.classfile.editor.CodeAttributeEditor;
import proguard.classfile.editor.ConstantPoolEditor;
import proguard.classfile.instruction.ConstantInstruction;
import proguard.classfile.instruction.Instruction;
import proguard.classfile.instruction.visitor.InstructionVisitor;
import proguard.classfile.visitor.ClassVisitor;
import proguard.util.ProcessingFlags;
@@ -200,7 +210,7 @@ implements InstructionVisitor,
// DGD-486: Only generalize members which are always available. Partial replacement of a class that is not
// available on all platforms may result in a VerifyError at runtime.
if (referencedMember != null &&
(referencedMember.getProcessingFlags() & ProcessingFlags.IS_CLASS_AVAILABLE) != 0)
(clazz.getProcessingFlags() & ProcessingFlags.IS_CLASS_AVAILABLE) != 0)
{
clazz.constantPoolEntryAccept(refConstant.u2classIndex, this);
@@ -287,13 +297,14 @@ implements InstructionVisitor,
// Otherwise, look in the super class itself.
// Only consider public classes and methods, to avoid any
// access problems.
// Only consider classes that are marked as available.
if (generalizedClass == null &&
(superClass.getAccessFlags() & AccessConstants.PUBLIC) != 0)
(superClass.getAccessFlags() & AccessConstants.PUBLIC) != 0 &&
(superClass.getProcessingFlags() & ProcessingFlags.IS_CLASS_AVAILABLE) != 0)
{
Method method = superClass.findMethod(memberName, memberType);
if (method != null &&
(method.getAccessFlags() & AccessConstants.PUBLIC) != 0 &&
(method.getProcessingFlags() & ProcessingFlags.IS_CLASS_AVAILABLE) != 0)
(method.getAccessFlags() & AccessConstants.PUBLIC) != 0)
{
// Remember the generalized class and class member.
generalizedClass = superClass;
@@ -308,7 +319,7 @@ implements InstructionVisitor,
Field field = clazz.findField(memberName, memberType);
if (field != null &&
(field.getProcessingFlags() & ProcessingFlags.IS_CLASS_AVAILABLE) != 0)
(clazz.getProcessingFlags() & ProcessingFlags.IS_CLASS_AVAILABLE) != 0)
{
// Remember the generalized class and class member.
generalizedClass = clazz;

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();
/**
@@ -124,6 +124,22 @@ implements MemberVisitor,
-1L : -2L);
}
if (programMethod.processingInfo instanceof ProgramMethodOptimizationInfo
&& parameterSize >= 64) {
int parameterSizesCummulative = 0;
for (int index = 0; parameterSizesCummulative < 64; index++) {
boolean isCategory2 =
((ProgramMethodOptimizationInfo) programMethod.processingInfo).getParameterSize(index)
== 2;
if (parameterSizesCummulative == 63 && isCategory2) {
markParameterUsed(programMethod, 63);
}
parameterSizesCummulative +=
((ProgramMethodOptimizationInfo) programMethod.processingInfo)
.getParameterSize(index);
}
}
// Is it a native method?
if ((accessFlags & AccessConstants.NATIVE) != 0)
{

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

@@ -1886,9 +1886,10 @@ implements ClassVisitor,
markAsUsed(kotlinPropertyMetadata.receiverType);
markAsUsed(kotlinPropertyMetadata.typeParameters);
markAsUsed(kotlinPropertyMetadata.setterParameters);
markAsUsed(kotlinPropertyMetadata.setterParameter);
markAsUsed(kotlinPropertyMetadata.type);
if (kotlinPropertyMetadata.flags.common.hasAnnotations &&
if (kotlinPropertyMetadata.flags.hasAnnotations &&
kotlinPropertyMetadata.syntheticMethodForAnnotations != null)
{
// Annotations are placed on a synthetic method (e.g. myProperty$annotations())
@@ -1971,23 +1972,28 @@ implements ClassVisitor,
{
visitAnyFunction(clazz, kotlinDeclarationContainerMetadata, kotlinFunctionMetadata);
// Non-abstract functions in interfaces should have default implementations, so keep it if the
// user kept the original function.
if (isUsed(kotlinFunctionMetadata))
{
if (kotlinDeclarationContainerMetadata.k == KotlinConstants.METADATA_KIND_CLASS &&
((KotlinClassKindMetadata)kotlinDeclarationContainerMetadata).flags.isInterface &&
!kotlinFunctionMetadata.flags.modality.isAbstract &&
(kotlinFunctionMetadata.referencedMethod.getProcessingFlags() & ProcessingFlags.DONT_SHRINK) != 0)
{
kotlinFunctionMetadata.referencedDefaultImplementationMethodAccept(
new MultiMemberVisitor(
ClassUsageMarker.this,
new MemberToClassVisitor(ClassUsageMarker.this)
)
);
}
boolean isInterface =
kotlinDeclarationContainerMetadata.k == KotlinConstants.METADATA_KIND_CLASS
&& ((KotlinClassKindMetadata) kotlinDeclarationContainerMetadata).flags.isInterface
&& !kotlinFunctionMetadata.flags.modality.isAbstract;
if (isUsed(kotlinFunctionMetadata)
&& isInterface
&& (kotlinFunctionMetadata.referencedMethod.getProcessingFlags()
& ProcessingFlags.DONT_SHRINK)
!= 0) {
kotlinFunctionMetadata.referencedDefaultImplementationMethodAccept(
new MultiMemberVisitor(
ClassUsageMarker.this, new MemberToClassVisitor(ClassUsageMarker.this)));
}
// If a default implementation is called directly,
// the interface should be marked as used as well.
if (kotlinFunctionMetadata.referencedDefaultImplementationMethod != null
&& isInterface
&& isUsed(kotlinFunctionMetadata.referencedDefaultImplementationMethod)) {
kotlinFunctionMetadata.referencedMethodAccept(ClassUsageMarker.this);
}
}
// Implementations for KotlinTypeAliasVisitor.
@@ -2063,7 +2069,10 @@ implements ClassVisitor,
else if (kotlinTypeMetadata.aliasName != null && !isUsed(kotlinTypeMetadata.referencedTypeAlias))
{
markAsUsed(kotlinTypeMetadata.referencedTypeAlias);
kotlinTypeMetadata.referencedTypeAlias.accept(null, null, this);
kotlinTypeMetadata.referencedTypeAlias.accept(
kotlinTypeMetadata.referencedTypeAlias.referencedDeclarationContainer.ownerReferencedClass,
kotlinTypeMetadata.referencedTypeAlias.referencedDeclarationContainer,
this);
}
markAsUsed(kotlinTypeMetadata.typeArguments);

View File

@@ -184,7 +184,6 @@ implements KotlinMetadataVisitor,
{
kotlinPropertyMetadata.getterSignature = null;
kotlinPropertyMetadata.referencedGetterMethod = null;
kotlinPropertyMetadata.flags.hasGetter = false;
}
if (shouldShrinkMetadata(kotlinPropertyMetadata.setterSignature,
@@ -192,7 +191,8 @@ implements KotlinMetadataVisitor,
{
kotlinPropertyMetadata.setterSignature = null;
kotlinPropertyMetadata.referencedSetterMethod = null;
kotlinPropertyMetadata.flags.hasSetter = false;
kotlinPropertyMetadata.flags.isVar = false;
kotlinPropertyMetadata.setterParameter = null;
kotlinPropertyMetadata.setterParameters.clear();
}
@@ -206,7 +206,7 @@ implements KotlinMetadataVisitor,
kotlinPropertyMetadata.syntheticMethodForAnnotations = null;
kotlinPropertyMetadata.referencedSyntheticMethodForAnnotations = null;
kotlinPropertyMetadata.referencedSyntheticMethodClass = null;
kotlinPropertyMetadata.flags.common.hasAnnotations = false;
kotlinPropertyMetadata.flags.hasAnnotations = false;
}
if (kotlinPropertyMetadata.syntheticMethodForDelegate != null &&

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,42 +29,22 @@ class ConfigurationWriterTest : FreeSpec({
"Keep rules tests" - {
"Keep class constructor should be kept" {
val rules = """
-keep class * {
<init>();
}
""".trimIndent()
val rules = "-keep class * {$eol <init>();$eol}"
val out = printConfiguration(rules)
out shouldBe rules
}
"Keep class initializer should be kept" {
val rules = """
-keep class * {
<clinit>();
}
""".trimIndent()
val rules = "-keep class * {$eol <clinit>();$eol}"
val out = printConfiguration(rules)
val expected = """
-keep class * {
void <clinit>();
}
""".trimIndent()
val expected = "-keep class * {$eol void <clinit>();$eol}"
out shouldBe expected
}
"Keep class initializer should respect allowobfuscation flag" {
val rules = """
-keep,allowobfuscation class ** extends com.example.A {
<clinit>();
}
""".trimIndent()
val rules = "-keep,allowobfuscation class ** extends com.example.A {$eol <clinit>();$eol}"
val out = printConfiguration(rules)
val expected = """
-keep,allowobfuscation class ** extends com.example.A {
void <clinit>();
}
""".trimIndent()
val expected = "-keep,allowobfuscation class ** extends com.example.A {$eol void <clinit>();$eol}"
out shouldBe expected
}
}
@@ -74,11 +55,11 @@ class ConfigurationWriterTest : FreeSpec({
}
"Comments should not be quoted" {
printConfiguration("# comment$EOL-keep class **") shouldBe "# comment$EOL-keep class **"
printConfiguration("# comment$eol-keep class **") shouldBe "# comment$eol-keep class **"
}
"Hash characters in comments should not be quoted" {
printConfiguration("# #comment$EOL-keep class **") shouldBe "# #comment$EOL-keep class **"
printConfiguration("# #comment$eol-keep class **") shouldBe "# #comment$eol-keep class **"
}
}

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

@@ -3,6 +3,7 @@ package proguard.optimize
import io.kotest.core.spec.style.FreeSpec
import io.kotest.matchers.shouldBe
import proguard.classfile.AccessConstants
import proguard.classfile.ClassPool
import proguard.classfile.Clazz
import proguard.classfile.Member
import proguard.classfile.attribute.visitor.AllAttributeVisitor
@@ -26,83 +27,95 @@ import proguard.util.ProcessingFlagSetter
import proguard.util.ProcessingFlags.IS_CLASS_AVAILABLE
class MemberDescriptorSpecializerTest : FreeSpec({
"Given a method with a more general parameter type than its use" - {
val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource(
JavaSource(
"Test.java",
"""
public class Test {
public static void main(String[] args) {
foo(new Foo());
}
public static void foo(Bar foo) {
System.out.println(foo);
}
}
class Bar { }
class Foo extends Bar { }
""".trimIndent()
)
)
"When specializing the member descriptors" - {
fun specializeMemberDescriptors(
programClassPool: ClassPool,
libraryClassPool: ClassPool,
) {
// Mark all program classes as available.
programClassPool.classesAccept(ProcessingFlagSetter(IS_CLASS_AVAILABLE))
// Mark all members as available.
programClassPool.classesAccept(AllMemberVisitor(ProcessingFlagSetter(IS_CLASS_AVAILABLE)))
// Setup the OptimizationInfo on the classes
val keepMarker = KeepMarker()
libraryClassPool.classesAccept(keepMarker)
libraryClassPool.classesAccept(AllMemberVisitor(keepMarker))
// Setup the OptimizationInfo on the classes
val keepMarker = KeepMarker()
libraryClassPool.classesAccept(keepMarker)
libraryClassPool.classesAccept(AllMemberVisitor(keepMarker))
programClassPool.classesAccept(ProgramClassOptimizationInfoSetter())
programClassPool.classesAccept(AllMemberVisitor(ProgramMemberOptimizationInfoSetter()))
programClassPool.classesAccept(ProgramClassOptimizationInfoSetter())
programClassPool.classesAccept(AllMemberVisitor(ProgramMemberOptimizationInfoSetter()))
// Create the optimization as in Optimizer
val fillingOutValuesClassVisitor = ClassVisitorFactory {
// Create the optimization as in Optimizer
val fillingOutValuesClassVisitor =
ClassVisitorFactory {
val valueFactory: ValueFactory = ParticularValueFactory()
val storingInvocationUnit: InvocationUnit = StoringInvocationUnit(
valueFactory,
true,
true,
true
)
val storingInvocationUnit: InvocationUnit =
StoringInvocationUnit(
valueFactory,
true,
true,
true,
)
ClassAccessFilter(
0, AccessConstants.SYNTHETIC,
0,
AccessConstants.SYNTHETIC,
AllMethodVisitor(
AllAttributeVisitor(
DebugAttributeVisitor(
"Filling out fields, method parameters, and return values",
PartialEvaluator(
valueFactory, storingInvocationUnit,
true
)
)
)
)
valueFactory,
storingInvocationUnit,
true,
),
),
),
),
)
}
programClassPool.classesAccept(fillingOutValuesClassVisitor.createClassVisitor())
programClassPool.classesAccept(fillingOutValuesClassVisitor.createClassVisitor())
// Specialize class member descriptors, based on partial evaluation.
programClassPool.classesAccept(
AllMemberVisitor(
OptimizationInfoMemberFilter(
MemberDescriptorSpecializer(
true,
true,
true,
null,
null,
null
)
)
)
// Specialize class member descriptors, based on partial evaluation.
programClassPool.classesAccept(
AllMemberVisitor(
OptimizationInfoMemberFilter(
MemberDescriptorSpecializer(
true,
true,
true,
null,
null,
null,
),
),
),
)
}
"Given a method with a more general program class pool parameter type than its use" - {
val (programClassPool, libraryClassPool) =
ClassPoolBuilder.fromSource(
JavaSource(
"Test.java",
"""
public class Test {
public static void main(String[] args) {
foo(new Foo());
}
public static void foo(Bar foo) {
System.out.println(foo);
}
}
class Bar { }
class Foo extends Bar { }
""".trimIndent(),
),
)
"When specializing the member descriptors" - {
specializeMemberDescriptors(programClassPool, libraryClassPool)
"Then the member descriptor should be correctly specialised" {
lateinit var memberDescriptor: String
programClassPool.classAccept(
@@ -111,15 +124,109 @@ class MemberDescriptorSpecializerTest : FreeSpec({
MemberNameFilter(
"foo*",
object : MemberVisitor {
override fun visitAnyMember(clazz: Clazz, member: Member) {
override fun visitAnyMember(
clazz: Clazz,
member: Member,
) {
memberDescriptor = member.getDescriptor(clazz)
}
}
)
)
},
),
),
)
memberDescriptor shouldBe "(LFoo;)V"
}
}
}
"Given a field with a more general program class pool parameter type than its use" - {
val (programClassPool, libraryClassPool) =
ClassPoolBuilder.fromSource(
JavaSource(
"Test.java",
"""
public class Test {
static Bar myField = null;
public static void main(String[] args) {
myField = new Foo();
}
}
class Bar { }
class Foo extends Bar { }
""".trimIndent(),
),
)
"When specializing the member descriptors" - {
specializeMemberDescriptors(programClassPool, libraryClassPool)
"Then the member descriptor should be correctly specialised" {
lateinit var memberDescriptor: String
programClassPool.classAccept(
"Test",
AllMemberVisitor(
MemberNameFilter(
"myField*",
object : MemberVisitor {
override fun visitAnyMember(
clazz: Clazz,
member: Member,
) {
memberDescriptor = member.getDescriptor(clazz)
}
},
),
),
)
memberDescriptor shouldBe "LFoo;"
}
}
}
"Given a field with a more general library class pool parameter type than its use" - {
val (programClassPool, libraryClassPool) =
ClassPoolBuilder.fromSource(
JavaSource(
"Test.java",
"""
public class Test {
static java.lang.Object myField = null;
public static void main(String[] args) {
myField = new java.lang.StringBuffer();
}
}
""".trimIndent(),
),
)
"When specializing the member descriptors" - {
specializeMemberDescriptors(programClassPool, libraryClassPool)
"Then the member descriptor should be correctly specialised" {
lateinit var memberDescriptor: String
programClassPool.classAccept(
"Test",
AllMemberVisitor(
MemberNameFilter(
"myField*",
object : MemberVisitor {
override fun visitAnyMember(
clazz: Clazz,
member: Member,
) {
memberDescriptor = member.getDescriptor(clazz)
}
},
),
),
)
// Library classes are not marked as available by default. Therefore, they are not specialized.
memberDescriptor shouldBe "Ljava/lang/Object;"
}
}
}
})

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

@@ -2,6 +2,7 @@ plugins {
id 'distribution'
id 'io.github.gradle-nexus.publish-plugin'
id 'signing'
id "org.jetbrains.kotlin.jvm" version "$kotlinVersion" apply false
}
allprojects {
@@ -23,6 +24,7 @@ task buildDocumentation(type: Exec) {
nexusPublishing {
repositories {
sonatype {
nexusUrl = uri("https://ossrh-staging-api.central.sonatype.com/service/local/")
username = findProperty('PROGUARD_STAGING_USERNAME')
password = findProperty('PROGUARD_STAGING_PASSWORD')
}

View File

@@ -1,7 +1,9 @@
This page lists all available options for ProGuard, grouped logically.
!!! android R8
R8, the default Android shrinker, is compatible with ProGuard keep rules.
R8, the default Android shrinker, is compatible with ProGuard keep rules. Both ProGuard and R8 were designed for app optimization, and although they employ minimal obfuscation techniques, they are not security tools and do not harden applications effectively against reverse engineering and tampering.
[DexGuard](https://hubs.la/Q02yCV0F0) is a protection tool for Android apps that is backward compatible with R8, making it easy to upgrade your R8 configuration with multi-layered security protections to your unprotected mobile application. Learn more in our blog: [Android Security and Obfuscation Realities of R8](https://hubs.la/Q02yCTlt0).
## Input/Output Options {: #iooptions}

View File

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

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