Perl 5: كيف أخفت وحدات الماكرو الأخطاء


لتكملة قائمة لغات البرمجة مفتوحة المصدر التي تم اختبارها باستخدام محلل الأكواد الثابتة PVS-Studio ، تم اختيار Perl 5. تتناول هذه المقالة الأخطاء والصعوبات في عرض نتائج التحليل. عدد وحدات الماكرو في الشفرة كبير جدًا لدرجة أنه يبدو أن الرمز غير مكتوب بلغة C ، ولكن في بعض لهجة غريبة منه. على الرغم من الصعوبات عند عرض الرمز ، تمكنت من جمع مشاكل مثيرة للاهتمام ، والتي سيتم مناقشتها في هذه المقالة.

مقدمة


لغة Perl هي لغة برمجة ديناميكية عالية المستوى يتم تفسيرها للأغراض العامة (لغة Perl هي عائلة من لغتي برمجة ديناميكية عالية المستوى للأغراض العامة ومفسرة وديناميكية). Perl 5 تم إصدارة عام 1994. بعد عقدين من الزمن ، يسبب كود C مع العديد من وحدات الماكرو العصبية للمبرمجين العصريين.

تم أخذ شفرة المصدر لـ Perl 5 من المستودع الرسمي (فرع blead ). للتحقق من المشروع ، استخدمنا محلل الشفرة الثابتة PVS-Studio . تم إجراء التحليل على نظام التشغيل Linux ، ولكن المحلل متاح أيضًا لنظامي التشغيل Windows و macOS.

لم يكن عرض نتائج التحليل مهمة سهلة. والحقيقة هي أن المحلل يتحقق من ملفات .i المعالجة مسبقًا ، حيث يتم بالفعل فتح جميع توجيهات المعالج المسبق ، ويقوم بإنشاء تحذيرات على الملفات التي تحتوي على التعليمات البرمجية المصدر. هذا هو السلوك الصحيح للمحلل ، لا تحتاج إلى تغيير أي شيء ، ولكن يتم إصدار العديد من التحذيرات لوحدات الماكرو! وخلف وحدات الماكرو رمز غير قابل للقراءة.

عامل الثلاثية لا يعمل بالطريقة التي تفكر بها


V502 ربما يكون عامل التشغيل '؟:' يعمل بطريقة مختلفة عما كان متوقعًا. عامل التشغيل '؟:' له أولوية أقل من عامل التشغيل '-'. توك ج 9494

STATIC char * S_scan_ident(pTHX_ char *s, char *dest, STRLEN destlen, I32 ck_uni) { .... if ((s <= PL_bufend - (is_utf8) ? UTF8SKIP(s) : 1) && VALID_LEN_ONE_IDENT(s, PL_bufend, is_utf8)) { .... } .... } 

لنبدأ المراجعة بخطأ جميل. يجب أن تكرر كل مراجعات الكود القليلة أن عامل التشغيل الثلاثي له أقل أولوية تقريبًا في الحوسبة.

خذ بعين الاعتبار الجزء مع الخطأ:

 s <= PL_bufend - (is_utf8) ? UTF8SKIP(s) : 1 

ترتيب العمليات التي يتوقعها المبرمج:
  1. ؟:
  2. -
  3. <=

ما يحدث حقًا:
  1. -
  2. <=
  3. ؟:

احتفظ بعلامة مع أولويات العمليات: " أولوية العمليات في C / C ++ ."

V502 ربما يكون عامل التشغيل '؟:' يعمل بطريقة مختلفة عما كان متوقعًا. عامل التشغيل '؟:' له أولوية أقل من عامل التشغيل '=='. re_exec.c 9193

 STATIC I32 S_regrepeat(pTHX_ regexp *prog, char **startposp, const regnode *p, regmatch_info *const reginfo, I32 max _pDEPTH) { .... assert(STR_LEN(p) == reginfo->is_utf8_pat ? UTF8SKIP(STRING(p)) : 1); .... } 

كود بسيط مع خطأ مشابه. ولكن إذا كنت لا تعرف أولويات العمليات ، فيمكنك ارتكاب خطأ في تعبير بأي حجم.

مكان آخر مع التأكيد:

  • V502 ربما يكون عامل التشغيل '؟:' يعمل بطريقة مختلفة عما كان متوقعًا. عامل التشغيل '؟:' له أولوية أقل من عامل التشغيل '=='. re_exec.c 9286

