diff --git a/.vscode/settings.json b/.vscode/settings.json index 2161cc5..bd78cce 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,6 +8,15 @@ ] } }, + "cmake.ctest.failurePatterns": [ + { + "regexp": "(.+):(\\d+):(\\d+): error: CHECK: (.+)", + "file": 1, + "line": 2, + "column": 3, + "message": 4, + }, + ], "terminal.integrated.defaultProfile.linux": "CoVer Wrapper Bash", "cmake.configureArgs": [ "-DMPI_HOME=/home/archuser/Desktop/ompi/install", @@ -18,4 +27,10 @@ "COVER_WRAP_TARGET_mpicc": "~/Desktop/ompi/install/bin/mpicc", "COVER_WRAP_TARGET_mpicxx": "~/Desktop/ompi/install/bin/mpicxx", }, + "terminal.integrated.env.linux": { + "COVER_WRAP_TARGET_mpifort": "~/Desktop/ompi/install/bin/mpifort", + "COVER_WRAP_TARGET_mpicc": "~/Desktop/ompi/install/bin/mpicc", + "COVER_WRAP_TARGET_mpicxx": "~/Desktop/ompi/install/bin/mpicxx", + }, + "cmake.ctest.testSuiteDelimiter": "-|::", } diff --git a/CMakeLists.txt b/CMakeLists.txt index fe856ad..12dc5f4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -76,12 +76,16 @@ target_compile_options (ContractLanguage PUBLIC -fexceptions) add_llvm_pass_plugin(CoVerPlugin Passes/Registrar.cpp + Passes/BasicTypes.cpp Passes/ContractManager.cpp Passes/ContractVerifierPreCall.cpp Passes/ContractVerifierPostCall.cpp Passes/ContractVerifierRelease.cpp - Passes/Instrument.cpp + Passes/ContractVerifierParam.cpp + Passes/ContractVerifierAlloc.cpp Passes/ContractPostProcess.cpp + Passes/Intrinsics.cpp + Passes/Instrument.cpp Utils/ContractPassUtility.cpp Include/ContractPassUtility.hpp ) @@ -136,6 +140,12 @@ install( WORLD_READ ) +## +## CoVer Intrinsics definitions +## + +add_subdirectory(Intrinsics) + ## ## Dynamic project ## @@ -196,6 +206,7 @@ endif(MPI_C_FOUND AND Python_FOUND) set(CONTR_PLUGIN_PATH "${CMAKE_INSTALL_PREFIX}/lib/CoVerPlugin.so") set(COVER_DYNAMIC_ANALYSER_PATH ${CMAKE_INSTALL_PREFIX}/lib/libCoVerDynamicAnalyzer.a) +set(COVER_INTRINSICS_LIB_PATH ${CMAKE_INSTALL_PREFIX}/lib/libCoVerIntrinsics.a) set(DSA_PLUGIN_PATH "${CMAKE_INSTALL_PREFIX}/lib/DSA.so") set(CONTR_INCLUDE_PATH "${CMAKE_INSTALL_PREFIX}/include") diff --git a/Dynamic/Analyses/AllocAnalysis.cpp b/Dynamic/Analyses/AllocAnalysis.cpp new file mode 100644 index 0000000..d55d8c9 --- /dev/null +++ b/Dynamic/Analyses/AllocAnalysis.cpp @@ -0,0 +1,61 @@ +#include "AllocAnalysis.h" +#include "BaseAnalysis.h" +#include "DynamicAnalysis.h" +#include "../DynamicUtils.h" + +#include +#include +#include +#include + +int vecContains(std::vector vec, void* f) { + for (int i = 0; i < vec.size(); i++) { + if (vec[i].func == f) return i; + } + return -1; +} + +Fulfillment AllocAnalysis::functionPreCBImpl(void* const& func, CallsiteInfo const& callsite) { + if (func == func_supplier) { + uintptr_t ptr = (uintptr_t)callsite.params[idx].value; + for (std::pair alloc_ptr : allocated) { + if ((uintptr_t)alloc_ptr.first == ptr) return Fulfillment::UNKNOWN; + if (ptr >= alloc_ptr.first && ptr < alloc_ptr.first + alloc_ptr.second) return Fulfillment::UNKNOWN; + } + references.push_back(callsite.location); + return Fulfillment::VIOLATED; + } + return Fulfillment::UNKNOWN; +} + +Fulfillment AllocAnalysis::functionPostCBImpl(void* const& func, CallsiteInfo const& callsite) { + if (int idx = vecContains(mem_allocators, func); idx != -1) { + MemOpFunc_t const& memop = mem_allocators[idx]; + uintptr_t alloc = (uintptr_t)(memop.rwOp->idx == 99 ? callsite.retval : callsite.params[memop.rwOp->idx].value); + if (memop.rwOp->accType == ParamAccess::DEREF) alloc = (uintptr_t)*(void**)alloc; + MathExpr_t const* cur = memop.size; + size_t res = cur->isArgValue ? (size_t)callsite.params[cur->value].value : cur->value; + while (cur->other != nullptr) { + switch (cur->type) { + case UNARY_VALUE: + break; + case MULT: + res *= cur->other->isArgValue ? (size_t)callsite.params[cur->other->value].value : cur->other->value; + break; + } + cur = cur->other; + } + allocated.push_back({alloc, res}); + } + else if (int idx = vecContains(mem_deallocators, func); idx != -1) { + MemOpFunc_t const& memop = mem_deallocators[idx]; + uintptr_t const& target = (uintptr_t)(memop.rwOp->idx == 99 ? callsite.retval : callsite.params[memop.rwOp->idx].value); + for (int i = 0; i < allocated.size(); i++) { + if (target == allocated[i].first) { + allocated.erase(allocated.begin() + i); + break; + } + } + } + return Fulfillment::UNKNOWN; +} diff --git a/Dynamic/Analyses/AllocAnalysis.h b/Dynamic/Analyses/AllocAnalysis.h new file mode 100644 index 0000000..6552c36 --- /dev/null +++ b/Dynamic/Analyses/AllocAnalysis.h @@ -0,0 +1,34 @@ +#pragma once + +#include "BaseAnalysis.h" +#include "DynamicAnalysis.h" +#include +#include + +struct AllocAnalysis : BaseAnalysis { + public: + AllocAnalysis(void const* _func_supplier, AllocOp_t* allocop) : idx(allocop->idx), acc(allocop->accType), func_supplier(_func_supplier) { + for (int i = 0; i < allocop->num_allocators; i++) { + mem_allocators.push_back(allocop->allocators[i]); + } + for (int i = 0; i < allocop->num_deallocators; i++) { + mem_deallocators.push_back(allocop->deallocators[i]); + } + } + + ANALYSIS_PREAMBLE Fulfillment functionPreCBImpl(void* const& func, CallsiteInfo const& callsite); + ANALYSIS_PREAMBLE Fulfillment functionPostCBImpl(void* const& func, CallsiteInfo const& callsite); + ANALYSIS_PREAMBLE Fulfillment memoryCBImpl(CodePtr const& location, void const* const& memory, bool const& isWrite) const { return Fulfillment::UNKNOWN; } + ANALYSIS_PREAMBLE Fulfillment exitCBImpl(CodePtr const& location) const { return Fulfillment::FULFILLED; }; // Evidently none of the callsites were erroneous + + constexpr CallBacks requiredCallbacksImpl() const { return {true, true, false, false}; } + + private: + // Configuration + void const* func_supplier; + int const idx; + ParamAccess const acc; + std::vector> allocated; + std::vector mem_allocators; + std::vector mem_deallocators; +}; diff --git a/Dynamic/Analyses/BaseAnalysis.h b/Dynamic/Analyses/BaseAnalysis.h index 29fa389..bef5877 100644 --- a/Dynamic/Analyses/BaseAnalysis.h +++ b/Dynamic/Analyses/BaseAnalysis.h @@ -6,7 +6,8 @@ enum struct Fulfillment { FULFILLED, UNKNOWN, VIOLATED, INACTIVE }; struct CallBacks { - bool FUNCTION; + bool FUNCTION_PRE; + bool FUNCTION_POST; bool MEMORY_R; bool MEMORY_W; }; @@ -19,7 +20,8 @@ class BaseAnalysis { public: // Event handlers. Return non-unknown if analysis is resolved and no longer needs to be analysed. // onFunctionCall does not forward return address, as it is included in callsiteinfo - inline Fulfillment onFunctionCall(CodePtr const& location, void* const& func, CallsiteInfo const& callsite) { return static_cast(this)->functionCBImpl(func, callsite); }; + inline Fulfillment onFunctionCallPre(CodePtr const& location, void* const& func, CallsiteInfo const& callsite) { return static_cast(this)->functionPreCBImpl(func, callsite); }; + inline Fulfillment onFunctionCallPost(CodePtr const& location, void* const& func, CallsiteInfo const& callsite) { return static_cast(this)->functionPostCBImpl(func, callsite); }; inline Fulfillment onMemoryAccess(CodePtr const& location, void const* const& memory, bool const& isWrite) { return static_cast(this)->memoryCBImpl(std::forward(location), memory, isWrite); }; inline Fulfillment onProgramExit(CodePtr const& location) { return static_cast(this)->exitCBImpl(std::forward(location)); }; diff --git a/Dynamic/Analyses/ParamAnalysis.cpp b/Dynamic/Analyses/ParamAnalysis.cpp new file mode 100644 index 0000000..0bf385f --- /dev/null +++ b/Dynamic/Analyses/ParamAnalysis.cpp @@ -0,0 +1,59 @@ +#include "ParamAnalysis.h" +#include "BaseAnalysis.h" +#include "DynamicAnalysis.h" +#include "../DynamicUtils.h" + +#include +#include + +static constexpr int64_t sign_extend(uintptr_t const ptr, int const size) { + switch (size) { + case 8: return (int64_t)(int8_t)ptr; break; + case 16: return (int64_t)(int16_t)ptr; break; + case 32: return (int64_t)(int32_t)ptr; break; + } + return ptr; +} + +Fulfillment ParamAnalysis::functionPreCBImpl(void* const& func, CallsiteInfo const& callsite) { + if (func != func_supplier) return Fulfillment::UNKNOWN; + + void const* act_callp = callsite.params[idx].value; + if (callval_need_deref) { + act_callp = (const void*)(*(void**)act_callp); + } + int64_t const int_callp = sign_extend((uintptr_t)act_callp, callsite.params[idx].size); + for (const ParamReq_t* req : param_requirements) { + void const* act_req = req->isArg ? callsite.params[(int64_t)req->value].value: req->value; + if (req->reqval_need_deref) { + act_req = (const void*)*(void**)act_req; + } + int64_t const int_req = sign_extend((uintptr_t)act_req, callsite.params[idx].size); + switch (req->comparator) { + case Comparator::EXEQ: + // EXEQ is the exception (pun), it overrides other forbidden values. + if (act_callp == act_req || int_callp == int_req) return Fulfillment::FULFILLED; + continue; + case Comparator::EQ: + if (act_callp == act_req && int_callp == int_req) continue; + references.push_back(callsite.location); return Fulfillment::VIOLATED; + case Comparator::NEQ: + if (act_callp != act_req && int_callp != int_req) continue; + references.push_back(callsite.location); return Fulfillment::VIOLATED; + // Smaller/Larger comparisons dont make sense for pointers. Assume ints from here + case Comparator::GTEQ: + if (int_callp >= int_req) continue; + references.push_back(callsite.location); return Fulfillment::VIOLATED; + case Comparator::GT: + if (int_callp > int_req) continue; + references.push_back(callsite.location); return Fulfillment::VIOLATED; + case Comparator::LTEQ: + if (int_callp <= int_req) continue; + references.push_back(callsite.location); return Fulfillment::VIOLATED; + case Comparator::LT: + if (int_callp < int_req) continue; + references.push_back(callsite.location); return Fulfillment::VIOLATED; + } + } + return Fulfillment::UNKNOWN; +} diff --git a/Dynamic/Analyses/ParamAnalysis.h b/Dynamic/Analyses/ParamAnalysis.h new file mode 100644 index 0000000..7df7811 --- /dev/null +++ b/Dynamic/Analyses/ParamAnalysis.h @@ -0,0 +1,28 @@ +#pragma once + +#include "BaseAnalysis.h" +#include "DynamicAnalysis.h" +#include + +struct ParamAnalysis : BaseAnalysis { + public: + ParamAnalysis(void const* _func_supplier, ParamOp_t* paramop) : idx(paramop->idx), func_supplier(_func_supplier), callval_need_deref(paramop->callval_need_deref) { + for (int i = 0; i < paramop->num_reqs; i++) { + param_requirements.push_back(¶mop->requirements[i]); + } + } + + ANALYSIS_PREAMBLE Fulfillment functionPreCBImpl(void* const& func, CallsiteInfo const& callsite); + ANALYSIS_PREAMBLE Fulfillment functionPostCBImpl(void* const& func, CallsiteInfo const& callsite) { return Fulfillment::UNKNOWN; }; + ANALYSIS_PREAMBLE Fulfillment memoryCBImpl(CodePtr const& location, void const* const& memory, bool const& isWrite) const { return Fulfillment::UNKNOWN; } + ANALYSIS_PREAMBLE Fulfillment exitCBImpl(CodePtr const& location) const { return Fulfillment::FULFILLED; }; // Evidently none of the callsites were erroneous + + constexpr CallBacks requiredCallbacksImpl() const { return {true, false, false, false}; } + + private: + // Configuration + void const* func_supplier; + int const idx; + bool const callval_need_deref; + std::vector param_requirements; +}; diff --git a/Dynamic/Analyses/PostCallAnalysis.cpp b/Dynamic/Analyses/PostCallAnalysis.cpp index d084c41..e906c8c 100644 --- a/Dynamic/Analyses/PostCallAnalysis.cpp +++ b/Dynamic/Analyses/PostCallAnalysis.cpp @@ -22,7 +22,7 @@ PostCallAnalysis::PostCallAnalysis(void const* _func_supplier, CallTagOp_t* call target_funcs = DynamicUtils::getFunctionsForTag(callop->target_tag); } -Fulfillment PostCallAnalysis::functionCBImpl(void* const& func, CallsiteInfo const& callsite) { +Fulfillment PostCallAnalysis::functionPreCBImpl(void* const& func, CallsiteInfo const& callsite) { for (void const* const& target_func : target_funcs) { if (target_func == func) { // Target function found, maybe analysis success diff --git a/Dynamic/Analyses/PostCallAnalysis.h b/Dynamic/Analyses/PostCallAnalysis.h index 73316d6..a2e13b7 100644 --- a/Dynamic/Analyses/PostCallAnalysis.h +++ b/Dynamic/Analyses/PostCallAnalysis.h @@ -10,11 +10,12 @@ struct PostCallAnalysis : public BaseAnalysis { PostCallAnalysis(void const* func_supplier, CallOp_t* callop); PostCallAnalysis(void const* func_supplier, CallTagOp_t* callop); - inline __attribute__((always_inline)) Fulfillment functionCBImpl(void* const& func, CallsiteInfo const& callsite); - inline __attribute__((always_inline)) Fulfillment memoryCBImpl(CodePtr const& location, void const* const& memory, bool const& isWrite) const { return Fulfillment::UNKNOWN; } - inline __attribute__((always_inline)) Fulfillment exitCBImpl(CodePtr const& location); + ANALYSIS_PREAMBLE Fulfillment functionPreCBImpl(void* const& func, CallsiteInfo const& callsite); + ANALYSIS_PREAMBLE Fulfillment functionPostCBImpl(void* const& func, CallsiteInfo const& callsite) { return Fulfillment::UNKNOWN; }; + ANALYSIS_PREAMBLE Fulfillment memoryCBImpl(CodePtr const& location, void const* const& memory, bool const& isWrite) const { return Fulfillment::UNKNOWN; } + ANALYSIS_PREAMBLE Fulfillment exitCBImpl(CodePtr const& location); - constexpr CallBacks requiredCallbacksImpl() const { return {true, false, false}; } + constexpr CallBacks requiredCallbacksImpl() const { return {true, false, false, false}; } private: void SharedInit(void const* _func_supplier, const char* target_str, CallParam_t *params, int64_t num_params); diff --git a/Dynamic/Analyses/PreCallAnalysis.cpp b/Dynamic/Analyses/PreCallAnalysis.cpp index 0235018..315726c 100644 --- a/Dynamic/Analyses/PreCallAnalysis.cpp +++ b/Dynamic/Analyses/PreCallAnalysis.cpp @@ -22,7 +22,7 @@ PreCallAnalysis::PreCallAnalysis(void const* _func_supplier, CallTagOp_t* callop target_funcs = DynamicUtils::getFunctionsForTag(callop->target_tag); } -Fulfillment PreCallAnalysis::functionCBImpl(void* const& func, CallsiteInfo const& callsite) { +Fulfillment PreCallAnalysis::functionPreCBImpl(void* const& func, CallsiteInfo const& callsite) { for (void const* const& target_func : target_funcs) { if (target_func == func) { // Possible match for precall diff --git a/Dynamic/Analyses/PreCallAnalysis.h b/Dynamic/Analyses/PreCallAnalysis.h index 1d4bcbd..ecedf55 100644 --- a/Dynamic/Analyses/PreCallAnalysis.h +++ b/Dynamic/Analyses/PreCallAnalysis.h @@ -12,11 +12,12 @@ struct PreCallAnalysis : BaseAnalysis { PreCallAnalysis(void const* func_supplier, CallOp_t* callop); PreCallAnalysis(void const* func_supplier, CallTagOp_t* callop); - inline __attribute__((always_inline)) Fulfillment functionCBImpl(void* const& func, CallsiteInfo const& callsite); - inline __attribute__((always_inline)) Fulfillment memoryCBImpl(CodePtr const& location, void const* const& memory, bool const& isWrite) const { return Fulfillment::UNKNOWN; } - inline __attribute__((always_inline)) Fulfillment exitCBImpl(CodePtr const& location) const { return Fulfillment::INACTIVE; }; + ANALYSIS_PREAMBLE Fulfillment functionPreCBImpl(void* const& func, CallsiteInfo const& callsite); + ANALYSIS_PREAMBLE Fulfillment functionPostCBImpl(void* const& func, CallsiteInfo const& callsite) { return Fulfillment::UNKNOWN; }; + ANALYSIS_PREAMBLE Fulfillment memoryCBImpl(CodePtr const& location, void const* const& memory, bool const& isWrite) const { return Fulfillment::UNKNOWN; } + ANALYSIS_PREAMBLE Fulfillment exitCBImpl(CodePtr const& location) const { return Fulfillment::INACTIVE; }; - constexpr CallBacks requiredCallbacksImpl() const { return {true, false, false}; } + constexpr CallBacks requiredCallbacksImpl() const { return {true, false, false, false}; } private: void SharedInit(void const* _func_supplier, const char* target_str, CallParam_t *params, int64_t num_params); diff --git a/Dynamic/Analyses/ReleaseAnalysis.cpp b/Dynamic/Analyses/ReleaseAnalysis.cpp index 1bd2955..1264b18 100644 --- a/Dynamic/Analyses/ReleaseAnalysis.cpp +++ b/Dynamic/Analyses/ReleaseAnalysis.cpp @@ -50,10 +50,10 @@ ReleaseAnalysis::ReleaseAnalysis(void const* _func_supplier, ReleaseOp_t* rOP) { CallBacks ReleaseAnalysis::requiredCallbacksImpl() const { if (!forbIsRW) return {true, false, false}; RWOp_t* rwOp = (RWOp_t*)forbiddenOp; - return {true, !rwOp->isWrite, rwOp->isWrite}; + return {true, false, !rwOp->isWrite, rwOp->isWrite}; } -Fulfillment ReleaseAnalysis::functionCBImpl(void* const& func, CallsiteInfo const& callsite) { +Fulfillment ReleaseAnalysis::functionPreCBImpl(void* const& func, CallsiteInfo const& callsite) { if (!forbiddenCallsites.empty()) { // First, check if release for (void const* const& rel_func : rel_funcs) { diff --git a/Dynamic/Analyses/ReleaseAnalysis.h b/Dynamic/Analyses/ReleaseAnalysis.h index a96e296..731e40b 100644 --- a/Dynamic/Analyses/ReleaseAnalysis.h +++ b/Dynamic/Analyses/ReleaseAnalysis.h @@ -8,9 +8,10 @@ struct ReleaseAnalysis : BaseAnalysis { public: ReleaseAnalysis(void const* func_supplier, ReleaseOp_t* rOP); - inline __attribute__((always_inline)) Fulfillment functionCBImpl(void* const& func, CallsiteInfo const& callsite); - inline __attribute__((always_inline)) Fulfillment memoryCBImpl(CodePtr const& location, void const* const& memory, bool const& isWrite); - inline __attribute__((always_inline)) Fulfillment exitCBImpl(CodePtr const& location) const { return Fulfillment::FULFILLED; }; + ANALYSIS_PREAMBLE Fulfillment functionPreCBImpl(void* const& func, CallsiteInfo const& callsite); + ANALYSIS_PREAMBLE Fulfillment functionPostCBImpl(void* const& func, CallsiteInfo const& callsite) { return Fulfillment::UNKNOWN; }; + ANALYSIS_PREAMBLE Fulfillment memoryCBImpl(CodePtr const& location, void const* const& memory, bool const& isWrite); + ANALYSIS_PREAMBLE Fulfillment exitCBImpl(CodePtr const& location) const { return Fulfillment::FULFILLED; }; CallBacks requiredCallbacksImpl() const; diff --git a/Dynamic/CMakeLists.txt b/Dynamic/CMakeLists.txt index d326171..d9eef6d 100644 --- a/Dynamic/CMakeLists.txt +++ b/Dynamic/CMakeLists.txt @@ -1,4 +1,6 @@ add_library(CoVerDynamicAnalyzer STATIC + Analyses/ParamAnalysis.cpp + Analyses/AllocAnalysis.cpp Analyses/PostCallAnalysis.cpp Analyses/PreCallAnalysis.cpp Analyses/ReleaseAnalysis.cpp @@ -7,14 +9,24 @@ add_library(CoVerDynamicAnalyzer STATIC ) set_property(TARGET CoVerDynamicAnalyzer PROPERTY CXX_STANDARD 20) -set_property(TARGET CoVerDynamicAnalyzer PROPERTY UNITY_BUILD ON) +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + set_property(TARGET CoVerDynamicAnalyzer PROPERTY UNITY_BUILD OFF) +else() + set_property(TARGET CoVerDynamicAnalyzer PROPERTY UNITY_BUILD ON) +endif() target_include_directories(CoVerDynamicAnalyzer PUBLIC ../Include/) +find_package(PkgConfig REQUIRED) +pkg_check_modules(FFI REQUIRED libffi) +target_include_directories(CoVerDynamicAnalyzer PRIVATE "${FFI_INCLUDE_DIRS}") + set_property(TARGET CoVerDynamicAnalyzer PROPERTY CXX_VISIBILITY_PRESET hidden) set_property(TARGET CoVerDynamicAnalyzer PROPERTY C_VISIBILITY_PRESET hidden) set_property(TARGET CoVerDynamicAnalyzer PROPERTY VISIBILITY_INLINES_HIDDEN ON) -target_compile_options(CoVerDynamicAnalyzer PUBLIC -fno-rtti -fno-exceptions -fomit-frame-pointer -fno-stack-protector) +target_compile_options(CoVerDynamicAnalyzer PUBLIC -fno-rtti -fno-exceptions -fomit-frame-pointer -fno-stack-protector -fno-math-errno -fno-trapping-math) +target_link_libraries(CoVerDynamicAnalyzer libffi) +target_include_directories(CoVerDynamicAnalyzer PRIVATE "${FFI_LIBRARIES}") if (CMAKE_ADDR2LINE) target_compile_definitions(CoVerDynamicAnalyzer PUBLIC CMAKE_ADDR2LINE="${CMAKE_ADDR2LINE}") diff --git a/Dynamic/DynamicUtils.cpp b/Dynamic/DynamicUtils.cpp index 2ee976c..6841524 100644 --- a/Dynamic/DynamicUtils.cpp +++ b/Dynamic/DynamicUtils.cpp @@ -33,19 +33,19 @@ namespace { } return result; } +} + +namespace DynamicUtils { + std::unordered_map> func_to_tags; + std::unordered_map> tags_to_func; - uint64_t truncate_bits(uintptr_t const val, int const bit_width) { + uint64_t TruncateBits(uintptr_t const val, int const bit_width) { // Mask off unused bits if (bit_width < 64) return val & ((1ULL << bit_width) - 1); return val; } -} - -namespace DynamicUtils { - std::unordered_map> func_to_tags; - std::unordered_map> tags_to_func; void Initialize(ContractDB_t const* DB) { // Initialize Tags @@ -62,11 +62,11 @@ namespace DynamicUtils { bool checkParamMatch(ParamAccess const& acc, ConcreteParam const& contrP, ConcreteParam const& callP) { switch (acc) { case ParamAccess::NORMAL: - return truncate_bits((uintptr_t)contrP.value, contrP.size) == truncate_bits((uintptr_t)callP.value, callP.size); + return TruncateBits((uintptr_t)contrP.value, contrP.size) == TruncateBits((uintptr_t)callP.value, callP.size); case ParamAccess::DEREF: - return truncate_bits(*(uintptr_t const*)contrP.value, callP.size) == (uintptr_t)callP.value; + return TruncateBits(*(uintptr_t const*)contrP.value, callP.size) == (uintptr_t)callP.value; case ParamAccess::ADDROF: - return truncate_bits(*(uintptr_t const*)callP.value, contrP.size) == (uintptr_t)contrP.value; + return TruncateBits(*(uintptr_t const*)callP.value, contrP.size) == (uintptr_t)contrP.value; } __builtin_unreachable(); } diff --git a/Dynamic/DynamicUtils.h b/Dynamic/DynamicUtils.h index 128bbee..652d7b0 100644 --- a/Dynamic/DynamicUtils.h +++ b/Dynamic/DynamicUtils.h @@ -10,6 +10,12 @@ #include #include +#ifdef NDEBUG +#define ANALYSIS_PREAMBLE inline __attribute__((always_inline)) +#else +#define ANALYSIS_PREAMBLE +#endif + using CodePtr = void const*; struct ConcreteParam { void const* value; @@ -19,6 +25,7 @@ struct ConcreteParam { struct CallsiteInfo { CodePtr location; std::vector params; + void const* retval = nullptr; bool operator==(CallsiteInfo const& other) const { return this->location == other.location && params == other.params; } @@ -38,6 +45,9 @@ struct std::hash { namespace DynamicUtils { // Initialize Utils void Initialize(ContractDB_t const* DB); + + // Truncate bits from a raw byte value + uint64_t TruncateBits(uintptr_t const val, int const bit_width); // Check if two parameters match bool checkParamMatch(ParamAccess const& acc, ConcreteParam const& contrP, ConcreteParam const& callP); diff --git a/Dynamic/Hooks.cpp b/Dynamic/Hooks.cpp index f166936..9c6eec3 100644 --- a/Dynamic/Hooks.cpp +++ b/Dynamic/Hooks.cpp @@ -11,6 +11,8 @@ #include #include +#include + #include "DynamicAnalysis.h" #include "DynamicUtils.h" @@ -88,19 +90,86 @@ extern "C" void __attribute__((visibility("default"))) PPDCV_Initialize(int32_t* DynamicUtils::createMessage("Finished Initializing!"); } -extern "C" void __attribute__((visibility("default"))) PPDCV_FunctionCallback(bool isRef, void* function, int32_t num_params, ...) { +ffi_type* getFFIType(int32_t size, bool isFloat) { + switch (size) { + case 0: return &ffi_type_void; + case 16: return &ffi_type_uint16; + case 32: return isFloat ? &ffi_type_float : &ffi_type_uint32; + default: return isFloat ? &ffi_type_double : &ffi_type_pointer; + } +} + +struct cifCache { + void* func; + std::vector arg_types; + ffi_cif cif; +}; +static std::vector cached_cifs; + +extern "C" void* __attribute__((visibility("default"))) PPDCV_FunctionCallback(bool isRef, void* function, int32_t ret_size, int32_t num_params, ...) { CallsiteInfo callsite = { .location = __builtin_return_address(0) }; + callsite.params.reserve(num_params); std::va_list list; + static std::vector ffi_arg_types; + static std::vector ffi_arg_values_ptr; + static std::vector ffi_arg_values_store; + + cifCache cif_c = {nullptr}; + for (cifCache& entry : cached_cifs) { + if (function == entry.func) { + cif_c = entry; + break; + } + } + ffi_arg_values_store.reserve(num_params); + ffi_arg_values_store.clear(); + ffi_arg_values_ptr.reserve(num_params); + ffi_arg_values_ptr.clear(); + if (!cif_c.func) { + cif_c.arg_types.reserve(num_params); + } + va_start(list, num_params); for (int i = 0; i < num_params; i++) { uint32_t param_size = va_arg(list,uint32_t); - void const* param_val = va_arg(list,void*); - callsite.params.push_back({param_val, param_size}); + void* param_val = va_arg(list,void*); + if (param_size >> 16 & 0b100) { + // This is an undefined value. The program must be buggy af + // LLVM is sometimes funny and chooses valid ptrs for undef. Need to undermine this. + param_val = (void*)0xDEADBEEF; + } + if ((param_size >> 16) & 0b1) { + // Need to deref value first + callsite.params.push_back({*(void**)param_val, param_size & 0xFF}); + } else { + callsite.params.push_back({param_val, param_size & 0xFF}); + } + if (!cif_c.func) { + cif_c.arg_types.push_back(getFFIType(param_size >> 8, (param_size >> 16) & 0b10)); + } + ffi_arg_values_store.push_back(param_val); + ffi_arg_values_ptr.push_back(&ffi_arg_values_store[i]); } va_end(list); // Run event handlers and remove analysis if done - HANDLE_CALLBACK(analyses_with_funcCB, onFunctionCall, function, callsite); + HANDLE_CALLBACK(analyses_with_funcPreCB, onFunctionCallPre, function, callsite); + + // Call the intercepted function + if (!cif_c.func) { + cif_c.func = function; + ffi_prep_cif(&cif_c.cif, FFI_DEFAULT_ABI, num_params, getFFIType(ret_size, (ret_size >> 16) & 0b10), cif_c.arg_types.data()); + cached_cifs.push_back(cif_c); + } + cif_c.cif.arg_types = cif_c.arg_types.data(); // Might have been moved, need to keep updated here. + void* res = nullptr; + ffi_call(&cif_c.cif, FFI_FN(function), &res, ffi_arg_values_ptr.data()); + + // Run event handlers again for the postCBs, including the newly returned value + callsite.retval = res; + HANDLE_CALLBACK(analyses_with_funcPostCB, onFunctionCallPost, function, callsite); + + return res; } extern "C" void __attribute__((visibility("default"))) PPDCV_MemRCallback(bool isRef, void const* buf) { diff --git a/Dynamic/Hooks.hpp b/Dynamic/Hooks.hpp index f99d9aa..84f186f 100644 --- a/Dynamic/Hooks.hpp +++ b/Dynamic/Hooks.hpp @@ -12,6 +12,8 @@ #include "Analyses/BaseAnalysis.h" #include "DynamicUtils.h" +#include "Analyses/AllocAnalysis.h" +#include "Analyses/ParamAnalysis.h" #include "Analyses/PreCallAnalysis.h" #include "Analyses/PostCallAnalysis.h" #include "Analyses/ReleaseAnalysis.h" @@ -26,7 +28,7 @@ namespace { std::unordered_map> contrs; - using AnalysisVariant = std::variant; + using AnalysisVariant = std::variant; struct AnalysisPair { ContractFormula_t* formula; @@ -34,14 +36,15 @@ namespace { }; std::unordered_set visitedLocs; - + std::filesystem::path const& coverage_prefix = std::getenv("COVER_COVERAGE_FOLDER") ? std::filesystem::path(std::getenv("COVER_COVERAGE_FOLDER")) : std::filesystem::current_path(); std::unordered_map contract_status; std::unordered_map formula_parents; std::unordered_map toplevel_to_contract; std::vector all_analyses; - std::vector analyses_with_funcCB; + std::vector analyses_with_funcPreCB; + std::vector analyses_with_funcPostCB; std::vector analyses_with_memRCB; std::vector analyses_with_memWCB; std::unordered_map> analysis_references; @@ -57,12 +60,13 @@ namespace { CallBacks reqCB = fastVisit([&](auto& analysis) { return analysis->requiredCallbacks(); }, new_pair.analysis); - if (reqCB.FUNCTION) analyses_with_funcCB.push_back(new_pair); + if (reqCB.FUNCTION_PRE) analyses_with_funcPreCB.push_back(new_pair); + if (reqCB.FUNCTION_POST) analyses_with_funcPostCB.push_back(new_pair); if (reqCB.MEMORY_R) analyses_with_memRCB.push_back(new_pair); if (reqCB.MEMORY_W) analyses_with_memWCB.push_back(new_pair); } - #define HANDLE_CALLBACK(pairs, CB, ...) \ + #define HANDLE_CALLBACK(pairs, CB, ...) {\ void const* location = __builtin_return_address(0);\ if (isRef) visitedLocs.insert(location);\ _Pragma("unroll(5)") for (auto it = pairs.begin(); it < pairs.end();) { \ @@ -76,10 +80,17 @@ namespace { }\ return ++it;\ }, it->analysis);\ - } + }} void validateState(ContractFormula_t* form) { - if (formula_parents[form] && contract_status.contains(formula_parents[form])) return; // If parent already decided return early + if (formula_parents[form] && contract_status.contains(formula_parents[form])) { + if (contract_status[form] == Fulfillment::VIOLATED && contract_status[formula_parents[form]] == Fulfillment::VIOLATED && !formula_parents[formula_parents[form]]) { + // Another child of top-level formula violated. Two possible errors, though maybe just a symptom of the first one. + DynamicUtils::out() << "Note: Possible secondary issue detected! This may be a true FP or a side effect of the previous report.\n"; + formatError(recurseCreateErrorMsg(form)); + } + return; // If parent already decided return early + } if (contract_status[form] != Fulfillment::VIOLATED && !(contract_status[form] == Fulfillment::FULFILLED && formula_parents[form] && formula_parents[form]->conn == XOR)) return; @@ -143,6 +154,16 @@ namespace { if (isPre) DynamicUtils::createMessage("Did not expect releaseop in precond!"); else addAnalysis(form, func_supplier, (ReleaseOp_t*)form->data); break; + case UNARY_PARAM: + if (isPre) addAnalysis(form, func_supplier, (ParamOp_t*)form->data); + else DynamicUtils::createMessage("Did not expect paramop in postcond!"); + break; + case UNARY_ALLOC: + if (isPre) addAnalysis(form, func_supplier, (AllocOp_t*)form->data); + break; // Normal to have alloc in post, but only used by the precond "actual check". + case UNARY_FREE: + if (isPre) DynamicUtils::createMessage("Did not expect freeop in precondition!"); + break; // Normal to have free in post, but only used by the precond "actual check". default: DynamicUtils::createMessage("Unknown top-level operation!"); break; @@ -153,20 +174,28 @@ namespace { } ErrorMessage recurseCreateErrorMsg(ContractFormula_t* form) { - if (contract_status[form] != Fulfillment::VIOLATED) return {}; + if (!contract_status.contains(form) || contract_status[form] != Fulfillment::VIOLATED) return {}; if (form->num_children == 0) { ErrorMessage msg; msg.msg = {std::string("Operation Message (if defined) or contract string: ") + form->msg}; switch (form->conn) { + #warning this should really be in the analyses themselves case UNARY_CALL: case UNARY_CALLTAG: { CallTagOp_t* cOP = (CallTagOp_t*)form->data; msg.msg.push_back(std::string("Did not find call to ") + cOP->target_tag); break; } + case UNARY_PARAM: { + msg.msg.push_back("Invalid param!"); + break; + } + case UNARY_ALLOC: { + msg.msg.push_back("Buffer not allocated or use-after-free!"); + break; + } case UNARY_RELEASE: { - ReleaseOp_t* rOP = (ReleaseOp_t*)form->data; - msg.msg.push_back(std::string("Found forbidden operation!")); + msg.msg.push_back("Found forbidden operation!"); break; } default: __builtin_unreachable(); diff --git a/Grammars/ContractLexer.g4 b/Grammars/ContractLexer.g4 index e2efa00..d574e19 100644 --- a/Grammars/ContractLexer.g4 +++ b/Grammars/ContractLexer.g4 @@ -7,13 +7,14 @@ ContractMarkerExpFail: 'CONTRACTXFAIL'; ContractMarkerExpSucc: 'CONTRACTXSUCC'; PreMarker: 'PRE'; PostMarker: 'POST'; +ParamMarker: 'PARAM'; TagMarker: 'TAGS'; MsgMarker: 'MSG'; ScopePrefix: '{'; ScopePostfix: '}'; String: '"' ([A-Z] | [a-z] | ' ' | '_' | '-' | '!' | '?' | ',' | [0-9])+ '"'; -Variable: ([A-Z] | [a-z]) ([A-Z] | [a-z] | [0-9] | '_')*; +Variable: ([A-Z] | [a-z]) ([A-Z] | [a-z] | [0-9] | '_')+; NatNum: ('0' | [1-9] [0-9]*); ListSep: ','; @@ -27,6 +28,10 @@ TagParam: '$'; Deref: '*'; AddrOf: '&'; +RetSym: 'R'; + +MarkArg: '_arg'; + // All ops must end with '!' to differentiate from variables OPRead: 'read!'; OPWrite: 'write!'; @@ -34,5 +39,19 @@ OPCall: 'call!'; OPCallTag: 'call_tag!'; OPRelease1: 'no!'; OPRelease2: 'until!'; +OPParam: 'param!'; +OPAlloc: 'alloc!'; +OPFree: 'free!'; + OPPrefix: '('; OPPostfix: ')'; +RWOffsetPrefix: '['; +RWOffsetSuffix: ']'; + +ParamForbidEq: '!='; +ParamGt: '>'; +ParamGtEq: '>='; +ParamLt: '<'; +ParamLtEq: '<='; +ParamEqExcept: '^='; +ParamEq: '=='; \ No newline at end of file diff --git a/Grammars/ContractParser.g4 b/Grammars/ContractParser.g4 index 2a82e31..d38b371 100644 --- a/Grammars/ContractParser.g4 +++ b/Grammars/ContractParser.g4 @@ -15,13 +15,18 @@ functags: TagMarker ScopePrefix tagUnit (ListSep tagUnit)* ScopePostfix; tagUnit: Variable (OPPrefix NatNum OPPostfix)?; -expression: primitive | composite; +expression: callOp | releaseOp | paramOp | rwOp; // rwOp only makes sense for alloc though -primitive: readOp | writeOp | callOp; -readOp: OPRead OPPrefix (Deref | AddrOf)? NatNum OPPostfix; -writeOp: OPWrite OPPrefix (Deref | AddrOf)? NatNum OPPostfix; +natExpr: NatNum MarkArg?; +multExpr: Deref mathExpr; +mathOp: multExpr; +mathExpr: natExpr mathOp?; + +rwOp: (OPRead | OPWrite | OPAlloc | OPFree) OPPrefix (Deref | AddrOf)? arg_index=(NatNum | RetSym) (RWOffsetPrefix alloc_size=mathExpr RWOffsetSuffix)? OPPostfix; varMap: (callP=NatNum | TagParam) MapSep (Deref | AddrOf)? contrP=NatNum; callOp: (OPCall | OPCallTag) OPPrefix Variable (ListSep varMap)* OPPostfix; +paramOp: OPParam OPPrefix NatNum MapSep paramReq (ListSep paramReq)* OPPostfix; +paramReq: (ParamEqExcept | ParamEq | ParamForbidEq | ParamGt | ParamGtEq | ParamLt | ParamLtEq) (value=Variable | (value=NatNum MarkArg?)); -composite: releaseOp; -releaseOp: OPRelease1 OPPrefix forbidden=primitive OPPostfix OPRelease2 OPPrefix until=callOp OPPostfix; +relForbidden: rwOp | callOp; +releaseOp: OPRelease1 OPPrefix forbidden=relForbidden OPPostfix OPRelease2 OPPrefix until=callOp OPPostfix; diff --git a/Include/ContractPassUtility.hpp b/Include/ContractPassUtility.hpp index 72e96e5..231c6c6 100644 --- a/Include/ContractPassUtility.hpp +++ b/Include/ContractPassUtility.hpp @@ -28,6 +28,9 @@ using namespace llvm; #define IS_DEBUG (getenv(DEBUG_ENV) != NULL && atoi(getenv(DEBUG_ENV)) == 1) namespace ContractPassUtility { + // Called automatically by ContractManager + void Initialize(Module& M); + template using TransferFunction = std::function; template @@ -66,6 +69,21 @@ namespace ContractPassUtility { * Get Pointer operand of a load, store, GEPinst *or GEPOp*. Last one would not work on normal getPointerOperand! */ const Value* betterGetPointerOperand(const Value* V); + + /* + * Check if V is definitely allocated + */ + bool isTrivialAlloc(const Value* V); + + /* + * Fortran Heuristic: Check if global, if so check if constint and return + */ + ConstantInt* fortCheckAndGetGlbInt(Value* V); + + /* + * Get last storeinst to a call argument, null if it could not be determined + */ + StoreInst* getLastStore(CallBase* CB, int idx, FunctionAnalysisManager* FAM); }; template diff --git a/Include/ContractTree.hpp b/Include/ContractTree.hpp index 47b0579..7ce3ed8 100644 --- a/Include/ContractTree.hpp +++ b/Include/ContractTree.hpp @@ -14,25 +14,55 @@ #include namespace ContractTree { - enum struct OperationType { READ, WRITE, CALL, CALLTAG, RELEASE }; + enum struct FormulaType { AND, OR, XOR, RWOP, READ, WRITE, ALLOC, FREE, CALL, CALLTAG, RELEASE, PARAM }; enum struct ParamAccess { NORMAL, DEREF, ADDROF }; + enum struct MathType { UNARY_VALUE, MULT }; + struct MathExpr { + int value; + bool isArg; + MathType type; + std::shared_ptr other = nullptr; + }; struct Operation { virtual ~Operation() = default; - virtual const OperationType type() const = 0; + virtual const FormulaType type() const = 0; }; struct RWOperation : Operation { const int contrP; const ParamAccess contrParamAccess; - protected: - RWOperation(int _contrP, ParamAccess _acc) : contrP(_contrP), contrParamAccess(_acc) {}; + virtual const FormulaType type() const override { return FormulaType::RWOP; }; + RWOperation(int _contrP, ParamAccess _acc) : contrP(_contrP), contrParamAccess(_acc) {}; }; struct ReadOperation : RWOperation { ReadOperation(int _contrP, ParamAccess _acc) : RWOperation(_contrP, _acc) {}; - virtual const OperationType type() const override { return OperationType::READ; }; + virtual const FormulaType type() const override { return FormulaType::READ; }; }; struct WriteOperation : RWOperation { WriteOperation(int _contrP, ParamAccess _acc) : RWOperation(_contrP, _acc) {}; - virtual const OperationType type() const override { return OperationType::WRITE; }; + virtual const FormulaType type() const override { return FormulaType::WRITE; }; + }; + struct AllocOperation : RWOperation { + const std::shared_ptr size; + AllocOperation(int _contrP, ParamAccess _acc, std::shared_ptr _size) : RWOperation(_contrP, _acc), size(_size) {}; + virtual const FormulaType type() const override { return FormulaType::ALLOC; }; + }; + struct FreeOperation : RWOperation { + FreeOperation(int _contrP, ParamAccess _acc) : RWOperation(_contrP, _acc) {}; + virtual const FormulaType type() const override { return FormulaType::FREE; }; + }; + enum Comparator { + NEQ, GT, GTEQ, LT, LTEQ, EXEQ, EQ + }; + struct ParamRequirement { + Comparator comp; + std::string value; + bool isArg; + }; + struct ParamOperation : Operation { + ParamOperation(int _idx) : idx{_idx} {}; + int idx; + std::vector reqs; + virtual const FormulaType type() const override { return FormulaType::PARAM; }; }; struct CallParam { int callP; @@ -44,22 +74,21 @@ namespace ContractTree { CallOperation(std::string _func, std::vector _params) : Function{_func}, Params{_params} {}; const std::string Function; const std::vector Params; - virtual const OperationType type() const override { return OperationType::CALL; }; + virtual const FormulaType type() const override { return FormulaType::CALL; }; }; struct CallTagOperation : CallOperation { CallTagOperation(std::string _func, std::vector _params) : CallOperation(_func, _params) {}; - virtual const OperationType type() const override { return OperationType::CALLTAG; }; + virtual const FormulaType type() const override { return FormulaType::CALLTAG; }; }; struct ReleaseOperation : Operation { ReleaseOperation(std::shared_ptr opNo, std::shared_ptr opUntil) : Forbidden{opNo}, Until{opUntil} {}; const std::shared_ptr Forbidden; const std::shared_ptr Until; - virtual const OperationType type() const override { return OperationType::RELEASE; }; + virtual const FormulaType type() const override { return FormulaType::RELEASE; }; }; enum struct Fulfillment { FULFILLED, UNKNOWN, BROKEN }; inline const std::string FulfillmentStr(Fulfillment f) { return std::vector{ "Fulfilled", "Unknown", "Violated"}[(int)f]; }; - enum struct FormulaType { AND = 5, OR = 6, XOR = 7 }; struct ContractFormula { ContractFormula(std::vector> _cF, std::string _str, FormulaType _type) : Children(_cF), ExprStr(_str), type(_type) {} ContractFormula(std::string _str) : ExprStr(_str) {} diff --git a/Include/Contracts.F90 b/Include/Contracts.F90 index 2fbca59..508b0cd 100644 --- a/Include/Contracts.F90 +++ b/Include/Contracts.F90 @@ -1,11 +1,19 @@ -! This module defined Declare_Contract, -! which can then be used for the contract declarations by -! passing the function/subroutine as well as the contract string +! This module defines helper functions for contract declarations for CoVer module contract_helper interface + ! Declare_Contract allows the definition of contracts. + ! First argument must be the API function to apply the contract to. + ! Second argument must be the contract string literal subroutine Declare_Contract(funcPtr, contrString) procedure() :: funcPtr character(len=*), intent(in) :: contrString end subroutine + ! Declare_Value allows exposing constant values to contracts for parameter checking. + ! First argument must be a string literal and will be the name used in the relevant contracts + ! Second argument is the value itself. + subroutine Declare_Value(name, value) + character(len=*), intent(in) :: name + class(*), optional, intent(in) :: value(..) + end subroutine end interface end module diff --git a/Include/Contracts.h b/Include/Contracts.h index 5a5d4cd..08df689 100644 --- a/Include/Contracts.h +++ b/Include/Contracts.h @@ -1,5 +1,28 @@ #pragma once +#include +#include +#include + +// Convenience Annotation Macros +// Example: int f() CONTRACT( ); #define CONTRACT(...) __attribute__((annotate("CONTRACT{" #__VA_ARGS__ "}"))) #define CONTRACTXF(...) __attribute__((annotate("CONTRACTXFAIL{" #__VA_ARGS__ "}"))) #define CONTRACTXS(...) __attribute__((annotate("CONTRACTXSUCC{" #__VA_ARGS__ "}"))) + +// Contract Value Names Definitions + +// DO NOT USE THE STRUCT DEFINITIONS DIRECTLY! +// Use CONTRACT_VALUE_PAIR macro instead +struct ContractValuePair { + const char* name; + void* value; +} __attribute__((packed)) typedef ContractValuePair_t; + +#define CONCAT_IMPL( x, y ) x##y +#define MACRO_CONCAT( x, y ) CONCAT_IMPL( x, y ) + +// Define a name for a constant value +// Example: CONTRACT_VALUE_PAIR(zero,0) +#define CONTRACT_VALUE_PAIR(x,y) \ + static ContractValuePair_t MACRO_CONCAT(ContractValueInfo_, __COUNTER__ ) __attribute__((used)) = {#x, (void*)y}; diff --git a/Include/DynamicAnalysis.h b/Include/DynamicAnalysis.h index e06abd4..5758bcd 100644 --- a/Include/DynamicAnalysis.h +++ b/Include/DynamicAnalysis.h @@ -29,6 +29,16 @@ struct CallParam_t { int32_t contrP; ParamAccess accType; }; +enum Comparator : int32_t { NEQ, GT, GTEQ, LT, LTEQ, EXEQ, EQ }; + +// Number must match those defined in ContractTree.hpp! +enum MathType : int32_t { UNARY_VALUE, MULT }; +struct MathExpr_t { + int32_t const value; + bool const isArgValue; + MathType const type; + MathExpr_t const* other = nullptr; +}; struct RWOp_t { int32_t idx; @@ -52,9 +62,49 @@ struct ReleaseOp_t { void** forbidden_op; int32_t forbidden_op_kind; }; +struct ParamReq_t { + const Comparator comparator; + const void* value; + const bool isArg; + const bool reqval_need_deref; +}; +struct ParamOp_t { + const int32_t idx; + const ParamReq_t* requirements; + const int32_t num_reqs; + const bool callval_need_deref; +}; +struct MemOpFunc_t { + const void* func; + const RWOp_t* rwOp; + const MathExpr_t* size = 0; +}; +struct AllocOp_t { + const int32_t idx; + const ParamAccess accType; + MemOpFunc_t* allocators; + int32_t num_allocators; + MemOpFunc_t* deallocators; + int32_t num_deallocators; +}; // Number must match those defined in enums in ContractTree.hpp (operation + connective)! -enum ContractConnective : int32_t { UNARY_READ = 0, UNARY_WRITE = 1, UNARY_CALL = 2, UNARY_CALLTAG = 3, UNARY_RELEASE = 4, AND = 5, OR = 6, XOR = 7 }; +enum ContractConnective : int32_t { + // Connectives + AND, + OR, + XOR, + // Operations + UNARY_RWOP, + UNARY_READ, + UNARY_WRITE, + UNARY_ALLOC, + UNARY_FREE, + UNARY_CALL, + UNARY_CALLTAG, + UNARY_RELEASE, + UNARY_PARAM +}; struct ContractFormula_t { ContractFormula_t* children; int32_t num_children; @@ -89,7 +139,7 @@ extern "C" { // Callback function declarations void PPDCV_Initialize(int32_t* argc, char*** argv, ContractDB_t const* DB); -void PPDCV_FunctionCallback(bool isRel, void* function, int32_t num_params, ...); // relevancy, funcptr, num params, then sizeof param and param each +void* PPDCV_FunctionCallback(bool isRel, void* function, int32_t ret_size, int32_t num_params, ...); // relevancy, funcptr, num params, then sizeof param and param each void PPDCV_MemRCallback(bool const isRel, void const* buf); void PPDCV_MemWCallback(bool const isRel, void const* buf); diff --git a/Intrinsics/CMakeLists.txt b/Intrinsics/CMakeLists.txt new file mode 100644 index 0000000..1e93814 --- /dev/null +++ b/Intrinsics/CMakeLists.txt @@ -0,0 +1,17 @@ +add_library(CoVerIntrinsics STATIC + Intrinsics.c +) + +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + set_property(TARGET CoVerIntrinsics PROPERTY UNITY_BUILD OFF) +else() + set_property(TARGET CoVerIntrinsics PROPERTY UNITY_BUILD ON) +endif() + +set_property(TARGET CoVerIntrinsics PROPERTY C_VISIBILITY_PRESET hidden) +set_property(TARGET CoVerIntrinsics PROPERTY VISIBILITY_INLINES_HIDDEN ON) + +target_compile_options(CoVerIntrinsics PUBLIC -fno-rtti -fno-exceptions -fomit-frame-pointer -fno-stack-protector) + +set_property(TARGET CoVerIntrinsics PROPERTY POSITION_INDEPENDENT_CODE ON) +install(TARGETS CoVerIntrinsics DESTINATION lib) diff --git a/Intrinsics/Intrinsics.c b/Intrinsics/Intrinsics.c new file mode 100644 index 0000000..f982c47 --- /dev/null +++ b/Intrinsics/Intrinsics.c @@ -0,0 +1,43 @@ +#include +#include +#include +#include + +// Stack allocation intrinsics (i.e. for IR allocas) +void __attribute__((visibility("default"))) CoVer_AllocStack(void const* ptr, size_t size) {}; +void __attribute__((visibility("default"))) CoVer_FreeStack(void const* ptr) {}; + +// Global "allocation" intrinsic (i.e. for Global variables or pseudoglobals in fortran) +void __attribute__((visibility("default"))) CoVer_RegisterGlobal(void const* ptr, int64_t size) {}; + +// Fortran intrinsics - allocate(), wrapped by CoVer_FPointerAllocate +// Example IR for allocate(buf(2,2)): +// call void @_FortranAPointerSetBounds(ptr @_QFEbuf, i32 0, i64 2, i64 2), !dbg !82 +// call void @_FortranAPointerSetBounds(ptr @_QFEbuf, i32 1, i64 2, i64 2), !dbg !82 +// %23 = call i32 @_FortranAPointerAllocate(ptr @_QFEbuf, i1 false, ptr null, ptr @_QQclX3a61d3c3006198469069f977f45ff921, i32 15), !dbg !82 +void __attribute__((weak)) _FortranAPointerSetBounds(void*, int32_t, int64_t, int64_t); +int32_t __attribute__((weak)) _FortranAPointerAllocate(void*, bool, void*, void*, int32_t); +int32_t __attribute__((weak)) _FortranAPointerDeallocate(void*, bool, void*, void*, int32_t); + +int32_t __attribute__((visibility("default"))) CoVer_FPointerAllocate(void* ptr, int64_t size, int num_dims, ...) { + va_list list; + va_start(list, num_dims); + for (int i = 0; i < num_dims; i++) { + int dim_idx = va_arg(list, int32_t); + int64_t start_idx = va_arg(list, int64_t); + int64_t end_idx = va_arg(list, int64_t); + _FortranAPointerSetBounds(ptr, dim_idx, start_idx, end_idx); + } + bool palloc_arg1 = va_arg(list, int); + void* palloc_arg2 = va_arg(list, void*); + void* fileArg = va_arg(list, void*); + int32_t palloc_arg4 = va_arg(list, int32_t); + return _FortranAPointerAllocate(ptr, palloc_arg1, palloc_arg2, fileArg, palloc_arg4); +} + +// Fortran intrinsics - deallocate(), wrapped by CoVer_FPointerDeallocate +// Example IR for deallocate(buf): +// %54 = call i32 @_FortranAPointerDeallocate(ptr @_QFEbuf, i1 false, ptr null, ptr @_QQclX67d9a87547f1793fa4a21c08cb286920, i32 18), !dbg !92 +int32_t __attribute__((visibility("default"))) CoVer_FPointerDeallocate(void* ptr, bool arg1, void* arg2, void* fileArg, int32_t arg4) { + return _FortranAPointerDeallocate(ptr, arg1, arg2, fileArg, arg4); +} diff --git a/LangCode/ContractDataVisitor.cpp b/LangCode/ContractDataVisitor.cpp index 5d4eba8..907de0b 100644 --- a/LangCode/ContractDataVisitor.cpp +++ b/LangCode/ContractDataVisitor.cpp @@ -68,24 +68,52 @@ std::any ContractDataVisitor::visitExpression(ContractParser::ExpressionContext return ContractExpression(ctx->getText(), opPtr); } -std::any ContractDataVisitor::visitReadOp(ContractParser::ReadOpContext *ctx) { - ParamAccess acc = ParamAccess::NORMAL; - if (ctx->Deref()) - acc = ParamAccess::DEREF; - if (ctx->AddrOf()) - acc = ParamAccess::ADDROF; - std::shared_ptr op = std::make_shared(std::stoi(ctx->NatNum()->getText()), acc); - return op; +std::any ContractDataVisitor::visitMathExpr(ContractParser::MathExprContext* ctx) { + MathExpr expr; + expr.isArg = ctx->natExpr()->MarkArg(); + expr.value = std::stoi(ctx->natExpr()->NatNum()->getText()); + if (ctx->mathOp()) { + if (ctx->mathOp()->multExpr()) { + expr.type = MathType::MULT; + expr.other = std::any_cast>(visitMathExpr(ctx->mathOp()->multExpr()->mathExpr())); + } + } else { + expr.type = MathType::UNARY_VALUE; + } + return std::make_shared(expr); } -std::any ContractDataVisitor::visitWriteOp(ContractParser::WriteOpContext *ctx) { + +std::any ContractDataVisitor::visitRwOp(ContractParser::RwOpContext *ctx) { ParamAccess acc = ParamAccess::NORMAL; - if (ctx->Deref()) - acc = ParamAccess::DEREF; - if (ctx->AddrOf()) - acc = ParamAccess::ADDROF; - std::shared_ptr op = std::make_shared(std::stoi(ctx->NatNum()->getText()), acc); + if (ctx->Deref()) acc = ParamAccess::DEREF; + if (ctx->AddrOf()) acc = ParamAccess::ADDROF; + int idx = ctx->RetSym() ? 99 : std::stoi(ctx->arg_index->getText()); + std::shared_ptr op; + if (ctx->OPRead()) + op = std::make_shared(idx, acc); + else if (ctx->OPWrite()) + op = std::make_shared(idx, acc); + else if (ctx->OPAlloc()) + op = std::make_shared(idx, acc, ctx->alloc_size ? std::any_cast>(visitMathExpr(ctx->alloc_size)) : std::make_shared(0, false, MathType::UNARY_VALUE)); + else if (ctx->OPFree()) + op = std::make_shared(idx, acc); return op; } +std::any ContractDataVisitor::visitParamOp(ContractParser::ParamOpContext *ctx) { + ParamOperation pOP(std::stoi(ctx->NatNum()->getText())); + for (ContractParser::ParamReqContext* req : ctx->paramReq()) { + Comparator comp; + if (req->ParamForbidEq()) comp = Comparator::NEQ; + if (req->ParamGt()) comp = Comparator::GT; + if (req->ParamGtEq()) comp = Comparator::GTEQ; + if (req->ParamLt()) comp = Comparator::LT; + if (req->ParamLtEq()) comp = Comparator::LTEQ; + if (req->ParamEqExcept()) comp = Comparator::EXEQ; + if (req->ParamEq()) comp = Comparator::EQ; + pOP.reqs.push_back({comp, req->value->getText(), req->MarkArg() != nullptr}); + } + return std::static_pointer_cast(std::make_shared(pOP)); +} std::any ContractDataVisitor::visitCallOp(ContractParser::CallOpContext *ctx) { std::vector params; for (ContractParser::VarMapContext* param : ctx->varMap()) { diff --git a/LangCode/ContractDataVisitor.hpp b/LangCode/ContractDataVisitor.hpp index a1c107a..9c63f1a 100644 --- a/LangCode/ContractDataVisitor.hpp +++ b/LangCode/ContractDataVisitor.hpp @@ -3,6 +3,7 @@ #include #include +#include "ContractParser.h" #include "ContractParserBaseVisitor.h" class ContractDataVisitor : public ContractParserBaseVisitor { @@ -11,8 +12,9 @@ class ContractDataVisitor : public ContractParserBaseVisitor { std::any visitExprList(ContractParser::ExprListContext *ctx) override; std::any visitExprFormula(ContractParser::ExprFormulaContext *ctx) override; std::any visitExpression(ContractParser::ExpressionContext *ctx) override; - std::any visitReadOp(ContractParser::ReadOpContext *ctx) override; - std::any visitWriteOp(ContractParser::WriteOpContext *ctx) override; + std::any visitMathExpr(ContractParser::MathExprContext* ctx) override; + std::any visitRwOp(ContractParser::RwOpContext *ctx) override; + std::any visitParamOp(ContractParser::ParamOpContext *ctx) override; std::any visitCallOp(ContractParser::CallOpContext *ctx) override; std::any visitReleaseOp(ContractParser::ReleaseOpContext *ctx) override; diff --git a/Passes/BasicTypes.cpp b/Passes/BasicTypes.cpp new file mode 100644 index 0000000..a7a00f7 --- /dev/null +++ b/Passes/BasicTypes.cpp @@ -0,0 +1,37 @@ +#include "BasicTypes.hpp" + +#include +#include +#include +#include + +using namespace llvm; + +BasicTypesAnalysis::BasicTypes BasicTypesAnalysis::run(Module &M, ModuleAnalysisManager &AM) { + BasicTypes types; + + DIBuilder DIB(M); + + // Basic Types + types.Ptr_Type = PointerType::get(M.getContext(), 0); + types.TypeToMD[types.Ptr_Type] = DIB.createBasicType("void*", 64, dwarf::DW_TAG_pointer_type); + + types.Int_Type = IntegerType::get(M.getContext(), 32); + types.TypeToMD[types.Int_Type] = DIB.createBasicType("int32_t", 32, dwarf::DW_ATE_signed); + + types.Int64_Type = IntegerType::get(M.getContext(), 64); + types.TypeToMD[types.Int64_Type] = DIB.createBasicType("int64_t", 64, dwarf::DW_ATE_signed); + + types.Bool_Type = IntegerType::get(M.getContext(), 1); + types.TypeToMD[types.Bool_Type] = DIB.createBasicType("bool", 1, dwarf::DW_ATE_boolean); + + types.Void_Type = Type::getVoidTy(M.getContext()); + types.TypeToMD[types.Void_Type] = nullptr; + + // Basic Constants + types.Null_Const = ConstantPointerNull::getNullValue(types.Ptr_Type); + + DIB.finalize(); + + return types; +} diff --git a/Passes/BasicTypes.hpp b/Passes/BasicTypes.hpp new file mode 100644 index 0000000..a0dd278 --- /dev/null +++ b/Passes/BasicTypes.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace llvm { + +class BasicTypesAnalysis : public AnalysisInfoMixin { + public: + static inline llvm::AnalysisKey Key; + //Result Type + class Result { + friend class BasicTypesAnalysis; + public: + PointerType* Ptr_Type; + IntegerType* Bool_Type; + Constant* getBool(bool x) { return ConstantInt::getBool(Bool_Type, x); } + IntegerType* Int_Type; + ConstantInt* getInt(int x) { return ConstantInt::get(Int_Type, x); } + IntegerType* Int64_Type; + ConstantInt* getInt64(int x) { return ConstantInt::get(Int64_Type, x); } + Type* Void_Type; + Constant* Null_Const; + bool invalidate(Module &, PreservedAnalyses const&, ModuleAnalysisManager::Invalidator const&) const { + return false; + } + Metadata* getMDForType(Type const* T) const { + if (TypeToMD.contains(T)) return TypeToMD.at(T); + errs() << "BasicTypes: Queried unknown type "; + T->print(errs()); + errs() << "!\n"; + return nullptr; + } + private: + std::map TypeToMD; + } typedef BasicTypes; + // Run Analysis + BasicTypes run(Module &M, ModuleAnalysisManager &AM); +}; + +} diff --git a/Passes/ContractManager.cpp b/Passes/ContractManager.cpp index 3c988d5..c48bbf4 100644 --- a/Passes/ContractManager.cpp +++ b/Passes/ContractManager.cpp @@ -11,10 +11,13 @@ #include #include +#include #include #include +#include #include #include +#include #include #include @@ -51,9 +54,25 @@ ContractManagerAnalysis::ContractDatabase ContractManagerAnalysis::run(Module &M extractFromAnnotations(M); extractFromFunction(M); - std::stringstream s; - s << "CoVer: Parsed contracts after " << std::fixed << std::chrono::duration(std::chrono::system_clock::now() - curDatabase.start_time).count() << "s\n"; - errs() << s.str(); + // Annotations done, now add value pairs to database + for (GlobalVariable& GV : M.globals()) { + if (GV.getName().contains("ContractValueInfo_")) { + Constant* data = GV.getInitializer(); + StringRef name = dyn_cast(dyn_cast(data->getOperand(0))->getInitializer())->getAsCString(); + Value* val = data->getOperand(1); + if (ConstantExpr* CE = dyn_cast(data->getOperand(1))) { + if (isa(CE->getAsInstruction())) { + val = CE->getOperand(0); + } + } + addValueDefinition(name.str(), val); + } + } + + std::string timestr = std::format("CoVer: Parsed contracts after {}s\n", std::chrono::duration(std::chrono::system_clock::now() - curDatabase.start_time).count()); + errs() << timestr; + + ContractPassUtility::Initialize(M); return curDatabase; } @@ -61,7 +80,7 @@ ContractManagerAnalysis::ContractDatabase ContractManagerAnalysis::run(Module &M void ContractManagerAnalysis::extractFromAnnotations(const Module& M) { GlobalVariable* Annotations = M.getGlobalVariable("llvm.global.annotations"); if (Annotations == nullptr) { - errs() << "Note: No string annotations found.\n"; + errs() << "Note: No contract annotations found in Function declarations.\n"; return; } @@ -76,7 +95,6 @@ void ContractManagerAnalysis::extractFromAnnotations(const Module& M) { } void ContractManagerAnalysis::extractFromFunction(Module& M) { - std::vector to_remove; for (Function& F : M) { F.removeFnAttr(Attribute::NoInline); if (F.getName().starts_with("contract_definitions_fort")) { @@ -84,20 +102,26 @@ void ContractManagerAnalysis::extractFromFunction(Module& M) { errs() << "Contract definition by function body failed, function body not found!\n"; continue; } - const BasicBlock& BB = F.getEntryBlock(); // Exactly one basic block allowed, so this is ok - for (const Instruction& I : BB) { - if (const CallBase* CB = dyn_cast(&I)) { + BasicBlock& BB = F.getEntryBlock(); // Exactly one basic block allowed, so this is ok + for (Instruction& I : BB) { + if (CallBase* CB = dyn_cast(&I)) { // Only care about this intrinsic #warning TODO probably should figure out a less hacky way. if (CB->getCalledFunction()->getName() != "llvm.memmove.p0.p0.i64" && CB->getCalledFunction()->getName() != "llvm.memcpy.p0.p0.i64") continue; - // Add CONTRACT { ... } brace. Its explicitly needed for C(++) to make sure we are not parsing irrelevant stuff, - // but for fortran its already implicit in declare_contract, making it superfluous - std::string CallStr = "CONTRACT { " + ((ConstantDataArray*)((GlobalVariable*)CB->getArgOperand(1))->getInitializer())->getAsString().str() + " }"; + std::string CallStr = ((ConstantDataArray*)((GlobalVariable*)CB->getArgOperand(1))->getInitializer())->getAsString().str(); // Call is from memmove -> insertvalue -> extractvalue -> funccall. on -O0, and memcpy -> funccall on -O1 and above - const CallBase* ContrCall = (CallBase*)(isa(*CB->getArgOperand(0)->user_begin()) ? *CB->getArgOperand(0)->user_begin() : *CB->getArgOperand(0)->user_begin()->user_begin()->user_begin()->user_begin()); + Instruction const* cur = CB->getNextNode(); + while (cur && !isa(cur)) cur = cur->getNextNode(); + if (!cur) { + errs() << "CRITICAL: Unable to determine a contract definition!\n"; + continue; + } + CallBase const* ContrCall = dyn_cast(cur); if (ContrCall->getCalledOperand()->getName() == "declare_contract_") { const Function* ContrSup = (Function*)ContrCall->getArgOperand(0); - if (ContrSup->hasOneUser()) continue; // Only used here where the contract is defined. No need to verify. + // Check if this function is actually used in the code apart from the contract definition. + // If not, no need to analyse this contract and can safely skip it. + if (ContrSup->hasOneUser()) continue; bool has_callsite = false; for (const User* U : ContrSup->users() ) { if (const CallBase* CB = dyn_cast(U)) { @@ -108,17 +132,69 @@ void ContractManagerAnalysis::extractFromFunction(Module& M) { } } if (!has_callsite) continue; - addContract(CallStr, (Function*)(ContrCall->getArgOperand(0))); + // Add CONTRACT { ... } brace. Its explicitly needed for C(++) to make sure we are not parsing irrelevant annotations, + // but for Fortran its already implicit in declare_contract, making it superfluous + addContract("CONTRACT { " + CallStr + " }", (Function*)(ContrCall->getArgOperand(0))); + } else if (ContrCall->getCalledOperand()->getName() == "declare_value_") { + #warning really super duper should find out a less hacky way + // If contr value is nullptr, its trivial + if (ContrCall->getArgOperand(1) == ConstantPointerNull::getNullValue(PointerType::get(M.getContext(), 0))) { + addValueDefinition(CallStr, ContrCall->getArgOperand(1)); + continue; + } + + // This is the start of a dirty heuristic. If it breaks in the future, good luck to you + // Start at the third instr after memmove. This skips the string parameter init (putting it in a struct with its length) + Instruction* ImportantInst = CB->getNextNode()->getNextNode()->getNextNode(); + Value* result = nullptr; + + // Possibility 1: StoreInst saving an integer into a ptr, where the integer is what we want + if (StoreInst* SI = dyn_cast(ImportantInst)) { + if (isa(SI->getValueOperand())) { + result = SI->getValueOperand(); + } + } + + // Possibility 2: SExtInst, then stuff, then an InsertValueInst with index 0 where that is what we want + if (SExtInst* SEI = dyn_cast(ImportantInst)) { + for (Instruction* cur = SEI; cur && !isa(cur); cur = cur->getNextNode()) { + if (InsertValueInst* IVI = dyn_cast(cur)) { + if (IVI->hasIndices() && IVI->getIndices()[0] == 0) { + result = IVI->getInsertedValueOperand(); + } + } + } + } + + // Possibility 3: GEPInst, directly after a StoreInst or LoadInst with correct value + if (GetElementPtrInst* GEP = dyn_cast(ImportantInst)) { + if (StoreInst* SI = dyn_cast(GEP->getNextNode())) { + result = SI->getValueOperand(); + } else if (LoadInst* LI = dyn_cast(GEP->getNextNode())) { + result = LI->getPointerOperand(); + } + } + + // Check for Fortran mangling stuff + if (!result) { + errs() << "Could not decipher call to Declare_Value for " << CallStr << "!\n"; + } else { + if (result->getName().starts_with("_QQ") && isa(result)) { + StringRef result_stem = result->getName().split('.').first; + for (GlobalVariable& GV : M.globals()) { + if (GV.getName().starts_with(result_stem)) { + addValueDefinition(CallStr, &GV); + } + } + } else { + addValueDefinition(CallStr, result); + } + } } } } - // This function is unreachable, and should definitely not be analysed, so no need to compile. Drop it - to_remove.push_back(&F); } } - // Remove unneeded functions - for (Function* F : to_remove) - F->eraseFromParent(); } void ContractManagerAnalysis::addContract(std::string contract, Function* F) { @@ -151,6 +227,13 @@ void ContractManagerAnalysis::addContract(std::string contract, Function* F) { curDatabase.Tags[newCtr.F].insert(curDatabase.Tags[newCtr.F].end(), newCtr.Data.Tags.begin(), newCtr.Data.Tags.end()); } +void ContractManagerAnalysis::addValueDefinition(std::string name, Value* val) { + if (IS_DEBUG) { + WithColor(errs(), HighlightColor::Remark) << "[ContractManager] IR Value \"" << *val << "\" stored for contract value \"" << name << "\"\n"; + } + curDatabase.ContractVariableData[name].insert(val); +} + const std::vector> ContractManagerAnalysis::linearizeContractFormula(const std::shared_ptr contrF) { if (contrF->Children.empty()) { return { std::static_pointer_cast(contrF) }; diff --git a/Passes/ContractManager.hpp b/Passes/ContractManager.hpp index d55f22d..4b9b1df 100644 --- a/Passes/ContractManager.hpp +++ b/Passes/ContractManager.hpp @@ -7,7 +7,9 @@ #include #include #include +#include #include +#include #include #include "ContractTree.hpp" @@ -39,6 +41,7 @@ class ContractManagerAnalysis : public AnalysisInfoMixin Contracts; // For postprocessing only std::vector LinearizedContracts; // For verification passes std::map> Tags; + std::map> ContractVariableData; std::chrono::time_point start_time; bool allowMultiReports = false; Json::Value processedReports; @@ -47,7 +50,7 @@ class ContractManagerAnalysis : public AnalysisInfoMixin +#include +#include +#include +#include +#include +#include + +using namespace llvm; +using namespace ContractTree; + +PreservedAnalyses ContractVerifierAllocPass::run(Module &M, + ModuleAnalysisManager &AM) { + DB = &AM.getResult(M); + MAM = &AM; + + // First, build list of all allocating funcs + for (ContractManagerAnalysis::LinearizedContract const& C : DB->LinearizedContracts) { + for (const std::shared_ptr Expr : C.Post) { + switch (Expr->OP->type()) { + case FormulaType::ALLOC: + if (!AllocFuncs.contains(C.F)) AllocFuncs[C.F] = {}; + AllocFuncs[C.F].insert(static_cast(Expr->OP.get())); + break; + case FormulaType::FREE: + if (!FreeFuncs.contains(C.F)) FreeFuncs[C.F] = {}; + FreeFuncs[C.F].insert(static_cast(Expr->OP.get())); + break; + default: continue; + } + *Expr->Status = Fulfillment::FULFILLED; // Always fulfilled. + } + } + + // Now, do analysis + for (ContractManagerAnalysis::LinearizedContract const& C : DB->LinearizedContracts) { + for (const std::shared_ptr Expr : C.Pre) { + if (*Expr->Status != Fulfillment::UNKNOWN) continue; + // Contract has a precondition + if (Expr->OP->type() != FormulaType::ALLOC) continue; + const AllocOperation* AllocOp = dynamic_cast(Expr->OP.get()); + C.DebugInfo->push_back("[ContractVerifierAlloc] Attempting to verify expression: " + Expr->ExprStr); + std::string err; + AllocStatusVal val = checkAllocReq(AllocOp, C, *Expr, M, C.F, err); + if (!err.empty()) { + errs() << err << "\n"; + *Expr->Status = Fulfillment::BROKEN; + return PreservedAnalyses::all(); + } + *Expr->Status = val == AllocStatusVal::ERROR ? Fulfillment::BROKEN : Fulfillment::FULFILLED; + } + } + return PreservedAnalyses::all(); +} + +struct IterTypeAlloc { + std::vector err; + std::vector dbg; + int param; + const Function* F; +}; + +ContractVerifierAllocPass::AllocStatus ContractVerifierAllocPass::transferAllocStat(AllocStatus cur, const Instruction* I, void* data) { + if (cur.CurVal == AllocStatusVal::ERROR) return cur; + if (cur.CurVal == AllocStatusVal::ALLOC) return cur; + + IterTypeAlloc* Data = static_cast(data); + + // Propagate allocations + if (StoreInst const* SI = dyn_cast(I)) { + if (cur.hasAllocInfo(SI->getValueOperand())) { + cur.addCopy(SI->getPointerOperand(), SI->getValueOperand(), I->getModule()->getFunction("_QQmain") ? ParamAccess::NORMAL : ParamAccess::ADDROF); + } + } + + // Propagate allocations - Pretty much just Fortran from here... + if (CallBase const* CB = dyn_cast(I)) { + if (CB->getCalledFunction() && CB->getCalledFunction()->getName().starts_with("llvm.memcpy.p0.p0")) { + Value* src = CB->getArgOperand(1); + Value* dest = CB->getArgOperand(0); + if (ContractPassUtility::isTrivialAlloc(src)) { + cur.addAllocatedValue(dest); + } + } + } + if (LoadInst const* LI = dyn_cast(I)) { + if (ContractPassUtility::isTrivialAlloc(LI->getPointerOperand())) { + cur.addAllocatedValue(LI); + } else if (cur.hasAllocInfo(LI->getPointerOperand())) { + cur.addCopy(LI, LI->getPointerOperand(), cur.getAllocInfo(LI->getPointerOperand()).acc); + } + } + if (GetElementPtrInst const* GEP = dyn_cast(I)) { + if (cur.hasAllocInfo(GEP->getPointerOperand())) { + cur.addCopy(GEP, GEP->getPointerOperand(), cur.getAllocInfo(GEP->getPointerOperand()).acc); + } + } + if (InsertValueInst const* IVI = dyn_cast(I)) { + if (cur.hasAllocInfo(IVI->getInsertedValueOperand())) { + cur.addCopy(IVI, IVI->getInsertedValueOperand(), cur.getAllocInfo(IVI->getInsertedValueOperand()).acc); + } else if (ContractPassUtility::isTrivialAlloc(IVI->getInsertedValueOperand())) { + cur.addAllocatedValue(IVI); + } + } + if (ExtractValueInst const* EVI = dyn_cast(I)) { + if (cur.hasAllocInfo(EVI->getAggregateOperand())) { + cur.addCopy(EVI, EVI->getAggregateOperand(), cur.getAllocInfo(EVI->getAggregateOperand()).acc); + } + } + if (IntToPtrInst const* ITPI = dyn_cast(I)) { + if (cur.hasAllocInfo(ITPI->getOperand(0))) { + cur.addCopy(ITPI, ITPI->getOperand(0), cur.getAllocInfo(ITPI->getOperand(0)).acc); + } + } + + if (const CallBase* CB = dyn_cast(I)) { + // Check for alloc/freefunc. Stack alloc better handled by isTriviallyAlloc. + if (AllocFuncs.contains(CB->getCalledOperand()) && CB->getCalledOperand()->getName() != "CoVer_AllocStack" && CB->getCalledOperand()->getName() != "CoVer_RegisterGlobal") { + for (const AllocOperation* alloc : AllocFuncs[CB->getCalledOperand()]) { + if (alloc->contrP == 99) cur.addAllocatedValue(CB, alloc->contrParamAccess); + else cur.addAllocatedValue(CB->getArgOperand(alloc->contrP), alloc->contrParamAccess); + } + // Dont return here! Maybe it also is contr sup + } + if (FreeFuncs.contains(CB->getCalledFunction()) && CB->getCalledOperand()->getName() != "CoVer_FreeStack") { + for (const FreeOperation* freeOp : FreeFuncs[CB->getCalledFunction()]) { + #warning TODO perform free, remove stuff from candidate tree + cur.freeValue(CB->getArgOperand(freeOp->contrP)); + } + // Dont return here! Maybe it also is contr sup + } + if (CB->getCalledFunction() == Data->F) { + // Found contract supplier. Check if param is allocated + if (ContractPassUtility::isTrivialAlloc(CB->getArgOperand(Data->param))) { + cur.CurVal = AllocStatusVal::ALLOC; + return cur; + } + // Not trivial, check if explicitly allocated + for (std::pair Candidate : cur.candidates()) { + if (ContractPassUtility::checkParamMatch(Candidate.first, CB->getArgOperand(Data->param), Candidate.second.acc, MAM)) { + // Success! + cur.CurVal = AllocStatusVal::ALLOC; + return cur; + } + } + // Any required parameter not used by any candidate + cur.CurVal = AllocStatusVal::ERROR; + Data->err.push_back({ + .error_id = "Alloc", + .text = std::format("Value in argument index {} of {} in {} is not allocated!", Data->param, Data->F->getName().str(), ContractPassUtility::getInstrLocStr(CB)), + .references = {ContractPassUtility::getFileReference(CB)}, + }); + return cur; + } + } + // Not a call. Just forward info + return cur; +} + +std::pair ContractVerifierAllocPass::mergeAllocStat(AllocStatus prev, AllocStatus cur, const Instruction* I, void* data) { + AllocStatus intersect = cur.intersect(prev); + return {intersect, intersect.CurVal > prev.CurVal}; +} + +ContractVerifierAllocPass::AllocStatusVal ContractVerifierAllocPass::checkAllocReq(const AllocOperation* AllocOp, ContractManagerAnalysis::LinearizedContract const& C, ContractExpression const& Expr, Module const& M, const Function* F, std::string& err) { + const Function* mainF = M.getFunction("main"); + if (!mainF) { + err = "Cannot find main function, cannot construct path to check precall!"; + return AllocStatusVal::ERROR; + } + const Instruction* Entry = &*mainF->getEntryBlock().getFirstNonPHIIt(); + + AllocStatus init; + IterTypeAlloc data = { {}, {}, AllocOp->contrP, F }; + auto bound_transfer = std::bind(&ContractVerifierAllocPass::transferAllocStat, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3); + auto bound_merge = std::bind(&ContractVerifierAllocPass::mergeAllocStat, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4); + std::map AnalysisInfo = ContractPassUtility::GenericWorklist(Entry, bound_transfer, bound_merge, &data, init); + + C.DebugInfo->insert(C.DebugInfo->end(), data.dbg.begin(), data.dbg.end()); + Expr.ErrorInfo->insert(Expr.ErrorInfo->end(), data.err.begin(), data.err.end()); + + // Take max over all analysis info + // Correct usage will not contain error + AllocStatusVal res = AllocStatusVal::ALLOC; + for (std::pair AI : AnalysisInfo) { + if (const CallBase* CB = dyn_cast(AI.first)) { + if (CB->getCalledFunction() == F) { + res = std::max(AI.second.CurVal, res); + } + } + } + return res; +} diff --git a/Passes/ContractVerifierAlloc.hpp b/Passes/ContractVerifierAlloc.hpp new file mode 100644 index 0000000..eceecc6 --- /dev/null +++ b/Passes/ContractVerifierAlloc.hpp @@ -0,0 +1,96 @@ +#pragma once + +#include "ContractTree.hpp" +#include "ContractManager.hpp" +#include "llvm/IR/PassManager.h" +#include +#include +#include +#include +#include +#include + +using namespace ContractTree; + +namespace llvm { + +class ContractVerifierAllocPass : public PassInfoMixin { + public: + enum struct AllocStatusVal { ALLOC, UNDEF, ERROR }; + class AllocStatus { + public: struct AllocInfo; + private: std::map candidate_set; + public: + struct AllocInfo { + std::set parents; + std::set children; + ParamAccess acc; + bool operator==(const AllocInfo other) const { + return true; + } + }; + AllocStatusVal CurVal = AllocStatusVal::UNDEF; + AllocInfo const& getAllocInfo(Value const* V) const { return candidate_set.at(V); } + bool hasAllocInfo(Value const* V) const { return candidate_set.contains(V); } + auto candidates() const { + return std::ranges::subrange(candidate_set.cbegin(), candidate_set.cend()); + } + void addCopy(Value const* Copy, Value const* Parent, ParamAccess acc) { + if (candidate_set.contains(Copy)) { + candidate_set[Copy].parents.insert(Parent); + } else { + candidate_set[Copy] = {{Parent}, {}, acc}; + candidate_set[Parent].children.insert(Copy); + } + } + void addAllocatedValue(Value const* V, ParamAccess acc = ParamAccess::NORMAL) { + candidate_set[V] = {{}, {}, acc}; + } + void freeValue(Value const* V) { + if (!hasAllocInfo(V)) return; + std::set to_erase = {V}; + while (!to_erase.empty()) { + V = *to_erase.begin(); + for (Value const* C : candidate_set[V].children) { + candidate_set[C].parents.erase(V); + if (candidate_set[C].parents.empty()) to_erase.insert(C); + } + candidate_set.erase(V); + to_erase.erase(V); + } + } + AllocStatus intersect(AllocStatus const& other) { + std::map candidate_intersect; + for (std::pair Candidate : this->candidates()) { + if (other.hasAllocInfo(Candidate.first)) { + AllocInfo AIres; + AIres.acc = Candidate.second.acc; + AllocInfo AI = other.getAllocInfo(Candidate.first); + std::set_intersection(AI.children.begin(), AI.children.end(), Candidate.second.children.begin(), Candidate.second.children.end(), std::inserter(AIres.children, AIres.children.end())); + std::set_intersection(AI.parents.begin(), AI.parents.end(), Candidate.second.parents.begin(), Candidate.second.parents.end(), std::inserter(AIres.parents, AIres.parents.end())); + candidate_intersect[Candidate.first] = AIres; + } + } + AllocStatus res; + res.candidate_set = candidate_intersect; + res.CurVal = std::max(this->CurVal, other.CurVal); + return res; + } + }; + PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM); + + private: + ContractManagerAnalysis::ContractDatabase* DB; + ModuleAnalysisManager* MAM; + + std::map> AllocFuncs; + std::map> FreeFuncs; + + AllocStatus transferAllocStat(AllocStatus s, const Instruction* I, void* data); + std::pair mergeAllocStat(AllocStatus prev, AllocStatus cur, const Instruction* I, void* data); + + AllocStatusVal checkAllocReq(const AllocOperation* AllocOp, ContractManagerAnalysis::LinearizedContract const& C, ContractExpression const& Expr, Module const& M, const Function* F, std::string& err); + +}; + +} // namespace llvm diff --git a/Passes/ContractVerifierParam.cpp b/Passes/ContractVerifierParam.cpp new file mode 100644 index 0000000..70f3ff9 --- /dev/null +++ b/Passes/ContractVerifierParam.cpp @@ -0,0 +1,251 @@ +#include "ContractVerifierParam.hpp" +#include "BasicTypes.hpp" +#include "ContractManager.hpp" +#include "ContractTree.hpp" +#include "ContractPassUtility.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace llvm; +using namespace ContractTree; + +PreservedAnalyses ContractVerifierParamPass::run(Module &M, + ModuleAnalysisManager &AM) { + ContractManagerAnalysis::ContractDatabase DB = AM.getResult(M); + MAM = &AM; + Basic_Types = MAM->getResult(M); + + for (ContractManagerAnalysis::LinearizedContract const& C : DB.LinearizedContracts) { + for (const std::shared_ptr Expr : C.Pre) { + if (*Expr->Status != Fulfillment::UNKNOWN) continue; + // Contract has a precondition + std::string err; + if (Expr->OP->type() != FormulaType::PARAM) continue; + const ParamOperation* ParamOp = dynamic_cast(Expr->OP.get()); + C.DebugInfo->push_back("[ContractVerifierParam] Attempting to verify expression: " + Expr->ExprStr); + Fulfillment resf = Fulfillment::FULFILLED; + + // Perform the check on each callsite + for (User* U : C.F->users()) { + if (CallBase* CB = dyn_cast(U); CB && CB->getCalledOperand() == C.F) { + for (ParamRequirement const& req : ParamOp->reqs) { + // Figure out value(s) to check against + std::set vars; + try { + // First, check if constant value provided + int ivalue = std::stoi(req.value); + if (req.isArg) vars = {CB->getArgOperand(ivalue)}; + else vars = {ConstantInt::get(Type::getInt64Ty(M.getContext()), ivalue)}; + } catch(std::exception& e) { + // Otherwise, check against value database + if (!DB.ContractVariableData.contains(req.value)) { + errs() << "Undefined non-constint contract value identifier \"" << req.value << "\"!\n"; + errs() << "Requirement will not be analysed!\n"; + continue; + } + vars = DB.ContractVariableData[req.value]; + } + + // Perform check + std::string errInfo = ""; + Fulfillment f = checkParamReq(vars, CB, ParamOp->idx, req.comp, errInfo); + if (f == Fulfillment::BROKEN) { + resf = Fulfillment::BROKEN; + Expr->ErrorInfo->push_back({ + .error_id = "Param", + .text = std::format("{:s} Parameter Index: {:d}, Contract Value: {:s}", errInfo.empty() ? "Parameter error detected!" : errInfo, ParamOp->idx, req.value), + .references = {ContractPassUtility::getFileReference(CB)}, + }); + goto exit_param_analysis; + } + if (f == Fulfillment::FULFILLED && req.comp == Comparator::EXEQ) { + // Parameter fulfills exception value. Stop checking this parameter + goto exit_param_analysis; + } + } + } + } + exit_param_analysis: + *Expr->Status = resf; + } + } + + return PreservedAnalyses::all(); +} + +std::string createCompErr(const Comparator comp, const ConstantInt* callCI, const ConstantInt* valueCI) { + SmallString<10> tmpCs; + callCI->getValue().toStringSigned(tmpCs); + std::string callCs = tmpCs.c_str(); + tmpCs = ""; + valueCI->getValue().toStringSigned(tmpCs); + std::string valueCs = tmpCs.c_str(); + switch (comp) { + case Comparator::NEQ: + return "Parameter matches forbidden value (" + callCs + ")!"; + case Comparator::EQ: + return "Parameter does not match required value (" + callCs + ")!"; + case Comparator::GT: + return "Call parameter value (" + callCs + ") not greater than contract value (" + valueCs + ")!"; + case Comparator::GTEQ: + return "Call parameter value (" + callCs + ") not greater or equal to contract value (" + valueCs + ")!"; + case Comparator::LT: + return "Call parameter value (" + callCs + ") not less than contract value (" + valueCs + ")!"; + case Comparator::LTEQ: + return "Call parameter value (" + callCs + ") not less or equal to contract value (" + valueCs + ")!"; + case Comparator::EXEQ: + llvm_unreachable("Exception Equal should not trigger an error!"); + } +} + +Fulfillment compareCI(const ConstantInt* CI, const ConstantInt* CI2, Comparator comp) { + switch (comp) { + case Comparator::NEQ: + return CI->getValue().getSExtValue() != CI2->getValue().getSExtValue() ? Fulfillment::UNKNOWN : Fulfillment::BROKEN; + case Comparator::EQ: + return CI->getValue().getSExtValue() == CI2->getValue().getSExtValue() ? Fulfillment::UNKNOWN : Fulfillment::BROKEN; + case Comparator::GTEQ: + return CI->getValue().sge(CI2->getValue()) ? Fulfillment::UNKNOWN : Fulfillment::BROKEN; + case Comparator::GT: + return CI->getValue().sgt(CI2->getValue()) ? Fulfillment::UNKNOWN : Fulfillment::BROKEN; + case Comparator::LTEQ: + return CI->getValue().sle(CI2->getValue()) ? Fulfillment::UNKNOWN : Fulfillment::BROKEN; + case Comparator::LT: + return CI->getValue().slt(CI2->getValue()) ? Fulfillment::UNKNOWN : Fulfillment::BROKEN; + case Comparator::EXEQ: + return CI->getValue().getSExtValue() == CI2->getValue().getSExtValue() ? Fulfillment::FULFILLED : Fulfillment::UNKNOWN; + } +} + +Fulfillment ContractVerifierParamPass::checkParamReq(std::set vars, CallBase* call, int idx, Comparator const& comp, std::string& ErrInfo) { + for (Value* var : vars) { + Value* callVal = call->getArgOperand(idx); + if (AllocaInst* AI = dyn_cast(callVal)) { + for (Instruction* cur = call->getPrevNode(); cur && !isa(cur); cur = cur->getPrevNode()) { + if (GetElementPtrInst* GEP = dyn_cast(cur)) { + if (GEP->getPointerOperand() != callVal) continue; + if (LoadInst* LI = dyn_cast(cur->getNextNode())) { + callVal = LI->getPointerOperand(); + } + } + } + } + // Always prefer constint comparisons. + // For C, check if its a constint inttoptr + if (ConstantExpr* CE = dyn_cast(callVal)) { + if (isa(CE->getAsInstruction())) { + callVal = CE->getOperand(0); + } + } + // For Fortran, this sometimes requires a lil trickery: + // First, try to get at the actual value instead of the weird pointer that is passed as the arg in IR + if (Instruction* I = dyn_cast(callVal)) { + StoreInst* SI = ContractPassUtility::getLastStore(call, idx, &MAM->getResult(*I->getModule()).getManager()); + if (SI && isa(SI->getValueOperand())) { + callVal = SI->getValueOperand(); + } + } + // Next, for some global vals its just a struct with one constint member, resolve that as well (for both param val and call val) + std::vector tmps = {&callVal, &var}; + for (Value** tmp : tmps) { + Value* res = ContractPassUtility::fortCheckAndGetGlbInt(*tmp); + *tmp = res ? res : *tmp; + } + + // Check if its a pointer with the weird fortran metadata descriptor. If so, need to get contained value using heuristic + if (AllocaInst const* AI = dyn_cast(callVal)) { + if (StructType const* T = dyn_cast(AI->getAllocatedType())) { + if (T->getElementType(0) == Basic_Types.Ptr_Type && + T->getElementType(1) == Basic_Types.Int64_Type && + T->getElementType(2) == Basic_Types.Int_Type && + T->getNumElements() == 9) { + // Fortran Metadata thing - Find first GEP to callVal + Instruction* cur = call->getPrevNode(); + for (; cur && !isa(cur); cur = cur->getPrevNode()) { + if (GetElementPtrInst const* GEP = dyn_cast(cur)) { + if (GEP->getPointerOperand() != callVal) continue; + if (ExtractValueInst const* EVI = dyn_cast(GEP->getPrevNode())) { + for (Use const& U : EVI->getAggregateOperand()->uses()) { + if (InsertValueInst* IVI = dyn_cast(U.get())) { + if (IVI->getIndices()[0] == 0 && IVI->getNumIndices() == 1) { + callVal = IVI->getInsertedValueOperand(); + } + } + } + } + } + } + } + } + } + + if (callVal->getType()->isPointerTy()) { + switch (comp) { + case Comparator::NEQ: + if (ContractPassUtility::checkParamMatch(callVal, var, ParamAccess::NORMAL, MAM)) { + ErrInfo = "Parameter matches or is alias to forbidden pointer value!"; + return Fulfillment::BROKEN; + } + return Fulfillment::FULFILLED; + case Comparator::EQ: + if (!ContractPassUtility::checkParamMatch(callVal, var, ParamAccess::NORMAL, MAM)) { + ErrInfo = "Parameter does not match required pointer value!"; + return Fulfillment::BROKEN; + } + return Fulfillment::FULFILLED; + case Comparator::EXEQ: + if (ContractPassUtility::checkParamMatch(callVal, var, ParamAccess::NORMAL, MAM)) { + ErrInfo = "Note: Parameter matches or is alias to exception value."; + return Fulfillment::FULFILLED; + } + // Not an exception. Continue analysis, so far no info gained + continue; + default: + errs() << "Attempt to compare pointers! Not performing parameter analysis at " + << ContractPassUtility::getInstrLocStr(call) << " for index " << idx << "\n"; + return Fulfillment::UNKNOWN; + } + } + // Ensured that !isPtr, can get constinfo if present + if (const ConstantInt* callCI = dyn_cast(callVal)) { + if (const ConstantInt* varCI = dyn_cast(var)) { + Fulfillment f = compareCI(callCI, varCI, comp); + if (f == Fulfillment::BROKEN) { + ErrInfo = createCompErr(comp, callCI, varCI); + return f; + } else if (f == Fulfillment::FULFILLED) return f; + } + } + } + + return Fulfillment::UNKNOWN; +} diff --git a/Passes/ContractVerifierParam.hpp b/Passes/ContractVerifierParam.hpp new file mode 100644 index 0000000..daeae55 --- /dev/null +++ b/Passes/ContractVerifierParam.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include "BasicTypes.hpp" +#include "ContractTree.hpp" +#include "llvm/IR/PassManager.h" +#include +#include + +namespace llvm { + +class ContractVerifierParamPass : public PassInfoMixin { + public: + PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM); + + private: + ModuleAnalysisManager* MAM; + BasicTypesAnalysis::BasicTypes Basic_Types; + ContractTree::Fulfillment checkParamReq(std::set vars, CallBase* call, int idx, ContractTree::Comparator const& comp, std::string& ErrInfo); +}; + +} // namespace llvm diff --git a/Passes/ContractVerifierPostCall.cpp b/Passes/ContractVerifierPostCall.cpp index 0794d0d..ad0a1f4 100644 --- a/Passes/ContractVerifierPostCall.cpp +++ b/Passes/ContractVerifierPostCall.cpp @@ -32,15 +32,15 @@ PreservedAnalyses ContractVerifierPostCallPass::run(Module &M, for (ContractManagerAnalysis::LinearizedContract const& C : DB.LinearizedContracts) { for (std::shared_ptr const& Expr : C.Post) { if (*Expr->Status != Fulfillment::UNKNOWN) continue; - // Contract has a precondition + // Contract has a postcondition std::string err; CallStatus result; switch (Expr->OP->type()) { - case OperationType::CALL: - case OperationType::CALLTAG: { + case FormulaType::CALL: + case FormulaType::CALLTAG: { const CallOperation* cOP = static_cast(Expr->OP.get()); C.DebugInfo->push_back("[ContractVerifierPostCall] Attempting to verify expression: " + Expr->ExprStr); - result = checkPostCall(cOP, C, *Expr, cOP->type() == OperationType::CALLTAG, M, err); + result = checkPostCall(cOP, C, *Expr, cOP->type() == FormulaType::CALLTAG, M, err); break; } default: continue; diff --git a/Passes/ContractVerifierPreCall.cpp b/Passes/ContractVerifierPreCall.cpp index 42f478d..e0b752b 100644 --- a/Passes/ContractVerifierPreCall.cpp +++ b/Passes/ContractVerifierPreCall.cpp @@ -38,11 +38,11 @@ PreservedAnalyses ContractVerifierPreCallPass::run(Module &M, std::string err; CallStatusVal result; switch (Expr->OP->type()) { - case OperationType::CALL: - case OperationType::CALLTAG: { + case FormulaType::CALL: + case FormulaType::CALLTAG: { const CallOperation* cOP = static_cast(Expr->OP.get()); C.DebugInfo->push_back("[ContractVerifierPreCall] Attempting to verify expression: " + Expr->ExprStr); - result = checkPreCall(cOP, C, *Expr, cOP->type() == OperationType::CALLTAG, M, err); + result = checkPreCall(cOP, C, *Expr, cOP->type() == FormulaType::CALLTAG, M, err); break; } default: continue; diff --git a/Passes/ContractVerifierRelease.cpp b/Passes/ContractVerifierRelease.cpp index 8eafa80..995773c 100644 --- a/Passes/ContractVerifierRelease.cpp +++ b/Passes/ContractVerifierRelease.cpp @@ -40,7 +40,7 @@ PreservedAnalyses ContractVerifierReleasePass::run(Module &M, std::string err; bool result = false; switch (Expr->OP->type()) { - case OperationType::RELEASE: { + case FormulaType::RELEASE: { const ReleaseOperation& relOP = static_cast(*Expr->OP); C.DebugInfo->push_back("[ContractVerifierRelease] Attempting to verify expression: " + Expr->ExprStr); result = checkRelease(relOP, C, *Expr, M, err) == ReleaseStatus::FULFILLED; @@ -65,7 +65,7 @@ PreservedAnalyses ContractVerifierReleasePass::run(Module &M, struct IterTypeRelease { std::vector err; std::vector dbg; - OperationType forbiddenType; + FormulaType forbiddenType; std::vector param; std::string releaseFunc; std::vector releaseParam; @@ -125,10 +125,10 @@ ContractVerifierReleasePass::ReleaseStatus ContractVerifierReleasePass::transfer } switch (Data->forbiddenType) { - case ContractTree::OperationType::CALL: - case ContractTree::OperationType::CALLTAG: + case ContractTree::FormulaType::CALL: + case ContractTree::FormulaType::CALLTAG: if (const CallBase* CB = dyn_cast(I)) { - if (ContractPassUtility::checkCalledApplies(CB, std::any_cast(Data->param[0]), Data->forbiddenType == ContractTree::OperationType::CALLTAG, Data->Tags)) { + if (ContractPassUtility::checkCalledApplies(CB, std::any_cast(Data->param[0]), Data->forbiddenType == ContractTree::FormulaType::CALLTAG, Data->Tags)) { // Found forbidden function. Current status is unknown, if we find forbidden parameter (and one is specified) this is an error const std::vector forbidParams = std::any_cast>(Data->param[1]); if (forbidParams.empty()) return ReleaseStatus::ERROR_UNFULFILLED; @@ -143,12 +143,12 @@ ContractVerifierReleasePass::ReleaseStatus ContractVerifierReleasePass::transfer } } break; - case ContractTree::OperationType::READ: + case ContractTree::FormulaType::READ: if (const LoadInst* LI = dyn_cast(I)) { RWHelper(LI); } break; - case ContractTree::OperationType::WRITE: + case ContractTree::FormulaType::WRITE: if (const StoreInst* SI = dyn_cast(I)) { RWHelper(SI); } @@ -174,16 +174,16 @@ std::pair ContractVerifierRelea ContractVerifierReleasePass::ReleaseStatus ContractVerifierReleasePass::checkRelease(const ContractTree::ReleaseOperation relOp, ContractManagerAnalysis::LinearizedContract const& C, ContractExpression const& Expr, const Module& M, std::string& error) { // Figure out release parameters - OperationType forbiddenType = relOp.Forbidden->type(); + FormulaType forbiddenType = relOp.Forbidden->type(); std::vector param; switch (forbiddenType) { - case ContractTree::OperationType::CALLTAG: - case ContractTree::OperationType::CALL: + case ContractTree::FormulaType::CALLTAG: + case ContractTree::FormulaType::CALL: param.push_back(static_cast(*relOp.Forbidden).Function); param.push_back(static_cast(*relOp.Forbidden).Params); break; - case ContractTree::OperationType::READ: - case ContractTree::OperationType::WRITE: + case ContractTree::FormulaType::READ: + case ContractTree::FormulaType::WRITE: param.push_back(static_cast(*relOp.Forbidden).contrP); param.push_back(static_cast(*relOp.Forbidden).contrParamAccess); break; @@ -193,7 +193,7 @@ ContractVerifierReleasePass::ReleaseStatus ContractVerifierReleasePass::checkRel } bool isTagRel = false; - if (relOp.Until->type() == OperationType::CALLTAG) { + if (relOp.Until->type() == FormulaType::CALLTAG) { isTagRel = true; } std::string releaseFunc = static_cast(*relOp.Until).Function; diff --git a/Passes/Instrument.cpp b/Passes/Instrument.cpp index fc1fa46..356416a 100644 --- a/Passes/Instrument.cpp +++ b/Passes/Instrument.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -29,9 +30,10 @@ #include #include #include +#include #include #include -#include +#include #include #include #include @@ -48,7 +50,9 @@ static cl::opt ClInstrumentType( PreservedAnalyses InstrumentPass::run(Module &M, ModuleAnalysisManager &AM) { - DB = &AM.getResult(M); + MAM = &AM; + DB = &MAM->getResult(M); + Basic_Types = MAM->getResult(M); Function* mainF = M.getFunction("main"); if (!mainF) return PreservedAnalyses::all(); // No point @@ -84,7 +88,7 @@ PreservedAnalyses InstrumentPass::run(Module &M, err_msgs.push_back(msg); } - // Generic Types and consts + // Contract Types and consts createTypes(M); // Create Tag globals @@ -103,7 +107,7 @@ PreservedAnalyses InstrumentPass::run(Module &M, // Package database GlobalVariable* GlobalDB = dyn_cast(M.getOrInsertGlobal("CONTR_DB", DB_Type)); - Constant* CDB = ConstantStruct::get(DB_Type, {ContractsVal, ConstantInt::get(Int_Type, num_contrs), TagVal, ReferencesVal, ConstantInt::get(Int_Type, num_refs)}); + Constant* CDB = ConstantStruct::get(DB_Type, {ContractsVal, Basic_Types.getInt(num_contrs), TagVal, ReferencesVal, Basic_Types.getInt(num_refs)}); GlobalDB->setInitializer(CDB); AttributeList fnAttr; @@ -111,29 +115,16 @@ PreservedAnalyses InstrumentPass::run(Module &M, fnAttr = fnAttr.addFnAttribute(M.getContext(), Attribute::WillReturn); fnAttr = fnAttr.addFnAttribute(M.getContext(), Attribute::NoCallback); - // Create initialization routine for tool - FunctionType* InitCBType = FunctionType::get(Void_Type, {Ptr_Type, Ptr_Type, Ptr_Type}, false); - FunctionCallee initFuncCallee = M.getOrInsertFunction("PPDCV_Initialize", InitCBType, fnAttr); - Function* initFunc = dyn_cast(initFuncCallee.getCallee()); - initFunc->setLinkage(GlobalValue::ExternalWeakLinkage); - Value* Vargc = mainF->getArg(0); - Value* Vargv = mainF->getArg(1); - Value* argcptr = new AllocaInst(Int_Type, 0, "argc_ptr", mainF->getEntryBlock().getFirstNonPHIOrDbg()); - Value* argvptr = new AllocaInst(Ptr_Type, 0, "argv_ptr", mainF->getEntryBlock().getFirstNonPHIOrDbg()); - CallInst* initFuncCI = CallInst::Create(initFuncCallee, {argcptr, argvptr, GlobalDB}); - initFuncCI->insertBefore(mainF->getEntryBlock().getFirstNonPHIOrDbgOrAlloca()); - instrument_ignore.insert(new StoreInst(Vargc, argcptr, initFuncCI->getIterator())); - instrument_ignore.insert(new StoreInst(Vargv, argvptr, initFuncCI->getIterator())); // Create callback function for rel func call - // Call sig: Function ptr, num operands, vararg list of operands. Format: {int64-as-bool isptr, size of param, param} for each param. - FunctionType* FunctionCBType = FunctionType::get(Void_Type, {Bool_Type, Ptr_Type, Int_Type}, true); + // Call sig: isRel, Function ptr, ret size, num operands, vararg list of operands. Format: {int64-as-bool isptr, size of param, param} for each param. + FunctionType* FunctionCBType = FunctionType::get(Basic_Types.Ptr_Type, {Basic_Types.Bool_Type, Basic_Types.Ptr_Type, Basic_Types.Int_Type, Basic_Types.Int_Type}, true); callbackFuncCallee = M.getOrInsertFunction("PPDCV_FunctionCallback", FunctionCBType, fnAttr); Function* callbackFunc = dyn_cast(callbackFuncCallee.getCallee()); callbackFunc->setLinkage(GlobalValue::ExternalWeakLinkage); // Create callback function for RW // Call sig: int64-as-bool isWrite, mem ptr - FunctionType* FunctionRWType = FunctionType::get(Void_Type, {Bool_Type, Ptr_Type}, false); + FunctionType* FunctionRWType = FunctionType::get(Basic_Types.Void_Type, {Basic_Types.Bool_Type, Basic_Types.Ptr_Type}, false); callbackRCallee = M.getOrInsertFunction("PPDCV_MemRCallback", FunctionRWType, fnAttr); Function* callbackR = dyn_cast(callbackRCallee.getCallee()); callbackR->setLinkage(GlobalValue::ExternalWeakLinkage); @@ -141,11 +132,34 @@ PreservedAnalyses InstrumentPass::run(Module &M, Function* callbackW = dyn_cast(callbackWCallee.getCallee()); callbackW->setLinkage(GlobalValue::ExternalWeakLinkage); + // Finally the init routine + FunctionType* InitCBType = FunctionType::get(Basic_Types.Void_Type, {Basic_Types.Ptr_Type, Basic_Types.Ptr_Type, Basic_Types.Ptr_Type}, false); + initFuncCallee = M.getOrInsertFunction("PPDCV_Initialize", InitCBType, fnAttr); + Function* initFunc = dyn_cast(initFuncCallee.getCallee()); + initFunc->setLinkage(GlobalValue::ExternalWeakLinkage); + + // Create initialization routine for tool + Value* Vargc = mainF->getArg(0); + Value* Vargv = mainF->getArg(1); + AllocaInst* argcptr = new AllocaInst(Basic_Types.Int_Type, 0, "argc_ptr", mainF->getEntryBlock().getFirstNonPHIOrDbg()); + AllocaInst* argvptr = new AllocaInst(Basic_Types.Ptr_Type, 0, "argv_ptr", argcptr->getIterator()); + CallInst* initFuncCI = CallInst::Create(initFuncCallee, {argcptr, argvptr, GlobalDB}); + initFuncCI->insertAfter(argcptr->getIterator()); + instrument_ignore.insert({argcptr, argvptr}); + instrument_ignore.insert(new StoreInst(Vargc, argcptr, initFuncCI->getIterator())); + instrument_ignore.insert(new StoreInst(Vargv, argvptr, initFuncCI->getIterator())); + // Create callbacks if (ClInstrumentType != "funconly") instrumentRW(M); instrumentFunctions(M); + std::vector to_remove; + for (Function& F : M) { + if (F.getName().starts_with("contract_definitions_fort")) to_remove.push_back(&F); + } + for (Function* F : to_remove) F->eraseFromParent(); + return PreservedAnalyses::none(); } @@ -156,7 +170,7 @@ Constant* InstrumentPass::createTagGlobal(Module& M) { int count = 0; for (std::pair> functags : DB->Tags) { for (TagUnit tag : functags.second) { - Constant* param = ConstantInt::get(Int_Type, tag.param ? *tag.param : -1); + Constant* param = ConstantInt::get(Basic_Types.Int_Type, tag.param ? *tag.param : -1); Constant* str = ConstantDataArray::getString(M.getContext(), tag.tag); GlobalVariable* strGlobal = createConstantGlobal(M, str, "CONTR_TAG_STR_" + tag.tag); Constant* TagC = ConstantStruct::get(Tag_Type, {strGlobal,param}); @@ -167,13 +181,13 @@ Constant* InstrumentPass::createTagGlobal(Module& M) { } // Create global const arrays for the tags - ArrayType* ArrFuncTy = ArrayType::get(Ptr_Type, count); + ArrayType* ArrFuncTy = ArrayType::get(Basic_Types.Ptr_Type, count); ArrayType* ArrTagTy = ArrayType::get(Tag_Type, count); GlobalVariable* ptrFuncs = createConstantGlobal(M, ConstantArray::get(ArrFuncTy, funcs), "CONTR_TAG_ARRAY_PTRS"); GlobalVariable* ptrTags = createConstantGlobal(M, ConstantArray::get(ArrTagTy, tags), "CONTR_TAG_ARRAY_TAGS"); // Full tag map structure - Constant* TagsStruct = ConstantStruct::get(Tags_Type, {ptrFuncs, ptrTags, ConstantInt::get(Int_Type, count)}); + Constant* TagsStruct = ConstantStruct::get(Tags_Type, {ptrFuncs, ptrTags, ConstantInt::get(Basic_Types.Int_Type, count)}); return TagsStruct; } @@ -213,19 +227,19 @@ std::pair InstrumentPass::createContractsGlobal(Module& M) { Constant* InstrumentPass::createScopeGlobal(Module& M, std::vector> forms) { std::vector formsConst; static Constant* scopeMsgConst = createConstantGlobal(M, ConstantDataArray::getString(M.getContext(), "Full Scope"), "CONTR_SCOPE_STR_"); - if (forms.empty()) return Null_Const; + if (forms.empty()) return Basic_Types.Null_Const; for (std::shared_ptr form : forms) { formsConst.push_back(createFormulaGlobal(M, form)); } ArrayType* ArrPreCond = ArrayType::get(Formula_Type, forms.size()); GlobalVariable* Sublevel = createConstantGlobalUnique(M, ConstantArray::get(ArrPreCond, formsConst), std::string("CONTR_SCOPECONDITIONS")); - return createConstantGlobalUnique(M, ConstantStruct::get(Formula_Type, { Sublevel, ConstantInt::get(Int_Type, forms.size()), ConstantInt::get(Int_Type, (int64_t)FormulaType::AND), scopeMsgConst, Null_Const}), "CONTR_SCOPE"); + return createConstantGlobalUnique(M, ConstantStruct::get(Formula_Type, { Sublevel, Basic_Types.getInt(forms.size()), Basic_Types.getInt((int64_t)FormulaType::AND), scopeMsgConst, Basic_Types.Null_Const}), "CONTR_SCOPE"); } Constant* InstrumentPass::createFormulaGlobal(Module& M, std::shared_ptr form) { - Constant* op_const = Null_Const; - Constant* children = Null_Const; - Constant* msg = Null_Const; + Constant* op_const = Basic_Types.Null_Const; + Constant* children = Basic_Types.Null_Const; + Constant* msg = Basic_Types.Null_Const; std::string descriptor = form->Message ? form->Message->text : form->ExprStr; msg = createConstantGlobal(M, ConstantDataArray::getString(M.getContext(), descriptor), "CONTR_MSG_" + descriptor); int64_t connective; @@ -243,55 +257,148 @@ Constant* InstrumentPass::createFormulaGlobal(Module& M, std::shared_ptrChildren.size()), ConstantInt::get(Int_Type, connective), msg, op_const}); + return ConstantStruct::get(Formula_Type, {children, Basic_Types.getInt(form->Children.size()), Basic_Types.getInt(connective), msg, op_const}); } Constant* InstrumentPass::createOperationGlobal(Module& M, std::shared_ptr op) { - Constant* data = Null_Const; - std::string name; + Constant* data = Basic_Types.Null_Const; + std::string name = "UNKNOWN"; switch (op->type()) { - case OperationType::READ: - case OperationType::WRITE: { + case FormulaType::AND: + case FormulaType::OR: + case FormulaType::XOR: + // Should not happen here! + errs() << "Unexpected connective in createOperationGlobal!\n"; + break; + case FormulaType::READ: + case FormulaType::WRITE: + case FormulaType::RWOP: { std::shared_ptr rwOP = static_pointer_cast(op); - Constant* isWrite = ConstantInt::getBool(Bool_Type, op->type() == OperationType::WRITE); - ConstantInt* const_paramacc = ConstantInt::get(Int_Type, (int)rwOP->contrParamAccess); - ConstantInt* const_idx = ConstantInt::get(Int_Type, (int)rwOP->contrP); + Constant* isWrite = Basic_Types.getBool(op->type() == FormulaType::WRITE); + ConstantInt* const_paramacc = Basic_Types.getInt((int)rwOP->contrParamAccess); + ConstantInt* const_idx = Basic_Types.getInt((int)rwOP->contrP); data = ConstantStruct::get(RWOp_Type, {const_idx, const_paramacc, isWrite}); name = "CONTR_RWOP"; break; } - case OperationType::CALL: { + case FormulaType::CALL: { std::shared_ptr cOP = static_pointer_cast(op); Function* F = M.getFunction(cOP->Function) ? M.getFunction(cOP->Function) : M.getFunction(StringRef(cOP->Function).lower() + "_"); if (!F) WithColor::warning() << "Specified function \"" << cOP->Function << "\" in calloperation does not exist or unused in module. This may cause issues for instrumentation.\n"; else mentioned_funcs.push_back(F); Constant* funcStr = ConstantDataArray::getString(M.getContext(), cOP->Function); std::pair paramGlobal = createParamList(M, cOP->Params); - data = ConstantStruct::get(CallOp_Type, {createConstantGlobal(M, funcStr, "CONTR_FUNC_STR_" + cOP->Function), paramGlobal.first, ConstantInt::get(Int_Type, paramGlobal.second), F ? F : Null_Const}); + data = ConstantStruct::get(CallOp_Type, {createConstantGlobal(M, funcStr, "CONTR_FUNC_STR_" + cOP->Function), paramGlobal.first, Basic_Types.getInt(paramGlobal.second), F ? F : Basic_Types.Null_Const}); name = "CONTR_CALLOP"; break; } - case OperationType::CALLTAG: { + case FormulaType::CALLTAG: { std::shared_ptr cOP = static_pointer_cast(op); data = createConstantGlobal(M, ConstantDataArray::getString(M.getContext(), cOP->Function), "CONTR_TAG_STR_" + cOP->Function); std::pair paramGlobal = createParamList(M, cOP->Params); - data = ConstantStruct::get(CallTagOp_Type, {data, paramGlobal.first, ConstantInt::get(Int_Type, paramGlobal.second)}); + data = ConstantStruct::get(CallTagOp_Type, {data, paramGlobal.first, Basic_Types.getInt(paramGlobal.second)}); name = "CONTR_CALLTAGOP"; break; } - case OperationType::RELEASE: + case FormulaType::RELEASE: { std::shared_ptr rOP = static_pointer_cast(op); Constant* forbidden_op = createOperationGlobal(M, rOP->Forbidden); - Constant* forb_type = ConstantInt::get(Int_Type, (int64_t)rOP->Forbidden->type()); + Constant* forb_type = Basic_Types.getInt((int64_t)rOP->Forbidden->type()); Constant* release_op = createOperationGlobal(M, rOP->Until); - Constant* release_type = ConstantInt::get(Int_Type, (int64_t)rOP->Until->type()); + Constant* release_type = Basic_Types.getInt((int64_t)rOP->Until->type()); data = ConstantStruct::get(ReleaseOp_Type, {release_op, release_type, forbidden_op, forb_type}); name = "CONTR_RELEASE"; break; + } + case FormulaType::PARAM: { + std::shared_ptr pOP = static_pointer_cast(op); + std::vector reqCs; + bool hasIntCmp = false; + for (ParamRequirement const& req : pOP->reqs) { + Constant* var = Basic_Types.Null_Const; + try { + int ivalue = std::stoi(req.value); + var = Basic_Types.getInt64(ivalue); + var = ConstantExpr::getIntToPtr(var, Basic_Types.Ptr_Type); + reqCs.push_back(ConstantStruct::get(ParamReq_Type, {Basic_Types.getInt(req.comp), var, Basic_Types.getBool(req.isArg), Basic_Types.getBool(false)})); + hasIntCmp = req.isArg ? hasIntCmp : true; + } catch(std::exception& e) { + if (!DB->ContractVariableData.contains(req.value)) { + errs() << "Undefined non-constint contract value identifier \"" << req.value << "\"!\n"; + errs() << "Param Requirement will not be instrumented!\n"; + continue; + } + for (Value* V : DB->ContractVariableData[req.value]) { + if (isa(V)) var = (Constant*)V; + if (isa(var)) var = ConstantExpr::getIntToPtr(var, Basic_Types.Ptr_Type); + if (!isa(var)) { + errs() << "Weird param error in instr pass\n"; + } + reqCs.push_back(ConstantStruct::get(ParamReq_Type, {Basic_Types.getInt(req.comp), var, Basic_Types.getBool(req.isArg), Basic_Types.getBool(!isC && (var->getName().starts_with("_QQ")))})); + hasIntCmp = ContractPassUtility::fortCheckAndGetGlbInt(var) ? true : hasIntCmp; + } + } + } + Constant* reqsC = ConstantArray::get(ArrayType::get(ParamReq_Type, reqCs.size()), reqCs); + reqsC = createConstantGlobalUnique(M, reqsC, "CONTR_PARAM_REQS"); + data = ConstantStruct::get(ParamOp_Type, {Basic_Types.getInt(pOP->idx), reqsC, Basic_Types.getInt(reqCs.size()), Basic_Types.getBool(!isC && hasIntCmp)}); + name = "CONTR_PARAMOP"; + break; + } + case FormulaType::ALLOC: { + static std::vector allocators; + static std::vector deallocators; + static Constant* allocs_C = Basic_Types.Null_Const; + static Constant* deallocs_C = Basic_Types.Null_Const; + std::shared_ptr allocOp = std::static_pointer_cast(op); + if (allocators.empty() && deallocators.empty()) { + for (ContractManagerAnalysis::LinearizedContract const& C : DB->LinearizedContracts) { + for (const std::shared_ptr Expr : C.Post) { + switch (Expr->OP->type()) { + case FormulaType::ALLOC: + case FormulaType::FREE: { + std::shared_ptr rwOp = std::make_shared(*std::static_pointer_cast(Expr->OP)); + Constant* rwOp_C = createOperationGlobal(M, rwOp); + Constant* memop_C = ConstantStruct::get(MemOpFunc_Type, {C.F, rwOp_C, Expr->OP->type() == FormulaType::ALLOC ? createMathExprGlobal(M, std::static_pointer_cast(Expr->OP)->size) : Basic_Types.Null_Const}); + if (Expr->OP->type() == FormulaType::ALLOC) allocators.push_back(memop_C); + else if (Expr->OP->type() == FormulaType::FREE) deallocators.push_back(memop_C); + else llvm_unreachable("Unexpected type when constructing alloc/free inst!"); + break; + } + default: continue; + } + } + } + static ArrayType* allocators_Type = ArrayType::get(MemOpFunc_Type, allocators.size()); + allocs_C = ConstantArray::get(allocators_Type, allocators); + allocs_C = createConstantGlobal(M, allocs_C, "CONTR_ALLOCATOR_LIST"); + static ArrayType* deallocators_Type = ArrayType::get(MemOpFunc_Type, deallocators.size()); + deallocs_C = ConstantArray::get(deallocators_Type, deallocators); + deallocs_C = createConstantGlobal(M, deallocs_C, "CONTR_DEALLOCATOR_LIST"); + } + data = ConstantStruct::get(AllocOp_Type, {Basic_Types.getInt(allocOp->contrP), Basic_Types.getInt((int32_t)allocOp->contrParamAccess), + allocs_C, Basic_Types.getInt(allocators.size()), + deallocs_C, Basic_Types.getInt(deallocators.size())}); + name = "CONTR_ALLOCOP"; + break; + } + case FormulaType::FREE: break; } return createConstantGlobalUnique(M, data, name); } +Constant* InstrumentPass::createMathExprGlobal(Module& M, std::shared_ptr expr) { + Constant* val = Basic_Types.getInt(expr->value); + Constant* isArg = Basic_Types.getBool(expr->isArg); + Constant* type = Basic_Types.getInt((int32_t)expr->type); + Constant* other = Basic_Types.Null_Const; + if (expr->type != MathType::UNARY_VALUE) { + other = createMathExprGlobal(M, expr->other); + } + Constant* result = ConstantStruct::get(MathExpr_Type, {val, isArg, type, other}); + return createConstantGlobalUnique(M, result, "CONT_MATHEXPR"); +} + GlobalVariable* InstrumentPass::createConstantGlobalUnique(Module& M, Constant* C, std::string name) { static uint64_t globals_counter = 0; // For name uniqueness return createConstantGlobal(M, C, name + "_" + std::to_string(globals_counter++)); @@ -304,48 +411,69 @@ GlobalVariable* InstrumentPass::createConstantGlobal(Module& M, Constant* C, std return GV; } -void InstrumentPass::createTypes(Module& M) { - // Basic Types - Ptr_Type = PointerType::get(M.getContext(), 0); - Int_Type = IntegerType::get(M.getContext(), 32); - Bool_Type = IntegerType::get(M.getContext(), 1); - Null_Const = ConstantPointerNull::getNullValue(Ptr_Type); - Void_Type = Type::getVoidTy(M.getContext()); +Instruction* InstrumentPass::anyValToPtr(Value** V, Instruction* pos) { + if ((*V)->getType()->isVoidTy()) { *V = Basic_Types.Null_Const; return pos; } + if (!(*V)->getType()->isPointerTy()) { + if ((*V)->getType()->isFloatingPointTy()) { + *V = CastInst::Create(Instruction::CastOps::BitCast, *V, Basic_Types.Int64_Type, "", pos->getIterator()); + } + // Now, actual pointer cast + *V = CastInst::Create(Instruction::CastOps::IntToPtr, *V, Basic_Types.Ptr_Type, "", pos->getIterator()); + return dyn_cast(*V)->getNextNode(); + } + return pos; +} +void InstrumentPass::createTypes(Module& M) { // Operations Param_Type = StructType::create(M.getContext(), "CallParam_t"); - Param_Type->setBody({Int_Type, Bool_Type, Int_Type, Int_Type}); // call param, bool param is tag ref, contr param, acc type + Param_Type->setBody({Basic_Types.Int_Type, Basic_Types.Bool_Type, Basic_Types.Int_Type, Basic_Types.Int_Type}); // call param, bool param is tag ref, contr param, acc type CallOp_Type = StructType::create(M.getContext(), "CallOp_t"); - CallOp_Type->setBody({Ptr_Type, Ptr_Type, Int_Type, Ptr_Type}); // char* Function Name, list of params, num of params, Function Pointer + CallOp_Type->setBody({Basic_Types.Ptr_Type, Basic_Types.Ptr_Type, Basic_Types.Int_Type, Basic_Types.Ptr_Type}); // char* Function Name, list of params, num of params, Function Pointer CallTagOp_Type = StructType::create(M.getContext(), "CallTagOp_t"); - CallTagOp_Type->setBody({Ptr_Type, Ptr_Type, Int_Type}); // char* Tag name, list of params, num of params + CallTagOp_Type->setBody({Basic_Types.Ptr_Type, Basic_Types.Ptr_Type, Basic_Types.Int_Type}); // char* Tag name, list of params, num of params ReleaseOp_Type = StructType::create(M.getContext(), "ReleaseOp_t"); - ReleaseOp_Type->setBody({Ptr_Type, Int_Type, Ptr_Type, Int_Type}); // void* release op, relop type, void* forbidden op, forbop type + ReleaseOp_Type->setBody({Basic_Types.Ptr_Type, Basic_Types.Int_Type, Basic_Types.Ptr_Type, Basic_Types.Int_Type}); // void* release op, relop type, void* forbidden op, forbop type RWOp_Type = StructType::create(M.getContext(), "RWOp_t"); - RWOp_Type->setBody({Int_Type, Int_Type, Bool_Type}); // idx, paramaccess, isWrite + RWOp_Type->setBody({Basic_Types.Int_Type, Basic_Types.Int_Type, Basic_Types.Bool_Type}); // idx, paramaccess, isWrite + + ParamOp_Type = StructType::create(M.getContext(), "ParamOp_t"); + ParamOp_Type->setBody({Basic_Types.Int_Type, Basic_Types.Ptr_Type, Basic_Types.Int_Type, Basic_Types.Bool_Type}); // idx, list of reqs, num reqs, need deref + + AllocOp_Type = StructType::create(M.getContext(), "AllocOp_t"); + AllocOp_Type->setBody({Basic_Types.Int_Type, Basic_Types.Int_Type, Basic_Types.Ptr_Type, Basic_Types.Int_Type, Basic_Types.Ptr_Type, Basic_Types.Int_Type}); // idx, accType, list of allocators, num allocs, list of deallocs, num deallocs // Composite Types Tag_Type = StructType::create(M.getContext(), "Tag_t"); - Tag_Type->setBody({Ptr_Type, Int_Type}); // tag str, param num + Tag_Type->setBody({Basic_Types.Ptr_Type, Basic_Types.Int_Type}); // tag str, param num Formula_Type = StructType::create(M.getContext(), "ContractFormula_t"); - Formula_Type->setBody({Ptr_Type, Int_Type, Int_Type, Ptr_Type, Ptr_Type}); // Children, number of children, connective, message char*, expression data ptr + Formula_Type->setBody({Basic_Types.Ptr_Type, Basic_Types.Int_Type, Basic_Types.Int_Type, Basic_Types.Ptr_Type, Basic_Types.Ptr_Type}); // Children, number of children, connective, message char*, expression data ptr Contract_Type = StructType::create(M.getContext(), "Contract_t"); - Contract_Type->setBody({Ptr_Type, Ptr_Type, Ptr_Type, Ptr_Type}); // Precondition ptr, Postcondition ptr, contr supplier ptr, supplier name + Contract_Type->setBody({Basic_Types.Ptr_Type, Basic_Types.Ptr_Type, Basic_Types.Ptr_Type, Basic_Types.Ptr_Type}); // Precondition ptr, Postcondition ptr, contr supplier ptr, supplier name Tags_Type = StructType::create(M.getContext(), "TagsMap_t"); - Tags_Type->setBody({Ptr_Type, Ptr_Type, Int_Type}); // Funcptr list, Tag + param struct list, num elems + Tags_Type->setBody({Basic_Types.Ptr_Type, Basic_Types.Ptr_Type, Basic_Types.Int_Type}); // Funcptr list, Tag + param struct list, num elems + + MathExpr_Type = StructType::create(M.getContext(), "MathExpr_t"); + MathExpr_Type->setBody({Basic_Types.Int_Type, Basic_Types.Bool_Type, Basic_Types.Int_Type, Basic_Types.Ptr_Type}); // int val, bool isarg, math type, other math Ref_Type = StructType::create(M.getContext(), "Reference_t"); - Ref_Type->setBody({Ptr_Type, Ptr_Type}); // char* file ref, char* type + Ref_Type->setBody({Basic_Types.Ptr_Type, Basic_Types.Ptr_Type}); // char* file ref, char* type + + ParamReq_Type = StructType::create(M.getContext(), "ParamReq_t"); + ParamReq_Type->setBody({Basic_Types.Int_Type, Basic_Types.Ptr_Type, Basic_Types.Bool_Type, Basic_Types.Bool_Type}); // Comparator, Value, isArg, need_deref + + MemOpFunc_Type = StructType::create(M.getContext(), "MemOpFunc_t"); + MemOpFunc_Type->setBody({Basic_Types.Ptr_Type, Basic_Types.Ptr_Type, Basic_Types.Ptr_Type}); // Func, rwOp, size mathexpr DB_Type = StructType::create(M.getContext(), "ContractDB_t"); - DB_Type->setBody({Ptr_Type, Int_Type, Tags_Type, Ptr_Type, Int_Type}); // contract list, num elems, tag container, reference list, num refs + DB_Type->setBody({Basic_Types.Ptr_Type, Basic_Types.Int_Type, Tags_Type, Basic_Types.Ptr_Type, Basic_Types.Int_Type}); // contract list, num elems, tag container, reference list, num refs } void InstrumentPass::instrumentFunctions(Module &M) { @@ -377,11 +505,16 @@ void InstrumentPass::instrumentRW(Module &M) { if (GlobalVariable const* GV = dyn_cast(GEPOp->getPointerOperand())) { SmallVector dbg_arr; GV->getDebugInfo(dbg_arr); - if (!isC && !dbg_arr.empty() && dbg_arr[0]->getVariable()->getType()->getTag() == (dwarf::Tag)DW_TAG_array_type) { + if (!isC && !dbg_arr.empty() && dbg_arr[0]->getVariable()->getType()->getTag() == dwarf::DW_TAG_array_type) { continue; } } } + // Filter out new instr from sroa + if (!isC && isa(&I) && dyn_cast(&I)->getPointerOperand()->getName().starts_with(".fca.")) { + continue; + } + insertCBIfNeeded(isa(I) ? callbackRCallee : callbackWCallee, {V}, &I); } } @@ -390,10 +523,10 @@ void InstrumentPass::instrumentRW(Module &M) { } std::pair InstrumentPass::createParamList(Module& M, std::vector params) { - if (params.empty()) return { Null_Const, 0 }; + if (params.empty()) return { Basic_Types.Null_Const, 0 }; std::vector paramConsts; for (CallParam param : params) { - Constant* pConst = ConstantStruct::get(Param_Type, {ConstantInt::get(Int_Type, param.callP), ConstantInt::getBool(Bool_Type, param.callPisTagVar), ConstantInt::get(Int_Type, param.contrP), ConstantInt::get(Int_Type, (int64_t)param.contrParamAccess)}); + Constant* pConst = ConstantStruct::get(Param_Type, {Basic_Types.getInt(param.callP), Basic_Types.getBool(param.callPisTagVar), Basic_Types.getInt(param.contrP), Basic_Types.getInt((int32_t)param.contrParamAccess)}); paramConsts.push_back(pConst); } ArrayType* paramArr_Type = ArrayType::get(Param_Type, paramConsts.size()); @@ -405,49 +538,80 @@ void InstrumentPass::insertFunctionInstrCallback(Function* F) { if (already_instrumented.contains(F)) return; std::vector callsites; for (User* U : F->users()) { - if (CallBase* CB = dyn_cast(U)) { + if (CallBase* CB = dyn_cast(U); CB && CB->getCalledOperand() == F) { callsites.push_back(CB); } } for (CallBase* callsite : callsites) { - int skipnum = 0; + int stringnum = 0; std::vector params; params.push_back(callsite->getCalledOperand()); // First param is funcptr - params.push_back(ConstantInt::get(Int_Type, callsite->arg_size())); + + // Get return value size + if (!callsite->getType()->isSized()) { + params.push_back(Basic_Types.getInt(0)); + } else { + int ret_size = callsite->getDataLayout().getTypeStoreSizeInBits(callsite->getType()); + if (callsite->getType()->isFloatingPointTy()) ret_size |= 2 << 16; // Set bit 2 > float tag + params.push_back(Basic_Types.getInt(ret_size)); + } + + params.push_back(Basic_Types.getInt(callsite->arg_size())); for (Use const& U : callsite->args()) { Value* actual_param = U; int const cur_argno = callsite->getArgOperandNo(&U); - if (cur_argno >= callsite->arg_size() - skipnum) break; // Store size of data type if (isC) { - params.push_back(ConstantInt::get(Int_Type, callsite->getDataLayout().getTypeStoreSizeInBits(U->getType()))); + int size_act = callsite->getDataLayout().getTypeStoreSizeInBits(U->getType()); + int size_meta = (size_act << 8) | size_act; + if (isa(actual_param)) size_meta |= 0b100 << 16; + params.push_back(Basic_Types.getInt(size_meta)); // Store actual parameter, making sure to cast if necessary - if (!U->getType()->isPointerTy()) { - if (U->getType()->isFloatingPointTy()) { - actual_param = CastInst::Create(Instruction::CastOps::BitCast, actual_param, Int_Type, "", callsite->getIterator()); - } - // Now, actual pointer cast - actual_param = CastInst::Create(Instruction::CastOps::IntToPtr, actual_param, Ptr_Type, "", callsite->getIterator()); - } + anyValToPtr(&actual_param, callsite); } else { if (Function const* F = dyn_cast(callsite->getCalledOperand())) { DISubprogram const* Dbg = F->getSubprogram(); - if (checkIsStrParam(U)) skipnum++; - if (Dbg->getType()->getTypeArray()->getNumOperands() <= cur_argno + 1) { - errs() << "Warning: During instrumentation, likely string param missed during detection. Normal if optimizations enabled.\n"; - errs() << "If unsure, check if function " << F->getName() << " has more than " << skipnum << " string arguments.\n"; - break; - } - // All parameters are sent as pointers. Need to check exact size using dbg info - DIType const* param_type = Dbg->getType()->getTypeArray()[cur_argno + 1]; // Offset by one, first is ret val - params.push_back(ConstantInt::get(Int_Type, param_type->getSizeInBits() == 0 || isa(actual_param) ? 64 : param_type->getSizeInBits())); - // On Fortran, deref if param is an allocate/ptr buffer - if (param_type->getTag() == (dwarf::Tag)DW_TAG_array_type) { - actual_param = new LoadInst(Ptr_Type, actual_param, "", callsite->getIterator()); + int size_call = 0; + int size_act = 0; + // Vararg intrinsics have to be handled specially due to missing debug info on vararg + if (F->getName() == "CoVer_FPointerAllocate") { + // Prefix - 0, 1 ptr and size, 2 num_dims + switch (cur_argno) { + case 0: + case 1: size_act = size_call = 64; break; + case 2: size_act = size_call = 32; + } + if (!size_act) { + // End - _FortranAPointerAllocate params + if (cur_argno == callsite->arg_size() - 4) size_act = size_call = 32; + if (cur_argno == callsite->arg_size() - 3) size_act = size_call = 64; + if (cur_argno == callsite->arg_size() - 2) size_act = size_call = 64; + if (cur_argno == callsite->arg_size() - 1) size_act = size_call = 32; + } + if (!size_act) { + // Middle - Descriptors for dims + int tmp = cur_argno - 3; + if (tmp % 3 == 0) size_act = size_call = 32; + else size_act = size_call = 64; + } + } else { + if (cur_argno >= callsite->arg_size() - stringnum) { + size_act = size_call = callsite->getParent()->getDataLayout().getTypeAllocSize(actual_param->getType()); + } else { + if (checkIsStrParam(callsite, cur_argno)) stringnum++; + // All parameters are sent as pointers. Need to check exact size using dbg info + DIType const* param_type = Dbg->getType()->getTypeArray()[cur_argno + 1]; // Offset by one, first is ret val + size_act = param_type->getSizeInBits() == 0 || isa(actual_param) ? 64 : param_type->getSizeInBits(); + size_call = callsite->getDataLayout().getTypeStoreSizeInBits(U->getType()); + // On Fortran, deref if param is an allocate/ptr buffer. + // Indicate with magic bit + if (param_type->getTag() == dwarf::DW_TAG_array_type) { + size_call = size_call | 1 << 8; + } + } } - } else { - errs() << "ERROR: Could not perform instrumentation! Unable to get debug info for function \"" << callsite->getCalledOperand()->getName() << "\""; + params.push_back(Basic_Types.getInt((size_call << 8) | (size_act & 0xFF))); } } params.push_back(actual_param); @@ -459,10 +623,31 @@ void InstrumentPass::insertFunctionInstrCallback(Function* F) { void InstrumentPass::insertCBIfNeeded(FunctionCallee FC, std::vector params, Instruction* I) { if (!isRelevant(I) && (isa(I) || isa(I)) && ClInstrumentType.starts_with("filtered")) return; - params.insert(params.begin(), ConstantInt::getBool(Bool_Type, isRelevant(I))); - CallInst* callbackCI = CallInst::Create(FC, params); - callbackCI->setDebugLoc(I->getDebugLoc()); - callbackCI->insertBefore(I->getIterator()); + params.insert(params.begin(), Basic_Types.getBool(isRelevant(I))); + CallBase* callbackCB; + if (InvokeInst const* II = dyn_cast(I)) { + callbackCB = InvokeInst::Create(FC, II->getNormalDest(), II->getUnwindDest(), params); + } else { + callbackCB = CallInst::Create(FC, params); + } + callbackCB->setDebugLoc(I->getDebugLoc()); + if (CallBase* CB = dyn_cast(I)) { + Type* OrigRT = CB->getCalledFunction()->getReturnType(); + if (!OrigRT->isPointerTy() && !OrigRT->isVoidTy()) { + callbackCB->insertBefore(I->getIterator()); + if (OrigRT->isIntegerTy()) { + CastInst* CI = CastInst::Create(Instruction::PtrToInt, callbackCB, OrigRT); + ReplaceInstWithInst(I, CI); + } else if (OrigRT->isFloatingPointTy()) { + CastInst* CI = CastInst::Create(Instruction::PtrToInt, callbackCB, Basic_Types.Int64_Type, "", I->getIterator()); + CI = CastInst::Create(Instruction::BitCast, CI, OrigRT); + ReplaceInstWithInst(I, CI); + } + } else { + ReplaceInstWithInst(I, callbackCB); + } + } + else callbackCB->insertBefore(I->getIterator()); } bool InstrumentPass::isRelevant(Instruction const* I) const { @@ -473,7 +658,8 @@ bool InstrumentPass::isRelevant(Instruction const* I) const { return false; } -bool InstrumentPass::checkIsStrParam(Value const* V) { +bool InstrumentPass::checkIsStrParam(CallBase* CB, int idx) { + Value const* V = CB->getArgOperand(idx); // We want to check if I is a string param. If so, instrumentation should omit the string size arg // Lowered FIR does not make this easy. // If its a str var, its just some global, then the str size appended as another (fake) param @@ -494,10 +680,20 @@ bool InstrumentPass::checkIsStrParam(Value const* V) { } } + // If optimization is turned it it will just do load from str -> store. + // So first do this, then check if global + StoreInst* SI = ContractPassUtility::getLastStore(CB, idx, &MAM->getResult(*CB->getModule()).getManager()); + if (SI && isa(SI->getValueOperand())) { + LoadInst* LI = dyn_cast(SI->getValueOperand()); + if (isa(LI->getPointerOperand())) { + V = LI->getPointerOperand(); + } + } + // Now, check if its a global string if (GlobalVariable const* GV = dyn_cast(V)) { Constant const* Init = GV->getInitializer(); - return Init && isa(Init->getType()) && dyn_cast(Init->getType())->getElementType() == IntegerType::get(V->getContext(), 8); + return Init && !Init->isZeroValue() && isa(Init->getType()) && dyn_cast(Init->getType())->getElementType() == IntegerType::get(V->getContext(), 8); } return false; } diff --git a/Passes/Instrument.hpp b/Passes/Instrument.hpp index 189ca48..a1d5881 100644 --- a/Passes/Instrument.hpp +++ b/Passes/Instrument.hpp @@ -10,6 +10,7 @@ #include #include #include +#include "BasicTypes.hpp" #include "ContractManager.hpp" #include "ContractTree.hpp" #include "ErrorMessage.h" @@ -29,6 +30,7 @@ class InstrumentPass : public PassInfoMixin { Constant* createFormulaGlobal(Module& M, std::shared_ptr form); Constant* createOperationGlobal(Module& M, std::shared_ptr op); std::pair createParamList(Module& M, std::vector p); + Constant* createMathExprGlobal(Module& M, std::shared_ptr expr); // Auxiliary GlobalVariable* createConstantGlobalUnique(Module& M, Constant* C, std::string name); @@ -36,37 +38,40 @@ class InstrumentPass : public PassInfoMixin { void createTypes(Module& M); // Instrumentation - void instrumentFunctions(Module &M); + FunctionCallee initFuncCallee; + FunctionCallee callbackFuncCallee; + FunctionCallee callbackRCallee; + FunctionCallee callbackWCallee; void instrumentRW(Module &M); + void instrumentFunctions(Module &M); void insertFunctionInstrCallback(Function* CB); void insertCBIfNeeded(FunctionCallee FC, std::vector params, Instruction* I); bool isRelevant(Instruction const* I) const; - FunctionCallee callbackFuncCallee; - FunctionCallee callbackRCallee; - FunctionCallee callbackWCallee; std::set already_instrumented; std::vector mentioned_funcs; // Filled by callops (non-tag) in createOperation // Types - PointerType* Ptr_Type; - IntegerType* Bool_Type; - IntegerType* Int_Type; - Type* Void_Type; + BasicTypesAnalysis::BasicTypes Basic_Types; StructType* Formula_Type; StructType* DB_Type; StructType* Tag_Type; StructType* Tags_Type; + StructType* MathExpr_Type; StructType* Param_Type; + StructType* MemOpFunc_Type; StructType* CallOp_Type; StructType* CallTagOp_Type; StructType* ReleaseOp_Type; StructType* RWOp_Type; + StructType* ParamOp_Type; + StructType* AllocOp_Type; StructType* Contract_Type; StructType* Ref_Type; - Constant* Null_Const; + StructType* ParamReq_Type; // Helpers - bool checkIsStrParam(Value const* I); + bool checkIsStrParam(CallBase* CB, int idx); + Instruction* anyValToPtr(Value** V, Instruction* pos); // Misc bool isC = true; @@ -75,6 +80,7 @@ class InstrumentPass : public PassInfoMixin { std::unordered_set instrument_ignore; ContractManagerAnalysis::ContractDatabase* DB; + ModuleAnalysisManager* MAM; }; } // namespace llvm diff --git a/Passes/Intrinsics.cpp b/Passes/Intrinsics.cpp new file mode 100644 index 0000000..a3f0eea --- /dev/null +++ b/Passes/Intrinsics.cpp @@ -0,0 +1,198 @@ +#include "Intrinsics.hpp" +#include "BasicTypes.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace llvm; + +PreservedAnalyses IntrinsicsPass::run(Module &M, ModuleAnalysisManager &AM) { + Basic_Types = AM.getResult(M); + createCallees(M); + instrumentIntrinsics(M); + return PreservedAnalyses::all(); +} + +void IntrinsicsPass::createCallees(Module& M) { + // Get callee for alloca instr + FunctionType* FunctionAllocStackType = FunctionType::get(Basic_Types.Void_Type, {Basic_Types.Ptr_Type, Basic_Types.Int64_Type}, false); + allocStackCallee = calleeHelper(M, "CoVer_AllocStack", FunctionAllocStackType); + + FunctionType* FunctionFreeStackType = FunctionType::get(Basic_Types.Void_Type, {Basic_Types.Ptr_Type}, false); + freeStackCallee = calleeHelper(M, "CoVer_FreeStack", FunctionFreeStackType); + + // Get callee for global vals + FunctionType* FunctionGlobalRegType = FunctionType::get(Basic_Types.Void_Type, {Basic_Types.Ptr_Type, Basic_Types.Int64_Type}, false); + globalRegCallee = calleeHelper(M, "CoVer_RegisterGlobal", FunctionGlobalRegType); + + // Get callee for fort intrinsics + FunctionType* FunctionFAllocIntrinsicType = FunctionType::get(Basic_Types.Int_Type, {Basic_Types.Ptr_Type, Basic_Types.Int64_Type, Basic_Types.Int_Type}, true); + fallocPointerCallee = calleeHelper(M, "CoVer_FPointerAllocate", FunctionFAllocIntrinsicType); + + FunctionType* FunctionFDeallocIntrinsicType = FunctionType::get(Basic_Types.Int_Type, {Basic_Types.Ptr_Type, Basic_Types.Bool_Type, Basic_Types.Ptr_Type, Basic_Types.Ptr_Type, Basic_Types.Int_Type}, false); + fdeallocPointerCallee = calleeHelper(M, "CoVer_FPointerDeallocate", FunctionFDeallocIntrinsicType); +} + +void IntrinsicsPass::instrumentIntrinsics(Module& M) { + // Instrument all allocas + Function *FrameAddrIntrin = Intrinsic::getOrInsertDeclaration(&M, Intrinsic::frameaddress, {Basic_Types.Ptr_Type}); + Function *StackSaveIntrin = Intrinsic::getOrInsertDeclaration(&M, Intrinsic::stacksave, {Basic_Types.Ptr_Type}); + for (Function& F : M) { + if (F.isDeclaration()) continue; + bool hasAlloca = false; + for (BasicBlock const& BB : F) { + for (Instruction const& I : BB) { + if (isa(&I)) { + hasAlloca = true; + break; + } + } + } + if (!hasAlloca) continue; + + IRBuilder<> Builder(&*F.getEntryBlock().getFirstNonPHIOrDbgOrAlloca()); + + CallInst *BasePtr = Builder.CreateCall(FrameAddrIntrin, {Basic_Types.getInt(0)}, "base_ptr"); + CallInst *StackPtr = Builder.CreateCall(StackSaveIntrin, {}, "stack_ptr"); + + Value *BaseInt = Builder.CreatePtrToInt(BasePtr, Basic_Types.Int64_Type, "base_int"); + Value *StackInt = Builder.CreatePtrToInt(StackPtr, Basic_Types.Int64_Type, "stack_int"); + + Value *StackSize = Builder.CreateSub(BaseInt, StackInt, "stack_size"); + Builder.CreateCall(allocStackCallee, {StackPtr, StackSize}); + + EscapeEnumerator EE(F, "", false); + while (IRBuilder<>* IRB = EE.Next()) { + IRB->CreateCall(freeStackCallee, {StackPtr}); + } + } + + // Instrument global vars + if (M.getFunction("main")) { + auto Entry = M.getFunction("main")->getEntryBlock().getFirstNonPHIOrDbgOrAlloca(); + for (GlobalVariable& GV : M.globals()) { + if (!GV.hasInitializer()) continue; + if (GV.hasPrivateLinkage() || GV.hasComdat()) continue; + if (GV.getName().starts_with("llvm.") || GV.getName().starts_with("ContractValueInfo")) continue; + size_t gv_size = GV.getParent()->getDataLayout().getTypeAllocSize(GV.getInitializer()->getType()); + CallInst::Create(globalRegCallee, {&GV, Basic_Types.getInt64(gv_size)}, "", Entry); + } + } + + // Instrument Fortran allocate() + Function* fortAllocate = M.getFunction("_FortranAPointerAllocate"); + if (fortAllocate) { + for (User* U : fortAllocate->users()) { + if (CallBase* CB = dyn_cast(U)) { + // Need to figure out the size. + struct DimInfo { + std::vector params; + Value* size; + }; + std::vector dim_params; + CallBase* cur = dyn_cast(CB->getPrevNode()); + while (cur) { + if (cur->getCalledOperand() != M.getFunction("_FortranAPointerSetBounds")) break; + Value* lower = cur->getArgOperand(2); + Value* upper = cur->getArgOperand(3); + Value* one = Basic_Types.getInt64(1); + Instruction* diff = BinaryOperator::CreateSub(upper, lower, "", CB->getIterator()); + Instruction* res = BinaryOperator::CreateAdd(diff, one, "", CB->getIterator()); + std::vector args; + // Skip 0, which is just the pointer + args.push_back(cur->getArgOperand(1)); + args.push_back(cur->getArgOperand(2)); + args.push_back(cur->getArgOperand(3)); + dim_params.push_back({args, res}); + CallBase* prev = cur; + cur = cur->getPrevNode() ? dyn_cast(cur->getPrevNode()) : nullptr; + prev->eraseFromParent(); + } + Value* total_elems = Basic_Types.getInt64(1); + for (DimInfo const& info : dim_params) { + total_elems = BinaryOperator::CreateMul(total_elems, info.size, "", CB->getIterator()); + } + // Finally, multiply by base type size + // First, get base type size from global descriptor + GetElementPtrInst* GEP = GetElementPtrInst::Create(CB->getArgOperand(0)->getType(), CB->getArgOperand(0), {Basic_Types.getInt(1)}); + GEP->insertBefore(CB->getIterator()); + LoadInst* BaseSize = new LoadInst(Basic_Types.Int64_Type, GEP, "", false, CB->getIterator()); + total_elems = BinaryOperator::CreateMul(total_elems, BaseSize, "", CB->getIterator()); + std::vector intrinsicparams = {CB->getArgOperand(0), total_elems, Basic_Types.getInt(dim_params.size())}; + std::ranges::reverse_view rv{dim_params}; // Reverse to set bounds in correct order + for (DimInfo const& info : rv) { + for (Value* arg : info.params) intrinsicparams.push_back(arg); + } + + intrinsicparams.push_back(CB->getArgOperand(1)); + intrinsicparams.push_back(CB->getArgOperand(2)); + intrinsicparams.push_back(CB->getArgOperand(3)); + intrinsicparams.push_back(CB->getArgOperand(4)); + CallInst* intrinsicCI = CallInst::Create(fallocPointerCallee, intrinsicparams); + ReplaceInstWithInst(CB, intrinsicCI); + } + } + } + + // Instrument Fortran deallocate() + Function* fortDeallocate = M.getFunction("_FortranAPointerDeallocate"); + if (fortDeallocate) { + for (User* U : fortDeallocate->users()) { + if (CallBase* CB = dyn_cast(U)) { + if (CB->getCalledOperand() != fortDeallocate) continue; + CB->setCalledFunction(fdeallocPointerCallee); + } + } + } +} + +FunctionCallee IntrinsicsPass::calleeHelper(Module& M, std::string name, FunctionType* fnType) { + AttributeList fnAttr; + fnAttr = fnAttr.addFnAttribute(M.getContext(), Attribute::NoUnwind); + fnAttr = fnAttr.addFnAttribute(M.getContext(), Attribute::WillReturn); + fnAttr = fnAttr.addFnAttribute(M.getContext(), Attribute::NoCallback); + + FunctionCallee res = M.getOrInsertFunction(name + "_TMP", fnType, fnAttr); + if (M.getFunction(name)) { + Function* old = M.getFunction(name); + old->replaceAllUsesWith(res.getCallee()); + old->eraseFromParent(); + } + res.getCallee()->setName(name); + + // Create debug info + DIBuilder DIB(M); + SmallVector MDTypes; + for (Type const* T : fnType->subtypes()) { + MDTypes.push_back(Basic_Types.getMDForType(T)); + } + DISubroutineType* SRT = DIB.createSubroutineType(DIB.getOrCreateTypeArray(MDTypes)); + DISubprogram* SP = DIB.createFunction(*M.debug_compile_units_begin(), name, name, M.debug_compile_units_begin()->getFile(), 0, SRT, 0); + + // Finalize + Function* F = dyn_cast(res.getCallee()); + F->setLinkage(GlobalValue::ExternalLinkage); + F->setSubprogram(SP); + + DIB.finalize(); + return res; +} diff --git a/Passes/Intrinsics.hpp b/Passes/Intrinsics.hpp new file mode 100644 index 0000000..54e9137 --- /dev/null +++ b/Passes/Intrinsics.hpp @@ -0,0 +1,26 @@ +#include "BasicTypes.hpp" +#include +#include +#include +#include + +namespace llvm { + +class IntrinsicsPass : public PassInfoMixin { + public: + PreservedAnalyses run(Module& M, ModuleAnalysisManager &AM); + private: + BasicTypesAnalysis::BasicTypes Basic_Types; + + FunctionCallee calleeHelper(Module& M, std::string name, FunctionType* type); + void createCallees(Module& M); + FunctionCallee allocStackCallee; + FunctionCallee freeStackCallee; + FunctionCallee globalRegCallee; + FunctionCallee fallocPointerCallee; + FunctionCallee fdeallocPointerCallee; + + void instrumentIntrinsics(Module& M); +}; + +} diff --git a/Passes/Registrar.cpp b/Passes/Registrar.cpp index f4fa132..9d94884 100644 --- a/Passes/Registrar.cpp +++ b/Passes/Registrar.cpp @@ -3,11 +3,15 @@ #include #include +#include "BasicTypes.hpp" #include "ContractManager.hpp" +#include "ContractVerifierAlloc.hpp" #include "ContractVerifierPreCall.hpp" #include "ContractVerifierPostCall.hpp" #include "ContractVerifierRelease.hpp" +#include "ContractVerifierParam.hpp" #include "ContractPostProcess.hpp" +#include "Intrinsics.hpp" #include "Instrument.hpp" using namespace llvm; @@ -26,10 +30,22 @@ namespace { MPM.addPass(ContractVerifierReleasePass()); return true; } + if (Name == "contractVerifierParam") { + MPM.addPass(ContractVerifierParamPass()); + return true; + } + if (Name == "contractVerifierAlloc") { + MPM.addPass(ContractVerifierAllocPass()); + return true; + } if (Name == "contractPostProcess") { MPM.addPass(ContractPostProcessingPass()); return true; } + if (Name == "instrumentIntrinsics") { + MPM.addPass(IntrinsicsPass()); + return true; + } if (Name == "instrumentContracts") { MPM.addPass(InstrumentPass()); return true; @@ -38,6 +54,7 @@ namespace { }; void MAMHook(ModuleAnalysisManager &MAM) { + MAM.registerPass([&] { return BasicTypesAnalysis(); }); MAM.registerPass([&] { return ContractManagerAnalysis(); }); }; diff --git a/README.md b/README.md index 44cb7b5..2bc0368 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,6 @@ See [Usage](./Docs/Usage.md) - LLVM 21 or newer - Java (Build dependency only) -- dwarf.h (Ubuntu: libdw-dev, build dependency only) Optionally: - ANTLR4 (provided if not installed) diff --git a/Scripts/clangContracts.cpp b/Scripts/clangContracts.cpp index 045c5f0..b40a985 100644 --- a/Scripts/clangContracts.cpp +++ b/Scripts/clangContracts.cpp @@ -49,6 +49,12 @@ static cl::opt GenerateJSONReport("generate-json-report", cl::value_desc("JSON output path"), cl::cat(WrapperCategory)); +static cl::opt DebuggerCoVerPlugin("debugger-cover-plugin", + cl::desc("Run debugger on opt step"), + cl::ValueOptional, + cl::value_desc("Debugger path"), + cl::cat(WrapperCategory)); + // String option with ValueOptional to handle "full", "funconly", and "filtered=path.json" static cl::opt InstrumentContracts("instrument-contracts", cl::desc("Perform instrumentation for runtime analysis.\n" @@ -90,7 +96,12 @@ std::vector link_time_sources; // For predef fort contracts std::string opt_flags = ""; -std::string exec(std::string const& cmd) { +std::string exec(std::string const& cmd, bool interactive = true) { + if (interactive) { + int rc = std::system(cmd.c_str()); + if (rc) exit(rc); + return ""; + } std::array buffer; std::string result; FILE* pipe = popen(cmd.c_str(), "r"); @@ -179,7 +190,7 @@ void sanityCheckCompiler() { // Check for LLVM-based compiler cmd = WrapTarget + " --version | head -n 1"; - compiler_ident = exec(cmd); + compiler_ident = exec(cmd, false); if (compiler_ident.find("clang") == std::string::npos && compiler_ident.find("flang") == std::string::npos) { std::cerr << "Unknown compiler \"" << compiler_ident.substr(0, compiler_ident.size()-1) << "\"!\n"; std::cerr << "Make sure to use an LLVM-based compiler that supports outputting bitcode.\n"; @@ -285,7 +296,9 @@ int main(int argc, const char** argv) { execSafe("llvm-link" + bitcode_files + " -o " + tmpfile); // Call LLVM passes - std::string passlist = "function(sroa),contractVerifierPreCall,contractVerifierPostCall,contractVerifierRelease,contractPostProcess"; + std::string passlist = "function(sroa),instrumentIntrinsics,contractVerifierPreCall,contractVerifierPostCall,contractVerifierRelease,contractVerifierParam,contractVerifierAlloc,contractPostProcess"; + // ALWAYS FIRST OPT THEN INSTR! + // Otherwise significant performance loss! if (!opt_level.empty()) { passlist += ",default<" + opt_level.substr(1) + ">"; // opt_level substr cuts "-" from "-O" } @@ -295,11 +308,11 @@ int main(int argc, const char** argv) { // ...and link against analyser. Need to hackily link against stdlib as well for C code rem_args.first += " -Wl,--whole-archive @COVER_DYNAMIC_ANALYSER_PATH@ -Wl,-no-whole-archive -lstdc++"; } - execSafe("opt --load-pass-plugin=\"@DSA_PLUGIN_PATH@\" --load-pass-plugin \"@CONTR_PLUGIN_PATH@\" -passes='" + passlist + "' " + opt_flags + " " + tmpfile + " -o " + tmpfile + ".opt"); + execSafe(DebuggerCoVerPlugin + " opt --load-pass-plugin=\"@DSA_PLUGIN_PATH@\" --load-pass-plugin \"@CONTR_PLUGIN_PATH@\" -passes='" + passlist + "' " + opt_flags + " " + tmpfile + " -o " + tmpfile + ".opt"); close(fd); // Finalize executable execSafe("llc -filetype=obj --relocation-model=pic " + opt_level + " " + tmpfile + ".opt -o " + tmpfile + ".opt.o"); - execSafe(WrapTarget + " -fPIC -lm -ldl -lpthread -g -I\"@CONTR_INCLUDE_PATH@\"" + rem_args.first + " " + tmpfile + ".opt.o" + dest_arg); + execSafe(WrapTarget + " -fPIC -lm -ldl -lffi -lpthread -g -I\"@CONTR_INCLUDE_PATH@\"" + rem_args.first + " " + tmpfile + ".opt.o @COVER_INTRINSICS_LIB_PATH@ " + dest_arg); return 0; } diff --git a/Scripts/gen_mpi_contr_h.py b/Scripts/gen_mpi_contr_h.py index 75da942..d6dfca1 100644 --- a/Scripts/gen_mpi_contr_h.py +++ b/Scripts/gen_mpi_contr_h.py @@ -297,6 +297,172 @@ def add_contract(func: str, scope: str, contr: str): for func, tag_idx in tag_typeuse: add_contract(func, "TAGS", f"type_use({tag_idx})") +# Parameter Errors +paramerror_comm = [ + ("MPI_Send", 5), + ("MPI_Isend", 5), + ("MPI_Recv", 5), + ("MPI_Irecv", 5), + ("MPI_Sendrecv", 10), + ("MPI_Allgather", 6), + ("MPI_Cart_get", 0), +] +for func, idx in paramerror_comm: + add_contract(func, "PRE", f"param!({idx}:^=MPI_COMM_WORLD,!=NULL,!=MPI_COMM_NULL) MSG \"Communicator is invalid\"") + +# MPI_PROC_NULL uses negative special value in OpenMPI, add exception for it +paramerror_rank_send = [ + ("MPI_Send", 3), + ("MPI_Get", 3), + ("MPI_Reduce", 5), +] +for func, idx in paramerror_rank_send: + add_contract(func, "PRE", f"param!({idx}:^=MPI_PROC_NULL,>=0) MSG \"Rank is invalid\"") + +# MPI_ANY_SOURCE uses negative special value in OpenMPI, add exception for it +paramerror_rank_recv = [ + ("MPI_Recv", 3), +] +for func, idx in paramerror_rank_recv: + add_contract(func, "PRE", f"param!({idx}:^=MPI_PROC_NULL,^=MPI_ANY_SOURCE,>=0) MSG \"Rank is invalid\"") + +# MPI_STATUSES_IGNORE == NULL == MPI_STATUS_IGNORE in OpenMPI. Add exception for MPI_STATUS_IGNORE +paramerror_status = [ + ("MPI_Wait", 1), +] +for func, idx in paramerror_status: + add_contract(func, "PRE", f"param!({idx}:^=MPI_STATUS_IGNORE,!=NULL,!=MPI_STATUSES_IGNORE) MSG \"Status is invalid\"") + +# Buffer should never be null +paramerror_null = [ + ("MPI_Initialized", 0), + ("MPI_Send", 0), + ("MPI_Isend", 0), + ("MPI_Recv", 0), + ("MPI_Irecv", 0), + ("MPI_Sendrecv", 0), + ("MPI_Win_create", 0), + ("MPI_Win_allocate", 4), + ("MPI_Get", 0), +] +for func, idx in paramerror_null: + add_contract(func, "PRE", f"param!({idx}:!=NULL,!=MPI_IN_PLACE,!=MPI_BOTTOM) MSG \"Parameter is null, MPI_IN_PLACE, or MPI_BOTTOM\"") + +# For sendrecv also dont allow same as send buf in recv buf +add_contract("MPI_Sendrecv", "PRE", f"param!(5:!=NULL,!=MPI_IN_PLACE,!=MPI_BOTTOM,!=0 _arg) MSG \"Buffer is null or same as send buffer\"") + +# Comm buffer should be allocated +paramerror_null = [ + ("MPI_Send", 0), + ("MPI_Isend", 0), + ("MPI_Recv", 0), + ("MPI_Irecv", 0), + ("MPI_Sendrecv", 0), + ("MPI_Sendrecv", 5), + ("MPI_Get", 0), + ("MPI_Put", 0), +] +for func, idx in paramerror_null: + add_contract(func, "PRE", f"alloc!({idx}) MSG \"Buffer is not allocated\"") + +allocators = [ + ("MPI_Win_allocate", 4), +] +for func, idx in allocators: + add_contract(func, "POST", f"alloc!(*{idx}[0 _arg])") + + +# Datatype should not be null when doing communication +paramerror_datatype = [ + ("MPI_Get", 6), + ("MPI_Bcast", 2), +] +for func, idx in paramerror_datatype: + add_contract(func, "PRE", f"param!({idx}:!=NULL,!=MPI_DATATYPE_NULL) MSG \"Data type is invalid\"") + +# When doing P2P sends, the tag should never be MPI_ANY_TAG +paramerror_tag_send = [ + ("MPI_Send", 4), + ("MPI_Isend", 4), +] +for func, idx in paramerror_tag_send: + add_contract(func, "PRE", f"param!({idx}:!=MPI_ANY_TAG,>=0) MSG \"Tag is invalid\"") + +# MPI_OP_NULL should not be used for concrete operations +paramerror_op = [ + ("MPI_Reduce", 4), +] +for func, idx in paramerror_op: + add_contract(func, "PRE", f"param!({idx}:!=NULL,!=MPI_OP_NULL) MSG \"Operation is invalid\"") + +def get_param_values(lang: str) -> str: + param_values = { + "NULL": { + "c": "NULL", + "fort": "NULL()", + }, + "MPI_BOTTOM": { + "c": "MPI_BOTTOM", + "fort": "MPI_BOTTOM", + }, + "MPI_PROC_NULL": { + "c": "MPI_PROC_NULL", + "fort": "MPI_PROC_NULL", + }, + "MPI_ANY_SOURCE": { + "c": "MPI_ANY_SOURCE", + "fort": "MPI_ANY_SOURCE", + }, + "MPI_ANY_TAG": { + "c": "MPI_ANY_TAG", + "fort": "MPI_ANY_TAG", + }, + "MPI_IN_PLACE": { + "c": "MPI_IN_PLACE", + "fort": "MPI_IN_PLACE", + }, + "MPI_REQUEST_NULL": { + "c": "MPI_REQUEST_NULL", + "fort": "MPI_REQUEST_NULL", + }, + "MPI_OP_NULL": { + "c": "MPI_OP_NULL", + "fort": "MPI_OP_NULL", + }, + "MPI_COMM_NULL": { + "c": "MPI_COMM_NULL", + "fort": "MPI_COMM_NULL", + }, + "MPI_COMM_WORLD": { + "c": "MPI_COMM_WORLD", + "fort": "MPI_COMM_WORLD", + }, + "MPI_STATUS_IGNORE": { + "c": "MPI_STATUS_IGNORE", + "fort": "MPI_STATUS_IGNORE", + }, + "MPI_STATUSES_IGNORE": { + "c": "MPI_STATUSES_IGNORE", + "fort": "MPI_STATUSES_IGNORE", + }, + "MPI_DATATYPE_NULL": { + "c": "MPI_DATATYPE_NULL", + "fort": "MPI_DATATYPE_NULL", + } + } + output_pre = "Constant values needed for contracts\n" + output_templ = "" + output_str = "" + if lang == "c": + output_pre = "// " + output_pre + output_templ = "CONTRACT_VALUE_PAIR(@VAL_NAME@,@VAL_DATA@)" + if lang == "fort": + output_pre = "! " + output_pre + output_templ = "call Declare_Value(\"@VAL_NAME@\",@VAL_DATA@)" + for val_name, val_data in param_values.items(): + output_str += output_templ.replace("@PRE@", output_pre).replace("@VAL_NAME@", val_name).replace("@VAL_DATA@", val_data[lang]) + "\n" + return output_pre + output_str + # Output file boilerplate_header_c = f""" // Automatically generated by {os.path.basename(__file__)} @@ -304,11 +470,36 @@ def add_contract(func: str, scope: str, contr: str): // Identifier: {ver_identifier} #pragma once + #include "Contracts.h" #include #define MACRO_SAFETY(x) (x) +{get_param_values("c")} + +#ifdef __cplusplus + +void* operator new[](size_t size) CONTRACT(POST {{alloc!(R[0 _arg])}}); + +extern "C" {{ +#endif + +void __attribute__((weak)) CoVer_AllocStack(void* ptr, size_t size) CONTRACT( POST {{ alloc!(0[1 _arg]) }}) {{}}; +void __attribute__((weak)) CoVer_FreeStack(void* ptr) CONTRACT( POST {{ free!(0) }}) {{}}; + +void __attribute__((weak)) CoVer_RegisterGlobal(void* ptr, int64_t size) CONTRACT( POST {{ alloc!(0[ 1 _arg ]) }}) {{}}; + + +void* calloc(size_t num, size_t size) __THROW CONTRACT( POST {{ alloc!(R[ 0 _arg * 1 _arg]) }}); +void* malloc(size_t size) __THROW CONTRACT( POST {{ alloc!(R[0 _arg]) }}); + +void free(void*) __THROW CONTRACT( POST {{ free!(0) }}); + +#ifdef __cplusplus +}} +#endif + """ header_output_c = boilerplate_header_c @@ -322,8 +513,34 @@ def add_contract(func: str, scope: str, contr: str): use contract_helper """ -header_output_fort = boilerplate_header_fort + " use mpi\n implicit none\n" -header_output_fort_f08 = boilerplate_header_fort + " use mpi_f08\n implicit none\n" +fortran_lang_intrinsics = """ + ! Contracts for intrinsics - Mainly for allocation tracking + interface + ! Fortran language intrinsics + subroutine FortAlloc() bind(c, name="CoVer_FPointerAllocate") + end subroutine FortAlloc + subroutine FortFree() bind(c, name="CoVer_FPointerDeallocate") + end subroutine FortFree + + ! CoVer intrinsics + subroutine CoVer_AllocStack(ptr,size) bind(c, name="CoVer_AllocStack") + integer, pointer :: ptr + integer(kind=8) :: size + end subroutine CoVer_AllocStack + subroutine CoVer_FreeStack() bind(c, name="CoVer_FreeStack") + end subroutine CoVer_FreeStack + subroutine CoVer_RegisterGlobal() bind(c, name="CoVer_RegisterGlobal") + end subroutine CoVer_RegisterGlobal + end interface + call Declare_Contract(FortAlloc, \"POST { alloc!(*0[1 _arg]) }\") + call Declare_Contract(FortFree, \"POST { free!(0) }\") + call Declare_Contract(CoVer_AllocStack, \"POST { alloc!(0[1 _arg]) }\") + call Declare_Contract(CoVer_FreeStack, \"POST { free!(0) }\") + call Declare_Contract(CoVer_RegisterGlobal, \"POST { alloc!(0) }\") +""" + +header_output_fort = boilerplate_header_fort + f" use mpi\n implicit none\n\n{fortran_lang_intrinsics}\n\n{get_param_values('fort')}\n\n" +header_output_fort_f08 = boilerplate_header_fort + f" use mpi_f08\n implicit none\n\n{fortran_lang_intrinsics}\n\n{get_param_values('fort')}\n\n" header_output_fort_f08ts = header_output_fort_f08 exclude_fortran = [ @@ -391,7 +608,7 @@ def create_f08_contract(func: str, contract: str, support_ts: bool): header_output_c += ");\n\n" if func in exclude_fortran or "c2f" in func or "f2c" in func or "f082c" in func or "c2f08" in func or func.endswith("_c") or func.endswith("_fromint") or func.endswith("_toint"): continue # Only defined for C # Now: Fortran - contract_str_fort = contract_str.replace('"', '""').replace("\n", "").replace(" ", "").replace("*", "").replace("&", "").replace("read!(", "read!(*").replace("write!(", "write!(*") + contract_str_fort = contract_str.replace('"', '""').replace("\n", "").replace(" ", "").replace(":*", ":").replace(":&", ":") if func not in exclude_fortran_nof08: header_output_fort += " call Declare_Contract(" + func + ", \"" + contract_str_fort + "\")\n" # Fortran f08(ts) diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 5340296..3f21828 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -2,13 +2,21 @@ set(COVER_INSTALLED_BINARIES_DIR "${CMAKE_INSTALL_PREFIX}/bin") configure_file(lit.cfg.in ${CMAKE_CURRENT_LIST_DIR}/lit.cfg @ONLY) function(add_cover_test TEST_NAME) - add_test(NAME "${TEST_NAME}_c" COMMAND lit --verbose ${CMAKE_CURRENT_LIST_DIR}/c/${TEST_NAME}.c) - add_test(NAME "${TEST_NAME}_fort" COMMAND lit --verbose ${CMAKE_CURRENT_LIST_DIR}/fort/${TEST_NAME}.F90) + add_test(NAME "C::${TEST_NAME}" COMMAND lit --verbose ${CMAKE_CURRENT_LIST_DIR}/c/${TEST_NAME}.c) + add_test(NAME "Fortran::${TEST_NAME}" COMMAND lit --verbose ${CMAKE_CURRENT_LIST_DIR}/fort/${TEST_NAME}.F90) endfunction() add_cover_test(Correct-Minimal) add_cover_test(Correct-P2P) +add_cover_test(Correct-P2PStackBuf) +add_cover_test(Correct-WinAlloc) +add_cover_test(Correct-P2POffset) +add_cover_test(Correct-RankNull) add_cover_test(PostCall-MissingFinalize) add_cover_test(PreCall-MissingInit) add_cover_test(Release-DataRace) +add_cover_test(Param-InvalidComm) +add_cover_test(Param-SendRecvOverlap) +add_cover_test(Alloc-BufNotAllocated) +add_cover_test(Alloc-BufUseAfterFree) diff --git a/Tests/c/Alloc-BufNotAllocated.c b/Tests/c/Alloc-BufNotAllocated.c new file mode 100644 index 0000000..4ec4fa5 --- /dev/null +++ b/Tests/c/Alloc-BufNotAllocated.c @@ -0,0 +1,38 @@ +// RUN: %clangContracts %run_common + +#include +#include +#include + +int main(int argc, char** argv) { + int rank; + int* buf; + MPI_Request req; + + MPI_Init(NULL, NULL); + + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + + if (rank == 0) { + MPI_Isend(buf, 1, MPI_INT, 1, 0, MPI_COMM_WORLD, &req); + printf("Buf: %d\n", buf[0]); + } else { + buf = (int*)malloc(sizeof(int)); + buf[0] = 42; + MPI_Irecv(buf, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, &req); + } + MPI_Wait(&req, MPI_STATUS_IGNORE); + + MPI_Finalize(); + return 0; +} + +// CHECK-LABEL: Running Contract Manager on Module +// CHECK: Contract violation detected! +// CHECK: Buffer is not allocated +// CHECK: CoVer: Total Tool Runtime + +// CHECK-LABEL: CoVer-Dynamic: Initializing... +// CHECK: Contract violation detected! +// CHECK: Buffer is not allocated +// Dont check for analysis finished, MPI implementation might crash. diff --git a/Tests/c/Alloc-BufUseAfterFree.c b/Tests/c/Alloc-BufUseAfterFree.c new file mode 100644 index 0000000..7417b6c --- /dev/null +++ b/Tests/c/Alloc-BufUseAfterFree.c @@ -0,0 +1,39 @@ +// RUN: %clangContracts %run_common + +#include +#include +#include + +int main(int argc, char** argv) { + int rank; + int* buf; + MPI_Request req; + + MPI_Init(NULL, NULL); + + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + + buf = (int*)malloc(sizeof(int)); + buf[0] = 42; + if (rank == 0) { + free(buf); + MPI_Isend(buf, 1, MPI_INT, 1, 0, MPI_COMM_WORLD, &req); + printf("Buf: %d\n", buf[0]); + } else { + MPI_Irecv(buf, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, &req); + } + MPI_Wait(&req, MPI_STATUS_IGNORE); + + MPI_Finalize(); + return 0; +} + +// CHECK-LABEL: Running Contract Manager on Module +// CHECK: Contract violation detected! +// CHECK: Buffer is not allocated +// CHECK: CoVer: Total Tool Runtime + +// CHECK-LABEL: CoVer-Dynamic: Initializing... +// CHECK: Contract violation detected! +// CHECK: Buffer is not allocated +// Dont check for analysis finished, MPI implementation might crash. diff --git a/Tests/c/Correct-P2P.c b/Tests/c/Correct-P2P.c index 30f0ca0..a1fcd82 100644 --- a/Tests/c/Correct-P2P.c +++ b/Tests/c/Correct-P2P.c @@ -17,7 +17,7 @@ int main(int argc, char** argv) { buf[0] = 42; if (rank == 0) { MPI_Isend(buf, 1, MPI_INT, 1, 0, MPI_COMM_WORLD, &req); - printf("Buf: %d", buf[0]); + printf("Buf: %d\n", buf[0]); } else { MPI_Irecv(buf, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, &req); } diff --git a/Tests/c/Correct-P2POffset.c b/Tests/c/Correct-P2POffset.c new file mode 100644 index 0000000..c32069c --- /dev/null +++ b/Tests/c/Correct-P2POffset.c @@ -0,0 +1,36 @@ +// RUN: %clangContracts %run_common + +#include +#include +#include + +int main(int argc, char** argv) { + int rank; + int* buf; + MPI_Request req; + + MPI_Init(NULL, NULL); + + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + + buf = (int*)malloc(sizeof(int)*3); + buf[2] = 42; + if (rank == 0) { + MPI_Isend(&buf[1], 1, MPI_INT, 1, 0, MPI_COMM_WORLD, &req); + printf("Buf: %d\n", buf[2]); + } else { + MPI_Irecv(buf, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, &req); + } + MPI_Wait(&req, MPI_STATUS_IGNORE); + + MPI_Finalize(); + return 0; +} + +// CHECK-LABEL: Running Contract Manager on Module +// CHECK-NOT: Contract violation detected! +// CHECK: CoVer: Total Tool Runtime + +// CHECK-LABEL: CoVer-Dynamic: Initializing... +// CHECK-NOT: Contract violation detected! +// CHECK: Analysis finished. diff --git a/Tests/c/Correct-P2PStackBuf.c b/Tests/c/Correct-P2PStackBuf.c new file mode 100644 index 0000000..e6727a6 --- /dev/null +++ b/Tests/c/Correct-P2PStackBuf.c @@ -0,0 +1,34 @@ +// RUN: %clangContracts %run_common + +#include +#include +#include + +int main(int argc, char** argv) { + int rank; + int buf = 42; + MPI_Request req; + + MPI_Init(NULL, NULL); + + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + + if (rank == 0) { + MPI_Isend(&buf, 1, MPI_INT, 1, 0, MPI_COMM_WORLD, &req); + printf("Buf: %d\n", buf); + } else { + MPI_Irecv(&buf, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, &req); + } + MPI_Wait(&req, MPI_STATUS_IGNORE); + + MPI_Finalize(); + return 0; +} + +// CHECK-LABEL: Running Contract Manager on Module +// CHECK-NOT: Contract violation detected! +// CHECK: CoVer: Total Tool Runtime + +// CHECK-LABEL: CoVer-Dynamic: Initializing... +// CHECK-NOT: Contract violation detected! +// CHECK: Analysis finished. diff --git a/Tests/c/Correct-RankNull.c b/Tests/c/Correct-RankNull.c new file mode 100644 index 0000000..4098562 --- /dev/null +++ b/Tests/c/Correct-RankNull.c @@ -0,0 +1,46 @@ +// Copied from MPI-BugBench: Correct-Rank-001 + +// RUN: %clangContracts %run_common + +#include +#include +#include +#include +#include + +int main(int argc, char **argv) { + int nprocs = -1; + int rank = -1; + MPI_Win mpi_win_0; + int *winbuf; + + MPI_Init(&argc, &argv); + MPI_Comm_size(MPI_COMM_WORLD, &nprocs); + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + if (nprocs < 2) + printf( + "MBB ERROR: This test needs at least 2 processes to produce a bug!\n"); + + int *buf = (int *)calloc(10, sizeof(int)); + + MPI_Win_allocate(10 * sizeof(int), sizeof(int), MPI_INFO_NULL, MPI_COMM_WORLD, + &winbuf, &mpi_win_0); + MPI_Win_fence(0, mpi_win_0); + if (rank == 0) { + MPI_Get(buf, 10, MPI_INT, MPI_PROC_NULL, 0, 10, MPI_INT, mpi_win_0); + } + MPI_Win_fence(0, mpi_win_0); + MPI_Win_free(&mpi_win_0); + + MPI_Finalize(); + printf("Rank %d finished normally\n", rank); + return 0; +} + +// CHECK-LABEL: Running Contract Manager on Module +// CHECK-NOT: Contract violation detected! +// CHECK: CoVer: Total Tool Runtime + +// CHECK-LABEL: CoVer-Dynamic: Initializing... +// CHECK-NOT: Contract violation detected! +// CHECK: Analysis finished. diff --git a/Tests/c/Correct-WinAlloc.c b/Tests/c/Correct-WinAlloc.c new file mode 100644 index 0000000..9f0ee57 --- /dev/null +++ b/Tests/c/Correct-WinAlloc.c @@ -0,0 +1,36 @@ +// RUN: %clangContracts %run_common + +#include +#include +#include + +int main(int argc, char** argv) { + int rank; + int* buf; + MPI_Win win; + + MPI_Init(NULL, NULL); + + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + + MPI_Win_allocate(sizeof(int), sizeof(int), MPI_INFO_NULL, MPI_COMM_WORLD, &buf, &win); + buf[0] = 42; + + MPI_Win_fence(0, win); + if (rank == 0) { + MPI_Put(buf, 1, MPI_INT, 1, sizeof(int), 1, MPI_INT, win); + printf("Buf: %d\n", buf[0]); + } + MPI_Win_fence(0, win); + + MPI_Finalize(); + return 0; +} + +// CHECK-LABEL: Running Contract Manager on Module +// CHECK-NOT: Contract violation detected! +// CHECK: CoVer: Total Tool Runtime + +// CHECK-LABEL: CoVer-Dynamic: Initializing... +// CHECK-NOT: Contract violation detected! +// CHECK: Analysis finished. diff --git a/Tests/c/Param-InvalidComm.c b/Tests/c/Param-InvalidComm.c new file mode 100644 index 0000000..cb2783c --- /dev/null +++ b/Tests/c/Param-InvalidComm.c @@ -0,0 +1,38 @@ +// RUN: %clangContracts %run_common + +#include +#include +#include + +int main(int argc, char** argv) { + int rank; + int* buf; + MPI_Request req; + + MPI_Init(NULL, NULL); + + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + + buf = (int*)malloc(sizeof(int)); + buf[0] = 42; + if (rank == 0) { + MPI_Isend(buf, 1, MPI_INT, 1, 0, MPI_COMM_NULL, &req); + printf("Buf: %d\n", buf[0]); + } else { + MPI_Irecv(buf, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, &req); + } + MPI_Wait(&req, MPI_STATUS_IGNORE); + + MPI_Finalize(); + return 0; +} + +// CHECK-LABEL: Running Contract Manager on Module +// CHECK: Contract violation detected! +// CHECK: Communicator is invalid +// CHECK: CoVer: Total Tool Runtime + +// CHECK-LABEL: CoVer-Dynamic: Initializing... +// CHECK: Contract violation detected! +// CHECK: Communicator is invalid +// Dont check for analysis finished, MPI implementation might crash. diff --git a/Tests/c/Param-SendRecvOverlap.c b/Tests/c/Param-SendRecvOverlap.c new file mode 100644 index 0000000..85abcef --- /dev/null +++ b/Tests/c/Param-SendRecvOverlap.c @@ -0,0 +1,54 @@ +// Copied from MPI-BugBench: InvalidParam-Buffer-mpi_sendrecv-001 + +// RUN: %clangContracts %run_common + +#include +#include +#include +#include +#include + +int main(int argc, char **argv) { + int nprocs = -1; + int rank = -1; + + MPI_Init(&argc, &argv); + MPI_Comm_size(MPI_COMM_WORLD, &nprocs); + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + if (nprocs < 2) + printf( + "MBB ERROR: This test needs at least 2 processes to produce a bug!\n"); + + int *buf = (int *)calloc(10, sizeof(int)); + + int *recv_buf = (int *)calloc(10, sizeof(int)); + + if (rank == 0) { + /*MBBERROR_BEGIN*/ MPI_Sendrecv(buf, 10, MPI_INT, 1, 0, buf, 10, MPI_INT, 1, + 0, MPI_COMM_WORLD, + MPI_STATUS_IGNORE); /*MBBERROR_END*/ + } + if (rank == 1) { + /*MBBERROR_BEGIN*/ MPI_Sendrecv(buf, 10, MPI_INT, 0, 0, buf, 10, MPI_INT, 0, + 0, MPI_COMM_WORLD, + MPI_STATUS_IGNORE); /*MBBERROR_END*/ + } + + free(buf); + + free(recv_buf); + + MPI_Finalize(); + printf("Rank %d finished normally\n", rank); + return 0; +} + +// CHECK-LABEL: Running Contract Manager on Module +// CHECK: Contract violation detected! +// CHECK: Buffer is null or same as send buffer +// CHECK: CoVer: Total Tool Runtime + +// CHECK-LABEL: CoVer-Dynamic: Initializing... +// CHECK: Contract violation detected! +// CHECK: Buffer is null or same as send buffer +// Dont check for analysis finished, MPI implementation might crash. diff --git a/Tests/c/Release-DataRace.c b/Tests/c/Release-DataRace.c index 37a368b..fc14a92 100644 --- a/Tests/c/Release-DataRace.c +++ b/Tests/c/Release-DataRace.c @@ -1,5 +1,6 @@ // RUN: %clangContracts %run_common +#include #include #include diff --git a/Tests/fort/Alloc-BufNotAllocated.F90 b/Tests/fort/Alloc-BufNotAllocated.F90 new file mode 100644 index 0000000..7161a89 --- /dev/null +++ b/Tests/fort/Alloc-BufNotAllocated.F90 @@ -0,0 +1,36 @@ +! RUN: %flangContracts %run_common + +program main + use mpi_f08 + implicit none + + integer :: rank + integer, pointer :: buf(:) + type(MPI_Request) :: req + + call MPI_Init() + + call MPI_Comm_rank(MPI_COMM_WORLD, rank) + + if (rank == 0) then + call MPI_Isend(buf, 1, MPI_INT, 1, 0, MPI_COMM_WORLD, req) + print *, "Buf: ", buf(1) + else + allocate(buf(1)) + buf(1) = 42 + call MPI_Irecv(buf, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, req) + end if + call MPI_Wait(req, MPI_STATUS_IGNORE) + + call MPI_Finalize() +end program + +! CHECK-LABEL: Running Contract Manager on Module +! CHECK: Contract violation detected! +! CHECK: Buffer is not allocated +! CHECK: CoVer: Total Tool Runtime + +! CHECK-LABEL: CoVer-Dynamic: Initializing... +! CHECK: Contract violation detected! +! CHECK: Buffer is not allocated +! Dont check for analysis finished, MPI implementation might crash. diff --git a/Tests/fort/Alloc-BufUseAfterFree.F90 b/Tests/fort/Alloc-BufUseAfterFree.F90 new file mode 100644 index 0000000..04cfd6b --- /dev/null +++ b/Tests/fort/Alloc-BufUseAfterFree.F90 @@ -0,0 +1,37 @@ +! RUN: %flangContracts %run_common + +program main + use mpi_f08 + implicit none + + integer :: rank + integer, pointer :: buf(:) + type(MPI_Request) :: req + + call MPI_Init() + + call MPI_Comm_rank(MPI_COMM_WORLD, rank) + + allocate(buf(1)) + buf(1) = 42 + if (rank == 0) then + deallocate(buf) + call MPI_Isend(buf, 1, MPI_INT, 1, 0, MPI_COMM_WORLD, req) + print *, "Buf: ", buf(1) + else + call MPI_Irecv(buf, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, req) + end if + call MPI_Wait(req, MPI_STATUS_IGNORE) + + call MPI_Finalize() +end program + +! CHECK-LABEL: Running Contract Manager on Module +! CHECK: Contract violation detected! +! CHECK: Buffer is not allocated +! CHECK: CoVer: Total Tool Runtime + +! CHECK-LABEL: CoVer-Dynamic: Initializing... +! CHECK: Contract violation detected! +! CHECK: Buffer is not allocated +! Dont check for analysis finished, MPI implementation might crash. diff --git a/Tests/fort/Correct-Minimal.F90 b/Tests/fort/Correct-Minimal.F90 index 5d6ac1e..c17abcc 100644 --- a/Tests/fort/Correct-Minimal.F90 +++ b/Tests/fort/Correct-Minimal.F90 @@ -3,6 +3,7 @@ program CorrectMinimal use mpi_f08 implicit none + integer :: ierr call MPI_Init(ierr) diff --git a/Tests/fort/Correct-P2P.F90 b/Tests/fort/Correct-P2P.F90 index 5a49f2e..e5c99e7 100644 --- a/Tests/fort/Correct-P2P.F90 +++ b/Tests/fort/Correct-P2P.F90 @@ -2,6 +2,8 @@ program main use mpi_f08 + implicit none + integer :: rank integer, pointer :: buf(:) type(MPI_Request) :: req diff --git a/Tests/fort/Correct-P2POffset.F90 b/Tests/fort/Correct-P2POffset.F90 new file mode 100644 index 0000000..af9e135 --- /dev/null +++ b/Tests/fort/Correct-P2POffset.F90 @@ -0,0 +1,34 @@ +! RUN: %flangContracts %run_common + +program main + use mpi_f08 + implicit none + + integer :: rank + integer, pointer :: buf(:) + type(MPI_Request) :: req + + call MPI_Init() + + call MPI_Comm_rank(MPI_COMM_WORLD, rank) + + allocate(buf(3)) + buf(3) = 42 + if (rank == 0) then + call MPI_Isend(buf(3), 1, MPI_INT, 1, 0, MPI_COMM_WORLD, req) + print *, "Buf: ", buf(3) + else + call MPI_Irecv(buf, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, req) + end if + call MPI_Wait(req, MPI_STATUS_IGNORE) + + call MPI_Finalize() +end program + +! CHECK-LABEL: Running Contract Manager on Module +! CHECK-NOT: Contract violation detected! +! CHECK: CoVer: Total Tool Runtime + +! CHECK-LABEL: CoVer-Dynamic: Initializing... +! CHECK-NOT: Contract violation detected! +! CHECK: Analysis finished. diff --git a/Tests/fort/Correct-P2PStackBuf.F90 b/Tests/fort/Correct-P2PStackBuf.F90 new file mode 100644 index 0000000..d9d9e56 --- /dev/null +++ b/Tests/fort/Correct-P2PStackBuf.F90 @@ -0,0 +1,32 @@ +! RUN: %flangContracts %run_common + +program main + use mpi_f08 + implicit none + + integer :: rank + integer :: buf(1) = 42 + type(MPI_Request) :: req + + call MPI_Init() + + call MPI_Comm_rank(MPI_COMM_WORLD, rank) + + if (rank == 0) then + call MPI_Isend(buf, 1, MPI_INT, 1, 0, MPI_COMM_WORLD, req) + print *, "Buf: ", buf(1) + else + call MPI_Irecv(buf, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, req) + end if + call MPI_Wait(req, MPI_STATUS_IGNORE) + + call MPI_Finalize() +end program + +! CHECK-LABEL: Running Contract Manager on Module +! CHECK-NOT: Contract violation detected! +! CHECK: CoVer: Total Tool Runtime + +! CHECK-LABEL: CoVer-Dynamic: Initializing... +! CHECK-NOT: Contract violation detected! +! CHECK: Analysis finished. diff --git a/Tests/fort/Correct-RankNull.F90 b/Tests/fort/Correct-RankNull.F90 new file mode 100644 index 0000000..dbe18ea --- /dev/null +++ b/Tests/fort/Correct-RankNull.F90 @@ -0,0 +1,51 @@ +! Copied from MPI-BugBench: Correct-Rank-001 + +! RUN: %flangContracts %run_common + +program main + use mpi_f08 + implicit none + + integer :: ierr + integer :: nprocs = -1 + integer :: rank = -1 + integer :: double_size + integer :: integer_size + integer :: logical_size + integer :: i ! Loop index used by some tests + type(MPI_Win) :: mpi_win_0 + type(c_ptr) :: winbuf + integer, pointer :: buf(:) + + call MPI_Init(ierr) + call MPI_Comm_size(MPI_COMM_WORLD, nprocs, ierr) + call MPI_Comm_rank(MPI_COMM_WORLD, rank, ierr) + if (nprocs .lt. 2) then + print *, "MBB ERROR: This test needs at least 2 processes to produce a bug!\n" + end if + + call mpi_type_size(MPI_DOUBLE_PRECISION, double_size, ierr) + call mpi_type_size(MPI_INTEGER, integer_size, ierr) + call mpi_type_size(MPI_LOGICAL, logical_size, ierr) + + allocate (buf(0:(10) - 1)) + + call MPI_Win_allocate(int(10*integer_size, mpi_address_kind), integer_size, MPI_INFO_NULL, MPI_COMM_WORLD, winbuf, mpi_win_0, ierr) + call MPI_Win_fence(0, mpi_win_0, ierr) + if (rank == 0) then + call MPI_Get(buf, 10, MPI_INTEGER, MPI_PROC_NULL, int(0, mpi_address_kind), 10, MPI_INTEGER, mpi_win_0, ierr) + end if + call MPI_Win_fence(0, mpi_win_0, ierr) + call MPI_Win_free(mpi_win_0, ierr) + + call MPI_Finalize(ierr) + print *, "Rank ", rank, " finished normally" +end program + +! CHECK-LABEL: Running Contract Manager on Module +! CHECK-NOT: Contract violation detected! +! CHECK: CoVer: Total Tool Runtime + +! CHECK-LABEL: CoVer-Dynamic: Initializing... +! CHECK-NOT: Contract violation detected! +! CHECK: Analysis finished. diff --git a/Tests/fort/Correct-WinAlloc.F90 b/Tests/fort/Correct-WinAlloc.F90 new file mode 100644 index 0000000..235fcb4 --- /dev/null +++ b/Tests/fort/Correct-WinAlloc.F90 @@ -0,0 +1,39 @@ +! RUN: %flangContracts %run_common + +program main + use mpi_f08 + use iso_c_binding + implicit none + + integer :: rank + type(c_ptr) :: winbuf + integer, pointer :: buf(:) + integer :: integer_size + type(MPI_Win) :: win + + call MPI_Init() + + call MPI_Type_size(MPI_INT, integer_size) + call MPI_Comm_rank(MPI_COMM_WORLD, rank) + + call MPI_Win_allocate(int(integer_size, kind=mpi_address_kind), integer_size, MPI_INFO_NULL, MPI_COMM_WORLD, winbuf, win) + call c_f_pointer(winbuf, buf, [1]) + buf(1) = 42 + + call MPI_Win_fence(0, win) + if (rank == 0) then + call MPI_Put(buf, 1, MPI_INT, 1, int(integer_size, kind=mpi_address_kind), 1, MPI_INT, win) + print *, "Buf: ", buf(1) + end if + call MPI_Win_fence(0, win) + + call MPI_Finalize() +end program + +! CHECK-LABEL: Running Contract Manager on Module +! CHECK-NOT: Contract violation detected! +! CHECK: CoVer: Total Tool Runtime + +! CHECK-LABEL: CoVer-Dynamic: Initializing... +! CHECK-NOT: Contract violation detected! +! CHECK: Analysis finished. diff --git a/Tests/fort/Param-InvalidComm.F90 b/Tests/fort/Param-InvalidComm.F90 new file mode 100644 index 0000000..301b43d --- /dev/null +++ b/Tests/fort/Param-InvalidComm.F90 @@ -0,0 +1,36 @@ +! RUN: %flangContracts %run_common + +program main + use mpi_f08 + implicit none + + integer :: rank + integer, pointer :: buf(:) + type(MPI_Request) :: req + + call MPI_Init() + + call MPI_Comm_rank(MPI_COMM_WORLD, rank) + + allocate(buf(1)) + buf(1) = 42 + if (rank == 0) then + call MPI_Isend(buf, 1, MPI_INT, 1, 0, MPI_COMM_NULL, req) + print *, "Buf: ", buf(1) + else + call MPI_Irecv(buf, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, req) + end if + call MPI_Wait(req, MPI_STATUS_IGNORE) + + call MPI_Finalize() +end program + +! CHECK-LABEL: Running Contract Manager on Module +! CHECK: Contract violation detected! +! CHECK: Communicator is invalid +! CHECK: CoVer: Total Tool Runtime + +! CHECK-LABEL: CoVer-Dynamic: Initializing... +! CHECK: Contract violation detected! +! CHECK: Communicator is invalid +! Dont check if analysis finished, MPI implementation might crash. diff --git a/Tests/fort/Param-SendRecvOverlap.F90 b/Tests/fort/Param-SendRecvOverlap.F90 new file mode 100644 index 0000000..2afc2da --- /dev/null +++ b/Tests/fort/Param-SendRecvOverlap.F90 @@ -0,0 +1,61 @@ +! Copied from MPI-BugBench: InvalidParam-Buffer-mpi_sendrecv-001 + +! RUN: %flangContracts %run_common + +program main + use mpi_f08 + implicit none + + integer :: ierr + integer :: nprocs = -1 + integer :: rank = -1 + integer :: double_size + integer :: integer_size + integer :: logical_size + integer :: i ! Loop index used by some tests + integer, pointer :: buf(:) + integer, pointer :: recv_buf(:) + + call MPI_Init(ierr) + call MPI_Comm_size(MPI_COMM_WORLD, nprocs, ierr) + call MPI_Comm_rank(MPI_COMM_WORLD, rank, ierr) + if (nprocs .lt. 2) then + print *, "MBB ERROR: This test needs at least 2 processes to produce a bug!\n" + end if + + call mpi_type_size(MPI_DOUBLE_PRECISION, double_size, ierr) + call mpi_type_size(MPI_INTEGER, integer_size, ierr) + call mpi_type_size(MPI_LOGICAL, logical_size, ierr) + + allocate (buf(0:(10) - 1)) + + allocate (recv_buf(0:(10) - 1)) + + if (rank == 0) then +! MBBERROR_BEGIN + call MPI_Sendrecv(buf, 10, MPI_INTEGER, 1, 0, buf, 10, MPI_INTEGER, 1, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE, ierr) +! MBBERROR_END + end if + if (rank == 1) then +! MBBERROR_BEGIN + call MPI_Sendrecv(buf, 10, MPI_INTEGER, 0, 0, buf, 10, MPI_INTEGER, 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE, ierr) +! MBBERROR_END + end if + + deallocate (buf) + + deallocate (recv_buf) + + call MPI_Finalize(ierr) + print *, "Rank ", rank, " finished normally" +end program + +! CHECK-LABEL: Running Contract Manager on Module +! CHECK: Contract violation detected! +! CHECK: Buffer is null or same as send buffer +! CHECK: CoVer: Total Tool Runtime + +! CHECK-LABEL: CoVer-Dynamic: Initializing... +! CHECK: Contract violation detected! +! CHECK: Buffer is null or same as send buffer +! Dont check if analysis finished, MPI implementation might crash. diff --git a/Tests/fort/PostCall-MissingFinalize.F90 b/Tests/fort/PostCall-MissingFinalize.F90 index 2f45881..e58464f 100644 --- a/Tests/fort/PostCall-MissingFinalize.F90 +++ b/Tests/fort/PostCall-MissingFinalize.F90 @@ -3,6 +3,7 @@ program PostCallMissingFinalize use mpi_f08 implicit none + integer :: ierr call MPI_Init(ierr) diff --git a/Tests/fort/PreCall-MissingInit.F90 b/Tests/fort/PreCall-MissingInit.F90 index 2f02295..ca1afd9 100644 --- a/Tests/fort/PreCall-MissingInit.F90 +++ b/Tests/fort/PreCall-MissingInit.F90 @@ -3,6 +3,7 @@ program PreCallMissingInit use mpi_f08 implicit none + integer :: ierr call MPI_Finalize(ierr) diff --git a/Tests/fort/Release-DataRace.F90 b/Tests/fort/Release-DataRace.F90 index ae0dfac..25c2322 100644 --- a/Tests/fort/Release-DataRace.F90 +++ b/Tests/fort/Release-DataRace.F90 @@ -2,6 +2,8 @@ program main use mpi_f08 + implicit none + integer :: rank integer, pointer :: buf(:) type(MPI_Request) :: req diff --git a/Utils/ContractPassUtility.cpp b/Utils/ContractPassUtility.cpp index ea74e28..920e20d 100644 --- a/Utils/ContractPassUtility.cpp +++ b/Utils/ContractPassUtility.cpp @@ -4,18 +4,25 @@ #include #include #include +#include +#include #include +#include #include #include +#include #include +#include #include #include #include #include #include #include +#include #include #include +#include #include #include #include @@ -34,6 +41,9 @@ using namespace llvm; // To only warn once if a CB is calling an unknown function static std::set UnknownCalledParam; +// For language-specific stuff +static bool isFort = false; + const Value* ContractPassUtility::betterGetPointerOperand(const Value* V) { const Value* b = getPointerOperand(V); if (b == nullptr) { @@ -42,6 +52,18 @@ const Value* ContractPassUtility::betterGetPointerOperand(const Value* V) { return b; } +StoreInst* ContractPassUtility::getLastStore(CallBase* CB, int idx, FunctionAnalysisManager* FAM) { + Instruction* cur = CB->getPrevNode(); + while (cur) { + if (isa(cur) && !dyn_cast(cur)->getCalledOperand()->getName().starts_with("PPDCV")) break; + if (StoreInst* SI = dyn_cast(cur)) { + if (SI->getPointerOperand() == CB->getArgOperand(idx)) return SI; + } + cur = cur->getPrevNode(); + } + return nullptr; +} + std::map getFunctionParentInstrCandidates(const Value* Ip) { if (!isa(Ip)) return {}; std::set> candidates = {{dyn_cast(Ip), 0}}; @@ -122,6 +144,10 @@ int resolveFunctionDifference(const Value** A, const Value** B) { namespace ContractPassUtility { +void Initialize(Module& M) { + isFort = M.getFunction("_QQmain"); +} + std::optional getLineNumber(const Instruction* I) { if (const DebugLoc& N = I->getDebugLoc()) { return N.getLine(); @@ -149,6 +175,67 @@ FileReference getFileReference(const Instruction* I) { }; } +bool isTrivialAlloc(Value const* V) { + // First possibility: Its a global alloc, trivially fulfilled + if (GlobalVariable const* GV = dyn_cast(V)) { + if (isFort) { + SmallVector dbg_arr; + GV->getDebugInfo(dbg_arr); + if (!dbg_arr.empty()) { + if (DICompositeType* T = dyn_cast(dbg_arr[0]->getVariable()->getType())) { + if (T->getDataLocationExp()) { + return false; + } + } + } + } + return true; + } + // Second possibility: Its a stack var, trivially fulfilled + const Value* tmp = V; + while (isa(tmp)) { + tmp = getPointerOperand(tmp); + } + + // Stack Variables + if (AllocaInst const* AI = dyn_cast(tmp)) { + if (isFort) { + // Fortran will do weird pointer / metadata stuff -> Need to check if stack thingy has external data location + Instruction const* dbgdeclare = AI; + while (dbgdeclare->getNextNode() && isa(dbgdeclare)) dbgdeclare = dbgdeclare->getNextNode(); + for (DbgRecord const& DR : dbgdeclare->getDbgRecordRange()) { + if (DbgVariableRecord const* DVR = dyn_cast(&DR)) { + if (DVR->getAddress() != AI) continue; + if (DICompositeType const* T = dyn_cast(DVR->getVariable()->getType())) { + if (!T->getDataLocationExp()) { + return true; + } + } + } + } + } else { + // C will just put the stack var in the function like a normal program -> already allocated + return true; + } + } + + // Not trivially allocated + return false; +} + +ConstantInt* fortCheckAndGetGlbInt(Value* V) { + if (V->getName().starts_with("_QQ")) { + if (GlobalVariable const* GV = dyn_cast(V)) { + if (GV->hasInitializer()) { + if (StructType const* T = dyn_cast(GV->getInitializer()->getType())) { + if (T->getNumElements() == 1 && T->getElementType(0)->isIntegerTy()) return dyn_cast(GV->getInitializer()->getAggregateElement((unsigned int)0)); + } + } + } + } + return nullptr; +} + bool checkCalledApplies(const CallBase* CB, const StringRef Target, bool isTag, std::map> Tags) { if (!isTag) { if (CB->getCalledOperand()->getName().empty()) { @@ -190,9 +277,7 @@ bool checkParamMatch(const Value* contrP, const Value* callP, ContractTree::Para } // Only use DSA for Fortran - const bool use_dsa = (isa(contrP) && dyn_cast(contrP)->getModule()->getFunction("_QQmain")) || - (isa(callP) && dyn_cast(callP)->getModule()->getFunction("_QQmain")); - if (!use_dsa) { + if (!isFort) { // Resolve function differences. // If one is a global, this does not matter, so check if they are instructions first if (isa(source) && isa(target)) { @@ -213,8 +298,11 @@ bool checkParamMatch(const Value* contrP, const Value* callP, ContractTree::Para case ContractTree::ParamAccess::DEREF: // Contr has a pointer, call has value. // If interproc, diff should be -1 if already resolved - if (diff == 0) - target = getLoadStorePointerOperand(target); + if (diff == 0) { + Value const* V = getLoadStorePointerOperand(target); + if (!V && IS_DEBUG) WithColor(errs(), HighlightColor::String) << "Note: Static deref failed, falling back to orig.\n"; + target = V ? V : target; + } else if (diff != -1) return false; break; case ContractTree::ParamAccess::ADDROF: @@ -228,7 +316,7 @@ bool checkParamMatch(const Value* contrP, const Value* callP, ContractTree::Para if (source == target) return true; - if (use_dsa) { + if (isFort) { std::shared_ptr steens = MAM->getResult(*getModule(contrP)); if (steens->hasNodeForValue(source) && steens->hasNodeForValue(target)) { DSNodeHandle sourceNode = steens->getNodeForValue(source); diff --git a/Utils/bash-autocomplete.sh b/Utils/bash-autocomplete.sh index 205858d..3f650e7 100644 --- a/Utils/bash-autocomplete.sh +++ b/Utils/bash-autocomplete.sh @@ -1,6 +1,6 @@ # Please add "source /path/to/bash-autocomplete.sh" to your .bashrc to use this. -_@EXECUTABLE_WRAPPER_NAME@_filedir() +_compile_wrapper_filedir() { # _filedir function provided by recent versions of bash-completion package is # better than "compgen -f" because the former honors spaces in pathnames while @@ -8,7 +8,7 @@ _@EXECUTABLE_WRAPPER_NAME@_filedir() _filedir 2> /dev/null || COMPREPLY=( $( compgen -f ) ) } -_@EXECUTABLE_WRAPPER_NAME@() +_compile_wrapper() { local cur prev words cword arg flags w1 w2 # If latest bash-completion is not supported just initialize COMPREPLY and @@ -40,19 +40,13 @@ _@EXECUTABLE_WRAPPER_NAME@() eval local path=${COMP_WORDS[0]} # Use $'\t' so that bash expands the \t for older versions of sed. flags=$( "$path" --autocomplete="$arg" 2>/dev/null | sed -e $'s/\t.*//' ) - # If @EXECUTABLE_WRAPPER_NAME@ is old that it does not support --autocomplete, - # fall back to the filename completion. - if [[ "$?" != 0 ]]; then - _@EXECUTABLE_WRAPPER_NAME@_filedir - return - fi # When @EXECUTABLE_WRAPPER_NAME@ does not emit any possible autocompletion, or user pushed tab after " ", # just autocomplete files. if [[ "$flags" == "$(echo -e '\n')" ]]; then # If -foo= and there was no possible values, autocomplete files. [[ "$cur" == '=' || "$cur" == -*= ]] && cur="" - _@EXECUTABLE_WRAPPER_NAME@_filedir + _compile_wrapper_filedir elif [[ "$cur" == '=' ]]; then COMPREPLY=( $( compgen -W "$flags" -- "") ) else @@ -62,4 +56,4 @@ _@EXECUTABLE_WRAPPER_NAME@() COMPREPLY=( $( compgen -W "$flags" -- "$cur" ) ) fi } -complete -F _@EXECUTABLE_WRAPPER_NAME@ @EXECUTABLE_WRAPPER_NAME@ +complete -F _compile_wrapper @EXECUTABLE_WRAPPER_NAME@