From 60ac3a0ffa87387b1c4447b7982dc3eb101e033e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Fri, 11 Sep 2026 00:03:03 -0300 Subject: [PATCH] Fixed the Unicode-path crash and misleading AI Assistant teardown fault (SpartanJ/ecode#953). - Windows executable paths now always use GetModuleFileNameW(), with dynamic long-path support. - Sys::getProcessPath() derives directly from the UTF-8 executable path. - Windows working-directory APIs now use wide-character functions under both MinGW and MSVC. - AI Assistant teardown checks that the editor splitter exists during partial initialization. - Added process-path and Unicode working-directory regression tests using EXPECT_STDSTREQ. --- src/eepp/system/filesystem.cpp | 30 ++++----- src/eepp/system/sys.cpp | 61 ++++++------------- src/tests/unit_tests/systempath_tests.cpp | 47 ++++++++++++++ .../plugins/aiassistant/aiassistantplugin.cpp | 3 +- 4 files changed, 80 insertions(+), 61 deletions(-) create mode 100644 src/tests/unit_tests/systempath_tests.cpp diff --git a/src/eepp/system/filesystem.cpp b/src/eepp/system/filesystem.cpp index ce3980f61..ae5c40762 100644 --- a/src/eepp/system/filesystem.cpp +++ b/src/eepp/system/filesystem.cpp @@ -655,12 +655,8 @@ std::string FileSystem::sizeToString( const Int64& Size ) { bool FileSystem::changeWorkingDirectory( const std::string& path ) { int res; -#ifdef EE_COMPILER_MSVC -#ifdef UNICODE - res = _wchdir( String::fromUtf8( path ).toWideString().c_str() ); -#else - res = _chdir( String::fromUtf8( path ).toAnsiString().c_str() ); -#endif +#if EE_PLATFORM == EE_PLATFORM_WIN + res = SetCurrentDirectoryW( String::fromUtf8( path ).toWideString().c_str() ) ? 0 : -1; #else res = chdir( path.c_str() ); #endif @@ -668,19 +664,19 @@ bool FileSystem::changeWorkingDirectory( const std::string& path ) { } std::string FileSystem::getCurrentWorkingDirectory() { -#ifdef EE_COMPILER_MSVC -#if defined( UNICODE ) && !defined( EE_NO_WIDECHAR ) - wchar_t dir[_MAX_PATH]; - return ( 0 != GetCurrentDirectoryW( _MAX_PATH, dir ) ) ? String( dir ).toUtf8() : std::string(); -#else - char dir[_MAX_PATH]; - return ( 0 != GetCurrentDirectory( _MAX_PATH, dir ) ) ? String( dir, std::locale() ).toUtf8() - : std::string(); -#endif +#if EE_PLATFORM == EE_PLATFORM_WIN + DWORD size = GetCurrentDirectoryW( 0, nullptr ); + if ( size == 0 ) + return {}; + std::wstring dir( size, 0 ); + DWORD length = GetCurrentDirectoryW( size, &dir[0] ); + if ( length == 0 || length >= size ) + return {}; + dir.resize( length ); + return String( dir ).toUtf8(); #else char dir[PATH_MAX + 1]; - getcwd( dir, PATH_MAX + 1 ); - return std::string( dir ); + return getcwd( dir, PATH_MAX + 1 ) != nullptr ? std::string( dir ) : std::string(); #endif } diff --git a/src/eepp/system/sys.cpp b/src/eepp/system/sys.cpp index 26321ede6..6a6e00f4c 100644 --- a/src/eepp/system/sys.cpp +++ b/src/eepp/system/sys.cpp @@ -627,44 +627,7 @@ static std::string sGetProcessPath() { return std::string( dirname( exe_file ) ) + "/"; } #elif EE_PLATFORM == EE_PLATFORM_WIN -#ifdef UNICODE - // Get path to executable: - char szDrive[_MAX_DRIVE]; - char szDir[_MAX_DIR]; - char szFilename[_MAX_DIR]; - char szExt[_MAX_DIR]; - std::wstring dllName( _MAX_DIR, 0 ); - - GetModuleFileName( 0, &dllName[0], _MAX_PATH ); - - std::string dllstrName( String( dllName ).toUtf8() ); - -#ifdef EE_COMPILER_MSVC - _splitpath_s( dllstrName.c_str(), szDrive, _MAX_DRIVE, szDir, _MAX_DIR, szFilename, _MAX_DIR, - szExt, _MAX_DIR ); -#else - _splitpath( dllstrName.c_str(), szDrive, szDir, szFilename, szExt ); -#endif - - return std::string( szDrive ) + std::string( szDir ); -#else - // Get path to executable: - TCHAR szDllName[_MAX_PATH]; - TCHAR szDrive[_MAX_DRIVE]; - TCHAR szDir[_MAX_DIR]; - TCHAR szFilename[_MAX_DIR]; - TCHAR szExt[_MAX_DIR]; - GetModuleFileName( 0, szDllName, _MAX_PATH ); - -#ifdef EE_COMPILER_MSVC - _splitpath_s( szDllName, szDrive, _MAX_DRIVE, szDir, _MAX_DIR, szFilename, _MAX_DIR, szExt, - _MAX_DIR ); -#else - _splitpath( szDllName, szDrive, szDir, szFilename, szExt ); -#endif - - return std::string( szDrive ) + std::string( szDir ); -#endif + return FileSystem::fileRemoveFileName( Sys::getProcessFilePath() ); #elif EE_PLATFORM == EE_PLATFORM_BSD int mib[4]; mib[0] = CTL_KERN; @@ -1393,11 +1356,23 @@ std::string Sys::getProcessFilePath() { #endif #if EE_PLATFORM == EE_PLATFORM_WIN - std::wstring exename( _MAX_DIR, 0 ); - DWORD size = GetModuleFileNameW( 0, &exename[0], _MAX_PATH ); - if ( size > 0 && size < _MAX_PATH ) - exename.resize( size ); // Resize to actual size without extra null characters - return String( exename ).toUtf8(); + // Windows paths are UTF-16. Using GetModuleFileNameA here makes executable paths containing + // characters outside the active ANSI code page unusable (for example, CJK install paths). + DWORD capacity = _MAX_PATH; + while ( capacity <= 32768 ) { + std::wstring exename( capacity, 0 ); + DWORD size = GetModuleFileNameW( nullptr, &exename[0], capacity ); + if ( size == 0 ) + return {}; + if ( size < capacity ) { + exename.resize( size ); + return String( exename ).toUtf8(); + } + if ( capacity == 32768 ) + break; + capacity = eemin( capacity * 2, 32768 ); + } + return {}; #elif EE_PLATFORM == EE_PLATFORM_LINUX || EE_PLATFORM == EE_PLATFORM_ANDROID char path[] = "/proc/self/exe"; ssize_t len = readlink( path, exename, PATH_MAX - 1 ); diff --git a/src/tests/unit_tests/systempath_tests.cpp b/src/tests/unit_tests/systempath_tests.cpp new file mode 100644 index 000000000..335640684 --- /dev/null +++ b/src/tests/unit_tests/systempath_tests.cpp @@ -0,0 +1,47 @@ +#include "utest.hpp" + +#include +#include + +using namespace EE; +using namespace EE::System; + +namespace { + +struct TemporaryWorkingDirectory { + TemporaryWorkingDirectory() : originalPath( FileSystem::getCurrentWorkingDirectory() ) { + path = Sys::getTempPath(); + FileSystem::dirAddSlashAtEnd( path ); + path += "eepp-path-路径-" + std::to_string( Sys::getProcessID() ) + "-" + + std::to_string( Sys::getTicks() ); + created = FileSystem::makeDir( path ); + changed = created && FileSystem::changeWorkingDirectory( path ); + } + + ~TemporaryWorkingDirectory() { + FileSystem::changeWorkingDirectory( originalPath ); + if ( created ) + FileSystem::dirRemoveAll( path ); + } + + std::string originalPath; + std::string path; + bool created{ false }; + bool changed{ false }; +}; + +} // namespace + +UTEST( SystemPath, processPathMatchesExecutablePath ) { + const std::string executablePath( Sys::getProcessFilePath() ); + ASSERT_FALSE( executablePath.empty() ); + EXPECT_TRUE( FileSystem::fileExists( executablePath ) ); + EXPECT_STDSTREQ( FileSystem::fileRemoveFileName( executablePath ), Sys::getProcessPath() ); +} + +UTEST( SystemPath, workingDirectoryRoundTripsUnicode ) { + TemporaryWorkingDirectory temp; + ASSERT_TRUE( temp.created ); + ASSERT_TRUE( temp.changed ); + EXPECT_STDSTREQ( temp.path, FileSystem::getCurrentWorkingDirectory() ); +} diff --git a/src/tools/ecode/plugins/aiassistant/aiassistantplugin.cpp b/src/tools/ecode/plugins/aiassistant/aiassistantplugin.cpp index 1a23e63c0..80b480d5b 100644 --- a/src/tools/ecode/plugins/aiassistant/aiassistantplugin.cpp +++ b/src/tools/ecode/plugins/aiassistant/aiassistantplugin.cpp @@ -276,7 +276,8 @@ AIAssistantPlugin::~AIAssistantPlugin() { mModelCatalog->cancel(); mModelCatalog.reset(); } - if ( SceneManager::existsSingleton() && !SceneManager::instance()->isShuttingDown() ) { + if ( SceneManager::existsSingleton() && !SceneManager::instance()->isShuttingDown() && + getPluginContext() && getPluginContext()->getSplitter() ) { getPluginContext()->getSplitter()->forEachWidgetClass( "llm_chatui", []( UIWidget* widget ) { LLMChatUI* chat = static_cast( widget );