تعود قصة كيفية تحليل محلل ثابت PVS-Studio ورمز Haiku OS إلى عام 2015. لقد كانت تجربة مثيرة وتجربة مفيدة لفرق كلا المشروعين. لماذا التجربة؟ في تلك اللحظة ، لم يكن لدينا محلل لنظام Linux ولن يكون لدينا لمدة عام ونصف العام. على أي حال ، تمت مكافأة جهود المتحمسين من فريقنا: لقد اجتمعنا مع مطوري Haiku ورفعنا من جودة الكود ، وسعنا من قاعدة الأخطاء الخاصة بنا مع الأخطاء النادرة التي أدلى بها المطورون وصقلنا المحلل. الآن يمكنك التحقق من كود هايكو لمعرفة الأخطاء بسهولة وبسرعة.
مقدمة
قابل الشخصيات الرئيسية في قصتنا -
Haiku مع شفرة مفتوحة المصدر ومحلل ثابت
PVS-Studio لـ C و C ++ و C # و Java. عندما كان لدينا حفر في تحليل المشروع قبل 4.5 سنوات ، كان علينا التعامل فقط مع ملف التحليل القابل للتنفيذ المترجمة. تم الحصول على جميع البنية التحتية لتحليل معلمات برنامج التحويل البرمجي ، وتشغيل معالج مسبق ، وموازاة التحليل وما إلى ذلك من الأداة المساعدة
Compiler Monitoring UI ، المكتوبة بلغة C #. تم نقل هذه الأداة المساعدة في أجزاء إلى النظام الأساسي Mono ليتم تشغيلها في Linux. تم بناء مشروع Haiku باستخدام برنامج التحويل البرمجي المتداخل تحت أنظمة تشغيل مختلفة ، باستثناء Windows. مرة أخرى ، أود أن أذكر الراحة والكمال الوثائق المتعلقة بناء هايكو. كما أود أن أشكر مطوري هايكو لمساعدتهم في بناء المشروع.
من الأسهل بكثير إجراء التحليل الآن. فيما يلي قائمة بجميع أوامر بناء المشروع وتحليله:
cd /opt git clone https:
بالمناسبة ، تم تنفيذ تحليل المشروع في حاوية Docker. لقد أعددنا مؤخرًا وثائق جديدة حول هذا الموضوع:
تشغيل PVS-Studio في Docker . هذا يمكن أن يجعل من السهل للغاية بالنسبة لبعض الشركات لتطبيق تقنيات التحليل الثابت لمشاريعها.
متغيرات غير مهيأة
V614 متغير غير مهيأ 'rval' المستخدمة. fetch.c 1727
int auto_fetch(int argc, char *argv[]) { volatile int argpos; int rval;
لم يتم تهيئة متغير
rval عند الإعلان ، لذلك ستؤدي المقارنة مع القيمة الخالية إلى نتيجة غير محددة. في حالة فشل الظروف ، تصبح القيمة غير
المؤكدة لمتغير
rval قيمة
إرجاع لوظيفة
الإحضار التلقائي .
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;
في ما يلي حالة مماثلة لاستخدام المتغير غير المهيأ ، باستثناء أن
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); }
المحلل صحيح حقًا -
إذا كانت الفروع متطابقة وغير ذلك. ولكن أين هي وظائف
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; };
من الخطأ الشائع استخدام عامل
الحذف بدلاً من
الحذف []. من الأسهل ارتكاب خطأ عند كتابة الفصل ، حيث أن رمز المدمر غالبًا ما يكون بعيدًا عن مواقع الذاكرة. هنا ، يحرر المبرمج بشكل غير صحيح الذاكرة المخزنة بواسطة مؤشر
fOutBuffer في destructor.
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; }
يفتقر عامل تعيين overloaded إلى قيمة الإرجاع. في هذه الحالة ، سيعود المشغل قيمة عشوائية ، والتي يمكن أن تؤدي إلى أخطاء غريبة.
فيما يلي مشكلات مماثلة في أجزاء التعليمات البرمجية الأخرى من هذه الفئة:
- 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); }
تعليق المستخدم NOTREACHED لا يعني أي شيء هنا. تحتاج إلى إضافة تعليق توضيحي للوظائف كأنها تدور من أجل كتابة تعليمات برمجية صحيحة لهذه السيناريوهات. للقيام بذلك ، هناك سمات 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);
كائن
الجهاز isfreed بواسطة عامل
الحذف . إنه منطقي تمامًا لوظيفة
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 الكشف عن تسرب الذاكرة . في هذا المثال ، في حالة حدوث خطأ ، لن يتم تحرير الذاكرة. قد يظن أحدهم أنه في حالة وجود أخطاء ، يجب ألا تهتم بإصدار الذاكرة ، لأن البرنامج سوف يستمر. لكنها ليست كذلك دائما. من متطلبات العديد من البرامج معالجة الأخطاء بشكل صحيح ومتابعة العمل.
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 قبل التحقق منها.
يسود تشخيص
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; .... }
مثال مماثل. يتم
إلغاء تحديد مؤشر
ملغ عدة أسطر في وقت أبكر مما هو محدد للتأكد من أنه لاغٍ. هناك الكثير من الأماكن المماثلة في المشروع. في بعض المقتطفات ، يكون استخدام المؤشر وفحصه بعيدًا عن بعضهم البعض ، لذلك في هذه المقالة ستجد فقط مثالين من هذه الأمثلة. المطورون مدعوون للتحقق من الأمثلة الأخرى في تقرير المحلل الكامل.
متنوع
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 ليس لديها مثل هذه المشكلة. ولكن عليك أن تمر عبر الأوتار ، تنتهي بـ null terminal بحيث تعمل بشكل صحيح.
فيما يلي قائمة كاملة بالأماكن الخطرة ذات الأوتار:
- V645 قد يؤدي استدعاء دالة "strncat" إلى تجاوز سعة المخزن المؤقت "الإخراج". يجب ألا تحتوي الحدود على حجم المخزن المؤقت ، ولكن يجب أن تحتوي على عدد من الأحرف. NamespaceDump.cpp 104
- V645 قد يؤدي استدعاء دالة "strncat" إلى تجاوز سعة المخزن المؤقت "الإخراج". يجب ألا تحتوي الحدود على حجم المخزن المؤقت ، ولكن يجب أن تحتوي على عدد من الأحرف. NamespaceDump.cpp 107
- V645 قد يؤدي استدعاء دالة "strncat" إلى تجاوز سعة المخزن المؤقت "الإخراج". يجب ألا تحتوي الحدود على حجم المخزن المؤقت ، ولكن يجب أن تحتوي على عدد من الأحرف. NamespaceDump.cpp 110
- V645 قد يؤدي استدعاء دالة "strncat" إلى تجاوز سعة المخزن المؤقت "الإخراج". يجب ألا تحتوي الحدود على حجم المخزن المؤقت ، ولكن يجب أن تحتوي على عدد من الأحرف. NamespaceDump.cpp 113
- V645 قد يؤدي استدعاء دالة "strncat" إلى تجاوز سعة المخزن المؤقت "الإخراج". يجب ألا تحتوي الحدود على حجم المخزن المؤقت ، ولكن يجب أن تحتوي على عدد من الأحرف. NamespaceDump.cpp 118
- V645 قد يؤدي استدعاء دالة "strncat" إلى تجاوز سعة المخزن المؤقت "الإخراج". يجب ألا تحتوي الحدود على حجم المخزن المؤقت ، ولكن يجب أن تحتوي على عدد من الأحرف. NamespaceDump.cpp 119
- V645 قد يؤدي استدعاء دالة "strncat" إلى تجاوز سعة المخزن المؤقت "الإخراج". يجب ألا تحتوي الحدود على حجم المخزن المؤقت ، ولكن يجب أن تحتوي على عدد من الأحرف. NamespaceDump.cpp 120
- V645 قد يؤدي استدعاء دالة "strncat" إلى تجاوز سعة المخزن المؤقت "الإخراج". يجب ألا تحتوي الحدود على حجم المخزن المؤقت ، ولكن يجب أن تحتوي على عدد من الأحرف. NamespaceDump.cpp 123
- V645 قد يؤدي استدعاء دالة "strncat" إلى تجاوز سعة المخزن المؤقت "الإخراج". يجب ألا تحتوي الحدود على حجم المخزن المؤقت ، ولكن يجب أن تحتوي على عدد من الأحرف. NamespaceDump.cpp 126
- V645 قد يؤدي استدعاء دالة "strncat" إلى تجاوز سعة المخزن المؤقت "الإخراج". يجب ألا تحتوي الحدود على حجم المخزن المؤقت ، ولكن يجب أن تحتوي على عدد من الأحرف. NamespaceDump.cpp 129
- V645 قد يؤدي استدعاء دالة "strncat" إلى تجاوز سعة المخزن المؤقت "الإخراج". يجب ألا تحتوي الحدود على حجم المخزن المؤقت ، ولكن يجب أن تحتوي على عدد من الأحرف. NamespaceDump.cpp 132
- V645 قد يؤدي استدعاء دالة "strncat" إلى تجاوز سعة المخزن المؤقت "الإخراج". يجب ألا تحتوي الحدود على حجم المخزن المؤقت ، ولكن يجب أن تحتوي على عدد من الأحرف. NamespaceDump.cpp 135
- V645 قد يؤدي استدعاء دالة "strncat" إلى تجاوز سعة المخزن المؤقت "الإخراج". يجب ألا تحتوي الحدود على حجم المخزن المؤقت ، ولكن يجب أن تحتوي على عدد من الأحرف. NamespaceDump.cpp 138
- V645 قد يؤدي استدعاء دالة "strncat" إلى تجاوز سعة المخزن المؤقت "الإخراج". يجب ألا تحتوي الحدود على حجم المخزن المؤقت ، ولكن يجب أن تحتوي على عدد من الأحرف. NamespaceDump.cpp 141
- V645 قد يؤدي استدعاء دالة "strncat" إلى تجاوز سعة المخزن المؤقت "الإخراج". يجب ألا تحتوي الحدود على حجم المخزن المؤقت ، ولكن يجب أن تحتوي على عدد من الأحرف. NamespaceDump.cpp 144
- V645 قد يؤدي استدعاء دالة "strncat" إلى تجاوز سعة المخزن المؤقت 'features_string'. يجب ألا تحتوي الحدود على حجم المخزن المؤقت ، ولكن يجب أن تحتوي على عدد من الأحرف. VirtioDevice.cpp 283
- V645 قد يؤدي استدعاء دالة "strncat" إلى تجاوز سعة المخزن المؤقت 'features_string'. يجب ألا تحتوي الحدود على حجم المخزن المؤقت ، ولكن يجب أن تحتوي على عدد من الأحرف. 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 ، لكن الجولة الأخيرة كانت قبل عامين تقريبًا. يجب أن يكون هذا مزعج لمستخدمي هايكو. على الرغم من أن التحليل قد تم تهيئته في عام 2014 باستخدام Coverity ، إلا أنه لم يمنعنا من كتابة مقالتين طويلتين حول مراجعة الأخطاء في عام 2015 (
الجزء 1 ،
الجزء 2 )
آخر مراجعة Haiku من الأخطاء سيصدر قريبا لأولئك الذين قرأوا هذا المنصب حتى النهاية. سيتم إرسال تقرير المحلل الكامل إلى المطورين قبل نشر مراجعة الأخطاء هذه ، لذلك قد يتم إصلاح بعض الأخطاء بحلول وقت قراءتك لهذا. لتمضية الوقت بين المقالات ، أقترح تنزيل
وتجربة PVS-Studio لمشروعك.
هل تريد أن تجرب هايكو ولديك أسئلة؟ يدعوك مطورو Haiku إلى
قناة التلغراف .