Skip to content

Enable unnecessary-virtual-specifier by default #133265

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 31, 2025
Merged

Conversation

DKLoehr
Copy link
Contributor

@DKLoehr DKLoehr commented Mar 27, 2025

This turns on the unnecessary-virtual-specifier warning in genera, but disables it when building LLVM. It also tweaks the warning description to be slightly more accurate.

Background: I've been working on cleaning up this warning in two codebases: LLVM and chromium (plus its dependencies). The chromium cleanup has been straightforward. Git archaeology shows that there are two reasons for the warnings: classes to which final was added after they were initially committed, and classes with virtual destructors that nobody remarks on. Presumably the latter case is because people are just very used to destructors being virtual.

The LLVM cleanup was more surprising: I discovered that we have an old policy about including out-of-line virtual functions in every class with a vtable, even final ones. This means our codebase has many virtual "anchor" functions which do nothing except control where the vtable is emitted, and which trigger the warning. I looked into alternatives to satisfy the policy, such as using destructors instead of introducing a new function, but it wasn't clear if they had larger implications.

Overall, it seems like the warning is genuinely useful in most codebases (evidenced by chromium and its dependencies), and LLVM is an unusual case. Therefore we should enable the warning by default, and turn it off only for LLVM builds.

@llvmbot llvmbot added cmake Build system in general and CMake in particular clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Mar 27, 2025
@DKLoehr
Copy link
Contributor Author

DKLoehr commented Mar 27, 2025

@zmodem Do you mind taking a look?

@llvmbot
Copy link
Member

llvmbot commented Mar 27, 2025

@llvm/pr-subscribers-clang-static-analyzer-1

@llvm/pr-subscribers-clang

Author: Devon Loehr (DKLoehr)

Changes

This turns on the unnecessary-virtual-specifier warning in genera, but disables it when building LLVM. It also tweaks the warning description to be slightly more accurate.

Background: I've been working on cleaning up this warning in two codebases: LLVM and chromium (plus its dependencies). The chromium cleanup has been straightforward. Git archaeology shows that there are two reasons for the warnings: classes to which final was added after they were initially committed, and classes with virtual destructors that nobody remarks on. Presumably the latter case is because people are just very used to destructors being virtual.

The LLVM cleanup was more surprising: I discovered that we have an old policy about including out-of-line virtual functions in every class with a vtable, even final ones. This means our codebase has many virtual "anchor" functions which do nothing except control where the vtable is emitted, and which trigger the warning. I looked into alternatives to satisfy the policy, such as using destructors instead of introducing a new function, but it wasn't clear if they had larger implications.

Overall, it seems like the warning is genuinely useful in most codebases (evidenced by chromium and its dependencies), and LLVM is an unusual case. Therefore we should enable the warning by default, and turn it off only for LLVM builds.


Full diff: https://github.com/llvm/llvm-project/pull/133265.diff

3 Files Affected:

  • (modified) clang/include/clang/Basic/DiagnosticGroups.td (+3-4)
  • (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+1-1)
  • (modified) llvm/cmake/modules/HandleLLVMOptions.cmake (+1-1)
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index b9f08d96151c9..e6e9ebbc2c304 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -377,13 +377,12 @@ def CXX11WarnSuggestOverride : DiagGroup<"suggest-override">;
 def WarnUnnecessaryVirtualSpecifier : DiagGroup<"unnecessary-virtual-specifier"> {
   code Documentation = [{
 Warns when a ``final`` class contains a virtual method (including virtual
-destructors). Since ``final`` classes cannot be subclassed, their methods
-cannot be overridden, and hence the ``virtual`` specifier is useless.
+destructors) that does not override anything. Since ``final`` classes cannot be
+subclassed, their methods cannot be overridden, so there is no point to
+introducing new ``virtual`` methods.
 
 The warning also detects virtual methods in classes whose destructor is
 ``final``, for the same reason.
-
-The warning does not fire on virtual methods which are also marked ``override``.
   }];
 }
 
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index c77cde297dc32..05ded7d9ecef1 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -2733,7 +2733,7 @@ def note_final_dtor_non_final_class_silence : Note<
   "mark %0 as '%select{final|sealed}1' to silence this warning">;
 def warn_unnecessary_virtual_specifier : Warning<
   "virtual method %0 is inside a 'final' class and can never be overridden">,
-  InGroup<WarnUnnecessaryVirtualSpecifier>, DefaultIgnore;
+  InGroup<WarnUnnecessaryVirtualSpecifier>;
 
 // C++11 attributes
 def err_repeat_attribute : Error<"%0 attribute cannot be repeated">;
