Verificarea Clang 11 cu PVS-Studio

(28 octombrie 2020 )

Din când în când, trebuie să scriem articole despre cum am verificat o altă versiune nouă a unui compilator. Nu este foarte distractiv. Cu toate acestea, așa cum arată practica, dacă nu mai facem asta pentru o vreme, oamenii încep să se îndoiască dacă PVS-Studio merită titlul său de bun captator de bug-uri și vulnerabilități. Ce se întâmplă dacă și noul compilator poate face asta? Sigur, compilatoarele evoluează, dar la fel și PVS-Studio – și demonstrează, din nou și din nou, capacitatea sa de a prinde bug-uri chiar și în proiecte de înaltă calitate, cum ar fi compilatoarele.

Timp pentru reverificarea Clang

Pentru a vă spune adevărul, am scris acest articol pe baza în postarea anterioară „ Verificarea compilatorului GCC 10 cu PVS-Studio ”. Deci, dacă unele paragrafe par familiare, este pentru că le-ați citit deja înainte :).

Nu este un secret faptul că compilatoarele folosesc propriile lor analizatoare de cod static încorporate, iar acestea se dezvoltă și ele. De aceea scriem articole din când în când pentru a arăta că analizorul nostru static, PVS-Studio, poate găsi erori chiar și în interiorul compilatoarelor și că ne merită sarea :).

De fapt, nu puteți comparați analizatoare statice clasice cu compilatoare. Analizoarele statice nu numai că detectează erori în codul sursă, ci implică și o infrastructură foarte dezvoltată. În primul rând, include integrarea cu sisteme precum SonarQube, PlatformIO, Azure DevOps, Travis CI, CircleCI, GitLab CI / CD, Jenkins și Visual Studio. Acesta include mecanisme pentru suprimarea în masă a avertismentelor, care vă permite să începeți să utilizați PVS-Studio chiar și într-un proiect mare. Include trimiterea notificărilor prin e-mail. Și așa mai departe și așa mai departe. Însă prima întrebare pe care dezvoltatorii o vor pune în continuare este „PVS-Studio poate găsi ceva ce compilatorii nu pot găsi?” Și asta înseamnă că suntem sortiți să scriem articole despre modul în care verificăm compilatorii înșiși din nou și din nou.

Să revenim la Clang. Nu este nevoie să vă gândiți la acest subiect și să vă spun despre ce este proiectul. De fapt, am verificat nu numai codul Clang 11 în sine, ci și codul bibliotecii LLVM 11 pe care se bazează. Din punctul de vedere al acestui articol, nu contează dacă s-a găsit un defect în codul compilatorului sau al bibliotecii.

Am găsit codul Clang / LLVM mult mai clar decât cel al GCC. Cel puțin nu este plin de toate acele macro-uri îngrozitoare și folosește pe scară largă caracteristicile moderne ale C ++.

Chiar și așa, proiectul este încă suficient de mare pentru a face ca examinarea raportului de analiză să fie plictisitoare fără personalizarea prealabilă. Ceea ce se împiedică în cea mai mare parte sunt pozitive „semi-false”. Prin pozitive „semi-false” mă refer la cazurile în care analizatorul este corect din punct de vedere tehnic pentru a evidenția anumite probleme, dar aceste avertismente nu au niciun folos practic. De exemplu, o mulțime de astfel de avertismente se referă la testele unitare și codul generat.

Iată un exemplu pentru testele unitare:

Spaces.SpacesInParentheses = false; // <=
Spaces.SpacesInCStyleCastParentheses = true; // <=
verifyFormat("Type *A = ( Type * )P;", Spaces);
verifyFormat("Type *A = ( vector )P;", Spaces);
verifyFormat("x = ( int32 )y;", Spaces);
verifyFormat("int a = ( int )(2.0f);", Spaces);
verifyFormat("#define AA(X) sizeof((( X * )NULL)->a)", Spaces);
verifyFormat("my_int a = ( my_int )sizeof(int);", Spaces);
verifyFormat("#define x (( int )-1)", Spaces);// Run the first set of tests again with:
Spaces.SpacesInParentheses = false; // <=
Spaces.SpaceInEmptyParentheses = true;
Spaces.SpacesInCStyleCastParentheses = true; // <=
verifyFormat("call(x, y, z);", Spaces);
verifyFormat("call( );", Spaces);

