Recent

Author Topic: AV during runtime, because Compiler does not check the String-Format by "Format"  (Read 3889 times)

paule32

  • Full Member
  • ***
  • Posts: 233
Hello,
- I download, and compile the latest FPC 3.2.2
- I faced with Format - a string operation function in following way:

Format('text: %d', [ IntToStr( aLongIntVariable ) ]);

I get no Compiler error.
But I get AV during runtime - else:

Format('text: %s', [ IntToStr( aLongIntVariable ) ]);

the format will not checked during compile.
But I get no AV during runtime.

Is there a possibility, to set a switch or option, that check the format of the string
within the Format function ?

440bx

  • Hero Member
  • *****
  • Posts: 4212
Is there a possibility, to set a switch or option, that check the format of the string
within the Format function ?
C++ can do it but, I don't think the FPC compiler checks variadic parameters.

(FPC v3.0.4 and Lazarus 1.8.2) or (FPC v3.2.2 and Lazarus v3.2) on Windows 7 SP1 64bit.

paule32

  • Full Member
  • ***
  • Posts: 233
I wonder me, why FPC don't.
In context, that FPC supports Generics and Co. ...

PascalDragon

  • Hero Member
  • *****
  • Posts: 5535
  • Compiler Developer
Is there a possibility, to set a switch or option, that check the format of the string
within the Format function ?

Format is no magic function, it's just an ordinary function implemented completely in Pascal. The compiler has no knowledge what Format does, thus it can not do any checks.

paule32

  • Full Member
  • ***
  • Posts: 233
and, how Assert work ? - is it a RTL Function, too ?

Eugene Loza

  • Hero Member
  • *****
  • Posts: 696
    • My games in Pascal
I must admit I really love C# interpolated strings in this context. Compile-time validation of syntax is really cool.

I guess in current game project I'm working on like half of all of the crashes are because of Format parameters mismatch. The most annoying are when it crashes when trying to report an error :D
My FOSS games in FreePascal&CastleGameEngine: https://decoherence.itch.io/ (Sources: https://gitlab.com/EugeneLoza)

Thaddy

  • Hero Member
  • *****
  • Posts: 14741
  • Censorship about opinions does not belong here.
and, how Assert work ? - is it a RTL Function, too ?
it is a compiler intrinsic. see
https://www.freepascal.org/docs-html/rtl/system/assert.html
bitrate is always calculated like this:sample rate * bitdepth * number of channels.

rvk

  • Hero Member
  • *****
  • Posts: 6257
I must admit I really love C# interpolated strings in this context. Compile-time validation of syntax is really cool.
How would this work if the function call itself has a variable string for the format-string?

Code: Pascal  [Select][+][-]
  1. function foo(A, B: String; C, D: Integer): String;
  2. begin
  3.   result := Format(A + B, [C, D]);
  4. end;

How would the compiler check if A + B has the correct %s or %d. Not to speak about the number of parameters etc.

How is this done in C#?

Thaddy

  • Hero Member
  • *****
  • Posts: 14741
  • Censorship about opinions does not belong here.
Hello,
- I download, and compile the latest FPC 3.2.2
- I faced with Format - a string operation function in following way:

Format('text: %d', [ IntToStr( aLongIntVariable ) ]);

I get no Compiler error.
But I get AV during runtime - else:

Format('text: %s', [ IntToStr( aLongIntVariable ) ]);

the format will not checked during compile.
But I get no AV during runtime.

Is there a possibility, to set a switch or option, that check the format of the string
within the Format function ?
It looks the other way around: the first should fail and the second is simply correct.
also, an EConvertError will be raised that you can handle with try/except.
« Last Edit: April 08, 2024, 08:04:24 am by Thaddy »
bitrate is always calculated like this:sample rate * bitdepth * number of channels.

rvk

  • Hero Member
  • *****
  • Posts: 6257
Format('text: %d', [ IntToStr( aLongIntVariable ) ]);

I get no Compiler error.
But I get AV during runtime:
BTW. This doesn't give an AV during runtime.
If it does, then there is something else wrong.
It should give you an Exception during runtime (that's something else than a AV).

Thaddy

  • Hero Member
  • *****
  • Posts: 14741
  • Censorship about opinions does not belong here.
You can also format the strings with writestr. Writestr is more forgiving and instead of an exception it throws a runtime error that can be controlled with the usual {$I+/-}.
Code: Pascal  [Select][+][-]
  1. //Format('text: %d', [ IntToStr( aLongIntVariable ) ]);
  2. writestr(aString, 'text: ',IntToStr(aLongVariable));
  3.  
  4. // other one can be replaced with simply
  5. writestr(aString, 'text: ', aLongVariable);
writestr is another compiler intrinsic, formatting is the same as for write/writeln.

Another point is that if you think the IntToStr()  may fail, use IntToStrDef()
« Last Edit: April 08, 2024, 08:26:55 am by Thaddy »
bitrate is always calculated like this:sample rate * bitdepth * number of channels.

440bx

  • Hero Member
  • *****
  • Posts: 4212
How is this done in C#?
There is no way they can do any checking in the case you presented.  The format spec is only known at runtime.  I know you know that, I'm just confirming what you implied in the statement.

Even the C++ compiler that checks variadic parameters in functions like printf has severe limitations, among them it will only check the format spec if the string that contains it is stated as a parameter constant in the printf call.  if the format spec is declared as a constant pointer to char (obviously, somewhere outside the printf call), C++ won't check it (at least not MS' and, I seriously doubt any compiler would try to do any checking in that case - even though it is theoretically possible.)

