Menguji Kompiler GCC 10 dengan PVS-Studio

PVS-Studio vs GCC 10

Kompiler GCC ditulis dengan penggunaan makro yang berlebihan. Pemeriksaan lain dari kode GCC menggunakan PVS-Studio sekali lagi menegaskan pendapat tim kami bahwa makro buruk. Kode seperti itu sulit dipahami tidak hanya untuk penganalisa statis, tetapi juga untuk seorang programmer. Tentu saja, pengembang GCC sudah terbiasa dengan proyek dan berpengalaman dalam hal itu. Tetapi dari sisi itu sangat sulit untuk memahami sesuatu. Sebenarnya, karena makro, tidak mungkin untuk melakukan verifikasi kode sepenuhnya. Namun demikian, penganalisa PVS-Studio, seperti biasa, menunjukkan bahwa ia dapat menemukan kesalahan bahkan dalam kompiler.

Saatnya memeriksa ulang kode kompiler GCC


Terakhir kali saya memeriksa kompiler GCC empat tahun lalu. Waktu berlalu dengan cepat dan tanpa terasa, dan entah bagaimana saya semua lupa untuk kembali ke proyek ini dan memeriksanya kembali. Publikasi " Analisis statis dalam GCC 10 " mendorong kembali ke ide ini .

Sebenarnya, bukan rahasia lagi bahwa kompiler memiliki penganalisa kode statis bawaan mereka sendiri dan mereka juga berkembang. Oleh karena itu, dari waktu ke waktu kami menulis artikel bahwa analisa statis PVS-Studio dapat menemukan kesalahan bahkan di dalam kompiler dan bahwa kami tidak sia-sia makan roti :).

Bahkan, Anda tidak dapat membandingkan analisa statis klasik dengan kompiler. Analisis statis tidak hanya mencari kesalahan dalam kode, tetapi juga infrastruktur yang dikembangkan. Misalnya, ini adalah integrasi dengan sistem seperti SonarQube, PlatformIO, Azure DevOps, Travis CI, CircleCI, GitLab CI / CD, Jenkins, Visual Studio. Ini adalah mekanisme yang dikembangkan untuk penindasan peringatan massal, yang memungkinkan Anda untuk dengan cepat mulai menggunakan PVS-Studio bahkan dalam proyek lama yang besar. Ini adalah milis pemberitahuan. Dan seterusnya dan seterusnya. Namun, bagaimanapun, pertanyaan pertama yang diajukan adalah: "Bisakah PVS-Studio menemukan sesuatu yang tidak dapat ditemukan oleh penyusun?". Jadi, kami akan menulis artikel berulang kali tentang memeriksa kompiler ini sendiri.

Mari kita kembali ke subjek verifikasi proyek GCC. Tidak perlu memikirkan proyek ini dan mengatakan apa itu. Lebih baik kita bicarakan apa yang ada di dalam proyek ini.

Dan di dalamnya ada sejumlah besar makro yang mengganggu verifikasi. Pertama, penganalisa PVS-Studio menghasilkan sejumlah besar kesalahan positif. Tidak ada yang salah dengan itu, tetapi tidak mudah untuk mengambil dan mulai mempelajari laporan yang dikeluarkan olehnya. Dengan cara yang baik, Anda perlu melakukan pekerjaan untuk menekan peringatan palsu di makro. Jika tidak, peringatan yang berguna hanya tenggelam dalam aliran suara. Pengaturan ini berada di luar cakupan tugas penulisan artikel ini. Sebenarnya, saya akan benar-benar jujur ​​- saya terlalu malas untuk melakukan ini, walaupun tidak ada yang rumit tentang itu. Karena keributan, melihat laporan itu cukup dangkal.

Kedua, sangat sulit bagi saya, sebagai orang yang tidak terbiasa dengan proyek, untuk memahami kode. Makro, makro ... Anda harus melihat apa yang dikerahkan untuk memahami mengapa penganalisa menghasilkan peringatan. Sangat keras. Saya tidak suka makro . Seseorang mungkin mengatakan bahwa tanpa makro di C tidak bisa dilakukan. Tetapi GCC belum ditulis dalam bahasa C sama sekali untuk waktu yang lama. Ya, file karena alasan historis memiliki ekstensi .c, tetapi Anda melihat di sana, dan di sana:

//  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;
};
....

Ini jelas bukan C, tetapi C ++.

Secara umum, makro dan gaya pengkodean membuatnya sangat sulit untuk mempelajari laporan penganalisa. Jadi kali ini saya tidak akan menyenangkan daftar panjang berbagai kesalahan. Dengan susah payah dan menggunakan beberapa cangkir kopi, saya menulis 10 fragmen menarik, dan ini membuat saya sendirian :).

10 cuplikan kode yang mencurigakan


Fragmen N1, Sepertinya Copy-Paste tidak berhasil

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 ();
  ....
}

Peringatan PVS-Studio: V778 Dua fragmen kode serupa ditemukan. Mungkin, ini adalah kesalahan ketik dan variabel 's2' harus digunakan daripada 's'. cfgcleanup.c 2126

Bahkan, saya tidak yakin apakah ini suatu kesalahan. Namun, saya memiliki kecurigaan kuat bahwa kode ini ditulis menggunakan Copy-Paste, dan di blok kedua di satu tempat mereka lupa mengganti s dengan s2 . Artinya, menurut saya blok kode kedua harus seperti ini:

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

Fragmen N2, Typo

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

PVS-Studio Warning: V519 Variabel 'vr1.set' diberi nilai dua kali berturut-turut. Mungkin ini sebuah kesalahan. Periksa baris: 3448, 3449. tree-ssa-sccvn.c 3449

Sangat aneh bahwa nilai yang berbeda ditulis ke variabel yang sama dua kali berturut-turut. Ini adalah kesalahan ketik yang jelas. Di sebelah file ini adalah kode ini:

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

Kemungkinan besar, dalam kode yang mencurigakan seharusnya ditulis persis sama.

Fragmen N3, Menetapkan variabel untuk dirinya sendiri

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;
    }
  ....
}

Peringatan PVS-Studio: V570 Variabel 'ctx-> outer_reduction_clauses' ditugaskan untuk dirinya sendiri. omp-low.c 935

Sangat aneh untuk menetapkan variabel ke dirinya sendiri.

Fragmen N4. 0,1,2, Freddy akan menjemput Anda.

Baru-baru ini saya menerbitkan sebuah artikel, " Nol, Satu, Dua, Freddy Will Take You ." Sepertinya saya bahwa potongan kode berikut melanjutkan kumpulan kesalahan yang dibahas dalam artikel ini.

#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);
  ....
}

Peringatan PVS-Studio: V560 Bagian dari ekspresi kondisional selalu salah:
((machine_mode) (xop1) -> mode) == xmode1. optabs.c 1053

Perhatikan dua sub-ekspresi ini:

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

Operasi DAN dilakukan pada hasil sub-ekspresi ini, yang jelas tidak memiliki arti praktis. Sebenarnya, jika subekspresi kedua mulai dijalankan, maka diketahui sebelumnya bahwa itu akan menghasilkan false .

Kemungkinan besar, ada kesalahan ketik di sini dalam nol dan satu, dan sebenarnya kondisinya seharusnya seperti ini:

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

Tentu saja, saya tidak yakin apakah saya mengubah kode dengan benar, karena saya tidak dipandu dalam proyek ini.

Fragmen N5. Perubahan nilai argumen yang mencurigakan

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;
}

Peringatan PVS-Studio: V763 Parameter 'mati' selalu ditulis ulang di badan fungsi sebelum digunakan. ipa-polymorphic-call.c 766

Nilai argumen tidak aktif segera diganti dengan 0. Selain itu, tidak ada komentar yang jelas. Semua ini sangat mencurigakan. Terkadang kode ini muncul saat debugging. Programmer perlu melihat bagaimana fungsi berperilaku dalam mode tertentu, jadi dia sementara mengubah nilai argumen, dan kemudian mereka lupa untuk menghapus baris ini. Akibatnya, kesalahan muncul dalam kode. Tentu saja, semuanya mungkin ada di sini, tetapi kode ini jelas perlu diperiksa dan diklarifikasi untuk memastikan bahwa pertanyaan serupa tidak muncul di masa depan.

Fragmen N6. Hal kecil

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 warning: V519 Variabel 'new_node-> merged_comdat' diberi nilai dua kali berturut-turut. Mungkin ini sebuah kesalahan. Periksa baris: 406, 409. cgraphclones.c 409 Tugas

digandakan secara acak. Kemungkinan besar tidak ada yang salah. Namun, selalu ada risiko bahwa dalam kenyataannya mereka lupa melakukan beberapa tugas lainnya.

Fragmen N7. Kode yang terlihat berbahaya

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)
  ....
}

Peringatan PVS-Studio: V595 Pointer 'm-> komponen' digunakan sebelum diverifikasi terhadap nullptr. Periksa baris: 407, 415. genmodes.c 407

Pada awalnya, penunjuk komponen m-> didereferensi dalam salah satu cabang pernyataan if . Maksud saya ungkapan ini: m-> component-> bytesize .

Lebih lanjut ternyata bahwa pointer ini mungkin nol. Ini mengikuti dari cek: if (m-> komponen) .

Kode ini tidak selalu salah. Sangat mungkin bahwa cabang dereferenced hanya dieksekusi jika pointer tidak nol. Artinya, ada hubungan tidak langsung antara nilai variabel m-> cl dan m-> komponen. Tetapi kode ini terlihat sangat berbahaya. Dan tidak ada komentar penjelasan.

Fragmen N8. Periksa ulang

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);
}

Peringatan PVS-Studio: V501 Ada sub-ekspresi identik 'wi_zero_p (ketik, lh_lb, lh_ub)' di kiri dan di kanan '' || ' operator. range-op.cc 2657

Semacam cek aneh. Fungsi wi_zero_p dipanggil dua kali dengan set argumen aktual yang sama. Orang mungkin menduga bahwa pada kenyataannya, panggilan kedua harus menggunakan argumen yang diterima dari luar: rh_lb , rh_ub. Tapi tidak, argumen ini ditandai sebagai tidak digunakan ( ATTRIBUTE_UNUSED ).

Oleh karena itu, tidak jelas bagi saya mengapa tidak menulis cek lebih sederhana:

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

Atau ada kesalahan ketik di sini? Atau kesalahan logis? Saya tidak tahu, tetapi kodenya sangat aneh.

Fragmen N9. Akses array berbahaya

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 Warning: V781 Nilai dari variabel 'best_alg-> ops' diperiksa setelah digunakan. Mungkin ada kesalahan dalam logika program. Periksa baris: 3157, 3164. expmed.c 3157

Mari kita persingkat kode untuk memperjelas apa yang tidak disukai penganalisis:

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

Pada awalnya, variabel best_alg-> ops digunakan untuk mengindeks array. Dan hanya dengan demikian variabel ini memeriksa nilai batas. Secara teoritis, overflow array dapat terjadi (jenis kesalahan klasik CWE-193: Kesalahan Off-by-one ).

Apakah ini kesalahan nyata? Dan karena ini terus-menerus terjadi di artikel ini, saya tidak yakin :). Mungkin ada hubungan antara nilai indeks ini dan variabel cache_hit . Mungkin tidak ada yang di-cache jika indeksnya maksimum ( MAX_BITS_PER_WORD ). Kode fungsinya besar, dan saya tidak mengetahuinya.

Bagaimanapun, kode ini paling baik diperiksa. Dan bahkan jika ternyata benar, saya akan merekomendasikan untuk menyertai bagian yang dipertimbangkan dari program dengan komentar. Itu bisa membingungkan tidak hanya saya atau PVS-Studio, tetapi juga orang lain.

Fragmen N10. Kode yang belum diperbaiki

dalam 4 tahun. Pada artikel terakhir , saya menarik perhatian pada kode ini:

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));
  ....
}

Peringatan PVS-Studio: V501 Ada sub-ekspresi yang identik '! Strcmp (a-> v.val_vms_delta.lbl1, b-> v.val_vms_delta.lbl1)' di kiri dan di kanan operator '&&'. dwarf2out.c 1481

Dua fungsi strcmp membandingkan pointer yang sama. Yaitu, pemeriksaan yang jelas berlebihan dilakukan. Dalam artikel sebelumnya, saya menyarankan itu salah ketik, dan harus ditulis:

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));

Namun, selama 4 tahun kode ini belum diperbaiki. Pada saat yang sama, kami memberi tahu penulis tentang bagian kode yang mencurigakan yang kami jelaskan di artikel. Sekarang saya tidak begitu yakin bahwa ini adalah kesalahan. Mungkin ini hanya kode yang berlebihan. Dalam hal ini, ekspresi dapat disederhanakan:

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

Mari kita lihat apakah pengembang GCC akan mengubah kode ini setelah artikel baru.

Kesimpulan


Saya mengingatkan Anda bahwa Anda dapat menggunakan opsi lisensi gratis ini untuk memeriksa proyek terbuka . Omong-omong, ada opsi lain untuk lisensi bebas PVS-Studio, termasuk bahkan untuk proyek tertutup. Mereka tercantum di sini: " Opsi Lisensi PVS-Studio Gratis ".

Terimakasih atas perhatiannya. Dan baca blog kami . Ada banyak hal menarik.

Artikel kami yang lain tentang memeriksa kompiler


  1. Cek LLVM (Dentang) (Agustus 2011), cek kedua (Agustus 2012), cek ketiga (Oktober 2016), cek keempat (April 2019)
  2. Ulasan GCC (Agustus 2016)
  3. Periksa Huawei Ark Compiler (Desember 2019)
  4. Verifikasi Platform .NET Compiler Platform ("Roslyn") (Desember 2015), inspeksi kedua (April 2019)
  5. Ulasan Roslyn Analyzers (Agustus 2019)
  6. Memeriksa PascalABC.NET (Maret 2017)



Jika Anda ingin berbagi artikel ini dengan audiens yang berbahasa Inggris, silakan gunakan tautan ke terjemahan: Andrey Karpov. Memeriksa GCC 10 Compiler dengan PVS-Studio .

All Articles