这是 XE4 版本中引入的错误,在 XE6 中仍然存在。以前的版本很好。看起来所做的更改是为将来切换到不可变字符串做好准备。
具有讽刺意味的是,该错误是由根本没有为字符串分配值引起的。着手不改变字符串是一回事,但从不初始化它又是另一回事!
所以到bug的分析。 TPerlRegEx.EscapeRegExChars 中有问题的方法在 System.RegularExpressionsCore 单元中定义。这是一个返回字符串的类函数。它的签名是:
class function EscapeRegExChars(const S: string): string;
XE4 实现只对结果变量进行一次引用。如下:
Result.Create(Tmp, 0, J);
这里,Tmp 是一个包含要返回的转义文本的 char 数组,J 是该文本的长度。
因此,作者显然打算将此代码分配给函数返回变量Result。可悲的是,这并没有发生。为什么不?好吧,被调用的Create 方法是在string 的帮助器中定义的。这是在System.SysUtils 单元中定义的TStringHelper。有三个Create 重载,这里起作用的是:
class function Create(const Value: array of Char; StartIndex: Integer;
Length: Integer): string; overload; static;
请注意,这是一个类静态函数。这意味着它不是实例方法并且没有Self 指针。所以当这样调用时:
Result.Create(Tmp, 0, J);
它只是一个函数调用,其返回值被忽略。可能会设置结果变量,但请记住这个Create 是一个类静态方法。因此它没有实例。编译器只使用Result 的类型来解析该方法。代码相当于:
string.Create(Tmp, 0, J);
没有什么比调用一个返回值被忽略的函数更令人兴奋的了。被允许我们忽略函数返回值的扩展语法打败了。
对代码的修复很简单。将最后一行替换为
Result := string.Create(Tmp, 0, J);
您可以在该单元的副本中应用修复,并将该单元包含在您的代码中。我首选的替代方法是使用代码挂钩。像这样:
unit FixTPerlRegExEscapeRegExChars;
interface
implementation
uses
System.SysUtils, Winapi.Windows, System.RegularExpressionsCore;
procedure PatchCode(Address: Pointer; const NewCode; Size: Integer);
var
OldProtect: DWORD;
begin
if VirtualProtect(Address, Size, PAGE_EXECUTE_READWRITE, OldProtect) then
begin
Move(NewCode, Address^, Size);
FlushInstructionCache(GetCurrentProcess, Address, Size);
VirtualProtect(Address, Size, OldProtect, @OldProtect);
end;
end;
type
PInstruction = ^TInstruction;
TInstruction = packed record
Opcode: Byte;
Offset: Integer;
end;
procedure RedirectProcedure(OldAddress, NewAddress: Pointer);
var
NewCode: TInstruction;
begin
NewCode.Opcode := $E9;//jump relative
NewCode.Offset := NativeInt(NewAddress)-NativeInt(OldAddress)-SizeOf(NewCode);
PatchCode(OldAddress, NewCode, SizeOf(NewCode));
end;
function EscapeRegExChars(Self: TPerlRegEx; const S: string): string;
var
I, J: Integer;
Tmp: TCharArray;
begin
SetLength(Tmp, S.Length * 2);
J := 0;
for I := Low(S) to High(S) do
begin
case S[I] of
'.', '[', ']', '(', ')', '?', '*', '+', '{', '}', '^', '$', '|', '\':
begin
Tmp[J] := '\';
Inc(j);
Tmp[J] := S[I];
end;
#0:
begin
Tmp[J] := '\';
Inc(j);
Tmp[J] := '0';
end;
else
Tmp[J] := S[I];
end;
Inc(J);
end;
Result := string.Create(Tmp, 0, J);
end;
initialization
RedirectProcedure(@TPerlRegEx.EscapeRegExChars, @EscapeRegExChars);
end.
将此单元添加到您的项目中,对TPerlRegEx.EscapeRegExChars 的调用将再次开始工作。
{$APPTYPE CONSOLE}
uses
System.RegularExpressionsCore,
FixTPerlRegExEscapeRegExChars in 'FixTPerlRegExEscapeRegExChars.pas';
begin
Writeln(TPerlRegEx.EscapeRegExChars('test'));
Readln;
end.
输出
测试
QC#124091