في وقت سابق قمنا بمراجعات الكود للحزم الرياضية الكبيرة ، على سبيل المثال ، Scilab و Octave ، وظلت الآلات الحاسبة جانباً كأدوات مساعدة صغيرة يصعب فيها ارتكاب الأخطاء بسبب حجمها الصغير. كنا مخطئين في عدم الاهتمام بهم. أظهرت الحالة المتعلقة بنشر الكود المصدري لحاسبة Windows أن كل شخص مهتم بمناقشة الأخطاء المخفية هناك ، وهناك أكثر من أخطاء كافية لكتابة مقال عنها. قررت أنا وزملائي فحص رمز عدد من الآلات الحاسبة الشائعة واتضح أن رمز حاسبة Windows لم يكن سيئًا للغاية (المفسد).
مقدمة
Qalculate! - آلة حاسبة عالمية عبر منصة. إنه سهل الاستخدام ، ولكنه يوفر القوة والتنوع الموجود عادة في حزم الرياضيات المعقدة ، وكذلك الأدوات المفيدة للاحتياجات اليومية (مثل تحويل العملات وحساب الفائدة). يتكون المشروع من مكونين:
libqalculate (مكتبة و CLI) و
qalculate-gtk (GTK + UI). وتشارك فقط رمز libqalculate في الدراسة.
لمقارنة المشروع بسهولة أكبر مع نفس حاسبة Windows التي درسناها مؤخرًا ، أعطي ناتج أداة Cloc المساعدة لـ libqalculate:
ذاتي ، هناك المزيد من الأخطاء ، وهي أكثر أهمية مما كانت عليه في رمز حاسبة Windows. لكنني أنصحك باستخلاص النتائج بنفسك عن طريق قراءة مراجعة الكود هذه.
بالمناسبة ، فيما يلي رابط لمقال حول التحقق من حاسبة من 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; .... }
نشأت هنا تعبيرات مكررة لأنه في مكان واحد بدلاً من الاسم
mtr كتبوا
mtr2 . وبالتالي ، في حالة عدم وجود استدعاء
mtr.number (). IsReal () وظيفة.
V501 هناك أشكال تعبير فرعية متطابقة 'vargs [1]. تمثل NonPositive ()' إلى اليسار وإلى يمين '||' المشغل. BuiltinFunctions.cc 5785

للعثور على الحالات الشاذة في هذا الرمز يدويا غير واقعي! لكنهم كذلك. علاوة على ذلك ، في الملف الأصلي ، تتم كتابة هذه الأجزاء في سطر واحد. اكتشف المحلل تعبيرًا
مكررًا vargs [1] .representNonPositive () ، مما قد يشير إلى خطأ مطبعي ، وبالتالي ، خطأ محتمل.
فيما يلي قائمة كاملة بالأماكن المشبوهة التي يصعب عليك اكتشافها:
- 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 () ، يوصى باستخدام مخزن مؤقت وسيط ، لأنه إذا كان من المستحيل تخصيص ذاكرة ، فسيتم فقد المؤشر إلى الذاكرة القديمة بشكل لا رجعة فيه.
استنتاج
مشروع كلكولات! يتصدر قائمة أفضل الآلات الحاسبة المجانية ، بينما يحتوي على العديد من الأخطاء الخطيرة. لكننا لم نر منافسيه. سنحاول الذهاب من خلال جميع الآلات الحاسبة الشعبية.
بالنسبة للمقارنة مع جودة الآلة الحاسبة من عالم Windows ، في حين أن الأداة المساعدة من Microsoft تبدو أكثر موثوقية وعالية الجودة.
تحقق من "الحاسبة" عن طريق تنزيل
PVS-Studio وتجربته في مشروعك. :-)

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