Beberapa hari yang lalu, Microsoft membuat kode sumber Kalkulator Windows mereka tersedia untuk umum. Kalkulator adalah aplikasi yang secara tradisional dikirimkan dengan setiap versi Windows. Sejumlah proyek Microsoft menjadi open-source selama beberapa tahun terakhir, tetapi kali ini beritanya diliput bahkan oleh media non-IT pada hari pertama. Ya, ini adalah program yang populer namun kecil di C ++. Terlepas dari ukurannya, kami masih berhasil menemukan sejumlah fragmen yang mencurigakan dalam kodenya menggunakan analisa statis PVS-Studio.
Pendahuluan
Saya tidak berpikir kita perlu memperkenalkan Kalkulator karena Anda tidak akan menemukan pengguna Windows yang tidak tahu apa itu. Sekarang siapa pun dapat mengunduh kode sumber aplikasi dari
GitHub dan menyarankan peningkatannya.
Fungsi berikut, misalnya, sudah menarik perhatian komunitas:
void TraceLogger::LogInvalidInputPasted(....) { if (!GetTraceLoggingProviderEnabled()) return;
LoggingFields fields{}; fields.AddString(L"Mode", NavCategory::GetFriendlyName(mode)->Data()); fields.AddString(L"Reason", reason); fields.AddString(L"PastedExpression", pastedExpression); fields.AddString(L"ProgrammerNumberBase", GetProgrammerType(...).c_str()); fields.AddString(L"BitLengthType", GetProgrammerType(bitLengthType).c_str()); LogTelemetryEvent(EVENT_NAME_INVALID_INPUT_PASTED, fields); }
Fungsi ini mencatat teks dari clipboard dan mengirimkannya ke server Microsoft. Namun, pos ini bukan tentang fungsi itu, tetapi Anda pasti akan melihat banyak cuplikan yang mencurigakan.
Kami menggunakan analisa statis
PVS-Studio untuk memeriksa kode sumber Kalkulator. Karena tidak ditulis dalam standar C ++, banyak pembaca reguler kami meragukan cek semacam itu akan mungkin, tetapi kami melakukannya. Alat analisis mendukung C ++ / CLI dan C ++ / CX, dan meskipun beberapa diagnostik memang menghasilkan beberapa kesalahan positif, kami tidak menemukan masalah kritis yang akan menghambat pekerjaan PVS-Studio.
Sama seperti pengingat, jika Anda ketinggalan berita tentang kemampuan lain dari alat kami, PVS-Studio tidak hanya mendukung C dan C ++ tetapi C # dan Java juga.
Perbandingan string salah
Ekspresi
V547 'm_resolvedName == L "en-US"' selalu salah. Untuk membandingkan string, Anda harus menggunakan fungsi wcscmp (). Kalkulator LocalizationSettings.h 180
wchar_t m_resolvedName[LOCALE_NAME_MAX_LENGTH]; Platform::String^ GetEnglishValueFromLocalizedDigits(....) const { if (m_resolvedName == L"en-US") { return ref new Platform::String(localizedString.c_str()); } .... }
Saat melihat laporan penganalisa, saya mengurutkan peringatan dengan kode diagnostik dalam urutan menaik, dan yang ini, yang membuat contoh yang sangat jelas, adalah yang pertama dalam daftar.
Anda lihat, contoh di atas menunjukkan perbandingan string yang salah. Programmer sebenarnya membandingkan pointer daripada nilai string dengan membandingkan alamat array karakter dengan string literal. Pointer ini tidak pernah sama, jadi kondisinya selalu salah juga. Untuk perbandingan string yang benar, seseorang harus menggunakan fungsi
wcscmp , misalnya.
Ngomong-ngomong, ketika saya sedang menulis artikel ini, array karakter
m_resolvedName diperbaiki dalam file header dan menjadi string tipe
std :: wstring , sehingga perbandingan dapat dilakukan dengan benar sekarang. Pada saat Anda akan membaca artikel ini, banyak bug lain mungkin akan diperbaiki juga berkat para penggemar dan ulasan seperti ini.
Kebocoran memori dalam kode asli
V773 Fungsi itu keluar tanpa melepaskan pointer 'temp'. Kebocoran memori dimungkinkan. CalcViewModel StandardCalculatorViewModel.cpp 529
void StandardCalculatorViewModel::HandleUpdatedOperandData(Command cmdenum) { .... wchar_t* temp = new wchar_t[100]; .... if (commandIndex == 0) { delete [] temp; return; } .... length = m_selectedExpressionLastData->Length() + 1; if (length > 50) { return; } .... String^ updatedData = ref new String(temp); UpdateOperand(m_tokenPosition, updatedData); displayExpressionToken->Token = updatedData; IsOperandUpdatedUsingViewModel = true; displayExpressionToken->CommandIndex = commandIndex; }
Pointer
temp merujuk ke array 100 elemen yang dialokasikan secara dinamis. Sayangnya, memori dilepaskan hanya di satu bagian dari fungsi, sementara sisanya berakhir dengan kebocoran memori. Ini tidak terlalu buruk, tetapi masih dianggap sebagai bug dalam kode C ++.
Pengecualian yang sulit dipahami
Kelas
V702 harus selalu diturunkan dari std :: exception (dan yang sama) sebagai 'publik' (tidak ada kata kunci yang ditentukan, jadi kompiler secara default ke 'private'). CalcManager CalcException.h 4
class CalcException : std::exception { public: CalcException(HRESULT hr) { m_hr = hr; } HRESULT GetException() { return m_hr; } private: HRESULT m_hr; };
Penganalisis telah mendeteksi kelas yang berasal dari kelas
std :: exception menggunakan pengubah
pribadi (yang default jika tidak ada pengubah lain yang ditentukan). Masalah dengan kode ini adalah bahwa pawang akan mengabaikan pengecualian tipe
CalcException ketika mencoba untuk menangkap
std :: generic generic karena warisan pribadi melarang konversi tipe implisit.
Hari yang terlewat
V719 Pernyataan beralih tidak mencakup semua nilai enum 'DateUnit': Hari. CalcViewModel DateCalculator.cpp 279
public enum class _Enum_is_bitflag_ DateUnit { Year = 0x01, Month = 0x02, Week = 0x04, Day = 0x08 }; Windows::Globalization::Calendar^ m_calendar; DateTime DateCalculationEngine::AdjustCalendarDate(Windows::Foundation::DateTime date, DateUnit dateUnit, int difference) { m_calendar→SetDateTime(date); switch (dateUnit) { case DateUnit::Year: { .... m_calendar->AddYears(difference); m_calendar->ChangeCalendarSystem(currentCalendarSystem); break; } case DateUnit::Month: m_calendar->AddMonths(difference); break; case DateUnit::Week: m_calendar->AddWeeks(difference); break; } return m_calendar->GetDateTime(); }
Sangat mencurigakan bahwa pernyataan beralih tidak memiliki kasus
DateUnit :: Hari . Karena itu, nilai hari tidak akan ditambahkan ke kalender (variabel
m_calendar ), meskipun kalender tersebut memiliki metode
AddDays .
Kasus mencurigakan lainnya dengan enumerasi lain:
- V719 Pernyataan sakelar tidak mencakup semua nilai enum 'eANGLE_TYPE': ANGLE_RAD. CalcManager trans.cpp 109
- V719 Pernyataan sakelar tidak mencakup semua nilai enum 'eANGLE_TYPE': ANGLE_RAD. CalcManager trans.cpp 204
- V719 Pernyataan sakelar tidak mencakup semua nilai enum 'eANGLE_TYPE': ANGLE_RAD. CalcManager trans.cpp 276
Perbandingan angka nyata yang mencurigakan
V550 Perbandingan akurat yang aneh: rasio == ambang batas. Mungkin lebih baik menggunakan perbandingan dengan presisi yang ditentukan: fabs (A - B) <Epsilon. Calculator AspectRatioTrigger.cpp 80
void AspectRatioTrigger::UpdateIsActive(Size sourceSize) { double numerator, denominator; .... bool isActive = false; if (denominator > 0) { double ratio = numerator / denominator; double threshold = abs(Threshold); isActive = ((ratio > threshold) || (ActiveIfEqual && (ratio == threshold))); } SetActive(isActive); }
Penganalisis menunjukkan
rasio ekspresi mencurigakan
== ambang batas . Variabel-variabel ini bertipe
ganda dan, oleh karena itu, hampir tidak dapat dibandingkan secara tepat menggunakan operator biasa yang sama. Selain itu, nilai variabel
rasio adalah hasil dari operasi divisi.
Kode seperti itu terlihat sangat aneh dalam aplikasi seperti Kalkulator. Saya menyertakan daftar lengkap peringatan jenis ini untuk berjaga-jaga:
- V550 Perbandingan presisi yang aneh. Mungkin lebih baik menggunakan perbandingan dengan presisi yang ditentukan: fabs (A - B) <Epsilon. CalcManager UnitConverter.cpp 752
- V550 Perbandingan akurat yang aneh: stod (roundedString)! = 0,0. Mungkin lebih baik menggunakan perbandingan dengan presisi yang ditentukan: fabs (A - B)> Epsilon. CalcManager UnitConverter.cpp 778
- V550 Perbandingan presisi yang aneh. Mungkin lebih baik menggunakan perbandingan dengan presisi yang ditentukan: fabs (A - B) <Epsilon. CalcManager UnitConverter.cpp 790
- V550 Perbandingan akurat yang aneh: stod (roundedString)! = 0,0. Mungkin lebih baik menggunakan perbandingan dengan presisi yang ditentukan: fabs (A - B)> Epsilon. CalcManager UnitConverter.cpp 820
- V550 Perbandingan akurat yang aneh: conversionTable [m_toType] .ratio == 1.0. Mungkin lebih baik menggunakan perbandingan dengan presisi yang ditentukan: fabs (A - B) <Epsilon. CalcManager UnitConverter.cpp 980
- V550 Perbandingan akurat yang aneh: conversionTable [m_toType] .offset == 0,0. Mungkin lebih baik menggunakan perbandingan dengan presisi yang ditentukan: fabs (A - B) <Epsilon. CalcManager UnitConverter.cpp 980
- V550 Perbandingan akurat yang aneh: returnValue! = 0. Mungkin lebih baik menggunakan perbandingan dengan presisi yang ditentukan: fabs (A - B)> Epsilon. CalcManager UnitConverter.cpp 1000
- V550 Perbandingan presisi yang aneh: sizeToUse! = 0,0. Mungkin lebih baik menggunakan perbandingan dengan presisi yang ditentukan: fabs (A - B)> Epsilon. CalcViewModel LocalizationService.cpp 270
- V550 Perbandingan presisi yang aneh: sizeToUse! = 0,0. Mungkin lebih baik menggunakan perbandingan dengan presisi yang ditentukan: fabs (A - B)> Epsilon. CalcViewModel LocalizationService.cpp 289
- V550 Perbandingan presisi yang aneh: sizeToUse! = 0,0. Mungkin lebih baik menggunakan perbandingan dengan presisi yang ditentukan: fabs (A - B)> Epsilon. CalcViewModel LocalizationService.cpp 308
- V550 Perbandingan presisi yang aneh: sizeToUse! = 0,0. Mungkin lebih baik menggunakan perbandingan dengan presisi yang ditentukan: fabs (A - B)> Epsilon. CalcViewModel LocalizationService.cpp 327
- V550 Perbandingan akurat yang aneh: stod (stringToLocalize) == 0. Mungkin lebih baik menggunakan perbandingan dengan presisi yang ditentukan: fabs (A - B) <Epsilon. CalcViewModel UnitConverterViewModel.cpp 388
Urutan fungsi yang mencurigakan
V1020 Fungsi keluar tanpa memanggil fungsi 'TraceLogger :: GetInstance (). LogNewWindowCreationEnd'. Periksa baris: 396, 375. Aplikasi Kalkulator.xaml.cpp 396
void App::OnAppLaunch(IActivatedEventArgs^ args, String^ argument) { .... if (!m_preLaunched) { auto newCoreAppView = CoreApplication::CreateNewView(); newCoreAppView->Dispatcher->RunAsync(....([....]() { TraceLogger::GetInstance().LogNewWindowCreationBegin(....);
Diagnostik
V1020 memeriksa blok kode dan mencari cabang dengan panggilan fungsi yang hilang menggunakan heuristik.
Cuplikan di atas berisi blok dengan panggilan ke fungsi
LogNewWindowCreationBegin dan
LogNewWindowCreationEnd . Ini diikuti oleh blok lain di mana fungsi
LogNewWindowCreationEnd dipanggil hanya jika kondisi tertentu terpenuhi, yang terlihat sangat mencurigakan.
Tes tidak dapat diandalkan
V621 Pertimbangkan untuk memeriksa operator 'untuk'. Ada kemungkinan bahwa loop akan dieksekusi secara tidak benar atau tidak akan dieksekusi sama sekali. CalculatorUnitTests UnitConverterViewModelUnitTests.cpp 500
public enum class NumbersAndOperatorsEnum { .... Add = (int) CM::Command::CommandADD,
Alat analisa telah mendeteksi loop
untuk yang tidak berjalan sama sekali, yang berarti tes tidak mengeksekusi baik. Nilai awal dari
tombol penghitung putaran (93) lebih besar dari nilai akhir (0) sejak awal.
V760 Dua blok teks yang identik ditemukan. Blok kedua dimulai dari baris 688. CalculatorUnitTests UnitConverterViewModelUnitTests.cpp 683
TEST_METHOD(TestSwitchAndReselectCurrentlyActiveValueDoesNothing) { shared_ptr<UnitConverterMock> mock = make_shared<UnitConverterMock>(); VM::UnitConverterViewModel vm(mock); const WCHAR * vFrom = L"1", *vTo = L"234"; vm.UpdateDisplay(vFrom, vTo); vm.Value2Active = true;
Tes mencurigakan lainnya. Penganalisis telah mendeteksi dua fragmen kode identik yang segera dieksekusi satu demi satu. Sepertinya kode ini ditulis menggunakan teknik salin-tempel dan programmer lupa untuk memodifikasi salinan.
V601 Nilai 'salah' secara implisit dilemparkan ke tipe integer. Periksa argumen kedua. CalculatorUnitTests CalcInputTest.cpp 352
Rational CalcInput::ToRational(uint32_t radix, int32_t precision) { .... } TEST_METHOD(ToRational) { .... auto rat = m_calcInput.ToRational(10, false); .... }
Fungsi
ToRational disebut dengan nilai Boolean
false , sedangkan parameter yang sesuai adalah tipe
int32_t dan disebut
presisi .
Saya memutuskan untuk melacak nilai kode dan melihat bahwa itu kemudian diteruskan ke fungsi
StringToRat :
PRAT StringToRat(...., int32_t precision) { .... }
dan kemudian ke
StringToNumber :
PNUMBER StringToNumber(...., int32_t precision) { .... stripzeroesnum(pnumret, precision); .... }
Inilah tubuh fungsi target:
bool stripzeroesnum(_Inout_ PNUMBER pnum, long starting) { MANTTYPE *pmant; long cdigits; bool fstrip = false; pmant=pnum->mant; cdigits=pnum->cdigit; if ( cdigits > starting )
Variabel
presisi sekarang bernama
starting dan berpartisipasi dalam ekspresi
cdigits> starting , yang sangat mencurigakan karena
false dilewatkan sebagai nilai asli.
Redundansi
V560 Bagian dari ekspresi kondisional selalu benar: NumbersAndOperatorsEnum :: Tidak ada! = Op. CalcViewModel UnitConverterViewModel.cpp 991
void UnitConverterViewModel::OnPaste(String^ stringToPaste, ViewMode mode) { .... NumbersAndOperatorsEnum op = MapCharacterToButtonId(*it, canSendNegate); if (NumbersAndOperatorsEnum::None != op)
Variabel
op sudah dibandingkan dengan nilai
NumbersAndOperatorsEnum :: Tidak ada , sehingga pemeriksaan duplikat dapat dihapus.
V728 Pemeriksaan berlebihan dapat disederhanakan. The '(A && B) || Ekspresi (! A &&! B) 'sama dengan ekspresi' bool (A) == bool (B) '. Calculator Calculator.xaml.cpp 239
void Calculator::AnimateCalculator(bool resultAnimate) { if (App::IsAnimationEnabled()) { m_doAnimate = true; m_resultAnimate = resultAnimate; if (((m_isLastAnimatedInScientific && IsScientific) || (!m_isLastAnimatedInScientific && !IsScientific)) && ((m_isLastAnimatedInProgrammer && IsProgrammer) || (!m_isLastAnimatedInProgrammer && !IsProgrammer))) { this->OnStoryboardCompleted(nullptr, nullptr); } } }
Ekspresi bersyarat besar ini awalnya 218 karakter, tetapi saya membaginya menjadi beberapa baris untuk tujuan demonstrasi. Ini dapat ditulis ulang menjadi versi yang jauh lebih pendek dan, yang paling penting, lebih jelas:
if ( m_isLastAnimatedInScientific == IsScientific && m_isLastAnimatedInProgrammer == IsProgrammer) { this->OnStoryboardCompleted(nullptr, nullptr); }
V524 Aneh bahwa tubuh fungsi 'ConvertBack' sepenuhnya setara dengan tubuh fungsi 'Konversi'. Kalkulator BooleanNegationConverter.cpp 24
Object^ BooleanNegationConverter::Convert(....) { (void) targetType;
Penganalisa telah mendeteksi dua fungsi yang diimplementasikan secara identik. Seperti yang
disarankan oleh nama mereka,
Convert dan
ConvertBack , mereka seharusnya melakukan hal-hal yang berbeda, tetapi para pengembang harus lebih tahu.
Kesimpulan
Saya kira setiap proyek Microsoft yang dibuat open-source memberi kami kesempatan untuk menunjukkan pentingnya analisis statis - bahkan pada proyek sekecil Kalkulator. Perusahaan besar, seperti Microsoft, Google, Amazon, dll., Mempekerjakan banyak pengembang berbakat, tetapi mereka masih manusia membuat kesalahan. Alat analisis statis adalah salah satu cara terbaik untuk membantu tim pengembang mana pun meningkatkan kualitas produk mereka.
Selamat mengunduh
PVS-Studio dan cobalah "Kalkulator" Anda sendiri. :-)