Skip to content

Commit c768129

Browse files
committed
Simplify Qt property exporter parsing
1 parent 8825355 commit c768129

4 files changed

Lines changed: 87 additions & 144 deletions

File tree

‎Makefile‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -642,7 +642,7 @@ $(libcppdir)/pathmatch.o: lib/pathmatch.cpp lib/config.h lib/path.h lib/pathmatc
642642
$(libcppdir)/platform.o: lib/platform.cpp externals/tinyxml2/tinyxml2.h lib/config.h lib/mathlib.h lib/path.h lib/platform.h lib/standards.h lib/utils.h lib/xml.h
643643
$(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/platform.cpp
644644

645-
$(libcppdir)/preprocessor.o: lib/preprocessor.cpp externals/simplecpp/simplecpp.h lib/checkers.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/preprocessor.h lib/settings.h lib/smallvector.h lib/standards.h lib/suppressions.h lib/templatesimplifier.h lib/token.h lib/utils.h lib/vfvalue.h
645+
$(libcppdir)/preprocessor.o: lib/preprocessor.cpp externals/simplecpp/simplecpp.h lib/checkers.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/preprocessor.h lib/settings.h lib/standards.h lib/suppressions.h lib/utils.h
646646
$(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/preprocessor.cpp
647647

648648
$(libcppdir)/programmemory.o: lib/programmemory.cpp lib/astutils.h lib/calculate.h lib/checkers.h lib/config.h lib/errortypes.h lib/infer.h lib/library.h lib/mathlib.h lib/platform.h lib/programmemory.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenlist.h lib/utils.h lib/valueflow.h lib/valueptr.h lib/vfvalue.h

‎lib/preprocessor.cpp‎

Lines changed: 33 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
#include "settings.h"
2929
#include "standards.h"
3030
#include "suppressions.h"
31-
#include "token.h"
3231
#include "utils.h"
3332

3433
#include <algorithm>
@@ -902,123 +901,40 @@ simplecpp::TokenList Preprocessor::preprocess(const std::string &cfgStr, std::ve
902901
return tokens2;
903902
}
904903

905-
static const simplecpp::Token* qtPropertyAttributes(const simplecpp::Token* tok)
906-
{
907-
while (tok && (tok->str() == "const" || tok->str() == "volatile"))
908-
tok = tok->next;
909-
if (!tok)
910-
return nullptr;
911-
if (Token::isStandardType(tok->str())) {
912-
do {
913-
tok = tok->next;
914-
} while (tok && Token::isStandardType(tok->str()));
915-
} else {
916-
if (tok->str() == "::")
917-
tok = tok->next;
918-
if (!tok || !tok->name)
919-
return nullptr;
920-
for (;;) {
921-
tok = tok->next;
922-
if (tok && tok->str() == "<") {
923-
unsigned int depth = 1;
924-
unsigned int parentheses = 0;
925-
do {
926-
tok = tok->next;
927-
if (!tok)
928-
return nullptr;
929-
if (tok->str() == "(")
930-
++parentheses;
931-
else if (tok->str() == ")" && parentheses)
932-
--parentheses;
933-
else if (!parentheses) {
934-
if (tok->str() == "<")
935-
++depth;
936-
else if (tok->str() == ">")
937-
--depth;
938-
else if (tok->str() == ">>") {
939-
if (depth < 2)
940-
return nullptr;
941-
depth -= 2;
942-
}
943-
}
944-
} while (depth);
945-
tok = tok->next;
946-
}
947-
if (!tok || tok->str() != "::" || !tok->next || !tok->next->name)
948-
break;
949-
tok = tok->next;
950-
}
951-
}
952-
while (tok && (tok->str() == "*" || tok->str() == "&" || tok->str() == "&&" ||
953-
tok->str() == "const" || tok->str() == "volatile"))
954-
tok = tok->next;
955-
// The property name is not a function reference.
956-
return tok && tok->name ? tok->next : nullptr;
957-
}
958-
959-
static std::set<std::string> qtPropertyFunctions(const simplecpp::Token* tok, const Library& library)
904+
static std::set<std::string> exporterFunctions(const simplecpp::Token* tok, const std::string& exporter, const Library& library)
960905
{
906+
// Match configured exporter keywords without parsing library-specific grammar.
961907
std::set<std::string> functions;
962-
tok = qtPropertyAttributes(tok);
963-
while (tok && tok->str() != ")") {
964-
const std::string& attribute = tok->str();
965-
tok = tok->next;
966-
if (attribute == "CONSTANT" || attribute == "FINAL" || attribute == "REQUIRED" ||
967-
attribute == "VIRTUAL" || attribute == "OVERRIDE")
968-
continue;
969-
if (!tok)
970-
return {};
971-
if (attribute == "REVISION") {
972-
if (tok->number)
973-
tok = tok->next;
974-
else if (tok->str() == "(") {
975-
do {
976-
tok = tok->next;
977-
} while (tok && (tok->number || tok->str() == ","));
978-
if (!tok || tok->str() != ")")
979-
return {};
980-
tok = tok->next;
981-
} else
982-
return {};
983-
} else if (attribute == "MEMBER") {
984-
if (!tok->name)
985-
return {};
986-
tok = tok->next;
987-
} else if (library.isexportedprefix("Q_PROPERTY", attribute)) {
988-
const bool parenthesized = tok->str() == "(";
908+
unsigned int depth = 0;
909+
for (; tok; tok = tok->next) {
910+
if (tok->str() == "(")
911+
++depth;
912+
else if (tok->str() == ")") {
913+
if (depth == 0)
914+
break;
915+
--depth;
916+
} else if (depth == 0) {
917+
if (library.isexportedsuffix(exporter, tok->str()) && tok->previous && tok->previous->name)
918+
functions.insert(tok->previous->str());
919+
if (!library.isexportedprefix(exporter, tok->str()))
920+
continue;
921+
const simplecpp::Token* name = tok->next;
922+
const bool parenthesized = name && name->str() == "(";
989923
if (parenthesized)
990-
tok = tok->next;
991-
if (!tok)
992-
return {};
993-
if (tok->str() == "::")
994-
tok = tok->next;
995-
if (!tok)
996-
return {};
997-
if (!tok->name)
998-
return {};
999-
std::string function = tok->str();
1000-
tok = tok->next;
1001-
while (tok && tok->str() == "::" && tok->next && tok->next->name) {
1002-
function = tok->next->str();
1003-
tok = tok->next->next;
1004-
}
1005-
if (function != "true" && function != "false" && function != "default")
1006-
functions.insert(function);
1007-
if (parenthesized) {
1008-
if (!tok || tok->str() != ")")
1009-
return {};
1010-
tok = tok->next;
1011-
}
1012-
if (tok && tok->str() == "(") {
1013-
tok = tok->next;
1014-
if (!tok || tok->str() != ")")
1015-
return {};
1016-
tok = tok->next;
1017-
}
1018-
} else
1019-
return {};
924+
name = name->next;
925+
if (name && name->str() == "::")
926+
name = name->next;
927+
if (!name || !name->name)
928+
continue;
929+
while (name->next && name->next->str() == "::" && name->next->next && name->next->next->name)
930+
name = name->next->next;
931+
if (parenthesized && (!name->next || name->next->str() != ")"))
932+
continue;
933+
if (name->str() != "true" && name->str() != "false" && name->str() != "default")
934+
functions.insert(name->str());
935+
}
1020936
}
1021-
return tok ? functions : std::set<std::string>{};
937+
return functions;
1022938
}
1023939

1024940
void Preprocessor::readQtAnnotations(simplecpp::TokenList& tokens)
@@ -1055,7 +971,7 @@ void Preprocessor::readQtAnnotations(simplecpp::TokenList& tokens)
1055971
}
1056972
const simplecpp::Token* type = tag->next->next;
1057973
if (type && type->str() == "qt_property" && type->next && type->next->str() == ",") {
1058-
const auto functions = qtPropertyFunctions(type->next->next, mSettings.library);
974+
const auto functions = exporterFunctions(type->next->next, "Q_PROPERTY", mSettings.library);
1059975
mExportedFunctions.insert(functions.begin(), functions.end());
1060976
mExportedLocations.insert(tok->location);
1061977
}
@@ -1091,25 +1007,8 @@ std::set<std::string> Preprocessor::getExportedFunctions() const
10911007
if (locations.find(tok->location) == locations.end() ||
10921008
!mSettings.library.isexporter(tok->str()) || !tok->next || tok->next->str() != "(")
10931009
continue;
1094-
if (tok->str() == "Q_PROPERTY") {
1095-
const auto accessors = qtPropertyFunctions(tok->next->next, mSettings.library);
1096-
functions.insert(accessors.begin(), accessors.end());
1097-
continue;
1098-
}
1099-
unsigned int depth = 1;
1100-
for (const simplecpp::Token* arg = tok->next->next; arg; arg = arg->next) {
1101-
if (arg->str() == "(")
1102-
++depth;
1103-
else if (arg->str() == ")") {
1104-
if (--depth == 0)
1105-
break;
1106-
} else if (depth == 1) {
1107-
if (mSettings.library.isexportedprefix(tok->str(), arg->str()) && arg->next && arg->next->name)
1108-
functions.insert(arg->next->str());
1109-
if (mSettings.library.isexportedsuffix(tok->str(), arg->str()) && arg->previous && arg->previous->name)
1110-
functions.insert(arg->previous->str());
1111-
}
1112-
}
1010+
const auto exported = exporterFunctions(tok->next->next, tok->str(), mSettings.library);
1011+
functions.insert(exported.begin(), exported.end());
11131012
}
11141013
};
11151014
collect(mTokens);

