diff --git a/setup/version.h b/setup/version.h index 57c65139..d3137733 100644 --- a/setup/version.h +++ b/setup/version.h @@ -1,9 +1,9 @@ -#define VLDVERSION L"2.5.5" -#define VERSION_NUMBER 2,5,5,0 -#define VERSION_STRING "2.5.5.0" +#define VLDVERSION L"2.5.6" +#define VERSION_NUMBER 2,5,6,0 +#define VERSION_STRING "2.5.6.0" #define VERSION_COPYRIGHT "Copyright (C) 2005-2020" #ifndef __FILE__ -!define VLD_VERSION "2.5.5" // NSIS Script +!define VLD_VERSION "2.5.6" // NSIS Script #endif diff --git a/setup/vld-setup.iss b/setup/vld-setup.iss index 0732776f..378b2148 100644 --- a/setup/vld-setup.iss +++ b/setup/vld-setup.iss @@ -2,7 +2,7 @@ ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! #define MyAppName "Visual Leak Detector" -#define MyAppVersion "2.5.5" +#define MyAppVersion "2.5.6" #define MyAppPublisher "VLD Team" #define MyAppURL "http://vld.codeplex.com/" #define MyAppRegKey "Software\Visual Leak Detector" diff --git a/src/callstack.cpp b/src/callstack.cpp index ecd2f05e..d2cf6ae6 100644 --- a/src/callstack.cpp +++ b/src/callstack.cpp @@ -546,8 +546,26 @@ UINT CallStack::isCrtStartupFunction( LPCWSTR functionName ) const } if (endWith(functionName, len, L"DllMainCRTStartup") - || endWith(functionName, len, L"mainCRTStartup") - || beginWith(functionName, len, L"`dynamic initializer for '")) { + || endWith(functionName, len, L"mainCRTStartup") + + // NOTE: This is tricky... + // This happens for c++ static initialization + // In some cases, we will see + // "namespace::`dynamic initializer for 'symbol'" + // In other cases, there is no namespace prepended, even if the symbol is in a namespace: + // "`dynamic initializer for 'namespace::symbol'" + // This happens above initterm in the stack, which means these statics can be ignored by the code above if they have the namespace + + // Ideally, we would ignore dynamic initializers if we (somehow) know there is also a matching "`dynamic atexit destructor for 'symbol'" + // It's possible to use some kind of heuristic to detect this (the stack will have ..., classname::classname, dynamic initializer for, initterm, ...) + // That means that the caller really needs a state machine as we are doing some context-sensitive parsing + + // For now, we just make the (possibly wrong) assumption that all dynamic initializations will be cleaned up + // Therefore, the following line is commented out. + // Clearly wrong when we have a global written as "static void* foo = malloc(1);" + // But we can look at a more complex fix if we need to handle that + //|| beginWith(functionName, len, L"`dynamic initializer for '") + ) { // When we reach this point there is no reason going further down the stack return CALLSTACK_STATUS_NOTSTARTUPCRT; } diff --git a/src/runtests.bat b/src/runtests.bat index b4c4fbef..a7058fc5 100644 --- a/src/runtests.bat +++ b/src/runtests.bat @@ -30,6 +30,10 @@ ECHO --------------------------------------------------------------------------- ECHO [ RUNNING ] %tests_path%\vld_main_test.exe ECHO ------------------------------------------------------------------------------- !tests_path!\vld_main_test.exe --gtest_output="xml:!tests_path!\vld_main_test.exe.xml" +ECHO ------------------------------------------------------------------------------- +ECHO [ RUNNING ] %tests_path%\static_string_test.exe +ECHO ------------------------------------------------------------------------------- +!tests_path!\static_string_test.exe --gtest_output="xml:!tests_path!\static_string_test.exe.xml" ECHO ------------------------------------------------------------------------------- EXIT /b 0 diff --git a/src/tests/static_string_test/static_string.h b/src/tests/static_string_test/static_string.h new file mode 100644 index 00000000..e93c6d62 --- /dev/null +++ b/src/tests/static_string_test/static_string.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +namespace my_string +{ + const std::string the_string("foobar"); +} + +const std::string string_global("xyz1234567"); \ No newline at end of file diff --git a/src/tests/static_string_test/static_string_test.cpp b/src/tests/static_string_test/static_string_test.cpp new file mode 100644 index 00000000..e5735cfb --- /dev/null +++ b/src/tests/static_string_test/static_string_test.cpp @@ -0,0 +1,35 @@ +// static_string_test.cpp : Defines the entry point for the console application. +// + +#include "stdafx.h" +#include "vld.h" +#include "static_string.h" + +void access_strings() +{ + // Just do something with the string so it isn't optimized away + std::string copied_string = my_string::the_string; + printf("Copied string %s\n", copied_string.c_str()); + + std::string copied_string2 = string_global; + printf("Copied string %s\n", copied_string2.c_str()); +} + +int main(int argc, char **argv) +{ + access_strings(); + + int leaks = static_cast(VLDGetLeaksCount()); + if (0 != leaks) + { + printf("!!! FAILED - Leaks detected: %i\n", leaks); + VLDReportLeaks(); + } + else + { + printf("PASSED\n"); + } + + + return leaks; +} diff --git a/src/tests/static_string_test/static_string_test.vcxproj b/src/tests/static_string_test/static_string_test.vcxproj new file mode 100644 index 00000000..ac658864 --- /dev/null +++ b/src/tests/static_string_test/static_string_test.vcxproj @@ -0,0 +1,343 @@ + + + + + Debug(Release)_StaticCrt + Win32 + + + Debug(Release)_StaticCrt + x64 + + + Debug(Release) + Win32 + + + Debug(Release) + x64 + + + Debug_StaticCrt + Win32 + + + Debug_StaticCrt + x64 + + + Debug + Win32 + + + Debug + x64 + + + Release_StaticCrt + Win32 + + + Release_StaticCrt + x64 + + + Release + Win32 + + + Release + x64 + + + + {530901ED-6D5A-4BCF-9925-66F5DD51C610} + Win32Proj + static_string_test + static_string_test + 10.0.19041.0 + + + + Application + Unicode + v142 + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + false + true + + + false + true + + + false + true + + + false + true + + + + + + + + + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + + Use + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + ProgramDatabase + + + Console + true + + + + + Use + Level3 + Disabled + STATIC_CRT;WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + ProgramDatabase + MultiThreadedDebug + + + Console + true + + + + + Use + Level3 + Disabled + ProgramDatabase + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + + + + + Use + Level3 + Disabled + STATIC_CRT;WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + ProgramDatabase + MultiThreadedDebug + + + Console + true + + + + + Use + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + ProgramDatabase + + + Console + true + + + + + Use + Level3 + Disabled + STATIC_CRT;WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + ProgramDatabase + MultiThreadedDebug + + + Console + true + + + + + Use + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + ProgramDatabase + + + Console + true + + + + + Use + Level3 + Disabled + STATIC_CRT;WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + ProgramDatabase + MultiThreadedDebug + + + Console + true + + + + + Level3 + Use + MaxSpeed + true + true + VLD_FORCE_ENABLE;WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + true + true + + + + + Level3 + Use + MaxSpeed + true + true + STATIC_CRT;VLD_FORCE_ENABLE;WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + MultiThreaded + + + Console + true + true + true + + + + + Level3 + Use + MaxSpeed + true + true + VLD_FORCE_ENABLE;WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + true + true + + + + + Level3 + Use + MaxSpeed + true + true + STATIC_CRT;VLD_FORCE_ENABLE;WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + MultiThreaded + + + Console + true + true + true + + + + + + + + + + + Create + + + + + {c8f6c172-56f2-4e76-b5fa-c3b423b31be7} + + + + + + \ No newline at end of file diff --git a/src/tests/static_string_test/static_string_test.vcxproj.filters b/src/tests/static_string_test/static_string_test.vcxproj.filters new file mode 100644 index 00000000..2f7606fc --- /dev/null +++ b/src/tests/static_string_test/static_string_test.vcxproj.filters @@ -0,0 +1,36 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/src/tests/static_string_test/stdafx.cpp b/src/tests/static_string_test/stdafx.cpp new file mode 100644 index 00000000..27f0ee01 --- /dev/null +++ b/src/tests/static_string_test/stdafx.cpp @@ -0,0 +1,8 @@ +// stdafx.cpp : source file that includes just the standard includes +// basics.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + +// TODO: reference any additional headers you need in STDAFX.H +// and not in this file diff --git a/src/tests/static_string_test/stdafx.h b/src/tests/static_string_test/stdafx.h new file mode 100644 index 00000000..b005a839 --- /dev/null +++ b/src/tests/static_string_test/stdafx.h @@ -0,0 +1,15 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#pragma once + +#include "targetver.h" + +#include +#include + + + +// TODO: reference additional headers your program requires here diff --git a/src/tests/static_string_test/targetver.h b/src/tests/static_string_test/targetver.h new file mode 100644 index 00000000..87c0086d --- /dev/null +++ b/src/tests/static_string_test/targetver.h @@ -0,0 +1,8 @@ +#pragma once + +// Including SDKDDKVer.h defines the highest available Windows platform. + +// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and +// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. + +#include diff --git a/src/tests/vld_dll1/dllmain.cpp b/src/tests/vld_dll1/dllmain.cpp index 5a1ee8c1..7158e09d 100644 --- a/src/tests/vld_dll1/dllmain.cpp +++ b/src/tests/vld_dll1/dllmain.cpp @@ -2,6 +2,12 @@ #include "stdafx.h" #include #include + +#include + +// Here we static initialize a string within the DLL +// Previous versions of VLD would flag this as a leak, make sure it does not +static std::string my_string("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, diff --git a/src/tests/vld_main/vld_main.cpp b/src/tests/vld_main/vld_main.cpp index cd22b6c6..9f8a9e03 100644 --- a/src/tests/vld_main/vld_main.cpp +++ b/src/tests/vld_main/vld_main.cpp @@ -4,11 +4,13 @@ #include "stdafx.h" #include #include -#include +#include +#include #define VLD_FORCE_ENABLE #include +static std::string str("my_string"); // 10 class MemoryLeak { public: @@ -22,9 +24,7 @@ static void* s_m = malloc(10); // 1 static char* s_n = new char[20]; // 2 static MemoryLeak* pml = new MemoryLeak(70); // 3: leaks a new pointer and malloc(70) -#if _MSC_VER > 1700 static MemoryLeak ml{ 80 }; // *should* be freed and not report as a memory leak -#endif void* g_m = malloc(30); // 6 char* g_n = new char[40]; // 7 @@ -49,11 +49,21 @@ int Test() // debug CRT allocation header. std::cout << "Test: cout"; //std::cerr << "Test: cerr"; - + + // NOTE: This test is a bit weird due to the fix we made in callstack.cpp + // All allocations that happen before main (anything static or global allocated in its initialization) + // will be ignored. + // This means that 2 leaks will be reported (8,9 above) + + // Really, we should report all leaks except 4,5,10 (which have known destructors that we have to assume will "do the right thing") + // Which means we would want the leak count to be reported as 7 + + // The note below is old (but relevant for historical purposes): // At this point VLDGetLeaksCount() and VLDReportLeaks() should report 9 leaks // including a leak for ml which has not been freed yet. ml will be freed after // _tmain exits but before VLDReportLeaks() is called internally by VLD and - // therefore correctly report 8 leaks. + // therefore correctly report 8 leaks. + int leaks = VLDGetLeaksCount(); VLDReportLeaks(); // at this point should report 9 leaks; return leaks; diff --git a/src/tests/vld_main_test/vld_main_test.cpp b/src/tests/vld_main_test/vld_main_test.cpp index 95d1da18..2c5ff5ff 100644 --- a/src/tests/vld_main_test/vld_main_test.cpp +++ b/src/tests/vld_main_test/vld_main_test.cpp @@ -34,11 +34,7 @@ TEST(TestWinMain, RunExe) // Close the handles. CloseHandle(processInformation.hProcess); CloseHandle(processInformation.hThread); -#if _MSC_VER > 1700 - ASSERT_EQ(9, exitCode); -#else - ASSERT_EQ(8, exitCode); -#endif + ASSERT_EQ(2, exitCode); } int _tmain(int argc, _TCHAR* argv[]) diff --git a/src/tests/vld_unload/vld_unload.cpp b/src/tests/vld_unload/vld_unload.cpp index 0bb99dac..e6455743 100644 --- a/src/tests/vld_unload/vld_unload.cpp +++ b/src/tests/vld_unload/vld_unload.cpp @@ -29,6 +29,31 @@ UINT VLDGetLeaksCount() return -1; } +UINT VLDReportLeaks() +{ + HMODULE vld_module = GetModuleHandle(sVld_dll); + if (vld_module != NULL) + { + typedef UINT(*VLDAPI_func)(); + VLDAPI_func func = (VLDAPI_func)GetProcAddress(vld_module, "VLDReportLeaks"); + assert(func); + if (func) + { + return func(); + } + } + return -1; +} + +void ExpectLeakCount(int expected, int actual) +{ + if (expected != actual) + { + VLDReportLeaks(); + } + EXPECT_EQ(expected, actual); +} + HMODULE GetModuleFromAddress(LPCVOID pAddress) { HMODULE hModule = NULL; @@ -46,17 +71,17 @@ TEST(TestUnloadDlls, Sequence1) ASSERT_EQ(NULL, GetModuleHandle(sVld_dll)); HMODULE hModule1 = ::LoadLibrary(_T("vld_dll1.dll")); int w = VLDGetLeaksCount(); // vld is loaded and counts 1 memory leak - EXPECT_EQ(1, w); + ExpectLeakCount(1, w); ::FreeLibrary(hModule1); // vld is unloaded here and reports the memory leak int x = VLDGetLeaksCount(); // vld is unloaded and cannot count any memory leaks - EXPECT_EQ(-1, x); + ExpectLeakCount(-1, x); HMODULE hModule2 = ::LoadLibrary(_T("vld_dll2.dll")); int y = VLDGetLeaksCount(); // vld is loaded and counts 1 memory leak - EXPECT_EQ(1, y); + ExpectLeakCount(1, y); ::FreeLibrary(hModule2); // vld is unloaded here and reports the memory leak int z = VLDGetLeaksCount(); // vld is unloaded and cannot count any memory leaks - EXPECT_EQ(-1, z); + ExpectLeakCount(-1, z); } TEST(TestUnloadDlls, Sequence2) @@ -64,24 +89,16 @@ TEST(TestUnloadDlls, Sequence2) ASSERT_EQ(NULL, GetModuleHandle(sVld_dll)); HMODULE hModule3 = ::LoadLibrary(_T("vld_dll1.dll")); int w = VLDGetLeaksCount(); // vld is loaded and counts 1 memory leak - EXPECT_EQ(1, w); + ExpectLeakCount(1, w); HMODULE hModule4 = ::LoadLibrary(_T("vld_dll2.dll")); int x = VLDGetLeaksCount(); // vld is still loaded and counts 2 memory leaks - EXPECT_EQ(2, x); + ExpectLeakCount(2, x); ::FreeLibrary(hModule4); // vld is *not* unloaded here int y = VLDGetLeaksCount(); -#if _MSC_VER <= 1600 && !defined(_DLL) // VS 2010 and bellow - // The reason for reporting 1 leak at this point is that the vld_dll1 module build with