Previous Next Contents Index Doc Set Home


Using LockLint

24


This chapter is organized as follows:

Basic Concepts

page 263

LockLint Overview

page 264

Collecting Information for LockLint

page 266

LockLint User Interface

page 266

How to Use LockLint

page 267

Source Code Annotations

page 281

Command Reference

page 297


Basic Concepts

In the multithreading model, a process consists of one or more threads of control that share a common address space and most other process resources. Threads must acquire and release locks associated with the data they share. If they fail to do so, a data race may ensue--a situation in which a program may produce different results when run repeatedly with the same input.

Data races are easy problems to introduce. Simply accessing a variable without first acquiring the appropriate lock can cause one. Data races are generally very difficult to find. Symptoms generally manifest themselves only if two threads access the improperly protected data at nearly the same time; hence a data race may easily run correctly for months without showing any signs of a problem. It is extremely difficult to exhaustively test all concurrent states of a program for even a simple multithreaded program, so conventional testing and debugging are not an adequate defense against data races.

Most processes share several resources. Operations within the application may require access to more than one of those resources. This means that the operation will need to grab a lock for each of the resources before performing the operation. If different operations use a common set of resources, but the order in which they acquire the locks is inconsistent, there is a potential for deadlock. For example, the simplest case of deadlock occurs when two threads hold locks for different resources and each thread tries to acquire the lock for the resource held by the other thread.


LockLint Overview

LockLint is a utility that analyzes the use of mutex and multiple readers/single writer locks, and looks for inconsistent use of these locking techniques.

When analyzing locks and how they are used, LockLint detects a common cause of data races: failure to hold the appropriate lock while accessing a variable.

Table 24-1, Table 24-2, and Table 24-3 list the routines of the Solaris and POSIX libthread APIs recognized by LockLint.

Table  24-1 Calls for Manipulating Mutex Locks

Solaris
POSIX
Kernel (Solaris only)

mutex_lock

mutex_unlock

mutex_trylock

pthread_mutex_lock

pthread_mutex_unlock

pthread_mutex_trylock

mutex_enter

mutex_exit

mutex_tryenter



Table  24-2 Calls for Manipulating Readers-Writer Locks (Solaris API)

User
Kernel

rw_rdlock, rw_wrlock
rw_unlock
rw_tryrdlock, rw_trywrlock

rw_enter

rw_exit

rw_tryenter

rw_downgrade

rw_tryupgrade



Table  24-3 Calls for Manipulating Condition Variables

Solaris
POSIX
Kernel (Solaris only)

cond_broadcast

cond_wait

cond_timedwait

cond_signal

pthread_cond_broadcast

pthread_cond_wait

pthread_cond_timedwait

pthread_cond_signal

cv_broadcast

cv_wait

cv_wait_sig

cv_wait_sig_swap

cv_timedwait

cv_timedwait_sig

cv_signal

Additionally, LockLint recognizes the thread types shown in Table 24-4.

Table  24-4 Thread Types Recognized by LockLint

Solaris
POSIX
Kernel (Solaris only)

mutex_t

pthread_mutex_t

kmutex_t

LockLint reports several kinds of basic information about the modules it analyzes, including:

LockLint provides a wealth of subcommands for specifying assertions about the application. During the analysis phase, LockLint reports any violation of the assertions.


Note - Add assertions liberally, and use the analysis phase to refine assertions and to make sure that new code does not violate the established locking conventions of the program.


Collecting Information for LockLint

The compiler gathers the information used by LockLint. More specifically, you specify a command-line option, -Zll, to the C compiler to generate a .ll file for each .c source code file. The .ll file contains information about the flow of control in each function and about each access to a variable or operation on a mutex or readers-writer lock.


Note - No .o file is produced when the -Zll flag is passed.


LockLint User Interface

There are two ways for you to interact with LockLint: source code annotations and the command-line interface.

See "Source Code Annotations" on page 281 for more information.

LockLint subcommands help you analyze your code and discover which variables are not consistently protected by locks. You may make assertions about which variables are supposed to be protected by a lock and which locks are supposed to be held whenever a function is called. Running the analysis with such assertions in place will show you where the assertions are violated.

See "Command Reference" on page 297.

Most programmers report that they find source code annotations preferable to command-line subcommands. However, there is not necessarily a one-to-one correspondence between the two.


How to Use LockLint

This section is organized as follows:

Managing LockLint's Environment

page 268

Compiling Code

page 270

LockLint Subcommands

page 271

Suggested Approach for Checking an Application

page 271

Program Knowledge Management

page 273

Analysis

page 276

Limitations of LockLint

page 277

Use LockLint to refine the set of assertions you maintain for the implementation of your system. Maintaining a rich set of assertions enables LockLint to validate existing and new source code as you work.

Using LockLint consists of two major steps:

Figure 24-1 shows a general scheme for using LockLint:

Figure  24-1 LockLint Control Flow

Managing LockLint's Environment

The LockLint interface is provided through the lock_lint command, which is executed in a shell. By default, LockLint uses $SHELL. Alternatively, LockLint can execute any shell by specifying the shell to use on the lock_lint start command. For example:

% lock_lint start /bin/ksh

starts a LockLint session up in the Korn shell.

LockLint creates an environment variable called LL_CONTEXT, which is visible in the child shell. If you are using a shell that provides for initialization, you can arrange to have lock_lint source a .ll_init file in your home directory, and then execute a .ll_init file in the current directory if it exists. If you use csh, you can do this by inserting the following code into your .cshrc file:

if ($?LL_CONTEXT) then
	if ( -x $(HOME)/.ll_init ) source $(HOME)/.ll_init
endif

It is better not to have your .cshrc source the file in your current working directory, since others may want to run LockLint on those same files, and they may not use the same shell you do. Since you are the only one who is going to use your $(HOME)/.ll_init, you should source that one, so that you can change the prompt and define aliases for use during your LockLint session. The following version of ~/.ll_init does this for csh:

# Cause analyze subcommand to save state before analysis.
alias analyze "lock_lint save before analyze;\
	lock_lint analyze"
# Change prompt to show we are in lock_lint.
set prompt="lock_lint~$prompt"

(Also see "start" on page 326.)

When executing subcommands, remember that you can use pipes, redirection, backticks, and so on to accomplish your aims. For example, the following command asserts that lock foo protects all global variables (the formal name for a global variable begins with a colon):

% lock_lint assert foo protects \Qlock_lint vars | grep ^:\Q

In general, the subcommands are set up for easy use with filters such as grep and sed. This is particularly true for vars and funcs, which put out a single line of information, for each variable or function. Each line contains the attributes (defined and derived) for that variable or function. The following example shows which members of struct bar are supposed to be protected by member lock:

% lock_lint vars -a \Qlock_lint members bar\Q | grep =bar::lock

Since you are using a shell interface, a log of user commands can be obtained by using the shell's history function (the history level may need to be made large in the .ll_init file).

Temporary Files

LockLint puts temporary files in /var/tmp unless $TMPDIR is set.

Makefile Rules

To modify your makefile to produce .ll files, first use the rule for creating a .o from a .c to write a rule to create a .ll from a .c. For example, from:

# Rule for making .o from .c in ../src.
%.o: ../src/%.c
	$(COMPILE.c) -o $@ $<

you might write:

# Rule for making .ll from .c in ../src.
%.ll: ../src/%.c
	cc $(CFLAGS) $(CPPFLAGS) $(FOO) $<

If you use a suffix rule, you will need to define .ll as a suffix. For that reason some prefer to use % rules.

If the appropriate .o files are contained in a make variable FOO_OBJS, you can create FOO_LLS with the line:

FOO_LLS = ${FOO_OBJS:%.o=%.ll}

or, if they are in a subdirectory ll:

FOO_LLS = ${FOO_OBJS:%.o=ll/%.ll}

If you want to keep the .ll files in subdirectory ll/, you can have the makefile automatically create this file with the label:

.INIT:
	@if [ ! -d ll ]; then mkdir ll; fi

Compiling Code

For LockLint to analyze your source code, you must first compile it using the -Zll option of the SunSoft ANSI C compiler. The SunSoft ANSI C compiler will then produce the LockLint database files (.ll files), one for each .c file compiled. Later you load the .ll files into LockLint with the load subcommand.

LockLint sometimes needs a simpler view of the code to return meaningful results during analysis. To allow you to provide this simpler view, the -Zll option automatically defines the macro __lock_lint.

Further discussions of the likely uses of __lock_lint can be found in "Limitations of LockLint" on page 277.

LockLint Subcommands

The interface to LockLint consists of a set of subcommands that can be specified with the lock_lint command:

lock_lint [subcommand]

In this example subcommand is one of a set of subcommands used to direct the analysis of the source code for data races and deadlocks. More information about subcommands can be found in "Command Reference" on page 297.

Starting and Exiting LockLint

The first subcommand of any LockLint session must be start, which starts a subshell of your choice with the appropriate LockLint context. Since a LockLint session is started within a subshell, you exit by exiting that subshell. For example, to exit LockLint when using the C shell, use the command exit.

Setting the Tool State

LockLint's state consists of the set of databases loaded and the specified assertions. Iteratively modifying that state and rerunning the analysis can provide optimal information on potential data races and deadlocks. Since the analysis can be done only once for any particular state, the save, restore, and refresh subcommands are provided as a means to reestablish a state, modify that state, and retry the analysis.

Suggested Approach for Checking an Application

Here are the basic steps involved in checking an application:

1. Annotate your source code and compile it to create .ll files.

See "Source Code Annotations" on page 281.

2. Load the .ll files using the load subcommand.

3. Make assertions about locks protecting functions and variables using the assert subcommand.


Note - These specifications may also be conveyed using source code annotations. See
"Source Code Annotations" on page 281.

4. Make assertions about the order in which locks should be acquired in order to avoid deadlocks, using the assert order subcommand.


Note - These specifications may also be conveyed using source code annotations. See
"Source Code Annotations" on page 281.

5. Check that LockLint has the right idea about which functions are roots.

If the funcs -o subcommand does not show a root function as root, use the declare root subcommand to fix it. If funcs -o shows a non-root function as root, it's likely that the function should be listed as a function target using the declare ... targets subcommand. See "declare root func" on page 310 for a discussion of root functions.

6. Describe any hierarchical lock relationships (if you have any--they are rare) using the rwlock subcommand.


Note - These specifications may also be conveyed using source code annotations. See
"Source Code Annotations" on page 281.

7. Ignore any functions or variables you want to exclude from the analysis using the ignore subcommand.

Be conservative in your use of the ignore command. Make sure you should not be using one of the source code annotations instead (for example, NO_COMPETING_THREADS_NOW).

8. Run the analysis using the analyze subcommand.

9. Deal with the errors.

This may involve modifying the source using #ifdef __lock_lint (see "Limitations of LockLint" on page 277) or adding source code annotations to accomplish steps 3, 4, 6, and 7 (see "Source Code Annotations" on page 281).

Restore LockLint to a preanalysis state and rerun the analysis as necessary.


Note - It is best to handle the errors in order. Otherwise, problems with locks not being held on entry to a function, or locks being released while not held, can cause lots of misleading messages about variables not being properly protected.

10. Run the analysis using the analyze -v subcommand and repeat the above step.

11. When the errors from the analyze subcommand are gone, check for variables that are not properly protected by any lock.

Use the command:

	lock_lint vars -h | fgrep \*

Rerun the analysis using appropriate assertions to find out where the variables are being accessed without holding the proper locks.

Remember that you cannot run analyze twice for a given state, so it will probably help to save the state of LockLint using the save subcommand before running analyze. Then restore that state using refresh or restore before adding more assertions. You may want to set up an alias for analyze that automatically does a save before analyzing.

Program Knowledge Management

LockLint acquires its information on the sources to be analyzed with a set of databases produced by the C compiler. The LockLint database for each source file is stored in a separate file. To analyze a set of source files, use the subcommandsload subcommand to load their associated database files. The files subcommand can be used to display a list of the source files represented by the loaded database files. Once a file is loaded, LockLint knows about all the functions, global data, and external functions referenced in the associated source files.

Function Management

As part of the analysis phase, LockLint builds a call graph for all the loaded sources. Information about the functions defined is available via the funcs subcommand. It is extremely important for a meaningful analysis that LockLint have the correct call graph for the code to be analyzed.

All functions that are not called by any of the loaded files are called root functions. You may want to treat certain functions as root functions even though they are called within the loaded modules (for example, the function is an entry point for a library that is also called from within the library). Do this by using the declare root subcommand. You may also remove functions from the call graph by issuing the ignore subcommand.

LockLint knows about all the references to function pointers and most of the assignments made to them. Information about the function pointers in the currently loaded files is available through the funcptrs subcommand. Information about the calls made via function pointers is available via the pointer calls subcommand. If there are function pointer assignments that LockLint could not discover, they may be specified with the declare ... targets subcommand.

By default, LockLint tries to examine all possible execution paths. If the code uses function pointers, it's possible that many of the execution paths will not actually be followed in normal operation of the code. This can result in the reporting of deadlocks that do not really occur. To prevent this, use the lock_lint disallow and reallow subcommands to inform LockLint of execution paths that will never occur. To print out existing constraints, use the reallows and disallows subcommands.

Variable Management

The LockLint database also contains information about all global variables accessed in the source code. Information about these variables is available via the vars subcommands.

One of LockLint's jobs is to determine if variable accesses are consistently protected. If you are unconcerned about accesses to a particular variable, you can remove it from consideration by using the ignore subcommand.

You may also consider using one of the source code annotations shown in Table 24-5, as appropriate.

Table  24-5 Variable Management Source Code Annotations 

SCHEME_PROTECTS_DATA

read_only_data

data_readable_without_lock

now_invisible_to_other_threads

now_visible_to_other_threads

For more information, see "Source Code Annotations" on page 281.

Lock Management

Source code annotations are an efficient way to refine the assertions you make about the locks in your code. There are three types of assertions: protection, order, and side effects.

Protection assertions state what is protected by a given lock. For example, the source code annotations shown in Table 24-6 can be used to assert how data is protected.

Table  24-6 Data Protection Source Code Annotations

MUTEX_PROTECTS_DATA

RWLOCK_PROTECTS_DATA

SCHEME_PROTECTS_DATA

DATA_READABLE_WITHOUT_LOCK

RWLOCK_COVERS_LOCK

A variation of the assert subcommand is used to assert that a given lock protects some piece of data or a function. Another variation, assert ... covers, asserts that a given lock protects another lock; this is used for hierarchical locking schemes.

Order assertions specify the order in which the given locks must be acquired. The source code annotation LOCK_ORDER or the assert order subcommand can be used to specify lock orderings.

Side effect assertions state that a function has the side effect of releasing or acquiring a given lock. Use the source code annotations listed in Table 24-7.

Table  24-7 Functions With Locking Side Effects 

MUTEX_ACQUIRED_AS_SIDE_EFFECT

READ_LOCK_ACQUIRED_AS_SIDE_EFFECT

WRITE_LOCK_ACQUIRED_AS_SIDE_EFFECT

LOCK_RELEASED_AS_SIDE_EFFECT

LOCK_UPGRADED_AS_SIDE_EFFECT

LOCK_DOWNGRADED_AS_SIDE_EFFECT

NO_COMPETING_THREADS_AS_SIDE_EFFECT

COMPETING_THREADS_AS_SIDE_EFFECT

You can also use the assert side effect subcommand to specify side effects. In some cases you may want to make side effect assertions about an external function and the lock is not visible from the loaded module (for example, it is static to the module of the external function). In such a case, you can "create" a lock by using a form of the declare subcommand.

Analysis

LockLint's primary role is to report on lock usage inconsistencies that may lead to data races and deadlocks. The analysis of lock usage occurs when you use the analyze subcommand. The result is a report on the following problems:

Postanalysis Queries

After analysis, you can use LockLint subcommands for:

One such subcommand is order, which you can use to make inquiries about the order in which locks have been acquired. This information is particularly useful in understanding lock ordering problems and making assertions about those orders so that LockLint can more accurately diagnose potential deadlocks.

Another such subcommand is vars. The vars subcommand reports which locks are consistently held when a variable is read or written (if any). This information can be useful in determining the protection conventions in code where the original conventions were never documented, or the documentation has become outdated.

Limitations of LockLint

There are limitations to LockLint's powers of analysis. At the root of many of its difficulties is the fact that LockLint doesn't know the values of your variables.

LockLint solves some of these problems by ignoring the likely cause or making simplifying assumptions. Some other problems can be avoided by using conditionally compiled code in the application. Towards this end, the compiler always defines the preprocessor macro __lock_lint when the -Zll switch is used. You can use this macro to make your code less ambiguous.

LockLint has trouble deducing:

When LockLint sees a call through a function pointer, it will test that call path for every possible value of that function pointer. If you know or suspect that some calling sequences is never executed, use the disallow and reallow subcommands to specify which sequences will be.

In this case, two execution paths are created, one holding the lock, and one not holding the lock, which will probably cause the generation of a side effect message at the unlock call. You may be able to work around this problem by using the __lock_lint macro to force LockLint to treat a lock as unconditionally taken. For example:

#ifdef __lock_lint
pthread_mutex_lock(&lock1);
#else
if (x) pthread_mutex_lock(&lock1);
#endif

Note that LockLint has no problem analyzing code like this:

if (x) {
	pthread_mutex_lock(&lock1);
	foo();
	pthread_mutex_unlock(&lock1);
}

In this case, there is only one execution path, along which the lock is held and released, causing no side effects.

In such cases, the lock call is ignored.

(So that it uses the names foo::lock and foo::bar -- see "Inversions" on page 331.)

Some other LockLint difficulties:

During analysis, LockLint may produce messages about a lock operation called rw_upgrade. Such a call does not really exist, but LockLint rewrites code like

if (rw_tryupgrade(&lock1)) { 		...  	}

as

if () { 		rw_tryupgrade(&lock1); 		...  	}

such that, wherever rw_tryupgrade() occurs, LockLint always assumes it succeeds.

One of the errors LockLint flags is an attempt to acquire a lock that is already held. However, if the lock is unnamed (e.g. foo::lock), this error is suppressed, since the name refers not to a single lock but to a set of locks. However, if the unnamed lock always refers to the same lock, use the declare one subcommand so that LockLint can report this type of potential deadlock.

If you have constructed your own locks out of these locks (for example, recursive mutexes are sometimes built from ordinary mutexes), LockLint will not know about them. Generally you can use #ifdef to make it appear to LockLint as though an ordinary mutex is being manipulated. For recursive locks, use an unnamed lock for this deception, since errors won't be generated when it is recursively locked. For example:

void get_lock() {
	#ifdef __.  
		struct bogus *p;
		pthread_mutex_lock(p->lock);
	#else
		<the real recursive locking code>
	#endif
}


Source Code Annotations

This section is organized as follows:

Assertions and NOTEs

page 281

Why Use Source Code Annotations?

page 282

The Annotations Scheme

page 282

LockLint NOTEs

page 283

Assertions Recognized by LockLint

page 294

Assertions and NOTEs

An annotation is some piece of text inserted into your source code. (Generally, it has some semantic information that normal analyses cannot deduce.) You use annotations to tell LockLint things about your program that it cannot deduce for itself, either to keep it from excessively flagging problems or to have LockLint test for certain conditions. Annotations also serve to document code, in much the same way that comments do.

Annotations are similar to some of the LockLint subcommands described in "Command Reference" on page 297. In general, it's preferable to use source code annotations over these subcommands, as explained in "Why Use Source Code Annotations?" on page 282.

There are two types of ssource code annotations: assertions and NOTEs.

Why Use Source Code Annotations?

There are several reasons to use source code annotations. In many cases, such annotations are preferable to using a script of LockLint subcommands.

The Annotations Scheme

LockLint shares the source code annotations scheme with several other tools. When you install the SunSoft ANSI C Compiler, you also automatically install the file /usr/lib/note/SUNW_SPRO-cc-ssbd, which contains the names of all the annotations that LockLint understands. However, the SunSoft ANSI C Compiler also checks all the files in /usr/lib/note and /opt/SUNWspro/SC4.0/note for all valid annotations.

You may specify a location other than /usr/lib/note by setting the environment variable NOTEPATH, as in:

	setenv NOTEPATH $NOTEPATH:other_location
To use source code annotations, include the file note.h in your source or header files:

#include <note.h>

LockLint NOTEs

NOTE Syntax

Many of the note-style annotations accept names--of locks or variables--as arguments. Names are specified using the syntax shown in Table 24-8.

Table  24-8 NOTE Syntax

Syntax
Meaning

Var

Named variable

Var.Mbr.Mbr...

Member of a named struct/union variable

Tag

Unnamed struct/union (with this tag)

Tag::Mbr.Mbr...

Member of an unnamed struct/union

Type

Unnamed struct/union (with this typedef)

Type::Mbr.Mbr...

Member of an unnamed struct/union

In C, structure tags and types are kept in separate namespaces, making it possible to have two different structs by the same name as far as LockLint is concerned. When LockLint sees foo::bar, it will first look for a struct with tag foo; if it does not find one, it will look for a type foo and check that it represents a struct.

However, the proper operation of LockLint requires that a given variable or lock be known by exactly one name. Therefore type will be used only when no tag is provided for the struct, and even then only when the struct is defined as part of a typedef.

For example, Foo would serve as the type name in this example:

typedef struct { int a, b; } Foo;
These restrictions ensure that there is only one name by which the struct is known.

Name arguments do not accept general expressions. It is not valid, for example, to write:

NOTE(MUTEX_PROTECTS_DATA(p->lock, p->a p->b))
However, some of the annotations do accept expressions (rather than names); they are clearly marked.

In many cases an annotation accepts a list of names as an argument. Members of a list should be separated by white space. To simplify the specification of lists, a generator mechanism similar to that of many shells is understood by all annotations taking such lists. The notation for this is:

Prefix{A B ...}Suffix

where Prefix, Suffix, A, B, ... are nothing at all, or any text containing no white space. The above notation is equivalent to:

PrefixASuffix PrefixBSuffix ...

For example, the notation:

struct_tag::{a b c d}
is equivalent to the far more cumbersome text:

struct_tag::a struct_tag::b struct_tag::c struct_tag::d
This construct may be nested, as in:

foo::{a b.{c d} e}
Where an annotation refers to a lock or another variable, a declaration or definition for that lock or variable should already have been seen.

If a name for data represents a structure, it refers to all non-lock (mutex or readers-writer) members of the structure. If one of those members is itself a structure, then all of its non-lock members are implied, and so on. However, LockLint understands the abstraction of a condition variable and therefore does not break it down into its constituent members.

NOTE and _NOTE

The NOTE interface is used so that you can easily turn off annotations when they are not being parsed. The basic syntax of a note-style annotation is either:

NOTE(NoteInfo)
or:

_NOTE(NoteInfo)
The preferred use is NOTE rather than _NOTE. Header files that are to be used in multiple, unrelated projects, should use _NOTE to avoid conflicts. If NOTE has already been used, and you do not want to change, you should define some other macro (such as ANNOTATION) using _NOTE. For example, you might define an include file (say, annotation.h) that contains the following:

#define ANNOTATION _NOTE
#include <sys/note.h>

The NoteInfo that gets passed to the NOTE interface must syntactically fit one of the following:

NoteName

NoteName(Args)

NoteName is simply an identifier indicating the type of annotation. Args can be anything, so long as it can be tokenized properly and any parenthesis tokens are matched (so that the closing parenthesis can be found). Each distinct NoteName will have its own requirements regarding arguments.

This text uses NOTE to mean both NOTE and _NOTE, unless explicitly stated otherwise.

Where NOTE May Be Used
NOTE may be invoked only at certain well-defined places in source code:

A note-style annotation is not a statement; NOTE() may not be used inside an if/else/for/while body unless braces are used to make a block. For example, the following causes a syntax error:

if (x)
	NOTE(...)

NOTE() may be used only in the locations described above. For example, the following are invalid:

a = b NOTE(...) + 1;
typedef NOTE(...) struct foo Foo;
for (i=0; NOTE(...) i<10; i++) ...

How Data Is Protected

The following annotations are allowed both outside and inside a function definition. Remember that any name mentioned in an annotation must already have been declared.

NOTE(MUTEX_PROTECTS_DATA(Mutex, DataNameList))
NOTE(RWLOCK_PROTECTS_DATA(Rwlock, DataNameList))
NOTE(SCHEME_PROTECTS_DATA("description", DataNameList))
The first two annotations tell LockLint that the lock should be held whenever the specified data is accessed.

The third annotation, SCHEME_PROTECTS_DATA, describes how data are protected if it does not have a mutex or readers-writer lock. The description supplied for the scheme is simply text and is not semantically significant; LockLint responds by ignoring the specified data altogether. You may make description anything you like.

Some examples will help show how these annotations are used. The first example is very simple, showing a lock that protects two variables:

mutex_t lock1;
int a,b;
NOTE(MUTEX_PROTECTS_DATA(lock1, a b))

In the next example, a number of different possibilities are shown. Some members of struct foo are protected by a static lock, while others are protected by the lock on foo. Another member of foo is protected by some convention regarding its use.

mutex_t lock1;
struct foo {
	mutex_t lock;
	int mbr1, mbr2;
	struct {
		int mbr1, mbr2;
		char* mbr3;
	} inner;
	int mbr4;
};
NOTE(MUTEX_PROTECTS_DATA(lock1, foo::{mbr1 inner.mbr1}))
NOTE(MUTEX_PROTECTS_DATA(foo::lock, foo::{mbr2 inner.mbr2}))
NOTE(SCHEME_PROTECTS_DATA("convention XYZ", inner.mbr3))

A datum can only be protected in one way. If multiple annotations about protection (not only these three but also READ_ONLY_DATA) are used for a single datum, later annotations silently override earlier annotations. This allows for easy description of a structure in which all but one or two members are protected in the same way. For example, most of the members of struct BAR below are protected by the lock on struct foo, but one is protected by a global lock.

mutex_t lock1;
typedef struct {
	int mbr1, mbr2, mbr3, mbr4;
} BAR;
NOTE(MUTEX_PROTECTS_DATA(foo::lock, BAR))
NOTE(MUTEX_PROTECTS_DATA(lock1, BAR::mbr3))

Read-Only Variables

NOTE(READ_ONLY_DATA(DataNameList))
This annotation is allowed both outside and inside a function definition. It tells LockLint how data should be protected. In this case, it tells LockLint that the data should only be read, and not written.


Note - No error will be signaled if read-only data is written while it is considered invisible. Data is considered invisible when other threads cannot access it; for example, if other threads do not know about it.
This annotation is often used with data that is initialized and never changed thereafter. If the initialization is done at runtime before the data is visible to other threads, use annotations to let LockLint know that the data is invisible during that time.

LockLint knows that const data is read-only.

Allowing Unprotected Reads

NOTE(DATA_READABLE_WITHOUT_LOCK(DataNameList))
This annotation is allowed both outside and inside a function definition. It informs LockLint that the specified data may be read without holding the protecting locks. This is useful with an atomically readable datum that stands alone (as opposed to a set of data whose values are used together), since it is valid to peek at the unprotected data if you do not intend to modify it.

Hierarchical Lock Relationships

NOTE(RWLOCK_COVERS_LOCKS(RwlockName, LockNameList))
This annotation is allowed both outside and inside a function definition. It tells LockLint that a hierarchical relationship exists between a readers-writer lock and a set of other locks. Under these rules, holding the cover lock for write access affords a thread access to all data protected by the covered locks. Also, a thread must hold the cover lock for read access whenever holding any of the covered locks.

Using a readers-writer lock to cover another lock in this way is simply a convention; there is no special lock type. However, if LockLint is not told about this coverage relationship, it assumes that the locks are being used according to the usual conventions and generates errors.

The following example specifies that member lock of unnamed foo structures covers member lock of unnamed bar and zot structures:

NOTE(RWLOCK_COVERS_LOCKS(foo::lock, {bar zot}::lock))

