View Issue Details

IDProjectCategoryView StatusLast Update
0038387FPCDocumentationpublic2021-01-24 11:51
ReporterPaul Robinson <xdpascal@xdpascal.com> Assigned ToMichael Van Canneyt  
PrioritynormalSeverityminorReproducibilityhave not tried
Status resolvedResolutionfixed 
Product Version3.2.0 
Fixed in Version3.3.1 
Summary0038387: Recommendation on preventing "Crock Recursion" in Macro usage
Description"Crock recursion" is the condition where an entity - such as a program source file, or a compiler directive, such as a compile-time macro - directly or indirectly invokes itself. Section 2.2 of the Free Pascal manual, online at https://www.freepascal.org/docs-html/current/prog/progse5.html gives an example of a macro indirectly calling itself, causing an endless loop, which is the classic definition of crock recursion. Since this problem has been mentioned, I suspect that there has not been discovered a reasonably easy solution. Therefore, I'd like to offer what I think is a potential solution.

Somewhere in the conditional code processor there is probably a record or object with something like this:

TYPE

     DefIneP = ^Define;
     Define = record
         Next: DefineP;
   ... // other things
         Name: String;
         Case isNumber:Boolean of
            False:(Value: String); // if a macro,the replacement text
            True: (Number: Integer); // or numeric value
     end;

So, one simple solution is to add an inuse flag, which is cleared before macro processing, and set when macro processing occurs. There are two ways to solve the problem, either macro recursion is never allowed, or limited macro recursion is allowed. To prevent crock recursion, if limited macro recursion is to be allowed, a constant or initialized variable needs to be created:

CONST
     MaxMacroRecursions = 5; // or whatever you feel is reasonable
{$DEFINE ALLOWMACRORECURSION}
( or )
VAR
     MaxMacroRecursions; integer = 5; // whatever you feel is reasonable
{$DEFINE ALLOWMACRORECURSION}

The second option above allows the limit to be potentially changed. Setting MaxMacroRecursions to 1 is the same as not allowing them.

Next, the Define record or object needs to be modified:


TYPE

     DefIneP = ^Define;
     Define = record
         Next: DefineP;
{$IFDEF ALLOWMACRORECURSION}
         inuse: Integer;
{$ELSE}
         inuse: Boolean;
{$ENDIF)
   ... // other things

         Name: String;
         Case isNumber:Boolean of
            False:(Value: String); // if a macro,the replacement text
            True: (Number: Integer); // or numeric value
     end;

Now, we need to reset the inUse flag before an {$IF/{$ELSEIF or when a macro is first processed:

  // At the start of a test or macro substitution,
  // clear the "inUse" flag on each defined item

  Procedure ClearInUseFlag;
    Var
     P: DefineP;

  begin
      P := DefineBase;
      While P<> Nil do
      begin
{$IFDEF ALLOWMACRORECURSION}
           P^.inUse:= := 0;
{$ELSE}
           P^.inUse:= := False;
{$ENDIF)
           P := P^.Next;
      end;
  end;

("DefineBase" is the pointer to the start of the define list.)

Now, at the start of the processor for {$IF/{$ELSEIF or the macro substitution routine needs to be a call to

ClearInUseFlag;

At each time a macro is to be invoked, the following needs to be added, where "ThisDefine" is a pointer to the current macro that is to be invoked:

{$IFDEF ALLOWMACRORECURSION}
    If ThisDefine^.inUse > MaxMacroRecursions then
{$ELSE}
    If ThisDefine^.inUse then
{$ENDIF)
   BEGIN // Error: Crock recursion (recursion too deep)
  ...
    END
    ELSE
     BEGIN
{$IFDEF ALLOWMACRORECURSION}
            ThisDefine^.inUse := True;
{$ELSE}
            Inc( ThisDefine^.inUse );
{$ENDIF)
      // process this macro
   ...
      END

I believe this provides a reasonable, and simple solution to the problem of crock recursion. I hope you will consider adding it. I think anything the compiler can do to catch programmer error is a good idea.

Thank you for your time.

Paul "Remember: Only YOU can prevent forest fires, err I mean crock recursion" Robinson
TagsNo tags attached.
Fixed in Revision1794
FPCOldBugId
FPCTarget-
Attached Files

Activities

When originally writing this message, I got caught by a MantisBT "Token error" because I spent so long typing it discarded a message I had spent an hour writing, and I had to start over, writing the whole thing from scratch, so I made a mistake retyping. The code at the end should read something like this:

{$IFDEF ALLOWMACRORECURSION}
    If ThisDefine^.inUse > MaxMacroRecursions then
{$ELSE}
    If ThisDefine^.inUse then
{$ENDIF)
   BEGIN // Error: Crock recursion (recursion too deep)
  ...
    END
    ELSE
     BEGIN
{$IFDEF ALLOWMACRORECURSION}
            Inc( ThisDefine^.inUse );
{$ELSE}
            ThisDefine^.inUse := True;
{$ENDIF)
      // process this macro
   ...
      END

My apologies, I'm sometimes a bit blurry eyed at 3AM, especially after getting really, really upset that the web software destroyed everything I'd spent about an hour writing.

Florian

2021-01-23 22:31

administrator   ~0128533

The compiler had already a check for this: it resulted in a warning but for some reason it was not triggered. I fixed this, now the comment in the docs can be change that the compiler will throw a warning if macros are nested too deeply.

Michael Van Canneyt

2021-01-24 11:51

administrator   ~0128548

Adapted the description in 2 locations, so it mentions the warning.

Thanks for reporting!

Issue History

Date Modified Username Field Change
2021-01-23 12:41 Paul Robinson <xdpascal@xdpascal.com> New Issue
2021-01-23 13:06 Paul Robinson <xdpascal@xdpascal.com> Note Added: 0128513
2021-01-23 22:30 Florian Assigned To => Michael Van Canneyt
2021-01-23 22:30 Florian Status new => assigned
2021-01-23 22:30 Florian Category Compiler => Documentation
2021-01-23 22:30 Florian FPCTarget => -
2021-01-23 22:31 Florian Note Added: 0128533
2021-01-24 11:51 Michael Van Canneyt Status assigned => resolved
2021-01-24 11:51 Michael Van Canneyt Resolution open => fixed
2021-01-24 11:51 Michael Van Canneyt Fixed in Version => 3.3.1
2021-01-24 11:51 Michael Van Canneyt Fixed in Revision => 1794
2021-01-24 11:51 Michael Van Canneyt Note Added: 0128548