analyzer ne avertizează că variabilelor li se atribuie aceleași valori pe care le au deja:

  • V1048 Variabilei „Spaces.SpacesInParentheses” i s-a atribuit aceeași valoare. FormatTest.cpp 11554
  • V1048 Variabilei „Spaces.SpacesInCStyleCastParentheses” i s-a atribuit aceeași valoare. FormatTest.cpp 11556

Din punct de vedere tehnic, acest avertisment este la obiect și fragmentul trebuie simplificat sau remediat. Dar este, de asemenea, clar că acest cod este în regulă și nu are rost să remediem nimic în acesta.

Iată un alt exemplu: analizorul afișează o mulțime de avertismente în fișierul autogenerat Options.inc. Uită-te la „peretele” codului pe care îl conține:

Acest volum de cod declanșează o mulțime de avertismente:

  • V501 Există sub-expresii identice la stânga și la dreapta operatorului „==”: nullptr == nullptr Options.inc 26
  • V501 Există sub-expresii identice la stânga și la dreapta operatorului „==”: nullptr == nullptr Options.inc 27
  • V501 There sunt sub-expresii identice la stânga și la dreapta operatorului „==”: nullptr == nullptr Options.inc 28
  • și așa mai departe – un avertisment pe linie …

Totuși, toate acestea nu sunt mare lucru. Acesta poate fi rezolvat prin excluderea fișierelor irelevante din analiză, marcarea anumitor macro-uri și funcții, suprimarea anumitor tipuri de diagnosticare și așa mai departe. Da, se poate, dar nu este o treabă foarte interesantă de făcut atunci când scrieți un articol. De aceea am făcut același lucru ca și în articolul despre verificarea compilatorului GCC: am continuat să citesc raportul până când am adunat 11 exemple interesante de inclus în articol. De ce 11? M-am gândit doar că, deoarece era a 11-a versiune a lui Clang, aveam nevoie de 11 exemple :).

11 fragmente de cod suspecte

Fragment 1, operație modulo pe 1

Acesta este unul cool! Îmi plac bug-urile de acest gen!

void Act() override {
....
// If the value type is a vector, and we allow vector select, then in 50%
// of the cases generate a vector select.
if (isa(Val0->getType()) && (getRandom() % 1)) {
unsigned NumElem =
cast(Val0->getType())->getNumElements();
CondTy = FixedVectorType::get(CondTy, NumElem);
}
....
}

Mesajul de diagnosticare PVS-Studio: V1063 Operația modulo by 1 nu are sens. Rezultatul va fi întotdeauna zero. llvm-stress.cpp 631

Programatorul folosește o operație modulo pentru a obține o valoare aleatorie de 0 sau 1. Dar valoarea 1 pare să confunde dezvoltatorii și faceți-i să scrie clasicul anti-model în care operația modulo este efectuată pe 1 în loc de 2. Operațiunea X% 1 nu are sens, deoarece se evaluează întotdeauna la 0 . Aceasta este versiunea fixă:

if (isa(Val0->getType()) && (getRandom() % 2)) {

Diagnosticul recent adăugat V1063 este extrem de simplu, dar, după cum puteți vedea, funcționează perfect.

Știm că dezvoltatorii de compilatoare ne urmăresc munca și ne împrumută ideile. Este în regulă. Este plăcut să știți că PVS-Studio este forța motrice a progresului . Să vedem cât va fi nevoie pentru ca un diagnostic similar să apară în Clang și GCC :).

Fragment 2, o greșeală de tip

