Skip to content

Syntax Highlighter for Delphi runs into infinite loops for certain strings #293

@csm101

Description

@csm101

this test case reproduces the issue. the bug is related to the parsing of the new syntax for multiline strings.
I have alredy issued a pull request that fixes this bug and another one about a NativeInt variable whose pointer is passed to an api that expects pointers to 32 bit integers regardless of the architecture

unit TestSynHighlighterDelphi;

interface

uses
  DUnitX.TestFramework,
  SynHighlighterDelphi;

type
  [TestFixture]
  TTestSynDelphiSynInfiniteLoop = class
  private
    procedure AssertLineScansWithoutHang(AHighlighter: TSynDelphiSyn;
      const ALine: string; ALineNumber: Integer);
  public
    [Test] procedure Next_MultilineStringRange_AdvancesRunToEol;
    [Test] procedure Next_EmptyLineInMultilineStringRange_AdvancesRunToEol;
    [Test] procedure Next_MultilineStringDelimiterLine_DoesNotHang;
    [Test] procedure Next_AnnotateStyleLineWithTripleQuote_AdvancesRunToEol;
  end;

implementation

uses
  System.SysUtils;

procedure TTestSynDelphiSynInfiniteLoop.AssertLineScansWithoutHang(
  AHighlighter: TSynDelphiSyn; const ALine: string; ALineNumber: Integer);
const
  MAX_ITERATIONS = 100000;
var
  LIterations: Integer;
  LPrevTokenPos: NativeInt;
begin
  AHighlighter.SetLine(ALine, ALineNumber);
  LIterations := 0;

  while not AHighlighter.GetEol do
  begin
    LPrevTokenPos := AHighlighter.GetTokenPos;
    AHighlighter.Next;
    Inc(LIterations);

    if (AHighlighter.GetTokenPos = LPrevTokenPos) and not AHighlighter.GetEol then
      Assert.Fail(Format('Tokenizer stopped advancing at line %d. TokenPos=%d, Text="%s"',
        [ALineNumber, LPrevTokenPos, ALine]));

    if LIterations > MAX_ITERATIONS then
      Assert.Fail(Format('Potential infinite loop at line %d. Iterations=%d, TokenPos=%d, Len=%d',
        [ALineNumber, LIterations, AHighlighter.GetTokenPos, Length(ALine)]));
  end;
end;

procedure TTestSynDelphiSynInfiniteLoop.Next_MultilineStringRange_AdvancesRunToEol;
var
  LHighlighter: TSynDelphiSyn;
begin
  LHighlighter := TSynDelphiSyn.Create(nil);
  try
    LHighlighter.ResetRange;
    AssertLineScansWithoutHang(LHighlighter, 'x := ' + #39#39#39, 0);
    AssertLineScansWithoutHang(LHighlighter, 'body line', 1);
  finally
    LHighlighter.Free;
  end;
end;

procedure TTestSynDelphiSynInfiniteLoop.Next_EmptyLineInMultilineStringRange_AdvancesRunToEol;
var
  LHighlighter: TSynDelphiSyn;
begin
  LHighlighter := TSynDelphiSyn.Create(nil);
  try
    LHighlighter.ResetRange;
    AssertLineScansWithoutHang(LHighlighter, 'x := ' + #39#39#39, 0);
    AssertLineScansWithoutHang(LHighlighter, '', 1);
  finally
    LHighlighter.Free;
  end;
end;

procedure TTestSynDelphiSynInfiniteLoop.Next_MultilineStringDelimiterLine_DoesNotHang;
var
  LHighlighter: TSynDelphiSyn;
begin
  LHighlighter := TSynDelphiSyn.Create(nil);
  try
    LHighlighter.ResetRange;
    AssertLineScansWithoutHang(LHighlighter, 'x := ' + #39#39#39, 0);
    AssertLineScansWithoutHang(LHighlighter, 'body line', 1);
    AssertLineScansWithoutHang(LHighlighter, #39#39#39, 2);
    Assert.AreEqual<NativeUInt>(NativeUInt(Pointer(rsMultilineString)),
      NativeUInt(LHighlighter.GetRange));
  finally
    LHighlighter.Free;
  end;
end;

procedure TTestSynDelphiSynInfiniteLoop.Next_AnnotateStyleLineWithTripleQuote_AdvancesRunToEol;
var
  LHighlighter: TSynDelphiSyn;
begin
  LHighlighter := TSynDelphiSyn.Create(nil);
  try
    LHighlighter.ResetRange;
    AssertLineScansWithoutHang(LHighlighter,
      '1.10     (user   01-Jan-24): S := ' + #39#39#39, 0);
    AssertLineScansWithoutHang(LHighlighter,
      '1.10     (user   01-Jan-24): hello world', 1);
  finally
    LHighlighter.Free;
  end;
end;

end.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions