Xamarin iOS new StackTrace() 杀死应用程序:不满足断言条件“klass”

Xamarin iOS new StackTrace() kills App: Assertion condition `klass’ not met

我们正在开发一个 Xamarin iOS 应用程序,突然之间,该应用程序开始在某些地方的某些日志代码处崩溃。除了应用程序本身,我们还有一个名为 WKGMobile.IOS 的额外 xamarin ios 库,其中包含一个名为 GenericTableViewCell 的 class,源自 UITableViewCell。在内部我们重写了 UITableViewSource.GetCell(UITableView tableView, NSIndexPath indexPath) 并在那里我们最终调用了一个虚拟方法调用 CellDidLoad 我们在我们的应用程序的派生 class 中使用它来初始化我们的单元格的一些数据并进行一些日志记录.然而,日志记录本身似乎不是问题,因为我可以用以下对 new StackTrace():

的毫无意义的调用来替换我们复杂的日志记录逻辑
public override void CellDidLoad(CheckableCellData? data)
{
    _ = new StackTrace();
}

结果是调试时打印到应用程序输出的以下异常消息:

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>
=================================================================

正如在托管堆栈跟踪中看到的那样,应用程序在 System.Diagnostics.StackFrame:get_frame_info 的某处停止运行,原因似乎是 Assertion at /Users/builder/jenkins/workspace/archive-mono/2020-02/ios/release/mono/mini/mini-exceptions.c:882, condition 'klass' not met。不管那是什么意思...

我查看了所有涉及的代码,但没有找到任何内容。神秘的错误消息也无济于事,因此非常感谢您澄清这里究竟出了什么问题(或该错误消息可能意味着什么)。

编辑 1:

This 是引发断言的单声道运行时中 mono_get_generic_context_from_stack_frame() 的代码(第 882 行是 while 循环的开头):

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;
}

编辑 2(添加一些代码):

显然,只要在我们的 GenericTableViewCell class 的任何后代中引发异常,那么不使用任何日志记录也不是一个选项,单声道运行时也会因本机异常而崩溃任何托管异常处理已过时。

我正在添加一些我们的内部库代码:

这是 GenericTableViewCell 似乎涉及(或负责?)这些崩溃:

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;
    }
}

IGenericCell接口定义如下:

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);
}

最后 IDataPoint 界面:

public interface IDataPoint
{
    int Id { get; }
}

这将是导致这些本机崩溃的示例实现:

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();
    }
}

现在所有本机单声道异常都暗示了泛型或它们在上面 classes 中的使用的一些问题,因为单声道运行时在 mono_get_generic_context_from_stack_frame() 中的断言时终止(参见 部分编辑 1),它显然试图收集有关 class 的通用上下文的堆栈帧信息,但一直失败地迭代到继承树的根(很可能是 System.Object),其中然后它提出一个断言,因为它找不到这个所谓的 MonoGenericContext。或者这就是我的理论......

我仍然想知道对通用 class 搜索失败的原因可能是什么,以及为什么这似乎只是我们 GenericTableViewCell(和后代)的问题,而不是任何问题其他通用 class?

我能想到的唯一区别是 GenericTableViewCell 的后代由 Xamarin 框架使用 myUITableView.DequeueReusableCell(myCellidentifier)

实例化

编辑 3

在调试器中单步执行代码后,我有了以下新发现:

到目前为止一切似乎都很好:

只有当虚拟接口方法 IGenericCell<TData>.SetData(TData? newData) 被压入堆栈时,调试器才会分离,mono 运行时才会结束。

SetData() 被压入堆栈:

现在从这种行为来看,我假设通用接口中的这​​个确切的虚拟方法 SetData() 是罪魁祸首,因为当单声道运行时试图为调试器提供调用堆栈信息时(就像它为 new StackTrace()throw new Exception()) 它使用 mono_get_generic_context_from_stack_frame() 方法来检索有关通用上下文的信息。 mono_get_generic_context_from_stack_frame() 然后在内部迭代所有 classes,而不是接口 (无论如何这是我的假设)使用 while 循环和 mono_class_get_generic_class(klass) 来检查通用上下文(这可能不起作用,因为推入堆栈的 SetData() 方法是在 接口 (而不是 class)中声明和实现的)。因此,运行时找不到它正在寻找的上下文,并在继承树上一直迭代到 System.Object ,它发现有问题并提出断言 condition `klass' not met.

我会做一些进一步的测试来证实或反驳我的理论:)

我可以证实我在 编辑 3 中陈述的理论:

Mono 运行时 完全 支持泛型接口中的虚拟方法!(或者至少不检索它们的堆栈帧信息).

因此将 IGenericCell<T> 的接口声明从

更改为
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);
}

public interface IGenericCell<TData> where TData : class, IDataPoint
{
    internal protected TData? DataPoint { get; set; }

    public string CellIdentifier { get; }

    UIViewController? ParentViewController { get; internal set; }

    internal void SetData(TData? newData);

    void CellDidLoad(TData? data);
}

然后像这样

GenericTableViewCell中显式实现SetData()
void IGenericCell<TData>.SetData(TData? newData)
{
    if (DataPoint is IDisposable disposable)
    {
        disposable.Dispose();
    }
    DataPoint = newData;
    CellDidLoad(newData);
}

成功了:)

现在调试器能够进入 SetData(),更重要的是进入 CellDidLoad() 以及这个错误最初出现的地方。