اختبار مترجم GCC 10 مع PVS-Studio

PVS-Studio مقابل GCC 10

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

حان الوقت للتحقق من كود مترجم دول مجلس التعاون الخليجي


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

في الواقع ، ليس سراً أن المترجمين لديهم محلل كود ثابت ثابت خاص بهم وأنهم يتطورون أيضًا. لذلك ، من وقت لآخر نكتب مقالات يمكن للمحلل الساكن PVS-Studio العثور على أخطاء حتى داخل المترجمات وأننا لسنا في أكل الخبز هباء :).

في الواقع ، لا يمكنك مقارنة أجهزة التحليل الساكنة الكلاسيكية مع المترجمات. المحللون الثابتون ليسوا مجرد بحث عن أخطاء في الشفرة ، ولكن أيضًا بنية تحتية متطورة. على سبيل المثال ، هذا هو التكامل مع أنظمة مثل SonarQube و PlatformIO و Azure DevOps و Travis CI و CircleCI و GitLab CI / CD و Jenkins و Visual Studio. هذه آليات مطورة لقمع التحذير الجماعي ، والتي تسمح لك بالبدء بسرعة في استخدام PVS-Studio حتى في مشروع قديم كبير. هذه قائمة بريدية للإخطار. وهلم جرا وهكذا دواليك. ومع ذلك ، على أي حال ، فإن السؤال الأول المطروح هو: "هل يمكن لـ PVS-Studio العثور على شيء لا يستطيع المترجمون العثور عليه؟". لذا ، سنكتب مقالات مرارًا وتكرارًا حول فحص هؤلاء المترجمين بأنفسهم.

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

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

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

//  alias.c
....
struct alias_set_hash : int_hash <int, INT_MIN, INT_MIN + 1> {};
struct GTY(()) alias_set_entry {
  alias_set_type alias_set;
  bool has_zero_child;
  bool is_pointer;
  bool has_pointer;
  hash_map<alias_set_hash, int> *children;
};
....

من الواضح أن هذا ليس C ، ولكن C ++.

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

10 مقتطفات رمز مشبوهة


الجزء N1 ، يبدو أن النسخ واللصق غير ناجح

static bool
try_crossjump_to_edge (int mode, edge e1, edge e2,
                       enum replace_direction dir)
{
  ....
  if (FORWARDER_BLOCK_P (s->dest))
    s->dest->count += s->count ();

  if (FORWARDER_BLOCK_P (s2->dest))
    s2->dest->count -= s->count ();
  ....
}

تحذير PVS-Studio: V778 تم العثور على جزأين رمز مشابه. ربما ، هذا هو خطأ مطبعي ويجب استخدام متغير 's2' بدلاً من 's'. cfgcleanup.c 2126

في الواقع ، لست متأكدًا مما إذا كان هذا خطأ. ومع ذلك ، لدي شك قوي في أن هذا الرمز مكتوب باستخدام Copy-Paste ، وفي الكتلة الثانية في مكان واحد نسيوا استبدال s بـ s2 . أي أنه يبدو لي أن الكود الثاني من الكود يجب أن يكون مثل هذا:

if (FORWARDER_BLOCK_P (s2->dest))
  s2->dest->count -= s2->count ();

جزء N2 ، خطأ مطبعي

tree
vn_reference_lookup_pieces (....)
{
  struct vn_reference_s vr1;
  ....
  vr1.set = set;
  vr1.set = base_set;
  ....
}

تحذير PVS-Studio: V519 يتم تعيين المتغير "vr1.set" مرتين متتاليتين. ربما هذا خطأ. تحقق من الأسطر: 3448 ، 3449. tree-ssa-sccvn.c 3449 من

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

vr1.set = set;
vr1.base_set = base_set;

على الأرجح ، في الشفرة المشبوهة يجب أن تكون مكتوبة بالطريقة نفسها تمامًا.

جزء N3 ، تعيين متغير لنفسه

