Git Product home page Git Product logo

heckej / proguard Goto Github PK

View Code? Open in Web Editor NEW

This project forked from guardsquare/proguard

2.0 1.0 0.0 6.35 MB

ProGuard, Java optimizer and obfuscator with additional optimisations for Kotlin lambda's

Home Page: https://www.guardsquare.com/en/products/proguard

License: GNU General Public License v2.0

Shell 0.06% Java 93.60% HTML 0.02% Batchfile 0.03% Kotlin 6.30%
kotlin lambda-merging lambda-groups optimizer

proguard's Introduction



ProGuard


Quick StartFeaturesContributingLicense


ProGuard is a free shrinker, optimizer, obfuscator, and preverifier for Java bytecode:

  • It detects and removes unused classes, fields, methods, and attributes.

  • It optimizes bytecode and removes unused instructions.

  • It renames the remaining classes, fields, and methods using short meaningless names.

The resulting applications and libraries are smaller, faster, and a bit better hardened against reverse engineering. ProGuard is very popular for Android development, but it also works for Java code in general.

❓ Getting Help

If you have usage or general questions please ask them in the Guardsquare Community.
Please use the issue tracker to report actual bugs 🐛, crashes, etc.

🚀 Quick Start

ProGuard has its own Gradle plugin, allowing you to shrink, optimize and obfuscate Android projects.

ProGuard Gradle Plugin

You can apply the ProGuard Gradle plugin in AGP 4+ projects by following these steps:

  1. Add a classpath dependency in your root level build.gradle file:
buildscript {
    repositories {
        google()       // For the Android Gradle plugin.
        mavenCentral() // For the ProGuard Gradle Plugin and anything else.
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:x.y.z'    // The Android Gradle plugin.
        classpath 'com.guardsquare:proguard-gradle:7.2.1'  // The ProGuard Gradle plugin.
    }
}
  1. Apply the proguard plugin after applying the Android Gradle plugin as shown below:
 apply plugin: 'com.android.application'
 apply plugin: 'com.guardsquare.proguard'
  1. ProGuard expects unobfuscated class files as input. Therefore, other obfuscators such as R8 have to be disabled.
android {
    ...
    buildTypes {
       release {
          // Deactivate R8.
          minifyEnabled false
       }
    }
}
  1. Configure variants to be processed with ProGuard using the proguard block:
android {
    ...
}

proguard {
   configurations {
      release {
         defaultConfiguration 'proguard-android-optimize.txt'
         configuration 'proguard-project.txt'
      }
   }
}

You can then build your application as usual:

gradle assembleRelease

The repository contains some sample configurations in the examples directory. Notably, examples/android has a small working Android project that applies the ProGuard Gradle plugin.

Integrated ProGuard (AGP < 7.0)

If you have an older Android Gradle project you can enable ProGuard instead of the default R8 compiler:

  1. Disable R8 in your gradle.properties:
android.enableR8=false
android.enableR8.libraries=false
  1. Override the default version of ProGuard with the most recent one in your main build.gradle:
buildscript {
    //...
    configurations.all {
        resolutionStrategy {
            dependencySubstitution {
                substitute module('net.sf.proguard:proguard-gradle') with module('com.guardsquare:proguard-gradle:7.2.1')
            }
        }
    }
}
  1. Enable minification as usual in your build.gradle:
android {
    //...
    buildTypes {
        release {
            minifyEnabled   true
            shrinkResources true
            proguardFile getDefaultProguardFile('proguard-android-optimize.txt')
            proguardFile 'proguard-project.txt'
        }
    }
}
  1. Add any necessary configuration to your proguard-project.txt.

You can then build your application as usual:

gradle assembleRelease

The repository contains some sample configurations in the examples directory. Notably, examples/android-agp3-agp4 has a small working Android project that uses the old integration.

✨ Features

ProGuard works like an advanced optimizing compiler, removing unused classes, fields, methods, and attributes, shortening identifiers, merging classes, inlining methods, propagating constants, removing unused parameters, etc.

  • The optimizations typically reduce the size of an application by anything between 20% and 90%. The reduction mostly depends on the size of external libraries that ProGuard can remove in whole or in part.

  • The optimizations may also improve the performance of the application, by up to 20%. For Java virtual machines on servers and desktops, the difference generally isn't noticeable. For the Dalvik virtual machine and ART on Android devices, the difference can be worth it.

  • ProGuard can also remove logging code, from applications and their libraries, without needing to change the source code — in fact, without needing the source code at all!

The manual pages (markdown, html) cover the features and usage of ProGuard in detail.

💻 Building ProGuard

Building ProGuard is easy - you'll just need a Java 8 JDK installed. To build from source, clone a copy of the ProGuard repository and run the following command:

./gradlew assemble

The artifacts will be generated in the lib directory. You can then execute ProGuard using the scripts in bin, for example:

bin/proguard.sh

You can publish the artifacts to your local Maven repository using:

./gradlew publishToMavenLocal

🤝 Contributing

Contributions, issues and feature requests are welcome in both projects. Feel free to check the issues page and the contributing guide if you would like to contribute.

📝 License

Copyright (c) 2002-2022 Guardsquare NV. ProGuard is released under the GNU General Public License, version 2, with exceptions granted to a number of projects.

proguard's People

Contributors

mrjameshamilton avatar ericlafortune avatar ericsalemi-gs avatar maqsoodahmadjan avatar ericsalemi avatar bigdaz avatar gkv38 avatar jelle-dc avatar rubenpieters avatar jagogyselinck avatar heckej avatar runningcode avatar dzan avatar britter avatar ingdas avatar jonnybbb avatar laurentferierguardsquare avatar lonedev6 avatar vorburger avatar nadeeshtv avatar paulerickson avatar autonomousapps avatar ulviyyamz avatar anatawa12 avatar chevreto avatar liaolintao avatar sleticalboy avatar

Stargazers

New Account: https://github.com/marrgiela avatar  avatar

Watchers

 avatar

proguard's Issues

Lambda classes with multiple <init> constructors are incorrectly merged

In general, lambda classes appear to contain exactly 1 <init> constructor, in which:

  • the free variables (i.e. constructor arguments) are assigned to their corresponding fields
  • the super constructor, i.e. the one of the Lambda class is called, with the arity of the lambda as an argument

Some lambda classes, however, contain 2 <init> constructors, with a different descriptor. One of them is private, while the other one is public. The current implementation of merging lambda's with free variables, copies the (public) <init> constructor. The problem here is that:

  • there are multiple <init> constructors: easily solved by copying the one that is public
  • the public <init> constructor calls the private one: this could but solved by copying the private one as well, or by inlining the private constructor into the public constructor
    • currently the method inliner never inlines <init> constructors, so a modified inliner would have to be created for this

Multiple usages of lambda class <init> or INSTANCE field

The current implementation of the lambda merger tries to find the code that must be updated to correctly instantiate a lambda in the following way:

  1. read the InnerClasses and EnclosingMethod attribute of the lambda class to find the enclosing class and enclosing method
  2. if the enclosing method is known: update its code
  3. else if the enclosing method refers to a constant in the constant pool at index 0: assume that the enclosing method is the static <clinit> constructor of the enclosing class

The problem that now arises is that some enclosing classes (1 case is known) contain multiple calls to the INSTANCE field or <init> constructor of a lambda class. This occurs for example in the following code from the Jetpack Compose class androidx.compose.animation.core.InfiniteAnimationPolicyKt:

suspend inline fun <R> withInfiniteAnimationFrameMillis(
    crossinline onFrame: (frameTimeMillis: Long) -> R
): R = withInfiniteAnimationFrameNanos { onFrame(it / 1_000_000L) }

The resulting bytecode contains two similar methods which both call the <init> constructor of the lambda androidx.compose.animation.core.InfiniteAnimationPolicyKt$withInfiniteAnimationFrameMillis$2:

public static final <R extends java.lang.Object> java.lang.Object withInfiniteAnimationFrameMillis(kotlin.jvm.functions.Function1<? super java.lang.Long, ? extends R>, kotlin.coroutines.Continuation<? super R>);
private static final <R extends java.lang.Object> java.lang.Object withInfiniteAnimationFrameMillis$$forInline(kotlin.jvm.functions.Function1<? super java.lang.Long, ? extends R>, kotlin.coroutines.Continuation<? super R>);

