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);
}
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 );
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With