6. Sharing

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.

6.1 Aliasing

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.

6.1.1 Unique Parameters

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.

6.1.2 Returned 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.

6.2 Exposure

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:

  1. 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).
  2. 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).
  3. 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.

6.2.1 Read-Only 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.

String Literals

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.

6.2.2 Exposed Storage

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 Section Contents LCLint Home Page David Evans
Software Devices and Systems
evs@sds.lcs.mit.edu