V502 ربما يكون عامل التشغيل '؟:' يعمل بطريقة مختلفة عما كان متوقعًا. عامل التشغيل '؟:' له أولوية أقل من عامل التشغيل "&&". pp_hot.c 3036

 PP(pp_match) { .... MgBYTEPOS_set(mg, TARG, truebase, RXp_OFFS(prog)[0].end); .... } 

وإليك تحذيرًا لماكرو ... لفهم ما يحدث هناك ، حتى تنفيذ الماكرو لن يساعد ، لأنه يستخدم بعض وحدات الماكرو!

لذلك ، أرفق جزءًا من الملف المعالج مسبقًا لهذا السطر من التعليمات البرمجية:

 (((targ)->sv_flags & 0x00000400) && (!((targ)->sv_flags & 0x00200000) || S_sv_only_taint_gmagic(targ)) ? (mg)->mg_len = ((prog->offs)[0].end), (mg)->mg_flags |= 0x40 : ((mg)->mg_len = (((targ)->sv_flags & 0x20000000) && !__builtin_expect(((((PL_curcop)->cop_hints + 0) & 0x00000008) ? (_Bool)1 :(_Bool)0),(0))) ? (ssize_t)Perl_utf8_length( (U8 *)(truebase), (U8 *)(truebase)+((prog->offs)[0].end)) : (ssize_t)((prog->offs)[0].end), (mg)->mg_flags &= ~0x40)); 

في مكان ما هنا ، شكك المحلل في الاستخدام الصحيح لعامل التشغيل الثلاثي (هناك 3 منهم) ، لكنني لم أجد القوة لفهم ما يجري في هذا الرمز. لقد رأينا بالفعل أن المطورين يرتكبون مثل هذه الأخطاء ، لذا يمكن أن يكون هناك احتمال كبير هنا.

ثلاثة استخدامات أخرى لهذا الماكرو:

  • V502 ربما يكون عامل التشغيل '؟:' يعمل بطريقة مختلفة عما كان متوقعًا. عامل التشغيل '؟:' له أولوية أقل من عامل التشغيل "&&". ص 324
  • V502 ربما يكون عامل التشغيل '؟:' يعمل بطريقة مختلفة عما كان متوقعًا. عامل التشغيل '؟:' له أولوية أقل من عامل التشغيل "&&". ريجكسيك ج 7335
  • V502 ربما يكون عامل التشغيل '؟:' يعمل بطريقة مختلفة عما كان متوقعًا. عامل التشغيل '؟:' له أولوية أقل من عامل التشغيل "&&". re_exec.c 7335

لاحظ الزملاء أندريه كاربوف. لقد تأملت في هذا الرمز لمدة 10 دقائق وأنا أميل إلى الاعتقاد بأنه لا يوجد خطأ. ولكن على أي حال ، من المؤلم للغاية قراءة مثل هذا الرمز ، ومن الأفضل عدم الكتابة بهذه الطريقة.

أخطاء في الظروف


V523 تعادل العبارة "then" العبارة "else". توك ج 12056

 static U8 * S_add_utf16_textfilter(pTHX_ U8 *const s, bool reversed) { .... SvCUR_set(PL_linestr, 0); if (FILTER_READ(0, PL_linestr, 0)) { SvUTF8_on(PL_linestr); } else { SvUTF8_on(PL_linestr); } PL_bufend = SvEND(PL_linestr); return (U8*)SvPVX(PL_linestr); } 

أعتقد أنه يمكنك الاستغناء عن فحص محتويات وحدات الماكرو للتأكد من وجود أجزاء تعليمات برمجية مكررة بشكل مريب.