A nice and practical solution is to create overloads for the functions that _internally_ use variadic parameters.  That way the "wrappers" can do all the necessary checking that the parameters are as required.  Internally, the programmer is still responsible for matching the parameter(s) to the correct format spec but, at least in theory, that only needs to be done once and it cannot be "broken" by mistake once it is right.
(FPC v3.0.4 and Lazarus 1.8.2) or (FPC v3.2.2 and Lazarus v3.2) on Windows 7 SP1 64bit.

Eugene Loza

  • Hero Member
  • *****
  • Posts: 696
    • My games in Pascal
I don't think interpolated strings work for variable strings. Format still exists in C# too. But of most logging features can be made by far safer, especially knowing that a lot of logging happens on very rare occasions, resulting in extremely hard-to detect bugs, especially on platforms like Android where getting the crashlog from a user (even if using the despicable remote telemetry) is by far less trivial.

But constructing basic log messages usually looks like this (not pure C# but Unity):

Debug.Log($"{name} was pressed {events.count} times");

Where "$" symbol means "interpolated string", so it looks for those curly brackets. Under the hood it just asks for name.ToString() and count.ToString().

Also helps a lot with issue when parameters while match the type don't match the content because it's much more "human-readable" than need to count the amount of %s and make sure that 7th corresponds to Self.ClassName.

With my most regular error would be:

Label1.Caption := Format('%s was pressed %d times', [Events.Count,  Button1.Name]);

which can go undetected for multiple versions of the app and lead to rare, apparently random but persistent crashes. Forcing me to wrap literally everything inside of try..except just to make sure that at least some hint will be displayed about where the crash occurred. E.g. debugging this crash took me almost a week:

Code: Pascal  [Select][+][-]
  1. if (ToX <= 0) or (ToY <=0) or (ToX >= Map.PredSizeX) or (ToY >= Map.PredSizeY) then
  2. begin
  3.   ShowError('%s is trying to teleport to broken coordinates (%d, %d) on map %dx%d. Teleporting to random point instead.', [ClassName, ToX, ToY, Map.SizeX, Map.SizeY]);
  4.   TeleportToRandomPoint;
  5.   Exit;
  6. end;

Yes, you've guessed correctly there are two versions of the procedure and one of them has ToX, ToY as Single, but I've forgotten about that while copy-pasting the error message when I was adding the Single version of the procedure, so it crashes the whole game trying to convert them to %d, %d and as the crash was reported from Android, go figure... Detecting such typos at compile time could have been so much more convenient.
My FOSS games in FreePascal&CastleGameEngine: https://decoherence.itch.io/ (Sources: https://gitlab.com/EugeneLoza)

paule32

  • Full Member
  • ***
  • Posts: 233
like an Assembler has at minimum two passes.
So I assume, that C++ or C# does pass two or more passes, too:
- 1x on read-in the function's and data
- 1x on compile time, where the guttering data is cross-check with main compiler action

not sure, only touch the magic bubbles

440bx

  • Hero Member
  • *****
  • Posts: 4212
Code: Pascal  [Select][+][-]
  1. if (ToX <= 0) or (ToY <=0) or (ToX >= Map.PredSizeX) or (ToY >= Map.PredSizeY) then
  2. begin
  3.   ShowError('%s is trying to teleport to broken coordinates (%d, %d) on map %dx%d. Teleporting to random point instead.', [ClassName, ToX, ToY, Map.SizeX, Map.SizeY]);
  4.   TeleportToRandomPoint;
  5.   Exit;
  6. end;
This is where overloading is great.  You would have probably _not_ have had a problem at all if you had defined an overload for ShowError as follows (or something along those lines):
Code: Pascal  [Select][+][-]
  1. procedure ShowError(InClassName : string; InToX, InToY, InSizeX, InSizeY : longint);
  2. begin
  3. ShowError('%s is trying to teleport to broken coordinates (%d, %d) on map %dx%d. Teleporting to random point instead.', [InClassName, InToX, ToY, Map.SizeX, Map.SizeY]);
  4. end;
  5.  
That way, the compiler ensures the parameters are integers which play along nicely with %d.

Of course, there is always the temptation to code directly - which is what causes the problems.




like an Assembler has at minimum two passes.
So I assume, that C++ or C# does pass two or more passes, too:
- 1x on read-in the function's and data
- 1x on compile time, where the guttering data is cross-check with main compiler action

not sure, only touch the magic bubbles
Checking variadic parameters is not a matter of two passes.  The compiler has to have a separate scanner and parser just for the string and communicate with the "standard" C++ parser to tell it what it found so the C++ parser can determine if the parameter match the types specified in the format spec.

I don't know how they chose to do it but, I think the scanner and parser do a little bit of backtracking to process the pairs of strings with format specs followed by parameter list.  Most likely the scanner has the heaviest burden. A little printf format spec scanner "on the side" talking to the C++ parser. 



(FPC v3.0.4 and Lazarus 1.8.2) or (FPC v3.2.2 and Lazarus v3.2) on Windows 7 SP1 64bit.

 

TinyPortal © 2005-2018