Skip to content

Commit

Permalink
Automatically process Kotlin metadata when keeping the annotation
Browse files Browse the repository at this point in the history
Add dontprocesskotlinmetadata configuration option.
  • Loading branch information
Toon Willemot authored and rubenpieters committed Oct 20, 2022
1 parent 03c8b9f commit cf5d492
Show file tree
Hide file tree
Showing 10 changed files with 115 additions and 73 deletions.
5 changes: 5 additions & 0 deletions base/src/main/java/proguard/Configuration.java
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,11 @@ public class Configuration
*/
public boolean keepKotlinMetadata = false;

/**
* Specifies not to process Kotlin metadata. Overwrites KeepKotlinMetadata.
*/
public boolean dontProcessKotlinMetadata = false;

// INTERNAL OPTIONS

/**
Expand Down
1 change: 1 addition & 0 deletions base/src/main/java/proguard/ConfigurationConstants.java
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ public class ConfigurationConstants
public static final String FORCE_PROCESSING_OPTION = "-forceprocessing";

public static final String KEEP_KOTLIN_METADATA = "-keepkotlinmetadata";
public static final String DONT_PROCESS_KOTLIN_METADATA = "-dontprocesskotlinmetadata";

public static final String ANY_FILE_KEYWORD = "**";

Expand Down
11 changes: 10 additions & 1 deletion base/src/main/java/proguard/ConfigurationParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,8 @@ else if (ConfigurationConstants.REPACKAGE_CLASSES_OPTION
else if (ConfigurationConstants.ADAPT_CLASS_STRINGS_OPTION .startsWith(nextWord)) configuration.adaptClassStrings = parseCommaSeparatedList("class name", true, true, false, false, true, false, false, true, false, configuration.adaptClassStrings);
else if (ConfigurationConstants.ADAPT_RESOURCE_FILE_NAMES_OPTION .startsWith(nextWord)) configuration.adaptResourceFileNames = parseCommaSeparatedList("resource file name", true, true, false, true, false, true, false, false, false, configuration.adaptResourceFileNames);
else if (ConfigurationConstants.ADAPT_RESOURCE_FILE_CONTENTS_OPTION .startsWith(nextWord)) configuration.adaptResourceFileContents = parseCommaSeparatedList("resource file name", true, true, false, true, false, true, false, false, false, configuration.adaptResourceFileContents);
else if (ConfigurationConstants.KEEP_KOTLIN_METADATA .startsWith(nextWord)) configuration.keepKotlinMetadata = parseNoArgument(true);
else if (ConfigurationConstants.DONT_PROCESS_KOTLIN_METADATA .startsWith(nextWord)) configuration.dontProcessKotlinMetadata = parseNoArgument(true);
else if (ConfigurationConstants.KEEP_KOTLIN_METADATA .startsWith(nextWord)) configuration.keepKotlinMetadata = parseKeepKotlinMetadata();

else if (ConfigurationConstants.DONT_PREVERIFY_OPTION .startsWith(nextWord)) configuration.preverify = parseNoArgument(false);
else if (ConfigurationConstants.MICRO_EDITION_OPTION .startsWith(nextWord)) configuration.microEdition = parseNoArgument(true);
Expand Down Expand Up @@ -250,6 +251,14 @@ public void close() throws IOException
}


private boolean parseKeepKotlinMetadata() throws IOException
{
System.err.println("The `-keepkotlinmetadata` option is deprecated and will be removed in a future ProGuard release." +
"Please use `-keep class kotlin.Metadata` instead.");
return parseNoArgument(true);
}


private long parseIncludeArgument(long lastModified) throws ParseException, IOException
{
// Read the configuration file name.
Expand Down
1 change: 1 addition & 0 deletions base/src/main/java/proguard/ConfigurationWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ public void write(Configuration configuration) throws IOException
writeOption(ConfigurationConstants.ADAPT_RESOURCE_FILE_NAMES_OPTION, configuration.adaptResourceFileNames);
writeOption(ConfigurationConstants.ADAPT_RESOURCE_FILE_CONTENTS_OPTION, configuration.adaptResourceFileContents);
writeOption(ConfigurationConstants.KEEP_KOTLIN_METADATA, configuration.keepKotlinMetadata);
writeOption(ConfigurationConstants.DONT_PROCESS_KOTLIN_METADATA, configuration.dontProcessKotlinMetadata);

writeOption(ConfigurationConstants.DONT_PREVERIFY_OPTION, !configuration.preverify);
writeOption(ConfigurationConstants.MICRO_EDITION_OPTION, configuration.microEdition);
Expand Down
17 changes: 17 additions & 0 deletions base/src/main/java/proguard/ProGuard.java
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,12 @@ public void execute() throws Exception
{
checkGpl();

// Set the -keepkotlinmetadata option if necessary.
if (!configuration.dontProcessKotlinMetadata)
{
configuration.keepKotlinMetadata = requiresKotlinMetadata();
}

if (configuration.printConfiguration != null)
{
printConfiguration();
Expand Down Expand Up @@ -268,6 +274,17 @@ private void checkGpl()
GPL.check();
}

private boolean requiresKotlinMetadata()
{
return configuration.keepKotlinMetadata ||
(configuration.keep != null &&
configuration.keep.stream().anyMatch(
keepClassSpecification -> ! keepClassSpecification.allowObfuscation &&
! keepClassSpecification.allowShrinking &&
"kotlin/Metadata".equals(keepClassSpecification.className)
));
}


/**
* Prints out the configuration that ProGuard is using.
Expand Down
6 changes: 6 additions & 0 deletions base/src/test/kotlin/proguard/MarkerTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,9 @@ class MarkerTest : FreeSpec({
-keep class Test {
<methods>;
}
# This option is deprecated and one should use '-keep class kotlin.Metadata' instead.
# In this test we use the deprecated option, as its value is set in ProGuard.java and not in
# the ConfigurationParser which is used by this unit test.
-keepkotlinmetadata
""".asConfiguration()
val marker = Marker(config)
Expand Down Expand Up @@ -153,6 +156,9 @@ class MarkerTest : FreeSpec({
-keep class Test {
<methods>;
}
# This option is deprecated and one should use '-keep class kotlin.Metadata' instead.
# In this test we use the deprecated option, as its value is set in ProGuard.java and not in
# the ConfigurationParser which is used by this unit test.
-keepkotlinmetadata
""".asConfiguration()
val marker = Marker(config)
Expand Down
5 changes: 3 additions & 2 deletions docs/md/manual/configuration/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -515,8 +515,9 @@ This page lists all available options for ProGuard, grouped logically.
obfuscated stack traces](examples.md#stacktrace). Only applicable when
obfuscating.

`-keepkotlinmetadata`{: #keepkotlinmetadata}
: Specifies to process `kotlin.Metadata` annotations if present.
`-keepkotlinmetadata`{: #keepkotlinmetadata} {: .deprecated}
: ** Deprecated: use `-keep class kotlin.Metadata` instead. **
Specifies to process `kotlin.Metadata` annotations if present.
Currently only shrinking and obfuscation of its content is supported.
Classes containing such annotations should be excuded from optimization
if this option is enabled.
Expand Down
14 changes: 8 additions & 6 deletions docs/md/manual/languages/kotlin.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ The Kotlin compiler injects code and metadata into the classes that it generates
## Configuration

In most cases, you do not need to keep Kotlin metadata for app projects - therefore, no configuration changes are necessary and the Kotlin metadata can be safely removed.
However, there are two common reasons to explicitly keep the metadata, [reflection](#reflection) and [libraries](#library-projects).

ProGuard will only keep the Kotlin metadata of a class if you explicitly keep that class or one of its members and you add `-keepkotlinmetadata` option to your configuration.
ProGuard will only keep the Kotlin metadata of a class if you explicitly keep that class or one of its members, and you add the `-keep class kotlin.Metadata` option to your configuration.
Note that this option may also be required by an SDK of your project, and indirectly added as a consumer rule.

For example, if you have the following keep rule for a Kotlin class named `com.example.KotlinExample`, by default the class will be kept but its metadata will not:

Expand All @@ -15,11 +17,11 @@ For example, if you have the following keep rule for a Kotlin class named `com.e
-keep class com.example.KotlinExample
```

You can add `-keepkotlinmetadata` to your configuration to instruct ProGuard to keep and adapt Kotlin metadata:
You can add `-keep class kotlin.Metadata` to your configuration to instruct ProGuard to keep and adapt Kotlin metadata:

```
# Add this option to tell ProGuard to keep and adapt Kotlin metadata
-keepkotlinmetadata
-keep class kotlin.Metadata
```


Expand All @@ -31,7 +33,7 @@ The most common case to keep Kotlin metadata would be if you use the [kotlin-ref
In this case, to instruct ProGuard to keep and adapt the corresponding Kotlin metadata, add the following to your configuration:

```
-keepkotlinmetadata
-keep class kotlin.Metadata
```

A popular framework that relies on reflection is [Jackson](https://github.com/FasterXML/jackson-module-kotlin).
Expand All @@ -41,11 +43,11 @@ A popular framework that relies on reflection is [Jackson](https://github.com/Fa
When developing an SDK that exposes Kotlin-specific features to its users, you need to preserve the metadata of the public API.
These include features such as named parameters, suspend functions, top-level functions and type aliases.

In the case of a library, you would already be keeping the public API so you can simply add the following
In the case of a library, you would already be keeping the public API, so you can simply add the following
to your configuration:

```
-keepkotlinmetadata
-keep class kotlin.Metadata
```

## Protection
Expand Down
Loading

0 comments on commit cf5d492

Please sign in to comment.