diff --git a/Code/Framework/AzCore/AzCore/Debug/StackTracer.h b/Code/Framework/AzCore/AzCore/Debug/StackTracer.h index 430fd69168..2d09c16e99 100644 --- a/Code/Framework/AzCore/AzCore/Debug/StackTracer.h +++ b/Code/Framework/AzCore/AzCore/Debug/StackTracer.h @@ -40,6 +40,12 @@ namespace AZ static unsigned int Record(StackFrame* frames, unsigned int maxNumOfFrames, unsigned int suppressCount = 0, void* nativeThread = 0); }; + class StackConverter + { + public: + static unsigned int FromNative(StackFrame* frames, unsigned int maxNumOfFrames, void* nativeContext); + }; + class SymbolStorage { public: diff --git a/Code/Framework/AzCore/AzCore/Debug/Trace.cpp b/Code/Framework/AzCore/AzCore/Debug/Trace.cpp index 3edcbaa273..cde8c36a4e 100644 --- a/Code/Framework/AzCore/AzCore/Debug/Trace.cpp +++ b/Code/Framework/AzCore/AzCore/Debug/Trace.cpp @@ -31,6 +31,8 @@ namespace AZ { namespace Debug { + struct StackFrame; + namespace Platform { #if defined(AZ_ENABLE_DEBUG_TOOLS) @@ -551,17 +553,19 @@ namespace AZ { StackFrame frames[25]; - // Without StackFrame explicit alignment frames array is aligned to 4 bytes - // which causes the stack tracing to fail. - //size_t bla = AZStd::alignment_of::value; - //printf("Alignment value %d address 0x%08x : 0x%08x\n",bla,frames); SymbolStorage::StackLine lines[AZ_ARRAY_SIZE(frames)]; + unsigned int numFrames = 0; if (!nativeContext) { - suppressCount += 1; /// If we don't provide a context we will capture in the RecordFunction, so skip us (Trace::PrinCallstack). + suppressCount += 1; /// If we don't provide a context we will capture in the RecordFunction, so skip us (Trace::PrintCallstack). + numFrames = StackRecorder::Record(frames, AZ_ARRAY_SIZE(frames), suppressCount); } - unsigned int numFrames = StackRecorder::Record(frames, AZ_ARRAY_SIZE(frames), suppressCount, nativeContext); + else + { + numFrames = StackConverter::FromNative(frames, AZ_ARRAY_SIZE(frames), nativeContext); + } + if (numFrames) { SymbolStorage::DecodeFrames(frames, numFrames, lines); diff --git a/Code/Framework/AzCore/Platform/Common/Unimplemented/AzCore/Debug/StackTracer_Unimplemented.cpp b/Code/Framework/AzCore/Platform/Common/Unimplemented/AzCore/Debug/StackTracer_Unimplemented.cpp index 0c091a7242..a751eb505c 100644 --- a/Code/Framework/AzCore/Platform/Common/Unimplemented/AzCore/Debug/StackTracer_Unimplemented.cpp +++ b/Code/Framework/AzCore/Platform/Common/Unimplemented/AzCore/Debug/StackTracer_Unimplemented.cpp @@ -17,6 +17,11 @@ namespace AZ return false; } + unsigned int StackConverter::FromNative(StackFrame*, unsigned int, void*) + { + return 0; + } + void SymbolStorage::LoadModuleData(const void*, unsigned int) {} diff --git a/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/Debug/StackTracer_UnixLike.cpp b/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/Debug/StackTracer_UnixLike.cpp index f66a9d3b18..1948bbf2fe 100644 --- a/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/Debug/StackTracer_UnixLike.cpp +++ b/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/Debug/StackTracer_UnixLike.cpp @@ -78,6 +78,12 @@ StackRecorder::Record(StackFrame* frames, unsigned int maxNumOfFrames, unsigned return count; } +unsigned int StackConverter::FromNative([[maybe_unused]] StackFrame* frames, [[maybe_unused]] unsigned int maxNumOfFrames, [[maybe_unused]] void* nativeContext) +{ + AZ_Assert(false, "StackConverter::FromNative() is not supported for UnixLike platform yet"); + return 0; +} + void SymbolStorage::DecodeFrames(const StackFrame* frames, unsigned int numFrames, StackLine* textLines) { diff --git a/Code/Framework/AzCore/Platform/Windows/AzCore/Debug/StackTracer_Windows.cpp b/Code/Framework/AzCore/Platform/Windows/AzCore/Debug/StackTracer_Windows.cpp index b2a1410bf5..90362a3ce8 100644 --- a/Code/Framework/AzCore/Platform/Windows/AzCore/Debug/StackTracer_Windows.cpp +++ b/Code/Framework/AzCore/Platform/Windows/AzCore/Debug/StackTracer_Windows.cpp @@ -1048,9 +1048,9 @@ cleanup: unsigned int StackRecorder::Record(StackFrame* frames, unsigned int maxNumOfFrames, unsigned int suppressCount, void* nativeThread) { -#if defined(AZ_ENABLE_DEBUG_TOOLS) unsigned int numFrames = 0; +#if defined(AZ_ENABLE_DEBUG_TOOLS) if (nativeThread == NULL) { ++suppressCount; // Skip current call @@ -1079,9 +1079,8 @@ cleanup: STACKFRAME64 sf; memset(&sf, 0, sizeof(STACKFRAME64)); - DWORD imageType; + DWORD imageType = IMAGE_FILE_MACHINE_AMD64; - imageType = IMAGE_FILE_MACHINE_AMD64; sf.AddrPC.Offset = context.Rip; sf.AddrPC.Mode = AddrModeFlat; sf.AddrFrame.Offset = context.Rsp; @@ -1090,8 +1089,7 @@ cleanup: sf.AddrStack.Mode = AddrModeFlat; EnterCriticalSection(&g_csDbgHelpDll); - s32 frame = -(s32)suppressCount; - for (; frame < (s32)maxNumOfFrames; ++frame) + for (s32 frame = -static_cast(suppressCount); frame < static_cast(maxNumOfFrames); ++frame) { if (!g_StackWalk64(imageType, g_currentProcess, hThread, &sf, &context, 0, g_SymFunctionTableAccess64, g_SymGetModuleBase64, 0)) { @@ -1111,15 +1109,68 @@ cleanup: } LeaveCriticalSection(&g_csDbgHelpDll); - } - return numFrames; + } #else - (void)frames; - (void)maxNumOfFrames; - (void)suppressCount; - (void)nativeThread; - return 0; + AZ_UNUSED(frames); + AZ_UNUSED(maxNumOfFrames); + AZ_UNUSED(suppressCount); + AZ_UNUSED(nativeThread); #endif // AZ_ENABLE_DEBUG_TOOLS + + return numFrames; + } + + unsigned int StackConverter::FromNative(StackFrame* frames, unsigned int maxNumOfFrames, void* nativeContext) + { + unsigned int numFrames = 0; + +#if defined(AZ_ENABLE_DEBUG_TOOLS) + if (!g_dbgHelpLoaded) + { + LoadDbgHelp(); + } + + HANDLE hThread; + DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &hThread, 0, false, DUPLICATE_SAME_ACCESS); + + PCONTEXT nativeContextType = reinterpret_cast(nativeContext); + STACKFRAME64 sf; + memset(&sf, 0, sizeof(STACKFRAME64)); + + DWORD imageType = IMAGE_FILE_MACHINE_AMD64; + + sf.AddrPC.Offset = nativeContextType->Rip; + sf.AddrPC.Mode = AddrModeFlat; + sf.AddrFrame.Offset = nativeContextType->Rsp; + sf.AddrFrame.Mode = AddrModeFlat; + sf.AddrStack.Offset = nativeContextType->Rsp; + sf.AddrStack.Mode = AddrModeFlat; + + EnterCriticalSection(&g_csDbgHelpDll); + for (unsigned int frame = 0; frame < maxNumOfFrames; ++frame) + { + if (!g_StackWalk64(imageType, g_currentProcess, hThread, &sf, nativeContext, 0, g_SymFunctionTableAccess64, g_SymGetModuleBase64, 0)) + { + break; + } + + if (sf.AddrPC.Offset == sf.AddrReturn.Offset) + { + // "StackWalk64-Endless-Callstack!" + break; + } + + frames[numFrames++].m_programCounter = sf.AddrPC.Offset; + } + + LeaveCriticalSection(&g_csDbgHelpDll); +#else + AZ_UNUSED(frames); + AZ_UNUSED(maxNumOfFrames); + AZ_UNUSED(nativeContext); +#endif + + return numFrames; } ////////////////////////////////////////////////////////////////////////// diff --git a/Code/Framework/AzCore/Tests/AZStd/Hashed.cpp b/Code/Framework/AzCore/Tests/AZStd/Hashed.cpp index b92dee44b6..c982868c4f 100644 --- a/Code/Framework/AzCore/Tests/AZStd/Hashed.cpp +++ b/Code/Framework/AzCore/Tests/AZStd/Hashed.cpp @@ -1413,7 +1413,7 @@ namespace UnitTest >; TYPED_TEST_CASE(HashedSetDifferentAllocatorFixture, SetTemplateConfigs); -#if GTEST_OS_SUPPORTS_DEATH_TEST +#if GTEST_HAS_DEATH_TEST TYPED_TEST(HashedSetDifferentAllocatorFixture, InsertNodeHandleWithDifferentAllocatorsLogsTraceMessages) { using ContainerType = typename TypeParam::ContainerType; @@ -1435,7 +1435,7 @@ namespace UnitTest } }, ".*"); } -#endif // GTEST_OS_SUPPORTS_DEATH_TEST +#endif // GTEST_HAS_DEATH_TEST template class HashedMapContainers @@ -1811,7 +1811,7 @@ namespace UnitTest >; TYPED_TEST_CASE(HashedMapDifferentAllocatorFixture, MapTemplateConfigs); -#if GTEST_OS_SUPPORTS_DEATH_TEST +#if GTEST_HAS_DEATH_TEST TYPED_TEST(HashedMapDifferentAllocatorFixture, InsertNodeHandleWithDifferentAllocatorsLogsTraceMessages) { using ContainerType = typename TypeParam::ContainerType; @@ -1833,7 +1833,7 @@ namespace UnitTest } } , ".*"); } -#endif // GTEST_OS_SUPPORTS_DEATH_TEST +#endif // GTEST_HAS_DEATH_TEST namespace HashedContainerTransparentTestInternal { diff --git a/Code/Framework/AzCore/Tests/AZStd/Ordered.cpp b/Code/Framework/AzCore/Tests/AZStd/Ordered.cpp index ca7d5cba42..26838eeb63 100644 --- a/Code/Framework/AzCore/Tests/AZStd/Ordered.cpp +++ b/Code/Framework/AzCore/Tests/AZStd/Ordered.cpp @@ -1095,7 +1095,7 @@ namespace UnitTest >; TYPED_TEST_CASE(TreeSetDifferentAllocatorFixture, SetTemplateConfigs); -#if GTEST_OS_SUPPORTS_DEATH_TEST +#if GTEST_HAS_DEATH_TEST TYPED_TEST(TreeSetDifferentAllocatorFixture, InsertNodeHandleWithDifferentAllocatorsLogsTraceMessages) { using ContainerType = typename TypeParam::ContainerType; @@ -1117,7 +1117,7 @@ namespace UnitTest } }, ".*"); } -#endif // GTEST_OS_SUPPORTS_DEATH_TEST +#endif // GTEST_HAS_DEATH_TEST TYPED_TEST(TreeSetDifferentAllocatorFixture, SwapMovesElementsWhenAllocatorsDiffer) { @@ -1516,7 +1516,7 @@ namespace UnitTest >; TYPED_TEST_CASE(TreeMapDifferentAllocatorFixture, MapTemplateConfigs); -#if GTEST_OS_SUPPORTS_DEATH_TEST +#if GTEST_HAS_DEATH_TEST TYPED_TEST(TreeMapDifferentAllocatorFixture, InsertNodeHandleWithDifferentAllocatorsLogsTraceMessages) { using ContainerType = typename TypeParam::ContainerType; @@ -1538,7 +1538,7 @@ namespace UnitTest } }, ".*"); } -#endif // GTEST_OS_SUPPORTS_DEATH_TEST +#endif // GTEST_HAS_DEATH_TEST TYPED_TEST(TreeMapDifferentAllocatorFixture, SwapMovesElementsWhenAllocatorsDiffer) { diff --git a/Code/Framework/AzCore/Tests/AZStd/Parallel.cpp b/Code/Framework/AzCore/Tests/AZStd/Parallel.cpp index 407cd3c258..6b9202133c 100644 --- a/Code/Framework/AzCore/Tests/AZStd/Parallel.cpp +++ b/Code/Framework/AzCore/Tests/AZStd/Parallel.cpp @@ -1595,7 +1595,7 @@ namespace UnitTest } }; -#if GTEST_OS_SUPPORTS_DEATH_TEST +#if GTEST_HAS_DEATH_TEST TEST_F(ThreadEventsDeathTest, UsingClientBus_AvoidsDeadlock) { EXPECT_EXIT( @@ -1608,5 +1608,5 @@ namespace UnitTest , ::testing::ExitedWithCode(0),".*"); } -#endif // GTEST_OS_SUPPORTS_DEATH_TEST +#endif // GTEST_HAS_DEATH_TEST } diff --git a/Code/Framework/AzCore/Tests/Debug/UnhandledExceptions.cpp b/Code/Framework/AzCore/Tests/Debug/UnhandledExceptions.cpp new file mode 100644 index 0000000000..cd9055085f --- /dev/null +++ b/Code/Framework/AzCore/Tests/Debug/UnhandledExceptions.cpp @@ -0,0 +1,31 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include + +namespace UnitTest +{ + class UnhandledExceptions + : public ScopedAllocatorSetupFixture + { + + public: + void causeAccessViolation() + { + int* someVariable = reinterpret_cast(0); + *someVariable = 0; + } + }; + +#if GTEST_HAS_DEATH_TEST + TEST_F(UnhandledExceptions, Handle) + { + EXPECT_DEATH(causeAccessViolation(), ""); + } +#endif +} diff --git a/Code/Framework/AzCore/Tests/Memory/LeakDetection.cpp b/Code/Framework/AzCore/Tests/Memory/LeakDetection.cpp index b4f129bb48..09255ce16f 100644 --- a/Code/Framework/AzCore/Tests/Memory/LeakDetection.cpp +++ b/Code/Framework/AzCore/Tests/Memory/LeakDetection.cpp @@ -144,14 +144,13 @@ namespace UnitTest } }; -#if GTEST_OS_SUPPORTS_DEATH_TEST - // SPEC-2669: Disabled since it is causing hangs on Linux +#if GTEST_HAS_DEATH_TEST TEST_F(AllocatorsTestFixtureLeakDetectionDeathTest_SKIPCODECOVERAGE, AllocatorLeak) { // testing that the TraceBusHook will fail on cause the test to die EXPECT_DEATH(TestAllocatorLeak(), ""); } -#endif // GTEST_OS_SUPPORTS_DEATH_TEST +#endif // GTEST_HAS_DEATH_TEST //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Testing ScopedAllocatorSetupFixture. Testing that detects leaks diff --git a/Code/Framework/AzCore/Tests/Serialization/Json/JsonRegistrationContextTests.cpp b/Code/Framework/AzCore/Tests/Serialization/Json/JsonRegistrationContextTests.cpp index cc4d31eca9..9d4af1def5 100644 --- a/Code/Framework/AzCore/Tests/Serialization/Json/JsonRegistrationContextTests.cpp +++ b/Code/Framework/AzCore/Tests/Serialization/Json/JsonRegistrationContextTests.cpp @@ -327,7 +327,7 @@ namespace JsonSerializationTests SerializerWithOneType::Unreflect(m_jsonRegistrationContext.get()); } -#if GTEST_OS_SUPPORTS_DEATH_TEST +#if GTEST_HAS_DEATH_TEST using JsonSerializationDeathTests = JsonRegistrationContextTests; TEST_F(JsonSerializationDeathTests, DoubleUnregisterSerializer_Asserts) { @@ -338,5 +338,6 @@ namespace JsonSerializationTests }, ".*" ); } -#endif // GTEST_OS_SUPPORTS_DEATH_TEST +#endif // GTEST_HAS_DEATH_TEST + } //namespace JsonSerializationTests diff --git a/Code/Framework/AzCore/Tests/azcoretests_files.cmake b/Code/Framework/AzCore/Tests/azcoretests_files.cmake index 6ba9944f7d..cc6000209f 100644 --- a/Code/Framework/AzCore/Tests/azcoretests_files.cmake +++ b/Code/Framework/AzCore/Tests/azcoretests_files.cmake @@ -72,6 +72,7 @@ set(FILES Debug/AssetTracking.cpp Debug/LocalFileEventLoggerTests.cpp Debug/Trace.cpp + Debug/UnhandledExceptions.cpp Name/NameJsonSerializerTests.cpp Name/NameTests.cpp RTTI/TypeSafeIntegralTests.cpp diff --git a/Code/Framework/AzTest/AzTest/AzTest.cpp b/Code/Framework/AzTest/AzTest/AzTest.cpp index eede87b29e..85e7f7e6b3 100644 --- a/Code/Framework/AzTest/AzTest/AzTest.cpp +++ b/Code/Framework/AzTest/AzTest/AzTest.cpp @@ -92,10 +92,14 @@ namespace AZ void ApplyGlobalParameters(int* argc, char** argv) { - // this is a hook that can be used to apply any other global non-google parameters - // that we use. + // this is a hook that can be used to apply any other global parameters that we use. AZ_UNUSED(argc); AZ_UNUSED(argv); + + // Disable gtest catching unhandled exceptions, instead, AzTestRunner will do it through: + // AZ::Debug::Trace::HandleExceptions(true). This gives us a stack trace when the exception + // is thrown (googletest does not). + testing::FLAGS_gtest_catch_exceptions = false; } //! Print out parameters that are not used by the framework diff --git a/Code/Tools/AssetBundler/tests/tests_main.cpp b/Code/Tools/AssetBundler/tests/tests_main.cpp index 51dd0ce67c..21a6bf8a6f 100644 --- a/Code/Tools/AssetBundler/tests/tests_main.cpp +++ b/Code/Tools/AssetBundler/tests/tests_main.cpp @@ -291,6 +291,9 @@ namespace AssetBundler int main(int argc, char* argv[]) { + AZ::Debug::Trace::HandleExceptions(true); + AZ::Test::ApplyGlobalParameters(&argc, argv); + INVOKE_AZ_UNIT_TEST_MAIN(); AZ::AllocatorInstance::Create(); diff --git a/Code/Tools/AssetProcessor/native/tests/SourceFileRelocatorTests.cpp b/Code/Tools/AssetProcessor/native/tests/SourceFileRelocatorTests.cpp index 4b70f94120..d7801abb3d 100644 --- a/Code/Tools/AssetProcessor/native/tests/SourceFileRelocatorTests.cpp +++ b/Code/Tools/AssetProcessor/native/tests/SourceFileRelocatorTests.cpp @@ -262,7 +262,7 @@ namespace UnitTests auto result = m_data->m_reporter->ComputeDestination(entryContainer, m_data->m_platformConfig.GetScanFolderByPath(scanFolderEntry.m_scanFolder.c_str()), source, destination, destInfo); - ASSERT_EQ(result.IsSuccess(), expectSuccess) << result.GetError().c_str(); + ASSERT_EQ(result.IsSuccess(), expectSuccess) << (!result.IsSuccess() ? result.GetError().c_str() : ""); if (expectSuccess) { diff --git a/Code/Tools/AssetProcessor/native/tests/test_main.cpp b/Code/Tools/AssetProcessor/native/tests/test_main.cpp index 57f14832aa..e0cdc8cb3c 100644 --- a/Code/Tools/AssetProcessor/native/tests/test_main.cpp +++ b/Code/Tools/AssetProcessor/native/tests/test_main.cpp @@ -29,6 +29,9 @@ int main(int argc, char* argv[]) { qputenv("QT_MAC_DISABLE_FOREGROUND_APPLICATION_TRANSFORM", "1"); + AZ::Debug::Trace::HandleExceptions(true); + AZ::Test::ApplyGlobalParameters(&argc, argv); + // If "--unittest" is present on the command line, run unit testing // and return immediately. Otherwise, continue as normal. AZ::Test::addTestEnvironment(new BaseAssetProcessorTestEnvironment()); diff --git a/Code/Tools/AzTestRunner/src/main.cpp b/Code/Tools/AzTestRunner/src/main.cpp index 346b4e3b53..acb208d3e5 100644 --- a/Code/Tools/AzTestRunner/src/main.cpp +++ b/Code/Tools/AzTestRunner/src/main.cpp @@ -189,6 +189,8 @@ namespace AzTestRunner int wrapped_main(int argc/*=0*/, char** argv/*=nullptr*/) { + AZ::Debug::Trace::HandleExceptions(true); + if (argc>0 && argv!=nullptr) { return wrapped_command_arg_main(argc, argv); diff --git a/Code/Tools/ProjectManager/tests/main.cpp b/Code/Tools/ProjectManager/tests/main.cpp index 3dd2fb75df..c666b7ffd0 100644 --- a/Code/Tools/ProjectManager/tests/main.cpp +++ b/Code/Tools/ProjectManager/tests/main.cpp @@ -18,6 +18,9 @@ int runDefaultRunner(int argc, char* argv[]) int main(int argc, char* argv[]) { + AZ::Debug::Trace::HandleExceptions(true); + AZ::Test::ApplyGlobalParameters(&argc, argv); + if (argc == 1) { // if no parameters are provided, add the --unittests parameter diff --git a/Code/Tools/PythonBindingsExample/tests/TestMain.cpp b/Code/Tools/PythonBindingsExample/tests/TestMain.cpp index 7399055ca5..4c0dddecad 100644 --- a/Code/Tools/PythonBindingsExample/tests/TestMain.cpp +++ b/Code/Tools/PythonBindingsExample/tests/TestMain.cpp @@ -23,6 +23,9 @@ int runDefaultRunner(int argc, char* argv[]) int main(int argc, char* argv[]) { + AZ::Debug::Trace::HandleExceptions(true); + AZ::Test::ApplyGlobalParameters(&argc, argv); + // ran with no parameters? if (argc == 1) { diff --git a/Gems/WhiteBox/Code/Source/Viewport/WhiteBoxManipulatorBounds.cpp b/Gems/WhiteBox/Code/Source/Viewport/WhiteBoxManipulatorBounds.cpp index 878ede5e68..a11f2c9905 100644 --- a/Gems/WhiteBox/Code/Source/Viewport/WhiteBoxManipulatorBounds.cpp +++ b/Gems/WhiteBox/Code/Source/Viewport/WhiteBoxManipulatorBounds.cpp @@ -48,10 +48,10 @@ namespace WhiteBox float time; AZ::Vector3 normal; const float rayLength = 1000.0f; - const int intersected = AZ::Intersect::IntersectSegmentTriangleCCW( + const bool intersected = AZ::Intersect::IntersectSegmentTriangleCCW( rayOrigin, rayOrigin + rayDirection * rayLength, p0, p1, p2, normal, time); - if (intersected != 0) + if (intersected) { rayIntersectionDistance = time * rayLength; intersectedTriangleIndex = triangleIndex / 3;