Search code examples
delphilazarus

Why BobJenkinsHash function returns range error in this case?


Under FPC Lazarus, I am using the following function to compute the hash of a string, this is used to save the object in a dictionary:

function MyClass.HashCode(): UInt32;
var
  keyStr: string;
begin
  keyStr := KeyToString();
  Result := BobJenkinsHash(keyStr[1], Length(keyStr) * SizeOf(Char), 0);
end;

This function sometimes throws an error:

Project < Name > raised exception class ' RunError(201)' with message:
Range check error

in file < source > at line < Line >:
Result := BobJenkinsHash(keyStr[1], Length(keyStr) * SizeOf(Char), 0);

This happens coherently after some usages. I am not sure if some specific strings or number of usages. For example: "Button_logo" seems to raise this error.

I could not find (unfortunately) relevant documentation about this: I found no documentation for FPC and Delphi documentation is (as most of the time) irrelevant.


Solution

  • I don't know if this applies to FreePascal, but in Delphi the BobJenkinsHash() function (which is deprecated1, BTW) returns a signed Integer:

    function BobJenkinsHash(const Data; Len, InitData: Integer): Integer;
                                                                 ^^^^^^^
    

    You are assigning that return value to an unsigned UInt32. If the function returns a negative value (see Can result of BobJenkinsHash function be negative?), you will get a runtime error if you have Range Checking enabled.

    1 this same issue exists for the function's successor in Delphi, THashBobJenkins.GetHashValue(), too.

    So, you can either:

    • turn off Range Checking. You can use the {$RANGECKECKS ON|OFF}/{$R±} compiler directive (Delphi) to disable Range Checking around the assignment of your UInt32, eg:

      function MyClass.HashCode(): UInt32;
      var
        keyStr: string;
      begin
        keyStr := KeyToString();
        {$IFOPT R+} {$RANGECHECKS OFF} {$DEFINE RC_WAS_ON} {$ENDIF}
        // or: {$IFOPT R+} {$R-} {$DEFINE RC_WAS_ON} {$ENDIF}
        // or: {$PUSH} {$R-} (FPC only)
        Result := BobJenkinsHash(PChar(keyStr)^, Length(keyStr) * SizeOf(Char), 0);
        {$IFDEF RC_WAS_ON} {$RANGECHECKS ON} {$UNDEF RC_WAS_ON} {$ENDIF}
        // or: {$IFDEF RC_WAS_ON} {$R+} {$UNDEF RC_WAS_ON} {$ENDIF}
        // or: {$POP} (FPC only)
      end;
      
    • type-cast the return value of BobJenkinsHash() before assigning it to your UInt32:

      function MyClass.HashCode(): UInt32;
      var
        keyStr: string;
      begin
        keyStr := KeyToString();
        Result := UInt32(BobJenkinsHash(PChar(keyStr)^, Length(keyStr) * SizeOf(Char), 0));
      end;
      
    • change the return type of your HashCode() method to Integer:

      function MyClass.HashCode(): Integer;
      var
        keyStr: string;
      begin
        keyStr := KeyToString();
        Result := BobJenkinsHash(PChar(keyStr)^, Length(keyStr) * SizeOf(Char), 0);
      end;