Functions With Locking Side Effects

NOTE(MUTEX_ACQUIRED_AS_SIDE_EFFECT(MutexExpr))
NOTE(READ_LOCK_ACQUIRED_AS_SIDE_EFFECT(RwlockExpr))
NOTE(WRITE_LOCK_ACQUIRED_AS_SIDE_EFFECT(RwlockExpr))
NOTE(LOCK_RELEASED_AS_SIDE_EFFECT(LockExpr))
NOTE(LOCK_UPGRADED_AS_SIDE_EFFECT(RwlockExpr))
NOTE(LOCK_DOWNGRADED_AS_SIDE_EFFECT(RwlockExpr))
NOTE(NO_COMPETING_THREADS_AS_SIDE_EFFECT)
NOTE(COMPETING_THREADS_AS_SIDE_EFFECT)
These annotations are allowed only inside a function definition. Each tells LockLint that the function has the specified side effect on the specified lock--that is, that the function deliberately leaves the lock in a different state on exit than it was in when the function was entered. In the case of the last two of these annotations, the side effect is not about a lock but rather about the state of concurrency.

When stating that a readers-writer lock is acquired as a side effect, it you must specify whether the lock was acquired for read or write access.

A lock is said to be upgraded if it changes from being acquired for read-only access to being acquired for read/write access. Downgraded means a transformation in the opposite direction.

LockLint analyzes each function for its side effects on locks (and concurrency). Ordinarily, LockLint expects that a function will have no such effects; if the code has such effects intentionally, you must inform LockLint of that intent using annotations. If it finds that a function has different side effects from those expressed in the annotations, an error message will result.

The annotations described in this section refer generally to the function's characteristics and not to a particular point in the code. Thus, these annotations are probably best written at the top of the function. There is, for example, no difference (other than readability) between this:

foo() {
	NOTE(MUTEX_ACQUIRED_AS_SIDE_EFFECT(&foo))
	...
	if (x && y) {
		...
	}
}

and this:

foo() {
	...
	if (x && y) {
	NOTE(MUTEX_ACQUIRED_AS_SIDE_EFFECT(&foo))
		...
	}
}

If a function has such a side effect, the effect should be the same on every path through the function. LockLint will complain about and refuse to analyze paths through the function that have side effects other than those specified.

Single-Threaded Code

NOTE(COMPETING_THREADS_NOW)
NOTE(NO_COMPETING_THREADS_NOW)
These two annotations are allowed only inside a function definition. The first annotation tells LockLint that after this point in the code, other threads exist that might try to access the same data that this thread will access. The second function specifies that this is no longer the case; either no other threads are running or whatever threads are running will not be accessing data that this thread will access. While there are no competing threads, LockLint does not complain if the code accesses data without holding the locks that ordinarily protect that data.

These annotations are useful in functions that initialize data without holding locks before starting up any additional threads. Such functions may access data without holding locks, after waiting for all other threads to exit. So one might see something like this:

main() {
	<initialize data structures>
	NOTE(COMPETING_THREADS_NOW)
	<create several threads>
	<wait for all of those threads to exit>
	NOTE(NO_COMPETING_THREADS_NOW)
	<look at data structures and print results>
}


Note - LockLint realizes that when main() starts, no other threads are running.
LockLint does not issue a warning if, during analysis, it encounters a COMPETING_THREADS_NOW annotation when it already thinks competing threads are present. The condition simply nests. No warning is issued because the annotation may mean different things in each use (that is the notion of which threads compete may differ from one piece of code to the next). On the other hand, a NO_COMPETING_THREADS_NOW annotation that does not match a prior COMPETING_THREADS_NOW (explicit or implicit) will cause a warning.

Unreachable Code

NOTE(NOT_REACHED)
This annotation is allowed only inside a function definition. It tells LockLint that a particular point in the code cannot be reached, and therefore LockLint should ignore the condition of locks held at that point. This annotation need not be used after every call to exit(), for example, the way the lint annotation /* NOTREACHED */ is used. Simply use it in definitions for exit() and the like (primarily in LockLint libraries), and LockLint will figure out that code following calls to such functions is not reached. This annotation should seldom appear outside LockLint libraries. An example of its use (in a LockLint library) would be:

exit(int code) { NOTE(NOT_REACHED) }

Lock Order

NOTE(LOCK_ORDER(LockNameList))
This annotation, which is allowed either outside or inside a function definition, specifies the order in which locks should be acquired. It is similar to the assert order and order subcommands. See "Command Reference" on page 297.

To avoid deadlocks, LockLint assumes that whenever multiple locks must be held at once they are always acquired in a well-known order. If LockLint has been informed of such orderings using this annotation, an informative message will be produced whenever the order is violated.

This annotation may be used multiple times, and the semantics will be combined appropriately. For example, given the annotations

NOTE(LOCK_ORDER(a b c))
NOTE(LOCK_ORDER(b d))
LockLint will deduce the ordering:

NOTE(LOCK_ORDER(a d))
It is not possible to deduce anything about the order of c with respect to d in this example.

If a cycle exists in the ordering, an appropriate error message will be generated.

Variables Invisible to Other Threads

NOTE(NOW_INVISIBLE_TO_OTHER_THREADS(DataExpr, ...))
NOTE(NOW_VISIBLE_TO_OTHER_THREADS(DataExpr, ...))
These annotations, which are allowed only within a function definition, tell LockLint whether or not the variables represented by the specified expressions are visible to other threads; that is, whether or not other threads could access the variables.

Another common use of these annotations is to inform LockLint that variables that it would ordinarily assume are visible are in fact not visible, because no other thread has a pointer to it. This frequently occurs when allocating data off the heap--you can safely initialize the structure without holding a lock, since no other thread can yet see the structure.

Foo* p = (Foo*) malloc(sizeof(*p));
NOTE(NOW_INVISIBLE_TO_OTHER_THREADS(*p))
p->a = bar;
p->b = zot;
NOTE(NOW_VISIBLE_TO_OTHER_THREADS(*p))
add_entry(&global_foo_list, p);

Calling a function never has the side effect of making variables visible or invisible. Upon return from the function, all changes in visibility caused by the function are reversed.

Assuming Variables Are Protected

NOTE(ASSUMING_PROTECTED(DataExpr, ...))
This annotation, which is allowed only within a function definition, tells LockLint that this function assumes that the variables represented by the specified expressions are protected in one of the following ways:

LockLint will issue an error if none of these conditions is true.

f(Foo* p, Bar* q) {
	NOTE(ASSUMING_PROTECTED(*p, *q))
	p->a++;
	...
}

Assertions Recognized by LockLint

Locklint recognizes some assertions as relevant to the state of threads and locks. (For more information, see the assert man page.)

Assertions may be made only within a function definition, where a statement is allowed.


Note - ASSERT() is used in kernel and driver code, whereas assert() is used in user (application) code. For simplicity's sake, this document uses assert() to refer to either one, unless explicitly stated otherwise.

Making Sure All Locks Are Released

assert(NO_LOCKS_HELD);
LockLint recognizes this assertion to mean that, when this point in the code is reached, no locks should be held by the thread executing this test. Violations will be reported during analysis. A routine that blocks might want to use such an assertion to ensure that no locks are held when a thread blocks or exits.

The assertion also clearly serves as a reminder to someone modifying the code that any locks acquired must be released at that point.

It is really only necessary to use this assertion in leaf-level functions that block. If a function blocks only inasmuch as it calls another function that blocks, the caller need not contain this assertion as long as the callee does. Therefore this assertion will probably see its heaviest use in versions of libraries (for example, libc) written specifically for LockLint (like lint libraries).

The file synch.h defines NO_LOCKS_HELD as 1 if it has not already been otherwise defined, causing the assertion to succeed; that is, the assertion is effectively ignored at runtime. You can override this default runtime meaning by defining NO_LOCKS_HELD before you include either note.h or synch.h (which may be included in either order). For example, if a body of code uses only two locks called a and b, the following definition would probably suffice:

#define NO_LOCKS_HELD (!MUTEX_HELD(&a) && !MUTEX_HELD(&b))
#include <note.h>
#include <synch.h>

Doing so will not affect LockLint's testing of the assertion; that is, LockLint will still complain if any locks are held (not just a or b).

Making Sure No Other Threads Are Running

assert(NO_COMPETING_THREADS);
LockLint recognizes this assertion to mean that, when this point in the code is reached, no other threads should be competing with the one running this code. Violations (based on info provided by certain NOTE-style assertions) are reported during analysis. Any function that accesses variables without holding their protecting locks (operating under the assumption that no other relevant threads are out there touching the same data), should be so marked.

By default, this assertion is ignored at runtime--that is, it always succeeds. No generic runtime meaning for NO_COMPETING_THREADS is possible, since the notion of which threads compete involves knowledge of the application. For example, a driver might make such an assertion to say that no other threads are running in this driver for the same device. Because no generic meaning is possible, synch.h will define NO_COMPETING_THREADS as 1 if it has not already been otherwise defined.

However, you can override the default meaning for NO_COMPETING_THREADS by defining it before including either note.h or synch.h (which may be included in either order). For example, if the program keeps a count of the number of running threads in a variable called num_threads, the following definition might suffice:

#define NO_COMPETING_THREADS (num_threads == 1)
#include <note.h>
#include <synch.h>

Doing so will not affect LockLint's testing of the assertion.

Asserting Lock State

