自从使用我们的PVS-Studio分析仪对LLVM项目代码进行最后一次验证以来,已经过去了两年多的时间。 让我们确保PVS-Studio分析仪仍然是检测错误和潜在漏洞的领先工具。 为此,请检查并找到LLVM 8.0.0发行版中的新错误。
要写的文章
老实说,我不想写这篇文章。 写一个我们已经反复测试过的项目(
1、2、3 )是没有意思的。 最好写些新东西,但我别无选择。
每次发行新版本的LLVM或更新
Clang Static Analyzer时 ,我们的邮件中都会出现以下问题:
瞧,新版本的Clang静态分析器已学会查找新错误! 在我看来,使用PVS-Studio的相关性正在下降。 Clang发现比以往更多的错误,并追赶了PVS-Studio的功能。 您如何看待?为此,我一直想以一种精神来回答:
我们也不是闲着! 我们大大提高了PVS-Studio分析仪的功能。 因此,请放心,我们将继续保持领先地位。
不幸的是,这是一个错误的答案。 没有任何证据。 这就是为什么我现在写这篇文章。 因此,LLVM项目再次经过测试,并发现了许多错误。 我现在将演示那些对我来说似乎很有趣的内容。 Clang静态分析器无法找到这些错误(或者这样做非常不便)。 而且我们可以。 我在一晚上找到并写下了所有这些错误。
但是这篇文章的写作拖了好几个星期。 我不能强迫自己以文字:)来安排所有这些内容。
顺便说一句,如果您对PVS-Studio分析仪中使用哪些技术来检测错误和潜在的漏洞感兴趣,那么我建议您熟悉本
说明 。
新旧诊断
如前所述,大约两年前,LLVM项目再次得到验证,发现的错误已得到纠正。 现在,本文将介绍错误的新部分。 为什么会发现新的错误? 这有3个原因:
- LLVM项目已开发,其中的旧代码已更改,然后出现了新的代码。 自然,修改和编写的代码中会出现新的错误。 这很好地说明了应该定期进行静态分析,而不是逐案进行。 我们的文章很好地展示了PVS-Studio分析仪的功能,但这与提高代码质量和降低修复错误的成本无关。 定期使用静态代码分析器!
- 我们正在定稿并改进现有的诊断程序。 因此,分析仪可以检测到先前检查中未发现的错误。
- PVS-Studio推出了2年前还没有的新诊断程序。 我决定将它们分为一个单独的部分,以清楚地展示PVS-Studio的发展。
通过2年前的诊断发现的缺陷
片段N1:复制粘贴static bool ShouldUpgradeX86Intrinsic(Function *F, StringRef Name) { if (Name == "addcarryx.u32" ||
PVS-Studio警告:
V501 [CWE-570]在“ ||”的左侧和右侧有相同的子表达式“ Name.startswith(“ avx512.mask.permvar。”)” 操作员。 自动升级.cpp 73
请仔细检查该名称是否以子字符串“ avx512.mask.permvar。”开头。 在第二个测试中,他们显然想写些其他东西,但忘记修复复制的文本。
片段N2:错字 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; .... }
PVS-Studio警告:V501在“ |”的左侧和右侧有相同的子表达式“ CXNameRange_WantQualifier” 操作员。 CIndex.cpp 7245
由于输入错误,
两次使用了相同的命名常量
CXNameRange_WantQualifier 。
片段N3:对操作员优先级的困惑 int PPCTTIImpl::getVectorInstrCost(unsigned Opcode, Type *Val, unsigned Index) { .... if (ISD == ISD::EXTRACT_VECTOR_ELT && Index == ST->isLittleEndian() ? 1 : 0) return 0; .... }
PVS-Studio警告:
V502 [CWE-783]也许'?:'运算符的工作方式与预期的不同。 '?:'运算符的优先级低于'=='运算符。 PPCTargetTransformInfo.cpp 404
我认为这是一个非常美丽的错误。 是的,我知道我对美感很奇怪:)。
现在,根据
运算符的
优先级 ,表达式计算如下:
(ISD == ISD::EXTRACT_VECTOR_ELT && (Index == ST->isLittleEndian())) ? 1 : 0
从实际的角度来看,这种情况没有意义,因为可以将其简化为:
(ISD == ISD::EXTRACT_VECTOR_ELT && Index == ST->isLittleEndian())
这是一个明显的错误。 0/1最有可能希望与
Index变量进行比较。 要修复代码,请在三元运算符周围添加方括号:
if (ISD == ISD::EXTRACT_VECTOR_ELT && Index == (ST->isLittleEndian() ? 1 : 0))
顺便说一下,三元运算符非常危险并且会引发逻辑错误。 请非常小心,不要贪婪地加上括号。 我在“害怕运算符?:并将其括在括号中”一章中对此主题进行了更详细的讨论。
片段N4,N5:空指针 Init *TGParser::ParseValue(Record *CurRec, RecTy *ItemType, IDParseMode Mode) { .... TypedInit *LHS = dyn_cast<TypedInit>(Result); .... LHS = dyn_cast<TypedInit>( UnOpInit::get(UnOpInit::CAST, LHS, StringRecTy::get()) ->Fold(CurRec)); if (!LHS) { Error(PasteLoc, Twine("can't cast '") + LHS->getAsString() + "' to string"); return nullptr; } .... }
PVS-Studio警告:
V522 [CWE-476]可能会取消引用空指针“ LHS”。 TGParser.cpp 2152
如果
LHS指针为空,则应发出警告。 但是,这将取消引用此非常为空的指针:
LHS-> getAsString() 。
当错误隐藏在错误处理程序中时,这是一种非常典型的情况,因为没有人在测试它们。 静态分析器会检查所有可访问的代码,无论使用频率如何。 这是一个很好的例子,说明静态分析如何补充其他测试和错误保护技术。
下面的代码对
RHS指针进行了类似的处理:V522 [CWE-476]可能会取消引用空指针“ RHS”。 TGParser.cpp 2186
片段N6:移动后使用光标 static Expected<bool> ExtractBlocks(....) { .... std::unique_ptr<Module> ProgClone = CloneModule(BD.getProgram(), VMap); .... BD.setNewProgram(std::move(ProgClone));
PVS-Studio警告:V522 [CWE-476]可能会取消引用空指针“ ProgClone”。 错误编译.cpp 601
首先,
ProgClone智能指针不再拥有该对象:
BD.setNewProgram(std::move(ProgClone));
实际上,现在
ProgClone是空指针。 因此,空指针的解引用应该在下面进行:
Function *NewF = ProgClone->getFunction(MisCompFunctions[i].first);
但是,实际上,这不会发生! 请注意,循环实际上并未运行。
首先,将清除
MiscompiledFunctions容器:
MiscompiledFunctions.clear();
接下来,在循环条件中使用此容器的大小:
for (unsigned i = 0, e = MisCompFunctions.size(); i != e; ++i) {
很容易看到循环没有开始。 我认为这也是一个错误,应该以不同的方式编写代码。
看来我们遇到了非常著名的错误校验! 一个错误掩饰了另一个:)。
片段N7:移动后使用光标 static Expected<bool> TestOptimizer(BugDriver &BD, std::unique_ptr<Module> Test, std::unique_ptr<Module> Safe) { outs() << " Optimizing functions being tested: "; std::unique_ptr<Module> Optimized = BD.runPassesOn(Test.get(), BD.getPassesToRun()); if (!Optimized) { errs() << " Error running this sequence of passes" << " on the input program!\n"; BD.setNewProgram(std::move(Test));
PVS-Studio警告:V522 [CWE-476]可能会取消引用空指针“测试”。 错误编译cpp 709
同样的情况。 首先,对象的内容被移动,然后就好像什么都没发生一样被使用。 在C ++中出现了运动的语义之后,我越来越在程序代码中看到这种情况。 为此,我喜欢C ++语言! 射击自己的腿的方法越来越多。 PVS-Studio分析仪将始终可以工作:)。
片段N8:空指针 void FunctionDumper::dump(const PDBSymbolTypeFunctionArg &Symbol) { uint32_t TypeId = Symbol.getTypeId(); auto Type = Symbol.getSession().getSymbolById(TypeId); if (Type) Printer << "<unknown-type>"; else Type->dump(*this); }
PVS-Studio警告:V522 [CWE-476]可能会取消引用空指针“类型”。 233.第233章
除错误处理程序外,通常不测试用于调试打印输出的功能。 摆在我们面前的就是这种情况。 该功能正在等待用户,而不是解决他的问题,而将被迫对其进行修复。
正确地:
if (Type) Type->dump(*this); else Printer << "<unknown-type>";
片段N9:空指针 void SearchableTableEmitter::collectTableEntries( GenericTable &Table, const std::vector<Record *> &Items) { .... RecTy *Ty = resolveTypes(Field.RecType, TI->getType()); if (!Ty)
PVS-Studio警告:V522 [CWE-476]可能会取消引用空指针“ Ty”。 SearchableTableEmitter.cpp 614
我认为,因此一切都清楚,不需要解释。
片段N10:错别字 bool FormatTokenLexer::tryMergeCSharpNullConditionals() { .... auto &Identifier = *(Tokens.end() - 2); auto &Question = *(Tokens.end() - 1); .... Identifier->ColumnWidth += Question->ColumnWidth; Identifier->Type = Identifier->Type;
PVS-Studio警告:
V570为其分配了“标识符->类型”变量。 格式令牌Lexer.cpp 249
给自己分配一个变量是没有意义的。 他们最有可能想写:
Identifier->Type = Question->Type;
片段N11:可疑的中断 void SystemZOperand::print(raw_ostream &OS) const { switch (Kind) { break; case KindToken: OS << "Token:" << getToken(); break; case KindReg: OS << "Reg:" << SystemZInstPrinter::getRegisterName(getReg()); break; .... }
PVS-Studio警告:
V622 [CWE-478]考虑检查“ switch”声明。 第一个“ case”运算符可能会丢失。 系统ZAsmParser.cpp 652
开始时有一个非常可疑的
break语句。 你忘了在这里写别的东西吗?
片段N12:解引用后检查指针 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"); .... }
PVS-Studio警告:
V595 [CWE-476]在针对nullptr对其进行验证之前,已使用了“被
调用方 ”指针。 检查行:172,174。AMDGPUInline.cpp 172
调用getTTI函数时,将取消引用开头的
Callee指针。
然后发现应该检查此指针是否具有
nullptr相等性:
if (!Callee || Callee->isDeclaration())
但是为时已晚...
片段N13-N ...:取消引用后检查指针先前代码片段中讨论的情况并非唯一。 她在这里找到:
static Value *optimizeDoubleFP(CallInst *CI, IRBuilder<> &B, bool isBinary, bool isPrecise = false) { .... Function *CalleeFn = CI->getCalledFunction(); StringRef CalleeNm = CalleeFn->getName();
PVS-Studio警告:V595 [CWE-476]在针对nullptr对其进行验证之前,已使用了'CalleeFn'指针。 检查行:1079、1081。SimplifyLibCalls.cpp 1079
在这里:
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());
PVS-Studio警告:V595 [CWE-476]在针对nullptr对其进行验证之前,已使用了'ND'指针。 检查行:532、534。SemaTemplateInstantiateDecl.cpp 532
在这里:
- V595 [CWE-476]在针对nullptr对其进行验证之前,已使用了“ U”指针。 检查行:404、407。DWARFFormValue.cpp 404
- V595 [CWE-476]在针对nullptr进行验证之前,已使用了'ND'指针。 检查行:2149,2151。SemaTemplateInstantiate.cpp 2149
然后,研究编号为V595的警告对我来说就没有兴趣了。 因此,除了这里列出的错误之外,我不知道是否还有其他类似的错误。 最有可能的。
N17,N18片段:可疑移位 static inline bool processLogicalImmediate(uint64_t Imm, unsigned RegSize, uint64_t &Encoding) { .... unsigned Size = RegSize; .... uint64_t NImms = ~(Size-1) << 1; .... }
PVS-Studio警告:
V629 [CWE-190]考虑检查'〜(大小-1)<< 1'表达式。 32位值的位移,随后扩展为64位类型。 260 AArch64AddressingModes.h
也许这不是一个错误,并且代码完全按预期工作。 但这显然是一个非常可疑的地方,需要对其进行检查。
假设
Size变量为16,然后计划编写代码以获取
NImms变量中的值的代码的作者:
11111111111111111111111111111111111111111111111111111111111111100000000
但是,实际上,结果是:
0000000000000000000000000000000000000000001111111111111111111111111100000
事实是所有计算都是使用32位无符号类型进行的。 而且只有这样,此32位无符号类型才会隐式扩展为
uint64_t 。 在这种情况下,最高有效位将为零。
您可以解决这种情况:
uint64_t NImms = ~static_cast<uint64_t>(Size-1) << 1;
相似的情况:V629 [CWE-190]考虑检查'Immr << 6'表达式。 32位值的位移,随后扩展为64位类型。 AArch64AddressingModes.h 269
片段N19:缺少 else 关键字 吗? void AMDGPUAsmParser::cvtDPP(MCInst &Inst, const OperandVector &Operands) { .... if (Op.isReg() && Op.Reg.RegNo == AMDGPU::VCC) {
PVS-Studio警告:
V646 [CWE-670]考虑检查应用程序的逻辑。 可能缺少“ else”关键字。 AMDGPUAsmParser.cpp 5655
这里没有错误。 由于第一个
if的then块以
continue结束,因此
else关键字是否存在并不重要。 在任何情况下,代码都将相同。 但是,缺少
其他元素会使代码更加晦涩和危险。 如果将来
继续消失,那么代码将以完全不同的方式开始工作。 我认为,最好添加
else 。
片段N20:四个相同类型的错别字 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();
PVS-Studio警告:
- V655 [CWE-480]字符串已连接,但未使用。 考虑检查“ Result + Name.str()”表达式。 Symbol.cpp 32
- V655 [CWE-480]字符串已连接,但未使用。 考虑检查'Result +“(ObjC Class)” + Name.str()'表达式。 Symbol.cpp 35
- V655 [CWE-480]字符串已连接,但未使用。 考虑检查'Result +“(ObjC Class EH)” + Name.str()'表达式。 Symbol.cpp 38
- V655 [CWE-480]字符串已连接,但未使用。 考虑检查“结果+“(ObjC IVar)” + Name.str()”表达式。 Symbol.cpp 41
偶然地,使用+运算符代替+ =运算符。 结果是毫无意义的设计。
片段N21:未定义的行为 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(); } } }
尝试自己找到危险代码。 这是一张分散注意力的图片,以便不要立即查看答案:
PVS-Studio警告:
V708 [CWE-758]使用了危险的构造:'FeaturesMap [Op] =
FeatureMap.size ()',其中'FeaturesMap'是'map'类。 这可能导致不确定的行为。 RISCVCompressInstEmitter.cpp 490
问题行:
FeaturesMap[Op] = FeaturesMap.size();
如果找不到
Op元素,则会在映射中创建一个新元素,并将该映射中的元素数量写入该映射中。 只是不清楚在添加新元素之前还是之后将调用
size函数。
片段N22-N24:重新分配 Error MachOObjectFile::checkSymbolTable() const { .... } else { MachO::nlist STE = getSymbolTableEntry(SymDRI); NType = STE.n_type;
PVS-Studio警告:
V519 [CWE-563]连续两次为'NType'变量分配值。 也许这是一个错误。 检查行:1663、1664。MachOObjectFile.cpp 1664
我认为这里没有真正的错误。 只是多余的重复分配。 但是仍然是一个错误。
类似地:
- V519 [CWE-563]连续两次为'B.NDesc'变量分配值。 也许这是一个错误。 检查行:1488、1489。llvm-nm.cpp 1489
- V519 [CWE-563]连续两次为变量分配值。 也许这是一个错误。 检查行:59,61。coff2yaml.cpp 61
片段N25-N27:更多重新分配现在考虑稍微不同的重新分配选项。
bool Vectorizer::vectorizeLoadChain( ArrayRef<Instruction *> Chain, SmallPtrSet<Instruction *, 16> *InstructionsProcessed) { .... unsigned Alignment = getAlignment(L0); .... unsigned NewAlign = getOrEnforceKnownAlignment(L0->getPointerOperand(), StackAdjustedAlignment, DL, L0, nullptr, &DT); if (NewAlign != 0) Alignment = NewAlign; Alignment = NewAlign; .... }
PVS-Studio警告:V519 [CWE-563]'Alignment'变量连续两次被赋值。 也许这是一个错误。 检查行:1158、1160。LoadStoreVectorizer.cpp 1160
这是一个非常奇怪的代码,似乎包含逻辑错误。 首先,根据条件为
Alignment变量分配一个值。 然后再次进行分配,但是现在没有任何验证。
在这里可以看到类似的情况:
- V519 [CWE-563]'效果'变量连续两次被赋值。 也许这是一个错误。 检查行:152,165。WebAssemblyRegStackify.cpp 165
- V519 [CWE-563]'ExpectNoDerefChunk'变量已连续两次分配值。 也许这是一个错误。 检查行:4970,4973。SemaType.cpp 4973
片段N28:始终为真实条件 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)
PVS-Studio
警告 :
V547 [CWE-571]表达式'nextByte!= 0x90'始终为true。 X86DisassemblerDecoder.cpp 379
验证没有意义。
nextByte变量始终不等于
0x90 ,这是从上一次检查得出的。 这是某种逻辑错误。
片段N29-N ...:始终为真/假条件分析仪发出很多警告,表明整个条件(
V547 )或部分条件(
V560 )始终为真或假。 这些通常不是真正的错误,而只是草率的代码,宏部署的结果等。 但是,查看所有这些警告还是有道理的,因为有时会出现真正的逻辑错误。 例如,这段代码是可疑的:
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; .... }
PVS-Studio警告:
V560 [CWE-570]条件表达式的一部分始终为false:RegNo == 0xe。 ARMDisassembler.cpp 939
常数0xE是十进制系统中的值14。 检查
RegNo == 0xe没有意义,因为如果
RegNo> 13 ,则该函数将完成其执行。
还有许多其他警告,其标识符为V547和V560,但与
V595一样 ,我对研究这些警告不感兴趣。 很显然,我有足够的材料来撰写文章:)。 因此,不知道使用PVS-Studio在LLVM中可以检测到多少这种类型的错误。
我将举一个例子来说明为什么研究这些响应很无聊。 分析仪发出以下代码警告是绝对正确的。 但这不是一个错误。
bool UnwrappedLineParser::parseBracedList(bool ContinueOnSemicolons, tok::TokenKind ClosingBraceKind) { bool HasError = false; .... HasError = true; if (!ContinueOnSemicolons) return !HasError; .... }
PVS-Studio警告:V547 [CWE-570]表达式'!HasError'始终为false。 UnwrappedLineParser.cpp 1635
片段N30:可疑回报 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(); } .... }
PVS-Studio警告:
V612 [CWE-670]循环内无条件的“返回”。 R600OptimizeVectorRegisters.cpp 63
这要么是一个错误,要么是一个特定的技巧,旨在向程序员阅读代码提供解释。 这种设计对我没有任何解释,看起来非常可疑。 最好不要这样写:)。
你累吗 然后时间做茶或咖啡。
新诊断程序检测到的缺陷
我认为30次触发旧诊断就足够了。 现在让我们看看通过
先前检查后在分析仪中出现的新诊断程序可以发现有趣的事情。 在此期间,总共向C ++分析器添加了66个通用诊断程序。
片段N31:无法访问的代码 Error 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(); }
PVS-Studio警告:
V779 [CWE-561]检测
不到代码。 可能存在错误。 ExecutionUtils.cpp 146
如您所见,
if语句的两个分支都以对
return语句的调用结尾。 因此,
CtorDtorsByPriority容器
将永远不会被
清空 。
片段N32:无法访问的代码 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);
PVS-Studio警告:V779 [CWE-561]检测不到代码。 可能存在错误。 LLParser.cpp 835
一个有趣的情况。 让我们看看这个地方的开头:
return ParseTypeIdEntry(SummaryID); break;
乍看之下,似乎没有错误。 看来
break语句在这里是多余的,您可以简单地将其删除。 但是,并非一切都那么简单。
分析仪在线上生成警告:
Lex.setIgnoreColonInIdentifiers(false); return false;
确实,此代码不可访问。
switch中的所有情况都以对
return语句的调用结尾。 现在,毫无意义的孤独
休息看起来并没有那么有害! 也许其中一个分支应该以
中断结尾,而不是以
返回结尾?
片段N33:高位的意外化 unsigned getStubAlignment() override { if (Arch == Triple::systemz) return 8; else return 1; } Expected<unsigned> RuntimeDyldImpl::emitSection(const ObjectFile &Obj, const SectionRef &Section, bool IsCode) { .... uint64_t DataSize = Section.getSize(); .... if (StubBufSize > 0) DataSize &= ~(getStubAlignment() - 1); .... }
PVS-Studio警告:
V784位掩码的大小小于第一个操作数的大小。 这将导致丢失更高的位。 运行系统Dyld.cpp 815
请注意,
getStubAlignment函数返回
无符号类型。 如果我们假设函数返回值8,则我们将计算表达式的值:
〜(getStubAlignment()-1)
〜(8u-1)
0xFFFFFFF8u
现在注意,
DataSize变量具有64位无符号类型。 事实证明,在操作DataSize&0xFFFFFFF88时,所有32个高位将被复位。 很有可能,这不是程序员想要的。 我怀疑他想计算:DataSize&0xFFFFFFFFFFFFFFFFFFF8u。
要解决该错误,您应该这样编写:
DataSize &= ~(static_cast<uint64_t>(getStubAlignment()) - 1);
大概:
DataSize &= ~(getStubAlignment() - 1ULL);
片段N34:显式转换失败 template <typename T> void scaleShuffleMask(int Scale, ArrayRef<T> Mask, SmallVectorImpl<T> &ScaledMask) { assert(0 < Scale && "Unexpected scaling factor"); int NumElts = Mask.size(); ScaledMask.assign(static_cast<size_t>(NumElts * Scale), -1); .... }
PVS-Studio警告:
V1028 [CWE-190]可能的溢出。 考虑将'NumElts * Scale'运算符的操作数强制转换为'size_t'类型,而不是结果。 1577年
显式类型转换用于在乘以
int类型的变量时防止溢出。 但是,此处的显式转换不能防止溢出。
首先,将对变量进行乘法运算,然后才将32位乘法结果扩展为size_t类型。片段N35:复制粘贴失败 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()));
V778 [CWE-682]找到了两个类似的代码片段。也许这是一个错字,应该使用“ Op1”变量而不是“ Op0”。InstCombineCompares.cpp 5507这种新的有趣的诊断方法揭示了以下情况:复制了一段代码,并且其中的一些名称开始更改,但没有在一个地方纠正。请注意,在第二个块中,他们将Op0更改为Op1。但是在一个地方,他们没有解决它。最有可能的是,它应该这样写: if (!match(Op1, m_PosZeroFP()) && isKnownNeverNaN(Op1, &TLI)) { I.setOperand(1, ConstantFP::getNullValue(Op1->getType())); return &I; }
片段N36:变量混乱 struct Status { unsigned Mask; unsigned Mode; Status() : Mask(0), Mode(0){}; Status(unsigned Mask, unsigned Mode) : Mask(Mask), Mode(Mode) { Mode &= Mask; }; .... };
PVS-Studio警告:V1001 [CWE-563]分配了'Mode'变量,但在功能结束时未使用。SIModeRegister.cpp 48为函数参数提供与类成员相同的名称非常危险。很容易感到困惑。摆在我们面前的就是这种情况。此表达式没有意义: Mode &= Mask;
函数的参数会更改。就是这样。该参数不再使用。最有可能的是,必须这样写: Status(unsigned Mask, unsigned Mode) : Mask(Mask), Mode(Mode) { this->Mode &= Mask; };
片段N37:变量混乱 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<Symbol>(Sym)); Size += this->EntrySize; }
PVS-Studio警告:V1001 [CWE-563]分配了'Size'变量,但在功能结束时未使用。Object.cpp 424情况与上一个类似。应该写成: this->Size += this->EntrySize;
片段N38-N47:指针忘记检查之前,我们研究了V595诊断触发的示例。其本质是指针在开始时被取消引用,然后才进行检查。V1004的年轻诊断功能与其含义相反,但也可以检测到很多错误。它标识了在开始时检查了指针,然后又忘记了检查指针的情况。考虑在LLVM内部发现的此类情况。 int getGEPCost(Type *PointeeType, const Value *Ptr, ArrayRef<const Value *> Operands) { .... if (Ptr != nullptr) {
PVS-Studio警告:V1004 [CWE-476]在针对nullptr进行验证之后,不安全地使用了'Ptr'指针。检查行:729,738。TargetTransformInfoImpl.h 738 Ptr变量可以为nullptr,如检查所示: if (Ptr != nullptr)
但是,下面的指针在没有事先验证的情况下被取消引用: auto PtrSizeBits = DL.getPointerTypeSizeInBits(Ptr->getType());
考虑另一种类似的情况。 llvm::DISubprogram *CGDebugInfo::getFunctionFwdDeclOrStub(GlobalDecl GD, bool Stub) { .... auto *FD = dyn_cast<FunctionDecl>(GD.getDecl()); SmallVector<QualType, 16> ArgTypes; if (FD)
PVS-Studio警告:V1004 [CWE-476]在针对nullptr对其进行验证之后,不安全地使用了“ FD”指针。检查行:3228、3231。CGDebugInfo.cpp 3231注意指针FD。我确信问题很明显,不需要任何特殊说明。并且: static void computePolynomialFromPointer(Value &Ptr, Polynomial &Result, Value *&BasePtr, const DataLayout &DL) { PointerType *PtrTy = dyn_cast<PointerType>(Ptr.getType()); if (!PtrTy) {
PVS-Studio警告:V1004 [CWE-476]在针对nullptr进行了验证之后,“ PtrTy”指针被不安全地使用。检查行:960、965。InterleavedLoadCombinePass.cpp 965如何保护自己免受此类错误的侵害?请仔细检查代码,并使用PVS-Studio静态分析器定期检查代码。带其他带有此类错误的代码段是没有意义的。我只会在文章中留下警告列表:- V1004 [CWE-476]在针对nullptr进行验证之后,不安全地使用了“ Expr”指针。检查行:1049、1078。DebugInfoMetadata.cpp 1078
- V1004 [CWE-476]在针对nullptr对其进行验证之后,不安全地使用了'PI'指针。检查行:733、753。LegacyPassManager.cpp 753
- V1004 [CWE-476] The 'StatepointCall' pointer was used unsafely after it was verified against nullptr. Check lines: 4371, 4379. Verifier.cpp 4379
- V1004 [CWE-476] The 'RV' pointer was used unsafely after it was verified against nullptr. Check lines: 2263, 2268. TGParser.cpp 2268
- V1004 [CWE-476] The 'CalleeFn' pointer was used unsafely after it was verified against nullptr. Check lines: 1081, 1096. SimplifyLibCalls.cpp 1096
- V1004 [CWE-476] The 'TC' pointer was used unsafely after it was verified against nullptr. Check lines: 1819, 1824. Driver.cpp 1824
N48-N60: , ( ) std::unique_ptr<IRMutator> createISelMutator() { .... std::vector<std::unique_ptr<IRMutationStrategy>> Strategies; Strategies.emplace_back( new InjectorIRStrategy(InjectorIRStrategy::getDefaultOps())); .... }
PVS-Studio警告:V1023 [CWE-460]通过“ emplace_back”方法将没有所有者的指针添加到“策略”容器中。发生异常时会发生内存泄漏。 llvm-isel-fuzzer.cpp 58要将项添加到像std :: vector <std :: unique_ptr <X >>这样的容器的末尾,您不能只写xxx.push_back(新X),因为没有从X *到std的隐式转换: :unique_ptr <X>。一种常见的解决方案是编写xxx.emplace_back(新X),因为它可以编译:emplace_back方法直接从参数构造元素,因此可以使用显式构造函数。这不安全。如果向量已满,则分配内存。内存重新分配操作可能会失败,导致抛出std :: bad_alloc异常。在这种情况下,指针将丢失,并且创建的对象将永远不会被删除。一个安全的解决方案是创建unique_ptr,它将在向量尝试重新分配内存之前拥有指针: xxx.push_back(std::unique_ptr<X>(new X))
从C ++ 14开始,您可以使用'std :: make_unique': xxx.push_back(std::make_unique<X>())
这种类型的缺陷对于LLVM并不重要。如果无法分配内存,则编译器将停止工作。但是,对于具有较长正常运行时间的应用程序,如果内存分配失败,该应用程序无法结束,那么这可能是一个令人讨厌的错误。因此,尽管此代码不会对LLVM造成实际威胁,但我发现谈论此错误模式和PVS-Studio分析仪学会检测到它很有用。此类型的其他警告:- V1023 [CWE-460]通过'emplace_back'方法将没有所有者的指针添加到'Passes'容器中。发生异常时会发生内存泄漏。PassManager.h 546
- V1023 [CWE-460] A pointer without owner is added to the 'AAs' container by the 'emplace_back' method. A memory leak will occur in case of an exception. AliasAnalysis.h 324
- V1023 [CWE-460] A pointer without owner is added to the 'Entries' container by the 'emplace_back' method. A memory leak will occur in case of an exception. DWARFDebugFrame.cpp 519
- V1023 [CWE-460] A pointer without owner is added to the 'AllEdges' container by the 'emplace_back' method. A memory leak will occur in case of an exception. CFGMST.h 268
- V1023 [CWE-460] A pointer without owner is added to the 'VMaps' container by the 'emplace_back' method. A memory leak will occur in case of an exception. SimpleLoopUnswitch.cpp 2012
- V1023 [CWE-460] A pointer without owner is added to the 'Records' container by the 'emplace_back' method. A memory leak will occur in case of an exception. FDRLogBuilder.h 30
- V1023 [CWE-460] A pointer without owner is added to the 'PendingSubmodules' container by the 'emplace_back' method. A memory leak will occur in case of an exception. ModuleMap.cpp 810
- V1023 [CWE-460] A pointer without owner is added to the 'Objects' container by the 'emplace_back' method. A memory leak will occur in case of an exception. DebugMap.cpp 88
- V1023 [CWE-460] A pointer without owner is added to the 'Strategies' container by the 'emplace_back' method. A memory leak will occur in case of an exception. llvm-isel-fuzzer.cpp 60
- V1023 [CWE-460] A pointer without owner is added to the 'Modifiers' container by the 'emplace_back' method. A memory leak will occur in case of an exception. llvm-stress.cpp 685
- V1023 [CWE-460] A pointer without owner is added to the 'Modifiers' container by the 'emplace_back' method. A memory leak will occur in case of an exception. llvm-stress.cpp 686
- V1023 [CWE-460] A pointer without owner is added to the 'Modifiers' container by the 'emplace_back' method. A memory leak will occur in case of an exception. llvm-stress.cpp 688
- V1023 [CWE-460] A pointer without owner is added to the 'Modifiers' container by the 'emplace_back' method. A memory leak will occur in case of an exception. llvm-stress.cpp 689
- V1023 [CWE-460] A pointer without owner is added to the 'Modifiers' container by the 'emplace_back' method. A memory leak will occur in case of an exception. llvm-stress.cpp 690
- V1023 [CWE-460] A pointer without owner is added to the 'Modifiers' container by the 'emplace_back' method. A memory leak will occur in case of an exception. llvm-stress.cpp 691
- V1023 [CWE-460] A pointer without owner is added to the 'Modifiers' container by the 'emplace_back' method. A memory leak will occur in case of an exception. llvm-stress.cpp 692
- V1023 [CWE-460] A pointer without owner is added to the 'Modifiers' container by the 'emplace_back' method. A memory leak will occur in case of an exception. llvm-stress.cpp 693
- V1023 [CWE-460] A pointer without owner is added to the 'Modifiers' container by the 'emplace_back' method. A memory leak will occur in case of an exception. llvm-stress.cpp 694
- V1023 [CWE-460] A pointer without owner is added to the 'Operands' container by the 'emplace_back' method. A memory leak will occur in case of an exception. GlobalISelEmitter.cpp 1911
- V1023 [CWE-460] A pointer without owner is added to the 'Stash' container by the 'emplace_back' method. A memory leak will occur in case of an exception. GlobalISelEmitter.cpp 2100
- V1023 [CWE-460] A pointer without owner is added to the 'Matchers' container by the 'emplace_back' method. A memory leak will occur in case of an exception. GlobalISelEmitter.cpp 2702
结论
总共我发出了60条警告,此后我停了下来。 PVS-Studio分析仪在LLVM中是否检测到其他任何缺陷?是的,有。但是,当我写出这篇文章的代码片段时,已经是傍晚了,或者甚至是晚上,我决定是时候结束了。希望您对此感兴趣,并且希望尝试使用PVS-Studio分析仪。您可以在此页面上下载分析仪并获得试用版密钥。最重要的是,定期使用静态分析。为了普及静态分析方法和PVS-Studio而进行的一次性检查不是正常情况。祝您提高代码的质量和可靠性!
如果您想与说英语的读者分享这篇文章,请使用以下链接:Andrey Karpov。使用PVS-Studio在LLVM 8中查找错误。