static omp_context *
new_omp_context (gimple *stmt, omp_context *outer_ctx)
{
  omp_context *ctx = XCNEW (omp_context);

  splay_tree_insert (all_contexts, (splay_tree_key) stmt,
         (splay_tree_value) ctx);
  ctx->stmt = stmt;

  if (outer_ctx)
    {
      ctx->outer = outer_ctx;
      ctx->cb = outer_ctx->cb;
      ctx->cb.block = NULL;
      ctx->local_reduction_clauses = NULL;
      ctx->outer_reduction_clauses = ctx->outer_reduction_clauses;  // <=
      ctx->depth = outer_ctx->depth + 1;
    }
  ....
}

تحذير PVS-Studio: V570 يتم تعيين متغير "ctx-> outer_reduction_clauses" لنفسه. omp-low.c 935 من

الغريب جدًا تعيين متغير لنفسه.

جزء N4. 0،1،2 ، سوف يقلك فريدي.

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

#define GET_MODE(RTX)    ((machine_mode) (RTX)->mode)
....
static int
add_equal_note (rtx_insn *insns, rtx target, enum rtx_code code, rtx op0,
                rtx op1, machine_mode op0_mode)
{
  ....
  if (commutative_p
      && GET_MODE (xop0) != xmode0 && GET_MODE (xop1) != xmode1
      && GET_MODE (xop0) == xmode1 && GET_MODE (xop1) == xmode1)
    std::swap (xop0, xop1);
  ....
}

تحذير PVS-Studio: V560 دائمًا ما يكون جزء من التعبير الشرطي خطأ:
((machine_mode) (xop1) -> mode) == xmode1. optabs.c 1053

لاحظ هذين التعبيرين الفرعيين :

  • GET_MODE (xop1)! = Xmode1
  • GET_MODE (xop1) == xmode1

يتم تنفيذ العملية "و" بناءً على نتائج هذه التعبيرات الفرعية ، والتي ليس لها معنى عمليًا. في الواقع ، إذا بدأ تنفيذ التعبير الثانوي الثاني ، فمن المعروف مسبقًا أنه سيؤدي إلى خطأ .

على الأرجح ، هناك خطأ مطبعي هنا في الأصفار والأصفار ، وفي الواقع كان يجب أن تكون الحالة على هذا النحو:

&& GET_MODE (xop0) != xmode0 && GET_MODE (xop1) != xmode1
&& GET_MODE (xop0) == xmode1 && GET_MODE (xop1) == xmode0

بالطبع ، لست متأكدًا مما إذا قمت بتغيير الشفرة بشكل صحيح ، لأنني لست مرشدًا في المشروع.

جزء N5. تغيير مريب في قيمة الحجة

bool
ipa_polymorphic_call_context::set_by_invariant (tree cst,
                                                tree otr_type,
                                                HOST_WIDE_INT off)
{
  poly_int64 offset2, size, max_size;
  bool reverse;
  tree base;

  invalid = false;
  off = 0;                // <=
  ....
  if (otr_type && !contains_type_p (TREE_TYPE (base), off, otr_type))
    return false;

  set_by_decl (base, off);
  return true;
}

تحذير PVS-Studio: يتم إعادة كتابة المعلمة V763 "إيقاف التشغيل" دائمًا في نص الوظيفة قبل استخدامها. ipa-polymorphic-call.c 766

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

جزء N6. شيء صغير

cgraph_node *
cgraph_node::create_clone (....)
{
  ....
  new_node->icf_merged = icf_merged;
  new_node->merged_comdat = merged_comdat;   // <=
  new_node->thunk = thunk;
  new_node->unit_id = unit_id;
  new_node->merged_comdat = merged_comdat;   // <=
  new_node->merged_extern_inline = merged_extern_inline;
  ....
}

تحذير PVS-Studio: V519 يتم تعيين قيم المتغير 'new_node-> merged_comdat' مرتين على التوالي. ربما هذا خطأ. تحقق من الخطوط: 406 ، 409. يتم

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

جزء N7. رمز يبدو خطيراً

static void
complete_mode (struct mode_data *m)
{
  ....
  if (m->cl == MODE_COMPLEX_INT || m->cl == MODE_COMPLEX_FLOAT)
    alignment = m->component->bytesize;
  else
    alignment = m->bytesize;

  m->alignment = alignment & (~alignment + 1);

  if (m->component)
  ....
}

تحذير PVS-Studio: V595 تم استخدام مؤشر "m-> للمكون" قبل التحقق من صلاحيته باستخدام nullptr. تحقق من السطور: 407 ، 415. genmodes.c 407

في البداية ، يتم إلغاء الإشارة إلى مؤشر مكون m-> في أحد فروع عبارة if . يعني هذا التعبير: م-> مكون-> بايتات .

وتبين كذلك أن هذا المؤشر قد يكون فارغًا. هذا يتبع من الشيك: إذا (م-> مكون) .

هذا الرمز ليس خطأ بالضرورة. من الممكن تمامًا أن يتم تنفيذ فرع غير محسوب فقط إذا لم يكن المؤشر فارغًا. أي أن هناك علاقة غير مباشرة بين قيمة المكون المتغير m-> cl و m->. لكن هذا الرمز يبدو خطيرًا جدًا على أي حال. ولا توجد تعليقات توضيحية.

جزء N8. التأكد مرتين

void
pointer_and_operator::wi_fold (value_range &r, tree type,
                               const wide_int &lh_lb,
                               const wide_int &lh_ub,
                               const wide_int &rh_lb ATTRIBUTE_UNUSED,
                               const wide_int &rh_ub ATTRIBUTE_UNUSED) const
{
  // For pointer types, we are really only interested in asserting
  // whether the expression evaluates to non-NULL.
  if (wi_zero_p (type, lh_lb, lh_ub) || wi_zero_p (type, lh_lb, lh_ub))
    r = range_zero (type);
  else 
    r = value_range (type);
}

تحذير PVS-Studio: V501 هناك تعبيرات فرعية متطابقة "wi_zero_p (النوع ، lh_lb ، lh_ub)" إلى اليسار وإلى يمين "||" المشغل أو العامل. range-op.cc 2657

نوع من الاختيار الغريب. يتم استدعاء الدالة wi_zero_p مرتين بنفس مجموعة الوسائط الفعلية. يمكن للمرء أن يشك في أن المكالمة الثانية يجب أن تستخدم الحجج المقبولة من الخارج: rh_lb ، rh_ub. ولكن لا ، تم وضع علامة على هذه الحجج على أنها غير مستخدمة ( ATTRIBUTE_UNUSED ).

لذلك ، ليس من الواضح لي سبب عدم كتابة الشيكات أبسط:

if (wi_zero_p (type, lh_lb, lh_ub))
  r = range_zero (type);
else 
  r = value_range (type);

أم أن هناك بعض الأخطاء المطبعية هنا؟ أم خطأ منطقي؟ لا أعلم ، لكن الشفرة غريبة جدًا.

جزء N9. وصول صفيف خطير

struct algorithm
{
  struct mult_cost cost;
  short ops;
  enum alg_code op[MAX_BITS_PER_WORD];
  char log[MAX_BITS_PER_WORD];
};

static void
synth_mult (struct algorithm *alg_out, unsigned HOST_WIDE_INT t,
            const struct mult_cost *cost_limit, machine_mode mode)
{
  int m;
  struct algorithm *alg_in, *best_alg;
  ....
  /* Cache the result.  */
  if (!cache_hit)
  {
    entry_ptr->t = t;
    entry_ptr->mode = mode;
    entry_ptr->speed = speed;
    entry_ptr->alg = best_alg->op[best_alg->ops];
    entry_ptr->cost.cost = best_cost.cost;
    entry_ptr->cost.latency = best_cost.latency;
  }

  /* If we are getting a too long sequence for `struct algorithm'
     to record, make this search fail.  */
  if (best_alg->ops == MAX_BITS_PER_WORD)
    return;
  ....
}

تحذير PVS-Studio: V781 يتم التحقق من قيمة المتغير 'best_alg-> ops' بعد استخدامه. ربما هناك خطأ في منطق البرنامج. تحقق من الأسطر: 3157 ، 3164. expmed.c 3157 لنقصر الشفرة لتوضيح

