diff --git a/Directory.Build.props b/Directory.Build.props
index 30f2da1db..ca9be7049 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -41,6 +41,19 @@
16.9
+
+ win
+ linux
+ osx
+
+ x64
+ arm64
+
+ $(RidOsPrefix)-$(RidOsArchitecture)
+
+ $(DefaultRuntimeIdentifier)
+
+
diff --git a/Directory.Build.targets b/Directory.Build.targets
index b4afce0a7..dbcb085dc 100644
--- a/Directory.Build.targets
+++ b/Directory.Build.targets
@@ -4,4 +4,6 @@
+
+
diff --git a/Directory.Traversal.targets b/Directory.Traversal.targets
new file mode 100644
index 000000000..805bd6e92
--- /dev/null
+++ b/Directory.Traversal.targets
@@ -0,0 +1,15 @@
+
+
+ true
+
+ false
+ false
+ true
+ true
+
+
+
+
+
+
diff --git a/Microsoft.VisualStudio.Threading.slnx b/Microsoft.VisualStudio.Threading.slnx
index 62b62e80b..037ae9ddc 100644
--- a/Microsoft.VisualStudio.Threading.slnx
+++ b/Microsoft.VisualStudio.Threading.slnx
@@ -11,16 +11,21 @@
+
+
+
+
+
@@ -33,12 +38,15 @@
+
+
+
diff --git a/azure-pipelines/build.yml b/azure-pipelines/build.yml
index 0f62763b6..d16bcb0cc 100644
--- a/azure-pipelines/build.yml
+++ b/azure-pipelines/build.yml
@@ -197,6 +197,7 @@ jobs:
Is1ESPT: ${{ parameters.Is1ESPT }}
RunTests: ${{ parameters.RunTests }}
IsOptProf: ${{ parameters.IsOptProf }}
+ osRID: win
- ${{ if and(parameters.EnableDotNetFormatCheck, not(parameters.EnableLinuxBuild)) }}:
- script: dotnet format --verify-no-changes
@@ -241,6 +242,7 @@ jobs:
parameters:
Is1ESPT: ${{ parameters.Is1ESPT }}
RunTests: ${{ parameters.RunTests }}
+ osRID: linux
- ${{ if parameters.EnableDotNetFormatCheck }}:
- script: dotnet format --verify-no-changes
displayName: 💅 Verify formatted code
@@ -276,6 +278,7 @@ jobs:
parameters:
Is1ESPT: ${{ parameters.Is1ESPT }}
RunTests: ${{ parameters.RunTests }}
+ osRID: osx
- job: WrapUp
dependsOn:
diff --git a/azure-pipelines/dotnet.yml b/azure-pipelines/dotnet.yml
index 2abca9eb2..f9ca66e10 100644
--- a/azure-pipelines/dotnet.yml
+++ b/azure-pipelines/dotnet.yml
@@ -5,10 +5,12 @@ parameters:
default: false
- name: Is1ESPT
type: boolean
+- name: osRID
+ type: string
steps:
-- script: dotnet build -t:build,pack --no-restore -c $(BuildConfiguration) -warnAsError -warnNotAsError:NU1901,NU1902,NU1903,NU1904,LOCTASK002 /bl:"$(Build.ArtifactStagingDirectory)/build_logs/build.binlog"
+- script: dotnet build tools/dirs.proj -t:build,pack,publish --no-restore -c $(BuildConfiguration) -warnAsError -warnNotAsError:NU1901,NU1902,NU1903,NU1904,LOCTASK002 /bl:"$(Build.ArtifactStagingDirectory)/build_logs/build.binlog"
displayName: 🛠dotnet build
- ${{ if not(parameters.IsOptProf) }}:
diff --git a/global.json b/global.json
index 6e89f4181..5fce40099 100644
--- a/global.json
+++ b/global.json
@@ -6,6 +6,7 @@
},
"msbuild-sdks": {
"MSBuild.Sdk.Extras": "3.0.44",
- "Microsoft.Build.NoTargets": "3.7.56"
+ "Microsoft.Build.NoTargets": "3.7.56",
+ "Microsoft.Build.Traversal": "4.1.82"
}
}
diff --git a/src/Microsoft.VisualStudio.Threading/AsyncReaderWriterLock+HangReportContributor.cs b/src/Microsoft.VisualStudio.Threading/AsyncReaderWriterLock+HangReportContributor.cs
index cbed5dad5..08f4ffcbf 100644
--- a/src/Microsoft.VisualStudio.Threading/AsyncReaderWriterLock+HangReportContributor.cs
+++ b/src/Microsoft.VisualStudio.Threading/AsyncReaderWriterLock+HangReportContributor.cs
@@ -3,11 +3,11 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading;
-using System.Threading.Tasks;
using System.Xml.Linq;
namespace Microsoft.VisualStudio.Threading
@@ -46,6 +46,7 @@ protected internal virtual SynchronizationContext NoMessagePumpSynchronizationCo
/// Contributes data for a hang report.
///
/// The hang report contribution. Null values should be ignored.
+ [RequiresUnreferencedCode(Reasons.DiagnosticAnalysisOnly)]
HangReportContribution IHangReportContributor.GetHangReport()
{
return this.GetHangReport();
@@ -55,7 +56,8 @@ HangReportContribution IHangReportContributor.GetHangReport()
/// Contributes data for a hang report.
///
/// The hang report contribution. Null values should be ignored.
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
+ [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity"), SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
+ [RequiresUnreferencedCode(Reasons.DiagnosticAnalysisOnly)]
protected virtual HangReportContribution GetHangReport()
{
using (this.NoMessagePumpSynchronizationContext.Apply())
@@ -125,6 +127,7 @@ private static XDocument CreateDgml(out XElement nodes, out XElement links)
///
/// Appends details of a given collection of awaiters to the hang report.
///
+ [RequiresUnreferencedCode(Reasons.DiagnosticAnalysisOnly)]
private static XElement CreateAwaiterNode(Awaiter awaiter)
{
Requires.NotNull(awaiter, nameof(awaiter));
@@ -211,9 +214,13 @@ public IEnumerable Categories
{
get
{
+#if NET
+ foreach (AwaiterCollection value in Enum.GetValues())
+#else
#pragma warning disable CS8605 // Unboxing a possibly null value.
foreach (AwaiterCollection value in Enum.GetValues(typeof(AwaiterCollection)))
#pragma warning restore CS8605 // Unboxing a possibly null value.
+#endif
{
if (this.Membership.HasFlag(value))
{
diff --git a/src/Microsoft.VisualStudio.Threading/IHangReportContributor.cs b/src/Microsoft.VisualStudio.Threading/IHangReportContributor.cs
index 7c320ff33..7b5cac146 100644
--- a/src/Microsoft.VisualStudio.Threading/IHangReportContributor.cs
+++ b/src/Microsoft.VisualStudio.Threading/IHangReportContributor.cs
@@ -1,11 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
+using System.Diagnostics.CodeAnalysis;
namespace Microsoft.VisualStudio.Threading;
@@ -18,5 +14,6 @@ public interface IHangReportContributor
/// Contributes data for a hang report.
///
/// The hang report contribution. Null values should be ignored.
+ [RequiresUnreferencedCode(Reasons.DiagnosticAnalysisOnly)]
HangReportContribution GetHangReport();
}
diff --git a/src/Microsoft.VisualStudio.Threading/InternalUtilities.cs b/src/Microsoft.VisualStudio.Threading/InternalUtilities.cs
index ad95047e6..f9eaba00a 100644
--- a/src/Microsoft.VisualStudio.Threading/InternalUtilities.cs
+++ b/src/Microsoft.VisualStudio.Threading/InternalUtilities.cs
@@ -1,14 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-using System;
-using System.Collections;
using System.Collections.Generic;
-using System.Globalization;
-using System.Linq;
-using System.Reflection;
-using System.Runtime.CompilerServices;
-using System.Threading.Tasks;
namespace Microsoft.VisualStudio.Threading;
@@ -17,15 +10,6 @@ namespace Microsoft.VisualStudio.Threading;
///
internal static class InternalUtilities
{
- ///
- /// The substring that should be inserted before each async return stack frame.
- ///
- ///
- /// When printing synchronous callstacks, .NET begins each frame with " at ".
- /// When printing async return stack, we use this to indicate continuations.
- ///
- private const string AsyncReturnStackPrefix = " -> ";
-
///
/// Removes an element from the middle of a queue without disrupting the other elements.
///
@@ -60,229 +44,4 @@ internal static bool RemoveMidQueue(this Queue queue, T valueToRemove)
return found;
}
-
- ///
- /// Walk the continuation objects inside "async state machines" to generate the return call stack.
- /// FOR DIAGNOSTIC PURPOSES ONLY.
- ///
- /// The delegate that represents the head of an async continuation chain.
- internal static IEnumerable GetAsyncReturnStackFrames(this Delegate continuationDelegate)
- {
- IAsyncStateMachine? stateMachine = FindAsyncStateMachine(continuationDelegate);
- if (stateMachine is null)
- {
- // Did not find the async state machine, so returns the method name as top frame and stop walking.
- yield return GetDelegateLabel(continuationDelegate);
- yield break;
- }
-
- do
- {
- object? state = GetStateMachineFieldValueOnSuffix(stateMachine, "__state");
- yield return string.Format(
- CultureInfo.CurrentCulture,
- "{2}{0} (state: {1}, address: 0x{3:X8})",
- stateMachine.GetType().FullName,
- state,
- AsyncReturnStackPrefix,
- (long)GetAddress(stateMachine)); // the long cast allows hex formatting
-
- Delegate[]? continuationDelegates = FindContinuationDelegates(stateMachine).ToArray();
- if (continuationDelegates.Length == 0)
- {
- break;
- }
-
- // Consider: It's possible but uncommon scenario to have multiple "async methods" being awaiting for one "async method".
- // Here we just choose the first awaiting "async method" as that should be good enough for postmortem.
- // In future we might want to revisit this to cover the other awaiting "async methods".
- stateMachine = continuationDelegates.Select((d) => FindAsyncStateMachine(d))
- .FirstOrDefault((s) => s is object);
- if (stateMachine is null)
- {
- yield return GetDelegateLabel(continuationDelegates.First());
- }
- }
- while (stateMachine is object);
- }
-
- ///
- /// A helper method to get the label of the given delegate.
- ///
- private static string GetDelegateLabel(Delegate invokeDelegate)
- {
- Requires.NotNull(invokeDelegate, nameof(invokeDelegate));
-
- MethodInfo? method = invokeDelegate.GetMethodInfo();
- if (invokeDelegate.Target is object)
- {
- string instanceType = string.Empty;
- if (!(method?.DeclaringType?.Equals(invokeDelegate.Target.GetType()) ?? false))
- {
- instanceType = " (" + invokeDelegate.Target.GetType().FullName + ")";
- }
-
- return string.Format(
- CultureInfo.CurrentCulture,
- "{3}{0}.{1}{2} (target address: 0x{4:X" + (IntPtr.Size * 2) + "})",
- method?.DeclaringType?.FullName,
- method?.Name,
- instanceType,
- AsyncReturnStackPrefix,
- GetAddress(invokeDelegate.Target).ToInt64()); // the cast allows hex formatting
- }
-
- return string.Format(
- CultureInfo.CurrentCulture,
- "{2}{0}.{1}",
- method?.DeclaringType?.FullName,
- method?.Name,
- AsyncReturnStackPrefix);
- }
-
- ///
- /// Gets the memory address of a given object.
- ///
- /// The object to get the address for.
- /// The memory address.
- ///
- /// This method works when GCHandle will refuse because the type of object is a non-blittable type.
- /// However, this method provides no guarantees that the address will remain valid for the caller,
- /// so it is only useful for diagnostics and when we don't expect addresses to be changing much any more.
- ///
- private static unsafe IntPtr GetAddress(object value) => new IntPtr(Unsafe.AsPointer(ref value));
-
- ///
- /// A helper method to find the async state machine from the given delegate.
- ///
- private static IAsyncStateMachine? FindAsyncStateMachine(Delegate invokeDelegate)
- {
- Requires.NotNull(invokeDelegate, nameof(invokeDelegate));
-
- if (invokeDelegate.Target is object)
- {
- // Some delegates are wrapped with a ContinuationWrapper object. We have to unwrap that in those cases.
- // In testing, this m_continuation field jump is only required when the debugger is attached -- weird.
- // I suspect however that it's a natural behavior of the async state machine (when there are >1 continuations perhaps).
- // So we check for the case in all cases.
- if (GetFieldValue(invokeDelegate.Target, "m_continuation") is Action continuation)
- {
- invokeDelegate = continuation;
- if (invokeDelegate.Target is null)
- {
- return null;
- }
- }
-
- var stateMachine = GetFieldValue(invokeDelegate.Target, "m_stateMachine") as IAsyncStateMachine;
- return stateMachine;
- }
-
- return null;
- }
-
- ///
- /// This is the core to find the continuation delegate(s) inside the given async state machine.
- /// The chain of objects is like this: async state machine -> async method builder -> task -> continuation object -> action.
- ///
- ///
- /// There are 3 types of "async method builder": AsyncVoidMethodBuilder, AsyncTaskMethodBuilder, AsyncTaskMethodBuilder<T>.
- /// We don't cover AsyncVoidMethodBuilder as it is used rarely and it can't be awaited either;
- /// AsyncTaskMethodBuilder is a wrapper on top of AsyncTaskMethodBuilder<VoidTaskResult>.
- ///
- private static IEnumerable FindContinuationDelegates(IAsyncStateMachine stateMachine)
- {
- Requires.NotNull(stateMachine, nameof(stateMachine));
-
- object? builder = GetStateMachineFieldValueOnSuffix(stateMachine, "__builder");
- if (builder is null)
- {
- yield break;
- }
-
- object? task = GetFieldValue(builder, "m_task");
- if (task is null)
- {
- // Probably this builder is an instance of "AsyncTaskMethodBuilder", so we need to get its inner "AsyncTaskMethodBuilder"
- builder = GetFieldValue(builder, "m_builder");
- if (builder is object)
- {
- task = GetFieldValue(builder, "m_task");
- }
- }
-
- if (task is null)
- {
- yield break;
- }
-
- // "task" might be an instance of the type deriving from "Task", but "m_continuationObject" is a private field in "Task",
- // so we need to use "typeof(Task)" to access "m_continuationObject".
- FieldInfo? continuationField = typeof(Task).GetTypeInfo().GetDeclaredField("m_continuationObject");
- if (continuationField is null)
- {
- yield break;
- }
-
- object? continuationObject = continuationField.GetValue(task);
- if (continuationObject is null)
- {
- yield break;
- }
-
- if (continuationObject is IEnumerable items)
- {
- foreach (object? item in items)
- {
- Delegate? action = item as Delegate ?? GetFieldValue(item!, "m_action") as Delegate;
- if (action is object)
- {
- yield return action;
- }
- }
- }
- else
- {
- Delegate? action = continuationObject as Delegate ?? GetFieldValue(continuationObject, "m_action") as Delegate;
- if (action is object)
- {
- yield return action;
- }
- }
- }
-
- ///
- /// A helper method to get field's value given the object and the field name.
- ///
- private static object? GetFieldValue(object obj, string fieldName)
- {
- Requires.NotNull(obj, nameof(obj));
- Requires.NotNullOrEmpty(fieldName, nameof(fieldName));
-
- FieldInfo? field = obj.GetType().GetTypeInfo().GetDeclaredField(fieldName);
- if (field is object)
- {
- return field.GetValue(obj);
- }
-
- return null;
- }
-
- ///
- /// The field names of "async state machine" are not fixed; the workaround is to find the field based on the suffix.
- ///
- private static object? GetStateMachineFieldValueOnSuffix(IAsyncStateMachine stateMachine, string suffix)
- {
- Requires.NotNull(stateMachine, nameof(stateMachine));
- Requires.NotNullOrEmpty(suffix, nameof(suffix));
-
- IEnumerable? fields = stateMachine.GetType().GetTypeInfo().DeclaredFields;
- FieldInfo? field = fields.FirstOrDefault((f) => f.Name.EndsWith(suffix, StringComparison.Ordinal));
- if (field is object)
- {
- return field.GetValue(stateMachine);
- }
-
- return null;
- }
}
diff --git a/src/Microsoft.VisualStudio.Threading/JoinableTaskContext+HangReportContributor.cs b/src/Microsoft.VisualStudio.Threading/JoinableTaskContext+HangReportContributor.cs
index 7f96afed4..caad35d6c 100644
--- a/src/Microsoft.VisualStudio.Threading/JoinableTaskContext+HangReportContributor.cs
+++ b/src/Microsoft.VisualStudio.Threading/JoinableTaskContext+HangReportContributor.cs
@@ -3,10 +3,10 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Text;
-using System.Threading.Tasks;
using System.Xml.Linq;
namespace Microsoft.VisualStudio.Threading;
@@ -17,6 +17,7 @@ public partial class JoinableTaskContext : IHangReportContributor
/// Contributes data for a hang report.
///
/// The hang report contribution.
+ [RequiresUnreferencedCode(Reasons.DiagnosticAnalysisOnly)]
HangReportContribution IHangReportContributor.GetHangReport()
{
return this.GetHangReport();
@@ -26,6 +27,7 @@ HangReportContribution IHangReportContributor.GetHangReport()
/// Contributes data for a hang report.
///
/// The hang report contribution. Null values should be ignored.
+ [RequiresUnreferencedCode(Reasons.DiagnosticAnalysisOnly)]
protected virtual HangReportContribution GetHangReport()
{
using (this.NoMessagePumpSynchronizationContext.Apply())
@@ -116,6 +118,7 @@ private static Dictionary CreateNodesForJoinab
return result;
}
+ [RequiresUnreferencedCode(Reasons.DiagnosticAnalysisOnly)]
private static List> CreateNodeLabels(Dictionary tasksAndElements)
{
Requires.NotNull(tasksAndElements, nameof(tasksAndElements));
@@ -146,6 +149,7 @@ private static List> CreateNodeLabels(Dictionary
internal string DelegateLabel
{
+ [RequiresUnreferencedCode(Reasons.DiagnosticAnalysisOnly)]
get
{
return this.WalkAsyncReturnStackFrames().First(); // Top frame of the return callstack.
@@ -1214,6 +1210,7 @@ internal void RemoveExecutingCallback(JoinableTask.ExecutionQueue callbackReceiv
/// Walk the continuation objects inside "async state machines" to generate the return callstack.
/// FOR DIAGNOSTIC PURPOSES ONLY.
///
+ [RequiresUnreferencedCode(Reasons.DiagnosticAnalysisOnly)]
internal IEnumerable WalkAsyncReturnStackFrames()
{
// This instance might be a wrapper of another instance of "SingleExecuteProtector".
diff --git a/src/Microsoft.VisualStudio.Threading/MemoryInspection.cs b/src/Microsoft.VisualStudio.Threading/MemoryInspection.cs
new file mode 100644
index 000000000..b9f9ca85b
--- /dev/null
+++ b/src/Microsoft.VisualStudio.Threading/MemoryInspection.cs
@@ -0,0 +1,252 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Threading.Tasks;
+
+namespace Microsoft.VisualStudio.Threading;
+
+[RequiresUnreferencedCode(Reasons.DiagnosticAnalysisOnly)]
+internal static class MemoryInspection
+{
+ ///
+ /// The substring that should be inserted before each async return stack frame.
+ ///
+ ///
+ /// When printing synchronous callstacks, .NET begins each frame with " at ".
+ /// When printing async return stack, we use this to indicate continuations.
+ ///
+ private const string AsyncReturnStackPrefix = " -> ";
+
+ ///
+ /// Walk the continuation objects inside "async state machines" to generate the return call stack.
+ /// FOR DIAGNOSTIC PURPOSES ONLY.
+ ///
+ /// The delegate that represents the head of an async continuation chain.
+ internal static IEnumerable GetAsyncReturnStackFrames(this Delegate continuationDelegate)
+ {
+ IAsyncStateMachine? stateMachine = FindAsyncStateMachine(continuationDelegate);
+ if (stateMachine is null)
+ {
+ // Did not find the async state machine, so returns the method name as top frame and stop walking.
+ yield return GetDelegateLabel(continuationDelegate);
+ yield break;
+ }
+
+ do
+ {
+ object? state = GetStateMachineFieldValueOnSuffix(stateMachine, "__state");
+ yield return string.Format(
+ CultureInfo.CurrentCulture,
+ "{2}{0} (state: {1}, address: 0x{3:X8})",
+ stateMachine.GetType().FullName,
+ state,
+ AsyncReturnStackPrefix,
+ (long)GetAddress(stateMachine)); // the long cast allows hex formatting
+
+ Delegate[]? continuationDelegates = FindContinuationDelegates(stateMachine).ToArray();
+ if (continuationDelegates.Length == 0)
+ {
+ break;
+ }
+
+ // Consider: It's possible but uncommon scenario to have multiple "async methods" being awaiting for one "async method".
+ // Here we just choose the first awaiting "async method" as that should be good enough for postmortem.
+ // In future we might want to revisit this to cover the other awaiting "async methods".
+ stateMachine = continuationDelegates.Select((d) => FindAsyncStateMachine(d))
+ .FirstOrDefault((s) => s is object);
+ if (stateMachine is null)
+ {
+ yield return GetDelegateLabel(continuationDelegates.First());
+ }
+ }
+ while (stateMachine is object);
+ }
+
+ ///
+ /// A helper method to get the label of the given delegate.
+ ///
+ private static string GetDelegateLabel(Delegate invokeDelegate)
+ {
+ Requires.NotNull(invokeDelegate, nameof(invokeDelegate));
+
+ MethodInfo? method = invokeDelegate.GetMethodInfo();
+ if (invokeDelegate.Target is object)
+ {
+ string instanceType = string.Empty;
+ if (!(method?.DeclaringType?.Equals(invokeDelegate.Target.GetType()) ?? false))
+ {
+ instanceType = " (" + invokeDelegate.Target.GetType().FullName + ")";
+ }
+
+ return string.Format(
+ CultureInfo.CurrentCulture,
+ "{3}{0}.{1}{2} (target address: 0x{4:X" + (IntPtr.Size * 2) + "})",
+ method?.DeclaringType?.FullName,
+ method?.Name,
+ instanceType,
+ AsyncReturnStackPrefix,
+ GetAddress(invokeDelegate.Target).ToInt64()); // the cast allows hex formatting
+ }
+
+ return string.Format(
+ CultureInfo.CurrentCulture,
+ "{2}{0}.{1}",
+ method?.DeclaringType?.FullName,
+ method?.Name,
+ AsyncReturnStackPrefix);
+ }
+
+ ///
+ /// A helper method to find the async state machine from the given delegate.
+ ///
+ private static IAsyncStateMachine? FindAsyncStateMachine(Delegate invokeDelegate)
+ {
+ Requires.NotNull(invokeDelegate, nameof(invokeDelegate));
+
+ if (invokeDelegate.Target is object)
+ {
+ // Some delegates are wrapped with a ContinuationWrapper object. We have to unwrap that in those cases.
+ // In testing, this m_continuation field jump is only required when the debugger is attached -- weird.
+ // I suspect however that it's a natural behavior of the async state machine (when there are >1 continuations perhaps).
+ // So we check for the case in all cases.
+ if (GetFieldValue(invokeDelegate.Target, "m_continuation") is Action continuation)
+ {
+ invokeDelegate = continuation;
+ if (invokeDelegate.Target is null)
+ {
+ return null;
+ }
+ }
+
+ var stateMachine = GetFieldValue(invokeDelegate.Target, "m_stateMachine") as IAsyncStateMachine;
+ return stateMachine;
+ }
+
+ return null;
+ }
+
+ ///
+ /// This is the core to find the continuation delegate(s) inside the given async state machine.
+ /// The chain of objects is like this: async state machine -> async method builder -> task -> continuation object -> action.
+ ///
+ ///
+ /// There are 3 types of "async method builder": AsyncVoidMethodBuilder, AsyncTaskMethodBuilder, AsyncTaskMethodBuilder<T>.
+ /// We don't cover AsyncVoidMethodBuilder as it is used rarely and it can't be awaited either;
+ /// AsyncTaskMethodBuilder is a wrapper on top of AsyncTaskMethodBuilder<VoidTaskResult>.
+ ///
+ private static IEnumerable FindContinuationDelegates(IAsyncStateMachine stateMachine)
+ {
+ Requires.NotNull(stateMachine, nameof(stateMachine));
+
+ object? builder = GetStateMachineFieldValueOnSuffix(stateMachine, "__builder");
+ if (builder is null)
+ {
+ yield break;
+ }
+
+ object? task = GetFieldValue(builder, "m_task");
+ if (task is null)
+ {
+ // Probably this builder is an instance of "AsyncTaskMethodBuilder", so we need to get its inner "AsyncTaskMethodBuilder"
+ builder = GetFieldValue(builder, "m_builder");
+ if (builder is object)
+ {
+ task = GetFieldValue(builder, "m_task");
+ }
+ }
+
+ if (task is null)
+ {
+ yield break;
+ }
+
+ // "task" might be an instance of the type deriving from "Task", but "m_continuationObject" is a private field in "Task",
+ // so we need to use "typeof(Task)" to access "m_continuationObject".
+ FieldInfo? continuationField = typeof(Task).GetTypeInfo().GetDeclaredField("m_continuationObject");
+ if (continuationField is null)
+ {
+ yield break;
+ }
+
+ object? continuationObject = continuationField.GetValue(task);
+ if (continuationObject is null)
+ {
+ yield break;
+ }
+
+ if (continuationObject is IEnumerable items)
+ {
+ foreach (object? item in items)
+ {
+ Delegate? action = item as Delegate ?? GetFieldValue(item!, "m_action") as Delegate;
+ if (action is object)
+ {
+ yield return action;
+ }
+ }
+ }
+ else
+ {
+ Delegate? action = continuationObject as Delegate ?? GetFieldValue(continuationObject, "m_action") as Delegate;
+ if (action is object)
+ {
+ yield return action;
+ }
+ }
+ }
+
+ ///
+ /// A helper method to get field's value given the object and the field name.
+ ///
+ private static object? GetFieldValue(object obj, string fieldName)
+ {
+ Requires.NotNull(obj, nameof(obj));
+ Requires.NotNullOrEmpty(fieldName, nameof(fieldName));
+
+ FieldInfo? field = obj.GetType().GetTypeInfo().GetDeclaredField(fieldName);
+ if (field is object)
+ {
+ return field.GetValue(obj);
+ }
+
+ return null;
+ }
+
+ ///
+ /// The field names of "async state machine" are not fixed; the workaround is to find the field based on the suffix.
+ ///
+ private static object? GetStateMachineFieldValueOnSuffix(IAsyncStateMachine stateMachine, string suffix)
+ {
+ Requires.NotNull(stateMachine, nameof(stateMachine));
+ Requires.NotNullOrEmpty(suffix, nameof(suffix));
+
+ IEnumerable? fields = stateMachine.GetType().GetTypeInfo().DeclaredFields;
+ FieldInfo? field = fields.FirstOrDefault((f) => f.Name.EndsWith(suffix, StringComparison.Ordinal));
+ if (field is object)
+ {
+ return field.GetValue(stateMachine);
+ }
+
+ return null;
+ }
+
+ ///
+ /// Gets the memory address of a given object.
+ ///
+ /// The object to get the address for.
+ /// The memory address.
+ ///
+ /// This method works when GCHandle will refuse because the type of object is a non-blittable type.
+ /// However, this method provides no guarantees that the address will remain valid for the caller,
+ /// so it is only useful for diagnostics and when we don't expect addresses to be changing much any more.
+ ///
+ private static unsafe IntPtr GetAddress(object value) => new IntPtr(Unsafe.AsPointer(ref value));
+}
diff --git a/src/Microsoft.VisualStudio.Threading/Microsoft.VisualStudio.Threading.csproj b/src/Microsoft.VisualStudio.Threading/Microsoft.VisualStudio.Threading.csproj
index 3e5321d89..14acd335d 100644
--- a/src/Microsoft.VisualStudio.Threading/Microsoft.VisualStudio.Threading.csproj
+++ b/src/Microsoft.VisualStudio.Threading/Microsoft.VisualStudio.Threading.csproj
@@ -7,6 +7,9 @@
Use the Microsoft.VisualStudio.Threading package to get the library and analyzers together.
true
+
+ true
+ truetrue
diff --git a/src/Microsoft.VisualStudio.Threading/Reasons.cs b/src/Microsoft.VisualStudio.Threading/Reasons.cs
new file mode 100644
index 000000000..8d8189e95
--- /dev/null
+++ b/src/Microsoft.VisualStudio.Threading/Reasons.cs
@@ -0,0 +1,9 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace Microsoft.VisualStudio.Threading;
+
+internal static class Reasons
+{
+ internal const string DiagnosticAnalysisOnly = "This API performs analysis of runtime objects for diagnostic purposes only, and isn't required for correct functionality.";
+}
diff --git a/src/Microsoft.VisualStudio.Threading/ThreadingEventSource.cs b/src/Microsoft.VisualStudio.Threading/ThreadingEventSource.cs
index 890718f74..59ae498a6 100644
--- a/src/Microsoft.VisualStudio.Threading/ThreadingEventSource.cs
+++ b/src/Microsoft.VisualStudio.Threading/ThreadingEventSource.cs
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-using System;
+using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Tracing;
namespace Microsoft.VisualStudio.Threading;
@@ -104,6 +104,7 @@ public void WaitReaderWriterLockStop(int lockId, AsyncReaderWriterLock.LockKind
/// Hash code of the task.
/// Whether the task is on the main thread.
[Event(CompleteOnCurrentThreadStartEvent)]
+ [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "We're only serializing primitive types.")]
public void CompleteOnCurrentThreadStart(int taskId, bool isOnMainThread)
{
this.WriteEvent(CompleteOnCurrentThreadStartEvent, taskId, Boxed.Box(isOnMainThread));
@@ -143,6 +144,7 @@ public void WaitSynchronouslyStop()
/// The request id.
/// The execution need happen on the main thread.
[Event(PostExecutionStartEvent, Level = EventLevel.Verbose)]
+ [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "We're only serializing primitive types.")]
public void PostExecutionStart(int requestId, bool mainThreadAffinitized)
{
this.WriteEvent(PostExecutionStartEvent, requestId, Boxed.Box(mainThreadAffinitized));
diff --git a/src/dirs.proj b/src/dirs.proj
new file mode 100644
index 000000000..b6fa81f93
--- /dev/null
+++ b/src/dirs.proj
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/test/AOT.props b/test/AOT.props
new file mode 100644
index 000000000..27d5cbf21
--- /dev/null
+++ b/test/AOT.props
@@ -0,0 +1,16 @@
+
+
+ Exe
+ true
+ true
+ true
+ $(AvailableRuntimeIdentifiers)
+ $(DefaultRuntimeIdentifier)
+
+
+
+
+ RuntimeIdentifier
+
+
+
diff --git a/test/Microsoft.VisualStudio.Threading.Tests/Microsoft.VisualStudio.Threading.Tests.csproj b/test/Microsoft.VisualStudio.Threading.Tests/Microsoft.VisualStudio.Threading.Tests.csproj
index b4bc53556..b17c4787e 100644
--- a/test/Microsoft.VisualStudio.Threading.Tests/Microsoft.VisualStudio.Threading.Tests.csproj
+++ b/test/Microsoft.VisualStudio.Threading.Tests/Microsoft.VisualStudio.Threading.Tests.csproj
@@ -22,8 +22,7 @@
RarelyRemoveItemSet`1.cs
-
+
WeakKeyDictionary`2.cs
@@ -39,8 +38,7 @@
-
+
@@ -48,8 +46,7 @@
true
-
+ falsetrue
diff --git a/test/NativeAOTCompatibility.Test/NativeAOTCompatibility.Test.csproj b/test/NativeAOTCompatibility.Test/NativeAOTCompatibility.Test.csproj
new file mode 100644
index 000000000..f663beb88
--- /dev/null
+++ b/test/NativeAOTCompatibility.Test/NativeAOTCompatibility.Test.csproj
@@ -0,0 +1,19 @@
+
+
+
+ net9.0
+ false
+
+
+ $(TargetFrameworks);net9.0-windows
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/NativeAOTCompatibility.Test/Program.cs b/test/NativeAOTCompatibility.Test/Program.cs
new file mode 100644
index 000000000..0c1d2e38a
--- /dev/null
+++ b/test/NativeAOTCompatibility.Test/Program.cs
@@ -0,0 +1,4 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+System.Console.WriteLine("This test is run by \"dotnet publish -c release -r [RID]-x64\" rather than by executing the program.");
diff --git a/test/dirs.proj b/test/dirs.proj
new file mode 100644
index 000000000..16cfe03d2
--- /dev/null
+++ b/test/dirs.proj
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/tools/dirs.proj b/tools/dirs.proj
new file mode 100644
index 000000000..8f1d6787a
--- /dev/null
+++ b/tools/dirs.proj
@@ -0,0 +1,6 @@
+
+
+
+
+
+