I wish Assert would be inlined properly - that would be so useful.

I wish Assert would be inlined properly -  that would be so useful.

Example:

program Project1;

{$APPTYPE CONSOLE}

uses
  SysUtils;

procedure EnsureAssigned(obj: TObject; const name: string); inline;
begin
  Assert(Assigned(obj), 'Parameter "' + name + ' was nil');
end;

var
  i: Integer;
  o: TObject;
begin
//  Assert(Assigned(o));
//  Assert(Assigned(o), 'Parameter "o" was nil');
  EnsureAssigned(o, 'o');
end.

Let's assume I want to do some parameter validation using Assert (because I can turn that off for release builds by compiler switch). But there is only a boolean and an optional message I can pass. That requires putting the actual check (in this case Assigned but you can think of any other condition as well) and an optional message there to get a better error message than just "Assertion failure (, line )". If I run the code with the first two Asserts I get the correct line of code and unit. However when I use the EnsureAssigned procedure even though the code gets inlined it uses the line the Assert is in (and the unit if I had it in another one).

This is one of the reasons some while ago in the Spring4D we thought it is good to have the Guard type to write more expressive pre and post condition code. But that causes the problem of not being able to easily turn such code off for release builds for example. Now if I could use Assert within inlined routines and it uses the correct line number and unit name that would be one step of improvement.

What would be even better though would be if we had magic functions to return the line number and unit name (I know FPC can do that since ages and actually the logic for that is inside the Delphi compiler because it uses it for Assert!) However these also should be properly return the value they are inlined to, otherwise I cannot use them in subroutines properly.

Allen Bauer Marco Cantù  FYI

Comments

  1. Another nice thing would be to be able to declare other procedures to be easily turned off the way assertions can be turned off e.g. logging code. I think I submitted a feature request for that many years ago. Probably got turned down and is forgotten by now.

    ReplyDelete
  2. Thomas Mueller I was thinking about that as well! I know there are ways to do that on the callee side (like putting ifdefs into the routine body that produce an empty routine that when inlined results in no code at all) but that is more of a workaround.

    ReplyDelete
  3. What bothers me with 'assert' is that you don't get the right call stack, and it raises an exception instead of simply pausing in the debugger. Here is one in a method inside a paintbox paint event:

    :751dc42d KERNELBASE.RaiseException + 0x58
    Unit1.DrawRotatedBitmap($35A9A00,$C8,((0, 0), 0, 0),((5.3265526641606e-39, 7.80614609288797e-39), 5.3265526641606e-39, 7.80614609288797e-39),???,1.29957204308624e-38)
    System._Assert('Message','C:\Users\Work\Documents\Embarcadero\Studio\Projects\fmx rotated bitmap\Unit1.pas',200)
    Unit1.DrawRotatedBitmap($35C4070,$7215DD0,((350, 20), 350, 20),((30, 200), 30, 200),((250, 30), 250, 30),2)
    Unit1.TForm1.PaintBox1Paint($35A9A70,$35C4070)
    FMX.Objects.TPaintBox.Paint

    In C or C++, an assert (either manually coded via a macro expanding to DebugBreak, or other depending on the implementation - you can change what backs it) will stop on the third call stack entry up. No nonsense inside the System unit, then back in the calling method (huh?), then in the exception code. Plus, if the debugger breaks here, I can't continue the method (ie if you think,  'good it fired, let's see what happens if I continue'.) No, because it's an exception the method will unwind. Gah.

    (Oh, and look at the parameters of the fifth call stack entry, the duplicate entry for DrawRotatedBitmap. Are those parameters correct? Hint: No.)

    I want to break on assertions, not raise an exception on assertions, and the call stack should be right there where the assertion occurred - only.

    ReplyDelete
  4. David Millington Why don't you just write some IDE plugin that does not show the exception dialog in case of EAssertionFailed and just pause the debugger at its location and fix the callstack? ;)

    ReplyDelete
  5. Stefan Glienke Hahaha :D Believe it or not, I have considered it. Quite seriously.  Pretty hard to pull off though.

    ReplyDelete
  6. David Millington Just Hook System.AssertErrorProc in your application if you don't want it to raise an exception. You can save off the current value of AssertErrorProc and vector it to your handler. In your handler you can set a breakpoint. From there, you can decide whether it should raise an exception (maybe set a global variable via the Eval/Modify dialog?) by calling the original handler.

    ReplyDelete
  7. Stefan Glienke I completely understand what you're trying to do and, yes it would be nice... However, please consider other cases where a function is inlined; In most of those cases you want to be able to step through the inlined code as if it were a separate function call. This is why the compiler gathers the line numbers of the inline function. An as yet to be implemented feature is to handle those line numbers properly within the debug information. It does work with generic instantiations because the same "code-inliner" is used for generics.

    ReplyDelete
  8. Talking about IDE plugins that modify exception handling: Sometimes it would be really useful if I could disable the debugger breaking on specific exceptions (not exception classes) that are raised all the time, e.g. the program routinely tries to open a file that does not exist and handles this exception, but I don't want to ignore all EFileOpenError exceptions, just that specific one. Sometimes that happens in program parts for which I don't have the source code, so I can't temporarily remove/change that offending exception, or I can't/don't want to chance the source code.

    ReplyDelete
  9. Thomas Mueller I'd handle that specific case by adding "if FileExists(filename) then" in the appropriate spot.

    (And before anyone goes talking about race conditions and how a FileExists check is the wrong thing to do, let me humbly suggest that if an external entity is deleting files while a running program is expecting them to be there, you have bigger problems.)

    ReplyDelete
  10. Mason Wheeler You can have your cake and eat it too, like for example with https://quality.embarcadero.com/browse/RSP-11107

    ReplyDelete
  11. Allen Bauer I've never seen the debugger step through an inlined function?

    ReplyDelete
  12. Asbjørn Heid From my statement "*An as yet to be implemented* feature is to handle those line numbers properly within the debug information."

    ReplyDelete
  13. Allen Bauer Gotcha, was a bit unclear that that applied to the earlier statements about debugger stepping.

    ReplyDelete
  14. Asbjørn Heid The compiler gathers the line number information within the code that persists the node-trees. This same code is used for inline and generics. For inline functions, the line information is currently left alone, but for generics it is generated for the instantiations. The compiler has to correlate code blocks with line numbers. This is why you can step through a generic method, and even inspect variables that, in the source, are of type 'T' yet still show as the actual concrete type.

    Another less understood requirement for debugging is that when you set a break point in a generic type's method, you are actually setting lots of breakpoints. One for each instantiation of that generic wherever it happens to be in the runtime code. For inline functions, that same thing would have to happen. That is much more difficult because you have to then locate those little snippets of code within other blocks of code throughout the compiled codebase... and that is more difficult than locating whole functions. That's one of the reasons you cannot step into an inline function.

    ReplyDelete
  15. Allen Bauer Thanks for the detailed explanation. Always fun to know what goes on under the hood :)

    And I can totally appreciate that it's very difficult to implement stepping of inlined functions (especially if optimization is enabled), I just thought I had been missing out on a feature.

    ReplyDelete
  16. Asbjørn Heid Yes, we all are missing out on that feature ;-)... I remember working for couple of weeks on the problem... and either it is a very hard problem or I was just not up to the task... I'd like to think it was the former, however I cannot discount the latter.

    ReplyDelete
  17. Allen Bauer And then there's the variables which have to be mapped so they can be inspected and yeah, I'll totally buy that it's a very hard problem.

    ReplyDelete
  18. Allen Bauer I probably missed stepping through inlined procedures a million times.
    I can also think of million other features more worth of your time than this one. Unless, someday solution hits you in the middle of the night ;-)

    ReplyDelete

Post a Comment