
مرحبا! على الرغم من أن موسم المؤتمرات 2019 لم ينته بعد ، إلا أننا نود التحدث عن تحديات اكتشاف الأخطاء التي قدمناها للزائرين في جناحنا خلال المؤتمرات السابقة. بدءًا من خريف عام 2019 ، بدأنا في طرح مجموعة جديدة من التحديات ، حتى نتمكن الآن من الكشف عن حلول للمهام السابقة لعام 2018 والنصف الأول من عام 2019 - بعد كل شيء ، جاء العديد منهم من مقالات سبق نشرها ، وكان لدينا رابط أو رمز الاستجابة السريعة مع معلومات حول المقالات المعنية المطبوعة على منشورات التحدي لدينا.
إذا حضرت الأحداث التي شاركنا فيها مع كشك ، فمن المحتمل أنك رأيت أو حتى حاولت حل بعض تحدياتنا. هذه مقتطفات من الكود من مشاريع حقيقية مفتوحة المصدر مكتوبة بلغة C أو C ++ أو C # أو Java. يحتوي كل مقتطف على خطأ ، ويواجه الضيوف صعوبة في محاولة العثور عليه. يكافأ الحل الناجح (أو المشاركة ببساطة في مناقشة الخطأ) على جائزة: حالة سطح المكتب الملتوية دوامة ، وسلسلة المفاتيح ، وما شابه ذلك:
تريد بعض جدا؟ ثم مرحبًا بك في معرضنا في الأحداث القادمة.
بالمناسبة ، في مقالتي "
وقت المؤتمر! ملخص عام 2018 " و "
المؤتمرات. المجاميع الفرعية للنصف الأول من عام 2019 " ، نشارك تجربتنا في المشاركة في الأحداث التي عقدت في وقت سابق من هذا العام وعام 2018.
حسنًا ، دعنا نلعب لعبة "Find the bug". أولاً ، سوف نلقي نظرة على التحديات السابقة لعام 2018 ، مصنفة حسب اللغة.
2018
C ++
علة الكروم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]; } }
حلربما كان هذا الخطأ الذي تم العثور عليه في Chromium هو التحدي "الطويل الأمد" ؛ كنا نقدمه طوال الطريق حتى عام 2018 وأدرجناه في العديد من العروض التقديمية أيضًا.
if (time.month == 2 && IsLeapYear(time.year)) { return time.month <= kDaysInMonth[time.month] + 1;
يحتوي نص
آخر كتلة
If-else على أخطاء في عبارات الإرجاع: تم كتابة
time.month بطريق الخطأ للمرة الثانية بدلاً من
time.day . يجعل هذا الخطأ وظيفة الإرجاع
صحيحة طوال الوقت. تمت مناقشة الخطأ بالتفصيل في المقالة "
31 فبراير " وهو مثال رائع على الأخطاء التي لم يتم رصدها بسهولة من خلال مراجعة التعليمات البرمجية. هذه الحالة هي أيضا مظاهرة جيدة لكيفية استخدام تدفق البيانات تحليل.
علة محرك غير واقعي 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) { .... }
حلأول ما يجب
ملاحظته هنا هو أن الوسيطة الأخيرة
للدالة VertInfluencedByActiveBone () لها قيمة افتراضية ولا يلزم تحديدها. الآن انظر إلى
if block في شكل مبسط:
if (!foo(....) && !foo(....) && !foo(....) & arg)
علة هو الآن واضحة للعيان. بسبب الخطأ المطبعي ، يتم تنفيذ الاستدعاء الثالث
للدالة VertInfluencedByActiveBone () بثلاث وسيطات بدلاً من أربعة ، مع قيمة الإرجاع ثم المشاركة في
& العملية (bitwise AND: المعامل الأيسر هو قيمة type
bool التي يتم إرجاعها بواسطة
VertInfluencedByActiveBone () ) ، والمعامل الصحيح هو المتغير الصحيح
BoneIndex3 ). الرمز لا يزال compilable. هذه هي النسخة الثابتة (تمت إضافة فاصلة ، انتقل قوس الإغلاق إلى نهاية التعبير):
if(!VertInfluencedByActiveBone( Owner, SourceComponent, VertIndex[0], &BoneIndex1) && !VertInfluencedByActiveBone( Owner, SourceComponent, VertIndex[1], &BoneIndex2) && !VertInfluencedByActiveBone( Owner, SourceComponent, VertIndex[2], &BoneIndex3))
تم ذكر هذا الخطأ في الأصل في مقال "
فحص طال انتظاره من محرك غير واقعي 4 " ، حيث كان يحمل عنوان "الخطأ الجميل" ، وأنا أتفق معه تمامًا.
البق الروبوت void TagMonitor::parseTagsToMonitor(String8 tagNames) { std::lock_guard<std::mutex> lock(mMonitorMutex);
حلكان للمبرمج افتراضات خاطئة حول أسبقية العمليات في حالة الكتلة
if . هذا الرمز لا يعمل كما هو متوقع:
if (ssize_t idx = (tagNames.find("3a") != -1))
سيتم تعيين القيمة 0 أو 1 لمتغير
idx ، وما إذا كان الشرط صحيحًا أو خاطئًا يعتمد على هذه القيمة ، وهذا خطأ. هذه هي النسخة الثابتة:
ssize_t idx = tagNames.find("3a"); if (idx != -1)
تم ذكر هذا الخطأ في المقالة "
لقد تحققنا من كود مصدر Android بواسطة PVS-Studio ، أو لا يوجد شيء مثالي ."
إليك تحد آخر غير تافه مع خلل في 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)); }
حلالمشكلة هي في
(د >> 24) + 1 التعبير.
أراد المبرمج التحقق من أن أهم 8 بتات من المتغير
d تم ضبطها على 1 ولكن ليس كلها في وقت واحد. بمعنى آخر ، أرادوا التحقق من أن البايت الأكثر أهمية يخزن أي قيمة باستثناء 0x00 و 0xFF. أولاً ، يتحقق المبرمج من البتات الأكثر أهمية لإلغاء استخدام تعبير (d >> 24). ثم يحولون البتات الثمانية الأكثر أهمية إلى البايت الأقل أهمية ، ويتوقعون أن تتكرر بتة الإشارة الأكثر أهمية في البتات الأخرى. أي إذا كانت قيمة المتغير d 0b11111111'00000000'00000000'00000000 ، فسوف تتحول إلى 0b11111111'1111111111'11111111'11111111 بعد التحول. عن طريق إضافة 1 إلى قيمة
int 0xFFFFFFFF ، يتوقع المبرمج الحصول على 0 (-1 + 1 = 0). وبالتالي ، يتم استخدام التعبير
((د >> 24) +1) للتحقق من أنه لم يتم تعيين كل البتات الثمانية الأكثر أهمية على 1.
ومع ذلك ، فإن أهم علامة بت لا تحصل بالضرورة على "فروق" عند إزاحتها. هذا ما يقوله المعيار: "قيمة E1 >> E2 هي مواضع E2 ذات تحول يميني E1. إذا كان E1 يحتوي على نوع غير موقَّع أو إذا كان E1 يحتوي على نوع موقَّع وقيمة غير سالبة ، فإن قيمة النتيجة هي جزء لا يتجزأ من حاصل E1 / 2 ^ E2.
إذا كان E1 يحتوي على نوع موقّع وقيمة سالبة ، فإن القيمة الناتجة معرفة بالتنفيذ . "
لذلك ، هذا مثال على السلوك المعرفة بالتنفيذ. تعتمد كيفية عمل هذا الرمز بالضبط على بنية وحدة المعالجة المركزية وتطبيق برنامج التحويل البرمجي. قد تنتهي البتات الأكثر أهمية كأصفار بعد النوبة ،
وسيظهر تعبير
((د >> 24) +1) دائمًا قيمة غير القيمة 0 ، أي القيمة الحقيقية دائمًا.
هذا ، في الواقع ، تحد غير تافه. مثل الخطأ السابق ، تمت مناقشة هذا الخطأ في الأصل في المقالة "
لقد فحصنا شفرة مصدر Android بواسطة PVS-Studio ، أو Nothing is Perfect ".
2019
C ++
"كل ذلك خطأ دول مجلس التعاون الخليجي" 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; }
مبرمج اللوم مترجم GCC 8 عن الخطأ. هل هو حقا خطأ دول مجلس التعاون الخليجي؟
حلتُرجع الدالة القيم السالبة لأن المترجم لا يُنشئ رمزًا لـ bitwise AND (&). الخطأ له علاقة مع سلوك غير محدد. يلاحظ المحول البرمجي أن المتغير
r يستخدم لحساب وتخزين مبلغ ، مع وجود قيم موجبة فقط. يجب ألا يتجاوز المتغير
r لأن ذلك سيكون سلوكًا غير محدد ، وهو أمر لا يرتبط به المترجم على الإطلاق.
لذلك يخلص إلى أن منذ ص لا يمكن أن يكون لها قيمة سلبية في نهاية الحلقة، ص تشغيل و0x7fffffff، الذي مسح بت تسجيل، لا لزوم لها، لذلك ببساطة يقول الدالة لإرجاع قيمة ص.
تم وصف هذا الخطأ في المقالة "
PVS-Studio 6.26 Released ".
علة كيو تي 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; }
حليتم التعامل مع مؤشر
mobj بطريقة غير آمنة: تم
إلغاء التسجيل أولاً ، ثم تم التحقق منه. كلاسيكي.
تم ذكر الخلل في المقالة "
فحص ثالث من Qt 5 مع PVS-Studio ".
C #
الخلل في 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}\" "); }
حلقد يحدث dereference خالية من متغير
القيمة عند تقييم
تعبير value.Equals (defaultValue) . سيحدث هذا عندما تكون قيم المتغيرات مثل
defaultValue! = خالية وقيمة == فارغة .
هذا الخطأ من المقال "
ما الأخطاء الكامنة في رمز Infer.NET؟ "
علة 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);
ما سوف إخراج البرنامج في وحدة التحكم؟ ما هو الخطأ في فئة
FastString ؟
حلسيقوم البرنامج بإخراج القيمة 32. والسبب هو أن هناك اسم خطأ إملائي للمتغير الذي تم تمريره إلى الطريقة
الأولية في المُنشئ:
public FastString(int iniCapacity){ Init(initCapacity); }
لن يتم استخدام المعلمة
المنشئ iniCapacity ؛ ما يحصل مرت بدلا من ذلك هو
initCapacity المستمر.
تمت مناقشة الخطأ في المقال "
أسرع التقارير في الغرب المتوحش - وحفنة من الأخطاء ... "
علة روكلين 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; }
حلdereference فارغة المحتملة
الحالية في التعبير الحالي.
FullSpan.Contains (....) . يمكن تعيين المتغير
الحالي قيمة فارغة نتيجة استدعاء الأسلوب
nodeOrToken.AsNode () .
هذا الخطأ هو من المادة "
التحقق من كود المصدر Roslyn ".
علة الوحدة .... staticFields = packedSnapshot.typeDescriptions .Where(t => t.staticFieldBytes != null & t.staticFieldBytes.Length > 0) .Select(t => UnpackStaticFields(t)) .ToArray() ....
حلخطأ مطبعي: يتم استخدام المشغل
& بدلاً من
&& . ينتج عن هذا تنفيذ
اختبار t.staticFieldBytes.Length> 0 طوال الوقت ، حتى إذا كان المتغير
t.staticFieldBytes خاليًا ، مما يؤدي بدوره إلى
إلغاء مرجع فارغ.
تم عرض هذا الخطأ في الأصل في المقالة "
مناقشة الأخطاء في مكونات Unity3D المفتوحة المصدر ".
جافا
خطأ IntelliJ IDEA private static boolean checkSentenceCapitalization(@NotNull String value) { List<String> words = StringUtil.split(value, " "); .... int capitalized = 1; .... return capitalized / words.size() < 0.2;
لماذا يحسب البرنامج بشكل غير صحيح عدد الكلمات الكبيرة؟
حلمن المتوقع أن تُرجع الدالة
صواب إذا كان عدد الكلمات المكتوبة بالأحرف الكبيرة أقل من 20٪. لكن عملية التحقق لا تعمل بسبب تقسيم عدد صحيح ، والذي يتم تقييمه فقط إلى 0 أو 1. سوف ترجع الدالة
false فقط إذا تم تكبير كل الكلمات. خلاف ذلك ، سوف يؤدي القسمة إلى 0 وستعود الدالة إلى
حقيقة .
علة من هذا المقال "
من PVS استوديو للجافا ".
البقع علة 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"); .... }
ما هو الخطأ في البحث عن علامة XML؟
حلشرط
العد <4 سيكون دائمًا صحيحًا لأن
العدد المتغير لا يتم زيادته داخل الحلقة. كان من المفترض أن يتم البحث عن علامة xml في الأسطر الأربعة الأولى من الملف ، ولكن بسبب نقص الزيادة ، سيقوم البرنامج بقراءة الملف بالكامل.
مثل الخطأ السابق ، تم وصف هذا الخطأ في المقالة "
PVS-Studio for Java ".
هذا كل شيء لهذا اليوم. تعال وانظر لنا في الأحداث القادمة - ابحث عن وحيد القرن. سنقدم تحديات جديدة مثيرة للاهتمام ، وبالطبع نقدم جوائز. اراك