Han pasado dos años desde la última vez que verificamos el código del proyecto LLVM con PVS-Studio, así que veamos si PVS-Studio sigue siendo el líder entre las herramientas para detectar errores y debilidades de seguridad. Lo haremos escaneando la versión LLVM 8.0.0 en busca de nuevos errores.
El artículo que debe ser escrito.
Francamente, no tenía ganas de escribir este artículo. No es muy divertido hablar sobre el proyecto que ya revisamos más de una vez (
1 ,
2 ,
3 ). Preferiría algo nuevo, pero no tenía otra opción.
Cada vez que se lanza una nueva versión de LLVM o se actualiza
Clang Static Analyzer , recibimos correos electrónicos que leen en estas líneas:
¡Hola, la nueva versión de Clang Static Analyzer tiene nuevos diagnósticos! PVS-Studio parece ser cada vez menos relevante. Clang puede detectar más errores que antes y ahora se está poniendo al día con PVS-Studio. Que dijisteA eso con mucho gusto respondería:
¡Tampoco hemos estado holgazaneando! Hemos aumentado significativamente las capacidades de PVS-Studio, así que no se preocupe, seguimos siendo los mejores.
Pero esa es una mala respuesta, me temo. No ofrece pruebas, y esa es la razón por la que estoy escribiendo este artículo. Entonces, revisé LLVM una vez más y encontré toneladas de errores de todo tipo. Los que más me gustaron serán discutidos más a fondo. Clang Static Analyzer no puede detectar estos errores (o hace que el proceso sea muy problemático), y nosotros sí. Y, por cierto, solo me llevó una noche escribir todos esos errores.
Sin embargo, el artículo me tomó varias semanas en completar. Simplemente no pude poner el material reunido en texto :).
Por cierto, si se pregunta qué técnicas emplea PVS-Studio para detectar errores y vulnerabilidades, eche un vistazo a esta
publicación .
Diagnósticos nuevos y existentes
Como ya dije, la última de las muchas verificaciones de LLVM se realizó hace dos años, y los errores encontrados fueron corregidos por los autores. Este artículo mostrará una nueva porción de errores. ¿Cómo es que hay nuevos errores? Hay tres razones:
- El proyecto LLVM está evolucionando; los autores modifican el código existente y agregan código nuevo. Ambas partes modificadas y nuevas tienen naturalmente nuevos errores. Este hecho es un fuerte argumento para ejecutar análisis estáticos regularmente en lugar de de vez en cuando. El formato de nuestros artículos es perfecto para mostrar las capacidades de PVS-Studio, pero no tiene nada que ver con mejorar la calidad del código o hacer que la corrección de errores sea menos costosa. ¡Use análisis estático regularmente!
- Modificamos y mejoramos los diagnósticos existentes, permitiendo que el analizador detecte errores que antes no podía detectar.
- PVS-Studio se ha mejorado con nuevos diagnósticos, que no existían hace dos años. Agrupe tales advertencias en una sección separada para que el progreso de PVS-Studio se vea más claramente.
Defectos encontrados por diagnósticos existentes
Fragmento no. 1: copiar y pegarstatic bool ShouldUpgradeX86Intrinsic(Function *F, StringRef Name) { if (Name == "addcarryx.u32" ||
Mensaje de diagnóstico de PVS-Studio:
V501 [CWE-570] Hay subexpresiones idénticas 'Name.startswith ("avx512.mask.permvar.")' A la izquierda y a la derecha de '||' operador AutoUpgrade.cpp 73
La aparición del "avx512.mask.permvar". la subcadena se verifica dos veces. La segunda condición obviamente era verificar otra cosa, pero el programador olvidó cambiar la línea copiada.
Fragmento no. 2: error tipográfico 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; .... }
Mensaje de diagnóstico de PVS-Studio: V501 Hay subexpresiones idénticas 'CXNameRange_WantQualifier' a la izquierda y a la derecha de '|' operador CIndex.cpp 7245
La constante nombrada
CXNameRange_WantQualifier se usa dos veces debido a un error tipográfico.
Fragmento no. 3: Confusión sobre la precedencia del operador int PPCTTIImpl::getVectorInstrCost(unsigned Opcode, Type *Val, unsigned Index) { .... if (ISD == ISD::EXTRACT_VECTOR_ELT && Index == ST->isLittleEndian() ? 1 : 0) return 0; .... }
Mensaje de diagnóstico de PVS-Studio:
V502 [CWE-783] Quizás el operador '?:' Funciona de una manera diferente a la esperada. El operador '?:' Tiene una prioridad menor que el operador '=='. PPCTargetTransformInfo.cpp 404
Encuentro este error muy lindo. Sí, sé que tengo un sabor extraño :).
Según lo dictado por la
precedencia del operador , la expresión original se evalúa de la siguiente manera:
(ISD == ISD::EXTRACT_VECTOR_ELT && (Index == ST->isLittleEndian())) ? 1 : 0
Sin embargo, desde el punto de vista práctico, esta condición no tiene sentido, ya que puede reducirse a:
(ISD == ISD::EXTRACT_VECTOR_ELT && Index == ST->isLittleEndian())
Esto obviamente es un error. Debe haber sido la variable de
índice que el programador quería comprobar para 0/1. Para corregir el código, el operador ternario debe estar entre paréntesis:
if (ISD == ISD::EXTRACT_VECTOR_ELT && Index == (ST->isLittleEndian() ? 1 : 0))
El operador ternario es realmente muy complicado y puede conducir a errores lógicos. Úselo con cuidado y no dude en poner paréntesis adicionales a su alrededor. Este tema se trata con más detalle
aquí , en la sección "¿Cuidado con el?: Operador y encerrarlo entre paréntesis".
Fragmentos no. 4, 5: puntero nulo 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; } .... }
Mensaje de diagnóstico de PVS-Studio:
V522 [CWE-476] Puede producirse una desreferenciación del puntero nulo 'LHS'. TGParser.cpp 2152
Si el puntero
LHS es nulo, se espera que el programa genere una advertencia. En cambio, desreferenciará ese puntero muy nulo:
LHS-> getAsString () .
Es una situación bastante típica que los controladores de errores contengan errores porque los desarrolladores no los prueban correctamente. Los analizadores estáticos verifican todo el código accesible, sin importar con qué frecuencia se ejecute realmente. Este es un buen ejemplo de cómo el análisis estático complementa otras pruebas de código y medios de protección.
Un manejador defectuoso similar para el puntero
RHS se encuentra un poco más lejos: V522 [CWE-476] Puede tener lugar la desreferenciación del puntero nulo 'RHS'. TGParser.cpp 2186
Fragmento no. 6: Usar un puntero después de un movimiento static Expected<bool> ExtractBlocks(....) { .... std::unique_ptr<Module> ProgClone = CloneModule(BD.getProgram(), VMap); .... BD.setNewProgram(std::move(ProgClone));
Mensaje de diagnóstico de PVS-Studio: V522 [CWE-476] Puede producirse una desreferenciación del puntero nulo 'ProgClone'. Miscompilation.cpp 601
El puntero inteligente
ProgClone primero libera la propiedad del objeto:
BD.setNewProgram(std::move(ProgClone));
De hecho,
ProgClone se ha convertido en un puntero nulo, por lo que, técnicamente, un puntero nulo se desreferencia un poco más:
Function *NewF = ProgClone->getFunction(MisCompFunctions[i].first);
¡Pero eso no sucederá! Tenga en cuenta que el bucle en realidad no se ejecuta en absoluto.
El contenedor
MiscompiledFunctions se borra primero:
MiscompiledFunctions.clear();
Y luego su tamaño se usa en la condición de bucle:
for (unsigned i = 0, e = MisCompFunctions.size(); i != e; ++i) {
Obviamente, el ciclo simplemente no comenzará. Creo que también es un error, y el código debía verse de alguna manera diferente.
Supongo que lo que vemos aquí es esa notoria paridad de error, donde un error actúa como un disfraz para otro :).
Fragmento no. 7: Usar un puntero después de un movimiento 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));
Mensaje de diagnóstico de PVS-Studio: V522 [CWE-476] Puede tener lugar la referencia del puntero nulo 'Prueba'. Miscompilation.cpp 709
Este es similar al caso anterior. El contenido del objeto se mueve primero y luego se usa como si nada hubiera pasado. Este error se ha vuelto cada vez más común después de agregar semántica de movimiento a C ++. ¡Eso es lo que me gusta de este idioma! Te dan nuevas formas de dispararte en el pie, lo que significa que PVS-Studio siempre tendrá trabajo que hacer :).
Fragmento no. 8: puntero nulo 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); }
Mensaje de diagnóstico de PVS-Studio: V522 [CWE-476] Puede producirse una desreferenciación del puntero nulo 'Tipo'. PrettyFunctionDumper.cpp 233
Al igual que los controladores de errores, las funciones de prueba que imprimen datos de depuración tampoco suelen obtener una cobertura de prueba adecuada, y este es un ejemplo de eso. En lugar de ayudar al usuario a resolver sus problemas, la función está esperando que lo solucionen.
Código fijo:
if (Type) Type->dump(*this); else Printer << "<unknown-type>";
Fragmento no. 9: puntero nulo void SearchableTableEmitter::collectTableEntries( GenericTable &Table, const std::vector<Record *> &Items) { .... RecTy *Ty = resolveTypes(Field.RecType, TI->getType()); if (!Ty)
Mensaje de diagnóstico de PVS-Studio: V522 [CWE-476] Puede producirse una desreferenciación del puntero nulo 'Ty'. SearchableTableEmitter.cpp 614
No creo que necesites ningún comentario sobre este.
Fragmento no. 10: error tipográfico bool FormatTokenLexer::tryMergeCSharpNullConditionals() { .... auto &Identifier = *(Tokens.end() - 2); auto &Question = *(Tokens.end() - 1); .... Identifier->ColumnWidth += Question->ColumnWidth; Identifier->Type = Identifier->Type;
Mensaje de diagnóstico de PVS-Studio:
V570 La variable 'Identificador-> Tipo' se asigna a sí misma. FormatTokenLexer.cpp 249
Asignar una variable a sí mismo es una operación sin sentido. El programador debe haber querido hacer lo siguiente:
Identifier->Type = Question->Type;
Fragmento no. 11: descanso sospechoso void SystemZOperand::print(raw_ostream &OS) const { switch (Kind) { break; case KindToken: OS << "Token:" << getToken(); break; case KindReg: OS << "Reg:" << SystemZInstPrinter::getRegisterName(getReg()); break; .... }
Mensaje de diagnóstico de PVS-Studio:
V622 [CWE-478] Considere la posibilidad de inspeccionar la declaración 'switch'. Es posible que falte el primer operador de 'caso'. SystemZAsmParser.cpp 652
Hay una declaración de
ruptura muy sospechosa al principio. ¿No debería haber algo más aquí?
Fragmento no. 12: Verificación de un puntero después de desreferenciar 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"); .... }
Mensaje de diagnóstico de PVS-Studio:
V595 [CWE-476] El puntero 'Callee' se utilizó antes de que se verificara contra nullptr. Líneas de verificación: 172, 174. AMDGPUInline.cpp 172
El puntero de
Callee se desreferencia primero cuando se
llama a la función
getTTI .
Y luego resulta que el puntero debe verificarse para
nullptr :
if (!Callee || Callee->isDeclaration())
Demasiado tarde ...
Fragmentos no. 13 - No ...: comprobar un puntero después de desreferenciarEl ejemplo anterior no es único. El mismo problema se encuentra en este fragmento:
static Value *optimizeDoubleFP(CallInst *CI, IRBuilder<> &B, bool isBinary, bool isPrecise = false) { .... Function *CalleeFn = CI->getCalledFunction(); StringRef CalleeNm = CalleeFn->getName();
Mensaje de diagnóstico de PVS-Studio: V595 [CWE-476] El puntero 'CalleeFn' se utilizó antes de que se verificara contra nullptr. Líneas de verificación: 1079, 1081. SimplifyLibCalls.cpp 1079
Y este:
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());
Mensaje de diagnóstico de PVS-Studio: V595 [CWE-476] El puntero 'ND' se utilizó antes de verificarlo con nullptr. Líneas de verificación: 532, 534. SemaTemplateInstantiateDecl.cpp 532
Y aqui:
- V595 [CWE-476] El puntero 'U' se utilizó antes de que se verificara contra nullptr. Verifique las líneas: 404, 407. DWARFFormValue.cpp 404
- V595 [CWE-476] El puntero 'ND' se utilizó antes de que se verificara contra nullptr. Líneas de verificación: 2149, 2151. SemaTemplateInstantiate.cpp 2149
Luego perdí interés en rastrear las advertencias V595, por lo que no puedo decirte si hay otros errores de este tipo además de los que se muestran arriba. Apuesto a que hay.
Fragmentos no. 17, 18: cambio sospechoso static inline bool processLogicalImmediate(uint64_t Imm, unsigned RegSize, uint64_t &Encoding) { .... unsigned Size = RegSize; .... uint64_t NImms = ~(Size-1) << 1; .... }
Mensaje de diagnóstico de PVS-Studio:
V629 [CWE-190] Considere inspeccionar la expresión '~ (Tamaño - 1) << 1'. Desplazamiento de bits del valor de 32 bits con una expansión posterior al tipo de 64 bits. AArch64AddressingModes.h 260
Este código podría ser correcto, pero parece extraño y necesita ser examinado.
Suponga que la variable
Tamaño tiene el valor 16; entonces se espera que la variable
NImms obtenga el siguiente valor:
11111111111111111111111111111111111111111111111111111111111111100000000
Pero en realidad obtendrá el valor:
0000000000000000000000000000000000001111111111111111111111111111100000
Esto sucede porque todos los cálculos se realizan en el tipo sin signo de 32 bits, y solo entonces se promociona implícitamente a
uint64_t , con los bits más significativos
puestos a cero.
El problema se puede solucionar de la siguiente manera:
uint64_t NImms = ~static_cast<uint64_t>(Size-1) << 1;
Otro error de este tipo: V629 [CWE-190] Considere inspeccionar la expresión 'Immr << 6'. Desplazamiento de bits del valor de 32 bits con una expansión posterior al tipo de 64 bits. AArch64AddressingModes.h 269
Fragmento no. 19: ¿Falta otra palabra clave ? void AMDGPUAsmParser::cvtDPP(MCInst &Inst, const OperandVector &Operands) { .... if (Op.isReg() && Op.Reg.RegNo == AMDGPU::VCC) {
Mensaje de diagnóstico de PVS-Studio:
V646 [CWE-670] Considere inspeccionar la lógica de la aplicación. Es posible que falte la palabra clave 'else'. AMDGPUAsmParser.cpp 5655
Este no es un error. Dado que el bloque de la primera instrucción
if termina con
continue , no importa si tiene la palabra clave
else o no. El comportamiento será el mismo en cualquier caso. Sin embargo, lo que falta hace que el código sea menos legible y, por lo tanto, potencialmente peligroso. Si
continuar desaparece un día, el comportamiento cambiará drásticamente. Recomiendo agregar
más .
Fragmento no. 20: cuatro errores tipográficos idénticos 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();
Mensajes de diagnóstico de PVS-Studio:
- V655 [CWE-480] Las cadenas se concatenaron pero no se utilizan. Considere inspeccionar la expresión 'Result + Name.str ()'. Symbol.cpp 32
- V655 [CWE-480] Las cadenas se concatenaron pero no se utilizan. Considere inspeccionar la expresión 'Resultado + "(Clase ObjC)" + Nombre.str ()'. Symbol.cpp 35
- V655 [CWE-480] Las cadenas se concatenaron pero no se utilizan. Considere inspeccionar la expresión 'Resultado + "(ObjC Clase EH)" + Nombre.str ()'. Symbol.cpp 38
- V655 [CWE-480] Las cadenas se concatenaron pero no se utilizan. Considere inspeccionar la expresión 'Resultado + "(ObjC IVar)" + Nombre.str ()'. Symbol.cpp 41
El programador usó accidentalmente el operador + en lugar de + = y terminó con cuatro construcciones sin sentido.
Fragmento no. 21: Comportamiento indefinido 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(); } } }
Intenta detectar el error por tu cuenta primero. Agregué la imagen para que no vea la respuesta de inmediato:
Mensaje de diagnóstico de PVS-Studio:
V708 [CWE-758] Se utiliza una construcción peligrosa: 'FeaturesMap [Op] = FeaturesMap.size ()', donde 'FeaturesMap' es de la clase 'map'. Esto puede conducir a un comportamiento indefinido. RISCVCompressInstEmitter.cpp 490
La línea defectuosa es esta:
FeaturesMap[Op] = FeaturesMap.size();
Si no se ha encontrado el elemento
Op , el programa crea un nuevo elemento en el mapa y le asigna el número total de elementos en este mapa. Simplemente no sabe si se llamará a la función de
tamaño antes o después de agregar el nuevo elemento.
Fragmentos no. 22 - No. 24: tareas duplicadas Error MachOObjectFile::checkSymbolTable() const { .... } else { MachO::nlist STE = getSymbolTableEntry(SymDRI); NType = STE.n_type;
Mensaje de diagnóstico de PVS-Studio:
V519 [CWE-563] A la variable 'NType' se le asignan valores dos veces sucesivamente. Quizás esto sea un error. Líneas de verificación: 1663, 1664. MachOObjectFile.cpp 1664
No creo que sea un verdadero error, sino una tarea duplicada. Pero sigue siendo un defecto.
Otros dos casos:
- V519 [CWE-563] A la variable 'B.NDesc' se le asignan valores dos veces seguidas. Quizás esto sea un error. Líneas de verificación: 1488, 1489. llvm-nm.cpp 1489
- V519 [CWE-563] A la variable se le asignan valores dos veces seguidas. Quizás esto sea un error. Verifique las líneas: 59, 61. coff2yaml.cpp 61
Fragmentos no. 25 - No. 27: Más tareas duplicadasEstos tratan con versiones ligeramente diferentes de tareas duplicadas.
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; .... }
Mensaje de diagnóstico de PVS-Studio: V519 [CWE-563] A la variable 'Alineación' se le asignan valores dos veces sucesivamente. Quizás esto sea un error. Líneas de verificación: 1158, 1160. LoadStoreVectorizer.cpp 1160
Este es un fragmento muy extraño, y probablemente contiene un error lógico. Primero se le asigna el valor a la variable
Alineamiento en función de la condición, y luego se le asigna el valor una vez más, pero sin ninguna verificación previa.
Defectos similares:
- V519 [CWE-563] A la variable 'Efectos' se le asignan valores dos veces seguidas. Quizás esto sea un error. Líneas de verificación: 152, 165. WebAssemblyRegStackify.cpp 165
- V519 [CWE-563] La variable 'ExpectNoDerefChunk' recibe valores asignados dos veces sucesivamente. Quizás esto sea un error. Líneas de verificación: 4970, 4973. SemaType.cpp 4973
Fragmento no. 28: condición siempre verdadera 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)
Mensaje de diagnóstico de PVS-Studio:
V547 [CWE-571] La expresión 'nextByte! = 0x90' siempre es verdadera. X86DisassemblerDecoder.cpp 379
El cheque no tiene sentido. La variable
nextByte nunca es igual a
0x90 : solo se deduce lógicamente de la verificación anterior. Esto debe ser un error lógico.
Fragmentos no. 29 - No ....: Siempre condiciones verdaderas / falsasHay muchas advertencias sobre una condición completa (
V547 ) o parte de una condición (
V560 ) que siempre es verdadera o falsa. En lugar de errores genuinos, estos a menudo son simplemente códigos incorrectos, los efectos de la expansión de macros, etc. Dicho esto, todas estas advertencias aún deben verificarse porque algunas de ellas pueden señalar errores lógicos genuinos. Por ejemplo, el siguiente fragmento no se ve bien:
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; .... }
Mensaje de diagnóstico de PVS-Studio:
V560 [CWE-570] Una parte de la expresión condicional siempre es falsa: RegNo == 0xe. ARMDisassembler.cpp 939
La constante
0xE es el número decimal 14. La comprobación
RegNo == 0xe no tiene sentido porque si
RegNo> 13 , la función volverá.
Vi muchas otras advertencias de V547 y V560, pero, al igual que con
V595 , no me sentí entusiasmado por
revisarlas ya que ya tenía suficiente material para un artículo :). Por lo tanto, no hay cifras para el número total de errores de este tipo en LLVM.
Aquí hay un ejemplo para ilustrar por qué es aburrido verificar esas advertencias. El analizador es totalmente correcto al emitir una advertencia sobre el siguiente código. Pero todavía no es un error.
bool UnwrappedLineParser::parseBracedList(bool ContinueOnSemicolons, tok::TokenKind ClosingBraceKind) { bool HasError = false; .... HasError = true; if (!ContinueOnSemicolons) return !HasError; .... }
Mensaje de diagnóstico de PVS-Studio: V547 [CWE-570] La expresión '! HasError' siempre es falsa. UnwrappedLineParser.cpp 1635
Fragmento no. 30: regreso sospechoso 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(); } .... }
Mensaje de diagnóstico de PVS-Studio:
V612 [CWE-670] Un 'retorno' incondicional dentro de un bucle. R600OptimizeVectorRegisters.cpp 63
Es un error o una técnica de codificación específica destinada a comunicar alguna idea a otros programadores. Para mí no dice nada, excepto que es un código muy sospechoso. Por favor no escriba código como ese :).
¿Te sientes cansado? Bien, es hora de hacer té o café.
Defectos encontrados por nuevos diagnósticos
Creo que 30 ejemplos son suficientes para los diagnósticos existentes. Ahora veamos si podemos encontrar algo interesante con los nuevos diagnósticos, que se agregaron después de la verificación
anterior . En los últimos dos años, el módulo analizador C ++ se amplió con 66 nuevos diagnósticos.
Fragmento no. 31: código inalcanzable 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(); }
Mensaje de diagnóstico de PVS-Studio:
V779 [CWE-561] Código inalcanzable detectado. Es posible que haya un error presente. ExecutionUtils.cpp 146
Como puede ver, ambas ramas de la declaración
if terminan con una declaración
return , lo que significa que el contenedor
CtorDtorsByPriority nunca se borrará.
Fragmento no. 32: código inalcanzable 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);
Mensaje de diagnóstico de PVS-Studio: V779 [CWE-561] Código inalcanzable detectado. Es posible que haya un error presente. LLParser.cpp 835
Este es interesante. Echa un vistazo a esta parte primero:
return ParseTypeIdEntry(SummaryID); break;
Parece que no hay nada extraño en este código; la declaración de
ruptura es innecesaria y se puede eliminar de forma segura. Pero no es tan simple.
La advertencia se activa mediante las siguientes líneas:
Lex.setIgnoreColonInIdentifiers(false); return false;
De hecho, este código es inalcanzable. ¡Todas las etiquetas de caso de la declaración de
cambio finalizan con un
retorno , y la
ruptura solitaria sin sentido ya no parece tan inofensiva! ¿Qué pasa si una de las ramas estaba destinada a terminar con un
descanso en lugar de
regresar ?
Fragmento no. 33: borrado accidental de los bits más significativos 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); .... }
Mensaje de diagnóstico de PVS-Studio:
V784 El tamaño de la máscara de bits es menor que el tamaño del primer operando. Esto causará la pérdida de bits más altos. RuntimeDyld.cpp 815
Tenga en cuenta que la función
getStubAlignment devuelve un valor
sin signo . Veamos cómo se evaluará la expresión, suponiendo que la función devolverá el valor 8:
~ (getStubAlignment () - 1)
~ (8u-1)
0xFFFFFFF8u
Tenga en cuenta ahora que el
tipo de la variable
DataSize es de 64 bits sin signo. Entonces resulta que ejecutar la operación DataSize & 0xFFFFFFF8 dará como resultado la eliminación de los 32 bits más significativos del valor. No creo que el programador quisiera eso. Quizás quisieron decir que era DataSize y 0xFFFFFFFFFFFFFFFFF8u.
Para corregir el error, el código debe reescribirse así:
DataSize &= ~(static_cast<uint64_t>(getStubAlignment()) - 1);
O así:
DataSize &= ~(getStubAlignment() - 1ULL);
Fragmento no. 34: Mala conversión de tipo explícito 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); .... }
Mensaje de diagnóstico de PVS-Studio:
V1028 [CWE-190] Posible desbordamiento. Considere convertir los operandos del operador 'NumElts * Scale' al tipo 'size_t', no el resultado. X86ISelLowering.h 1577
La conversión explícita de tipos se utiliza para evitar un desbordamiento al multiplicar variables de tipo
int . En este caso, sin embargo, no funciona porque la multiplicación ocurrirá primero y solo entonces el resultado de 32 bits se promocionará a tipo
size_t .
Fragmento no. 35: Mal copiar-pegar 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] Se encontraron dos fragmentos de código similares. Quizás, este es un error tipográfico y se debe usar la variable 'Op1' en lugar de 'Op0'. InstCombineCompares.cpp 5507
Este nuevo diagnóstico fresco detecta situaciones en las que se escribe un fragmento de código usando copiar y pegar, con todos los nombres cambiados, excepto uno.
Tenga en cuenta que todos los
Op0 excepto uno se cambiaron a
Op1 en el segundo bloque. El código probablemente debería verse así:
if (!match(Op1, m_PosZeroFP()) && isKnownNeverNaN(Op1, &TLI)) { I.setOperand(1, ConstantFP::getNullValue(Op1->getType())); return &I; }
Fragmento no. 36: Variables mezcladas struct Status { unsigned Mask; unsigned Mode; Status() : Mask(0), Mode(0){}; Status(unsigned Mask, unsigned Mode) : Mask(Mask), Mode(Mode) { Mode &= Mask; }; .... };
Mensaje de diagnóstico de PVS-Studio:
V1001 [CWE-563] La variable 'Modo' se asigna pero no se usa al final de la función. SIModeRegister.cpp 48
Es muy peligroso tener los mismos nombres para los argumentos de la función que para los miembros de la clase porque corre el riesgo de mezclarlos. Lo que ves aquí es un ejemplo de eso. La siguiente expresión no tiene sentido:
Mode &= Mask;
El argumento cambia pero nunca se usa después de eso. Este fragmento probablemente debería verse así:
Status(unsigned Mask, unsigned Mode) : Mask(Mask), Mode(Mode) { this->Mode &= Mask; };
Fragmento no. 37: Variables mezcladas 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; }
Mensaje de diagnóstico de PVS-Studio: V1001 [CWE-563] La variable 'Tamaño' se asigna pero no se usa al final de la función. Object.cpp 424
Este es similar al ejemplo anterior. Versión correcta:
this->Size += this->EntrySize;
Fragmentos no. 38 - No. 47: Falta la comprobación del punteroObservamos algunos ejemplos de la advertencia
V595 un poco antes. Lo que detecta es una situación en la que primero se desreferencia un puntero y solo luego se verifica. El nuevo diagnóstico
V1004 es lo opuesto a eso, y también detecta toneladas de errores. Busca punteros ya probados que no se vuelven a probar cuando es necesario. Aquí hay algunos errores de este tipo que se encuentran en el código de LLVM.
int getGEPCost(Type *PointeeType, const Value *Ptr, ArrayRef<const Value *> Operands) { .... if (Ptr != nullptr) {
Mensaje de diagnóstico de PVS-Studio: V1004 [CWE-476] El puntero 'Ptr' se usó de manera insegura después de que se verificó contra nullptr. Líneas de verificación: 729, 738. TargetTransformInfoImpl.h 738
Ptr puede ser
nullptr , lo que se indica mediante la comprobación:
if (Ptr != nullptr)
Sin embargo, el mismo puntero se desreferencia sin tal verificación un poco más:
auto PtrSizeBits = DL.getPointerTypeSizeInBits(Ptr->getType());
Otro caso similar.
llvm::DISubprogram *CGDebugInfo::getFunctionFwdDeclOrStub(GlobalDecl GD, bool Stub) { .... auto *FD = dyn_cast<FunctionDecl>(GD.getDecl()); SmallVector<QualType, 16> ArgTypes; if (FD)
Mensaje de diagnóstico de PVS-Studio: V1004 [CWE-476] El puntero 'FD' se usó de manera insegura después de que se verificó contra nullptr. Líneas de verificación: 3228, 3231. CGDebugInfo.cpp 3231
Tenga en cuenta el puntero
FD . Este error es sencillo, por lo que no hay comentarios sobre este.
Uno más aquí:
static void computePolynomialFromPointer(Value &Ptr, Polynomial &Result, Value *&BasePtr, const DataLayout &DL) { PointerType *PtrTy = dyn_cast<PointerType>(Ptr.getType()); if (!PtrTy) {
Mensaje de diagnóstico de PVS-Studio: V1004 [CWE-476] El puntero 'PtrTy' se usó de manera insegura después de que se verificó contra nullptr. Líneas de verificación: 960, 965. InterleavedLoadCombinePass.cpp 965
¿Cómo evitas errores como ese? Tenga mucho cuidado al revisar su código y verifíquelo regularmente con PVS-Studio.
No creo que debamos examinar otros ejemplos de este tipo, así que aquí hay solo una lista de las advertencias:
- V1004 [CWE-476] El puntero 'Expr' se usó de forma insegura después de que se verificó contra nullptr. Líneas de verificación: 1049, 1078. DebugInfoMetadata.cpp 1078
- V1004 [CWE-476] El puntero 'PI' se usó de forma insegura después de que se verificó contra nullptr. Líneas de verificación: 733, 753. LegacyPassManager.cpp 753
- V1004 [CWE-476] El puntero 'StatepointCall' se usó de manera insegura después de que se verificó contra nullptr. Líneas de verificación: 4371, 4379. Verifier.cpp 4379
- V1004 [CWE-476] El puntero 'RV' se usó de forma insegura después de que se verificó contra nullptr. Líneas de verificación: 2263, 2268. TGParser.cpp 2268
- V1004 [CWE-476] El puntero 'CalleeFn' se usó de forma insegura después de que se verificó contra nullptr. Líneas de verificación: 1081, 1096. SimplifyLibCalls.cpp 1096
- V1004 [CWE-476] El puntero 'TC' se usó de forma insegura después de que se verificó contra nullptr. Líneas de verificación: 1819, 1824. Driver.cpp 1824
Fragmentos no. 48 - No. 60: No es crítico pero sigue siendo un defecto (pérdida potencial de memoria) std::unique_ptr<IRMutator> createISelMutator() { .... std::vector<std::unique_ptr<IRMutationStrategy>> Strategies; Strategies.emplace_back( new InjectorIRStrategy(InjectorIRStrategy::getDefaultOps())); .... }
Mensaje de diagnóstico de PVS-Studio:
V1023 [CWE-460] El método 'emplace_back' agrega un puntero sin propietario al contenedor 'Estrategias'. Se producirá una pérdida de memoria en caso de una excepción. llvm-isel-fuzzer.cpp 58
No puede simplemente escribir
xxx.push_back (nueva X) para agregar un elemento a un contenedor de tipo
std :: vector <std :: unique_ptr <X>> porque no hay
conversión implícita de
X * a
std :: unique_ptr < X> .
La solución popular es escribir
xxx.emplace_back (nueva X) ya que es compilable: el método
emplace_back construye el elemento directamente a partir de los argumentos y, por lo tanto, puede usar constructores explícitos.
Pero esa solución no es segura. Si el vector está lleno, la memoria se reasignará. Esta operación puede fallar y terminar generando una excepción
std :: bad_alloc . En este caso, el puntero se perderá y el programa no podrá eliminar el objeto creado.
Una solución más segura es crear un
unique_ptr , que retendrá el puntero hasta que el vector intente reasignar la memoria:
xxx.push_back(std::unique_ptr<X>(new X))
El estándar C ++ 14 le permite usar 'std :: make_unique':
xxx.push_back(std::make_unique<X>())
Este tipo de defecto no tiene efecto en LLVM. La compilación simplemente terminará si falla la asignación de memoria. Dicho esto, puede ser bastante crítico en aplicaciones con un tiempo de
actividad prolongado , que no puede terminar simplemente cuando ocurre una falla de asignación de memoria.
Entonces, a pesar de que este código no es peligroso para LLVM, pensé que aún debería informarle sobre este patrón de error y el hecho de que PVS-Studio ahora puede detectarlo.
Otros casos similares:
- V1023 [CWE-460] El método 'emplace_back' agrega un puntero sin propietario al contenedor 'Passes'. Se producirá una pérdida de memoria en caso de una excepción. 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
Conclusión
Escribí 60 advertencias y me detuve en eso. ¿PVS-Studio encontró otros errores en LLVM? Sí lo hizo. Pero mientras escribía los ejemplos, cayó la noche, así que decidí dejarlo.Espero que hayas disfrutado leyendo este artículo y te haya animado a probar el analizador PVS-Studio por ti mismo.Visite esta página para descargar el analizador y obtener una clave de prueba.Lo más importante, utilice análisis estático regularmente. Los controles únicos , como los que hacemos para popularizar el análisis estático y promover PVS-Studio, no son el escenario normal.¡Buena suerte con la mejora de la calidad y fiabilidad de su código!