Write tests for lambda merging in general

  • The behaviour of a program must remain unchanged after merging (invoking a lambda should return the same result before and after merging, print the same output)
  • It should be possible to load classes and invoke their methods (the bytecode can be interpreted by the JVM)
  • ...

Interfaces are removed from lambda groups when shrinking is disabled

When shrinking is disabled, all classes and methods are marked as used, excluding the lambda groups. The methods that call methods of the lambda groups, will cause those lambda group methods to be marked as used. Afterwards, the unused lambda group methods are shrunk away. However, apparently also the interfaces are removed, which causes cast exceptions when the enclosing class tries to cast the lambda group to some kotlin.jvm.functions.Function interface.

Lambda not cast to Function interface and invoked directly

The function addParam(text: String, start: Int, end: Int, value: String) which is inside the function private fun parseHeaderValueParameter(text: String, start: Int, parameters: Lazy<ArrayList<HeaderValueParam>>): Int in the ktor.io library is compiled into a lambda class.

However, the function is technically not a lambda (or anonymous function), because it has a name. Because of this name, the Kotlin compiler permits itself to call the resulting lambda class by name without ever casting the instance to the respective function interface. As a result, all invocations of this 'lambda' are like this:

104: invokevirtual #264                // Method io/ktor/http/HttpHeaderValueParserKt$parseHeaderValueParameter$1.invoke:(Ljava/lang/String;IILjava/lang/String;)V

The current implementation of the lambda merger (enclosing method updater) cannot handle this correctly, as it assumes that the name of a lambda is only ever used when instantiating it (or referring to an INSTANCE field).

Lambda class contains other methods next to <init>, <clinit> and invoke

240 out of 2104 lambda classes in a test app (ImageViewer) contain other methods than the expected <init>, <clinit> and invoke methods. This is a non-trivial amount. Currently, they are excluded from merging, because the lambda merger (MethodCopier) does not copy methods that are called from the invoke method, even if they are inside the same class. This might be easy to implement, but there are a couple of things to note:

  • technically, if the referenced methods are public, we cannot assume that the methods are not used outside the lambda class, so extensive analysis might be needed
  • if the referenced methods are copied to the lambda group, there might occur a conflict with a method from a different lambda class that has the same name as an already copied method:
    • either methods have to be renamed in a unique manner,
    • or the method copier must be able to rename the copied method and all references to that method must also use the new name

Implement support for lambda's with non-empty closure

Lambda's that use free variables, result in in lambda classes that take the free variables as constructor arguments and store them in fields, which are referenced by the invoke method. If lambda groups were to implement this, possibly multiple lambda groups would have to be created to merge lambda's with of a common closure size. Probably, adding multiple constructors with a differing number of arguments could also do the job. Another approach would be to group the arguments in an array, which would be the only 'closure argument' of a lambda group constructor. The invoke methods would then have to be modified to access the array or all elements of the array would have to be assigned to respective fields.

Write script to measure code size of lambda's

To automate the evaluation of lambda code size, it would be helpful to have a script that can measure some things for this evaluation. The script should be able to:

  • find all lambda classes in an application
  • calculate the total size of all lambda classes

A lambda class can be defined as: a class that

  • extends the class kotlin.jvm.internal.Lambda

Possibly an extra condition in the above definition should be added:

  • implements at least one of the interfaces kotlin.jvm.functions.Function0 ... FunctionN.

Fix incomplete instruction replacement in enclosing method of lambda

Context

Currently only lambda's with an empty closure are merged. One of the assumptions is that such a lambda always contains a public static field named INSTANCE:

getstatic       #40    // Field app/AppKt$main$lambda1$1.INSTANCE:Lapp/AppKt$main$lambda1$1;
checkcast     #42                 // class kotlin/jvm/functions/Function1

to which an instance of the lambda is assigned in the static constructor of the lambda class.

Problem

In some cases, it appears that some lambda's are not 'instantiated' in their enclosing class by referring to a static INSTANCE field. Instead, the lambda is explicitly instantiated:

new                   #54    // androidx/compose/foundation/layout/BoxScopeInstance$matchParentSize$$inlined$debugInspectorInfo$1
dup
invokespecial    #55    // androidx/compose/foundation/layout/BoxScopeInstance$matchParentSize$$inlined$debugInspectorInfo$1.<init>()V
checkcast kotlin/jvm/functions/Function1

It turns out that the lambda merger only replaces the lambda class reference for the <init>()V constructor. However, also the reference for the new operation should be updated.

Proposed solution

Use an InstructionSequenceReplacer to define the patterns that must be replaced and the instructions by which they should be replaced. The patterns that must be replaced, are the ones mentioned above.

Testing

TODO: find a way to test this

Lambda classes with same <init> descriptor with free variables incorrectly merged

Given two lambda classes that are to be merged into a common lambda group. The two classes have the same <init> constructor descriptor, e.g.

public <init>(Function0 arg1, boolean arg2)

The current implementation of lambda merging (with free variables) simply copies the <init> constructor from a lambda class to the lambda group. The implementation of this constructor already contains the instructions that are needed to assign the free variables (constructor arguments) to the corresponding fields.

Say, we have lambda classes LambdaA and LambdaB with the following implementations for there <init> constructor:

class LambdaA {
  private Function0 fieldA1;
  private boolean fieldA2;
  public LambdaA(Function0 arg1, boolean arg2) {
    this.fieldA1 = arg1;
    this.fieldA2 = arg2;
  }
}

class LambdaB {
  private Function0 fieldB1;
  private boolean fieldB2;
  public LambdaB(Function0 arg1, boolean arg2) {
    this.fieldB1 = arg1;
    this.fieldB2 = arg2;
  }
}

The only difference between the two lambda classes (except for their name) are the names of the fields. Now, when the <init> constructor of LambdaA is copied to the lambda group and invoked, it will store the arguments in the fields that are copied from LambdaA, but the fields that are copied from LambdaB will not have been assigned. There value will be null, so when the invoke implementation of LambdaB is executed, these fields will not contain any value, leave alone the correct value, possibly resulting in a NullPointerException.

Implement support for merging nested lambda's

Problem

The lambda's in the following code are not always merged correctly by the simple merger:

fun main() {
  val outerLambda = {
    val innerLambda = {
      println("Hello!")
    }
    innerLambda()
  outerLambda()
}

In case the order of adding the lambda's to the new lambda group is

  1. innerLambda
  2. outerLambda

then the merging succeeds. However, if the order is reversed:

  1. outerLambda
  2. innerLambda

then merging the innerLambda will result in modifications of the outerLambda, but these modifications are not added to the lambda group, so the resulting lambda group is incorrect. More precisely:

  1. the implementation of outerLambda contains a reference to the original lambda class of innerLambda, which is removed by the merger
  2. the implementation of outerLambda does not reference the merged implementation of innerLambda inside the lambda group, so the program behaviour has been altered

Proposed solution

Make the lambda merger aware of the fact that lambda's can be nested.

  • A correct, but undesirable solution would be to to avoid merging nested lambda's at all.
  • The preferred solution is to let the merger merge the inner lambda's before trying to merge the outer lambda
    • This can be done recursively
    • or using a queue of lambda's that still have to be merged, to which outer lambda's are added if they are encountered before the inner lambda's have been merged

Requirements

  • A mechanism to check whether a lambda contains nested lambda's
  • A mechanism to check whether a lambda has already been merged

Testing

  • Assert that the lambda groups don't reference removed (lambda) classes
  • Assert that each method/class where a lambda is defined, references the correct lambda group instance that contains the implementation of the merged lambda

Cannot 'cast' lambda group to some FunctionX interface

Problem

In some JVM's the lambda groups cannot be cast to the type that they implement (e.g. kotlin.jvm.functions.Function3), even though they do implement the interface and the necessary methods.

Cause of the problem

The cause of this problem might be the fact that the lambda groups don't contain any generics information. The
JVM classfile specification describes what signature attributes are needed w.r.t. generics or parametrisation.

Proposed solution

  • Read the JVM classfile specification w.r.t. generics
  • Add the necessary signature attributes to the lambda groups

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.