Errors involving unexpected sharing of storage can cause serious problems.
Undocumented sharing may lead to unpredictable modifications, and some library
calls (e.g., strcpy) have undefined behavior if parameters share storage.
Another class of sharing errors occurs when clients of an abstract type may
obtain a reference to mutable storage that is part of the abstract
representation. This exposes the representation of the abstract type, since
clients may modify an instance of the abstract type indirectly through this
shared storage.
LCLint detects errors involving dangerous aliasing of parameters. Some of
these errors are already detected through the standard memory annotations
(e.g., only parameters may not be aliases.) Two additional annotations are
provided for constraining aliasing of parameters and return values.
The unique annotation denotes a parameter that may not be aliased by any other
storage reachable from the function implementation -- that is, any storage
reachable through the other parameters or global variables used by the
function. The unique annotation places similar constraints on function
parameters as the only annotation, but it does not transfer the obligation to
release storage.
LCLint will report an error if a unique parameter may be aliased by another
parameter or global variable.
Figure 10. Unique parameters.
LCLint reports an error if a function returns a reference to storage reachable
from one of its parameters (if retalias is on) since this may introduce
unexpected aliases in the body of the calling function when the result is
assigned.
The returned annotation denotes a parameter that may be aliased by the return
value. LCLint checks the call assuming the result may be an alias to the
returned parameter. Figure 11 shows an example use of a returned annotation.
LCLint detects places where the representation of an abstract type is exposed.
This occurs if a client has a pointer to storage that is part of the
representation of an instance of the abstract type. The client can then modify
or examine the storage this points to, and manipulate the value of the abstract
type instance without using its operations.
There are three ways a representation may be exposed:
- Returning (or assigning to a global variable) an object that includes a
pointer to a mutable component of an abstract type representation. (Controlled
by retexpose).
- Assigning a mutable component of an abstract object to storage reachable
from an actual parameter or a global variable that may be used after the call.
This means the client may manipulate the abstract object using the actual
parameter after the call. Note that if the corresponding formal parameter is
declared only, the caller may not use the actual parameter after the call so
the representation is not exposed. (Controlled by assignexpose).
- Casting mutable storage to or from an abstract type. (Controlled by
castexpose).
Annotations may be used to allow exposed storage to be returned safely by
restricting how the caller may use the returned storage.
It is often useful for a function to return a pointer to internal storage (or a
instance of a mutable abstract type) that is intended only as an
observer. The caller may use the result, but should not modify the
storage it points to. For example, consider an naïve implementation of
the employee_getName operation for the abstract employee type:
typedef /*@abstract@*/ struct {
char *name;
int id;
} *employee;
...
char *employee_getName (employee e) { return e->name; }
LCLint produces a message to indicate that the return value exposes the
representation. One solution would be to return a fresh copy of e->name.
This is expensive, though, especially if we expect employee_getName is used
mainly just to get a string for searching or printing. Instead, we could
change the declaration of employee_getName to:
extern /*@observer@*/ char *employee_getName (employee e);
Now, the original implementation is correct. The declaration indicates that
the result may not be modified by the caller, so it is acceptable to return
shared storage.[17] LCLint checks that the
return value is not modified by the caller. An error is reported if observer
storage is modified directly, passed as a function parameter that may be
modified, assigned to a global variable or reference derivable from a global
variable that is not declared with an observer annotation, or returned as a
function result or a reference derivable from the function result that is not
annotation with an observer annotation.
A program that attempts to modify a string literal has undefined behavior
[ANSI, Section 3.1.4]. This is not enforced by most C compilers, and can lead
to particularly pernicious bugs that only appear when optimizations are turned
on and the compiler attempts to minimize storage for string literals. LCLint
can be used to check that string literals are not modified, by treating them as
observer storage. If readonlystrings is on (default in standard mode), LCLint
will report an error if a string literal is modified.
Sometimes it is necessary to expose the representation of an abstract type.
This may be evidence of a design flaw, but in some cases is justified for
efficiency reasons. The exposed annotation denotes storage that is exposed.
It may be used on a return value for results that reference storage internal to
an abstract representation, on a parameter value to indicate a parameter that
may be assigned directly to part of an abstract representation,[18] or on a field of an abstract representation
to indicate that external references to the storage may exist.
An
error is reported if exposed storage is released, but unlike an observer, no
error is reported if it is modified.
Figure 12 shows examples of exposure problems detected by LCLint.
Next: Value Constraints
Contents