-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Implement new COM interop API for RCW/CCW creation/management #32091
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
AaronRobinsonMSFT
merged 83 commits into
dotnet:master
from
AaronRobinsonMSFT:winui_rcw_ccw_api
Mar 7, 2020
Merged
Changes from all commits
Commits
Show all changes
83 commits
Select commit
Hold shift + click to select a range
6dae493
Add API to SPCL.
AaronRobinsonMSFT aa43ec8
Revert previous work.
AaronRobinsonMSFT e8846f1
Add API in stub form.
AaronRobinsonMSFT 7e0c328
Implement RuntimeHelpers.AllocateTypeAssociatedMemory()
AaronRobinsonMSFT a1b0507
Add tests for RuntimeHelpers.AllocateTypeAssociatedMemory
AaronRobinsonMSFT 39d5155
Update interop lib import/export API headers
AaronRobinsonMSFT 8e4e655
Implement the ComWrappers.GetIUnknownImpl() API
AaronRobinsonMSFT 1e88958
Add project for testing ComWrappers API.
AaronRobinsonMSFT c375198
Stub out remaining ComWrapper QCall APIs
AaronRobinsonMSFT 2b118c6
Add pseudo implementation and test for ComWrappers.RegisterForReferen…
AaronRobinsonMSFT 705d02f
Add and validate metasigs for invoking static methods in ComWrappers …
AaronRobinsonMSFT 7ec6cc9
Implement the runtime interation for managed object wrapper creation.
AaronRobinsonMSFT 67949a7
Write test for creation of managed object wrapper with tracker support.
AaronRobinsonMSFT 4ed9275
Make managed object wrappers persistent until the associated managed …
AaronRobinsonMSFT 9249f51
Update export namespaces in InteropLib
AaronRobinsonMSFT 2a22388
Surround UNREACHABLE() with braces because of how it is defined.
AaronRobinsonMSFT 33720f7
Remove unnecessary GCProtect sections.
AaronRobinsonMSFT 1a87a11
Retain the External Object Context in the SyncBlock and handle cleanup.
AaronRobinsonMSFT dbfa73f
Rename ExtObjCxtIterator to RuntimeCallContext.
AaronRobinsonMSFT 1073eba
Change the CreateReference() API to an informing style API (i.e. Foun…
AaronRobinsonMSFT 0d80d43
Make global pegging field on RCWWalker public.
AaronRobinsonMSFT 152a5e4
Place all exported functions into a single compilation unit for Inter…
AaronRobinsonMSFT 1bfb390
Add new API to make object release requests.
AaronRobinsonMSFT 3792204
Address object release issues.
AaronRobinsonMSFT c83e6ec
Update public API
AaronRobinsonMSFT d402fec
Add support for checking if an external object implements the
AaronRobinsonMSFT e508c34
Add comment about IEnumerable optimization.
AaronRobinsonMSFT 46c357c
Delay load ole32.dll to get RoGetAgileReference using GetProcAddress.
AaronRobinsonMSFT 0d3b9cf
TEST - WIP
AaronRobinsonMSFT 6e41699
Reduce uses of GetReferenceTracker() - unsafe function.
AaronRobinsonMSFT 4a58326
Validation bug for converting to MOW.
AaronRobinsonMSFT f40127f
Move the ComInterfaceEntry to the ABI namespace.
AaronRobinsonMSFT a023e85
Mock Tracker Runtime impl
AaronRobinsonMSFT 942b19e
Forward declare Win32 API.
AaronRobinsonMSFT 76eae00
Spelling mistake.
AaronRobinsonMSFT fa9af01
Move registration of global ComWrappers to be entirely in managed code.
AaronRobinsonMSFT c00ebd2
Remove unused message.
AaronRobinsonMSFT 4eb1240
Add error message to managed resources.
AaronRobinsonMSFT 61dc744
Implement Peg/Unpeg
AaronRobinsonMSFT 4d41a28
Add additional testing.
AaronRobinsonMSFT 241e204
Test ignore cache logic.
AaronRobinsonMSFT aeb9403
Change EnsureActiveWrapperAndAddRef() API to take an indirect OBJECTREF
AaronRobinsonMSFT 362ea4b
Fix Release builds
AaronRobinsonMSFT 4d4d395
Apply review feedback about active wrapper API.
AaronRobinsonMSFT 5a9cbb4
Update src/coreclr/src/interop/comwrappers.h
AaronRobinsonMSFT 48a1480
Bug in ignore cache scenario - found during local GCStress run.
AaronRobinsonMSFT 4bea8da
Finish off minimal testing scenarios.
AaronRobinsonMSFT 1bfea8c
Fix build issues on macOS using clang.
AaronRobinsonMSFT 1bb76c9
Update Windows' build in response to macOS build fixes.
AaronRobinsonMSFT 96ca3dc
Release build fixes
AaronRobinsonMSFT c05b565
Remove ATL dependency from Mock Reference Tracker runtime library.
AaronRobinsonMSFT 6e4fcce
Always include ComHelper.h in COM tests.
AaronRobinsonMSFT 2006fc6
Add Release() impl on test ComSmartPtr<T>.
AaronRobinsonMSFT 0041a35
Create a "support" and "nosupport" ComWrappers implementation.
AaronRobinsonMSFT 05c0035
Properly add a platform not supported implementation for ComWrappers.
AaronRobinsonMSFT 8a69732
Really remove the ComWrappers.NoSupport.cs file.
AaronRobinsonMSFT e8d616c
Forgot GetIUnknownImpl() API in the not supported version of ComWrapp…
AaronRobinsonMSFT b8703b1
Remove InteropLib shutdown logic.
AaronRobinsonMSFT 07cbc68
Acquire RefCache at the same time as creating ObjectCache. This avoids
AaronRobinsonMSFT c609b1c
Add StressLogs to InteropLibInterface
AaronRobinsonMSFT 20a5385
Cast OBJECTREFs so they work in StressLog macros.
AaronRobinsonMSFT 8e97865
Add a ComWrappers feature flag.
AaronRobinsonMSFT 5fa679c
Remove duplicate entry.
AaronRobinsonMSFT e836507
Review feedback.
AaronRobinsonMSFT e60bce2
Add Type? to the GetOrCreateObjectForComInstance() API call.
AaronRobinsonMSFT 75e8401
Revert "Add Type? to the GetOrCreateObjectForComInstance() API call."
AaronRobinsonMSFT 33194f9
Update GetOrCreateObjectForComInstance() to accept 'object?' as the
AaronRobinsonMSFT 7147244
Validate failure for reuse of wrapper for external object.
AaronRobinsonMSFT 1d87df4
Fix bug in failure case of attempting to reuse a wrapper.
AaronRobinsonMSFT 17b5404
Remove IAgileReference usage.
AaronRobinsonMSFT 9cc5ccc
Apply API review comments.
AaronRobinsonMSFT 1ae8587
Update test to align with API changes.
AaronRobinsonMSFT 96fa8cc
Merge conflict.
AaronRobinsonMSFT fc2f20a
Review feedback.
AaronRobinsonMSFT 9c4ecd5
Add stub for AllocateTypeAssociatedMemory() in Mono build.
AaronRobinsonMSFT bc25078
Style and name.
AaronRobinsonMSFT d96ef4b
Move RuntimeHelpers.AllocateTypeAssociatedMemory tests to src/libraries.
AaronRobinsonMSFT bfa455d
Missed static
AaronRobinsonMSFT b7c5c44
Skip tests on Mono.
AaronRobinsonMSFT f305cb0
Unit test fix for RuntimeHelpers.AllocateTypeAssociatedMemory().
AaronRobinsonMSFT 642ca9b
Bad merge on rebase.
AaronRobinsonMSFT fa45add
Minor rewording for the interoplib interface.
AaronRobinsonMSFT c7c6839
Feedback on naming.
AaronRobinsonMSFT File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
252 changes: 252 additions & 0 deletions
252
src/coreclr/src/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,252 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
// See the LICENSE file in the project root for more information. | ||
|
||
using System; | ||
using System.Collections; | ||
using System.Threading; | ||
using System.Runtime.CompilerServices; | ||
using Internal.Runtime.CompilerServices; | ||
|
||
namespace System.Runtime.InteropServices | ||
{ | ||
/// <summary> | ||
/// Enumeration of flags for <see cref="ComWrappers.GetOrCreateComInterfaceForObject(object, CreateComInterfaceFlags)"/>. | ||
/// </summary> | ||
[Flags] | ||
public enum CreateComInterfaceFlags | ||
{ | ||
None = 0, | ||
|
||
/// <summary> | ||
/// The caller will provide an IUnknown Vtable. | ||
AaronRobinsonMSFT marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
/// </summary> | ||
/// <remarks> | ||
/// This is useful in scenarios when the caller has no need to rely on an IUnknown instance | ||
/// that is used when running managed code is not possible (i.e. during a GC). In traditional | ||
/// COM scenarios this is common, but scenarios involving <see href="https://docs.microsoft.com/windows/win32/api/windows.ui.xaml.hosting.referencetracker/nn-windows-ui-xaml-hosting-referencetracker-ireferencetrackertarget">Reference Tracker hosting</see> | ||
/// calling of the IUnknown API during a GC is possible. | ||
/// </remarks> | ||
CallerDefinedIUnknown = 1, | ||
|
||
/// <summary> | ||
/// Flag used to indicate the COM interface should implement <see href="https://docs.microsoft.com/windows/win32/api/windows.ui.xaml.hosting.referencetracker/nn-windows-ui-xaml-hosting-referencetracker-ireferencetrackertarget">IReferenceTrackerTarget</see>. | ||
/// When this flag is passed, the resulting COM interface will have an internal implementation of IUnknown | ||
/// and as such none should be supplied by the caller. | ||
/// </summary> | ||
TrackerSupport = 2, | ||
} | ||
|
||
/// <summary> | ||
/// Enumeration of flags for <see cref="ComWrappers.GetOrCreateObjectForComInstance(IntPtr, CreateObjectFlags)"/>. | ||
/// </summary> | ||
[Flags] | ||
public enum CreateObjectFlags | ||
{ | ||
None = 0, | ||
|
||
/// <summary> | ||
/// Indicate if the supplied external COM object implements the <see href="https://docs.microsoft.com/windows/win32/api/windows.ui.xaml.hosting.referencetracker/nn-windows-ui-xaml-hosting-referencetracker-ireferencetracker">IReferenceTracker</see>. | ||
/// </summary> | ||
TrackerObject = 1, | ||
|
||
/// <summary> | ||
/// Ignore any internal caching and always create a unique instance. | ||
/// </summary> | ||
UniqueInstance = 2, | ||
} | ||
|
||
/// <summary> | ||
/// Class for managing wrappers of COM IUnknown types. | ||
/// </summary> | ||
[CLSCompliant(false)] | ||
public abstract partial class ComWrappers | ||
{ | ||
/// <summary> | ||
/// Interface type and pointer to targeted VTable. | ||
/// </summary> | ||
public struct ComInterfaceEntry | ||
{ | ||
/// <summary> | ||
/// Interface IID. | ||
/// </summary> | ||
public Guid IID; | ||
|
||
/// <summary> | ||
/// Memory must have the same lifetime as the memory returned from the call to <see cref="ComputeVtables(object, CreateComInterfaceFlags, out int)"/>. | ||
/// </summary> | ||
public IntPtr Vtable; | ||
} | ||
|
||
/// <summary> | ||
/// ABI for function dispatch of a COM interface. | ||
/// </summary> | ||
public struct ComInterfaceDispatch | ||
{ | ||
public IntPtr Vtable; | ||
|
||
/// <summary> | ||
/// Given a <see cref="System.IntPtr"/> from a generated Vtable, convert to the target type. | ||
/// </summary> | ||
/// <typeparam name="T">Desired type.</typeparam> | ||
/// <param name="dispatchPtr">Pointer supplied to Vtable function entry.</param> | ||
/// <returns>Instance of type associated with dispatched function call.</returns> | ||
public static unsafe T GetInstance<T>(ComInterfaceDispatch* dispatchPtr) where T : class | ||
{ | ||
// See the dispatch section in the runtime for details on the masking below. | ||
const long DispatchThisPtrMask = ~0xfL; | ||
var comInstance = *(ComInterfaceInstance**)(((long)dispatchPtr) & DispatchThisPtrMask); | ||
|
||
return Unsafe.As<T>(GCHandle.InternalGet(comInstance->GcHandle)); | ||
} | ||
|
||
private struct ComInterfaceInstance | ||
{ | ||
public IntPtr GcHandle; | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Globally registered instance of the ComWrappers class. | ||
/// </summary> | ||
private static ComWrappers? s_globalInstance; | ||
|
||
/// <summary> | ||
/// Create a COM representation of the supplied object that can be passed to a non-managed environment. | ||
/// </summary> | ||
/// <param name="instance">The managed object to expose outside the .NET runtime.</param> | ||
/// <param name="flags">Flags used to configure the generated interface.</param> | ||
/// <returns>The generated COM interface that can be passed outside the .NET runtime.</returns> | ||
public IntPtr GetOrCreateComInterfaceForObject(object instance, CreateComInterfaceFlags flags) | ||
{ | ||
if (instance == null) | ||
throw new ArgumentNullException(nameof(instance)); | ||
|
||
AaronRobinsonMSFT marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
ComWrappers impl = this; | ||
return GetOrCreateComInterfaceForObjectInternal(ObjectHandleOnStack.Create(ref impl), ObjectHandleOnStack.Create(ref instance), flags); | ||
} | ||
|
||
[DllImport(RuntimeHelpers.QCall)] | ||
private static extern IntPtr GetOrCreateComInterfaceForObjectInternal(ObjectHandleOnStack comWrappersImpl, ObjectHandleOnStack instance, CreateComInterfaceFlags flags); | ||
|
||
/// <summary> | ||
/// Compute the desired Vtable for <paramref name="obj"/> respecting the values of <paramref name="flags"/>. | ||
/// </summary> | ||
/// <param name="obj">Target of the returned Vtables.</param> | ||
/// <param name="flags">Flags used to compute Vtables.</param> | ||
/// <param name="count">The number of elements contained in the returned memory.</param> | ||
/// <returns><see cref="ComInterfaceEntry" /> pointer containing memory for all COM interface entries.</returns> | ||
/// <remarks> | ||
/// All memory returned from this function must either be unmanaged memory, pinned managed memory, or have been | ||
/// allocated with the <see cref="System.Runtime.CompilerServices.RuntimeHelpers.AllocateTypeAssociatedMemory(Type, int)"/> API. | ||
/// | ||
/// If the interface entries cannot be created and <code>null</code> is returned, the call to <see cref="ComWrappers.GetOrCreateComInterfaceForObject(object, CreateComInterfaceFlags)"/> will throw a <see cref="System.ArgumentNullException"/>. | ||
/// </remarks> | ||
protected unsafe abstract ComInterfaceEntry* ComputeVtables(object obj, CreateComInterfaceFlags flags, out int count); | ||
|
||
// Call to execute the abstract instance function | ||
internal static unsafe void* CallComputeVtables(ComWrappers? comWrappersImpl, object obj, CreateComInterfaceFlags flags, out int count) | ||
=> (comWrappersImpl ?? s_globalInstance!).ComputeVtables(obj, flags, out count); | ||
|
||
/// <summary> | ||
/// Get the currently registered managed object or creates a new managed object and registers it. | ||
/// </summary> | ||
/// <param name="externalComObject">Object to import for usage into the .NET runtime.</param> | ||
/// <param name="flags">Flags used to describe the external object.</param> | ||
/// <returns>Returns a managed object associated with the supplied external COM object.</returns> | ||
public object GetOrCreateObjectForComInstance(IntPtr externalComObject, CreateObjectFlags flags) | ||
{ | ||
return GetOrCreateObjectForComInstanceInternal(externalComObject, flags, null); | ||
} | ||
|
||
/// <summary> | ||
/// Create a managed object for the object pointed at by <paramref name="externalComObject"/> respecting the values of <paramref name="flags"/>. | ||
/// </summary> | ||
/// <param name="externalComObject">Object to import for usage into the .NET runtime.</param> | ||
/// <param name="flags">Flags used to describe the external object.</param> | ||
/// <returns>Returns a managed object associated with the supplied external COM object.</returns> | ||
/// <remarks> | ||
/// If the object cannot be created and <code>null</code> is returned, the call to <see cref="ComWrappers.GetOrCreateObjectForComInstance(IntPtr, CreateObjectFlags)"/> will throw a <see cref="System.ArgumentNullException"/>. | ||
AaronRobinsonMSFT marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
/// </remarks> | ||
protected abstract object? CreateObject(IntPtr externalComObject, CreateObjectFlags flags); | ||
|
||
// Call to execute the abstract instance function | ||
internal static object? CallCreateObject(ComWrappers? comWrappersImpl, IntPtr externalComObject, CreateObjectFlags flags) | ||
=> (comWrappersImpl ?? s_globalInstance!).CreateObject(externalComObject, flags); | ||
|
||
/// <summary> | ||
/// Get the currently registered managed object or uses the supplied managed object and registers it. | ||
/// </summary> | ||
/// <param name="externalComObject">Object to import for usage into the .NET runtime.</param> | ||
/// <param name="flags">Flags used to describe the external object.</param> | ||
/// <param name="wrapper">The <see cref="object"/> to be used as the wrapper for the external object</param> | ||
/// <returns>Returns a managed object associated with the supplied external COM object.</returns> | ||
/// <remarks> | ||
/// If the <paramref name="wrapper"/> instance already has an associated external object a <see cref="System.NotSupportedException"/> will be thrown. | ||
/// </remarks> | ||
public object GetOrRegisterObjectForComInstance(IntPtr externalComObject, CreateObjectFlags flags, object wrapper) | ||
AaronRobinsonMSFT marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
{ | ||
if (wrapper == null) | ||
throw new ArgumentNullException(nameof(externalComObject)); | ||
|
||
return GetOrCreateObjectForComInstanceInternal(externalComObject, flags, wrapper); | ||
} | ||
|
||
private object GetOrCreateObjectForComInstanceInternal(IntPtr externalComObject, CreateObjectFlags flags, object? wrapperMaybe) | ||
{ | ||
if (externalComObject == IntPtr.Zero) | ||
throw new ArgumentNullException(nameof(externalComObject)); | ||
|
||
ComWrappers impl = this; | ||
AaronRobinsonMSFT marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
object? wrapperMaybeLocal = wrapperMaybe; | ||
object? retValue = null; | ||
GetOrCreateObjectForComInstanceInternal(ObjectHandleOnStack.Create(ref impl), externalComObject, flags, ObjectHandleOnStack.Create(ref wrapperMaybeLocal), ObjectHandleOnStack.Create(ref retValue)); | ||
|
||
return retValue!; | ||
} | ||
|
||
[DllImport(RuntimeHelpers.QCall)] | ||
private static extern void GetOrCreateObjectForComInstanceInternal(ObjectHandleOnStack comWrappersImpl, IntPtr externalComObject, CreateObjectFlags flags, ObjectHandleOnStack wrapper, ObjectHandleOnStack retValue); | ||
|
||
/// <summary> | ||
/// Called when a request is made for a collection of objects to be released outside of normal object or COM interface lifetime. | ||
/// </summary> | ||
/// <param name="objects">Collection of objects to release.</param> | ||
protected abstract void ReleaseObjects(IEnumerable objects); | ||
|
||
// Call to execute the virtual instance function | ||
internal static void CallReleaseObjects(ComWrappers? comWrappersImpl, IEnumerable objects) | ||
=> (comWrappersImpl ?? s_globalInstance!).ReleaseObjects(objects); | ||
|
||
/// <summary> | ||
/// Register this class's implementation to be used as the single global instance. | ||
/// </summary> | ||
/// <remarks> | ||
/// This function can only be called a single time. Subsequent calls to this function will result | ||
/// in a <see cref="System.InvalidOperationException"/> being thrown. | ||
/// | ||
/// Scenarios where the global instance may be used are: | ||
/// * Object tracking via the <see cref="CreateComInterfaceFlags.TrackerSupport" /> and <see cref="CreateObjectFlags.TrackerObject" /> flags. | ||
/// * Usage of COM related Marshal APIs. | ||
/// </remarks> | ||
public void RegisterAsGlobalInstance() | ||
{ | ||
if (null != Interlocked.CompareExchange(ref s_globalInstance, this, null)) | ||
{ | ||
throw new InvalidOperationException(SR.InvalidOperation_ResetGlobalComWrappersInstance); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Get the runtime provided IUnknown implementation. | ||
/// </summary> | ||
/// <param name="fpQueryInterface">Function pointer to QueryInterface.</param> | ||
/// <param name="fpAddRef">Function pointer to AddRef.</param> | ||
/// <param name="fpRelease">Function pointer to Release.</param> | ||
protected static void GetIUnknownImpl(out IntPtr fpQueryInterface, out IntPtr fpAddRef, out IntPtr fpRelease) | ||
=> GetIUnknownImplInternal(out fpQueryInterface, out fpAddRef, out fpRelease); | ||
|
||
[DllImport(RuntimeHelpers.QCall)] | ||
private static extern void GetIUnknownImplInternal(out IntPtr fpQueryInterface, out IntPtr fpAddRef, out IntPtr fpRelease); | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -478,6 +478,9 @@ End | |
Crst RCWCleanupList | ||
End | ||
|
||
Crst ExternalObjectContextCache | ||
End | ||
|
||
Crst ReDacl | ||
End | ||
|
||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.