Zig menjalankan serangkaian pemeriksaan keamanan saat program berjalan, dan cakupan pemeriksaan itu ditentukan oleh mode kompilasi yang dipilih. Topik ini membahas perilaku mode Debug dan ReleaseSafe yang memeriksa, mode ReleaseFast yang tidak, panic beserta bentuk pesannya, pernyataan unreachable sebagai kontrak, serta fungsi asersi std.debug.assert. Setelah membacanya, pilihan mode kompilasi dapat diambil dengan sadar, dan batas antara pemeriksaan otomatis dan kontrak yang ditulis sendiri menjadi jelas.
Konsep#
Pemeriksaan runtime pada mode Debug dan ReleaseSafe#
Mode kompilasi menentukan apakah pemeriksaan keamanan ikut disertakan dalam program. Mode Debug adalah mode bawaan zig run dan mode ReleaseSafe tetap menyertakan seluruh pemeriksaan, sedangkan mode ReleaseFast dan ReleaseSmall menghapusnya demi ukuran dan kecepatan. Pemeriksaan yang dimaksud meliputi luapan bilangan bulat, akses indeks di luar batas, pembagian dengan nol, dan pembukaan nilai yang tidak ada. Tabel berikut merangkum pembagiannya.
| Mode | Pemeriksaan runtime |
|---|---|
| Debug | Aktif |
| ReleaseSafe | Aktif |
| ReleaseFast | Dihapus |
| ReleaseSmall | Dihapus |
Luapan yang ditolak otomatis dapat dideteksi secara sengaja dengan fungsi @addWithOverflow, yang mengembalikan hasil penjumlahan beserta tanda luapannya tanpa pernah menghentikan program. Program stok berikut memeriksa sendiri penjumlahan yang berpotensi melewati batas u8.
const std = @import("std");
pub fn main() void {
const stok: u8 = 200;
const tambahan: u8 = 100;
const hasil = @addWithOverflow(stok, tambahan);
if (hasil[1] == 1) {
std.debug.print("penjumlahan melewati batas u8\n", .{});
} else {
std.debug.print("stok baru: {d}\n", .{hasil[0]});
}
const aman = @addWithOverflow(@as(u8, 120), @as(u8, 7));
std.debug.print("stok aman: {d}\n", .{aman[0]});
}penjumlahan melewati batas u8
stok aman: 127Hasil @addWithOverflow berupa pasangan nilai, yaitu elemen [0] memuat hasil penjumlahannya dan elemen [1] memuat tanda luapan yang bernilai satu bila batas terlewati. Perlu diketahui bahwa pemeriksaan juga dilakukan pada waktu kompilasi apabila seluruh nilainya sudah diketahui saat itu, sehingga penulisan daftar[3] pada array berpanjang tiga ditolak dengan pesan error: index 3 outside array of length 3 sebelum program dibangun. Pemeriksaan runtime hanya berperan bagi indeks dan nilai yang baru diketahui saat program berjalan.
Panic dan bentuk pesannya#
Panic adalah penghentian program yang dilakukan Zig ketika pemeriksaan runtime menemukan pelanggaran, dan pesannya menjelaskan penyebabnya secara langsung. Baris pertamanya memuat nomor thread dan penyebabnya, misalnya integer overflow untuk luapan atau index out of bounds: index 7, len 3 untuk akses di luar batas, lalu diikuti stack trace yang menunjuk baris kode pemicunya. Setiap alamat memori pada stack trace berubah antar eksekusi, sehingga keluaran panic tidak pernah dipakai sebagai contoh keluaran yang ditempel pada topik ini, dan program yang panic berakhir dengan kode keluar bukan nol.
// tidak-dijalankan
const daftar = [_]u8{ 10, 20, 30 };
const indeks: usize = 7;
std.debug.print("{d}\n", .{daftar[indeks]});Pada mode Debug dan ReleaseSafe, pola di atas menghentikan program dengan pesan index out of bounds, sedangkan pada ReleaseFast pemeriksaannya dihapus sehingga program membaca memori di luar batas tanpa pemberitahuan apa pun. Panic juga dapat dipicu secara sengaja melalui std.debug.panic("pesan: {d}", .{nilai}), yang berguna menandai keadaan yang seharusnya mustahil tercapai dengan pesan pilihan sendiri. Karena pemeriksaan bawaan berhenti pada panic, perlakukan setiap panic sebagai laporan bug yang perlu diperbaiki, bukan sebagai jalur penanganan yang disengaja.
Contoh Lengkap#
unreachable sebagai kontrak#
Pernyataan unreachable menyatakan bahwa eksekusi tidak pernah boleh mencapai baris itu, dan pernyataan tersebut berfungsi sebagai kontrak yang ditulis di dalam kode. Pada mode Debug dan ReleaseSafe, baris yang tercapai memicu panic dengan pesan reached unreachable code, sedangkan pada ReleaseFast kompilator memperlakukannya sebagai undefined behavior, yaitu perilaku yang tidak terdefinisi sama sekali tanpa jaminan apa pun. Karena perbedaan yang tajam ini, unreachable hanya layak ditulis untuk kondisi yang mustahil menurut logika program, bukan untuk keadaan yang mungkin terjadi menurut data dari luar.
const std = @import("std");
fn digitTengah(kode: []const u8) u8 {
if (kode.len % 2 == 1) {
return kode[kode.len / 2];
}
unreachable; // kontrak: panjang kode selalu ganjil
}
pub fn main() void {
std.debug.assert(digitTengah("A31C5") == '1');
std.debug.print("digit tengah sesuai kontrak\n", .{});
const panjang = [_]usize{ 5, 7, 9 };
for (panjang) |n| {
std.debug.assert(n % 2 == 1);
}
std.debug.print("seluruh panjang terbukti ganjil\n", .{});
}digit tengah sesuai kontrak
seluruh panjang terbukti ganjilFungsi digitTengah menerima kode yang panjangnya dijamin ganjil oleh desain, sehingga jalur panjang genap tidak memiliki arti apa pun dan dinyatakan dengan unreachable. Pada program di atas baris itu tidak pernah tercapai, sehingga program berakhir dengan kode keluar nol. Bila kontraknya dilanggar, misalnya kode berpanjang genap diberikan, mode berpemeriksaan akan menghentikannya dengan panic, dan itulah cara kontrak memberi peringatan paling awal.
std.debug.assert untuk asersi#
Fungsi std.debug.assert(kondisi) menegaskan bahwa kondisinya benar pada titik tersebut, dan panggilannya tidak menghasilkan apa pun bila kondisi terpenuhi. Pada mode Debug dan ReleaseSafe, kondisi yang salah memicu panic karena di dalamnya terdapat unreachable, sedangkan pada mode tanpa pemeriksaan panggilan asersi dihilangkan dari program dan asumsinya diserahkan kepada pengoptimal. Asersi berbeda dari validasi input, karena asersi memeriksa kebenaran internal program, sedangkan input dari luar wajib diperiksa dengan percabangan biasa dan ditangani hasilnya secara eksplisit.
Asersi yang menyebar di badan fungsi berfungsi sebagai dokumentasi yang dijaga mesin, sebab setiap asersi yang berlalu membuktikan bahwa asumsi di atasnya masih berlaku. Program digit-tengah.zig di atas memakai pola ini dua kali, yaitu memeriksa hasil fungsi dan memeriksa setiap panjang kode dalam daftar. Letakkan asersi pada batas antar bagian program, misalnya sebelum memakai nilai yang dihitung bagian lain, agar pelanggarannya tertangkap di titik terdekat dengan penyebabnya.
Kesalahan umum#
Kesalahan pertama adalah memakai unreachable sebagai penanganan input yang mungkin sah saja. Input dari berkas, jaringan, atau pengguna dapat berisi apa pun, sehingga kondisi yang dinyatakan mustahil itu nyatanya tercapai, dan pada ReleaseFast pelanggarannya tidak menghentikan program melainkan berubah menjadi undefined behavior. Batasi unreachable untuk kontrak internal yang dijaga oleh kode sendiri, dan tangani input luar dengan error union yang dibahas pada penanganan error.
Kesalahan kedua adalah mengabaikan pesan panic karena programnya “tetap selesai”. Panic menghentikan program di tengah pekerjaan dengan kode keluar bukan nol, sehingga hasil yang tampak lengkap bisa jadi hasil parsial yang belum tuntas. Perbaiki penyebabnya sampai program berakhir dengan kode keluar nol pada mode berpemeriksaan, baru kemudian nilai kecepatan mode lain dipertimbangkan.
Ringkasan#
- Mode Debug dan ReleaseSafe menyertakan pemeriksaan runtime atas luapan, batas indeks, dan pembagian dengan nol, sedangkan ReleaseFast dan ReleaseSmall menghapusnya.
- Pemeriksaan yang gagal memicu panic, yaitu penghentian program dengan pesan penyebab dan stack trace yang alamatnya berubah antar eksekusi.
unreachableadalah kontrak bahwa jalur itu mustahil, yang memicu panic bila tercapai pada mode berpemeriksaan dan menjadi undefined behavior pada ReleaseFast.std.debug.assertmenegaskan kondisi internal program, dan panggilannya dihilangkan pada mode tanpa pemeriksaan sehingga bukan pengganti validasi input.
Referensi#
- Illegal-Behavior — Zig Language Reference — Daftar perilaku ilegal yang diperiksa dan hubungannya dengan mode build.
- Optimization-Mode — Zig Language Reference — Empat mode build beserta konsekuensi pemeriksaan runtime-nya.