What Is the C++ Compiler Required to Do with Ill-Formed Programs According to the Standard

Is it standard-compliant to compile ill-formed program in success?

Yes, it is legal for the implementation to produce a binary when the input is an ill-formed program. Here is [intro.compliance]/8 in C++14:

A conforming implementation may have extensions (including additional library functions), provided they do
not alter the behavior of any well-formed program. Implementations are required to diagnose programs that
use such extensions that are ill-formed according to this International Standard. Having done so, however,
they can compile and execute such programs.

In such cases the diagnostic would usually be referred to as a "warning" (as opposed to "error").

Is it true that ill-formed program does not always lead to UB (either at compile time or at run time)?

If a program is ill-formed, a conforming implementation which has adequate resources to process it would be required to issue a diagnostic. Once an implementation has done so, anything it might do after that would be outside the Standard's jurisdiction. Some possible outcomes would be:

  1. Terminate processing after outputting the diagnostic.

  2. After outputting the diagnostic, proceed as though the language were extended to allow whatever constructs would otherwise make the program ill-formed.

  3. Run the last successfully-compiled version of the program, whose behavior might bear no relationship to anything in the current source files.

If an construct is characterized as "Ill-formed; no diagnostic required", all of the above remain possible in addition to a fourth possibility, namely proceeding as though the language were extended to allow the construct without bothering to output a diagnostic first.

Since the Standard only seeks to describe program behavior in terms of the current contents of the source files, and not anything they might have contained at some time in the past, it cannot meaningfully describe might happen if an implementation is fed a ill-formed source file. That doesn't imply that quality implementations shouldn't seek to avoid accidental execution of obsolete versions of programs, but any measures they might take toward that end would be outside the Standard's jurisdiction.

Note that on some implementations that target embedded or remote systems, option #3 may not be nearly as absurd as it sounds. In many cases, a build-and-execute cycle would involve stopping execution on the remote system, feeding it new code, and then restarting execution on that system. If no new code image is available, restarting whatever code image was previously present may in many cases may be more useful than leaving the system in a dead state, but there's no way the Standard can impose anything meaningful about what that program does, implying that its execution as a consequence of the failure to build the new program cannot be characterized as anything other than Undefined Behavior.

Difference between Undefined Behavior and Ill-formed, no diagnostic message required

The standard is not always as coherent as we would like, since
it is a very large document, written (in practice) by a number
of different people, and despite all of the proof-reading that
does occur, inconsistencies slip through. In the case of
undefined behavior (and errors in general), I think there is an
additional problem in that for much of the most basic things
(pointers, etc.), the C++ standard inspires from C. But the
C standard takes the point of view that all errors are undefined
behavior, unless stated otherwise, where as the C++ standard
tries to take the point of view that all errors require
a diagnostic, unless stated otherwise. (Although they still
have to allow for the case where the standard omits to specify
a behavior.) I think this accounts for a lot of the
inconsistency in the wording.

