1. 程式人生 > 其它 >位元組碼檢測的解決方案

位元組碼檢測的解決方案

  如果能在APK編譯期間,通過自動化工具對所有JAR、AAR包中每個類做一遍檢測,檢測其中呼叫的方法、屬性的使用是否存在引用問題,將檢測出疑似問題的地方在編譯時進行提示,有必要的情況下直接報錯終止編譯,並輸出錯誤日誌來提醒開發人員檢查,防止問題流入線上出現執行時異常。

  原理:各子倉的Java類(或Kotlin類)在編譯成AAR或JAR後,AAR、JAR中會有所有類的Class檔案,我們實際上就是需要對編譯後生成的Class檔案進行分析。

  如何對Class檔案進行位元組碼分析?

  這裡推薦使用 JavaAssist 或 ASM,我們知道Android編譯過程主要通過Gradle來控制的,要想分析Class檔案位元組碼,我們需要實現自己的Gradle Transform,在Transform裡對Class位元組碼進行分析,這裡我們直接做成Gradle外掛。

  在編譯期間自動分析Class位元組碼是否存在方法引用、屬性引用、類引用找不到或者當前類無權訪問的問題,發現問題停止編譯,並輸出相關日誌,提醒開發人員分析,並支援對外掛的配置。

  到這裡,整個方案的主體框架就比較清晰了,如下圖所示:

  

  3.1 方法和屬性引用檢測原理

  方法和屬性引用問題的識別:

  如何識別一個方法引用存在問題?

  該方法被刪除,找不到相關方法名;

  找不到方法簽名相同的方法,主要是指方法的入引數量、入參型別無法匹配;

  方法是非public方法,當前類無許可權訪問該方法。

  如何識別一個屬性(欄位)引用存在問題?

  該屬性被刪除,找不到相關屬性、欄位;

  屬性是非public屬性,當前類無許可權訪問該屬性。

  許可權修飾符說明:

  

  方法和屬性引用的位元組碼檢測:我們可以利用JavaAssist、ASM等支援位元組碼操作的庫來實現對所有類中方法、屬性的掃描,並分析方法呼叫、屬性引用是否存在引用問題。

  3.2 方法和屬性引用檢測實戰

  以下程式碼均已Kotlin編寫,實現Gradle Plugin、Transform具體過程省略,直接上檢測功能的程式碼。方法、欄位引用檢測:

  // Gradle Plugin、自定義Transform的部分這裡不做贅述

  // 方法引用檢測

  // 遍歷每個類中的 每個方法 (包括構造方法 addBy Qihaoxin)

  classObj.declaredBehaviors.forEach { ctMethod ->

  //遍歷當前類中所有方法

  ctMethod.instrument(object : ExprEditor() {

  override fun edit(m: MethodCall?) {

  super.edit(m)

  //每個方法呼叫都會回撥此方法,在此方法中進行檢測

  //引用檢查功能

  try {

  //這裡不是每個方法都需要校驗的,過濾掉 我們不需要處理的 系統方法,第三方sdk方法 等等 只校驗我們自己的業務邏輯程式碼

  if (ctMethod.declaringClass.name.isNeedCheck()) {

  return

  }

  if (m == null) {

  throw Exception("MethodCall is null")

  }

  //不需要檢查的包名

  if (m.className.isNotWarn() || classObj.name.isNotWarn()) {

  return

  }

  //method找不到,底層會直接拋異常的,包括方法刪除、方法簽名不匹配的情況

  m.method.instrument(ExprEditor())

  //訪問許可權檢測,該方法非public,且對當前呼叫這個方法的類是不可見的

  if (!m.method.visibleFrom(classObj)) {

  throw Exception("${m.method.name} 對 ${classObj.name} 這個類是不可見的")

  }

  } catch (e: Exception) {

  e.message?.let {

  errorInfo += "--方法分析 Exception Message: ${e.message} \n"

  }

  errorInfo += "--方法分析異常發生在 ${ctMethod.declaringClass.name} 這個類的${m?.lineNumber}行, ${ctMethod.name} 這個方法 \n"

  errorInfo += "------------------------------------------------\n"

  isError = true;

  }

  }

  /**

  * 成員變數呼叫的分析主要有:

  * 變數直接被刪掉後找不到的問題

  * private變數的只能定義該變數的類試用

  * protected變數的可被類自己\子類\同包名的訪問

  * */

  override fun edit(f: FieldAccess?) {

  super.edit(f)

  try {

  if (f == null) {

  throw Exception("FieldAccess is null")

  }

  //不需要檢查的包名

  if (f.className.isNotWarn() || classObj.name.isNotWarn()) {

  return

  }

  //這裡不用判空,如果field找不到(這個屬性被刪掉了),底層會直接拋異常NotFoundException

  val modifiers = f.field.modifiers

  if (ctMethod.declaringClass.name == classObj.name) {

  //只處理定義在本類中的方法,不然基類裡的方法也會被處理到--會出現本類實際沒訪問基類裡的private變數但報錯的問題

  if (ctMethod.declaringClass.name == classObj.name) {

  if (!f.field.visibleFrom(classObj)) {

  throw Exception("${f.field.name} 對 ${classObj.name} 這個類是不可見的")

  }

  }

  }

  } catch (e: Exception) {

  e.message?.let {

  errorInfo += "--欄位分析 Exception Message: ${e.message} \n"

  }

  errorInfo += "--欄位分析異常發生在 ${classObj.name} 該類在 ${f?.lineNumber}行,使用 ${f?.fieldName} 這個屬性時\n"

  errorInfo += "------------------------------------------------\n"

  isError = true

  }

  }鄭州看心理醫生多少錢http://www.hyde8025.com/

  })

  }

  在以上程式碼實現中,是遍歷了所有的方法,對方法內的方法呼叫、欄位訪問進行了檢測。那麼全域性變數如何檢查呢?

  class BillActivity {

  ...

  private String mTest1 = CreateNewAddressActivity.TAG;

  private static String mTest2 = new CreateNewAddressActivity().getFormatProvinceInfo("a","b", "c");

  ...

  }

  例如以上程式碼中,mTest1屬性的值以及mTest2屬性的值應該如何做檢測?這個問題困擾筆者良久。在JavaAssist、ASM中均未能找到獲取屬性當前值的相關的Api、也未能找到Class位元組碼直接分析屬性值的相關思路以及資料。

  在研究了Class位元組碼相關知識,並做了大量的實驗,打了大量的Log後,解決思路才慢慢浮出水面。