Fixed all messages reported in standard mode without memory checks.
Running LCLint with -memchecks detects no errors.
Here we us the -allimponly flag to disable all implicit only annotations. LCLint reports 15 anomalies. In instead we ran lclint using +allimponly, LCLint would assume that unannotated function results, structure fields and global variables are only storage. With +allimponly, LCLint reports 21 anomalies. We could find the real errors more quickly using implicit annotations, but for instructive purposes it will be clearer to see how LCLint checking leads us to add the annotations explicitly.
erc.c: (in function erc_create) erc.c:39,10: Null storage c->vals derivable from return value: c A possibly null pointer is dereferenced or misused. Value is either the result of a function which may return null (in which case, code should check it is not null), or a global, parameter or structure field declared with the null qualifier. Use -null to suppress message. erc.c:37,13: Storage c->vals becomes null erc.c: (in function erc_clear) erc.c:55,2: Function returns with null storage derivable from parameter c->vals erc.c:53,13: Storage c->vals becomes nullHere, the value of c->vals is NULL (in one case in the return value, in the second, of a passed parameter when the function returns), but there is no null annotation in the type definition. We add the null annotation to the type definition of erc:
typedef struct { /*@null@*/ ercList vals; int size; } *erc;This annotation will propagate to detect new anomalies in the next iteration.
erc.c: (in function erc_create) erc.c:39,10: Fresh storage returned as unqualified (should be only): c Fresh storage (newly allocated in this function) is transferred in a way that the obligation to release storage is not propagated. Use the /*@only@*/ annotation to indicate the a return value is the only reference to the returned storage. Use -freshtrans to suppress message. erc.c:29,3: Fresh storage c allocatedSince there is no only annotation on the function result, the obligation to release storage was not transferred. We add only annotations to the specifications of erc_create and erc_sprint. These annotations propagate in the next iteration.
All of these errors relate to fields of eref_Pool, type eref_ERP:
eref.c: (in function eref_alloc) eref.c:19,24: Unqualified storage passed as only: realloc (eref_Pool.conts, ...) Unqualified storage is transferred in an inconsistent way. Use -unqualifiedtrans to suppress message. eref.c:29,27: Unqualified storage passed as only: realloc (eref_Pool.status, ...) eref.c:45,21: Storage eref_Pool.status reachable from global is only (should be unqualified) Storage derivable from a parameter does not match the alias kind expected for the formal parameter. Use -compmempass to suppress message. eref.c:30,48: Storage eref_Pool.status becomes only eref.c:45,21: Storage eref_Pool.conts reachable from global is only (should be unqualified) eref.c:20,49: Storage eref_Pool.conts becomes only eref.c: (in function eref_initMod) eref.c:84,2: Storage eref_Pool.conts reachable from global is fresh (should be unqualified) eref.c:62,3: Fresh storage eref_Pool.conts allocated eref.c:84,2: Storage eref_Pool.status reachable from global is fresh (should be unqualified) eref.c:70,3: Fresh storage eref_Pool.status allocated
We fix these by adding only annotations to two fields in the type definition:
typedef struct { /*@only@*/ employee *conts; /*@only@*/ eref_status *status; int size; } eref_ERP;
erc.c:60,9: Implicitly temp storage c passed as only param: free (c) Temp storage (associated with a formal parameter) is transferred to a non-temporary reference. The storage may be released or new aliases created. Use -temptrans to suppress message.Here, c is a parameter to erc_final. Since it has no annotation, it is implicitly temp storage, so it is an anomaly to pass it to free as an only parameter. An only annotation is added to the parameter declaration. This annotation will propagate to detect new anomalies in the next iteration.
employee.c: (in function employee_setName) employee.c:14,11: Parameter 1 (e->name) to function strcpy is declared unique but may be aliased externally by parameter 2 (na) A unique or only parameter may be aliased by some other parameter or visible global. Use -mayaliasunique to suppress message.The strcpy library function is declared,
| void : char * | strcpy (returned out unique char *s1, char *s2) { modifies *s1; }There is a unique qualifier on the first parameter since the behavior of strcpy is undefined if the parameters share storage. Here, e and na are parameters to employee_setName so there is no guaranteed that e->name and na do not share storage. We fix the problem by adding a unique annotation to the specification of employee_setName:
bool employee_setName (employee *e, unique char na[])
eref.c:84,2: Global storage *(eref_Pool.conts) contains 4 undefined fields when call returns: ssNum, salary, gen, j Storage derivable from a parameter, return value or global is not defined. Use /*@out@*/ to denote passed or returned storage which need not be defined. Use -compdef to suppress message. eref.c:84,2: Global storage eref_Pool contains 1 undefined field when call returns: statusThe value of eref_Pool.conts is only defined if the size of the eref_Pool is greater than 0. We add a reldef qualifier to the conts field declaration to relax definition checking:
typedef struct { /*@reldef@*/ /*@only@*/ employee *conts; /*@only@*/ eref_status *status; int size; } eref_ERP;The second error results from LCLint not being able to determine that the loop defining eref_Pool.status must always execute. We use the +loopexec syntactic comment to indicate that the loop must always execute at least once.
erc.c:108,16: Released storage c->vals reachable from parameter at return point Memory is used after it has been released (either by passing as an only param or assigning to and only global. Use -usereleased to suppress message. erc.c:106,10: Storage c->vals is releasedis a spurious error resulting from a complicated aliasing relationship.
In the loop,
for (prev = 0, elem = c->vals; elem != 0; prev = elem, elem = elem->next) { if (eref_equal (elem->val, er)) { if (prev == 0) c->vals = elem->next; else prev->next = elem->next; free (elem); c->size--; return TRUE; } }elem aliases c->vals, but only on the first iteration where prev is 0. The free call releases elem (which may alias c->vals. Except, looking at the code, we know that elem only aliases c->vals when prev is 0, and if prev is 0, c->vals is reassigned. So, elem passed to free does not alias c->vals. LCLint's alias analysis is not good enough to figure this out, so an anomaly is reported to indicate that c->vals may be released when the function returns.
We could suppress the message using the -compmempass flag. Instead, we rewrite the code in a clearer way. This makes the loop easier for human readers to understand, and allows LCLint to check it correctly.