Repository navigation
Fix #15126 FP syntaxError (function try block with override in struct) - #8955
chrchr-github wants to merge 32 commits into
Conversation
dmcppcheck
left a comment
There was a problem hiding this comment.
AI review
The fix works for the common forms, void f() [const|noexcept|noexcept(true)] override|final try {...} catch(...) {...}, and I found no regressions. It only handles a single override or final directly after ) or after a keyword, though. Two forms with override still give the same syntaxError:
- a ref-qualifier:
void i() & override try,void i() && override try,void i() const & override try - a trailing return type:
auto k() -> void override try
The base version fails on these forms without override as well (void i() & try, auto k() -> void try), so they are a gap that already existed rather than a regression. It is still a gap in the same ticket area. When final or override is used as a variable name before a try inside a function body, the PR gives no new false positive.
Testing
I built cppcheck and testrunner in pr/. ./testrunner TestTokenizer TestGarbage TestSymbolDatabase TestSimplifyTokens TestClass passed with 0 failures.
Handled correctly, no errors:
const overridenoexcept overridenoexcept(true) overrideoverride finalint final = 0; try {...}inside a function bodyint override[2]; ... try {...}inside a function body
Still syntaxError, ./cppcheck -q x.cpp in each case:
struct E : B {
void i() & override try {} catch (...) {}
};error: syntax error: keyword 'try' is not allowed in class/struct/union/enum scope [syntaxError]
These forms give the same error:
void i() && override try {} catch (...) {}void i() const & override try {} catch (...) {}auto k() -> void override try {} catch (...) {}
Without override, the base version also rejects void i() & try {} catch (...) {} and auto k() -> void try {} catch (...) {}, so this limitation existed before the PR.
Automated review of aa91b3a by Claude. The pull request was built and tested locally.
| continue; | ||
| const Token* par = tok->previous(); | ||
| while (par && par->isKeyword()) | ||
| while (par && (par->isKeyword() || Token::Match(par, "final|override"))) |
There was a problem hiding this comment.
AI review
This loop still misses override or final after a ref-qualifier or a trailing return type. These still produce syntaxError:
struct E : B { void i() & override try {} catch (...) {} };
struct F : B { auto k() -> void override try {} catch (...) {} };&/&& and -> type before try already fail in base without override, so this is a gap that existed before, not a regression. It may still be worth covering here, or noting in the ticket.
|
Test results for commit aa91b3a (tools/test-my-pr.py, main compared to this PR): Test: http://ec2-16-170-140-253.eu-north-1.compute.amazonaws.com/pr-8955/ Posted automatically by the cppcheck PR test runner. +N: warnings only with this PR, -N: warnings only with main. The AI review is written by Claude and can be wrong. |
dmcppcheck
left a comment
There was a problem hiding this comment.
AI review
The fix now covers ref-qualifiers and trailing return types, but the trailing return type part only works when the return type is made of plain names and ::. The backward walk in simplifyFunctionTryCatch stops at any other token, so these still give syntaxError:
- a pointer return type:
auto k() -> int* override try {} catch (...) {} - a template return type:
auto k() -> std::vector<int> override try {} catch (...) {}
Forms that do work now include -> std::size_t final, -> const int& override, -> decltype(1) override and -> int (*)() override, so whether this case works depends on the spelling of the return type. Apart from this, I found no new problems: I saw no regressions in function bodies or in constructor try blocks. The earlier review already covered -> T try without override, which still fails in both base and the PR.
Testing
I built cppcheck and testrunner in pr/, and cppcheck in base/. ./testrunner TestTokenizer TestGarbage TestSymbolDatabase TestSimplifyTokens TestClass TestFunctions TestVarID passed: 1627 tests, 0 failures.
Still syntaxError. I put each member below in struct B{virtual ~B();}; struct D : B { <member> }; and ran ./cppcheck -q t.cpp:
auto k() -> int* override try {} catch (...) {}
auto k() -> std::vector<int> override try {} catch (...) {}Output:
t1.cpp:3:28: error: syntax error: keyword 'try' is not allowed in class/struct/union/enum scope [syntaxError]
t2.cpp:3:40: error: syntax error: keyword 'try' is not allowed in class/struct/union/enum scope [syntaxError]
Fixed by the PR (base gives syntaxError, the PR gives none):
-> const int& override-> decltype(1) override-> int (*)() override-> std::size_t finalconst & noexcept overridenoexcept(false) overridethrow() overridevoid f() &override final
Some of these give missingReturn, which is correct for those bodies.
Function bodies. I compared the --debug-normal output of base and the PR on try blocks after if (x) MACRO, while (x), x = x && p; and for (;;), and on a constructor try block with an initializer list. The output is identical.
Automated review of bb851af by Claude. The pull request was built and tested locally.
dmcppcheck
left a comment
There was a problem hiding this comment.
AI review
I found one new problem: the backward walk in simplifyFunctionTryCatch stops at the first ) it finds. If that ) is nested inside a template argument of a trailing return type, it is not the end of the parameter list, so isFunctionHead() fails. For example, auto f() -> W<decltype(1)> override try {...} catch (...) {...} still gives syntaxError. -> decltype(1) override try works only because there the ) that is found happens to be followed directly by override. Base also rejects this form, so it is a remaining gap rather than a regression. Apart from that, I found no new problems. The walk can only find more function try blocks than base does, not fewer: base stopped at the same ) or failed.
Testing
I built cppcheck and testrunner in pr/. ./testrunner TestTokenizer TestGarbage TestSymbolDatabase passed with 0 failures.
Still syntaxError:
template<class T> struct W {};
struct B { virtual W<int> f(); };
struct D : B {
auto f() -> W<decltype(1)> override try { return {}; } catch (...) { return {}; }
};$ ./cppcheck -q g.cpp
g.cpp:4:41: error: syntax error: keyword 'try' is not allowed in class/struct/union/enum scope [syntaxError]
auto f() -> std::vector<decltype(x)> override try ... gives the same error.
No errors for any of these:
-> decltype(1) override trynoexcept(sizeof(int) > 2) override trynoexcept(n(1) > 0) override trynoexcept(noexcept(B()) && true) override trynoexcept(sizeof(int) > 2) try, withoutoverride
Automated review of c10d38f by Claude. The pull request was built and tested locally.
| continue; | ||
| const Token* par = tok->previous(); | ||
| while (par && par->isKeyword()) | ||
| while (par && !Token::Match(par, "[);]")) |
There was a problem hiding this comment.
AI review
The walk stops at the first ). When that ) is inside a template argument of the trailing return type, isFunctionHead() sees ) > override try and returns null. So this still gives syntaxError:
struct D : B {
auto f() -> W<decltype(1)> override try { return {}; } catch (...) { return {}; }
};A fix could skip linked tokens while walking back: when par is a > or ) with a link, jump to par->link(). It could also keep walking until par->link()->previous() is a name that is not decltype/noexcept/throw. Either way, the walk would reach the ) that closes the parameter list.
|
Test results for commit c10d38f (tools/test-my-pr.py, main compared to this PR): Test: http://ec2-16-170-140-253.eu-north-1.compute.amazonaws.com/pr-8955/ Posted automatically by the cppcheck PR test runner. +N: warnings only with this PR, -N: warnings only with main. The AI review is written by Claude and can be wrong. |
danmar
left a comment
There was a problem hiding this comment.
spontanously the AI review comment looks correct to me.
However if you want to limit the scope of this PR and ignore that comment then feel free to do it. A ticket wouldn't hurt then so we don't forget it.
…when includes are not in order (cppcheck-opensource#8957)
Extend the GUI help with detailed explanations and new screenshots: - rewrite the existing pages and describe every option in the project file dialog and the preferences - add pages for the main window, suppressions, results files/printing/ statistics, scratchpad/library editor, command line options and troubleshooting - add a shared stylesheet, a contents tree and index keywords - replace the old screenshots with new ones of the current GUI - remove the obsolete manual.html (Cppcheck 1.46)
…urce#8952) Trac: https://trac.cppcheck.net/ticket/12282 A float literal out of the range of `double` stops the analysis of the whole file: Internal Error. MathLib::toDoubleNumber: conversion failed: 1.0e4931L `MathLib::toDoubleNumber()` reads the literal with an `istringstream`. For a value out of range the stream stores the largest finite value and sets `failbit`, which became the internal error. Compilers make such a literal infinity, so this does the same. Besides `long double` constants like the one in the ticket, a common trigger is glibc's `math.h` when the GCC builtins are not used: `HUGE_VAL`/`HUGE_VALF` are then `1e10000`/`1e10000f`, so any file using `INFINITY` or `HUGE_VAL` hit this. Tests: new cases in `TestMathLib::toDoubleNumber` (`1e10000`, `1e10000f`, `1e10000L`, `1.5E+400`, `1.0e4931L`); they fail without the change.
…t UB in fmtDouble/Round) (cppcheck-opensource#8950) fmtDouble and the Round branch in ImportProject::applyMSBuildStaticFunction cast the double result of a System.Math property function to long long without a range check, so a project value like `$([System.Math]::Pow(10,30))` or `$([System.Math]::Round(1e30))` is undefined behaviour (UBSan float-cast-overflow, as noted in the AI review on cppcheck-opensource#8938). fmtDouble now takes the integer path only when the value fits in long long, and Round does its half-to-even step in double and formats through fmtDouble, so in-range results are unchanged and Round(1e30) gives the same text as Pow(10,30) instead of LLONG_MIN (out-of-range values still print via std::to_string rather than MSBuild's `1E+30`, which is left for a separate change). Tested with new asserts in testMSBuildStaticFunctions, the full testrunner, and `cppcheck --project` on a crafted vcxproj with a UBSan build. I don't have Trac access, so could you create a ticket for this one?
…on pointer returning a reference (cppcheck-opensource#8931) https://trac.cppcheck.net/ticket/15094 For a function pointer returning a reference, the reference of the return type was taken as reference of the variable: ```cpp struct S { std::string& (*f)(int, int); int& (*g[2])(); }; void foo(int& (*fp)(), int&& (*fp2)()); ``` `Variable::isReference()` (and `isRValueReference()` for `fp2`) was true for all of them. `Variable::evaluate()` now drops the reference collected before the `(` of a function declarator, as it belongs to the return type. A `&` after the `(` still makes the variable a reference, so `int (*&f)()`, `int (&f)()`, `int*& r`, `int (&a)[2]` and `int& (r)` are unchanged. The `ValueType` of the function pointer is not changed: the type of a call `fp()` is derived from it, so its reference has to stay the one of the return type (clearing it caused `returnTempReference` FPs). Two places relied on the wrong flags and are adjusted: - `checkConstVariable`: a reference to a function returning a reference (`int& (&f)()`) was skipped only because it was wrongly flagged as rvalue reference. References to functions are now skipped explicitly (this also fixes the existing FP for `int (&f)()`). - Copy/move constructor detection: `Foo(Foo&& (*f)())` was a move constructor. With the fix it is a normal constructor, and parameters that are references to functions (`Foo(Foo& (&f)())`) no longer make a copy/move constructor either. Found with cppcheck-opensource#8910, where it suppressed `uninitMemberVarNoCtor` for a struct with such a member (codelite in the PR test results). cppcheck-opensource#8910 contains a workaround in CheckClass for this; with this PR merged it can be replaced by `var.isReference()` again. This PR was created by Claude Code (an AI coding agent) on behalf of @autoantwort. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…-opensource#8920) AnnotateCodeFormatter.wrap put the error message into the annotated source page, and main put the file name into the index, stats and per-file pages, without html_escape, so text from the checked code such as `#error <img src=x onerror=alert(1)>` or a file named `<b>x.c` became live markup in the report; both now go through html_escape like the verbose text and the index message already do, and test_htmlreport.py has a test for it.
…ppcheck-opensource#8910) https://trac.cppcheck.net/ticket/15080 ```cpp struct S { const std::array<std::uint8_t, 2> a; const std::vector<std::uint8_t>::const_iterator it; }; S f(const std::vector<std::uint8_t>& v) { return S{ {}, v.cbegin() }; } ``` gives `Member variable 'S::it' has no initializer. [uninitMemberVarNoCtor]`, and `struct S { int a = 0; const int b; int& c; };` warns for `b` and `c`. A non-static const or reference member without default member initializer can't stay uninitialized: for trivial types the implicit default constructor is deleted, so every instance must initialize it, and a const class type is default-constructed. These members are now skipped by the no-constructor check. Since f90a76f (#15072) this also shows up for class templates whose member type is a `using` alias. This PR was created by Claude Code (an AI coding agent) on behalf of @autoantwort. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
… handlers read (cppcheck-opensource#8948) Follow-up to cppcheck-opensource#8939. Several `createTokens` handlers call `mExtTokens.back()`/`front()` or index relative to `size()` without checking that the node line had any ext tokens, so a `--clang` dump line with nothing after the node name (e.g. `` `-IntegerLiteral `` followed by a space) reads out of bounds. The same happens for StringLiteral, CharacterLiteral, CXXBoolLiteralExpr, LabelStmt, EnumDecl, DeclRefExpr and others, for a VarDecl that only has an address (`mExtTokens[typeIndex - 1]` with `typeIndex` 0), and for a FunctionDecl ending in `prev` (dereferences `end()`). The AstNode constructor now throws an AST internal error when the node has no ext tokens, and `createTokensVarDecl` / `createTokensFunctionDecl` throw the same error for the other two shapes. An address-only FunctionDecl / CXXMethodDecl / CXXConstructorDecl or EnumConstantDecl (`` `-FunctionDecl 0x1 ``) is rejected as well: `getSpelling()` is empty there, `addtoken("")` adds nothing and returns a null `back()`, so both handlers now check the spelling before adding any token. Real clang dumps always carry the node address, location, name and type, so they are unaffected (checked `--clang` on the samples and a few lib files). `crash2` now expects the internal error instead of silently producing no tokens. The size()-2 sites are guarded in cppcheck-opensource#8939 and not touched here.
…urce#8951) Co-authored-by: chrchr-github <noreply@github.com>
Before glibc 2.32, `strerror`() is not MT-Safe. (from man strerror on Ubuntu 26.04). glibc 2.32 was released on 2020-08-05 and glibc 2.43 on 2026-01-24 (from https://sourceware.org/glibc/wiki/Glibc%20Timeline) This patch stops the `threadsafety.py` addon from warning about `strerror()`. `strerror` was used in `test/cli/other_test.py` to test `threadsafety.py` so we also patch that to use a different thread-unsafe function.
…s()) (cppcheck-opensource#8905) Co-authored-by: chrchr-github <noreply@github.com>
…rce#8705) Lots of copy constructible classes were using const and ref members which makes the classes non-copyable due to no longer supporting a copy-assignment. I replaced the ref members with a `RefThunk` class which is kind of like `std::reference_wrapper`, but it can access the member with `()` so its easier to access the members. It doesnt use `*` or `->` so it wont be confused for a possible null pointer. Now this PR doesnt replace all reference members, just for the classes that are copy-constructible. If we want to convert a class back to use reference or const members then we can delete the copy constructor and assignment. Furthermore, I enabled the clang-tidy check `cppcoreguidelines-avoid-const-or-ref-data-members` to check for these cases in the future. Here are some references explaining why this is bad practice: * https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#rc-constref * https://lesleylai.info/en/const-and-reference-member-variables/ * https://quuxplusone.github.io/blog/2022/01/23/dont-const-all-the-things/#data-members-never-const Beyond just being bad practice, this also has prevent me from doing certain things with the `ForwardAnalyzer` recently that might have improved it further such as joining or swaping a forked analyzer. I intentionally made these classes copyable for this reason(and were changed to non-copyable against [my feedback](cppcheck-opensource#4868 (comment)) as well). I understand that using references help prevent dereferencing a nullptr which is why I added a `RefThunk` class instead of using raw pointers like previously.
…ppcheck-opensource#8970) Makes it so that hash based suppressions are indexed by hash. Matching hash based suppressions is now O(1) (ignoring collisions) instead of O(n). For large code bases using baseline suppressions (which may be in the number of tens of thousands) this leads to a substantial speed up. We could probably speed up matching of other suppressions in the future as well, but I assume these are in general manually added and won't even get close to the same number as baseline suppressions. Also fixed a bug in ProcessExecutor that would cause new suppressions to be added unintentionally.
No description provided.