ما لا يعجب المحلل:

if (!cache_hit)
{
  entry_ptr->alg = best_alg->op[best_alg->ops];
}
if (best_alg->ops == MAX_BITS_PER_WORD)

في البداية ، يتم استخدام المتغير best_alg-> ops لفهرسة المصفوفة. وعندها فقط يقوم هذا المتغير بالتحقق من قيمة الحدود. من الناحية النظرية ، يمكن أن يحدث تجاوز للصفيف (نوع كلاسيكي من الخطأ CWE-193: خطأ خارج من جانب واحد ).

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

في أي حال ، من الأفضل التحقق من هذا الرمز. وحتى إذا اتضح أنه صحيح ، فإنني أوصي بمرافقة القسم المدروس من البرنامج مع تعليق. يمكن أن يخلط بيني ليس فقط أو PVS-Studio ، ولكن أيضًا شخص آخر.

جزء N10. الرمز الذي لم يتم إصلاحه

منذ 4 سنوات ، في المقالة الأخيرة ، لفتت الانتباه إلى هذا الرمز:

static bool
dw_val_equal_p (dw_val_node *a, dw_val_node *b)
{
  ....
  case dw_val_class_vms_delta:
    return (!strcmp (a->v.val_vms_delta.lbl1, b->v.val_vms_delta.lbl1)
            && !strcmp (a->v.val_vms_delta.lbl1, b->v.val_vms_delta.lbl1));
  ....
}

تحذير PVS-Studio: V501 توجد عبارات فرعية متطابقة "! Strcmp (a-> v.val_vms_delta.lbl1، b-> v.val_vms_delta.lbl1)" إلى اليسار وإلى يمين عامل التشغيل "&&". dwarf2out.c 1481

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

return (   !strcmp (a->v.val_vms_delta.lbl1, b->v.val_vms_delta.lbl1)
        && !strcmp (a->v.val_vms_delta.lbl2, b->v.val_vms_delta.lbl2));

ومع ذلك ، لمدة 4 سنوات لم يتم إصلاح هذا الرمز. في الوقت نفسه ، أبلغنا المؤلفين بالأقسام المشبوهة من التعليمات البرمجية التي وصفناها في المقالة. لست متأكدا الآن أن هذا خطأ. ربما هذا مجرد رمز زائدة عن الحاجة. في هذه الحالة ، يمكن تبسيط التعبير:

return (!strcmp (a->v.val_vms_delta.lbl1, b->v.val_vms_delta.lbl1));

دعونا نرى ما إذا كان مطورو GCC سيغيرون هذا الرمز بعد مقال جديد.

استنتاج


أذكرك أنه يمكنك استخدام خيار الترخيص المجاني هذا للتحقق من المشاريع المفتوحة . بالمناسبة ، هناك خيارات أخرى للترخيص المجاني لـ PVS-Studio ، بما في ذلك حتى للمشاريع المغلقة. وهي مدرجة هنا: " خيارات الترخيص المجانية PVS-Studio ".

شكرا للانتباه. وتأتي قراءة لدينا بلوق . هناك العديد من الأشياء المثيرة للاهتمام.

مقالاتنا الأخرى حول فحص المترجمات


  1. فحص LLVM (Clang) (أغسطس 2011) ، الفحص الثاني (أغسطس 2012) ، الفحص الثالث (أكتوبر 2016) ، الفحص الرابع (أبريل 2019)
  2. مراجعة دول مجلس التعاون الخليجي (أغسطس 2016)
  3. تحقق من مترجم Huawei Ark (ديسمبر 2019)
  4. التحقق من برنامج .NET Compiler Platform ("Roslyn") (ديسمبر 2015) ، الفحص الثاني (أبريل 2019)
  5. مراجعة Roslyn Analyzers (أغسطس 2019)
  6. التحقق من PascalABC.NET (مارس 2017)



إذا كنت ترغب في مشاركة هذه المقالة مع جمهور يتحدث الإنجليزية ، فيرجى استخدام رابط الترجمة: Andrey Karpov. فحص مترجم GCC 10 باستخدام برنامج PVS-Studio .

All Articles