#include "il2cpp-config.h" #include "metadata/GenericMetadata.h" #include "metadata/GenericMethod.h" #include "metadata/GenericSharing.h" #include "metadata/Il2CppGenericMethodCompare.h" #include "metadata/Il2CppGenericMethodHash.h" #include "os/Atomic.h" #include "os/Mutex.h" #include "utils/Memory.h" #include "vm/Class.h" #include "vm/Exception.h" #include "vm/GenericClass.h" #include "vm/MetadataAlloc.h" #include "vm/MetadataCache.h" #include "vm/MetadataLock.h" #include "vm/Method.h" #include "vm/Runtime.h" #include "vm/Type.h" #include "utils/Il2CppHashMap.h" #include "utils/InitOnce.h" #include "il2cpp-class-internals.h" #include "il2cpp-runtime-metadata.h" #include "il2cpp-runtime-stats.h" #include using il2cpp::metadata::GenericMetadata; using il2cpp::metadata::GenericSharing; using il2cpp::os::FastAutoLock; using il2cpp::vm::Class; using il2cpp::vm::GenericClass; using il2cpp::vm::MetadataCalloc; using il2cpp::vm::MetadataCache; using il2cpp::vm::Method; using il2cpp::vm::Runtime; using il2cpp::vm::Type; struct FullySharedGenericMethodInfo : public MethodInfo { FullySharedGenericMethodInfo() { memset(this, 0, sizeof(*this)); } Il2CppMethodPointer rawVirtualMethodPointer; Il2CppMethodPointer rawDirectMethodPointer; InvokerMethod rawInvokerMethod; }; static size_t SizeOfGenericMethodInfo(bool hasFullGenericSignature) { if (hasFullGenericSignature) return sizeof(FullySharedGenericMethodInfo); return sizeof(MethodInfo); } static MethodInfo* AllocGenericMethodInfo(bool hasFullGenericSignature) { return (MethodInfo*)MetadataCalloc(1, SizeOfGenericMethodInfo(hasFullGenericSignature)); } static MethodInfo* AllocCopyGenericMethodInfo(const MethodInfo* sourceMethodInfo) { MethodInfo* newMethodInfo = AllocGenericMethodInfo(sourceMethodInfo->has_full_generic_sharing_signature); memcpy(newMethodInfo, sourceMethodInfo, SizeOfGenericMethodInfo(sourceMethodInfo->has_full_generic_sharing_signature)); return newMethodInfo; } static void FullySharedGenericInvokeRedirectHasAdjustorThunk(Il2CppMethodPointer methodPointer, const MethodInfo* method, void* obj, void** args, void* retVal) { IL2CPP_ASSERT(Method::IsGenericInstance(method)); IL2CPP_ASSERT(il2cpp::vm::Runtime::IsFullGenericSharingEnabled()); IL2CPP_ASSERT(methodPointer == method->virtualMethodPointer || methodPointer == method->methodPointer); const FullySharedGenericMethodInfo* sharedMethodInfo = reinterpret_cast(method); IL2CPP_ASSERT(sharedMethodInfo->rawDirectMethodPointer != sharedMethodInfo->rawVirtualMethodPointer); if (methodPointer == sharedMethodInfo->virtualMethodPointer) sharedMethodInfo->rawInvokerMethod(sharedMethodInfo->rawVirtualMethodPointer, method, obj, args, retVal); else sharedMethodInfo->rawInvokerMethod(sharedMethodInfo->rawDirectMethodPointer, method, obj, args, retVal); } static void FullySharedGenericInvokeRedirectNoAdjustorThunk(Il2CppMethodPointer methodPointer, const MethodInfo* method, void* obj, void** args, void* retVal) { IL2CPP_ASSERT(Method::IsGenericInstance(method)); IL2CPP_ASSERT(il2cpp::vm::Runtime::IsFullGenericSharingEnabled()); IL2CPP_ASSERT(methodPointer == method->methodPointer || methodPointer == method->virtualMethodPointer); const FullySharedGenericMethodInfo* sharedMethodInfo = reinterpret_cast(method); IL2CPP_ASSERT(sharedMethodInfo->rawDirectMethodPointer == sharedMethodInfo->rawVirtualMethodPointer); sharedMethodInfo->rawInvokerMethod(sharedMethodInfo->rawDirectMethodPointer, method, obj, args, retVal); } namespace il2cpp { namespace metadata { typedef Il2CppReaderWriterLockedHashMap Il2CppGenericMethodMap; static Il2CppGenericMethodMap s_GenericMethodMap; static Il2CppGenericMethodMap s_PendingGenericMethodMap; static bool HasFullGenericSharedParametersOrReturn(const MethodInfo* methodDefinition, const Il2CppType** inflatedParameterTypes) { // If a method has a variable sized return type, the FGS method will always // expect the return value to be passed as a by ref parameter if (Type::HasVariableRuntimeSizeWhenFullyShared(methodDefinition->return_type)) return true; for (int i = 0; i < methodDefinition->parameters_count; i++) { // Value types are passed by ref, but reference types are passed normally, so if the inflated parameter is a // reference type, we don't have a signature difference. if (Type::IsValueType(inflatedParameterTypes[i]) && Type::HasVariableRuntimeSizeWhenFullyShared(methodDefinition->parameters[i])) return true; } return false; } static void AnUnresolvedCallStubWasNotFound() { vm::Exception::Raise(vm::Exception::GetExecutionEngineException("An unresolved indirect call lookup failed")); } // This method must have a different signature than AnUnresolvedCallStubWasNotFound to prevent identical COMDAT folding // FullySharedGenericInvokeRedirectHasAdjustorThunk relies on this method having a different address static void AnUnresolvedCallStubWasNotFoundValueType(void* obj) { vm::Exception::Raise(vm::Exception::GetExecutionEngineException("An unresolved indirect call to a value type failed")); } static void AGenericMethodWhichIsTooDeeplyNestedWasInvoked() { vm::Exception::Raise(vm::Exception::GetMaximumNestedGenericsException()); } static void AGenericMethodWhichIsTooDeeplyNestedWasInvokedInvoker(Il2CppMethodPointer ptr, const MethodInfo* method, void* obj, void** args, void* ret) { AGenericMethodWhichIsTooDeeplyNestedWasInvoked(); } static FullySharedGenericMethodInfo ambiguousMethodInfo; bool GenericMethod::IsGenericAmbiguousMethodInfo(const MethodInfo* method) { return method == &ambiguousMethodInfo; } const MethodInfo* GenericMethod::GetGenericVirtualMethod(const MethodInfo* vtableSlotMethod, const MethodInfo* genericVirtualMethod) { IL2CPP_NOT_IMPLEMENTED_NO_ASSERT(GetGenericVirtualMethod, "We should only do the following slow method lookup once and then cache on type itself."); const Il2CppGenericInst* classInst = NULL; if (vtableSlotMethod->is_inflated) { classInst = vtableSlotMethod->genericMethod->context.class_inst; vtableSlotMethod = vtableSlotMethod->genericMethod->methodDefinition; } return metadata::GenericMethod::GetMethod(vtableSlotMethod, classInst, genericVirtualMethod->genericMethod->context.method_inst); } const MethodInfo* GenericMethod::GetMethod(const MethodInfo* methodDefinition, const Il2CppGenericInst* classInst, const Il2CppGenericInst* methodInst) { Il2CppGenericMethod gmethod = { 0 }; gmethod.methodDefinition = methodDefinition; gmethod.context.class_inst = classInst; gmethod.context.method_inst = methodInst; return GetMethod(&gmethod, true); } MethodInfo* GenericMethod::AllocateNewMethodInfo(const MethodInfo* methodDefinition, const Il2CppGenericInst* classInst, const Il2CppGenericInst* methodInst) { const MethodInfo* methodInfo = GetMethod(methodDefinition, classInst, methodInst); return AllocCopyGenericMethodInfo(methodInfo); } const MethodInfo* GenericMethod::GetMethod(const Il2CppGenericMethod* gmethod) { return GetMethod(gmethod, false); } const MethodInfo* GenericMethod::GetMethod(const Il2CppGenericMethod* gmethod, bool copyMethodPtr) { // This can be NULL only when we have hit the generic recursion depth limit. if (gmethod == NULL) { MethodInfo* newMethod = AllocGenericMethodInfo(il2cpp::vm::Runtime::IsFullGenericSharingEnabled()); if (il2cpp::vm::Runtime::IsFullGenericSharingEnabled()) { ((FullySharedGenericMethodInfo*)newMethod)->rawVirtualMethodPointer = AGenericMethodWhichIsTooDeeplyNestedWasInvoked; ((FullySharedGenericMethodInfo*)newMethod)->rawDirectMethodPointer = AGenericMethodWhichIsTooDeeplyNestedWasInvoked; ((FullySharedGenericMethodInfo*)newMethod)->rawInvokerMethod = AGenericMethodWhichIsTooDeeplyNestedWasInvokedInvoker; } newMethod->methodPointer = AGenericMethodWhichIsTooDeeplyNestedWasInvoked; newMethod->virtualMethodPointer = AGenericMethodWhichIsTooDeeplyNestedWasInvoked; newMethod->invoker_method = AGenericMethodWhichIsTooDeeplyNestedWasInvokedInvoker; return newMethod; } // First check for an already constructed generic method using the shared/reader lock MethodInfo* existingMethod; if (s_GenericMethodMap.TryGet(gmethod, &existingMethod)) return existingMethod; if (Method::IsAmbiguousMethodInfo(gmethod->methodDefinition)) { // is_inflated is used as an initialized check if (!ambiguousMethodInfo.is_inflated) { memcpy(&ambiguousMethodInfo, gmethod->methodDefinition, sizeof(MethodInfo)); ambiguousMethodInfo.is_inflated = true; ambiguousMethodInfo.rawVirtualMethodPointer = gmethod->methodDefinition->virtualMethodPointer; ambiguousMethodInfo.rawDirectMethodPointer = gmethod->methodDefinition->methodPointer; ambiguousMethodInfo.invoker_method = gmethod->methodDefinition->invoker_method; } return &ambiguousMethodInfo; } return CreateMethodLocked(gmethod, copyMethodPtr); } const MethodInfo* GenericMethod::CreateMethodLocked(const Il2CppGenericMethod* gmethod, bool copyMethodPtr) { // We need to inflate a new generic method, take the metadata mutex // All code below this point can and does assume mutual exclusion FastAutoLock lock(&il2cpp::vm::g_MetadataLock); // Recheck the s_GenericMethodMap in case there was a race to add this generic method MethodInfo* existingMethod; if (s_GenericMethodMap.TryGet(gmethod, &existingMethod)) return existingMethod; // GetMethodLocked may be called recursively, we keep tracking of pending inflations if (s_PendingGenericMethodMap.TryGet(gmethod, &existingMethod)) return existingMethod; if (copyMethodPtr) gmethod = MetadataCache::GetGenericMethod(gmethod->methodDefinition, gmethod->context.class_inst, gmethod->context.method_inst); const MethodInfo* methodDefinition = gmethod->methodDefinition; Il2CppClass* declaringClass = methodDefinition->klass; if (gmethod->context.class_inst) { Il2CppGenericClass* genericClassDeclaringType = GenericMetadata::GetGenericClass(methodDefinition->klass, gmethod->context.class_inst); declaringClass = GenericClass::GetClass(genericClassDeclaringType); // we may fail if we cannot construct generic type if (!declaringClass) return NULL; } const Il2CppType** parameters = GenericMetadata::InflateParameters(methodDefinition->parameters, methodDefinition->parameters_count, &gmethod->context, true); il2cpp::vm::Il2CppGenericMethodPointers methodPointers = MetadataCache::GetGenericMethodPointers(methodDefinition, &gmethod->context); bool hasFullGenericSharingSignature = methodPointers.isFullGenericShared && HasFullGenericSharedParametersOrReturn(gmethod->methodDefinition, parameters); MethodInfo* newMethod = AllocGenericMethodInfo(hasFullGenericSharingSignature); // we set the pending generic method map here because the initialization may recurse and try to retrieve the same generic method // this is safe because we *always* take the lock when retrieving the MethodInfo from a generic method. // if we move lock to only if MethodInfo needs constructed then we need to revisit this since we could return a partially initialized MethodInfo s_PendingGenericMethodMap.Add(gmethod, newMethod); newMethod->klass = declaringClass; newMethod->flags = methodDefinition->flags; newMethod->iflags = methodDefinition->iflags; newMethod->slot = methodDefinition->slot; newMethod->name = methodDefinition->name; newMethod->is_generic = false; newMethod->is_inflated = true; newMethod->token = methodDefinition->token; newMethod->return_type = GenericMetadata::InflateIfNeeded(methodDefinition->return_type, &gmethod->context, true); newMethod->parameters_count = methodDefinition->parameters_count; newMethod->parameters = parameters; newMethod->genericMethod = gmethod; if (!gmethod->context.method_inst) { if (methodDefinition->is_generic) newMethod->is_generic = true; if (!declaringClass->generic_class) { newMethod->genericContainerHandle = methodDefinition->genericContainerHandle; } newMethod->methodMetadataHandle = methodDefinition->methodMetadataHandle; } else if (!il2cpp::vm::Runtime::IsLazyRGCTXInflationEnabled() && !il2cpp::metadata::GenericMetadata::ContainsGenericParameters(newMethod)) { // we only need RGCTX for generic instance methods newMethod->rgctx_data = InflateRGCTXLocked(gmethod, lock); } newMethod->virtualMethodPointer = methodPointers.virtualMethodPointer; newMethod->methodPointer = methodPointers.methodPointer; if (methodPointers.methodPointer) { newMethod->invoker_method = methodPointers.invoker_method; } else { newMethod->invoker_method = Runtime::GetMissingMethodInvoker(); il2cpp::vm::Il2CppUnresolvedCallStubs stubs = MetadataCache::GetUnresovledCallStubs(newMethod); newMethod->methodPointer = stubs.methodPointer; newMethod->virtualMethodPointer = stubs.virtualMethodPointer; } newMethod->has_full_generic_sharing_signature = hasFullGenericSharingSignature; ++il2cpp_runtime_stats.inflated_method_count; if (il2cpp::vm::Method::HasFullGenericSharingSignature(newMethod)) { // The method has a full generic sharing signature - that is it a fully shared method an has any fully shared parameter types or return type, // then its signature doesn't match the expected signature // e.g. If List::Insert(T t) is fully shared then for List::Insert(int), the C++ fully shared instance would be List::Insert(void*) and require an int* to be passed in. // So in that case we use the unresolved call stubs to find a matching standard signature to wrap any indirect/virtual calls FullySharedGenericMethodInfo* sharedMethodInfo = reinterpret_cast(newMethod); sharedMethodInfo->rawVirtualMethodPointer = newMethod->virtualMethodPointer; sharedMethodInfo->rawDirectMethodPointer = newMethod->methodPointer; sharedMethodInfo->rawInvokerMethod = newMethod->invoker_method; bool hasAdjustorThunk = newMethod->methodPointer != newMethod->virtualMethodPointer; if (hasAdjustorThunk) newMethod->invoker_method = FullySharedGenericInvokeRedirectHasAdjustorThunk; else newMethod->invoker_method = FullySharedGenericInvokeRedirectNoAdjustorThunk; il2cpp::vm::Il2CppUnresolvedCallStubs stubs = MetadataCache::GetUnresovledCallStubs(newMethod); if (stubs.stubsFound) { newMethod->methodPointer = stubs.methodPointer; newMethod->virtualMethodPointer = stubs.virtualMethodPointer; } else { newMethod->methodPointer = AnUnresolvedCallStubWasNotFound; newMethod->virtualMethodPointer = AnUnresolvedCallStubWasNotFound; if (hasAdjustorThunk) { // The FullySharedGenericInvokeRedirectHasAdjustorThunk requires that methodPointer and virtualMethodPointer be different // so it can tell which raw* method it should call even though it doesn't directly call them IL2CPP_ASSERT(reinterpret_cast(AnUnresolvedCallStubWasNotFoundValueType) != AnUnresolvedCallStubWasNotFound); if (reinterpret_cast(AnUnresolvedCallStubWasNotFoundValueType) != AnUnresolvedCallStubWasNotFound) { newMethod->methodPointer = reinterpret_cast(AnUnresolvedCallStubWasNotFoundValueType); } else { // If we got hit by COMDAT folding (but in DEBUG, which is the most likely way it would happen) // Ensure that are methodPointers are definitely different // We'll get an less specific error message, but it's better than corruption // Make the change on methodPointer because we're most likely to be called newMethod->methodPointer = il2cpp::vm::Method::GetEntryPointNotFoundMethodInfo()->methodPointer; } } } } // If we are a default interface method on a generic instance interface we need to ensure that the interfaces rgctx is inflated if (Method::IsDefaultInterfaceMethodOnGenericInstance(newMethod)) vm::Class::InitLocked(declaringClass, lock); // The generic method is fully created, // Update the generic method map, this needs to take an exclusive lock // **** This must happen with the metadata lock held and be released before the metalock is released **** // **** This prevents deadlocks and ensures that there is no race condition // **** creating a new method adding it to s_GenericMethodMap and removing it from s_PendingGenericMethodMap s_GenericMethodMap.Add(gmethod, newMethod); // Remove the method from the pending table s_PendingGenericMethodMap.Remove(gmethod); return newMethod; } const Il2CppRGCTXData* GenericMethod::InflateRGCTX(const MethodInfo* method) { IL2CPP_ASSERT(method->is_inflated); IL2CPP_ASSERT(method->genericMethod); IL2CPP_ASSERT(method->genericMethod->context.method_inst); return il2cpp::utils::InitOnce(const_cast(&method->rgctx_data), &il2cpp::vm::g_MetadataLock, [method](const il2cpp::os::FastAutoLock& lock) { return const_cast(GenericMethod::InflateRGCTXLocked(method->genericMethod, lock)); }); } const Il2CppRGCTXData* GenericMethod::InflateRGCTXLocked(const Il2CppGenericMethod* gmethod, const il2cpp::os::FastAutoLock &lock) { return GenericMetadata::InflateRGCTXLocked(gmethod->methodDefinition->klass->image, gmethod->methodDefinition->token, &gmethod->context, lock); } const Il2CppGenericContext* GenericMethod::GetContext(const Il2CppGenericMethod* gmethod) { return &gmethod->context; } static std::string FormatGenericArguments(const Il2CppGenericInst* inst) { std::string output; if (inst) { output.append("<"); for (size_t i = 0; i < inst->type_argc; ++i) { if (i != 0) output.append(", "); output.append(Type::GetName(inst->type_argv[i], IL2CPP_TYPE_NAME_FORMAT_FULL_NAME)); } output.append(">"); } return output; } std::string GenericMethod::GetFullName(const Il2CppGenericMethod* gmethod) { const MethodInfo* method = gmethod->methodDefinition; std::string output; output.append(Type::GetName(&gmethod->methodDefinition->klass->byval_arg, IL2CPP_TYPE_NAME_FORMAT_FULL_NAME)); output.append(FormatGenericArguments(gmethod->context.class_inst)); output.append("::"); output.append(Method::GetName(method)); output.append(FormatGenericArguments(gmethod->context.method_inst)); return output; } void GenericMethod::ClearStatics() { s_GenericMethodMap.Clear(); } } /* namespace vm */ } /* namespace il2cpp */