Lompat ke konten
dokumentasi.id
Commit 98ceeb4
v2
Star3,6 rb

Kamu membaca versi lama. Halaman ini hanya bisa dibaca.

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

#GerbangPesan gagal (apa adanya)Yang dijegalRujukan
1compile probe lewat javacverify(1/4) compile probe FAILED — the jar cannot resolve core public + hidden APIs:simbol @hide dan rantai supertype publik tidak resolvecli/hiddenjar:388-394
2conflict guardverify(2/4) conflict guard FAILED — jar ships JDK/dependency-shadowing namespaces:runtime ART dan pustaka repackaged yang menaungi JDK serta dependency aplikasicli/hiddenjar:396-408
3public-API floorverify(3/4) public-API floor FAILED — jar has <out_n> classes but base SDK jar has <base_n>merge kehilangan kelas yang sudah dikurasi SDKcli/hiddenjar:410-420
4supertype closure lewat ClosureVerifyverify(4/4) supertype closure FAILED — a public supertype is missing (issue #100 symptom)supertype publik yang menggantungcli/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.
Fig. 1: Mesin keadaan verify_compileJar 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-49). 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-84). 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-190).

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-32). Konstanta itu ada di FAIL_PREFIXES (cli/ClosureVerify.java:39-40).

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.
Fig. 2: Klasifikasi supertype di ClosureVerify.verifySetiap 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-111).

Baris stderrArtiExit
[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 disengaja0
[closure] HARD FAIL — public supertypes missing from the jar:ada lubang di permukaan publik; tiap baris dicetak sebagai cn.name -> r1
[closure] OK — public supertype graph is closedseluruh supertype publik resolve0

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-32). 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-95).

PemakaiFungsiPerilaku
ClosureVerify.main (gerbang 4)verifyhard → exit 1, soft → exit 0
BuildJar (prune)hardMissingdaftar tidak kosong → abort, bukan prune
BuildJar (prune)soakDanglinghimpunan 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-130). 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-162). Konsekuensinya, jar default yang keluar dari BuildJar sudah tertutup dan gerbang 4 seharusnya melaporkan nol hard maupun nol soft.

⚠️ Opsi --keep-dangling melewati prune sepenuhnya (cli/hiddenjar:325-326). Pada build seperti itu gerbang 4 tetap berjalan dan hanya menangkap supertype yang hilang di android/, dalvik/, java/, atau javax/; 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-37). 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-318). 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-408).

Untuk perintah yang memicu verifikasi ini, lihat Panduan CLI hiddenjar; untuk urutan build yang mendahuluinya, lihat Pipeline Build hiddenjar.