From 657a64f59cd286f147b373c82f04c03997b19304 Mon Sep 17 00:00:00 2001 From: Brian Fiete Date: Fri, 11 Feb 2022 08:12:30 -0500 Subject: [PATCH] Added `Runtime.AddErrorHandler` --- BeefLibs/corlib/src/Allocator.bf | 31 +++++ BeefLibs/corlib/src/Diagnostics/Debug.bf | 2 + BeefLibs/corlib/src/Runtime.bf | 139 ++++++++++++++++++++++- BeefLibs/corlib/src/Test.bf | 2 + BeefRT/rt/BfObjects.h | 5 +- BeefRT/rt/Internal.cpp | 15 +++ IDEHelper/Compiler/BfConstResolver.cpp | 2 +- IDEHelper/Compiler/BfModule.cpp | 2 +- 8 files changed, 192 insertions(+), 6 deletions(-) diff --git a/BeefLibs/corlib/src/Allocator.bf b/BeefLibs/corlib/src/Allocator.bf index 4c04db78..10a9c7dd 100644 --- a/BeefLibs/corlib/src/Allocator.bf +++ b/BeefLibs/corlib/src/Allocator.bf @@ -1,3 +1,6 @@ +using System.Reflection; +using System.Threading; + namespace System { interface IRawAllocator @@ -18,4 +21,32 @@ namespace System Internal.StdFree(ptr); } } + + struct AllocWrapper where T : new, delete + { + alloctype(T) mVal; + public alloctype(T) Val + { + get mut + { + if (mVal != default) + return mVal; + var newVal = new T(); + let prevValue = Interlocked.CompareExchange(ref mVal, default, newVal); + if (prevValue != default) + { + delete newVal; + return prevValue; + } + return newVal; + } + } + + public void Dispose() mut + { + delete mVal; + mVal = default; + } + } } + diff --git a/BeefLibs/corlib/src/Diagnostics/Debug.bf b/BeefLibs/corlib/src/Diagnostics/Debug.bf index dd22b0ab..af75f601 100644 --- a/BeefLibs/corlib/src/Diagnostics/Debug.bf +++ b/BeefLibs/corlib/src/Diagnostics/Debug.bf @@ -9,6 +9,8 @@ namespace System.Diagnostics { if (!condition) { + if (Runtime.CheckErrorHandlers(scope Runtime.AssertError(.Debug, error, filePath, line)) == .Ignore) + return; String failStr = scope .()..AppendF("Assert failed: {} at line {} in {}", error, line, filePath); Internal.FatalError(failStr, 1); } diff --git a/BeefLibs/corlib/src/Runtime.bf b/BeefLibs/corlib/src/Runtime.bf index eb25c60a..87845164 100644 --- a/BeefLibs/corlib/src/Runtime.bf +++ b/BeefLibs/corlib/src/Runtime.bf @@ -1,4 +1,5 @@ using System.Threading; +using System.Collections; #if BF_ENABLE_OBJECT_DEBUG_FLAGS || BF_DEBUG_ALLOC #define BF_DBG_RUNTIME #endif @@ -8,7 +9,7 @@ namespace System [StaticInitPriority(101)] static class Runtime { - const int32 cVersion = 8; + const int32 cVersion = 9; [CRepr, AlwaysInclude] struct BfDebugMessageData @@ -125,6 +126,7 @@ namespace System function void (char8* str) mDebugMessageData_SetupProfilerCmd; function void () mDebugMessageData_Fatal; function void () mDebugMessageData_Clear; + function int32 (char8* kind, char8* arg1, char8* arg2, int arg3) mCheckErrorHandler; static void* Alloc(int size) { @@ -228,6 +230,22 @@ namespace System BfDebugMessageData.gBfDebugMessageData.Clear(); } + static int32 CheckErrorHandler(char8* kind, char8* arg1, char8* arg2, int arg3) + { + Error error = null; + switch (StringView(kind)) + { + case "FatalError": + error = scope:: FatalError() { mError = new .(arg1) }; + case "LoadSharedLibrary": + error = scope:: LoadSharedLibraryError() { mPath = new .(arg1) }; + case "GetSharedProcAddress": + error = scope:: GetSharedProcAddressError() { mPath = new .(arg1), mProcName = new .(arg2) }; + } + if (error == null) + return 0; + return (int32)Runtime.CheckErrorHandlers(error); + } public void Init() mut { @@ -247,7 +265,8 @@ namespace System mDebugMessageData_SetupError = => DebugMessageData_SetupError; mDebugMessageData_SetupProfilerCmd = => DebugMessageData_SetupProfilerCmd; mDebugMessageData_Fatal = => DebugMessageData_Fatal; - mDebugMessageData_Clear = => DebugMessageData_Clear; + mDebugMessageData_Clear = => DebugMessageData_Clear; + mCheckErrorHandler = => CheckErrorHandler; } }; @@ -276,7 +295,68 @@ namespace System NoThreadExitWait = 0x10 } + public enum ErrorHandlerResult + { + ContinueFailure, + Ignore, + } + + public class Error + { + + } + + public class FatalError : Error + { + public String mError ~ delete _; + } + + public class LoadSharedLibraryError : Error + { + public String mPath ~ delete _; + } + + public class GetSharedProcAddressError : Error + { + public String mPath ~ delete _; + public String mProcName ~ delete _; + } + + public class AssertError : Error + { + public enum Kind + { + Debug, + Runtime, + Test + } + + public Kind mKind; + public String mError ~ delete _; + public String mFilePath ~ delete _; + public int mLineNum; + + public this(Kind kind, String error, String filePath, int lineNum) + { + mKind = kind; + mError = new .(error); + mFilePath = new .(filePath); + mLineNum = lineNum; + } + } + + public enum ErrorStage + { + PreFail, + Fail + } + + public delegate ErrorHandlerResult ErrorHandler(ErrorStage stage, Error error); + static RtFlags sExtraFlags; + static AllocWrapper sMonitor ~ _.Dispose(); + static List sErrorHandlers ~ DeleteContainerAndItems!(_); + static bool sInsideErrorHandler; public static this() { @@ -317,9 +397,64 @@ namespace System { if (!condition) { + if (Runtime.CheckErrorHandlers(scope Runtime.AssertError(.Runtime, error, filePath, line)) == .Ignore) + return; String failStr = scope .()..AppendF("Assert failed: {} at line {} in {}", error, line, filePath); Internal.FatalError(failStr, 1); } } + + public static void AddErrorHandler(ErrorHandler handler) + { + using (sMonitor.Val.Enter()) + { + if (sErrorHandlers == null) + sErrorHandlers = new .(); + sErrorHandlers.Add(handler); + } + } + + public static Result RemoveErrorHandler(ErrorHandler handler) + { + using (sMonitor.Val.Enter()) + { + if (sErrorHandlers.Remove(handler)) + return .Ok; + } + return .Err; + } + + public static ErrorHandlerResult CheckErrorHandlers(Error error) + { + using (sMonitor.Val.Enter()) + { + if (sInsideErrorHandler) + return .ContinueFailure; + + sInsideErrorHandler = true; + defer { sInsideErrorHandler = false; } + + for (int pass = 0; pass < 2; pass++) + { + int idx = (sErrorHandlers?.Count).GetValueOrDefault() - 1; + while (idx >= 0) + { + if (idx < sErrorHandlers.Count) + { + var handler = sErrorHandlers[idx]; + var result = handler((pass == 0) ? .PreFail : .Fail, error); + if (result == .Ignore) + { + if (pass == 1) + Internal.FatalError("Can only ignore error on prefail"); + return .Ignore; + } + } + idx--; + } + } + } + return .ContinueFailure; + } } } diff --git a/BeefLibs/corlib/src/Test.bf b/BeefLibs/corlib/src/Test.bf index 07524549..2e73cb13 100644 --- a/BeefLibs/corlib/src/Test.bf +++ b/BeefLibs/corlib/src/Test.bf @@ -70,6 +70,8 @@ namespace System { if (!condition) { + if (Runtime.CheckErrorHandlers(scope Runtime.AssertError(.Test, error, filePath, line)) == .Ignore) + return; String failStr = scope .()..AppendF("Assert failed: {} at line {} in {}", error, line, filePath); Internal.[Friend]Test_Error(failStr); } diff --git a/BeefRT/rt/BfObjects.h b/BeefRT/rt/BfObjects.h index c7e79574..237d69c9 100644 --- a/BeefRT/rt/BfObjects.h +++ b/BeefRT/rt/BfObjects.h @@ -3,7 +3,7 @@ #include "BeefySysLib/Common.h" #include "BeefySysLib/util/String.h" -#define BFRT_VERSION 8 +#define BFRT_VERSION 9 #ifdef BFRT_DYNAMIC #define BFRT_EXPORT __declspec(dllexport) @@ -106,7 +106,8 @@ namespace bf void(*DebugMessageData_SetupError)(const char* str, int32 stackWindbackCount); void(*DebugMessageData_SetupProfilerCmd)(const char* str); void(*DebugMessageData_Fatal)(); - void(*DebugMessageData_Clear)(); + void(*DebugMessageData_Clear)(); + int(*CheckErrorHandler)(const char* kind, const char* arg1, const char* arg2, intptr arg3); }; public: diff --git a/BeefRT/rt/Internal.cpp b/BeefRT/rt/Internal.cpp index 5bb1d054..9f8e01bc 100644 --- a/BeefRT/rt/Internal.cpp +++ b/BeefRT/rt/Internal.cpp @@ -214,6 +214,9 @@ static void TestReadCmd(Beefy::String& str); static void Internal_FatalError(const char* error) { + if (gBfRtCallbacks.CheckErrorHandler != NULL) + gBfRtCallbacks.CheckErrorHandler("FatalError", error, NULL, 0); + if ((gClientPipe != NULL) && (!gTestBreakOnFailure)) { Beefy::String str = ":TestFatal\t"; @@ -532,6 +535,12 @@ void* Internal::LoadSharedLibrary(char* libName) void* libHandle = BfpDynLib_Load(libName); if (libHandle == NULL) { + if (gBfRtCallbacks.CheckErrorHandler != NULL) + { + if (gBfRtCallbacks.CheckErrorHandler("LoadSharedLibrary", libName, NULL, 0) == 1) + return NULL; + } + Beefy::String errorStr = StrFormat("Failed to load shared library: %s", libName); SETUP_ERROR(errorStr.c_str(), 1); BF_DEBUG_BREAK(); @@ -558,6 +567,12 @@ void* Internal::GetSharedProcAddress(void* libHandle, char* procName) int libFileNameLen = 4096; BfpDynLib_GetFilePath((BfpDynLib*)libHandle, libFileName, &libFileNameLen, NULL); + if (gBfRtCallbacks.CheckErrorHandler != NULL) + { + if (gBfRtCallbacks.CheckErrorHandler("GetSharedProcAddress", libFileName, procName, 0) == 1) + return NULL; + } + Beefy::String errorStr = StrFormat("Failed to load shared procedure '%s' from '%s'", procName, libFileName); SETUP_ERROR(errorStr.c_str(), 1); BF_DEBUG_BREAK(); diff --git a/IDEHelper/Compiler/BfConstResolver.cpp b/IDEHelper/Compiler/BfConstResolver.cpp index 6bb58881..ef165eb4 100644 --- a/IDEHelper/Compiler/BfConstResolver.cpp +++ b/IDEHelper/Compiler/BfConstResolver.cpp @@ -373,7 +373,7 @@ bool BfConstResolver::PrepareMethodArguments(BfAstNode* targetSrc, BfMethodMatch } if (mModule->PreFail()) - mModule->Fail(StrFormat("Not enough parameters specified. Expected %d fewer.", methodInstance->GetParamCount() - (int)arguments.size()), refNode); + mModule->Fail(StrFormat("Not enough parameters specified. Expected %d more.", methodInstance->GetParamCount() - (int)arguments.size()), refNode); return false; } diff --git a/IDEHelper/Compiler/BfModule.cpp b/IDEHelper/Compiler/BfModule.cpp index f14633bf..11fc2a70 100644 --- a/IDEHelper/Compiler/BfModule.cpp +++ b/IDEHelper/Compiler/BfModule.cpp @@ -19107,7 +19107,7 @@ void BfModule::ProcessMethod(BfMethodInstance* methodInstance, bool isInlineDup, { BfIRValue dllImportGlobalVar = CreateDllImportGlobalVar(methodInstance, true); methodInstance->mIRFunction = mBfIRBuilder->GetFakeVal(); - BF_ASSERT(dllImportGlobalVar); + BF_ASSERT(dllImportGlobalVar || methodInstance->mHasFailed); mFuncReferences[mCurMethodInstance] = dllImportGlobalVar; }