This commit is contained in:
Rolf Bjarne Kvinge 2024-10-16 19:27:38 +02:00
Родитель 6a514490e0
Коммит ec81cc6aa9
22 изменённых файлов: 363 добавлений и 66 удалений

Просмотреть файл

@ -167,8 +167,19 @@
<!-- Directory to store project specific stamp files used by certain targets for incremental builds -->
<_MaciOSStampDirectory>$(IntermediateOutputPath)stamp\</_MaciOSStampDirectory>
<!-- Our own runtime features -->
<!-- Set default DisposeTaggedPointers value, we stop disposing them by default in .NET 10 -->
<!-- The default logic here must match the default logic in NSObject.DisposeTaggedPointers -->
<DisposeTaggedPointers Condition="'$(DisposeTaggedPointers)' == '' And $([MSBuild]::VersionGreaterThanOrEquals($(TargetFrameworkVersion), 10.0))">false</DisposeTaggedPointers>
<DisposeTaggedPointers Condition="'$(DisposeTaggedPointers)' == ''">true</DisposeTaggedPointers>
</PropertyGroup>
<ItemGroup>
<RuntimeHostConfigurationOption Include="Foundation.NSObject.DisposeTaggedPointers" Condition="'$(DisposeTaggedPointers)' != ''" Value="$(DisposeTaggedPointers)" Trim="true" />
</ItemGroup>
<PropertyGroup>
<TargetPlatformSupported Condition=" '$(TargetPlatformIdentifier)' == '$(_PlatformName)' ">true</TargetPlatformSupported>
</PropertyGroup>
@ -573,9 +584,6 @@
<!-- Set default ValidateObjectPointers value -->
<_ValidateObjectPointers Condition="'$(_ValidateObjectPointers)' == ''">false</_ValidateObjectPointers>
<!-- Set default DisposeTaggedPointers value -->
<_DisposeTaggedPointers Condition="'$(_DisposeTaggedPointers)' == ''">true</_DisposeTaggedPointers>
<_CustomLinkerOptions>
AreAnyAssembliesTrimmed=$(_AreAnyAssembliesTrimmed)
AssemblyName=$(AssemblyName).dll
@ -690,7 +698,6 @@
<RuntimeHostConfigurationOption Include="ObjCRuntime.Runtime.IsManagedStaticRegistrar" Value="$(_IsManagedStaticRegistrarFeature)" Trim="true" />
<RuntimeHostConfigurationOption Include="ObjCRuntime.Runtime.IsNativeAOT" Value="$(_IsNativeAOTFeature)" Trim="true" />
<RuntimeHostConfigurationOption Include="ObjCRuntime.Class.ValidateObjectPointers" Value="$(_ValidateObjectPointers)" Trim="true" />
<RuntimeHostConfigurationOption Include="ObjCRuntime.NSObject.DisposeTaggedPointers" Value="$(_DisposeTaggedPointers)" Trim="true" />
<!-- Mark all assemblies to be copied if we're not linking any assemblies -->
<ResolvedFileToPublish

Просмотреть файл

