Skip to content

Commit

Permalink
Added bundled proguard rules (#2092)
Browse files Browse the repository at this point in the history
* added delivery of ProGuard/R8 rules along with the core artifact.
* the documentation about the rules has been made more readable
* added an example of rules for named companions for R8 full mode
* added R8-only rules to support full mode
* rules have been introduced suppressing some warnings about serialization classes

Resolves #1121
Resolves #1899
Resolves #1900
Resolves #2050
  • Loading branch information
shanshin committed Dec 2, 2022
1 parent ccf9c2c commit ea69eb3
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 70 deletions.
112 changes: 42 additions & 70 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,96 +178,68 @@ dependencies {
### Android

The library works on Android, but, if you're using ProGuard,
you need to add rules to your `proguard-rules.pro` configuration to cover all classes that are serialized at runtime.
By default, proguard rules are supplied with the library.
[These rules](rules/common.pro) keep serializers for _all_ serializable classes that are retained after shrinking,
so you don't need additional setup.

The following configuration keeps serializers for _all_ serializable classes that are retained after shrinking.
Uncomment and modify the last section in case you're serializing classes with named companion objects.
**However, these rules do not affect serializable classes if they have named companion objects.**

If you want to serialize classes with named companion objects, you need to add and edit rules below to your `proguard-rules.pro` configuration.

Note that the rules for R8 differ depending on the [compatibility mode](https://r8.googlesource.com/r8/+/refs/heads/master/compatibility-faq.md) used.

<details>
<summary>Example of named companion rules for ProGuard and R8 compatibility mode</summary>

```proguard
# Keep `Companion` object fields of serializable classes.
# This avoids serializer lookup through `getDeclaredClasses` as done for named companion objects.
-if @kotlinx.serialization.Serializable class **
-keepclassmembers class <1> {
static <1>$Companion Companion;
}
# Serializer for classes with named companion objects are retrieved using `getDeclaredClasses`.
# If you have any, replace classes with those containing named companion objects.
-keepattributes InnerClasses # Needed for `getDeclaredClasses`.
# Keep `serializer()` on companion objects (both default and named) of serializable classes.
-if @kotlinx.serialization.Serializable class ** {
-if @kotlinx.serialization.Serializable class
com.example.myapplication.HasNamedCompanion, # <-- List serializable classes with named companions.
com.example.myapplication.HasNamedCompanion2
{
static **$* *;
}
-keepclassmembers class <2>$<3> {
kotlinx.serialization.KSerializer serializer(...);
-keepnames class <1>$$serializer { # -keepnames suffices; class is kept when serializer() is kept.
static <1>$$serializer INSTANCE;
}
# Keep `INSTANCE.serializer()` of serializable objects.
-if @kotlinx.serialization.Serializable class ** {
public static ** INSTANCE;
}
-keepclassmembers class <1> {
public static <1> INSTANCE;
kotlinx.serialization.KSerializer serializer(...);
}
# @Serializable and @Polymorphic are used at runtime for polymorphic serialization.
-keepattributes RuntimeVisibleAnnotations,AnnotationDefault
# Serializer for classes with named companion objects are retrieved using `getDeclaredClasses`.
# If you have any, uncomment and replace classes with those containing named companion objects.
#-keepattributes InnerClasses # Needed for `getDeclaredClasses`.
#-if @kotlinx.serialization.Serializable class
#com.example.myapplication.HasNamedCompanion, # <-- List serializable classes with named companions.
#com.example.myapplication.HasNamedCompanion2
#{
# static **$* *;
#}
#-keepnames class <1>$$serializer { # -keepnames suffices; class is kept when serializer() is kept.
# static <1>$$serializer INSTANCE;
#}
```
</details>

In case you want to exclude serializable classes that are used, but never serialized at runtime,
you will need to write custom rules with narrower [class specifications](https://www.guardsquare.com/manual/configuration/usage).

<details>
<summary>Example of custom rules</summary>

```proguard
-keepattributes RuntimeVisibleAnnotations,AnnotationDefault
<summary>Example of named companion rules for R8 full mode</summary>

# kotlinx-serialization-json specific. Add this if you have java.lang.NoClassDefFoundError kotlinx.serialization.json.JsonObjectSerializer
-keepclassmembers class kotlinx.serialization.json.** {
*** Companion;
}
-keepclasseswithmembers class kotlinx.serialization.json.** {
kotlinx.serialization.KSerializer serializer(...);
}
# Application rules
```proguard
# Serializer for classes with named companion objects are retrieved using `getDeclaredClasses`.
# If you have any, replace classes with those containing named companion objects.
-keepattributes InnerClasses # Needed for `getDeclaredClasses`.
# Change here com.yourcompany.yourpackage
-keepclassmembers @kotlinx.serialization.Serializable class com.yourcompany.yourpackage.** {
# lookup for plugin generated serializable classes
*** Companion;
# lookup for serializable objects
*** INSTANCE;
kotlinx.serialization.KSerializer serializer(...);
-if @kotlinx.serialization.Serializable class
com.example.myapplication.HasNamedCompanion, # <-- List serializable classes with named companions.
com.example.myapplication.HasNamedCompanion2
{
static **$* *;
}
# lookup for plugin generated serializable classes
-if @kotlinx.serialization.Serializable class com.yourcompany.yourpackage.**
-keepclassmembers class com.yourcompany.yourpackage.<1>$Companion {
kotlinx.serialization.KSerializer serializer(...);
-keepnames class <1>$$serializer { # -keepnames suffices; class is kept when serializer() is kept.
static <1>$$serializer INSTANCE;
}
# Serialization supports named companions but for such classes it is necessary to add an additional rule.
# This rule keeps serializer and serializable class from obfuscation. Therefore, it is recommended not to use wildcards in it, but to write rules for each such class.
-keepattributes InnerClasses # Needed for `getDeclaredClasses`.
-keep class com.yourcompany.yourpackage.SerializableClassWithNamedCompanion$$serializer {
*** INSTANCE;
# Keep both serializer and serializable classes to save the attribute InnerClasses
-keepclasseswithmembers, allowshrinking, allowobfuscation, allowaccessmodification class
com.example.myapplication.HasNamedCompanion, # <-- List serializable classes with named companions.
com.example.myapplication.HasNamedCompanion2
{
*;
}
```
</details>

In case you want to exclude serializable classes that are used, but never serialized at runtime,
you will need to write custom rules with narrower [class specifications](https://www.guardsquare.com/manual/configuration/usage).

### Multiplatform (Common, JS, Native)

Most of the modules are also available for Kotlin/JS and Kotlin/Native.
Expand Down
20 changes: 20 additions & 0 deletions core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,26 @@ kotlin {
to reject runtime if runtime's Require-Kotlin-Version is greater then the current compiler.
*/
tasks.withType(Jar).named(kotlin.jvm().artifactsTaskName) {

// adding the ProGuard rules to the jar
from(rootProject.file("rules/common.pro")) {
rename { "kotlinx-serialization-common.pro" }
into("META-INF/proguard")
}
from(rootProject.file("rules/common.pro")) {
rename { "kotlinx-serialization-common.pro" }
into("META-INF/com.android.tools/proguard")
}
from(rootProject.file("rules/common.pro")) {
rename { "kotlinx-serialization-common.pro" }
into("META-INF/com.android.tools/r8")
}
from(rootProject.file("rules/r8.pro")) {
rename { "kotlinx-serialization-r8.pro" }
into("META-INF/com.android.tools/r8")
}


manifest {
attributes(
"Implementation-Version": version,
Expand Down
33 changes: 33 additions & 0 deletions rules/common.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Keep `Companion` object fields of serializable classes.
# This avoids serializer lookup through `getDeclaredClasses` as done for named companion objects.
-if @kotlinx.serialization.Serializable class **
-keepclassmembers class <1> {
static <1>$Companion Companion;
}

# Keep `serializer()` on companion objects (both default and named) of serializable classes.
-if @kotlinx.serialization.Serializable class ** {
static **$* *;
}
-keepclassmembers class <2>$<3> {
kotlinx.serialization.KSerializer serializer(...);
}

# Keep `INSTANCE.serializer()` of serializable objects.
-if @kotlinx.serialization.Serializable class ** {
public static ** INSTANCE;
}
-keepclassmembers class <1> {
public static <1> INSTANCE;
kotlinx.serialization.KSerializer serializer(...);
}

# @Serializable and @Polymorphic are used at runtime for polymorphic serialization.
-keepattributes RuntimeVisibleAnnotations,AnnotationDefault

# Don't print notes about potential mistakes or omissions in the configuration for kotlinx-serialization classes
# See also https://github.com/Kotlin/kotlinx.serialization/issues/1900
-dontnote kotlinx.serialization.**

# Serialization core uses `Class.forName("java.lang.ClassValue")` for caching in JVM-only, so it is an expected situation that this class is not in Android
-dontwarn java.lang.ClassValue
12 changes: 12 additions & 0 deletions rules/r8.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Rule to save runtime annotations on serializable class.
# If the R8 full mode is used, annotations are removed from classes-files.
#
# For the annotation serializer, it is necessary to read the `Serializable` annotation inside the serializer<T>() function - if it is present,
# then `SealedClassSerializer` is used, if absent, then `PolymorphicSerializer'.
#
# When using R8 full mode, all interfaces will be serialized using `PolymorphicSerializer`.
#
# see https://github.com/Kotlin/kotlinx.serialization/issues/2050

-if @kotlinx.serialization.Serializable class **
-keep, allowshrinking, allowoptimization, allowobfuscation, allowaccessmodification class <1>

0 comments on commit ea69eb3

Please sign in to comment.