1. Configuring the Compiler

  1. 1.1. Configuration files
  2. 1.2. Low level configuration
  3. 1.3. Scoping options

This document describes the capabilities of the TenDRA C checker for enforcing the ISO C standard as well as features for detecting areas left undefined by the standard. It also lists the non-ISO dialect features supported by the checker in order to provide compatibility with older versions of C and allow the use of third-party source which may contain non-standard constructs.

This majority of this document describes how the C++ producer can be configured to apply extra static checks or to support various dialects of C++. In all cases the default behaviour is precisely that specified in the ISO C++ standard with no extra checks.

1.1. Configuration files

Certain basic type information is specified using a portability table, which may be specified to the producer using the -n option. The syntax for this file is documented by tdfc2portability.

Mappings to arbitary execution character sets may be specified using the -C option. The default is to the use the same character set as the host system. The syntax for this file is documented by tdfc2charset.

The tcc frontend is typically responsible for providing these files; see the TCC Users' Guide for details.

1.2. Low level configuration

The primary method of configuration is by means of #pragma directives. These directives may be placed within the program itself, however it is generally more convenient to group them into a start-up file in order to create a user-defined compilation profile (see the -X option for tcc). The #pragma directives recognised by the C++ producer have one of the equivalent forms:

#pragma TenDRA ....
#pragma TenDRA++ ....

Some of these are common to the C and C++ producers (although often with differing default behaviour). The C producer will ignore any TenDRA++ directives, so these may be used in compilation profiles which are to be used by both producers. In the descriptions below, the presence of a ++ is used to indicate a directive which is C++ specific; the other directives are common to both producers.

Within the description of the #pragma syntax, on stands for on, off or warning, allow stands for allow, disallow or warning, string-literal is any string literal, integer-literal is any integer literal, identifier is any simple, unqualified identifier name, and type-id is any type identifier. Other syntactic items are described in the text. A complete grammar for the #pragma directives accepted by the C++ producer is given in The Pragma Token Syntax.

The simplest level of configuration is to reset the severity level of a particular error message using:

#pragma TenDRA++ error string-literal on
#pragma TenDRA++ error string-literal allow

The given string-literal should name an error from the make_err error catalogue. A severity of on or disallow indicates that the associated diagnostic message should be an error, which causes the compilation to fail. A severity of warning indicates that the associated diagnostic message should be a warning, which is printed but allows the compilation to continue. A severity of off or allow indicates that the associated error should be ignored. Reducing the severity of any error from its default value, other than via one of the dialect directives described in this section, results in undefined behaviour.

The next level of configuration is to reset the severity level of a particular compiler option using:

#pragma TenDRA++ option string-literal on
#pragma TenDRA++ option string-literal allow

The given string-literal should name an option from the option catalogue. The simplest form of compiler option just sets the severity level of one or more error messages. Some of these options may require additional processing to be applied.

It is possible to link a particular error message to a particular compiler option using:

#pragma TenDRA++ error string-literal as option string-literal

Note that the directive:

#pragma TenDRA++ use error string-literal

can be used to raise a given error at any point in a translation unit in a similar fashion to the #error directive. The values of any parameters for this error are unspecified.

The directives just described give the primitive operations on error messages and compiler options. Many of the remaining directives in this section are merely higher level ways of expressing these primitives.

1.3. Scoping options

A new checking scope may be started by inserting the pragma:

#pragma TenDRA begin

at the outermost level. The scope runs until the matching:

#pragma TenDRA end

