8
Verifikasi Jar Kustom
verify_compile adalah gerbang sukses hiddenjar. Fungsi ini dipanggil tepat setelah assemble_jar menulis jar hasil perakitan dan sebelum install_jar menyentuh SDK; kegagalan menghentikan seluruh alur di die "verification failed (see checks above)" (cli/hiddenjar:337-338).
Versi lama hanya memakai satu probe yang menyentuh tiga simbol @hide dan tidak pernah mengompilasi kelas publik biasa, sehingga isu #100 lolos. Penggantinya menjalankan empat pemeriksaan independen yang masing-masing menangkap mode kegagalan berbeda (cli/hiddenjar:348-354).
Empat gerbang
| # | Gerbang | Pesan gagal (apa adanya) | Yang dijegal | Rujukan |
|---|---|---|---|---|
| 1 | compile probe lewat javac | verify(1/4) compile probe FAILED — the jar cannot resolve core public + hidden APIs: | simbol @hide dan rantai supertype publik tidak resolve | cli/hiddenjar:388-394 |
| 2 | conflict guard | verify(2/4) conflict guard FAILED — jar ships JDK/dependency-shadowing namespaces: | runtime ART dan pustaka repackaged yang menaungi JDK serta dependency aplikasi | cli/hiddenjar:396-408 |
| 3 | public-API floor | verify(3/4) public-API floor FAILED — jar has <out_n> classes but base SDK jar has <base_n> | merge kehilangan kelas yang sudah dikurasi SDK | cli/hiddenjar:410-420 |
| 4 | supertype closure lewat ClosureVerify | verify(4/4) supertype closure FAILED — a public supertype is missing (issue #100 symptom) | supertype publik yang menggantung | cli/hiddenjar:422-429 |
Keempat gerbang selalu dijalankan tanpa short-circuit. Masing-masing hanya menyetel ok=1 saat gagal, lalu satu keputusan diambil di akhir lewat log "Verification OK: all 4 checks passed" atau warn "Verification FAILED (see checks above)", dan hasilnya dikembalikan sebagai return "$ok" (cli/hiddenjar:431-432).
stateDiagram-v2
state "verify(1/4) compile probe" as probe
state "verify(2/4) conflict guard" as guard
state "verify(3/4) public-API floor" as floor
state "verify(4/4) supertype closure" as closure
state "Verification OK: all 4 checks passed" as passed
state "Verification FAILED" as failed
state "install_jar" as install
state verdict <<choice>>
[*] --> probe: verify_compile jar base
probe --> guard
guard --> floor
floor --> closure
closure --> verdict: ok akumulatif 0 atau 1
verdict --> passed: ok = 0
verdict --> failed: ok = 1
passed --> install: OPT_INSTALL=true
install --> [*]
passed --> [*]
failed --> [*]: die "verification failed (see checks above)"
style probe fill:#f59e0b,color:#000
style guard fill:#f59e0b,color:#000
style floor fill:#f59e0b,color:#000
style closure fill:#8b5cf6,color:#fff
style passed fill:#2d7d46,color:#fff
style failed fill:#dc2626,color:#fff
%% fig: Mesin keadaan verify_compile
%% desc: Jar hasil perakitan melewati empat gerbang berurutan yang mengakumulasi flag ok, lalu satu keputusan menentukan pemasangan ke SDK atau penghentian alur.Gerbang 1: probe kompilasi
verify_compile menulis HiddenApiProbe.java ke ${WORK_DIR}/probe. Kelas probe memperluas ViewGroup, sehingga javac wajib melewati rantai View → Drawable.Callback, KeyEvent.Callback, dan seterusnya. Metodenya menyentuh ServiceManager.getService("window") (@hide android.os), ActivityThread.currentActivityThread() (@hide android.app), VMRuntime.getRuntime() (@hide dalvik.system, tidak ada di jar stok), dan com.android.internal.R.class (cli/hiddenjar:364-387).
Kompilasi dijalankan sebagai javac -nowarn -cp "$jar" -d "${probe}/out" "${probe}/HiddenApiProbe.java" dengan stderr dialihkan ke ${probe}/javac.log. Saat gagal, log itu dicetak apa adanya ke stderr pengguna (cli/hiddenjar:388-394). Karena javac hanya membaca signature, gerbang ini tetap sahih untuk jar yang sudah di-stub oleh Stubifier; mekanisme stub itu ada di Perakitan Jar: BuildJar dan Stubifier.
Gerbang 2: conflict guard
Daftar entri jar diambil lewat unzip -l, dipotong dengan awk '{print $NF}', lalu dicocokkan dengan grep -oE '^(sun|jdk|libcore|com/google|com/android/okhttp|com/android/org|gov/nist|org/apache/(xml|xpath|xalan))/' dan dirapikan dengan sort -u. Hasil yang tidak kosong berarti jar membawa runtime ART atau pustaka repackaged yang menaungi JDK dan dependency aplikasi — penyebab isu #100 — sehingga gerbang ini gagal (cli/hiddenjar:396-408).
Pemeriksaan ini juga mengunci filter namespace overlay agar tidak meregresi. Daftar namespace yang diizinkan masuk ke jar dijelaskan di Perakitan Jar: BuildJar dan Stubifier.
Gerbang 3: public-API floor
Jumlah kelas dihitung dengan unzip -l <jar> | grep -c '\.class$', sekali untuk jar hasil dan sekali untuk base SDK. Karena merge hanya menumpuk overlay di atas jar SDK, hasilnya tidak boleh lebih sedikit dari basisnya. Bila out_n -lt base_n, berarti merge membuang API publik yang sudah dikurasi, dan gerbang gagal dengan pesan yang menyebut kedua angka tersebut (cli/hiddenjar:410-420).
Gerbang 4: supertype closure lewat ClosureVerify
closure_verify menjalankan java -cp "${JAVA_TOOLS_OUT}:${JAVA_TOOLS_CP}" ClosureVerify "$jar". Kelas Java pendukung dikompilasi sekali oleh ensure_java_tools dari Stubifier.java, ClosureVerify.java, dan BuildJar.java dengan ASM dari direktori lib dex-tools, lalu di-cache di ${WORK_DIR}/cli-out (cli/hiddenjar:205-222). Bila tool itu tidak tersedia, gerbang ini hanya memberi warn "closure check skipped (ASM lib or cli/*.java not found)" dan mengembalikan 0, sehingga build tidak pernah diblokir oleh checker yang gagal berjalan (cli/hiddenjar:435-442).
ClosureVerify menerima satu argumen <jar|dir>, keluar dengan kode 2 tanpa argumen, dan memakai System.exit(verify(all) > 0 ? 1 : 0) sebagai hasil (cli/ClosureVerify.java:42-491). Setiap entri .class dibaca header-nya saja lewat ClassReader.SKIP_CODE | SKIP_DEBUG | SKIP_FRAMES, karena yang dibutuhkan hanya superName dan interfaces (cli/ClosureVerify.java:79-842). Supertype dianggap resolve bila namanya ada di dalam map kelas jar atau bisa dimuat JDK lewat Class.forName(..., false, jdk) dengan cache hasil; supers() menggabungkan superName dan seluruh interfaces, dan namespace() memotong nama internal menjadi dua segmen pertama (cli/ClosureVerify.java:165-1903).
Tingkat keparahan ditentukan namespace. Missing supertype di android/, dalvik/, java/, atau javax/ adalah HARD (exit 1) karena jar menjanjikan permukaan publik itu secara penuh; missing supertype internal seperti com/android/ims, com/android/adservices, protobuf, atau libcore/ hanya dilaporkan sebagai soft (exit 0) (cli/ClosureVerify.java:27-324). Konstanta itu ada di FAIL_PREFIXES (cli/ClosureVerify.java:39-405).
graph TD
A["supers(cn): superName + interfaces"] --> B{"all.containsKey(r)?"}
B -->|"ya"| R["resolved"]
B -->|"tidak"| C{"jdkLoadable(r, jdk, jdkCache)?"}
C -->|"ya"| R
C -->|"tidak"| D{"isHardNs(r)?"}
D -->|"android/ dalvik/ java/ javax/"| H["hard: cn.name -> r, maksimal 50 entri"]
D -->|"com/android/ims, libcore/, protobuf"| S["soft: namespace(r) dihitung"]
H --> X["[closure] HARD FAIL, exit 1"]
S --> Y["[closure] soft (ok), exit 0"]
style A fill:#f59e0b,color:#000
style R fill:#2d7d46,color:#fff
style H fill:#dc2626,color:#fff
style X fill:#dc2626,color:#fff
style S fill:#8b5cf6,color:#fff
style Y fill:#2d7d46,color:#fff
%% fig: Klasifikasi supertype di ClosureVerify.verify
%% desc: Setiap supertype dicek ke isi jar lalu ke JDK, dan yang tidak resolve dipisah menjadi hard pada namespace publik atau soft pada namespace internal.Keluaran akhirnya selalu satu ringkasan di stderr, lalu salah satu dari dua verdict. Daftar hard dipotong pada 50 entri, tetapi kode keluar tetap 1 selama daftar itu tidak kosong (cli/ClosureVerify.java:87-1116).
| Baris stderr | Arti | Exit |
|---|---|---|
[closure] scanned N classes: H hard, S soft (intentionally-dropped internals) | rekap jumlah kelas yang dipindai | — |
[closure] soft (ok): <ns>=<n> ... | supertype internal yang dibuang memang disengaja | 0 |
[closure] HARD FAIL — public supertypes missing from the jar: | ada lubang di permukaan publik; tiap baris dicetak sebagai cn.name -> r | 1 |
[closure] OK — public supertype graph is closed | seluruh supertype publik resolve | 0 |
Satu definisi "resolvable" untuk prune dan verifikasi
Algoritma prune tinggal di kelas yang sama dengan checker, jadi prune dan verifikasi memakai satu definisi "resolvable" (cli/ClosureVerify.java:27-324). BuildJar memakainya saat jar belum di-stub: ClosureVerify.read dipanggil per entri .class, ClosureVerify.hardMissing dijalankan lebih dulu dan langsung System.exit(1) dengan pesan [build] ABORT — public supertype(s) missing; a real regression, not pruning: bila hasilnya tidak kosong, baru ClosureVerify.soakDangling menentukan kelas mana yang dibuang (cli/BuildJar.java:74-957).
| Pemakai | Fungsi | Perilaku |
|---|---|---|
ClosureVerify.main (gerbang 4) | verify | hard → exit 1, soft → exit 0 |
BuildJar (prune) | hardMissing | daftar tidak kosong → abort, bukan prune |
BuildJar (prune) | soakDangling | himpunan nama internal yang entri .class-nya dihapus |
hardMissing sengaja memaksa pemanggil membatalkan alur, karena prune yang tetap berjalan akan diam-diam menghapus API publik asli dan menutupi bug isu #100 (cli/ClosureVerify.java:113-1308). soakDangling adalah fixpoint: ia mengulang sapuan sampai tidak ada lagi kelas yang supertype-nya tidak resolve, dan hanya mengikuti sisi superclass/interface — bukan referensi method atau field (cli/ClosureVerify.java:132-1629). Konsekuensinya, jar default yang keluar dari BuildJar sudah tertutup dan gerbang 4 seharusnya melaporkan nol hard maupun nol soft.
⚠️ Opsi
--keep-danglingmelewati prune sepenuhnya (cli/hiddenjar:325-326). Pada build seperti itu gerbang 4 tetap berjalan dan hanya menangkap supertype yang hilang diandroid/,dalvik/,java/, ataujavax/; kelas yang supertype internalnya hilang tetap lolos sebagai soft.
Batas cakupan: #100, bukan #46
Keempat gerbang menutup isu #100, tetapi tidak menyentuh isu #46. Masalah Cannot read field "outgoingEdges" because "handlerRangeBlock" is null muncul di MockableJarGenerator milik Gradle saat lint atau unit test berjalan, bukan saat javac mengompilasi probe, dan penanganannya ada di Stubifier (cli/Stubifier.java:26-3710). Karena itu build dengan --keep-bodies bisa lolos keempat gerbang namun tetap gagal di Gradle; hiddenjar memperingatkan hal itu lebih awal dengan warn "--keep-bodies: keeping real method bodies; Gradle lint/unit tests (MockableJarTransform) will FAIL on this jar" (cli/hiddenjar:325-326). Sisi Gradle-nya dibahas di Task Gradle Pembungkus hiddenjar.
Port PowerShell
Port Windows menjalankan rangkaian gerbang yang sama. Komentar Test-Compile menyatakan empat pemeriksaan independen itu meniru verify_compile beserta alasan yang sama (cli/hiddenjar.ps1:316-31811). Gerbang keempat memanggil ClosureVerify lewat Test-Closure, yang mengembalikan $false hanya saat ada HARD failure dan $true saat tool tidak tersedia — perilaku skip yang identik dengan versi bash (cli/hiddenjar.ps1:399-40812).
Untuk perintah yang memicu verifikasi ini, lihat Panduan CLI hiddenjar; untuk urutan build yang mendahuluinya, lihat Pipeline Build hiddenjar.