InstantiatedMethodDesc, METHODDESCS );
#ifdef FEATURE_COMINTEROP
- if (imd->IMD_HasComPlusCallInfo())
+ if (imd->IsGenericComPlusCall())
{
PTR_ComPlusCallInfo compluscall = imd->IMD_GetComPlusCallInfo();
DumpComPlusCallInfo( compluscall, METHODDESCS );
return;
// Step 1: Validate compatibility of return types on overriding methods
-
- for (WORD i = 0; i < pParentMT->GetNumVirtuals(); i++)
+ if (pMT->GetClass()->HasCovariantOverride() && (!pMT->GetModule()->IsReadyToRun() || !pMT->GetModule()->GetReadyToRunInfo()->SkipTypeValidation()))
{
- MethodDesc* pMD = pMT->GetMethodDescForSlot(i);
- MethodDesc* pParentMD = pParentMT->GetMethodDescForSlot(i);
+ for (WORD i = 0; i < pParentMT->GetNumVirtuals(); i++)
+ {
+ if (pMT->GetRestoredSlot(i) == pParentMT->GetRestoredSlot(i))
+ {
+ // The real check is that the MethodDesc's must not match, but a simple VTable check will
+ // work most of the time, and is far faster than the GetMethodDescForSlot method.
+ _ASSERTE(pMT->GetMethodDescForSlot(i) == pParentMT->GetMethodDescForSlot(i));
+ continue;
+ }
+ MethodDesc* pMD = pMT->GetMethodDescForSlot(i);
+ MethodDesc* pParentMD = pParentMT->GetMethodDescForSlot(i);
- if (pMD == pParentMD)
- continue;
+ if (pMD == pParentMD)
+ continue;
- if (!pMD->RequiresCovariantReturnTypeChecking() && !pParentMD->RequiresCovariantReturnTypeChecking())
- continue;
+ if (!pMD->RequiresCovariantReturnTypeChecking() && !pParentMD->RequiresCovariantReturnTypeChecking())
+ continue;
- // If the bit is not set on this method, but we reach here because it's been set on the method at the same slot on
- // the base type, set the bit for the current method to ensure any future overriding method down the chain gets checked.
- if (!pMD->RequiresCovariantReturnTypeChecking())
- pMD->SetRequiresCovariantReturnTypeChecking();
+ // If the bit is not set on this method, but we reach here because it's been set on the method at the same slot on
+ // the base type, set the bit for the current method to ensure any future overriding method down the chain gets checked.
+ if (!pMD->RequiresCovariantReturnTypeChecking())
+ pMD->SetRequiresCovariantReturnTypeChecking();
- // The context used to load the return type of the parent method has to use the generic method arguments
- // of the overriding method, otherwise the type comparison below will not work correctly
- SigTypeContext context1(pParentMD->GetClassInstantiation(), pMD->GetMethodInstantiation());
- MetaSig methodSig1(pParentMD);
- TypeHandle hType1 = methodSig1.GetReturnProps().GetTypeHandleThrowing(pParentMD->GetModule(), &context1, ClassLoader::LoadTypesFlag::LoadTypes, CLASS_LOAD_EXACTPARENTS);
+ // The context used to load the return type of the parent method has to use the generic method arguments
+ // of the overriding method, otherwise the type comparison below will not work correctly
+ SigTypeContext context1(pParentMD->GetClassInstantiation(), pMD->GetMethodInstantiation());
+ MetaSig methodSig1(pParentMD);
+ TypeHandle hType1 = methodSig1.GetReturnProps().GetTypeHandleThrowing(pParentMD->GetModule(), &context1, ClassLoader::LoadTypesFlag::LoadTypes, CLASS_LOAD_EXACTPARENTS);
- SigTypeContext context2(pMD);
- MetaSig methodSig2(pMD);
- TypeHandle hType2 = methodSig2.GetReturnProps().GetTypeHandleThrowing(pMD->GetModule(), &context2, ClassLoader::LoadTypesFlag::LoadTypes, CLASS_LOAD_EXACTPARENTS);
+ SigTypeContext context2(pMD);
+ MetaSig methodSig2(pMD);
+ TypeHandle hType2 = methodSig2.GetReturnProps().GetTypeHandleThrowing(pMD->GetModule(), &context2, ClassLoader::LoadTypesFlag::LoadTypes, CLASS_LOAD_EXACTPARENTS);
- if (!IsCompatibleWith(hType1, hType2))
- {
- SString strAssemblyName;
- pMD->GetAssembly()->GetDisplayName(strAssemblyName);
+ if (!IsCompatibleWith(hType1, hType2))
+ {
+ SString strAssemblyName;
+ pMD->GetAssembly()->GetDisplayName(strAssemblyName);
- SString strInvalidTypeName;
- TypeString::AppendType(strInvalidTypeName, TypeHandle(pMD->GetMethodTable()));
+ SString strInvalidTypeName;
+ TypeString::AppendType(strInvalidTypeName, TypeHandle(pMD->GetMethodTable()));
- SString strInvalidMethodName;
- TypeString::AppendMethod(strInvalidMethodName, pMD, pMD->GetMethodInstantiation());
+ SString strInvalidMethodName;
+ TypeString::AppendMethod(strInvalidMethodName, pMD, pMD->GetMethodInstantiation());
- SString strParentMethodName;
- TypeString::AppendMethod(strParentMethodName, pParentMD, pParentMD->GetMethodInstantiation());
+ SString strParentMethodName;
+ TypeString::AppendMethod(strParentMethodName, pParentMD, pParentMD->GetMethodInstantiation());
- COMPlusThrow(
- kTypeLoadException,
- IDS_CLASSLOAD_MI_BADRETURNTYPE,
- strInvalidMethodName,
- strInvalidTypeName,
- strAssemblyName,
- strParentMethodName);
+ COMPlusThrow(
+ kTypeLoadException,
+ IDS_CLASSLOAD_MI_BADRETURNTYPE,
+ strInvalidMethodName,
+ strInvalidTypeName,
+ strAssemblyName,
+ strParentMethodName);
+ }
}
}
// Step 2: propate overriding MethodImpls to applicable vtable slots if the declaring method has the attribute
- MethodTable::MethodDataWrapper hMTData(MethodTable::GetMethodData(pMT, FALSE));
-
- for (WORD i = 0; i < pParentMT->GetNumVirtuals(); i++)
+ if (pMT->GetClass()->HasVTableMethodImpl())
{
- MethodDesc* pMD = pMT->GetMethodDescForSlot(i);
- MethodDesc* pParentMD = pParentMT->GetMethodDescForSlot(i);
- if (pMD == pParentMD)
- continue;
-
- // The attribute is only applicable to MethodImpls. For anything else, it will be treated as a no-op
- if (!pMD->IsMethodImpl())
- continue;
+ MethodTable::MethodDataWrapper hMTData(MethodTable::GetMethodData(pMT, FALSE));
- // Search if the attribute has been applied on this vtable slot, either by the current MethodImpl, or by a previous
- // MethodImpl somewhere in the base type hierarchy.
- bool foundAttribute = false;
- MethodTable* pCurrentMT = pMT;
- while (!foundAttribute && pCurrentMT != NULL && i < pCurrentMT->GetNumVirtuals())
+ for (WORD i = 0; i < pParentMT->GetNumVirtuals(); i++)
{
- MethodDesc* pCurrentMD = pCurrentMT->GetMethodDescForSlot(i);
+ if (pMT->GetRestoredSlot(i) == pParentMT->GetRestoredSlot(i))
+ {
+ // The real check is that the MethodDesc's must not match, but a simple VTable check will
+ // work most of the time, and is far faster than the GetMethodDescForSlot method.
+ _ASSERTE(pMT->GetMethodDescForSlot(i) == pParentMT->GetMethodDescForSlot(i));
+ continue;
+ }
+
+ MethodDesc* pMD = pMT->GetMethodDescForSlot(i);
+ MethodDesc* pParentMD = pParentMT->GetMethodDescForSlot(i);
+ if (pMD == pParentMD)
+ continue;
// The attribute is only applicable to MethodImpls. For anything else, it will be treated as a no-op
- if (pCurrentMD->IsMethodImpl())
+ if (!pMD->IsMethodImpl())
+ continue;
+
+ // Search if the attribute has been applied on this vtable slot, either by the current MethodImpl, or by a previous
+ // MethodImpl somewhere in the base type hierarchy.
+ bool foundAttribute = false;
+ MethodTable* pCurrentMT = pMT;
+ while (!foundAttribute && pCurrentMT != NULL && i < pCurrentMT->GetNumVirtuals())
{
- BYTE* pVal = NULL;
- ULONG cbVal = 0;
- if (pCurrentMD->GetCustomAttribute(WellKnownAttribute::PreserveBaseOverridesAttribute, (const void**)&pVal, &cbVal) == S_OK)
- foundAttribute = true;
- }
+ MethodDesc* pCurrentMD = pCurrentMT->GetMethodDescForSlot(i);
- pCurrentMT = pCurrentMT->GetParentMethodTable();
- }
+ // The attribute is only applicable to MethodImpls. For anything else, it will be treated as a no-op
+ if (pCurrentMD->IsMethodImpl())
+ {
+ BYTE* pVal = NULL;
+ ULONG cbVal = 0;
+ if (pCurrentMD->GetCustomAttribute(WellKnownAttribute::PreserveBaseOverridesAttribute, (const void**)&pVal, &cbVal) == S_OK)
+ foundAttribute = true;
+ }
- if (!foundAttribute)
- continue;
+ pCurrentMT = pCurrentMT->GetParentMethodTable();
+ }
- // Search for any vtable slot still pointing at the parent method, and update it with the current overriding method
- for (WORD j = i; j < pParentMT->GetNumVirtuals(); j++)
- {
- MethodDesc* pCurrentMD = pMT->GetMethodDescForSlot(j);
- if (pCurrentMD == pParentMD)
+ if (!foundAttribute)
+ continue;
+
+ // Search for any vtable slot still pointing at the parent method, and update it with the current overriding method
+ for (WORD j = i; j < pParentMT->GetNumVirtuals(); j++)
{
- // This is a vtable slot that needs to be updated to the new overriding method because of the
- // presence of the attribute.
- pMT->SetSlot(j, pMT->GetSlot(i));
- _ASSERT(pMT->GetMethodDescForSlot(j) == pMD);
+ MethodDesc* pCurrentMD = pMT->GetMethodDescForSlot(j);
+ if (pCurrentMD == pParentMD)
+ {
+ // This is a vtable slot that needs to be updated to the new overriding method because of the
+ // presence of the attribute.
+ pMT->SetSlot(j, pMT->GetSlot(i));
+ _ASSERT(pMT->GetMethodDescForSlot(j) == pMD);
- hMTData->UpdateImplMethodDesc(pMD, j);
+ hMTData->UpdateImplMethodDesc(pMD, j);
+ }
}
}
}
}
#endif // FEATURE_COMINTEROP
+ inline void SetHasVTableMethodImpl()
+ {
+ LIMITED_METHOD_CONTRACT;
+ m_VMFlags |= VMFLAG_VTABLEMETHODIMPL;
+ }
+
+ inline BOOL HasVTableMethodImpl()
+ {
+ LIMITED_METHOD_CONTRACT;
+ return (m_VMFlags & VMFLAG_VTABLEMETHODIMPL);
+ }
+
+ inline void SetHasCovariantOverride()
+ {
+ LIMITED_METHOD_CONTRACT;
+ m_VMFlags |= VMFLAG_COVARIANTOVERRIDE;
+ }
+
+ inline BOOL HasCovariantOverride()
+ {
+ LIMITED_METHOD_CONTRACT;
+ return (m_VMFlags & VMFLAG_COVARIANTOVERRIDE);
+ }
+
#ifdef _DEBUG
inline DWORD IsDestroyed()
{
VMFLAG_HASCOCLASSATTRIB = 0x01000000,
VMFLAG_COMEVENTITFMASK = 0x02000000, // class is a special COM event interface
#endif // FEATURE_COMINTEROP
- // unused = 0x04000000,
- // unused = 0x08000000,
+ VMFLAG_VTABLEMETHODIMPL = 0x04000000, // class uses MethodImpl to override virtual function defined on class
+ VMFLAG_COVARIANTOVERRIDE = 0x08000000, // class has a covariant override
// This one indicates that the fields of the valuetype are
// not tightly packed and is used to check whether we can
adjustment + sizeof(ComPlusCallMethodDesc), /* mcComInterOp */ \
adjustment + sizeof(DynamicMethodDesc) /* mcDynamic */
-const SIZE_T MethodDesc::s_ClassificationSizeTable[] = {
+const BYTE MethodDesc::s_ClassificationSizeTable[] = {
// This is the raw
METHOD_DESC_SIZES(0),
// We index using optional slot flags into it
METHOD_DESC_SIZES(sizeof(NonVtableSlot)),
METHOD_DESC_SIZES(sizeof(MethodImpl)),
- METHOD_DESC_SIZES(sizeof(NonVtableSlot) + sizeof(MethodImpl))
+ METHOD_DESC_SIZES(sizeof(NonVtableSlot) + sizeof(MethodImpl)),
+
+ METHOD_DESC_SIZES(sizeof(NativeCodeSlot)),
+ METHOD_DESC_SIZES(sizeof(NonVtableSlot) + sizeof(NativeCodeSlot)),
+ METHOD_DESC_SIZES(sizeof(MethodImpl) + sizeof(NativeCodeSlot)),
+ METHOD_DESC_SIZES(sizeof(NonVtableSlot) + sizeof(MethodImpl) + sizeof(NativeCodeSlot)),
+
+#ifdef FEATURE_COMINTEROP
+ METHOD_DESC_SIZES(sizeof(ComPlusCallInfo)),
+ METHOD_DESC_SIZES(sizeof(NonVtableSlot) + sizeof(ComPlusCallInfo)),
+ METHOD_DESC_SIZES(sizeof(MethodImpl) + sizeof(ComPlusCallInfo)),
+ METHOD_DESC_SIZES(sizeof(NonVtableSlot) + sizeof(MethodImpl) + sizeof(ComPlusCallInfo)),
+
+ METHOD_DESC_SIZES(sizeof(NativeCodeSlot) + sizeof(ComPlusCallInfo)),
+ METHOD_DESC_SIZES(sizeof(NonVtableSlot) + sizeof(NativeCodeSlot) + sizeof(ComPlusCallInfo)),
+ METHOD_DESC_SIZES(sizeof(MethodImpl) + sizeof(NativeCodeSlot) + sizeof(ComPlusCallInfo)),
+ METHOD_DESC_SIZES(sizeof(NonVtableSlot) + sizeof(MethodImpl) + sizeof(NativeCodeSlot) + sizeof(ComPlusCallInfo))
+#endif
};
#ifndef FEATURE_COMINTEROP
{
LIMITED_METHOD_DAC_CONTRACT;
- SIZE_T size = s_ClassificationSizeTable[m_wFlags & (mdcClassification | mdcHasNonVtableSlot | mdcMethodImpl)];
+ SIZE_T size = s_ClassificationSizeTable[m_wFlags &
+ (mdcClassification
+ | mdcHasNonVtableSlot
+ | mdcMethodImpl
+#ifdef FEATURE_COMINTEROP
+ | mdcHasComPlusCallInfo
+#endif
+ | mdcHasNativeCodeSlot)];
+#ifdef FEATURE_PREJIT
if (HasNativeCodeSlot())
{
- size += (*dac_cast<PTR_TADDR>(dac_cast<TADDR>(this) + size) & FIXUP_LIST_MASK) ?
- (sizeof(NativeCodeSlot) + sizeof(FixupListSlot)) : sizeof(NativeCodeSlot);
+ size += (*dac_cast<PTR_TADDR>(GetAddrOfNativeCodeSlot()) & FIXUP_LIST_MASK) ?
+ sizeof(FixupListSlot) : 0;
}
-
-#ifdef FEATURE_COMINTEROP
- if (IsGenericComPlusCall())
- size += sizeof(ComPlusCallInfo);
-#endif // FEATURE_COMINTEROP
+#endif
return size;
}
if (pMethodInfo->m_fHasNativeCodeSlot)
{
- pNewMD->m_bFlags2 |= enum_flag2_HasNativeCodeSlot;
+ pNewMD->m_wFlags |= mdcHasNativeCodeSlot;
}
else
{
- pNewMD->m_bFlags2 &= ~enum_flag2_HasNativeCodeSlot;
+ pNewMD->m_wFlags &= ~mdcHasNativeCodeSlot;
}
#ifdef FEATURE_COMINTEROP
// where the function explicitly implements IInterface.foo() instead of foo().
mdcMethodImpl = 0x0010,
- // Method is static
- mdcStatic = 0x0020,
+ // Has slot for native code
+ mdcHasNativeCodeSlot = 0x0020,
+#ifdef FEATURE_COMINTEROP
+ mdcHasComPlusCallInfo = 0x0040,
+#else
// unused = 0x0040,
- // unused = 0x0080,
+#endif
+
+ // Method is static
+ mdcStatic = 0x0080,
+
// unused = 0x0100,
// unused = 0x0200,
enum_flag2_HasPrecode = 0x02, // Precode has been allocated for this method
enum_flag2_IsUnboxingStub = 0x04,
- enum_flag2_HasNativeCodeSlot = 0x08, // Has slot for native code
+ // unused = 0x08,
enum_flag2_IsJitIntrinsic = 0x10, // Jit may expand method as an intrinsic
inline BOOL HasNativeCodeSlot()
{
LIMITED_METHOD_DAC_CONTRACT;
- return (m_bFlags2 & enum_flag2_HasNativeCodeSlot) != 0;
+ return (m_wFlags & mdcHasNativeCodeSlot) != 0;
}
inline void SetHasNativeCodeSlot()
{
LIMITED_METHOD_CONTRACT;
- m_bFlags2 |= enum_flag2_HasNativeCodeSlot;
+ m_wFlags |= mdcHasNativeCodeSlot;
}
inline BOOL IsJitIntrinsic()
m_bFlags2 |= enum_flag2_RequiresCovariantReturnTypeChecking;
}
- static const SIZE_T s_ClassificationSizeTable[];
+ static const BYTE s_ClassificationSizeTable[];
static SIZE_T GetBaseSize(DWORD classification)
{
}
#ifdef FEATURE_COMINTEROP
- BOOL IMD_HasComPlusCallInfo()
- {
- LIMITED_METHOD_CONTRACT;
- return ((m_wFlags2 & HasComPlusCallInfo) != 0);
- }
-
void IMD_SetupGenericComPlusCall()
{
LIMITED_METHOD_CONTRACT;
- m_wFlags2 |= InstantiatedMethodDesc::HasComPlusCallInfo;
-
IMD_GetComPlusCallInfo()->InitStackArgumentSize();
}
{
LIMITED_METHOD_CONTRACT;
- _ASSERTE(IMD_HasComPlusCallInfo());
- SIZE_T size = s_ClassificationSizeTable[m_wFlags & (mdcClassification | mdcHasNonVtableSlot | mdcMethodImpl)];
+ _ASSERTE(IsGenericComPlusCall());
+ SIZE_T size = s_ClassificationSizeTable[m_wFlags & (mdcClassification | mdcHasNonVtableSlot | mdcMethodImpl | mdcHasNativeCodeSlot)];
+#ifdef FEATURE_PREJIT
if (HasNativeCodeSlot())
{
- size += (*dac_cast<PTR_TADDR>(dac_cast<TADDR>(this) + size) & FIXUP_LIST_MASK) ?
- (sizeof(NativeCodeSlot) + sizeof(FixupListSlot)) : sizeof(NativeCodeSlot);
+ size += (*dac_cast<PTR_TADDR>(GetAddrOfNativeCodeSlot()) & FIXUP_LIST_MASK) ?
+ sizeof(FixupListSlot) : 0;
}
+#endif
return dac_cast<PTR_ComPlusCallInfo>(dac_cast<TADDR>(this) + size);
}
FORCEINLINE DWORD MethodDesc::IsGenericComPlusCall()
{
LIMITED_METHOD_CONTRACT;
- return (mcInstantiated == GetClassification() && AsInstantiatedMethodDesc()->IMD_HasComPlusCallInfo());
+ return m_wFlags & mdcHasComPlusCallInfo;
}
+
inline void MethodDesc::SetupGenericComPlusCall()
{
LIMITED_METHOD_CONTRACT;
+ m_wFlags |= mdcHasComPlusCallInfo;
+
AsInstantiatedMethodDesc()->IMD_SetupGenericComPlusCall();
}
#endif // FEATURE_COMINTEROP
compatibleSignatures = TRUE;
bmtMetaData->rgMethodImplTokens[i].fRequiresCovariantReturnTypeChecking = true;
+ bmtMetaData->fHasCovariantOverride = true;
}
}
{
STANDARD_VM_CONTRACT;
+ if (bmtMetaData->fHasCovariantOverride)
+ {
+ GetHalfBakedClass()->SetHasCovariantOverride();
+ }
+ if (GetParentMethodTable() != NULL)
+ {
+ EEClass* parentClass = GetParentMethodTable()->GetClass();
+ if (parentClass->HasCovariantOverride())
+ GetHalfBakedClass()->SetHasCovariantOverride();
+ if (parentClass->HasVTableMethodImpl())
+ GetHalfBakedClass()->SetHasVTableMethodImpl();
+ }
+
if (bmtMethod->dwNumberMethodImpls == 0)
return;
}
Substitution *pDeclSubst = &bmtMetaData->pMethodDeclSubsts[m];
+
MethodTable * pDeclMT = NULL;
MethodSignature declSig(GetModule(), szName, pSig, cbSig, NULL);
}
else
{
+ GetHalfBakedClass()->SetHasVTableMethodImpl();
declMethod = FindDeclMethodOnClassInHierarchy(it, pDeclMT, declSig);
}
MethodImplTokenPair *rgMethodImplTokens;
Substitution *pMethodDeclSubsts; // Used to interpret generic variables in the interface of the declaring type
+ bool fHasCovariantOverride;
+
//-----------------------------------------------------------------------------------------
inline bmtMetaDataInfo() { LIMITED_METHOD_CONTRACT; memset((void *)this, NULL, sizeof(*this)); }
}; // struct bmtMetaDataInfo
class DeclaredMethodIterator
{
private:
- MethodTableBuilder &m_mtb;
- int m_idx; // not SLOT_INDEX?
+ const int m_numDeclaredMethods;
+ bmtMDMethod ** const m_declaredMethods;
+ int m_idx; // not SLOT_INDEX?
#ifdef _DEBUG
- bmtMDMethod * m_debug_pMethod;
+ bmtMDMethod * m_debug_pMethod;
#endif
public:
//***************************************************************************************
inline MethodTableBuilder::DeclaredMethodIterator::DeclaredMethodIterator(
- MethodTableBuilder &mtb) : m_mtb(mtb), m_idx(-1)
+ MethodTableBuilder &mtb) :
+ m_numDeclaredMethods((int)mtb.NumDeclaredMethods()),
+ m_declaredMethods(mtb.bmtMethod->m_rgDeclaredMethods),
+ m_idx(-1)
{
LIMITED_METHOD_CONTRACT;
}
inline int MethodTableBuilder::DeclaredMethodIterator::CurrentIndex()
{
LIMITED_METHOD_CONTRACT;
- CONSISTENCY_CHECK_MSG(0 <= m_idx && m_idx < (int)m_mtb.NumDeclaredMethods(),
+ CONSISTENCY_CHECK_MSG(0 <= m_idx && m_idx < m_numDeclaredMethods,
"Invalid iterator state.");
return m_idx;
}
inline BOOL MethodTableBuilder::DeclaredMethodIterator::Next()
{
LIMITED_METHOD_CONTRACT;
- if (m_idx + 1 >= (int)m_mtb.NumDeclaredMethods())
+ if (m_idx + 1 >= m_numDeclaredMethods)
return FALSE;
m_idx++;
INDEBUG(m_debug_pMethod = GetMDMethod();)
inline void MethodTableBuilder::DeclaredMethodIterator::ResetToEnd()
{
LIMITED_METHOD_CONTRACT;
- m_idx = (int)m_mtb.NumDeclaredMethods();
+ m_idx = m_numDeclaredMethods;
}
//***************************************************************************************
{
LIMITED_METHOD_CONTRACT;
_ASSERTE(FitsIn<SLOT_INDEX>(m_idx)); // Review: m_idx should probably _be_ a SLOT_INDEX, but that asserts.
- return (*m_mtb.bmtMethod)[static_cast<SLOT_INDEX>(m_idx)];
+ return m_declaredMethods[m_idx];
}
//*******************************************************************************
pGenericTypeModule = pModule;
}
- TypeHandle genericType = psig.GetGenericInstType(pModule, fLoadTypes, level, pZapSigContext);
+ TypeHandle genericType = psig.GetGenericInstType(pModule, fLoadTypes, level < CLASS_LOAD_APPROXPARENTS ? level : CLASS_LOAD_APPROXPARENTS, pZapSigContext);
if (genericType.IsNull())
{