From c09306ffd894f95be195723327d5b17dca73ebd1 Mon Sep 17 00:00:00 2001 From: Felix Rath Date: Fri, 22 May 2020 14:09:10 +0200 Subject: Implemented support for C++ Exceptions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We implement the Itanium ABI unwinding base-API, and leave the C++-specific parts to libcxxabi. Co-authored-by: Lukas Wölfer --- test/CXX/symex/basic_c++/diamond.cpp | 45 +++++++++++ test/CXX/symex/basic_c++/inheritance.cpp | 35 +++++++++ test/CXX/symex/basic_c++/lambda.cpp | 20 +++++ test/CXX/symex/basic_c++/namespace.cpp | 18 +++++ test/CXX/symex/basic_c++/reinterpret_cast.cpp | 26 +++++++ test/CXX/symex/basic_c++/simple.cpp | 33 ++++++++ test/CXX/symex/basic_c++/templates.cpp | 28 +++++++ test/CXX/symex/basic_c++/virtual.cpp | 35 +++++++++ test/CXX/symex/libc++/atexit.cpp | 19 ++--- test/CXX/symex/libc++/can_catch_test.cpp | 88 ++++++++++++++++++++++ test/CXX/symex/libc++/catch_recover.cpp | 26 +++++++ .../catch_with_adjusted_exception_pointer.cpp | 37 +++++++++ test/CXX/symex/libc++/cout.cpp | 4 +- test/CXX/symex/libc++/cout_sym.cpp | 16 ++-- test/CXX/symex/libc++/dynamic_cast.cpp | 56 +++++++------- test/CXX/symex/libc++/exception.cpp | 33 ++++++++ test/CXX/symex/libc++/exception_inheritance.cpp | 78 +++++++++++++++++++ test/CXX/symex/libc++/general_catch.cpp | 60 +++++++++++++++ test/CXX/symex/libc++/landingpad.cpp | 53 +++++++++++++ test/CXX/symex/libc++/multi_throw.cpp | 47 ++++++++++++ test/CXX/symex/libc++/multi_unwind.cpp | 70 +++++++++++++++++ test/CXX/symex/libc++/nested.cpp | 26 +++++++ test/CXX/symex/libc++/nested_fail.cpp | 26 +++++++ test/CXX/symex/libc++/rethrow.cpp | 35 +++++++++ test/CXX/symex/libc++/simple_exception.cpp | 19 +++++ test/CXX/symex/libc++/simple_exception_fail.cpp | 13 ++++ test/CXX/symex/libc++/symbolic_exception.cpp | 29 +++++++ test/CXX/symex/libc++/throw_specifiers.cpp | 35 +++++++++ .../symex/libc++/throwing_exception_destructor.cpp | 45 +++++++++++ test/CXX/symex/libc++/uncaught_exception.cpp | 13 ++++ test/CXX/symex/libc++/vector.cpp | 5 +- 31 files changed, 1024 insertions(+), 49 deletions(-) create mode 100644 test/CXX/symex/basic_c++/diamond.cpp create mode 100644 test/CXX/symex/basic_c++/inheritance.cpp create mode 100644 test/CXX/symex/basic_c++/lambda.cpp create mode 100644 test/CXX/symex/basic_c++/namespace.cpp create mode 100644 test/CXX/symex/basic_c++/reinterpret_cast.cpp create mode 100644 test/CXX/symex/basic_c++/simple.cpp create mode 100644 test/CXX/symex/basic_c++/templates.cpp create mode 100644 test/CXX/symex/basic_c++/virtual.cpp create mode 100644 test/CXX/symex/libc++/can_catch_test.cpp create mode 100644 test/CXX/symex/libc++/catch_recover.cpp create mode 100644 test/CXX/symex/libc++/catch_with_adjusted_exception_pointer.cpp create mode 100644 test/CXX/symex/libc++/exception.cpp create mode 100644 test/CXX/symex/libc++/exception_inheritance.cpp create mode 100644 test/CXX/symex/libc++/general_catch.cpp create mode 100644 test/CXX/symex/libc++/landingpad.cpp create mode 100644 test/CXX/symex/libc++/multi_throw.cpp create mode 100644 test/CXX/symex/libc++/multi_unwind.cpp create mode 100644 test/CXX/symex/libc++/nested.cpp create mode 100644 test/CXX/symex/libc++/nested_fail.cpp create mode 100644 test/CXX/symex/libc++/rethrow.cpp create mode 100644 test/CXX/symex/libc++/simple_exception.cpp create mode 100644 test/CXX/symex/libc++/simple_exception_fail.cpp create mode 100644 test/CXX/symex/libc++/symbolic_exception.cpp create mode 100644 test/CXX/symex/libc++/throw_specifiers.cpp create mode 100644 test/CXX/symex/libc++/throwing_exception_destructor.cpp create mode 100644 test/CXX/symex/libc++/uncaught_exception.cpp (limited to 'test/CXX') diff --git a/test/CXX/symex/basic_c++/diamond.cpp b/test/CXX/symex/basic_c++/diamond.cpp new file mode 100644 index 00000000..479a9090 --- /dev/null +++ b/test/CXX/symex/basic_c++/diamond.cpp @@ -0,0 +1,45 @@ +// REQUIRES: uclibc +// RUN: %clangxx %s -emit-llvm %O0opt -c -o %t.bc +// RUN: rm -rf %t.klee-out +// RUN: %klee --output-dir=%t.klee-out --libc=uclibc %t.bc 2>&1 | FileCheck %s + +#include + +struct A { + int a; + A() { + printf("A constructor\n"); + } +}; + +struct B : virtual A { + int b; + B() { + printf("B constructor\n"); + } +}; + +struct C : virtual A { + int c; + C() { + printf("C constructor\n"); + } +}; + +struct D : B, C { + int d; +}; + +int main(int argc, char **args) { + D x; + // CHECK: A constructor + // CHECK: B constructor + // CHECK: C constructor + + x.a = 7; + B *y = &x; + printf("B::a = %d\n", y->a); + // CHECK: B::a = 7 + + return 0; +} diff --git a/test/CXX/symex/basic_c++/inheritance.cpp b/test/CXX/symex/basic_c++/inheritance.cpp new file mode 100644 index 00000000..88a0d150 --- /dev/null +++ b/test/CXX/symex/basic_c++/inheritance.cpp @@ -0,0 +1,35 @@ +// REQUIRES: uclibc +// RUN: %clangxx %s -emit-llvm %O0opt -c -o %t.bc +// RUN: rm -rf %t.klee-out +// RUN: %klee --output-dir=%t.klee-out --libc=uclibc %t.bc 2>&1 | FileCheck %s + +#include +class Testclass { +public: + Testclass() { + printf("Testclass constructor\n"); + } + int a() { + return 1; + } +}; +class Derivedclass : public Testclass { +public: + Derivedclass() { + printf("Derivedclass constructor\n"); + } + int b() { + return 2; + } +}; + +int main(int argc, char **args) { + Derivedclass x; + // CHECK: Testclass constructor + // CHECK: Derivedclass constructor + + printf("%d / %d", x.a(), x.b()); + // CHECK: 1 / 2 + + return x.a() + x.b(); +} diff --git a/test/CXX/symex/basic_c++/lambda.cpp b/test/CXX/symex/basic_c++/lambda.cpp new file mode 100644 index 00000000..e7f8f971 --- /dev/null +++ b/test/CXX/symex/basic_c++/lambda.cpp @@ -0,0 +1,20 @@ +// REQUIRES: uclibc +// RUN: %clangxx %s -emit-llvm %O0opt -c -std=c++11 -o %t.bc +// RUN: rm -rf %t.klee-out +// RUN: %klee --output-dir=%t.klee-out --libc=uclibc %t.bc 2>&1 | FileCheck %s + +#include + +int main(int argc, char **args) { + int x = 0; + auto y = [&](int h) { return x + h; }; + auto z = [=](int h) { return x + h; }; + x = 1; + + printf("y(0) == %d\n", y(0)); + // CHECK: y(0) == 1 + printf("z(0) == %d\n", z(0)); + // CHECK: z(0) == 0 + + return 0; +} diff --git a/test/CXX/symex/basic_c++/namespace.cpp b/test/CXX/symex/basic_c++/namespace.cpp new file mode 100644 index 00000000..939e9bcd --- /dev/null +++ b/test/CXX/symex/basic_c++/namespace.cpp @@ -0,0 +1,18 @@ +// REQUIRES: uclibc +// RUN: %clangxx %s -emit-llvm %O0opt -c -o %t.bc +// RUN: rm -rf %t.klee-out +// RUN: %klee --output-dir=%t.klee-out --libc=uclibc %t.bc 2>&1 | FileCheck %s + +#include + +namespace test { +int a() { + return 2; +} +} // namespace test + +int main(int argc, char **args) { + printf("test::a() == %d\n", test::a()); + //CHECK: test::a() == 2 + return 0; +} diff --git a/test/CXX/symex/basic_c++/reinterpret_cast.cpp b/test/CXX/symex/basic_c++/reinterpret_cast.cpp new file mode 100644 index 00000000..6883226b --- /dev/null +++ b/test/CXX/symex/basic_c++/reinterpret_cast.cpp @@ -0,0 +1,26 @@ +// REQUIRES: uclibc +// RUN: %clangxx %s -emit-llvm %O0opt -c -o %t.bc +// RUN: rm -rf %t.klee-out +// RUN: %klee --output-dir=%t.klee-out --libc=uclibc %t.bc 2>&1 | FileCheck %s + +#include + +int main(int argc, char **args) { + unsigned int *x = new unsigned int(0); + char *y = reinterpret_cast(x); + + printf("address(x) == %p\n", x); + printf("address(y) == %p\n", y); + // CHECK: address(x) == [[POINTER_X:0x[a-fA-F0-9]+]] + // CHECK: address(y) == [[POINTER_X]] + + printf("first x == 0x%08X\n", *x); + // CHECK: first x == 0x00000000 + y[3] = 0xAB; + y[1] = 0x78; + printf("second x == 0x%08X\n", *x); + // CHECK: second x == 0xAB007800 + + delete x; + return 0; +} diff --git a/test/CXX/symex/basic_c++/simple.cpp b/test/CXX/symex/basic_c++/simple.cpp new file mode 100644 index 00000000..f66dbd1e --- /dev/null +++ b/test/CXX/symex/basic_c++/simple.cpp @@ -0,0 +1,33 @@ +// REQUIRES: uclibc +// RUN: %clangxx %s -emit-llvm %O0opt -c -o %t.bc +// RUN: rm -rf %t.klee-out +// RUN: %klee --output-dir=%t.klee-out --libc=uclibc %t.bc 2>&1 | FileCheck %s + +// CHECK: KLEE: done: completed paths = 5 + +#include "klee/klee.h" + +class Test { + int x; + +public: + Test(int _x) : x(_x) {} + + int test() { + if (x % 2 == 0) + return 0; + if (x % 3 == 0) + return 1; + if (x % 5 == 0) + return 2; + if (x % 7 == 0) + return 3; + return 4; + } +}; + +int main(int argc, char **args) { + Test a(klee_int("Test")); + + return a.test(); +} diff --git a/test/CXX/symex/basic_c++/templates.cpp b/test/CXX/symex/basic_c++/templates.cpp new file mode 100644 index 00000000..c5abb983 --- /dev/null +++ b/test/CXX/symex/basic_c++/templates.cpp @@ -0,0 +1,28 @@ +// REQUIRES: uclibc +// RUN: %clangxx %s -emit-llvm %O0opt -c -o %t.bc +// RUN: rm -rf %t.klee-out +// RUN: %klee --output-dir=%t.klee-out --libc=uclibc %t.bc 2>&1 + +#include + +template +X multiply(X a, Y b) { + return a * b; +} + +template +int add(int summand) { + return A + summand; +} + +int main(int argc, char **args) { + assert(add<7>(3) == 10); + + assert(multiply(7, 3) == 21); + + // Check if the float arguments are handled correctly + assert(multiply(7.1f, 3) > 21); + assert(multiply(3, 7.1f) == 21); + + return 0; +} diff --git a/test/CXX/symex/basic_c++/virtual.cpp b/test/CXX/symex/basic_c++/virtual.cpp new file mode 100644 index 00000000..b4850992 --- /dev/null +++ b/test/CXX/symex/basic_c++/virtual.cpp @@ -0,0 +1,35 @@ +// REQUIRES: uclibc +// RUN: %clangxx %s -emit-llvm %O0opt -c -o %t.bc +// RUN: rm -rf %t.klee-out +// RUN: %klee --output-dir=%t.klee-out --exit-on-error --libc=uclibc %t.bc 2>&1 + +#include + +class Testclass { +public: + virtual int vtl() { + return 1; + } +}; + +class Derivedclass : public Testclass { +}; + +class Overridingclass : public Derivedclass { +public: + int vtl() override { + return 2; + } +}; + +int main(int argc, char **args) { + Testclass x; + Derivedclass y; + Overridingclass z; + + assert(x.vtl() == 1); + assert(y.vtl() == 1); + assert(z.vtl() == 2); + + return 0; +} diff --git a/test/CXX/symex/libc++/atexit.cpp b/test/CXX/symex/libc++/atexit.cpp index 03b559ae..fa8df475 100644 --- a/test/CXX/symex/libc++/atexit.cpp +++ b/test/CXX/symex/libc++/atexit.cpp @@ -1,19 +1,20 @@ +// REQUIRES: not-msan +// Disabling msan because it times out on CI // REQUIRES: libcxx // REQUIRES: uclibc // RUN: %clangxx %s -emit-llvm %O0opt -c -std=c++11 -I "%libcxx_include" -g -nostdinc++ -o %t1.bc // RUN: rm -rf %t.klee-out -// RUN: %klee --output-dir=%t.klee-out --exit-on-error --libc=uclibc --libcxx %t1.bc | FileCheck %s -// -// CHECK: Returning from main -// CHECK: atexit_1 -// CHECK: atexit_2 +// RUN: %klee --output-dir=%t.klee-out --libc=uclibc --libcxx %t1.bc 2>&1 | FileCheck %s -#include #include +#include -int main(int argc, char** args){ - auto atexithandler2 = std::atexit([](){std::cout << "atexit_2" << std::endl;}); - auto atexithandler1 = std::atexit([](){std::cout << "atexit_1" << std::endl;}); +int main(int argc, char **args) { + auto atexithandler2 = std::atexit([]() { std::cout << "atexit_2" << std::endl; }); + auto atexithandler1 = std::atexit([]() { std::cout << "atexit_1" << std::endl; }); std::cout << "Returning from main" << std::endl; + // CHECK: Returning from main return 0; + // CHECK: atexit_1 + // CHECK: atexit_2 } diff --git a/test/CXX/symex/libc++/can_catch_test.cpp b/test/CXX/symex/libc++/can_catch_test.cpp new file mode 100644 index 00000000..9d3a2d23 --- /dev/null +++ b/test/CXX/symex/libc++/can_catch_test.cpp @@ -0,0 +1,88 @@ +// REQUIRES: uclibc +// REQUIRES: libcxx +// RUN: %clangxx %s -emit-llvm %O0opt -std=c++11 -c -I "%libcxx_include" -g -nostdinc++ -o %t.bc +// RUN: rm -rf %t.klee-out +// RUN: %klee --output-dir=%t.klee-out --exit-on-error --libcxx --libc=uclibc %t.bc 2>&1 | FileCheck %s + +#include +#include + +extern bool _klee_eh_can_catch(const void *catcher, const void *exception); + +class other {}; +class yet_another {}; +class ya_derived : private yet_another {}; + +class TestClass { +public: + const char *classname = "TestClass"; + + TestClass() { printf("%s: Normal constructor called\n", classname); } + TestClass(const TestClass &other) { + printf("%s: Copy constructor called\n", classname); + } + TestClass(TestClass &&other) { + printf("%s: Move constructor called\n", classname); + } + TestClass &operator=(const TestClass &&other) { + printf("%s: Assignment constructor called\n", classname); + return *this; + } + virtual ~TestClass() { printf("%s: Destructor called\n", classname); } +}; + +class DerivedClass : public TestClass { +public: + const char *classname = "DerivedClass"; + + DerivedClass() { printf("%s: Normal constructor called\n", classname); } + DerivedClass(const DerivedClass &other) { + printf("%s: Copy constructor called\n", classname); + } + DerivedClass(DerivedClass &&other) { + printf("%s: Move constructor called\n", classname); + } + DerivedClass &operator=(const DerivedClass &&other) { + printf("%s: Assignment constructor called\n", classname); + return *this; + } + ~DerivedClass() { printf("%s: Destructor called\n", classname); } +}; + +class SecondDerivedClass : public DerivedClass, other, yet_another { +public: + const char *classname = "SecondDerivedClass"; + + SecondDerivedClass() { printf("%s: Normal constructor called\n", classname); } + SecondDerivedClass(const SecondDerivedClass &other) { + printf("%s: Copy constructor called\n", classname); + } + SecondDerivedClass(SecondDerivedClass &&other) { + printf("%s: Move constructor called\n", classname); + } + SecondDerivedClass &operator=(const SecondDerivedClass &&other) { + printf("%s: Assignment constructor called\n", classname); + return *this; + } + ~SecondDerivedClass() { printf("%s: Destructor called\n", classname); } +}; + +int main(int argc, char **args) { + try { + try { + throw SecondDerivedClass(); + } catch (SecondDerivedClass &p) { + puts("DerivedClass caught\n"); + // CHECK: DerivedClass caught + throw; + } + } catch (TestClass &tc) { + puts("TestClass caught\n"); + // CHECK: TestClass caught + } catch (int) { + } catch (int *) { + } catch (ya_derived &) { + } + + return 0; +} diff --git a/test/CXX/symex/libc++/catch_recover.cpp b/test/CXX/symex/libc++/catch_recover.cpp new file mode 100644 index 00000000..44362b25 --- /dev/null +++ b/test/CXX/symex/libc++/catch_recover.cpp @@ -0,0 +1,26 @@ +// REQUIRES: not-msan +// Disabling msan because it times out on CI +// REQUIRES: libcxx +// RUN: %clangxx %s -emit-llvm %O0opt -c -std=c++11 -I "%libcxx_include" -g -nostdinc++ -o %t1.bc +// RUN: rm -rf %t.klee-out +// RUN: %klee --output-dir=%t.klee-out --libc=uclibc --libcxx %t1.bc 2>&1 | FileCheck %s + +#include +#include + +int main(int argc, char **args) { + try { + throw std::runtime_error("foo"); + } catch (const std::runtime_error &ex) { + printf("In first catch\n"); + // CHECK-DAG: In first catch + } + try { + throw std::runtime_error("bar"); + } catch (const std::runtime_error &ex) { + printf("In second catch\n"); + // CHECK-DAG: In second catch + } +} + +// CHECK-DAG: KLEE: done: completed paths = 1 diff --git a/test/CXX/symex/libc++/catch_with_adjusted_exception_pointer.cpp b/test/CXX/symex/libc++/catch_with_adjusted_exception_pointer.cpp new file mode 100644 index 00000000..bfb69be4 --- /dev/null +++ b/test/CXX/symex/libc++/catch_with_adjusted_exception_pointer.cpp @@ -0,0 +1,37 @@ +// REQUIRES: uclibc +// REQUIRES: libcxx +// RUN: %clangxx %s -emit-llvm %O0opt -std=c++11 -c -I "%libcxx_include" -g -nostdinc++ -o %t.bc +// RUN: rm -rf %t.klee-out +// RUN: %klee --output-dir=%t.klee-out --exit-on-error --libcxx --libc=uclibc %t.bc 2>&1 | FileCheck %s + +#include + +class Base { +public: + int base_int = 42; + virtual void print() { + std::cout << "Base\n"; + } +}; + +class Derived : public Base { +public: + virtual void print() { + std::cout << "Derived\n"; + } +}; + +int main() { + Base *bd = new Derived(); + // CHECK: Derived + bd->print(); + + try { + throw bd; + } catch (Base *b) { + // CHECK: Derived + b->print(); + // CHECK: 42 + std::cout << b->base_int << "\n"; + } +} diff --git a/test/CXX/symex/libc++/cout.cpp b/test/CXX/symex/libc++/cout.cpp index 0f3c338a..62cd0406 100644 --- a/test/CXX/symex/libc++/cout.cpp +++ b/test/CXX/symex/libc++/cout.cpp @@ -1,8 +1,10 @@ +// REQUIRES: not-msan +// Disabling msan because it times out on CI // REQUIRES: libcxx // REQUIRES: uclibc // RUN: %clangxx %s -emit-llvm %O0opt -c -std=c++11 -I "%libcxx_include" -g -nostdinc++ -o %t1.bc // RUN: rm -rf %t.klee-out -// RUN: %klee --output-dir=%t.klee-out --exit-on-error --libc=uclibc --libcxx %t1.bc 2>&1 | FileCheck %s +// RUN: %klee --output-dir=%t.klee-out --libc=uclibc --libcxx %t1.bc 2>&1 | FileCheck %s // CHECK-DAG: cout // CHECK-DAG: cerr diff --git a/test/CXX/symex/libc++/cout_sym.cpp b/test/CXX/symex/libc++/cout_sym.cpp index 8bf81122..177c3ed7 100644 --- a/test/CXX/symex/libc++/cout_sym.cpp +++ b/test/CXX/symex/libc++/cout_sym.cpp @@ -1,22 +1,22 @@ +// REQUIRES: not-msan +// Disabling msan because it times out on CI // REQUIRES: libcxx // REQUIRES: uclibc // RUN: %clangxx %s -emit-llvm %O0opt -c -std=c++11 -I "%libcxx_include" -g -nostdinc++ -o %t1.bc // RUN: rm -rf %t.klee-out -// RUN: %klee --output-dir=%t.klee-out --exit-on-error --libc=uclibc --libcxx %t1.bc | FileCheck %s +// RUN: %klee --output-dir=%t.klee-out --libc=uclibc --libcxx %t1.bc 2>&1 | FileCheck %s -// CHECK-DAG: greater -// CHECK-DAG: lower - - -#include #include "klee/klee.h" +#include -int main(int argc, char** args){ +int main(int argc, char **args) { int x = klee_int("x"); - if (x > 0){ + if (x > 0) { std::cout << "greater: " << x << std::endl; + // CHECK-DAG: greater } else { std::cout << "lower: " << x << std::endl; + // CHECK-DAG: lower } return 0; } diff --git a/test/CXX/symex/libc++/dynamic_cast.cpp b/test/CXX/symex/libc++/dynamic_cast.cpp index c2d8b528..a2fc8b82 100644 --- a/test/CXX/symex/libc++/dynamic_cast.cpp +++ b/test/CXX/symex/libc++/dynamic_cast.cpp @@ -1,54 +1,52 @@ +// REQUIRES: not-msan +// Disabling msan because it times out on CI // REQUIRES: libcxx // REQUIRES: uclibc // RUN: %clangxx %s -emit-llvm %O0opt -c -std=c++11 -I "%libcxx_include" -g -nostdinc++ -o %t1.bc // RUN: rm -rf %t.klee-out -// RUN: %klee --output-dir=%t.klee-out --exit-on-error --libc=uclibc --libcxx %t1.bc -// +// RUN: %klee --output-dir=%t.klee-out --libc=uclibc --libcxx %t1.bc + // Copied from 'http://en.cppreference.com/w/cpp/language/dynamic_cast' struct V { - virtual void f() {}; // must be polymorphic to use runtime-checked dynamic_cast + virtual void f(){}; // must be polymorphic to use runtime-checked dynamic_cast }; struct A : virtual V {}; struct B : virtual V { - B(V* v, A* a) { + B(V *v, A *a) { // casts during construction (see the call in the constructor of D below) - dynamic_cast(v); // well-defined: v of type V*, V base of B, results in B* - dynamic_cast(a); // undefined behavior: a has type A*, A not a base of B + dynamic_cast(v); // well-defined: v of type V*, V base of B, results in B* + dynamic_cast(a); // undefined behavior: a has type A*, A not a base of B } }; struct D : A, B { - D() : B((A*)this, this) { } + D() : B((A *)this, this) {} }; struct Base { - virtual ~Base() {} + virtual ~Base() {} }; -struct Derived: Base { - virtual void name() {} +struct Derived : Base { + virtual void name() {} }; -int main() -{ - D d; // the most derived object - A& a = d; // upcast, dynamic_cast may be used, but unnecessary - D& new_d = dynamic_cast(a); // downcast - B& new_b = dynamic_cast(a); // sidecast - +int main() { + D d; // the most derived object + A &a = d; // upcast, dynamic_cast may be used, but unnecessary + D &new_d = dynamic_cast(a); // downcast + B &new_b = dynamic_cast(a); // sidecast - Base* b1 = new Base; - if(Derived* d = dynamic_cast(b1)) - { - d->name(); // safe to call - } + Base *b1 = new Base; + if (Derived *d = dynamic_cast(b1)) { + d->name(); // safe to call + } - Base* b2 = new Derived; - if(Derived* d = dynamic_cast(b2)) - { - d->name(); // safe to call - } + Base *b2 = new Derived; + if (Derived *d = dynamic_cast(b2)) { + d->name(); // safe to call + } - delete b1; - delete b2; + delete b1; + delete b2; } diff --git a/test/CXX/symex/libc++/exception.cpp b/test/CXX/symex/libc++/exception.cpp new file mode 100644 index 00000000..b46d474e --- /dev/null +++ b/test/CXX/symex/libc++/exception.cpp @@ -0,0 +1,33 @@ +// REQUIRES: not-msan +// Disabling msan because it times out on CI +// REQUIRES: libcxx +// RUN: %clangxx %s -emit-llvm %O0opt -c -std=c++11 -I "%libcxx_include" -g -nostdinc++ -o %t1.bc +// RUN: rm -rf %t.klee-out +// RUN: %klee --output-dir=%t.klee-out --libc=uclibc --libcxx %t1.bc 2>&1 | FileCheck %s + +#include "klee/klee.h" +#include + +int main(int argc, char **args) { + int x = klee_int("Test"); + + try { + if (x % 2 == 0) { + printf("Normal return\n"); + // CHECK-DAG: Normal return + return 0; + } + throw x; + } catch (int b) { + if (b % 3 == 0) { + printf("First exceptional return\n"); + // CHECK-DAG: First exceptional return + return 2; + } + printf("Second exceptional return\n"); + // CHECK-DAG: Second exceptional return + return 3; + } +} + +// CHECK-DAG: KLEE: done: completed paths = 3 diff --git a/test/CXX/symex/libc++/exception_inheritance.cpp b/test/CXX/symex/libc++/exception_inheritance.cpp new file mode 100644 index 00000000..4e42fbb7 --- /dev/null +++ b/test/CXX/symex/libc++/exception_inheritance.cpp @@ -0,0 +1,78 @@ +// REQUIRES: uclibc +// REQUIRES: libcxx +// RUN: %clangxx %s -emit-llvm %O0opt -std=c++11 -c -I "%libcxx_include" -g -nostdinc++ -o %t.bc +// RUN: rm -rf %t.klee-out +// RUN: %klee --output-dir=%t.klee-out --exit-on-error --libcxx --libc=uclibc %t.bc 2>&1 | FileCheck %s + +#include + +class TestClass { +public: + const char *classname = "TestClass"; + + TestClass() { + printf("%s: Normal constructor called\n", classname); + } + TestClass(const TestClass &other) { + printf("%s: Copy constructor called\n", classname); + } + TestClass(TestClass &&other) { printf("%s: Move constructor called\n", classname); } + TestClass &operator=(const TestClass &&other) { + printf("%s: Assignment constructor called\n", classname); + return *this; + } + virtual ~TestClass() { printf("%s: Destructor called\n", classname); } +}; + +class DerivedClass : public TestClass { +public: + const char *classname = "DerivedClass"; + + DerivedClass() { + printf("%s: Normal constructor called\n", classname); + } + DerivedClass(const DerivedClass &other) { + printf("%s: Copy constructor called\n", classname); + } + DerivedClass(DerivedClass &&other) { printf("%s: Move constructor called\n", classname); } + DerivedClass &operator=(const DerivedClass &&other) { + printf("%s: Assignment constructor called\n", classname); + return *this; + } + ~DerivedClass() { printf("%s: Destructor called\n", classname); } +}; + +class SecondDerivedClass : public DerivedClass { +public: + const char *classname = "SecondDerivedClass"; + + SecondDerivedClass() { + printf("%s: Normal constructor called\n", classname); + } + SecondDerivedClass(const SecondDerivedClass &other) { + printf("%s: Copy constructor called\n", classname); + } + SecondDerivedClass(SecondDerivedClass &&other) { printf("%s: Move constructor called\n", classname); } + SecondDerivedClass &operator=(const SecondDerivedClass &&other) { + printf("%s: Assignment constructor called\n", classname); + return *this; + } + ~SecondDerivedClass() { printf("%s: Destructor called\n", classname); } +}; + +int main(int argc, char **args) { + try { + try { + throw SecondDerivedClass(); + } catch (DerivedClass &p) { + puts("DerivedClass caught\n"); + // CHECK: DerivedClass caught + throw; + } + } catch (TestClass &tc) { + puts("TestClass caught\n"); + // CHECK: TestClass caught + } + + return 0; +} diff --git a/test/CXX/symex/libc++/general_catch.cpp b/test/CXX/symex/libc++/general_catch.cpp new file mode 100644 index 00000000..ee7f9983 --- /dev/null +++ b/test/CXX/symex/libc++/general_catch.cpp @@ -0,0 +1,60 @@ +// REQUIRES: not-msan +// Disabling msan because it times out on CI +// REQUIRES: libcxx +// RUN: %clangxx %s -emit-llvm %O0opt -c -std=c++11 -I "%libcxx_include" -g -nostdinc++ -o %t1.bc +// RUN: rm -rf %t.klee-out +// RUN: %klee --output-dir=%t.klee-out --libc=uclibc --libcxx %t1.bc 2>&1 | FileCheck %s + +#include "klee/klee.h" +#include +#include + +struct CustomStruct { + int a; +}; + +int thrower(int x) { + if (x == 0) { + CustomStruct p; + throw p; + } else if (x == 1) { + throw 7; + } else if (x == 2) { + throw new CustomStruct(); + } else { + throw &thrower; + } +} + +int catcher(int x) { + try { + thrower(x); + } catch (int ex) { + printf("Caught int\n"); + // CHECK-DAG: Caught int + return 1; + } catch (CustomStruct ex) { + printf("Caught normal CustomStruct\n"); + // CHECK-DAG: Caught normal CustomStruct + return 2; + } catch (CustomStruct *ex) { + printf("Caught pointer CustomStruct\n"); + // CHECK-DAG: Caught pointer CustomStruct + return 3; + } catch (...) { + printf("Caught general exception\n"); + // CHECK-DAG: Caught general exception + return 4; + } + // Unreachable instruction + assert(0); + return 0; +} + +int main(int argc, char **args) { + int x = klee_int("x"); + int res = catcher(x); + return res; +} + +// CHECK-DAG: KLEE: done: completed paths = 4 diff --git a/test/CXX/symex/libc++/landingpad.cpp b/test/CXX/symex/libc++/landingpad.cpp new file mode 100644 index 00000000..21e55536 --- /dev/null +++ b/test/CXX/symex/libc++/landingpad.cpp @@ -0,0 +1,53 @@ +// Testcase for proper handling of +// c++ type, constructors and destructors. +// Based on: https://gcc.gnu.org/wiki/Dwarf2EHNewbiesHowto +// REQUIRES: uclibc +// REQUIRES: libcxx +// RUN: %clangxx %s -emit-llvm %O0opt -std=c++11 -c -I "%libcxx_include" -g -nostdinc++ -o %t.bc +// RUN: rm -rf %t.klee-out +// RUN: %klee --output-dir=%t.klee-out --exit-on-error --libcxx --libc=uclibc %t.bc | FileCheck %s +// Expect the following output: +// CHECK: Throwing 1... +// CHECK: A() 1 +// CHECK: A(const A&) 2 +// CHECK: Caught. +// CHECK: ~A() 2 +// CHECK: ~A() 1 +// CHECK: c == 2, d == 2 + +#include + +int c, d; + +struct A { + int i; + A() { + i = ++c; + printf("A() %d\n", i); + } + A(const A &) { + i = ++c; + printf("A(const A&) %d\n", i); + } + ~A() { + printf("~A() %d\n", i); + ++d; + } +}; + +void f() { + printf("Throwing 1...\n"); + throw A(); +} + +int main() { + c = 0; + d = 0; + try { + f(); + } catch (A) { + printf("Caught.\n"); + } + printf("c == %d, d == %d\n", c, d); + return c != d; +} diff --git a/test/CXX/symex/libc++/multi_throw.cpp b/test/CXX/symex/libc++/multi_throw.cpp new file mode 100644 index 00000000..be07bc17 --- /dev/null +++ b/test/CXX/symex/libc++/multi_throw.cpp @@ -0,0 +1,47 @@ +// REQUIRES: not-msan +// Disabling msan because it times out on CI +// REQUIRES: libcxx +// RUN: %clangxx %s -emit-llvm %O0opt -c -std=c++11 -I "%libcxx_include" -g -nostdinc++ -o %t1.bc +// RUN: rm -rf %t.klee-out +// RUN: %klee --output-dir=%t.klee-out --libc=uclibc --libcxx %t1.bc 2>&1 | FileCheck %s + +#include "klee/klee.h" +#include + +static void x(int a) { + if (a == 7) { + throw static_cast(a); + } +} + +struct ThrowingDestructor { + int p = 0; + ~ThrowingDestructor() { + try { + x(p); + } catch (float ex) { + printf("Caught float in ThrowingDestructor()\n"); + // CHECK-DAG: Caught float in ThrowingDestructor() + } + } +}; + +static void y(int a) { + ThrowingDestructor p; + p.p = klee_int("struct_int"); + try { + if (a == 7) { + throw a; + } + } catch (int ex) { + printf("Caught int in y()\n"); + // CHECK-DAG: Caught int in y() + } +} + +int main(int argc, char **args) { + y(klee_int("fun_int")); + return 0; +} + +// CHECK-DAG: KLEE: done: completed paths = 4 diff --git a/test/CXX/symex/libc++/multi_unwind.cpp b/test/CXX/symex/libc++/multi_unwind.cpp new file mode 100644 index 00000000..98bae1dc --- /dev/null +++ b/test/CXX/symex/libc++/multi_unwind.cpp @@ -0,0 +1,70 @@ +// REQUIRES: not-msan +// Disabling msan because it times out on CI +// REQUIRES: libcxx +// RUN: %clangxx %s -emit-llvm %O0opt -c -std=c++11 -I "%libcxx_include" -g -nostdinc++ -o %t1.bc +// RUN: rm -rf %t.klee-out +// RUN: %klee --output-dir=%t.klee-out --libc=uclibc --libcxx %t1.bc 2>&1 | FileCheck %s + +#include "klee/klee.h" +#include +#include + +struct CustomStruct { + int a; +}; + +int thrower(int x) { + if (x == 0) { + CustomStruct p; + throw p; + } else if (x == 1) { + throw 7; + } else { + throw new CustomStruct(); + } +} +int rethrower(int x) { + thrower(x); + return 7; +} + +int failed_catch(int x) { + try { + rethrower(x); + } catch (char *ex) { + // Don't catch wrong type + assert(0); + } + // rethrower always throws, cannot reach this instruction + assert(0); + return 0; +} + +int catcher(int x) { + try { + failed_catch(x); + } catch (int ex) { + printf("Caught int\n"); + // CHECK-DAG: Caught int + return 1; + } catch (CustomStruct ex) { + printf("Caught normal CustomStruct\n"); + // CHECK-DAG: Caught normal CustomStruct + return 2; + } catch (CustomStruct *ex) { + printf("Caught pointer CustomStruct\n"); + // CHECK-DAG: Caught pointer CustomStruct + return 3; + } + // Unreachable instruction + assert(0); + return 0; +} + +int main(int argc, char **args) { + int x = klee_int("x"); + int res = catcher(x); + return res; +} + +// CHECK-DAG: KLEE: done: completed paths = 3 diff --git a/test/CXX/symex/libc++/nested.cpp b/test/CXX/symex/libc++/nested.cpp new file mode 100644 index 00000000..b85d6759 --- /dev/null +++ b/test/CXX/symex/libc++/nested.cpp @@ -0,0 +1,26 @@ +// REQUIRES: not-msan +// Disabling msan because it times out on CI +// REQUIRES: libcxx +// RUN: %clangxx %s -emit-llvm %O0opt -c -std=c++11 -I "%libcxx_include" -g -nostdinc++ -o %t1.bc +// RUN: rm -rf %t.klee-out +// RUN: %klee --output-dir=%t.klee-out --exit-on-error --libc=uclibc --libcxx %t1.bc 2>&1 | FileCheck %s + +#include + +int main(int argc, char **args) { + try { + try { + char *p = new char[8]; + throw p; + } catch (char *ex) { + int i = 7; + throw i; + } + } catch (int ex) { + printf("Reached outer catch with i = %d\n", ex); + // CHECK-DAG: Reached outer catch with i = 7 + } + return 0; +} + +// CHECK-DAG: KLEE: done: completed paths = 1 diff --git a/test/CXX/symex/libc++/nested_fail.cpp b/test/CXX/symex/libc++/nested_fail.cpp new file mode 100644 index 00000000..919434f1 --- /dev/null +++ b/test/CXX/symex/libc++/nested_fail.cpp @@ -0,0 +1,26 @@ +// REQUIRES: not-msan +// Disabling msan because it times out on CI +// REQUIRES: libcxx +// RUN: %clangxx %s -emit-llvm %O0opt -c -std=c++11 -I "%libcxx_include" -g -nostdinc++ -o %t1.bc +// RUN: rm -rf %t.klee-out +// RUN: %klee --output-dir=%t.klee-out --libc=uclibc --libcxx %t1.bc 2>&1 | FileCheck %s + +#include "klee/klee.h" +#include + +int main(int argc, char **args) { + try { + try { + char *p = new char[8]; + throw p; + } catch (int ex) { + printf("Landed in wrong inner catch\n"); + // CHECK-NOT: Landed in wrong inner catch + } + } catch (int ex) { + printf("Landed in wrong outer catch\n"); + // CHECK-NOT: Landed in wrong outer catch + } + return 0; +} +// CHECK: terminating with uncaught exception of type char* diff --git a/test/CXX/symex/libc++/rethrow.cpp b/test/CXX/symex/libc++/rethrow.cpp new file mode 100644 index 00000000..d6a0954d --- /dev/null +++ b/test/CXX/symex/libc++/rethrow.cpp @@ -0,0 +1,35 @@ +// REQUIRES: uclibc +// REQUIRES: libcxx +// RUN: %clangxx %s -emit-llvm %O0opt -std=c++11 -c -I "%libcxx_include" -g -nostdinc++ -o %t.bc +// RUN: rm -rf %t.klee-out +// RUN: %klee --output-dir=%t.klee-out --exit-on-error --libcxx --libc=uclibc %t.bc 2>&1 | FileCheck %s + +#include + +class TestClass { +public: + virtual ~TestClass() {} +}; + +class DerivedClass : public TestClass { +}; + +class SecondDerivedClass : public DerivedClass { +}; + +int main(int argc, char **args) { + try { + try { + throw SecondDerivedClass(); + } catch (DerivedClass &p) { + puts("DerivedClass caught\n"); + // CHECK: DerivedClass caught + throw; + } + } catch (TestClass &tc) { + puts("TestClass caught\n"); + // CHECK: TestClass caught + } + + return 0; +} diff --git a/test/CXX/symex/libc++/simple_exception.cpp b/test/CXX/symex/libc++/simple_exception.cpp new file mode 100644 index 00000000..af002253 --- /dev/null +++ b/test/CXX/symex/libc++/simple_exception.cpp @@ -0,0 +1,19 @@ +// REQUIRES: not-msan +// Disabling msan because it times out on CI +// REQUIRES: libcxx +// RUN: %clangxx %s -emit-llvm %O0opt -c -std=c++11 -I "%libcxx_include" -g -nostdinc++ -o %t1.bc +// RUN: rm -rf %t.klee-out +// RUN: %klee --output-dir=%t.klee-out --libc=uclibc --libcxx %t1.bc 2>&1 | FileCheck %s + +// CHECK: KLEE: done: completed paths = 1 + +#include "klee/klee.h" + +#include + +int main(int argc, char **args) { + try { + throw std::runtime_error("foo"); + } catch (const std::runtime_error &ex) { + } +} diff --git a/test/CXX/symex/libc++/simple_exception_fail.cpp b/test/CXX/symex/libc++/simple_exception_fail.cpp new file mode 100644 index 00000000..d4a30b25 --- /dev/null +++ b/test/CXX/symex/libc++/simple_exception_fail.cpp @@ -0,0 +1,13 @@ +// REQUIRES: not-msan +// Disabling msan because it times out on CI +// REQUIRES: libcxx +// RUN: %clangxx %s -emit-llvm %O0opt -c -std=c++11 -I "%libcxx_include" -g -nostdinc++ -o %t1.bc +// RUN: rm -rf %t.klee-out +// RUN: %klee --output-dir=%t.klee-out --libc=uclibc --libcxx %t1.bc 2>&1 | FileCheck %s + +#include + +int main(int argc, char **args) { + throw std::runtime_error("foo"); +} +// CHECK: terminating with uncaught exception of type std::runtime_error: foo \ No newline at end of file diff --git a/test/CXX/symex/libc++/symbolic_exception.cpp b/test/CXX/symex/libc++/symbolic_exception.cpp new file mode 100644 index 00000000..49d432a8 --- /dev/null +++ b/test/CXX/symex/libc++/symbolic_exception.cpp @@ -0,0 +1,29 @@ +// REQUIRES: not-msan +// Disabling msan because it times out on CI +// REQUIRES: libcxx +// RUN: %clangxx %s -emit-llvm %O0opt -c -std=c++11 -I "%libcxx_include" -g -nostdinc++ -o %t1.bc +// RUN: rm -rf %t.klee-out +// RUN: %klee --output-dir=%t.klee-out --libc=uclibc --libcxx %t1.bc 2>&1 | FileCheck %s + +#include "klee/klee.h" +#include + +int main(int argc, char **args) { + try { + throw klee_int("x"); + } catch (int ex) { + if (ex > 7) { + puts("ex > 7"); + // CHECK-DAG: ex > 7 + } else if (ex < 2) { + puts("ex < 2"); + // CHECK-DAG: ex < 2 + } else { + puts("2 <= ex <= 7"); + // CHECK-DAG: 2 <= ex <= 7 + } + } + return 0; +} + +// CHECK-DAG: KLEE: done: completed paths = 3 diff --git a/test/CXX/symex/libc++/throw_specifiers.cpp b/test/CXX/symex/libc++/throw_specifiers.cpp new file mode 100644 index 00000000..5a7955fb --- /dev/null +++ b/test/CXX/symex/libc++/throw_specifiers.cpp @@ -0,0 +1,35 @@ +// Testcase for proper handling of +// throw specifications on functions +// REQUIRES: uclibc +// REQUIRES: libcxx +// RUN: %clangxx %s -emit-llvm %O0opt -std=c++11 -c -I "%libcxx_include" -g -nostdinc++ -o %t.bc +// RUN: rm -rf %t.klee-out +// RUN: %klee --output-dir=%t.klee-out --libcxx --libc=uclibc %t.bc | FileCheck %s + +#include + +namespace { +void throw_expected() throw(int) { + throw 5; +} + +void throw_unexpected() throw(int, double) { + throw "unexpected string"; +} +} // namespace + +int main() { + try { + throw_expected(); + } catch (int i) { + puts("caught expected int"); + // CHECK: caught expected int + } + + try { + throw_unexpected(); + } catch (char const *s) { + puts("caught unexpected string"); + // CHECK-NOT: caught unexpected string + } +} diff --git a/test/CXX/symex/libc++/throwing_exception_destructor.cpp b/test/CXX/symex/libc++/throwing_exception_destructor.cpp new file mode 100644 index 00000000..d24a15b0 --- /dev/null +++ b/test/CXX/symex/libc++/throwing_exception_destructor.cpp @@ -0,0 +1,45 @@ +// Testcase for proper handling of exception destructors that throw. +// REQUIRES: uclibc +// REQUIRES: libcxx +// RUN: %clangxx %s -emit-llvm -O0 -std=c++11 -c -I "%libcxx_include" -g -nostdinc++ -o %t.bc +// RUN: rm -rf %t.klee-out +// RUN: %klee --output-dir=%t.klee-out --libcxx --libc=uclibc --exit-on-error %t.bc + +#include +#include +#include + +bool destructor_throw = false; +bool test_catch = false; +bool int_catch = false; + +struct Test { + ~Test() noexcept(false) { + destructor_throw = true; + throw 5; + } +}; + +int main() { + // For some reason, printing to stdout in this test did not work when running + // with FileCheck. I tried puts, printf and std::cout, using fflush(stdout) + // and std::endl as well, but the output would only be there when not piping + // to FileCheck. Therefore we use a terminate handler here, which will just + // do the checks instead of printing strings and using FileCheck. + std::set_terminate([]() { + assert(destructor_throw && test_catch && !int_catch); + std::exit(0); + }); + + try { + try { + throw Test(); + } catch (Test &t) { + test_catch = true; + } + } catch (int i) { + int_catch = true; + } + + assert(false && "should call terminate due to an uncaught exception"); +} diff --git a/test/CXX/symex/libc++/uncaught_exception.cpp b/test/CXX/symex/libc++/uncaught_exception.cpp new file mode 100644 index 00000000..4e8f90f5 --- /dev/null +++ b/test/CXX/symex/libc++/uncaught_exception.cpp @@ -0,0 +1,13 @@ +// REQUIRES: not-msan +// Disabling msan because it times out on CI +// REQUIRES: libcxx +// RUN: %clangxx %s -emit-llvm %O0opt -c -std=c++11 -I "%libcxx_include" -g -nostdinc++ -o %t1.bc +// RUN: rm -rf %t.klee-out +// RUN: %klee --output-dir=%t.klee-out --libc=uclibc --libcxx %t1.bc 2>&1 | FileCheck %s + +int main() { + throw 7; +} + +// CHECK: KLEE: ERROR: +// CHECK: KLEE: done: completed paths = 1 diff --git a/test/CXX/symex/libc++/vector.cpp b/test/CXX/symex/libc++/vector.cpp index 32afecd2..6f69ad65 100644 --- a/test/CXX/symex/libc++/vector.cpp +++ b/test/CXX/symex/libc++/vector.cpp @@ -1,10 +1,10 @@ +// REQUIRES: not-msan +// Disabling msan because it times out on CI // REQUIRES: libcxx // REQUIRES: uclibc // RUN: %clangxx %s -emit-llvm %O0opt -c -std=c++11 -I "%libcxx_include" -g -nostdinc++ -o %t1.bc // RUN: rm -rf %t.klee-out // RUN: %klee --output-dir=%t.klee-out --exit-on-error --libc=uclibc --libcxx %t1.bc 2>&1 | FileCheck %s -// -// CHECK: KLEE: done: completed paths = 1 #include "klee/klee.h" #include @@ -14,3 +14,4 @@ int main(int argc, char **args) { a.push_back(klee_int("Test")); return a.at(0); } +// CHECK: KLEE: done: completed paths = 1 -- cgit 1.4.1