directive, or to the end of the translation unit (the ISO C standard definition of a translation unit as being a source file, together with any headers or source files included using the #include preprocessing directive, less any source lines skipped by any of the conditional inclusion preprocessing directives, is used throughout this document).

Checking scopes may be nested in the obvious way.

Each new checking scope inherits its initial set of checks from the checking scope which immediately contains it (this includes the implicit main checking scope consisting of the entire source file). Any checks switched on or off within the scope apply only to that scope and any scope it contains. The set of checks applied reverts to its previous state at the end of a scope. Thus, for example:

#pragma TenDRA variable analysis on
/* Variable analysis is on here */

#pragma TenDRA begin
#pragma TenDRA variable analysis off
	/* Variable analysis is off here */
#pragma TenDRA end

/* Variable analysis is on again here */

Once a check has been set any attempt to change its status within the same scope is flagged as an error. If checks need to be switched on and off in the same source file, they must be properly scoped. The built-in compilation modes have the entire source file as their scope.

The method of applying different checking profiles to different parts of a program clearly needs to take into account those properties of C which can circumvent such scoping. Consider for example:

#pragma TenDRA begin
#pragma TenDRA unknown escape allow
#define STRING "hello\!"
#pragma TenDRA end

char * f () {
	return ( STRING ) ;
}

The macro STRING is defined in an area where unknown escape sequences, such as \!, are allowed, but it is expanded in an area where they are not allowed (this is the default setting). The conventional approach to macro expansion would lead to the unknown escape sequence being flagged as an error, even though the user probably intended to avoid this. The checker therefore expands all macros using the checking profile in which they were defined, rather than the current checking scope.

The directives describing the user's desired checking profile could be included directly in the program itself, ideally in some configuration file which is #include'd in all source files. It is however perhaps more appropriate to store the directives as a startup file, file say, which is passed to the checker using the -ffilecommand line option. It should be noted that user-defined compilation modes are defined on top of a built-in mode base (normally Xc, the default mode). It is therefore important to scope the new checking profile as described above.

Names may be associated with checking scopes by using an alternative form of the begin directive:

#pragma TenDRA begin name environment identifier

where identifier is any valid C identifier. Thereafter a statement of the form:

#pragma TenDRA use environment identifier

changes the current checking environment to the environment associated with identifier.

Sometimes it may be desirable to use different checking profiles for different parts of a translation unit, e.g. applying less strict checks to any system headers which may be included. The checker can be configured to apply a named checking scope, env_name, to any files included from a directory which has been named dir_name, using:

#pragma TenDRA directory dir_name use environment env_name

The directory name must be passed to the checker using the -N dir_name : dir -I dir command line option. This is equivalent to the usual -Idir option for specifying include paths, except that it also attaches the name dir_name to the directory.

Most compiler options are scoped. A checking scope may be defined by enclosing a list of declarations within:

#pragma TenDRA begin
....
#pragma TenDRA end

If the final end directive is omitted then the scope ends at the end of the translation unit. Checking scopes may be nested in the obvious way. A checking scope inherits its initial set of checks from its enclosing scope (this includes the implicit main checking scope consisting of the entire input file). Any checks switched on or off within a scope apply only to the remainder of that scope and any scope it contains. A particular check can only be set once in a given scope. The set of applied checks reverts to its previous state at the end of the scope.

A checking scope can be named using the directives:

#pragma TenDRA begin name environment identifier
....
#pragma TenDRA end

Checking scope names occupy a namespace distinct from any other namespace within the translation unit. A named scope defines a set of modifications to the current checking scope. These modifications may be reapplied within a different scope using:

#pragma TenDRA use environment identifier

The default behaviour is not to allow checks set in the named checking scope to be reset in the current scope. This can however be modified using:

#pragma TenDRA use environment identifier reset allow

Another use of a named checking scope is to associate a checking scope with a named include file directory. This is done using:

#pragma TenDRA directory identifier use environment identifier

where the directory name is one introduced via a -N command-line option. The effect of this directive, if a #include directive is found to resolve to a file from the given directory, is as if the file was enclosed in directives of the form:

#pragma TenDRA begin
#pragma TenDRA use environment identifier reset allow
....
#pragma TenDRA end

The checks applied to the expansion of a macro definition are those from the scope in which the macro was defined, not that in which it was expanded. The macro arguments are checked in the scope in which they are specified, that is to say, the scope in which the macro is expanded. This enables macro definitions to remain localised with respect to checking scopes.