We are developing a Xamarin iOS App and all of the sudden the app starts crashing at some of our logging code in certain places. Apart from the app itself we have an additional xamarin ios library called WKGMobile.IOS containing amongst other things a class called GenericTableViewCell deriving from UITableViewCell. Inside we override UITableViewSource.GetCell(UITableView tableView, NSIndexPath indexPath) and in there we end up calling a virtual method call CellDidLoad which we use in a derived class in our app to initialize some data for our cell and to do some logging. The logging itself however doesn't seem to be the issue as I could replace our complex logging logic with the following pretty meaningless call to new StackTrace():
public override void CellDidLoad(CheckableCellData? data)
{
_ = new StackTrace();
}
the result is the following exception message as printed to Application Output when debugging:
2021-05-27 13:59:06.830061+0200 ClubmappAppIOS[2840:116592] error: * Assertion at /Users/builder/jenkins/workspace/archive-mono/2020-02/ios/release/mono/mini/mini-exceptions.c:882, condition `klass’ not met
=================================================================
Native Crash Reporting
=================================================================
Got a abrt while executing native code. This usually indicates
a fatal error in the mono runtime or one of the native libraries
used by your application.
=================================================================
=================================================================
Native stacktrace:
=================================================================
0x10c802275 - /Users/wkg/Library/Developer/CoreSimulator/Devices/CBA6C26F-5AF5-4A20-91B3-6F3DE56A77A3/data/Containers/Bundle/Application/63A2CC35-6283-494B-A483-A7DBC1BBFE32/ClubmappAppIOS.app/ClubmappAppIOS : mono_dump_native_crash_info
0x10c7f6dbe - /Users/wkg/Library/Developer/CoreSimulator/Devices/CBA6C26F-5AF5-4A20-91B3-6F3DE56A77A3/data/Containers/Bundle/Application/63A2CC35-6283-494B-A483-A7DBC1BBFE32/ClubmappAppIOS.app/ClubmappAppIOS : mono_handle_native_crash
0x10c80188f - /Users/wkg/Library/Developer/CoreSimulator/Devices/CBA6C26F-5AF5-4A20-91B3-6F3DE56A77A3/data/Containers/Bundle/Application/63A2CC35-6283-494B-A483-A7DBC1BBFE32/ClubmappAppIOS.app/ClubmappAppIOS : sigabrt_signal_handler
0x7fff6115a5fd - /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/usr/lib/system/libsystem_platform.dylib : _sigtramp
0x0 - Unknown
0x7fff200fab94 - /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/usr/lib/system/libsystem_c.dylib : abort
0x10ca14d20 - /Users/wkg/Library/Developer/CoreSimulator/Devices/CBA6C26F-5AF5-4A20-91B3-6F3DE56A77A3/data/Containers/Bundle/Application/63A2CC35-6283-494B-A483-A7DBC1BBFE32/ClubmappAppIOS.app/ClubmappAppIOS : _ZL12log_callbackPKcS0_S0_iPv
0x10c9eda35 - /Users/wkg/Library/Developer/CoreSimulator/Devices/CBA6C26F-5AF5-4A20-91B3-6F3DE56A77A3/data/Containers/Bundle/Application/63A2CC35-6283-494B-A483-A7DBC1BBFE32/ClubmappAppIOS.app/ClubmappAppIOS : monoeg_g_logv_nofree
0x10c9edbaf - /Users/wkg/Library/Developer/CoreSimulator/Devices/CBA6C26F-5AF5-4A20-91B3-6F3DE56A77A3/data/Containers/Bundle/Application/63A2CC35-6283-494B-A483-A7DBC1BBFE32/ClubmappAppIOS.app/ClubmappAppIOS : monoeg_assertion_message
0x10c9edbda - /Users/wkg/Library/Developer/CoreSimulator/Devices/CBA6C26F-5AF5-4A20-91B3-6F3DE56A77A3/data/Containers/Bundle/Application/63A2CC35-6283-494B-A483-A7DBC1BBFE32/ClubmappAppIOS.app/ClubmappAppIOS : mono_assertion_message
0x10c7f3c5b - /Users/wkg/Library/Developer/CoreSimulator/Devices/CBA6C26F-5AF5-4A20-91B3-6F3DE56A77A3/data/Containers/Bundle/Application/63A2CC35-6283-494B-A483-A7DBC1BBFE32/ClubmappAppIOS.app/ClubmappAppIOS : mono_get_generic_context_from_stack_frame
0x10c7f4171 - /Users/wkg/Library/Developer/CoreSimulator/Devices/CBA6C26F-5AF5-4A20-91B3-6F3DE56A77A3/data/Containers/Bundle/Application/63A2CC35-6283-494B-A483-A7DBC1BBFE32/ClubmappAppIOS.app/ClubmappAppIOS : get_method_from_stack_frame
0x10c7f4a80 - /Users/wkg/Library/Developer/CoreSimulator/Devices/CBA6C26F-5AF5-4A20-91B3-6F3DE56A77A3/data/Containers/Bundle/Application/63A2CC35-6283-494B-A483-A7DBC1BBFE32/ClubmappAppIOS.app/ClubmappAppIOS : ves_icall_get_frame_info
0x11381ff2d - Unknown
0x11381fdbb - Unknown
0x11381fa83 - Unknown
=================================================================
Basic Fault Address Reporting
=================================================================
Memory around native instruction pointer (0x7fff6113133a):0x7fff6113132a ff ff c3 90 90 90 b8 48 01 00 02 49 89 ca 0f 05 .......H...I....
0x7fff6113133a 73 08 48 89 c7 e9 e5 a2 ff ff c3 90 90 90 b8 53 s.H............S
0x7fff6113134a 00 00 02 49 89 ca 0f 05 73 08 48 89 c7 e9 cd a2 ...I....s.H.....
0x7fff6113135a ff ff c3 90 90 90 b8 83 01 00 02 49 89 ca 0f 05 ...........I....
=================================================================
Managed Stacktrace:
=================================================================
at <unknown> <0xffffffff>
at System.Diagnostics.StackFrame:get_frame_info <0x000dc>
at System.Diagnostics.StackFrame:.ctor <0x0014a>
at System.Diagnostics.StackTrace:init_frames <0x00252>
at System.Diagnostics.StackTrace:.ctor <0x00092>
at ClubmappAppIOS.Common.Views.TableView.CheckableClubmappCell.CheckableClubmappCell:CellDidLoad <0x001c2>
at WKGMobile.IOS.Components.Views.DataPresentation.IGenericCell`1:SetData <0x002c0>
at WKGMobile.IOS.Components.Views.DataPresentation.GenericDataViewSource`1:GetCell <0x00360>
at WKGMobile.IOS.Components.Views.DataPresentation.Tables.GenericTableViewSource`1:GetCell <0x0013a>
at <Module>:runtime_invoke_object__this___object_object <0x0036f>
at <unknown> <0xffffffff>
at UIKit.UIApplication:UIApplicationMain <0x00254>
at UIKit.UIApplication:Main <0x000b2>
at UIKit.UIApplication:Main <0x00132>
at ClubmappAppIOS.Application:Main <0x00302>
at <Module>:runtime_invoke_void_object <0x00198>
=================================================================
As can be seen in the Managed stack trace the application dies somewhere at System.Diagnostics.StackFrame:get_frame_info and the reason for it seems to be an Assertion at /Users/builder/jenkins/workspace/archive-mono/2020-02/ios/release/mono/mini/mini-exceptions.c:882, condition 'klass' not met. Whatever that means ...
I've looked through all the code involved and couldn't find anything. The cryptic error message doesn't help either so any clarification as to what exactly is going wrong here (or what that error message could mean) would be greatly appreciated.
Edit 1:
This is the code of mono_get_generic_context_from_stack_frame() in the mono runtime that is raising the assertion (line 882 is the head of the while loop):
MonoGenericContext
mono_get_generic_context_from_stack_frame (MonoJitInfo *ji, gpointer generic_info)
{
MonoGenericContext context = { NULL, NULL };
MonoClass *klass, *method_container_class;
MonoMethod *method;
g_assert (generic_info);
method = jinfo_get_method (ji);
g_assert (method->is_inflated);
if (mono_method_get_context (method)->method_inst) {
MonoMethodRuntimeGenericContext *mrgctx = (MonoMethodRuntimeGenericContext *)generic_info;
klass = mrgctx->class_vtable->klass;
context.method_inst = mrgctx->method_inst;
g_assert (context.method_inst);
} else {
MonoVTable *vtable = (MonoVTable *)generic_info;
klass = vtable->klass;
}
//g_assert (!mono_class_is_gtd (method->klass));
if (mono_class_is_ginst (method->klass))
method_container_class = mono_class_get_generic_class (method->klass)->container_class;
else
method_container_class = method->klass;
/* class might refer to a subclass of method's class */
while (!(klass == method->klass || (mono_class_is_ginst (klass) && mono_class_get_generic_class (klass)->container_class == method_container_class))) {
klass = m_class_get_parent (klass);
g_assert (klass);
}
if (mono_class_is_ginst (klass) || mono_class_is_gtd (klass))
context.class_inst = mini_class_get_context (klass)->class_inst;
if (mono_class_is_ginst (klass))
g_assert (mono_class_has_parent_and_ignore_generics (mono_class_get_generic_class (klass)->container_class, method_container_class));
else
g_assert (mono_class_has_parent_and_ignore_generics (klass, method_container_class));
return context;
}
Edit 2 (adding some code):
Apparently just not using any logging isn't an option either as as soon as an exception is raised in any descendants of our GenericTableViewCell class the mono runtime crashes with a native exception too making any managed exception handling obsolete.
I am adding some of our internal library code:
This is the GenericTableViewCell that seems to be involved in (or responsible for?) these crashes:
public abstract class GenericTableViewCell<TData> : UITableViewCell, IGenericCell<TData> where TData : class, IDataPoint
{
public abstract float CellHeight { get; }
public abstract string CellIdentifier { get; }
public TData? DataPoint { get; private set; }
TData? IGenericCell<TData>.DataPoint { get => DataPoint; set => DataPoint = value; }
public UIViewController? ParentViewController { get; internal set; }
UIViewController? IGenericCell<TData>.ParentViewController
{
get => ParentViewController;
set => ParentViewController = value;
}
public GenericTableViewCell()
{
}
public GenericTableViewCell(IntPtr handle) : base(handle)
{
}
public virtual void CellDidLoad(TData? data)
{
SelectedBackgroundView ??= new UIView();
SelectedBackgroundView.BackgroundColor = UIColor.Clear;
}
}
The IGenericCell interface is defined as follows:
public interface IGenericCell<TData> where TData : class, IDataPoint
{
internal protected TData? DataPoint { get; set; }
public string CellIdentifier { get; }
UIViewController? ParentViewController { get; internal set; }
internal virtual void SetData(TData? newData)
{
if (DataPoint is IDisposable disposable)
{
disposable.Dispose();
}
DataPoint = newData;
CellDidLoad(newData);
}
void CellDidLoad(TData? data);
}
And finally the IDataPoint interface:
public interface IDataPoint
{
int Id { get; }
}
This would be an example implementation that's causing these native crashes:
public partial class MusicTableViewCell : GenericTableViewCell<MusicListData>
{
public override float CellHeight => 80f;
public override string CellIdentifier => "MusicTableViewCell";
public MusicTableViewCell()
{
}
protected MusicTableViewCell(IntPtr handle) : base(handle)
{
// Note: this .ctor should not contain any initialization logic.
}
public override void CellDidLoad(MusicListData? data)
{
_ = new StackTrace();
}
}
Now all native mono exceptions hint at some issue with generics or their usage in the classes above as the mono runtime dies at an assertion in mono_get_generic_context_from_stack_frame() (see section Edit 1) where it apparently tries to collect stack frame info about the generic context of the class iterating unsuccessfully all the way to the root of the inheritance tree (most likely System.Object) where it then raises an assertion because it can't find this so called MonoGenericContext. Or that's my theory anyways...
Still I am wondering what the cause of this unsuccessful search for the generic class could be and why this only seems to be a problem in our GenericTableViewCell (and descendants) and not in any other generic class?
The only difference I could think of is that the descendants of GenericTableViewCell are instantiated by the Xamarin framework using myUITableView.DequeueReusableCell(myCellidentifier)
Edit 3
After stepping through the code in the debugger I made the following new discovery:
Everything seems to be fine up until this point:

it is only when the virtual interface method IGenericCell<TData>.SetData(TData? newData) is pushed onto the stack when things go south, the debugger detaches and the mono runtime dies.
SetData() is pushed onto the stack:

Now judging from this behavior I assume this exact virtual method SetData() in the generic interface to be the culprit as when the mono runtime tries to provide call stack information for the dubugger (just like it does for new StackTrace() or throw new Exception()) it uses the mono_get_generic_context_from_stack_frame() method to retrieve info about the generic context. mono_get_generic_context_from_stack_frame() internally then iterates over all classes, not interfaces (that's my assumption anyways) using a while loop and mono_class_get_generic_class(klass) to check for the generic context (which probably doesn't work as the SetData() method pushed onto the stack is declared and implemeted in the interface (not the class)). Therefore the runtime doesn't find the context it is looking for and iterates all the way up the inheritance tree to System.Object where it figures that something's wrong and raises the assertion condition `klass’ not met.
I'll do some further testing to confirm or disprove my theory :)