From 74b0803c582150c8dc6f3e435b932970bcf2e1d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Thu, 6 Feb 2025 14:41:53 +0100 Subject: [PATCH] Fix #13521 (Duplicate warnings nullPointerOutOfMemory and ctunullpointer) --- lib/checkbufferoverrun.cpp | 18 +++++------ lib/checkbufferoverrun.h | 6 ++-- lib/checknullpointer.cpp | 22 +++++++++++--- lib/checkuninitvar.cpp | 4 +++ lib/ctu.cpp | 55 +++++++++++++++++++++------------- lib/ctu.h | 26 ++++++++++++---- test/cli/whole-program_test.py | 32 ++++++++++++++++++++ test/testnullpointer.cpp | 28 +++++++++++++++++ 8 files changed, 149 insertions(+), 42 deletions(-) diff --git a/lib/checkbufferoverrun.cpp b/lib/checkbufferoverrun.cpp index d0cbe34eff7..cc512e1d629 100644 --- a/lib/checkbufferoverrun.cpp +++ b/lib/checkbufferoverrun.cpp @@ -914,7 +914,7 @@ namespace }; } -bool CheckBufferOverrun::isCtuUnsafeBufferUsage(const Settings &settings, const Token *argtok, MathLib::bigint *offset, int type) +bool CheckBufferOverrun::isCtuUnsafeBufferUsage(const Settings &settings, const Token *argtok, CTU::FileInfo::Value *offset, int type) { if (!offset) return false; @@ -931,16 +931,16 @@ bool CheckBufferOverrun::isCtuUnsafeBufferUsage(const Settings &settings, const return false; if (!indexTok->hasKnownIntValue()) return false; - *offset = indexTok->getKnownIntValue() * argtok->valueType()->typeSize(settings.platform); + offset->value = indexTok->getKnownIntValue() * argtok->valueType()->typeSize(settings.platform); return true; } -bool CheckBufferOverrun::isCtuUnsafeArrayIndex(const Settings &settings, const Token *argtok, MathLib::bigint *offset) +bool CheckBufferOverrun::isCtuUnsafeArrayIndex(const Settings &settings, const Token *argtok, CTU::FileInfo::Value *offset) { return isCtuUnsafeBufferUsage(settings, argtok, offset, 1); } -bool CheckBufferOverrun::isCtuUnsafePointerArith(const Settings &settings, const Token *argtok, MathLib::bigint *offset) +bool CheckBufferOverrun::isCtuUnsafePointerArith(const Settings &settings, const Token *argtok, CTU::FileInfo::Value* offset) { return isCtuUnsafeBufferUsage(settings, argtok, offset, 2); } @@ -1030,14 +1030,14 @@ bool CheckBufferOverrun::analyseWholeProgram1(const std::map 0) - errmsg = "Array index out of bounds; '" + unsafeUsage.myArgumentName + "' buffer size is " + MathLib::toString(functionCall->callArgValue) + " and it is accessed at offset " + MathLib::toString(unsafeUsage.value) + "."; + if (unsafeUsage.value.value > 0) + errmsg = "Array index out of bounds; '" + unsafeUsage.myArgumentName + "' buffer size is " + MathLib::toString(functionCall->callArgValue.value) + " and it is accessed at offset " + MathLib::toString(unsafeUsage.value.value) + "."; else - errmsg = "Array index out of bounds; buffer '" + unsafeUsage.myArgumentName + "' is accessed at offset " + MathLib::toString(unsafeUsage.value) + "."; - cwe = (unsafeUsage.value > 0) ? CWE_BUFFER_OVERRUN : CWE_BUFFER_UNDERRUN; + errmsg = "Array index out of bounds; buffer '" + unsafeUsage.myArgumentName + "' is accessed at offset " + MathLib::toString(unsafeUsage.value.value) + "."; + cwe = (unsafeUsage.value.value > 0) ? CWE_BUFFER_OVERRUN : CWE_BUFFER_UNDERRUN; } else { errorId = "ctuPointerArith"; - errmsg = "Pointer arithmetic overflow; '" + unsafeUsage.myArgumentName + "' buffer size is " + MathLib::toString(functionCall->callArgValue); + errmsg = "Pointer arithmetic overflow; '" + unsafeUsage.myArgumentName + "' buffer size is " + MathLib::toString(functionCall->callArgValue.value); cwe = CWE_POINTER_ARITHMETIC_OVERFLOW; } diff --git a/lib/checkbufferoverrun.h b/lib/checkbufferoverrun.h index 66d8966b6e5..89fbe0b94ea 100644 --- a/lib/checkbufferoverrun.h +++ b/lib/checkbufferoverrun.h @@ -107,9 +107,9 @@ class CPPCHECKLIB CheckBufferOverrun : public Check { ValueFlow::Value getBufferSize(const Token *bufTok) const; // CTU - static bool isCtuUnsafeBufferUsage(const Settings &settings, const Token *argtok, MathLib::bigint *offset, int type); - static bool isCtuUnsafeArrayIndex(const Settings &settings, const Token *argtok, MathLib::bigint *offset); - static bool isCtuUnsafePointerArith(const Settings &settings, const Token *argtok, MathLib::bigint *offset); + static bool isCtuUnsafeBufferUsage(const Settings &settings, const Token *argtok, CTU::FileInfo::Value *offset, int type); + static bool isCtuUnsafeArrayIndex(const Settings &settings, const Token *argtok, CTU::FileInfo::Value *offset); + static bool isCtuUnsafePointerArith(const Settings &settings, const Token *argtok, CTU::FileInfo::Value *offset); Check::FileInfo * loadFileInfoFromXml(const tinyxml2::XMLElement *xmlElement) const override; static bool analyseWholeProgram1(const std::map> &callsMap, const CTU::FileInfo::UnsafeUsage &unsafeUsage, diff --git a/lib/checknullpointer.cpp b/lib/checknullpointer.cpp index add42e49f7a..723e66e2860 100644 --- a/lib/checknullpointer.cpp +++ b/lib/checknullpointer.cpp @@ -583,7 +583,7 @@ void CheckNullPointer::redundantConditionWarning(const Token* tok, const ValueFl } // NOLINTNEXTLINE(readability-non-const-parameter) - used as callback so we need to preserve the signature -static bool isUnsafeUsage(const Settings &settings, const Token *vartok, MathLib::bigint *value) +static bool isUnsafeUsage(const Settings &settings, const Token *vartok, CTU::FileInfo::Value *value) { (void)value; bool unknown = false; @@ -659,6 +659,7 @@ bool CheckNullPointer::analyseWholeProgram(const CTU::FileInfo &ctu, const std:: if (warning == 1 && !settings.severity.isEnabled(Severity::warning)) break; + std::uint8_t unknownFunctionReturn = 0; const std::list &locationList = CTU::FileInfo::getErrorPath(CTU::FileInfo::InvalidValueType::null, unsafeUsage, @@ -666,15 +667,28 @@ bool CheckNullPointer::analyseWholeProgram(const CTU::FileInfo &ctu, const std:: "Dereferencing argument ARG that is null", nullptr, warning, - settings.maxCtuDepth); + settings.maxCtuDepth, + &unknownFunctionReturn); if (locationList.empty()) continue; + std::string id = "ctunullpointer"; + std::string message = "Null pointer dereference: " + unsafeUsage.myArgumentName; + if (unknownFunctionReturn == (std::uint8_t)ValueFlow::Value::UnknownFunctionReturn::outOfMemory) { + id += "OutOfMemory"; + warning = 1; + message = "If memory allocation fails, then there is a possible null pointer dereference: " + unsafeUsage.myArgumentName; + } else if (unknownFunctionReturn == (std::uint8_t)ValueFlow::Value::UnknownFunctionReturn::outOfResources) { + id += "OutOfResources"; + warning = 1; + message = "If resource allocation fails, then there is a possible null pointer dereference: " + unsafeUsage.myArgumentName; + } + const ErrorMessage errmsg(locationList, fi->file0, warning ? Severity::warning : Severity::error, - "Null pointer dereference: " + unsafeUsage.myArgumentName, - "ctunullpointer", + message, + id, CWE_NULL_POINTER_DEREFERENCE, Certainty::normal); errorLogger.reportErr(errmsg); diff --git a/lib/checkuninitvar.cpp b/lib/checkuninitvar.cpp index d6f5798d46d..c9596bcf799 100644 --- a/lib/checkuninitvar.cpp +++ b/lib/checkuninitvar.cpp @@ -1722,6 +1722,10 @@ namespace }; } +static bool isVariableUsage(const Settings &settings, const Token *argtok, CTU::FileInfo::Value *value) { + return isVariableUsage(settings, argtok, &value->value); +} + Check::FileInfo *CheckUninitVar::getFileInfo(const Tokenizer &tokenizer, const Settings &settings) const { const std::list &unsafeUsage = CTU::getUnsafeUsage(tokenizer, settings, ::isVariableUsage); diff --git a/lib/ctu.cpp b/lib/ctu.cpp index 171087f447c..2ee59f8f242 100644 --- a/lib/ctu.cpp +++ b/lib/ctu.cpp @@ -45,6 +45,7 @@ static constexpr char ATTR_CALL_ARGNR[] = "call-argnr"; static constexpr char ATTR_CALL_ARGEXPR[] = "call-argexpr"; static constexpr char ATTR_CALL_ARGVALUETYPE[] = "call-argvaluetype"; static constexpr char ATTR_CALL_ARGVALUE[] = "call-argvalue"; +static constexpr char ATTR_CALL_UNKNOWN_FUNCTION_RETURN[] = "call-argvalue-ufr"; static constexpr char ATTR_WARNING[] = "warning"; static constexpr char ATTR_LOC_FILENAME[] = "file"; static constexpr char ATTR_LOC_LINENR[] = "line"; @@ -54,6 +55,7 @@ static constexpr char ATTR_MY_ID[] = "my-id"; static constexpr char ATTR_MY_ARGNR[] = "my-argnr"; static constexpr char ATTR_MY_ARGNAME[] = "my-argname"; static constexpr char ATTR_VALUE[] = "value"; +static constexpr char ATTR_UNKNOWN_FUNCTION_RETURN[] = "ufr"; std::string CTU::getFunctionId(const Tokenizer &tokenizer, const Function *function) { @@ -102,7 +104,8 @@ std::string CTU::FileInfo::FunctionCall::toXmlString() const << toBaseXmlString() << " " << ATTR_CALL_ARGEXPR << "=\"" << ErrorLogger::toxml(callArgumentExpression) << "\"" << " " << ATTR_CALL_ARGVALUETYPE << "=\"" << static_cast(callValueType) << "\"" - << " " << ATTR_CALL_ARGVALUE << "=\"" << callArgValue << "\""; + << " " << ATTR_CALL_ARGVALUE << "=\"" << callArgValue.value << "\"" + << " " << ATTR_CALL_UNKNOWN_FUNCTION_RETURN << "=\"" << (int)callArgValue.unknownFunctionReturn << "\""; if (warning) out << " " << ATTR_WARNING << "=\"true\""; if (callValuePath.empty()) @@ -141,7 +144,8 @@ std::string CTU::FileInfo::UnsafeUsage::toString() const << " " << ATTR_LOC_FILENAME << "=\"" << ErrorLogger::toxml(location.fileName) << '\"' << " " << ATTR_LOC_LINENR << "=\"" << location.lineNumber << '\"' << " " << ATTR_LOC_COLUMN << "=\"" << location.column << '\"' - << " " << ATTR_VALUE << "=\"" << value << "\"" + << " " << ATTR_VALUE << "=\"" << value.value << "\"" + << " " << ATTR_UNKNOWN_FUNCTION_RETURN << "=\"" << (int)value.unknownFunctionReturn << "\"" << "/>\n"; return out.str(); } @@ -201,7 +205,8 @@ bool CTU::FileInfo::FunctionCall::loadFromXml(const tinyxml2::XMLElement *xmlEle bool error=false; callArgumentExpression = readAttrString(xmlElement, ATTR_CALL_ARGEXPR, &error); callValueType = (ValueFlow::Value::ValueType)readAttrInt(xmlElement, ATTR_CALL_ARGVALUETYPE, &error); - callArgValue = readAttrInt(xmlElement, ATTR_CALL_ARGVALUE, &error); + callArgValue.value = readAttrInt(xmlElement, ATTR_CALL_ARGVALUE, &error); + callArgValue.unknownFunctionReturn = readAttrInt(xmlElement, ATTR_CALL_UNKNOWN_FUNCTION_RETURN, &error); const char *w = xmlElement->Attribute(ATTR_WARNING); warning = w && std::strcmp(w, "true") == 0; for (const tinyxml2::XMLElement *e2 = xmlElement->FirstChildElement(); !error && e2; e2 = e2->NextSiblingElement()) { @@ -267,7 +272,8 @@ std::list CTU::loadUnsafeUsageListFromXml(const tiny unsafeUsage.location.fileName = readAttrString(e, ATTR_LOC_FILENAME, &error); unsafeUsage.location.lineNumber = readAttrInt(e, ATTR_LOC_LINENR, &error); unsafeUsage.location.column = readAttrInt(e, ATTR_LOC_COLUMN, &error); - unsafeUsage.value = readAttrInt(e, ATTR_VALUE, &error); + unsafeUsage.value.value = readAttrInt(e, ATTR_VALUE, &error); + unsafeUsage.value.unknownFunctionReturn = readAttrInt(e, ATTR_UNKNOWN_FUNCTION_RETURN, &error); if (!error) ret.push_back(std::move(unsafeUsage)); @@ -342,7 +348,7 @@ CTU::FileInfo *CTU::getFileInfo(const Tokenizer &tokenizer) functionCall.location = FileInfo::Location(tokenizer,tok); functionCall.callArgNr = argnr + 1; functionCall.callArgumentExpression = argtok->expressionString(); - functionCall.callArgValue = value.intvalue; + functionCall.callArgValue = value; functionCall.warning = !value.errorSeverity(); for (const ErrorPathItem &i : value.errorPath) { const std::string& file = tokenizer.list.file(i.first); @@ -364,7 +370,7 @@ CTU::FileInfo *CTU::getFileInfo(const Tokenizer &tokenizer) functionCall.callArgNr = argnr + 1; functionCall.callArgumentExpression = argtok->expressionString(); const auto typeSize = argtok->valueType()->typeSize(tokenizer.getSettings().platform); - functionCall.callArgValue = typeSize > 0 ? argtok->variable()->dimension(0) * typeSize : -1; + functionCall.callArgValue.value = typeSize > 0 ? argtok->variable()->dimension(0) * typeSize : -1; functionCall.warning = false; fileInfo->functionCalls.push_back(std::move(functionCall)); } @@ -377,7 +383,7 @@ CTU::FileInfo *CTU::getFileInfo(const Tokenizer &tokenizer) functionCall.location = FileInfo::Location(tokenizer, tok); functionCall.callArgNr = argnr + 1; functionCall.callArgumentExpression = argtok->expressionString(); - functionCall.callArgValue = argtok->astOperand1()->valueType()->typeSize(tokenizer.getSettings().platform); + functionCall.callArgValue.value = argtok->astOperand1()->valueType()->typeSize(tokenizer.getSettings().platform); functionCall.warning = false; fileInfo->functionCalls.push_back(std::move(functionCall)); } @@ -411,7 +417,8 @@ CTU::FileInfo *CTU::getFileInfo(const Tokenizer &tokenizer) functionCall.callFunctionName = tok->astOperand1()->expressionString(); functionCall.location = FileInfo::Location(tokenizer, tok); functionCall.callArgNr = argnr + 1; - functionCall.callArgValue = 0; + functionCall.callArgValue = v; + functionCall.callArgValue.value = 0; functionCall.callArgumentExpression = argtok->expressionString(); functionCall.warning = false; fileInfo->functionCalls.push_back(std::move(functionCall)); @@ -436,9 +443,9 @@ CTU::FileInfo *CTU::getFileInfo(const Tokenizer &tokenizer) return fileInfo; } -static std::list> getUnsafeFunction(const Settings &settings, const Scope *scope, int argnr, bool (*isUnsafeUsage)(const Settings &settings, const Token *argtok, MathLib::bigint *value)) +static std::vector> getUnsafeFunction(const Settings &settings, const Scope *scope, int argnr, bool (*isUnsafeUsage)(const Settings &settings, const Token *argtok, CTU::FileInfo::Value *value)) { - std::list> ret; + std::vector> ret; const Variable * const argvar = scope->function->getArgumentVar(argnr); if (!argvar->isArrayOrPointer() && !argvar->isReference()) return ret; @@ -459,7 +466,7 @@ static std::list> getUnsafeFunction(co } if (tok2->variable() != argvar) continue; - MathLib::bigint value = 0; + CTU::FileInfo::Value value; if (!isUnsafeUsage(settings, tok2, &value)) return ret; // TODO: Is this a read? then continue.. ret.emplace_back(tok2, value); @@ -468,7 +475,7 @@ static std::list> getUnsafeFunction(co return ret; } -std::list CTU::getUnsafeUsage(const Tokenizer &tokenizer, const Settings &settings, bool (*isUnsafeUsage)(const Settings &settings, const Token *argtok, MathLib::bigint *value)) +std::list CTU::getUnsafeUsage(const Tokenizer &tokenizer, const Settings &settings, bool (*isUnsafeUsage)(const Settings &settings, const Token *argtok, CTU::FileInfo::Value *value)) { std::list unsafeUsage; @@ -482,9 +489,9 @@ std::list CTU::getUnsafeUsage(const Tokenizer &token // "Unsafe" functions unconditionally reads data before it is written.. for (int argnr = 0; argnr < function->argCount(); ++argnr) { - for (const std::pair &v : getUnsafeFunction(settings, &scope, argnr, isUnsafeUsage)) { + for (const std::pair &v : getUnsafeFunction(settings, &scope, argnr, isUnsafeUsage)) { const Token *tok = v.first; - const MathLib::bigint val = v.second; + const CTU::FileInfo::Value val = v.second; unsafeUsage.emplace_back(CTU::getFunctionId(tokenizer, function), argnr+1, tok->str(), CTU::FileInfo::Location(tokenizer,tok), val); } } @@ -520,7 +527,7 @@ static bool findPath(const std::string &callId, continue; switch (invalidValue) { case CTU::FileInfo::InvalidValueType::null: - if (functionCall->callValueType != ValueFlow::Value::ValueType::INT || functionCall->callArgValue != 0) + if (functionCall->callValueType != ValueFlow::Value::ValueType::INT || functionCall->callArgValue.value != 0) continue; break; case CTU::FileInfo::InvalidValueType::uninit: @@ -530,7 +537,7 @@ static bool findPath(const std::string &callId, case CTU::FileInfo::InvalidValueType::bufferOverflow: if (functionCall->callValueType != ValueFlow::Value::ValueType::BUFFER_SIZE) continue; - if (unsafeValue < 0 || (unsafeValue >= functionCall->callArgValue && functionCall->callArgValue >= 0)) + if (unsafeValue < 0 || (unsafeValue >= functionCall->callArgValue.value && functionCall->callArgValue.value >= 0)) break; continue; } @@ -557,14 +564,20 @@ std::list CTU::FileInfo::getErrorPath(InvalidValueTy const char info[], const FunctionCall ** const functionCallPtr, bool warning, - int maxCtuDepth) + int maxCtuDepth, + std::uint8_t *unknownFunctionReturn) { - std::list locationList; - const CTU::FileInfo::CallBase *path[10] = {nullptr}; - if (!findPath(unsafeUsage.myId, unsafeUsage.myArgNr, unsafeUsage.value, invalidValue, callsMap, path, 0, warning, maxCtuDepth)) - return locationList; + if (!findPath(unsafeUsage.myId, unsafeUsage.myArgNr, unsafeUsage.value.value, invalidValue, callsMap, path, 0, warning, maxCtuDepth)) + return {}; + + if (unknownFunctionReturn && path[0] && dynamic_cast(path[0])) { + const auto* v = dynamic_cast(path[0]); + *unknownFunctionReturn = v->callArgValue.unknownFunctionReturn; + } + + std::list locationList; const std::string value1 = (invalidValue == InvalidValueType::null) ? "null" : "uninitialized"; diff --git a/lib/ctu.h b/lib/ctu.h index 1efcae59397..8119d1a7265 100644 --- a/lib/ctu.h +++ b/lib/ctu.h @@ -65,14 +65,29 @@ namespace CTU { nonneg int column{}; }; + struct Value { + Value& operator=(const ValueFlow::Value& val) & { + value = val.intvalue; + unknownFunctionReturn = (std::uint8_t)val.unknownFunctionReturn; + return *this; + } + MathLib::bigint value{}; + std::uint8_t unknownFunctionReturn{}; + }; + struct UnsafeUsage { UnsafeUsage() = default; - UnsafeUsage(std::string myId, nonneg int myArgNr, std::string myArgumentName, Location location, MathLib::bigint value) : myId(std::move(myId)), myArgNr(myArgNr), myArgumentName(std::move(myArgumentName)), location(std::move(location)), value(value) {} + UnsafeUsage(std::string myId, nonneg int myArgNr, std::string myArgumentName, Location location, Value value) + : myId(std::move(myId)) + , myArgNr(myArgNr) + , myArgumentName(std::move(myArgumentName)) + , location(std::move(location)) + , value(value) {} std::string myId; nonneg int myArgNr{}; std::string myArgumentName; Location location; - MathLib::bigint value{}; + Value value; std::string toString() const; }; @@ -97,7 +112,7 @@ namespace CTU { class FunctionCall : public CallBase { public: std::string callArgumentExpression; - MathLib::bigint callArgValue; + Value callArgValue; ValueFlow::Value::ValueType callValueType; std::vector callValuePath; bool warning; @@ -136,7 +151,8 @@ namespace CTU { const char info[], const FunctionCall ** functionCallPtr, bool warning, - int maxCtuDepth); + int maxCtuDepth, + std::uint8_t *unknownFunctionReturn = nullptr); }; CPPCHECKLIB std::string toString(const std::list &unsafeUsage); @@ -146,7 +162,7 @@ namespace CTU { /** @brief Parse current TU and extract file info */ CPPCHECKLIB RET_NONNULL FileInfo *getFileInfo(const Tokenizer &tokenizer); - CPPCHECKLIB std::list getUnsafeUsage(const Tokenizer &tokenizer, const Settings &settings, bool (*isUnsafeUsage)(const Settings &settings, const Token *argtok, MathLib::bigint *value)); + CPPCHECKLIB std::list getUnsafeUsage(const Tokenizer &tokenizer, const Settings &settings, bool (*isUnsafeUsage)(const Settings &settings, const Token *argtok, FileInfo::Value *value)); CPPCHECKLIB std::list loadUnsafeUsageListFromXml(const tinyxml2::XMLElement *xmlElement); } diff --git a/test/cli/whole-program_test.py b/test/cli/whole-program_test.py index f45966e9389..19af01bbee9 100644 --- a/test/cli/whole-program_test.py +++ b/test/cli/whole-program_test.py @@ -391,3 +391,35 @@ def test_nullpointer_file0_builddir_j(tmpdir): build_dir = os.path.join(tmpdir, 'b1') os.mkdir(build_dir) __test_nullpointer_file0(['-j2', '--cppcheck-build-dir={}'.format(build_dir)]) + +@pytest.mark.parametrize("single_file", (False,True)) +def test_nullpointer_out_of_memory(tmpdir, single_file): + """Ensure that there are not duplicate warnings related to memory/resource allocation failures + https://trac.cppcheck.net/ticket/13521 + """ + code1 = 'void f(int* p) { *p = 0; }\n' + code2 = 'int main() { int* p = malloc(10); f(p); return 0; }\n' + if single_file: + with open(tmpdir / 'test.c', 'wt') as f: + f.write(code1 + code2) + else: + with open(tmpdir / 'header.h', 'wt') as f: + f.write('void f(int* p);\n') + with open(tmpdir / 'test1.c', 'wt') as f: + f.write('#include "header.h"\n' + code1) + with open(tmpdir / 'test2.c', 'wt') as f: + f.write('#include "header.h"\n' + code2) + + _, _, stderr = cppcheck(['--enable=style', '.'], cwd=tmpdir) + results = [] + for line in stderr.splitlines(): + if line.endswith(']'): + results.append(line[line.find('['):]) + + if single_file: + # the bug is found and reported using normal valueflow analysis + # ctu finding is not reported + assert results == ['[nullPointerOutOfMemory]'] + else: + # the bug is found using ctu analysis + assert results == ['[ctunullpointerOutOfMemory]'] diff --git a/test/testnullpointer.cpp b/test/testnullpointer.cpp index 0632f6c1093..c42dc583b97 100644 --- a/test/testnullpointer.cpp +++ b/test/testnullpointer.cpp @@ -4673,6 +4673,34 @@ class TestNullPointer : public TestFixture { " f(NULL);\n" "}\n"); ASSERT_EQUALS("", errout_str()); + + // ctu: memory allocation fails + ctu("void f(int* p) {\n" + " *p = 0;\n" + "}\n" + "void g() {\n" + " int* q = (int*)malloc(4);\n" + " f(q);\n" + "}\n"); + ASSERT_EQUALS("test.cpp:2:warning:If memory allocation fails, then there is a possible null pointer dereference: p\n" + "test.cpp:5:note:Assuming allocation function fails\n" + "test.cpp:5:note:Assignment 'q=(int*)malloc(4)', assigned value is 0\n" + "test.cpp:6:note:Calling function f, 1st argument is null\n" + "test.cpp:2:note:Dereferencing argument p that is null\n", errout_str()); + + // ctu: resource allocation fails + ctu("void foo(FILE* f) {\n" + " fprintf(f, a);\n" + "}\n" + "void bar() {\n" + " FILE* f = fopen(notexist,t);\n" + " foo(f);\n" + "}\n"); + ASSERT_EQUALS("test.cpp:2:warning:If resource allocation fails, then there is a possible null pointer dereference: f\n" + "test.cpp:5:note:Assuming allocation function fails\n" + "test.cpp:5:note:Assignment 'f=fopen(notexist,t)', assigned value is 0\n" + "test.cpp:6:note:Calling function foo, 1st argument is null\n" + "test.cpp:2:note:Dereferencing argument f that is null\n", errout_str()); } };