diff --git a/src/core/main/app/BerryBind.cpp b/src/core/main/app/BerryBind.cpp index e2ddcecb..dabff8cd 100644 --- a/src/core/main/app/BerryBind.cpp +++ b/src/core/main/app/BerryBind.cpp @@ -1,11 +1,13 @@ #include "BerryBind.h" #include +#include +#include "EuphLogger.h" +#include "be_debug.h" using namespace berry; berry::moduleMap berry::moduleLambdas; - VmState::VmState(bvm* vm) : vm(vm) {} int berryStdoutBSD(void* ctx, const char* buf, int len) { @@ -24,9 +26,8 @@ static char stdoutLineBuffer[256] = {0}; VmState::VmState() { #ifdef __linux__ - stdoutRedirect = fopencookie(this, "w", (cookie_io_functions_t) { - .write = berryStdoutLinux - }); + stdoutRedirect = fopencookie( + this, "w", (cookie_io_functions_t){.write = berryStdoutLinux}); #else stdoutRedirect = funopen(this, NULL, &berryStdoutBSD, NULL, NULL); #endif @@ -98,52 +99,176 @@ int VmState::call(bvm* vm) { return (*function)(l); } -std::pair VmState::debug_get_lineinfo() { - int line = -1; - std::string file; +void VmState::execute_string(const std::string& data, + const std::string& tag = "script") { + // create an array + if (be_loadbuffer(vm, tag.c_str(), data.c_str(), data.size()) == 0) { + if (be_pcall(vm, 0) != 0) { - be_save_stacktrace(vm); - bcallsnapshot* cf; - bcallsnapshot* base = (bcallsnapshot*)be_stack_base(&vm->tracestack); - bcallsnapshot* top = (bcallsnapshot*)be_stack_top(&vm->tracestack); + auto errorMessage = dumpException(); - cf = top; - // decrease callstack until we find nearest closure - while (!var_isclosure(&cf->func) && cf >= base) { - cf--; + bell::bellGlobalLogger->error(tag, 0, TAG, "Berry error: %s", + errorMessage.c_str()); + if (stdoutCallback != nullptr) { + stdoutCallback(errorMessage.c_str(), errorMessage.size()); + stdoutCallback("\n", 1); + } + throw BerryErrorException(errorMessage); + + } + } else { + auto errorMessage = dumpException(); + bell::bellGlobalLogger->error(tag, 0, TAG, "Berry error: %s", + errorMessage.c_str()); + if (stdoutCallback != nullptr) { + stdoutCallback(errorMessage.c_str(), errorMessage.size()); + stdoutCallback("\n", 1); + } + throw BerryErrorException(errorMessage); } - cf--; + be_pop(vm, 1); +} - // if we found a closure, get its line info and source name - if (var_isclosure(&cf->func)) { - bclosure* cl = (bclosure*)var_toobj(&cf->func); - auto proto = cl->proto; - blineinfo* it = (blineinfo*)proto->lineinfo; +/** + * @brief Native function pushed to the berry stack that a value as a string. + * + * @param vm + * @param esc + * @return int + */ +static int _dvfunc(bvm* vm) { + const char* s = be_tostring(vm, 1); + std::string* exceptionMsg = (std::string*)be_tocomptr(vm, 2); + *exceptionMsg += s; + be_return_nil(vm); +} - if (it) { - line = it->linenumber; - file = std::string(be_str2cstr(cl->proto->source)); - } +int VmState::dumpValue(int index, std::string& exceptionMsg) { + int res, top = be_top(vm) + 1; + index = be_absindex(vm, index); + be_pushntvfunction(vm, _dvfunc); + be_pushvalue(vm, index); + be_pushcomptr(vm, std::addressof(exceptionMsg)); + res = be_pcall(vm, 2); /* using index to store result */ + be_remove(vm, top); /* remove '_dumpvalue' function */ + be_remove(vm, top); /* remove the value */ + be_remove(vm, top); /* remove the exception message pointer */ + if (res == BE_EXCEPTION) { + be_dumpexcept(vm); } + return res; +} - return std::make_pair(line, file); +std::string VmState::dumpException() { + std::string exceptionMsg; + do { + + /* print exception value */ + if (dumpValue(-2, exceptionMsg)) + break; + + exceptionMsg += ": "; + /* print exception argument */ + if (dumpValue(-1, exceptionMsg)) + break; + exceptionMsg += "\n"; + /* print stack traceback */ + exceptionMsg += traceStack(); + + } while (0); + if (exceptionMsg.ends_with("\n")) { + exceptionMsg = exceptionMsg.substr(0, exceptionMsg.size() - 1); + } + be_pop(vm, 2); /* pop the exception value & argument */ + return exceptionMsg; } -bool VmState::execute_string(const std::string& data, - const std::string& tag = "script") { - // create an array - if (be_loadbuffer(vm, tag.c_str(), data.c_str(), data.size()) == 0) { - if (be_pcall(vm, 0) != 0) { - be_dumpexcept(vm); - return false; +// Copied from be_debug.c +static inline void repair_stack(bvm* vm) { + bcallsnapshot* cf; + bcallsnapshot* base = + static_cast(be_stack_base(&vm->tracestack)); + bcallsnapshot* top = + static_cast(be_stack_top(&vm->tracestack)); + /* Because the native function does not push `ip` to the + * stack, the ip on the native function frame corresponds + * to the previous Berry closure. */ + for (cf = top; cf >= base; --cf) { + if (!var_isclosure(&cf->func)) { + /* the last native function stack frame has the `ip` of + * the previous Berry frame */ + binstruction* ip = cf->ip; + /* skip native function stack frames */ + for (; cf >= base && !var_isclosure(&cf->func); --cf) + ; + /* fixed `ip` of Berry closure frame near native function frame */ + if (cf >= base) + cf->ip = ip; } + } +} + +static inline std::string sourceinfo(bproto* proto, binstruction* ip) { + + std::string result; +#if BE_DEBUG_RUNTIME_INFO + char buf[24]; + be_assert(proto != NULL); + if (proto->lineinfo && proto->nlineinfo) { + blineinfo* it = proto->lineinfo; + blineinfo* end = it + proto->nlineinfo; + int pc = cast_int(ip - proto->code - 1); /* now vm->ip has been increased */ + for (; it < end && pc > it->endpc; ++it) + ; + snprintf(buf, sizeof(buf), ":%d:", it->linenumber); +#if BE_DEBUG_SOURCE_FILE + result += be_str2cstr(proto->source); +#endif + result += buf; } else { - be_dumpexcept(vm); - return false; + result += ":"; } - be_pop(vm, 1); +#else + (void)proto; + (void)ip; + result += ":"; +#endif + + return result; +} + +std::string VmState::traceStack() { + std::string result; + EUPH_LOG(error, TAG, "traceStack()"); + if (be_stack_count(&vm->tracestack)) { + EUPH_LOG(error, TAG, " ...OK"); + repair_stack(vm); + bcallsnapshot* cf; + bcallsnapshot* base = + static_cast(be_stack_base(&vm->tracestack)); + bcallsnapshot* top = + static_cast(be_stack_top(&vm->tracestack)); + result += "stack traceback:\n"; + for (cf = top; cf >= base; --cf) { + if (cf <= top - 10 && cf >= base + 10) { + if (cf == top - 10) + result += "\t...\n"; + continue; + } + if (var_isclosure(&cf->func)) { + bclosure* cl = static_cast(var_toobj(&cf->func)); + result += "\t"; + result += sourceinfo(cl->proto, cf->ip); + result += " in function `"; + result += be_str2cstr(cl->proto->name); + result += "'\n"; - return true; + } else { + result += "\t: in native function\n"; + } + } + } + return result; } void VmState::lambda(std::function* function, diff --git a/src/core/main/app/Core.cpp b/src/core/main/app/Core.cpp index b5430bc5..22957c89 100644 --- a/src/core/main/app/Core.cpp +++ b/src/core/main/app/Core.cpp @@ -1,6 +1,8 @@ #include "Core.h" #include +#include "BerryBind.h" #include "CoreEvents.h" +#include "EmergencyMode.h" #include "EventBus.h" #include "OTAHandler.h" #include "URLParser.h" @@ -115,17 +117,23 @@ void Core::initialize() { return this->http->getServer()->makeJsonResponse(state.dump()); }); - // Load system packages - this->pkgLoader->loadWithHook("system"); - this->pkgLoader->loadWithHook("plugin"); + try { + + // Load system packages + this->pkgLoader->loadWithHook("system"); + this->pkgLoader->loadWithHook("plugin"); #ifdef ESP_PLATFORM - this->pkgLoader->loadWithHook("plugin_esp32"); - this->pkgLoader->loadWithHook("platform_esp32"); + this->pkgLoader->loadWithHook("plugin_esp32"); + this->pkgLoader->loadWithHook("platform_esp32"); #else - this->pkgLoader->loadWithHook("platform_desktop"); + this->pkgLoader->loadWithHook("platform_desktop"); #endif + } catch (berry::BerryErrorException& e) { + this->ctx->emergencyMode->trip(EmergencyModeReason::BERRY_INIT_ERROR, e.what()); + } + // Initialize HTTP this->http->initialize(); diff --git a/src/core/main/app/EmergencyMode.cpp b/src/core/main/app/EmergencyMode.cpp index 4b361708..76c70549 100644 --- a/src/core/main/app/EmergencyMode.cpp +++ b/src/core/main/app/EmergencyMode.cpp @@ -1,32 +1,45 @@ #include "EmergencyMode.h" +#include #include #include "EuphLogger.h" using namespace euph; -EmergencyMode::EmergencyMode() {} +EmergencyMode::EmergencyMode() { + EUPH_LOG(info, "EmergencyMode", + "std::atomic::is_lock_free() = %d", + std::atomic{}.is_lock_free()); +} bool EmergencyMode::isActive() const { return this->reason != EmergencyModeReason::NOT_ACTIVE; } -void EmergencyMode::trip(EmergencyModeReason reason) { +void EmergencyMode::trip(EmergencyModeReason reason, + const std::string& message) { + EUPH_LOG(error, "EmergencyMode", "==============================="); EUPH_LOG(error, "EmergencyMode", "Tripped emergency mode with reason: %s", getReasonString(reason).c_str()); + EUPH_LOG(error, "EmergencyMode", "==============================="); this->reason = reason; + std::unique_lock lock(this->messageMutex); + this->message = message; } bool EmergencyMode::tryServe(struct mg_connection* conn) { if (!this->isActive()) { return false; } - + std::shared_lock lock(this->messageMutex); // Serve the emergency mode page mg_printf(conn, "HTTP/1.1 200 OK\r\n" "Content-Type: text/html\r\n" "Connection: close\r\n\r\n" - "Emergency mode!"); + "Emergency mode!
\n" + "Reason: %s
\n" + "Message:
%s
", + getReasonString(this->reason).c_str(), this->message.c_str()); return true; } diff --git a/src/core/main/app/PackageLoader.cpp b/src/core/main/app/PackageLoader.cpp index b53c4c56..2603335b 100644 --- a/src/core/main/app/PackageLoader.cpp +++ b/src/core/main/app/PackageLoader.cpp @@ -1,4 +1,5 @@ #include "PackageLoader.h" +#include "BerryBind.h" using namespace euph; @@ -68,7 +69,7 @@ void PackageLoader::loadWithHook(const std::string& hook) { std::stringstream initCodeBuffer; initCodeBuffer << initCodeFile.rdbuf(); - ctx->vm->execute_string(initCodeBuffer.str(), pkg.name); + ctx->vm->execute_string(initCodeBuffer.str(), initPath); } } } diff --git a/src/core/main/app/include/BerryBind.h b/src/core/main/app/include/BerryBind.h index ecaac371..5d165876 100644 --- a/src/core/main/app/include/BerryBind.h +++ b/src/core/main/app/include/BerryBind.h @@ -6,22 +6,22 @@ #include #include #include -#include #include #include #include +#include #include extern "C" { -#include "berry.h" #include #include #include #include -#include #include #include #include +#include +#include "berry.h" } #undef str @@ -29,7 +29,6 @@ extern "C" { #include "BellUtils.h" #include "EuphLogger.h" - namespace berry { typedef std::unordered_map map; typedef std::vector list; @@ -42,8 +41,20 @@ berry::map to_map(std::unordered_map inputMap) { } return berryMap; } + +class BerryErrorException : public std::exception { + public: + BerryErrorException(const std::string& message) : message(message) {} + + const char* what() const noexcept override { return message.c_str(); } + + private: + std::string message; +}; + class VmState { private: + static constexpr std::string TAG = "core-bindings"; bool del; bvm* vm; FILE* stdoutRedirect; @@ -115,6 +126,13 @@ class VmState { } }; + /** + * @brief Used by dumpException to print the exception message. + * + * @return int Whether the operation was successful + */ + int dumpValue(int index, std::string& out); + public: VmState(); VmState(bvm* vm); @@ -122,9 +140,30 @@ class VmState { bvm* raw_ptr() { return vm; } - std::pair debug_get_lineinfo(); void stdoutIntercept(const char* buf, ssize_t len); - bool execute_string(const std::string& string, const std::string& tag); + /** + * @brief Executes a string of Berry code. + * @throws BerryErrorException if a berry error occurs during execution + * + * @param string The berry code to execute + * @param tag The tag to use for logging and stack traces (original source file name is recommended) + */ + void execute_string(const std::string& string, const std::string& tag); + + /** + * @brief Returns the current exception message as a string. Additionally, it + * sends it to stdoutCallback if it is set. + * + * This is an alternative to be_dumpexcept + */ + std::string dumpException(); + + /** + * @brief Returns the current stack trace as a string. + * + * @return std::string The stack trace + */ + std::string traceStack(); void lambda(std::function* function, const std::string& name, const std::string& module = ""); @@ -296,7 +335,8 @@ class VmState { StdoutCallback stdoutCallback = NULL; }; -typedef std::unordered_map*> moduleMap; +typedef std::unordered_map*> + moduleMap; extern berry::moduleMap moduleLambdas; diff --git a/src/core/main/app/include/EmergencyMode.h b/src/core/main/app/include/EmergencyMode.h index e7adba69..9efec198 100644 --- a/src/core/main/app/include/EmergencyMode.h +++ b/src/core/main/app/include/EmergencyMode.h @@ -1,8 +1,11 @@ #pragma once #include +#include +#include #include - +#include +#include #include "civetweb.h" namespace euph { @@ -38,8 +41,9 @@ class EmergencyMode { * @brief Enable emergency mode with a specific reason. * * @param reason The reason why emergency mode has been activated. + * @param message Optional message displayed on the emergency mode page, with additional information. */ - void trip(EmergencyModeReason reason); + void trip(EmergencyModeReason reason, const std::string& message = ""); /** * @brief Serve the emergency mode page, if emergency mode is active. @@ -59,6 +63,8 @@ class EmergencyMode { static std::string getReasonString(EmergencyModeReason reason); private: - EmergencyModeReason reason = EmergencyModeReason::NOT_ACTIVE; + std::atomic reason = EmergencyModeReason::NOT_ACTIVE; + std::string message; + std::shared_mutex messageMutex; }; } // namespace euph diff --git a/src/core/main/app/include/EuphLogger.h b/src/core/main/app/include/EuphLogger.h index 3b1e8a5e..69d4c9f1 100644 --- a/src/core/main/app/include/EuphLogger.h +++ b/src/core/main/app/include/EuphLogger.h @@ -93,7 +93,22 @@ class EuphoniumLogger : public bell::AbstractLogger { }; void printFilename(std::string filename, std::stringstream& logStr) { - std::string basenameStr(filename.substr(filename.rfind("/") + 1)); + bool isBeFile = filename.ends_with(".be"); + std::string basenameStr; + if (isBeFile) { + //last 2 parts of the path + size_t lastSlash = filename.rfind("/"); + if (lastSlash == std::string::npos) { + lastSlash = 0; + } + size_t secondLastSlash = filename.rfind("/", lastSlash - 1); + if (secondLastSlash == std::string::npos) { + secondLastSlash = 0; + } + basenameStr = filename.substr(secondLastSlash + 1); + } else { + basenameStr = (filename.substr(filename.rfind("/") + 1)); + } logStr << basenameStr; unsigned long hash = 5381; for (char const& c : basenameStr) {