class ReturnValueSlot {
....
bool isNull() const { return !Addr.isValid(); }
....
};static bool haveSameParameterTypes(ASTContext &Context, const FunctionDecl *F1,
const FunctionDecl *F2, unsigned NumParams) {
....
unsigned I1 = 0, I2 = 0;
for (unsigned I = 0; I != NumParams; ++I) {
QualType T1 = NextParam(F1, I1, I == 0);
QualType T2 = NextParam(F2, I2, I == 0);
if (!T1.isNull() && !T1.isNull() && !Context.hasSameUnqualifiedType(T1, T2))
return false;
}
return true;
}

Mesaj de diagnostic PVS-Studio: V501 Există sub-expresii identice la stânga și la dreapta & & operator:! T1.isNull () & &! T1.isNull () SemaOverload.cpp 9493

Verificarea ! T1.isNull () se efectuează de două ori. Aceasta este evident o greșeală de scriere; a doua parte a condiției trebuie să verifice variabila T2 .

Fragment 3, potențial array-index-out-of- bound

std::vector DeclsLoaded;SourceLocation ASTReader::getSourceLocationForDeclID(GlobalDeclID ID) {
....
unsigned Index = ID - NUM_PREDEF_DECL_IDS; if (Index > DeclsLoaded.size()) {
Error("declaration ID out-of-range for AST file");
return SourceLocation();
} if (Decl *D = DeclsLoaded[Index])
return D->getLocation();
....
}

Mesaj de diagnostic PVS-Studio: este posibilă depășirea matricei V557. Indicele „Index” indică dincolo de limitele matricei. ASTReader.cpp 7318

Să presupunem că matricea stochează un element și valoarea variabilei Index este, de asemenea, 1. Apoi condiția (1> 1) este fals și, prin urmare, matricea va fi indexată dincolo de limitele sale. Iată verificarea corectă:

if (Index >= DeclsLoaded.size()) {

Fragment 4, ordin de evaluare a argumentelor

void IHexELFBuilder::addDataSections() {
....
uint32_t SecNo = 1;
....
Section = &Obj->addSection(
".sec" + std::to_string(SecNo++), RecAddr,
ELF::SHF_ALLOC | ELF::SHF_WRITE, SecNo);
....
}

Mesaj de diagnostic PVS-Studio: V567 Comportament nespecificat. Ordinea de evaluare a argumentelor nu este definită pentru funcția „addSection”. Luați în considerare inspectarea variabilei „SecNo”. Object.cpp 1223

Rețineți că argumentul SecNo este utilizat de două ori, iar în același timp se incrementează. Problema este că nu puteți spune în ce ordine exactă vor fi evaluate argumentele. Prin urmare, rezultatul va varia în funcție de versiunea compilatorului sau de parametrii de compilare.

Iată un exemplu sintetic pentru a ilustra acest punct:

#include 
int main()
{
int i = 1;
printf("%d, %d\n", i, i++);
return 0;
}

În funcție de compilator, acest cod poate emite fie „1, 2”, fie „2, 1”. L-am rulat pe Compiler Explorer și am obținut următoarele ieșiri:

  • când a fost compilat cu Clang 11.0.0, programul ieșiri 1 , 1.
  • atunci când este compilat cu GCC 10.2, programul a ieșit s 2, 1.

Interesant, acest caz simplu determină Clang să emită un avertisment:

:6:26: warning:
unsequenced modification and access to "i" [-Wunsequenced]
printf("%d, %d\n", i, i++);

Din anumite motive, însă, acest avertisment nu a fost emis pe codul real. Fie este dezactivat ca fiind unul nu foarte practic, fie acest caz este prea complicat pentru ca compilatorul să poată face față.

Fragment 5, o verificare ciudată duplicat

template <class ELFT>
void GNUStyle::printVersionSymbolSection(const ELFFile *Obj,
const Elf_Shdr *Sec) { ....
Expected NameOrErr =
this->dumper()->getSymbolVersionByIndex(Ndx, IsDefault);
if (!NameOrErr) {
if (!NameOrErr) {
unsigned SecNdx = Sec - &cantFail(Obj->sections()).front();
this->reportUniqueWarning(createError(
"unable to get a version for entry " + Twine(I) +
" of SHT_GNU_versym section with index " + Twine(SecNdx) + ": " +
toString(NameOrErr.takeError())));
}
Versions.emplace_back("");
continue;
}
....
}

Mesaj de diagnostic PVS-Studio: V571 Verificare recurentă. Condiția „if (! NameOrErr)” a fost deja verificată în linia 4666. ELFDumper.cpp 4667

A doua verificare este o clonă a primei și, prin urmare, este redundantă. Poate că ar putea fi îndepărtat în siguranță. Dar ceea ce este mai probabil este că conține o greșeală de scriere și a fost menit să verifice o altă variabilă.

Fragment 6, potențială dereferință a indicatorului nul

void RewriteObjCFragileABI::RewriteObjCClassMetaData(
ObjCImplementationDecl *IDecl, std::string &Result)
{
ObjCInterfaceDecl *CDecl = IDecl->getClassInterface(); if (CDecl->isImplicitInterfaceDecl()) {
RewriteObjCInternalStruct(CDecl, Result);
} unsigned NumIvars = !IDecl->ivar_empty()
? IDecl->ivar_size()
: (CDecl ? CDecl->ivar_size() : 0);
....
}

Mesaj de diagnostic PVS-Studio: V595 Pointerul„ CDecl ”a fost utilizat înainte de a fi verificat împotriva nullptr. Linii de verificare: 5275, 5284. RewriteObjC.cpp 5275

La efectuarea primei verificări, dezvoltatorul nu ezită niciodată să renunțe la indicatorul CDecl :

if (CDecl->isImplicitInterfaceDecl())

Dar dacă te uiți la cod câteva linii mai departe, devine clar că indicatorul poate fi nul:

(CDecl ? CDecl->ivar_size() : 0)

Prima verificare a fost probabil menită să arate astfel:

if (CDecl && CDecl->isImplicitInterfaceDecl())

Fragment 7, potențială dereferință a indicatorului nul

bool
Sema::InstantiateClass(....)
{
....
NamedDecl *ND = dyn_cast(I->NewDecl);
CXXRecordDecl *ThisContext =
dyn_cast_or_null(ND->getDeclContext());
CXXThisScopeRAII ThisScope(*this, ThisContext, Qualifiers(),
ND && ND->isCXXInstanceMember());
....
}

Mesaj de diagnostic PVS-Studio: V595 Pointerul „ND” a fost utilizat înainte de a fi verificat împotriva nullptr.Verificați liniile: 2803, 2805. SemaTemplateInstantiate.cpp 2803

Această eroare este similară celei anterioare. Este periculos să dererați un pointer fără o verificare prealabilă atunci când valoarea acestuia este dobândită utilizând un cast dinamic. Mai mult decât atât, codul ulterior confirmă că este necesară o astfel de verificare.

Fragment 8, o funcție care rămâne în funcțiune în ciuda unei stări de eroare

bool VerifyObject(llvm::yaml::Node &N,
std::map Expected) {
....
auto *V = llvm::dyn_cast_or_null(Prop.getValue());
if (!V) {
ADD_FAILURE() << KS << " is not a string";
Match = false;
}
std::string VS = V->getValue(Tmp).str();
....
}

Mesaj de diagnostic PVS-Studio: V1004 Pointerul„ V ”a fost folosit în condiții de siguranță după ce a fost verificat împotriva nullptr. Verificați liniile: 61, 65. TraceTests.cpp 65

Pointerul V poate fi un pointer nul. Aceasta este evident o stare de eroare, care este raportată chiar cu un mesaj de eroare. Dar funcția va continua să ruleze ca și când nu s-ar fi întâmplat nimic și va sfârși prin a dereferenția acel indicator foarte nul. Programatorul a dorit probabil ca funcția să se oprească în acest moment, caz în care ar trebui să fie fixată după cum urmează:

auto *V = llvm::dyn_cast_or_null(Prop.getValue());
if (!V) {
ADD_FAILURE() << KS << " is not a string";
Match = false;
return false;
}
std::string VS = V->getValue(Tmp).str();

Fragment 9, o greșeală de tip

const char *tools::SplitDebugName(const ArgList &Args, const InputInfo &Input,
const InputInfo &Output) {
if (Arg *A = Args.getLastArg(options::OPT_gsplit_dwarf_EQ))
if (StringRef(A->getValue()) == "single")
return Args.MakeArgString(Output.getFilename()); Arg *FinalOutput = Args.getLastArg(options::OPT_o);
if (FinalOutput && Args.hasArg(options::OPT_c)) {
SmallString<128> T(FinalOutput->getValue());
llvm::sys::path::replace_extension(T, "dwo");
return Args.MakeArgString(T);
} else {
// Use the compilation dir.
SmallString<128> T(
Args.getLastArgValue(options::OPT_fdebug_compilation_dir));
SmallString<128> F(llvm::sys::path::stem(Input.getBaseInput()));
llvm::sys::path::replace_extension(F, "dwo");
T += F;
return Args.MakeArgString(F); // <=
}
}

Mesaj de diagnostic PVS-Studio: V1001 „T” variabila este atribuită, dar nu este utilizată până la sfârșitul funcției. CommonArgs.cpp 873

Uită-te la ultimele linii ale funcției. Variabila locală T se modifică, dar nu este utilizată în niciun fel. Aceasta trebuie să fie o greșeală de greșeală și funcția ar trebui să se încheie probabil după cum urmează:

T += F;
return Args.MakeArgString(T);

Fragment 10, zero ca divizorul

typedef int32_t si_int;
typedef uint32_t su_int;typedef union {
du_int all;
struct {
#if _YUGA_LITTLE_ENDIAN
su_int low;
su_int high;
#else
su_int high;
su_int low;
#endif // _YUGA_LITTLE_ENDIAN
} s;
} udwords;COMPILER_RT_ABI du_int __udivmoddi4(du_int a, du_int b, du_int *rem) {
....
if (d.s.low == 0) {
if (d.s.high == 0) {
// K X
// ---
// 0 0
if (rem)
*rem = n.s.high % d.s.low;
return n.s.high / d.s.low;
}
....
}

Mesaje de diagnostic PVS-Studio:

  • V609 Mod de zero. Denominator ‘d.s.low’ == 0. udivmoddi4.c 61
  • V609 Împarte la zero. Denominator ‘d.s.low’ == 0. udivmoddi4.c 62

Nu știu dacă este vorba de o eroare sau de o înșelătorie dificilă, dar codul pare ciudat. Are două variabile întregi regulate, dintre care una este împărțită la cealaltă. Dar partea interesantă este că operația de divizare are loc numai dacă ambele variabile sunt zero. Ce sarcină ar trebui să îndeplinească?

Fragment 11, copiere-lipire

bool MallocChecker::mayFreeAnyEscapedMemoryOrIsModeledExplicitly(....)
{
....
StringRef FName = II->getName();
....
if (FName == "postEvent" &&
FD->getQualifiedNameAsString() == "QCoreApplication::postEvent") {
return true;
} if (FName == "postEvent" &&
FD->getQualifiedNameAsString() == "QCoreApplication::postEvent") {
return true;
}
....
}

Mesaj de diagnostic PVS-Studio: V581 Expresiile condiționale ale instrucțiunilor „if” situate una lângă alta sunt identice. Linii de verificare: 3108, 3113. MallocChecker.cpp 3113

Un fragment de cod a fost clonat, dar nu a mai fost modificat ulterior. Această clonă trebuie fie eliminată, fie modificată pentru a efectua o verificare utilă.

Concluzie

Amintiți-vă că puteți utiliza această licență gratuită opțiune pentru a verifica proiectele open-source. Oferim și alte modalități de a folosi PVS-Studio gratuit, unele dintre ele permitând chiar analiza codului proprietar. Consultați lista completă a opțiunilor aici: „ Modalități de a obține o licență gratuită PVS-Studio ”. Vă mulțumim că ați citit!

Lasă un răspuns

Adresa ta de email nu va fi publicată. Câmpurile obligatorii sunt marcate cu *