في السابق ، قمنا بمراجعات الكود للحزم الرياضية الكبيرة ، على سبيل المثال ، Scilab و Octave ، حيث بقيت الآلات الحاسبة بمعزل عن المرافق الصغيرة ، والتي يصعب فيها ارتكاب الأخطاء نظرًا لقواعدها البرمجية الصغيرة. كنا مخطئين أننا لم ننتبه إليهم. أظهرت حالة نشر التعليمات البرمجية المصدر الخاصة بحاسبة Windows أن كل شخص مهتم فعلاً بمناقشة أنواع الأخطاء المختبئة فيها. علاوة على ذلك ، كان عدد الأخطاء هناك أكثر من كافية لكتابة مقال حول ذلك. أنا وزملائي ، قررنا استكشاف رمز عدد من الآلات الحاسبة الشائعة ، واتضح أن رمز حاسبة Windows لم يكن بهذا السوء (المفسد).
مقدمة
Qalculate! هو آلة حاسبة سطح المكتب عبر منصة متعددة الأغراض. إنه سهل الاستخدام ولكنه يوفر القوة والتنوع المحجوزين عادة لحزم الرياضيات المعقدة ، وكذلك الأدوات المفيدة للاحتياجات اليومية (مثل تحويل العملات وحساب النسبة المئوية). يتكون المشروع من مكونين:
libqalculate (مكتبة و CLI) و
qalculate-gtk (GTK + UI). وشملت الدراسة فقط رمز libqalculate.
لمقارنة المشروع مع Windows Calculator بسهولة ، والذي فحصناه مؤخرًا ، أقتبس إخراج أداة Cloc المساعدة من أجل libqalculate:
النظر في ذلك بشكل شخصي ، هناك المزيد من الأخطاء في أنها أكثر أهمية مما كانت عليه في رمز آلة حاسبة ويندوز. ومع ذلك ، أوصي بإجراء استنتاجات بمفردك ، بعد قراءة نظرة عامة على الكود.
بالمناسبة ، إليك رابط لمقال حول التحقق من الحاسبة من Microsoft: "
حساب الأخطاء في حاسبة Windows ".
أداة التحليل هي محلل الكود الثابت
PVS-Studio . إنها مجموعة من الحلول لمراقبة جودة الكود والبحث عن الأخطاء ونقاط الضعف المحتملة. تتضمن اللغات المدعومة: C و C ++ و C # و Java. يمكنك تشغيل المحلل على أنظمة التشغيل Windows و Linux و macOS.
نسخ ولصق الأخطاء المطبعية مرة أخرى!
V523 بيان "ثم" مكافئ لبيان "آخر". Number.cc 4018
bool Number::square() { .... if(mpfr_cmpabs(i_value->internalLowerFloat(), i_value->internalUpperFloat()) > 0) { mpfr_sqr(f_tmp, i_value->internalLowerFloat(), MPFR_RNDU); mpfr_sub(f_rl, f_rl, f_tmp, MPFR_RNDD); } else { mpfr_sqr(f_tmp, i_value->internalLowerFloat(), MPFR_RNDU); mpfr_sub(f_rl, f_rl, f_tmp, MPFR_RNDD); } .... }
الكود هو نفسه تمامًا في الكتل
if و
else . تشبه أجزاء التعليمات البرمجية المجاورة هذه إلى حد كبير ، ولكن يتم استخدام وظائف مختلفة فيها:
internalLowerFloat () و
internalUpperFloat () . من الآمن افتراض أن مطورًا قام بنسخ الكود ونسي تصحيح اسم الوظيفة هنا.
V501 هناك تعبيرات فرعية متطابقة '! Mtr2.number (). IsReal ()' إلى اليسار وإلى يمين '||' المشغل. BuiltinFunctions.cc 6274
int IntegrateFunction::calculate(....) { .... if(!mtr2.isNumber() || !mtr2.number().isReal() || !mtr.isNumber() || !mtr2.number().isReal()) b_unknown_precision = true; .... }
في هذه الحالة ، ظهرت تعبيرات مكررة بسبب حقيقة أنه في مكان واحد تمت كتابة
mtr2 بدلاً من
mtr. وبالتالي ، استدعاء
دالة mtr.number (). IsReal () غير موجودة في الشرط.
V501 هناك أشكال تعبير فرعية متطابقة 'vargs [1]. تمثل NonPositive ()' إلى اليسار وإلى يمين '||' المشغل. BuiltinFunctions.cc 5785
لن نجد عيوبًا في هذا الرمز يدويًا! ولكن هنا هناك. علاوة على ذلك ، في الملف الأصلي ، تتم كتابة هذه الأجزاء في سطر واحد. اكتشف المحلل تعبيرًا
مكررًا vargs [1] .reesNonPositive () ، مما قد يشير إلى خطأ مطبعي أو نتيجة لذلك ، خطأ محتمل.
فيما يلي قائمة كاملة بالأماكن المشبوهة ، والتي بالكاد يمكن أن تتخلص منها.
- V501 هناك أشكال تعبير فرعية متطابقة 'vargs [1]. تمثل NonPositive ()' إلى اليسار وإلى يمين '||' المشغل. BuiltinFunctions.cc 5788
- V501 هناك "إلحاق" تعبيرات فرعية مماثلة إلى اليسار وإلى يمين المشغل "&&". MathStructure.cc 1780
- V501 هناك "إلحاق" تعبيرات فرعية مماثلة إلى اليسار وإلى يمين المشغل "&&". MathStructure.cc 2043
- V501 هناك تعابير فرعية متطابقة '(* v_subs [v_order [1]]). تمثل سالب (صواب)' إلى اليسار وإلى يمين عامل التشغيل '&&'. MathStructure.cc 5569
حلقة مع شرط غير صحيح
V534 من المحتمل أن يتم مقارنة متغير خاطئ داخل عامل التشغيل "من أجل". النظر في مراجعة "أنا". MathStructure.cc 28741
bool MathStructure::isolate_x_sub(....) { .... for(size_t i = 0; i < mvar->size(); i++) { if((*mvar)[i].contains(x_var)) { mvar2 = &(*mvar)[i]; if(mvar->isMultiplication()) { for(size_t i2 = 0; i < mvar2->size(); i2++) { if((*mvar2)[i2].contains(x_var)) {mvar2 = &(*mvar2)[i2]; break;} } } break; } } .... }
في الحلقة الداخلية ، يمثل المتغير
i2 عدادًا ، ولكن بسبب خطأ مطبعي ، حدث خطأ - يتم استخدام المتغير
i من الحلقة الخارجية في شرط الخروج من الحلقة.
التكرار أم خطأ؟
V590 النظر في فحص هذا التعبير. التعبير مفرط أو يحتوي على خطأ مطبعي. Number.cc 6564
bool Number::add(const Number &o, MathOperation op) { .... if(i1 >= COMPARISON_RESULT_UNKNOWN && (i2 == COMPARISON_RESULT_UNKNOWN || i2 != COMPARISON_RESULT_LESS)) return false; .... }
منذ 3 سنوات بعد حصولي على هذه الشفرة ، كتبت ورقة غش لي ولغيرك من المطورين: "
التعبيرات المنطقية في C / C ++. الأخطاء التي يرتكبها المحترفون ". عندما صادفت هذه الشفرة ، أتأكد من أن الملاحظة لم يصبح أقل أهمية. يمكنك الاطلاع على المقالة ، والعثور على نمط للخطأ المطابق للرمز ، ومعرفة جميع الفروق الدقيقة.
في حالة هذا المثال ، سنذهب إلى قسم "التعبير == ||" ! = "واكتشف أن تعبير
i2 == COMPARISON_RESULT_UNKNOWN لا يؤثر في شيء.
إلغاء التأشير مؤشرات غير محددة
V595 تم استخدام مؤشر 'o_data' قبل أن يتم التحقق منه ضد nullptr. خطوط التحقق: 1108 ، 1112. DataSet.cc 1108
string DataObjectArgument::subprintlong() const { string str = _("an object from"); str += " \""; str += o_data->title();
في وظيفة واحدة ، يتم
إلغاء مؤشر
o_data على حد سواء دون ومع التحقق. هذا يمكن أن يكون رمز زائدة عن الحاجة ، أو خطأ محتمل. أنا أميل نحو الأخير.
هناك مكانان مشابهان:
- V595 تم استخدام مؤشر 'o_assumption' قبل أن يتم التحقق منه ضد nullptr. خطوط الفحص: 229 ، 230. Variable.cc 229
- V595 تم استخدام مؤشر 'i_value' قبل أن يتم التحقق منه مقابل nullptr. خطوط الفحص: 3412 ، 3427. Number.cc 3412
مجاني () أو حذف []؟
V611 تم تخصيص الذاكرة باستخدام عامل التشغيل "جديد" ولكن تم إصدارها باستخدام الدالة "الحرة". النظر في فحص منطق العملية وراء المتغير "remcopy". Number.cc 8123
string Number::print(....) const { .... while(!exact && precision2 > 0) { if(try_infinite_series) { remcopy = new mpz_t[1];
يتم تخصيص ذاكرة صفيف
remcopy وإصدارها بطرق مختلفة ، وهذا خطأ خطير.
التغييرات المفقودة
bool expand_partial_fractions(MathStructure &m, ....) { .... if(b_poly && !mquo.isZero()) { MathStructure m = mquo; if(!mrem.isZero()) { m += mrem; m.last() *= mtest[i]; m.childrenUpdated(); } expand_partial_fractions(m, eo, false); return true; } .... }
يتم تمرير المتغير
m في الوظيفة بالرجوع ، مما يعني تعديله. ومع ذلك ، اكتشف المحلل أن الكود يحتوي على المتغير الذي يحمل نفس الاسم ، والذي يتداخل مع نطاق معلمة الوظيفة ، مما يسمح بفقدان التغييرات.
مؤشرات غريبة
V774 تم استخدام مؤشر 'cu' بعد تحرير الذاكرة. Calculator.cc 3595
MathStructure Calculator::convertToBestUnit(....) { .... CompositeUnit *cu = new CompositeUnit("", "...."); cu->add(....); Unit *u = getBestUnit(cu, false, eo.local_currency_conversion); if(u == cu) { delete cu;
يحذر المحلل من أن التعليمات البرمجية يستدعي طريقة كائن
cu مباشرة بعد إلغاء تخصيص الذاكرة. ولكن عندما تحاول التعامل معها ، فإن الكود يصبح أكثر غرابة. أولاً ، يحدث استدعاء
الحذف cu دائمًا - سواء في الحالة أو بعد ذلك. ثانيًا ، يشير الرمز بعد الشرط إلى أن المؤشرين
u و
cu غير متساويين ، مما يعني أنه بعد حذف كائن
cu ، من المنطقي تمامًا استخدام كائن
u . على الأرجح ، تم إجراء خطأ مطبعي في الكود وأراد مؤلف الكود استخدام المتغير
u فقط.
استخدام وظيفة البحث
V797 يتم استخدام وظيفة "find" كما لو أنها أعادت نوع منطقي. ربما يجب مقارنة القيمة المرجعة للدالة مع std :: string :: npos. Unit.cc 404
MathStructure &AliasUnit::convertFromFirstBaseUnit(....) const { if(i_exp != 1) mexp /= i_exp; ParseOptions po; if(isApproximate() && suncertainty.empty() && precision() == -1) { if(sinverse.find(DOT) || svalue.find(DOT)) po.read_precision = READ_PRECISION_WHEN_DECIMALS; else po.read_precision = ALWAYS_READ_PRECISION; } .... }
على الرغم من أنه يمكن تجميع التعليمات البرمجية بنجاح ، إلا أنها تبدو مشبوهة ، حيث تقوم دالة
find بإرجاع رقم النوع
std :: string :: size_type . يكون الشرط صحيحًا إذا تم العثور على النقطة في أي جزء من السلسلة إلا إذا كانت النقطة في البداية. إنه فحص غريب. لست متأكدًا ، لكن ربما ينبغي إعادة كتابة هذا الرمز على النحو التالي:
if( sinverse.find(DOT) != std::string::npos || svalue.find(DOT) != std::string::npos) { po.read_precision = READ_PRECISION_WHEN_DECIMALS; }
تسرب الذاكرة المحتملة
realloc () V701 تسرب محتمل: عند فشل realloc () في تخصيص الذاكرة ، يتم فقد "المخزن المؤقت" الأصلي للمؤشر. خذ بعين الاعتبار تعيين realloc () إلى مؤشر مؤقت. util.cc 703
char *utf8_strdown(const char *str, int l) { #ifdef HAVE_ICU .... outlength = length + 4; buffer = (char*) realloc(buffer, outlength * sizeof(char));
عند العمل مع الدالة
realloc () يوصى باستخدام مخزن مؤقت وسيط ، كما في حالة إذا كان من المستحيل تخصيص الذاكرة ، فسيتم فقد المؤشر إلى منطقة الذاكرة القديمة بشكل غير قابل للاسترجاع.
استنتاج
Qalculate! يتصدر المشروع قائمة بأفضل الآلات الحاسبة المجانية ، بينما يحتوي على العديد من الأخطاء الخطيرة. من ناحية أخرى ، لم نتحقق من منافسيها حتى الآن. سنحاول تجاوز جميع الآلات الحاسبة الشائعة.
بالنسبة إلى مقارنة جودة الحاسبة من عالم Windows ، تبدو الأداة المساعدة من Microsoft أكثر موثوقية وعملت جيدًا حتى الآن.
تحقق من "الحاسبة" الخاصة بك - قم بتنزيل
PVS-Studio وجربه لمشروعك. :-)