Can I call static private class method with class helper?

前端 未结 2 832
猫巷女王i
猫巷女王i 2021-01-06 10:34

In particular, I feel the need in TCharacter.IsLatin1 which is private.

type
  TCharacterHelper = class helper for TCharacter
  pu         


        
2条回答
  •  忘掉有多难
    2021-01-06 11:18

    See update below

    As is widely known, helpers do crack private visibility. So, private members are visible from a class helper. However, this behaviour does not extend to static members, so TCharacter.IsLatin1 is inaccessible (by fair means) outside the unit in which it is declared.

    What about unfair means? Well, some public methods of TCharacter do call IsLatin1. And even though IsLatin1 is declared inline, it seems that these methods are compiled with call statements rather than the code inlined. Perhaps that's because they calls occur in the same unit, or the same type, and the inline engine is not capable of inlining.

    Anyway, where I am going with this is that you could, at runtime, disassemble one of these calls. For sake of argument, let's consider IsControl:

    class function TCharacter.IsControl(C: Char): Boolean;
    begin
      if IsLatin1(C) then
        Result := InternalGetLatin1Category(C) = TUnicodeCategory.ucControl
      else
        Result := InternalGetUnicodeCategory(UCS4Char(C)) = TUnicodeCategory.ucControl;
    end;
    

    Its first act is to call IsLatin1. The compiled code looks like this:

    System.Character.pas.517: 
    00411135 C3               ret 
    00411136 8BC0             mov eax,eax
    TCharacter.IsControl:
    00411138 53               push ebx
    00411139 8BD8             mov ebx,eax
    System.Character.pas.533: 
    0041113B 8BC3             mov eax,ebx
    0041113D E852FFFFFF       call TCharacter.IsLatin1
    00411142 84C0             test al,al
    00411144 740F             jz $00411155
    

    So, you could do the following:

    1. Take the address of TCharacter.IsControl.
    2. Disassemble the code at that address until you found the first call instruction.
    3. Decode that call instruction to find the target address, and that's where IsLatin1 can be found.

    I'm not remotely advocating this for IsLatin1. It's such a simple function, and not subject to change, that it's surely better to re-implement it. But for more complex situations, this method can be used.

    And I'm also not claiming originality. I learnt this technique from the madExcept source code.


    OK, @LU RD resourcefully found a way to prove me wrong. Congratulations for that. What I said about static methods is accurate, however, @LU RD used a very adept trick of introducing a non-static class method and by that way crack the private members.

    I'd like to take his answer a bit further by showing how to use two helpers to expose the functionality using the original name:

    unit CharacterCracker;
    
    interface
    
    uses
      System.Character;
    
    type
      TCharacterHelper = class helper for TCharacter
      public
        class function IsLatin1(C: Char): Boolean; static; inline;
      end;
    
    implementation
    
    type
      TCharacterCracker = class helper for TCharacter
      public
        class function IsLatin1Cracker(C: Char): Boolean; inline;
      end;
    
    class function TCharacterCracker.IsLatin1Cracker(C: Char): Boolean;
    begin
      Result := TCharacter.IsLatin1(C); // resolves to the original method
    end;
    
    class function TCharacterHelper.IsLatin1(C: Char): Boolean;
    begin
      Result := TCharacter.IsLatin1Cracker(C);
    end;
    
    end.
    

    You can use this unit, and the only helper that is active outside the unit is the one declared in the interface section. Which means you can write code like this:

    {$APPTYPE CONSOLE}
    
    uses
      System.Character,
      CharacterCracker in 'CharacterCracker.pas';
    
    var
      c: Char;
    
    begin
      c := #42;
      Writeln(TCharacter.IsLatin1(c));
      c := #666;
      Writeln(TCharacter.IsLatin1(c));
      Readln;
    end.
    

提交回复
热议问题