PVS-Studio เป็นโปรแกรมที่ค้นหาจุดบกพร่องในซอร์สโค้ดของโปรเจ็กต์ C++ และ C# ที่คอมไพลเลอร์ไม่สามารถมองเห็นได้ แต่เกือบจะแน่ใจว่าเป็นความผิดพลาดในการเขียนโปรแกรม

หมายเหตุ บทความนี้เดิมที "เผยแพร่" เป็นภาษารัสเซียในบล็อก blog.harrix.org เวอร์ชันต้นฉบับและฉบับแปลถูกโพสต์บนเว็บไซต์ของเราโดยได้รับอนุญาตจากผู้เขียน

การแนะนำ

ฉันได้รับการติดต่อจากทีมงาน PVS-Studio เพื่อขอข้อเสนอการทำงานร่วมกัน ฉันได้อ่านเกี่ยวกับผลิตภัณฑ์ของพวกเขามามากในหน้า Habrahabr แต่ไม่เคยลองเลย ดังนั้นฉันจึงแนะนำสิ่งต่อไปนี้: พวกเขาจะให้ใบอนุญาตผลิตภัณฑ์แก่ฉัน และฉันจะสแกนโปรแกรมของฉันและเขียนบทวิจารณ์เครื่องมือ โดยฉันจะหารือเกี่ยวกับวิธีที่ฉันใช้เครื่องวิเคราะห์ วิธีตรวจสอบรหัส และอื่นๆ . พวกเขาบอกว่าใช่

ดังนั้น สิ่งที่คุณจะเห็นในบทความนี้ เนื่องจากการเรียกกันทั่วไปในทุกวันนี้ คือการทบทวนอย่างตรงไปตรงมาโดยปราศจากการเย็บปักถักร้อยใดๆ โดยโปรแกรมเมอร์ทั่วๆ ไปที่เกี่ยวข้องกับการเขียนโปรแกรม "เชิงวิชาการ" มากกว่าการเขียนโปรแกรมแอปพลิเคชัน กล่าวอีกนัยหนึ่ง ฉันไม่ใช่กูรูจากบริษัทขนาดใหญ่บางแห่งที่ทำงานในโครงการที่ซับซ้อน ซึ่งรู้วิธีใช้ยูทิลิตี้มากมายและเก่งในการปรับแต่งคอมไพเลอร์ และทั้งหมดนั้น

อีกสิ่งหนึ่งที่คุณควรรู้คือฉันเป็นแฟนตัวยงของการเขียนโปรแกรมเชิงฟังก์ชันเมื่อไม่กี่ปีก่อน ฉันไม่ชอบ OOP ไม่เคยใช้เนมสเปซ คิดค้นวงล้อใหม่หลายครั้ง และอื่นๆ ตอนนี้ฉันกำลังนึกถึงช่วงเวลาในชีวิตของฉันว่าเป็นฝันร้ายและเขียนโปรแกรมหลายโปรแกรมของฉันในสมัยนั้นใหม่อย่างจริงจัง แม้ว่าโปรแกรมเหล่านั้นจะยังไม่พร้อมสำหรับการวิเคราะห์แบบคงที่ก็ตาม ด้วยเหตุผลดังกล่าว ฉันจะนำโปรเจ็กต์จากการเขียนโปรแกรมฟังก์ชันในอดีตของฉัน (ดูทั้งหมดได้ใน GitHub) เพื่อการวิเคราะห์ แม้ว่าฟังก์ชันการทำงานจะครอบงำอยู่ที่นั่น แต่ฉันก็ระมัดระวังอย่างมากเกี่ยวกับการเขียนโค้ด การทดสอบ และการจัดทำเอกสาร ดังนั้นฉันไม่คิดว่าจะมีข้อบกพร่องร้ายแรงมากมายในโปรเจ็กต์เหล่านั้น

ไปเลย.

การติดตั้ง

การติดตั้งไม่ได้ทำให้เกิดปัญหาใดๆ มีปุ่มขนาดใหญ่ “ดาวน์โหลดและลอง” บน “หน้าแรก” ของไซต์ PVS-Studio ซึ่งจะพาคุณไปยังหน้าที่มีลิงก์ดาวน์โหลดที่คุณไม่ควรพลาด

การติดตั้งเป็นมาตรฐานโดยสิ้นเชิง ไม่มีตัวเลือกพิเศษให้เลือกด้วยซ้ำ อย่างไรก็ตาม ในบทความของฉัน ฉันพยายามอธิบายแม้กระทั่งขั้นตอนที่ง่ายที่สุดเสมอ ดังนั้นนี่คือภาพหน้าจอ:

กระบวนการติดตั้ง PVS-Studio

ขั้นตอนที่ 1.

ขั้นตอนที่ 2.

ขั้นตอนที่ 3

ขั้นตอนที่ 4

ขั้นตอนที่ 5

ขั้นตอนที่ 6

ขั้นตอนที่ 7

มันล้มเหลวทั้งหมดอย่างไร

ฉันกำลังบอกทันทีว่าฉันไม่ได้อ่านเอกสารใด ๆ ในตอนแรก ฉันเพิ่งติดตั้งโปรแกรมและคิดว่า “แล้วจะทำยังไงต่อไป” ฉันพบรายการใหม่ต่อไปนี้ในเมนู 'เริ่ม':

สัญชาตญาณบอกฉันว่ารายการที่ฉันต้องการควรมีชื่อเดียวกันกับโปรแกรม คลิก. และที่นี่ทำให้ฉันล้มเหลวและแสดงข้อความนี้:

จริงๆ แล้วฉันเริ่มกังวลไม่น้อย คุณเห็นไหมว่าส่วนใหญ่ฉันทำงานใน Qt และเก็บ Visual Studio ไว้เป็นโปรแกรมการสอนสำหรับนักเรียนของฉัน

ตกลง. บางทีฉันควรลองรายการเมนูอื่น สแตนด์อโลน

นั่นดีกว่า. ตอนนี้หมายเหตุด้านที่สำคัญ สิ่งที่ฉัน คาดหวัง ว่าเป็นอัลกอริธึมในการทำงานกับตัววิเคราะห์คือ: ฉันเปิดมัน โหลดไฟล์ต้นฉบับของโปรเจ็กต์ และพบจุดบกพร่องสำหรับฉัน สมมติฐานนี้กลับกลายเป็นว่าผิดอย่างสิ้นเชิง แต่เราจะพูดถึงเรื่องนั้นในภายหลัง

ในตอนแรกฉันพยายามโหลดไฟล์ใดไฟล์หนึ่งของฉัน (ฉันกังวลว่ามันจะอนุญาตให้ฉันเลือกได้ทีละไฟล์เท่านั้น)

นี่แน่ะ แต่จะเป็นยังไงต่อไปล่ะ? ไม่มีปุ่มขนาดใหญ่หรือมีสีสันอีกต่อไป

มีเพียงรายการเดียวในเมนูหลักที่ดูเหมือนว่าฉันต้องการ:

การคลิกที่มันจะเปิดหน้าต่างต่อไปนี้

และนี่คือจุดที่ฉันทำท่างี่เง่า แทนที่จะอ่านข้อความ ฉันเริ่มคลิกปุ่มต่างๆ เมื่อฉันคลิก เลือก โปรแกรมขอไฟล์ *.suppress บางไฟล์ ซึ่งเห็นได้ชัดว่าไม่ใช่สิ่งที่ฉันต้องการ คำว่า คอมไพเลอร์ สะดุดตาฉัน ตกลง ฉันควรคลิกที่ เริ่มการตรวจสอบ

ฉันคิดว่าโปรแกรมกำลังสแกนคอมพิวเตอร์ของฉันเพื่อหาคอมไพเลอร์ ดังนั้นจึงควรใช้เวลานานพอสมควร และมันก็เป็นเช่นนั้นจริงๆ (ฉันรอมาหลายชั่วโมง) แต่ฉันดีใจที่เห็นว่ามันเริ่มพบบางสิ่งในที่สุด:

หลังจากนั้นไม่นานฉันก็รู้สาเหตุ: ฉันทำงานกับโปรเจ็กต์ของฉันและรวบรวมมันในขณะที่กระบวนการตรวจสอบกำลังทำงานอยู่

สองสามชั่วโมงต่อมา ฉันรู้สึกว่าเครื่องมือนี้พบคอมไพเลอร์เพียงพอแล้วจึงหยุดทำงาน อย่างไรก็ตาม มันไม่ได้แสดงผลลัพธ์ใดๆ ฉันจะทำอย่างไร? ให้ตายเถอะ ตอนนี้ฉันต้องอ่านเอกสารแล้ว (

ลิงก์ที่เกี่ยวข้องนั้นดูไม่ชัดเจนนัก

หลังจากอ่านบทความนี้แล้ว ในที่สุดฉันก็รู้ว่าต้องทำอย่างไร

ทุกอย่างทำงานอย่างไร

นี่คือวิธีการทำงานของเครื่องวิเคราะห์จริง ๆ

คุณเริ่มกระบวนการตรวจสอบใน PVS-Studio จากนั้นรันคอมไพลเลอร์ในโปรเจ็กต์ของคุณ เมื่อการคอมไพล์เสร็จสิ้น ให้หยุดกระบวนการตรวจสอบ และรอสักครู่เพื่อให้โปรแกรมส่งออกบันทึกการวิเคราะห์

ฉันจะแสดงให้คุณเห็นว่ามันทำงานอย่างไรโดยใช้แอปพลิเคชันทดสอบ Qt 5.7 กับ MinGW ซึ่งใช้ไลบรารี Harrix MathLibrary ของฉันเป็นตัวอย่าง

ไปที่เมนูการวิเคราะห์

เริ่มการตรวจสอบการเปิดตัวคอมไพเลอร์

กระบวนการตรวจสอบสามารถทำงานในเบื้องหลังได้

รวบรวมโครงการ:

PVS-Studio ตรวจพบอินสแตนซ์ที่เปิดตัวของคอมไพเลอร์ของเรา

หยุดการตรวจสอบ

และที่นี่ PVS-Studio ก็ส่งคำเตือนมากมาย สาปแช่ง. ฉันหวังว่าจะได้ผลลัพธ์ที่ดีขึ้น ((

การคลิกสองครั้งที่คำเตือนจะนำคุณไปยังไฟล์ต้นฉบับที่เกี่ยวข้องซึ่งพบจุดบกพร่อง

ในที่สุดเมื่อคุณเข้าใจแนวคิดเบื้องหลังโปรแกรมแล้ว การทำงานกับโปรแกรมจะกลายเป็นเรื่องง่าย แต่ก็ไม่ง่ายนักสำหรับผู้เริ่มต้น

ตอนนี้เรามาดูกันว่าเรามีข้อบกพร่องอะไรบ้าง พวกมันเป็นแมลงจริงๆเหรอ?

คำเตือน เมื่อเริ่มคอมไพเลอร์ ให้สร้างโปรเจ็กต์ใหม่ทั้งหมด เมื่อเร็วๆ นี้ ฉันรู้สึกเศร้ากับคำเตือน 71 แต่หลังจากที่ฉันแก้ไขและสร้างโครงการใหม่ เครื่องมือดังกล่าวก็ออกคำเตือนเพิ่มอีก 1900

ตอนนี้ฉันรู้สึกอยากสาปแช่ง

การวิเคราะห์ข้อผิดพลาด

เราได้เดินตามเส้นทางแห่งการเสด็จขึ้นสู่สวรรค์ของฉันเพื่อทำความเข้าใจวิธีใช้โปรแกรม ทีนี้มาดูผลการวิเคราะห์กันดีกว่า

ฉันไม่ค่อยสนใจข้อบกพร่องที่พบใน Qt มากนัก — มันเป็นความรับผิดชอบของผู้พัฒนาคอมไพเลอร์

แล้วความผิดพลาดของตัวเองล่ะ?

คำเตือนมากกว่า 1900 รายการส่วนใหญ่เป็น V550คำเตือน:

V550. การเปรียบเทียบที่แม่นยำอย่างแปลกประหลาด ควรใช้การเปรียบเทียบด้วยความแม่นยำที่กำหนดไว้: fabs(A — B) ‹ Epsilon หรือ fabs(A — B) › Epsilon

และฉันเห็นด้วยกับคำเตือนนั้นในกรณีส่วนใหญ่ ตัวอย่างเช่น รหัสต่อไปนี้ที่มี (F[i]==F[i+1])อาจทำให้เกิดปัญหา:

//identical elements
//are assigned identical ranks as arithmetic mean
for (i=0;i<VHML_N-1;i++)
{
if (F[i]==F[i+1])
  {
  j=i+1;
  while ((F[i]==F[j])&&(j<VHML_N)) j++;
  Sn=HML_SumOfArithmeticalProgression(i+1,1,j-i);
  Sn/=double(j-i);
  for (k=0;k<VHML_N;k++)
   if (Fitness[k]==F[i]) VHML_ResultVector[k]=Sn;
  i=j-1;
  }
}

ความคิดที่แย่ยิ่งกว่านั้นคือการตรวจสอบตำแหน่งสุดขีดของวงล้อของ Maxwell เหมือนที่ทำในโค้ดที่น่ากลัวต่อไปนี้:

//if the wheel is in extreme positions,
if (((x==R)&&(v<0))||((x==l)&&(v>0))) v=-v*(1.-k);

และนี่คือสิ่งที่ฉันได้รับจากส่วนต่อไปนี้

//Calculating arithmetic mean of two samples
xn=HML_Mean(x,VHML_N);
yn=HML_Mean(x,VHML_N);

ตัวแปร V656 'xn', 'yn' ได้รับการเตรียมใช้งานผ่านการเรียกไปยังฟังก์ชันเดียวกัน อาจเป็นข้อผิดพลาดหรือรหัสที่ไม่ได้รับการปรับให้เหมาะสม ลองตรวจสอบนิพจน์ 'HML_Mean(x, VHML_N)' ตรวจสอบบรรทัด: 3712, 3713. harrixmathlibrary.h 3713

มันเป็นข้อผิดพลาดที่ค่อนข้างน่าผิดหวัง ฉันต้องคัดลอกส่วนของโค้ดแต่ลืมเปลี่ยนโทเค็นบางส่วน

ความผิดพลาดโง่ๆ อีก

int VHML_Result=0;
    if (VHML_N1==VHML_N2)
        for (int i=0;i<VHML_N1;i++)
            if (a[i]!=b[i]) VHML_Result=-1;
            else
                VHML_Result=-1;

V523 คำสั่ง 'then' เทียบเท่ากับคำสั่ง 'else' harrixmathlibrary.h 695

ฟังก์ชันนี้จะให้คำตอบเชิงบวกเกี่ยวกับโซลูชันที่มีอยู่เสมอ ฉันไม่เคยคิดเลยว่าอะไรทำให้ฉันทำลายการคำนวณทั้งหมดของตัวแปร solutionis ที่ส่วนท้ายของฟังก์ชัน

double HML_LineTwoPoint(double x, double x1, double y1,
                        double x2, double y2, int *solutionis)
{
/*
This function is a two-point linear equation.
Value of y is returned for given x.
Input parameters:
 x - abscissa of point in question;
 x1 - abscissa of first point;
 y1 - ordinate of first point;
 x2 - abscissa of second point;
 y2 - ordinate of second point;
 solutionis - stores the returned solution:
  0 - no solution;
  1 - solution found;
  2 - any number is a solution (the line is parallel to y-axis).
Return value:
 Value of y for given x.
*/
double y=0;
 
if ((x1==x2)&&(y1==y2))
{
  //this is the same point, so any number is a solution
  y=y1;
  *solutionis=2;
}
else
{
  if (y1==y2)
  {
    //this line is parallel to x-axis
    y=y1;
    *solutionis=1;
  }
  else
  {
    if (x1==x2)
    {
      //this line is parallel to y-axis
      if (x==x1)
      {
        y=y1;
        *solutionis=2;
      }
      else
      {
        y=0;
        *solutionis=0;
      }
    }
    else
    {
      y=(x-x1)*(y2-y1)/(x2-x1)+y1;
    }
  }
}
 
*solutionis=1;
return y;
}

V519 ตัวแปร '* solutionis' ได้รับการกำหนดค่าสองครั้งติดต่อกัน บางทีนี่อาจเป็นความผิดพลาด ตรวจสอบบรรทัด: 1788, 1821. harrixmathlibrary.cpp 1821

คำเตือนถัดไปเกี่ยวข้องกับการที่ฉันระมัดระวังมากเกินไปมากกว่าเป็นข้อผิดพลาดจริง: ฉันตั้งค่าตัวแปรสรุปให้เป็นศูนย์ก่อน ในกรณี:

if (VHML_N>0) VHML_Result=0;
 
...
 
//Evaluating real-vector objective function
VHML_Result=VHML_TempFunction(VHML_TempDouble3,RealLength);
 
return VHML_Result;

V519 ตัวแปร 'VHML_Result' ได้รับการกำหนดค่าสองครั้งติดต่อกัน บางทีนี่อาจเป็นความผิดพลาด ตรวจสอบบรรทัด: 385, 395. harrixmathlibrary.cpp 395

PVS-Studio ยังพบฟังก์ชันที่เหมือนกันสองฟังก์ชันในโค้ดของฉัน (ตอนนั้นฉันไม่ชอบ std) นอกจากสองสิ่งนี้แล้ว ยังพบอีกสองสามอย่างซึ่งมีประโยชน์มากเมื่อคุณมีโปรเจ็กต์ขนาดใหญ่ที่มีฟังก์ชันมากมาย และจำไม่ได้ว่าคุณได้ใช้ฟังก์ชันนี้หรือฟังก์ชันนั้นไปแล้วหรือไม่

template <class T> void HML_Swap(T &a, T &b)
{
/*
This function swaps values of two numbers.
Input parameters:
a - first number;
b - second number.
Return value:
None.
*/
T x;
x = b;
b = a;
a = x;
}
 
template <class T> void HML_NumberInterchange(T &a, T &b)
{
/*
This function swaps values of two numbers.
Input parameters:
a - first number;
b - second number.
Return value:
None.
*/
T x;
x = b;
b = a;
a = x;
}

V524 เป็นเรื่องแปลกที่เนื้อหาของฟังก์ชัน 'HML_Swap' เทียบเท่ากับเนื้อหาของฟังก์ชัน 'HML_NumberInterchange' โดยสมบูรณ์ harrixmathlibrary.h 2349

และนี่คือข้อผิดพลาดแบบคลาสสิกที่เกี่ยวข้องกับการแปลงประเภทที่ขาดหายไป

double HML_TestFunction_HyperEllipsoid(double *x, int VHML_N)
{
/*
Function of multiple variables: Hyperellipsoid.
Test function for real optimization.
Input parameters:
x - pointer to original array;
VHML_N - size of array x.
Return value:
Value of test function at point x.
*/
double VHML_Result=0;
 
for (int i=0;i<VHML_N;i++)
VHML_Result += (i+1)*(i+1)*x[i]*x[i];
 
return VHML_Result;
}

V636 นิพจน์ '(i + 1) * (i + 1)' ถูกส่งโดยปริยายจากประเภท 'int' ไปเป็นประเภท 'double' พิจารณาใช้การส่งประเภทที่ชัดเจนเพื่อหลีกเลี่ยงการล้น ตัวอย่าง: double A = (double)(X) * Y;. harrixmathlibrary.cpp 10509

สำหรับโค้ดนี้ ตัววิเคราะห์ออกคำเตือนที่ผิดพลาด เนื่องจาก HML_ProportionalSelectionV2ส่งคืนค่าสุ่ม:

NumberOfParent1=HML_ProportionalSelectionV2(....);
NumberOfParent2=HML_ProportionalSelectionV2(....);

ตัวแปร V656 'NumberOfParent1', 'NumberOfParent2' ได้รับการเตรียมใช้งานผ่านการเรียกไปยังฟังก์ชันเดียวกัน อาจเป็นข้อผิดพลาดหรือรหัสที่ไม่ได้รับการปรับให้เหมาะสม ตรวจสอบบรรทัด: 1106, 1107. harrixmathlibrary.cpp 1107

พบปัญหาหลายประการในไลบรารี Harrix QtLibrary

ตัวอย่างเช่น มีฟังก์ชันสำหรับแยกสตริงออกเป็นพยางค์ เครื่องมือนี้ให้คำแนะนำที่ดีว่าฉันควรรวมเงื่อนไขเข้าด้วยกัน

if ((i>=1)&&(i!=N-1))
{
  if ((HQt_GetTypeCharRus(S.at(i-1))==3) &&
     (HQt_GetTypeCharRus(S.at(i))!=0)    &&
     (HQt_GetTypeCharRus(S.at(i+1))!=0))
    cut=true;
}
 
if ((i>=1)&&(i!=N-1))
{
  if ((HQt_GetTypeCharRus(S.at(i-1))==1) &&
     (HQt_GetTypeCharRus(S.at(i))==1)    &&
     (HQt_GetTypeCharRus(S.at(i+1))!=0))
    cut=true;
}

V581 นิพจน์แบบมีเงื่อนไขของตัวดำเนินการ 'if' ที่อยู่ข้างๆ กันจะเหมือนกัน ตรวจสอบบรรทัด: 1140, 1147. harrixqtlibrary.cpp 1147

การวนซ้ำในส่วนต่อไปนี้ประกอบด้วยตัวแปรบูลีน ใน ซึ่งจะ จริง เสมอ

int VHQt_Result = -1;
    bool in=false;
    int i=0;
 
    while ((i<StringList.count())&&(in!=true))
    {
        if (StringList.at(i)==String)
            VHQt_Result=i;
        i++;
    }
   return VHQt_Result;

V560 ส่วนหนึ่งของนิพจน์เงื่อนไขเป็นจริงเสมอ: (ใน != จริง) harrixqtlibrary.cpp 2342

นอกจากนี้ยังมีแฟรกเมนต์ที่มีโค้ดซ้ำกันเมื่อเติมโมเดลด้วยไอเท็ม:

item = new QStandardItem(QString("HML_RealGeneticAlgorith...."));
model->appendRow(item);
 
item = new QStandardItem(QString("HML_RealGeneticAlgorith...."));
model->appendRow(item);

V760 พบข้อความที่เหมือนกันสองช่วงตึก บล็อกที่สองเริ่มต้นจากบรรทัด 86 mainwindow.cpp 83

คำตัดสิน

ข้อเสีย:

  • โปรแกรมไม่ใช้งานง่าย มันไม่ง่ายเลยที่จะเริ่มต้น ถ้าฉันเข้าไปที่เว็บไซต์ของพวกเขา ดาวน์โหลดเวอร์ชันสาธิตและลองดู ฉันก็คงถอนการติดตั้งมันออกไป ไม่มีอะไรฉลาดไปกว่านี้อีกแล้ว
  • การออกแบบ 'สมัยเก่า'
  • การเน้นไวยากรณ์คล้ายกับสิ่งนั้นใน Notepad++ (และนั่นเป็นข้อดี) แต่ฉันยังคุ้นเคยกับ Notepad++ ที่เน้นอินสแตนซ์อื่นๆ ทั้งหมดของคำหลักที่เลือก รวมถึงการเน้นวงเล็บปิดที่เกี่ยวข้องเมื่อเลือกคำเปิด

ข้อดี:

  • โปรแกรมรู้วิธีการทำงานซึ่งเป็นสิ่งสำคัญที่สุด สามารถตรวจจับข้อบกพร่องหรือคำเตือนที่ซ่อนอยู่มากมายซึ่งคุณไม่น่าจะสังเกตเห็นได้
  • เมื่อคุณทราบวิธีใช้งานแล้ว การทำงานกับเครื่องวิเคราะห์จะกลายเป็นเรื่องง่ายและสะดวกสบาย
  • เครื่องมือนี้รองรับคอมไพเลอร์หลายตัว รวมถึงคอมไพเลอร์ที่ใช้ใน Qt builds

ข้อสรุปสุดท้าย: โปรแกรมนี้ต้องมีอย่างแน่นอน เครื่องมือที่มีประโยชน์มากในการจัดการคุณภาพของโค้ดของคุณ

ป.ล. และฉันหวังว่าจะไม่มีข้อบกพร่อง (

ป.ล. กว่า 1,900 คำเตือน!