Last time, we studied a crash involving improper use of the COM_
INTERFACE_
ENTRY
macro. Can we make it better at detecting misuse at compile time?
We want to make sure that IWidgetÂProviderÂInfo
derives directly from IUnknown
, so that, given the declaration
IWidgetProviderInfo* p;
we can guarantee that IWidgetÂProviderÂInfo
is convertible to IUnknown
and that
reinterpret_cast<IUnknown*>(p) == static_cast<IUnknown*>(p)
Otherwise, somebody could try this:
struct Strange { ... }; interface IWidgetProvider : Strange, IUnknown { ... };
The IWidgetÂProvider
can be converted to a IUnknown
, but it will probably also need to be adjusted by sizeof(Strange)
.¹
ATL already has a macro for calculating the amount by which the two parts of the above comparison differ. It uses it for deciding how much to adjust the pointer to get to the desired interface:
#define offsetofclass(base, derived) \ ((DWORD_PTR)(static_cast<base*>((derived*)_ATL_PACKING)) \ -_ATL_PACKING)
So we just need to assert that the value is (1)Â calculable, and (2)Â zero.
The original definition of COM_
INTERFACE_
ENTRY
is
#define COM_INTERFACE_ENTRY(x)\ {&IID_##x, \ offsetofclass(x, _ComMapClass), \ _ATL_SIMPLEMAPENTRY},
We can make this minor adjustment to ensure that converting from x
to IUnknown
is both possible and a nop:
#define COM_INTERFACE_ENTRY(x)\
{&IID_##x, \
offsetofclass(x, _ComMapClass)/!offsetofclass(IUnknown, x), \
_ATL_SIMPLEMAPENTRY},
If x
cannot be converted to IUnknown
, then the offsetofclass
will encounter a compile-time error because the static_cast
from x*
to IUnknown*
is not possible.
If the conversion is possible but requires a pointer adjustment, then the offsetof
will produce a nonzero value. Negating it with !
will produce 0
, and dividing by it will trigger a divide-by-zero compile-time error.
If the conversion is possible and does not require a pointer adjustment (which is the case we want to permit), then the offsetof
will result in the value 0
. Negating it with !
will produce 1
, and dividing by 1
has no effect.
You can apply this same fix to the other COM_
INTERFACE_
ENTRY
macros, and to any other macro that assumes that its type parameter is derived from IUnknown
.
¹ Types that are not standard layout are not required to place the first named base class at offset zero relative to the derived class, so this is something worth checking even if you think everything is set up correctly.
0 comments