STOP() Happens - Improve it with SYSTEM StopHook and HALT() HaltHook

STOP() should never be in production code, but sometimes :poop: happens. You’ll find this line in a popular library:

? stop('logic error in FindMatch') ! should never happen

The STOP window is ugly and confusing to end users.

image

Using PROP:StopHook it’s easy to replace that with a Message like below with these changes:

  • Rename ABORT to “Close Application”, make second button
  • Rename "IGNORE to “Continue”, make first button and the default
  • Footer text to explain to End Users this message is “Unexpected”

In Debug Builds I add a “Stack Trace” button that does an ASSERT(). The Assertion window Call Stack makes it easy to find the “Stop code”, provided you used the Debug ClaRun.DLL which shows Procedure Names, “R$” for Routine names, and line numbers.

This is the code that makes it happen. At the bottom is a link to GitHub with the complete code.

StopBetter PROCEDURE(<STRING Message>) 

  SYSTEM{PROP:StopHook} = ADDRESS(StopBetter)  !Tell RTL for STOP to call StopBetter()
  
!---------------------------------------------------------------------------
StopBetter  PROCEDURE(<STRING pMessage>)
BlankMsg    STRING('Exit Application?')  !for STOP('')
StopMessage &STRING
FooterText  STRING('<13,10>_{60}' & |
            '<13,10>This message is displayed for an unexpected condition.' & |
            '<13,10>Take a screen capture and note the steps you took.' & |
            '<13,10>Please contact Technical Support for assistance.' )
AssertBtn   PSTRING(24)
    CODE
    IF ~OMITTED(pMessage) AND pMessage THEN
        StopMessage &= pMessage 
    ELSE
        StopMessage &= BlankMsg   !Called as STOP() or STOP('')
    END
    
    COMPILE('** debug **',_Debug_) !w/o Debug *No Assert() or Stack Trace
    AssertBtn='|Stack Trace'        
            !** debug **            

    CASE MESSAGE(CLIP(StopMessage) & FooterText, | ! Message Text
                 'Stop - Unexpected Condition',  | ! Caption
                 ICON:Hand,                      | ! Icon 
                 'Continue|Close Application' & AssertBtn, | 
                   1 , MSGMODE:CANCOPY )
    OF 2 ; HALT()         !Close Application
    OF 3 ; ASSERT(0,'Stop Assert')
    END
    RETURN  

If you use CapeSoft MessageBox, I’ll cover the changes to ds_Stop next time.

5 Likes

Hi Carl

that gave me a laugh as that is my code and it is there for a good reason.

if someone comes along and changes the code and inadvertantly breaks it I want it to yell out to the world about it and not go gentle into that good night.

you can argue about whether it is a good idea but I want to know if something fails.

of course you could also argue about whether these things should only be in debug mode (as it is here…) or on all the time. Asserts also only show up in debug mode so this is similar to an assert.

1 Like

My main point was if a STOP got into released code then which dialog below do you want a user to see?

Because it should never happen you coded it as a STOP, and I often do that too. Since the Abort is not needed this could have been a Message():

IF Message('Logic error in FindMatch: ' & pRegex,'ST',,'Ok|Stack Trace')=2 THEN ASSERT(0).

A favorite book Debugging Applications by John Robbins makes your point in the “Debugging During Coding” chapter. It has 2 points:

  1. Code should alert you to a problem with Assert, Trace, Stop, Message, etc.
  2. Code must handle the problem to prevent it causing further failure

I think of the #2 point often. Don’t just Assert, code to handle it! You did not do #2, after the STOP you Return. The LOOP pEnd = pStart TO endPos will end with pEnd=EndPos+1 and pStart=StrPos(. This likely returns with an invalid slice [pStart:pEnd] beyond the end of the &String. Failure should set pStart=0 ; pEnd=0 to indicate failure.

Again, I agree it should be impossible for this Stop to happen. But someone at Soft Velocity might break StrPos() and the problem only appear for certain RegEx, so it’s never seen in your testing.

1 Like

good points Carl.

I remember DAB did an article relating to this years ago entitled “Offensive Programming”.

I think it was originally published in Tom Moseley’s Clarion OnLine but is currently on David Bayliss’ dabhand site at:

http://www.dabhand.org/OldStuff/offensive.htm

It includes:

I was once sent a cartoon by fax, the picture was of an aeroplane flying over a busy city, the aeroplane had a speech bubble containing the words “Hello this is flight 932 from Sidney to Heathrow Central, what does ‘Numeric Exception at 000A:0089 – program halted’ mean?”

We can forgive the misspelling of Sydney and it does bring up the notion of when some form of tolerance/recovery is required although it also states:

The fundamental problem with fault tolerant systems is that they tolerate faults.

anyway well worth the read…

1 Like

I added a Halt Better replacement to the Repo. The template generated code will often HALT() when there is a file error, e.g… Legacy CheckOpen(FileXxx) when FileXxx does not exist.

The default message gives no hint to the User that OK is going to close the application. My replacement tells him “WILL CLOSE” so he can take a screen capture and notes. The “Stack Trace” button shows the Assert window to find the problem code (example above in STOP).

HALT() or HALT(’’) displays just a blank dialog with an OK button, so I display ‘HALT with Blank Reason’. Another way blank could happen is passing a blank string like HALT(ERROR()) when there is no error.

To use this set: SYSTEM{PROP:HaltHook} = ADDRESS(HaltBetter)

HaltBetter    PROCEDURE(UNSIGNED pErrorLevel=0, <STRING pMessage>)
BlankMsg    STRING('HALT with Blank Reason.')  !for HALT('')
HaltMessage &STRING
FooterText  STRING('<13,10>_{60}' & |
            '<13,10>This message is displayed for an unexpected condition.' & |
            '<13,10>The Application WILL CLOSE and NOT SAVE current data.' & |
            '<13,10>Take a screen capture and note the steps you took.' & |
            '<13,10>Please contact Technical Support for assistance.' )
AssertBtn   PSTRING(24)
    CODE
    SYSTEM{PROP:HaltHook}=0     !So HALT() does RTL HALT() 
    IF OMITTED(pMessage) THEN 
       HALT(pErrorLevel)
    ELSIF pMessage THEN
        HaltMessage &= pMessage 
    ELSE
        HaltMessage &= BlankMsg   !Was HALT('') or HALT(Var) where Var=''
    END
    
    COMPILE('** debug **',_Debug_) !w/o Debug *No Assert() or Stack Trace
    AssertBtn='|Stack Trace'        
            !** debug **            

    CASE MESSAGE(CLIP(HaltMessage) & FooterText, | ! Message Text
                 'HALT - Unexpected Condition',  | ! Caption
                 ICON:Hand,                      | ! Icon 
                 'Close Application'& AssertBtn, | 
                   1, MSGMODE:CANCOPY)
    OF 2 ; ASSERT(0,'HALT() Stack Trace Assert')
    END 
    !IF ~BAND(KEYSTATE(),0300h) THEN 
    !   ... could check for Ctrl+Shift down and Continue. Will need to Restore Prop:HaltHook
    HALT(pErrorLevel)
    RETURN

If you use CapeSoft MessageBox one of its options is to hook STOP and then it calls a ds_Stop procedure in one of your APPs. All of that code is generated to create a STOP just like the RTL using the Message() function.

There is an embed point for you to insert your own code ahead of the generated. Below is my code that works just like StopBetter:

ds_Stop  PROCEDURE (<string StopText>) 
   !Data Generated by Capesoft
FooterText  STRING('<13,10>_{60}' & |
            '<13,10>This message is displayed for an unexpected condition.' & |
            '<13,10>Take a screen capture and note the steps you took.' & |
            '<13,10>Please contact Technical Support for assistance.' )    
AssertBtn   PSTRING(24)

  CODE     ! Begin processed code
  
  if ~omitted(StopText) then Loc:StopText=StopText .
  if ~Loc:StopText      THEN Loc:StopText='Exit Application?'.

     COMPILE('** debug **',_Debug_) !w/o Debug *No Assert() or Stack Trace
  AssertBtn='|Stack Trace'        
             !** debug **    

  CASE Message(CLIP(Loc:StopText) & FooterText,        |
              'STOP - Unexpected Condition', ICON:Hand,|
              'Continue|Close Application' & AssertBtn )
  OF 1               !Continue
  OF 2 ; HALT()      !Was ABORT now button 2 named 'Close Application'
  OF 3 ; ASSERT(0)
  END 

  RETURN 

 !Generated code is NOT used that does ... TranslationFile and TimeOut

Note this short circuits some features like Translation and a Time Out counter. You can easily paste that code in to get the feature.

1 Like