Jawaban untuk tugas-tugas dari stan PVS-Studio di konferensi 2018-2019

Gambar 2


Hai Terlepas dari kenyataan bahwa musim konferensi 2019 masih berjalan lancar, kami ingin membahas tugas-tugas yang ditawarkan kepada pengunjung stan kami sebelumnya. Kami memulai musim gugur 2019 dengan serangkaian tugas baru, jadi sudah mungkin untuk mempublikasikan solusi masalah lama untuk 2018, serta paruh pertama 2019. Selain itu, banyak dari mereka diambil dari artikel yang diterbitkan sebelumnya, dan selebaran dengan tugas berisi tautan atau kode QR dengan informasi tentang artikel tersebut.

Jika Anda menghadiri konferensi di mana kami berdiri dengan berdiri, Anda mungkin melihat atau bahkan memecahkan beberapa masalah kami. Ini selalu cuplikan kode dari proyek sumber terbuka nyata dalam bahasa pemrograman C, C ++, C #, atau Java. Kode berisi kesalahan yang kami sarankan untuk dicari pengunjung. Untuk solusinya (atau hanya diskusi tentang kesalahan) kami memberikan hadiah - status di desktop, pernak-pernik, dll .:

Gambar 4

Apakah Anda menginginkan hal yang sama? Datanglah ke gerai kami di konferensi berikutnya.

Ngomong-ngomong, artikel " Waktu konferensi! Menyimpulkan hasil 2018 " dan " Konferensi. Hasil sementara untuk paruh pertama 2019 " berisi deskripsi kegiatan kami di konferensi ini dan tahun lalu.

Jadi, mari kita mulai permainan "Temukan kesalahan dalam kode". Pertama, pertimbangkan tugas-tugas lama untuk 2018, kami akan menggunakan pengelompokan berdasarkan bahasa pemrograman.

2018


C ++


Bug kromium

static const int kDaysInMonth[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; bool ValidateDateTime(const DateTime& time) { if (time.year < 1 || time.year > 9999 || time.month < 1 || time.month > 12 || time.day < 1 || time.day > 31 || time.hour < 0 || time.hour > 23 || time.minute < 0 || time.minute > 59 || time.second < 0 || time.second > 59) { return false; } if (time.month == 2 && IsLeapYear(time.year)) { return time.month <= kDaysInMonth[time.month] + 1; } else { return time.month <= kDaysInMonth[time.month]; } } 

Jawabannya
Mungkin tugas yang paling "lama bermain" dari set kami. Kami menyarankan bahwa kesalahan ini dalam proyek Chromium ditemukan oleh pengunjung ke stan kami sepanjang 2018. Itu juga telah ditampilkan dalam beberapa laporan.

 if (time.month == 2 && IsLeapYear(time.year)) { return time.month <= kDaysInMonth[time.month] + 1; // <= day } else { return time.month <= kDaysInMonth[time.month]; // <= day } 

Badan blok If-else terakhir berisi kesalahan ketik dalam nilai kembali. Alih-alih time.day, programmer dua kali salah menentukan time.month . Ini telah mengarah pada kenyataan bahwa yang benar akan selalu dikembalikan. Kesalahan dijelaskan secara rinci dalam artikel " 31 Februari ". Sebuah contoh yang bagus dari bug yang tidak mudah dikenali pada tinjauan kode. Ini juga merupakan ilustrasi yang baik tentang penggunaan teknologi analisis aliran data.

Bug mesin tidak nyata

 bool VertInfluencedByActiveBone( FParticleEmitterInstance* Owner, USkeletalMeshComponent* InSkelMeshComponent, int32 InVertexIndex, int32* OutBoneIndex = NULL); void UParticleModuleLocationSkelVertSurface::Spawn(....) { .... int32 BoneIndex1, BoneIndex2, BoneIndex3; BoneIndex1 = BoneIndex2 = BoneIndex3 = INDEX_NONE; if(!VertInfluencedByActiveBone( Owner, SourceComponent, VertIndex[0], &BoneIndex1) && !VertInfluencedByActiveBone( Owner, SourceComponent, VertIndex[1], &BoneIndex2) && !VertInfluencedByActiveBone( Owner, SourceComponent, VertIndex[2]) &BoneIndex3) { .... } 

Jawabannya
Hal pertama yang perlu diperhatikan adalah bahwa argumen terakhir ke fungsi VertInfluencedByActiveBone () memiliki nilai default dan mungkin tidak ditentukan. Sekarang lihat blok if . Sederhana itu dapat ditulis ulang sebagai berikut:
 if (!foo(....) && !foo(....) && !foo(....) & arg) 

Sekarang jelas bahwa telah terjadi kesalahan. Karena kesalahan ketik, panggilan ketiga ke fungsi VertInfluencedByActiveBone () dibuat dengan tiga argumen alih-alih empat, dan operator & diterapkan pada hasil panggilan ini (bitwise AND, di sebelah kiri adalah hasil fungsi VertInfluencedByActiveBone () dari tipe bool , di sebelah kanan adalah variabel bilangan typeIndex ). Kode dikompilasi. Versi kode yang diperbaiki (koma ditambahkan, penutup braket dipindahkan ke tempat lain):

 if(!VertInfluencedByActiveBone( Owner, SourceComponent, VertIndex[0], &BoneIndex1) && !VertInfluencedByActiveBone( Owner, SourceComponent, VertIndex[1], &BoneIndex2) && !VertInfluencedByActiveBone( Owner, SourceComponent, VertIndex[2], &BoneIndex3)) 

Kesalahan yang awalnya dijelaskan dalam artikel " Pemeriksaan Unreal Engine 4 " yang telah lama ditunggu-tunggu . Artikel tersebut berjudul "Yang Paling Indah dari Kesalahan Ditemukan" dalam artikel tersebut. Saya setuju dengan pernyataan ini.

Bug Android

 void TagMonitor::parseTagsToMonitor(String8 tagNames) { std::lock_guard<std::mutex> lock(mMonitorMutex); // Expand shorthands if (ssize_t idx = tagNames.find("3a") != -1) { ssize_t end = tagNames.find(",", idx); char* start = tagNames.lockBuffer(tagNames.size()); start[idx] = '\0'; .... } .... } 

Jawabannya
Dalam kondisi blok if , prioritas operasi dikacaukan. Kode tidak berfungsi seperti yang dimaksudkan oleh programmer:

 if (ssize_t idx = (tagNames.find("3a") != -1)) 

Variabel idx akan menerima nilai 0 atau 1, dan pemenuhan kondisi akan tergantung pada nilai ini, yang merupakan kesalahan. Versi kode yang diperbaiki:

 ssize_t idx = tagNames.find("3a"); if (idx != -1) 

Kesalahan dari artikel "Kami memeriksa kode sumber Android menggunakan PVS-Studio, atau tidak ada yang sempurna ."

Dan satu tugas lagi untuk menemukan kesalahan non-sepele di Android:

 typedef int32_t GGLfixed; GGLfixed gglFastDivx(GGLfixed n, GGLfixed d) { if ((d>>24) && ((d>>24)+1)) { n >>= 8; d >>= 8; } return gglMulx(n, gglRecip(d)); } 

Jawabannya
Masalahnya ada pada ekspresi (d >> 24) +1 .

Pemrogram ingin memverifikasi bahwa 8 bit orde tinggi dari variabel d berisi unit, tetapi tidak semua bit sekaligus. Dengan kata lain, pemrogram ingin memverifikasi bahwa nilai apa pun selain 0x00 dan 0xFF berada dalam byte tinggi. Pertama, dia memeriksa bahwa bit yang paling signifikan adalah nol dengan menulis ekspresi (d >> 24). Kemudian menggeser bit delapan tinggi ke byte rendah. Pada saat yang sama, ia menghitung bahwa bit tanda paling signifikan diduplikasi di semua bit lainnya. Artinya, jika variabel d sama dengan 0b11111111'00000000'00000000'00000000, maka setelah shift kita mendapatkan nilai 0b11111111'11111111'11111111111111111111. Menambahkan 1 ke nilai 0xFFFFFFFF dari tipe int , programmer berencana untuk mendapatkan 0 (-1 + 1 = 0). Jadi, dengan ekspresi ((d >> 24) +1), ia memeriksa bahwa tidak semua bit delapan tinggi sama dengan 1.

Namun, ketika bergeser, bit yang paling signifikan tidak harus "dioleskan". Standar tersebut mengatakan: "Nilai E1 >> E2 adalah posisi bit E2 bergeser kanan E1. Jika E1 memiliki tipe yang tidak ditandatangani atau jika E1 memiliki tipe yang ditandatangani dan nilai yang tidak negatif, nilai hasilnya adalah bagian integral dari hasil bagi dari E1 / 2 ^ E2. Jika E1 memiliki tipe yang ditandatangani dan nilai negatif, nilai yang dihasilkan ditentukan oleh implementasi . "

Jadi ini adalah contoh perilaku yang ditentukan implementasi. Bagaimana kode ini akan bekerja tergantung pada arsitektur mikroprosesor dan implementasi kompiler. Setelah shift, nol mungkin muncul dalam bit paling signifikan, dan kemudian hasil dari ekspresi ((d >> 24) +1) akan selalu berbeda dari 0, yaitu, itu akan selalu menjadi nilai yang benar.

Memang tugas yang sulit. Kesalahan ini, seperti yang sebelumnya, dijelaskan dalam artikel "Kami memeriksa kode sumber Android menggunakan PVS-Studio, atau tidak ada yang sempurna ."

2019


C ++


"GCC yang harus disalahkan"

 int foo(const unsigned char *s) { int r = 0; while(*s) { r += ((r * 20891 + *s *200) | *s ^ 4 | *s ^ 3) ^ (r >> 1); s++; } return r & 0x7fffffff; } 

Programmer mengklaim bahwa kode ini berfungsi dengan kesalahan karena kesalahan dari kompiler GCC 8. Apakah ini benar?

Jawabannya
Fungsi mengembalikan nilai negatif. Alasannya adalah bahwa kompiler tidak menghasilkan kode untuk operator bitwise AND (&). Kesalahan ini disebabkan oleh perilaku yang tidak terdefinisi. Kompiler melihat bahwa jumlah tertentu dipertimbangkan dalam variabel r . Dalam hal ini, hanya angka positif yang ditambahkan. Kelimpahan variabel r seharusnya tidak terjadi, jika tidak ini adalah perilaku yang tidak ditentukan yang tidak boleh dipertimbangkan oleh kompilator dan memperhitungkannya dengan cara apa pun. Jadi, kompiler percaya bahwa karena nilai dalam variabel r setelah akhir siklus tidak boleh negatif, operasi r & 0x7fffffff untuk me-reset bit tanda berlebihan dan kompiler hanya mengembalikan nilai variabel r dari fungsi.

Kesalahan dari artikel " Rilis PVS-Studio 6.26 ".

Bug QT

 static inline const QMetaObjectPrivate *priv(const uint* data) { return reinterpret_cast<const QMetaObjectPrivate*>(data); } bool QMetaEnum::isFlag() const { const int offset = priv(mobj->d.data)->revision >= 8 ? 2 : 1; return mobj && mobj->d.data[handle + offset] & EnumIsFlag; } 

Jawabannya
Pointer mobj tidak aman. Pertama, dereferensi, dan kemudian diperiksa. Klasik

Kesalahan itu dijelaskan dalam artikel " Tes Qt 5 Ketiga dengan PVS-Studio ".

C #


Bug Infer.NET

 public static void WriteAttribute(TextWriter writer, string name, object defaultValue, object value, Func<object, string> converter = null) { if ( defaultValue == null && value == null || value.Equals(defaultValue)) { return; } string stringValue = converter == null ? value.ToString() : converter(value); writer.Write($"{name}=\"{stringValue}\" "); } 

Jawabannya
Dalam ekspresi value.Equals (defaultValue) , akses dimungkinkan melalui referensi nol nilai . Ini akan terjadi dengan nilai variabel tersebut, ketika defaultValue! = Null , dan nilai == null .

Kesalahan dari artikel " Kesalahan apa yang disembunyikan dalam kode Infer.NET? "

Bug FastReport

 public class FastString { private const int initCapacity = 32; private void Init(int iniCapacity) { sb = new StringBuilder(iniCapacity); .... } public FastString() { Init(initCapacity); } public FastString(int iniCapacity) { Init(initCapacity); } public StringBuilder StringBuilder => sb; } .... Console.WriteLine(new FastString(256).StringBuilder.Capacity); 

Apa yang akan ditampilkan di konsol? Apa yang salah dengan kelas FastString ?

Jawabannya
32 akan ditampilkan di konsol. Alasannya adalah kesalahan ketik pada nama variabel yang diteruskan ke metode Init di konstruktor:

 public FastString(int iniCapacity){ Init(initCapacity); } 

Parameter konstruktor iniCapacity tidak akan digunakan. Sebaliknya, konstanta initCapacity diteruskan ke metode Init .

Kesalahan itu dijelaskan dalam artikel " Laporan tercepat di barat liar. Dan beberapa bug di samping ... "

Roslyn bug

 private SyntaxNode GetNode(SyntaxNode root) { var current = root; .... while (current.FullSpan.Contains(....)) { .... var nodeOrToken = current.ChildThatContainsPosition(....); .... current = nodeOrToken.AsNode(); } .... } public SyntaxNode AsNode() { if (_token != null) { return null; } return _nodeOrParent; } 

Jawabannya
Akses dimungkinkan melalui arus referensi nol dalam ekspresi current.FullSpan.Contains (....) . Variabel saat ini bisa mendapatkan nilai nol sebagai hasil dari mengeksekusi metode nodeOrToken.AsNode () .

Kesalahan dari artikel " Memeriksa kode sumber Roslyn ".

Bug persatuan

 .... staticFields = packedSnapshot.typeDescriptions .Where(t => t.staticFieldBytes != null & t.staticFieldBytes.Length > 0) .Select(t => UnpackStaticFields(t)) .ToArray() .... 

Jawabannya
Kesalahan ketik diizinkan: alih-alih operator && , operator & digunakan. Ini mengarah pada fakta bahwa centang t.staticFieldBytes.Length> 0 selalu dilakukan, bahkan jika variabel nol adalah t.staticFieldBytes , yang, pada gilirannya, akan menghasilkan akses melalui referensi nol.

Kesalahan ini pertama kali diperlihatkan dalam artikel " Menganalisis Kesalahan dalam Komponen Unity3D Terbuka ".

Jawa


Bug IntelliJ IDEA

 private static boolean checkSentenceCapitalization(@NotNull String value) { List<String> words = StringUtil.split(value, " "); .... int capitalized = 1; .... return capitalized / words.size() < 0.2; // allow reasonable amount of // capitalized words } 

Diusulkan untuk menentukan mengapa jumlah kata dengan huruf kapital akan dihitung secara tidak benar.

Jawabannya
Fungsi harus mengembalikan true jika kurang dari 20% dari kata-kata dimulai dengan huruf kapital. Tetapi cek tidak berfungsi, karena pembagian bilangan bulat terjadi, yang hasilnya hanya berupa nilai 0 atau 1. Fungsi akan mengembalikan nilai yang salah hanya jika semua kata dimulai dengan huruf kapital. Dalam kasus lain, pembagian akan menghasilkan 0, dan fungsi akan mengembalikan true.

Kesalahan dari artikel " PVS-Studio untuk Java ".

Bug Spotbugs

 public static String getXMLType(@WillNotClose InputStream in) throws IOException { .... String s; int count = 0; while (count < 4) { s = r.readLine(); if (s == null) { break; } Matcher m = tag.matcher(s); if (m.find()) { return m.group(1); } } throw new IOException("Didn't find xml tag"); .... } 

Diusulkan untuk menentukan apa kesalahan pencarian tag xml itu.

Jawabannya
Kondisi hitung <4 akan selalu dipenuhi, karena jumlah variabel tidak bertambah di dalam loop. Diasumsikan bahwa pencarian untuk tag xml harus dilakukan hanya dalam empat baris pertama file, tetapi karena kesalahan seluruh file akan dibaca.

Kesalahan ini, seperti yang sebelumnya, dijelaskan dalam artikel " PVS-Studio untuk Java ".

Itu saja. Kami menunggu Anda di konferensi berikutnya. Cari tempat unicorn. Kami akan memberikan teka-teki baru yang menarik dan, tentu saja, hadiah. Sampai ketemu lagi!



Jika Anda ingin berbagi artikel ini dengan audiens yang berbahasa Inggris, silakan gunakan tautan ke terjemahan: Sergey Khrenov. Solusi untuk Tantangan Penemuan Bug yang Ditawarkan oleh Tim PVS-Studio di Konferensi pada 2018-2019 .

Source: https://habr.com/ru/post/id476272/


All Articles