@ -998,11 +998,24 @@ namespace Foundation {
handle = NativeHandle.Zero;
}
// This option is turned on by setting _DisposeTaggedPointers property to true in the project file.
static bool dispose_tagged_pointers;
// This option is changed by setting the DisposeTaggedPointers MSBuild property in the project file.
static bool? dispose_tagged_pointers;
static bool DisposeTaggedPointers {
get => dispose_tagged_pointers;
set => dispose_tagged_pointers = value;
get {
if (!dispose_tagged_pointers.HasValue) {
if (AppContext.TryGetSwitch ("Foundation.NSObject.DisposeTaggedPointers", out var dtp)) {
dispose_tagged_pointers = dtp;
} else {
// The default logic here must match how we set the default value for the DisposeTaggedPointers MSBuild property.
#if NET10_0_OR_GREATER
dispose_tagged_pointers = false;
#else
dispose_tagged_pointers = true;
#endif
}
}
return dispose_tagged_pointers.Value;
}
}
protected virtual void Dispose (bool disposing)
@ -1011,34 +1024,37 @@ namespace Foundation {
return;
disposed = true;
/* Tagged pointer is limited to 64bit, which is all we support anyway.
*
* The tagged pointer bit is:
*
* Arm64: most significant bit
* Simulators (both on arm64 and x64 desktops): most significant bit
* Desktop/x64 (macOS + Mac Catalyst): least significant bit
* Ref: https://github.com/apple-oss-distributions/objc4/blob/89543e2c0f67d38ca5211cea33f42c51500287d5/runtime/objc-internal.h#L603-L672
*/
var isTaggedPointerThatShouldNotBeDisposed = false;
if (!DisposeTaggedPointers) {
/* Tagged pointer is limited to 64bit, which is all we support anyway.
*
* The bit that identifies if a pointer is a tagged pointer is:
*
* Arm64 (everywhere): most significant bit
* Simulators (both on arm64 and x64 desktops): most significant bit
* Desktop/x64 (macOS + Mac Catalyst): least significant bit
*
* Ref: https://github.com/apple-oss-distributions/objc4/blob/89543e2c0f67d38ca5211cea33f42c51500287d5/runtime/objc-internal.h#L603-L672
*/
#if __MACOS__ || __MACCATALYST__
ulong _OBJC_TAG_MASK;
if (Runtime.IsARM64CallingConvention) {
_OBJC_TAG_MASK = 1UL << 63;
} else {
_OBJC_TAG_MASK = 1UL;
}
ulong _OBJC_TAG_MASK;
if (Runtime.IsARM64CallingConvention) {
_OBJC_TAG_MASK = 1UL << 63;
} else {
_OBJC_TAG_MASK = 1UL;
}
#else
const ulong _OBJC_TAG_MASK = 1UL << 63;
const ulong _OBJC_TAG_MASK = 1UL << 63;
#endif
bool isTaggedPointer;
unchecked {
var ulongHandle = (ulong) (IntPtr) handle;
isTaggedPointer = (ulongHandle & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
unchecked {
var ulongHandle = (ulong) (IntPtr) handle;
var isTaggedPointer = (ulongHandle & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
isTaggedPointerThatShouldNotBeDisposed = isTaggedPointer;
}
}
if (!DisposeTaggedPointers && isTaggedPointer) {
// don't dispose tagged pointers.
if (isTaggedPointerThatShouldNotBeDisposed) {
FreeData (); // still need to do this though.
} else if (handle != NativeHandle.Zero) {
if (disposing) {

Просмотреть файл

@ -1,5 +1,9 @@
<linker>
<assembly fullname="Microsoft.MacCatalyst">
<type fullname="Foundation.NSObject">
<method signature="System.Boolean get_DisposeTaggedPointers()" body="stub" feature="Foundation.NSObject.DisposeTaggedPointers" featurevalue="false" value="false" />
<method signature="System.Boolean get_DisposeTaggedPointers()" body="stub" feature="Foundation.NSObject.DisposeTaggedPointers" featurevalue="true" value="true" />
</type>
<type fullname="ObjCRuntime.Dlfcn">
<method signature="System.Void WarnOnce()" body="stub" feature="System.Diagnostics.Debugger.IsSupported" featurevalue="false" />
</type>

Просмотреть файл

@ -1,5 +1,9 @@
<linker>
<assembly fullname="Microsoft.iOS">
<type fullname="Foundation.NSObject">
<method signature="System.Boolean get_DisposeTaggedPointers()" body="stub" feature="Foundation.NSObject.DisposeTaggedPointers" featurevalue="false" value="false" />
<method signature="System.Boolean get_DisposeTaggedPointers()" body="stub" feature="Foundation.NSObject.DisposeTaggedPointers" featurevalue="true" value="true" />
</type>
<type fullname="ObjCRuntime.Dlfcn">
<method signature="System.Void WarnOnce()" body="stub" feature="System.Diagnostics.Debugger.IsSupported" featurevalue="false" />
</type>

Просмотреть файл

@ -1,5 +1,9 @@
<linker>
<assembly fullname="Microsoft.tvOS">
<type fullname="Foundation.NSObject">
<method signature="System.Boolean get_DisposeTaggedPointers()" body="stub" feature="Foundation.NSObject.DisposeTaggedPointers" featurevalue="false" value="false" />
<method signature="System.Boolean get_DisposeTaggedPointers()" body="stub" feature="Foundation.NSObject.DisposeTaggedPointers" featurevalue="true" value="true" />
</type>
<type fullname="ObjCRuntime.Dlfcn">
<method signature="System.Void WarnOnce()" body="stub" feature="System.Diagnostics.Debugger.IsSupported" featurevalue="false" />
</type>

Просмотреть файл

@ -73,7 +73,7 @@ PLATFORM=$(shell basename "$(CURDIR)")
endif
ifneq ($(RUNTIMEIDENTIFIERS)$(RUNTIMEIDENTIFIER),)
$(error "Don't set RUNTIMEIDENTIFIER or RUNTIMEIDENTIFIERS, set RID instead")
$(error "Don't set RUNTIMEIDENTIFIER or RUNTIMEIDENTIFIERS, set RID instead (RUNTIMEIDENTIFIER=$(RUNTIMEIDENTIFIER), RUNTIMEIDENTIFIERS=$(RUNTIMEIDENTIFIERS))")
endif
ifeq ($(RID),)

Просмотреть файл

@ -0,0 +1,93 @@
using System;
using System.Runtime.InteropServices;
using Foundation;
namespace DisposeTaggedPointersTestApp {
public class Program {
static int Main (string [] args)
{
var testCaseString = Environment.GetEnvironmentVariable ("TEST_CASE");
if (string.IsNullOrEmpty (testCaseString)) {
Console.WriteLine ($"The environment variable TEST_CASE wasn't set.");
return 2;
}
#if NET10_0_OR_GREATER
var taggedPointersDisposedByDefault = false;
#else
var taggedPointersDisposedByDefault = true;
#endif
switch (testCaseString) {
case "DisableWithAppContext":
AppContext.SetSwitch ("Foundation.NSObject.DisposeTaggedPointers", false);
if (ThrowsObjectDisposedExceptions ()) {
Console.WriteLine ($"❌ {testCaseString}: Failure: unexpected ObjectDisposedException when DisposeTaggedPointers=false");
return 1;
}
Console.WriteLine ($"✅ {testCaseString}: Success");
return 0;
case "EnableWithAppContext":
AppContext.SetSwitch ("Foundation.NSObject.DisposeTaggedPointers", true);
if (!ThrowsObjectDisposedExceptions ()) {
Console.WriteLine ($"❌ {testCaseString}: Failure: unexpected lack of ObjectDisposedException when DisposeTaggedPointers=true");
return 1;
}
Console.WriteLine ($"✅ {testCaseString}: Success");
return 0;
case "DisableWithMSBuildPropertyTrimmed":
if (ThrowsObjectDisposedExceptions ()) {
Console.WriteLine ($"❌ {testCaseString}: Failure: unexpected ObjectDisposedException when DisposeTaggedPointers=false");
return 1;
}
Console.WriteLine ($"✅ {testCaseString}: Success");
return 0;
case "EnableWithMSBuildPropertyTrimmed":
if (!ThrowsObjectDisposedExceptions ()) {
Console.WriteLine ($"❌ {testCaseString}: Failure: unexpected lack of ObjectDisposedException when DisposeTaggedPointers=true");
return 1;
}
Console.WriteLine ($"✅ {testCaseString}: Success");
return 0;
case "DisableWithMSBuildPropertyUntrimmed": {
var throws = ThrowsObjectDisposedExceptions ();
if (throws == taggedPointersDisposedByDefault) {
Console.WriteLine ($"❌ {testCaseString}: Failure: unexpected ObjectDisposedException when DisposeTaggedPointers=false");
return 1;
}
Console.WriteLine ($"✅ {testCaseString}: Success");
return 0;
}
case "EnableWithMSBuildPropertyUntrimmed": {
var throws = ThrowsObjectDisposedExceptions ();
if (throws != taggedPointersDisposedByDefault) {
Console.WriteLine ($"❌ {testCaseString}: Failure: unexpected lack of ObjectDisposedException when DisposeTaggedPointers=true");
return 1;
}
Console.WriteLine ($"✅ {testCaseString}: Success");
return 0;
}
case "Default": {
var throws = ThrowsObjectDisposedExceptions ();
if (throws != taggedPointersDisposedByDefault) {
Console.WriteLine ($"❌ {testCaseString}: Failure: Expected ObjectDisposedException: {taggedPointersDisposedByDefault} Threw ObjectDisposedException: {throws}");
return 1;
}
Console.WriteLine ($"✅ {testCaseString}: Success");
return 0;
}
default:
Console.WriteLine ($"❌ Unknown test case: {testCaseString }");
return 2;
}
}
static bool ThrowsObjectDisposedExceptions () => MonoTouchFixtures.ObjCRuntime.TaggedPointerTest.ThrowsObjectDisposedExceptions ();
}
}

Просмотреть файл

@ -0,0 +1,7 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition="'$(SetAppendRuntimeIdentifierToOutputPathToFalse)' == 'true'">
<!-- Used by the AppendRuntimeIdentifierToOutputPath_* tests -->
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
</PropertyGroup>
<Import Project="$(MSBuildThisFileDirectory)../../Directory.Build.props" />
</Project>

Просмотреть файл

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net$(BundledNETCoreAppTargetFrameworkVersion)-maccatalyst</TargetFramework>
</PropertyGroup>
<Import Project="..\shared.csproj" />
</Project>

Просмотреть файл

@ -0,0 +1 @@
include ../shared.mk

Просмотреть файл

@ -0,0 +1,2 @@
TOP=../../..
include $(TOP)/tests/common/shared-dotnet-test.mk

Просмотреть файл

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net$(BundledNETCoreAppTargetFrameworkVersion)-ios</TargetFramework>
</PropertyGroup>
<Import Project="..\shared.csproj" />
</Project>

Просмотреть файл

@ -0,0 +1 @@
include ../shared.mk

Просмотреть файл

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net$(BundledNETCoreAppTargetFrameworkVersion)-macos</TargetFramework>
</PropertyGroup>
<Import Project="..\shared.csproj" />
</Project>

Просмотреть файл

@ -0,0 +1 @@
include ../shared.mk

Просмотреть файл

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<Project>
<PropertyGroup>
<OutputType>Exe</OutputType>
<ApplicationTitle>DisposeTaggedPointersTestApp</ApplicationTitle>
<ApplicationId>com.xamarin.disposetaggedpointerstestapp</ApplicationId>
<UseInterpreter>true</UseInterpreter> <!-- to speed up the test run -->
</PropertyGroup>
<Import Project="../../common/shared-dotnet.csproj" />
<ItemGroup>
<Compile Include="../*.cs" />
<Compile Include="../../../monotouch-test/ObjCRuntime/TaggedPointerTest.Shared.cs" />
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,44 @@
TOP=../../../..
TESTNAME=DisposeTaggedPointersTestApp
include $(TOP)/tests/common/shared-dotnet.mk
export RUNTIMEIDENTIFIER=
export RUNTIMEIDENTIFIERS=
.stamp-restore:
$(Q) $(DOTNET) restore
$(Q) touch $@
define Test
clean-$(2):
$(Q) rm -f .stamp-build-$(2)
$(Q) rm -rf bin/$(1) obj/$(1)
CLEAN_TARGETS+=clean-$(2)
.stamp-build-$(2): .stamp-restore
$(MAKE) build CONFIG=$(1) BUILD_ARGUMENTS="/tl:off $(3)"
$(Q) touch $$@
BUILD_TARGETS+=.stamp-build-$(2)
run-$(2): export TEST_CASE=$(1)
run-$(2): .stamp-build-$(2)
$(MAKE) run-bare CONFIG=$(1)
RUN_TARGETS+=run-$(2)
test-$(2): run-$(2)
endef
$(eval $(call Test,Default,default))
$(eval $(call Test,DisableWithAppContext,disablewithappcontext))
$(eval $(call Test,EnableWithAppContext,enablewithappcontext))
$(eval $(call Test,DisableWithMSBuildPropertyUntrimmed,disablewithmsbuildpropertyuntrimmed,/p:DisposeTaggedPointers=false /p:_LinkMode=None))
$(eval $(call Test,EnableWithMSBuildPropertyUntrimmed,enablewithmsbuildpropertyuntrimmed,/p:DisposeTaggedPointers=true /p:_LinkMode=None))
$(eval $(call Test,DisableWithMSBuildPropertyTrimmed,disablewithmsbuildpropertytrimmed,/p:DisposeTaggedPointers=false /p:_LinkMode=SdkOnly))
$(eval $(call Test,EnableWithMSBuildPropertyTrimmed,enablewithmsbuildpropertytrimmed,/p:DisposeTaggedPointers=true /p:_LinkMode=SdkOnly))
build-all: $(BUILD_TARGETS)
clean-all:: $(CLEAN_TARGETS)
$(Q) rm -f .stamp-* *.binlog
test-all run-tests run-all run: $(RUN_TARGETS)
.PHONY: $(TEST_CASES)

Просмотреть файл

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net$(BundledNETCoreAppTargetFrameworkVersion)-tvos</TargetFramework>
</PropertyGroup>
<Import Project="..\shared.csproj" />
</Project>

Просмотреть файл

@ -0,0 +1 @@
include ../shared.mk

Просмотреть файл

@ -2864,5 +2864,29 @@ namespace Xamarin.Tests {
var errors = BinLog.GetBuildLogErrors (rv.BinLogPath).ToArray ();
AssertErrorMessages (errors, $"The SupportedOSPlatformVersion value '{version}' in the project file is lower than the minimum value '{minVersion}'.");
}
[TestCase (ApplePlatform.MacOSX)]
[TestCase (ApplePlatform.MacCatalyst)]
public void DisposeTaggedPointersTest (ApplePlatform platform)
{
var project = "DisposeTaggedPointersTestApp";
Configuration.IgnoreIfIgnoredPlatform (platform);
var project_path = GetProjectPath (project, platform: platform);
Clean (project_path);
var arguments = new string [] {
"-C", Path.GetDirectoryName (project_path)!,
"clean-all"
};
AssertExecute ("make", arguments, out var _);
arguments = new string [] {
"-C", Path.GetDirectoryName (project_path)!,
"test-all",
"-j",
};
AssertExecute ("make", arguments, out var _);
}
}
}

Просмотреть файл

@ -0,0 +1,53 @@
using System;
using Foundation;
namespace MonoTouchFixtures.ObjCRuntime {
public partial class TaggedPointerTest {
public static bool ThrowsObjectDisposedExceptions ()
{
try {
var notificationData =
"""
{
action = "action";
aps =
{
category = "ee";
"content-available" = dd;
"mutable-content" = cc;
sound = bb;
"thread-id" = "aa";
};
"em-account" = "a";
"em-account-id" = "b";
"em-body" = "c";
"em-date" = "d";
"em-from" = "e";
"em-from-address" = "f";
"em-notification" = g;
"em-notification-id" = h;
"em-subject" = "i";
"gcm.message_id" = j;
"google.c.a.e" = k;
"google.c.fid" = l;
"google.c.sender.id" = m;
}
""";
var data = NSData.FromString (notificationData);
var fmt = NSPropertyListFormat.OpenStep;
var userInfo = (NSMutableDictionary) NSPropertyListSerialization.PropertyListWithData (data, NSPropertyListReadOptions.Immutable, ref fmt, out var error);
var apsKey = new NSString ("aps");
// Iteration here should not throw ObjectDisposedExceptions
foreach (var kv in userInfo) {
apsKey.Dispose ();
}
return false;
} catch (ObjectDisposedException) {
return true;
}
}
}
}

Просмотреть файл

@ -1,4 +1,6 @@
using System;
using System.Linq;
using System.Runtime.CompilerServices;
using Foundation;
@ -8,47 +10,34 @@ namespace MonoTouchFixtures.ObjCRuntime {
[TestFixture]
[Preserve (AllMembers = true)]
public class TaggedPointerTest {
public partial class TaggedPointerTest {
[Test]
public void TaggedPointersArentDisposed ()
{
var notificationData =
"""
{
action = "action";
aps =
{
category = "ee";
"content-available" = dd;
"mutable-content" = cc;
sound = bb;
"thread-id" = "aa";
};
"em-account" = "a";
"em-account-id" = "b";
"em-body" = "c";
"em-date" = "d";
"em-from" = "e";
"em-from-address" = "f";
"em-notification" = g;
"em-notification-id" = h;
"em-subject" = "i";
"gcm.message_id" = j;
"google.c.a.e" = k;
"google.c.fid" = l;
"google.c.sender.id" = m;
}
""";
#if NET10_0_OR_GREATER
var taggedPointersDisposedByDefault = false;
#else
var taggedPointersDisposedByDefault = true;
#endif
Assert.AreEqual (taggedPointersDisposedByDefault, ThrowsObjectDisposedExceptions (), "Default behavior");
}
var data = NSData.FromString (notificationData);
var fmt = NSPropertyListFormat.OpenStep;
var userInfo = (NSMutableDictionary) NSPropertyListSerialization.PropertyListWithData (data, NSPropertyListReadOptions.Immutable, ref fmt, out var error);
var apsKey = new NSString ("aps");
// Iteration here should not throw ObjectDisposedExceptions
foreach (var kv in userInfo) {
apsKey.Dispose ();
[Test]
public void MemoryUsage ()
{
var taggedStringValue = "a";
var objA = new NSString (taggedStringValue);
var objB = new NSString (taggedStringValue);
Assert.AreEqual (objA.Handle, objB.Handle, "Pointer equality for tagged pointers");
var cwt = new ConditionalWeakTable<string, NSObject> ();
var count = 1000;
for (var i = 0; i < count; i++) {
cwt.Add (i.ToString (), new NSString (taggedStringValue));
}
GC.Collect ();
Assert.That (cwt.Count (), Is.LessThan (count), "At least some objects should have gotten garbage collected.");
}
}
}