diff options
author | Felix Rath <felix.rath@comsys.rwth-aachen.de> | 2020-05-22 14:09:10 +0200 |
---|---|---|
committer | MartinNowack <2443641+MartinNowack@users.noreply.github.com> | 2020-10-12 11:19:24 +0100 |
commit | c09306ffd894f95be195723327d5b17dca73ebd1 (patch) | |
tree | 592773383280012bce8856b28503ab61de0deb98 | |
parent | d920e049fa955877f772188fdc58cab1b31aabc9 (diff) | |
download | klee-c09306ffd894f95be195723327d5b17dca73ebd1.tar.gz |
Implemented support for C++ Exceptions
We implement the Itanium ABI unwinding base-API, and leave the C++-specific parts to libcxxabi. Co-authored-by: Lukas Wölfer <lukas.woelfer@rwth-aachen.de>
46 files changed, 1866 insertions, 74 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 1388d3f3..8aaae43b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -653,6 +653,12 @@ if (ENABLE_KLEE_LIBCXX) endif() message(STATUS "Use libc++ include path: \"${KLEE_LIBCXX_INCLUDE_DIR}\"") + if (NOT IS_DIRECTORY "${KLEE_LIBCXXABI_DIR}") + message(FATAL_ERROR + "${KLEE_LIBCXXABI_DIR} does not exist. Try passing -DKLEE_LIBCXXABI_DIR=<path>") + endif() + message(STATUS "Use libc++abi path: \"${KLEE_LIBCXXABI_DIR}\"") + # Find the library bitcode archive # Check for static first diff --git a/lib/Core/ExecutionState.cpp b/lib/Core/ExecutionState.cpp index 5dfabe9e..9004d706 100644 --- a/lib/Core/ExecutionState.cpp +++ b/lib/Core/ExecutionState.cpp @@ -106,6 +106,9 @@ ExecutionState::ExecutionState(const ExecutionState& state): openMergeStack(state.openMergeStack), steppedInstructions(state.steppedInstructions), instsSinceCovNew(state.instsSinceCovNew), + unwindingInformation(state.unwindingInformation + ? state.unwindingInformation->cloned() + : nullptr), coveredNew(state.coveredNew), forkDisabled(state.forkDisabled) { for (const auto &cur_mergehandler: openMergeStack) diff --git a/lib/Core/ExecutionState.h b/lib/Core/ExecutionState.h index 7be69ea7..b3560caf 100644 --- a/lib/Core/ExecutionState.h +++ b/lib/Core/ExecutionState.h @@ -21,6 +21,7 @@ #include "klee/System/Time.h" #include <map> +#include <memory> #include <set> #include <vector> @@ -63,6 +64,83 @@ struct StackFrame { ~StackFrame(); }; +/// Contains information related to unwinding (Itanium ABI/2-Phase unwinding) +struct UnwindingInformation { + enum class Kind { + SearchPhase, // first phase + CleanupPhase // second phase + }; + +private: + const Kind kind; + +public: + // _Unwind_Exception* of the thrown exception, used in both phases + ref<ConstantExpr> exceptionObject; + + Kind getKind() const { return kind; } + + explicit UnwindingInformation(ref<ConstantExpr> exceptionObject, Kind k) + : kind(k), exceptionObject(exceptionObject) {} + virtual ~UnwindingInformation() = default; + + virtual std::unique_ptr<UnwindingInformation> cloned() const = 0; +}; + +struct SearchPhaseUnwindingInformation : public UnwindingInformation { + // Keeps track of the stack index we have so far unwound to. + std::size_t unwindingProgress; + + // MemoryObject that contains a serialized version of the last executed + // landingpad, so we can clean it up after the personality fn returns. + MemoryObject *serializedLandingpad = nullptr; + + SearchPhaseUnwindingInformation(ref<ConstantExpr> exceptionObject, + std::size_t const unwindingProgress) + : UnwindingInformation(exceptionObject, + UnwindingInformation::Kind::SearchPhase), + unwindingProgress(unwindingProgress) {} + + std::unique_ptr<UnwindingInformation> cloned() const { + return std::make_unique<SearchPhaseUnwindingInformation>(*this); + } + + static bool classof(const UnwindingInformation *u) { + return u->getKind() == UnwindingInformation::Kind::SearchPhase; + } +}; + +struct CleanupPhaseUnwindingInformation : public UnwindingInformation { + // Phase 1 will try to find a catching landingpad. + // Phase 2 will unwind up to this landingpad or return from + // _Unwind_RaiseException if none was found. + + // The selector value of the catching landingpad that was found + // during the search phase. + ref<ConstantExpr> selectorValue; + + // Used to know when we have to stop unwinding and to + // ensure that unwinding stops at the frame for which + // we first found a handler in the search phase. + const std::size_t catchingStackIndex; + + CleanupPhaseUnwindingInformation(ref<ConstantExpr> exceptionObject, + ref<ConstantExpr> selectorValue, + const std::size_t catchingStackIndex) + : UnwindingInformation(exceptionObject, + UnwindingInformation::Kind::CleanupPhase), + selectorValue(selectorValue), + catchingStackIndex(catchingStackIndex) {} + + std::unique_ptr<UnwindingInformation> cloned() const { + return std::make_unique<CleanupPhaseUnwindingInformation>(*this); + } + + static bool classof(const UnwindingInformation *u) { + return u->getKind() == UnwindingInformation::Kind::CleanupPhase; + } +}; + /// @brief ExecutionState representing a path under exploration class ExecutionState { #ifdef KLEE_UNITTEST @@ -141,6 +219,9 @@ public: /// instruction was covered. std::uint32_t instsSinceCovNew; + /// @brief Keep track of unwinding state while unwinding, otherwise empty + std::unique_ptr<UnwindingInformation> unwindingInformation; + /// @brief the global state counter static std::uint32_t nextID; diff --git a/lib/Core/Executor.cpp b/lib/Core/Executor.cpp index de35710f..64dcee43 100644 --- a/lib/Core/Executor.cpp +++ b/lib/Core/Executor.cpp @@ -65,6 +65,7 @@ #include "llvm/IR/IntrinsicInst.h" #include "llvm/IR/LLVMContext.h" #include "llvm/IR/Module.h" +#include "llvm/IR/Operator.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/FileSystem.h" @@ -80,10 +81,12 @@ typedef unsigned TypeSize; #include <algorithm> #include <cassert> #include <cerrno> +#include <cstring> #include <cxxabi.h> #include <fstream> #include <iomanip> #include <iosfwd> +#include <limits> #include <sstream> #include <string> #include <sys/mman.h> @@ -284,6 +287,10 @@ cl::list<Executor::TerminateReason> ExitOnErrorType( clEnumValN(Executor::ReportError, "ReportError", "klee_report_error called"), clEnumValN(Executor::User, "User", "Wrong klee_* functions invocation"), + clEnumValN(Executor::UncaughtException, "UncaughtException", + "Exception was not caught"), + clEnumValN(Executor::UnexpectedException, "UnexpectedException", + "An unexpected exception was thrown"), clEnumValN(Executor::Unhandled, "Unhandled", "Unhandled instruction hit") KLEE_LLVM_CL_VAL_END), cl::ZeroOrMore, @@ -433,6 +440,8 @@ const char *Executor::TerminateReasonNames[] = { [ ReadOnly ] = "readonly", [ ReportError ] = "reporterror", [ User ] = "user", + [ UncaughtException ] = "uncaught_exception", + [ UnexpectedException ] = "unexpected_exception", [ Unhandled ] = "xxx", }; @@ -1407,6 +1416,235 @@ static inline const llvm::fltSemantics *fpWidthToSemantics(unsigned width) { } } +MemoryObject *Executor::serializeLandingpad(ExecutionState &state, + const llvm::LandingPadInst &lpi, + bool &stateTerminated) { + stateTerminated = false; + + std::vector<unsigned char> serialized; + + for (unsigned current_clause_id = 0; current_clause_id < lpi.getNumClauses(); + ++current_clause_id) { + llvm::Constant *current_clause = lpi.getClause(current_clause_id); + if (lpi.isCatch(current_clause_id)) { + // catch-clause + serialized.push_back(0); + + std::uint64_t ti_addr = 0; + + llvm::BitCastOperator *clause_bitcast = + dyn_cast<llvm::BitCastOperator>(current_clause); + if (clause_bitcast) { + llvm::GlobalValue *clause_type = + dyn_cast<GlobalValue>(clause_bitcast->getOperand(0)); + + ti_addr = globalAddresses[clause_type]->getZExtValue(); + } else if (current_clause->isNullValue()) { + ti_addr = 0; + } else { + terminateStateOnError( + state, "Internal: Clause is not a bitcast or null (catch-all)", + Exec); + stateTerminated = true; + return nullptr; + } + const std::size_t old_size = serialized.size(); + serialized.resize(old_size + 8); + memcpy(serialized.data() + old_size, &ti_addr, sizeof(ti_addr)); + } else if (lpi.isFilter(current_clause_id)) { + if (current_clause->isNullValue()) { + // special handling for a catch-all filter clause, i.e., "[0 x i8*]" + // for this case we serialize 1 element.. + serialized.push_back(1); + // which is a 64bit-wide 0. + serialized.resize(serialized.size() + 8, 0); + } else { + llvm::ConstantArray const *ca = + cast<llvm::ConstantArray>(current_clause); + + // serialize `num_elements+1` as unsigned char + unsigned const num_elements = ca->getNumOperands(); + unsigned char serialized_num_elements = 0; + + if (num_elements >= + std::numeric_limits<decltype(serialized_num_elements)>::max()) { + terminateStateOnError( + state, "Internal: too many elements in landingpad filter", Exec); + stateTerminated = true; + return nullptr; + } + + serialized_num_elements = num_elements; + serialized.push_back(serialized_num_elements + 1); + + // serialize the exception-types occurring in this filter-clause + for (llvm::Value const *v : ca->operands()) { + llvm::BitCastOperator const *bitcast = + dyn_cast<llvm::BitCastOperator>(v); + if (!bitcast) { + terminateStateOnError(state, + "Internal: expected value inside a " + "filter-clause to be a bitcast", + Exec); + stateTerminated = true; + return nullptr; + } + + llvm::GlobalValue const *clause_value = + dyn_cast<GlobalValue>(bitcast->getOperand(0)); + if (!clause_value) { + terminateStateOnError(state, + "Internal: expected value inside a " + "filter-clause bitcast to be a GlobalValue", + Exec); + stateTerminated = true; + return nullptr; + } + + std::uint64_t const ti_addr = + globalAddresses[clause_value]->getZExtValue(); + + const std::size_t old_size = serialized.size(); + serialized.resize(old_size + 8); + memcpy(serialized.data() + old_size, &ti_addr, sizeof(ti_addr)); + } + } + } + } + + MemoryObject *mo = + memory->allocate(serialized.size(), true, false, nullptr, 1); + ObjectState *os = bindObjectInState(state, mo, false); + for (unsigned i = 0; i < serialized.size(); i++) { + os->write8(i, serialized[i]); + } + + return mo; +} + +void Executor::unwindToNextLandingpad(ExecutionState &state) { + UnwindingInformation *ui = state.unwindingInformation.get(); + assert(ui && "unwinding without unwinding information"); + + std::size_t startIndex; + std::size_t lowestStackIndex; + bool popFrames; + + if (auto *sui = dyn_cast<SearchPhaseUnwindingInformation>(ui)) { + startIndex = sui->unwindingProgress; + lowestStackIndex = 0; + popFrames = false; + } else if (auto *cui = dyn_cast<CleanupPhaseUnwindingInformation>(ui)) { + startIndex = state.stack.size() - 1; + lowestStackIndex = cui->catchingStackIndex; + popFrames = true; + } else { + assert(false && "invalid UnwindingInformation subclass"); + } + + for (std::size_t i = startIndex; i > lowestStackIndex; i--) { + auto const &sf = state.stack.at(i); + + Instruction *inst = sf.caller ? sf.caller->inst : nullptr; + + if (popFrames) { + state.popFrame(); + if (statsTracker != nullptr) { + statsTracker->framePopped(state); + } + } + + if (InvokeInst *invoke = dyn_cast<InvokeInst>(inst)) { + // we found the next invoke instruction in the call stack, handle it + // depending on the current phase. + if (auto *sui = dyn_cast<SearchPhaseUnwindingInformation>(ui)) { + // in the search phase, run personality function to check if this + // landingpad catches the exception + + LandingPadInst *lpi = invoke->getUnwindDest()->getLandingPadInst(); + assert(lpi && "unwind target of an invoke instruction did not lead to " + "a landingpad"); + + // check if this is a pure cleanup landingpad first + if (lpi->isCleanup() && lpi->getNumClauses() == 0) { + // pure cleanup lpi, this can't be a handler, so skip it + continue; + } + + bool stateTerminated = false; + MemoryObject *clauses_mo = + serializeLandingpad(state, *lpi, stateTerminated); + assert((stateTerminated != bool(clauses_mo)) && + "illegal serializeLandingpad result"); + + if (stateTerminated) { + return; + } + + assert(sui->serializedLandingpad == nullptr && + "serializedLandingpad should be reset"); + sui->serializedLandingpad = clauses_mo; + + llvm::Function *personality_fn = + kmodule->module->getFunction("_klee_eh_cxx_personality"); + KFunction *kf = kmodule->functionMap[personality_fn]; + + state.pushFrame(state.prevPC, kf); + state.pc = kf->instructions; + bindArgument(kf, 0, state, sui->exceptionObject); + bindArgument(kf, 1, state, clauses_mo->getSizeExpr()); + bindArgument(kf, 2, state, clauses_mo->getBaseExpr()); + + if (statsTracker) { + statsTracker->framePushed(state, + &state.stack[state.stack.size() - 2]); + } + + // make sure we remember our search progress afterwards + sui->unwindingProgress = i - 1; + } else { + // in the cleanup phase, redirect control flow + transferToBasicBlock(invoke->getUnwindDest(), invoke->getParent(), + state); + } + + // we are done, stop search/unwinding here + return; + } + } + + // no further invoke instruction/landingpad found + if (isa<SearchPhaseUnwindingInformation>(ui)) { + // in phase 1, simply stop unwinding. this will return + // control flow back to _Unwind_RaiseException, which will + // return the correct error. + + // clean up unwinding state + state.unwindingInformation.reset(); + } else { + // in phase 2, this represent a situation that should + // not happen, as we only progressed to phase 2 because + // we found a handler in phase 1. + // therefore terminate the state. + terminateStateOnExecError(state, + "Missing landingpad in phase 2 of unwinding"); + } +} + +ref<klee::ConstantExpr> Executor::getEhTypeidFor(ref<Expr> type_info) { + auto eh_type_iterator = + std::find(std::begin(eh_typeids), std::end(eh_typeids), type_info); + if (eh_type_iterator == std::end(eh_typeids)) { + eh_typeids.push_back(type_info); + eh_type_iterator = std::prev(std::end(eh_typeids)); + } + // +1 because typeids must always be positive, so they can be distinguished + // from 'no landingpad clause matched' which has value 0 + auto res = ConstantExpr::create(eh_type_iterator - std::begin(eh_typeids) + 1, + Expr::Int32); + return res; +} + void Executor::executeCall(ExecutionState &state, KInstruction *ki, Function *f, std::vector<ref<Expr>> &arguments) { Instruction *i = ki->inst; @@ -1500,6 +1738,12 @@ void Executor::executeCall(ExecutionState &state, KInstruction *ki, Function *f, } break; } + + case Intrinsic::eh_typeid_for: { + bindLocal(ki, state, getEhTypeidFor(arguments.at(0))); + break; + } + case Intrinsic::vaend: // va_end is a noop for the interpreter. // @@ -1518,8 +1762,15 @@ void Executor::executeCall(ExecutionState &state, KInstruction *ki, Function *f, return; } - if (InvokeInst *ii = dyn_cast<InvokeInst>(i)) - transferToBasicBlock(ii->getNormalDest(), i->getParent(), state); + // __cxa_throw & _rethrow are already handled in their + // SpecialFunctionHandlers and have already been redirected to their unwind + // destinations, so we must not transfer them to their regular targets. + if (InvokeInst *ii = dyn_cast<InvokeInst>(i)) { + if (f->getName() != std::string("__cxa_throw") && + f->getName() != std::string("__cxa_rethrow")) { + transferToBasicBlock(ii->getNormalDest(), i->getParent(), state); + } + } } else { // Check if maximum stack size was reached. // We currently only count the number of stack frames @@ -1747,6 +1998,8 @@ void Executor::executeInstruction(ExecutionState &state, KInstruction *ki) { Instruction *caller = kcaller ? kcaller->inst : 0; bool isVoidReturn = (ri->getNumOperands() == 0); ref<Expr> result = ConstantExpr::alloc(0, Expr::Bool); + bool isReturnFromPersonalityFn = + ri->getFunction()->getName() == "_klee_eh_cxx_personality"; if (!isVoidReturn) { result = eval(ki, 0, state).value; @@ -1768,6 +2021,40 @@ void Executor::executeInstruction(ExecutionState &state, KInstruction *ki) { ++state.pc; } + if (isReturnFromPersonalityFn) { + assert(dyn_cast<ConstantExpr>(result) && + "result from personality fn must be a concrete value"); + + auto *sui = dyn_cast_or_null<SearchPhaseUnwindingInformation>( + state.unwindingInformation.get()); + assert(sui && "return from personality function outside of " + "search phase unwinding"); + + // unbind the MO we used to pass the serialized landingpad + state.addressSpace.unbindObject(sui->serializedLandingpad); + sui->serializedLandingpad = nullptr; + + if (result->isZero()) { + // this lpi doesn't handle the exception, continue the search + unwindToNextLandingpad(state); + } else { + // a clause (or a catch-all clause or filter clause) matches: + // remember the stack index and switch to cleanup phase + state.unwindingInformation = + std::make_unique<CleanupPhaseUnwindingInformation>( + sui->exceptionObject, cast<ConstantExpr>(result), + sui->unwindingProgress); + // this pointer is now invalidated + sui = nullptr; + // continue the unwinding process (which will now start with the + // cleanup phase) + unwindToNextLandingpad(state); + } + + // never return normally from the personality fn + break; + } + if (!isVoidReturn) { Type *t = caller->getType(); if (t != Type::getVoidTy(i->getContext())) { @@ -2858,6 +3145,79 @@ void Executor::executeInstruction(ExecutionState &state, KInstruction *ki) { // instructions. terminateStateOnExecError(state, "Unexpected ShuffleVector instruction"); break; + + case Instruction::Resume: { + auto *cui = dyn_cast_or_null<CleanupPhaseUnwindingInformation>( + state.unwindingInformation.get()); + + if (!cui) { + terminateStateOnExecError( + state, + "resume-instruction executed outside of cleanup phase unwinding"); + break; + } + + ref<Expr> arg = eval(ki, 0, state).value; + ref<Expr> expPtr = ExtractExpr::create(arg, 0, Expr::Int64); + ref<Expr> selector = ExtractExpr::create(arg, Expr::Int64, Expr::Int32); + + if (!Expr::createIsZero(selector)->isTrue()) { + llvm::errs() << "resume-instruction called with non-0 selector value '" + << selector << "'\n"; + } + + if (!EqExpr::create(expPtr, cui->exceptionObject)->isTrue()) { + terminateStateOnExecError( + state, "resume-instruction called with unexpected exception pointer"); + break; + } + + unwindToNextLandingpad(state); + break; + } + + case Instruction::LandingPad: { + auto *cui = dyn_cast_or_null<CleanupPhaseUnwindingInformation>( + state.unwindingInformation.get()); + + if (!cui) { + terminateStateOnExecError( + state, "Executing landing pad but not in unwinding phase 2"); + break; + } + + ref<ConstantExpr> exceptionPointer = cui->exceptionObject; + ref<ConstantExpr> selectorValue; + + // check on which frame we are currently + if (state.stack.size() - 1 == cui->catchingStackIndex) { + // we are in the target stack frame, return the selector value + // that was returned by the personality fn in phase 1 and stop unwinding. + selectorValue = cui->selectorValue; + + // stop unwinding by cleaning up our unwinding information. + state.unwindingInformation.reset(); + + // this would otherwise now be a dangling pointer + cui = nullptr; + } else { + // we are not yet at the target stack frame. the landingpad might have + // a cleanup clause or not, anyway, we give it the selector value "0", + // which represents a cleanup, and expect it to handle it. + // This is explicitly allowed by LLVM, see + // https://llvm.org/docs/ExceptionHandling.html#id18 + selectorValue = ConstantExpr::create(0, Expr::Int32); + } + + // we have to return a {i8*, i32} + ref<Expr> result = ConcatExpr::create( + ZExtExpr::create(selectorValue, Expr::Int32), exceptionPointer); + + bindLocal(ki, state, result); + + break; + } + case Instruction::AtomicRMW: terminateStateOnExecError(state, "Unexpected Atomic instruction, should be " "lowered by LowerAtomicInstructionPass"); diff --git a/lib/Core/Executor.h b/lib/Core/Executor.h index 25a874cd..30d1dd92 100644 --- a/lib/Core/Executor.h +++ b/lib/Core/Executor.h @@ -42,6 +42,7 @@ namespace llvm { class BasicBlock; class BranchInst; class CallInst; + class LandingPadInst; class Constant; class ConstantExpr; class Function; @@ -109,7 +110,9 @@ public: ReadOnly, ReportError, User, - Unhandled + UncaughtException, + UnexpectedException, + Unhandled, }; /// The random number generator. @@ -219,6 +222,12 @@ private: /// `nullptr` if merging is disabled MergingSearcher *mergingSearcher = nullptr; + /// Typeids used during exception handling + std::vector<ref<Expr>> eh_typeids; + + /// Return the typeid corresponding to a certain `type_info` + ref<ConstantExpr> getEhTypeidFor(ref<Expr> type_info); + llvm::Function* getTargetFunction(llvm::Value *calledVal, ExecutionState &state); @@ -304,7 +313,17 @@ private: void executeFree(ExecutionState &state, ref<Expr> address, KInstruction *target = 0); - + + /// Serialize a landingpad instruction so it can be handled by the + /// libcxxabi-runtime + MemoryObject *serializeLandingpad(ExecutionState &state, + const llvm::LandingPadInst &lpi, + bool &stateTerminated); + + /// Unwind the given state until it hits a landingpad. This is used + /// for exception handling. + void unwindToNextLandingpad(ExecutionState &state); + void executeCall(ExecutionState &state, KInstruction *ki, llvm::Function *f, diff --git a/lib/Core/SpecialFunctionHandler.cpp b/lib/Core/SpecialFunctionHandler.cpp index 57f059a4..a36d4324 100644 --- a/lib/Core/SpecialFunctionHandler.cpp +++ b/lib/Core/SpecialFunctionHandler.cpp @@ -15,6 +15,7 @@ #include "MemoryManager.h" #include "MergeHandler.h" #include "Searcher.h" +#include "StatsTracker.h" #include "TimingSolver.h" #include "klee/Module/KInstruction.h" @@ -26,6 +27,7 @@ #include "llvm/ADT/Twine.h" #include "llvm/IR/DataLayout.h" +#include "llvm/IR/Instructions.h" #include "llvm/IR/Module.h" #include <errno.h> @@ -112,6 +114,7 @@ static SpecialFunctionHandler::HandlerInfo handlerInfo[] = { add("malloc", handleMalloc, true), add("memalign", handleMemalign, true), add("realloc", handleRealloc, true), + add("_klee_eh_Unwind_RaiseException_impl", handleEhUnwindRaiseExceptionImpl, false), // operator delete[](void*) add("_ZdaPv", handleDeleteArray, false), @@ -136,6 +139,7 @@ static SpecialFunctionHandler::HandlerInfo handlerInfo[] = { add("__ubsan_handle_sub_overflow", handleSubOverflow, false), add("__ubsan_handle_mul_overflow", handleMulOverflow, false), add("__ubsan_handle_divrem_overflow", handleDivRemOverflow, false), + add("klee_eh_typeid_for", handleEhTypeid, true), #undef addDNR #undef add @@ -453,6 +457,52 @@ void SpecialFunctionHandler::handleMemalign(ExecutionState &state, alignment); } +void SpecialFunctionHandler::handleEhUnwindRaiseExceptionImpl( + ExecutionState &state, KInstruction *target, + std::vector<ref<Expr>> &arguments) { + if (arguments.size() != 1) { + executor.terminateStateOnError( + state, + "Internal Error: Incorrect number of arguments to " + "_klee_eh_Unwind_RaiseException_impl, " + "should not happen", + Executor::Unhandled); + return; + } + + ref<ConstantExpr> exceptionObject = dyn_cast<ConstantExpr>(arguments[0]); + if (!exceptionObject.get()) { + executor.terminateStateOnError(state, + "Internal error: Symbolic exception pointer", + Executor::Unhandled); + return; + } + + if (isa_and_nonnull<SearchPhaseUnwindingInformation>( + state.unwindingInformation)) { + executor.terminateStateOnError( + state, + "Internal error: Unwinding restarted during an ongoing search phase", + Executor::Unhandled); + return; + } + + state.unwindingInformation = + std::make_unique<SearchPhaseUnwindingInformation>(exceptionObject, + state.stack.size() - 1); + + executor.unwindToNextLandingpad(state); +} + +void SpecialFunctionHandler::handleEhTypeid(ExecutionState &state, + KInstruction *target, + std::vector<ref<Expr>> &arguments) { + assert(arguments.size() == 1 && + "invalid number of arguments to klee_eh_typeid_for"); + + executor.bindLocal(target, state, executor.getEhTypeidFor(arguments[0])); +} + void SpecialFunctionHandler::handleAssume(ExecutionState &state, KInstruction *target, std::vector<ref<Expr> > &arguments) { diff --git a/lib/Core/SpecialFunctionHandler.h b/lib/Core/SpecialFunctionHandler.h index 0ad04049..3db650d7 100644 --- a/lib/Core/SpecialFunctionHandler.h +++ b/lib/Core/SpecialFunctionHandler.h @@ -119,6 +119,8 @@ namespace klee { HANDLER(handleMakeSymbolic); HANDLER(handleMalloc); HANDLER(handleMemalign); + HANDLER(handleEhUnwindRaiseExceptionImpl); + HANDLER(handleEhTypeid); HANDLER(handleMarkGlobal); HANDLER(handleOpenMerge); HANDLER(handleCloseMerge); diff --git a/lib/Module/IntrinsicCleaner.cpp b/lib/Module/IntrinsicCleaner.cpp index 030a75ca..cd93b3ec 100644 --- a/lib/Module/IntrinsicCleaner.cpp +++ b/lib/Module/IntrinsicCleaner.cpp @@ -364,7 +364,6 @@ bool IntrinsicCleanerPass::runOnBasicBlock(BasicBlock &b, Module &M) { #if LLVM_VERSION_CODE >= LLVM_VERSION(7, 0) case Intrinsic::dbg_label: #endif - case Intrinsic::eh_typeid_for: case Intrinsic::exp2: case Intrinsic::exp: case Intrinsic::expect: @@ -400,6 +399,11 @@ bool IntrinsicCleanerPass::runOnBasicBlock(BasicBlock &b, Module &M) { dirty = true; break; + case Intrinsic::eh_typeid_for: { + // Don't lower this, we need it for exception handling + break; + } + // Warn about any unrecognized intrinsics. default: { const Function *Callee = ii->getCalledFunction(); diff --git a/lib/Module/ModuleUtil.cpp b/lib/Module/ModuleUtil.cpp index f4cc3048..2316ef45 100644 --- a/lib/Module/ModuleUtil.cpp +++ b/lib/Module/ModuleUtil.cpp @@ -203,41 +203,69 @@ klee::linkModules(std::vector<std::unique_ptr<llvm::Module>> &modules, return nullptr; } - while (true) { + auto containsUsedSymbols = [](const llvm::Module *module) { + GlobalValue *GV = + dyn_cast_or_null<GlobalValue>(module->getNamedValue("llvm.used")); + if (!GV) + return false; + KLEE_DEBUG_WITH_TYPE("klee_linker", dbgs() << "Used attribute in " + << module->getModuleIdentifier() + << '\n'); + return true; + }; + + for (auto &module : modules) { + if (!module || !containsUsedSymbols(module.get())) + continue; + if (!linkTwoModules(composite.get(), std::move(module), errorMsg)) { + // Linking failed + errorMsg = "Linking module containing '__attribute__((used))'" + " symbols with composite failed:" + + errorMsg; + return nullptr; + } + module = nullptr; + } + + bool symbolsLinked = true; + while (symbolsLinked) { + symbolsLinked = false; std::set<std::string> undefinedSymbols; GetAllUndefinedSymbols(composite.get(), undefinedSymbols); + auto hasRequiredDefinition = [&undefinedSymbols]( + const llvm::Module *module) { + for (auto symbol : undefinedSymbols) { + GlobalValue *GV = + dyn_cast_or_null<GlobalValue>(module->getNamedValue(symbol)); + if (GV && !GV->isDeclaration()) { + KLEE_DEBUG_WITH_TYPE("klee_linker", + dbgs() << "Found " << GV->getName() << " in " + << module->getModuleIdentifier() << "\n"); + return true; + } + } + return false; + }; // Stop in nothing is undefined if (undefinedSymbols.empty()) break; - bool merged = false; for (auto &module : modules) { if (!module) continue; - for (auto symbol : undefinedSymbols) { - GlobalValue *GV = - dyn_cast_or_null<GlobalValue>(module->getNamedValue(symbol)); - if (!GV || GV->isDeclaration()) - continue; + if (!hasRequiredDefinition(module.get())) + continue; - // Found symbol, therefore merge in module - KLEE_DEBUG_WITH_TYPE("klee_linker", - dbgs() << "Found " << GV->getName() << " in " - << module->getModuleIdentifier() << "\n"); - if (linkTwoModules(composite.get(), std::move(module), errorMsg)) { - module = nullptr; - merged = true; - break; - } + if (!linkTwoModules(composite.get(), std::move(module), errorMsg)) { // Linking failed errorMsg = "Linking archive module with composite failed:" + errorMsg; return nullptr; } + module = nullptr; + symbolsLinked = true; } - if (!merged) - break; } // Condense the module array diff --git a/runtime/CMakeLists.txt b/runtime/CMakeLists.txt index e3619f3d..8e7e7226 100644 --- a/runtime/CMakeLists.txt +++ b/runtime/CMakeLists.txt @@ -32,6 +32,12 @@ else() set(BUILD_POSIX_RUNTIME 0) endif() +if (ENABLE_KLEE_LIBCXX) + set(BUILD_KLEE_EH_CXX 1) +else() + set(BUILD_KLEE_EH_CXX 0) +endif() + # Configure the bitcode build system configure_file("Makefile.cmake.bitcode.config.in" "Makefile.cmake.bitcode.config" @@ -48,6 +54,9 @@ set(BITCODE_LIBRARIES "Intrinsic" "klee-libc" "FreeStanding") if (ENABLE_POSIX_RUNTIME) list(APPEND BITCODE_LIBRARIES "POSIX") endif() +if (ENABLE_KLEE_LIBCXX) + list(APPEND BITCODE_LIBRARIES "klee-eh-cxx") +endif() foreach (bl ${BITCODE_LIBRARIES}) configure_file("${bl}/Makefile.cmake.bitcode" "${CMAKE_CURRENT_BINARY_DIR}/${bl}/Makefile.cmake.bitcode" @@ -136,6 +145,11 @@ if (ENABLE_POSIX_RUNTIME) "${KLEE_RUNTIME_DIRECTORY}/libkleeRuntimePOSIX.bca") endif() +if (ENABLE_KLEE_LIBCXX) + list(APPEND RUNTIME_FILES_TO_INSTALL + "${KLEE_RUNTIME_DIRECTORY}/libklee-eh-cxx.bca") +endif() + install(FILES ${RUNTIME_FILES_TO_INSTALL} DESTINATION "${KLEE_INSTALL_RUNTIME_DIR}") diff --git a/runtime/Makefile.cmake.bitcode b/runtime/Makefile.cmake.bitcode index 03da435e..1dfdfde6 100644 --- a/runtime/Makefile.cmake.bitcode +++ b/runtime/Makefile.cmake.bitcode @@ -17,4 +17,8 @@ ifneq ($(ENABLE_POSIX_RUNTIME),0) DIRS += POSIX endif +ifneq ($(BUILD_KLEE_EH_CXX),0) + DIRS += klee-eh-cxx +endif + include $(LEVEL)/Makefile.cmake.bitcode.rules diff --git a/runtime/Makefile.cmake.bitcode.config.in b/runtime/Makefile.cmake.bitcode.config.in index 5916d79a..4e2354b8 100644 --- a/runtime/Makefile.cmake.bitcode.config.in +++ b/runtime/Makefile.cmake.bitcode.config.in @@ -11,6 +11,7 @@ # #===------------------------------------------------------------------------===# LLVMCC := @LLVMCC@ +LLVMCXX := @LLVMCXX@ LLVM_LINK := @LLVM_LINK@ LLVM_AR := @LLVM_AR@ LLVM_VERSION_MAJOR := @LLVM_VERSION_MAJOR@ @@ -31,6 +32,10 @@ RUNTIME_CONFIG_STRING := @KLEE_RUNTIME_BUILD_TYPE@ # Optional features ENABLE_POSIX_RUNTIME := @BUILD_POSIX_RUNTIME@ +BUILD_KLEE_CXXABI := @BUILD_KLEE_CXXABI@ +KLEE_LIBCXX_INCLUDE_DIR := @KLEE_LIBCXX_INCLUDE_DIR@ +KLEE_LIBCXXABI_DIR := @KLEE_LIBCXXABI_DIR@ +KLEE_INCLUDE_DIR := @CMAKE_SOURCE_DIR@/include # Commands MKDIR := mkdir diff --git a/runtime/klee-eh-cxx/Makefile.cmake.bitcode b/runtime/klee-eh-cxx/Makefile.cmake.bitcode new file mode 100644 index 00000000..f29b947d --- /dev/null +++ b/runtime/klee-eh-cxx/Makefile.cmake.bitcode @@ -0,0 +1,13 @@ +include ../Makefile.cmake.bitcode.config + +SRC_DIR=$(ROOT_SRC)/klee-eh-cxx + +ARCHIVE_FILE=$(ARCHIVE_DEST)/libklee-eh-cxx.bca + +all: $(ARCHIVE_FILE) + +klee_eh_cxx.bc: $(SRC_DIR)/klee_eh_cxx.cpp + $(LLVMCXX) -nostdinc++ -emit-llvm -c -I$(KLEE_INCLUDE_DIR) -I $(KLEE_LIBCXXABI_DIR)/include -I $(KLEE_LIBCXXABI_DIR)/src -I $(KLEE_LIBCXX_INCLUDE_DIR) $(SRC_DIR)/klee_eh_cxx.cpp -o $@ + +$(ARCHIVE_FILE): klee_eh_cxx.bc + $(LLVM_AR) rcs $@ $< diff --git a/runtime/klee-eh-cxx/klee_eh_cxx.cpp b/runtime/klee-eh-cxx/klee_eh_cxx.cpp new file mode 100644 index 00000000..8eafac2c --- /dev/null +++ b/runtime/klee-eh-cxx/klee_eh_cxx.cpp @@ -0,0 +1,218 @@ +#include <cassert> +#include <cstdint> +#include <cstdio> +#include <cstdlib> +#include <typeinfo> + +#include <unwind.h> + +#include <klee/klee.h> + +// from libcxxabi +#include <cxa_exception.h> +#include <private_typeinfo.h> + +// implemented in the SpecialFunctionHandler +extern "C" std::uint32_t klee_eh_typeid_for(const void *); + +// we use some internals of libcxxabi, so we make our life easier here +using __shim_type_info = __cxxabiv1::__shim_type_info; +using __cxa_exception = __cxxabiv1::__cxa_exception; + +namespace { +// Used to determine whether a catch-clause can catch an exception +bool _klee_eh_can_catch(const void *catcherType, + const __shim_type_info *exceptionType, + void *&adjustedPtr) { + return static_cast<const __shim_type_info *>(catcherType) + ->can_catch(exceptionType, adjustedPtr); +} + +// Some utility functions to convert between the different exception +// representations. These are mostly copied from libcxxabi, except +// for small adjustments so they work in our code as well. + +// Copied from libcxxabi/cxa_exception.cpp (including comments) +inline +__cxa_exception* +cxa_exception_from_thrown_object(void* thrown_object) +{ + return static_cast<__cxa_exception*>(thrown_object) - 1; +} + +// Copied from libcxxabi/cxa_exception.cpp (including comments) +// +// Get the exception object from the unwind pointer. +// Relies on the structure layout, where the unwind pointer is right in +// front of the user's exception object +inline +__cxa_exception* +cxa_exception_from_exception_unwind_exception(_Unwind_Exception* unwind_exception) +{ + return cxa_exception_from_thrown_object(unwind_exception + 1); +} + +// Copied from libcxxabi/cxa_personality.cpp (including comments) +void* +get_thrown_object_ptr(_Unwind_Exception* unwind_exception) +{ + // added for usage inside KLEE (i.e., outside of libcxxabi) + using namespace __cxxabiv1; + + // Even for foreign exceptions, the exception object is *probably* at unwind_exception + 1 + // Regardless, this library is prohibited from touching a foreign exception + void* adjustedPtr = unwind_exception + 1; + if (__getExceptionClass(unwind_exception) == kOurDependentExceptionClass) + adjustedPtr = ((__cxa_dependent_exception*)adjustedPtr - 1)->primaryException; + return adjustedPtr; +} +} // namespace + +// Our implementation of a personality function for handling +// libcxxabi-exceptions. Follows how libcxxabi's __gxx_personality_v0 handles +// exceptions. +[[gnu::used]] extern "C" std::int32_t +_klee_eh_cxx_personality(_Unwind_Exception *exceptionPointer, + const std::size_t num_bytes, + const unsigned char *lp_clauses) { + assert(__cxxabiv1::__isOurExceptionClass(exceptionPointer) && + "unexpected exception class found"); + + __cxa_exception *exception = + cxa_exception_from_exception_unwind_exception(exceptionPointer); + const __shim_type_info *exceptionType = + static_cast<const __shim_type_info *>(exception->exceptionType); + void *const exceptionObjectPtr = get_thrown_object_ptr(exceptionPointer); + + const unsigned char *cur_pos = lp_clauses; + const unsigned char *lp_clauses_end = lp_clauses + num_bytes; + + while (cur_pos < lp_clauses_end) { + const unsigned char type = *cur_pos; + cur_pos += 1; + if (type == 0) { // catch-clause + const void *catcherType = *reinterpret_cast<const void *const *>(cur_pos); + cur_pos += sizeof(const void *); + if (catcherType == NULL) { + // catch-all clause + exception->adjustedPtr = exceptionObjectPtr; + return klee_eh_typeid_for(catcherType); + } + void *adjustedPtr = exceptionObjectPtr; + if (_klee_eh_can_catch(catcherType, exceptionType, adjustedPtr)) { + exception->adjustedPtr = adjustedPtr; + return klee_eh_typeid_for(catcherType); + } + } else { // filter-clause + unsigned char num_filters = type - 1; + while (num_filters > 0) { + const void *catcher = *reinterpret_cast<const void *const *>(cur_pos); + cur_pos += sizeof(const void *); + if (catcher == NULL) { + // catch-all filter clause, should always be entered + exception->adjustedPtr = exceptionObjectPtr; + return -1; + } + void *adjustedPtr = exceptionObjectPtr; + if (_klee_eh_can_catch(catcher, exceptionType, adjustedPtr)) { + exception->adjustedPtr = adjustedPtr; + break; + } + num_filters -= 1; + } + if (num_filters == 0) { + // exception was not one of the given ones -> filter it out + return -1; + } + } + } + + // no handling clause found + return 0; +} + +// Implemented in the SpecialFunctionHandler +extern "C" void _klee_eh_Unwind_RaiseException_impl(void *); + +// Provide an implementation of the Itanium unwinding base-API +// (https://itanium-cxx-abi.github.io/cxx-abi/abi-eh.html#base-abi) +// These functions will be called by language-specific exception handling +// implementations, such as libcxxabi. +extern "C" { +// The main entry point for stack unwinding. This function performs the 2-phase +// unwinding process, calling the personality function as required. We implement +// this mostly inside KLEE as it requires direct manipulation of the state's +// stack. +_Unwind_Reason_Code +_Unwind_RaiseException(struct _Unwind_Exception *exception_object) { + // this will either unwind the stack if a handler is found, or return if not + _klee_eh_Unwind_RaiseException_impl(exception_object); + + // if we couldn't find a handler, return an error + return _URC_END_OF_STACK; +} + +// According to the Itanium ABI: +// "Deletes the given exception object. If a given runtime resumes normal +// execution after catching a foreign exception, it will not know how to delete +// that exception. Such an exception will be deleted by calling +// _Unwind_DeleteException. This is a convenience function that calls the +// function pointed to by the exception_cleanup field of the exception header." +void _Unwind_DeleteException(struct _Unwind_Exception *exception_object) { + // Implemented in the same way as LLVM's libunwind version + if (exception_object->exception_cleanup != NULL) + exception_object->exception_cleanup(_URC_FOREIGN_EXCEPTION_CAUGHT, + exception_object); +} + +// At the LLVM IR level we will encounter the `resume`-Instruction instead of +// calls to this function. LLVM lowers `resume` to a call to `_Unwind_Resume` +// during later compilation stages. +void _Unwind_Resume(struct _Unwind_Exception *exception_object) { + klee_report_error(__FILE__, __LINE__, "_Unwind_Resume should not appear", + "unexpected.err"); +} + +// The rest of these assume a binary execution environment and thus aren't +// supported. While ForcedUnwind could be called by users, it is never actually +// called inside of libcxxabi. The others are usually called by the personality +// function, but we implement that ourselves. +_Unwind_Reason_Code +_Unwind_ForcedUnwind(struct _Unwind_Exception *exception_object, + _Unwind_Stop_Fn stop, void *stop_parameter) { + klee_report_error(__FILE__, __LINE__, "_Unwind_ForcedUnwind not supported", + "unsupported.err"); +} + +_Unwind_Word _Unwind_GetGR(struct _Unwind_Context *context, int index) { + klee_report_error(__FILE__, __LINE__, "_Unwind_GetGR not supported", + "unsupported.err"); +} + +void _Unwind_SetGR(struct _Unwind_Context *context, int index, + _Unwind_Word new_value) { + klee_report_error(__FILE__, __LINE__, "_Unwind_SetGR not supported", + "unsupported.err"); +} + +_Unwind_Word _Unwind_GetIP(struct _Unwind_Context *context) { + klee_report_error(__FILE__, __LINE__, "_Unwind_GetIP not supported", + "unsupported.err"); +} + +void _Unwind_SetIP(struct _Unwind_Context *context, _Unwind_Word new_value) { + klee_report_error(__FILE__, __LINE__, "_Unwind_SetIP not unsupported", + "unsupported.err"); +} + +void *_Unwind_GetLanguageSpecificData(struct _Unwind_Context *context) { + klee_report_error(__FILE__, __LINE__, + "_Unwind_GetLanguageSpecificData not supported", + "unsupported.err"); +} + +_Unwind_Ptr _Unwind_GetRegionStart(struct _Unwind_Context *context) { + klee_report_error(__FILE__, __LINE__, "_Unwind_GetRegionStart not supported", + "unsupported.err"); +} +} \ No newline at end of file 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 <cstdio> + +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 <cstdio> +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 <cstdio> + +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 <cstdio> + +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 <cstdio> + +int main(int argc, char **args) { + unsigned int *x = new unsigned int(0); + char *y = reinterpret_cast<char *>(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 <cassert> + +template <typename X, typename Y> +X multiply(X a, Y b) { + return a * b; +} + +template <int A> +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 <cassert> + +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 <iostream> #include <cstdlib> +#include <iostream> -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 <cstdio> +#include <typeinfo> + +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 <cstdio> +#include <stdexcept> + +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 <iostream> + +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 <iostream> #include "klee/klee.h" +#include <iostream> -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<B*>(v); // well-defined: v of type V*, V base of B, results in B* - dynamic_cast<B*>(a); // undefined behavior: a has type A*, A not a base of B + dynamic_cast<B *>(v); // well-defined: v of type V*, V base of B, results in B* + dynamic_cast<B *>(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<D&>(a); // downcast - B& new_b = dynamic_cast<B&>(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<D &>(a); // downcast + B &new_b = dynamic_cast<B &>(a); // sidecast - Base* b1 = new Base; - if(Derived* d = dynamic_cast<Derived*>(b1)) - { - d->name(); // safe to call - } + Base *b1 = new Base; + if (Derived *d = dynamic_cast<Derived *>(b1)) { + d->name(); // safe to call + } - Base* b2 = new Derived; - if(Derived* d = dynamic_cast<Derived*>(b2)) - { - d->name(); // safe to call - } + Base *b2 = new Derived; + if (Derived *d = dynamic_cast<Derived *>(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 <cstdio> + +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 <cstdio> + +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 <cassert> +#include <cstdio> + +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 <stdio.h> + +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 <cstdio> + +static void x(int a) { + if (a == 7) { + throw static_cast<float>(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 <cassert> +#include <cstdio> + +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 <cstdio> + +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 <cstdio> + +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 <cstdio> + +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 <stdexcept> + +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 <stdexcept> + +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 <cstdio> + +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 <stdio.h> + +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 <cassert> +#include <cstdlib> +#include <exception> + +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 <vector> @@ -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 diff --git a/tools/klee/main.cpp b/tools/klee/main.cpp index 87d7ccf9..61a9f164 100644 --- a/tools/klee/main.cpp +++ b/tools/klee/main.cpp @@ -782,6 +782,8 @@ static const char *modelledExternals[] = { "klee_warning", "klee_warning_once", "klee_stack_trace", + "_klee_eh_Unwind_RaiseException_impl", + "klee_eh_typeid_for", "llvm.dbg.declare", "llvm.dbg.value", "llvm.va_start", @@ -1267,9 +1269,17 @@ int main(int argc, char **argv, char **envp) { llvm::sys::path::append(LibcxxBC, KLEE_LIBCXX_BC_NAME); if (!klee::loadFile(LibcxxBC.c_str(), mainModule->getContext(), loadedModules, errorMsg)) - klee_error("error loading free standing support '%s': %s", - LibcxxBC.c_str(), errorMsg.c_str()); + klee_error("error loading libcxx '%s': %s", LibcxxBC.c_str(), + errorMsg.c_str()); klee_message("NOTE: Using libcxx : %s", LibcxxBC.c_str()); + + SmallString<128> EhCxxPath(Opts.LibraryDir); + llvm::sys::path::append(EhCxxPath, "libklee-eh-cxx.bca"); + if (!klee::loadFile(EhCxxPath.c_str(), mainModule->getContext(), + loadedModules, errorMsg)) + klee_error("error loading libklee-eh-cxx '%s': %s", EhCxxPath.c_str(), + errorMsg.c_str()); + #endif } |