Skip to content

Commit 264fe96

Browse files
committed
Fix #6259: track possible use of embedded member addresses
1 parent 8bb772d commit 264fe96

2 files changed

Lines changed: 295 additions & 1 deletion

File tree

‎lib/checkleakautovar.cpp‎

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1016,10 +1016,56 @@ void CheckLeakAutoVarImpl::changeAllocStatus(VarInfo &varInfo, const VarInfo::Al
10161016
}
10171017
}
10181018

1019+
static const Token* addressedMemberOwner(const Token* arg, const Token* argStart, const Token* callOpening)
1020+
{
1021+
if (!arg || !arg->isUnaryOp("&"))
1022+
return nullptr;
1023+
const Token* nextArg = argStart->nextArgument();
1024+
const Token* argEnd = nextArg ? nextArg->previous() : callOpening->link();
1025+
const Token* parent = arg->astParent();
1026+
// The address must be the passed value, not a comparison, discarded
1027+
// comma operand, or scalar cast. Commas outside this argument are separators.
1028+
while (parent && parent->index() >= argStart->index() && parent->index() < argEnd->index()) {
1029+
if (!parent->isCast() || !parent->valueType() || !parent->valueType()->pointer)
1030+
return nullptr;
1031+
parent = parent->astParent();
1032+
}
1033+
if (parent != callOpening && !Token::simpleMatch(parent, ","))
1034+
return nullptr;
1035+
const Token* member = arg->astOperand1();
1036+
if (arg->isCpp()) {
1037+
// A class/enum address-of expression may call a member, inherited or
1038+
// free operator& that returns storage unrelated to this object.
1039+
const ValueType* vt = member ? member->valueType() : nullptr;
1040+
if (!vt || vt->typeScope || (!vt->pointer && !vt->isIntegral() && !vt->isFloat()))
1041+
return nullptr;
1042+
}
1043+
while (Token::simpleMatch(member, ".")) {
1044+
const Token* field = member->astOperand2();
1045+
if (!field || (field->variable() && (field->variable()->isStatic() || field->variable()->isReference())))
1046+
return nullptr;
1047+
const Token* object = member->astOperand1();
1048+
const ValueType* vt = object ? object->valueType() : nullptr;
1049+
if (!vt)
1050+
return nullptr;
1051+
if (vt->pointer) {
1052+
// Stop at a pointer member: its pointee is not embedded storage
1053+
// of the allocation containing that member.
1054+
return vt->pointer == 1 && object->variable() && object->isName() ? object : nullptr;
1055+
}
1056+
if (member->originalName() == "->")
1057+
return nullptr; // overloaded member access
1058+
member = object;
1059+
}
1060+
return nullptr;
1061+
}
1062+
10191063
void CheckLeakAutoVarImpl::functionCall(const Token *tokName, const Token *tokOpeningPar, VarInfo &varInfo, const VarInfo::AllocInfo& allocation, const Library::AllocFunc* af)
10201064
{
10211065
// Ignore function call?
1022-
const bool isLeakIgnore = mSettings.library.isLeakIgnore(mSettings.library.getFunctionName(tokName));
1066+
const std::string functionName = mSettings.library.getFunctionName(tokName);
1067+
const bool isLeakIgnore = mSettings.library.isLeakIgnore(functionName);
1068+
const bool isPure = mSettings.library.isFunctionConst(functionName, true);
10231069
if (mSettings.library.getReallocFuncInfo(tokName))
10241070
return;
10251071
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
10541100
arg = arg->astOperand2() ? arg->astOperand2() : arg->astOperand1();
10551101
const Token * const argTypeStartTok = arg;
10561102

1103+
if (!isLeakIgnore && !isPure && allocation.status == VarInfo::NOALLOC) {
1104+
if (const Token* owner = addressedMemberOwner(arg, funcArg, tokOpeningPar)) {
1105+
if (varInfo.alloctype.count(owner->varId()))
1106+
varInfo.possibleUsage[owner->varId()] = {tokName, VarInfo::USED};
1107+
}
1108+
}
1109+
10571110
if (Token::simpleMatch(arg, "."))
10581111
arg = arg->next();
10591112

‎test/testleakautovar.cpp‎

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,15 @@ class TestLeakAutoVar : public TestFixture {
118118

119119
// handling function calls
120120
TEST_CASE(functioncall1);
121+
TEST_CASE(functioncallMemberAddress);
122+
TEST_CASE(functioncallMemberAddressOwnership);
123+
TEST_CASE(functioncallMemberAddressValue);
124+
TEST_CASE(functioncallMemberAddressDerived);
125+
TEST_CASE(functioncallMemberAddressIndirect);
126+
TEST_CASE(functioncallMemberAddressLeakIgnore);
127+
TEST_CASE(functioncallMemberAddressDoubleFree);
128+
TEST_CASE(functioncallMemberAddressNested);
129+
TEST_CASE(functioncallMemberAddressCppStorage);
121130

122131
// goto
123132
TEST_CASE(goto1);
@@ -1910,6 +1919,238 @@ class TestLeakAutoVar : public TestFixture {
19101919
ASSERT_EQUALS("[test.cpp:4:1]: (error) Memory leak: b [memleak]\n", errout_str());
19111920
}
19121921

1922+
void functioncallMemberAddress() { // #6259
1923+
check("void f() {\n"
1924+
" line_element *wall = malloc(sizeof(line_element));\n"
1925+
" list_add_tail(&(wall->list), &(state.l_elements_head));\n"
1926+
" state.l_direction = direction;\n"
1927+
"}\n");
1928+
ASSERT_EQUALS("[test.c:5:1]: (information) --check-library: Function list_add_tail() should have <use>/<leak-ignore> configuration [checkLibraryUseIgnore]\n", errout_str());
1929+
1930+
check("struct S { int value; };\n"
1931+
"void retain(void *);\n"
1932+
"void f(int *state) {\n"
1933+
" struct S *p = malloc(sizeof(*p));\n"
1934+
" retain(&p->value);\n"
1935+
" *state = 1;\n"
1936+
"}\n");
1937+
ASSERT_EQUALS("[test.c:7:1]: (information) --check-library: Function retain() should have <use>/<leak-ignore> configuration [checkLibraryUseIgnore]\n", errout_str());
1938+
1939+
check("struct S { int value; };\n"
1940+
"void retain(void *);\n"
1941+
"void f(int *state) {\n"
1942+
" struct S *p = malloc(sizeof(*p));\n"
1943+
" retain(&(p->value));\n"
1944+
" *state = 1;\n"
1945+
"}\n");
1946+
ASSERT_EQUALS("[test.c:7:1]: (information) --check-library: Function retain() should have <use>/<leak-ignore> configuration [checkLibraryUseIgnore]\n", errout_str());
1947+
1948+
check("struct S { int value; };\n"
1949+
"void retain(void *);\n"
1950+
"void f(int *state) {\n"
1951+
" struct S *p = malloc(sizeof(*p));\n"
1952+
" retain((void *)&p->value);\n"
1953+
" *state = 1;\n"
1954+
"}\n");
1955+
ASSERT_EQUALS("[test.c:7:1]: (information) --check-library: Function retain() should have <use>/<leak-ignore> configuration [checkLibraryUseIgnore]\n", errout_str());
1956+
}
1957+
1958+
void functioncallMemberAddressOwnership() {
1959+
check("struct link { struct link *next; };\n"
1960+
"struct item { int value; struct link entry; };\n"
1961+
"struct link *head;\n"
1962+
"void retain_entry(struct link *entry) {\n"
1963+
" entry->next = head;\n"
1964+
" head = entry;\n"
1965+
"}\n"
1966+
"void append_item(int value, int *state) {\n"
1967+
" struct item *p = malloc(sizeof(*p));\n"
1968+
" if (!p) return;\n"
1969+
" p->value = value;\n"
1970+
" retain_entry(&p->entry);\n"
1971+
" *state = 1;\n"
1972+
"}\n");
1973+
ASSERT_EQUALS("", errout_str());
1974+
}
1975+
1976+
void functioncallMemberAddressDerived() {
1977+
check("struct S { int value; };\n"
1978+
"void observe(int);\n"
1979+
"void f(int *saved, int *state) {\n"
1980+
" struct S *p = malloc(sizeof(*p));\n"
1981+
" observe(&p->value == saved);\n"
1982+
" *state = 1;\n"
1983+
"}\n");
1984+
ASSERT_EQUALS("[test.c:7:1]: (error) Memory leak: p [memleak]\n", errout_str());
1985+
1986+
check("struct S { int value; };\n"
1987+
"void observe(int);\n"
1988+
"void f(int *state) {\n"
1989+
" struct S *p = malloc(sizeof(*p));\n"
1990+
" observe((&p->value, 1));\n"
1991+
" *state = 1;\n"
1992+
"}\n");
1993+
ASSERT_EQUALS("[test.c:7:1]: (error) Memory leak: p [memleak]\n", errout_str());
1994+
1995+
check("struct S { int value; };\n"
1996+
"void observe(long);\n"
1997+
"void f(int *state) {\n"
1998+
" struct S *p = malloc(sizeof(*p));\n"
1999+
" observe((long)&p->value);\n"
2000+
" *state = 1;\n"
2001+
"}\n");
2002+
ASSERT_EQUALS("[test.c:7:1]: (error) Memory leak: p [memleak]\n", errout_str());
2003+
}
2004+
2005+
void functioncallMemberAddressValue() {
2006+
check("struct S { int value; };\n"
2007+
"void observe(int);\n"
2008+
"void f(int *state) {\n"
2009+
" struct S *p = malloc(sizeof(*p));\n"
2010+
" p->value = 1;\n"
2011+
" observe(p->value);\n"
2012+
" *state = 1;\n"
2013+
"}\n");
2014+
ASSERT_EQUALS("[test.c:8:1]: (error) Memory leak: p [memleak]\n", errout_str());
2015+
}
2016+
2017+
void functioncallMemberAddressIndirect() {
2018+
check("struct S { int value; struct S *child; };\n"
2019+
"void retain(int *);\n"
2020+
"void f(struct S *child, int *state) {\n"
2021+
" struct S *p = malloc(sizeof(*p));\n"
2022+
" p->child = child;\n"
2023+
" retain(&p->child->value);\n"
2024+
" *state = 1;\n"
2025+
"}\n");
2026+
ASSERT_EQUALS("[test.c:8:1]: (error) Memory leak: p [memleak]\n", errout_str());
2027+
2028+
check("struct S { int *value; };\n"
2029+
"void retain(int *);\n"
2030+
"void f(int *value, int *state) {\n"
2031+
" struct S *p = malloc(sizeof(*p));\n"
2032+
" p->value = value;\n"
2033+
" retain(p->value);\n"
2034+
" *state = 1;\n"
2035+
"}\n");
2036+
ASSERT_EQUALS("[test.c:8:1]: (error) Memory leak: p [memleak]\n", errout_str());
2037+
}
2038+
2039+
void functioncallMemberAddressLeakIgnore() {
2040+
check("struct S { int value; };\n"
2041+
"void f(int *state) {\n"
2042+
" struct S *p = malloc(sizeof(*p));\n"
2043+
" memset(&p->value, 0, sizeof(p->value));\n"
2044+
" *state = 1;\n"
2045+
"}\n");
2046+
ASSERT_EQUALS("[test.c:6:1]: (error) Memory leak: p [memleak]\n", errout_str());
2047+
2048+
const Settings pureSettings = settingsBuilder(settings).libraryxml(
2049+
"<def format=\"2\"><function name=\"observe\"><pure/><noreturn>false</noreturn>"
2050+
"<arg nr=\"1\"><not-uninit/></arg></function></def>").build();
2051+
check("struct S { int value; };\n"
2052+
"int observe(const int *);\n"
2053+
"void f(int *state) {\n"
2054+
" struct S *p = malloc(sizeof(*p));\n"
2055+
" p->value = 1;\n"
2056+
" *state = observe(&p->value);\n"
2057+
" *state += 1;\n"
2058+
"}\n", dinit(CheckOptions, $.s = &pureSettings));
2059+
ASSERT_EQUALS("[test.c:8:1]: (error) Memory leak: p [memleak]\n", errout_str());
2060+
}
2061+
2062+
void functioncallMemberAddressDoubleFree() {
2063+
check("struct S { int value; };\n"
2064+
"void retain(int *);\n"
2065+
"void f() {\n"
2066+
" struct S *p = malloc(sizeof(*p));\n"
2067+
" retain(&p->value);\n"
2068+
" free(p);\n"
2069+
" free(p);\n"
2070+
"}\n");
2071+
ASSERT_EQUALS("[test.c:6:5] -> [test.c:7:5]: (error) Memory pointed to by 'p' is freed twice. [doubleFree]\n", errout_str());
2072+
2073+
check("struct S { int value; };\n"
2074+
"void retain(int *);\n"
2075+
"void f() {\n"
2076+
" S *p = new S;\n"
2077+
" retain(&p->value);\n"
2078+
" free(p);\n"
2079+
"}\n", dinit(CheckOptions, $.cpp = true));
2080+
ASSERT_EQUALS("[test.cpp:4:12] -> [test.cpp:6:5]: (error) Mismatching allocation and deallocation: p [mismatchAllocDealloc]\n", errout_str());
2081+
}
2082+
2083+
void functioncallMemberAddressNested() {
2084+
check("struct Entry { int value; };\n"
2085+
"struct S { struct Entry embedded; };\n"
2086+
"void retain(int *);\n"
2087+
"void f(int *state) {\n"
2088+
" struct S *p = malloc(sizeof(*p));\n"
2089+
" retain(&p->embedded.value);\n"
2090+
" *state = 1;\n"
2091+
"}\n");
2092+
ASSERT_EQUALS("[test.c:8:1]: (information) --check-library: Function retain() should have <use>/<leak-ignore> configuration [checkLibraryUseIgnore]\n", errout_str());
2093+
}
2094+
2095+
void functioncallMemberAddressCppStorage() {
2096+
check("struct S { int value; };\n"
2097+
"void retain(int *);\n"
2098+
"void f(int *state) {\n"
2099+
" S *p = new S;\n"
2100+
" retain(&p->value);\n"
2101+
" *state = 1;\n"
2102+
"}\n", dinit(CheckOptions, $.cpp = true));
2103+
ASSERT_EQUALS("[test.cpp:7:1]: (information) --check-library: Function retain() should have <use>/<leak-ignore> configuration [checkLibraryUseIgnore]\n", errout_str());
2104+
2105+
check("struct S { int *value; };\n"
2106+
"void retain(int **);\n"
2107+
"void f(int *state) {\n"
2108+
" S *p = new S;\n"
2109+
" retain(&p->value);\n"
2110+
" *state = 1;\n"
2111+
"}\n", dinit(CheckOptions, $.cpp = true));
2112+
ASSERT_EQUALS("[test.cpp:7:1]: (information) --check-library: Function retain() should have <use>/<leak-ignore> configuration [checkLibraryUseIgnore]\n", errout_str());
2113+
2114+
check("struct S { static int value; };\n"
2115+
"void retain(int *);\n"
2116+
"void f(int *state) {\n"
2117+
" S *p = new S;\n"
2118+
" retain(&p->value);\n"
2119+
" *state = 1;\n"
2120+
"}\n", dinit(CheckOptions, $.cpp = true));
2121+
ASSERT_EQUALS("[test.cpp:7:1]: (error) Memory leak: p [memleak]\n", errout_str());
2122+
2123+
check("struct S { int &value; };\n"
2124+
"void retain(int *);\n"
2125+
"void f(int &value, int *state) {\n"
2126+
" S *p = new S{value};\n"
2127+
" retain(&p->value);\n"
2128+
" *state = 1;\n"
2129+
"}\n", dinit(CheckOptions, $.cpp = true));
2130+
ASSERT_EQUALS("[test.cpp:7:1]: (error) Memory leak: p [memleak]\n", errout_str());
2131+
2132+
check("struct Entry { int *operator&(); };\n"
2133+
"struct S { Entry entry; };\n"
2134+
"void retain(int *);\n"
2135+
"void f(int *state) {\n"
2136+
" S *p = (S *)malloc(sizeof(S));\n"
2137+
" retain(&p->entry);\n"
2138+
" *state = 1;\n"
2139+
"}\n", dinit(CheckOptions, $.cpp = true));
2140+
ASSERT_EQUALS("[test.cpp:8:1]: (error) Memory leak: p [memleak]\n", errout_str());
2141+
2142+
check("struct Entry { int value; };\n"
2143+
"struct Link { Entry *operator->(); };\n"
2144+
"struct S { Link link; };\n"
2145+
"void retain(int *);\n"
2146+
"void f(int *state) {\n"
2147+
" S *p = (S *)malloc(sizeof(S));\n"
2148+
" retain(&p->link->value);\n"
2149+
" *state = 1;\n"
2150+
"}\n", dinit(CheckOptions, $.cpp = true));
2151+
ASSERT_EQUALS("[test.cpp:9:1]: (error) Memory leak: p [memleak]\n", errout_str());
2152+
}
2153+
19132154
void goto1() {
19142155
check("static void f() {\n"
19152156
" int err = -ENOMEM;\n"

0 commit comments

Comments
 (0)