From 3072605693a404ebdffaf16452617d8a3e6d6d4b Mon Sep 17 00:00:00 2001 From: firewave Date: Sat, 25 Jan 2025 23:09:28 +0100 Subject: [PATCH] refs #13532 - reworked `--debug*` output --- cli/cmdlineparser.cpp | 9 + lib/cppcheck.cpp | 3 +- lib/settings.h | 9 + lib/tokenize.cpp | 64 ++++---- lib/tokenize.h | 5 +- test/cli/clang-import_test.py | 8 +- test/cli/other_test.py | 298 ++++++++++++++++++++++++++++++++-- test/testcmdlineparser.cpp | 24 +++ 8 files changed, 365 insertions(+), 55 deletions(-) diff --git a/cli/cmdlineparser.cpp b/cli/cmdlineparser.cpp index 0ea355358d6..cbcc456cbc6 100644 --- a/cli/cmdlineparser.cpp +++ b/cli/cmdlineparser.cpp @@ -647,6 +647,9 @@ CmdLineParser::Result CmdLineParser::parseFromArgs(int argc, const char* const a mSettings.cppHeaderProbe = true; } + else if (std::strcmp(argv[i], "--debug-ast") == 0) + mSettings.debugast = true; + // Show debug warnings for lookup for configuration files else if (std::strcmp(argv[i], "--debug-clang-output") == 0) mSettings.debugClangOutput = true; @@ -687,10 +690,16 @@ CmdLineParser::Result CmdLineParser::parseFromArgs(int argc, const char* const a else if (std::strcmp(argv[i], "--debug-simplified") == 0) mSettings.debugSimplified = true; + else if (std::strcmp(argv[i], "--debug-symdb") == 0) + mSettings.debugsymdb = true; + // Show template information else if (std::strcmp(argv[i], "--debug-template") == 0) mSettings.debugtemplate = true; + else if (std::strcmp(argv[i], "--debug-valueflow") == 0) + mSettings.debugvalueflow = true; + // Show debug warnings else if (std::strcmp(argv[i], "--debug-warnings") == 0) mSettings.debugwarnings = true; diff --git a/lib/cppcheck.cpp b/lib/cppcheck.cpp index 5dd0576db71..65f9e7ef1ae 100644 --- a/lib/cppcheck.cpp +++ b/lib/cppcheck.cpp @@ -725,8 +725,7 @@ unsigned int CppCheck::checkClang(const FileWithDetails &file) mErrorLogger, mSettings, &s_timerResults); - if (mSettings.debugnormal) - tokenizer.printDebugOutput(1, std::cout); + tokenizer.printDebugOutput(std::cout); checkNormalTokens(tokenizer, nullptr); // TODO: provide analyzer information // create dumpfile diff --git a/lib/settings.h b/lib/settings.h index 64e007b46ce..7e9be6c6a40 100644 --- a/lib/settings.h +++ b/lib/settings.h @@ -188,6 +188,9 @@ class CPPCHECKLIB WARN_UNUSED Settings { /** @brief Are we running from DACA script? */ bool daca{}; + /** @brief Is --debug-ast given? */ + bool debugast{}; + /** @brief Is --debug-clang-output given? */ bool debugClangOutput{}; @@ -215,9 +218,15 @@ class CPPCHECKLIB WARN_UNUSED Settings { /** @brief Is --debug-simplified given? */ bool debugSimplified{}; + /** @brief Is --debug-symdb given? */ + bool debugsymdb{}; + /** @brief Is --debug-template given? */ bool debugtemplate{}; + /** @brief Is --debug-valueflow given? */ + bool debugvalueflow{}; + /** @brief Is --debug-warnings given? */ bool debugwarnings{}; diff --git a/lib/tokenize.cpp b/lib/tokenize.cpp index 38c1d207565..8123d5f4e85 100644 --- a/lib/tokenize.cpp +++ b/lib/tokenize.cpp @@ -3441,7 +3441,7 @@ bool Tokenizer::simplifyTokens1(const std::string &configuration) mSymbolDatabase->setArrayDimensionsUsingValueFlow(); } - printDebugOutput(1, std::cout); + printDebugOutput(std::cout); return true; } @@ -5890,39 +5890,39 @@ bool Tokenizer::simplifyTokenList1(const char FileName[]) } //--------------------------------------------------------------------------- -void Tokenizer::printDebugOutput(int simplification, std::ostream &out) const +// TODO: do not depend on --verbose +void Tokenizer::printDebugOutput(std::ostream &out) const { - const bool debug = (simplification != 1U && mSettings.debugSimplified) || - (simplification != 2U && mSettings.debugnormal); + if (!list.front()) + return; - if (debug && list.front()) { - const bool xml = (mSettings.outputFormat == Settings::OutputFormat::xml); + const bool debug = mSettings.debugSimplified || mSettings.debugnormal || mSettings.debugsymdb || mSettings.debugast || mSettings.debugvalueflow; + if (!debug) + return; - if (!xml) - list.front()->printOut(out, xml, nullptr, list.getFiles()); + const bool xml = (mSettings.outputFormat == Settings::OutputFormat::xml); - if (xml) - { - out << "" << std::endl; - list.front()->printOut(out, xml, nullptr, list.getFiles()); - } + if (xml) + out << "" << std::endl; - if (mSymbolDatabase) { - if (xml) - mSymbolDatabase->printXml(out); - else if (mSettings.verbose) { - mSymbolDatabase->printOut("Symbol database"); - } - } + if (mSettings.debugSimplified || mSettings.debugnormal) + list.front()->printOut(out, xml, nullptr, list.getFiles()); + + if (mSymbolDatabase) { + if (xml) + mSymbolDatabase->printXml(out); + else if (mSettings.debugsymdb || (mSettings.debugnormal && mSettings.verbose)) + mSymbolDatabase->printOut("Symbol database"); + } - if (mSettings.verbose) - list.front()->printAst(mSettings.verbose, xml, list.getFiles(), out); + if (mSettings.debugast || (mSettings.debugnormal && mSettings.verbose)) + list.front()->printAst(mSettings.verbose, xml, list.getFiles(), out); + if (mSettings.debugnormal || mSettings.debugvalueflow) list.front()->printValueFlow(list.getFiles(), xml, out); - if (xml) - out << "" << std::endl; - } + if (xml) + out << "" << std::endl; } void Tokenizer::dump(std::ostream &out) const @@ -8077,13 +8077,15 @@ bool Tokenizer::isScopeNoReturn(const Token *endScopeToken, bool *unknown) const void Tokenizer::syntaxError(const Token *tok, const std::string &code) const { - printDebugOutput(0, std::cout); + if (mSettings.debugSimplified || mSettings.debugnormal) + printDebugOutput(std::cout); throw InternalError(tok, code.empty() ? "syntax error" : "syntax error: " + code, InternalError::SYNTAX); } void Tokenizer::unmatchedToken(const Token *tok) const { - printDebugOutput(0, std::cout); + if (mSettings.debugSimplified || mSettings.debugnormal) + printDebugOutput(std::cout); throw InternalError(tok, "Unmatched '" + tok->str() + "'. Configuration: '" + mConfiguration + "'.", InternalError::SYNTAX); @@ -8091,13 +8093,15 @@ void Tokenizer::unmatchedToken(const Token *tok) const void Tokenizer::syntaxErrorC(const Token *tok, const std::string &what) const { - printDebugOutput(0, std::cout); + if (mSettings.debugSimplified || mSettings.debugnormal) + printDebugOutput(std::cout); throw InternalError(tok, "Code '"+what+"' is invalid C code.", "Use --std, -x or --language to enforce C++. Or --cpp-header-probe to identify C++ headers via the Emacs marker.", InternalError::SYNTAX); } void Tokenizer::unknownMacroError(const Token *tok1) const { - printDebugOutput(0, std::cout); + if (mSettings.debugSimplified || mSettings.debugnormal) + printDebugOutput(std::cout); throw InternalError(tok1, "There is an unknown macro here somewhere. Configuration is required. If " + tok1->str() + " is a macro then please configure it.", InternalError::UNKNOWN_MACRO); } @@ -8131,7 +8135,7 @@ void Tokenizer::invalidConstFunctionTypeError(const Token *tok) const void Tokenizer::cppcheckError(const Token *tok) const { - printDebugOutput(0, std::cout); + printDebugOutput(std::cout); throw InternalError(tok, "Analysis failed. If the code is valid then please report this failure.", InternalError::INTERNAL); } diff --git a/lib/tokenize.h b/lib/tokenize.h index 0f0363354e9..a5b34fa091f 100644 --- a/lib/tokenize.h +++ b/lib/tokenize.h @@ -558,11 +558,8 @@ class CPPCHECKLIB Tokenizer { void createSymbolDatabase(); /** print --debug output if debug flags match the simplification: - * 0=unknown/both simplifications - * 1=1st simplifications - * 2=2nd simplifications */ - void printDebugOutput(int simplification, std::ostream &out) const; + void printDebugOutput(std::ostream &out) const; void dump(std::ostream &out) const; diff --git a/test/cli/clang-import_test.py b/test/cli/clang-import_test.py index 74136453044..f55f80f0837 100644 --- a/test/cli/clang-import_test.py +++ b/test/cli/clang-import_test.py @@ -47,8 +47,8 @@ def __check_symbol_database(tmpdir, code): testfile = os.path.join(tmpdir, 'test.cpp') with open(testfile, 'w+t') as f: f.write(code) - ret1, stdout1, _ = cppcheck(['--clang', '--debug', '-v', testfile]) - ret2, stdout2, _ = cppcheck(['--debug', '-v', testfile]) + ret1, stdout1, _ = cppcheck(['--clang', '--debug-symdb', testfile]) + ret2, stdout2, _ = cppcheck(['--debug-symdb', testfile]) assert 0 == ret1, stdout1 assert 0 == ret2, stdout2 assert __get_debug_section('### Symbol database', stdout1) == __get_debug_section('### Symbol database', stdout2) @@ -58,8 +58,8 @@ def __check_ast(tmpdir, code): testfile = os.path.join(tmpdir, 'test.cpp') with open(testfile, 'w+t') as f: f.write(code) - ret1, stdout1, _ = cppcheck(['--clang', '--debug', '-v', testfile]) - ret2, stdout2, _ = cppcheck(['--debug', '-v', testfile]) + ret1, stdout1, _ = cppcheck(['--clang', '--debug-ast', testfile]) + ret2, stdout2, _ = cppcheck(['--debug-ast', testfile]) assert 0 == ret1, stdout1 assert 0 == ret2, stdout1 assert __get_debug_section('##AST', stdout1) == __get_debug_section('##AST', stdout2) diff --git a/test/cli/other_test.py b/test/cli/other_test.py index ac1c303a804..0d792b90b98 100644 --- a/test/cli/other_test.py +++ b/test/cli/other_test.py @@ -3033,32 +3033,47 @@ def test_debug_verbose_xml(tmp_path): # TODO: test with --xml -def test_debug_template(tmp_path): +def __test_debug_template(tmp_path, verbose): test_file = tmp_path / 'test.cpp' with open(test_file, "w") as f: f.write( """template class TemplCl; -void f +void f() { - (void)*((int*)0); + (void)*((int*)nullptr); } """) args = [ '-q', - '--debug', # TODO: remove depdency on this + '--template=simple', '--debug-template', str(test_file) ] + if verbose: + args += ['--verbose'] + exitcode, stdout, stderr = cppcheck(args) assert exitcode == 0, stdout - assert stdout.find('##file ') != -1 - assert stdout.find('##Value flow') != -1 + assert stdout.find('##file ') == -1 + assert stdout.find('##Value flow') == -1 assert stdout.find('### Symbol database ###') == -1 assert stdout.find('##AST') == -1 assert stdout.find('### Template Simplifier pass ') != -1 - assert stderr.splitlines() == [] + assert stderr.splitlines() == [ + '{}:4:13: error: Null pointer dereference: (int*)nullptr [nullPointer]'.format(test_file) + ] + return stdout + + +def test_debug_template(tmp_path): + __test_debug_template(tmp_path, False) + + +def test_debug_template_verbose_nodiff(tmp_path): + # make sure --verbose does not change the output + assert __test_debug_template(tmp_path, False) == __test_debug_template(tmp_path, True) def test_file_ignore_2(tmp_path): # #13570 @@ -3087,7 +3102,7 @@ def test_file_ignore_2(tmp_path): # #13570 assert stderr.splitlines() == [] -def test_debug_valueflow(tmp_path): +def test_debug_valueflow_data(tmp_path): test_file = tmp_path / 'test.c' with open(test_file, "w") as f: f.write( @@ -3100,7 +3115,7 @@ def test_debug_valueflow(tmp_path): args = [ '-q', - '--debug', # TODO: limit to valueflow output + '--debug-valueflow', str(test_file) ] @@ -3108,7 +3123,7 @@ def test_debug_valueflow(tmp_path): assert exitcode == 0, stdout # check sections in output - assert stdout.find('##file ') != -1 + assert stdout.find('##file ') == -1 assert stdout.find('##Value flow') != -1 assert stdout.find('### Symbol database ###') == -1 assert stdout.find('##AST') == -1 @@ -3130,7 +3145,7 @@ def test_debug_valueflow(tmp_path): ] -def test_debug_valueflow_xml(tmp_path): # #13606 +def test_debug_valueflow_data_xml(tmp_path): # #13606 test_file = tmp_path / 'test.c' with open(test_file, "w") as f: f.write( @@ -3143,7 +3158,7 @@ def test_debug_valueflow_xml(tmp_path): # #13606 args = [ '-q', - '--debug', # TODO: limit to valueflow output + '--debug-valueflow', '--xml', str(test_file) ] @@ -3155,7 +3170,7 @@ def test_debug_valueflow_xml(tmp_path): # #13606 assert ElementTree.fromstring(stderr) is not None # check sections in output - assert stdout.find('##file ') != -1 # also exists in CDATA + assert stdout.find('##file ') == -1 assert stdout.find('##Value flow') == -1 assert stdout.find('### Symbol database ###') == -1 assert stdout.find('##AST') == -1 @@ -3165,8 +3180,6 @@ def test_debug_valueflow_xml(tmp_path): # #13606 debug_xml = ElementTree.fromstring(stdout) assert debug_xml is not None assert debug_xml.tag == 'debug' - file_elem = debug_xml.findall('file') - assert len(file_elem) == 1 valueflow_elem = debug_xml.findall('valueflow') assert len(valueflow_elem) == 1 scopes_elem = debug_xml.findall('scopes') @@ -3608,3 +3621,258 @@ def test_preprocess_enforced_cpp(tmp_path): # #10989 assert stderr.splitlines() == [ '{}:2:2: error: #error "err" [preprocessorErrorDirective]'.format(test_file) ] + + +# TODO: test with --xml +def __test_debug_normal(tmp_path, verbose): + test_file = tmp_path / 'test.c' + with open(test_file, "w") as f: + f.write( +"""void f() +{ + (void)*((int*)0); +} +""") + + args = [ + '-q', + '--template=simple', + '--debug-normal', + str(test_file) + ] + + if verbose: + args += ['--verbose'] + + exitcode, stdout, stderr = cppcheck(args) + assert exitcode == 0, stdout + assert stdout.find('##file ') != -1 + assert stdout.find('##Value flow') != -1 + if verbose: + assert stdout.find('### Symbol database ###') != -1 + else: + assert stdout.find('### Symbol database ###') == -1 + if verbose: + assert stdout.find('##AST') != -1 + else: + assert stdout.find('##AST') == -1 + assert stdout.find('### Template Simplifier pass ') == -1 + assert stderr.splitlines() == [ + '{}:3:13: error: Null pointer dereference: (int*)0 [nullPointer]'.format(test_file) + ] + return stdout + + +def test_debug_normal(tmp_path): + __test_debug_normal(tmp_path, False) + + +@pytest.mark.xfail(strict=True) # TODO: remove dependency on --verbose +def test_debug_normal_verbose_nodiff(tmp_path): + # make sure --verbose does not change the output + assert __test_debug_normal(tmp_path, False) == __test_debug_normal(tmp_path, True) + + +# TODO: test with --xml +def __test_debug_simplified(tmp_path, verbose): + test_file = tmp_path / 'test.c' + with open(test_file, "w") as f: + f.write( +"""void f() +{ + (void)*((int*)0); +} +""") + + args = [ + '-q', + '--template=simple', + '--debug-simplified', + str(test_file) + ] + + if verbose: + args += ['--verbose'] + + exitcode, stdout, stderr = cppcheck(args) + assert exitcode == 0, stdout + assert stdout.find('##file ') != -1 + assert stdout.find('##Value flow') == -1 + assert stdout.find('### Symbol database ###') == -1 + assert stdout.find('##AST') == -1 + assert stdout.find('### Template Simplifier pass ') == -1 + assert stderr.splitlines() == [ + '{}:3:13: error: Null pointer dereference: (int*)0 [nullPointer]'.format(test_file) + ] + return stdout + + +def test_debug_simplified(tmp_path): + __test_debug_simplified(tmp_path, False) + + +def test_debug_simplified_verbose_nodiff(tmp_path): + # make sure --verbose does not change the output + assert __test_debug_simplified(tmp_path, False) == __test_debug_simplified(tmp_path, True) + + +# TODO: test with --xml +def __test_debug_symdb(tmp_path, verbose): + test_file = tmp_path / 'test.c' + with open(test_file, "w") as f: + f.write( +"""void f() +{ + (void)*((int*)0); +} +""") + + args = [ + '-q', + '--template=simple', + '--debug-symdb', + str(test_file) + ] + + if verbose: + args += ['--verbose'] + + exitcode, stdout, stderr = cppcheck(args) + assert exitcode == 0, stdout + assert stdout.find('##file ') == -1 + assert stdout.find('##Value flow') == -1 + assert stdout.find('### Symbol database ###') != -1 + assert stdout.find('##AST') == -1 + assert stdout.find('### Template Simplifier pass ') == -1 + assert stderr.splitlines() == [ + '{}:3:13: error: Null pointer dereference: (int*)0 [nullPointer]'.format(test_file) + ] + return stdout + + +def test_debug_symdb(tmp_path): + __test_debug_symdb(tmp_path, False) + + +@pytest.mark.skip # TODO: this contains memory addresses the output will always differ - would require stable identifier +def test_debug_symdb_verbose_nodiff(tmp_path): + # make sure --verbose does not change the output + assert __test_debug_symdb(tmp_path, False) == __test_debug_symdb(tmp_path, True) + + +# TODO: test with --xml +def __test_debug_ast(tmp_path, verbose): + test_file = tmp_path / 'test.c' + with open(test_file, "w") as f: + f.write( +"""void f() +{ + (void)*((int*)0); +} +""") + + args = [ + '-q', + '--template=simple', + '--debug-ast', + str(test_file) + ] + + if verbose: + args += ['--verbose'] + + exitcode, stdout, stderr = cppcheck(args) + assert exitcode == 0, stdout + assert stdout.find('##file ') == -1 + assert stdout.find('##Value flow') == -1 + assert stdout.find('### Symbol database ###') == -1 + assert stdout.find('##AST') != -1 + assert stdout.find('### Template Simplifier pass ') == -1 + assert stderr.splitlines() == [ + '{}:3:13: error: Null pointer dereference: (int*)0 [nullPointer]'.format(test_file) + ] + return stdout + + +def test_debug_ast(tmp_path): + __test_debug_ast(tmp_path, False) + + +@pytest.mark.xfail(strict=True) # TODO: remove dependency on --verbose +def test_debug_ast_verbose_nodiff(tmp_path): + # make sure --verbose does not change the output + assert __test_debug_ast(tmp_path, False) == __test_debug_ast(tmp_path, True) + + +# TODO: test with --xml +def __test_debug_valueflow(tmp_path, verbose): + test_file = tmp_path / 'test.c' + with open(test_file, "w") as f: + f.write( +"""void f() +{ + (void)*((int*)0); +} +""") + + args = [ + '-q', + '--template=simple', + '--debug-valueflow', + str(test_file) + ] + + if verbose: + args += ['--verbose'] + + exitcode, stdout, stderr = cppcheck(args) + assert exitcode == 0, stdout + assert stdout.find('##file ') == -1 + assert stdout.find('##Value flow') != -1 + assert stdout.find('### Symbol database ###') == -1 + assert stdout.find('##AST') == -1 + assert stdout.find('### Template Simplifier pass ') == -1 + assert stderr.splitlines() == [ + '{}:3:13: error: Null pointer dereference: (int*)0 [nullPointer]'.format(test_file) + ] + return stdout + + +def test_debug_valueflow(tmp_path): + __test_debug_valueflow(tmp_path, False) + + +def test_debug_valueflow_verbose_nodiff(tmp_path): + # make sure --verbose does not change the output + assert __test_debug_valueflow(tmp_path, False) == __test_debug_valueflow(tmp_path, True) + + +def test_debug_syntaxerror_c(tmp_path): + test_file = tmp_path / 'test.c' + with open(test_file, "w") as f: + f.write( +""" +template class TemplCl; +void f() +{ + (void)*((int*)0); +} +""") + + args = [ + '-q', + '--template=simple', + '--debug-normal', + str(test_file) + ] + + exitcode, stdout, stderr = cppcheck(args) + assert exitcode == 0, stdout + assert stdout.find('##file ') != -1 + assert stdout.find('##Value flow') != -1 + assert stdout.find('### Symbol database ###') == -1 + assert stdout.find('##AST') == -1 + assert stdout.find('### Template Simplifier pass ') == -1 + assert stderr.splitlines() == [ + "{}:2:1: error: Code 'template<...' is invalid C code. [syntaxError]".format(test_file) + ] diff --git a/test/testcmdlineparser.cpp b/test/testcmdlineparser.cpp index fab73b838bb..55c4ba8562f 100644 --- a/test/testcmdlineparser.cpp +++ b/test/testcmdlineparser.cpp @@ -455,6 +455,9 @@ class TestCmdlineParser : public TestFixture { TEST_CASE(analyzeAllVsConfigs); TEST_CASE(noAnalyzeAllVsConfigs); TEST_CASE(noAnalyzeAllVsConfigs2); + TEST_CASE(debugSymdb); + TEST_CASE(debugAst); + TEST_CASE(debugValueflow); TEST_CASE(ignorepaths1); TEST_CASE(ignorepaths2); @@ -3106,6 +3109,27 @@ class TestCmdlineParser : public TestFixture { ASSERT_EQUALS("cppcheck: error: --no-analyze-all-vs-configs has no effect - no Visual Studio project provided.\n", logger->str()); } + void debugSymdb() { + REDIRECT; + const char * const argv[] = {"cppcheck", "--debug-symdb", "file.cpp"}; + ASSERT_EQUALS_ENUM(CmdLineParser::Result::Success, parseFromArgs(argv)); + ASSERT_EQUALS(true, settings->debugsymdb); + } + + void debugAst() { + REDIRECT; + const char * const argv[] = {"cppcheck", "--debug-ast", "file.cpp"}; + ASSERT_EQUALS_ENUM(CmdLineParser::Result::Success, parseFromArgs(argv)); + ASSERT_EQUALS(true, settings->debugast); + } + + void debugValueflow() { + REDIRECT; + const char * const argv[] = {"cppcheck", "--debug-valueflow", "file.cpp"}; + ASSERT_EQUALS_ENUM(CmdLineParser::Result::Success, parseFromArgs(argv)); + ASSERT_EQUALS(true, settings->debugvalueflow); + } + void ignorepaths1() { REDIRECT; const char * const argv[] = {"cppcheck", "-isrc", "file.cpp"};