Skip to content

Fix #15126 FP syntaxError (function try block with override in struct) - #8955

Open
chrchr-github wants to merge 32 commits into
cppcheck-opensource:mainfrom
chrchr-github:chr_15126
Open

chrchr-github wants to merge 32 commits into
cppcheck-opensource:mainfrom
chrchr-github:chr_15126

Conversation

@chrchr-github

Copy link
Copy Markdown
Collaborator

No description provided.

@dmcppcheck dmcppcheck left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 override
  • noexcept override
  • noexcept(true) override
  • override final
  • int final = 0; try {...} inside a function body
  • int 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.

Comment thread lib/tokenize.cpp Outdated
continue;
const Token* par = tok->previous();
while (par && par->isKeyword())
while (par && (par->isKeyword() || Token::Match(par, "final|override")))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@dmcppcheck

Copy link
Copy Markdown
Contributor

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/
packages: 1000
Differing warnings: 0 (+0 -0)
Timing: +0.1% (http://ec2-16-170-140-253.eu-north-1.compute.amazonaws.com/pr-8955/2026-10-22_12-50-47-aa91b3a3c299_timing.html)

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
danmar previously approved these changes Oct 25, 2026

@dmcppcheck dmcppcheck left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 final
  • const & noexcept override
  • noexcept(false) override
  • throw() override
  • void 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.

Comment thread lib/tokenize.cpp Resolved
Comment thread lib/tokenize.cpp Fixed

@dmcppcheck dmcppcheck left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 try
  • noexcept(sizeof(int) > 2) override try
  • noexcept(n(1) > 0) override try
  • noexcept(noexcept(B()) && true) override try
  • noexcept(sizeof(int) > 2) try, without override

Automated review of c10d38f by Claude. The pull request was built and tested locally.

Comment thread lib/tokenize.cpp Outdated
continue;
const Token* par = tok->previous();
while (par && par->isKeyword())
while (par && !Token::Match(par, "[);]"))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@dmcppcheck

Copy link
Copy Markdown
Contributor

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/
packages: 1000
Differing warnings: 10 (+9 -1)
AI review: 10 improvement (http://ec2-16-170-140-253.eu-north-1.compute.amazonaws.com/pr-8955/2026-10-26_12-15-08-c10d38f45519-review.txt)
Timing: -0.2% (http://ec2-16-170-140-253.eu-north-1.compute.amazonaws.com/pr-8955/2026-10-26_12-15-08-c10d38f45519_timing.html)

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
danmar previously approved these changes Nov 7, 2026

@danmar danmar left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

wjakobsson and others added 14 commits November 7, 2026 19:36
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.
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.
chrchr-github and others added 11 commits November 7, 2026 19:36
…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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.