Skip to content

Commit faaabb1

Browse files
New check: eraseIteratorOutOfBounds (#5913)
1 parent db3ce5e commit faaabb1

File tree

5 files changed

+152
-4
lines changed

5 files changed

+152
-4
lines changed

lib/astutils.cpp

+3-1
Original file line numberDiff line numberDiff line change
@@ -3027,8 +3027,10 @@ const Token* findExpressionChangedSkipDeadCode(const Token* expr,
30273027
const Token* getArgumentStart(const Token* ftok)
30283028
{
30293029
const Token* tok = ftok;
3030-
if (Token::Match(tok, "%name% (|{"))
3030+
if (Token::Match(tok, "%name% (|{|)"))
30313031
tok = ftok->next();
3032+
while (Token::simpleMatch(tok, ")"))
3033+
tok = tok->next();
30323034
if (!Token::Match(tok, "(|{|["))
30333035
return nullptr;
30343036
const Token* startTok = tok->astOperand2();

lib/checkstl.cpp

+71
Original file line numberDiff line numberDiff line change
@@ -3119,6 +3119,77 @@ void CheckStl::knownEmptyContainer()
31193119
}
31203120
}
31213121

3122+
void CheckStl::eraseIteratorOutOfBoundsError(const Token *ftok, const Token* itertok, const ValueFlow::Value* val)
3123+
{
3124+
if (!ftok || !itertok || !val) {
3125+
reportError(ftok, Severity::error, "eraseIteratorOutOfBounds",
3126+
"Calling function 'erase()' on the iterator 'iter' which is out of bounds.", CWE628, Certainty::normal);
3127+
reportError(ftok, Severity::warning, "eraseIteratorOutOfBoundsCond",
3128+
"Either the condition 'x' is redundant or function 'erase()' is called on the iterator 'iter' which is out of bounds.", CWE628, Certainty::normal);
3129+
return;
3130+
}
3131+
const std::string& func = ftok->str();
3132+
const std::string iter = itertok->expressionString();
3133+
3134+
const bool isConditional = val->isPossible();
3135+
std::string msg;
3136+
if (isConditional) {
3137+
msg = ValueFlow::eitherTheConditionIsRedundant(val->condition) + " or function '" + func + "()' is called on the iterator '" + iter + "' which is out of bounds.";
3138+
} else {
3139+
msg = "Calling function '" + func + "()' on the iterator '" + iter + "' which is out of bounds.";
3140+
}
3141+
3142+
const Severity severity = isConditional ? Severity::warning : Severity::error;
3143+
const std::string id = isConditional ? "eraseIteratorOutOfBoundsCond" : "eraseIteratorOutOfBounds";
3144+
reportError(ftok, severity,
3145+
id,
3146+
msg, CWE628, Certainty::normal);
3147+
}
3148+
3149+
static const ValueFlow::Value* getOOBIterValue(const Token* tok, const ValueFlow::Value* sizeVal)
3150+
{
3151+
auto it = std::find_if(tok->values().begin(), tok->values().end(), [&](const ValueFlow::Value& v) {
3152+
if (v.isPossible() || v.isKnown()) {
3153+
switch (v.valueType) {
3154+
case ValueFlow::Value::ValueType::ITERATOR_END:
3155+
return v.intvalue >= 0;
3156+
case ValueFlow::Value::ValueType::ITERATOR_START:
3157+
return (v.intvalue < 0) || (sizeVal && v.intvalue >= sizeVal->intvalue);
3158+
default:
3159+
break;
3160+
}
3161+
}
3162+
return false;
3163+
});
3164+
return it != tok->values().end() ? &*it : nullptr;
3165+
}
3166+
3167+
void CheckStl::eraseIteratorOutOfBounds()
3168+
{
3169+
logChecker("CheckStl::eraseIteratorOutOfBounds");
3170+
for (const Scope *function : mTokenizer->getSymbolDatabase()->functionScopes) {
3171+
for (const Token *tok = function->bodyStart; tok != function->bodyEnd; tok = tok->next()) {
3172+
3173+
if (!tok->valueType())
3174+
continue;
3175+
const Library::Container* container = tok->valueType()->container;
3176+
if (!container || !astIsLHS(tok) || !Token::simpleMatch(tok->astParent(), "."))
3177+
continue;
3178+
const Token* const ftok = tok->astParent()->astOperand2();
3179+
const Library::Container::Action action = container->getAction(ftok->str());
3180+
if (action != Library::Container::Action::ERASE)
3181+
continue;
3182+
const std::vector<const Token*> args = getArguments(ftok);
3183+
if (args.size() != 1) // TODO: check range overload
3184+
continue;
3185+
3186+
const ValueFlow::Value* sizeVal = tok->getKnownValue(ValueFlow::Value::ValueType::CONTAINER_SIZE);
3187+
if (const ValueFlow::Value* errVal = getOOBIterValue(args[0], sizeVal))
3188+
eraseIteratorOutOfBoundsError(ftok, args[0], errVal);
3189+
}
3190+
}
3191+
}
3192+
31223193
static bool isMutex(const Variable* var)
31233194
{
31243195
const Token* tok = Token::typeDecl(var->nameToken()).first;

lib/checkstl.h

+7
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ class CPPCHECKLIB CheckStl : public Check {
7878
checkStl.mismatchingContainers();
7979
checkStl.mismatchingContainerIterator();
8080
checkStl.knownEmptyContainer();
81+
checkStl.eraseIteratorOutOfBounds();
8182

8283
checkStl.stlBoundaries();
8384
checkStl.checkDereferenceInvalidIterator();
@@ -183,6 +184,8 @@ class CPPCHECKLIB CheckStl : public Check {
183184

184185
void knownEmptyContainer();
185186

187+
void eraseIteratorOutOfBounds();
188+
186189
void checkMutexes();
187190

188191
bool isContainerSize(const Token *containerToken, const Token *expr) const;
@@ -234,6 +237,8 @@ class CPPCHECKLIB CheckStl : public Check {
234237

235238
void knownEmptyContainerError(const Token *tok, const std::string& algo);
236239

240+
void eraseIteratorOutOfBoundsError(const Token* ftok, const Token* itertok, const ValueFlow::Value* val = nullptr);
241+
237242
void globalLockGuardError(const Token *tok);
238243
void localMutexError(const Token *tok);
239244

@@ -271,6 +276,7 @@ class CPPCHECKLIB CheckStl : public Check {
271276
c.uselessCallsEmptyError(nullptr);
272277
c.uselessCallsRemoveError(nullptr, "remove");
273278
c.dereferenceInvalidIteratorError(nullptr, "i");
279+
c.eraseIteratorOutOfBoundsError(nullptr, nullptr);
274280
c.useStlAlgorithmError(nullptr, emptyString);
275281
c.knownEmptyContainerError(nullptr, emptyString);
276282
c.globalLockGuardError(nullptr);
@@ -296,6 +302,7 @@ class CPPCHECKLIB CheckStl : public Check {
296302
"- common mistakes when using string::c_str()\n"
297303
"- useless calls of string and STL functions\n"
298304
"- dereferencing an invalid iterator\n"
305+
"- erasing an iterator that is out of bounds\n"
299306
"- reading from empty STL container\n"
300307
"- iterating over an empty STL container\n"
301308
"- consider using an STL algorithm instead of raw loop\n"

releasenotes.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Release Notes for Cppcheck 2.14
22

33
New checks:
4-
-
4+
eraseIteratorOutOfBounds: warns when erase() is called on an iterator that is out of bounds
55

66
Improved checking:
77
-

test/teststl.cpp

+70-2
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ class TestStl : public TestFixture {
7474
TEST_CASE(iteratorExpression);
7575
TEST_CASE(iteratorSameExpression);
7676
TEST_CASE(mismatchingContainerIterator);
77+
TEST_CASE(eraseIteratorOutOfBounds);
7778

7879
TEST_CASE(dereference);
7980
TEST_CASE(dereference_break); // #3644 - handle "break"
@@ -2160,6 +2161,69 @@ class TestStl : public TestFixture {
21602161
ASSERT_EQUALS("", errout.str());
21612162
}
21622163

2164+
void eraseIteratorOutOfBounds() {
2165+
check("void f() {\n"
2166+
" std::vector<int> v;\n"
2167+
" v.erase(v.begin());\n"
2168+
"}\n");
2169+
ASSERT_EQUALS("[test.cpp:3]: (error) Calling function 'erase()' on the iterator 'v.begin()' which is out of bounds.\n", errout.str());
2170+
2171+
check("void f() {\n"
2172+
" std::vector<int> v;\n"
2173+
" v.erase(v.end());\n"
2174+
"}\n");
2175+
ASSERT_EQUALS("[test.cpp:3]: (error) Calling function 'erase()' on the iterator 'v.end()' which is out of bounds.\n", errout.str());
2176+
2177+
check("void f() {\n"
2178+
" std::vector<int> v;\n"
2179+
" auto it = v.begin();\n"
2180+
" v.erase(it);\n"
2181+
"}\n");
2182+
ASSERT_EQUALS("[test.cpp:4]: (error) Calling function 'erase()' on the iterator 'it' which is out of bounds.\n", errout.str());
2183+
2184+
check("void f() {\n"
2185+
" std::vector<int> v{ 1, 2, 3 };\n"
2186+
" auto it = v.end();\n"
2187+
" v.erase(it);\n"
2188+
"}\n");
2189+
ASSERT_EQUALS("[test.cpp:4]: (error) Calling function 'erase()' on the iterator 'it' which is out of bounds.\n", errout.str());
2190+
2191+
check("void f() {\n"
2192+
" std::vector<int> v{ 1, 2, 3 };\n"
2193+
" auto it = v.begin();\n"
2194+
" v.erase(it);\n"
2195+
"}\n");
2196+
ASSERT_EQUALS("", errout.str());
2197+
2198+
check("void f() {\n"
2199+
" std::vector<int> v{ 1, 2, 3 };\n"
2200+
" v.erase(v.end() - 1);\n"
2201+
"}\n");
2202+
ASSERT_EQUALS("", errout.str());
2203+
2204+
check("void f() {\n"
2205+
" std::vector<int> v{ 1, 2, 3 };\n"
2206+
" v.erase(v.begin() - 1);\n"
2207+
"}\n");
2208+
ASSERT_EQUALS("[test.cpp:3]: (error) Calling function 'erase()' on the iterator 'v.begin()-1' which is out of bounds.\n"
2209+
"[test.cpp:3]: (error) Dereference of an invalid iterator: v.begin()-1\n",
2210+
errout.str());
2211+
2212+
check("void f(std::vector<int>& v, std::vector<int>::iterator it) {\n"
2213+
" if (it == v.end()) {}\n"
2214+
" v.erase(it);\n"
2215+
"}\n");
2216+
ASSERT_EQUALS("[test.cpp:3]: (warning) Either the condition 'it==v.end()' is redundant or function 'erase()' is called on the iterator 'it' which is out of bounds.\n",
2217+
errout.str());
2218+
2219+
check("void f() {\n"
2220+
" std::vector<int> v;\n"
2221+
" ((v).erase)(v.begin());\n"
2222+
"}\n");
2223+
ASSERT_EQUALS("[test.cpp:3]: (error) Calling function 'erase()' on the iterator 'v.begin()' which is out of bounds.\n",
2224+
errout.str());
2225+
}
2226+
21632227
// Dereferencing invalid pointer
21642228
void dereference() {
21652229
check("void f()\n"
@@ -2219,7 +2283,9 @@ class TestStl : public TestFixture {
22192283
" ints.erase(iter);\n"
22202284
" std::cout << iter->first << std::endl;\n"
22212285
"}");
2222-
ASSERT_EQUALS("[test.cpp:7] -> [test.cpp:6]: (error) Iterator 'iter' used after element has been erased.\n", errout.str());
2286+
ASSERT_EQUALS("[test.cpp:7] -> [test.cpp:6]: (error) Iterator 'iter' used after element has been erased.\n"
2287+
"[test.cpp:6]: (error) Calling function 'erase()' on the iterator 'iter' which is out of bounds.\n",
2288+
errout.str());
22232289

22242290
// Reverse iterator
22252291
check("void f()\n"
@@ -2230,7 +2296,9 @@ class TestStl : public TestFixture {
22302296
" ints.erase(iter);\n"
22312297
" std::cout << iter->first << std::endl;\n"
22322298
"}");
2233-
ASSERT_EQUALS("[test.cpp:7] -> [test.cpp:6]: (error) Iterator 'iter' used after element has been erased.\n", errout.str());
2299+
ASSERT_EQUALS("[test.cpp:7] -> [test.cpp:6]: (error) Iterator 'iter' used after element has been erased.\n"
2300+
"[test.cpp:6]: (error) Calling function 'erase()' on the iterator 'iter' which is out of bounds.\n",
2301+
errout.str());
22342302
}
22352303

22362304
void dereference_auto() {

0 commit comments

Comments
 (0)