assert(MUTEX_HELD(lock_expr) && ...);
This assertion is widely used within the kernel. It performs runtime checking if assertions are enabled. The same capability exists in user code.

This code does roughly the same thing during LockLint analysis as it does when the code is actually run with assertions enabled; that is, it reports an error if the executing thread does not hold the lock as described.


Note - The threads library performs a weaker test, only checking that some thread holds the lock. LockLint performs the stronger test.
LockLint recognizes the use of MUTEX_HELD(), RW_READ_HELD(), RW_WRITE_HELD(), and RW_LOCK_HELD() macros, and negations thereof. Such macro calls may be combined using the && operators. For example, the following assertion will cause LockLint to check that a mutex is not held and that a readers-writer lock is write-held:

assert(p && !MUTEX_HELD(&p->mtx) && RW_WRITE_HELD(&p->rwlock));

LockLint also recognizes expressions like:

MUTEX_HELD(&foo) == 0


Command Reference

This section is organized as follows:

Subcommand Summary

page 297

Exit Status of LockLint Subcommands

page 298

Naming Conventions

page 299

LockLint Subcommands

page 302

Inversions

page 331

Subcommand Summary

Table 24-9 identifies the LockLint subcommands.

Table  24-9 LockLint Subcommands 

Subcommand
Effect

analyze

Tests the loaded files for lock inconsistencies; also validates against assertions

assert

Specifies what LockLint should expect to see regarding accesses and modifications to locks and variables

declare

Passes information to LockLint that it cannot deduce

disallow

Excludes the specified calling sequence in the analysis

disallows

Lists the calling sequences that are excluded from the analysis

files

Lists the source code files loaded via the load subcommand

funcptrs

Lists information about function pointers

funcs

Lists information about specific functions

help

Provides information about the specified keyword

ignore

Excludes the specified functions and variables from analysis

load

Specifies the .ll files to be loaded

locks

Lists information about locks

members

Lists members of the specified struct

order

Shows information about the order in which locks are acquired

pointer calls

Lists calls made through function pointers

reallow

Allows exceptions to the disallow subcommand

reallows

Lists the calling sequences reallowed through the reallow subcommand

refresh

Restores and resaves the latest saved state

restore

Restores the latest saved state

save

Saves the current state on a stack

saves

Lists the states saved on the stack through the save subcommand

start

Starts a LockLint session

sym

Lists the fully qualified names of functions and variables associated with the specified name

unassert

Removes some assertions specified through the assert subcommand

vars

Lists information about variables

Many LockLint subcommands require you to specify names of locks, variables, pointers, and functions. In C, it is possible for names to be ambiguous. See "Naming Conventions" on page 299 for details on specifying names to LockLint subcommands.

Exit Status of LockLint Subcommands

Table 24-10 lists the exit status of LockLint subcommands.

Table  24-10 Subcommand Exit Status 

Value
Meaning

0

Normal

1

System error

2

User error, such as incorrect options or undefined name

3

Multiple errors

5

LockLint detected error: violation of an assertion, potential data race or deadlock may have been found, unprotected data references, and so on.

10

Licensing error

Naming Conventions

Many LockLint subcommands require you to specify names of locks, variables, pointers, and functions. In C, it is possible for names to be ambiguous; for example, there may be several variables named foo, one of them extern and others static.

The C language does not provide a way of referring to ambiguously named variables that are hidden by the scoping rules. In LockLint, however, a way of referring to such variables is needed. Therefore, every symbol in the code being analyzed is given a formal name, a name that LockLint uses when referring to the symbol. Table 24-11 lists some examples of formal names for a function.

Table  24-11 LockLint Formal Names of Functions

Formal Name
Meaning

:func

extern function

file:func

static function

Table 24-12 lists the formal names for a variable, depending on its use as a lock, a pointer, or an actual variable.

Table  24-12 LockLint Formal Names of Variables

Formal Name
Meaning

:var

extern variable

file:var

static variable with file scope

:func/var

Variable defined in an extern function

file:func/var

Variable defined in a static function

tag::mbr

Member of an unnamed struct

file@line::mbr

Member of an unnamed, untagged struct

In addition, any of these may be followed by an arbitrary number of .mbr specifications to denote members of a structure.

Table 24-13 contains some examples of the LockLint naming scheme.

Table  24-13 Examples of LockLint Naming Conventions

Example
Meaning

:bar

External variable or function bar

:main/bar

static variable bar that is defined within extern function main

zot.c:foo/bar.zot

Member zot of static variable bar, which is defined within static function foo in file zot.c

foo::bar.zot.bim

Member bim of member zot of member bar of a struct with tag foo, where no name is associated with that instance of the struct (it was accessed through a pointer)

While LockLint refers to symbols in this way, you are not required to. You may use as little of the name as is required to unambiguously identify it. For example, you could refer to zot.c:foo/bar as foo/bar as long as there is only one function foo defining a variable bar. You can even refer to it simply as bar as long as there is no other variable by that name.

C allows the programmer to declare a structure without assigning it a tag. When you use a pointer to such a structure, LockLint must make up a tag by which to refer to the structure. It generates a tag of the format filename@line_number. For example, if you declare a structure without a tag at line 42 of file foo.c, and then refer to member bar of an instance of that structure using a pointer, as in:

typedef struct { ...  } foo;
foo *p;
func1() { p->bar = 0; }

LockLint sees that as a reference to foo.c@42::bar.

Because members of a union share the same memory location, LockLint treats all members of a union as the same variable. This is accomplished by using a member name of % regardless of which member is accessed. Since bit fields typically involve sharing of memory between variables, they are handled similarly: % is used in place of the bit field member name.

When you list locks and variables, you are only seeing those locks and variables that are actually used within the code represented by the .ll files. No information is available from LockLint on locks, variables, pointers, and functions that are declared but not used. Likewise, no information is available for accesses through pointers to simple types, such as this one:

int *ip = &i;
*ip = 0;

When simple names (for example, foo) are used, there is the possibility of conflict with keywords in the subcommand language. Such conflicts can be resolved by surrounding the word with double quotes, but remember that you are typing commands to a shell, and shells typically consume the outermost layer of quotes. Therefore you have to escape the quotes, as in this example:

% lock_lint ignore foo in func \"func\"

If two files with the same base name are included in an analysis, and these two files contain static variables by the same name, confusion can result. LockLint will think the two variables are the same.

If you duplicate the definition for a struct with no tag, LockLint will not recognize the definitions as the same struct. The problem is that LockLint makes up a tag based on the file and line number where the struct is defined (such as x.c@24), and that tag will differ for the two copies of the definition.

If a function contains multiple automatic variables of the same name, LockLint cannot tell them apart. Since LockLint ignores automatic variables except when they are used as function pointers, this does not come up often. In the following code, for example, LockLint uses the name :foo/fp for both function pointers:

int foo(void (*fp)()) {
	(*fp)();
	{
		void (*fp)() = get_func();
		(*fp)();
		 ...

LockLint Subcommands

Some of these are equivalent to subcommands such as assert. Source code annotations are often preferable to subcommands, because they

See also "Source Code Annotations" on page 281 for information on annotations you can put directly into your source code.

analyze

analyze has the following syntax:

analyze [-hv]

Analyzes the loaded files for lock inconsistencies that may lead to data races and deadlocks. This subcommand may produce a great deal of output, so you may want to redirect the output to a file. This subcommand can be run only once for each saved state. (See "save" on page 325).

-h (history) produces detailed information for each phase of the analysis. No additional errors are issued.

-v (verbose) generates additional messages during analysis:

Output from the analyze subcommand can be particularly abundant if:

LockLint analyze Phases
Each problem encountered during analysis is reported on one or more lines, the first of which begins with an asterisk. Where possible, LockLint provides a complete traceback of the calls taken to arrive at the point of the problem. The analysis goes through the following phases:

1. Checking for functions with variable side effects on locks

If a disallow sequence specifies that a function with locking side effects should not be analyzed, LockLint will produce incorrect results. If such disallow sequences are found, they are reported and analysis will not proceed.

2. Preparing locks to hold order info

LockLint processes the asserted lock order information available to it. If LockLint detects a cycle in the asserted lock order, the cycle is reported as an error.

3. Checking for function pointers with no targets

LockLint cannot always deduce assignments to function pointers. During this phase, LockLint reports any function pointer for which it does not think there is at least one target, whether deduced from the source or declared a func.ptr target.

4. Removing accesses to ignored variables

To improve performance, LockLint removes references to ignored variables at this point. (This affects the output of the vars subcommands.)

5. Preparing functions for analysis

During this phase, LockLint determines what side effects each function has on locks. (A side effect is a change in a lock's state that is not reversed before returning.) An error results if

LockLint expects that a function will have no side effects on locks, except where side effects have been added using the assert side effect subcommand.

6. Preparing to recognize calling sequences to allow/disallow

Here LockLint is processing the various allow/disallow subcommands that were issued, if any. No errors or warnings are reported here.

7. Checking locking side effects in function pointer targets

Calls through function pointers may target several functions. All functions that are targets of a particular function pointer must have the same side effects on locks (if any). If a function pointer has targets that differ in their side effects, analysis does not proceed.

8. Checking for consistent use of locks with condition variables

Here LockLint checks that all waits on a particular condition variable use the same mutex. Also, if you assert that particular lock to protect that condition variable, LockLint makes sure you use that lock when waiting on the condition variable.

9. Determining locks consistently held when each function is entered

During this phase, LockLint reports violations of assertions that locks should be held upon entry to a function (see assert subcommand). Errors such as locking a mutex lock that is already held, or releasing a lock that is not held, are also reported. Locking an anonymous lock, such as foo::lock, more than once is not considered an error, unless the declare one command has been used to indicate otherwise. (See "Inversions" on page 331 for details on anonymous data.)

10. Determining locks consistently held when each variable is accessed

During this phase, LockLint reports violations of assertions that a lock should be held when a variable is accessed (see the assert subcommand). Also, any writes to read-only variables are reported.

Occasionally you may get messages that certain functions were never called. This can occur if a set of functions (none of which are root functions) call each other. If none of the functions is called from outside the set, LockLint reports that the functions were never called at all. The declare root subcommand can be used to fix this situation for a subsequent analysis.

Using the disallow subcommand to disallow all sequences that reach a function will also cause a message that the function is never called.

Once the analysis is done, you can find still more potential problems in the output of the vars and order subcommands.

assert

assert has the following syntax:

assert side effect

mutex

acquired in

func ...

assert side effect

rwlock [read]

acquired in

func ...

assert side effect

lock

released in

func ...

assert side effect

rwlock

upgraded in

func ...

assert side effect

rwlock

downgraded in

func ...

assert mutex|rwlock

protects

var ...

assert mutex

protects

func ...

assert rwlock

protects

[reads in]

func ...

assert order

lock lock ...

assert read only

var ...

assert rwlock

covers

lock ...

These subcommands tell LockLint how the programmer expects locks and variables to be accessed and modified in the application being checked. During analysis any violations of such assertions are reported.


Note - If a variable is asserted more than once, only the last assert takes effect.
assert side effect

The following assertions state that given functions have side effects with respect to lock states:

assert side effect mutex acquired in

assert side effect rwlock [read] acquired in

assert side effect lock released in

assert side effect rwlock upgraded in

assert side effect rwlock downgraded in

A side effect is a change made by a function in the state of a lock, a change that is not reversed before the function returns. If a function contains locking side effects and no assertion is made about the side effects, or the side effects differ from those that are asserted, a warning will be issued during the analysis. The analysis then continues as if the unexpected side effect never occurred.


Note - There is another kind of side effect called an inversion. See
"Inversions" on page 331, and the locks or funcs subcommands, for more details.

Warnings are also issued if the side effects produced by a function could differ from call to call (for example, conditional side effects). The keywords acquired in, released in, upgraded in, and downgraded in describe the type of locking side effect being asserted about the function. The keywords correspond to the side effects available via the threads library interfaces and the DDI and DKI Kernel Functions (see mutex(3T), rwlock(3T), mutex(9F) and rwlock(9F)).

The side effect assertion for rwlocks takes an optional argument read; if read is present, the side effect is that the function acquires read-level access for that lock. If read is not present, the side effect specifies that the function acquires write-level access for that lock.

assert mutex|rwlock protects

Asserting that a mutex lock protects a variable causes an error whenever the variable is accessed without holding the mutex lock. Asserting that a readers-writer lock protects a variable causes an error whenever the variable is read without holding the lock for read access or written without holding the lock for write access. Subsequent assertions as to which lock protects a variable override any previous assertions; that is, only the last lock asserted to protect a variable is used during analysis.

assert mutex protects

Asserting that a mutex lock protects a function causes an error whenever the function is called without holding the lock. For root functions, the analysis is performed as if the root function were called with this assertion being true.

assert rwlock protects

Asserting that a readers-writer lock protects a function causes an error whenever the function is called without holding the lock for write access. Asserting that a readers-writer lock protects reads in a function causes an error whenever the function is called without holding the lock for read access. For root functions, the analysis is performed as if the root function were called with this assertion being true.


Note - To avoid flooding the output with too many violations of a single assert... protects subcommand, a maximum of 20 violations of any given assertion is shown. This limit does not apply to the assert order subcommand.
assert order

Informs LockLint of the order in which locks should be acquired. That is, LockLint assumes that the program avoids deadlocks by adhering to a well-known lock order. Using this subcommand, you can make LockLint aware of the intended order so that violations of the order can be printed during analysis.

assert read only

States that the given set of variables should never be written by the application; LockLint reports any writes to the variables. Unless a variable is read-only, reading the variable while no locks are held will elicit an error since LockLint assumes that the variable could be written by another thread at the same time.

assert rwlock covers

Informs LockLint of the existence of a hierarchical locking relationship. A readers-writer lock may be used in conjunction with other locks (mutex or readers-writer) in the following way to increase performance in certain situations:

Using assert rwlock covers prevents LockLint from issuing error messages when a thread accesses variables while holding the cover for write access but not the covered lock. It also enables checks to ensure that a covered lock is never held when its cover is not.

declare

declare has the following syntax:

declare

mutex

mutex . . .

declare

rwlocks

rwlock ...

declare

func_ptr

targets

func ...

declare

nonreturning

func ...

declare

one

tag ...

declare

readable

var ...

declare

root

func ...

These subcommands tell LockLint things that it cannot deduce from the source presented to it.

declare mutex mutex
declare rwlocks rwlock

These subcommands (along with declare root, below) are typically used when analyzing libraries without a supporting harness. The subcommands declare mutex and declare rwlocks create mutex and reader-writer locks of the given names. These symbols can be used in subsequent assert subcommands.

declare func_ptr targets func

Adds the specified functions to the list of functions that could be called through the specified function pointer.

LockLint manages to gather a good deal of information about function pointer targets on its own by watching initializations and assignments. For example, for the code

struct foo { int (*fp)(); } foo1 = { bar };

LockLint does the equivalent of the command

% lock_lint declare foo::fp targets  bar


Caution - LockLint does not yet do the following (for the above example):

% lock_lint declare foo1.fp targets  bar


However, it does manage to do both for assignments to function pointers. See
"Inversions" on page 331.


declare nonreturning func

Tells LockLint that the specified functions do not return. Armed with this knowledge, LockLint will not give errors about lock state after calls to such functions.

declare one tag

Tells LockLint that only one unnamed instance exists of each structure whose tag is specified. This knowledge makes it possible for LockLint to give an error if a lock in that structure is acquired multiple times without being released. Without this knowledge, LockLint does not complain about multiple acquisitions of anonymous locks (for example, foo::lock), since two different instances of the structure could be involved. (See "Inversions" on page 331.)

declare readable var

Tells LockLint that the specified variables may be safely read without holding any lock, thus suppressing the errors that would ordinarily occur for such unprotected reads.

declare root func

Tells LockLint to analyze the given functions as a root function; by default, if a function is called from any other function, LockLint will not attempt to analyze that function as the root of a calling sequence.

A root function is a starting point for the analysis; functions that are not called from within the loaded files are naturally roots. This includes, for example, functions that are never called directly but are the initial starting point of a thread (for example, the target function of a thread_create call). However, a function that is called from within the loaded files might also be called from outside the loaded files, in which case you should use this subcommand to tell LockLint to use the function as a starting point in the analysis.

disallow

disallow has the following syntax:

disallow func ...

Tells LockLint that the specified calling sequence should not be analyzed. For example, to prevent LockLint from analyzing any calling sequence in which f() calls g() calls h(), use the subcommand

% lock_lint disallow f g h

Function pointers can make a program appear to follow many calling sequences that do not in practice occur. Bogus locking problems, particularly deadlocks, can appear in such sequences. (See also the description of the subcommand "reallow" on page 324.) disallow prevents LockLint from following such sequences.

disallows

disallows has the following syntax:

disallows

Lists the calling sequences that are disallowed by the disallow subcommand.

exit

Actually, there is no exit subcommand for LockLint. To exit LockLint, use the exit command for the shell you are using.

files

files has the following syntax:

files

Lists the .ll versions of the source code files loaded with the load subcommand.

funcptrs

funcptrs has the following syntax:

funcptrs [-botu] func_ptr ...  
funcptrs [-blotuz]

Lists information about the function pointers used in the loaded files. One line is produced for each function pointer.

-b

(bound) This option lists only function pointers to which function targets have been bound, that is it suppresses the display of function pointers for which there are no bound targets.

-l

(long) Equivalent to -ot.

-o

(other) This presents the following information about each function pointer:

Calls=#

Indicates the number of places in the loaded files this function pointer is used to call a function.

=nonreturning

Indicates that a call through this function pointer never returns (none of the functions targeted ever return).

-t

(targets) This option lists the functions currently bound as targets to each function pointer listed, as follows:

targets={ func ...  }

-u

(unbound) This lists only those function pointers to which no function targets are bound. That is, suppresses the display of function pointers for which there are bound targets.

-z

(zero) This lists function pointers for which there are no calls. Without this option information is given only on function pointers through which calls are made.

You can combine various options to funcptrs:

1. This example lists information about the specified function pointers. By default, this variant of the subcommand gives all the details about the function pointers, as if -ot had been specified.

funcptrs [-botu] func_ptr ...

2. This example lists information about all function pointers through which calls are made. If -z is used, even function pointers through which no calls are made are listed.

funcptrs [-blotuz]

funcs

funcs has the following syntax:

funcs [-adehou]	func ...
funcs [-adehilou]
funcs [-adehlou] [directly] called by	func ...
funcs [-adehlou] [directly] calling	func ...
funcs [-adehlou] [directly] reading	var ...
funcs [-adehlou] [directly] writing	var ...
funcs [-adehlou] [directly] accessing	var ...
funcs [-adehlou] [directly] affecting	lock ...
funcs [-adehlou] [directly] inverting	lock ...

funcs lists information about the functions defined and called in the loaded files. Exactly one line is printed for each function.

-a

(asserts) This option shows information about which locks are supposed to be held on entry to each function, as set by the assert subcommand. When such assertions have been made, they show as:

	asserts={ lock ...  }
	read_asserts={ lock ...  }

An asterisk will appear before the name of any lock that was not consistently held upon entry (after analysis).

-e

(effects) This option shows information about the side effects each function has on locks (for example, "acquires mutex lock foo"). If a function has such side effects, they are shown as:

side_effects={ effect [, effect] ...  }

Using this option prior to analysis shows side effects asserted by an assert side effect subcommand. After analysis, information on side effects discovered during the analysis is also shown.

-d

(defined) This option shows only those functions which are defined in the loaded files. That is, that it suppresses the display of undefined functions.

-h

(held) This option shows information about which locks were consistently held when the function was called (after analysis). Locks consistently held for read (or write) on entry show as:

	held={ lock ...  }+{ lock ...  }
	read_held={ lock ...  }+{ lock ...  }

The first list in each set is the list of locks consistently held when the function was called; the second is a list of inconsistently held locks--locks that were sometimes held when the function was called, but not every time.

-i

(ignored) This option lists ignored functions.

-l

(long) Equivalent to -aeoh.

-o

(other) This option causes LockLint to present, where applicable, the following information about each function:

=ignored

Indicates that LockLint has been told to ignore the function using the ignore subcommand.

=nonreturning

Indicates that a call through this function never returns (none of the functions targeted ever return).

=rooted

Indicates that the function was made a root using the declare root subcommand.

=root

Indicates that the function is naturally a root (is not called by any function).

=recursive

Indicates that the function makes a call to itself.

=unanalyzed

Indicates that the function was never called during analysis (and is therefore unanalyzed). This differs from =root in that this can happen when foo calls bar and bar calls foo, and no other function calls either foo or bar, and neither have been rooted (see =rooted). So, because foo and bar are not roots, and they can never be reached from any root function, they have not been analyzed.

calls=#

Indicates the number of places in the source code, as represented by the loaded files, where this function is called. These calls may not actually be analyzed; for example, a disallow subcommand may prevent a call from ever really taking place.

-u

(undefined) This option shows only those functions that are undefined in the loaded files.

funcs [-adehou] func ...

Lists information about individual functions. By default, this variant of the subcommand gives all the details about the functions, as if -aeho had been specified.

funcs [-adehilou]

Lists information about all unignored functions. If -i is used, even ignored functions are listed.

funcs [-adehlou] [directly] called by func ...

Lists only those functions that may be called as a result of calling the specified functions. If directly is used, only those functions called by the specified functions are listed. If directly is not used, any functions those functions called are also listed, and so on.

funcs [-adehlou] [directly] calling func ...

Lists only those functions that, when called, may result in one or more of the specified functions being called. See notes below on directly.

funcs [-adehlou] [directly] reading var ...

Lists only those functions that, when called, may result in one or more of the specified variables being read. See notes below on directly.

funcs [-adehlou] [directly] writing var ...

Lists only those functions that, when called, may result in one or more of the specified variables being written. See notes below on directly.

funcs [-adehlou] [directly] accessing var ...

Lists only those functions that, when called, may result in one or more of the specified variables being accessed (read or written). See notes below on directly.

funcs [-adehlou] [directly] affecting lock ...

Lists only those functions that, when called, may result in one or more of the specified locks being affected (acquired, released, upgraded, or downgraded). See notes below on directly.

funcs [-adehlou] [directly] inverting lock ...

Lists only those functions that invert one or more of the specified locks. (See "Inversions" on page 331.) If directly is used, only those functions that themselves invert one or more of the locks (actually release them) are listed. If directly is not used, any function that is called with a lock already held, and then calls another function that inverts the lock, is also listed, and so on.

For example, in the following code, f3() directly inverts lock m, and f2() indirectly inverts it:

f1() { pthread_mutex_unlock(&m); f2(); pthread_mutex_lock(&m); }
f2() { f3(); }
f3() { pthread_mutex_unlock(&m); pthread_mutex_lock(&m); } 

About directly
Except where stated otherwise, variants that allow the keyword directly only list the functions that themselves fit the description. If directly is not used, all the functions that call those functions will be listed, and any functions that call those functions, and so on.

help

help has the following syntax:

help [keyword]

Without a keyword, help displays the subcommand set.

With a keyword, help gives helpful information relating to the specified keyword. The keyword may be the first word of any LockLint subcommand. There are also a few other keywords for which help is available:

condvars
locking
example
makefile
ifdef
names
inversions
overview
limitations
shell

If environment variable PAGER is set, that program is used as the pager for help. If PAGER is not set, more is used.

ignore

ignore has the following syntax:

ignore func|var ...  [ in func ...  ]

Tells LockLint to exclude certain functions and variables from the analysis. This exclusion may be limited to specific functions using the in func ... clause; otherwise the exclusion applies to all functions.

The commands

% lock_lint funcs -io | grep =ignored
% lock_lint vars -io | grep =ignored

show which functions and variables are ignored.

load

load has the following syntax:

load file ...

Loads the specified .ll files. The extension may be omitted, but if an extension is specified, it must be .ll. Absolute and relative paths are allowed. You are talking to a shell, so the following are perfectly legal (depending upon your shell's capabilities):

% lock_lint load *.ll
% lock_lint load ../foo/abcdef{1,2}
% lock_lint load \Qfind .  -name \*.ll -print\Q

The text for load is changed extensively. To set the new text, type:

% lock_lint help load

locks

locks has the following syntax:

locks [-co] lock ...
locks [-col]
locks [-col] [directly] affected by func ...
locks [-col] [directly] inverted by func ...

Lists information about the locks of the loaded files. Only those variables that are actually used in lock manipulation routines are shown; locks that are simply declared but never manipulated will not be shown.

-c

(cover) This option shows information about lock hierarchies. Such relationships are described using the assert rwlock covers subcommand. (When locks are arranged in such a hierarchy, the covering lock must be held, at least for read access, whenever any of the covered locks is held. While holding the covering lock for write access, it is unnecessary to acquire any of the covered locks.) If a lock covers other locks, those locks show as:

covered={ lock ...  } 

If a lock is covered by another lock, the covering lock shows as

cover=lock

-l

(long) Equivalent to -co.

-o

(other) Causes the type of the lock to be shown as (type) where type is mutex, rwlock, or ambiguous type [used as a mutex in some places and as a rwlock (readers-writer) in other places].

locks [-co] lock ...

Lists information about individual locks. By default, this variant of the subcommand gives all the details about the locks, as if -co had been specified.

locks [-col]

Lists information about all locks.

locks [-col] [directly] affected by func ...

Lists only those locks that may be affected (acquired, released, upgraded, or downgraded) as a result of calling the specified functions. If the keyword directly is used, only functions that use the threads library routines directly to affect a lock (acquire, release, upgrade, or downgrade) are listed. If the keyword directly is not used, any function that calls a function that affects a lock will be listed, and any function calling that function will be listed, and so on.

locks [-col] [directly] inverted by func ...

Lists only those locks that may be inverted by calling one of the specified functions. (See "Inversions" on page 331.)

If the keyword directly is used, only those locks that are directly inverted by the specified functions (that is, the functions that actually release and reacquire locks using a threads library routine) are listed. If the keyword directly is not used, a lock that is held by one of the specified functions and inverted by some function called from it (and so on) is also listed. For example, in the following code f1 directly inverts m1, and indirectly inverts m2.

f1() { pthread_mutex_unlock(&m1); f2(); pthread_mutex_lock(&m1); }
f2() { f3(); }
f3() { pthread_mutex_unlock(&m2); pthread_mutex_lock(&m2); } 

members

members has the following syntax:

members struct_tag

Lists the members of the struct with the specified tag, one per line. For structures that were not assigned a tag, the notation file@line is used (for example, x.c@29), where the file and line number are the source location of the struct's declaration.

members is particularly useful to use as input to other LockLint subcommands. For example, when trying to assert that a lock protects all the members of a struct, the following command suffices:

% lock_lint assert foo::lock protects \Qlock_lint members foo\Q


Note - The members subcommand does not list any fields of the struct that are defined to be of type mutex_t, rwlock_t, krwlock_t, or kmutex_t.

order

order has the following syntax:

order [lock [lock]]
order summary

The order subcommand lists information about the order in which locks are acquired by the code being analyzed. It may be run only after the analyze subcommand.

order [lock [lock]]

Shows the details about lock pairs. For example, the command

% lock_lint order foo bar

shows whether an attempt was made to acquire lock bar while holding lock foo. The output looks something like the following:

:foo :bar seen (first never write-held), valid

First the output tells whether such an attempt actually occurred (seen or unseen). If the attempt occurred, but never with one or both of the locks write-held, a parenthetical message to that effect appears, as shown. In this case, foo was never write-held while acquiring bar.

If an assertion was made about the lock order, the output shows whether the specified order is valid or invalid according to the assertion. If there was no assertion about the order of foo and bar, or if both orders were asserted (presumably because the user wanted to see all places where one of the locks was held while acquiring the other), the output will indicate neither valid nor invalid.

order summary

Shows in a concise format the order in which locks are acquired. For example, the subcommand might show

:f :e :d :g :a
:f :c :g :a

In this example, there are two orderings because there is not enough information to allow locks e and d to be ordered with respect to lock c.

Some cycles will be shown, while others won't. For example,

	:a :b :c :b

is shown, but

	:a :b :c :a

(where no other lock is ever held while trying to acquire one of these) is not. Deadlock information from the analysis will still be reported.

pointer calls

pointer calls has the following syntax:

pointer calls

Lists calls made through function pointers in the loaded files. Each call is shown as:

function [location of call] calls through funcptr func_ptr

For example,

foo.c:func1 [foo.c,84] calls through funcptr bar::read

means that at line 84 of foo.c, in func1 of foo.c, the function pointer bar::read (member read of a pointer to struct of type bar) is used to call a function.

reallow

reallow has the following syntax:

reallow func ...

Allows you to make exceptions to disallow subcommands. For example, to prevent LockLint from analyzing any calling sequence in which f() calls g() calls h(), except when f() is called by e() which was called by d(), use the commands

% lock_lint disallow f g h
% lock_lint reallow d e f g h

In some cases you may want to state that a function should only be called from a particular function, as in this example:

% lock_lint disallow f
% lock_lint reallow e f


Note - A reallow subcommand only suppresses the effect of a disallow subcommand if the sequences end the same. For example, after the following commands, the sequence d e f g h would still be disallowed:

% lock_lint disallow e f g h
% lock_lint reallow d e f g


reallows

reallows has the following syntax:

reallows

Lists the calling sequences that are reallowed, as specified using the reallow subcommand.

refresh

refresh has the following syntax:

refresh

1. Pops the saved-state stack, restoring LockLint to the state of the top of the saved-state stack

2. Prints the description, if any, associated with that state

3. Immediately resaves (pushes) the state again (see the restore and save subcommands)

restore

restore has the following syntax:

restore

Pops the saved state stack, restoring LockLint to the state of the top of the saved-state stack, and prints the description, if any, associated with that state.

The saved state stack is a LIFO (Last-In-First-Out) stack. Once a saved state is restored (popped) from the stack, that state is no longer on the saved-state stack. If the state needs to be saved and restored repeatedly, simply save the state again immediately after restoring it, or use the refresh subcommand.

save

save has the following syntax:

save description

Saves the current state of the tool on a stack. The user-specified description is attached to the state. Saved states form a LIFO (Last-In-First-Out) stack, so that the last state saved is the first one restored.

This subcommand is commonly used to save the state of the tool before running the analyze subcommand, which may be run only once on a given state. For example, you may do the following:

%: lock_lint load *.ll
%: lock_lint save Before Analysis
%: lock_lint analyze
	<output from analyze>
%: lock_lint vars -h | grep \*
	<apparent members of struct foo are not consistently protected>
%: lock_lint refresh Before Analysis
%: lock_lint assert lock1 protects \Qlock_lint members foo\Q
%: lock_lint analyze
	<output now contains info about where the assertion is violated>

saves

saves has the following syntax:

saves

Lists the descriptions of the states saved on the saved stack via the save subcommand. The descriptions are shown from top to bottom, with the first description being the most recently saved state that has not been restored, and the last description being the oldest state saved that has not been restored.

start

start has the following syntax:

start [cmd]

Starts a LockLint session. A LockLint session must be started prior to using any other LockLint subcommand. By default, start establishes LockLint's context and starts a subshell for the user, as specified via $SHELL, within that context. The only piece of the LockLint context exported to the shell is the environment variable LL_CONTEXT. LL_CONTEXT contains the path to the temporary directory of files used to maintain a LockLint session.

cmd specifies a command and its path and options. By default, if cmd is not specified, the value of $SHELL is used.


Note - To exit a LockLint session use the exit command of the shell you are using.
See "Limitations of LockLint" on page 277, for more on setting up the LockLint environment for a start subcommand.

Examples
The following examples show variations of the start subcommand.

1. Using the default

% lock_lint start

LockLint's context is established and LL_CONTEXT is set. Then the program identified by $SHELL is executed. Normally, this will be your default shell. LockLint subcommands can now be entered. Upon exiting the shell, the LockLint context will be removed.

2. Using a pre-written script

% lock_lint start foo

The LockLint context is established and LL_CONTEXT is set. Then, the command /bin/csh -c foo is executed. This results in executing the C shell command file foo, which contains LockLint commands. Upon completing the execution of the commands in foo by /bin/csh, the LockLint context is removed.

If you use a shell script to start LockLint, insert #! in the first line of the script to define the name of the interpreter that processes that script. For example, to specify the C-shell the first line of the script is:

#! /bin/csh

3. Starting up with a specific shell

In this case, the user starts LockLint with the Korn shell:

% lock_lint start /bin/ksh

After establishing the LockLint context and setting LL_CONTEXT, the command /bin/ksh is executed. This results in the user interacting with an interactive Korn shell. Upon exiting the Korn shell, the LockLint context is removed.

sym

sym has the following syntax:

sym name ...

Lists the fully qualified names of various things the specified names could refer to within the loaded files. For example, foo might refer both to variable x.c:func1/foo and to function y.c:foo, depending on context.

unassert

unassert has the following syntax:

unassert vars var ...

Undoes any assertion about locks protecting the specified variables. There is no way to remove an assertion about a lock protecting a function.

vars

vars has the following syntax:

vars [-aho] var ...
vars [-ahilo]
vars [-ahlo] protected by lock
vars [-ahlo] [directly] read by func ...
vars [-ahlo] [directly] written by func ...
vars [-ahlo] [directly] accessed by func ...

Lists information about the variables of the loaded files. Only those variables that are actually used are shown; variables that are simply declared in the program but never accessed will not be shown.

-a

(assert) Shows information about which lock is supposed to protect each variable, as specified by the assert mutex|rwlock protects subcommand. The information is shown as follows:

assert=lock

If the assertion is violated, then after analysis this will be preceded by an asterisk, such as *assert=<lock>.

-h

(held) Shows information about which locks were consistently held when the variable was accessed. This information is shown after the analyze subcommand has been run. If the variable was never accessed, this information is not shown. When it is shown, it looks like this:

held={ <lock> ...  }

If no locks were consistently held and the variable was written, this is preceded by an asterisk, such as *held={ }.

Unlike funcs, the vars subcommand lists a lock as protecting a variable even if the lock was not actually held, but was simply covered by another lock. (See assert rwlock covers in the description of "assert" on page 305.)

-i

(ignored) causes even ignored variables to be listed.

-l

(long) Equivalent to -aho.

-o

(other) Where applicable, shows information about each variable:

=cond_var

Indicates that this variable is used as a condition variable.

=ignored

Indicates that LockLint has been told to ignore the variable explicitly via an ignore subcommand.

=read-only

Means that LockLint has been told (by assert read only) that the variable is read-only, and will complain if it is written. If it is written, then after analysis this will be followed by an asterisk, such as =read-only* for example.

=readable

Indicates that LockLint has been told by a declare readable subcommand that the variable may be safely read without holding a lock.

=unwritten

May appear after analysis, meaning that while the variable was not declared read-only, it was never written.

vars [-aho] var ...

Lists information about individual variables. By default, this variant of the subcommand gives all the details about the variables, as if -aho had been specified.

vars [-ahilo]

Lists information about all unignored variables. If -i is used, even ignored variables are listed.

vars [-ahlo] protected by lock

Lists only those variables that are protected by the specified lock. This subcommand may be run only after the analyze subcommand has been run.

vars [-ahlo] [directly] read by func ...

Lists only those variables that may be read as a result of calling the specified functions. See notes below on directly.

vars [-ahlo] [directly] written by func ...

Lists only those variables that may be written as a result of calling the specified functions. See notes below on directly.

vars [-ahlo] [directly] accessed by func ...

Lists only those variables that may be accessed (read or written) as a result of calling the specified functions.

About directly
Those variants that list the variables accessed by a list of functions can be told to list only those variables that are directly accessed by the specified functions. Otherwise the variables accessed by those functions, the functions they call, the functions those functions call, and so on, will be listed.

Inversions

A function is said to invert a lock if the lock is already held when the function is called, and the function releases the lock, such as:

foo() {
	pthread_mutex_unlock(&mtx);
	...
	pthread_mutex_lock(&mtx);
}

Lock inversions are a potential source of insidious race conditions, since observations made under the protection of a lock may be invalidated by the inversion. In the following example, if foo() inverts mtx, then upon its return zort_list may be NULL (another thread may have emptied the list while the lock was dropped):

ZORT* zort_list;
	/* VARIABLES PROTECTED BY mtx: zort_list */

void f() {
	pthread_mutex_lock(&mtx);
	if (zort_list == NULL) /* trying to be careful here */
	return;
	foo();
	zort_list->count++; /* but zort_list may be NULL here!! */
	pthread_mutex_unlock(&mtx);
}

Lock inversions may be found using the commands:

% lock_lint funcs [directly] inverting lock ...
% lock_lint locks [directly] inverted by func ...

An interesting question to ask is "Which functions acquire locks that then get inverted by calls they make?" That is, which functions are in danger of having stale data? The following (Bourne shell) code can answer this question:

$ LOCKS=\Qlock_lint locks`
$ lock_lint funcs calling \Qlock_lint funcs inverting $LOCKS\Q

The following gives similar output, separated by lock:

for lock in \Qlock_lint locks\Q
do
	echo "functions endangered by inversions of lock $lock"
	lock_lint funcs calling \Qlock_lint funcs inverting $lock\Q
done




Previous Next Contents Index Doc Set Home