Prolint

Prolint is a tool for automated source code review of Progress 4GL code. It reads one or more sourcefiles and examines it for bad programming practice
When you are interested Prolint, you are encouraged to subscribe to this group where you find the on-line tools to collaborate and discuss Prolint. There is a discussion forum, you can submit issues (for bugs and enhancement requests), you can modify the on-line documentation. So subscribe, and then don't forget to go to your subscription details to enable the e-mail notification!


Prolint and Dataservers

Prolint could or should try to determine if the source code that you wrote, will have problems when you ever attempt to run it with a different dataserver like Oracle or MS-SQL.

But before we can teach Prolint what to look for, we first have to learn the "bad programming patterns" for Datasources.
The attached PDF files may help. Actually, the teach us best programming practices, so you have to read between the lines...


memory leaks

Create buffer and create query statements should always be followed by delete object statements, in order to prevent memory leaks. How about introducing this rule?


ambiguous find

Suppose there is a statement:

FIND something WHERE something.color = "red" NO-ERROR etc

but there is no unique key on the selected fields (color). The FIND statement will return nothing. Of course this can be intentional, but I think it is often a mistake.


temp-table defined without unique index

I have been searching for a full week :!: trying to figure out why a particular program was slowing down. In the end, the problem appeared to be a stupid little typo in an assign statement, that caused duplicate records to be created in a temp-table. Something like this:

Code:

FOR EACH .....
   FIND FIRST tt_table WHERE tt_table.idnr = someNumber NO-ERROR.
   IF NOT AVAILABLE tt_table THEN DO:
       CREATE tt_table.
       ASSIGN 
           tt_table.idnr  = sameNumber
           tt_table.text  = someText.
   END.
END.

Well it wasn't exactly like this but you get the idea.... sameNumber is 0 so a lot of identical records are created. This temp-table was supposed to hold only a small number of records, but instead it would grow to 100000 identical records because each FIND failed.


probably bad rounding

If an operation contains number 0.01 or 0.49 (or even 0.5) you should warn that these constants strongly suggest a broken approach to rounding (or bad implementation of ceil and floor functions).
Same is true for 0.001, 0.0001, 0.00001, 0.499, 0.4999 and so on.

Suggested by Tom Bascom.

The help file should tell how to write a proper round (ceil, floor) function or at least explain what goes wrong.

Greg Higgins submitted these functions :

function floor   returns integer ( input decimal ) forward.
function ceiling returns integer ( input decimal ) forward.

function floor returns integer

no literal numbers

Literal numbers should be replaced by constants or variables.
But 0 and 1 should be allowed because of the large number of false positives (do i=1 to 15: 1 is fine but 15 is not).

Suggested by Tom Bascom


buffer accessed in transaction without strong buffer scoping

buffers used for updates should be either
* explicitely strong scoped
* local to an internal procedure

Finding all updates seems hard to me so I would look for
* retrieve (find/for each,query) with lock
* create
* buffer-copy: can be used to create a record
as those are needed to update a buffer, I hope I didn't miss a technique.


Dataserver portability

If your Progress 4GL needs run with different dataservers (like Oracle, AS400, ODBC, SQL, ...) then maybe you need to consider some special coding practices. If we know what dataservers require, then maybe we can write the Prolint rules to help you check if your 4GL is portable.

* Character fields on AS/400 do not have variable length. If the Progress format is "x(20)" then the AS/400 field will have a fixed length of 20 characters. Your Progress session will crash when you try to write more than 20 characters to the field.

* cannot rely on the existence of VST, or system tables like _File and _Field


MESSAGE statement not closed with a period

I have made this mistake a couple of times:

Code:

     MESSAGE "Please enter a valid code"
     IsValid = FALSE.

     IF NOT IsValid THEN RETURN.

These were supposed to be two statements, but I keep forgetting to add a period to the first statement. The result is: customer gets to see a message "Please enter a valid code no" and the program continues as if the validation found no problem, resulting in a logic error.

I suppose the Prolint rule would have to check for indentation.


transaction scoped to comp.unit level?

see if there is a transaction scoped to the procedure level (I mean, compilation unit level). Prolint must probably read the compile listing for this


check for strong scoping of buffers, or at least RELEASE

check for strong scoping of buffers, or at least RELEASE buffer, especially in persistent procedures. Reason: dirty reads when some persistent procedure has an old copy of a record in memory.


Find absolute path names in the code

Find absolute path names in the code. (Run, include, triggers, etc.)


use EMPTY TEMP-TABLE

replace

FOR EACH temptablename :
     DELETE temptablename.
END.

by the EMPTY TEMP-TABLE statement, because it is much faster.


buffer in a FOR EACH statement is affected by IP

try to check if the buffer in a FOR EACH statement is affected by internal procedures. For example:

Code:

	
   FOR EACH customer :
       RUN Something.
       DISPLAY customer.cust-num.
   END.

   PROCEDURE Something :
       FIND FIRST customer.
   END PROCEDURE.

You would expect Progress error "FIND cannot be processed for a FOR EACH mode record. (213)"


an IP should not access variables/buffers that are external

an Internal Procedure should not access variables or buffers that are defined outside the ip (ie should only access its own parameters and local variables). This is less severe than the similar rule for User Defined Functions, because a procedure call is always a stand-alone procedure


#
Syndicate content