C++ Producer Guide

The TenDRA Documentation Team

$TenDRA: book.xml 2447 2006-03-23 21:15:51Z verm $

Extensions to this document from the original TenDRA-4.1.2-doc.tar.gz source distribution are covered by the BSDL, while all prior modifications remain under the Crown Copyright.

Berkeley Systems Design License

Redistribution and use in source (SGML DocBook) and 'compiled' forms (SGML, HTML, PDF, PostScript, RTF and so forth) with or without modification, are permitted provided that the following conditions are met:

  1. Redistributions of source code (SGML DocBook) must retain the above copyright notice, this list of conditions and the following disclaimer as the first lines of this file unmodified.

  2. Redistributions in compiled form (transformed to other DTDs, converted to PDF, PostScript, RTF and other formats) must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

Important

THIS DOCUMENTATION IS PROVIDED BY THE TENDRA DOCUMENTATION TEAM "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE TENDRA DOCUMENTATION TEAM BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Crown Copyright (c) 1997, 1998

This TenDRA(r) Computer Program is subject to Copyright owned by the United Kingdom Secretary of State for Defence acting through the Defence Evaluation and Research Agency (DERA). It is made available to Recipients with a royalty-free licence for its use, reproduction, transfer to other parties and amendment for any purpose not excluding product development provided that any such use et cetera shall be deemed to be acceptance of the following conditions:

  1. Its Recipients shall ensure that this Notice is reproduced upon any copies or amended versions of it;

  2. Any amended version of it shall be clearly marked to show both the nature of and the organisation responsible for the relevant amendment or amendments;

  3. Its onward transfer from a recipient to another party shall be deemed to be that party's acceptance of these conditions;

  4. DERA gives no warranty or assurance as to its quality or suitability for any purpose and DERA accepts no liability whatsoever in relation to any use to which it may be put.

This document was generated on 2006-09-07 16:04:45

Abstract

Please email us at if you see any errors


Table of Contents

Introduction
Bibliography
1. Interface Descriptions
1.1. Invocation
1.1.1. Compilation scheme
1.1.2. Producer options
1.2. Configuration
1.2.1. Portability tables
1.2.2. Low level configuration
1.2.3. Checking scopes
1.2.4. Implementation limits
1.2.5. Lexical analysis
1.2.6. Keywords
1.2.7. Comments
1.2.8. Identifier names
1.2.9. Predefined identifiers
1.2.10. Integer literals
1.2.11. Character literals and built-in types
1.2.12. String literals
1.2.13. Escape sequences
1.2.14. Preprocessing directives
1.2.15. Target dependent conditional inclusion
1.2.16. File inclusion directives
1.2.17. Macro definitions
1.2.18. Empty source files
1.2.19. The std namespace
1.2.20. Object linkage
1.2.21. Static identifiers
1.2.22. Empty declarations
1.2.23. Mixed declarations and statements
1.2.24. Implicit int
1.2.25. Extended integral types
1.2.26. Hexadecimal floating constants
1.2.27. Bitfield types
1.2.28. Elaborated type specifiers
1.2.29. Flexible array members
1.2.30. Implicit function declarations
1.2.31. Weak function prototypes
1.2.32. printf and scanf argument checking
1.2.33. Type declarations
1.2.34. Type qualifiers
1.2.35. Type compatibility
1.2.36. Incomplete types
1.2.37. Type conversions
1.2.38. Cast expressions
1.2.39. Ellipsis functions
1.2.40. Overloaded functions
1.2.41. Expressions
1.2.42. Initialiser expressions
1.2.43. Lvalue expressions
1.2.44. Discarded expressions
1.2.45. Conditional and iteration statements
1.2.46. Switch statements
1.2.47. For statements
1.2.48. Return statements
1.2.49. Unreached code analysis
1.2.50. Variable flow analysis
1.2.51. Variable hiding
1.2.52. Exception analysis
1.2.53. Template compilation
1.2.54. Other checks
1.3. Token syntax
1.3.1. Token specifications
1.3.2. Token arguments
1.3.3. Defining tokens
1.4. Symbol table dump
1.4.1. Lexical elements
1.4.2. Overall syntax
1.4.3. File locations
1.4.4. Identifiers
1.4.5. Types
1.4.6. Sorts
1.4.7. Token applications
1.4.8. Errors
1.4.9. File inclusions
1.4.10. String literals
1.5. Intermodule analysis
1.6. Implementation Details
1.6.1. Arithmetic types
1.6.2. Integer literal types
1.6.3. Bitfield types
1.6.4. Generic pointers
1.6.5. Undefined conversions
1.6.6. Integer division
1.6.7. Calling conventions
1.6.8. Pointers to data members
1.6.9. Pointers to function members
1.6.10. Class layout
1.6.11. Derived class layout
1.6.12. Constructors and destructors
1.6.13. Virtual function tables
1.6.14. Run-time type information
1.6.15. Dynamic initialisation
1.6.16. Exception handling
1.6.17. Mangled identifier names
1.7. Standard library
1.7.1. Common porting problems
1.7.2. Porting libio
2. Program Overview
2.1. Source Code Organisation / Style Guide
2.1.1. API usage and target dependencies
2.1.2. Source code modules
2.2. Type System
2.2.1. Primitive types
2.2.2. CV_SPEC
2.2.3. BUILTIN_TYPE
2.2.4. BASE_TYPE
2.2.5. INT_TYPE
2.2.6. FLOAT_TYPE
2.2.7. CLASS_INFO
2.2.8. CLASS_USAGE
2.2.9. CLASS_TYPE
2.2.10. GRAPH
2.2.11. VIRTUAL
2.2.12. ENUM_TYPE
2.2.13. TYPE
2.2.14. DECL_SPEC
2.2.15. HASHID
2.2.16. QUALIFIER
2.2.17. IDENTIFIER
2.2.18. MEMBER
2.2.19. NAMESPACE
2.2.20. NAT
2.2.21. FLOAT
2.2.22. STRING
2.2.23. NTEST
2.2.24. RMODE
2.2.25. EXP
2.2.26. OFFSET
2.2.27. TOKEN
2.2.28. INSTANCE
2.2.29. ERROR
2.2.30. VARIABLE
2.2.31. LOCATION
2.2.32. POSITION
2.2.33. BITSTREAM
2.2.34. BUFFER
2.2.35. OPTIONS
2.2.36. PPTOKEN
2.3. Error catalogue
2.4. Parsing C++
2.5. TDF generation
3. Manual Pages
1. #pragma directive syntax
2. Symbol table dump syntax
3. Error catalogue syntax
4. Revision History

List of Figures

1.1. Compile Scheme
1.2. Class Layout
1.3. Class Layout
1.4. Single Inheritance - Class A
1.5. Single Inheritance - Class B
1.6. Single Inheritance - Class C
1.7. Multiple Inheritance - Class D
1.8. Acyclic Graph
1.9. Single Inheritance - Class A
1.10. Virtual B
1.11. Virtual C
1.12. Virtual D
1.13. Run-time Type Information Structures
1.14. Exception Handling

List of Tables

1.1. Implementation Limits
1.2. Identifier Keys
1.3. Symbol Table Dump
1.4. Subtypes
1.5. Variety Tokens
1.6. Floating Variety Tokens
1.7. Signed Bit Pattern
1.8. External Tokens
1.9. Integer Literal Types
1.10. Information Structure Tag Numbers
1.11. Information Structure Base Types
1.12. Base Class Access Encoding
1.13. Type Encodiing
1.14. Overloaded Operator Functions
2.1. C++ Producer Modules

Introduction

This document is designed as a technical overview of the TenDRA C++ to TDF/ANDF producer. It is divided into two broad areas; descriptions of the public interfaces of the producer, and an overview of the producer source code.

Whereas the interface description contains most of the information which would be required in a users' guide, it is not necessarily in a readily digestible form. The C++ producer is designed to complement the existing TenDRA C to TDF producer; although they are completely distinct programs, the same design philosophy underlies both and they share a number of common interfaces. There are no radical differences between the two producers, besides the fact that the C++ producer covers a vastly larger and more complex language. This means that much of the C Checker Reference Manual existing documentation on the C producer can be taken as also applying to the C++ producer. This document tries to make clear where the C++ producer extends the C producer's interfaces, and those portions of these interfaces which are not directly applicable to C++.

A familiarity with both C++ and TDF is assumed. The version of C++ implemented is that given by the [bib-draft-pro-std] . All references to "ISO C++" within the document should strictly be qualified using the word "draft", but for convenience this has been left implicit. The C++ producer has a number of switches which allow it to be configured for older dialects of C++. In particular, the version of C++ described in the ARM ([bib-anno-cpp-ref-man] ) is fully supported.

The TDF Specification may be consulted for a description of the compiler intermediate language used. The paper TDF Portability Guide TDF and Portability provides a useful (if slightly old) introduction to some of the ideas relating to static program analysis and interface checking which underlie the whole TenDRA compilation system.

Bibliography

Tdfc: The C to TDF Producer Issue 1.0 (DRA/CIS3/OSSG/TR/95/102/1.0).

[bib-anno-cpp-ref-man] Margaret Ellis and Bjarne Stroustrup. The Annotated C++ Reference Manual. Copyright © 1990. Addison-Wesley. 0-201-51459-1.

[bib-ccs] C Coding Standards, DRA/CIS(SE2)/WI/94/57/2.0 (OSSG internal document).

Chapter 1. Interface Descriptions

Table of Contents

1.1. Invocation
1.1.1. Compilation scheme
1.1.2. Producer options
1.2. Configuration
1.2.1. Portability tables
1.2.2. Low level configuration
1.2.3. Checking scopes
1.2.4. Implementation limits
1.2.5. Lexical analysis
1.2.6. Keywords
1.2.7. Comments
1.2.8. Identifier names
1.2.9. Predefined identifiers
1.2.10. Integer literals
1.2.11. Character literals and built-in types
1.2.12. String literals
1.2.13. Escape sequences
1.2.14. Preprocessing directives
1.2.15. Target dependent conditional inclusion
1.2.16. File inclusion directives
1.2.17. Macro definitions
1.2.18. Empty source files
1.2.19. The std namespace
1.2.20. Object linkage
1.2.21. Static identifiers
1.2.22. Empty declarations
1.2.23. Mixed declarations and statements
1.2.24. Implicit int
1.2.25. Extended integral types
1.2.26. Hexadecimal floating constants
1.2.27. Bitfield types
1.2.28. Elaborated type specifiers
1.2.29. Flexible array members
1.2.30. Implicit function declarations
1.2.31. Weak function prototypes
1.2.32. printf and scanf argument checking
1.2.33. Type declarations
1.2.34. Type qualifiers
1.2.35. Type compatibility
1.2.36. Incomplete types
1.2.37. Type conversions
1.2.38. Cast expressions
1.2.39. Ellipsis functions
1.2.40. Overloaded functions
1.2.41. Expressions
1.2.42. Initialiser expressions
1.2.43. Lvalue expressions
1.2.44. Discarded expressions
1.2.45. Conditional and iteration statements
1.2.46. Switch statements
1.2.47. For statements
1.2.48. Return statements
1.2.49. Unreached code analysis
1.2.50. Variable flow analysis
1.2.51. Variable hiding
1.2.52. Exception analysis
1.2.53. Template compilation
1.2.54. Other checks
1.3. Token syntax
1.3.1. Token specifications
1.3.2. Token arguments
1.3.3. Defining tokens
1.4. Symbol table dump
1.4.1. Lexical elements
1.4.2. Overall syntax
1.4.3. File locations
1.4.4. Identifiers
1.4.5. Types
1.4.6. Sorts
1.4.7. Token applications
1.4.8. Errors
1.4.9. File inclusions
1.4.10. String literals
1.5. Intermodule analysis
1.6. Implementation Details
1.6.1. Arithmetic types
1.6.2. Integer literal types
1.6.3. Bitfield types
1.6.4. Generic pointers
1.6.5. Undefined conversions
1.6.6. Integer division
1.6.7. Calling conventions
1.6.8. Pointers to data members
1.6.9. Pointers to function members
1.6.10. Class layout
1.6.11. Derived class layout
1.6.12. Constructors and destructors
1.6.13. Virtual function tables
1.6.14. Run-time type information
1.6.15. Dynamic initialisation
1.6.16. Exception handling
1.6.17. Mangled identifier names
1.7. Standard library
1.7.1. Common porting problems
1.7.2. Porting libio

The most important public interfaces of the C++ producer are the ISO C++ standard and the TDF 4.0 specification; however there are other interfaces, mostly common to both the C and C++ producers, which are described in this section.

An important design criterion of the C++ producer was that it should be strictly ISO conformant by default, but have a method whereby dialect features and extra static program analysis can be enabled. This compiler configuration is controlled by the #pragma TenDRA directives described in the first section.

The requirement that the C and C++ producers should be able to translate portable C or C++ programs into target independent TDF requires a mechanism whereby the target dependent implementations of APIs can be represented. This mechanism, the #pragma token syntax, is described in the following section. Note that at present this mechanism only contains support for C APIs; it is considered that the C++ language itself contains sufficient interface mechanisms for C++ APIs to be described.

The C and C++ producers provide two mechanisms whereby type and declaration information derived from a translation unit can be stored to a file for post-processing by other tools. The first is the symbol table dump, which is a public interface designed for use by third party tools. The second is the C++ spec file, which is designed for ease of reading and writing by the producers themselves, and is used for intermodule analysis.

