Skip to content

Commit 47f3137

Browse files
MarijnS95Greg Roth
andauthored
[Linux] WinAdapter: Remove virtual dtors from IUnknown to fix vtable ABI (microsoft#3793)
* WinAdapter: Remove virtual dtors from IUnknown to fix vtable ABI The vtable for `IUnknown` and its subclasses contain two deletion pointers when compiled on non-Windows systems with `IUnknown` from `WinAdapter.h`: vtable for 'DxcLibrary' @ 0x7ffff7cbc5f8 (subobject @ 0x5555556bb9e0): [0]: 0x7ffff6a56d40 <DxcLibrary::QueryInterface(_GUID const&, void**)> [1]: 0x7ffff6a56d20 <DxcLibrary::AddRef()> [2]: 0x7ffff6a56d30 <DxcLibrary::Release()> [3]: 0x7ffff6b36bc0 <IUnknown::~IUnknown()> // Complete object destructor [4]: 0x7ffff6a57130 <DxcLibrary::~DxcLibrary()> // Deleting destructor [5]: 0x7ffff6a56d50 <DxcLibrary::SetMalloc(IMalloc*)> [6]: 0x7ffff6a56d60 <DxcLibrary::CreateBlobFromBlob(IDxcBlob*, unsigned int, unsigned int, IDxcBlob**)> ... More DxcLibrary virtual functions This shifts the the pointers for functions for all subclasses, and is [annoying] to deal with in otherwise cross-platform applications using DirectXShaderCompiler as library. `dxcompiler.dll` compiled on/for Windows without `WinAdapter.h` does not suffer this problem, and only has three function pointers for `IUnknown`. Fortunately, it is easily solved by removing the virtual destructor from `IUnknown`. LLVM enables `-Wnon-virtual-dtor` that warns against classes with virtual methods but no virtual destructor, though this warning is best not enabled akin to Windows builds where `IUnknown` from `windows.h` (`unknwn.h`) results in the same warning on MSVC ([1]/[2]). [annoying]: https://github.com/Traverse-Research/hassle-rs/blob/1e624792fc3a252ac7788e3c1c5feda52887272f/src/unknown.rs [1]: microsoft#3783 (comment) [2]: https://godbolt.org/z/hKPT6ThEf * WinAdapter: Make `IUnknown` and `IMalloc` pure-virtual classes `IUnknown` in Windows' `unknwn.h` and `IMalloc` in `ObjIdl.h` are marked as pure virtual, and are best marked as such in `WinAdapter` for non-Windows platforms too [1]. Only the shim for `IMalloc` was relying on the default refcounting implementation, all other subclasses either contain pure-virtual methods themselves or provide an implementation for `AddRef`/`Release` as required. Likewise the default implementation for `IMalloc` was only instantiated once by `CoGetMalloc`, and has been moved into a local class implementing the `IMalloc` interface instead. [1]: microsoft#3793 (comment) * WinAdapter: Add three missing virtual functions to `IMalloc` interface To prevent unexpected vtable breakage, add the missing functions from the [documentation]. Note that they are listed in the wrong order, the right order is retrieved from the `ObjIdl.h` header and implementations for `IMalloc` in DirectXShaderCompiler. All implementations are now properly using the `override` keyword too, to enforce virtual method existence in the base class. [documentation]: https://docs.microsoft.com/en-us/windows/win32/api/objidl/nn-objidl-imalloc * Make all WinAdapter destructions explicit This prevents warnings about non-virtual destructor usage that trip up the Linux build. It represents status quo on Windows. Co-authored-by: Greg Roth <[email protected]>
1 parent 9975a80 commit 47f3137

File tree

9 files changed

+96
-91
lines changed

9 files changed

+96
-91
lines changed

cmake/modules/HandleLLVMOptions.cmake

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -412,19 +412,31 @@ elseif( LLVM_COMPILER_IS_GCC_COMPATIBLE )
412412
append_if(USE_NO_UNINITIALIZED "-Wno-uninitialized" CMAKE_CXX_FLAGS)
413413
append_if(USE_NO_MAYBE_UNINITIALIZED "-Wno-maybe-uninitialized" CMAKE_CXX_FLAGS)
414414

415-
# Check if -Wnon-virtual-dtor warns even though the class is marked final.
416-
# If it does, don't add it. So it won't be added on clang 3.4 and older.
417-
# This also catches cases when -Wnon-virtual-dtor isn't supported by
418-
# the compiler at all.
419-
set(OLD_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS})
420-
set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -std=c++11 -Werror=non-virtual-dtor")
421-
CHECK_CXX_SOURCE_COMPILES("class base {public: virtual void anchor();protected: ~base();};
422-
class derived final : public base { public: ~derived();};
423-
int main() { return 0; }"
424-
CXX_WONT_WARN_ON_FINAL_NONVIRTUALDTOR)
425-
set(CMAKE_REQUIRED_FLAGS ${OLD_CMAKE_REQUIRED_FLAGS})
426-
append_if(CXX_WONT_WARN_ON_FINAL_NONVIRTUALDTOR
427-
"-Wnon-virtual-dtor" CMAKE_CXX_FLAGS)
415+
# HLSL Change Starts
416+
417+
# Windows' and by extension WinAdapter's non-Windows implementation for IUnknown
418+
# use virtual methods without virtual destructor, as that would add two extra
419+
# function-pointers to the vtable in turn offsetting those for every subclass,
420+
# resulting in ABI mismatches:
421+
# https://github.com/microsoft/DirectXShaderCompiler/issues/3783.
422+
# The -Wnon-virtual-dtor warning is disabled to allow this, conforming
423+
# with MSVC behaviour.
424+
425+
# # Check if -Wnon-virtual-dtor warns even though the class is marked final.
426+
# # If it does, don't add it. So it won't be added on clang 3.4 and older.
427+
# # This also catches cases when -Wnon-virtual-dtor isn't supported by
428+
# # the compiler at all.
429+
# set(OLD_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS})
430+
# set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -std=c++11 -Werror=non-virtual-dtor")
431+
# CHECK_CXX_SOURCE_COMPILES("class base {public: virtual void anchor();protected: ~base();};
432+
# class derived final : public base { public: ~derived();};
433+
# int main() { return 0; }"
434+
# CXX_WONT_WARN_ON_FINAL_NONVIRTUALDTOR)
435+
# set(CMAKE_REQUIRED_FLAGS ${OLD_CMAKE_REQUIRED_FLAGS})
436+
# append_if(CXX_WONT_WARN_ON_FINAL_NONVIRTUALDTOR
437+
# "-Wnon-virtual-dtor" CMAKE_CXX_FLAGS)
438+
439+
# HLSL Change Ends
428440

429441
# Check if -Wcomment is OK with an // comment ending with '\' if the next
430442
# line is also a // comment.

include/dxc/Support/WinAdapter.h

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -614,28 +614,26 @@ template <typename T> inline void **IID_PPV_ARGS_Helper(T **pp) {
614614

615615
CROSS_PLATFORM_UUIDOF(IUnknown, "00000000-0000-0000-C000-000000000046")
616616
struct IUnknown {
617-
IUnknown() : m_count(0) {};
617+
IUnknown() {};
618618
virtual HRESULT QueryInterface(REFIID riid, void **ppvObject) = 0;
619-
virtual ULONG AddRef();
620-
virtual ULONG Release();
621-
virtual ~IUnknown();
619+
virtual ULONG AddRef() = 0;
620+
virtual ULONG Release() = 0;
622621
template <class Q> HRESULT QueryInterface(Q **pp) {
623622
return QueryInterface(__uuidof(Q), (void **)pp);
624623
}
625-
626-
private:
627-
std::atomic<unsigned long> m_count;
628624
};
629625

630626
CROSS_PLATFORM_UUIDOF(INoMarshal, "ECC8691B-C1DB-4DC0-855E-65F6C551AF49")
631627
struct INoMarshal : public IUnknown {};
632628

633629
CROSS_PLATFORM_UUIDOF(IMalloc, "00000002-0000-0000-C000-000000000046")
634630
struct IMalloc : public IUnknown {
635-
virtual void *Alloc(size_t size);
636-
virtual void *Realloc(void *ptr, size_t size);
637-
virtual void Free(void *ptr);
638-
virtual HRESULT QueryInterface(REFIID riid, void **ppvObject);
631+
virtual void *Alloc(size_t size) = 0;
632+
virtual void *Realloc(void *ptr, size_t size) = 0;
633+
virtual void Free(void *ptr) = 0;
634+
virtual size_t GetSize(void *pv) = 0;
635+
virtual int DidAlloc(void *pv) = 0;
636+
virtual void HeapMinimize(void) = 0;
639637
};
640638

641639
CROSS_PLATFORM_UUIDOF(ISequentialStream, "0C733A30-2A1C-11CE-ADE5-00AA0044773D")

include/dxc/Support/microcom.h

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,11 @@ class CComInterfaceArray {
7676
}
7777
};
7878

79+
template<typename T>
80+
void DxcCallDestructor(T *obj) {
81+
obj->T::~T();
82+
}
83+
7984
#define DXC_MICROCOM_REF_FIELD(m_dwRef) \
8085
volatile std::atomic<llvm::sys::cas_flag> m_dwRef = {0};
8186
#define DXC_MICROCOM_ADDREF_IMPL(m_dwRef) \
@@ -86,8 +91,10 @@ class CComInterfaceArray {
8691
DXC_MICROCOM_ADDREF_IMPL(m_dwRef) \
8792
ULONG STDMETHODCALLTYPE Release() override { \
8893
ULONG result = (ULONG)--m_dwRef; \
89-
if (result == 0) \
90-
delete this; \
94+
if (result == 0) { \
95+
DxcCallDestructor(this); \
96+
operator delete(this); \
97+
} \
9198
return result; \
9299
}
93100

@@ -99,11 +106,6 @@ inline T *CreateOnMalloc(IMalloc * pMalloc, Args&&... args) {
99106
return (T *)P;
100107
}
101108

102-
template<typename T>
103-
void DxcCallDestructor(T *obj) {
104-
obj->~T();
105-
}
106-
107109
// The "TM" version keep an IMalloc field that, if not null, indicate
108110
// ownership of 'this' and of any allocations used during release.
109111
#define DXC_MICROCOM_TM_REF_FIELDS() \

include/dxc/Test/CompilationResult.h

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include <atomic>
2121

2222
#include "dxc/Support/WinIncludes.h"
23+
#include "dxc/Support/microcom.h"
2324

2425
#include "dxc/dxcapi.h"
2526
#include "dxc/dxcisense.h"
@@ -49,14 +50,15 @@ inline HRESULT GetFirstChildFromCursor(IDxcCursor *cursor,
4950
return hr;
5051
}
5152

52-
class TrivialDxcUnsavedFile : IDxcUnsavedFile
53+
class TrivialDxcUnsavedFile : public IDxcUnsavedFile
5354
{
5455
private:
55-
volatile std::atomic<llvm::sys::cas_flag> m_dwRef;
56+
DXC_MICROCOM_REF_FIELD(m_dwRef)
5657
LPCSTR m_fileName;
5758
LPCSTR m_contents;
5859
unsigned m_length;
5960
public:
61+
DXC_MICROCOM_ADDREF_RELEASE_IMPL(m_dwRef)
6062
TrivialDxcUnsavedFile(LPCSTR fileName, LPCSTR contents)
6163
: m_dwRef(0), m_fileName(fileName), m_contents(contents)
6264
{
@@ -68,13 +70,8 @@ class TrivialDxcUnsavedFile : IDxcUnsavedFile
6870
CComPtr<TrivialDxcUnsavedFile> pNewValue = new TrivialDxcUnsavedFile(fileName, contents);
6971
return pNewValue.QueryInterface(pResult);
7072
}
71-
ULONG STDMETHODCALLTYPE AddRef() { return (ULONG)++m_dwRef; }
72-
ULONG STDMETHODCALLTYPE Release() {
73-
ULONG result = (ULONG)--m_dwRef;
74-
if (result == 0) delete this;
75-
return result;
76-
}
77-
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void** ppvObject)
73+
74+
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void** ppvObject) override
7875
{
7976
if (ppvObject == nullptr) return E_POINTER;
8077
if (IsEqualIID(iid, __uuidof(IUnknown)) ||
@@ -88,19 +85,19 @@ class TrivialDxcUnsavedFile : IDxcUnsavedFile
8885

8986
return E_NOINTERFACE;
9087
}
91-
HRESULT STDMETHODCALLTYPE GetFileName(LPSTR* pFileName)
88+
HRESULT STDMETHODCALLTYPE GetFileName(LPSTR* pFileName) override
9289
{
9390
*pFileName = (LPSTR)CoTaskMemAlloc(1 + strlen(m_fileName));
9491
strcpy(*pFileName, m_fileName);
9592
return S_OK;
9693
}
97-
HRESULT STDMETHODCALLTYPE GetContents(LPSTR* pContents)
94+
HRESULT STDMETHODCALLTYPE GetContents(LPSTR* pContents) override
9895
{
9996
*pContents = (LPSTR)CoTaskMemAlloc(m_length + 1);
10097
memcpy(*pContents, m_contents, m_length + 1);
10198
return S_OK;
10299
}
103-
HRESULT STDMETHODCALLTYPE GetLength(unsigned* pLength)
100+
HRESULT STDMETHODCALLTYPE GetLength(unsigned* pLength) override
104101
{
105102
*pLength = m_length;
106103
return S_OK;

lib/DxcSupport/FileIOHelper.cpp

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,13 @@ struct HeapMalloc : public IMalloc {
4444
STDMETHODIMP QueryInterface(REFIID iid, void** ppvObject) override {
4545
return DoBasicQueryInterface<IMalloc>(this, iid, ppvObject);
4646
}
47-
virtual void *STDMETHODCALLTYPE Alloc (
47+
void *STDMETHODCALLTYPE Alloc (
4848
/* [annotation][in] */
4949
_In_ SIZE_T cb) override {
5050
return HeapAlloc(GetProcessHeap(), 0, cb);
5151
}
5252

53-
virtual void *STDMETHODCALLTYPE Realloc (
53+
void *STDMETHODCALLTYPE Realloc (
5454
/* [annotation][in] */
5555
_In_opt_ void *pv,
5656
/* [annotation][in] */
@@ -59,30 +59,28 @@ struct HeapMalloc : public IMalloc {
5959
return HeapReAlloc(GetProcessHeap(), 0, pv, cb);
6060
}
6161

62-
virtual void STDMETHODCALLTYPE Free (
62+
void STDMETHODCALLTYPE Free (
6363
/* [annotation][in] */
6464
_In_opt_ void *pv) override
6565
{
6666
HeapFree(GetProcessHeap(), 0, pv);
6767
}
6868

69-
70-
virtual SIZE_T STDMETHODCALLTYPE GetSize(
69+
SIZE_T STDMETHODCALLTYPE GetSize(
7170
/* [annotation][in] */
72-
_In_opt_ _Post_writable_byte_size_(return) void *pv)
71+
_In_opt_ _Post_writable_byte_size_(return) void *pv) override
7372
{
7473
return HeapSize(GetProcessHeap(), 0, pv);
7574
}
7675

77-
virtual int STDMETHODCALLTYPE DidAlloc(
76+
int STDMETHODCALLTYPE DidAlloc(
7877
/* [annotation][in] */
79-
_In_opt_ void *pv)
78+
_In_opt_ void *pv) override
8079
{
8180
return -1; // don't know
8281
}
8382

84-
85-
virtual void STDMETHODCALLTYPE HeapMinimize(void) {}
83+
void STDMETHODCALLTYPE HeapMinimize(void) override {}
8684
};
8785

8886
static HeapMalloc g_HeapMalloc;
@@ -321,7 +319,7 @@ class InternalDxcBlobEncoding_Impl : public _T {
321319
ULONG result = (ULONG)--m_dwRef;
322320
if (result == 0) {
323321
CComPtr<IMalloc> pTmp(m_pMalloc);
324-
this->~InternalDxcBlobEncoding_Impl();
322+
this->InternalDxcBlobEncoding_Impl::~InternalDxcBlobEncoding_Impl();
325323
pTmp->Free(this);
326324
}
327325
return result;
@@ -1138,7 +1136,7 @@ class MemoryStream : public AbstractMemoryStream, public IDxcBlob {
11381136
ULONG result = (ULONG)--m_dwRef;
11391137
if (result == 0) {
11401138
CComPtr<IMalloc> pTmp(m_pMalloc);
1141-
this->~MemoryStream();
1139+
this->MemoryStream::~MemoryStream();
11421140
pTmp->Free(this);
11431141
}
11441142
return result;

lib/DxcSupport/WinAdapter.cpp

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,31 +12,6 @@
1212
#include "dxc/Support/WinAdapter.h"
1313
#include "dxc/Support/WinFunctions.h"
1414

15-
//===--------------------------- IUnknown ---------------------------------===//
16-
17-
ULONG IUnknown::AddRef() {
18-
++m_count;
19-
return m_count;
20-
}
21-
ULONG IUnknown::Release() {
22-
ULONG result = --m_count;
23-
if (m_count == 0) {
24-
delete this;
25-
}
26-
return result;
27-
}
28-
IUnknown::~IUnknown() {}
29-
30-
//===--------------------------- IMalloc ----------------------------------===//
31-
32-
void *IMalloc::Alloc(size_t size) { return malloc(size); }
33-
void *IMalloc::Realloc(void *ptr, size_t size) { return realloc(ptr, size); }
34-
void IMalloc::Free(void *ptr) { free(ptr); }
35-
HRESULT IMalloc::QueryInterface(REFIID riid, void **ppvObject) {
36-
assert(false && "QueryInterface not implemented for IMalloc.");
37-
return E_NOINTERFACE;
38-
}
39-
4015
//===--------------------------- CAllocator -------------------------------===//
4116

4217
void *CAllocator::Reallocate(void *p, size_t nBytes) throw() {

lib/DxcSupport/WinFunctions.cpp

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include <unistd.h>
2121

2222
#include "dxc/Support/WinFunctions.h"
23+
#include "dxc/Support/microcom.h"
2324

2425
HRESULT StringCchCopyEx(LPSTR pszDest, size_t cchDest, LPCSTR pszSrc,
2526
LPSTR *ppszDestEnd, size_t *pcchRemaining, DWORD dwFlags) {
@@ -154,8 +155,28 @@ unsigned char _BitScanForward(unsigned long * Index, unsigned long Mask) {
154155
return 1;
155156
}
156157

158+
struct CoMalloc : public IMalloc {
159+
CoMalloc() : m_dwRef(0) {};
160+
161+
DXC_MICROCOM_ADDREF_RELEASE_IMPL(m_dwRef)
162+
STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject) override {
163+
assert(false && "QueryInterface not implemented for CoMalloc.");
164+
return E_NOINTERFACE;
165+
}
166+
167+
void *STDMETHODCALLTYPE Alloc(size_t size) override { return malloc(size); }
168+
void *STDMETHODCALLTYPE Realloc(void *ptr, size_t size) override { return realloc(ptr, size); }
169+
void STDMETHODCALLTYPE Free(void *ptr) override { free(ptr); }
170+
size_t STDMETHODCALLTYPE GetSize(void *pv) override { return -1; }
171+
int STDMETHODCALLTYPE DidAlloc(void *pv) override { return -1; }
172+
void STDMETHODCALLTYPE HeapMinimize(void) override {}
173+
174+
private:
175+
DXC_MICROCOM_REF_FIELD(m_dwRef)
176+
};
177+
157178
HRESULT CoGetMalloc(DWORD dwMemContext, IMalloc **ppMalloc) {
158-
*ppMalloc = new IMalloc;
179+
*ppMalloc = new CoMalloc;
159180
(*ppMalloc)->AddRef();
160181
return S_OK;
161182
}

tools/clang/tools/libclang/dxcisenseimpl.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -581,7 +581,9 @@ HRESULT DxcBasicUnsavedFile::Create(
581581
HRESULT hr = newValue->Initialize(fileName, contents, contentLength);
582582
if (FAILED(hr))
583583
{
584-
delete newValue;
584+
CComPtr<IMalloc> pTmp(newValue->m_pMalloc);
585+
newValue->DxcBasicUnsavedFile::~DxcBasicUnsavedFile();
586+
pTmp->Free(newValue);
585587
return hr;
586588
}
587589
newValue->AddRef();

tools/clang/unittests/HLSL/CompilerTest.cpp

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3038,17 +3038,17 @@ struct InstrumentedHeapMalloc : public IMalloc {
30383038
m_FailAlloc = index;
30393039
}
30403040

3041-
ULONG STDMETHODCALLTYPE AddRef() {
3041+
ULONG STDMETHODCALLTYPE AddRef() override {
30423042
return ++m_RefCount;
30433043
}
3044-
ULONG STDMETHODCALLTYPE Release() {
3044+
ULONG STDMETHODCALLTYPE Release() override {
30453045
if (m_RefCount == 0) VERIFY_FAIL();
30463046
return --m_RefCount;
30473047
}
3048-
STDMETHODIMP QueryInterface(REFIID iid, void** ppvObject) {
3048+
STDMETHODIMP QueryInterface(REFIID iid, void** ppvObject) override {
30493049
return DoBasicQueryInterface<IMalloc>(this, iid, ppvObject);
30503050
}
3051-
virtual void *STDMETHODCALLTYPE Alloc(_In_ SIZE_T cb) {
3051+
virtual void *STDMETHODCALLTYPE Alloc(_In_ SIZE_T cb) override {
30523052
++m_AllocCount;
30533053
if (m_FailAlloc && m_AllocCount >= m_FailAlloc) {
30543054
return nullptr; // breakpoint for i failure - m_FailAlloc == 1+VAL
@@ -3072,7 +3072,7 @@ struct InstrumentedHeapMalloc : public IMalloc {
30723072
return P + 1;
30733073
}
30743074

3075-
virtual void *STDMETHODCALLTYPE Realloc(_In_opt_ void *pv, _In_ SIZE_T cb) {
3075+
virtual void *STDMETHODCALLTYPE Realloc(_In_opt_ void *pv, _In_ SIZE_T cb) override {
30763076
SIZE_T priorSize = pv == nullptr ? (SIZE_T)0 : GetSize(pv);
30773077
void *R = Alloc(cb);
30783078
if (!R)
@@ -3083,7 +3083,7 @@ struct InstrumentedHeapMalloc : public IMalloc {
30833083
return R;
30843084
}
30853085

3086-
virtual void STDMETHODCALLTYPE Free(_In_opt_ void *pv) {
3086+
virtual void STDMETHODCALLTYPE Free(_In_opt_ void *pv) override {
30873087
if (!pv)
30883088
return;
30893089
PtrData *P = DataFromPtr(pv);
@@ -3101,18 +3101,18 @@ struct InstrumentedHeapMalloc : public IMalloc {
31013101

31023102
virtual SIZE_T STDMETHODCALLTYPE GetSize(
31033103
/* [annotation][in] */
3104-
_In_opt_ _Post_writable_byte_size_(return) void *pv)
3104+
_In_opt_ _Post_writable_byte_size_(return) void *pv) override
31053105
{
31063106
if (pv == nullptr) return 0;
31073107
return DataFromPtr(pv)->Size;
31083108
}
31093109

31103110
virtual int STDMETHODCALLTYPE DidAlloc(
3111-
_In_opt_ void *pv) {
3111+
_In_opt_ void *pv) override {
31123112
return -1; // don't know
31133113
}
31143114

3115-
virtual void STDMETHODCALLTYPE HeapMinimize(void) {}
3115+
virtual void STDMETHODCALLTYPE HeapMinimize(void) override {}
31163116

31173117
void DumpLeaks() {
31183118
PtrData *ptr = (PtrData*)AllocList.Flink;;

0 commit comments

Comments
 (0)