تعود قصة اجتماع محلل ثابت PVS-Studio برمز نظام التشغيل Haiku إلى عام 2015. لقد كانت تجربة ممتعة وتجربة مفيدة لفرق كلا المشروعين. لماذا تجربة؟ لم يكن هناك محلل لنظام Linux بعد ذلك ولن يكون هناك سنة ونصف أخرى. ولكن تمت مكافأة عمل المتحمسين لفريقنا: التقينا بمطوري Haiku وحسننا جودة الكود ، وأعدنا تزويد قاعدة البيانات بالأخطاء المبرمجة النادرة وصقلنا المحلل. التحقق من رمز Haiku للأخطاء سريع وسهل الآن.
مقدمة
أبطال تاريخنا هم نظام التشغيل
Haiku مفتوح المصدر ومحلل ثابت
PVS-Studio لـ C و C ++ و C # و Java. عندما بدأنا منذ 4.5 سنوات في تحليل المشروع ، كان علينا فقط العمل مع الملف القابل للتنفيذ الخاص بالمحلل المترجم. البنية الأساسية بأكملها لتحليل معلمات الترجمة ، وبدء معالج مسبق ، وموازنة التحليل ، إلخ. مأخوذة من الأداة المساعدة
Compiler Monitoring UI في C # ، والتي تم نقلها في أجزاء إلى النظام الأساسي Mono لتشغيلها على Linux. تم بناء مشروع Haiku نفسه باستخدام برنامج التحويل البرمجي المتداخل تحت أنظمة تشغيل مختلفة ، باستثناء Windows. مرة أخرى ، أود أن أشير إلى راحة واكتمال وثائق تجميع Haiku ، كما أشكر مطوري Haiku على مساعدتهم في بناء المشروع.
الآن التحليل أسهل بكثير. تبدو قائمة جميع أوامر بناء المشروع وتحليله كما يلي:
cd /opt git clone https:
بالمناسبة ، تم تنفيذ تحليل المشروع في حاوية دوكر. في الآونة الأخيرة ، قمنا بإعداد وثائق جديدة حول هذا الموضوع:
تشغيل PVS-Studio في Docker . هذا يمكن أن يبسط إلى حد كبير استخدام تقنيات تحليل المشاريع الثابتة لبعض الشركات.
متغيرات غير مهيأة
V614 متغير غير مهيأ 'rval' المستخدمة. fetch.c 1727
int auto_fetch(int argc, char *argv[]) { volatile int argpos; int rval;
لم يتم تهيئة متغير
rval عند التصريح به ، لذا فإن مقارنته بقيمة فارغة ستؤدي إلى نتيجة غير محددة. في حالة فشل الظروف ، قد تصبح القيمة غير
المحددة للمتغير
rval هي قيمة
الإرجاع للدالة
auto_fetch .
V614 مؤشر غير مهيأ 'res' المستخدمة. أوامر. ج 2873
struct addrinfo { int ai_flags; int ai_family; int ai_socktype; int ai_protocol; socklen_t ai_addrlen; char *ai_canonname; struct sockaddr *ai_addr; struct addrinfo *ai_next; }; static int sourceroute(struct addrinfo *ai, char *arg, char **cpp, int *lenp, int *protop, int *optp) { static char buf[1024 + ALIGNBYTES]; char *cp, *cp2, *lsrp, *ep; struct sockaddr_in *_sin; #ifdef INET6 struct sockaddr_in6 *sin6; struct ip6_rthdr *rth; #endif struct addrinfo hints, *res;
حالة مماثلة لاستخدام متغير غير مهيأ ، هنا فقط هو
الدقة المؤشر غير مهيأ.
V506 يتم تخزين المؤشر إلى متغير محلي 'تطبيع' خارج نطاق هذا المتغير. سيصبح هذا المؤشر غير صالح. TextView.cpp 5596
void BTextView::_ApplyStyleRange(...., const BFont* font, ....) { if (font != NULL) { BFont normalized = *font; _NormalizeFont(&normalized); font = &normalized; } .... fStyles->SetStyleRange(fromOffset, toOffset, fText->Length(), mode, font, color); }
ربما ، مبرمج اللازمة لتطبيع الكائن من خلال متغير وسيطة. ولكن الآن ، في مؤشر
الخط ، تم
حفظ المؤشر إلى الكائن المؤقت
الطبيعي ، والذي سيتم إتلافه بعد مغادرة النطاق الذي تم إنشاء هذا الكائن المؤقت فيه.
V603 تم إنشاء الكائن ولكن لا يتم استخدامه. إذا كنت ترغب في استدعاء المنشئ ، فيجب استخدام "هذا-> BUnicodeChar :: BUnicodeChar (....)". UnicodeChar.cpp 27
int8 BUnicodeChar::Type(uint32 c) { BUnicodeChar(); return u_charType(c); }
هناك خطأ شائع جدًا بين مبرمجي C ++ وهو استخدام استدعاء المنشئ المفترض أن يقوم بتهيئة / صفر حقول الفئة. في هذه الحالة ، لا يوجد أي تعديل لحقول الفصل ، ولكن يتم إنشاء كائن جديد لم يتم تسميته من هذه الفئة ، والذي يتم إتلافه على الفور. لسوء الحظ ، هناك الكثير من هذه الأماكن:
- V603 تم إنشاء الكائن ولكن لا يتم استخدامه. إذا كنت ترغب في استدعاء المنشئ ، فيجب استخدام "هذا-> BUnicodeChar :: BUnicodeChar (....)". UnicodeChar.cpp 37
- V603 تم إنشاء الكائن ولكن لا يتم استخدامه. إذا كنت ترغب في استدعاء المنشئ ، فيجب استخدام "هذا-> BUnicodeChar :: BUnicodeChar (....)". UnicodeChar.cpp 49
- V603 تم إنشاء الكائن ولكن لا يتم استخدامه. إذا كنت ترغب في استدعاء المنشئ ، فيجب استخدام "هذا-> BUnicodeChar :: BUnicodeChar (....)". UnicodeChar.cpp 58
- V603 تم إنشاء الكائن ولكن لا يتم استخدامه. إذا كنت ترغب في استدعاء المنشئ ، فيجب استخدام "هذا-> BUnicodeChar :: BUnicodeChar (....)". UnicodeChar.cpp 67
- V603 تم إنشاء الكائن ولكن لا يتم استخدامه. إذا كنت ترغب في استدعاء المنشئ ، فيجب استخدام "هذا-> BUnicodeChar :: BUnicodeChar (....)". UnicodeChar.cpp 77
- V603 تم إنشاء الكائن ولكن لا يتم استخدامه. إذا كنت ترغب في استدعاء المنشئ ، فيجب استخدام "هذا-> BUnicodeChar :: BUnicodeChar (....)". UnicodeChar.cpp 89
- V603 تم إنشاء الكائن ولكن لا يتم استخدامه. إذا كنت ترغب في استدعاء المنشئ ، فيجب استخدام "هذا-> BUnicodeChar :: BUnicodeChar (....)". UnicodeChar.cpp 103
- V603 تم إنشاء الكائن ولكن لا يتم استخدامه. إذا كنت ترغب في استدعاء المنشئ ، فيجب استخدام "هذا-> BUnicodeChar :: BUnicodeChar (....)". UnicodeChar.cpp 115
- V603 تم إنشاء الكائن ولكن لا يتم استخدامه. إذا كنت ترغب في استدعاء المنشئ ، فيجب استخدام "هذا-> BUnicodeChar :: BUnicodeChar (....)". UnicodeChar.cpp 126
- V603 تم إنشاء الكائن ولكن لا يتم استخدامه. إذا كنت ترغب في استدعاء المنشئ ، فيجب استخدام "هذا-> BUnicodeChar :: BUnicodeChar (....)". UnicodeChar.cpp 142
- V603 تم إنشاء الكائن ولكن لا يتم استخدامه. إذا كنت ترغب في استدعاء المنشئ ، فيجب استخدام "هذا-> BUnicodeChar :: BUnicodeChar (....)". UnicodeChar.cpp 152
- V603 تم إنشاء الكائن ولكن لا يتم استخدامه. إذا كنت ترغب في استدعاء المنشئ ، فيجب استخدام "هذا-> BUnicodeChar :: BUnicodeChar (....)". UnicodeChar.cpp 163
- V603 تم إنشاء الكائن ولكن لا يتم استخدامه. إذا كنت ترغب في استدعاء المنشئ ، فيجب استخدام "هذا-> BUnicodeChar :: BUnicodeChar (....)". UnicodeChar.cpp 186
- V603 تم إنشاء الكائن ولكن لا يتم استخدامه. إذا كنت ترغب في استدعاء المنشئ ، فيجب استخدام "هذا-> BUnicodeChar :: BUnicodeChar (....)". UnicodeChar.cpp 196
- V603 تم إنشاء الكائن ولكن لا يتم استخدامه. إذا كنت ترغب في استدعاء المنشئ ، فيجب استخدام "هذا-> BUnicodeChar :: BUnicodeChar (....)". UnicodeChar.cpp 206
- V603 تم إنشاء الكائن ولكن لا يتم استخدامه. إذا كنت ترغب في استدعاء المنشئ ، فيجب استخدام "هذا-> BUnicodeChar :: BUnicodeChar (....)". UnicodeChar.cpp 214
- V603 تم إنشاء الكائن ولكن لا يتم استخدامه. إذا كنت ترغب في استدعاء المنشئ ، فيجب استخدام "هذا-> BUnicodeChar :: BUnicodeChar (....)". UnicodeChar.cpp 222
- V603 تم إنشاء الكائن ولكن لا يتم استخدامه. إذا كنت ترغب في استدعاء المنشئ ، فيجب استخدام "هذا-> BUnicodeChar :: BUnicodeChar (....)". UnicodeChar.cpp 230
V670 يتم استخدام عضو الفئة غير المهيأ 'fPatternHandler' لتهيئة العضو 'fInternal'. تذكر أن تتم تهيئة الأعضاء بترتيب إعلاناتهم داخل الفصل. Painter.cpp 184
Painter::Painter() : fInternal(fPatternHandler), .... fPatternHandler(), .... { .... }; class Painter { .... private: mutable PainterAggInterface fInternal;
مثال آخر على التهيئة غير الصحيحة. تتم تهيئة حقول الفصل بالترتيب الذي تم الإعلان عنه في الفصل نفسه. في هذا المثال ، سيتم تهيئة الحقل
fInternal أولاً باستخدام القيمة غير المهيأة
fPatternHandler .
المشبوهة # تعريف
V523 بيان "ثم" مكافئ لبيان "آخر". subr_gtaskqueue.c 191
#define TQ_LOCK(tq) \ do { \ if ((tq)->tq_spin) \ mtx_lock_spin(&(tq)->tq_mutex); \ else \ mtx_lock(&(tq)->tq_mutex); \ } while (0) #define TQ_ASSERT_LOCKED(tq) mtx_assert(&(tq)->tq_mutex, MA_OWNED) #define TQ_UNLOCK(tq) \ do { \ if ((tq)->tq_spin) \ mtx_unlock_spin(&(tq)->tq_mutex); \ else \ mtx_unlock(&(tq)->tq_mutex); \ } while (0) void grouptask_block(struct grouptask *grouptask) { .... TQ_LOCK(queue); gtask->ta_flags |= TASK_NOENQUEUE; gtaskqueue_drain_locked(queue, gtask); TQ_UNLOCK(queue); }
لا يبدو مقتطف الشفرة مشبوهًا حتى تنظر إلى نتيجة المعالج المسبق:
void grouptask_block(struct grouptask *grouptask) { .... do { if ((queue)->tq_spin) mtx_lock(&(queue)->tq_mutex); else mtx_lock(&(queue)->tq_mutex); } while (0); gtask->ta_flags |= 0x4; gtaskqueue_drain_locked(queue, gtask); do { if ((queue)->tq_spin) mtx_unlock(&(queue)->tq_mutex); else mtx_unlock(&(queue)->tq_mutex); } while (0); }
المحلل هو حق حق - فروع
if و
if متطابقة. ولكن أين
ذهبت وظائف
mtx_lock_spin و
mtx_unlock_spin ؟ يتم الإعلان عن وحدات الماكرو
TQ_LOCK و
TQ_UNLOCK ووظيفة
grouptask_block في نفس الملف وتقريبًا جنبًا إلى جنب ، ومع ذلك ، كان هناك بديل في مكان ما.
البحث عن محتويات الملفات وجد فقط
mutex.h مع المحتويات التالية:
#define mtx_lock_spin(x) mtx_lock(x) #define mtx_unlock_spin(x) mtx_unlock(x)
ما إذا كان هذا الاستبدال صحيحًا أم لا يستحق التحقق من مطوري المشروع. لقد راجعت المشروع على نظام Linux ، وبدا هذا الاستبدال مشبوهاً بالنسبة لي.
أخطاء مع وظيفة حرة
V575 يتم تمرير المؤشر الفارغ إلى وظيفة "حرة". تفقد الحجة الأولى. setmime.cpp 727
void MimeType::_PurgeProperties() { fShort.Truncate(0); fLong.Truncate(0); fPrefApp.Truncate(0); fPrefAppSig.Truncate(0); fSniffRule.Truncate(0); delete fSmallIcon; fSmallIcon = NULL; delete fBigIcon; fBigIcon = NULL; fVectorIcon = NULL;
يمكنك تمرير مؤشر فارغ إلى الوظيفة
الحرة ، لكن هذا الإدخال مشبوه بوضوح. لذلك ، وجد المحلل الأسطر المشوشة من التعليمات البرمجية. أولاً ، كان عليك تحرير الذاكرة باستخدام المؤشر
fVectorIcon ، وبعد ذلك فقط قم بتعيينها على
NULL .
V575 يتم تمرير المؤشر الفارغ إلى وظيفة "حرة". تفقد الحجة الأولى. driver_settings.cpp 461
static settings_handle * load_driver_settings_from_file(int file, const char *driverName) { .... handle = new_settings(text, driverName); if (handle != NULL) {
مثال آخر لتمرير مؤشر فارغ إلى الدالة
الحرة بشكل صريح. يمكن حذف هذا الخط ، لأن عند الاستلام الناجح للمؤشر ، يتم إنهاء الوظيفة.
V575 يتم تمرير المؤشر الفارغ إلى وظيفة "حرة". تفقد الحجة الأولى. PackageFileHeapWriter.cpp 166
void* _GetBuffer() { .... void* buffer = malloc(fBufferSize); if (buffer == NULL && !fBuffers.AddItem(buffer)) { free(buffer); throw std::bad_alloc(); } return buffer; }
هنا خطأ. بدلاً من عامل التشغيل && ، يجب استخدام عامل التشغيل ||. في هذه الحالة فقط ، سيتم طرح استثناء
std :: bad_alloc () إذا لم يكن من الممكن تخصيص ذاكرة باستخدام وظيفة
malloc .
أخطاء مع حذف المشغل
V611 تم تخصيص الذاكرة باستخدام عامل التشغيل "T []" الجديد ولكن تم إصدارها باستخدام عامل التشغيل "delete". النظر في فحص هذا الرمز. من الأفضل استخدام 'delete [] fMsg؛'. Err.cpp 65
class Err { public: .... private: char *fMsg; ssize_t fPos; }; void Err::Unset() { delete fMsg;
يُستخدم مؤشر
fMsg لتخصيص ذاكرة لتخزين صفيف من الأحرف ، ويستخدم عامل
الحذف لتحرير الذاكرة ، بدلاً من
حذف [] .
V611 تم تخصيص الذاكرة باستخدام عامل التشغيل "جديد" ولكن تم إصدارها باستخدام الدالة "الحرة". النظر في فحص منطق العملية وراء المتغير "wrapperPool". vm_page.cpp 3080
status_t vm_page_write_modified_page_range(....) { .... PageWriteWrapper* wrapperPool = new(malloc_flags(allocationFlags)) PageWriteWrapper[maxPages + 1]; PageWriteWrapper** wrappers = new(malloc_flags(allocationFlags)) PageWriteWrapper*[maxPages]; if (wrapperPool == NULL || wrappers == NULL) { free(wrapperPool);
هنا
malloc_flags هي الوظيفة التي تصنع
malloc . ومن ثم يقوم
الموضع الجديد بإنشاء الكائن هنا. وبما أنه
يتم تطبيق فئة
PageWriteWrapper على النحو التالي:
class PageWriteWrapper { public: PageWriteWrapper(); ~PageWriteWrapper(); void SetTo(vm_page* page); bool Done(status_t result); private: vm_page* fPage; struct VMCache* fCache; bool fIsActive; }; PageWriteWrapper::PageWriteWrapper() : fIsActive(false) { } PageWriteWrapper::~PageWriteWrapper() { if (fIsActive) panic("page write wrapper going out of scope but isn't completed"); }
ثم لن يتم استدعاء المدمرات الكائن من هذه الفئة بسبب استخدام الدالة الحرة لتحرير الذاكرة.
V611 تم تخصيص الذاكرة باستخدام عامل التشغيل "T []" الجديد ولكن تم إصدارها باستخدام عامل التشغيل "delete". النظر في فحص هذا الرمز. من الأفضل استخدام "delete [] fOutBuffer؛". خطوط الفحص: 26 ، 45. PCL6Rasterizer.h 26
class PCL6Rasterizer : public Rasterizer { public: .... ~PCL6Rasterizer() { delete fOutBuffer; fOutBuffer = NULL; } .... virtual void InitializeBuffer() { fOutBuffer = new uchar[fOutBufferSize]; } private: uchar* fOutBuffer; int fOutBufferSize; };
يعد استخدام عامل
الحذف بدلاً من
الحذف [] خطأ شائعًا للغاية. أسهل طريقة لارتكاب خطأ عند كتابة الفصل هي غالبًا ما يكون رمز المدمر بعيدًا عن مواقع تخصيص الذاكرة. هنا ، يحرر المبرمج بشكل غير صحيح الذاكرة في destructor بواسطة المؤشر
fOutBuffer .
V772 استدعاء عامل "حذف" لمؤشر باطل سيؤدي إلى سلوك غير محدد. Hashtable.cpp 207
void Hashtable::MakeEmpty(int8 keyMode,int8 valueMode) { .... for (entry = fTable[index]; entry; entry = next) { switch (keyMode) { case HASH_EMPTY_DELETE:
بالإضافة إلى الاختيار الخاطئ بين
حذف /
حذف [] ومجاني ، يمكنك إضافة سلوك غير محدد إلى البرنامج بطريقة أخرى - حاول مسح الذاكرة عن طريق مؤشر إلى نوع غير محدد
(باطل *) .
وظائف دون قيمة الإرجاع
V591 يجب أن ترجع الدالة Non-void قيمة. الإحالة
BReference& operator=(const BReference<const Type>& other) { fReference = other.fReference; }
لا يحتوي مشغل المهمة الذي تم تجاوزه على قيمة إرجاع كافية. في هذه الحالة ، سيعود المشغل قيمة عشوائية ، والتي يمكن أن تؤدي إلى أخطاء غريبة.
مشاكل مماثلة في مقتطفات شفرة أخرى من هذه الفئة:
- V591 يجب أن ترجع الدالة Non-void قيمة. الإحالة. h 233
- V591 يجب أن ترجع الدالة Non-void قيمة. الإحالة. h 239
V591 يجب أن ترجع الدالة Non-void قيمة. main.c 1010
void errx(int, const char *, ...) ; char * getoptionvalue(const char *name) { struct option *c; if (name == NULL) errx(1, "getoptionvalue() invoked with NULL name"); c = getoption(name); if (c != NULL) return (c->value); errx(1, "getoptionvalue() invoked with unknown option '%s'", name); }
تعليق مستخدم غير معروف هنا لا يعني شيئًا. لكتابة التعليمات البرمجية لمثل هذه السيناريوهات بشكل صحيح ، تحتاج إلى ترميز وظائف مثل noreturn. هناك سمات noreturn لهذا: القياسية والمترجم محددة. بادئ ذي بدء ، يتم أخذ هذه السمات في الاعتبار من قبل المترجمين لإنشاء الشفرة الصحيحة أو الإخطار ببعض أنواع الأخطاء بمساعدة التحذيرات. تأخذ أدوات التحليل الثابتة المختلفة أيضًا السمات في الاعتبار لتحسين جودة التحليل.
التعامل مع استثناء
V596 تم
تكوين العنصر ولكن لا يتم استخدامه. الكلمة المفتاحية "رمي" قد تكون مفقودة: throw ParseException (FOO)؛ Response.cpp 659
size_t Response::ExtractNumber(BDataIO& stream) { BString string = ExtractString(stream); const char* end; size_t number = strtoul(string.String(), (char**)&end, 10); if (end == NULL || end[0] != '\0') ParseException("Invalid number!"); return number; }
هنا ، يتم نسيان كلمة "
رمي" بالخطأ. وبالتالي ، لن يتم
طرح ParseException ، وسيتم ببساطة إتلاف كائن من هذه الفئة عندما يترك النطاق. بعد ذلك ستواصل الوظيفة عملها كما لو لم يحدث شيء ، كما لو أن الرقم الصحيح قد تم إدخاله.
V1022 تم طرح استثناء بواسطة المؤشر. فكر في رميها بالقيمة بدلاً من ذلك. gensyscallinfos.cpp 316
int main(int argc, char** argv) { try { return Main().Run(argc, argv); } catch (Exception& exception) {
اكتشف المحلل
IOException الذي ألقاه المؤشر. يؤدي رمي المؤشر إلى عدم اكتشاف الاستثناء ، لذلك يتم اكتشاف الاستثناء بالرجوع في النهاية. وأيضًا ، يؤدي استخدام المؤشر إلى إجبار المعترض على استدعاء عامل
الحذف لتدمير الكائن الذي تم إنشاؤه ، والذي لا يتم أيضًا.
مجالان آخران من مشاكل الكود:
- V1022 تم طرح استثناء بواسطة المؤشر. فكر في رميها بالقيمة بدلاً من ذلك. gensyscallinfos.cpp 347
- V1022 تم طرح استثناء بواسطة المؤشر. فكر في رميها بالقيمة بدلاً من ذلك. gensyscallinfos.cpp 413
السلامة الرسمية
V597 يمكن للمترجم حذف استدعاء دالة 'memset' ، والذي يستخدم لمسح كائن 'f_key'. يجب استخدام الدالة memset_s () لمسح البيانات الخاصة. dst_api.c 1018
#ifndef SAFE_FREE #define SAFE_FREE(a) \ do{if(a != NULL){memset(a,0, sizeof(*a)); free(a); a=NULL;}} while (0) .... #endif DST_KEY * dst_free_key(DST_KEY *f_key) { if (f_key == NULL) return (f_key); if (f_key->dk_func && f_key->dk_func->destroy) f_key->dk_KEY_struct = f_key->dk_func->destroy(f_key->dk_KEY_struct); else { EREPORT(("dst_free_key(): Unknown key alg %d\n", f_key->dk_alg)); } if (f_key->dk_KEY_struct) { free(f_key->dk_KEY_struct); f_key->dk_KEY_struct = NULL; } if (f_key->dk_key_name) SAFE_FREE(f_key->dk_key_name); SAFE_FREE(f_key); return (NULL); }
اكتشف المحلل رمزًا مشبوهًا مصممًا لتنظيف البيانات الخاصة بأمان. لسوء الحظ ، لا
يجعل الماكرو
SAFE_FREE ، الذي يتم توسيعه إلى
memset والمكالمات
المجانية وتعيين
NULL ، الرمز أكثر أمانًا ، لأنه تتم إزالة كل هذا بواسطة المحول البرمجي أثناء تحسين
O2 .
بالمناسبة ، هذا ليس مثل
CWE-14 : إزالة المحول البرمجي من التعليمات البرمجية لمسح المخازن المؤقتة.
القائمة الكاملة للأماكن التي لم يتم بالفعل مسح المخازن المؤقتة:
- V597 يمكن للمترجم حذف استدعاء دالة "memset" ، والذي يستخدم لمسح المخزن المؤقت 'encoded_block'. يجب استخدام الدالة memset_s () لمسح البيانات الخاصة. dst_api.c 446
- V597 يمكن للمترجم حذف استدعاء دالة 'memset' ، والذي يستخدم لمسح كائن 'key_st'. يجب استخدام الدالة memset_s () لمسح البيانات الخاصة. dst_api.c 685
- V597 يمكن للمترجم حذف استدعاء دالة "memset" ، والذي يستخدم لمسح 'in_buff' المخزن المؤقت. يجب استخدام الدالة memset_s () لمسح البيانات الخاصة. dst_api.c 916
- V597 يمكن للمترجم حذف استدعاء دالة "memset" ، والذي يستخدم لمسح كائن "ce". يجب استخدام الدالة memset_s () لمسح البيانات الخاصة. fs_cache.c 1078
مقارنات غير موقعة
V547 التعبير 'المتبقي <0' غير صحيح دائمًا. قيمة الكتابة غير الموقعة هي أبداً <0. DwarfFile.cpp 1947
status_t DwarfFile::_UnwindCallFrame(....) { .... uint64 remaining = lengthOffset + length - dataReader.Offset(); if (remaining < 0) return B_BAD_DATA; .... }
وجد المحلل مقارنة واضحة لمتغير غير موقّع مع قيم سالبة. ربما يجب عليك مقارنة المتغير
المتبقي بصفر فقط أو تطبيق فحص تجاوز السعة.
تعبير
V547 'النوم ((غير الموقَّع)) <0' دائمًا ما يكون خطأ. قيمة الكتابة غير الموقعة ليست أبداً <0. misc.cpp 56
status_t snooze(bigtime_t amount) { if (amount <= 0) return B_OK; int64 secs = amount / 1000000LL; int64 usecs = amount % 1000000LL; if (secs > 0) { if (sleep((unsigned)secs) < 0)
لفهم ماهية الخطأ ، دعنا ننتقل إلى تواقيع وظائف
النوم والاستخدام :
extern unsigned int sleep (unsigned int __seconds); extern int usleep (__useconds_t __useconds);
كما نرى ، ترجع الدالة
السكون قيمة نوع غير موقّع واستخدامها في الكود غير صحيح.
مؤشرات خطيرة
V774 تم استخدام مؤشر "الجهاز" بعد إصدار الذاكرة. xhci.cpp 1572
void XHCI::FreeDevice(Device *device) { uint8 slot = fPortSlots[device->HubPort()]; TRACE("FreeDevice() port %d slot %d\n", device->HubPort(), slot);
يتم مسح كائن
الجهاز بواسطة مشغل
الحذف . هذا إجراء منطقي لوظيفة تسمى
FreeDevice . ولكن ، لسبب ما ، لإطلاق موارد أخرى في التعليمات البرمجية ، هناك مرة أخرى نداء إلى كائن محذوف بالفعل.
مثل هذا الرمز خطير للغاية ويحدث في عدة أماكن أخرى:
- V774 تم استخدام مؤشر "الذات" بعد إصدار الذاكرة. TranslatorRoster.cpp 884
- V774 تم استخدام مؤشر "السلسلة" بعد تحرير الذاكرة. RemoteView.cpp 1269
- V774 تم استخدام مؤشر 'bs' بعد إصدار الذاكرة. mkntfs.c 4291
- V774 تم استخدام مؤشر 'bs' بعد إصدار الذاكرة. mkntfs.c 4308
- V774 تم استخدام مؤشر 'al' بعد إعادة تخصيص الذاكرة. inode.c 1155
V522 قد يتم إلغاء تحديد "بيانات" المؤشر الخالي. يتم تمرير المؤشر الفارغ إلى وظيفة "malo_hal_send_helper". تفقد الحجة الثالثة. خطوط التحقق: 350 ، 394. if_malohal.c 350
static int malo_hal_fwload_helper(struct malo_hal *mh, char *helper) { .... error = malo_hal_send_helper(mh, 0, NULL, 0, MALO_NOWAIT);
كشف تحليل Interprocedural عن موقف
يتم فيه تمرير
NULL إلى الدالة ثم يتم إلغاء تحديد مؤشر
البيانات بهذه القيمة في وظيفة
memcpy .
V773 تم إنهاء الوظيفة دون تحرير المؤشر 'inputFileFile'. تسرب الذاكرة ممكن. command_recompress.cpp 119
int command_recompress(int argc, const char* const* argv) { .... BFile* inputFileFile = new BFile; error = inputFileFile->SetTo(inputPackageFileName, O_RDONLY); if (error != B_OK) { fprintf(stderr, "Error: Failed to open input file \"%s\": %s\n", inputPackageFileName, strerror(error)); return 1; } inputFile = inputFileFile; .... }
يمكن PVS-Studio الكشف عن تسرب الذاكرة . لا يتم تحرير الذاكرة لـ
inputFileFile هنا في حالة وجود نوع من الخطأ. يعتقد شخص ما أنه في حالة وجود أخطاء ، لا يمكنك أن تهتم بتحرير الذاكرة - سيظل البرنامج منتهيًا. ولكن هذا ليس هو الحال دائما. تعامل بشكل صحيح مع الأخطاء واستمر في العمل - وهو مطلب للعديد من البرامج.
V595 تم استخدام مؤشر 'fReply' قبل أن يتم التحقق منه ضد nullptr. خطوط التحقق: 49 ، 52. ReplyBuilder.cpp 49
RPC::CallbackReply* ReplyBuilder::Reply() { fReply->Stream().InsertUInt(fStatusPosition, _HaikuErrorToNFS4(fStatus)); fReply->Stream().InsertUInt(fOpCountPosition, fOpCount); if (fReply == NULL || fReply->Stream().Error() == B_OK) return fReply; else return NULL; }
كم مرة يقوم مطورو مؤشرات dereference قبل التحقق منها. يعتبر برنامج Diagnostics
V595 دائمًا رائدًا في عدد التحذيرات في المشروع. الاستخدام الخطير لمؤشر
fReply في هذا الجزء من الكود.
V595 تم استخدام مؤشر 'mq' قبل أن يتم التحقق منه ضد nullptr. خطوط الفحص: 782 ، 786. oce_queue.c 782
static void oce_mq_free(struct oce_mq *mq) { POCE_SOFTC sc = (POCE_SOFTC) mq->parent; struct oce_mbx mbx; struct mbx_destroy_common_mq *fwcmd; if (!mq) return; .... }
مثال مماثل. يتم إلغاء تحديد مؤشر
mg قبل بضعة أسطر من التحقق من وجود قيمة فارغة. هناك العديد من الأماكن المماثلة في المشروع. في بعض الأماكن ، تتم إزالة استخدام المؤشرات والتحقق منها بعيدًا عن بعضها البعض ، لذلك يتم تضمين مثالين في المقالة. سيكون الباقي قادرًا على رؤية المطورين في تقرير المحلل الكامل.
أخطاء متنوعة
V645 قد يؤدي استدعاء دالة "strncat" إلى تجاوز سعة المخزن المؤقت "الإخراج". يجب ألا تحتوي الحدود على حجم المخزن المؤقت ، ولكن يجب أن تحتوي على عدد من الأحرف. NamespaceDump.cpp 101
static void dump_acpi_namespace(acpi_ns_device_info *device, char *root, int indenting) { char output[320]; char tabs[255] = ""; .... strlcat(tabs, "|--- ", sizeof(tabs)); .... while (....) { uint32 type = device->acpi->get_object_type(result); snprintf(output, sizeof(output), "%s%s", tabs, result + depth); switch(type) { case ACPI_TYPE_INTEGER: strncat(output, " INTEGER", sizeof(output)); break; case ACPI_TYPE_STRING: strncat(output, " STRING", sizeof(output)); break; .... } .... } .... }
الفرق بين
وظائف strlcat و
strncat ليس واضحًا تمامًا لأي شخص جديد في وصف هذه الوظائف. تأخذ الدالة
strlcat حجم المخزن المؤقت بأكمله كوسيطة ثالثة ،
وتأخذ الدالة
strncat حجم المساحة الخالية في المخزن المؤقت ، مما يتطلب حساب القيمة المطلوبة قبل استدعاء الوظيفة. لكن المطورين غالباً ما ينسون أو لا يعرفون ذلك. قد يؤدي تمرير وظيفة
strncat إلى حجم المخزن المؤقت بأكمله إلى تجاوز سعة المخزن المؤقت ، لأن ستعتبر الدالة هذه القيمة هي عدد الأحرف المسموح بنسخها.
لا تحتوي وظيفة
strlcat على هذه المشكلة ، ولكن لكي تعمل بشكل صحيح ، من الضروري تمرير السلاسل التي تنتهي بصفر المحطة الطرفية.
قائمة كاملة من الأماكن الخطرة مع خطوط:
- V645 قد يؤدي استدعاء دالة "strncat" إلى تجاوز سعة المخزن المؤقت "الإخراج". يجب ألا تحتوي الحدود على حجم المخزن المؤقت ، ولكن يجب أن تحتوي على عدد من الأحرف. NamespaceDump.cpp 104
- V645 قد يؤدي استدعاء دالة "strncat" إلى تجاوز سعة المخزن المؤقت "الإخراج". The bounds should not contain the size of the buffer, but a number of characters it can hold. NamespaceDump.cpp 107
- V645 The 'strncat' function call could lead to the 'output' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. NamespaceDump.cpp 110
- V645 The 'strncat' function call could lead to the 'output' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. NamespaceDump.cpp 113
- V645 The 'strncat' function call could lead to the 'output' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. NamespaceDump.cpp 118
- V645 The 'strncat' function call could lead to the 'output' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. NamespaceDump.cpp 119
- V645 The 'strncat' function call could lead to the 'output' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. NamespaceDump.cpp 120
- V645 The 'strncat' function call could lead to the 'output' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. NamespaceDump.cpp 123
- V645 The 'strncat' function call could lead to the 'output' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. NamespaceDump.cpp 126
- V645 The 'strncat' function call could lead to the 'output' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. NamespaceDump.cpp 129
- V645 The 'strncat' function call could lead to the 'output' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. NamespaceDump.cpp 132
- V645 The 'strncat' function call could lead to the 'output' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. NamespaceDump.cpp 135
- V645 The 'strncat' function call could lead to the 'output' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. NamespaceDump.cpp 138
- V645 The 'strncat' function call could lead to the 'output' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. NamespaceDump.cpp 141
- V645 The 'strncat' function call could lead to the 'output' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. NamespaceDump.cpp 144
- V645 The 'strncat' function call could lead to the 'features_string' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. VirtioDevice.cpp 283
- V645 The 'strncat' function call could lead to the 'features_string' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. VirtioDevice.cpp 284
- V645 قد يؤدي استدعاء دالة "strncat" إلى تجاوز سعة المخزن المؤقت 'features_string'. يجب ألا تحتوي الحدود على حجم المخزن المؤقت ، ولكن يجب أن تحتوي على عدد من الأحرف. VirtioDevice.cpp 285
V792 تقع وظيفة "SetDecoratorSettings" على يمين المشغل "|" سيتم استدعاء بغض النظر عن قيمة المعامل الأيسر. ربما ، من الأفضل استخدام '||'. DesktopListener.cpp 324 class DesktopListener : public DoublyLinkedListLinkImpl<DesktopListener> { public: .... virtual bool SetDecoratorSettings(Window* window, const BMessage& settings) = 0; .... }; bool DesktopObservable::SetDecoratorSettings(Window* window, const BMessage& settings) { if (fWeAreInvoking) return false; InvokeGuard invokeGuard(fWeAreInvoking); bool changed = false; for (DesktopListener* listener = fDesktopListenerList.First(); listener != NULL; listener = fDesktopListenerList.GetNext(listener)) changed = changed | listener->SetDecoratorSettings(window, settings); return changed; }
على الأرجح ، المشغلين "|" و "||". يؤدي هذا الخطأ إلى استدعاءات غير ضرورية لوظيفة SetDecoratorSettings .V627 النظر في فحص التعبير. وسيطة sizeof () هي الماكرو الذي يمتد إلى رقم. device.c 72 #define PCI_line_size 0x0c static status_t wb840_open(const char* name, uint32 flags, void** cookie) { .... data->wb_cachesize = gPci->read_pci_config(data->pciInfo->bus, data->pciInfo->device, data->pciInfo->function, PCI_line_size, sizeof(PCI_line_size)) & 0xff; .... }
يبدو أن تمرير القيمة 0x0c إلى عامل sizeof أمر مشبوه. ربما يجب عليك حساب حجم بعض الكائنات ، على سبيل المثال ، البيانات . V562 من الغريب مقارنة قيمة نوع منطقي بقيمة 18: 0x12 == IsProfessionalSpdif (). CEchoGals_mixer.cpp 533 typedef bool BOOL; virtual BOOL IsProfessionalSpdif() { ... } #define ECHOSTATUS_DSP_DEAD 0x12 ECHOSTATUS CEchoGals::ProcessMixerFunction(....) { .... if ( ECHOSTATUS_DSP_DEAD == IsProfessionalSpdif() )
ترجع الدالة IsProfessionalSpdif قيمة نوع منطقي ، بينما في حالة مقارنة نتيجة الدالة بالرقم 0x12 .استنتاج
فاتنا إطلاق أول إصدار تجريبي من Haiku في الخريف الماضي كانوا مشغولين في إطلاق PVS-Studio للغة Java. لكن طبيعة أخطاء المبرمجين تجعلهم لا يذهبون إلى أي مكان ما لم تبحث عنهم ولا يهتمون بجودة الكود على الإطلاق. حاول المطورون استخدام Coverity Scan ، لكن آخر عملية تحليل كانت منذ عامين تقريبًا. هذا يجب أن يزعج مستخدمي هايكو. على الرغم من أن التحليل باستخدام Coverity قد تم تهيئته في عام 2014 ، فإن هذا لم يمنعنا من كتابة مقالتين كبيرتين مع مراجعات الخطأ في عام 2015 ( الجزء 1 ، الجزء 2 ).أولئك الذين قرأوا حتى النهاية ينتظرون مراجعة أخرى لرمز هايكو لا تقل حجمها مع أنواع جديدة من الأخطاء. سيتم إرسال تقرير محلل كامل للمطورين قبل نشر مراجعات الكود ، لذلك يمكن إصلاح بعض الأخطاء. لتمضية الوقت بين المنشورات ، أقترح تنزيل وتجربة PVS-Studio في مشروعك.تريد أن تجرب هايكو ولديك أي أسئلة؟ يدعوك مطورو Haiku إلى
قناة التلغراف .

إذا كنت ترغب في مشاركة هذه المقالة مع جمهور يتحدث الإنجليزية ، فالرجاء استخدام الرابط الخاص بالترجمة: Svyatoslav Razmyslov.
كيف تطلق النار على قدمك في C و C ++. هايكو OS كتاب الطبخ