diff --git a/src/mono/mono/mini/method-to-ir.c b/src/mono/mono/mini/method-to-ir.c index f4e661edd4eea2..c1ace95e11c54e 100644 --- a/src/mono/mono/mini/method-to-ir.c +++ b/src/mono/mono/mini/method-to-ir.c @@ -10167,9 +10167,14 @@ mono_method_to_ir (MonoCompile *cfg, MonoMethod *method, MonoBasicBlock *start_b if (!field || CLASS_HAS_FAILURE (klass)) { HANDLE_TYPELOAD_ERROR (cfg, klass); - // Reached only in AOT. Cannot turn a token into a class. We silence the compilation error - // and generate a runtime exception. - if (cfg->error->error_code == MONO_ERROR_BAD_IMAGE) + /* + * Reached only in AOT. After lowering the field resolution failure into a runtime + * throw, consume the expected recoverable metadata errors as well. Memberref field + * resolution can report MissingField/BadImage directly through cfg->error without + * setting cfg->exception_type, and leaving one of those live lets an accepted inline + * trip the inline_method () cfg->error assert later on. + */ + if (cfg->error->error_code == MONO_ERROR_BAD_IMAGE || cfg->error->error_code == MONO_ERROR_MISSING_FIELD) clear_cfg_error (cfg); // We need to push a dummy value onto the stack, respecting the intended type. diff --git a/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/AotTypeLoadRecoveryProbe.cs b/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/AotTypeLoadRecoveryProbe.cs new file mode 100644 index 00000000000000..975b32c825ab0a --- /dev/null +++ b/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/AotTypeLoadRecoveryProbe.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; +using ReproCore; + +internal static class AotTypeLoadRecoveryProbe +{ + [MethodImpl(MethodImplOptions.NoInlining)] + public static void Run() + { + AotTypeLoadRecoveryHarness.Run(); + } +} diff --git a/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/Contracts/ReproContracts/Contract.cs b/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/Contracts/ReproContracts/Contract.cs new file mode 100644 index 00000000000000..355311a4e420de --- /dev/null +++ b/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/Contracts/ReproContracts/Contract.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace ReproContracts; + +public static class ContractBridge +{ + public static T FromPointer(nint pointer) + where T : class + => default!; +} + +public sealed class MissingReference +{ +} + +public struct MissingInitObjValue +{ + public int Value; +} + +public sealed class MissingFieldOwner +{ + public static int Counter; + + public int InstanceCounter; +} diff --git a/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/Contracts/ReproContracts/ReproContracts.Contract.csproj b/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/Contracts/ReproContracts/ReproContracts.Contract.csproj new file mode 100644 index 00000000000000..7909391941be1e --- /dev/null +++ b/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/Contracts/ReproContracts/ReproContracts.Contract.csproj @@ -0,0 +1,17 @@ + + + $(NetCoreAppCurrent) + ReproContracts + ReproContracts + false + 1.0.0 + 1.0.0.0 + 1.0.0.0 + false + false + + + + + + diff --git a/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/Program.cs b/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/Program.cs new file mode 100644 index 00000000000000..4bf8635f7a8128 --- /dev/null +++ b/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/Program.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +public static class Program +{ + public static int Main(string[] args) + { + AotTypeLoadRecoveryProbe.Run(); + Console.WriteLine("Done!"); + return 42; + } +} diff --git a/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/ReproCore/AotTypeLoadRecoveryHarness.cs b/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/ReproCore/AotTypeLoadRecoveryHarness.cs new file mode 100644 index 00000000000000..2af94603d57003 --- /dev/null +++ b/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/ReproCore/AotTypeLoadRecoveryHarness.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace ReproCore; + +public static class AotTypeLoadRecoveryHarness +{ + public static void Run() + { + StorePathHarness.Run(); + InitObjTypeLoadHarness.Run(); + RunAndExpectFailure(nameof(LoadSideInlineHarness), LoadSideInlineHarness.Run); + } + + private static void RunAndExpectFailure(string scenarioName, Action scenario) + { + try + { + scenario(); + } + catch (Exception ex) when (IsExpectedRecoveryFailure(ex)) + { + return; + } + + throw new InvalidOperationException($"{scenarioName} completed without the expected typeload recovery failure."); + } + + private static bool IsExpectedRecoveryFailure(Exception ex) + { + return ex is TypeLoadException + or MissingFieldException + or BadImageFormatException; + } +} diff --git a/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/ReproCore/InitObjTypeLoadVariant.cs b/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/ReproCore/InitObjTypeLoadVariant.cs new file mode 100644 index 00000000000000..092adb970c405a --- /dev/null +++ b/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/ReproCore/InitObjTypeLoadVariant.cs @@ -0,0 +1,49 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.CompilerServices; +using ReproContracts; + +namespace ReproCore; + +public static class InitObjTypeLoadHarness +{ + [MethodImpl(MethodImplOptions.NoInlining)] + public static void Run() + { + if (Environment.TickCount == int.MinValue) + { + DirectInitObj(); + InitObjInsideExceptionClause(); + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void DirectInitObj() + { + MissingInitObjValue value = default; + Consume(value); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void InitObjInsideExceptionClause() + { + try + { + MissingInitObjValue value = default; + Consume(value); + } + catch (Exception ex) when (ex.GetHashCode() == int.MinValue) + { + GC.KeepAlive(ex); + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void Consume(T value) + { + if (Environment.TickCount == int.MinValue) + GC.KeepAlive(value); + } +} diff --git a/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/ReproCore/LoadSideInlineVariant.cs b/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/ReproCore/LoadSideInlineVariant.cs new file mode 100644 index 00000000000000..261e5f99c40f49 --- /dev/null +++ b/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/ReproCore/LoadSideInlineVariant.cs @@ -0,0 +1,119 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.CompilerServices; +using ReproContracts; + +namespace ReproCore; + +public static class LoadSideInlineHarness +{ + [MethodImpl(MethodImplOptions.NoInlining)] + public static void Run() + { + Consume(LoadThroughInlineCandidate()); + Consume(LoadThroughInlineInstanceCandidate()); + Consume(LoadInsideExceptionClause()); + Consume(new LoadSideTargetNode().ReadTarget()); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static int LoadThroughInlineCandidate() + { + return InlineableMissingFieldLoad(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int InlineableMissingFieldLoad() + { + return MissingFieldOwner.Counter; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static int LoadThroughInlineInstanceCandidate() + { + return InlineableMissingInstanceFieldLoad(new MissingFieldOwner()); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int InlineableMissingInstanceFieldLoad(MissingFieldOwner owner) + { + return owner.InstanceCounter; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static int LoadInsideExceptionClause() + { + try + { + return new MissingFieldOwner().InstanceCounter; + } + finally + { + Consume(0); + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void Consume(int value) + { + if (Environment.TickCount == int.MinValue) + GC.KeepAlive(value); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void Consume(object? value) + { + if (Environment.TickCount == int.MinValue) + GC.KeepAlive(value); + } +} + +public static class LoadSideStepExtensions +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TTarget ReadTarget( + LoadSideBoundStep step) + where TCurve : struct + where TTarget : class + => step.Target; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static LoadSideTargetNode ReadTarget(this LoadSideTargetNode target) + where TCurve : struct + => ReadTarget(new LoadSideConcreteStep()); +} + +public abstract class LoadSideBoundStep + where TCurve : struct + where TTarget : class +{ + public TTarget Target + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } = null!; +} + +public sealed class LoadSideTargetNode +{ + [MethodImpl(MethodImplOptions.NoInlining)] + public LoadSideTargetNode ReadTarget() + { + return this.ReadTarget(); + } +} + +public sealed class LoadSideConcreteStep : LoadSideBoundStep + where TCurve : struct +{ +} + +public readonly struct LoadSidePayload +{ +} + +public readonly struct LoadSideCurve +{ +} diff --git a/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/ReproCore/ReproCore.csproj b/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/ReproCore/ReproCore.csproj new file mode 100644 index 00000000000000..7540b4b58adb60 --- /dev/null +++ b/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/ReproCore/ReproCore.csproj @@ -0,0 +1,22 @@ + + + $(NetCoreAppCurrent) + ReproCore + ReproCore + false + false + false + $([MSBuild]::NormalizeDirectory('$(BaseOutputPath)', '$(Configuration)', '$(TargetFramework)')) + $([MSBuild]::NormalizeDirectory('$(BaseIntermediateOutputPath)', '$(Configuration)', '$(TargetFramework)')) + false + false + + + + + + + + + + diff --git a/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/ReproCore/StorePathVariant.cs b/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/ReproCore/StorePathVariant.cs new file mode 100644 index 00000000000000..15ff3f8681b867 --- /dev/null +++ b/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/ReproCore/StorePathVariant.cs @@ -0,0 +1,96 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.CompilerServices; +using ReproContracts; + +namespace ReproCore; + +public static class StorePathHarness +{ + [MethodImpl(MethodImplOptions.NoInlining)] + public static void Run() + { + // Keep the swapped-contract typeload reachable without forcing the store-path queue + // method itself to fail at signature/methodspec import time. + if (Environment.TickCount == int.MinValue) + StorePathContractProbe.Touch(); + + new StorePathTargetNode().Queue(); + } +} + +public static class StorePathContractProbe +{ + [MethodImpl(MethodImplOptions.NoInlining)] + public static void Touch() + { + var reference = ContractBridge.FromPointer(0); + _ = $"{reference}".Length; + } +} + +public static class StorePathStepExtensions +{ + public static StorePathBoundStep Attach( + TTarget target, + StorePathBoundStep step, + TValue value, + double duration, + TCurve curve) + where TCurve : struct + where TTarget : class + { + if (step.Target is not null) + throw new InvalidOperationException("A step can only be attached once."); + + step.Target = target; + step.Value = value; + step.Curve = curve; + step.EndTime = duration; + return step; + } + + public static void Schedule(this StorePathTargetNode target, StorePathPayload value, double duration, TCurve curve) + where TCurve : struct + => Attach(target, new StorePathConcreteStep(), value, duration, curve); +} + +public abstract class StorePathStep +{ + public TValue Value { get; internal set; } = default!; +} + +public abstract class StorePathBoundStep : StorePathStep + where TCurve : struct + where TTarget : class +{ + public TCurve Curve { get; internal set; } + + public double EndTime { get; internal set; } + + public TTarget Target { get; internal set; } = null!; +} + +public sealed class StorePathTargetNode +{ + [MethodImpl(MethodImplOptions.NoInlining)] + public void Queue() + { + this.Schedule(default, 100, default(StorePathCurve)); + } +} + +public sealed class StorePathConcreteStep : StorePathBoundStep + where TCurve : struct +{ +} + +public readonly struct StorePathPayload +{ +} + +public readonly struct StorePathCurve +{ +} diff --git a/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/SourceOnlyValidation/AotTypeLoadRecovery.SourceOnly.iOS/AotTypeLoadRecovery.SourceOnly.iOS.csproj b/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/SourceOnlyValidation/AotTypeLoadRecovery.SourceOnly.iOS/AotTypeLoadRecovery.SourceOnly.iOS.csproj new file mode 100644 index 00000000000000..97edec54a1def6 --- /dev/null +++ b/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/SourceOnlyValidation/AotTypeLoadRecovery.SourceOnly.iOS/AotTypeLoadRecovery.SourceOnly.iOS.csproj @@ -0,0 +1,44 @@ + + + Exe + net11.0-ios + iossimulator-arm64 + 13.4 + com.microsoft.runtime.aottypeloadrecovery.sourceonly + true + true + partial + false + $(DOTNET_HOST_PATH) + dotnet + <_SourceOnlyLinkedDir>obj\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\linked\ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/SourceOnlyValidation/AotTypeLoadRecovery.SourceOnly.iOS/AppDelegate.cs b/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/SourceOnlyValidation/AotTypeLoadRecovery.SourceOnly.iOS/AppDelegate.cs new file mode 100644 index 00000000000000..c28d89015f0552 --- /dev/null +++ b/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/SourceOnlyValidation/AotTypeLoadRecovery.SourceOnly.iOS/AppDelegate.cs @@ -0,0 +1,14 @@ +using Foundation; +using UIKit; + +namespace AotTypeLoadRecovery.SourceOnly.iOS; + +[Register("AppDelegate")] +public class AppDelegate : UIApplicationDelegate +{ + public override bool FinishedLaunching(UIApplication application, NSDictionary? launchOptions) + { + StorePathProbe.RootWithoutRunning(); + return true; + } +} diff --git a/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/SourceOnlyValidation/AotTypeLoadRecovery.SourceOnly.iOS/Main.cs b/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/SourceOnlyValidation/AotTypeLoadRecovery.SourceOnly.iOS/Main.cs new file mode 100644 index 00000000000000..9d89f39c7ef953 --- /dev/null +++ b/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/SourceOnlyValidation/AotTypeLoadRecovery.SourceOnly.iOS/Main.cs @@ -0,0 +1,8 @@ +using UIKit; + +namespace AotTypeLoadRecovery.SourceOnly.iOS; + +public static class Program +{ + public static void Main(string[] args) => UIApplication.Main(args, null, typeof(AppDelegate)); +} diff --git a/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/SourceOnlyValidation/AotTypeLoadRecovery.SourceOnly.iOS/StorePathProbe.cs b/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/SourceOnlyValidation/AotTypeLoadRecovery.SourceOnly.iOS/StorePathProbe.cs new file mode 100644 index 00000000000000..49df8808c12859 --- /dev/null +++ b/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/SourceOnlyValidation/AotTypeLoadRecovery.SourceOnly.iOS/StorePathProbe.cs @@ -0,0 +1,13 @@ +using System.Runtime.CompilerServices; +using ReproCore; + +namespace AotTypeLoadRecovery.SourceOnly.iOS; + +internal static class StorePathProbe +{ + [MethodImpl(MethodImplOptions.NoInlining)] + public static void RootWithoutRunning() + { + StorePathHarness.RootWithoutRunning(); + } +} diff --git a/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/SourceOnlyValidation/Contracts/ReproContracts/Contract.cs b/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/SourceOnlyValidation/Contracts/ReproContracts/Contract.cs new file mode 100644 index 00000000000000..d4c64e3c887f58 --- /dev/null +++ b/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/SourceOnlyValidation/Contracts/ReproContracts/Contract.cs @@ -0,0 +1,24 @@ +namespace ReproContracts; + +public static class ContractBridge +{ + public static T FromPointer(nint pointer) + where T : class + => default!; +} + +public sealed class MissingReference +{ +} + +public struct MissingInitObjValue +{ + public int Value; +} + +public sealed class MissingFieldOwner +{ + public static int Counter; + + public int InstanceCounter; +} diff --git a/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/SourceOnlyValidation/Contracts/ReproContracts/ReproContracts.csproj b/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/SourceOnlyValidation/Contracts/ReproContracts/ReproContracts.csproj new file mode 100644 index 00000000000000..f5f42588f78c22 --- /dev/null +++ b/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/SourceOnlyValidation/Contracts/ReproContracts/ReproContracts.csproj @@ -0,0 +1,10 @@ + + + netstandard2.0 + ReproContracts + ReproContracts + 1.0.0 + 1.0.0.0 + 1.0.0.0 + + diff --git a/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/SourceOnlyValidation/Directory.Build.props b/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/SourceOnlyValidation/Directory.Build.props new file mode 100644 index 00000000000000..3adedeffbf9a23 --- /dev/null +++ b/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/SourceOnlyValidation/Directory.Build.props @@ -0,0 +1,6 @@ + + + latest + enable + + diff --git a/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/SourceOnlyValidation/Directory.Build.targets b/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/SourceOnlyValidation/Directory.Build.targets new file mode 100644 index 00000000000000..058246e4086204 --- /dev/null +++ b/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/SourceOnlyValidation/Directory.Build.targets @@ -0,0 +1 @@ + diff --git a/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/SourceOnlyValidation/ReproCore/ReproCore.csproj b/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/SourceOnlyValidation/ReproCore/ReproCore.csproj new file mode 100644 index 00000000000000..e36cd966d4b219 --- /dev/null +++ b/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/SourceOnlyValidation/ReproCore/ReproCore.csproj @@ -0,0 +1,11 @@ + + + netstandard2.0 + ReproCore + ReproCore + + + + + + diff --git a/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/SourceOnlyValidation/ReproCore/StorePathHarness.cs b/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/SourceOnlyValidation/ReproCore/StorePathHarness.cs new file mode 100644 index 00000000000000..2a9deb7b466bb8 --- /dev/null +++ b/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/SourceOnlyValidation/ReproCore/StorePathHarness.cs @@ -0,0 +1,96 @@ +using System; +using System.Runtime.CompilerServices; +using ReproContracts; + +namespace ReproCore; + +public static class StorePathHarness +{ + [MethodImpl(MethodImplOptions.NoInlining)] + public static void Arm() + { + ContractProbe.Touch(); + new TargetNode().Queue(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static void RootWithoutRunning() + { + if (Environment.TickCount == int.MinValue) + Arm(); + } +} + +public static class ContractProbe +{ + [MethodImpl(MethodImplOptions.NoInlining)] + public static void Touch() + { + var reference = ContractBridge.FromPointer(0); + _ = $"{reference}".Length; + } +} + +public static class StepExtensions +{ + public static BoundStep Attach( + TTarget target, + BoundStep step, + TValue value, + double duration, + TCurve curve) + where TCurve : struct + where TTarget : class + { + if (step.Target != null) + throw new InvalidOperationException("A step can only be attached once."); + + step.Target = target; + step.Value = value; + step.Curve = curve; + step.EndTime = duration; + return step; + } + + public static void Schedule(this TargetNode target, Payload value, double duration, TCurve curve) + where TCurve : struct + => Attach(target, new ConcreteStep(), value, duration, curve); +} + +public abstract class Step +{ + public TValue Value { get; internal set; } = default!; +} + +public abstract class BoundStep : Step + where TCurve : struct + where TTarget : class +{ + public TCurve Curve { get; internal set; } + + public double EndTime { get; internal set; } + + public TTarget Target { get; internal set; } = null!; +} + +public sealed class TargetNode +{ + [MethodImpl(MethodImplOptions.NoInlining)] + public void Queue() + { + this.Schedule(default, 100, default(Curve)); + } +} + +public sealed class ConcreteStep : BoundStep + where TCurve : struct +{ +} + +public readonly struct Payload +{ +} + +public readonly struct Curve +{ +} diff --git a/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/SourceOnlyValidation/Stubs/ReproContracts/Placeholder.cs b/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/SourceOnlyValidation/Stubs/ReproContracts/Placeholder.cs new file mode 100644 index 00000000000000..d83fb052b4d3d1 --- /dev/null +++ b/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/SourceOnlyValidation/Stubs/ReproContracts/Placeholder.cs @@ -0,0 +1,9 @@ +namespace ReproContracts; + +internal static class Placeholder +{ +} + +public sealed class MissingFieldOwner +{ +} diff --git a/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/SourceOnlyValidation/Stubs/ReproContracts/ReproContracts.csproj b/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/SourceOnlyValidation/Stubs/ReproContracts/ReproContracts.csproj new file mode 100644 index 00000000000000..e7c8267ece380b --- /dev/null +++ b/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/SourceOnlyValidation/Stubs/ReproContracts/ReproContracts.csproj @@ -0,0 +1,13 @@ + + + netstandard2.0 + ReproContracts + ReproContracts + false + false + + + + + + diff --git a/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/SourceOnlyValidation/issue129613-sourceonly-run-mono-aot.sh b/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/SourceOnlyValidation/issue129613-sourceonly-run-mono-aot.sh new file mode 100755 index 00000000000000..81bce02d02c24d --- /dev/null +++ b/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/SourceOnlyValidation/issue129613-sourceonly-run-mono-aot.sh @@ -0,0 +1,175 @@ +#!/usr/bin/env bash + +set -euo pipefail + +project_dir="" +configuration="Debug" +framework="" +rid="iossimulator-arm64" +assembly="aot-instances.dll" +compiler="" +expect_exit_code=0 + +usage() { + cat <<'EOF' +Usage: issue129613-sourceonly-run-mono-aot.sh --project-dir --framework [options] + +Required: + --project-dir Source-only app project directory + --framework Target framework (for example net11.0-ios) + +Optional: + --configuration Build configuration (default: Debug) + --rid Runtime identifier (default: iossimulator-arm64) + --assembly Assembly entry from _AssembliesToAOT.items (default: aot-instances.dll) + --compiler mono-aot-cross to run; defaults to the published compiler path file + --expect-exit-code N Expected compiler exit code (default: 0) +EOF +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --project-dir) + project_dir="$2" + shift 2 + ;; + --configuration) + configuration="$2" + shift 2 + ;; + --framework) + framework="$2" + shift 2 + ;; + --rid) + rid="$2" + shift 2 + ;; + --assembly) + assembly="$2" + shift 2 + ;; + --compiler) + compiler="$2" + shift 2 + ;; + --expect-exit-code) + expect_exit_code="$2" + shift 2 + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "Unknown argument: $1" >&2 + usage >&2 + exit 2 + ;; + esac +done + +if [[ -z "$project_dir" || -z "$framework" ]]; then + usage >&2 + exit 2 +fi + +project_dir="$(cd "$project_dir" && pwd)" +base_dir="$project_dir/obj/$configuration/$framework/$rid" +items_file="$base_dir/linker-items/_AssembliesToAOT.items" + +if [[ ! -f "$items_file" ]]; then + echo "Missing _AssembliesToAOT.items at $items_file" >&2 + exit 3 +fi + +if [[ -z "$compiler" ]]; then + compiler_path_file="$(find "$base_dir" -maxdepth 1 -name 'aot-compiler-path-*.txt' | head -n 1)" + if [[ -z "$compiler_path_file" ]]; then + echo "No compiler override supplied and no published compiler path file was found in $base_dir" >&2 + exit 4 + fi + + compiler="$(<"$compiler_path_file")" +fi + +if [[ ! -x "$compiler" ]]; then + echo "Compiler is not executable: $compiler" >&2 + exit 5 +fi + +command_json="$(python3 - "$items_file" "$assembly" "$compiler" "$project_dir" <<'PY' +import json +import os +import shlex +import sys +import xml.etree.ElementTree as ET + +items_file, assembly_name, compiler_path, project_dir = sys.argv[1:] +ns = {"msbuild": "http://schemas.microsoft.com/developer/msbuild/2003"} +root = ET.parse(items_file).getroot() +items = [] +target = None + +for item in root.findall(".//msbuild:_AssembliesToAOT", ns): + metadata = {} + for child in item: + tag = child.tag.split("}", 1)[-1] + metadata[tag] = child.text or "" + + row = {"Include": item.attrib["Include"], **metadata} + items.append(row) + + include = row["Include"].replace("\\", "/") + if include.endswith("/" + assembly_name) or include.endswith(assembly_name): + target = row + +if target is None: + raise SystemExit(f"Assembly '{assembly_name}' was not found in {items_file}") + +inputs = [target["Include"]] +if target.get("IsDedupAssembly", "").lower() == "true": + inputs.extend(item["Include"] for item in items if item["Include"] != target["Include"]) + +linked_dir = os.path.join(project_dir, os.path.dirname(target["Include"])) +aot_argument = ",".join(shlex.split(target["Arguments"])) +command = [compiler_path] +command.append(f"--path={linked_dir}") +command.append(aot_argument) +command.extend(shlex.split(target.get("ProcessArguments", ""))) +command.extend(os.path.join(project_dir, path) for path in inputs) + +print(json.dumps(command)) +PY +)" + +command=() +while IFS= read -r part; do + command+=("$part") +done < <(python3 - "$command_json" <<'PY' +import json +import sys + +for part in json.loads(sys.argv[1]): + print(part) +PY +) + +printf 'Running:' +for part in "${command[@]}"; do + printf ' %q' "$part" +done +printf '\n' + +set +e +( + cd "$project_dir" + "${command[@]}" +) +actual_exit_code=$? +set -e + +if [[ "$actual_exit_code" -ne "$expect_exit_code" ]]; then + echo "mono-aot-cross exit code $actual_exit_code did not match expected $expect_exit_code" >&2 + exit 6 +fi diff --git a/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/Stubs/ReproContracts/Placeholder.cs b/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/Stubs/ReproContracts/Placeholder.cs new file mode 100644 index 00000000000000..b2e9c6cb4b8f61 --- /dev/null +++ b/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/Stubs/ReproContracts/Placeholder.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace ReproContracts; + +internal static class Placeholder +{ +} + +public static class ContractBridge +{ + public static T FromPointer(nint pointer) + where T : class + => default!; +} + +public sealed class MissingFieldOwner +{ +} diff --git a/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/Stubs/ReproContracts/ReproContracts.Stub.csproj b/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/Stubs/ReproContracts/ReproContracts.Stub.csproj new file mode 100644 index 00000000000000..ebe2dc13d863ee --- /dev/null +++ b/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/Stubs/ReproContracts/ReproContracts.Stub.csproj @@ -0,0 +1,17 @@ + + + $(NetCoreAppCurrent) + ReproContracts + ReproContracts + 1.0.0 + 1.0.0.0 + 1.0.0.0 + false + false + false + + + + + + diff --git a/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/iOS.Simulator.AotTypeLoadRecovery.Test.csproj b/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/iOS.Simulator.AotTypeLoadRecovery.Test.csproj new file mode 100644 index 00000000000000..d6a38b5e8e608c --- /dev/null +++ b/src/tests/FunctionalTests/iOS/Simulator/AotTypeLoadRecovery/iOS.Simulator.AotTypeLoadRecovery.Test.csproj @@ -0,0 +1,124 @@ + + + + Exe + Program + false + false + true + true + $(NetCoreAppCurrent) + iossimulator + iOS.Simulator.AotTypeLoadRecovery.Test.dll + false + 42 + true + true + <_AotTypeLoadRecoveryContractOutputDir>$([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'ReproContracts.Contract', '$(Configuration)', '$(TargetFramework)')) + <_AotTypeLoadRecoveryContractOutputDir Condition="'$(TargetOS)' != '' and '$(TargetArchitecture)' != ''">$([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'ReproContracts.Contract', '$(Configuration)', '$(TargetFramework)', '$(TargetOS)-$(TargetArchitecture.ToLowerInvariant())')) + <_AotTypeLoadRecoveryReproCoreOutputDir>$([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'ReproCore', '$(Configuration)', '$(TargetFramework)')) + <_AotTypeLoadRecoveryStubOutputDir>$([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'ReproContracts.Stub', '$(Configuration)', '$(TargetFramework)')) + <_AotTypeLoadRecoverySourceOnlyRoot>$([MSBuild]::NormalizeDirectory('$(MSBuildProjectDirectory)', 'SourceOnlyValidation')) + <_AotTypeLoadRecoverySourceOnlyProjectDir>$([MSBuild]::NormalizeDirectory('$(_AotTypeLoadRecoverySourceOnlyRoot)', 'AotTypeLoadRecovery.SourceOnly.iOS')) + <_AotTypeLoadRecoverySourceOnlyProject>$([MSBuild]::NormalizePath('$(_AotTypeLoadRecoverySourceOnlyProjectDir)', 'AotTypeLoadRecovery.SourceOnly.iOS.csproj')) + <_AotTypeLoadRecoverySourceOnlyScript>$([MSBuild]::NormalizePath('$(_AotTypeLoadRecoverySourceOnlyRoot)', 'issue129613-sourceonly-run-mono-aot.sh')) + <_AotTypeLoadRecoverySourceOnlyFramework>$(NetCoreAppCurrent)-ios + <_AotTypeLoadRecoverySourceOnlyRid>iossimulator-arm64 + <_AotTypeLoadRecoverySourceOnlyBaseDir>$([MSBuild]::NormalizeDirectory('$(_AotTypeLoadRecoverySourceOnlyProjectDir)', 'obj', '$(Configuration)', '$(_AotTypeLoadRecoverySourceOnlyFramework)', '$(_AotTypeLoadRecoverySourceOnlyRid)')) + <_AotTypeLoadRecoverySourceOnlyItemsFile>$([MSBuild]::NormalizePath('$(_AotTypeLoadRecoverySourceOnlyBaseDir)', 'linker-items', '_AssembliesToAOT.items')) + <_AotTypeLoadRecoverySourceOnlyBuildLog>$([MSBuild]::NormalizePath('$(IntermediateOutputPath)', 'sourceonly-validation.build.log')) + <_AotTypeLoadRecoverySourceOnlyWorkingDir>$([System.IO.Path]::GetTempPath()) + <_AotTypeLoadRecoverySourceOnlyUserDotNetPath>$(HOME)/Library/Application Support/dotnet/dotnet + $(DOTNET_HOST_PATH) + $(_AotTypeLoadRecoverySourceOnlyUserDotNetPath) + /usr/local/share/dotnet/dotnet + dotnet + <_AotTypeLoadRecoverySourceOnlyDotNetDir>$([System.IO.Path]::GetDirectoryName('$(AotTypeLoadRecoverySourceOnlyDotNetPath)')) + + + + + + + + + + + + + + + + + + + + + + + + + <_AotTypeLoadRecoveryStubArtifacts Include="$(_AotTypeLoadRecoveryStubOutputDir)ReproContracts.dll" /> + <_AotTypeLoadRecoveryStubArtifacts Include="$(_AotTypeLoadRecoveryStubOutputDir)ReproContracts.pdb" Condition="Exists('$(_AotTypeLoadRecoveryStubOutputDir)ReproContracts.pdb')" /> + + <_AotTypeLoadRecoveryStubDestinations Include="$(IntermediateLinkDir)" Condition="'$(IntermediateLinkDir)' != '' and Exists('$(IntermediateLinkDir)ReproContracts.dll')" /> + <_AotTypeLoadRecoveryStubDestinations Include="$(IntermediateOutputPath)linked\" Condition="'$(IntermediateLinkDir)' == '' and '$(IntermediateOutputPath)' != '' and Exists('$(IntermediateOutputPath)linked\ReproContracts.dll')" /> + <_AotTypeLoadRecoveryStubDestinations Include="$(AppleBuildDir)" Condition="'$(AppleBuildDir)' != '' and Exists('$(AppleBuildDir)ReproContracts.dll')" /> + <_AotTypeLoadRecoveryStubDestinations Include="$(PublishDir)" Condition="'$(PublishDir)' != '' and Exists('$(PublishDir)ReproContracts.dll')" /> + + + + + + + + + + + + <_AotTypeLoadRecoverySourceOnlyCompiler>@(MonoAotCrossCompiler->WithMetadataValue('RuntimeIdentifier', '$(_AotTypeLoadRecoverySourceOnlyRid)')) + + + + <_AotTypeLoadRecoverySourceOnlyGeneratedState Include="$(_AotTypeLoadRecoverySourceOnlyItemsFile)" /> + <_AotTypeLoadRecoverySourceOnlyGeneratedState Include="$(_AotTypeLoadRecoverySourceOnlyBaseDir)aot-compiler-path-*.txt" /> + + + + + + + + + + + + + +