diff --git a/llvm/cmake/modules/HandleLLVMOptions.cmake b/llvm/cmake/modules/HandleLLVMOptions.cmake
index 185c9b63aada3..f2c5cf4241e3d 100644
--- a/llvm/cmake/modules/HandleLLVMOptions.cmake
+++ b/llvm/cmake/modules/HandleLLVMOptions.cmake
@@ -689,7 +689,7 @@ if ( LLVM_COMPILER_IS_GCC_COMPATIBLE OR CMAKE_CXX_COMPILER_ID MATCHES "XL" )
 endif( LLVM_COMPILER_IS_GCC_COMPATIBLE OR CMAKE_CXX_COMPILER_ID MATCHES "XL" )
 
 if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
-  append("-Werror=unguarded-availability-new" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
+  append("-Werror=unguarded-availability-new -Wno-unnecessary-virtual-specifier" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
 endif()
 
 if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND LLVM_ENABLE_LTO)

Copy link
Member

@Sirraide Sirraide left a comment

Choose a reason for hiding this comment

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

Looks like you’ll have to update some tests because CI is failing (I’d be very surprised if we didn’t have tests for this). Also, this still needs a release note.

Maybe a fix for the virtual anchor pattern would be to somehow not emit the warning for the first (out-of-line) virtual function in a class, but that feels like too much of a hack...

As to whether this should be enabled by default, I don’t really have strong opinions on that. It does seem like you’d usually not want to introduce new virtual functions in a final class; the anchor is really the only use case I can think of.

@DKLoehr
Copy link
Contributor Author

DKLoehr commented Mar 27, 2025

Ach, I was so focused on making the build work that I forgot to run tests...
Fixed now, either by disabling the warning or adding it as expected, as seemed appropriate.

Copy link
Member

@Sirraide Sirraide left a comment

Choose a reason for hiding this comment

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

The release note is still missing.

Other than that this looks fine I think, but I’d say wait a few days before merging in case anyone else has any opinions as to whether this should be enabled by default or not.

@DKLoehr
Copy link
Contributor Author

DKLoehr commented Mar 27, 2025

Ah, missed that, sorry. It seems there hasn't been a release since the warning was added, and the existing release note seems to still apply:

- The ``-Wunnecessary-virtual-specifier`` warning has been added to warn about

@Sirraide
Copy link
Member

Ah, missed that, sorry. It seems there hasn't been a release since the warning was added, and the existing release note seems to still apply:

Ah, I thought it was an older warning. In that case we don’t need a release note

Copy link
Collaborator

@zmodem zmodem left a comment

Choose a reason for hiding this comment

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

lgtm

Copy link
Collaborator

@AaronBallman AaronBallman left a comment

Choose a reason for hiding this comment

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

LGTM!

@zmodem zmodem merged commit 4007de0 into llvm:main Mar 31, 2025
11 checks passed
SchrodingerZhu pushed a commit to SchrodingerZhu/llvm-project that referenced this pull request Mar 31, 2025
This turns on the unnecessary-virtual-specifier warning in general, but
disables it when building LLVM. It also tweaks the warning description
to be slightly more accurate.

Background: I've been working on cleaning up this warning in two
codebases: LLVM and chromium (plus its dependencies). The chromium
cleanup has been straightforward. Git archaeology shows that there are
two reasons for the warnings: classes to which `final` was added after
they were initially committed, and classes with virtual destructors that
nobody remarks on. Presumably the latter case is because people are just
very used to destructors being virtual.

The LLVM cleanup was more surprising: I discovered that we have an [old
policy](https://llvm.org/docs/CodingStandards.html#provide-a-virtual-method-anchor-for-classes-in-headers)
about including out-of-line virtual functions in every class with a
vtable, even `final` ones. This means our codebase has many virtual
"anchor" functions which do nothing except control where the vtable is
emitted, and which trigger the warning. I looked into alternatives to
satisfy the policy, such as using destructors instead of introducing a
new function, but it wasn't clear if they had larger implications.

Overall, it seems like the warning is genuinely useful in most codebases
(evidenced by chromium and its dependencies), and LLVM is an unusual
case. Therefore we should enable the warning by default, and turn it off
only for LLVM builds.
@llvm-ci
Copy link
Collaborator

llvm-ci commented Mar 31, 2025

LLVM Buildbot has detected a new failure on builder sanitizer-x86_64-linux-android running on sanitizer-buildbot-android while building clang,llvm at step 2 "annotate".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/186/builds/7809

Here is the relevant piece of the build log for the reference
Step 2 (annotate) failure: 'python ../sanitizer_buildbot/sanitizers/zorg/buildbot/builders/sanitizers/buildbot_selector.py' (failure)
...
[       OK ] AddressSanitizer.AtoiAndFriendsOOBTest (2281 ms)
[ RUN      ] AddressSanitizer.HasFeatureAddressSanitizerTest
[       OK ] AddressSanitizer.HasFeatureAddressSanitizerTest (0 ms)
[ RUN      ] AddressSanitizer.CallocReturnsZeroMem
[       OK ] AddressSanitizer.CallocReturnsZeroMem (11 ms)
[ DISABLED ] AddressSanitizer.DISABLED_TSDTest
[ RUN      ] AddressSanitizer.IgnoreTest
[       OK ] AddressSanitizer.IgnoreTest (0 ms)
[ RUN      ] AddressSanitizer.SignalTest
[       OK ] AddressSanitizer.SignalTest (185 ms)
[ RUN      ] AddressSanitizer.ReallocTest
[       OK ] AddressSanitizer.ReallocTest (34 ms)
[ RUN      ] AddressSanitizer.WrongFreeTest
[       OK ] AddressSanitizer.WrongFreeTest (120 ms)
[ RUN      ] AddressSanitizer.LongJmpTest
[       OK ] AddressSanitizer.LongJmpTest (0 ms)
[ RUN      ] AddressSanitizer.ThreadStackReuseTest
[       OK ] AddressSanitizer.ThreadStackReuseTest (7 ms)
[ DISABLED ] AddressSanitizer.DISABLED_MemIntrinsicUnalignedAccessTest
[ DISABLED ] AddressSanitizer.DISABLED_LargeFunctionSymbolizeTest
[ DISABLED ] AddressSanitizer.DISABLED_MallocFreeUnwindAndSymbolizeTest
[ RUN      ] AddressSanitizer.UseThenFreeThenUseTest
[       OK ] AddressSanitizer.UseThenFreeThenUseTest (122 ms)
[ RUN      ] AddressSanitizer.FileNameInGlobalReportTest
[       OK ] AddressSanitizer.FileNameInGlobalReportTest (128 ms)
[ DISABLED ] AddressSanitizer.DISABLED_StressStackReuseAndExceptionsTest
[ RUN      ] AddressSanitizer.MlockTest
[       OK ] AddressSanitizer.MlockTest (0 ms)
[ DISABLED ] AddressSanitizer.DISABLED_DemoThreadedTest
[ DISABLED ] AddressSanitizer.DISABLED_DemoStackTest
[ DISABLED ] AddressSanitizer.DISABLED_DemoThreadStackTest
[ DISABLED ] AddressSanitizer.DISABLED_DemoUAFLowIn
[ DISABLED ] AddressSanitizer.DISABLED_DemoUAFLowLeft
[ DISABLED ] AddressSanitizer.DISABLED_DemoUAFLowRight
[ DISABLED ] AddressSanitizer.DISABLED_DemoUAFHigh
[ DISABLED ] AddressSanitizer.DISABLED_DemoOOM
[ DISABLED ] AddressSanitizer.DISABLED_DemoDoubleFreeTest
[ DISABLED ] AddressSanitizer.DISABLED_DemoNullDerefTest
[ DISABLED ] AddressSanitizer.DISABLED_DemoFunctionStaticTest
[ DISABLED ] AddressSanitizer.DISABLED_DemoTooMuchMemoryTest
[ RUN      ] AddressSanitizer.LongDoubleNegativeTest
[       OK ] AddressSanitizer.LongDoubleNegativeTest (0 ms)
[----------] 19 tests from AddressSanitizer (28332 ms total)

[----------] Global test environment tear-down
[==========] 22 tests from 2 test suites ran. (28335 ms total)
[  PASSED  ] 22 tests.

  YOU HAVE 1 DISABLED TEST

Step 24 (run instrumented asan tests [aarch64/aosp_coral-userdebug/AOSP.MASTER]) failure: run instrumented asan tests [aarch64/aosp_coral-userdebug/AOSP.MASTER] (failure)
...
[ RUN      ] AddressSanitizer.HasFeatureAddressSanitizerTest
[       OK ] AddressSanitizer.HasFeatureAddressSanitizerTest (0 ms)
[ RUN      ] AddressSanitizer.CallocReturnsZeroMem
[       OK ] AddressSanitizer.CallocReturnsZeroMem (8 ms)
[ DISABLED ] AddressSanitizer.DISABLED_TSDTest
[ RUN      ] AddressSanitizer.IgnoreTest
[       OK ] AddressSanitizer.IgnoreTest (0 ms)
[ RUN      ] AddressSanitizer.SignalTest
[       OK ] AddressSanitizer.SignalTest (313 ms)
[ RUN      ] AddressSanitizer.ReallocTest
[       OK ] AddressSanitizer.ReallocTest (20 ms)
[ RUN      ] AddressSanitizer.WrongFreeTest
[       OK ] AddressSanitizer.WrongFreeTest (264 ms)
[ RUN      ] AddressSanitizer.LongJmpTest
[       OK ] AddressSanitizer.LongJmpTest (0 ms)
[ RUN      ] AddressSanitizer.ThreadStackReuseTest
[       OK ] AddressSanitizer.ThreadStackReuseTest (25 ms)
[ DISABLED ] AddressSanitizer.DISABLED_MemIntrinsicUnalignedAccessTest
[ DISABLED ] AddressSanitizer.DISABLED_LargeFunctionSymbolizeTest
[ DISABLED ] AddressSanitizer.DISABLED_MallocFreeUnwindAndSymbolizeTest
[ RUN      ] AddressSanitizer.UseThenFreeThenUseTest
[       OK ] AddressSanitizer.UseThenFreeThenUseTest (270 ms)
[ RUN      ] AddressSanitizer.FileNameInGlobalReportTest
[       OK ] AddressSanitizer.FileNameInGlobalReportTest (300 ms)
[ DISABLED ] AddressSanitizer.DISABLED_StressStackReuseAndExceptionsTest
[ RUN      ] AddressSanitizer.MlockTest
[       OK ] AddressSanitizer.MlockTest (0 ms)
[ DISABLED ] AddressSanitizer.DISABLED_DemoThreadedTest
[ DISABLED ] AddressSanitizer.DISABLED_DemoStackTest
[ DISABLED ] AddressSanitizer.DISABLED_DemoThreadStackTest
[ DISABLED ] AddressSanitizer.DISABLED_DemoUAFLowIn
[ DISABLED ] AddressSanitizer.DISABLED_DemoUAFLowLeft
[ DISABLED ] AddressSanitizer.DISABLED_DemoUAFLowRight
[ DISABLED ] AddressSanitizer.DISABLED_DemoUAFHigh
[ DISABLED ] AddressSanitizer.DISABLED_DemoOOM
[ DISABLED ] AddressSanitizer.DISABLED_DemoDoubleFreeTest
[ DISABLED ] AddressSanitizer.DISABLED_DemoNullDerefTest
[ DISABLED ] AddressSanitizer.DISABLED_DemoFunctionStaticTest
[ DISABLED ] AddressSanitizer.DISABLED_DemoTooMuchMemoryTest
[ RUN      ] AddressSanitizer.LongDoubleNegativeTest
[       OK ] AddressSanitizer.LongDoubleNegativeTest (0 ms)
[----------] 19 tests from AddressSanitizer (71465 ms total)

[----------] Global test environment tear-down
[==========] 22 tests from 2 test suites ran. (71468 ms total)
[  PASSED  ] 22 tests.

  YOU HAVE 1 DISABLED TEST

Serial 17031FQCB00176

@llvm-ci
Copy link
Collaborator

llvm-ci commented Mar 31, 2025

LLVM Buildbot has detected a new failure on builder llvm-clang-x86_64-darwin running on doug-worker-3 while building clang,llvm at step 6 "test-build-unified-tree-check-all".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/23/builds/8938

Here is the relevant piece of the build log for the reference
Step 6 (test-build-unified-tree-check-all) failure: test (failure)
******************** TEST 'Clang-Unit :: DirectoryWatcher/./DirectoryWatcherTests/5/8' FAILED ********************
Script(shard):
--
GTEST_OUTPUT=json:/Volumes/RAMDisk/buildbot-root/x86_64-darwin/build/tools/clang/unittests/DirectoryWatcher/./DirectoryWatcherTests-Clang-Unit-11964-5-8.json GTEST_SHUFFLE=0 GTEST_TOTAL_SHARDS=8 GTEST_SHARD_INDEX=5 /Volumes/RAMDisk/buildbot-root/x86_64-darwin/build/tools/clang/unittests/DirectoryWatcher/./DirectoryWatcherTests
--

Script:
--
/Volumes/RAMDisk/buildbot-root/x86_64-darwin/build/tools/clang/unittests/DirectoryWatcher/./DirectoryWatcherTests --gtest_filter=DirectoryWatcherTest.DeleteWatchedDir
--
/Users/buildbot/buildbot-root/x86_64-darwin/llvm-project/clang/unittests/DirectoryWatcher/DirectoryWatcherTest.cpp:259: Failure
Value of: WaitForExpectedStateResult.wait_for(EventualResultTimeout) == std::future_status::ready
  Actual: false
Expected: true
The expected result state wasn't reached before the time-out.

/Users/buildbot/buildbot-root/x86_64-darwin/llvm-project/clang/unittests/DirectoryWatcher/DirectoryWatcherTest.cpp:262: Failure
Value of: TestConsumer.result().has_value()
  Actual: false
Expected: true


/Users/buildbot/buildbot-root/x86_64-darwin/llvm-project/clang/unittests/DirectoryWatcher/DirectoryWatcherTest.cpp:259
Value of: WaitForExpectedStateResult.wait_for(EventualResultTimeout) == std::future_status::ready
  Actual: false
Expected: true
The expected result state wasn't reached before the time-out.

/Users/buildbot/buildbot-root/x86_64-darwin/llvm-project/clang/unittests/DirectoryWatcher/DirectoryWatcherTest.cpp:262
Value of: TestConsumer.result().has_value()
  Actual: false
Expected: true



********************


@jyknight
Copy link
Member

Wait, on by default?

Perhaps I'm out of line with current thinking here, but IMO, on-by-default should only diagnose things which are likely to be harmful -- NOT just a style or inefficiency issue, which seems to be all this is diagnosing. That LLVM's style conflicts with it also seems like a good indication that it should not be an on by default warning.

I'd actually say it belongs in -Wextra -- not even -Wall.

@AaronBallman
Copy link
Collaborator

AaronBallman commented Mar 31, 2025

Wait, on by default?

Perhaps I'm out of line with current thinking here, but IMO, on-by-default should only diagnose things which are likely to be harmful -- NOT just a style or inefficiency issue, which seems to be all this is diagnosing. That LLVM's style conflicts with it also seems like a good indication that it should not be an on by default warning.

I'd actually say it belongs in -Wextra -- not even -Wall.

I think this one is on the fence. Considering code like:

struct S final {
  // .. lots of code that hides the declaration of `struct S final`
  virtual void func(); // What was intended here?
};

This isn't harmful in the sense of finding a correctness issue because, presumably, something elsewhere tries to derive from that class and gets an error. And in the absence of a derived class, there's no semantic issues with the code.

However, it is still mildly harmful if the person writing this code misses the final on the class and commits the code to a library for others to use later. The person writing the code may have no idea they introduced the bug, the person consuming the code may have no easy way to fix the header, so alerting the programmer when they're writing the code that something isn't right can be useful.

I can't think of a situation where someone would write final on the class, virtual on the function, and think "yeah, this all makes sense." Even LLVM's uses seem highly suspicious to me. That said, I could see an argument for -Wextra being a more appropriate home for this and could support that.

@llvm-ci
Copy link
Collaborator

llvm-ci commented Mar 31, 2025

LLVM Buildbot has detected a new failure on builder clang-s390x-linux running on systemz-1 while building clang,llvm at step 5 "ninja check 1".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/42/builds/3933

Here is the relevant piece of the build log for the reference
Step 5 (ninja check 1) failure: stage 1 checked (failure)
******************** TEST 'libFuzzer-s390x-default-Linux :: fuzzer-timeout.test' FAILED ********************
Exit Code: 1

Command Output (stderr):
--
/home/uweigand/sandbox/buildbot/clang-s390x-linux/stage1/./bin/clang    -Wthread-safety -Wthread-safety-reference -Wthread-safety-beta   --driver-mode=g++ -O2 -gline-tables-only -fsanitize=address,fuzzer -I/home/uweigand/sandbox/buildbot/clang-s390x-linux/llvm/compiler-rt/lib/fuzzer  /home/uweigand/sandbox/buildbot/clang-s390x-linux/llvm/compiler-rt/test/fuzzer/TimeoutTest.cpp -o /home/uweigand/sandbox/buildbot/clang-s390x-linux/stage1/runtimes/runtimes-bins/compiler-rt/test/fuzzer/S390XDefaultLinuxConfig/Output/fuzzer-timeout.test.tmp-TimeoutTest # RUN: at line 1
+ /home/uweigand/sandbox/buildbot/clang-s390x-linux/stage1/./bin/clang -Wthread-safety -Wthread-safety-reference -Wthread-safety-beta --driver-mode=g++ -O2 -gline-tables-only -fsanitize=address,fuzzer -I/home/uweigand/sandbox/buildbot/clang-s390x-linux/llvm/compiler-rt/lib/fuzzer /home/uweigand/sandbox/buildbot/clang-s390x-linux/llvm/compiler-rt/test/fuzzer/TimeoutTest.cpp -o /home/uweigand/sandbox/buildbot/clang-s390x-linux/stage1/runtimes/runtimes-bins/compiler-rt/test/fuzzer/S390XDefaultLinuxConfig/Output/fuzzer-timeout.test.tmp-TimeoutTest
/home/uweigand/sandbox/buildbot/clang-s390x-linux/stage1/./bin/clang    -Wthread-safety -Wthread-safety-reference -Wthread-safety-beta   --driver-mode=g++ -O2 -gline-tables-only -fsanitize=address,fuzzer -I/home/uweigand/sandbox/buildbot/clang-s390x-linux/llvm/compiler-rt/lib/fuzzer  /home/uweigand/sandbox/buildbot/clang-s390x-linux/llvm/compiler-rt/test/fuzzer/TimeoutEmptyTest.cpp -o /home/uweigand/sandbox/buildbot/clang-s390x-linux/stage1/runtimes/runtimes-bins/compiler-rt/test/fuzzer/S390XDefaultLinuxConfig/Output/fuzzer-timeout.test.tmp-TimeoutEmptyTest # RUN: at line 2
+ /home/uweigand/sandbox/buildbot/clang-s390x-linux/stage1/./bin/clang -Wthread-safety -Wthread-safety-reference -Wthread-safety-beta --driver-mode=g++ -O2 -gline-tables-only -fsanitize=address,fuzzer -I/home/uweigand/sandbox/buildbot/clang-s390x-linux/llvm/compiler-rt/lib/fuzzer /home/uweigand/sandbox/buildbot/clang-s390x-linux/llvm/compiler-rt/test/fuzzer/TimeoutEmptyTest.cpp -o /home/uweigand/sandbox/buildbot/clang-s390x-linux/stage1/runtimes/runtimes-bins/compiler-rt/test/fuzzer/S390XDefaultLinuxConfig/Output/fuzzer-timeout.test.tmp-TimeoutEmptyTest
not  /home/uweigand/sandbox/buildbot/clang-s390x-linux/stage1/runtimes/runtimes-bins/compiler-rt/test/fuzzer/S390XDefaultLinuxConfig/Output/fuzzer-timeout.test.tmp-TimeoutTest -timeout=1 2>&1 | FileCheck /home/uweigand/sandbox/buildbot/clang-s390x-linux/llvm/compiler-rt/test/fuzzer/fuzzer-timeout.test --check-prefix=TimeoutTest # RUN: at line 3
+ not /home/uweigand/sandbox/buildbot/clang-s390x-linux/stage1/runtimes/runtimes-bins/compiler-rt/test/fuzzer/S390XDefaultLinuxConfig/Output/fuzzer-timeout.test.tmp-TimeoutTest -timeout=1
+ FileCheck /home/uweigand/sandbox/buildbot/clang-s390x-linux/llvm/compiler-rt/test/fuzzer/fuzzer-timeout.test --check-prefix=TimeoutTest
not  /home/uweigand/sandbox/buildbot/clang-s390x-linux/stage1/runtimes/runtimes-bins/compiler-rt/test/fuzzer/S390XDefaultLinuxConfig/Output/fuzzer-timeout.test.tmp-TimeoutTest -timeout=1 /home/uweigand/sandbox/buildbot/clang-s390x-linux/llvm/compiler-rt/test/fuzzer/hi.txt 2>&1 | FileCheck /home/uweigand/sandbox/buildbot/clang-s390x-linux/llvm/compiler-rt/test/fuzzer/fuzzer-timeout.test --check-prefix=SingleInputTimeoutTest # RUN: at line 12
+ not /home/uweigand/sandbox/buildbot/clang-s390x-linux/stage1/runtimes/runtimes-bins/compiler-rt/test/fuzzer/S390XDefaultLinuxConfig/Output/fuzzer-timeout.test.tmp-TimeoutTest -timeout=1 /home/uweigand/sandbox/buildbot/clang-s390x-linux/llvm/compiler-rt/test/fuzzer/hi.txt
+ FileCheck /home/uweigand/sandbox/buildbot/clang-s390x-linux/llvm/compiler-rt/test/fuzzer/fuzzer-timeout.test --check-prefix=SingleInputTimeoutTest
/home/uweigand/sandbox/buildbot/clang-s390x-linux/stage1/runtimes/runtimes-bins/compiler-rt/test/fuzzer/S390XDefaultLinuxConfig/Output/fuzzer-timeout.test.tmp-TimeoutTest -timeout=1 -timeout_exitcode=0 # RUN: at line 16
+ /home/uweigand/sandbox/buildbot/clang-s390x-linux/stage1/runtimes/runtimes-bins/compiler-rt/test/fuzzer/S390XDefaultLinuxConfig/Output/fuzzer-timeout.test.tmp-TimeoutTest -timeout=1 -timeout_exitcode=0
INFO: Running with entropic power schedule (0xFF, 100).
INFO: Seed: 3845018601
INFO: Loaded 1 modules   (13 inline 8-bit counters): 13 [0x2aa1be50e88, 0x2aa1be50e95), 
INFO: Loaded 1 PC tables (13 PCs): 13 [0x2aa1be50e98,0x2aa1be50f68), 
INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes
INFO: A corpus is not provided, starting from an empty corpus
#2	INITED cov: 2 ft: 2 corp: 1/1b exec/s: 0 rss: 31Mb
#889	NEW    cov: 3 ft: 3 corp: 2/2b lim: 11 exec/s: 0 rss: 32Mb L: 1/1 MS: 2 ChangeByte-ChangeBit-
#920	NEW    cov: 4 ft: 4 corp: 3/4b lim: 11 exec/s: 0 rss: 32Mb L: 2/2 MS: 1 CopyPart-
#5157	NEW    cov: 5 ft: 5 corp: 4/6b lim: 48 exec/s: 0 rss: 32Mb L: 2/2 MS: 2 CopyPart-ChangeByte-
#5213	NEW    cov: 6 ft: 6 corp: 5/30b lim: 48 exec/s: 0 rss: 32Mb L: 24/24 MS: 1 InsertRepeatedBytes-
#5216	REDUCE cov: 6 ft: 6 corp: 5/27b lim: 48 exec/s: 0 rss: 32Mb L: 21/21 MS: 3 InsertRepeatedBytes-CMP-EraseBytes- DE: "\000\000\000\000\000\000\000/"-
#5252	REDUCE cov: 6 ft: 6 corp: 5/26b lim: 48 exec/s: 0 rss: 32Mb L: 20/20 MS: 1 EraseBytes-
#5275	REDUCE cov: 6 ft: 6 corp: 5/18b lim: 48 exec/s: 0 rss: 32Mb L: 12/12 MS: 3 ChangeBit-ChangeByte-EraseBytes-
#5419	REDUCE cov: 6 ft: 6 corp: 5/16b lim: 48 exec/s: 0 rss: 32Mb L: 10/10 MS: 4 ShuffleBytes-CrossOver-ChangeBit-EraseBytes-
#5665	REDUCE cov: 6 ft: 6 corp: 5/12b lim: 48 exec/s: 0 rss: 32Mb L: 6/6 MS: 1 EraseBytes-
#5811	REDUCE cov: 6 ft: 6 corp: 5/9b lim: 48 exec/s: 0 rss: 32Mb L: 3/3 MS: 1 EraseBytes-
ALARM: working on the last Unit for 1 seconds
       and the timeout value is 1 (use -timeout=N to change)
MS: 2 ChangeBit-ChangeBit-; base unit: d0c024ef2b7b748a5af917ab7fd801b15d318c88
0x48,0x69,0x21,
Hi!
artifact_prefix='./'; Test unit written to ./timeout-c0a0ad26a634840c67a210fefdda76577b03a111
Base64: SGkh
==1840255== ERROR: libFuzzer: timeout after 1 seconds
AddressSanitizer:DEADLYSIGNAL
=================================================================
AddressSanitizer:DEADLYSIGNAL
=================================================================
AddressSanitizer: CHECK failed: asan_report.cpp:199 "((current_error_.kind)) == ((kErrorKindInvalid))" (0x1, 0x0) (tid=1840255)
    <empty stack>

MS: 2 ChangeBit-ChangeBit-; base unit: d0c024ef2b7b748a5af917ab7fd801b15d318c88
...

@jyknight
Copy link
Member

LLVM's intentional usage is either to reduce redundant vtable emission via ensuring there is a key method, OR, to intentionally create a useless vtable, in order to reduce redundant debuginfo emission (it can be emitted only in the vtable's TU when there's a vtable.) I expect both are likely to be rather unusual.

I'd say it's highly likely that most code where this diagnostic triggers is simply because "virtual" was added to a function which simply doesn't need it, and the solution is to remove "virtual". This diagnostic can definitely be useful -- it likely indicates an issue in the code. I just don't think the issue it identifies is that important as to warrant being on by default, such that we'd emit this even on a project with has not enabled any warning flags.

E.g. as comparison, is this really more important than -Wall diagnostics like "-Wdelete-non-virtual-dtor" ("warning: delete called on non-final that has virtual functions but non-virtual destructor"), for example? I'd say definitely not.

@DKLoehr
Copy link
Contributor Author

DKLoehr commented Apr 1, 2025

To give my point of view, I judge "on-by-default worthiness" roughly by a combination of the false-positive rate and the difficulty of silencing the warning when false positives occur. I'm defining false positives as "a warning instance which the code author thinks isn't worth fixing". In other words, how much effort are we imposing on people by forcing them to deal with this warning?

In this case, what I've seen is that the warning catches real issues in most codebases, and it does so without any false positives that I noticed. Fixing instances of the warning is trivial (just remove "virtual"), and can result in minor but real code improvements.

The only exception I've seen is LLVM, where pretty much all instances of the warning were false positives. However, since that's due to a project-wide policy, the right solution is to simply disable the warning as not-applicable.

The problematic case IMO is where false positives are mixed with real positives, so you'd like to enable the warning but can't do it without convoluting your code to suppress FPs. I haven't seen that here.

tl;dr: Yes, this warning provides only a small benefit, but the cost (to users) is also small, so I think it makes sense for it to be enabled by default.

philnik777 added a commit to philnik777/llvm-project that referenced this pull request Apr 2, 2025
philnik777 added a commit that referenced this pull request Apr 2, 2025
philnik777 added a commit that referenced this pull request Apr 2, 2025
Reverts #133265

This causes the whole libc++ CI to fail, since we're not building
against a compiler built from current trunk. Specifically, the CMake
changes causes some feature detection to fail, resulting in CMake
being unable to configure libc++.
llvm-sync bot pushed a commit to arm/arm-toolchain that referenced this pull request Apr 2, 2025
…#134105)

Reverts llvm/llvm-project#133265

This causes the whole libc++ CI to fail, since we're not building
against a compiler built from current trunk. Specifically, the CMake
changes causes some feature detection to fail, resulting in CMake
being unable to configure libc++.
@jyknight
Copy link
Member

jyknight commented Apr 2, 2025

I view on-by-default warnings as being something we should only add when the code is possibly dangerous. That is, if you don't at least enable -Wall, you're effectively saying "don't bother me about minor issues with my code, I'm not interested in changing it" (e.g., could be third-party code which you don't care to fix unless it's outright broken).

@philnik777
Copy link
Contributor

@DKLoehr @AaronBallman Did you see the revert?

@AaronBallman
Copy link
Collaborator

@DKLoehr @AaronBallman Did you see the revert?

Thank you for the ping, I did not see the revert. Thanks for catching that and sorry for the disruption!

Given that this disrupts both LLVM and libc++, I think that's plenty of signal that "on by default" isn't really the best approach here. I think moving it to -Wextra may be a more palatable approach. What do others think?

@zmodem
Copy link
Collaborator

zmodem commented Apr 7, 2025

I think moving it to -Wextra may be a more palatable approach. What do others think?

Sounds good to me, and I think James makes a good argument.

Does that actually help LLVM and libc++ though? I think at least LLVM does enable -Wextra.

zmodem pushed a commit that referenced this pull request May 7, 2025
Effectively a reland of #133265, though due to discussion there we add
the warning to -Wextra instead of turning it on by default. We still
need to disable it for LLVM due to our unusual policy of using virtual
`anchor` functions even in final classes. We now check if the warning
exists before disabling it in LLVM builds, so hopefully this will fix
the issues libcxx ran into last time.

From the previous PR:

I've been working on cleaning up this warning in two codebases: LLVM and
chromium (plus its dependencies). The chromium + dependency cleanup has
been straightforward. Git archaeology shows that there are two reasons
for the warnings: classes to which `final` was added after they were
initially committed, and classes with virtual destructors that nobody
remarks on. Presumably the latter case is because people are just very
used to destructors being virtual.

The LLVM cleanup was more surprising: I discovered that we have an [old
policy](https://llvm.org/docs/CodingStandards.html#provide-a-virtual-method-anchor-for-classes-in-headers)
about including out-of-line virtual functions in every class with a
vtable, even `final` ones. This means our codebase has many virtual
"anchor" functions which do nothing except control where the vtable is
emitted, and which trigger the warning. I looked into alternatives to
satisfy the policy, such as using destructors instead of introducing a
new function, but it wasn't clear if they had larger implications.

Overall, it seems like the warning is genuinely useful in most codebases
(evidenced by chromium and its dependencies), and LLVM is an unusual
case. Therefore we should enable the warning by default, and turn it off
only for LLVM builds.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:static analyzer clang Clang issues not falling into any other category cmake Build system in general and CMake in particular
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants