diff --git a/lib/checkmemoryleak.cpp b/lib/checkmemoryleak.cpp index cc8b6853f11..4bf0353b5ff 100644 --- a/lib/checkmemoryleak.cpp +++ b/lib/checkmemoryleak.cpp @@ -997,6 +997,53 @@ void CheckMemoryLeakNoVarImpl::check() } } +static const Variable *getAssignedMemberFromArg(const Scope *classScope, const Scope *ctorScope, const Token *initListBegin, const Variable *arg) +{ + for (const Token *tok = ctorScope->bodyStart; tok != ctorScope->bodyEnd; tok = tok->next()) { + if (!Token::Match(tok, "%name% = %varid%", arg->declarationId())) + continue; + + if (!tok->variable()) + continue; + + const Variable *member = tok->variable(); + + if (member->scope() == classScope && member->isMember()) + return member; + } + + if (initListBegin) { + for (const Token *tok = initListBegin; Token::Match(tok, ":|, %name% ("); tok = tok->linkAt(2)->next()) { + if (Token::Match(tok, ":|, %name% ( %varid% )", arg->declarationId())) + return tok->next()->variable(); + } + } + + return nullptr; +} + +bool CheckMemoryLeakImpl::isMemberDeallocated(const Variable *member, const Scope *scope, AllocType alloc) const +{ + for (const Token *tok = scope->bodyStart; tok != scope->bodyEnd; tok = tok->next()) { + if (tok->varId() != member->declarationId()) + continue; + + const Token *parent = tok; + while (parent && (!parent->astOperand1() || !parent->astOperand1()->isName())) + parent = parent->astParent(); + + if (!parent) + continue; + + const Token *funcTok = parent->astOperand1(); + + if (getDeallocationType(funcTok, tok->varId()) == alloc) + return true; + } + + return false; +} + //--------------------------------------------------------------------------- // Checks if an input argument to a function is the return value of an allocation function // like malloc(), and the function does not release it. @@ -1071,6 +1118,27 @@ void CheckMemoryLeakNoVarImpl::checkForUnreleasedInputArgument(const Scope *scop const size_t argSize = argvar->valueType()->getSizeOf(mSettings, ValueType::Accuracy::ExactOrZero, ValueType::SizeOf::Pointer); if (argSize == 0 || argSize >= mSettings.platform.sizeof_pointer) continue; + + if (tok->function()->isConstructor()) { + if (!tok->function()->nestedIn || !tok->function()->functionScope) { + continue; + } + + const Scope *classScope = tok->function()->nestedIn; + const Scope *ctorScope = tok->function()->functionScope; + const Token *initListBegin = tok->function()->constructorMemberInitialization(); + const Variable *member = getAssignedMemberFromArg(classScope, ctorScope, initListBegin, argvar); + + if (member) { + const Function *dtor = classScope->getDestructor(); + + if (!dtor || !dtor->functionScope) + continue; + + if (isMemberDeallocated(member, dtor->functionScope, alloc)) + continue; + } + } } functionCallLeak(arg, arg->str(), functionName); } diff --git a/lib/checkmemoryleak.h b/lib/checkmemoryleak.h index fdb7748f9ed..4e90d902922 100644 --- a/lib/checkmemoryleak.h +++ b/lib/checkmemoryleak.h @@ -209,6 +209,14 @@ class CPPCHECKLIB CheckMemoryLeakImpl : public CheckImpl { * @param varname name of variable */ void memleakError(const Token *tok, const std::string &varname) const; + /** + * Check if a class member is deallocated in a function scope + * @param member member variable + * @param scope the scope + * @param alloc allocation type + */ + bool isMemberDeallocated(const Variable *member, const Scope *scope, AllocType alloc) const; + /** * Report that there is a resource leak (fopen/popen/etc) diff --git a/test/testmemleak.cpp b/test/testmemleak.cpp index 783a10ad089..3f0fe87e97b 100644 --- a/test/testmemleak.cpp +++ b/test/testmemleak.cpp @@ -2628,6 +2628,34 @@ class TestMemleakNoVar : public TestFixture { "[test.cpp:7:12]: (error) Allocation with f2, assert doesn't release it. [leakNoVarFunctionCall]\n" "[test.cpp:8:12]: (error) Allocation with f3, assert doesn't release it. [leakNoVarFunctionCall]\n", errout_str()); + + check("struct S {\n" + " explicit S(int fd) { m_fd = fd; }\n" + " ~S() { if (m_fd >= 0) close(m_fd); }\n" + " int m_fd = -1;\n" + "};\n" + "S g(char *name) { return S(mkostemp(name, 0)); }\n"); + ASSERT_EQUALS("", + errout_str()); + + check("struct S {\n" + " explicit S(int fd) { m_fd = fd; }\n" + " ~S() {} // no close(...) \n" + " int m_fd = -1;\n" + "};\n" + "S g(char *name) { return S(mkostemp(name, 0)); }\n"); + ASSERT_EQUALS("[test.cpp:6:28]: (error) Allocation with mkostemp, S doesn't release it. [leakNoVarFunctionCall]\n", + errout_str()); + + check("struct S {\n" + " explicit S(int fd) : x(1 + 2), m_fd(fd) {}\n" + " ~S() { if (m_fd >= 0) close(m_fd); }\n" + " int m_fd = -1;\n" + " int x;\n" + "};\n" + "S g(char *name) { return S(mkostemp(name, 0)); }\n"); + ASSERT_EQUALS("", + errout_str()); } void missingAssignment() {