Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to configure Proguard to remove non-trivial log messages?

Proguard removes trivial Log calls very well. (with the assumenosideeffects keyword)
But it handles non-trivial Log calls poorly.

By "Non-Trivial" I mean anything more than a single String.
For example: Log.i(TAG,"velocity="+velocity)".
Proguard keeps the new StringBuilder("velocity="), and also the appended variable holing the value, not much obscuring that variable. It removes only the eventual call to Log.
The strings will remain there, wasting memory, cpu cycles and also help crackers understand the code.


So to solve this, I wrap every non-trivial debug Log call in my app with if(BuildConfig.DEBUG){...}. But wrapping every log with if(..){..} is tedious and error prone.
It's certainly not DRY (Don't repeat yourself).

Is there a way to mark a method for complete removal by Proguard (or by any other means), including all calling methods?

Something like:

@proguard_purge
public static void vanishingDebug(String whatever) {
  Log.i(TAG,whatever);
}

So the method will vanish by the obfuscator, and also all calls to this method will vanish recursively as well?

ELABORATION

Obfuscation will optimize code and remove unused or excluded methods.
But code compilation generates extra byte codes prior to the method call that will be removed, and that preceding code will stay even after obfuscation, and leave behind code such as:

new StringBuilder("velocity=").append(a)

(Assuming a cannot be determined at compile time. To test, use velocity=Math.random();)

Which makes the obfuscated code quite trivial to understand.
To reproduce the issue, you'll need to install dex2jar to convert apk to jar, and JAD to convert jar to java code.
You'll see what's really left behind, and be horrified.

EXAMPLE

@Override
protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);
    Log.i("TAG", "Simple Comment"); // << Disappears well!

    double index = Math.random();
    index++;

    Log.i("TAG2", "log_index=" + index); // << hmm... (!)

    // class with only log calls inside
    new ReferencedClass();

    // simple method call
    MyLogger.notLog("no_log" + index); // << stays, as expected

    // simple method call with only Log call inside (aka "Log Wrapper")
    MyLogger.log("log" + index);  // << stays, as expected

    Log.i("TAG2", "This is random:" + Math.random()); // << stays, same as above

    setContentView(R.layout.activity_main);
}

Using this obfuscation configuration:

-assumenosideeffects class android.util.Log {
    public static *** isLoggable(java.lang.String, int);
    public static *** d(...);
    public static *** v(...);
    public static *** i(...);
    public static *** w(...);
    public static *** e(...);
}

It will be obfuscated to a state which can be de-compiled and de-obfuscated to this:

  protected void onCreate(Bundle paramBundle)
  {
    super.onCreate(paramBundle);
    double d = 1.0D + Math.random();
    new StringBuilder("log_index=").append(d).toString();
    new b();
    a.a("no_log" + d);
    new StringBuilder("log").append(d).toString();
    a.a();
    new StringBuilder("This is random:").append(Math.random()).toString();
    setContentView(2130903064);
  }
like image 601
Amir Uval Avatar asked Oct 03 '22 07:10

Amir Uval


1 Answers

well as of my researches proguard cant do this at all. what you can do though, is setting up your ant build scripts.

<target name="-commentoutlogs">
    <replaceregexp match="(Log\..*?;\s*\n)" replace="/*\1*/" flags="gs" byline="false">
        <fileset dir="src">
            <include name="**/*.java"/>
        </fileset>
    </replaceregexp>
</target>


<target name="-uncommentlogs">
    <replaceregexp match="\/\*(Log\..*?;\s*\n)\*\/" replace="\1" flags="gs" byline="false">
        <fileset dir="src">
            <include name="**/*.java"/>
        </fileset>
    </replaceregexp>
</target>

this is a simple regex-based script that you can use in your build.xml file, adding it to ant release target like this:

<target name="release"
            depends="-uncommentlogsbefore, -commentoutlogs, -set-release-mode, -release-obfuscation-check, -package, -post-package, -release-prompt-for-password, -release-nosign, -release-sign, -uncommentlogsafter, -post-build"
            description="Builds the application in release mode.">
</target>

of course you need to create target called uncommentlogsbefore aswell, with same body as uncommentlogs

this basicly puts /* before any Log. and */ after nearest );

like image 177
Maciej Boguta Avatar answered Oct 07 '22 16:10

Maciej Boguta