Masalah tahun 2038? Bukankah itu seharusnya diselesaikan sekali dan untuk semua tahun yang lalu?
Tidak cukup.
Apa itu masalah Tahun 2038
? Wikipedia menjelaskannya dengan baik, tetapi TL;DR bermuara pada, mengutip artikel ini:
Waktu Unix secara historis dikodekan sebagai ditandatangani Integer 32-bit, tipe data yang terdiri dari 32 digit biner (bit) yang mewakili nilai integer, dengan ‘bertanda’ artinya satu bit dicadangkan untuk menunjukkan tanda (+/–). Jadi, bilangan bulat 32-bit bertanda hanya dapat mewakili nilai bilangan bulat dari (2³¹) hingga 2³¹ 1 inklusif. Akibatnya, jika integer 32-bit yang ditandatangani digunakan untuk menyimpan waktu Unix, waktu terakhir yang dapat disimpan adalah 2³¹ 1 (2.147.483.647) detik setelah epoch, yaitu 03:14:07 pada hari Selasa, 19 Januari 2038. Sistem yang upaya untuk menaikkan nilai ini satu detik lagi hingga 2³¹ detik setelah Epoch (03:14:08) akan mengalami integer overflow, secara tidak sengaja membalik bit tanda untuk menunjukkan angka negatif. Ini mengubah nilai bilangan bulat menjadi (2³¹), atau 2³¹ detik sebelum epoch daripada sesudahnya, yang akan ditafsirkan oleh sistem sebagai 20:45:52 pada hari Jumat, 13 Desember 1901.
Masalah ini saat ini sebagian besar diatasi dengan membuat time_t
, waktu penyimpanan tipe data, integer 64-bit yang ditandatangani, bukan integer 32-bit yang ditandatangani. Menggandakan lebar tipe data memberi lebih banyak ruang daripada yang dibutuhkan siapa pun – nilai waktu 64-bit yang ditandatangani tidak akan meluap selama 292 miliar tahun
time_t
adalah 64-bit secara default di hampir semua kompiler dan sistem operasi, jadi di atas kertas kode baru harus gratis dari masalah 2038 tahun. Dalam praktiknya… bug tersebut masih ada dan dapat tetap tidak diketahui untuk waktu yang lama.
Di MSDN, ada artikel lama berjudul Mengonversi nilai time_t ke FILETIME. Sampai saat ini, artikel itu memiliki potongan kode yang terlihat seperti ini – di sini, saya sengaja membuatnya tidak dikompilasi sehingga Anda tidak tergoda untuk menggunakannya dalam produksi:
#termasuk
#termasuk kosong TimetToFileTime(waktu_t t, LPFILETIME pft) {L0NGL0NGtime_value = Int32x32To64(T, 10000000 ) + 116444736000000000; pft->dwLowDateTime = (DW0RD) nilai waktu; pft->dwHighDateTime = nilai waktu >> 32 ; } Pada pandangan pertama, semuanya terlihat baik-baik saja. Namun, setelah diperiksa lebih dekat,
Int32x32To64
mencurigakan – seperti namanya, ini adalah makro yang mengalikan dua bilangan bulat 32-bit bertanda dan menghasilkan hasil 64-bit yang ditandatangani; penekanan pada 32-bit. Makro ini didefinisikan sebagai:#define Int32x32To64(a, b) ((__int64) (((__int64)((panjang)(a))) ((panjang)(b))))
Kedua nilai input dilemparkan ke panjang 32-bit
nilai1, sebelum diperpanjang untuk perkalian. Jika
a
ataub
lebih lebar dari 32 bit, operasi ini memotongnya. Oleh karena itu, jika variabel input bertipetime_t
, menggunakan makro ini memperkenalkan kembali masalah tahun 2038! Lebih buruk lagi, dari apa yang saya tahu, setidaknya MSVC (secara default) tidak menghasilkan peringatan untuk pemotongan ini, kecuali ini berubah dengan VS2022 yang belum saya coba.Saya berharap ini tidak terjadi dan saya terlalu paranoid, tetapi sayangnya, perakitan yang dipratinjau di Godbolt membuktikan teori ini. Dalam cuplikan kode di atas,
t
dimuat melaluimovsxd rax, DWORD PTR t $
, sehingga ditafsirkan sebagai nilai 32-bit yang ditandatangani (DWORD
) dan kemudian diperluas ke nilai 64-bit. Masalah klasik tahun 2038.Pada bulan November tahun lalu, saya mengajukan proposal untuk potongan kode tetap untuk diubah ke artikel ini, yang segera diterima dan digabungkan. Sekarang, kode terakhir terlihat sebagai berikut, dan berfungsi seperti yang diharapkan untuk 32-bit atau 64-bit
time_t
:#termasuk
# termasuk void TimetToFileTime( waktu_t T, LPFILETIME pft) { ULARGE_INTEGER nilai waktu; nilai waktu.QuadPart = (T * 10000000LL)+ 116444736000000000LL ; pft->dwLowDateTime = nilai waktu.Bagian bawah; pft->dwHighDateTime = nilai waktu.Bagian Tinggi; }Cuplikan ini berfungsi dengan baik terlepas dari jenis
time_t
, karenat 10000000LL
selalu mengembang ke nilai 64-bit melalui penggunaan literalLL
.Meskipun cuplikan ini sekarang sudah diperbaiki, saya butuh waktu terlalu lama untuk menyadari bahwa itu hanya setengah dari solusi. Saat bekerja di OpenRCT2, saya melihat fungsi yang sangat familiar dalam kode permainan, dan saya sadar bahwa fungsi yang rusak dari MSDN mungkin telah diadopsi secara luas, menyebarkan bug Y2038 bahkan di sekitar basis kode yang sangat modern. Melihat permintaan pencarian Sourcegraph, ada lebih dari 500 repositori aktif di GitHub yang berpotensi menggunakan cuplikan kode yang rusak ini baik secara langsung , atau melalui kode pihak ketiga.
Dengan mengingat hal ini, ini adalah saat yang tepat untuk menerapkan prinsip “jadilah perubahan yang ingin Anda lihat” dan mencoba untuk memperbaiki situasi ini.
Sebagai "proyek" pribadi, saya memutuskan untuk mencoba mendokumentasikan sebanyak mungkin contoh cuplikan kode ini yang digunakan dalam basis kode, dan menyarankan perbaikan jika memungkinkan. Saya akan mencoba untuk terus memperbarui daftar ini seiring dengan kemajuan.
Pembaruan terakhir: 18 Februari 2022
Repositori yang terpengaruh langsung oleh bug ini (yang saya temukan):
- DuckStation; status: tambalan terkirim
O3DE; status: -
- dokany; status: -
- ceph-dokan; status: -
- perpustakaan; status: -
- ghc::filesystem; saat ini hanya ada dalam fungsi yang tidak digunakan tetapi fungsi itu ada di header publik; status: -
- ImageMagick; hanya jika stempel waktunya di
stat
adalah 64-bit; status: -OpenRCT2; status: perbaikan digabungkan
Repositori terpengaruh secara tidak langsung, terutama oleh
Int32x32To64
secara diam-diam memotong parameter:
- Cxbx -Dimuat ulang; menggunakan
Int32x32To64
dengan int32 yang tidak ditandatangani; status: -- ReactOS; menggunakan
Int32x32To64
dengan int32 yang tidak ditandatangani; status: -Kali ini, saya hanya bisa memikirkan satu nasihat – harap hindari menggunakan
Int32x32To64
danUInt32x32To64 karena pemotongan nilai input yang tenang. Cukup kalikan angka seperti biasa.