From 264fe96879326ae24e519ce5661e7b6621ff8c66 Mon Sep 17 00:00:00 2001 From: KiritoYG <118127895+KiritoYG@users.noreply.github.com> Date: Thu, 24 Sep 2026 22:19:35 +0900 Subject: [PATCH] Fix #6259: track possible use of embedded member addresses --- lib/checkleakautovar.cpp | 55 ++++++++- test/testleakautovar.cpp | 241 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 295 insertions(+), 1 deletion(-) diff --git a/lib/checkleakautovar.cpp b/lib/checkleakautovar.cpp index e5220e701a3..23663b1136e 100644 --- a/lib/checkleakautovar.cpp +++ b/lib/checkleakautovar.cpp @@ -1016,10 +1016,56 @@ void CheckLeakAutoVarImpl::changeAllocStatus(VarInfo &varInfo, const VarInfo::Al } } +static const Token* addressedMemberOwner(const Token* arg, const Token* argStart, const Token* callOpening) +{ + if (!arg || !arg->isUnaryOp("&")) + return nullptr; + const Token* nextArg = argStart->nextArgument(); + const Token* argEnd = nextArg ? nextArg->previous() : callOpening->link(); + const Token* parent = arg->astParent(); + // The address must be the passed value, not a comparison, discarded + // comma operand, or scalar cast. Commas outside this argument are separators. + while (parent && parent->index() >= argStart->index() && parent->index() < argEnd->index()) { + if (!parent->isCast() || !parent->valueType() || !parent->valueType()->pointer) + return nullptr; + parent = parent->astParent(); + } + if (parent != callOpening && !Token::simpleMatch(parent, ",")) + return nullptr; + const Token* member = arg->astOperand1(); + if (arg->isCpp()) { + // A class/enum address-of expression may call a member, inherited or + // free operator& that returns storage unrelated to this object. + const ValueType* vt = member ? member->valueType() : nullptr; + if (!vt || vt->typeScope || (!vt->pointer && !vt->isIntegral() && !vt->isFloat())) + return nullptr; + } + while (Token::simpleMatch(member, ".")) { + const Token* field = member->astOperand2(); + if (!field || (field->variable() && (field->variable()->isStatic() || field->variable()->isReference()))) + return nullptr; + const Token* object = member->astOperand1(); + const ValueType* vt = object ? object->valueType() : nullptr; + if (!vt) + return nullptr; + if (vt->pointer) { + // Stop at a pointer member: its pointee is not embedded storage + // of the allocation containing that member. + return vt->pointer == 1 && object->variable() && object->isName() ? object : nullptr; + } + if (member->originalName() == "->") + return nullptr; // overloaded member access + member = object; + } + return nullptr; +} + void CheckLeakAutoVarImpl::functionCall(const Token *tokName, const Token *tokOpeningPar, VarInfo &varInfo, const VarInfo::AllocInfo& allocation, const Library::AllocFunc* af) { // Ignore function call? - const bool isLeakIgnore = mSettings.library.isLeakIgnore(mSettings.library.getFunctionName(tokName)); + const std::string functionName = mSettings.library.getFunctionName(tokName); + const bool isLeakIgnore = mSettings.library.isLeakIgnore(functionName); + const bool isPure = mSettings.library.isFunctionConst(functionName, true); if (mSettings.library.getReallocFuncInfo(tokName)) return; if (tokName->next()->valueType() && tokName->next()->valueType()->container && tokName->next()->valueType()->container->stdStringLike) @@ -1054,6 +1100,13 @@ void CheckLeakAutoVarImpl::functionCall(const Token *tokName, const Token *tokOp arg = arg->astOperand2() ? arg->astOperand2() : arg->astOperand1(); const Token * const argTypeStartTok = arg; + if (!isLeakIgnore && !isPure && allocation.status == VarInfo::NOALLOC) { + if (const Token* owner = addressedMemberOwner(arg, funcArg, tokOpeningPar)) { + if (varInfo.alloctype.count(owner->varId())) + varInfo.possibleUsage[owner->varId()] = {tokName, VarInfo::USED}; + } + } + if (Token::simpleMatch(arg, ".")) arg = arg->next(); diff --git a/test/testleakautovar.cpp b/test/testleakautovar.cpp index cb5f9d53c8a..61fa9e94168 100644 --- a/test/testleakautovar.cpp +++ b/test/testleakautovar.cpp @@ -118,6 +118,15 @@ class TestLeakAutoVar : public TestFixture { // handling function calls TEST_CASE(functioncall1); + TEST_CASE(functioncallMemberAddress); + TEST_CASE(functioncallMemberAddressOwnership); + TEST_CASE(functioncallMemberAddressValue); + TEST_CASE(functioncallMemberAddressDerived); + TEST_CASE(functioncallMemberAddressIndirect); + TEST_CASE(functioncallMemberAddressLeakIgnore); + TEST_CASE(functioncallMemberAddressDoubleFree); + TEST_CASE(functioncallMemberAddressNested); + TEST_CASE(functioncallMemberAddressCppStorage); // goto TEST_CASE(goto1); @@ -1910,6 +1919,238 @@ class TestLeakAutoVar : public TestFixture { ASSERT_EQUALS("[test.cpp:4:1]: (error) Memory leak: b [memleak]\n", errout_str()); } + void functioncallMemberAddress() { // #6259 + check("void f() {\n" + " line_element *wall = malloc(sizeof(line_element));\n" + " list_add_tail(&(wall->list), &(state.l_elements_head));\n" + " state.l_direction = direction;\n" + "}\n"); + ASSERT_EQUALS("[test.c:5:1]: (information) --check-library: Function list_add_tail() should have / configuration [checkLibraryUseIgnore]\n", errout_str()); + + check("struct S { int value; };\n" + "void retain(void *);\n" + "void f(int *state) {\n" + " struct S *p = malloc(sizeof(*p));\n" + " retain(&p->value);\n" + " *state = 1;\n" + "}\n"); + ASSERT_EQUALS("[test.c:7:1]: (information) --check-library: Function retain() should have / configuration [checkLibraryUseIgnore]\n", errout_str()); + + check("struct S { int value; };\n" + "void retain(void *);\n" + "void f(int *state) {\n" + " struct S *p = malloc(sizeof(*p));\n" + " retain(&(p->value));\n" + " *state = 1;\n" + "}\n"); + ASSERT_EQUALS("[test.c:7:1]: (information) --check-library: Function retain() should have / configuration [checkLibraryUseIgnore]\n", errout_str()); + + check("struct S { int value; };\n" + "void retain(void *);\n" + "void f(int *state) {\n" + " struct S *p = malloc(sizeof(*p));\n" + " retain((void *)&p->value);\n" + " *state = 1;\n" + "}\n"); + ASSERT_EQUALS("[test.c:7:1]: (information) --check-library: Function retain() should have / configuration [checkLibraryUseIgnore]\n", errout_str()); + } + + void functioncallMemberAddressOwnership() { + check("struct link { struct link *next; };\n" + "struct item { int value; struct link entry; };\n" + "struct link *head;\n" + "void retain_entry(struct link *entry) {\n" + " entry->next = head;\n" + " head = entry;\n" + "}\n" + "void append_item(int value, int *state) {\n" + " struct item *p = malloc(sizeof(*p));\n" + " if (!p) return;\n" + " p->value = value;\n" + " retain_entry(&p->entry);\n" + " *state = 1;\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + } + + void functioncallMemberAddressDerived() { + check("struct S { int value; };\n" + "void observe(int);\n" + "void f(int *saved, int *state) {\n" + " struct S *p = malloc(sizeof(*p));\n" + " observe(&p->value == saved);\n" + " *state = 1;\n" + "}\n"); + ASSERT_EQUALS("[test.c:7:1]: (error) Memory leak: p [memleak]\n", errout_str()); + + check("struct S { int value; };\n" + "void observe(int);\n" + "void f(int *state) {\n" + " struct S *p = malloc(sizeof(*p));\n" + " observe((&p->value, 1));\n" + " *state = 1;\n" + "}\n"); + ASSERT_EQUALS("[test.c:7:1]: (error) Memory leak: p [memleak]\n", errout_str()); + + check("struct S { int value; };\n" + "void observe(long);\n" + "void f(int *state) {\n" + " struct S *p = malloc(sizeof(*p));\n" + " observe((long)&p->value);\n" + " *state = 1;\n" + "}\n"); + ASSERT_EQUALS("[test.c:7:1]: (error) Memory leak: p [memleak]\n", errout_str()); + } + + void functioncallMemberAddressValue() { + check("struct S { int value; };\n" + "void observe(int);\n" + "void f(int *state) {\n" + " struct S *p = malloc(sizeof(*p));\n" + " p->value = 1;\n" + " observe(p->value);\n" + " *state = 1;\n" + "}\n"); + ASSERT_EQUALS("[test.c:8:1]: (error) Memory leak: p [memleak]\n", errout_str()); + } + + void functioncallMemberAddressIndirect() { + check("struct S { int value; struct S *child; };\n" + "void retain(int *);\n" + "void f(struct S *child, int *state) {\n" + " struct S *p = malloc(sizeof(*p));\n" + " p->child = child;\n" + " retain(&p->child->value);\n" + " *state = 1;\n" + "}\n"); + ASSERT_EQUALS("[test.c:8:1]: (error) Memory leak: p [memleak]\n", errout_str()); + + check("struct S { int *value; };\n" + "void retain(int *);\n" + "void f(int *value, int *state) {\n" + " struct S *p = malloc(sizeof(*p));\n" + " p->value = value;\n" + " retain(p->value);\n" + " *state = 1;\n" + "}\n"); + ASSERT_EQUALS("[test.c:8:1]: (error) Memory leak: p [memleak]\n", errout_str()); + } + + void functioncallMemberAddressLeakIgnore() { + check("struct S { int value; };\n" + "void f(int *state) {\n" + " struct S *p = malloc(sizeof(*p));\n" + " memset(&p->value, 0, sizeof(p->value));\n" + " *state = 1;\n" + "}\n"); + ASSERT_EQUALS("[test.c:6:1]: (error) Memory leak: p [memleak]\n", errout_str()); + + const Settings pureSettings = settingsBuilder(settings).libraryxml( + "false" + "").build(); + check("struct S { int value; };\n" + "int observe(const int *);\n" + "void f(int *state) {\n" + " struct S *p = malloc(sizeof(*p));\n" + " p->value = 1;\n" + " *state = observe(&p->value);\n" + " *state += 1;\n" + "}\n", dinit(CheckOptions, $.s = &pureSettings)); + ASSERT_EQUALS("[test.c:8:1]: (error) Memory leak: p [memleak]\n", errout_str()); + } + + void functioncallMemberAddressDoubleFree() { + check("struct S { int value; };\n" + "void retain(int *);\n" + "void f() {\n" + " struct S *p = malloc(sizeof(*p));\n" + " retain(&p->value);\n" + " free(p);\n" + " free(p);\n" + "}\n"); + ASSERT_EQUALS("[test.c:6:5] -> [test.c:7:5]: (error) Memory pointed to by 'p' is freed twice. [doubleFree]\n", errout_str()); + + check("struct S { int value; };\n" + "void retain(int *);\n" + "void f() {\n" + " S *p = new S;\n" + " retain(&p->value);\n" + " free(p);\n" + "}\n", dinit(CheckOptions, $.cpp = true)); + ASSERT_EQUALS("[test.cpp:4:12] -> [test.cpp:6:5]: (error) Mismatching allocation and deallocation: p [mismatchAllocDealloc]\n", errout_str()); + } + + void functioncallMemberAddressNested() { + check("struct Entry { int value; };\n" + "struct S { struct Entry embedded; };\n" + "void retain(int *);\n" + "void f(int *state) {\n" + " struct S *p = malloc(sizeof(*p));\n" + " retain(&p->embedded.value);\n" + " *state = 1;\n" + "}\n"); + ASSERT_EQUALS("[test.c:8:1]: (information) --check-library: Function retain() should have / configuration [checkLibraryUseIgnore]\n", errout_str()); + } + + void functioncallMemberAddressCppStorage() { + check("struct S { int value; };\n" + "void retain(int *);\n" + "void f(int *state) {\n" + " S *p = new S;\n" + " retain(&p->value);\n" + " *state = 1;\n" + "}\n", dinit(CheckOptions, $.cpp = true)); + ASSERT_EQUALS("[test.cpp:7:1]: (information) --check-library: Function retain() should have / configuration [checkLibraryUseIgnore]\n", errout_str()); + + check("struct S { int *value; };\n" + "void retain(int **);\n" + "void f(int *state) {\n" + " S *p = new S;\n" + " retain(&p->value);\n" + " *state = 1;\n" + "}\n", dinit(CheckOptions, $.cpp = true)); + ASSERT_EQUALS("[test.cpp:7:1]: (information) --check-library: Function retain() should have / configuration [checkLibraryUseIgnore]\n", errout_str()); + + check("struct S { static int value; };\n" + "void retain(int *);\n" + "void f(int *state) {\n" + " S *p = new S;\n" + " retain(&p->value);\n" + " *state = 1;\n" + "}\n", dinit(CheckOptions, $.cpp = true)); + ASSERT_EQUALS("[test.cpp:7:1]: (error) Memory leak: p [memleak]\n", errout_str()); + + check("struct S { int &value; };\n" + "void retain(int *);\n" + "void f(int &value, int *state) {\n" + " S *p = new S{value};\n" + " retain(&p->value);\n" + " *state = 1;\n" + "}\n", dinit(CheckOptions, $.cpp = true)); + ASSERT_EQUALS("[test.cpp:7:1]: (error) Memory leak: p [memleak]\n", errout_str()); + + check("struct Entry { int *operator&(); };\n" + "struct S { Entry entry; };\n" + "void retain(int *);\n" + "void f(int *state) {\n" + " S *p = (S *)malloc(sizeof(S));\n" + " retain(&p->entry);\n" + " *state = 1;\n" + "}\n", dinit(CheckOptions, $.cpp = true)); + ASSERT_EQUALS("[test.cpp:8:1]: (error) Memory leak: p [memleak]\n", errout_str()); + + check("struct Entry { int value; };\n" + "struct Link { Entry *operator->(); };\n" + "struct S { Link link; };\n" + "void retain(int *);\n" + "void f(int *state) {\n" + " S *p = (S *)malloc(sizeof(S));\n" + " retain(&p->link->value);\n" + " *state = 1;\n" + "}\n", dinit(CheckOptions, $.cpp = true)); + ASSERT_EQUALS("[test.cpp:9:1]: (error) Memory leak: p [memleak]\n", errout_str()); + } + void goto1() { check("static void f() {\n" " int err = -ENOMEM;\n"