GeezerVisonThe World Wide Web, restored to 800 pixels of glory. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Home|About|Gaming cabinet|Archives|How it works|Starter sites | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
GeezerVison is showing github.com — Fix 15102: Make copy-constructible classes copyable by pfultz2 · Pull Request #8705 · cppcheck-opensource/cppcheck Conversation
|
|
A lot of the changes are just mechanical changes of filter_arrows.py#!/usr/bin/env python3 import sys, re AGGRESSIVE = any(a in ('-a', '--aggressive') for a in sys.argv[1:]) ANSI = re.compile(r'\x1b\[[0-9;?]*[a-zA-Z]') def plain(s): return ANSI.sub('', s) # strip color for detection def norm(l): return plain(l)[1:].replace('->', '.') # drop marker, normalize arrows def hunk_is_noise(body): removed = [norm(l) for l in body if plain(l).startswith('-')] added = [norm(l) for l in body if plain(l).startswith('+')] if not removed and not added: return False return sorted(removed) == sorted(added) # every change is arrow-only def aggressive_body(body): """Cancel only the arrow-noise -/+ pairs, keep everything else.""" out, rem, add = [], [], [] def flush(): nonlocal rem, add used = [False] * len(add) anorm = [norm(a) for a in add] for r in rem: rn = norm(r); matched = False for i in range(len(add)): if not used[i] and anorm[i] == rn: # same once arrows normalized used[i] = matched = True break if not matched: out.append(r) # a real removal, keep it out.extend(a for i, a in enumerate(add) if not used[i]) rem, add = [], [] for line in body: pl = plain(line) if pl.startswith('-'): if add: flush() # new change block began rem.append(line) elif pl.startswith('+'): add.append(line) else: flush(); out.append(line) # context / "\ No newline" flush() return out out, file_header, file_header_emitted = [], [], False hunk_header, hunk_body = None, [] def flush_hunk(): global hunk_header, hunk_body, file_header_emitted if hunk_header is None: return if not hunk_is_noise(hunk_body): # drop fully-noise hunks entirely body = aggressive_body(hunk_body) if AGGRESSIVE else hunk_body if not file_header_emitted: out.extend(file_header); file_header_emitted = True out.append(hunk_header); out.extend(body) hunk_header, hunk_body = None, [] for line in sys.stdin: pl = plain(line) if pl.startswith('diff --git') or pl.startswith('diff --cc'): flush_hunk() file_header, file_header_emitted = [line], False elif pl.startswith('@@'): flush_hunk() hunk_header, hunk_body = line, [] elif hunk_header is not None: hunk_body.append(line) else: file_header.append(line) flush_hunk() sys.stdout.write(''.join(out)) And then you can view the diff locally with: |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
I would prefer if this was actually tool-driven (i.e. the clang-tidy check(s) enabled). And if these are "non-null" pointers there is no reason to use pointers at al we should be getting a reference from the object. This makes things correct but worse as it looks like we have unchecked pointer dereferences all over the place again. I have been working towards this for ages (with the focus on const correctness instead) in #4785 and cppcheck-opensource/simplecpp#548 but things stalled and I kept getting side tracked. Contributions on that would have been welcome. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
Sorry, I somehow I overlooked this because I am feeling more under the weather than usual and should not be reviewing things. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
These only construct from the reference, so they cant be constructed as a null pointer. This is where it behaves like A reference cant be used as a member variable because they cant rebind(making the class non-copyable) where as |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
I looked into making However, there is one caveat with this. The const is still fairly shallow as you can just copy the variable and then modify it(this isnt a problem for |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
I think this is mostly caused by Also we need to split the actual output from the
If you want to, you can get obvious get around it but it greatly improves things and helps the tooling to suggest more constness (see the simplecpp check).
I meant we should not be providing a pointer from the class i.e. using |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
I agree, just something to be aware of.
That is an unfortunate due to not allowing Best practices(like from CppCoreGuideline) also consider this an acceptable tradeoff as well. But there are some alternatives that I can think of. We could use the |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| - name: Self check (unusedFunction / no test / no gui) | ||
| run: | | ||
| supprs="--suppress=unusedFunction:lib/errorlogger.h:198 --suppress=unusedFunction:lib/importproject.cpp:1671 --suppress=unusedFunction:lib/importproject.cpp:1695" | ||
| supprs="--suppress=unusedFunction:lib/errorlogger.h:199 --suppress=unusedFunction:lib/importproject.cpp:1671 --suppress=unusedFunction:lib/importproject.cpp:1695" |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
I replaced this suppression #8714 . If you revert this change and rebase it should work.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
@firewave @danmar Before I do any work on this, would making this use |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
|
I switched it to struct A { [[noreturn]] void g(int); }; template<class T> struct Thunk { T& operator()() const; }; void f(Thunk<A> thunk, int* p) { if (!p) thunk().g(0); *p = 1; // <- false positive here } #8755 fixes this issue and needs to be merged in first. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
I don't feel that is bad if we never need copy-assignment. If copy-assignment is needed then yes we need to solve const/ref members. I like to use const and references. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
We need copy assignment if we want to do joins for forked analyzers(something I was trying to experiment with recently and it failed because of missing copy-assignment) as already mentioned in the PR description. Furthermore, it prevents using the class with std library functions and algorithms like swap, sort, partition, etc. Its a fundamental architectural decision that may not be needed at first, but when it is needed, it would require a significant refactor(that may not be feasible) that we should just design the classes correctly from the start(which I did originally but it was changed against my feedback). Thus I would like to use clang tidy to enforce this practice(which is also considered best practice for c++).
And you can, but if you want to use it for a class member, you need to either use the RefThunk class or delete the copy constructor. There could be some classes in this PR that could have a deleted copy constructor but for now I didnt make that change and just focused on keeping the same behavior. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
why do I need to delete the copy constructor to have a const class member? The copy constructor can create an identical copy no matter if the class member is const or not. I can understand that the assignment operator should be deleted when there are const members. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
The copy constructor can exist but clang tidy check It doesnt make much sense to make a class copy constructible but not copyable. The description also explains why this is bad practice and how it negatively affected
The compiler automatically deletes since its not possible to write one. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
@danmar Anymore feedback on this? Are we able to get this merged in? |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
@pfultz2 sorry for slow responses lately. Can you please check the merge conflicts. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
@claude review |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| * false if no modifications are done. | ||
| */ | ||
| bool simplifyCalculations(Token* frontToken = nullptr, const Token *backToken = nullptr, bool isTemplate = true); | ||
| bool simplifyCalculations(Token* frontToken = nullptr, const Token *backToken = nullptr, bool isTemplate = true) const; |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
simplifyCalculations() changes the token list (deleteNext(), str(...)), but it's now marked const. I think this comes from RefThunk::operator()() const returning T&: cppcheck's functionConst (and the compiler) can no longer see writes through mTokenList()/mTokenizer(), so it now suggests const on methods that modify state. The same thing happened to checkComplicatedSyntaxErrorsInTemplates() and Tokenizer::findComplicatedSyntaxErrorsInTemplates(). A const member function that rewrites the token list is misleading. It would be better to keep these non-const and suppress the warning, or to make RefThunk propagate constness (a const overload that returns const T&).
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
|
||
| /** settings */ | ||
| const Settings& mSettings; | ||
| RefThunk<const Settings> mSettings; |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
TokenList explicitly deletes its copy constructor and copy assignment (lines 56-57), so this change doesn't make it copyable. The PR description says non-copyable classes should keep their reference/const members. The same applies to other classes converted here that are effectively non-copyable, such as SymbolDatabase, Preprocessor, TemplateSimplifier and CmdLineParser. Either keep const Settings& here (with a NOLINT for the new clang-tidy check), or update the description.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
I'll remove RefThunk for these classes, and make them explictly non-copyable.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
I update to use refs for those classes. Since TokenList is still movable clang tidy still complains, so I added a NOLINT for now.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Review summary: the mechanical conversion looks correct. I checked the converted call sites, and
|
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
This pull request has merge conflicts that must be resolved. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
There should probably be a trac ticket for this PR. Please create it, or ask us to create it. Then the PR title should be updated. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
Ticket added. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
AI review
I found no new problems. The change is a mechanical conversion and I could not find any behaviour difference between the PR and its merge base.
testrunner passes, and cppcheck output on the samples and test/cfg files is identical to base apart from one shifted valueflow.cpp line number in debug messages.Makefile and oss-fuzz/Makefile match what dmake produces.refthunk.h and testrefthunk.cpp are not added to the .vcxproj.filters files (inline).cppcoreguidelines-avoid-const-or-ref-data-members check is clean. The GUI change in gui/resultitem.h was not compiled.The methods that modify the token list but are now const (TemplateSimplifier::simplifyCalculations and the two others) were already raised in the earlier review and are still in the diff.
Build
make -j8 cppcheck testrunner in pr/: succeeded with no compiler warnings.make -j8 cppcheck in base/: succeeded.Unit tests
./testrunner TestRefThunk: 10 tests, 0 failed../testrunner (everything): 5361 tests, 362 todos, 0 failed.PR against base (output sorted and diffed)
| Command | Result |
|---|---|
--enable=all --inconclusive --debug-warnings u.cpp (own file: unions, containers, null pointer, array index, templates) |
identical |
--enable=all --xml --checkers-report=<file> u.cpp |
XML and checkers report identical |
--report-progress=0 u.cpp |
identical |
--enable=all --inconclusive --library=std --library=posix --library=gnu samples/ |
identical |
--enable=all --inconclusive --library=qt --library=std -q test/cfg/{std.cpp,std.c,posix.c,qt.cpp,boost.cpp} |
identical (11570 lines) |
-j2 --executor=process --enable=all -q --debug-warnings test/cfg/std.c test/cfg/gnu.c |
identical except the valueflow.cpp:4909 → 4910 line number inside debug bailout messages |
UnionZeroInit (the path that now uses largestMember.name()) gives the same message in both builds for union U { char c; long long big; }; void f(void) { union U u = {0}; }.test/cfg/qt.cpp + test/cfg/std.cpp with --library=qt: 9.79 s for base, 9.81 s for the PR.Generated files
tools/dmake/dmake.cpp and ran it in pr/: Makefile and oss-fuzz/Makefile came out unchanged, so they are consistent with the dmake.cpp change.Not tested
lib/clangimport.cpp), because clang is not installed.Automated review of c620dd6 by Claude. The pull request was built and tested locally.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| <ClInclude Include="precompiled.h" /> | ||
| <ClInclude Include="preprocessor.h" /> | ||
| <ClInclude Include="programmemory.h" /> | ||
| <ClInclude Include="refthunk.h" /> |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
AI review
refthunk.h is added here but not to lib/cppcheck.vcxproj.filters, where the other lib headers are listed (for example smallvector.h). The same goes for testrefthunk.cpp and test/testrunner.vcxproj.filters, which lists testregex.cpp and testprogrammemory.cpp. Without these entries the new files show up outside the Header/Source filter groups in Visual Studio.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
This was automatically added by dmake I believe.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Test results for commit c620dd6 (tools/test-my-pr.py, main compared to this PR): Test: http://ec2-16-170-140-253.eu-north-1.compute.amazonaws.com/pr-8705/ 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. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
@danmar Anymore feedback? |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
@danmar Can this be merged now? There is no regressions. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
|
It seems that this broke the build of the main branch |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
…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.
There was an error while loading. Please reload this page.
Successfully merging this pull request may close these issues.
There was an error while loading. Please reload this page.