Recommendation on preventing "Crock Recursion" in Macro usage
Original Reporter info from Mantis: XDPascal
-
Reporter name: Paul Robinson xdpascal@xdpascal.com
Original Reporter info from Mantis: XDPascal
- Reporter name: Paul Robinson xdpascal@xdpascal.com
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
Mantis conversion info:
- Mantis ID: 38387
- Version: 3.2.0
- Fixed in version: 3.3.1
- Fixed in revision: 1794 (#9c63e130)