Skip to content

Commit 55cdc57

Browse files
committed
Improve performance of virtualDestructor check
CheckClassImpl::virtualDestructor() scanned the whole variable list and the whole token list for every (derived class, public base class) pair. With many template instantiations (for example etl) this took up to 26% of the analysis time. When the first base class is checked, the pointer variables of each type are collected and a single pass over the tokens records, for each pointer variable, the classes 'Derived' of a "ptr = new Derived" that is followed by a "delete ptr;". Checking a pair is then a lookup. This also fixes a false negative: the name of a template instantiation such as 'Derived < int >' is a single token that contains spaces, so the old pattern "new Derived < int >" never matched it and the pair was never reported. On a project using etl, glaze and Catch2 (211 translation units in two configurations) total CPU time drops from 88.7 to 61.6 minutes, with identical output for every translation unit.
1 parent 07254e9 commit 55cdc57

2 files changed

Lines changed: 72 additions & 31 deletions

File tree

‎lib/checkclass.cpp‎

Lines changed: 42 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2025,6 +2025,39 @@ void CheckClassImpl::virtualDestructor()
20252025

20262026
logChecker("CheckClass::virtualDestructor");
20272027

2028+
// pointer variables of type 'Base *', for each type 'Base'
2029+
std::unordered_map<const Type *, std::vector<int>> pointersByType;
2030+
2031+
// for each pointer variable, the classes 'Derived' of a "ptr = new Derived" that is followed by a "delete ptr;"
2032+
std::unordered_map<int, std::set<std::string>> deletedAfterNew;
2033+
2034+
// collect these when the first base class is checked
2035+
bool deletionsCollected = false;
2036+
const auto collectDeletions = [&]() {
2037+
if (deletionsCollected)
2038+
return;
2039+
deletionsCollected = true;
2040+
2041+
for (const Variable* var : mSymbolDatabase->variableList()) {
2042+
if (var && var->isPointer() && var->type())
2043+
pointersByType[var->type()].push_back(var->declarationId());
2044+
}
2045+
2046+
// the classes 'Derived' of the "ptr = new Derived" seen so far
2047+
std::unordered_map<int, std::set<std::string>> newClasses;
2048+
2049+
for (const Token *tok = mTokenizer->tokens(); tok; tok = tok->next()) {
2050+
if (Token::Match(tok, "[;{}] %var% = new %name%"))
2051+
newClasses[tok->next()->varId()].insert(tok->strAt(4));
2052+
2053+
else if (Token::Match(tok, "delete %var% ;")) {
2054+
const auto it = newClasses.find(tok->next()->varId());
2055+
if (it != newClasses.end())
2056+
deletedAfterNew[it->first].insert(it->second.cbegin(), it->second.cend());
2057+
}
2058+
}
2059+
};
2060+
20282061
for (const Scope * scope : mSymbolDatabase->classAndStructScopes) {
20292062

20302063
// Skip base classes (unless inconclusive)
@@ -2073,40 +2106,18 @@ void CheckClassImpl::virtualDestructor()
20732106
//
20742107
// If this pattern is not seen then bailout the checking of these base/derived classes
20752108
{
2076-
// pointer variables of type 'Base *'
2077-
std::set<int> baseClassPointers;
2109+
collectDeletions();
20782110

2079-
for (const Variable* var : mSymbolDatabase->variableList()) {
2080-
if (var && var->isPointer() && var->type() == derivedFrom)
2081-
baseClassPointers.insert(var->declarationId());
2082-
}
2083-
2084-
// pointer variables of type 'Base *' that should not be deleted
2085-
std::set<int> dontDelete;
2086-
2087-
// No deletion of derived class instance through base class pointer found => the code is ok
2088-
bool ok = true;
2089-
2090-
for (const Token *tok = mTokenizer->tokens(); tok; tok = tok->next()) {
2091-
if (Token::Match(tok, "[;{}] %var% =") &&
2092-
baseClassPointers.find(tok->next()->varId()) != baseClassPointers.end()) {
2093-
// new derived class..
2094-
const std::string tmp("new " + derivedClass->str());
2095-
if (Token::simpleMatch(tok->tokAt(3), tmp.c_str(), tmp.size())) {
2096-
dontDelete.insert(tok->next()->varId());
2097-
}
2098-
}
2099-
2100-
// Delete base class pointer that might point at derived class
2101-
else if (Token::Match(tok, "delete %var% ;") &&
2102-
dontDelete.find(tok->next()->varId()) != dontDelete.end()) {
2103-
ok = false;
2104-
break;
2105-
}
2106-
}
2111+
// pointer variables of type 'Base *'
2112+
const auto pointers = pointersByType.find(derivedFrom);
2113+
if (pointers == pointersByType.end())
2114+
continue;
21072115

21082116
// No base class pointer that points at a derived class is deleted
2109-
if (ok)
2117+
if (std::none_of(pointers->second.cbegin(), pointers->second.cend(), [&](int varId) {
2118+
const auto deleted = deletedAfterNew.find(varId);
2119+
return deleted != deletedAfterNew.end() && deleted->second.count(derivedClass->str()) > 0;
2120+
}))
21102121
continue;
21112122
}
21122123

‎test/testclass.cpp‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2839,6 +2839,36 @@ class TestClass : public TestFixture {
28392839
checkVirtualDestructor("class Base { public: ~Base(); };\n"
28402840
"class Derived : public Base { public: ~Derived() { (void)11; } };\n");
28412841
ASSERT_EQUALS("", errout_str());
2842+
2843+
// the pointer is deleted before it points at the derived class, or another pointer is deleted
2844+
checkVirtualDestructor("class Base { public: ~Base(); };\n"
2845+
"class Derived : public Base { public: ~Derived() { (void)11; } };\n"
2846+
"void f(Base *p, Base *q) {\n"
2847+
" delete p;\n"
2848+
" p = new Derived;\n"
2849+
" delete q;\n"
2850+
"}\n");
2851+
ASSERT_EQUALS("", errout_str());
2852+
2853+
// only the derived class that the deleted pointer points at
2854+
checkVirtualDestructor("class Base { public: ~Base(); };\n"
2855+
"class Derived1 : public Base { public: ~Derived1() { (void)11; } };\n"
2856+
"class Derived2 : public Base { public: ~Derived2() { (void)11; } };\n"
2857+
"void f(Base *p, Base *q) {\n"
2858+
" p = new Derived1;\n"
2859+
" q = new Derived2;\n"
2860+
" delete q;\n"
2861+
"}\n");
2862+
ASSERT_EQUALS("[test.cpp:1:23]: (error) Class 'Base' which is inherited by class 'Derived2' does not have a virtual destructor. [virtualDestructor]\n", errout_str());
2863+
2864+
// the derived class is a template instantiation, its name contains spaces
2865+
checkVirtualDestructor("class Base { public: ~Base(); };\n"
2866+
"template<class T> class Derived : public Base { public: ~Derived() { (void)11; } };\n"
2867+
"void f(Base *p) {\n"
2868+
" p = new Derived<unsigned int>;\n"
2869+
" delete p;\n"
2870+
"}\n");
2871+
ASSERT_EQUALS("[test.cpp:1:23]: (error) Class 'Base' which is inherited by class 'Derived < unsigned int >' does not have a virtual destructor. [virtualDestructor]\n", errout_str());
28422872
}
28432873

28442874
void virtualDestructorProtected() {

0 commit comments

Comments
 (0)