Bug in bidi dict inverse SetItem code

Issue #318 resolved
David Heffernan created an issue

Since we introduced the new ordered bidi dict, there has been a bug in the inverse dict SetItem code.

When compiled against b686db5 (for example) this program demonstrates the issue:

program SpringBidiInverseDictBug;

{$APPTYPE CONSOLE}

uses
  System.SysUtils,
  Spring.Collections;

procedure Main;
var
  i: Integer;
  dict: IBidiDictionary<string, Integer>;
  inv: IBidiDictionary<Integer, string>;
begin
  dict := TCollections.CreateBidiDictionary<string, Integer>;
  inv := dict.Inverse;

  for i := 1 to 100 do
    inv.Add(i, IntToStr(i));

  for i := 1 to 100 do
    inv[i] := 'foo' + IntToStr(i);

  for i := 1 to 100 do
    if not inv.ContainsKey(i) then
    begin
      Writeln(i, 'failed');
      Exit;
    end;

  Writeln('passed');
end;

begin
  try
    Main;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  Readln;
end.

This should output passed, but instead an assertion failure is triggered.

EAssertionFailed: Assertion failure (C:\Development_Miscellaneous\spring4d\Source\Base\Collections\Spring.Collections.Dictionaries.pas, line 1238)

The defect is in TBidiDictionary<TKey, TValue>.DoSetKey. This line

fValueBuckets[oldKeyBucketIndex] := UsedBucket;

should be

fKeyBuckets[oldKeyBucketIndex] := UsedBucket;

I’m sure this mistake was down to me originally. Sorry!

Comments (2)

  1. Stefan Glienke repo owner

    Since we introduced the new ordered bidi dict, there has been a bug in the inverse dict SetItem code.

    When compiled against b686db5 (for example) this program demonstrates the issue:

    program SpringBidiInverseDictBug;
    
    {$APPTYPE CONSOLE}
    
    uses
      System.SysUtils,
      Spring.Collections;
    
    procedure Main;
    var
      i: Integer;
      dict: IBidiDictionary<string, Integer>;
      inv: IBidiDictionary<Integer, string>;
    begin
      dict := TCollections.CreateBidiDictionary<string, Integer>;
      inv := dict.Inverse;
    
      for i := 1 to 100 do
        inv.Add(i, IntToStr(i));
    
      for i := 1 to 100 do
        inv[i] := 'foo' + IntToStr(i);
    
      for i := 1 to 100 do
        if not inv.ContainsKey(i) then
        begin
          Writeln(i, 'failed');
          Exit;
        end;
    
      Writeln('passed');
    end;
    
    begin
      try
        Main;
      except
        on E: Exception do
          Writeln(E.ClassName, ': ', E.Message);
      end;
      Readln;
    end.
    

    This should output passed, but instead an assertion failure is triggered.

    EAssertionFailed: Assertion failure (C:\Development_Miscellaneous\spring4d\Source\Base\Collections\Spring.Collections.Dictionaries.pas, line 1238)

    The defect is in TBidiDictionary<TKey, TValue>.DoSetKey. This line

    fValueBuckets[oldKeyBucketIndex] := UsedBucket;
    

    should be

    fKeyBuckets[oldKeyBucketIndex] := UsedBucket;
    

    I’m sure this mistake was down to me originally. Sorry!

  2. Log in to comment