Skip to content

[analyzer] Static Analysis runs out of memory on a tiny test-case #143440

Open
@wjristow

Description

@wjristow

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);
  }
}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions