2.2
Siklus Hidup Kelas APKLeaks
APKLeaks adalah satu kelas yang instansnya dibentuk sekali per pemanggilan CLI. Urutan fase hidupnya tidak ditentukan oleh kelas itu sendiri, melainkan oleh main di apkleaks/cli.py: APKLeaks(args) dibuat sebagai init, lalu init.integrity(), init.decompile(), dan init.scanning() dipanggil di dalam try, sementara init.cleanup() dipanggil di blok finally apkleaks/cli.py:32-411. Karena finally selalu dieksekusi, cleanup tetap berjalan walaupun salah satu fase memanggil sys.exit.
Diagram keadaan siklus hidup
stateDiagram-v2
[*] --> Integrity
Integrity --> Decompile: self.apk terisi
Integrity --> Berhenti: sys.exit
Decompile --> Scanning: perintah jadx selesai
Scanning --> Berhenti: self.apk masih None
Scanning --> Cleanup: semua thread extract dimulai
Berhenti --> Cleanup: blok finally
Cleanup --> Tersimpan: self.scanned True
Cleanup --> Kosong: self.scanned False
Tersimpan --> [*]
Kosong --> [*]
style Integrity fill:#f59e0b,color:#000
style Decompile fill:#8b5cf6,color:#fff
style Scanning fill:#2d7d46,color:#fff
style Cleanup fill:#336791,color:#fff
style Berhenti fill:#dc2626,color:#fff
style Tersimpan fill:#2d7d46,color:#fff
style Kosong fill:#dc2626,color:#fff
%% fig: Siklus hidup instance APKLeaks
%% desc: Empat fase dijalankan berurutan oleh cli.main; sys.exit di fase mana pun tetap berakhir di cleanup lewat blok finally.Satu-satunya jalur menuju Decompile adalah integrity yang berhasil mengisi self.apk. integrity memang mengembalikan nilai self.apk untuk berkas yang valid, tetapi main memanggilnya tanpa menampung nilai kembalian; yang dipakai scanning adalah atribut self.apk sebagai efek samping apkleaks/apkleaks.py:78-872. Berhenti bukan state internal kelas, melainkan nama untuk setiap sys.exit di dalam fase, dan semuanya bermuara ke cleanup yang sama.
Atribut yang dibentuk __init__
Seluruh state siklus hidup disiapkan di __init__, sebelum satu pun fase berjalan apkleaks/apkleaks.py:25-393. Tabel berikut memetakan setiap atribut ke nilai awalnya dan ke fase yang memakainya.
| Atribut | Nilai awal | Dipakai oleh |
|---|---|---|
self.apk | None | diisi integrity, diperiksa scanning |
self.file | os.path.realpath(args.file) | integrity, decompile |
self.json | args.json | extract, cleanup |
self.disarg | args.args | decompile |
self.prefix | "apkleaks-" | prefiks tempdir dan output |
self.tempdir | tempfile.mkdtemp(prefix=self.prefix) | decompile, scanning, cleanup |
self.main_dir | os.path.dirname(os.path.realpath(__file__)) | self.pattern, self.jadx, dependencies |
self.output | tempfile.mkstemp(...) atau args.output | self.fileout, cleanup |
self.fileout | open(self.output, "w" if self.json else "a") | extract, cleanup |
self.pattern | config/regexes.json atau args.pattern | scanning |
self.jadx | which("jadx") atau <root>/jadx/bin/jadx | integrity, dependencies, decompile |
self.out_json | {} | scanning, extract, cleanup |
self.scanned | False | extract, cleanup |
Dua efek samping penting melekat pada konstruksi ini. Pertama, self.tempdir dan self.output sudah dibuat saat __init__ selesai, sebelum integrity tahu apakah jadx ada apkleaks/apkleaks.py:314. Kedua, self.fileout langsung dibuka sehingga berkas output sudah ada di disk sejak awal, dan penanganannya diserahkan ke cleanup. __init__ juga memanggil logging.config.dictConfig({"version": 1, "disable_existing_loggers": True}) untuk mematikan logger yang sudah terdaftar, sehingga satu-satunya keluaran yang terlihat adalah yang ditulis lewat util.
Gerbang integrity: jadx, berkas, dan APK
integrity adalah satu-satunya fase yang punya interaksi dengan pengguna, dan ia memeriksa dua hal secara berurutan: keberadaan binary jadx lalu validitas berkas masukan apkleaks/apkleaks.py:55-875.
graph TD
A["integrity()"] --> B{"os.path.exists(self.jadx)"}
B -- False --> C["writeln: Can't find jadx binary."]
C --> P["while True: prompt (Y/n)"]
P --> D{"choice = input().lower()"}
D -- "kosong, yes, y, ye" --> E["dependencies()"]
D -- "no, n" --> I["sys.exit: ** Aborted."]
D -- "nilai lain" --> R["writeln: Please respond with 'yes' or 'no' (or 'y' or 'n')."]
R --> P
D -- KeyboardInterrupt --> J["sys.exit: ** Interrupted. Aborting."]
E --> F{"urlopen, extractall, chmod"}
F -- error --> G["writeln str(error) lalu sys.exit()"]
F -- sukses --> H{"os.path.isfile(self.file)"}
B -- True --> H
H -- False --> K["sys.exit: It's not a valid file!"]
H -- True --> L["self.apk = self.apk_info()"]
L -- exception --> M["writeln str(error) lalu sys.exit()"]
L -- sukses --> N["return self.apk"]
style A fill:#f59e0b,color:#000
style E fill:#8b5cf6,color:#fff
style N fill:#2d7d46,color:#fff
style G fill:#dc2626,color:#fff
style I fill:#dc2626,color:#fff
style J fill:#dc2626,color:#fff
style K fill:#dc2626,color:#fff
style M fill:#dc2626,color:#fff
%% fig: Keputusan di dalam integrity
%% desc: integrity memeriksa keberadaan jadx lebih dulu, menawarkan unduhan, lalu memvalidasi berkas dan membentuk objek APK.Loop prompt memakai input() tanpa argumen; pesan Do you want to download jadx? (Y/n) ditulis lebih dulu lewat util.write, dan jawaban kosong dipetakan ke valid["y"] sehingga menekan Enter berarti setuju apkleaks/apkleaks.py:56-776. Kamus valid hanya memuat kunci yes, y, ye, no, dan n; masukan lain memicu peringatan dan loop diulang. KeyboardInterrupt saat menunggu masukan langsung mengakhiri proses dengan pesan ** Interrupted. Aborting..
Bila pengguna memilih unduh, dependencies mengambil zip jadx dari URL rilis yang dipatok ke versi 1.2.0, mengekstraknya ke <root>/jadx, lalu memanggil os.chmod(self.jadx, 33268) apkleaks/apkleaks.py:44-537. Kegagalan di jalur mana pun ditangkap sebagai satu Exception, ditulis dengan warna col.WARNING, lalu diakhiri sys.exit(). Perhatikan bahwa self.jadx sudah dihitung di __init__ dari which("jadx") dengan cadangan path bawaan yang memakai akhiran .bat ketika os.name == "nt" apkleaks/apkleaks.py:368.
⚠️ Jalur keluar tercepat ada di jawaban
noataun: proses berhenti dengan** Aborted.tanpa pernah menyentuhdecompile. Pembatalan ini tetap memicucleanuplewatfinally, sehinggatempdirdihapus dan berkasoutputyang sudah dibuka di__init__ikut dibuang.
decompile: satu perintah jadx ke tempdir
Fase ini menyusun daftar argumen [self.jadx, self.file, "-d", self.tempdir], menambahkan argumen disassembler dengan re.split(r"\s|=", self.disarg), menggabungkan semuanya lewat quote, mengganti petik tunggal menjadi petik ganda, lalu menjalankannya melalui os.system apkleaks/apkleaks.py:89-989. Karena hasil os.system tidak diperiksa dan tidak ada nilai kembali, decompile menganggap apa pun yang terjadi sebagai selesai, sehingga scanning akan tetap dijalankan walaupun jadx gagal menulis ke tempdir. Rincian opsi --args dan pengaruhnya dibahas di Opsi CLI dan Argumen Disassembler.
scanning: satu thread per pola
scanning dibuka dengan pemeriksaan self.apk is None; bila jadx atau validasi berkas tadi berhenti tanpa mengisi self.apk, fase ini mengakhiri proses dengan ** Undefined package. Exit! apkleaks/apkleaks.py:117-11910. Setelah itu self.out_json["package"] diisi self.apk.package dan self.out_json["results"] direset menjadi daftar kosong.
Berkas self.pattern dibaca sebagai JSON, lalu setiap entri diproses menurut bentuknya: nilai berupa list menghasilkan satu threading.Thread untuk setiap elemen, sedangkan nilai tunggal menghasilkan satu thread apkleaks/apkleaks.py:120-13811. Setiap thread menjalankan self.extract dengan (name, util.finder(p, self.tempdir)) sebagai argumen; cara util.finder mencocokkan pola dijelaskan di Mesin Pencarian Regex dan Keluaran Warna.
extract adalah satu-satunya tempat self.scanned berubah menjadi True, dan perubahan itu hanya terjadi bila len(matches) tidak nol apkleaks/apkleaks.py:100-11512. Method ini juga yang menambahkan {"name": name, "matches": matches} ke self.out_json["results"] dan menulis baris hasil ke self.fileout ketika mode JSON tidak aktif.
⚠️
scanningmemanggilthread.start()tanpajoin, sehingga method itu kembali tanpa menunggu thread mana pun selesai apkleaks/apkleaks.py:120-13811.cleanupdi blokfinallylangsung berjalan sesudahnya:shutil.rmtree(self.tempdir)dapat menghapus direktori yang masih dibaca thread, danself.fileoutdapat ditutup saat thread masih menulis.self.scannedjuga masihFalsepada saat itu, sehingga berkasoutputbisa dihapus padahal hasil pemindaian menyusul.
cleanup: dua cabang berdasarkan scanned
cleanup selalu menghapus self.tempdir lebih dulu, lalu memilih cabang berdasarkan self.scanned apkleaks/apkleaks.py:140-14913.
| Kondisi | Aksi | Akibat pada self.output |
|---|---|---|
self.scanned True, self.json True | tulis json.dumps(self.out_json, indent=4) lalu close() | berkas berisi laporan JSON |
self.scanned True, self.json False | tulis string kosong lalu close(), cetak ** Results saved into ... | berkas berisi baris hasil dari extract |
self.scanned False | close() lalu os.remove(self.output) | berkas dihapus, tercetak ** Done with nothing. ¯\_(ツ)_/¯ |
⚠️
os.remove(self.output)menghapusself.outputapa adanya, danself.outputbisa berasal dari opsi--outputpengguna, bukan hanya berkas sementara di__init__. Pemindaian yang tidak menemukan satu pun kecocokan akan menghapus berkas tujuan tersebut tanpa konfirmasi. Format isi berkas hasil dibahas di Format Keluaran Hasil Pemindaian.