The mapping from C++ to TDF implemented by the C++ producer is largely straightforward. There are however target dependencies arising within the language itself which require special handling. These are represented by certain standard tokens which the producer requires to be defined on the target machine. These tokens are also used to describe the interface between the producer and the run-time system. Note that the C++ producer is primarily concerned with the C++ language, not with the standard C++ library. An example implementation of those library components which are required as an integral part of the language (memory allocation, exception handling, run-time type information etc.) is provided. Otherwise, libraries should be obtained from third parties. A number of hints on integrating such libraries with the C++ producer are given.

1.1. Invocation

In this section it is described how the C++ to TDF producer, tcpplus, fits into the overall compilation scheme controlled by the TenDRA compiler front-end, tcc, or the TenDRA checker front-end, tchk. While it is possible to use tcpplus as a stand-alone program, it is recommended that it should be invoked via tcc or tchk. The tcc users' guide should be consulted for more details.

tcc and tchk require the -Yc++ command-line option in order to enable their C++ capabilities. Files with a .C suffix are recognised as C++ source files and passed to tcpplus for processing (see Section 1.1.1, “Compilation scheme” ). It is possible to change the suffix used for C++ source files; for example -sC:cc causes .cc files to be recognised as C++ source files. An interesting variation is -sC:c which causes C source files to be processed by the C++ producer. Similarly .I files are recognised as preprocessed C++ source files and .K files are recognised as C++ spec files.

Most of the command-line option handling for tcpplus is done by tcc and tchk, however it is possible to pass the option opt directly to tcpplus using the option -Wx,opt to tcc or tchk. Similarly -Wg,opt and -WS,opt can be used to pass options to the C++ preprocessor and the C++ spec linker (both of which are actually tcpplus invoked with different options) respectively.

1.1.1. Compilation scheme

The overall compilation scheme controlled by tcc, as it relates to the C++ producer, can be represented as follows:

Figure 1.1. Compile Scheme

Compile Scheme

Each C++ source file, a.C say, is processed using tcpplus to give an output TDF capsule, a.j, which is passed to the installer phase of tcc. The capsule is linked with any target dependent token definition libraries, translated to assembler and assembled to give a binary object file, a.o. The various object files comprising the program are then linked with the system libraries to give a final executable, a.out.

In addition to this main compilation scheme, tcpplus can additionally be made to output a C++ spec file for each C++ source file, a.K say. These C++ spec files can be linked, using tcpplus in its spec linker mode, to give an additional TDF capsule, x.j say, and a combined C++ spec file, x.K. The main purpose of this C++ spec linking is to perform intermodule checks on the program, however in the course of this checking exported templates which are defined in one module and used in another are instantiated. This extra code is output to x.j, which is then installed and linked in the normal way.

Note that intermodule checks, and hence intermodule template instantiations, are only performed if the -im option is passed to tcc.

The TenDRA checker, tchk, is similar to tcc except that it disables TDF output and has intermodule analysis enabled by default.

1.1.2. Producer options

Please see the tcpplus manual page for a complete list of command-line options.

1.2. Configuration

This section 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.

Certain very basic configuration information is specified using a portability table, however 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 startup file in order to create a user-defined compilation profile. 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 as an annex.

1.2.1. Portability tables

Certain very basic configuration information is read from a file called a portability table, which may be specified to the producer using a -n option. This information includes the minimum sizes of the basic integral types, the sign of plain char, and whether signed types can be assumed to be symmetric (for example, [-127,127]) or maximum (for example, [-128,127]).

The default portability table values, which are built into the producer, can be expressed in the form:

char_bits           8
short_bits          16
int_bits            16
long_bits           32
signed_range            symmetric
char_type           either
ptr_int             none
ptr_fn              no
non_prototype_checks        yes
multibyte           1

This illustrates the syntax for the portability table; note that all ten entries are required, even though the last four are ignored.

1.2.2. Low level configuration

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 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.2.3. Checking scopes

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.

1.2.4. Implementation limits

This table gives the default implementation limits imposed by the C++ producer for the various implementation quantities listed in Annex B of the ISO C++ standard, together with the minimum limits allowed in ISO C and C++. A default limit of none means that the quantity is limited only by the size of the host machine (either ULONG_MAX or until it runs out of memory). A limit of target means that while no limits is imposed by the C++ front-end, particular target machines may impose such limits.

Table 1.1. Implementation Limits

Quantity identifier Min C limit Min C++ limit Default limit
statement_depth 15 256 none
hash_if_depth 8 256 none
declarator_max 12 256 none
paren_depth 32 256 none
name_limit 31 1024 none
extern_name_limit 6 1024 target
external_ids 511 65536 target
block_ids 127 1024 none
macro_ids 1024 65536 none
func_pars 31 256 none
func_args 31 256 none
macro_pars 31 256 none
macro_args 31 256 none
line_length 509 65536 none
string_length 509 65536 none
sizeof_object 32767 262144 target
include_depth 8 256 256
switch_cases 257 16384 none
data_members 127 16384 none
enum_consts 127 4096 none
nested_class 15 256 none
atexit_funcs 32 32 target
base_classes N/A 16384 none
direct_bases N/A 1024 none
class_members N/A 4096 none
virtual_funcs N/A 16384 none
virtual_bases N/A 1024 none
static_members N/A 1024 none
friends N/A 4096 none
access_declarations N/A 4096 none
ctor_initializers N/A 6144 none
scope_qualifiers N/A 256 none
external_specs N/A 1024 none
template_pars N/A 1024 none
instance_depth N/A 17 17
exception_handlers N/A 256 none
exception_specs N/A 256 none

It is possible to impose lower limits on most of the quantities listed above by means of the directive:

#pragma TenDRA++ option value string-literal integer-literal

where string-literal gives one of the quantity identifiers listed above and integer-literal gives the limit to be imposed. An error is reported if the quantity exceeds this limit (note however that checks have not yet been implemented for all of the quantities listed). Note that the name_limit and include_depth implementation limits can be set using dedicated directives.

The maximum number of errors allowed before the producer bails out can be set using the directive:

#pragma TenDRA++ set error limit integer-literal

The default value is 32.

1.2.5. Lexical analysis

During lexical analysis, a source file which is not empty should end in a newline character. It is possible to relax this constraint using the directive:

#pragma TenDRA no nline after file end allow

1.2.6. Keywords

In several places in this section it is described how to introduce keywords for TenDRA language extensions. By default, no such extra keywords are defined. There are also low-level directives for defining and undefining keywords. The directive:

#pragma TenDRA++ keyword identifier for keyword identifier

can be used to introduce a keyword (the first identifier) standing for the standard C++ keyword given by the second identifier. The directive:

#pragma TenDRA++ keyword identifier for operator operator

can similarly be used to introduce a keyword giving an alternative representation for the given operator or punctuator, as, for example, in:

#pragma TenDRA++ keyword and for operator &&

Finally the directive:

#pragma TenDRA++ undef keyword identifier

can be used to undefine a keyword.

