Description
The test-case shown below demonstrates a problem with Static Analysis running out of memory. This is reduced from a large C++20 test-case reported to us, although the reduced test-case here is valid C++17 (or C++20) code.
I've tested with a modern compiler from main
: 5d6218d (llvmorg-21-init-14792-g5d6218d31185).
Setting a 60GB virtual memory limit:
$ ulimit -v 60000000
$
shows the problem:
$ clang++ --version | grep ^clang
clang version 21.0.0git (https://github.com/llvm/llvm-project.git 5d6218d311854a0b5d48ae19636f6abe1e67fc69)
$ time clang++ --analyze -std=c++17 test.cpp
LLVM ERROR: out of memory
Allocation failed
PLEASE submit a bug report to https://github.com/llvm/llvm-project/issues/ and include the crash backtrace, preprocessed source, and associated run script.
Stack dump:
0. Program arguments: clang++ --analyze -std=c++17 test.cpp
1. <eof> parser at end of file
2. While analyzing stack:
#0 Calling Class3::operator=(const Class3 &) at line 77
#1 Calling Class4::Struct8::operator=(const Struct8 &) at line 89
#2 Calling Class4::Struct8::m_f2(const Struct8 &) at line 90
#3 Calling Class4::Struct10::m_f3(const Struct10 &) at line 101
#4 Calling Class4::m_f4()
3. test.cpp:56:7: Error evaluating statement
#0 0x000055b69b1027bf llvm::sys::PrintStackTrace(llvm::raw_ostream&, int) (/home/warren/llvm/current/bin/clang+++0x47647bf)
#1 0x000055b69b1002b4 llvm::sys::CleanupOnSignal(unsigned long) (/home/warren/llvm/current/bin/clang+++0x47622b4)
#2 0x000055b69b03f538 CrashRecoverySignalHandler(int) CrashRecoveryContext.cpp:0:0
#3 0x00007fccefa11420 __restore_rt (/lib/x86_64-linux-gnu/libpthread.so.0+0x14420)
...
#35 0x000055b697df4672 clang_main(int, char**, llvm::ToolContext const&) (/home/warren/llvm/current/bin/clang+++0x1456672)
#36 0x000055b697c93e9b main (/home/warren/llvm/current/bin/clang+++0x12f5e9b)
#37 0x00007fccef4dd083 __libc_start_main /build/glibc-B3wQXB/glibc-2.31/csu/../csu/libc-start.c:342:3
#38 0x000055b697dee72e _start (/home/warren/llvm/current/bin/clang+++0x145072e)
clang++: error: clang frontend command failed with exit code 134 (use -v to see invocation)
clang version 21.0.0git (https://github.com/llvm/llvm-project.git 5d6218d311854a0b5d48ae19636f6abe1e67fc69)
Target: x86_64-unknown-linux-gnu
Thread model: posix
InstalledDir: /home/warren/llvm/current/bin
Build config: +assertions
clang++: note: diagnostic msg:
********************
PLEASE ATTACH THE FOLLOWING FILES TO THE BUG REPORT:
Preprocessed source(s) and associated run script(s) are located at:
clang++: note: diagnostic msg: /tmp/test-59716f.cpp
clang++: note: diagnostic msg: /tmp/test-59716f.sh
clang++: note: diagnostic msg:
********************
1m11.19s real 0m52.79s user 0m17.48s system
$
As noted in the comments in the test-case, suppressing various (unused) members results in the static analysis completing using about 4 GB. Suppressing one particular unused member makes it complete essentially immediately (not using much memory at all):
$ time clang++ --analyze -std=c++17 -DUSE_4GB test.cpp
0m57.16s real 0m54.30s user 0m02.84s system
$ time clang++ --analyze -std=c++17 -DAVOID_PROBLEM test.cpp
0m00.16s real 0m00.02s user 0m00.03s system
$
I've bisected when this issue for this reduced test-case appeared to 6194229 (llvmorg-16-init-8141-g6194229c6287).
Pinging @tomasz-kaminski-sonarsource
I've verified that if I revert that commit in main
(and also the immediately preceding commit, which that commit depends on), then my reduced test-case here does work fine. But the original full test-case still runs out of memory with that llvm16-era work reverted from the head of main
. (I can't easily try the original full test-case with a compiler from that llvm16-era, because that test-case is C++20 code, and it uses C++20 constructs that weren't supported by Clang back then.) And of course I'm not suggesting that reverting that work is the right thing to do -- I'm just pointing this out for reference.
In any case, here is the reduced test-case:
// When running the Static Analyzer on this test-case, in C++17 mode, it runs
// out of memory in a few minutes:
// clang++ --analyze -std=c++17 test.cpp
// As an aside, in C++20 mode, it takes a very long time (more than an hour),
// and uses a massive amount of memory (around 55GB), but it does eventually
// complete.
//
// If any of the lines declaring `x1`. `x2`. `x3`, ... `x9` are commented out,
// then the `--analyze` run completes. For most of them, the static analysis
// run uses about 4 GB of memory, and takes a couple minutes, Use the switch:
// -DUSE_4GB
// to see that behavior. For `x9`, if that one is suppressed, the problem
// disappears completely (very fast run, without using massive memory). Use:
// -DAVOID_PROBLEM
// to see that behavior.
// Note that eliminating many other items also makes the `--analyze` run
// complete. But these x1, x2, ..x9 are somewhat interesting in that they are
// private members of classes that are unused (and so warnings happen with
// `-Wall`).
struct Struct1 {};
template <bool, class, class> using StructName = Struct1;
template <class T> class Arr {
T Arr[1];
};
template <auto BITS> class Bits {
StructName<BITS, unsigned, unsigned> TheBits;
};
enum EnumA : int;
class Class1 {
public:
struct Struct2 {
unsigned m_f1();
};
};
enum EnumB : unsigned;
struct Struct3 {
unsigned m_1;
unsigned m_2;
unsigned m_3;
};
struct Struct4 {
unsigned m_1;
unsigned m_2;
unsigned m_3;
};
struct Struct5 {
Arr<EnumB> m_ArrEnumB;
};
struct Struct6 {
Arr<char> m_CharArr;
};
class Class2 {
public:
void operator=(Class2 const &);
};
class Class3 {
enum { SomeEnum };
Bits<SomeEnum> x1;
Struct3 x2;
#if !defined(USE_4GB)
Struct4 x3;
#endif
Struct4 x4;
Struct5 x5;
Struct6 x6;
Arr<EnumA> x7;
Arr<EnumA> x8;
#if !defined(AVOID_PROBLEM)
// `--analyze` finishes quickly if this is commented out.
Arr<Class2> x9;
#endif
};
class Class4 : Class1 {
struct Struct7 {
Struct2 m_S2;
};
struct Struct8 {
Class3 m_C3;
void m_f2(const Struct8 &);
};
struct Struct9 {
Struct8 m_S8;
};
struct Struct10 : Struct9 {
void m_f3(const Struct10 &);
} m_S10Arr[];
int m_f4();
};
void Class4::Struct8::m_f2(const Struct8 &s8_ref) { *this = s8_ref; }
void Class4::Struct10::m_f3(const Struct10 &s10_ref) { m_S8.m_f2(s10_ref.m_S8); }
long glb_l;
int Class4::m_f4() {
Struct10 *s10_p1;
Struct10 *s10_p2;
Struct7 *c = (Struct7 *)glb_l;
int i = c->m_S2.m_f1(), j = i;
s10_p2 = &m_S10Arr[j];
for (;;) {
j = 0;
s10_p1 = &m_S10Arr[j];
s10_p2->m_f3(*s10_p1);
}
}