V564 "|" يتم تطبيق عامل التشغيل على قيمة نوع منطقية. ربما نسيت تضمين الأقواس أو قصدت استخدام '||' عامل. مرجع سبق ذكره 11494

 OP * Perl_ck_rvconst(pTHX_ OP *o) { .... gv = gv_fetchsv(kidsv, o->op_type == OP_RV2CV && o->op_private & OPpMAY_RETURN_CONSTANT ? GV_NOEXPAND : iscv | !(kid->op_private & OPpCONST_ENTERED), iscv // <= ? SVt_PVCV : o->op_type == OP_RV2SV ? SVt_PV : o->op_type == OP_RV2AV ? SVt_PVAV : o->op_type == OP_RV2HV ? SVt_PVHV : SVt_PVGV); .... } 

كود غريب جدا. التعبير "iscv | ! (kid-> op_private & OPpCONST_ENTERED) "لا يتم استخدامها بأي شكل من الأشكال. من الواضح أن هناك خطأ مطبعي هنا. على سبيل المثال ، ربما يجب أن تكتب هنا:

 : iscv = !(kid->op_private & OPpCONST_ENTERED), iscv // <= 

تعبير V547 "RETVAL == 0" صحيح دائمًا. Typemap.c 710

 XS_EUPXS(XS_XS__Typemap_T_SYSRET_pass); XS_EUPXS(XS_XS__Typemap_T_SYSRET_pass) { dVAR; dXSARGS; if (items != 0) croak_xs_usage(cv, ""); { SysRet RETVAL; #line 370 "Typemap.xs" RETVAL = 0; #line 706 "Typemap.c" { SV * RETVALSV; RETVALSV = sv_newmortal(); if (RETVAL != -1) { // <= if (RETVAL == 0) // <= sv_setpvn(RETVALSV, "0 but true", 10); else sv_setiv(RETVALSV, (IV)RETVAL); } ST(0) = RETVALSV; } } XSRETURN(1); } 

يتم التحقق من متغير RETVAL مرتين على التوالي. علاوة على ذلك ، يمكن أن نرى من الكود أن هذا المتغير هو صفر دائمًا. ربما أرادوا في أحد الشرطين أو كلاهما التحقق من مؤشر RETVALSV ، لكنهم قاموا بعمل خطأ مطبعي.

رمي تحذيرات حول عامل sizeof


هناك عدة أنواع من قواعد التشخيص في المحلل تبحث عن أخطاء باستخدام عامل sizeof . في مشروع بيرل 5 ، أنتج اثنان من هذه التشخيصات ما مجموعه حوالي ألف تحذير. في هذه الحالة ، ليس المحلل هو المسؤول ، ولكن وحدات الماكرو.

V568 من الغريب أن حجة عامل sizeof () هي التعبير 'len + 1'. ج: 1084

 char * Perl_savepvn(pTHX_ const char *pv, I32 len) { .... Newx(newaddr,len+1,char); .... } 

يحتوي الرمز على الكثير من وحدات الماكرو المتشابهة. اخترت واحدة كمثال ، نحن مهتمون بحجة "len + 1".

يتم توسيع معالج ماكرو إلى التعليمات البرمجية التالية:

 (newaddr = ((void)(__builtin_expect(((((( sizeof(size_t) < sizeof(len+1) || sizeof(char) > ((size_t)1 << 8*(sizeof(size_t) - sizeof(len+1)))) ? (size_t)(len+1) : ((size_t)-1)/sizeof(char)) > ((size_t)-1)/sizeof(char))) ? (_Bool)1 : (_Bool)0),(0)) && (S_croak_memory_wrap(),0)), (char*)(Perl_safesysmalloc((size_t)((len+1)*sizeof(char)))))); 

يتم إصدار تحذير محلل للبنية (len +1) . والحقيقة هي أنه لا يتم إجراء حسابات في وسيطات عامل sizeof . يتم توسيع العديد من وحدات الماكرو إلى رمز مشابه. ربما يكون هذا رمزًا قديمًا لا يرغب فيه أحد في لمس أي شيء ، لكن المطورين الحاليين يواصلون استخدام وحدات الماكرو القديمة ، مما يشير إلى سلوكهم المختلف.

إلغاء الإشارة إلى مؤشرات فارغة


V522 قد يحدث إلغاء الإشارة للمؤشر "sv". ص 577

 OP * Perl_pp_formline(void) { .... SV *sv = ((void *)0); .... switch (*fpc++) { .... case 4: arg = *fpc++; f += arg; fieldsize = arg; if (mark < sp) sv = *++mark; else { sv = &(PL_sv_immortals[2]); Perl_ck_warner( (28 ), "...."); } .... break; case 5: { const char *s = item = ((((sv)->sv_flags & (....)) == 0x00000400) ? .... .... } .... } 

يتم أخذ جزء التعليمات البرمجية هذا بالكامل من الملف المعالج مسبقًا ، لأنه من المستحيل التحقق من وجود المشكلة من الملف المصدر ، مرة أخرى بسبب وحدات الماكرو.

تتم تهيئة مؤشر sv إلى الصفر عند الإعلان. وجد المحلل أنه عند تمرير أكثر من 5 في بيان التبديل ، يتم إلغاء الإشارة إلى هذا المؤشر ، والذي لم تتم تهيئته من قبل. يوجد تغيير في مؤشر sv في الفرع بقيمة 4 ، ولكن في نهاية كتلة التعليمات البرمجية هذه هو عبارة الفاصل . على الأرجح ، مطلوب رمز إضافي في هذا المكان.

V595 تم استخدام المؤشر 'k' قبل التحقق من صحته باستخدام nullptr. التحقق من الخطوط: 15919 ، 15920. opcc 15919

 void Perl_rpeep(pTHX_ OP *o) { .... OP *k = o->op_next; U8 want = (k->op_flags & OPf_WANT); // <= if ( k // <= && k->op_type == OP_KEYS && ( want == OPf_WANT_VOID || want == OPf_WANT_SCALAR) && !(k->op_private & OPpMAYBE_LVSUB) && !(k->op_flags & OPf_MOD) ) { .... } 

في مقتطف الشفرة هذا ، وجد المحلل المؤشر k ، والذي تم إلغاء الإشارة إليه بسطر واحد قبل التحقق من صلاحيته. يمكن أن يكون هذا خطأ أو رمزًا إضافيًا.

التشخيص V595 يجد الكثير من التحذيرات في أي مشروع ، بيرل 5 ليست استثناء. من المستحيل احتواء كل هذا في المقالة ، لذلك سنقتصر على مثال واحد ، وسيتحقق المطورون من المشروع بأنفسهم إذا رغبوا في ذلك.

متفرقات


تم الكشف عن V779 رمز يتعذر الوصول إليه. من الممكن أن يكون هناك خطأ. عالمي ج 457

 XS(XS_utf8_valid); XS(XS_utf8_valid) { dXSARGS; if (items != 1) croak_xs_usage(cv, "sv"); else { SV * const sv = ST(0); STRLEN len; const char * const s = SvPV_const(sv,len); if (!SvUTF8(sv) || is_utf8_string((const U8*)s,len)) XSRETURN_YES; else XSRETURN_NO; } XSRETURN_EMPTY; } 

على الخط مع XSRETURN_EMPTY ، اكتشف المحلل رمزًا لا يمكن الوصول إليه. هناك عبارتان للرجوع في هذه الوظيفة و croak_xs_usage - ماكرو يتم توسيعه إلى دالة noreturn:

 void Perl_croak_xs_usage(const CV *const cv, const char *const params) __attribute__((noreturn)); 

في أماكن مماثلة في كود Perl 5 ، يتم استخدام الماكرو NOT_REACHED للإشارة إلى فرع لا يمكن الوصول إليه.

V784 حجم قناع البت أقل من حجم المعامل الأول. سيؤدي ذلك إلى فقدان بتات أعلى. 296

 void ZLIB_INTERNAL inflate_fast(z_streamp strm, unsigned start) { .... unsigned long hold; /* local strm->hold */ unsigned bits; /* local strm->bits */ .... hold &= (1U << bits) - 1; .... } 

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

الخلاصة


الصورة 6



كان العثور على الأخطاء من خلال وحدات الماكرو أمرًا صعبًا للغاية. استغرق عرض التقرير الكثير من الوقت والجهد. ومع ذلك ، فإن المقالة تتضمن حالات مثيرة جدا للاهتمام تشبه الأخطاء الحقيقية. تقرير المحلل كبير للغاية ، هناك بالتأكيد أكثر إثارة للاهتمام هناك. لكني لا أستطيع أن أنظر إليها أكثر :). أنصح المطور بالتحقق من المشروع بمفرده وإزالة العيوب التي يمكنه اكتشافها.

PS نريد بالتأكيد دعم هذا المشروع المثير للاهتمام ، ونحن على استعداد لمنح المطورين ترخيصًا لعدة شهور.



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

Source: https://habr.com/ru/post/ar425415/


All Articles