Delphi的TPerlRegEx.EscapeRegExChars()总是返回一个空字符串?

时间:2014-04-15 14:20:14

标签: delphi delphi-xe4

使用Delphi XE4,请尝试以下代码:

procedure TForm3.Button1Click(Sender: TObject);
var
  myStr: string;
begin
  Edit1.Text := TPerlRegEx.EscapeRegExChars('test');
end;

结果(Edit1.Text)为空。

这是一个错误还是我错过了什么?我以前使用regular-expressions.info之前的DelphiXE版本的TPerlRegEx.EscapeRegExChars函数没问题。

更新2 :只是升级在D2010中编写的应用并遇到此错误,但只是想知道这么长的错误是如何存在的......现在我正在认真考虑制作我的错误代码与 Free Pascal 兼容,但我真的很喜欢这种反义词...

更新1:我使用的是Delphi XE4 Update 1.

2 个答案:

答案 0 :(得分:2)

这似乎是一个错误。如果是这种情况,XE4和XE5版本都包含它。我打开了一个QC report来报告XE4..XE6。

问题似乎与函数的最后一行有关:

Result.Create(Tmp, 0, J);

在调试器中单步执行显示Tmp(一个TCharArray)在此时正确包含't','e','s','t', #0, #0, #0, #0,但当函数实际返回时Result包含'',在该行之后的end;上设置断点表示该点的结果包含''(以及函数返回时)。

在类助手中提供替换版本,并进行微小更改以实际存储从Create调用的返回值,从而解决问题:

type
  TPerlRegExHelper = class helper for TPerlRegEx
  public
    class function EscapeRegExCharsEx(const S: string): string; static;
  end;

class function TPerlRegExHelper.EscapeRegExCharsEx(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.Create(Tmp, 0, J); }  // The problem code from the original
  Result := String.Create(Tmp, 0, J);
end;

XE3(以及你提到的开源版本)完全不同地实现逻辑,使用Result的更标准操作从函数的第一行开始Result := S;,然后使用根据需要System.Insert为转义字符添加空间。

答案 1 :(得分:0)

这是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的帮助器中定义的。这是TStringHelper单位中定义的System.SysUtils。有三个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.

<强>输出

test

QC#124091