Wednesday, March 29, 2017

The label number bug

Priority, in common with old fashioned versions of BASIC or even Pascal, has labels which are numbered. A label is the target of a GOTO or LOOP command, and unfortunately is the only loop control structure available to the Priority programmer. One feature which distinguishes Priority labels from BASIC labels is that in BASIC, each label has to be unique, whereas in Priority, one can use the same label several times in the same procedure/trigger. This normally helps, as I can copy entire loops out of one procedure then paste them into another without causing a syntax error. Unfortunately, of course, there is a downside to this.

Consider a post-update trigger which I wrote some time ago and started misbehaving a few days ago. It used to start like this:
GOTO 99 WHERE :$.BRANCHNAME <> '400'; #INCLUDE ORDERS/YYYY_BUF1 GOTO 1 WHERE :$.ORDSTATUSDES = :$1.ORDSTATUSDES; /* do something */ LABEL 1; GOTO 99 WHERE (:$.BRANCHNAME <> '400') OR (:$.MODELNAME IN ('CAD', 'F2D')); ... LABEL 99;
I was looking at this trigger the other day when it occurred to me that there was no point in checking the value of BRANCHNAME twice: if it's not 400, then execution should go to label 99, which is at the end of the trigger. So I deleted the second comparison of BRANCHNAME which occurs just after label 1. Today, a part of the trigger executed, despite the fact that BRANCHNAME <> 400. What happened?

It took me a while to figure it out: I had been bitten by the 'label number' bug. The second line in the snippet above includes a second trigger, and when I looked at that trigger, I saw that the final line in it was LABEL 99! In other words, the effective code was this
GOTO 99 WHERE :$.BRANCHNAME <> '400'; /* INCLUDE ORDERS/YYYY_BUF1 */ GOTO 99 WHERE :$.BRANCHNAME <> '400'; :YYYY_TOTO = 0; /* do something */ LABEL 99; /* end of ORDERS/YYYY_BUF1, back to the original trigger */ GOTO 1 WHERE :$.ORDSTATUSDES = :$1.ORDSTATUSDES; /* do something */ LABEL 1; GOTO 99 WHERE :$.MODELNAME IN ('CAD', 'F2D'); ... LABEL 99;
What happened was that the first line of the second trigger once again checked the value of BRANCHNAME, and because it was not 400, execution jumped to LABEL 99 which is at the end of the included trigger. But in the context of the first trigger, LABEL 99 was not at the very end of the trigger, but rather in the middle, so when the comparison :$.MODELNAME IN ('CAD', 'F2D') was executed (and failed), part of the trigger executed, even though it was not supposed to.

Moral of the story: each trigger should use its own labels, especially for 'procedure end'. I have changed the labels inside the included trigger to be 888 instead of 99. This really is only a problem when the trigger is a 'buffer' - common code which is called by other triggers - but one can never be sure. The entire problem would not have arisen had the final line of the above trigger been the inclusion of YYYY_BUF1; true, there would have been an unnecessary extra comparison of BRANCHNAME, but that's better than having code mis-execute.

No comments: