Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions src/Controls/src/Build.Tasks/SetPropertiesVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1280,9 +1280,9 @@ static IEnumerable<Instruction> ConnectEvent(VariableDefinition parent, string l
// IL_0008: ldarg.0
//
// IL_0009: ldftn instance void class Microsoft.Maui.Controls.Xaml.XamlcTests.MyPage::OnButtonClicked(object, class [mscorlib]System.EventArgs)
//OR, if the handler is virtual
// IL_000x: ldarg.0
// IL_0009: ldvirtftn instance void class Microsoft.Maui.Controls.Xaml.XamlcTests.MyPage::OnButtonClicked(object, class [mscorlib]System.EventArgs)
//OR, if the handler is virtual (non-static)
// IL_000x: dup ; copy target already on stack
// IL_000y: ldvirtftn instance void class Microsoft.Maui.Controls.Xaml.XamlcTests.MyPage::OnButtonClicked(object, class [mscorlib]System.EventArgs)
//
// IL_000f: newobj instance void class [mscorlib]System.EventHandler::'.ctor'(object, native int)
// IL_0014: callvirt instance void class [Microsoft.Maui.Controls]Microsoft.Maui.Controls.Button::add_Clicked(class [mscorlib]System.EventHandler)
Expand Down Expand Up @@ -1342,7 +1342,14 @@ static IEnumerable<Instruction> ConnectEvent(VariableDefinition parent, string l

if (methodDef.IsVirtual)
{
yield return Create(Ldarg_0);
// ldvirtftn needs the object whose vtable drives virtual dispatch.
Comment thread
BagavathiPerumal marked this conversation as resolved.
// In a DataTemplate context, Ldarg_0 is the anonymous nested class, not the root
// XAML element — using it causes iOS/Mac Full AOT to crash on virtual handlers.
// The delegate target (already on the stack) IS the correct vtable object, so
// dup it: stack goes [parent, target] → [parent, target, target], ldvirtftn pops one, leaving
// [parent, target, ftn] for the delegate ctor.
if (!methodDef.IsStatic)
yield return Create(Dup);
Comment thread
BagavathiPerumal marked this conversation as resolved.
Outdated
yield return Create(Ldvirtftn, handlerRef);
}
Comment thread
BagavathiPerumal marked this conversation as resolved.
Outdated
else
Expand Down
11 changes: 11 additions & 0 deletions src/Controls/tests/Xaml.UnitTests/Issues/Maui18055.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Microsoft.Maui.Controls.Xaml.UnitTests"
x:Class="Microsoft.Maui.Controls.Xaml.UnitTests.Maui18055">
<ContentPage.Resources>
<DataTemplate x:Key="virtualHandlerTemplate">
<local:ElementWithEvent Clicked="HandleVirtualClicked" />
</DataTemplate>
</ContentPage.Resources>
</ContentPage>
48 changes: 48 additions & 0 deletions src/Controls/tests/Xaml.UnitTests/Issues/Maui18055.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using System;
using Xunit;

namespace Microsoft.Maui.Controls.Xaml.UnitTests;

// Regression test for https://github.com/dotnet/maui/issues/18055
// XamlC must use Dup (not Ldarg_0) before ldvirtftn when wiring virtual event handlers
// inside a DataTemplate, so the correct vtable object is used for virtual dispatch.
public partial class Maui18055 : ContentPage
{
public Maui18055() => InitializeComponent();

public int baseForVirtualClicked;
protected virtual void HandleVirtualClicked(object sender, EventArgs e) => baseForVirtualClicked++;

Comment thread
BagavathiPerumal marked this conversation as resolved.
Outdated
[Collection("Issue")]
public class Tests
{
[Theory]
[XamlInflatorData]
// Verifies that a virtual handler wired inside a DataTemplate dispatches to the override,
// not the base class. Without the fix, XamlC emitted Ldarg_0 (the anonymous DataTemplate
// class) as the ldvirtftn vtable source — causing wrong dispatch on JIT and a hard crash
// on iOS/macOS Full AOT.
internal void VirtualHandlerInDataTemplateCallsOverride(XamlInflator inflator)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[minor] Regression Prevention - Only XamlInflator.XamlC exercises the IL fix.

[XamlInflatorData] drives this theory with all three inflators, but:

  • Runtime uses reflection-based LoadFromXaml; virtual dispatch is always correct via the CLR.
  • SourceGen emits elementWithEvent.Clicked += HandleVirtualClicked in generated C#; the CLR handles vtable lookup normally.

Neither path would fail without the Dup fix. Only XamlInflator.XamlC runs the rewritten InitializeComponentXamlC that contains the fixed IL. The test remains valuable for guarding SourceGen/Runtime regressions, but a comment noting that XamlC is the critical path for this specific fix would help future maintainers.

{
var page = new SubMaui18055(inflator);
Assert.Equal(0, page.baseForVirtualClicked);
Assert.Equal(0, page.overrideClicked);

var template = (Microsoft.Maui.Controls.DataTemplate)page.Resources["virtualHandlerTemplate"];
var element = (ElementWithEvent)template.CreateContent();
element.SendClicked();

// Override must be called; base must NOT be called.
Assert.Equal(1, page.overrideClicked);
Assert.Equal(0, page.baseForVirtualClicked);
}
}
}

class SubMaui18055 : Maui18055
{
public SubMaui18055(XamlInflator inflator) : base(inflator) { }

public int overrideClicked;
protected override void HandleVirtualClicked(object sender, EventArgs e) => overrideClicked++;
Comment thread
BagavathiPerumal marked this conversation as resolved.
Outdated
}
Loading