Sebelumnya kami melakukan review kode dari paket matematika besar, misalnya, Scilab dan Oktaf, di mana kalkulator tetap menyendiri sebagai utilitas kecil, di mana sulit untuk membuat kesalahan karena basis kode kecil mereka. Kami salah bahwa kami tidak memperhatikan mereka. Kasus dengan memposting kode sumber kalkulator Windows menunjukkan bahwa sebenarnya semua orang tertarik untuk mendiskusikan jenis kesalahan yang bersembunyi di dalamnya. Apalagi jumlah kesalahan di sana lebih dari cukup untuk menulis artikel tentang itu. Kolega saya dan saya, kami memutuskan untuk menjelajahi kode sejumlah kalkulator populer, dan ternyata kode kalkulator Windows tidak terlalu buruk (spoiler).
Pendahuluan
Hitung Ulang! adalah kalkulator desktop lintas platform multi guna. Mudah digunakan tetapi memberikan daya dan keserbagunaan yang biasanya dicadangkan untuk paket matematika yang rumit, serta alat yang berguna untuk kebutuhan sehari-hari (seperti konversi mata uang dan perhitungan persen). Proyek ini terdiri dari dua komponen:
libqalculate (library dan CLI) dan
qalculate-gtk (GTK + UI). Studi ini hanya melibatkan kode libqalculate.
Untuk membandingkan proyek dengan Windows Calculator dengan mudah, yang baru-baru ini kami periksa, saya mengutip keluaran dari utilitas Cloc untuk libqalculate:
Mempertimbangkannya secara subjektif, ada lebih banyak kesalahan di dalamnya dan mereka lebih kritis daripada dalam kode kalkulator Windows. Namun demikian, saya akan merekomendasikan membuat kesimpulan sendiri, setelah membaca ikhtisar kode ini.
Ngomong-ngomong, inilah tautan ke artikel tentang pemeriksaan kalkulator dari Microsoft: "
Menghitung Bug di Kalkulator Windows ".
Alat analisis adalah penganalisa kode statis
PVS-Studio . Ini adalah serangkaian solusi untuk kontrol kualitas kode, mencari bug dan kerentanan potensial. Bahasa yang didukung meliputi: C, C ++, C # dan Java. Anda dapat menjalankan penganalisis pada Windows, Linux dan macOS.
Salin dan tempel lagi dan lagi!
V523 Pernyataan 'lalu' sama dengan pernyataan 'lain'. Number.cc 4018
bool Number::square() { .... if(mpfr_cmpabs(i_value->internalLowerFloat(), i_value->internalUpperFloat()) > 0) { mpfr_sqr(f_tmp, i_value->internalLowerFloat(), MPFR_RNDU); mpfr_sub(f_rl, f_rl, f_tmp, MPFR_RNDD); } else { mpfr_sqr(f_tmp, i_value->internalLowerFloat(), MPFR_RNDU); mpfr_sub(f_rl, f_rl, f_tmp, MPFR_RNDD); } .... }
Kode ini benar-benar sama di blok
if and
else . Fragmen kode yang berdekatan sangat mirip dengan yang ini, tetapi fungsi yang berbeda digunakan di dalamnya:
internalLowerFloat () dan
internalUpperFloat () . Aman untuk mengasumsikan bahwa pengembang menyalin kode dan lupa untuk memperbaiki nama fungsi di sini.
V501 Ada sub-ekspresi identik '! Mtr2.number (). IsReal ()' di kiri dan di kanan '||' operator. BuiltinFunctions.cc 6274
int IntegrateFunction::calculate(....) { .... if(!mtr2.isNumber() || !mtr2.number().isReal() || !mtr.isNumber() || !mtr2.number().isReal()) b_unknown_precision = true; .... }
Dalam hal ini, ekspresi duplikat muncul karena fakta bahwa di satu tempat
mtr2 ditulis, bukan
mtr. Dengan demikian, panggilan fungsi
mtr.number (). IsReal () tidak ada dalam kondisi tersebut.
V501 Ada sub-ekspresi identik 'muatan [1] .representsNonPositive ()' ke kiri dan ke kanan '||' operator. BuiltinFunctions.cc 5785
Kami tidak akan pernah menemukan cacat dalam kode ini secara manual! Tapi di sini ada. Selain itu, dalam file asli fragmen ini ditulis dalam satu baris. Penganalisis telah mendeteksi duplikasi ekspresi
muatan [1] .representsNonPositive () , yang dapat menunjukkan kesalahan ketik atau, akibatnya, kesalahan potensial.
Inilah seluruh daftar tempat mencurigakan, yang sulit ditemukan.
- V501 Ada sub-ekspresi identik 'muatan [1] .representsNonPositive ()' ke kiri dan ke kanan '||' operator. BuiltinFunctions.cc 5788
- V501 Ada sub-ekspresi identik 'tambahkan' ke kiri dan ke kanan operator '&&'. MathStructure.cc 1780
- V501 Ada sub-ekspresi identik 'tambahkan' ke kiri dan ke kanan operator '&&'. MathStructure.cc 2043
- V501 Ada sub-ekspresi yang identik '(* v_subs [v_order [1]]). Merupakan Representatif (benar)' di sebelah kiri dan di sebelah kanan operator '&&'. MathStructure.cc 5569
Loop Dengan Kondisi Yang Tidak Benar
V534 Kemungkinan variabel yang salah sedang dibandingkan di dalam operator 'untuk'. Pertimbangkan untuk meninjau 'i'. MathStructure.cc 28741
bool MathStructure::isolate_x_sub(....) { .... for(size_t i = 0; i < mvar->size(); i++) { if((*mvar)[i].contains(x_var)) { mvar2 = &(*mvar)[i]; if(mvar->isMultiplication()) { for(size_t i2 = 0; i < mvar2->size(); i2++) { if((*mvar2)[i2].contains(x_var)) {mvar2 = &(*mvar2)[i2]; break;} } } break; } } .... }
Di loop dalam, variabel
i2 mewakili penghitung, tetapi karena kesalahan ketik kesalahan dibuat - variabel
i dari loop luar digunakan dalam kondisi loop keluar.
Redundansi atau kesalahan?
V590 Pertimbangkan untuk memeriksa ungkapan ini. Ekspresi berlebihan atau mengandung kesalahan cetak. Number.cc 6564
bool Number::add(const Number &o, MathOperation op) { .... if(i1 >= COMPARISON_RESULT_UNKNOWN && (i2 == COMPARISON_RESULT_UNKNOWN || i2 != COMPARISON_RESULT_LESS)) return false; .... }
3 tahun yang lalu setelah saya melihat kode semacam itu, saya menulis lembar contekan untuk saya dan pengembang lain: "
Ekspresi logis dalam C / C ++. Kesalahan yang Dibuat oleh Profesional ". Ketika saya menemukan kode seperti itu, saya memastikan bahwa catatan itu belum menjadi kurang relevan. Anda dapat melihat ke dalam artikel, menemukan pola kesalahan yang sesuai dengan kode, dan mencari tahu semua nuansanya.
Dalam kasus contoh ini, kita akan pergi ke bagian “Ekspresi == || ! = "Dan cari tahu bahwa ekspresi
i2 == COMPARISON_RESULT_UNKNOWN tidak mempengaruhi apa pun.
Dereferencing Pointer Tidak Dicentang
V595 Pointer 'o_data' digunakan sebelum diverifikasi terhadap nullptr. Periksa baris: 1108, 1112. DataSet.cc 1108
string DataObjectArgument::subprintlong() const { string str = _("an object from"); str += " \""; str += o_data->title();
Dalam satu fungsi pointer
o_data dereferenced baik tanpa maupun dengan cek. Ini bisa berupa kode yang berlebihan, atau kesalahan potensial. Saya condong ke arah yang terakhir.
Ada dua tempat serupa:
- V595 Pointer 'o_assumption' digunakan sebelum diverifikasi terhadap nullptr. Periksa baris: 229, 230. Variable.cc 229
- V595 Pointer 'i_value' digunakan sebelum diverifikasi terhadap nullptr. Periksa baris: 3412, 3427. Number.cc 3412
gratis () atau hapus []?
V611 Memori dialokasikan menggunakan operator 'baru' tetapi dirilis menggunakan fungsi 'bebas'. Pertimbangkan untuk memeriksa logika operasi di belakang variabel 'remcopy'. Number.cc 8123
string Number::print(....) const { .... while(!exact && precision2 > 0) { if(try_infinite_series) { remcopy = new mpz_t[1];
Memori untuk array
salinan dialokasikan dan dirilis dengan cara yang berbeda, yang merupakan kesalahan serius.
Perubahan yang hilang
bool expand_partial_fractions(MathStructure &m, ....) { .... if(b_poly && !mquo.isZero()) { MathStructure m = mquo; if(!mrem.isZero()) { m += mrem; m.last() *= mtest[i]; m.childrenUpdated(); } expand_partial_fractions(m, eo, false); return true; } .... }
Variabel
m dalam fungsi dilewatkan oleh referensi, yang berarti modifikasi. Namun, penganalisa telah mendeteksi bahwa kode tersebut berisi variabel dengan nama yang sama, yang tumpang tindih dengan cakupan parameter fungsi, yang memungkinkan hilangnya perubahan.
Pointer aneh
V774 Penunjuk 'cu' digunakan setelah memori dilepaskan. Calculator.cc 3595
MathStructure Calculator::convertToBestUnit(....) { .... CompositeUnit *cu = new CompositeUnit("", "...."); cu->add(....); Unit *u = getBestUnit(cu, false, eo.local_currency_conversion); if(u == cu) { delete cu;
Penganalisa memperingatkan bahwa kode memanggil metode objek
cu setelah deallocating memori. Tetapi ketika mencoba untuk bergulat dengannya, kode tersebut ternyata menjadi lebih aneh. Pertama, panggilan
delete cu selalu terjadi - baik dalam kondisi dan setelah itu. Kedua, kode setelah kondisi menyiratkan bahwa pointer
u dan
cu tidak sama, yang berarti bahwa setelah menghapus objek
cu , cukup logis untuk menggunakan objek
u . Kemungkinan besar, kesalahan ketik dibuat dalam kode dan pembuat kode hanya ingin menggunakan variabel
u .
Penggunaan fungsi find
V797 Fungsi 'find' digunakan seolah-olah mengembalikan tipe bool. Nilai pengembalian fungsi mungkin harus dibandingkan dengan std :: string :: npos. Unit.cc 404
MathStructure &AliasUnit::convertFromFirstBaseUnit(....) const { if(i_exp != 1) mexp /= i_exp; ParseOptions po; if(isApproximate() && suncertainty.empty() && precision() == -1) { if(sinverse.find(DOT) || svalue.find(DOT)) po.read_precision = READ_PRECISION_WHEN_DECIMALS; else po.read_precision = ALWAYS_READ_PRECISION; } .... }
Meskipun kode berhasil dikompilasi, kode tersebut terlihat mencurigakan, karena fungsi
find mengembalikan jumlah tipe
std :: string :: size_type . Kondisi ini akan benar jika titik ditemukan di bagian mana pun dari string kecuali jika titiknya ada di awal. Ini cek aneh. Saya tidak yakin tetapi, mungkin, kode ini harus ditulis ulang sebagai berikut:
if( sinverse.find(DOT) != std::string::npos || svalue.find(DOT) != std::string::npos) { po.read_precision = READ_PRECISION_WHEN_DECIMALS; }
Potensi kebocoran memori
V701 realloc () kemungkinan kebocoran: ketika realloc () gagal mengalokasikan memori, 'buffer' pointer asli hilang. Pertimbangkan untuk menetapkan realloc () ke pointer sementara. util.cc 703
char *utf8_strdown(const char *str, int l) { #ifdef HAVE_ICU .... outlength = length + 4; buffer = (char*) realloc(buffer, outlength * sizeof(char));
Ketika bekerja dengan fungsi
realloc () disarankan untuk menggunakan buffer perantara, seperti dalam kasus jika tidak mungkin mengalokasikan memori, penunjuk ke area memori lama akan hilang.
Kesimpulan
The Qalculate! proyek menduduki puncak daftar kalkulator gratis terbaik, sedangkan itu berisi banyak kesalahan serius. Di sisi lain, kami belum memeriksa pesaingnya. Kami akan mencoba memeriksa semua kalkulator populer.
Sedangkan untuk membandingkan dengan kualitas kalkulator dari dunia Windows, utilitas dari Microsoft terlihat lebih andal dan berfungsi dengan baik sejauh ini.
Periksa "Kalkulator" Anda sendiri - unduh
PVS-Studio dan coba untuk proyek Anda. :-)