Difference Between Undefined Behavior and Ill-Formed, No Diagnostic Message Required

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".)

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.

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.

What is the rationale to no diagnostic required ?

There was a discussion here: https://groups.google.com/a/isocpp.org/forum/#!topic/std-discussion/lk1qAvCiviY with utterances by various committee members.

The general consensus appears to be

  • there is no normative difference
  • ill-formed; no diagnostic required is used only for compile-time rule violations, never for runtime rule violations.

As I said in that thread, I did once hear in a discussion (I can't remember anymore in which one, but I'm certain there were insightful committee members involved)

  • ill-formed; no diagnostic required for cases that clearly are bad rule violations and that can in principle be diagnosed at compile time, but would require huge efforts from an implementation.
  • undefined behavior for things that implementations could find useful meanings for, so don't neccessarily are pure evil, and for any runtime violations that results in arbitrary consequences.

The rough guide for me is; if it is at compile time, it tends to be "ill-formed; no diagnostic required" and if it is at runtime, it always is "undefined behavior".

Is missing a required include undefined behavior?

It is unspecified whether this program is well-formed or ill-formed (with a required diagnostic, because name lookup doesn’t find pow). The possibilities arise from the statement that one C++ header may include another, which grants permission to the implementation to give this program either of just two possible interpretations.

Several similar rules (e.g., that a template must have at least one valid potential specialization) are described as rendering the program ill-formed, no diagnostic required, but in this situation that freedom is not extended to the implementation (which is arguably preferable). That said, an implementation is allowed to process an ill-formed program in an arbitrary fashion so long as it issues at least one diagnostic message, so it’s not completely unreasonable to group this situation with true undefined behavior even though the symptoms differ usefully in practice.

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.

Are concepts with only a boolean literal value ill-formed, no diagnostic required?

These are the two first examples in the "trailing requires-clause" section of the standard:

void f1(int a) requires true;               // error: non-templated function

template<typename T>
auto f2(T a) -> bool requires true; // OK

Although examples given in the standard are explicitly non-normative, these make the intent abundantly clear.

Essentially, concepts can only be applied to template functions (so your code is invalid C++), but Boolean literals (e.g., requires true) are perfectly valid.

However, templates whose requirements are never satisfied (whether by a literal false in a required clause or otherwise) seem to be ill-formed, as indicated by this example,

Why is calling the main function supposedly undefined behavior (UB)

I think your analysis is correct: calls to main are ill-formed.

You have to pass the -pedantic flag to make GCC and Clang conform. In that case, Clang says

warning: ISO C++ does not allow 'main' to be used by a program [-Wmain]

and GCC says

warning: ISO C++ forbids taking address of function '::main' [-Wpedantic]

But they allow calls to main as an extension. The standard permits such an extension, since it doesn't change the meaning of any conforming programs.



Related Topics



Leave a reply



Submit