Skip to content

Commit

Permalink
PR feedback + fix failing coreclr unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
GrabYourPitchforks authored Nov 20, 2020
2 parents c1ecb07 + 0ef275c commit 4c1f5dd
Show file tree
Hide file tree
Showing 12 changed files with 374 additions and 246 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@
<Compile Include="$(BclSourcesRoot)\Internal\Runtime\InteropServices\ComponentActivator.cs" />
<Compile Include="$(BclSourcesRoot)\Internal\Runtime\InteropServices\IsolatedComponentLoadContext.cs" />
<Compile Include="$(BclSourcesRoot)\System\__Canon.cs" />
<Compile Include="$(BclSourcesRoot)\System\ActivatorCache.CoreCLR.cs" />
<Compile Include="$(BclSourcesRoot)\System\ArgIterator.cs" />
<Compile Include="$(BclSourcesRoot)\System\Array.CoreCLR.cs" />
<Compile Include="$(BclSourcesRoot)\System\Attribute.CoreCLR.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace System
{
internal sealed partial class RuntimeType
{
/// <summary>
/// A cache which allows optimizing <see cref="Activator.CreateInstance"/>,
/// <see cref="RuntimeType.CreateInstanceDefaultCtor"/>, and related APIs.
/// </summary>
private sealed unsafe class ActivatorCache
{
// The managed calli to the newobj allocator, plus its first argument (MethodTable*).
// In the case of the COM allocator, first arg is ComClassFactory*, not MethodTable*.
private readonly delegate*<IntPtr, object?> _pfnAllocator;
private readonly IntPtr _allocatorFirstArg;

// The managed calli to the parameterless ctor, taking "this" (as object) as its first argument.
// For value type ctors, we'll point to a special unboxing stub.
private readonly delegate*<object?, void> _pfnCtor;

#if DEBUG
private readonly WeakReference<RuntimeType> _originalRuntimeType; // don't prevent the RT from being collected
#endif

internal ActivatorCache(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] RuntimeType rt,
bool wrapExceptions)
{
Debug.Assert(rt != null);

#if DEBUG
_originalRuntimeType = new WeakReference<RuntimeType>(rt);
#endif

// The check below is redundant since these same checks are performed at the
// unmanaged layer, but this call will throw slightly different exceptions
// than the unmanaged layer, and callers might be dependent on this.

rt.CreateInstanceCheckThis();

_pfnAllocator = (delegate*<IntPtr, object>)RuntimeTypeHandle.GetAllocatorFtn(rt, out MethodTable* pMT, forGetUninitializedObject: false, wrapExceptions);
_allocatorFirstArg = (IntPtr)pMT;

RuntimeMethodHandleInternal ctorHandle = RuntimeMethodHandleInternal.EmptyHandle; // default nullptr

if (pMT->IsValueType)
{
if (pMT->IsNullable)
{
// Activator.CreateInstance returns null given typeof(Nullable<T>).

static object? ReturnNull(IntPtr _) => null;
_pfnAllocator = &ReturnNull;
}
else if (pMT->HasDefaultConstructor)
{
// Value type with an explicit default ctor; we'll ask the runtime to create
// an unboxing stub on our behalf.

ctorHandle = RuntimeTypeHandle.GetDefaultConstructor(rt, forceBoxedEntryPoint: true);
}
else
{
// ValueType with no explicit parameterless ctor; assume ctor returns default(T)
}
}
else
{
// Reference type - we can't proceed unless there's a default ctor we can call.

Debug.Assert(rt.IsClass);

if (pMT->IsComObject)
{
if (rt.IsGenericCOMObjectImpl())
{
// This is the __ComObject base type, which means that the MethodTable* we have
// doesn't contain CLSID information. The CLSID information is instead hanging
// off of the RuntimeType's sync block. We'll set the allocator to our stub, and
// instead of a MethodTable* we'll pass in the handle to the RuntimeType. The
// handles we create live for the lifetime of the app, but that's ok since it
// matches coreclr's internal implementation anyway (see GetComClassHelper).

[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2072:UnrecognizedReflectionPattern",
Justification = "Linker already saw this type through Activator/Type.CreateInstance.")]
static object AllocateComObject(IntPtr runtimeTypeHandle)
{
RuntimeType rt = (RuntimeType)GCHandle.FromIntPtr(runtimeTypeHandle).Target!;
Debug.Assert(rt != null);

return RuntimeTypeHandle.AllocateComObject(rt);
}
_pfnAllocator = &AllocateComObject;
_allocatorFirstArg = GCHandle.ToIntPtr(GCHandle.Alloc(rt));
}

// Neither __ComObject nor any derived type gets its parameterless ctor called.
// Activation is handled entirely by the allocator.

ctorHandle = default;
}
else if (!pMT->HasDefaultConstructor)
{
throw new MissingMethodException(SR.Format(SR.Arg_NoDefCTor, rt));
}
else
{
// Reference type with explicit parameterless ctor

ctorHandle = RuntimeTypeHandle.GetDefaultConstructor(rt, forceBoxedEntryPoint: false);
Debug.Assert(!ctorHandle.IsNullHandle());
}
}

if (ctorHandle.IsNullHandle())
{
static void CtorNoopStub(object? uninitializedObject) { }
_pfnCtor = &CtorNoopStub; // we use null singleton pattern if no ctor call is necessary
CtorIsPublic = true; // implicit parameterless ctor is always considered public
}
else
{
_pfnCtor = (delegate*<object?, void>)RuntimeMethodHandle.GetFunctionPointer(ctorHandle);
CtorIsPublic = (RuntimeMethodHandle.GetAttributes(ctorHandle) & MethodAttributes.Public) != 0;
}

Debug.Assert(_pfnAllocator != null);
Debug.Assert(_allocatorFirstArg != IntPtr.Zero);
Debug.Assert(_pfnCtor != null); // we use null singleton pattern if no ctor call is necessary
}

internal bool CtorIsPublic { get; }

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal object? CreateUninitializedObject(RuntimeType rt)
{
// We don't use RuntimeType, but we force the caller to pass it so
// that we can keep it alive on their behalf. Once the object is
// constructed, we no longer need the reference to the type instance,
// as the object itself will keep the type alive.

#if DEBUG
Debug.Assert(_originalRuntimeType.TryGetTarget(out RuntimeType? originalRT) && originalRT == rt,
"Caller passed the wrong RuntimeType to this routine.");
#endif

object? retVal = _pfnAllocator(_allocatorFirstArg);
GC.KeepAlive(rt);
return retVal;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void CallConstructor(object? uninitializedObject) => _pfnCtor(uninitializedObject);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -140,15 +140,16 @@ private static unsafe object GetUninitializedObjectInternal(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]
Type type)
{
Debug.Assert(type != null);
RuntimeType rt = (RuntimeType)type;
Debug.Assert(rt != null);

// If type is Nullable<T>, get newobj for boxed T instead.
delegate*<MethodTable*, object> newobjHelper = RuntimeTypeHandle.GetNewobjHelperFnPtr(rt, out MethodTable* pMT, unwrapNullable: true);
Debug.Assert(newobjHelper != null);
// If type is Nullable<T>, returns the allocator and MethodTable* for the underlying T.
delegate*<MethodTable*, object> pfnAllocator = RuntimeTypeHandle.GetAllocatorFtn(rt, out MethodTable* pMT, forGetUninitializedObject: true, wrapExceptions: false);
Debug.Assert(pfnAllocator != null);
Debug.Assert(pMT != null);
Debug.Assert(!pMT->IsNullable, "Should've unwrapped any Nullable<T> input.");

object retVal = newobjHelper(pMT);
object retVal = pfnAllocator(pMT);
GC.KeepAlive(rt); // don't allow the type to be collected before the object is instantiated

return retVal;
Expand Down
86 changes: 68 additions & 18 deletions src/coreclr/src/System.Private.CoreLib/src/System/RuntimeHandles.cs
Original file line number Diff line number Diff line change
Expand Up @@ -199,61 +199,111 @@ internal static bool HasElementType(RuntimeType type)
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern object CreateInstance(RuntimeType type, bool publicOnly, bool wrapExceptions, ref bool canBeCached, ref RuntimeMethodHandleInternal ctor, ref bool hasNoDefaultCtor);

[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern object Allocate(RuntimeType type);

[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern object CreateInstanceForAnotherGenericParameter(RuntimeType type, RuntimeType genericParameter);

/// <summary>
/// Given a RuntimeType, returns both the address of the JIT's newobj helper for that type and the
/// MethodTable* corresponding to that type. If the type is <see cref="Nullable{T}"/> closed over
/// some T, then returns the newobj helper and MethodTable* for the 'T'.
/// Return value signature is managed calli (MethodTable* pMT) -> object.
/// Given a RuntimeType, returns both the address of the JIT's newobj allocator helper for
/// that type and the MethodTable* corresponding to that type. Return value signature is
/// managed calli (MethodTable* pMT) -> object.
/// </summary>
internal static delegate*<MethodTable*, object> GetNewobjHelperFnPtr(
internal static delegate*<MethodTable*, object> GetAllocatorFtn(
// This API doesn't call any constructors, but the type needs to be seen as constructed.
// A type is seen as constructed if a constructor is kept.
// This obviously won't cover a type with no constructor. Reference types with no
// constructor are an academic problem. Valuetypes with no constructors are a problem,
// but IL Linker currently treats them as always implicitly boxed.
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] RuntimeType type,
out MethodTable* pMT, bool unwrapNullable)
out MethodTable* pMT, bool forGetUninitializedObject, bool wrapExceptions)
{
Debug.Assert(type != null);

delegate*<MethodTable*, object> pNewobjHelperTemp = null;
MethodTable* pMTTemp = null;
Interop.BOOL fFailedWhileRunningCctor = Interop.BOOL.FALSE;

GetNewobjHelperFnPtr(
new QCallTypeHandle(ref type),
&pNewobjHelperTemp,
&pMTTemp,
unwrapNullable ? Interop.BOOL.TRUE : Interop.BOOL.FALSE);
try
{
GetAllocatorFtn(
new QCallTypeHandle(ref type),
&pNewobjHelperTemp,
&pMTTemp,
forGetUninitializedObject ? Interop.BOOL.TRUE : Interop.BOOL.FALSE,
&fFailedWhileRunningCctor);
}
catch (Exception ex)
{
// If the cctor failed, propagate the exception as-is, wrapping in a TIE
// if needed. Otherwise, make the error message friendlier by including
// the name of the type that couldn't be instantiated.

if (fFailedWhileRunningCctor != Interop.BOOL.FALSE)
{
if (wrapExceptions) throw new TargetInvocationException(ex);
else throw; // rethrow original, no TIE
}

string friendlyMessage = SR.Format(SR.ActivatorCache_CannotGetAllocator, type, ex.Message);
switch (ex)
{
case ArgumentException: throw new ArgumentException(friendlyMessage);
case NotSupportedException: throw new NotSupportedException(friendlyMessage);
case MethodAccessException: throw new MethodAccessException(friendlyMessage);
case MissingMethodException: throw new MissingMethodException(friendlyMessage);
case MemberAccessException: throw new MemberAccessException(friendlyMessage);
}

throw; // can't make a friendlier message, rethrow original exception
}

pMT = pMTTemp;
return pNewobjHelperTemp;
}

[DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
private static extern void GetNewobjHelperFnPtr(QCallTypeHandle typeHandle, delegate*<MethodTable*, object>* ppNewobjHelper, MethodTable** ppMT, Interop.BOOL fUnwrapNullable);
private static extern void GetAllocatorFtn(QCallTypeHandle typeHandle, delegate*<MethodTable*, object>* ppNewobjHelper, MethodTable** ppMT, Interop.BOOL fGetUninitializedObject, Interop.BOOL* pfFailedWhileRunningCctor);

/// <summary>
/// Returns the MethodDesc* for this type's parameterless instance ctor.
/// For reference types, signature is (object @this) -> void.
/// For value types, signature is (ref T @thisUnboxed) -> void.
/// For value types, unboxed signature is (ref T @thisUnboxed) -> void.
/// For value types, forced boxed signature is (object @this) -> void.
/// Returns nullptr if no parameterless ctor is defined.
/// </summary>
internal static RuntimeMethodHandleInternal GetDefaultConstructor(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] RuntimeType type,
bool forceBoxedEntryPoint)
{
Debug.Assert(type != null);

return GetDefaultCtor(new QCallTypeHandle(ref type), (forceBoxedEntryPoint) ? Interop.BOOL.TRUE : Interop.BOOL.FALSE);
}

[DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
private static extern RuntimeMethodHandleInternal GetDefaultCtor(QCallTypeHandle typeHandle, Interop.BOOL forceBoxedEntryPoint);

/// <summary>
/// Given a RuntimeType which represents __ComObject, activates the class and creates
/// a RCW around it.
/// </summary>
/// <exception cref="InvalidComObjectException">No CLSID present, or invalid CLSID.</exception>
internal static object AllocateComObject(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] RuntimeType type)
{
Debug.Assert(type != null);

return GetDefaultCtor(new QCallTypeHandle(ref type));
// n.b. use ObjectHandleOnStack instead of QCallTypeHandle since runtime needs the actual RuntimeType instance,
// not just its underlying TypeHandle.

object activatedInstance = null!;
AllocateComObject(ObjectHandleOnStack.Create(ref type), ObjectHandleOnStack.Create(ref activatedInstance));

Debug.Assert(activatedInstance != null);
return activatedInstance;
}

[DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
private static extern RuntimeMethodHandleInternal GetDefaultCtor(QCallTypeHandle typeHandle);
private static extern void AllocateComObject(ObjectHandleOnStack runtimeType, ObjectHandleOnStack activatedInstance);

internal RuntimeType GetRuntimeType()
{
Expand Down
Loading

0 comments on commit 4c1f5dd

Please sign in to comment.