//This file is part of the reprodyne project and is licensed under the terms of the LGPL-3.0-only #include #include #include "user-include/reprodyne.h" #include "programhandlers.h" std::unique_ptr recorder; std::unique_ptr player; reprodyne_playback_failure_handler playbackErrorHandler = nullptr; std::string jumpSafeString; //(re: using std::strings in exceptions) //Playing it on the loose and easy with these exceptions because they are playback, // not memory errors. And it's prettier this way~ //We need to catch all exceptions and deal with them here because you don't want // the library to leak through a C boundary. And as such, we want to be longjmp safe. static void handlePlaybackErrorException(const reprodyne::PlaybackError& e) { if(!playbackErrorHandler) { std::cerr << "Reprodyne PLAYBACK FAILURE (No handler provided): " << e.what() << std::endl << std::flush; std::terminate(); } jumpSafeString = e.what(); //what() references an std::string, so it's not safe beyond this point. playbackErrorHandler(e.getCode(), jumpSafeString.c_str()); std::terminate(); //If you don't do it, I will. } static void handleRuntimeErrrr(const char* what) { std::cerr << "Reprodyne runtime error: " << what << std::endl; std::terminate(); } static void unknownErrorDie() { std::terminate(); } //Uniform exception handler so that we can translate to C-safe errors consistently. template static T safeBlock(std::function thingamajigulator) { try { return thingamajigulator(); } catch(const reprodyne::PlaybackError& e) { handlePlaybackErrorException(e); } catch(const std::runtime_error& e) { handleRuntimeErrrr(e.what()); } //"eh, what??" catch(...) { //fucc unknownErrorDie(); } std::terminate(); //Why won't you fuCKING DIE ALREADY?!? } template<> void safeBlock(std::function thingamajigulator) { try { thingamajigulator(); } catch(const reprodyne::PlaybackError& e) { handlePlaybackErrorException(e); } catch(const std::runtime_error& e) { handleRuntimeErrrr(e.what()); } catch(...) { //fucc unknownErrorDie(); } } //Generic reset so I can have n-number of player types and swap between them arbitrarily static void reset() { recorder.reset(); player.reset(); } extern "C" { void reprodyne_do_not_call_this_function_directly_set_playback_failure_handler(reprodyne_playback_failure_handler handler) { playbackErrorHandler = handler; /*Not my problem anymore...*/ } void reprodyne_do_not_call_this_function_directly_record() { safeBlock([&] { reset(); recorder = std::make_unique(); }); } void reprodyne_do_not_call_this_function_directly_save(const char* path) { safeBlock([&] { if(!recorder) throw std::logic_error("bad doG! No!"); recorder->save(path); }); } void reprodyne_do_not_call_this_function_directly_play(const char* path) { safeBlock([&] { reset(); player = std::make_unique(path); }); } void reprodyne_do_not_call_this_function_directly_assert_complete_read() { safeBlock([&] { if(!player) throw std::logic_error("Cannot assert complete read in record mode"); player->assertCompleteRead(); }); } void reprodyne_do_not_call_this_function_directly_mark_frame() { safeBlock([&] { if(recorder) recorder->markFrame(); else if(player) player->markFrame(); }); } void reprodyne_do_not_call_this_function_directly_open_scope(void* ptr) { safeBlock([&] { if(recorder) recorder->openScope(ptr); else if(player) player->openScope(ptr); }); } void reprodyne_do_not_call_this_function_directly_write_indeterminate(void* scopePtr, const char* key, double indeterminate) { safeBlock([&] { if(!recorder) throw std::logic_error("Bad mode. Expected record"); recorder->intercept(scopePtr, key, indeterminate); }); } double reprodyne_do_not_call_this_function_directly_read_indeterminate(void* scopePtr, const char* subscopeKey) { return safeBlock([&] { if(!player) throw std::logic_error("Bad mode. Expected player"); return player->intercept(scopePtr, subscopeKey, 0); //Value is ignored for read. }); } double reprodyne_do_not_call_this_function_directly_intercept_double(void* scopePtr, const char* scopeKey, const double indeterminate) { return safeBlock([&]() { if(recorder) return recorder->intercept(scopePtr, scopeKey, indeterminate); else if(player) return player->intercept(scopePtr, scopeKey, indeterminate); throw std::logic_error("die"); }); } void reprodyne_do_not_call_this_function_directly_validate_string(void* scopePtr, const char* subScopeKey, const char* call) { return safeBlock([&] { //God I wish I had lisp macros... if(recorder) recorder->serialize(scopePtr, subScopeKey, call); else if(player) player->serialize(scopePtr, subScopeKey, call); else throw std::logic_error("die"); }); } void reprodyne_do_not_call_this_function_directly_validate_bitmap_hash(void* scope, const char* key, const unsigned int width, const unsigned int height, const unsigned int stride, void* bytes) { safeBlock([&] { //I like my typos sometimes auto errorHahnDler = [&](const char* msg) { //This is only safe because jumpSafeString is only otherwise used by // the playback error handler. Even still, I don't like this... jumpSafeString = msg; jumpSafeString += " in validate bitmap hash function!"; jumpSafeString += " Subscope key: "; jumpSafeString += key; throw std::runtime_error(jumpSafeString.c_str()); }; if(height < 1) errorHahnDler("Useless height"); if(width < 1) errorHahnDler("Useless width"); if(stride < width) errorHahnDler("Stride smaller than width"); if(!bytes) errorHahnDler("NULL data pointer"); //int8_t because that's how flatbuffers likes it, I ain't judge. std::vector hash(SHA256_DIGEST_LENGTH); //unsigned char because that's how OpenSSL likes it, I ain't judge. std::vector clippedImageRegion; clippedImageRegion.reserve(width * height); for(int h = 0; h != height; ++h) for(int w = 0; w != width; ++w) clippedImageRegion.push_back(reinterpret_cast(bytes)[h * stride + w]); SHA256(&clippedImageRegion[0], clippedImageRegion.size(), reinterpret_cast(&hash[0])); if(recorder) recorder->serialize(scope, key, width, height, hash); //Variadic templates are badass. else if(player) player->serialize(scope, key, width, height, hash); }); } } //extern "C"