
تحية! على الرغم من حقيقة أن موسم المؤتمرات 2019 لا يزال على قدم وساق ، نود أن نناقش المهام التي عرضت على زوار جناحنا في وقت سابق. بدأنا خريف عام 2019 بمجموعة جديدة من المهام ، لذلك من الممكن بالفعل نشر حل المشكلات القديمة لعام 2018 ، وكذلك النصف الأول من عام 2019. علاوة على ذلك ، تم أخذ الكثير منها من المقالات المنشورة مسبقًا ، وتحتوي منشورات المهام على رابط أو رمز الاستجابة السريعة مع معلومات حول المقال.
إذا حضرت مؤتمرات حيث وقفنا مع موقف ، فربما تكون قد رأيت أو حتى حل بعض مشكلاتنا. هذه هي دائمًا مقتطفات برمجية من مشاريع حقيقية مفتوحة المصدر بلغات برمجة C أو C ++ أو C # أو Java. تحتوي الشفرة على أخطاء نقترح على الزوار البحث عنها. بالنسبة للحل (أو مجرد مناقشة للخطأ) ، فإننا نعطيه جوائز - الحالات على سطح المكتب ، وخواتم المفاتيح ، وما إلى ذلك:
هل تريد نفس الشيء؟ تعال إلى جناحنا في المؤتمرات القادمة.
بالمناسبة ، تحتوي المقالات "
وقت المؤتمر! تلخيص نتائج 2018 " و "
المؤتمرات. النتائج المؤقتة للنصف الأول من عام 2019 " على وصف لنشاطنا في المؤتمرات هذا العام الماضي.
لذلك ، دعونا نبدأ لعبة "البحث عن خطأ في الكود". أولاً ، ضع في اعتبارك المهام الأقدم لعام 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.day ، حدد المبرمج مرتين عن طريق الخطأ
time.month . وقد أدى ذلك إلى حقيقة أن الحقيقة سوف تعاد دائمًا. تم وصف الخطأ بالتفصيل في المقالة "
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 (!foo(....) && !foo(....) && !foo(....) & arg)
من الواضح الآن أنه قد ارتكب خطأ. نظرًا
لخطأ مطبعي ، يتم إجراء المكالمة الثالثة
للدالة VertInfluencedByActiveBone () بثلاث وسيطات بدلاً من أربع وسيطات ، ويتم تطبيق
& عامل التشغيل على نتيجة هذه الاستدعاء (bitwise AND ، على اليسار هي نتيجة
دالة VertInfluencedByActiveBone () من نوع
bool ، على اليمين هي عدد صحيح
BoneIndex3 المتغير. يتم تجميع الرمز. نسخة مصححة من الكود (أضيفت بفاصلة ، تم نقل قوس الإغلاق إلى مكان آخر):
if(!VertInfluencedByActiveBone( Owner, SourceComponent, VertIndex[0], &BoneIndex1) && !VertInfluencedByActiveBone( Owner, SourceComponent, VertIndex[1], &BoneIndex2) && !VertInfluencedByActiveBone( Owner, SourceComponent, VertIndex[2], &BoneIndex3))
الخطأ الذي تم وصفه في الأصل في المقالة "
التحقق الذي طال انتظاره من Unreal Engine 4 ". المقال بعنوان "أجمل الأخطاء الموجودة" في المقال. وأنا أتفق مع هذا البيان.
البق الروبوت void TagMonitor::parseTagsToMonitor(String8 tagNames) { std::lock_guard<std::mutex> lock(mMonitorMutex);
الجوابفي حالة الكتلة
if ، يتم خلط أولوية العمليات. الكود لا يعمل كمبرمج المقصود:
if (ssize_t idx = (tagNames.find("3a") != -1))
سيتلقى متغير
idx القيم 0 أو 1 ، وسيعتمد تحقيق الشرط على هذه القيمة ، وهذا خطأ. النسخة الصحيحة من الكود:
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 تحتوي على وحدات ، لكن ليس كل البتات دفعة واحدة. بمعنى آخر ، أراد مبرمج التحقق من أن أي قيمة أخرى غير 0x00 و 0xFF في البايت عالية. أولاً ، تحقق من أن البتات الأكثر أهمية غير صفرية عن طريق كتابة تعبير (د >> 24). ثم ينقل ارتفاع الثمانية بت إلى البايت المنخفض. في الوقت نفسه ، فإنه يحسب أن بت الأكثر أهمية علامة بت في جميع البتات الأخرى. وهذا يعني ، إذا كان المتغير d يساوي 0b11111111'00000000'00000000'00000000 ، ثم بعد التحول نحصل على القيمة 0b11111111'11111111'111111111111111111. إضافة 1 إلى القيمة 0xFFFFFFFF من النوع
int ، يخطط المبرمج للحصول على 0 (-1 + 1 = 0). وهكذا ، مع تعبير
((د >> 24) +1) ، يتحقق من أن جميع البتات الثمانية العالية لا تساوي 1.
ومع ذلك ، عند التحول ، فإن البتة الأكثر أهمية ليست بالضرورة "ملطخة". يقول المعيار: "قيمة E1 >> E2 هي مواضع E2 ذات إزاحة يمين E1. إذا كان E1 يحتوي على نوع غير موقَّع أو إذا كان E1 يحتوي على نوع موقَّع وقيمة غير سالبة ، فإن قيمة النتيجة هي جزء لا يتجزأ من حاصل E1 / 2 ^ E2.
إذا كان E1 يحتوي على نوع موقّع وقيمة سالبة ، فإن القيمة الناتجة معرفة بالتنفيذ . "
لذلك هذا مثال على السلوك المعرفة بالتنفيذ. تعتمد كيفية عمل هذا الرمز على بنية المعالج الدقيق وتطبيق برنامج التحويل البرمجي. بعد التحول ، قد تظهر الأصفار جيدًا في البتات الأكثر أهمية ، ومن ثم ستكون نتيجة التعبير
((د >> 24) +1) دائمًا مختلفة عن 0 ، أي أنها ستكون دائمًا قيمة حقيقية.
في الواقع ، مهمة صعبة. تم وصف هذا الخطأ ، مثل الخطأ السابق ، في المقالة "لقد
تحققنا من رموز مصدر Android باستخدام PVS-Studio ، أو لا يوجد أحد مثالي ".
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. هل هذا صحيح؟
الجوابترجع الدالة القيم السالبة. السبب هو أن المحول البرمجي لا يقوم بإنشاء رمز عامل التشغيل AND (&) bitwise. الخطأ بسبب سلوك غير محدد. المترجم يرى أن كمية معينة تعتبر في المتغير
r . في هذه الحالة ، يتم إضافة أرقام موجبة فقط. يجب ألا تحدث تجاوزات المتغير
r ، وإلا فإن هذا سلوك غير محدد لا يجب على المحول البرمجي مراعاته وأخذه في الاعتبار بأي طريقة. لذلك ، يعتقد المترجم أنه نظرًا لأنه لا يمكن أن تكون القيمة في المتغير
r بعد نهاية الدورة سالبة ، فإن العملية
r & 0x7fffffff لإعادة تعيين بت الإشارة غير ضرورية وإرجاع المحول البرمجي ببساطة قيمة المتغير
r من الوظيفة.
خطأ من المقال "
PVS-Studio 6.26 Release ".
علة كيو تي 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 #
الخلل في 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}\" "); }
الجوابفي
تعبير 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 إلى الأسلوب
Init .
تم وصف الخطأ في المقال "
أسرع التقارير في الغرب المتوحش. وحفنة من الأخطاء بالإضافة ... "
علة روكلين 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; }
الجوابالوصول ممكن عبر المرجع الفارغ
الحالي في التعبير
الحالي. FullSpan.Contains (....) . يمكن للمتغير
الحالي الحصول على قيمة فارغة نتيجة لتنفيذ الأسلوب
nodeOrToken.AsNode () .
خطأ من المقال "
التحقق من شفرة المصدر لروزلين ".
علة الوحدة .... staticFields = packedSnapshot.typeDescriptions .Where(t => t.staticFieldBytes != null & t.staticFieldBytes.Length > 0) .Select(t => UnpackStaticFields(t)) .ToArray() ....
الجوابخطأ مطبعي: بدلاً من عامل التشغيل
&& ، تم استخدام عامل التشغيل
& . يؤدي هذا إلى حقيقة أن التحقق
t.staticFieldBytes.Length> 0 يتم إجراؤه دائمًا ، حتى إذا كان المتغير
الفارغ هو
t.staticFieldBytes ، والذي بدوره سيؤدي إلى الوصول عبر مرجع فارغ.
تم عرض هذا الخطأ لأول مرة في المقالة "
تحليل الأخطاء في مكونات Open 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;
يُقترح تحديد سبب احتساب عدد الكلمات بحروف كبيرة بشكل غير صحيح.
الجوابيجب أن ترجع الدالة true إذا كان أقل من 20٪ من الكلمات يبدأ بحرف كبير. لكن عملية التحقق لا تعمل ، نظرًا لحدوث عدد صحيح ، وستكون النتيجة فقط هي القيم 0 أو 1. وستُرجع الدالة قيمة خاطئة فقط إذا كانت كل الكلمات تبدأ بحرف كبير. في حالات أخرى ، ستنتج القسمة 0 ، وستعود الوظيفة إلى true.
خطأ من المقال "
PVS-Studio for Java ".
البقع علة 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 ".
هذا كل شيء. نحن في انتظاركم في المؤتمرات القادمة. ابحث عن موقف يونيكورن. سوف نعطي الألغاز الجديدة المثيرة للاهتمام ، وبالطبع الجوائز. اراك قريبا!

إذا كنت ترغب في مشاركة هذه المقالة مع جمهور يتحدث الإنجليزية ، فالرجاء استخدام الرابط الخاص بترجمة: سيرجي خرينوف.
حلول لتحديات اكتشاف الأخطاء التي يقدمها فريق PVS-Studio في المؤتمرات في 2018-2019 .