LifePlay-Revival/Source/LifePlayEditorTools/LPTestStringCompatCommandlet.cpp
edujiro5972 8015f182b9 L7i (CX-B12) : Contains/Split/FindSubstring avec motif vide au comportement 4.27 (385 noeuds, 28 Blueprints) ; ERR-005
FString::Find("" , IgnoreCase, FromStart) renvoie 0 en UE5 et INDEX_NONE en 4.27 : Contains(x, "") devenu vrai.
Test 3120 cas (transcription 4.27) : 0 ecart ; diff independant 385/385 ; compilation et cuisson 0 avertissement.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 19:24:20 +02:00

164 lines
5.6 KiB
C++

// Portage UE 5.5 - voir LPTestStringCompatCommandlet.h
#include "LPTestStringCompatCommandlet.h"
#include "Kismet/KismetStringLibrary.h"
#include "LPStringCompat.h"
#include <wchar.h>
DEFINE_LOG_CATEGORY_STATIC(LogLPTestStringCompat, Log, All);
namespace
{
/** TCString<TCHAR>::Stristr, UE 4.27 (CString.h l. 622-650), transcrit a l'identique. */
const TCHAR* Stristr427(const TCHAR* Str, const TCHAR* Find)
{
if (Find == nullptr || Str == nullptr)
{
return nullptr;
}
TCHAR FindInitial = FChar::ToUpper(*Find);
int32 Length = FCString::Strlen(Find++) - 1;
TCHAR StrChar = *Str++;
while (StrChar)
{
StrChar = FChar::ToUpper(StrChar);
if (StrChar == FindInitial && !FCString::Strnicmp(Str, Find, Length))
{
return Str - 1;
}
StrChar = *Str++;
}
return nullptr;
}
/** FString::Find, UE 4.27 (String.cpp l. 180-227), transcrit a l'identique. */
int32 Find427(const FString& This, const TCHAR* SubStr, ESearchCase::Type SearchCase, ESearchDir::Type SearchDir, int32 StartPosition)
{
if (SubStr == nullptr)
{
return INDEX_NONE;
}
if (SearchDir == ESearchDir::FromStart)
{
const TCHAR* Start = *This;
if (StartPosition != INDEX_NONE && This.Len() > 0)
{
Start += FMath::Clamp(StartPosition, 0, This.Len() - 1);
}
// FCString::Strstr 4.27 sous Windows : FWindowsPlatformString::Strstr = _tcsstr (wcsstr).
const TCHAR* Tmp = SearchCase == ESearchCase::IgnoreCase
? Stristr427(Start, SubStr)
: (const TCHAR*)wcsstr((const wchar_t*)Start, (const wchar_t*)SubStr);
return Tmp ? UE_PTRDIFF_TO_INT32(Tmp - *This) : INDEX_NONE;
}
if (SearchCase == ESearchCase::IgnoreCase)
{
return Find427(This.ToUpper(), *FString(SubStr).ToUpper(), ESearchCase::CaseSensitive, SearchDir, StartPosition);
}
const int32 SearchStringLength = FMath::Max(1, FCString::Strlen(SubStr));
if (StartPosition == INDEX_NONE || StartPosition >= This.Len())
{
StartPosition = This.Len();
}
// En 4.27, (*this)[i+j] pouvait lire le '\0' final sans controle : on lit le tampon brut.
const TCHAR* Data = *This;
for (int32 i = StartPosition - SearchStringLength; i >= 0; i--)
{
int32 j;
for (j = 0; SubStr[j]; j++)
{
if (Data[i + j] != SubStr[j])
{
break;
}
}
if (!SubStr[j])
{
return i;
}
}
return INDEX_NONE;
}
}
ULPTestStringCompatCommandlet::ULPTestStringCompatCommandlet()
{
IsClient = false;
IsServer = false;
IsEditor = true;
LogToConsole = true;
}
int32 ULPTestStringCompatCommandlet::Main(const FString& Params)
{
const TArray<FString> Strings = { TEXT(""), TEXT("a"), TEXT("A"), TEXT("abc"), TEXT("ABC"), TEXT("aXbXc"), TEXT("round, oval"), TEXT(" "), TEXT("teen"), TEXT("Daz") };
const TArray<FString> Subs = { TEXT(""), TEXT("a"), TEXT("A"), TEXT("b"), TEXT("X"), TEXT("bc"), TEXT("abc"), TEXT("abcd"), TEXT(", "), TEXT(" "), TEXT("oval"), TEXT("TEEN"), TEXT("daz") };
const TArray<int32> Starts = { -1, 0, 1, 2, 5, 50 };
int32 Cases = 0, CompatMismatch = 0, EngineMismatch = 0;
for (const FString& S : Strings)
{
for (const FString& Sub : Subs)
{
for (int32 CaseIdx = 0; CaseIdx < 2; ++CaseIdx)
{
for (int32 DirIdx = 0; DirIdx < 2; ++DirIdx)
{
for (const int32 Start : Starts)
{
const ESearchCase::Type Case = CaseIdx ? ESearchCase::CaseSensitive : ESearchCase::IgnoreCase;
const ESearchDir::Type Dir = DirIdx ? ESearchDir::FromEnd : ESearchDir::FromStart;
++Cases;
const int32 Ref = Find427(S, *Sub, Case, Dir, Start);
const int32 Compat = ULPStringCompat::FindUE4(S, Sub, Case, Dir, Start);
const int32 Engine = S.Find(Sub, Case, Dir, Start);
if (Compat != Ref)
{
++CompatMismatch;
UE_LOG(LogLPTestStringCompat, Error, TEXT("COMPAT Find(\"%s\", \"%s\", %s, %s, %d) : 4.27=%d compat=%d"),
*S, *Sub, CaseIdx ? TEXT("Case") : TEXT("Ignore"), DirIdx ? TEXT("FromEnd") : TEXT("FromStart"), Start, Ref, Compat);
}
if (Engine != Ref)
{
++EngineMismatch;
UE_LOG(LogLPTestStringCompat, Display, TEXT("moteur Find(\"%s\", \"%s\", %s, %s, %d) : 4.27=%d 5.5=%d"),
*S, *Sub, CaseIdx ? TEXT("Case") : TEXT("Ignore"), DirIdx ? TEXT("FromEnd") : TEXT("FromStart"), Start, Ref, Engine);
}
// Fonctions Blueprint : uniquement avec la position de depart par defaut.
if (Start == -1)
{
const bool bUseCase = CaseIdx == 1, bFromEnd = DirIdx == 1;
if (ULPStringCompat::Contains(S, Sub, bUseCase, bFromEnd) != (Ref != INDEX_NONE)
|| ULPStringCompat::FindSubstring(S, Sub, bUseCase, bFromEnd) != Ref)
{
++CompatMismatch;
UE_LOG(LogLPTestStringCompat, Error, TEXT("COMPAT Contains/FindSubstring(\"%s\", \"%s\") ecart"), *S, *Sub);
}
FString L1, R1, L2, R2;
const bool B1 = ULPStringCompat::Split(S, Sub, L1, R1, Case, Dir);
const bool B2 = Ref >= 0;
if (B1 != B2 || (B2 && (L1 != S.Left(Ref) || R1 != S.Mid(Ref + Sub.Len()))))
{
++CompatMismatch;
UE_LOG(LogLPTestStringCompat, Error, TEXT("COMPAT Split(\"%s\", \"%s\") ecart"), *S, *Sub);
}
// Cas Blueprint par defaut, moteur 5.5 contre 4.27 : documente l'ecart vise.
if (!bUseCase && !bFromEnd && UKismetStringLibrary::Contains(S, Sub) != (Ref != INDEX_NONE))
{
UE_LOG(LogLPTestStringCompat, Display, TEXT("moteur Contains(\"%s\", \"%s\") : 4.27=%d 5.5=%d"),
*S, *Sub, Ref != INDEX_NONE, UKismetStringLibrary::Contains(S, Sub));
}
}
}
}
}
}
}
UE_LOG(LogLPTestStringCompat, Warning, TEXT("=== %d cas : compat != 4.27 : %d ; moteur 5.5 != 4.27 : %d ==="), Cases, CompatMismatch, EngineMismatch);
return CompatMismatch > 0 ? 1 : 0;
}