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.
This commit is contained in:
Martín Lucas Golini
2026-09-11 00:03:03 -03:00
parent 3effffd16f
commit 60ac3a0ffa
4 changed files with 80 additions and 61 deletions

View File

@@ -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
}

View File

@@ -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<DWORD>( 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 );

View File

@@ -0,0 +1,47 @@
#include "utest.hpp"
#include <eepp/system/filesystem.hpp>
#include <eepp/system/sys.hpp>
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() );
}

View File

@@ -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<LLMChatUI*>( widget );