From 2df3d0f7477e682464dcf8dbf8c7e96100cb0106 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 25 Feb 2024 14:54:57 +0100 Subject: [PATCH] Fix crash when loading level with abstract script class #1990 --- .../Engine/Engine/NativeInterop.Unmanaged.cs | 12 +++++++-- Source/Engine/Engine/NativeInterop.cs | 19 ++++++++------ Source/Engine/Scripting/BinaryModule.cpp | 26 ++++++++++++------- 3 files changed, 38 insertions(+), 19 deletions(-) diff --git a/Source/Engine/Engine/NativeInterop.Unmanaged.cs b/Source/Engine/Engine/NativeInterop.Unmanaged.cs index e99276a4e..52a5924b7 100644 --- a/Source/Engine/Engine/NativeInterop.Unmanaged.cs +++ b/Source/Engine/Engine/NativeInterop.Unmanaged.cs @@ -551,8 +551,16 @@ namespace FlaxEngine.Interop internal static ManagedHandle NewObject(ManagedHandle typeHandle) { TypeHolder typeHolder = Unsafe.As(typeHandle.Target); - object value = typeHolder.CreateObject(); - return ManagedHandle.Alloc(value); + try + { + object value = typeHolder.CreateObject(); + return ManagedHandle.Alloc(value); + } + catch (Exception ex) + { + Debug.LogException(ex); + } + return new ManagedHandle(); } [UnmanagedCallersOnly] diff --git a/Source/Engine/Engine/NativeInterop.cs b/Source/Engine/Engine/NativeInterop.cs index 7dfd52d22..2c606465c 100644 --- a/Source/Engine/Engine/NativeInterop.cs +++ b/Source/Engine/Engine/NativeInterop.cs @@ -130,12 +130,10 @@ namespace FlaxEngine.Interop object obj = objectHandle.Target; if (obj is not Object) return; - { ref IntPtr fieldRef = ref FieldHelper.GetReferenceTypeFieldReference(unmanagedPtrFieldOffset, ref obj); fieldRef = unmanagedPtr; } - if (idPtr != IntPtr.Zero) { ref Guid nativeId = ref Unsafe.AsRef(idPtr.ToPointer()); @@ -148,8 +146,16 @@ namespace FlaxEngine.Interop internal static ManagedHandle ScriptingObjectCreate(ManagedHandle typeHandle, IntPtr unmanagedPtr, IntPtr idPtr) { TypeHolder typeHolder = Unsafe.As(typeHandle.Target); - object obj = typeHolder.CreateScriptingObject(unmanagedPtr, idPtr); - return ManagedHandle.Alloc(obj); + try + { + object obj = typeHolder.CreateScriptingObject(unmanagedPtr, idPtr); + return ManagedHandle.Alloc(obj); + } + catch (Exception ex) + { + Debug.LogException(ex); + } + return new ManagedHandle(); } #endif @@ -1462,7 +1468,6 @@ namespace FlaxEngine.Interop ref IntPtr fieldRef = ref FieldHelper.GetReferenceTypeFieldReference(unmanagedPtrFieldOffset, ref obj); fieldRef = unmanagedPtr; } - if (idPtr != IntPtr.Zero) { ref Guid nativeId = ref Unsafe.AsRef(idPtr.ToPointer()); @@ -1470,12 +1475,10 @@ namespace FlaxEngine.Interop fieldRef = nativeId; } } - if (ctor != null) ctor.Invoke(obj, null); else - Debug.LogException(new Exception($"Missing empty constructor in type '{wrappedType}'.")); - + throw new NativeInteropException($"Missing empty constructor in type '{wrappedType}'."); return obj; } #endif diff --git a/Source/Engine/Scripting/BinaryModule.cpp b/Source/Engine/Scripting/BinaryModule.cpp index 0cded22b4..daa952285 100644 --- a/Source/Engine/Scripting/BinaryModule.cpp +++ b/Source/Engine/Scripting/BinaryModule.cpp @@ -349,6 +349,20 @@ void ScriptingType::DefaultInitRuntime() ScriptingObject* ScriptingType::DefaultSpawn(const ScriptingObjectSpawnParams& params) { +#if !BUILD_RELEASE + // Provide more context information on object spawn failure in development builds + if (params.Type) + { + auto& type = params.Type.GetType(); + if (type.ManagedClass) + { + if (type.ManagedClass->IsAbstract()) + LOG(Error, "Cannot spawn abstract type '{}'", type.ToString()); + if (type.ManagedClass->IsStatic()) + LOG(Error, "Cannot spawn static type '{}'", type.ToString()); + } + } +#endif return nullptr; } @@ -1040,18 +1054,11 @@ void ManagedBinaryModule::InitType(MClass* mclass) baseType.Module->TypeNameToTypeIndex.TryGet(genericClassName, *(int32*)&baseType.TypeIndex); } - if (!baseType) - { - LOG(Error, "Missing base class for managed class {0} from assembly {1}.", String(typeName), Assembly->ToString()); - return; - } - - if (baseType.TypeIndex == -1) + if (baseType.TypeIndex == -1 || baseType.Module == nullptr) { if (baseType.Module) LOG(Error, "Missing base class for managed class {0} from assembly {1}.", String(baseClass->GetFullName()), baseType.Module->GetName().ToString()); else - // Not sure this can happen but never hurts to account for it LOG(Error, "Missing base class for managed class {0} from unknown assembly.", String(baseClass->GetFullName())); return; } @@ -1112,7 +1119,8 @@ void ManagedBinaryModule::InitType(MClass* mclass) // Create scripting type descriptor for managed-only type based on the native base class const int32 typeIndex = Types.Count(); Types.AddUninitialized(); - new(Types.Get() + Types.Count() - 1)ScriptingType(typeName, this, baseType.GetType().Size, ScriptingType::DefaultInitRuntime, ManagedObjectSpawn, baseType, nullptr, nullptr, interfaces); + const ScriptingType::SpawnHandler spawner = mclass->IsAbstract() ? ScriptingType::DefaultSpawn : ManagedObjectSpawn; + new(Types.Get() + Types.Count() - 1)ScriptingType(typeName, this, baseType.GetType().Size, ScriptingType::DefaultInitRuntime, spawner, baseType, nullptr, nullptr, interfaces); TypeNameToTypeIndex[typeName] = typeIndex; auto& type = Types[typeIndex]; type.ManagedClass = mclass;