
Minęły ponad dwa lata od ostatniego sprawdzenia kodu projektu LLVM za pomocą naszego analizatora PVS-Studio. Sprawdźmy, czy analizator PVS-Studio nadal jest wiodącym narzędziem do wykrywania błędów i potencjalnych luk. W tym celu sprawdźmy i znajdźmy nowe błędy w wersji LLVM 8.0.0.
Artykuł, który ma zostać napisany
Szczerze mówiąc, nie chciałem pisać tego artykułu. Nie jest interesujące pisać o projekcie, który już wielokrotnie sprawdzaliśmy (, , ). Lepiej napisać o czymś nowym, ale nie mam wyboru.
Za każdym razem, gdy wydawana jest nowa wersja LLVM lub aktualizowana , w naszej skrzynce pocztowej pojawiają się pytania następującego rodzaju:
Zobacz, nowa wersja Clang Static Analyzer nauczyła się znajdować nowe błędy! Wydaje mi się, że zasadność używania PVS-Studio maleje. Clang znajduje więcej błędów niż wcześniej i dogania pod względem możliwości PVS-Studio. Co o tym sądzicie?
Na to zawsze chcę odpowiedzieć coś w stylu:
My też nie siedzimy bezczynnie! Znacznie poprawiliśmy możliwości analizatora PVS-Studio. Nic się nie martwcie, wciąż prowadzimy, tak jak wcześniej.
Niestety, to zła odpowiedź. Nie ma w niej dowodów. I właśnie dlatego piszę ten artykuł. Tak więc, projekt LLVM został po raz kolejny sprawdzony i znaleziono w nim różnorodne błędy. Te, które uznałem za interesujące, teraz zaprezentuję. Te błędy nie mogą być znalezione przez Clang Static Analyzer (lub jest to bardzo niewygodne, aby to zrobić przy jego pomocy). A my możemy. I to znalazłem i zapisałem wszystkie te błędy w jeden wieczór.
Jednak pisanie artykułu zajęło kilka tygodni. Nie mogłem się zmusić do przekształcenia tego w tekst : ).
Swoją drogą, jeśli chcesz wiedzieć, jakie technologie są używane w analizatorze PVS-Studio do wykrywania błędów i potencjalnych luk, to proponuję zapoznać się z tą .
Nowe i stare diagnozy
Jak już wspomniano, około dwóch lat temu projekt LLVM został po raz kolejny sprawdzony, a znalezione błędy naprawione. Teraz w tym artykule przedstawiona zostanie nowa porcja błędów. Dlaczego znaleziono nowe błędy? Jest na to 3 powody:
- Projekt LLVM rozwija się, kod jest modyfikowany, a nowy jest tworzony. Oczywiście w zmienionym i nowym kodzie pojawiają się nowe błędy. To świetnie ilustruje, że statyczna analiza powinna być stosowana regularnie, a nie od czasu do czasu. Nasze artykuły doskonale pokazują możliwości analizatora PVS-Studio, ale nie ma to nic wspólnego z poprawą jakości kodu i obniżeniem kosztów naprawy błędów. Używaj statycznego analizatora kodu regularnie!
- Pracujemy nad ulepszaniem istniejących diagnostyk. Dzięki temu analizator może wykryć błędy, które były niezauważone podczas wcześniejszych sprawdzeń.
- W PVS-Studio pojawiły się nowe diagnostyki, które nie były dostępne 2 lata temu. Postanowiłem wydzielić je do oddzielnej sekcji, aby wyraźnie pokazać rozwój PVS-Studio.
Defekty wykryte przez diagnostyki, które istniały 2 lata temu
Fragment N1: Copy-Paste
static bool ShouldUpgradeX86Intrinsic(Function *F, StringRef Name) {
if (Name == "addcarryx.u32" || // Dodano w 8.0
....
Name == "avx512.mask.cvtps2pd.128" || // Dodano w 7.0
Name == "avx512.mask.cvtps2pd.256" || // Dodano w 7.0
Name == "avx512.cvtusi2sd" || // Dodano w 7.0
Name.startswith("avx512.mask.permvar.") || // Dodano w 7.0 // <=
Name.startswith("avx512.mask.permvar.") || // Dodano w 7.0 // <=
Name == "sse2.pmulu.dq" || // Dodano w 7.0
Name == "sse41.pmuldq" || // Dodano w 7.0
Name == "avx2.pmulu.dq" || // Dodano w 7.0
....
}Ostrzeżenie PVS-Studio: [CWE-570] Są identyczne subwyrażenia 'Name.startswith(«avx512.mask.permvar.»)' po lewej i prawej stronie operatora '||'. AutoUpgrade.cpp 73
Podwójnie sprawdzane jest, że nazwa zaczyna się od podciągu «avx512.mask.permvar.» W drugim sprawdzeniu wyraźnie chciano napisać coś innego, ale zapomniano poprawić skopiowany tekst.
Fragment N2: Literówka
enum CXNameRefFlags {
CXNameRange_WantQualifier = 0x1,
CXNameRange_WantTemplateArgs = 0x2,
CXNameRange_WantSinglePiece = 0x4
};
void AnnotateTokensWorker::HandlePostPonedChildCursor(
CXCursor Cursor, unsigned StartTokenIndex) {
const auto flags = CXNameRange_WantQualifier | CXNameRange_WantQualifier;
....
}Ostrzeżenie PVS-Studio: V501 Są identyczne subwyrażenia 'CXNameRange_WantQualifier' po lewej i prawej stronie operatora '|'. CIndex.cpp 7245
Z powodu literówki ta sama nazwana stała jest używana podwójnie CXNameRange_WantQualifier.
Fragment N3: Zamieszanie z priorytetami operatorów
int PPCTTIImpl::getVectorInstrCost(unsigned Opcode, Type *Val, unsigned Index) {
....
if (ISD == ISD::EXTRACT_VECTOR_ELT && Index == ST->isLittleEndian() ? 1 : 0)
return 0;
....
}Ostrzeżenie PVS-Studio: [CWE-783] Może operator '?:' działa w inny sposób, niż oczekiwano. Operator '?:' ma niższy priorytet niż operator '=='. PPCTargetTransformInfo.cpp 404
Moim zdaniem to bardzo piękny błąd. Tak, wiem, że mam dziwne poczucie piękna :)
Teraz, zgodnie z , wyrażenie jest obliczane w następujący sposób:
(ISD == ISD::EXTRACT_VECTOR_ELT && (Index == ST->isLittleEndian())) ? 1 : 0Z praktycznego punktu widzenia, taki warunek nie ma sensu, ponieważ można go skrócić do:
(ISD == ISD::EXTRACT_VECTOR_ELT && Index == ST->isLittleEndian())To jest ewidentny błąd. Prawdopodobnie 0/1 chciano porównać z zmienną Index. Aby naprawić kod, należy dodać nawiasy wokół operatora ternarnego:
if (ISD == ISD::EXTRACT_VECTOR_ELT && Index == (ST->isLittleEndian() ? 1 : 0))Tak przy okazji, operator ternarny jest bardzo niebezpieczny i prowokuje do błędów logicznych. Bądź bardzo ostrożny z nim i nie bądź chciwy, wrzucając nawiasy. Temat ten omówiłem , w rozdziale „Bądźcie ostrożni z operatorem ?: i otaczajcie go nawiasami”.
Fragment N4, N5: Null pointer
Init *TGParser::ParseValue(Record *CurRec, RecTy *ItemType, IDParseMode Mode) {
....
TypedInit *LHS = dyn_cast(Result);
....
LHS = dyn_cast(
UnOpInit::get(UnOpInit::CAST, LHS, StringRecTy::get())
->Fold(CurRec));
if (!LHS) {
Error(PasteLoc, Twine("can't cast '") + LHS->getAsString() +
"' to string");
return nullptr;
}
....
}Ostrzeżenie PVS-Studio: [CWE-476] Może nastąpić dereferencja wskaźnika null ‘LHS’. TGParser.cpp 2152
Jeśli wskaźnik LHS okaże się nullem, należy zgłosić ostrzeżenie. Jednak zamiast tego dojdzie do dereferencji tego samego wskaźnika null: LHS->getAsString().
To jest typowa sytuacja, kiedy błąd ukrywa się w obsłudze błędów, ponieważ nikt ich nie testuje. Statyczne analizatory sprawdzają cały osiągalny kod, niezależnie od tego, jak często jest używany. To bardzo dobry przykład, jak analiza statyczna uzupełnia inne metody testowania i ochrony przed błędami.
Podobny błąd obsługi wskaźnika RHS popełniono w kodzie nieco niżej: V522 [CWE-476] Może nastąpić dereferencja wskaźnika null ‘RHS’. TGParser.cpp 2186
Fragment N6: Użycie wskaźnika po przeniesieniu
static Expected
ExtractBlocks(....)
{
....
std::unique_ptr ProgClone = CloneModule(BD.getProgram(), VMap);
....
BD.setNewProgram(std::move(ProgClone)); // getFunction(MisCompFunctions[i].first); // <=
assert(NewF && "Function not found??");
MiscompiledFunctions.push_back(NewF);
}
....
}Ostrzeżenie PVS-Studio: V522 [CWE-476] Może nastąpić dereferencja wskaźnika null ‘ProgClone’. Miscompilation.cpp 601
Na początku wskaźnik inteligentny ProgClone przestaje posiadać obiekt:
BD.setNewProgram(std::move(ProgClone));W rzeczywistości, teraz ProgClone — to wskaźnik zerowy. Dlatego poniżej powinno nastąpić dereferencja wskaźnika zerowego:
Function *NewF = ProgClone->getFunction(MisCompFunctions[i].first);Ale tak naprawdę do tego nie dojdzie! Zauważ, że pętla faktycznie się nie wykonuje.
Na początku kontenera MiscompiledFunctions jest czyszczony:
MiscompiledFunctions.clear();Następnie rozmiar tego kontenera jest używany w warunku pętli:
for (unsigned i = 0, e = MisCompFunctions.size(); i != e; ++i) {Łatwo zobaczyć, że pętla się nie uruchamia. Myślę, że to też jest błąd, i kod powinien być napisany inaczej.
Wygląda na to, że napotkaliśmy tę słynną parzystość błędów! Jeden błąd maskuje drugi :).
Fragment N7: Użycie wskaźnika po przeniesieniu
static Expected TestOptimizer(BugDriver &BD, std::unique_ptr Test,
std::unique_ptr Safe) {
outs() << " Optymalizowanie testowanych funkcji: ";
std::unique_ptr Optimized =
BD.runPassesOn(Test.get(), BD.getPassesToRun());
if (!Optimized) {
errs() << " Błąd podczas uruchamiania tej sekwencji przejść"
<< " na programie wejściowym!n";
BD.setNewProgram(std::move(Test)); \/\/ <=
BD.EmitProgressBitcode(*Test, "pass-error", false); \/\/ <=
if (Error E = BD.debugOptimizerCrash())
return std::move(E);
return false;
}
....
}Ostrzeżenie PVS-Studio: V522 [CWE-476] Dereferencja wskaźnika zerowego ‘Test’ może mieć miejsce. Miscompilation.cpp 709
Znowu ta sama sytuacja. Na początku zawartość obiektu jest przenoszona, a następnie jest używana jakby nic się nie stało. Coraz częściej spotykam tę sytuację w kodzie programów, po tym jak w C++ pojawiła się semantyka przenoszenia. To właśnie za to uwielbiam język C++! Pojawiają się coraz to nowe sposoby, by strzelić sobie w stopę. Analityk PVS-Studio zawsze będzie miał zajęcie :).
Fragment N8: Wskaźnik zerowy
void FunctionDumper::dump(const PDBSymbolTypeFunctionArg &Symbol) {
uint32_t TypeId = Symbol.getTypeId();
auto Type = Symbol.getSession().getSymbolById(TypeId);
if (Type)
Printer << "";
else
Type->dump(*this);
}Ostrzeżenie PVS-Studio: V522 [CWE-476] Dereferencja wskaźnika zerowego ‘Type’ może mieć miejsce. PrettyFunctionDumper.cpp 233
Oprócz obsługi błędów, zazwyczaj nie testuje się również funkcji do debugowania wydruków danych. Mamy tu właśnie taki przypadek. Funkcja czeka na użytkownika, który zamiast rozwiązać swoje problemy, będzie zmuszony zająć się jej naprawą.
Poprawnie:
if (Type)
Type->dump(*this);
else
Printer << "";Fragment N9: Wskaźnik zerowy
void SearchableTableEmitter::collectTableEntries(
GenericTable &Table, const std::vector &Items) {
....
RecTy *Ty = resolveTypes(Field.RecType, TI->getType());
if (!Ty) // getAsString() + " vs. " + // getType()->getAsString());
....
}Ostrzeżenie PVS-Studio: V522 [CWE-476] Dereferencja wskaźnika null ‘Ty’ może mieć miejsce. SearchableTableEmitter.cpp 614
Myślę, że to jasne i nie wymaga wyjaśnień.
Fragment N10: Literówka
bool FormatTokenLexer::tryMergeCSharpNullConditionals() {
....
auto &Identifier = *(Tokens.end() - 2);
auto &Question = *(Tokens.end() - 1);
....
Identifier->ColumnWidth += Question->ColumnWidth;
Identifier->Type = Identifier->Type; // <=
Tokens.erase(Tokens.end() - 1);
return true;
}Ostrzeżenie PVS-Studio: Zmienna ‘Identifier->Type’ jest przypisana sama do siebie. FormatTokenLexer.cpp 249
Nie ma sensu przypisywać zmiennej samej siebie. Prawdopodobnie chcieliśmy napisać:
Identifier->Type = Question->Type;Fragment N11: Podejrzany break
void SystemZOperand::print(raw_ostream &OS) const {
switch (Kind) {
break;
case KindToken:
OS << "Token:" << getToken();
break;
case KindReg:
OS << "Reg:" << SystemZInstPrinter::getRegisterName(getReg());
break;
....
}Ostrzeżenie PVS-Studio: [CWE-478] Rozważ sprawdzenie instrukcji ‘switch’. Możliwe, że brakuje pierwszego operatora ‘case’. SystemZAsmParser.cpp 652
Na początku występuje bardzo podejrzany operator break. Czy nie zapomniano tu napisać czegoś jeszcze?
Fragment N12: Sprawdzenie wskaźnika po dereferencji
InlineCost AMDGPUInliner::getInlineCost(CallSite CS) {
Function *Callee = CS.getCalledFunction();
Function *Caller = CS.getCaller();
TargetTransformInfo &TTI = TTIWP->getTTI(*Callee);
if (!Callee || Callee->isDeclaration())
return llvm::InlineCost::getNever("undefined callee");
....
}Ostrzeżenie PVS-Studio: [CWE-476] Wskaźnik ‘Callee’ był używany przed sprawdzeniem przeciwko nullptr. Sprawdź linie: 172, 174. AMDGPUInline.cpp 172
Wskaźnik Callee na początku jest dereferencjonowany w momencie wywołania funkcji getTTI.
A następnie okazuje się, że ten wskaźnik należy sprawdzić na równość nullptr:
if (!Callee || Callee->isDeclaration())Ale już za późno…
Fragment N13 — N…: Sprawdzenie wskaźnika po dereferencji
Sytuacja omówiona w poprzednim fragmencie kodu nie jest unikalna. Występuje tu:
static Value *optimizeDoubleFP(CallInst *CI, IRBuilder &B,
bool isBinary, bool isPrecise = false) {
....
Function *CalleeFn = CI->getCalledFunction();
StringRef CalleeNm = CalleeFn->getName(); // getAttributes();
if (CalleeFn && !CalleeFn->isIntrinsic()) { // <=
....
}Ostrzeżenie PVS-Studio: V595 [CWE-476] Wskaźnik ‘CalleeFn’ był używany przed sprawdzeniem przeciwko nullptr. Sprawdź linie: 1079, 1081. SimplifyLibCalls.cpp 1079
I tutaj:
void Sema::InstantiateAttrs(const MultiLevelTemplateArgumentList &TemplateArgs,
const Decl *Tmpl, Decl *New,
LateInstantiatedAttrVec *LateAttrs,
LocalInstantiationScope *OuterMostScope) {
....
NamedDecl *ND = dyn_cast<NamedDecl>(New);
CXXRecordDecl *ThisContext =
dyn_cast_or_null<CXXRecordDecl>(ND->getDeclContext());
CXXThisScopeRAII ThisScope(*this, ThisContext, Qualifiers(),
ND && ND->isCXXInstanceMember());
....
}Ostrzeżenie PVS-Studio: V595 [CWE-476] Wskaźnik ‘ND’ został użyty zanim potwierdzono, że nie jest nullptr. Sprawdź linie: 532, 534. SemaTemplateInstantiateDecl.cpp 532
I tutaj:
- V595 [CWE-476] Wskaźnik ‘U’ został użyty zanim potwierdzono, że nie jest nullptr. Sprawdź linie: 404, 407. DWARFFormValue.cpp 404
- V595 [CWE-476] Wskaźnik ‘ND’ został użyty zanim potwierdzono, że nie jest nullptr. Sprawdź linie: 2149, 2151. SemaTemplateInstantiate.cpp 2149
A potem przestałem się interesować ostrzeżeniami oznaczonymi V595. Więc nie wiem, czy są jakieś inne podobne błędy, poza wymienionymi tutaj. Najprawdopodobniej są.
Fragment N17, N18: Podejrzany przesunięcie
static inline bool processLogicalImmediate(uint64_t Imm, unsigned RegSize,
uint64_t &Encoding) {
....
unsigned Size = RegSize;
....
uint64_t NImms = ~(Size-1) << 1;
....
}Ostrzeżenie PVS-Studio: [CWE-190] Rozważ sprawdzenie wyrażenia ‘~(Size — 1) << 1’. Przesunięcie bitowe 32-bitowej wartości z późniejszym rozszerzeniem do 64-bitowego typu. AArch64AddressingModes.h 260
Możliwe, że to nie jest błąd i kod działa dokładnie tak, jak zamierzono. Ale to zdecydowanie bardzo podejrzane miejsce i trzeba to sprawdzić.
Załóżmy, że zmienna Size wynosi 16, a więc autor kodu zamierzał uzyskać w zmiennej NImms wartość:
1111111111111111111111111111111111111111111111111111111111100000
Jednak w rzeczywistości uzyskamy wartość:
0000000000000000000000000000000011111111111111111111111111100000
Rzecz w tym, że wszystkie obliczenia odbywają się z użyciem 32-bitowego typu unsigned. I dopiero potem ten 32-bitowy typ bez znaku zostanie niejawnie rozszerzony do uint64_t. Przy czym starsze bity będą zerowe.
Kwestia może być rozwiązana w ten sposób:
uint64_t NImms = ~static_cast<uint64_t>(Size-1) << 1;Podobna sytuacja: V629 [CWE-190] Rozważ sprawdzenie wyrażenia ‘Immr << 6’. Przesunięcie bitowe 32-bitowej wartości z późniejszym rozszerzeniem do 64-bitowego typu. AArch64AddressingModes.h 269
Fragment N19: Pominięto kluczowe słowo inaczej?
void AMDGPUAsmParser::cvtDPP(MCInst &Inst, const OperandVector &Operands) {
....
if (Op.isReg() && Op.Reg.RegNo == AMDGPU::VCC) {
continue;
} if (isRegOrImmWithInputMods(Desc, Inst.getNumOperands())) {
Op.addRegWithFPInputModsOperands(Inst, 2);
} else if (Op.isDPPCtrl()) {
Op.addImmOperands(Inst, 1);
} else if (Op.isImm()) {
OptionalIdx[Op.getImmTy()] = I;
} else {
llvm_unreachable("Nieprawidłowy typ operandu");
}
....
}Ostrzeżenie PVS-Studio: [CWE-670] Rozważ sprawdzenie logiki aplikacji. Możliwe, że brakuje słowa kluczowego ‘else’. AMDGPUAsmParser.cpp 5655
W tej lokacji nie ma błędu. Ponieważ blok then pierwszego if kończy się na continue, to nie jest ważne, czy istnieje słowo kluczowe inaczej czy nie. W każdym razie kod będzie działał tak samo. Niemniej jednak, pominięcie inaczej sprawia, że kod staje się mniej zrozumiały i niebezpieczny. Jeśli w przyszłości continue zniknie, kod zacznie działać zupełnie inaczej. Moim zdaniem lepiej dodać inaczej.
Fragment N20: Cztery podobne błędy typograficzne
LLVM_DUMP_METHOD void Symbol::dump(raw_ostream &OS) const {
std::string Result;
if (isUndefined())
Result += "(undef) ";
if (isWeakDefined())
Result += "(weak-def) ";
if (isWeakReferenced())
Result += "(weak-ref) ";
if (isThreadLocalValue())
Result += "(tlv) ";
switch (Kind) {
case SymbolKind::GlobalSymbol:
Result + Name.str(); \/\/ <=
break;
case SymbolKind::ObjectiveCClass:
Result + "(ObjC Class) " + Name.str(); \/\/ <=
break;
case SymbolKind::ObjectiveCClassEHType:
Result + "(ObjC Class EH) " + Name.str(); \/\/ <=
break;
case SymbolKind::ObjectiveCInstanceVariable:
Result + "(ObjC IVar) " + Name.str(); \/\/ <=
break;
}
OS << Result;
}Ostrzeżenia PVS-Studio:
- V655 [CWE-480] Łańcuchy zostały połączone, ale nie są wykorzystywane. Rozważ zbadanie wyrażenia 'Result + Name.str()'. Symbol.cpp 32
- V655 [CWE-480] Łańcuchy zostały połączone, ale nie są wykorzystywane. Rozważ zbadanie wyrażenia 'Result + «(ObjC Class) » + Name.str()'. Symbol.cpp 35
- V655 [CWE-480] Łańcuchy zostały połączone, ale nie są wykorzystywane. Rozważ zbadanie wyrażenia 'Result + «(ObjC Class EH) » + Name.str()'. Symbol.cpp 38
- V655 [CWE-480] Łańcuchy zostały połączone, ale nie są wykorzystywane. Rozważ zbadanie wyrażenia 'Result + «(ObjC IVar) » + Name.str()'. Symbol.cpp 41
Przypadkowo zamiast operatora += użyty został operator +. W rezultacie powstają konstrukcje pozbawione sensu.
Fragment N21: Niezdefiniowane zachowanie
static void getReqFeatures(std::map<StringRef, int> &FeaturesMap,
const std::vector<Record *> &ReqFeatures) {
for (auto &R : ReqFeatures) {
StringRef AsmCondString = R->getValueAsString("AssemblerCondString");
SmallVector<StringRef, 4> Ops;
SplitString(AsmCondString, Ops, ",");
assert(!Ops.empty() && "AssemblerCondString cannot be empty");
for (auto &Op : Ops) {
assert(!Op.empty() && "Empty operator");
if (FeaturesMap.find(Op) == FeaturesMap.end())
FeaturesMap[Op] = FeaturesMap.size();
}
}
}Spróbuj samodzielnie znaleźć niebezpieczny kod. A to obrazek, by odwrócić uwagę, aby natychmiast nie zajrzeć do odpowiedzi:

Ostrzeżenie PVS-Studio: [CWE-758] Użyto niebezpiecznej konstrukcji: 'FeaturesMap[Op] = FeaturesMap.size()', gdzie 'FeaturesMap' jest klasy 'map'. Może to prowadzić do niezdefiniowanego zachowania. RISCVCompressInstEmitter.cpp 490
Problemowa linia:
FeaturesMap[Op] = FeaturesMap.size();Jeśli element Op nie został znaleziony, nowy element zostaje utworzony na liście i zapisywana jest tam liczba elementów na tej liście. Tylko nie wiadomo, czy funkcja rozmiar zostanie wywołana przed czy po dodaniu nowego elementu.
Fragment N22-N24: Powtarzające się przypisania
Błąd MachOObjectFile::checkSymbolTable() const {
....
} else {
MachO::nlist STE = getSymbolTableEntry(SymDRI);
NType = STE.n_type; // <=
NType = STE.n_type; // <=
NSect = STE.n_sect;
NDesc = STE.n_desc;
NStrx = STE.n_strx;
NValue = STE.n_value;
}
....
}Ostrzeżenie PVS-Studio: [CWE-563] Zmienna ‘NType’ jest przypisywana wartościom dwukrotnie po sobie. Może to być błąd. Sprawdź linie: 1663, 1664. MachOObjectFile.cpp 1664
Myślę, że nie ma tu rzeczywistego błędu. Po prostu zbędne powtarzające się przypisanie. Mimo to to wpadka.
Analogicznie:
- V519 [CWE-563] Zmienna ‘B.NDesc’ jest przypisywana wartościom dwukrotnie po sobie. Może to być błąd. Sprawdź linie: 1488, 1489. llvm-nm.cpp 1489
- V519 [CWE-563] Zmienna jest przypisywana wartościom dwukrotnie po sobie. Może to być błąd. Sprawdź linie: 59, 61. coff2yaml.cpp 61
Fragment N25-N27: Jeszcze powtarzające się przypisania
Teraz rozważmy nieco inną wersję powtarzającego się przypisania.
bool Vectorizer::vectorizeLoadChain(
ArrayRef Chain,
SmallPtrSet *InstructionsProcessed) {
....
unsigned Alignment = getAlignment(L0);
....
unsigned NewAlign = getOrEnforceKnownAlignment(L0->getPointerOperand(),
StackAdjustedAlignment,
DL, L0, nullptr, &DT);
if (NewAlign != 0)
Alignment = NewAlign;
Alignment = NewAlign;
....
}Ostrzeżenie PVS-Studio: V519 [CWE-563] Zmienna ‘Alignment’ jest przypisywana wartościom dwukrotnie po sobie. Może to być błąd. Sprawdź linie: 1158, 1160. LoadStoreVectorizer.cpp 1160
To bardzo dziwny kod, który najwyraźniej zawiera błąd logiczny. Na początku, zmiennej Alignment przypisywana jest wartość w zależności od warunku. A potem znów następuje przypisanie, ale teraz już bez jakiejkolwiek kontroli.
Podobne sytuacje można zobaczyć tutaj:
- V519 [CWE-563] Zmienna ‘Effects’ jest przypisywana wartościom dwukrotnie po sobie. Może to być błąd. Sprawdź linie: 152, 165. WebAssemblyRegStackify.cpp 165
- V519 [CWE-563] Zmienna ‘ExpectNoDerefChunk’ jest przypisywana wartościom dwukrotnie po sobie. Może to być błąd. Sprawdź linie: 4970, 4973. SemaType.cpp 4973
Fragment N28: Zawsze prawdziwy warunek
static int readPrefixes(struct InternalInstruction* insn) {
....
uint8_t byte = 0;
uint8_t nextByte;
....
if (byte == 0xf3 && (nextByte == 0x88 || nextByte == 0x89 ||
nextByte == 0xc6 || nextByte == 0xc7)) {
insn->xAcquireRelease = true;
if (nextByte != 0x90) // wsparcie dla instrukcji PAUSE // <=
break;
}
....
}Ostrzeżenie PVS-Studio: [CWE-571] Wyrażenie ‘nextByte != 0x90’ jest zawsze prawdziwe. X86DisassemblerDecoder.cpp 379
Sprawdzenie nie ma sensu. Zmienna nextByte zawsze nie jest równa wartości 0x90, co wynika z poprzedniego sprawdzenia. To jakiś błąd logiczny.
Fragment N29 — N…: Zawsze prawdziwe/falszywe warunki
Analizator wydaje wiele ostrzeżeń o tym, że całe warunki () lub ich część () zawsze jest prawdą lub fałszem. Często nie są to prawdziwe błędy, a jedynie niechlujny kod, wynik działania makr i tym podobne. Niemniej jednak warto przyjrzeć się wszystkim tym ostrzeżeniom, ponieważ od czasu do czasu można natrafić na prawdziwe błędy logiczne. Na przykład, podejrzany jest ten kawałek kodu:
static DecodeStatus DecodeGPRPairRegisterClass(MCInst &Inst, unsigned RegNo,
uint64_t Address, const void *Decoder) {
DecodeStatus S = MCDisassembler::Success;
if (RegNo > 13)
return MCDisassembler::Fail;
if ((RegNo & 1) || RegNo == 0xe)
S = MCDisassembler::SoftFail;
....
}Ostrzeżenie PVS-Studio: [CWE-570] Część wyrażenia warunkowego jest zawsze fałszywa: RegNo == 0xe. ARMDisassembler.cpp 939
Stała 0xE to wartość 14 w systemie dziesiętnym. Sprawdzenie RegNo == 0xe nie ma sensu, ponieważ jeśli RegNo > 13, to funkcja zakończy swoje wykonanie.
Było wiele innych ostrzeżeń z identyfikatorami V547 i V560, ale, podobnie jak w przypadku , badanie tych ostrzeżeń nie interesowało mnie. I tak było wiadomo, że wystarczy mi materiału do napisania artykułu :). Więc nie wiadomo, ile można wykryć błędów tego typu w LLVM za pomocą PVS-Studio.
Podam przykład, dlaczego badanie tych zdarzeń jest nudne. Analizator ma całkowicie rację, wydając ostrzeżenie dla poniższego kodu. Ale to nie jest błąd.
bool UnwrappedLineParser::parseBracedList(bool ContinueOnSemicolons,
tok::TokenKind ClosingBraceKind) {
bool HasError = false;
....
HasError = true;
if (!ContinueOnSemicolons)
return !HasError;
....
}Ostrzeżenie PVS-Studio: V547 [CWE-570] Wyrażenie ‘!HasError’ jest zawsze fałszywe. UnwrappedLineParser.cpp 1635
Fragment N30: Podejrzany return
static bool
isImplicitlyDef(MachineRegisterInfo &MRI, unsigned Reg) {
for (MachineRegisterInfo::def_instr_iterator It = MRI.def_instr_begin(Reg),
E = MRI.def_instr_end(); It != E; ++It) {
return (*It).isImplicitDef();
}
....
}Ostrzeżenie PVS-Studio: [CWE-670] Bezwzględny ‘return’ w pętli. R600OptimizeVectorRegisters.cpp 63
To albo błąd, albo specyficzna sztuczka, mająca na celu wyjaśnienie czegoś programistom czytającym kod. Dla mnie ta konstrukcja niczego nie wyjaśnia i wygląda bardzo podejrzanie. Lepiej tego nie pisać :).
Zmęczony? Czas na zaparzenie herbaty lub kawy.

Wady ujawnione przez nowe diagnozy
Myślę, że 30 zdarzeń starych diagnoz to wystarczająco dużo. Spójrzmy teraz, co interesującego można znaleźć w nowych diagnozach, które zostały dodane do analizatora już po sprawdzaniu. W tym okresie w analizatorze C++ dodano 66 diagnoz ogólnego przeznaczenia.
Fragment N31: Kod niedostępny
Błąd CtorDtorRunner::run() {
....
if (auto CtorDtorMap =
ES.lookup(JITDylibSearchList({{&JD, true}}), std::move(Names),
NoDependenciesToRegister, true))
{
....
return Error::success();
} else
return CtorDtorMap.takeError();
CtorDtorsByPriority.clear();
return Error::success();
}Ostrzeżenie PVS-Studio: [CWE-561] Wykryto nieosiągalny kod. Możliwe, że wystąpił błąd. ExecutionUtils.cpp 146
Jak widzicie, obie gałęzie operatora if kończą się wywołaniem operatora return. W związku z tym kontener CtorDtorsByPriority nigdy nie zostanie wyczyszczony.
Fragment N32: Nieosiągalny kod
bool LLParser::ParseSummaryEntry() {
....
switch (Lex.getKind()) {
case lltok::kw_gv:
return ParseGVEntry(SummaryID);
case lltok::kw_module:
return ParseModuleEntry(SummaryID);
case lltok::kw_typeid:
return ParseTypeIdEntry(SummaryID);
break;
default:
return Error(Lex.getLoc(), "nieoczekiwany typ podsumowania");
}
Lex.setIgnoreColonInIdentifiers(false);
return false;
}Ostrzeżenie PVS-Studio: V779 [CWE-561] Wykryto nieosiągalny kod. Możliwe, że wystąpił błąd. LLParser.cpp 835
Interesująca sytuacja. Spójrzmy najpierw na to miejsce:
return ParseTypeIdEntry(SummaryID);
break;Na pierwszy rzut oka wydaje się, że nie ma tutaj błędu. Wygląda na to, że operator break jest tutaj zbędny i można go po prostu usunąć. Jednak nie wszystko jest takie proste.
Analizator generuje ostrzeżenie dla linii:
Lex.setIgnoreColonInIdentifiers(false);
return false;I rzeczywiście, ten kod jest nieosiągalny. Wszystkie przypadki w switch kończą się wywołaniem operatorem return. I teraz bezsensowny samotny break nie wygląda już tak niewinnie! Może jedna z gałęzi powinna kończyć się na break, a nie na return?
Fragment N33: Przypadkowe wyzerowanie wyższych bitów
unsigned getStubAlignment() override {
if (Arch == Triple::systemz)
return 8;
else
return 1;
}
Expected
RuntimeDyldImpl::emitSection(const ObjectFile &Obj,
const SectionRef &Section,
bool IsCode) {
....
uint64_t DataSize = Section.getSize();
....
if (StubBufSize > 0)
DataSize &= ~(getStubAlignment() - 1);
....
}Ostrzeżenie PVS-Studio: Rozmiar maski bitowej jest mniejszy niż rozmiar pierwszego operandu. Spowoduje to utratę wyższych bitów. RuntimeDyld.cpp 815
Zauważcie, że funkcja getStubAlignment zwraca typ unsigned. Obliczmy wartość wyrażenia, zakładając, że funkcja zwróci wartość 8:
~(getStubAlignment() - 1)
~(8u-1)
0xFFFFFFF8u
Teraz zwróćcie uwagę, że zmienna DataSize ma typ bez znaku 64-bitowy. Wychodzi na to, że przy wykonywaniu operacji DataSize & 0xFFFFFFF8u wszystkie trzydzieści dwa wyższe bity zostaną wyzerowane. Prawdopodobnie programista nie tego chciał. Podejrzewam, że chciał obliczyć: DataSize & 0xFFFFFFFFFFFFFFF8u.
Aby naprawić błąd, powinno być napisane tak:
DataSize &= ~(static_cast(getStubAlignment()) - 1);Albo tak:
DataSize &= ~(getStubAlignment() - 1ULL);Fragment N34: Nieudane jawne rzutowanie typu
szablon <typename T>
void scaleShuffleMask(int Scale, ArrayRef<T> Mask,
SmallVectorImpl<T> &ScaledMask) {
assert(0 < Scale && "Nieoczekiwana wartość skalowania");
int NumElts = Mask.size();
ScaledMask.assign(static_cast<size_t>(NumElts * Scale), -1);
....
}Ostrzeżenie PVS-Studio: [CWE-190] Możliwy przepełnienie. Rozważ rzutowanie operandów operatora 'NumElts * Scale' na typ 'size_t', a nie wynik. X86ISelLowering.h 1577
Jawne rzutowanie typu jest używane, aby uniknąć przepełnienia podczas mnożenia zmiennych typu int. Jednak tutaj jawne rzutowanie typu nie chroni przed przepełnieniem. Najpierw zmienne zostaną pomnożone, a dopiero potem 32-bitowy wynik mnożenia zostanie rozszerzony do typu. .
Fragment N35: Nieudane Copy-Paste
Instruction *InstCombiner::visitFCmpInst(FCmpInst &I) {
....
if (!match(Op0, m_PosZeroFP()) && isKnownNeverNaN(Op0, &TLI)) {
I.setOperand(0, ConstantFP::getNullValue(Op0->getType()));
return &I;
}
if (!match(Op1, m_PosZeroFP()) && isKnownNeverNaN(Op1, &TLI)) {
I.setOperand(1, ConstantFP::getNullValue(Op0->getType())); /<=
return &I;
}
....
}[CWE-682] Znaleziono dwa podobne fragmenty kodu. Być może jest to błąd i zamiast 'Op0' powinien być użyty zmienna ‘Op1’. InstCombineCompares.cpp 5507
Ta nowa, interesująca diagnoza ujawnia sytuacje, gdy fragment kodu został skopiowany, a niektóre nazwy zaczęto zmieniać, ale w jednym miejscu nie zmieniono.
Zauważ, że w drugim bloku zmieniano Op0 na Op1. Ale w jednym miejscu nie zmieniono. Prawdopodobnie powinno być napisane tak:
if (!match(Op1, m_PosZeroFP()) && isKnownNeverNaN(Op1, &TLI)) {
I.setOperand(1, ConstantFP::getNullValue(Op1->getType()));
return &I;
}Fragment N36: Zamieszanie w zmiennych
struct Status {
unsigned Mask;
unsigned Mode;
Status() : Mask(0), Mode(0){};
Status(unsigned Mask, unsigned Mode) : Mask(Mask), Mode(Mode) {
Mode &= Mask;
};
....
};Ostrzeżenie PVS-Studio: [CWE-563] Zmienna 'Mode' jest przypisywana, ale nie jest używana do końca funkcji. SIModeRegister.cpp 48
Bardzo niebezpiecznie jest nadawać argumentom funkcji te same nazwy co członom klasy. Bardzo łatwo można się pomylić. Mamy tutaj właśnie taki przypadek. To wyrażenie nie ma sensu:
Mode &= Mask;Zmienia się argument funkcji. I wszystko. Ten argument nie jest więcej używany. Prawdopodobnie powinno być napisane tak:
Status(unsigned Mask, unsigned Mode) : Mask(Mask), Mode(Mode) {
this->Mode &= Mask;
};Fragment N37: Zamieszanie w zmiennych
class SectionBase {
....
uint64_t Size = 0;
....
};
class SymbolTableSection : public SectionBase {
....
};
void SymbolTableSection::addSymbol(Twine Name, uint8_t Bind, uint8_t Type,
SectionBase *DefinedIn, uint64_t Value,
uint8_t Visibility, uint16_t Shndx,
uint64_t Size) {
....
Sym.Value = Value;
Sym.Visibility = Visibility;
Sym.Size = Size;
Sym.Index = Symbols.size();
Symbols.emplace_back(llvm::make_unique(Sym));
Size += this->EntrySize;
}Ostrzeżenie PVS-Studio: V1001 [CWE-563] Zmienna 'Size' jest przypisywana, ale nie jest używana do końca funkcji. Object.cpp 424
Sytuacja jest analogiczna do poprzedniej. Powinno być napisane:
this->Size += this->EntrySize;Fragment N38-N47: Wskaźnik zapomniano sprawdzić
Wcześniej omawialiśmy przykłady wyzwalania diagnostyki. . Jej istota polega na tym, że wskaźnik jest najpierw dereferencjonowany, a dopiero potem sprawdzany. Młoda diagnostyka jest odwrotnością pod względem znaczenia, ale również wykrywa wiele błędów. Wykrywa sytuacje, kiedy wskaźnik został na początku sprawdzony, a później zapomniano to zrobić. Rozważmy takie przypadki znalezione w LLVM.
int getGEPCost(Type *PointeeType, const Value *Ptr,
ArrayRef Operands) {
....
if (Ptr != nullptr) { // <=
assert(....);
BaseGV = dyn_cast(Ptr->stripPointerCasts());
}
bool HasBaseReg = (BaseGV == nullptr);
auto PtrSizeBits = DL.getPointerTypeSizeInBits(Ptr->getType()); // <=
....
}Ostrzeżenie PVS-Studio: V1004 [CWE-476] Wskaźnik 'Ptr' był używany w sposób niebezpieczny po jego sprawdzeniu względem nullptr. Sprawdź linie: 729, 738. TargetTransformInfoImpl.h 738
Zmienna Ptr może być równy nullptr, co potwierdza sprawdzenie:
if (Ptr != nullptr)Jednak poniżej ten wskaźnik jest dereferencjonowany już bez wcześniejszego sprawdzenia:
auto PtrSizeBits = DL.getPointerTypeSizeInBits(Ptr->getType());Rozważmy inny podobny przypadek.
llvm::DISubprogram *CGDebugInfo::getFunctionFwdDeclOrStub(GlobalDecl GD,
bool Stub) {
....
auto *FD = dyn_cast(GD.getDecl());
SmallVector ArgTypes;
if (FD) // parameters())
ArgTypes.push_back(Parm->getType());
CallingConv CC = FD->getType()->castAs()->getCallConv(); // <=
....
}Ostrzeżenie PVS-Studio: V1004 [CWE-476] Wskaźnik 'FD' był używany w sposób niebezpieczny po jego sprawdzeniu względem nullptr. Sprawdź linie: 3228, 3231. CGDebugInfo.cpp 3231
Zwróć uwagę na wskaźnik FD. Jestem pewien, że problem jest dobrze widoczny, a specjalne wyjaśnienia nie są potrzebne.
I jeszcze:
static void computePolynomialFromPointer(Value &Ptr, Polynomial &Result,
Value *&BasePtr,
const DataLayout &DL) {
PointerType *PtrTy = dyn_cast(Ptr.getType());
if (!PtrTy) { // getPointerAddressSpace()); // <=
....
}Ostrzeżenie PVS-Studio: V1004 [CWE-476] Wskaźnik ‘PtrTy’ był używany w sposób niebezpieczny po tym, jak został zweryfikowany czy nie jest nullem. Sprawdź linie: 960, 965. InterleavedLoadCombinePass.cpp 965
Jak chronić się przed takimi błędami? Bądź bardziej uważny podczas przeglądu kodu i używaj do regularnej kontroli kodu statycznego analizatora PVS-Studio.
Nie ma sensu podawać innych fragmentów kodu z błędami tego typu. Zostawię w artykule tylko listę ostrzeżeń:
- V1004 [CWE-476] Wskaźnik ‘Expr’ był używany w sposób niebezpieczny po tym, jak został zweryfikowany czy nie jest nullem. Sprawdź linie: 1049, 1078. DebugInfoMetadata.cpp 1078
- V1004 [CWE-476] Wskaźnik ‘PI’ był używany w sposób niebezpieczny po tym, jak został zweryfikowany czy nie jest nullem. Sprawdź linie: 733, 753. LegacyPassManager.cpp 753
- V1004 [CWE-476] Wskaźnik ‘StatepointCall’ był używany w sposób niebezpieczny po tym, jak został zweryfikowany czy nie jest nullem. Sprawdź linie: 4371, 4379. Verifier.cpp 4379
- V1004 [CWE-476] Wskaźnik ‘RV’ był używany w sposób niebezpieczny po tym, jak został zweryfikowany czy nie jest nullem. Sprawdź linie: 2263, 2268. TGParser.cpp 2268
- V1004 [CWE-476] Wskaźnik ‘CalleeFn’ był używany w sposób niebezpieczny po tym, jak został zweryfikowany czy nie jest nullem. Sprawdź linie: 1081, 1096. SimplifyLibCalls.cpp 1096
- V1004 [CWE-476] Wskaźnik ‘TC’ był używany w sposób niebezpieczny po tym, jak został zweryfikowany czy nie jest nullem. Sprawdź linie: 1819, 1824. Driver.cpp 1824
Fragment N48-N60: Nie jest krytyczny, ale to defekt (możliwy wyciek pamięci)
std::unique_ptr createISelMutator() {
....
std::vector<std::unique_ptr> Strategies;
Strategies.emplace_back(
new InjectorIRStrategy(InjectorIRStrategy::getDefaultOps()));
....
}Ostrzeżenie PVS-Studio: [CWE-460] Wskaźnik bez właściciela został dodany do kontenera ‘Strategies’ za pomocą metody ’emplace_back’. W przypadku wyjątku wystąpi wyciek pamięci. llvm-isel-fuzzer.cpp 58
Aby dodać element na końcu kontenera typu std::vector<std::unique_ptr> nie można po prostu napisać xxx.push_back(new X), ponieważ nie ma niejawnej konwersji z X* do std::unique_ptr.
Powszechnym rozwiązaniem jest napisanie xxx.emplace_back(new X), ponieważ jest to kompilowane: metoda emplace_back konstruuje element bezpośrednio z argumentów, a zatem może używać jawnych konstruktorów.
To jest niebezpieczne. Jeśli wektor jest pełny, następuje ponowne przydzielenie pamięci. Operacja ponownego przydzielania pamięci może zakończyć się niepowodzeniem, co spowoduje wygenerowanie wyjątku std::bad_alloc. W tym przypadku wskaźnik zostanie utracony, a stworzony obiekt nigdy nie zostanie usunięty.
Bezpiecznym rozwiązaniem jest stworzenie unique_ptr, który będzie posiadał wskaźnik zanim wektor podejmie próbę ponownego przydzielenia pamięci:
xxx.push_back(std::unique_ptr(new X))Od C++14 można używać 'std::make_unique':
xxx.push_back(std::make_unique())Ten rodzaj defektu nie jest krytyczny dla LLVM. Jeśli nie uda się przydzielić pamięci, działanie kompilatora zostanie po prostu zatrzymane. Jednak dla aplikacji o długim , które nie mogą po prostu się zakończyć, jeśli nie uda się przydzielić pamięci, może to być rzeczywiście poważny problem.
Zatem, chociaż ten kod nie stanowi praktycznego zagrożenia dla LLVM, uznałem za użyteczne opowiedzenie o tym wzorze błędów i że analizator PVS-Studio nauczył się go wykrywać.
Inne ostrzeżenia tego rodzaju:
- V1023 [CWE-460] Wskaźnik bez właściciela został dodany do kontenera 'Passes' przez metodę 'emplace_back'. W przypadku wyjątku wystąpi wyciek pamięci. PassManager.h 546
- V1023 [CWE-460] Wskaźnik bez właściciela został dodany do kontenera 'AAs' przez metodę 'emplace_back'. W przypadku wyjątku wystąpi wyciek pamięci. AliasAnalysis.h 324
- V1023 [CWE-460] Wskaźnik bez właściciela został dodany do kontenera 'Entries' przez metodę 'emplace_back'. W przypadku wyjątku wystąpi wyciek pamięci. DWARFDebugFrame.cpp 519
- V1023 [CWE-460] Wskaźnik bez właściciela został dodany do kontenera 'AllEdges' przez metodę 'emplace_back'. W przypadku wyjątku wystąpi wyciek pamięci. CFGMST.h 268
- V1023 [CWE-460] Wskaźnik bez właściciela został dodany do kontenera 'VMaps' przez metodę 'emplace_back'. W przypadku wyjątku wystąpi wyciek pamięci. SimpleLoopUnswitch.cpp 2012
- V1023 [CWE-460] Wskaźnik bez właściciela został dodany do kontenera 'Records' przez metodę 'emplace_back'. W przypadku wyjątku wystąpi wyciek pamięci. FDRLogBuilder.h 30
- V1023 [CWE-460] Wskaźnik bez właściciela został dodany do kontenera 'PendingSubmodules' przez metodę 'emplace_back'. W przypadku wyjątku wystąpi wyciek pamięci. ModuleMap.cpp 810
- V1023 [CWE-460] Wskaźnik bez właściciela został dodany do kontenera 'Objects' przez metodę 'emplace_back'. W przypadku wyjątku wystąpi wyciek pamięci. DebugMap.cpp 88
- V1023 [CWE-460] Wskaźnik bez właściciela został dodany do kontenera 'Strategies' przez metodę 'emplace_back'. W przypadku wyjątku wystąpi wyciek pamięci. llvm-isel-fuzzer.cpp 60
- V1023 [CWE-460] Wskaźnik bez właściciela został dodany do kontenera 'Modifiers' przez metodę 'emplace_back'. W przypadku wyjątku wystąpi wyciek pamięci. llvm-stress.cpp 685
- V1023 [CWE-460] Wskaźnik bez właściciela został dodany do kontenera 'Modifiers' przez metodę 'emplace_back'. W przypadku wyjątku wystąpi wyciek pamięci. llvm-stress.cpp 686
- V1023 [CWE-460] Wskaźnik bez właściciela został dodany do kontenera 'Modifiers' przez metodę 'emplace_back'. W przypadku wyjątku wystąpi wyciek pamięci. llvm-stress.cpp 688
- V1023 [CWE-460] Wskaźnik bez właściciela został dodany do kontenera 'Modifiers' przez metodę 'emplace_back'. W przypadku wyjątku wystąpi wyciek pamięci. llvm-stress.cpp 689
- V1023 [CWE-460] Wskaźnik bez właściciela został dodany do kontenera 'Modifiers' przez metodę 'emplace_back'. W przypadku wyjątku wystąpi wyciek pamięci. llvm-stress.cpp 690
- V1023 [CWE-460] Wskaźnik bez właściciela został dodany do kontenera 'Modifiers' przez metodę 'emplace_back'. W przypadku wyjątku wystąpi wyciek pamięci. llvm-stress.cpp 691
- V1023 [CWE-460] Wskaźnik bez właściciela jest dodawany do kontenera „Modifiers” przez metodę „emplace_back”. W przypadku wystąpienia wyjątku dojdzie do wycieku pamięci. llvm-stress.cpp 692
- V1023 [CWE-460] Wskaźnik bez właściciela jest dodawany do kontenera „Modifiers” przez metodę „emplace_back”. W przypadku wystąpienia wyjątku dojdzie do wycieku pamięci. llvm-stress.cpp 693
- V1023 [CWE-460] Wskaźnik bez właściciela jest dodawany do kontenera „Modifiers” przez metodę „emplace_back”. W przypadku wystąpienia wyjątku dojdzie do wycieku pamięci. llvm-stress.cpp 694
- V1023 [CWE-460] Wskaźnik bez właściciela jest dodawany do kontenera „Operands” przez metodę „emplace_back”. W przypadku wystąpienia wyjątku dojdzie do wycieku pamięci. GlobalISelEmitter.cpp 1911
- V1023 [CWE-460] Wskaźnik bez właściciela jest dodawany do kontenera „Stash” przez metodę „emplace_back”. W przypadku wystąpienia wyjątku dojdzie do wycieku pamięci. GlobalISelEmitter.cpp 2100
- V1023 [CWE-460] Wskaźnik bez właściciela jest dodawany do kontenera „Matchers” przez metodę „emplace_back”. W przypadku wystąpienia wyjątku dojdzie do wycieku pamięci. GlobalISelEmitter.cpp 2702
Podsumowanie
Łącznie wystawiłem 60 ostrzeżeń, po czym się zatrzymałem. Czy są inne defekty, które analizator PVS-Studio wykrywa w LLVM? Tak, są. Jednak gdy sporządzałem fragmenty kodu do artykułu, nastał późny wieczór, a właściwie już noc, i postanowiłem, że czas skończyć.
Mam nadzieję, że było to interesujące i będziecie chcieli spróbować analizatora PVS-Studio.
Możecie pobrać analizator i otrzymać klucz próbny na .
Najważniejsze to regularnie korzystać z analizy statycznej. Pojedyncze kontrole, które wykonujemy w celu popularyzacji metodologii analizy statycznej i PVS-Studio, nie są normalnym scenariuszem.
Powodzenia w poprawie jakości i niezawodności kodu!
Jeśli chcecie podzielić się tym artykułem z anglojęzyczną publicznością, to proszę użyć linku do tłumaczenia: Andrey Karpov. .
Źródło: habr.com