‎oss-fuzz/Makefile‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ $(libcppdir)/pathmatch.o: ../lib/pathmatch.cpp ../lib/config.h ../lib/path.h ../
312312
$(libcppdir)/platform.o: ../lib/platform.cpp ../externals/tinyxml2/tinyxml2.h ../lib/config.h ../lib/mathlib.h ../lib/path.h ../lib/platform.h ../lib/standards.h ../lib/utils.h ../lib/xml.h
313313
$(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/platform.cpp
314314

315-
$(libcppdir)/preprocessor.o: ../lib/preprocessor.cpp ../externals/simplecpp/simplecpp.h ../lib/checkers.h ../lib/config.h ../lib/errorlogger.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/path.h ../lib/platform.h ../lib/preprocessor.h ../lib/settings.h ../lib/smallvector.h ../lib/standards.h ../lib/suppressions.h ../lib/templatesimplifier.h ../lib/token.h ../lib/utils.h ../lib/vfvalue.h
315+
$(libcppdir)/preprocessor.o: ../lib/preprocessor.cpp ../externals/simplecpp/simplecpp.h ../lib/checkers.h ../lib/config.h ../lib/errorlogger.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/path.h ../lib/platform.h ../lib/preprocessor.h ../lib/settings.h ../lib/standards.h ../lib/suppressions.h ../lib/utils.h
316316
$(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/preprocessor.cpp
317317

318318
$(libcppdir)/programmemory.o: ../lib/programmemory.cpp ../lib/astutils.h ../lib/calculate.h ../lib/checkers.h ../lib/config.h ../lib/errortypes.h ../lib/infer.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/programmemory.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenlist.h ../lib/utils.h ../lib/valueflow.h ../lib/valueptr.h ../lib/vfvalue.h

‎test/cli/unused_function_test.py‎

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -141,13 +141,13 @@ def test_unused_functions_qt_property_configurations(tmp_path):
141141
assert stderr == "unusedFunction:The function 'unused' is never used.\n"
142142

143143

144-
@pytest.mark.parametrize('property_type, property_name', [
145-
('READ', 'property'),
146-
('const Names::READ*', 'property'),
147-
('unsigned long', 'READ'),
148-
('Box<Pair<::READ, ::WRITE>>', 'property'),
144+
@pytest.mark.parametrize('property_type, property_name, reported', [
145+
('READ', 'property', ['unrelated']),
146+
('const Names::READ*', 'property', ['property', 'unrelated']),
147+
('unsigned long', 'READ', ['property', 'unrelated']),
148+
('Box<Pair<::READ, ::WRITE>>', 'property', ['property', 'unrelated']),
149149
])
150-
def test_unused_functions_qt_property_keyword_names(tmp_path, property_type, property_name):
150+
def test_unused_functions_qt_property_keyword_names(tmp_path, property_type, property_name, reported):
151151
source = tmp_path / 'test.cpp'
152152
source.write_text('''class READ {};
153153
class WRITE {};
@@ -162,6 +162,7 @@ class MyType {
162162
void valueChanged() {}
163163
void property() {}
164164
void NOTIFY() {}
165+
void unrelated() {}
165166
};
166167
int main() { MyType t; }
167168
'''.replace('@TYPE@', property_type).replace('@PROPERTY@', property_name)
@@ -170,8 +171,11 @@ class MyType {
170171
'--enable=unusedFunction', '--library=qt',
171172
'-j1', '--no-cppcheck-build-dir', str(source)])
172173
assert ret == 0, stdout
173-
assert stderr == ("unusedFunction:The function 'property' is never used.\n"
174-
"unusedFunction:The function 'NOTIFY' is never used.\n")
174+
# Exporter keywords are matched without parsing the property's type/name.
175+
# Keyword collisions can hide unused property/NOTIFY methods, while the
176+
# actual accessors must stay used and unrelated methods must still warn.
177+
assert stderr.splitlines() == ["unusedFunction:The function '{}' is never used.".format(name)
178+
for name in reported]
175179

176180

177181
def test_unused_functions_qt_property_reset_keyword_name(tmp_path):
@@ -348,6 +352,46 @@ class MyType : public Base {
348352
assert stderr == "unusedFunction:The function 'unused' is never used.\n"
349353

350354

355+
@pytest.mark.parametrize('metadata', [
356+
'GET value',
357+
'GET (Base::value)',
358+
'value USED',
359+
'NESTED(GET unrelated) GET value',
360+
])
361+
def test_unused_functions_library_exporter(tmp_path, metadata):
362+
library = tmp_path / 'exporter.cfg'
363+
library.write_text('''<?xml version="1.0"?>
364+
<def format="2">
365+
<markup ext=".meta">
366+
<exported>
367+
<exporter prefix="EXPORT">
368+
<prefix>GET</prefix>
369+
<suffix>USED</suffix>
370+
</exporter>
371+
</exported>
372+
</markup>
373+
</def>
374+
''')
375+
source = tmp_path / 'test.cpp'
376+
source.write_text('''#define EXPORT(...)
377+
class Base {
378+
public:
379+
int value() const { return 0; }
380+
};
381+
class MyType : public Base {
382+
EXPORT(@METADATA@)
383+
public:
384+
void unrelated() {}
385+
};
386+
int main() { MyType t; }
387+
'''.replace('@METADATA@', metadata))
388+
ret, stdout, stderr = cppcheck(['-q', '--template={id}:{message}',
389+
'--enable=unusedFunction', '--library=' + str(library),
390+
'-j1', '--no-cppcheck-build-dir', str(source)])
391+
assert ret == 0, stdout
392+
assert stderr == "unusedFunction:The function 'unrelated' is never used.\n"
393+
394+
351395
def test_unused_functions_j():
352396
args = [
353397
'-q',

0 commit comments

Comments
 (0)