Confusion about running clang tests

I am trying to implement something in clang and am investigating test cases. This test for example, starts with:

// RUN: %clang_cc1 -fsyntax-only -verify %s -std=c++11

// Implicitly-defined default constructors are constexpr if the implicit
// definition would be.
struct NonConstexpr1 { // expected-note {{here}}
  int a;
};
struct NonConstexpr2 { // expected-note {{here}}
  NonConstexpr1 nl;
};
struct NonConstexpr2a : NonConstexpr1 { };
constexpr NonConstexpr1 nc1 = NonConstexpr1(); // ok, does not call constructor
constexpr NonConstexpr2 nc2 = NonConstexpr2(); // ok, does not call constructor
constexpr NonConstexpr2a nc2a = NonConstexpr2a(); // ok, does not call constructor
constexpr int nc2_a = NonConstexpr2().nl.a; // ok
constexpr int nc2a_a = NonConstexpr2a().a; // ok
struct Helper {
  friend constexpr NonConstexpr1::NonConstexpr1(); // expected-error {{follows non-constexpr declaration}}
  friend constexpr NonConstexpr2::NonConstexpr2(); // expected-error {{follows non-constexpr declaration}}
};

if I run that test through llvm-lit, it passes:

$ ./bin/llvm-lit -sv ../clang/test/CXX/special/class.ctor/p6-0x.cpp 
llvm-lit: /home/brevzin/sandbox/llvm-project/llvm/utils/lit/lit/llvm/config.py:520: note: using clang: /home/brevzin/sandbox/llvm-project/build/bin/clang
llvm-lit: /home/brevzin/sandbox/llvm-project/llvm/utils/lit/lit/llvm/subst.py:126: note: Did not find cir-opt in /home/brevzin/sandbox/llvm-project/build/bin:/home/brevzin/sandbox/llvm-project/build/bin

Testing Time: 0.04s

Total Discovered Tests: 1
  Passed: 1 (100.00%)

If I directly attempt to compile the part of this test though, I get a different error on those lines:

$ ./bin/clang++ -fsyntax-only -std=c++11 ex.cxx 
ex.cxx:14:35: error: 'NonConstexpr1' is missing exception specification 'noexcept'
   14 |   friend constexpr NonConstexpr1::NonConstexpr1(); // expected-error {{follows non-constexpr declaration}}
      |                                   ^              
      |                                                   noexcept
ex.cxx:1:8: note: previous declaration is here
    1 | struct NonConstexpr1 { // expected-note {{here}}
      |        ^
ex.cxx:15:35: error: 'NonConstexpr2' is missing exception specification 'noexcept'
   15 |   friend constexpr NonConstexpr2::NonConstexpr2(); // expected-error {{follows non-constexpr declaration}}
      |                                   ^              
      |                                                   noexcept
ex.cxx:4:8: note: previous declaration is here
    4 | struct NonConstexpr2 { // expected-note {{here}}
      |        ^
2 errors generated.

And then there are other differences depending on how I test this file:

  • ./bin/clang -cc1 -verify -std=c++11 ex.cxx passes
  • ./bin/clang -cc1 -verify -std=c++11 ex.cxx -fcxx-exceptions fails
  • ./bin/clang++ -Xclang -verify -std=c++11 ex.cxx fails

So I’m confused as to what’s actually going on. How is it that the test case passes?

The -verify flag enables the diagnostic consumer to catch expected errors. It parses expected-{error,warning,note} and if it matches the expected diagnostic it considers the test to pass.

I’d be interested in seeing the reason why it fails.

EDIT: oh and ./bin/clang++ turns on exceptions – you can check the actual full set of flags by adding a -v.

When executing clang, you’re running the compiler driver. The job of the driver is to come up with a command line to pass to the clang frontend which does the actual compilation, via a clang -cc1 invocation. So when you run clang -cc1 (which is what happens with %clang_cc1 in the lit test), you’re getting a compilation that uses only the passed command line options. But when you run clang without -cc1, the driver invents a ton of parameters to pass on your behalf. You can see this in action by using -### when executing the driver: Compiler Explorer

So clang -cc1 foo.cpp will not have exceptions enabled, while clang++ foo.cpp will as will clang -cc1 -fcxx-exceptions foo.cpp, which may account for the differences you’re seeing.

1 Like

So in this case if I just, on main (i.e. I haven’t broken anything I swear), add this line to that test case:

  // RUN: %clang_cc1 -fsyntax-only -verify %s -std=c++11
+ // RUN: %clang_cc1 -fsyntax-only -verify %s -std=c++26

The test starts failing in that spot as well (complaining about missing noexcept). I ran into this because the changes I’m making are union-related and don’t affect that line at all, but I wanted to correctly test a union example that occurs later in the file.

So that

$ ./bin/clang -cc1 -verify -std=c++11 ex.cxx # passes
$ ./bin/clang -cc1 -verify -std=c++26 ex.cxx # fails (missing exception specification)

Is it that something about -std=c++26 enables exceptions?

I’ve not had the chance to look at this under a debugger, but I did some psychic debugging and found: llvm-project/clang/lib/Sema/SemaExceptionSpec.cpp at ac76e4d8a9aedc23123571b3801684babb78424f · llvm/llvm-project · GitHub

I suspect that’s what’s causing the difference in behavior here; the test is normally run in C++11 so we’d early return. I verified that using -std=c++20 gives the same diagnostic behavior.

1 Like

Ah! Yes that makes perfect sense.

So yeah, the initial flag situation confused me so much that it didn’t even occur to me that … other things changed between C++11 and C++26 that are relevant here and that there would actually be a real difference in this example.

Thanks!

2 Likes

Aaron, you got a laugh out of me with that one :)