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 คำเตือน!