Jump to content
David Heffernan

Tool for finding non-literal format strings

Recommended Posts

I've recently come across a defect in my code that looks like this:

 

msg := Format('%s' + foo, [bar]);

If foo contains any format placeholders, e.g. %s, %20, etc. then this will result in an exception being raised.

 

It's a stupid mistake, but now I want to check my entire codebase to see we've done it elsewhere.

 

Ideally I'd like a static tool that detects any call to Format or common equivalent like Exception.CreateFmt for which the format string is not a literal. I don't think FixInsight has such a warning.

 

Does anybody know of such a tool?

Share this post


Link to post

That would mean, that this tool "compiles" the code and is able to calculate the contents of the variable "foo".

This can be from millions of different complex calculations and sources and even user input.
I doubt that such tool will exists, but perhaps a Pascal parser together with a Pascal scripter may be able to simulate the codeflow and catch this.

 

Maybe its easier, if you replace/mock the Format() and try to catch or log this error before it is raised.

 

 

Edited by Rollo62

Share this post


Link to post
25 minutes ago, Rollo62 said:

That would mean, that this tool "compiles" the code and is able to calculate the contents of the variable "foo".

This can be from millions of different complex calculations and sources and even user input.
I doubt that such tool will exists, but perhaps a Pascal parser together with a Pascal scripter may be able to simulate the codeflow and catch this.

 

Maybe its easier, if you replace/mock the Format() and try to catch or log this error before it is raised.

 

 

No, I am just looking for a tool that detects non literal args by static analysis, which is what I wrote

Share this post


Link to post

Copilot.

Quote

Write a console utility in Delphi that will scan the specified folder with subfolders, open files with the PAS extension, and select all strings starting with "Format(" and ending with "])". Among the strings found, you need to select those that contain the "+" character before the "," character, and save these strings in the text file "format.log".

The result:

program FormatStringExtractor;

uses
  SysUtils, Classes;

function ContainsPlusBeforeComma(const S: string): Boolean;
var
  PlusPos, CommaPos: Integer;
begin
  PlusPos := Pos('+', S);
  CommaPos := Pos(',', S);
  Result := (PlusPos > 0) and (CommaPos > PlusPos);
end;

procedure ProcessFile(const FileName: string; LogFile: TStrings);
var
  Lines: TStringList;
  Line, SubStr: string;
  StartPos, EndPos: Integer;
begin
  Lines := TStringList.Create;
  try
    Lines.LoadFromFile(FileName);
    for Line in Lines do
    begin
      StartPos := Pos('Format(', Line);
      while StartPos > 0 do
      begin
        EndPos := PosEx('])', Line, StartPos);
        if EndPos > 0 then
        begin
          SubStr := Copy(Line, StartPos, EndPos - StartPos + 2);
          if ContainsPlusBeforeComma(SubStr) then
            LogFile.Add(SubStr);
          // Move the start position forward to find the next occurrence
          StartPos := PosEx('Format(', Line, EndPos + 2);
        end
        else
          Break;
      end;
    end;
  finally
    Lines.Free;
  end;
end;

procedure ProcessFolder(const Folder: string; LogFile: TStrings);
var
  SearchRec: TSearchRec;
  FullPath: string;
begin
  if FindFirst(Folder + '\*.*', faAnyFile, SearchRec) = 0 then
  begin
    repeat
      FullPath := Folder + '\' + SearchRec.Name;
      if (SearchRec.Attr and faDirectory) = faDirectory then
      begin
        if (SearchRec.Name <> '.') and (SearchRec.Name <> '..') then
          ProcessFolder(FullPath, LogFile);
      end
      else if ExtractFileExt(SearchRec.Name).ToLower = '.pas' then
      begin
        ProcessFile(FullPath, LogFile);
      end;
    until FindNext(SearchRec) <> 0;
    FindClose(SearchRec);
  end;
end;

var
  Folder: string;
  LogFile: TStringList;
begin
  Write('Enter folder path to scan: ');
  ReadLn(Folder);

  LogFile := TStringList.Create;
  try
    ProcessFolder(Folder, LogFile);
    LogFile.SaveToFile('format.log');
    WriteLn('Strings saved to format.log');
  finally
    LogFile.Free;
  end;
end.

 

Edited by Kryvich

Share this post


Link to post
1 hour ago, David Heffernan said:

I know how to code a solution. And I'd use a proper parser to do it. But I'm looking for a tool. 

Definitely. But time is money. If Copilot can do it for you - why not?

1 hour ago, David Heffernan said:

Doesn't look like it handles code laid out over multiple lines. 

for Line in Lines do   -->  Line := Lines.Text;

Share this post


Link to post
5 hours ago, Rollo62 said:

That would mean, that this tool "compiles" the code and is able to calculate the contents of the variable "foo".

Or, more simply, just detect a function call involving an "array of const" argument preceded by a string argument, and then warn if the string argument is not a literal.

  • Like 1

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×