1.2.7. Comments

C-style comments do not nest. The directive:

#pragma TenDRA nested comment analysis on

enables a check for the characters /* within C-style comments.

Whether // comments are recognised is controlled by the pragma:

#pragma TenDRA line comment allow
They are enabled for C++ code and in the C99 environment.

1.2.8. Identifier names

During lexical analysis, each character in the source file has an associated look-up value which is used to determine whether the character can be used in an identifier name, is a white space character etc. These values are stored in a simple look-up table. It is possible to set the look-up value using:

#pragma TenDRA++ character character-literal as character-literal allow

which sets the look-up for the first character to be the default look-up for the second character. The form:

#pragma TenDRA++ character character-literal disallow

sets the look-up of the character to be that of an invalid character. The forms:

#pragma TenDRA++ character string-literal as character-literal allow
#pragma TenDRA++ character string-literal disallow

can be used to modify the look-up values for the set of characters given by the string literal. For example:

#pragma TenDRA character '$' as 'a' allow
#pragma TenDRA character '\r' as ' ' allow

allows $ to be used in identifier names (like a) and carriage return to be a white space character. The former is a common dialect feature and can also be controlled by the directive:

#pragma TenDRA dollar as ident allow

The maximum number of characters allowed in an identifier name can be set using the directives:

#pragma TenDRA set name limit integer-literal
#pragma TenDRA++ set name limit integer-literal warning

This length is given by the name_limit implementation quantity mentioned above. Identifiers which exceed this length raise an error or a warning, but are not truncated.

1.2.9. Predefined identifiers

The C99 standard introduces the concept of predefined identifiers, the only one of which currently is __func__ . It behaves as if each function started with the declaration

static const char __func__[] = "func-name";

where func-name is the name of the current function. The identifier __func__ can be enabled with the directive:

#pragma TenDRA identifier __func__ allow

The identifier __func__ can also be used in C++ code with one limitation: within constructors, destructors, conversion functions and overloaded operators __func__ is an empty string.

1.2.10. Integer literals

The rules for finding the type of an integer literal can be described using directives of the form:

#pragma TenDRA integer literal literal-spec

where:

literal-spec :
  literal-base literal-suffixopt literal-type-list

literal-base :
  octal
  decimal
  hexadecimal

literal-suffix :
  unsigned
  long
  unsigned long
  long long
  unsigned long long

literal-type-list :
  * literal-type-spec
  integer-literal literal-type-spec | literal-type-list
  ? literal-type-spec | literal-type-list

literal-type-spec :
  : type-id
  * allowopt : identifier
  * * allowopt :

Each directive gives a literal base and suffix, describing the form of an integer literal, and a list of possible types for literals of this form. This list gives a mapping from the value of the literal to the type to be used to represent the literal. There are three cases for the literal type; it may be a given integral type, it may be calculated using a given literal type token, or it may cause an error to be raised. There are also three cases for describing a literal range; it may be given by values less than or equal to a given integer literal, it may be given by values which are guaranteed to fit into a given integral type, or it may be match any value. For example:

#pragma token PROC ( VARIETY c ) VARIETY l_i # ~lit_int
#pragma TenDRA integer literal decimal 32767 : int | ** : l_i

describes how to find the type of a decimal literal with no suffix. Values less that or equal to 32767 have type int; larger values have target dependent type calculated using the token ~lit_int. Introducing a warning into the directive will cause a warning to be printed if the token is used to calculate the value.

Note that this scheme extends that implemented by the C producer, because of the need for more accurate information in the C++ producer. For example, the specification above does not fully express the ISO rule that the type of a decimal integer is the first of the types int, long and unsigned long which it fits into (it only expresses the first step). However with the C++ extensions it is possible to write:

#pragma token PROC ( VARIETY c ) VARIETY l_i # ~lit_int
#pragma TenDRA integer literal decimal ? : int | ? : long |\
? : unsigned long | ** : l_i

1.2.11. Character literals and built-in types

By default, a simple character literal has type int in C and type char in C++. The type of such literals can be controlled using the directive:

#pragma TenDRA++ set character literal : type-id

The type of a wide character literal is given by the implementation defined type wchar_t. By default, the definition of this type is taken from the target machine's <stddef.h> C header (note that in ISO C++, wchar_t is actually a keyword, but its underlying representation must be the same as in C). This definition can be overridden in the producer by means of the directive:

#pragma TenDRA set wchar_t : type-id

for an integral type type-id. Similarly, the definitions of the other implementation dependent integral types which arise naturally within the language - the type of the difference of two pointers, ptrdiff_t, and the type of the sizeof operator, size_t - given in the <stddef.h> header can be overridden using the directives:

#pragma TenDRA set ptrdiff_t : type-id
#pragma TenDRA set size_t : type-id

These directives are useful when targeting a specific machine on which the definitions of these types are known; while they may not affect the code generated they can cut down on spurious conversion warnings. Note that although these types are built into the producer they are not visible to the user unless an appropriate header is included (with the exception of the keyword wchar_t in ISO C++), however the directives:

#pragma TenDRA++ type identifier for type-name

can be used to make these types visible. They are equivalent to a typedef declaration of identifier as the given built-in type, ptrdiff_t, size_t or wchar_t.

Whether plain char is signed or unsigned is implementation dependent. By default the implementation is determined by the definition of the ~char token, however this can be overridden in the producer either by means of the portability table or by the directive:

#pragma TenDRA character character-sign

where character-sign can be signed, unsigned or either (the default). Again this directive is useful primarily when targeting a specific machine on which the signedness of char is known.

1.2.12. String literals

By default, character string literals have type char [n] in C and older dialects of C++, but type const char [n] in ISO C++. Similarly wide string literals have type wchar_t [n] or const wchar_t [n]. Whether string literals are const or not can be controlled using the two directives:

#pragma TenDRA++ set string literal : const
#pragma TenDRA++ set string literal : no const

In the case where literals are const, the array-to-pointer conversion is allowed to cast away the const to allow for a degree of backwards compatibility. The status of this deprecated conversion can be controlled using the directive:

#pragma TenDRA writeable string literal allow

(yes, I know that that should be writable). Note that this directive has a slightly different meaning in the C producer.

Adjacent string literals tokens of similar types (either both character string literals or both wide string literals) are concatenated at an early stage in parser, however it is unspecified what happens if a character string literal token is adjacent to a wide string literal token. By default this gives an error, but the directive:

#pragma TenDRA unify incompatible string literal allow

can be used to enable the strings to be concatenated to give a wide string literal.

If a ' or " character does not have a matching closing quote on the same line then it is undefined whether an implementation should report an unterminated string or treat the quote as a single unknown character. By default, the C++ producer treats this as an unterminated string, but this behaviour can be controlled using the directive:

#pragma TenDRA unmatched quote allow

1.2.13. Escape sequences

By default, if the character following the \ in an escape sequence is not one of those listed in the ISO C or C++ standards then an error is given. This behaviour, which is left unspecified by the standards, can be controlled by the directive:

#pragma TenDRA unknown escape allow

The result is that the \ in unknown escape sequences is ignored, so that \z is interpreted as z, for example. Individual escape sequences can be enabled or disabled using the directives:

#pragma TenDRA++ escape character-literal as character-literal allow
#pragma TenDRA++ escape character-literal disallow

so that, for example:

#pragma TenDRA++ escape 'e' as '\033' allow
#pragma TenDRA++ escape 'a' disallow

sets \e to be the ASCII escape character and disables the alert character \a.

By default, if the value of a character, given for example by a \x escape sequence, does not fit into its type then an error is given. This implementation dependent behaviour can however be controlled by the directive:

#pragma TenDRA character escape overflow allow

the value being converted to its type in the normal way.

1.2.14. Preprocessing directives

Non-standard preprocessing directives can be controlled using the directives:

#pragma TenDRA directive ppdir allow
#pragma TenDRA directive ppdir (ignore) allow

where ppdir can be assert, file, ident, import (C++ only), include_next (C++ only), unassert, warning (C++ only) or weak. The second form causes the directive to be processed but ignored (note that there is no (ignore) disallow form). The treatment of other unknown preprocessing directives can be controlled using:

#pragma TenDRA unknown directive allow

Cases where the token following the # in a preprocessing directive is not an identifier can be controlled using:

#pragma TenDRA no directive/nline after ident allow

When permitted, unknown preprocessing directives are ignored.

By default, unknown #pragma directives are ignored without comment, however this behaviour can be modified using the directive:

#pragma TenDRA unknown pragma allow

Note that any unknown #pragma TenDRA directives always give an error.

Older preprocessors allowed text after #else and #endif directives. The following directive can be used to enable such behaviour:

#pragma TenDRA text after directive allow

Such text after a directive is ignored.

Some older preprocessors have problems with white space in preprocessing directives - whether at the start of the line, before the initial #, or between the # and the directive identifier. Such white space can be detected using the directives:

#pragma TenDRA indented # directive allow
#pragma TenDRA indented directive after # allow

respectively.

1.2.15. Target dependent conditional inclusion

One of the effects of trying to compile code in a target independent manner is that it is not always possible to completely evaluate the condition in a #if directive. Thus the conditional inclusion needs to be preserved until the installer phase. This can only be done if the target dependent #if is more structured than is normally required for preprocessing directives. There are two cases; in the first, where the #if appears in a statement, it is treated as if it were a if statement with braces including its branches; that is:

#if cond
  true_statements
#else
  false_statements
#endif

maps to:

if ( cond ) {
  true_statements
} else {
  false_statements
}

In the second case, where the #if appears in a list of declarations, normally gives an error. The can however be overridden by the directive:

#pragma TenDRA++ conditional declaration allow

which causes both branches of the #if to be analysed.

1.2.16. File inclusion directives

There is a maximum depth of nested #include directives allowed by the C++ producer. This depth is given by the include_depth implementation quantity mentioned above. Its value is fairly small in order to detect recursive inclusions. The maximum depth can be set using:

#pragma TenDRA includes depth integer-literal

A further check, for full pathnames in #include directives (which may not be portable), can be enabled using the directive:

#pragma TenDRA++ complete file includes allow

1.2.17. Macro definitions

By default, multiple consistent definitions of a macro are allowed. This behaviour can be controlled using the directive:

#pragma TenDRA extra macro definition allow

The ISO C/C++ rules for determining whether two macro definitions are consistent are fairly restrictive. A more relaxed rule allowing for consistent renaming of macro parameters can be enabled using:

#pragma TenDRA weak macro equality allow

In the definition of macros with parameters, a # in the replacement list must be followed by a parameter name, indicating the stringising operation. This behaviour can be controlled by the directive:

#pragma TenDRA no ident after # allow

which allows a # which is not followed by a parameter name to be treated as a normal preprocessing token.

In a list of macro arguments, the effect of a sequence of preprocessing tokens which otherwise resembles a preprocessing directive is undefined. The C++ producer treats such directives as normal sequences of preprocessing tokens, but can be made to report such behaviour using:

#pragma TenDRA directive as macro argument allow

Empty macro arguments invoke undefined behaviour according to the C90 and C++ standard. The C99 standard allows them however. They can be allowed with the directive:

#pragma TenDRA empty macro argument allow

The C99 standard requires white space between an object macro and the replacement list in a macro definition (eg. the macro definition #define A{} is invalid). The directive

#pragma TenDRA no space after macro allow
can be used to specify if an error should be reported.

Another C99 feature are macros with a variable number of arguments. If the last parameter of a function-like macro is ..., it matches all remaining arguments (but there has to be at least one). The identifier __VA_ARGS__ can be used to refer to these arguments in the replacement list. For example:

#define debug(lvl, ...) do { \
        if (lvl >= debuglvl) \
                fprintf(stderr, __VA_ARGS__); \
} while (0)

Variadic macros are enabled with:

#pragma TenDRA variable argument macro allow

1.2.18. Empty source files

ISO C requires that a translation unit should contain at least one declaration. C++ and older dialects of C allow translation units which contain no declarations. This behaviour can be controlled using the directive:

#pragma TenDRA no external declaration allow

1.2.19. The std namespace

Several classes declared in the std namespace arise naturally as part of the C++ language specification. These are as follows:

std::type_info      // type of typeid construct
std::bad_cast       // thrown by dynamic_cast construct
std::bad_typeid     // thrown by typeid construct
std::bad_alloc      // thrown by new construct
std::bad_exception  // used in exception specifications

The definitions of these classes are found, when needed, by looking up the appropriate class name in the std namespace. Depending on the context, an error may be reported if the class is not found. It is possible to modify the namespace which is searched for these classes using the directive:

#pragma TenDRA++ set std namespace : scope-name

where scope-name can be an identifier giving a namespace name or ::, indicating the global namespace.

1.2.20. Object linkage

If an object is declared with both external and internal linkage in the same translation unit then, by default, an error is given. This behaviour can be changed using the directive:

#pragma TenDRA incompatible linkage allow

When incompatible linkages are allowed, whether the resultant identifier has external or internal linkage can be set using one of the directives:

#pragma TenDRA linkage resolution : off
#pragma TenDRA linkage resolution : (external) on
#pragma TenDRA linkage resolution : (internal) on

It is possible to declare objects with external linkage in a block. C leaves it undefined whether declarations of the same object in different blocks, such as:

void f ()
{
  extern int a ;
  ....
}

void g ()
{
  extern double a ;
  ....
}

are checked for compatibility. However in C++ the one definition rule implies that such declarations are indeed checked for compatibility. The status of this check can be set using the directive:

#pragma TenDRA unify external linkage on

Note that it is not possible in ISO C or C++ to declare objects or functions with internal linkage in a block. While static object definitions in a block have a specific meaning, there is no real reason why static functions should not be declared in a block. This behaviour can be enabled using the directive:

#pragma TenDRA block function static allow

Inline functions have external linkage by default in ISO C++, but internal linkage in older dialects. The default linkage can be set using the directive:

#pragma TenDRA++ inline linkage linkage-spec

where linkage-spec can be external or internal. Similarly const objects have internal linkage by default in C++, but external linkage in C. The default linkage can be set using the directive:

#pragma TenDRA++ const linkage linkage-spec

Older dialects of C treated all identifiers with external linkage as if they had been declared volatile (i.e. by being conservative in optimising such values). This behaviour can be enabled using the directive:

#pragma TenDRA external volatile_t

It is possible to set the default language linkage using the directive:

#pragma TenDRA++ external linkage string-literal

This is equivalent to enclosing the rest of the current checking scope in:

extern string-literal {
  ....
}

It is unspecified what happens if such a directive is used within an explicit linkage specification and does not nest correctly. This directive is particularly useful when used in a named environment associated with an include directory. For example, it can be used to express the fact that all the objects declared in headers included from that directory have C linkage.

A change in ISO C++ relative to older dialects is that the language linkage of a function now forms part of the function type. For example:

extern "C" int f ( int ) ;
int ( *pf ) ( int ) = f ;       // error

The directive:

#pragma TenDRA++ external function linkage on

can be used to control whether function types with differing language linkages, but which are otherwise compatible, are considered compatible or not.

1.2.21. Static identifiers

By default, objects and functions with internal linkage are mapped to tags without external names in the output TDF capsule. Thus such names are not available to the installer and it needs to make up internal names to represent such objects in its output. This is not desirable in such operations as profiling, where a meaningful internal name is needed to make sense of the output. The directive:

#pragma TenDRA preserve identifier-list

can be used to preserve the names of the given list of identifiers with internal linkage. This is done using the static_name_def TDF construct. The form:

#pragma TenDRA preserve *

will preserve the names of all identifiers with internal linkage in this way.

1.2.22. Empty declarations

ISO C++ requires every declaration or member declaration to introduce one or more names into the program. The directive:

#pragma TenDRA unknown struct/union allow

can be used to relax one particular instance of this rule, by allowing anonymous class definitions (recall that anonymous unions are objects, not types, in C++ and so are not covered by this rule). The C++ grammar also allows a solitary semicolon as a declaration or member declaration; however such a declaration does not introduce a name and so contravenes the rule above. The rule can be relaxed in this case using the directive:

#pragma TenDRA extra ; allow

Note that the C++ grammar explicitly allows for an extra semicolon following an inline member function definition, but that semicolons following other function definitions are actually empty declarations of the form above. A solitary semicolon in a statement is interpreted as an empty expression statement rather than an empty declaration statement.

1.2.23. Mixed declarations and statements

C90 allowed declarations only at the beginning of a compound statement. In C++ and C99 code, declarations and statements can be mixed freely. The directive

#pragma TenDRA declaration after code allow

is used to switch reporting errors for mixed declarations and statements on or off.

1.2.24. Implicit int

The C "implicit int" rule, whereby a type of int is inferred in a list of type or declaration specifiers which does not contain a type name, has been removed in ISO C++, although it was supported in older dialects of C++. This check is controlled by the directive:

#pragma TenDRA++ implicit int type allow

Partial relaxations of this rules are allowed. The directive:

#pragma TenDRA++ implicit int type for const/volatile allow

will allow for implicit int when the list of type specifiers contains a cv-qualifier. Similarly the directive:

#pragma TenDRA implicit int type for function return allow

will allow for implicit int in the return type of a function definition (this excludes constructors, destructors and conversion functions, where special rules apply). A function definition is the only kind of declaration in ISO C where a declaration specifier is not required. Older dialects of C allowed declaration specifiers to be omitted in other cases. Support for this behaviour can be enabled using:

#pragma TenDRA implicit int type for external declaration allow

The four cases can be demonstrated in the following example:

extern a ;      // implicit int
const b = 1 ;       // implicit const int

f ()            // implicit function return
{
  return 2 ;
}

c = 3 ;         // error: not allowed in C++

1.2.25. Extended integral types

The long long integral types are not part of ISO C or C++ by default, however support for them can be enabled using the directive:

#pragma TenDRA longlong type allow

This support includes allowing long long in type specifiers and allowing LL and ll as integer literal suffixes.

There is a further directive given by the two cases:

#pragma TenDRA set longlong type : long long
#pragma TenDRA set longlong type : long

which can be used to control the implementation of the long long types. Either they can be mapped to the default representation, which is guaranteed to contain at least 64 bits, or they can be mapped to the corresponding long types.

Because these long long types are not an intrinsic part of C++ the implementation does not integrate them into the language as fully as is possible. This is to prevent the presence or otherwise of long long types affecting the semantics of code which does not use them. For example, it would be possible to extend the rules for the types of integer literals, integer promotion types and arithmetic types to say that if the given value does not fit into the standard integral types then the extended types are tried. This has not been done, although these rules could be implemented by changing the definitions of the standard tokens used to determine these types. By default, only the rules for arithmetic types involving a long long operand and for LL integer literals mention long long types.

1.2.26. Hexadecimal floating constants

The C99 standard allows floating point literals to be represented in a hexadecimal notation. The format for them is:

hex-float:
        0x hex-digitsopt . hex-digits binary-exponent floating-suffixopt
        0x hex-digits . binary-exponent floating-suffixopt
        0x hex-digits binary-exponent floating-suffixopt
binary-exponent:
        p signopt digits
        P signopt digits

Note that the exponent uses a decimal representation. The value of a hexadecimal float constant is sign * mantissa * 2exponent.

This feature is controlled with the directive:

#pragma TenDRA hexadecimal float literal allow

1.2.27. Bitfield types

The C++ rules on bitfield types differ slightly from the C rules. Firstly any integral or enumeration type is allowed in a bitfield, and secondly the bitfield width may exceed the underlying type size (the extra bits being treated as padding). These properties can be controlled using the directives:

#pragma TenDRA extra bitfield int type allow
#pragma TenDRA bitfield overflow allow

respectively.

1.2.28. Elaborated type specifiers

In elaborated type specifiers, the class key (class, struct, union or enum) should agree with any previous declaration of the type (except that class and struct are interchangeable). This requirement can be relaxed using the directive:

#pragma TenDRA ignore struct/union/enum tag on

In ISO C and C++ it is not possible to give a forward declaration of an enumeration type. This constraint can be relaxed using the directive:

#pragma TenDRA forward enum declaration allow

Until the end of its definition, an enumeration type is treated as an incomplete type (as with class types). In enumeration definitions, and a couple of other contexts where comma-separated lists are required, the directive:

#pragma TenDRA extra , allow

can be used to allow a trailing comma at the end of the list.

The directive:

#pragma TenDRA complete struct/union analysis on

can be used to enable a check that every class or union has been completed within each translation unit in which it is declared.

1.2.29. Flexible array members

The C99 standard introduces flexible array members. This means that the last member of a (otherwise not empty) struct is allowed to be an array without an actual array size.

struct s {
        int i;
        char fam[];
};

Additional memory following the i member (eg. if a larger chunk of memory has been allocated with malloc) can be legally accessed through the fam member.

This feature is controlled with this directive:

#pragma TenDRA flexible array member on

Note: Flexible array members have not really been tested yet with C++ code.

1.2.30. Implicit function declarations

C, but not C++, allows calls to undeclared functions, the function being declared implicitly. It is possible to enable support for implicit function declarations using the directive:

#pragma TenDRA implicit function declaration on

Such implicitly declared functions have C linkage and type int ( ... ).

1.2.31. Weak function prototypes

The C producer supports a concept, weak prototypes, whereby type checking can be applied to the arguments of a non-prototype function. This checking can be enabled using the directive:

#pragma TenDRA weak prototype analysis on

The concept of weak prototypes is not applicable to C++, where all functions are prototyped. The C++ producer does allow the syntax for explicit weak prototype declarations, but treats them as if they were normal prototypes. These declarations are denoted by means of a keyword, WEAK say, introduced by the directive:

#pragma TenDRA keyword identifier for weak

preceding the ( of the function declarator. The directives:

#pragma TenDRA prototype allow
#pragma TenDRA prototype (weak) allow

which can be used in the C producer to warn of prototype or weak prototype declarations, are similarly ignored by the C++ producer.

The C producer also allows the directives:

#pragma TenDRA argument type-id as type-id
#pragma TenDRA argument type-id as ...
#pragma TenDRA extra ... allow
#pragma TenDRA incompatible promoted function argument allow

which control the compatibility of function types. These directives are ignored by the C++ producer (some of them would make sense in the context of C++ but would over-complicate function overloading).

1.2.32. printf and scanf argument checking

The C producer includes a number of checks that the arguments in a call to a function in the printf or scanf families match the given format string. The check is implemented by using the directives:

#pragma TenDRA type identifier for ... printf
#pragma TenDRA type identifier for ... scanf

to introduce a type representing a printf or scanf format string. For most purposes this type is treated as const char *, but when it appears in a function declaration it alerts the producer that any extra arguments passed to that function should match the format string passed as the corresponding argument. The TenDRA API headers conditionally declare printf, scanf and similar functions in something like the form:

#ifdef __NO_PRINTF_CHECKS
typedef const char *__printf_string ;
#else
#pragma TenDRA type __printf_string for ... printf
#endif

int printf ( __printf_string, ... ) ;
int fprintf ( FILE *, __printf_string, ... ) ;
int sprintf ( char *, __printf_string, ... ) ;

These declarations can be skipped, effectively disabling this check, by defining the __NO_PRINTF_CHECKS macro.

Warning

These printf and scanf format string checks have not yet been implemented in the C++ producer due to presence of an alternative, type checked, I/O package - namely <iostream>. The format string types are simply treated as const char *.

1.2.33. Type declarations

C does not allow multiple definitions of a typedef name, whereas C++ allows multiple consistent definitions. This behaviour can be controlled using the directive:

#pragma TenDRA extra type definition allow

1.2.34. Type qualifiers

The C99 standards allows multiple occurrences of the same type qualifiers in a declaration, as in:

const const int i;

Whether an error should be reported in such a case can be controlled by:

#pragma TenDRA extra type qualifier allow

Multiple occurrences of a type qualifier can also be caused indirectly by the usage of a typedef:

typedef const int t;
const t i;

The directive

#pragma TenDRA extra type qualifier (typedef) allow

is used to enable or disable this.

1.2.35. Type compatibility

The directive:

#pragma TenDRA incompatible type qualifier allow

allows objects to be redeclared with different cv-qualifiers (normally such redeclarations would be incompatible). The composite type is qualified using the join of the cv-qualifiers in the various redeclarations.

The directive:

#pragma TenDRA compatible type : type-id == type-id : allow

asserts that the given two types are compatible. Currently the only implemented version is char * == void * which enables char * to be used as a generic pointer as it was in older dialects of C.

1.2.36. Incomplete types

Some dialects of C allow incomplete arrays as member types. These are generally used as a place-holder at the end of a structure to allow for the allocation of an arbitrarily sized array. Support for this feature can be enabled using the directive:

#pragma TenDRA incomplete type as object type allow

1.2.37. Type conversions

There are a number of directives which allow various classes of type conversion to be checked. The directives:

#pragma TenDRA conversion analysis (int-int explicit) on
#pragma TenDRA conversion analysis (int-int implicit) on

will check for unsafe explicit or implicit conversions between arithmetic types. Similarly conversions between pointers and arithmetic types can be checked using:

#pragma TenDRA conversion analysis (int-pointer explicit) on
#pragma TenDRA conversion analysis (int-pointer implicit) on

or equivalently:

#pragma TenDRA conversion analysis (pointer-int explicit) on
#pragma TenDRA conversion analysis (pointer-int implicit) on

Conversions between pointer types can be checked using:

#pragma TenDRA conversion analysis (pointer-pointer explicit) on
#pragma TenDRA conversion analysis (pointer-pointer implicit) on

There are some further variants which can be used to enable useful sets of conversion checks. For example:

#pragma TenDRA conversion analysis (int-int) on

enables both implicit and explicit arithmetic conversion checks. The directives:

#pragma TenDRA conversion analysis (int-pointer) on
#pragma TenDRA conversion analysis (pointer-int) on
#pragma TenDRA conversion analysis (pointer-pointer) on

are equivalent to their corresponding explicit forms (because the implicit forms are illegal by default). The directive:

#pragma TenDRA conversion analysis on

is equivalent to the four directives just given. It enables checks on implicit and explicit arithmetic conversions, explicit arithmetic to pointer conversions and explicit pointer conversions.

The default settings for these checks are determined by the implicit and explicit conversions allowed in C++. Note that there are differences between the conversions allowed in C and C++. For example, an arithmetic type can be converted implicitly to an enumeration type in C, but not in C++. The directive:

#pragma TenDRA conversion analysis (int-enum implicit) on

can be used to control the status of this conversion. The level of severity for an error message arising from such a conversion is the maximum of the severity set by this directive and that set by the int-int implicit directive above.

The implicit pointer conversions described above do not include conversions to and from the generic pointer void *, which have their own controlling directives. A pointer of type void * can be converted implicitly to another pointer type in C but not in C++; this is controlled by the directive:

#pragma TenDRA++ conversion analysis (void*-pointer implicit) on

The reverse conversion, from a pointer type to void * is allowed in both C and C++, and has a controlling directive:

#pragma TenDRA++ conversion analysis (pointer-void* implicit) on

In ISO C and C++, a function pointer can only be cast to other function pointers, not to object pointers or void *. Many dialects however allow function pointers to be cast to and from other pointers. This behaviour can be controlled using the directive:

#pragma TenDRA function pointer as pointer allow

which causes function pointers to be treated in the same way as all other pointers.

The integer conversion checks described above only apply to unsafe conversions. A simple-minded check for shortening conversions is not adequate, as is shown by the following example:

char a = 1, b = 2 ;
char c = a + b ;

the sum a + b is evaluated as an int which is then shortened to a char. Any check which does not distinguish this sort of "safe" shortening conversion from unsafe shortening conversions such as:

int a = 1, b = 2 ;
char c = a + b ;

is not likely to be very useful. The producer therefore associates two types with each integral expression; the first is the normal, representation type and the second is the underlying, semantic type. Thus in the first example, the representation type of a + b is int, but semantically it is still a char. The conversion analysis is based on the semantic types.

Warning

The C producer supports a directive:

#pragma TenDRA keyword identifier for type representation

whereby a keyword can be introduced which can be used to explicitly declare a type with given representation and semantic components. Unfortunately this makes the C++ grammar ambiguous, so it has not yet been implemented in the C++ producer.

It is possible to allow individual conversions by means of conversion tokens. A procedure token which takes one rvalue expression program parameter and returns an rvalue expression, such as:

#pragma token PROC ( EXP : t : ) EXP : s : conv #

can be regarded as mapping expressions of type t to expressions of type s. The directive:

#pragma TenDRA conversion identifier-list allow

can be used to nominate such a token as a conversion token. That is to say, if the conversion, whether explicit or implicit, from t to s cannot be done by other means, it is done by applying the token conv, so:

t a ;
s b = a ;       // maps to conv ( a )

Note

Unlike conversion functions, conversion tokens can be applied to any types.

1.2.38. Cast expressions

ISO C++ introduces the constructs static_cast, const_cast and reinterpret_cast, which can be used in various contexts where an old style explicit cast would previously have been used. By default, an explicit cast can perform any combination of the conversions performed by these three constructs. To aid migration to the new style casts the directives:

#pragma TenDRA++ explicit cast as cast-state allow
#pragma TenDRA++ explicit cast allow

where cast-state is defined as follows:

cast-state :
  static_cast
  const_cast
  reinterpret_cast
  static_cast | cast-state
  const_cast | cast-state
  reinterpret_cast | cast-state

can be used to restrict the conversions which can be performed using explicit casts. The first form sets the interpretation of explicit cast to be combinations of the given constructs; the second resets the interpretation to the default. For example:

#pragma TenDRA++ explicit cast as static_cast | const_cast allow

means that conversions requiring reinterpret_cast (the most unportable conversions) will not be allowed to be performed using explicit casts, but will have to be given as a reinterpret_cast construct. Changing allow to warning will also cause a warning to be issued for every explicit cast expression.

1.2.39. Ellipsis functions

The directive:

#pragma TenDRA ident ... allow

may be used to enable or disable the use of ... as a primary expression in a function defined with ellipsis. The type of such an expression is implementation defined. This expression is used in the definition of the va_start macro in the <stdarg.h> header. This header automatically enables this switch.

1.2.40. Overloaded functions

Older dialects of C++ did not report ambiguous overloaded function resolutions, but instead resolved the call to the first of the most viable candidates to be declared. This behaviour can be controlled using the directive:

#pragma TenDRA++ ambiguous overload resolution allow

There are occasions when the resolution of an overloaded function call is not clear. The directive:

#pragma TenDRA++ overload resolution allow

can be used to report the resolution of any such call (whether explicit or implicit) where there is more than one viable candidate.

An interesting consequence of compiling C++ in a target independent manner is that certain overload resolutions can only be determined at install-time. For example, in:

int f ( int ) ;
int f ( unsigned int ) ;
int f ( long ) ;
int f ( unsigned long ) ;

int a = f ( sizeof ( int ) ) ;  // which f?

the type of the sizeof operator, size_t, is target dependent, but its promotion must be one of the types int, unsigned int, long or unsigned long. Thus the call to f always has a unique resolution, but what it is is target dependent. The equivalent directives:

#pragma TenDRA++ conditional overload resolution allow
#pragma TenDRA++ conditional overload resolution (complete) allow

can be used to warn about such target dependent overload resolutions. By default, such resolutions are only allowed if there is a unique resolution for each possible implementation of the argument types (note that, for simplicity, the possibility of long long implementation types is ignored). The directive:

#pragma TenDRA++ conditional overload resolution (incomplete) allow

can be used to allow target dependent overload resolutions which only have resolutions for some of the possible implementation types (if one of the f declarations above was removed, for example). If the implementation does not match one of these types then an install-time error is given.

There are restrictions on the set of candidate functions involved in a target dependent overload resolution. Most importantly, it should be possible to bring their return types to a common type, as if by a series of ?: operations. This common type is the type of the target dependent call. By this means, target dependent types are prevented from propagating further out into the program. Note that since sets of overloaded functions usually have the same semantics, this does not usually present a problem.

1.2.41. Expressions

The directive:

#pragma TenDRA operator precedence analysis on

can be used to enable a check for expressions where the operator precedence is not necessarily what might be expected. The intended precedence can be clarified by means of explicit parentheses. The precedence levels checked are as follows:

  • && versus ||.

  • << and >> versus binary + and -.

  • Binary & versus binary +, -, ==, !=, >, >=, < and <=.

  • ^ versus binary &, +, -, ==, !=, >, >=, < and <=.

  • | versus binary ^, &, +, -, ==, !=, >, >=, < and <= .

Also checked are expressions such as a < b < c which do not have their normal mathematical meaning. For example, in:

d = a << b + c ;  // precedence is a << ( b + c )

the precedence is counter-intuitive, although strangely enough, it isn't in:

cout << b + c ;       // precedence is cout << ( b + c )

Other dubious arithmetic operations can be checked for using the directive:

#pragma TenDRA integer operator analysis on

This includes checks for operations, such as division by a negative value, which are implementation dependent, and those such as testing whether an unsigned value is less than zero, which serve no purpose. Similarly the directive:

#pragma TenDRA++ pointer operator analysis on

checks for dubious pointer operations. This includes very simple bounds checking for arrays and checking that only the simple literal 0 is used in null pointer constants:

char *p = 1 - 1 ;   // valid, but weird

The directive:

#pragma TenDRA integer overflow analysis on

is used to control the treatment of overflows in the evaluation of integer constant expressions. This includes the detection of division by zero.

1.2.42. Initialiser expressions

C, but not C++, only allows constant expressions in static initialisers. The directive:

#pragma TenDRA variable initialization allow

can be enable support for C++-style dynamic initialisers. Conversely, it