From d5f60bdffa5507359af953bed646d47f89929c18 Mon Sep 17 00:00:00 2001 From: Eugene Zemtsov Date: Wed, 18 Mar 2015 14:40:26 -0700 Subject: [PATCH] Implement runtime support for ICastable interface The goal of this change is to facilitate an alternative (MCG based) way of doing COM interop, we're going to use it on Unix platforms. New ICastable interface allows objects to pretend at runtime that they support an interface and to provide an alternative type that is used to resolve actual calls to interface methods. BE VERY CAREFUL: This is a very dangerous feature, and at this stage it can easily lead to memory corruption without any native code involved. Reviewers: Yi Zhang, Noah Falk, Jan Kotas. DDR clean. [tfs-changeset: 1435198] --- CMakeLists.txt | 10 +- src/debug/daccess/nidump.cpp | 6 +- src/inc/dacvars.h | 4 + src/mscorlib/model.xml | 58 +++--- src/mscorlib/mscorlib.shared.sources.props | 1 + .../System/Runtime/CompilerServices/ICastable.cs | 63 ++++++ src/vm/amd64/asmconstants.h | 8 +- src/vm/appdomain.cpp | 4 + src/vm/i386/asmconstants.h | 7 +- src/vm/jithelpers.cpp | 44 ++++- src/vm/metasig.h | 7 +- src/vm/methodtable.cpp | 56 ++++++ src/vm/methodtable.h | 23 ++- src/vm/methodtablebuilder.cpp | 27 +++ src/vm/mscorlib.h | 8 +- src/vm/prestub.cpp | 29 ++- src/vm/vars.cpp | 5 + src/vm/vars.hpp | 4 + src/vm/virtualcallstub.cpp | 132 +++++++++---- src/vm/virtualcallstub.h | 6 +- tests/src/Interop/ICastable/Castable.cs | 219 +++++++++++++++++++++ tests/src/Interop/ICastable/Castable.csproj | 42 ++++ tests/src/Interop/ICastable/app.config | 7 + tests/src/Interop/ICastable/packages.config | 3 + tests/src/dir.targets | 3 +- 25 files changed, 662 insertions(+), 114 deletions(-) create mode 100644 src/mscorlib/src/System/Runtime/CompilerServices/ICastable.cs create mode 100644 tests/src/Interop/ICastable/Castable.cs create mode 100644 tests/src/Interop/ICastable/Castable.csproj create mode 100644 tests/src/Interop/ICastable/app.config create mode 100644 tests/src/Interop/ICastable/packages.config diff --git a/CMakeLists.txt b/CMakeLists.txt index 217e408..9520068 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -378,7 +378,7 @@ else () endif (IS_64BIT_BUILD EQUAL 1) if(WIN32) -add_definitions(-D_CRT_SECURE_NO_WARNINGS) + add_definitions(-D_CRT_SECURE_NO_WARNINGS) endif(WIN32) add_definitions(-DNTMAKEENV) add_definitions(-D_BLD_CLR) @@ -397,17 +397,19 @@ if (CLR_CMAKE_PLATFORM_UNIX) endif(CLR_CMAKE_PLATFORM_UNIX) add_definitions(-DFEATURE_ASYNC_IO) add_definitions(-DFEATURE_BCL_FORMATTING) -if(WIN32) - add_definitions(-DFEATURE_CLASSIC_COMINTEROP) -endif(WIN32) add_definitions(-DFEATURE_COLLECTIBLE_TYPES) + if(WIN32) + add_definitions(-DFEATURE_CLASSIC_COMINTEROP) add_definitions(-DFEATURE_APPX) add_definitions(-DFEATURE_COMINTEROP) add_definitions(-DFEATURE_COMINTEROP_APARTMENT_SUPPORT) add_definitions(-DFEATURE_COMINTEROP_UNMANAGED_ACTIVATION) add_definitions(-DFEATURE_COMINTEROP_WINRT_MANAGED_ACTIVATION) endif(WIN32) + +add_definitions(-DFEATURE_ICASTABLE) + add_definitions(-DFEATURE_CORECLR) add_definitions(-DFEATURE_CORESYSTEM) add_definitions(-DFEATURE_CORRUPTING_EXCEPTIONS) diff --git a/src/debug/daccess/nidump.cpp b/src/debug/daccess/nidump.cpp index 3d60db3..470643d 100644 --- a/src/debug/daccess/nidump.cpp +++ b/src/debug/daccess/nidump.cpp @@ -5669,7 +5669,7 @@ NativeImageDumper::EnumMnemonics s_MTFlagsLow[] = #if defined(FEATURE_REMOTING) MTFLAG_ENTRY(ContextStatic), #endif - MTFLAG_ENTRY(UNUSED_ComponentSize_2), + MTFLAG_ENTRY(HasRemotingVtsInfo), MTFLAG_ENTRY(HasVariance), MTFLAG_ENTRY(HasDefaultCtor), MTFLAG_ENTRY(HasPreciseInitCctors), @@ -5717,7 +5717,9 @@ NativeImageDumper::EnumMnemonics s_MTFlagsHigh[] = #if defined(FEATURE_COMINTEROP) MTFLAG_ENTRY(IfInterfaceThenHasGuidInfo), #endif - MTFLAG_ENTRY(HasRemotingVtsInfo), +#if defined(FEATURE_ICASTABLE) + MTFLAG_ENTRY(ICastable), +#endif MTFLAG_ENTRY(HasIndirectParent), MTFLAG_ENTRY(ContainsPointers), MTFLAG_ENTRY(HasTypeEquivalence), diff --git a/src/inc/dacvars.h b/src/inc/dacvars.h index 35006f1..560d63a 100644 --- a/src/inc/dacvars.h +++ b/src/inc/dacvars.h @@ -234,6 +234,10 @@ DEFINE_DACVAR(ULONG, UNKNOWN_POINTER_TYPE, dac__g_pBaseCOMObject, ::g_pBaseCOMOb DEFINE_DACVAR(ULONG, UNKNOWN_POINTER_TYPE, dac__g_pBaseRuntimeClass, ::g_pBaseRuntimeClass) #endif +#ifdef FEATURE_ICASTABLE +DEFINE_DACVAR(ULONG, UNKNOWN_POINTER_TYPE, dac__g_pICastableInterface, ::g_pICastableInterface) +#endif // FEATURE_ICASTABLE + DEFINE_DACVAR(ULONG, UNKNOWN_POINTER_TYPE, dac__g_pPrepareConstrainedRegionsMethod, ::g_pPrepareConstrainedRegionsMethod) DEFINE_DACVAR(ULONG, UNKNOWN_POINTER_TYPE, dac__g_pExecuteBackoutCodeHelperMethod, ::g_pExecuteBackoutCodeHelperMethod) diff --git a/src/mscorlib/model.xml b/src/mscorlib/model.xml index d3ae026..63fed69 100644 --- a/src/mscorlib/model.xml +++ b/src/mscorlib/model.xml @@ -1007,7 +1007,7 @@ - + @@ -2039,7 +2039,7 @@ - + @@ -5750,6 +5750,10 @@ + + + + @@ -9772,19 +9776,19 @@ - - + + - - - - - - + + + + + + @@ -9810,7 +9814,7 @@ - + @@ -9849,7 +9853,7 @@ - + @@ -9874,11 +9878,11 @@ - - - - - + + + + + @@ -9889,7 +9893,7 @@ - + @@ -9927,7 +9931,7 @@ - + @@ -9979,8 +9983,8 @@ - - + + @@ -9995,10 +9999,10 @@ - + - + @@ -11667,10 +11671,10 @@ - - - - + + + + diff --git a/src/mscorlib/mscorlib.shared.sources.props b/src/mscorlib/mscorlib.shared.sources.props index e94fa2f..e75b0df 100644 --- a/src/mscorlib/mscorlib.shared.sources.props +++ b/src/mscorlib/mscorlib.shared.sources.props @@ -41,6 +41,7 @@ + diff --git a/src/mscorlib/src/System/Runtime/CompilerServices/ICastable.cs b/src/mscorlib/src/System/Runtime/CompilerServices/ICastable.cs new file mode 100644 index 0000000..bbc53e5 --- /dev/null +++ b/src/mscorlib/src/System/Runtime/CompilerServices/ICastable.cs @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + + + +// +// Support for dynamic interface casting. Specifically implementing this interface on a type will allow the +// type to support interfaces (for the purposes of casting and interface dispatch) that do not appear in its +// interface map. +// + +using System; + +namespace System.Runtime.CompilerServices +{ + public interface ICastable + { + // This is called if casting this object to the given interface type would otherwise fail. Casting + // here means the IL isinst and castclass instructions in the case where they are given an interface + // type as the target type. + // + // A return value of true indicates the cast is valid. + // + // If false is returned when this is called as part of a castclass then the usual InvalidCastException + // will be thrown unless an alternate exception is assigned to the castError output parameter. This + // parameter is ignored on successful casts or during the evaluation of an isinst (which returns null + // rather than throwing on error). + // + // No exception should be thrown from this method (it will cause unpredictable effects, including the + // possibility of an immediate failfast). + // + // The results of this call are not cached, so it is advisable to provide a performant implementation. + // + // The results of this call should be invariant for the same class, interface type pair. That is + // because this is the only guard placed before an interface invocation at runtime. If a type decides + // it no longer wants to implement a given interface it has no way to synchronize with callers that + // have already cached this relationship and can invoke directly via the interface pointer. + bool IsInstanceOfInterface(RuntimeTypeHandle interfaceType, out Exception castError); + + // This is called as part of the interface dispatch mechanism when the dispatcher logic cannot find + // the given interface type in the interface map of this object. + // + // It allows the implementor to return an alternate class type which does implement the interface. The + // interface lookup shall be performed again on this type (failure to find the interface this time + // resulting in a fail fast) and the corresponding implemented method on that class called instead. + // + // Naturally, since the call is dispatched to a method on a class which does not match the type of the + // this pointer, extreme care must be taken in the implementation of the interface methods of this + // surrogate type. + // + // No exception should be thrown from this method (it will cause unpredictable effects, including the + // possibility of an immediate failfast). + // + // There is no error path defined here. By construction all interface dispatches will already have + // been verified via the castclass/isinst mechanism (and thus a call to IsInstanceOfInterface above) + // so this method is expected to succeed in all cases. The contract for interface dispatch does not + // include any errors from the infrastructure, of which this is a part. + // + // The results of this lookup are cached so computation of the result is not as perf-sensitive as + // IsInstanceOfInterface. + RuntimeTypeHandle GetImplType(RuntimeTypeHandle interfaceType); + } +} diff --git a/src/vm/amd64/asmconstants.h b/src/vm/amd64/asmconstants.h index ed2e284..e356c9b 100644 --- a/src/vm/amd64/asmconstants.h +++ b/src/vm/amd64/asmconstants.h @@ -290,15 +290,9 @@ ASMCONSTANTS_C_ASSERT(METHODTABLE_EQUIVALENCE_FLAGS #define METHODTABLE_EQUIVALENCE_FLAGS 0x0 #endif -#ifdef FEATURE_COMINTEROP -#define METHODTABLE_NONTRIVIALINTERFACECAST_FLAGS 0x40080000 +#define METHODTABLE_NONTRIVIALINTERFACECAST_FLAGS (0x00080000 + 0x40000000 + 0x00400000) ASMCONSTANTS_C_ASSERT(METHODTABLE_NONTRIVIALINTERFACECAST_FLAGS == MethodTable::enum_flag_NonTrivialInterfaceCast); -#else -#define METHODTABLE_NONTRIVIALINTERFACECAST_FLAGS 0x00080000 -ASMCONSTANTS_C_ASSERT(METHODTABLE_NONTRIVIALINTERFACECAST_FLAGS - == MethodTable::enum_flag_NonTrivialInterfaceCast); -#endif #define MethodTable__enum_flag_ContainsPointers 0x01000000 ASMCONSTANTS_C_ASSERT(MethodTable__enum_flag_ContainsPointers diff --git a/src/vm/appdomain.cpp b/src/vm/appdomain.cpp index de34384..8e84eb0 100644 --- a/src/vm/appdomain.cpp +++ b/src/vm/appdomain.cpp @@ -2979,6 +2979,10 @@ void SystemDomain::LoadBaseSystemClasses() #endif // _DEBUG #endif +#ifdef FEATURE_ICASTABLE + g_pICastableInterface = MscorlibBinder::GetClass(CLASS__ICASTABLE); +#endif // FEATURE_ICASTABLE + // Load a special marker method used to detect Constrained Execution Regions // at jit time. g_pPrepareConstrainedRegionsMethod = MscorlibBinder::GetMethod(METHOD__RUNTIME_HELPERS__PREPARE_CONSTRAINED_REGIONS); diff --git a/src/vm/i386/asmconstants.h b/src/vm/i386/asmconstants.h index 748569a..530f9da 100644 --- a/src/vm/i386/asmconstants.h +++ b/src/vm/i386/asmconstants.h @@ -418,13 +418,8 @@ ASMCONSTANTS_C_ASSERT(ComPlusCallInfo__m_pRetThunk == offsetof(ComPlusCallInfo, #endif // FEATURE_COMINTEROP -#ifdef FEATURE_COMINTEROP -#define NonTrivialInterfaceCastFlags 0x40080000 -ASMCONSTANTS_C_ASSERT(NonTrivialInterfaceCastFlags == MethodTable::public_enum_flag_NonTrivialInterfaceCast) -#else -#define NonTrivialInterfaceCastFlags 0x00080000 +#define NonTrivialInterfaceCastFlags (0x00080000 + 0x40000000 + 0x00400000) ASMCONSTANTS_C_ASSERT(NonTrivialInterfaceCastFlags == MethodTable::public_enum_flag_NonTrivialInterfaceCast) -#endif #define ASM__VTABLE_SLOTS_PER_CHUNK 8 ASMCONSTANTS_C_ASSERT(ASM__VTABLE_SLOTS_PER_CHUNK == VTABLE_SLOTS_PER_CHUNK) diff --git a/src/vm/jithelpers.cpp b/src/vm/jithelpers.cpp index 71a36b0..d93228d 100644 --- a/src/vm/jithelpers.cpp +++ b/src/vm/jithelpers.cpp @@ -2360,8 +2360,11 @@ TypeHandle::CastResult STDCALL ObjIsInstanceOfNoGC(Object *pObject, TypeHandle t return TypeHandle::CanCast; if (pMT->IsTransparentProxy() || - pMT->IsComObjectType() && toTypeHnd.IsInterface()) + (toTypeHnd.IsInterface() && ( pMT->IsComObjectType() || pMT->IsICastable() )) + ) + { return TypeHandle::MaybeCast; + } if (pMT->IsArray()) { @@ -2439,6 +2442,35 @@ BOOL ObjIsInstanceOf(Object *pObject, TypeHandle toTypeHnd) // allow an object of type T to be cast to Nullable (they have the same representation) fCast = TRUE; } +#ifdef FEATURE_ICASTABLE + // If type implements ICastable interface we give it a chance to tell us if it can be casted + // to a given type. + else if (toTypeHnd.IsInterface() && fromTypeHnd.GetMethodTable()->IsICastable()) + { + // Make actuall call to obj.IsInstanceOfInterface(interfaceTypeObj, out exception) + OBJECTREF exception = NULL; + GCPROTECT_BEGIN(exception); + MethodTable *pFromTypeMT = fromTypeHnd.GetMethodTable(); + MethodDesc *pIsInstanceOfMD = pFromTypeMT->GetMethodDescForInterfaceMethod(MscorlibBinder::GetMethod(METHOD__ICASTABLE__ISINSTANCEOF)); //GC triggers + OBJECTREF managedType = toTypeHnd.GetManagedClassObject(); //GC triggers + + PREPARE_NONVIRTUAL_CALLSITE_USING_METHODDESC(pIsInstanceOfMD); + + DECLARE_ARGHOLDER_ARRAY(args, 3); + args[ARGNUM_0] = OBJECTREF_TO_ARGHOLDER(obj); + args[ARGNUM_1] = OBJECTREF_TO_ARGHOLDER(managedType); + args[ARGNUM_2] = PTR_TO_ARGHOLDER(&exception); + + CALL_MANAGED_METHOD(fCast, BOOL, args); + INDEBUG(managedType = NULL); // managedType isn't protected during the call + + if (exception != NULL) + { + RealCOMPlusThrow(exception); + } + GCPROTECT_END(); //exception + } +#endif // FEATURE_ICASTABLE GCPROTECT_END(); @@ -5033,12 +5065,12 @@ HCIMPL1(void, IL_Throw, Object* obj) // then do not clear the "_stackTrace" field of the exception object. if (GetThread()->GetExceptionState()->IsRaisingForeignException()) { - ((EXCEPTIONREF)oref)->SetStackTraceString(NULL); + ((EXCEPTIONREF)oref)->SetStackTraceString(NULL); } else #endif // defined(FEATURE_EXCEPTIONDISPATCHINFO) { - ((EXCEPTIONREF)oref)->ClearStackTracePreservingRemoteStackTrace(); + ((EXCEPTIONREF)oref)->ClearStackTracePreservingRemoteStackTrace(); } } @@ -5297,9 +5329,9 @@ void DoJITFailFast () #if defined(_TARGET_X86_) __report_gsfailure(); #else // !defined(_TARGET_X86_) - // On AMD64/IA64/ARM, we need to pass a stack cookie, which will be saved in the context record - // that is used to raise the buffer-overrun exception by __report_gsfailure. - __report_gsfailure((ULONG_PTR)0); + // On AMD64/IA64/ARM, we need to pass a stack cookie, which will be saved in the context record + // that is used to raise the buffer-overrun exception by __report_gsfailure. + __report_gsfailure((ULONG_PTR)0); #endif // defined(_TARGET_X86_) #else // FEATURE_PAL if(ETW_EVENT_ENABLED(MICROSOFT_WINDOWS_DOTNETRUNTIME_PRIVATE_PROVIDER_Context, FailFast)) diff --git a/src/vm/metasig.h b/src/vm/metasig.h index 03f7f50..6baf098 100644 --- a/src/vm/metasig.h +++ b/src/vm/metasig.h @@ -347,9 +347,9 @@ DEFINE_METASIG_T(SM(FrameSecurityDescriptor_PMS_OutPMS_RuntimeMethodHandleIntern C(FRAME_SECURITY_DESCRIPTOR) C(PERMISSION_SET) r(C(PERMISSION_SET)) g(METHOD_HANDLE_INTERNAL), F)) DEFINE_METASIG_T(SM(FrameSecurityDescriptor_RetInt, C(FRAME_SECURITY_DESCRIPTOR), i)) DEFINE_METASIG_T(SM(DynamicResolver_IPermission_PermissionToken_RuntimeMethodHandleInternal_RetBool, \ - C(DYNAMICRESOLVER) C(IPERMISSION) C(PERMISSION_TOKEN) g(METHOD_HANDLE_INTERNAL), F)) + C(DYNAMICRESOLVER) C(IPERMISSION) C(PERMISSION_TOKEN) g(METHOD_HANDLE_INTERNAL), F)) DEFINE_METASIG_T(SM(DynamicResolver_PMS_OutPMS_RuntimeMethodHandleInternal_RetBool, \ - C(DYNAMICRESOLVER) C(PERMISSION_SET) r(C(PERMISSION_SET)) g(METHOD_HANDLE_INTERNAL), F)) + C(DYNAMICRESOLVER) C(PERMISSION_SET) r(C(PERMISSION_SET)) g(METHOD_HANDLE_INTERNAL), F)) DEFINE_METASIG_T(SM(PermissionListSet_PMS_PMS_RetPermissionListSet, \ C(PERMISSION_LIST_SET) C(PERMISSION_SET) C(PERMISSION_SET), C(PERMISSION_LIST_SET))) DEFINE_METASIG_T(SM(PMS_IntPtr_RuntimeMethodHandleInternal_Assembly_SecurityAction_RetVoid, C(PERMISSION_SET) I g(METHOD_HANDLE_INTERNAL) C(ASSEMBLY) g(SECURITY_ACTION), v)) @@ -675,6 +675,9 @@ DEFINE_METASIG(SM(RefObject_Object_Object_RetObject, r(j) j j, j)) DEFINE_METASIG_T(SM(RefCleanupWorkList_RetVoid, r(C(CLEANUP_WORK_LIST)), v)) DEFINE_METASIG_T(SM(RefCleanupWorkList_SafeHandle_RetIntPtr, r(C(CLEANUP_WORK_LIST)) C(SAFE_HANDLE), I)) +DEFINE_METASIG_T(IM(RuntimeTypeHandle_RefException_RetBool, g(RT_TYPE_HANDLE) r(C(EXCEPTION)), F)) +DEFINE_METASIG_T(IM(RuntimeTypeHandle_RetRuntimeTypeHandle, g(RT_TYPE_HANDLE), g(RT_TYPE_HANDLE))) + // Undefine macros in case we include the file again in the compilation unit #undef DEFINE_METASIG diff --git a/src/vm/methodtable.cpp b/src/vm/methodtable.cpp index 0d95a9c..644b9e6 100644 --- a/src/vm/methodtable.cpp +++ b/src/vm/methodtable.cpp @@ -633,6 +633,25 @@ void MethodTable::SetHasTypeEquivalence() } #endif +#ifdef FEATURE_ICASTABLE +void MethodTable::SetICastable() +{ + LIMITED_METHOD_CONTRACT; + SetFlag(enum_flag_ICastable); +} +#endif + +BOOL MethodTable::IsICastable() +{ + LIMITED_METHOD_DAC_CONTRACT; +#ifdef FEATURE_ICASTABLE + return GetFlag(enum_flag_ICastable); +#else + return FALSE; +#endif +} + + #endif // !DACCESS_COMPILE //========================================================================================== @@ -776,6 +795,43 @@ PTR_MethodTable InterfaceInfo_t::GetApproxMethodTable(Module * pContainingModule RETURN(pItfMD); } +#ifdef FEATURE_ICASTABLE + // In case of ICastable, instead of trying to find method implementation in the real object type + // we call pObj.GetValueInternal() and call GetMethodDescForInterfaceMethod() again with whatever type it returns. + // It allows objects that implement ICastable to mimic behavior of other types. + if (pServerMT->IsICastable() && + !pItfMD->HasMethodInstantiation() && + !TypeHandle(pServerMT).CanCastTo(ownerType)) // we need to make sure object doesn't implement this interface in a natural way + { + GCStress::MaybeTrigger(); + + // Make call to obj.GetImplType(interfaceTypeObj) + MethodDesc *pGetImplTypeMD = pServerMT->GetMethodDescForInterfaceMethod(MscorlibBinder::GetMethod(METHOD__ICASTABLE__GETIMPLTYPE)); + OBJECTREF ownerManagedType = ownerType.GetManagedClassObject(); //GC triggers + + PREPARE_NONVIRTUAL_CALLSITE_USING_METHODDESC(pGetImplTypeMD); + + DECLARE_ARGHOLDER_ARRAY(args, 2); + args[ARGNUM_0] = OBJECTREF_TO_ARGHOLDER(*pServer); + args[ARGNUM_1] = OBJECTREF_TO_ARGHOLDER(ownerManagedType); + + OBJECTREF impTypeObj = NULL; + CALL_MANAGED_METHOD_RETREF(impTypeObj, OBJECTREF, args); + + INDEBUG(ownerManagedType = NULL); //ownerManagedType wasn't protected during the call + if (impTypeObj == NULL) // GetImplType returns default(RuntimeTypeHandle) + { + COMPlusThrow(kEntryPointNotFoundException); + } + + ReflectClassBaseObject* resultTypeObj = ((ReflectClassBaseObject*)OBJECTREFToObject(impTypeObj)); + TypeHandle resulTypeHnd = resultTypeObj->GetType(); + MethodTable *pResultMT = resulTypeHnd.GetMethodTable(); + + RETURN(pResultMT->GetMethodDescForInterfaceMethod(ownerType, pItfMD)); + } +#endif + #ifdef FEATURE_COMINTEROP if (pServerMT->IsComObjectType() && !pItfMD->HasMethodInstantiation()) { diff --git a/src/vm/methodtable.h b/src/vm/methodtable.h index 8a31213..8e6a59b 100644 --- a/src/vm/methodtable.h +++ b/src/vm/methodtable.h @@ -856,6 +856,12 @@ public: } #endif // !FEATURE_COMINTEROP +#ifdef FEATURE_ICASTABLE + void SetICastable(); +#endif + + BOOL IsICastable(); // This type implements ICastable interface + #ifdef FEATURE_TYPEEQUIVALENCE // type has opted into type equivalence or is instantiated by/derived from a type that is BOOL HasTypeEquivalence() @@ -3762,7 +3768,7 @@ private: #ifdef FEATURE_REMOTING enum_flag_ContextStatic = 0x00000040, #endif - enum_flag_UNUSED_ComponentSize_2 = 0x00000080, + enum_flag_HasRemotingVtsInfo = 0x00000080, // Optional data present indicating VTS methods and optional fields enum_flag_HasVariance = 0x00000100, // This is an instantiated type some of whose type parameters are co or contra-variant @@ -3843,7 +3849,7 @@ private: enum_flag_IfInterfaceThenHasGuidInfo = 0x00200000, // Does the type has optional GuidInfo #endif // FEATURE_COMINTEROP - enum_flag_HasRemotingVtsInfo = 0x00400000, // Optional data present indicating VTS methods and optional fields + enum_flag_ICastable = 0x00400000, // class implements ICastable interface enum_flag_HasIndirectParent = 0x00800000, // m_pParentMethodTable has double indirection @@ -3859,18 +3865,15 @@ private: enum_flag_Collectible = 0x10000000, enum_flag_ContainsGenericVariables = 0x20000000, // we cache this flag to help detect these efficiently and // to detect this condition when restoring -#ifdef FEATURE_COMINTEROP - enum_flag_ComObject = 0x40000000, // class is a com object -#endif + enum_flag_ComObject = 0x40000000, // class is a com object + enum_flag_HasComponentSize = 0x80000000, // This is set if component size is used for flags. -#ifdef FEATURE_COMINTEROP // Types that require non-trivial interface cast have this bit set in the category - enum_flag_NonTrivialInterfaceCast = 0x00080000 | enum_flag_ComObject, -#else - enum_flag_NonTrivialInterfaceCast = 0x00080000, -#endif + enum_flag_NonTrivialInterfaceCast = enum_flag_Category_Array + | enum_flag_ComObject + | enum_flag_ICastable }; // enum WFLAGS_HIGH_ENUM diff --git a/src/vm/methodtablebuilder.cpp b/src/vm/methodtablebuilder.cpp index 949bba6..b64772e 100644 --- a/src/vm/methodtablebuilder.cpp +++ b/src/vm/methodtablebuilder.cpp @@ -2116,6 +2116,13 @@ MethodTableBuilder::BuildMethodTableThrowing( } } +#ifdef FEATURE_ICASTABLE + if (!IsValueClass() && g_pICastableInterface != NULL && pMT->CanCastToInterface(g_pICastableInterface)) + { + pMT->SetICastable(); + } +#endif // FEATURE_ICASTABLE + // Grow the typedef ridmap in advance as we can't afford to // fail once we set the resolve bit pModule->EnsureTypeDefCanBeStored(bmtInternal->pType->GetTypeDefToken()); @@ -10066,6 +10073,26 @@ VOID MethodTableBuilder::ScanTypeForVtsInfo() } CONTRACTL_END; + // + // Do not mark System.String as needing vts info. The MethodTable bit used for VtsInfo + // is used for other purpose on System.String, and System.String does need VtsInfo anyway + // because of it is special-cased by the object cloner. + // + if (g_pStringClass == NULL) + { + LPCUTF8 name, nameSpace; + + if (FAILED(GetMDImport()->GetNameOfTypeDef(GetCl(), &name, &nameSpace))) + { + BuildMethodTableThrowException(IDS_CLASSLOAD_BADFORMAT); + } + + if (strcmp(name, g_StringName) == 0 && strcmp(nameSpace, g_SystemNS) == 0) + { + return; + } + } + DWORD i; // Scan all the non-virtual, non-abstract, non-generic instance methods for // one of the special custom attributes indicating a VTS event method. diff --git a/src/vm/mscorlib.h b/src/vm/mscorlib.h index 7bbc9c2..149d358 100644 --- a/src/vm/mscorlib.h +++ b/src/vm/mscorlib.h @@ -2188,12 +2188,18 @@ DEFINE_CLASS(FRAME_SECURITY_DESCRIPTOR_WITH_RESOLVER, Security, FrameSecurityDes #endif // FEATURE_COMPRESSEDSTACK #ifdef FEATURE_COMINTEROP -DEFINE_CLASS(ASYNC_TRACING_EVENT_ARGS, WindowsFoundationDiag, TracingStatusChangedEventArgs) +DEFINE_CLASS(ASYNC_TRACING_EVENT_ARGS, WindowsFoundationDiag, TracingStatusChangedEventArgs) DEFINE_CLASS(IASYNC_TRACING_EVENT_ARGS, WindowsFoundationDiag, ITracingStatusChangedEventArgs) #endif // FEATURE_COMINTEROP DEFINE_CLASS(MODULEBASE, Reflection, Module) +#ifdef FEATURE_ICASTABLE +DEFINE_CLASS(ICASTABLE, CompilerServices, ICastable) +DEFINE_METHOD(ICASTABLE, ISINSTANCEOF, IsInstanceOfInterface, IM_RuntimeTypeHandle_RefException_RetBool) +DEFINE_METHOD(ICASTABLE, GETIMPLTYPE, GetImplType, IM_RuntimeTypeHandle_RetRuntimeTypeHandle) +#endif // FEATURE_ICASTABLE + #undef DEFINE_CLASS #undef DEFINE_METHOD #undef DEFINE_FIELD diff --git a/src/vm/prestub.cpp b/src/vm/prestub.cpp index a2e6fd3..79c34d2 100644 --- a/src/vm/prestub.cpp +++ b/src/vm/prestub.cpp @@ -984,6 +984,26 @@ extern "C" PCODE STDCALL PreStubWorker(TransitionBlock * pTransitionBlock, Metho { pDispatchingMT = curobj->GetTrueMethodTable(); +#ifdef FEATURE_ICASTABLE + if (pDispatchingMT->IsICastable()) + { + MethodTable *pMDMT = pMD->GetMethodTable(); + TypeHandle objectType(pDispatchingMT); + TypeHandle methodType(pMDMT); + + GCStress::MaybeTrigger(); + INDEBUG(curobj = NULL); // curobj is unprotected and CanCastTo() can trigger GC + if (!objectType.CanCastTo(methodType)) + { + // Apperantly ICastable magic was involved when we chose this method to be called + // that's why we better stick to the MethodTable it belongs to, otherwise + // DoPrestub() will fail not being able to find implementation for pMD in pDispatchingMT. + + pDispatchingMT = pMDMT; + } + } +#endif // FEATURE_ICASTABLE + // For value types, the only virtual methods are interface implementations. // Thus pDispatching == pMT because there // is no inheritance in value types. Note the BoxedEntryPointStubs are shared @@ -1958,13 +1978,14 @@ EXTERN_C PCODE STDCALL ExternalMethodFixupWorker(TransitionBlock * pTransitionBl else token = DispatchToken::CreateDispatchToken(slot); - OBJECTREF pObj = pEMFrame->GetThis(); - if (pObj == NULL) { + OBJECTREF *protectedObj = pEMFrame->GetThisPtr(); + _ASSERTE(protectedObj != NULL); + if (*protectedObj == NULL) { COMPlusThrow(kNullReferenceException); } - + StubCallSite callSite(pIndirection, pEMFrame->GetReturnAddress()); - pCode = pMgr->ResolveWorker(&callSite, pObj, token, VirtualCallStubManager::SK_LOOKUP); + pCode = pMgr->ResolveWorker(&callSite, protectedObj, token, VirtualCallStubManager::SK_LOOKUP); _ASSERTE(pCode != NULL); } else diff --git a/src/vm/vars.cpp b/src/vm/vars.cpp index 5275a92..21cafef 100644 --- a/src/vm/vars.cpp +++ b/src/vm/vars.cpp @@ -94,6 +94,11 @@ GPTR_IMPL(MethodTable, g_pBaseCOMObject); GPTR_IMPL(MethodTable, g_pBaseRuntimeClass); #endif +#ifdef FEATURE_ICASTABLE +GPTR_IMPL(MethodTable, g_pICastableInterface); +#endif // FEATURE_ICASTABLE + + GPTR_IMPL(MethodDesc, g_pPrepareConstrainedRegionsMethod); GPTR_IMPL(MethodDesc, g_pExecuteBackoutCodeHelperMethod); diff --git a/src/vm/vars.hpp b/src/vm/vars.hpp index c212885..cfb4a93 100644 --- a/src/vm/vars.hpp +++ b/src/vm/vars.hpp @@ -432,6 +432,10 @@ GPTR_DECL(MethodTable, g_pBaseCOMObject); GPTR_DECL(MethodTable, g_pBaseRuntimeClass); #endif +#ifdef FEATURE_ICASTABLE +GPTR_DECL(MethodTable, g_pICastableInterface); +#endif // FEATURE_ICASTABLE + GPTR_DECL(MethodDesc, g_pPrepareConstrainedRegionsMethod); GPTR_DECL(MethodDesc, g_pExecuteBackoutCodeHelperMethod); diff --git a/src/vm/virtualcallstub.cpp b/src/vm/virtualcallstub.cpp index d1ad6ec..c234e8c 100644 --- a/src/vm/virtualcallstub.cpp +++ b/src/vm/virtualcallstub.cpp @@ -1204,6 +1204,10 @@ extern "C" PCODE STDCALL StubDispatchFixupWorker(TransitionBlock * pTransitionBl MAKE_CURRENT_THREAD_AVAILABLE(); +#ifdef _DEBUG + Thread::ObjectRefFlush(CURRENT_THREAD); +#endif + FrameWithCookie frame(pTransitionBlock); StubDispatchFrame * pSDFrame = &frame; @@ -1280,12 +1284,13 @@ extern "C" PCODE STDCALL StubDispatchFixupWorker(TransitionBlock * pTransitionBl else token = DispatchToken::CreateDispatchToken(slot); - OBJECTREF pObj = pSDFrame->GetThis(); - if (pObj == NULL) { + OBJECTREF *protectedObj = pSDFrame->GetThisPtr(); + _ASSERTE(protectedObj != NULL); + if (*protectedObj == NULL) { COMPlusThrow(kNullReferenceException); } - pTarget = pMgr->ResolveWorker(&callSite, pObj, token, VirtualCallStubManager::SK_LOOKUP); + pTarget = pMgr->ResolveWorker(&callSite, protectedObj, token, VirtualCallStubManager::SK_LOOKUP); _ASSERTE(pTarget != NULL); // Ready to return @@ -1509,6 +1514,10 @@ PCODE VSD_ResolveWorker(TransitionBlock * pTransitionBlock, MAKE_CURRENT_THREAD_AVAILABLE(); +#ifdef _DEBUG + Thread::ObjectRefFlush(CURRENT_THREAD); +#endif + FrameWithCookie frame(pTransitionBlock); StubDispatchFrame * pSDFrame = &frame; @@ -1516,7 +1525,9 @@ PCODE VSD_ResolveWorker(TransitionBlock * pTransitionBlock, StubCallSite callSite(siteAddrForRegisterIndirect, returnAddress); - OBJECTREF pObj = pSDFrame->GetThis(); + OBJECTREF *protectedObj = pSDFrame->GetThisPtr(); + _ASSERTE(protectedObj != NULL); + OBJECTREF pObj = *protectedObj; PCODE target = NULL; @@ -1588,7 +1599,7 @@ PCODE VSD_ResolveWorker(TransitionBlock * pTransitionBlock, } #endif - target = pMgr->ResolveWorker(&callSite, pObj, token, stubKind); + target = pMgr->ResolveWorker(&callSite, protectedObj, token, stubKind); GCPROTECT_END(); @@ -1624,7 +1635,7 @@ void VirtualCallStubManager::BackPatchWorkerStatic(PCODE returnAddress, TADDR si } PCODE VirtualCallStubManager::ResolveWorker(StubCallSite* pCallSite, - OBJECTREF pObj, + OBJECTREF *protectedObj, DispatchToken token, StubKind stubKind) { @@ -1633,15 +1644,14 @@ PCODE VirtualCallStubManager::ResolveWorker(StubCallSite* pCallSite, GC_TRIGGERS; MODE_COOPERATIVE; INJECT_FAULT(COMPlusThrowOM();); - PRECONDITION(pObj != NULL); + PRECONDITION(protectedObj != NULL); + PRECONDITION(*protectedObj != NULL); + PRECONDITION(IsProtectedByGCFrame(protectedObj)); } CONTRACTL_END; - MethodTable* objectType = pObj->GetMethodTable(); + MethodTable* objectType = (*protectedObj)->GetMethodTable(); CONSISTENCY_CHECK(CheckPointer(objectType)); - // pObj is not protected. Clear it in debug builds to avoid accidental use. - INDEBUG(pObj = NULL); - #ifdef STUB_LOGGING if (g_dumpLogCounter != 0) { @@ -1772,11 +1782,12 @@ PCODE VirtualCallStubManager::ResolveWorker(StubCallSite* pCallSite, if (target == NULL) { CONSISTENCY_CHECK(stub == CALL_STUB_EMPTY_ENTRY); - patch = Resolver(objectType, token, &target); + patch = Resolver(objectType, token, protectedObj, &target); #if defined(_DEBUG) - if (!objectType->IsTransparentProxy() && - !objectType->IsComObjectType()) + if ( !objectType->IsTransparentProxy() && + !objectType->IsComObjectType() && + !objectType->IsICastable()) { CONSISTENCY_CHECK(!MethodTable::GetMethodDescForSlotAddress(target)->IsGenericMethodDefinition()); } @@ -2065,7 +2076,8 @@ it means that the token is not resolvable. BOOL VirtualCallStubManager::Resolver( MethodTable * pMT, - DispatchToken token, + DispatchToken token, + OBJECTREF * protectedObj, // this one can actually be NULL, consider using pMT is you don't need the object itself PCODE * ppTarget) { CONTRACTL { @@ -2215,40 +2227,73 @@ VirtualCallStubManager::Resolver( } } #ifdef FEATURE_COMINTEROP - else if (IsInterfaceToken(token)) + else if (pMT->IsComObjectType() && IsInterfaceToken(token)) { - if (pMT->IsComObjectType()) - { - MethodTable * pItfMT = GetTypeFromToken(token); - implSlot = pItfMT->FindDispatchSlot(token.GetSlotNumber()); + MethodTable * pItfMT = GetTypeFromToken(token); + implSlot = pItfMT->FindDispatchSlot(token.GetSlotNumber()); - if (pItfMT->HasInstantiation()) + if (pItfMT->HasInstantiation()) + { + DispatchSlot ds(implSlot); + MethodDesc * pTargetMD = ds.GetMethodDesc(); + if (!pTargetMD->HasMethodInstantiation()) { - DispatchSlot ds(implSlot); - MethodDesc * pTargetMD = ds.GetMethodDesc(); - if (!pTargetMD->HasMethodInstantiation()) - { - _ASSERTE(pItfMT->IsProjectedFromWinRT() || pItfMT->IsWinRTRedirectedInterface(TypeHandle::Interop_ManagedToNative)); + _ASSERTE(pItfMT->IsProjectedFromWinRT() || pItfMT->IsWinRTRedirectedInterface(TypeHandle::Interop_ManagedToNative)); - MethodDesc *pInstMD = MethodDesc::FindOrCreateAssociatedMethodDesc( - pTargetMD, - pItfMT, - FALSE, // forceBoxedEntryPoint - Instantiation(), // methodInst - FALSE, // allowInstParam - TRUE); // forceRemotableMethod + MethodDesc *pInstMD = MethodDesc::FindOrCreateAssociatedMethodDesc( + pTargetMD, + pItfMT, + FALSE, // forceBoxedEntryPoint + Instantiation(), // methodInst + FALSE, // allowInstParam + TRUE); // forceRemotableMethod - _ASSERTE(pInstMD->IsComPlusCall() || pInstMD->IsGenericComPlusCall()); + _ASSERTE(pInstMD->IsComPlusCall() || pInstMD->IsGenericComPlusCall()); - *ppTarget = pInstMD->GetStableEntryPoint(); - return TRUE; - } + *ppTarget = pInstMD->GetStableEntryPoint(); + return TRUE; } - - fShouldPatch = TRUE; } + + fShouldPatch = TRUE; } #endif // FEATURE_COMINTEROP +#ifdef FEATURE_ICASTABLE + else if (pMT->IsICastable() && protectedObj != NULL && *protectedObj != NULL) + { + GCStress::MaybeTrigger(); + + // In case of ICastable, instead of trying to find method implementation in the real object type + // we call pObj.GetValueInternal() and call Resolver() again with whatever type it returns. + // It allows objects that implement ICastable to mimic behavior of other types. + MethodTable * pTokenMT = GetTypeFromToken(token); + + // Make call to obj.GetImplType(interfaceTypeObj) + MethodDesc *pGetImplTypeMD = pMT->GetMethodDescForInterfaceMethod(MscorlibBinder::GetMethod(METHOD__ICASTABLE__GETIMPLTYPE)); + OBJECTREF tokenManagedType = pTokenMT->GetManagedClassObject(); //GC triggers + + PREPARE_NONVIRTUAL_CALLSITE_USING_METHODDESC(pGetImplTypeMD); + + DECLARE_ARGHOLDER_ARRAY(args, 2); + args[ARGNUM_0] = OBJECTREF_TO_ARGHOLDER(*protectedObj); + args[ARGNUM_1] = OBJECTREF_TO_ARGHOLDER(tokenManagedType); + + OBJECTREF impTypeObj = NULL; + CALL_MANAGED_METHOD_RETREF(impTypeObj, OBJECTREF, args); + + INDEBUG(tokenManagedType = NULL); //tokenManagedType wasn't protected during the call + if (impTypeObj == NULL) // GetImplType returns default(RuntimeTypeHandle) + { + COMPlusThrow(kEntryPointNotFoundException); + } + + ReflectClassBaseObject* resultTypeObj = ((ReflectClassBaseObject*)OBJECTREFToObject(impTypeObj)); + TypeHandle resulTypeHnd = resultTypeObj->GetType(); + MethodTable *pResultMT = resulTypeHnd.GetMethodTable(); + + return Resolver(pResultMT, token, protectedObj, ppTarget); + } +#endif // FEATURE_ICASTABLE if (implSlot.IsNull()) { @@ -2586,7 +2631,10 @@ VirtualCallStubManager::GetTarget( // No match, now do full resolve BOOL fPatch; - fPatch = Resolver(pMT, token, &target); + + // TODO: passing NULL as protectedObj here can lead to incorrect behavior for ICastable objects + // We need to review if this is the case and refactor this code if we want ICastable to become officially supported + fPatch = Resolver(pMT, token, NULL, &target); _ASSERTE(target != NULL); #ifndef STUB_DISPATCH_PORTABLE @@ -4162,7 +4210,9 @@ MethodDesc *VirtualCallStubManagerManager::Entry2MethodDesc( CONSISTENCY_CHECK(!pMT->IsTransparentProxy()); PCODE target = NULL; - VirtualCallStubManager::Resolver(pMT, token, &target); + // TODO: passing NULL as protectedObj here can lead to incorrect behavior for ICastable objects + // We need to review if this is the case and refactor this code if we want ICastable to become officially supported + VirtualCallStubManager::Resolver(pMT, token, NULL, &target); return pMT->GetMethodDescForSlotAddress(target); } diff --git a/src/vm/virtualcallstub.h b/src/vm/virtualcallstub.h index cc68d2f..f5bf0d9 100644 --- a/src/vm/virtualcallstub.h +++ b/src/vm/virtualcallstub.h @@ -9,7 +9,6 @@ // - // See code:VirtualCallStubManager for details // // ============================================================================ @@ -494,11 +493,12 @@ private: size_t token, void *target); - //Given a dispatch token and a method table, determine the + //Given a dispatch token, an object and a method table, determine the //target address to go to. The return value (BOOL) states whether this address //is cacheable or not. static BOOL Resolver(MethodTable * pMT, DispatchToken token, + OBJECTREF * protectedObj, PCODE * ppTarget); // This can be used to find a target without needing the ability to throw @@ -554,7 +554,7 @@ private: static void STDCALL BackPatchWorkerStatic(PCODE returnAddr, TADDR siteAddrForRegisterIndirect); public: - PCODE ResolveWorker(StubCallSite* pCallSite, OBJECTREF pObj, DispatchToken token, StubKind stubKind); + PCODE ResolveWorker(StubCallSite* pCallSite, OBJECTREF *protectedObj, DispatchToken token, StubKind stubKind); void BackPatchWorker(StubCallSite* pCallSite); //Change the callsite to point to stub diff --git a/tests/src/Interop/ICastable/Castable.cs b/tests/src/Interop/ICastable/Castable.cs new file mode 100644 index 0000000..56a0efb --- /dev/null +++ b/tests/src/Interop/ICastable/Castable.cs @@ -0,0 +1,219 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +public interface IRetArg +{ + T ReturnArg(T t); +} + +public interface IRetThis +{ + IRetThis ReturnThis(); + Type GetMyType(); +} + + +public interface IUnimplemented +{ + void UnimplementedMethod(); +} + +public interface IExtra +{ + int InnocentMethod(); +} + +public class CastableException : Exception {}; + + +public class RetArgImpl: IRetArg +{ + public string ReturnArg(string t) + { + Console.WriteLine("ReturnArg has been called."); + return t; + } +} + +public class GenRetArgImpl: IRetArg +{ + public T ReturnArg(T t) + { + Console.WriteLine("Generic ReturnArg has been called. My type is {0}", GetType()); + return t; + } +} + + +public class RetThisImpl: IRetThis +{ + public IRetThis ReturnThis() + { + Console.WriteLine("RetThis has been called."); + return this; + } + + public Type GetMyType() + { + Console.WriteLine("GetMyType has been called. My type is {0}", GetType()); + return GetType(); + } +} + + +public class Castable : ICastable, IExtra +{ + private Dictionary _interface2impl; + + public Castable(Dictionary interface2impl) + { + _interface2impl = interface2impl; + } + + public bool IsInstanceOfInterface(RuntimeTypeHandle interfaceType, out Exception castError) + { + Console.WriteLine("IsInstanceOfInterface has been called for type {0}", Type.GetTypeFromHandle(interfaceType)); + if (_interface2impl == null) + { + castError = new CastableException(); + return false; + } + castError = null; + return _interface2impl.ContainsKey(Type.GetTypeFromHandle(interfaceType)); + } + + public RuntimeTypeHandle GetImplType(RuntimeTypeHandle interfaceType) + { + Console.WriteLine("GetImplType has been called for type {0}", Type.GetTypeFromHandle(interfaceType)); + return _interface2impl[Type.GetTypeFromHandle(interfaceType)].TypeHandle; + } + + public int InnocentMethod() + { + Console.WriteLine("InnocentMethod has been called. My type is {0}", GetType()); + return 3; + } +} + +public class BadCastable : ICastable +{ + public bool IsInstanceOfInterface(RuntimeTypeHandle interfaceType, out Exception castError) + { + castError = null; + return true; + } + + public RuntimeTypeHandle GetImplType(RuntimeTypeHandle interfaceType) + { + return default(RuntimeTypeHandle); + } +} + + +public class Program +{ + private static bool passed = true; + + public static void Assert(bool value, string message) + { + if (!value) + { + Console.WriteLine("FAIL! " + message); + passed = false; + } + } + + public static int Main() + { + //Console.WriteLine("Execution started. Attach debugger and press enter."); + //Console.ReadLine(); + + try + { + object implProxy = new Castable( + new Dictionary() + { + { typeof(IRetArg), typeof(RetArgImpl) }, + { typeof(IRetArg), typeof(GenRetArgImpl) }, + { typeof(IRetThis), typeof(RetThisImpl) }, + { typeof(IExtra), null }, //we should never use it + } + ); + + // testing simple cases + Assert(implProxy is IRetThis, "implProxy should be castable to IRetThis via is"); + Assert(!(implProxy is IUnimplemented), "implProxy should not be castable to IUnimplemented via is"); + Assert((implProxy as IRetThis) != null, "implProxy should be castable to IRetThis is as"); + Assert((implProxy as IUnimplemented) == null, "implProxy should not be castable to IUnimplemented is as"); + var retThis = (IRetThis)implProxy; + Assert(object.ReferenceEquals(retThis.ReturnThis(), implProxy), "RetThis should return implProxy"); + Assert(retThis.GetMyType() == typeof(Castable), "GetMyType should return typeof(Castable)"); + + Assert(!(implProxy is IUnimplemented), "implProxy should not be castable to IUnimplemented via is"); + Assert((implProxy as IUnimplemented) == null, "implProxy should not be castable to IUnimplemented via as"); + + + // testing generics + IRetArg retArgStr = (IRetArg)implProxy; + Assert(retArgStr.ReturnArg("hohoho") == "hohoho", "retArgStr.ReturnArg() should return arg"); + + IRetArg retArgInt = (IRetArg)implProxy; + Assert(retArgInt.ReturnArg(42) == 42, "retArgInt.ReturnArg() should return arg"); + + + // testing Castable implemeting other interfaces + var extra = (IExtra)implProxy; + Assert(extra.InnocentMethod() == 3, "InnocentMethod() should be called on Castable and return 3"); + + // testing error handling + try + { + var _ = (IUnimplemented)implProxy; + Assert(false, "pProxy should not be castable to I1"); + } + catch (InvalidCastException) {} + + object nullCastable = new Castable(null); + try + { + var _ = (IRetThis)nullCastable; + Assert(false, "Exceptions should be thrown from IsInstanceOfInterface"); + } + catch (CastableException) {} + + object badCastable = new BadCastable(); + try + { + var r = (IRetThis)badCastable; + r.ReturnThis(); + Assert(false, "Exceptions should be thrown from ReturnThis()"); + } + catch (EntryPointNotFoundException) {} + + //delegate testing + Func fInt = new Func(extra.InnocentMethod); + Assert(fInt() == 3, "Delegate call to InnocentMethod() should return 3"); + + Func func = new Func(retThis.ReturnThis); + Assert(object.ReferenceEquals(func(), implProxy), "Delegate call to ReturnThis() should return this"); + } + catch (Exception e) + { + Assert(false, e.ToString()); + } + + if (passed) + { + Console.WriteLine("Test PASSED!"); + return 100; + } + else + { + Console.WriteLine("Test FAILED!"); + return -1; + } + + } +} \ No newline at end of file diff --git a/tests/src/Interop/ICastable/Castable.csproj b/tests/src/Interop/ICastable/Castable.csproj new file mode 100644 index 0000000..dac6ca1 --- /dev/null +++ b/tests/src/Interop/ICastable/Castable.csproj @@ -0,0 +1,42 @@ + + + + + Debug + AnyCPU + Castable + 2.0 + {95DFC527-4DC1-495E-97D7-E94EE1F7140D} + Exe + Properties + 512 + {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + $(ProgramFiles)\Common Files\microsoft shared\VSTT\11.0\UITestExtensionPackages + ..\..\ + true + 7a9bfb7d + $(DefineConstants);STATIC + true + + + + + + + + + False + + + + + + + + + + + + + + diff --git a/tests/src/Interop/ICastable/app.config b/tests/src/Interop/ICastable/app.config new file mode 100644 index 0000000..58c0121 --- /dev/null +++ b/tests/src/Interop/ICastable/app.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/tests/src/Interop/ICastable/packages.config b/tests/src/Interop/ICastable/packages.config new file mode 100644 index 0000000..569e1be --- /dev/null +++ b/tests/src/Interop/ICastable/packages.config @@ -0,0 +1,3 @@ + + + diff --git a/tests/src/dir.targets b/tests/src/dir.targets index bb05059..cd45da5 100644 --- a/tests/src/dir.targets +++ b/tests/src/dir.targets @@ -35,7 +35,8 @@ > - + + -- 2.7.4