天天看點

Android/Java 混淆中使用-assumenosideeffects删除日志代碼遇到的問題

今天發包給客戶,發現混淆後的庫時序有點問題。再三調試,發現鎖失效了。wait()沒有任何阻塞就跳過了。

ok,90%情況就是在哪裡觸發了notify/notifyAll咯。但找了很久,notify确實沒有被調用。我就納悶了。

最後我把我的庫反編譯出來看,發現我的鎖的wait()語句被删了!源代碼:EsLock.java

public void lock() { synchronized (this) { setLocked(true); try { Log.e("wtf", "" + 1); wait(); Log.e("wtf", "" + 2); } catch (Exception e) { e.printStackTrace(); LogUtil.w(TAG, e.getLocalizedMessage()); } setLocked(false); } }

12345678910111213141516 public void lock(){    synchronized (this) {        setLocked(true);        try {            Log.e("wtf", "" + 1);            wait();            Log.e("wtf", "" + 2);        } catch (Exception e) {            e.printStackTrace();            LogUtil.w(TAG, e.getLocalizedMessage());        }        setLocked(false);    }}

混淆後代碼:

Android/Java 混淆中使用-assumenosideeffects删除日志代碼遇到的問題
Android/Java 混淆中使用-assumenosideeffects删除日志代碼遇到的問題

我翻了一下我CI上的庫記錄,發現前兩個月的庫是沒問題的,看來是中間某段時間修改混淆腳本出了問題。

一番定位,找到了元兇:

-assumenosideeffects class com.excelsecu.driver.util.LogUtil { public *; }

1234 -assumenosideeffects class com.excelsecu.driver.util.LogUtil {    public *;}

我使用了assumenosideeffects,并嘗試将所有com.excelsecu.driver.util.LogUtil的調用删除。官方有關assumenosideeffects的介紹:http://proguard.sourceforge.net/manual/usage.html

assumenosideeffects需要你自己保證你所選擇的類的方法沒有邊界效應(簡單來說就是删掉也不會影響程式運作),然後proguard會幫你删掉這些方法的調用。典型例子就是打包時删掉日志輸出。官方例子:http://proguard.sourceforge.net/manual/examples.html#logging

需要注意的是:他隻會删除這個方法的調用,但是你如何建構你的日志内容(表現形式為StringBuilder)仍然會保留下來。你無法通過這個方法完全删掉你日志的痕迹,以用于保護代碼。為什麼這樣做?因為如果有個傻子圖友善直接在log參數裡面調用了有邊界效應的方法(也就是流程中必不可缺的方法),那你删掉就要出事了。

回到正題:官方給出的示例其實是沒有這樣的用法,隻有填寫特定方法的用法。但是這個标簽也是支援通配符的,官方對其定義是“-assumenosideeffects class_specification”,class_specification描述了你可以如何描述這個class中的方法。和-keepclass是一樣的。這個用法我是在http://stackoverflow.com/questions/6408574/how-to-use-assumenosideeffects-class-android-util-log-in-my-app最下面看到的。

我用回官方的寫法:

-assumenosideeffects class com.excelsecu.driver.util.LogUtil { public static void v(...); public static void i(...); public static void w(...); public static void d(...); public static void e(...); }

12345678 -assumenosideeffects class com.excelsecu.driver.util.LogUtil {    public static void v(...);    public static void i(...);    public static void w(...);    public static void d(...);    public static void e(...);}

問題沒有出現。

是以問題就在于:使用了通配符“public *”之後,proguard把LogUtil之外的方法删了,例如我的EsLock.java中的wait()的調用。(因為這個調用沒有傳回值,proguard認為是沒有邊界效應的)

綜合來說,我覺得依然是個bug,因為無論如何它不應該把LogUtil之外的方法也删掉。我在https://sourceforge.net/p/proguard/bugs/629/上送出了bug(文法錯誤好多。。),暫時沒有回複。

送出bug後第二天就收到了回複。項目人員給出了相關解釋,大概結論就是:這不是bug,proguard是設計成這個樣子的。

項目人員提供的相關内容:

http://proguard.sourceforge.net/中:Troubleshooting > “Note: the configuration specifies that none of the methods of class ‘…’ have any side effects”。

簡單解釋一下:

proguard的混淆是需要往上尋找父類的方法的,是以通配符*也會包括父類的方法。而所有的類都繼承與Object,自然Object.wait()和Object.notify()也會在檢測清單中。是以當你使用了統配符的時候,這兩個方法也是會被影響的。

那麼問題來了,為什麼不是LogUtil.wait()這樣的調用才會被删除,而是EsLock.wait()的方法也會被删除?我猜測proguard采用的是一種展開的方式去處理的,當你配置了LogUtil的所有方法時,他會同時産生一個Object的所有方法的配置。這樣處理起來會高效很多。

class specifications是一個統一的定義,-keep等配置也會用到。是以可能很難兼顧所有配置項的使用場景。

官方文檔也明确說明了,最好别在assumenosideeffects中使用通配符,這樣會影響到wait和notify。