
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 .:
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 kromiumstatic 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]; } }
JawabannyaMungkin 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;
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) { .... }
JawabannyaHal 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);
JawabannyaDalam 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)); }
JawabannyaMasalahnya 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?
JawabannyaFungsi 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; }
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}\" "); }
JawabannyaDalam
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 ?
Jawabannya32 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; }
JawabannyaAkses 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() ....
JawabannyaKesalahan 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;
Diusulkan untuk menentukan mengapa jumlah kata dengan huruf kapital akan dihitung secara tidak benar.
JawabannyaFungsi 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.
JawabannyaKondisi
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 .