Globally, the inconsistency is regrettable, but on the whole, if
the standard says that something is erroneous, or ill-formed,
then it requires a diagnostic, unless the standard says that it
doesn't, or that it is undefined behavior. In something like
"ill-formed; no diagnostic required", the "no diagnostic
required
" is important, because otherwise, it would require
a diagnostic. As for the difference between "ill-formed; no
diagnostic required" and "undefined behavior", there isn't any.
The first is probably more frequent in cases where the code is
incorrect, the second where it is a run-time issue, but it's not
systematic. (The specification of the one definition
rule—clearly a compile time issue—ends with "then
the behavior is undefined".)

Meaning of ill-formed declaration in L(n)

I think that the example demonstrates that declarator may be enclosed in parentheses.

So this declaration

M(m);

is equivalent to

M m;

that is there is declared an object m of the type M.

However this record

L(n);

can be considered as an expression statement with calling the constructor L( M & ) with the argument n of the type M or as a declaration.

The C++ Standard resolves such an ambiguity as a declaration instead of the expression statement. So in this record n is the name of the created object. But the class L does not have the default constructor. So the declaration is ill-formed because the structure L does not have the default constructor that is required for this declaration.

From the C++ 14 Standard (6.8 Ambiguity resolution)

1 There is an ambiguity in the grammar involving expression-statements
and declarations: An expression-statement with a function-style
explicit type conversion (5.2.3) as its leftmost subexpression can be
indistinguishable from a declaration where the first declarator starts
with a (. In those cases the statement is a declaration.

To make an expression statement instead of the declaration you can write for example

( L )( n );

This record

L(l)(m);

is a correct declaration. There is declared the object l of the type L using the constructor L( M & ).

Does IFNDR take precedence over diagnosable rule violations?

Section 2.3 is clear – "this document places no requirement on implementations". Not "this document except for 2.2", but "this document". If an IFNDR situation exists, then the implementation is free to do anything.

Necessary

Overriding 2.2 is necessary. Hypothetically, an IFNDR situation could throw compilation off-track, resulting in it missing an ill-formed situation that does require a diagnostic. (Bugs hidden by other bugs is not a novel concept.) This does not make the implementation non-conforming. The existence of the IFNDR situation gives implementations the leeway they need to potentially miss a diagnostic in related code despite good-faith processing.

This does mean that implementations also have leeway to miss a diagnostic in unrelated code. Oh well. The standard could try to distinguish between "related code" and "unrelated code", but it would be wasted effort. At some point, it is better to trust that implementations work in good faith, rather than excessively regulate.

A "diagnostic not required" scenario is a valid reason for missing a diagnostic. If an implementation uses it as an excuse to actively omit a diagnostic, then it is compliant, but you should stay away.
Just like you should stay far away from a compliant implementation that detects undefined behavior and uses that as an excuse to format your hard drive.

Practical

From a practical perspective, though, 2.3 does not override 2.2. While the official terminology is "no diagnostic required", a more practical view is "the situation does not need to be detected". Situations that require a diagnostic must be detected so that the diagnostic can be produced. Situations that do not require any particular action by the implementation do not need to be detected.

If an implementation does not try to detect an IFNDR situation, then it cannot make decisions based upon the IFNDR existing. That is, it must provide diagnostics for the ill-formedness that it does detect. This is not just "quality-of-implementation", nor is it just "good faith", but rather it is necessary to ensure compliance in the face of an unknown.

If an implementation does detect an IFNDR situation, then we fall back on good faith.

In the C++ standard does well-formed means that the code compiles?

A well-formed program can have undefined behaviour.

It's in a note, and thus not technically authoritative, but it seems that it is intention that termination of compilation (or "translation" as the standard calls it) is within the scope of possible UB:

[intro.defs]

undefined behavior

behavior for which this document imposes no requirements

[ Note: Undefined behavior may be expected when this document omits any explicit definition of behavior or when a program uses an erroneous construct or erroneous data.

Permissible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message).

Many erroneous program constructs do not engender undefined behavior; they are required to be diagnosed.

Evaluation of a constant expression never exhibits behavior explicitly specified as undefined in [intro] through [cpp] of this document ([expr.const]).
— end note
]


There are also practical implementation limits:

[implemits]

Because computers are finite, C++ implementations are inevitably limited in the size of the programs they can successfully process.

Every implementation shall document those limitations where known. This documentation may cite fixed limits where they exist, say how to compute variable limits as a function of available resources, or say that fixed limits do not exist or are unknown.


Furthermore, compilers can have, and do have bugs. Well-formed simply means the a standard conforming compiler should compile it (within the limitations mentioned above). A buggy compiler does not necessarily conform to the standard.


Lastly, the standard document itself is not perfect. If there is disagreement about what the rules mean, then it is possible for a program to be well-formed under one interpretation, and ill-formed under another interpretation.

If a compiler disagrees with the programmer or another compiler, then it might fail to compile a program that is believed to be well-formed by the other party.

Ill-Formed, No Diagnostic Required (NDR): ConstExpr Function Throw in C++14

Is this code correct (and there is a mistake in the Standard)

The standard is what decides if a program is "correct" i.e. well-formed. The standard explicitly says that the program is ill-formed.

or is it incorrect (and there is a bug in both Clang and GCC)?

The program is ill-formed (incorrect). Both clang and gcc are standard compliant in their observed behaviour.

Is it possible that a/this program is "ill-formed, no diagnostic required" and that the compilers are allowed to compile it successfully?

Yes. The standard doesn't require that an ill-formed program must fail to compile. It merely requires that the implementation issues at least one diagnostic message, if the program violates the rules. Some rules are not diagnosable, and diagnostic is not required if such rules are violated.

In fact, the rule is considered "not diagnosable" because it would be very difficult for the compiler to prove (in general) that the rule is violated. It is quite typical that "no diagnostic required" rule violations compile successfully.

If an ill-formed program does compile, the standard does not specify how such program should behave.

Does the C++ standard specify that for some cases the compiling should fail with an error?

The standard doesn't use the terms "error" and "warning", it only talks about cases where the compiler must "issue a diagnostic".

In your example, if the program is "ill-formed", the compiler is required to tell you that somehow - issue a diagnostic.

After that, it can do anything it likes - including compiling and running the program anyway. The standard only specifies what happens for conforming code, everything else is undefined. And then, as we know, anything can happen.



Related Topics



Leave a reply



Submit