From edce8b0932d9554612a1956984f5955a5ff9294e Mon Sep 17 00:00:00 2001 From: jberne4 Date: Sat, 5 Apr 2025 08:23:37 +0200 Subject: [PATCH 1/3] P3596R3 Undefined Behavior and IFNDR Annexes --- source/basic.tex | 78 +- source/classes.tex | 25 +- source/declarations.tex | 38 +- source/exceptions.tex | 2 +- source/expressions.tex | 74 +- source/ifndr.tex | 1061 +++++++++++++++++++++ source/lex.tex | 2 +- source/macros.tex | 42 + source/modules.tex | 9 +- source/overloading.tex | 2 +- source/preprocessor.tex | 7 +- source/statements.tex | 8 +- source/std.tex | 2 + source/templates.tex | 30 +- source/ub.tex | 1976 +++++++++++++++++++++++++++++++++++++++ tools/check-output.sh | 2 +- tools/check-source.sh | 2 +- 17 files changed, 3227 insertions(+), 133 deletions(-) create mode 100644 source/ifndr.tex create mode 100644 source/ub.tex diff --git a/source/basic.tex b/source/basic.tex index c1f58de60a..d986160fcd 100644 --- a/source/basic.tex +++ b/source/basic.tex @@ -674,7 +674,7 @@ \pnum Every program shall contain at least one definition of every function or variable that is odr-used in that program -outside of a discarded statement\iref{stmt.if}; no diagnostic required. +outside of a discarded statement\iref{stmt.if}; no diagnostic required\ifndrdef{basic.def.odr.minimum.one.def}. The definition can appear explicitly in the program, it can be found in the standard or a user-defined library, or (when appropriate) it is implicitly defined (see~\ref{class.default.ctor}, \ref{class.copy.ctor}, @@ -762,21 +762,23 @@ that definition shall be an injected declaration having the same characteristic sequence as $X$; a diagnostic is required only if \tcode{D} is attached to a named module and -a prior definition is reachable at the point where a later definition occurs. +a prior definition is reachable +at the point where a later definition occurs\ifndrdef{basic.def.odr.injected.match}. \pnum For any other definable item \tcode{D} with definitions in multiple translation units, \begin{itemize} \item -if \tcode{D} is a non-inline non-templated function or variable, or +if \tcode{D} is a non-inline non-templated function or variable\ifndrdef{basic.def.odr.maximum.one.def}, or \item if the definitions in different translation units -do not satisfy the following requirements, +do not satisfy the following requirements\ifndrdef{basic.def.odr.definition.matches}, \end{itemize} the program is ill-formed; a diagnostic is required only if the definable item is attached to a named module and -a prior definition is reachable at the point where a later definition occurs. +a prior definition is reachable +at the point where a later definition occurs. Given such an item, for all definitions of \tcode{D}, or, if \tcode{D} is an unnamed enumeration, @@ -957,7 +959,7 @@ reachable unnamed enumeration definition in the same scope that have the same first enumerator name and do not have typedef names for linkage purposes\iref{dcl.enum}, -those unnamed enumeration types shall be the same; no diagnostic required. +those unnamed enumeration types shall be the same; no diagnostic required\ifndrdef{basic.def.odr.unnamed.enum.same.type}. \indextext{one-definition rule|)} \rSec1[basic.scope]{Scope}% @@ -1838,7 +1840,7 @@ If it is an invalid set, the program is ill-formed. If it differs from the result of a search in $T$ for $M$ in a complete-class context\iref{class.mem} of $T$, -the program is ill-formed, no diagnostic required. +the program is ill-formed, no diagnostic required\ifndrdef{class.member.lookup.name.refers.diff.decl}. \begin{example} \begin{codeblock} struct A { int x; }; // S(x,A) = \{ \{ \tcode{A::x} \}, \{ \tcode{A} \} \} @@ -3166,7 +3168,8 @@ declarations for an array object can specify array types that differ by the presence or absence of a major array bound\iref{dcl.array}. -No diagnostic is required if neither declaration is reachable from the other. +No diagnostic is required +if neither declaration is reachable from the other\ifndrdef{basic.link.consistent.types}. \begin{example} \begin{codeblock} int f(int x, int x); // error: different entities for \tcode{x} @@ -3768,7 +3771,7 @@ zero or more objects of implicit-lifetime types\iref{term.implicit.lifetime.type} in its specified region of storage if doing so would result in the program having defined behavior. -If no such set of objects would give the program defined behavior, +If no such set of objects would give the program defined behavior\ubdef{intro.object.implicit.create}, the behavior of the program is undefined. If multiple such sets of objects would give the program defined behavior, it is unspecified which such set of objects is created. @@ -3786,7 +3789,7 @@ and produce a pointer value that points to that object, if that value would result in the program having defined behavior. If no such pointer value would give the program defined behavior, -the behavior of the program is undefined. +the behavior of the program is undefined\ubdef{intro.object.implicit.pointer}. If multiple such pointer values would give the program defined behavior, it is unspecified which such pointer value is produced. @@ -3840,7 +3843,7 @@ using the \grammarterm{alignment-specifier}\iref{dcl.align}. Attempting to create an object\iref{intro.object} in storage that does not meet the alignment requirements of the object's type -is undefined behavior. +is undefined behavior\ubdef{basic.align.object.alignment}. \pnum A \defnadj{fundamental}{alignment} is represented by an alignment @@ -4029,19 +4032,20 @@ if the pointer were of type \tcode{\keyword{void}*} is well-defined. Indirection through such a pointer is permitted but the resulting lvalue may only be used in limited ways, as described below. The -program has undefined behavior if +program has undefined behavior if: \begin{itemize} \item - the pointer is used as the operand of a \grammarterm{delete-expression}, + the pointer is used as the operand of a \grammarterm{delete-expression}\ubdef{lifetime.outside.pointer.delete}, \item the pointer is used to access a non-static data member or call a - non-static member function of the object, or + non-static member function of the object\ubdef{lifetime.outside.pointer.member}, or \item the pointer is converted\iref{conv.ptr,expr.static.cast} to a pointer - to a virtual base class or to a base class thereof, or + to a virtual base class\ubdef{lifetime.outside.pointer.virtual} or + to a base class thereof, or \item the pointer is used as the operand of a - \keyword{dynamic_cast}\iref{expr.dynamic.cast}. + \keyword{dynamic_cast}\iref{expr.dynamic.cast}\ubdef{lifetime.outside.pointer.dynamic.cast}. \end{itemize} \begin{example} \begin{codeblock} @@ -4084,14 +4088,14 @@ a glvalue refers to allocated storage\iref{basic.stc.dynamic.allocation}, and using the properties of the glvalue that do not depend on its value is -well-defined. The program has undefined behavior if +well-defined. The program has undefined behavior if: \begin{itemize} -\item the glvalue is used to access the object, or -\item the glvalue is used to call a non-static member function of the object, or -\item the glvalue is bound to a reference to a virtual base class\iref{dcl.init.ref}, or +\item the glvalue is used to access the object\ubdef{lifetime.outside.glvalue.access}, or +\item the glvalue is used to call a non-static member function of the object\ubdef{lifetime.outside.glvalue.member}, or +\item the glvalue is bound to a reference to a virtual base class\iref{dcl.init.ref}\ubdef{lifetime.outside.glvalue.virtual}, or \item the glvalue is used as the operand of a \keyword{dynamic_cast}\iref{expr.dynamic.cast} or as the operand of -\keyword{typeid}. +\keyword{typeid}\ubdef{lifetime.outside.glvalue.dynamic.cast}. \end{itemize} \begin{note} @@ -4176,7 +4180,7 @@ \end{footnote} and another object of the original type does not occupy that same storage location when the implicit destructor call takes -place, the behavior of the program is undefined. This is true +place, the behavior of the program is undefined\ubdef{original.type.implicit.destructor}. This is true even if the block is exited with an exception. \begin{example} \begin{codeblock} @@ -4196,7 +4200,7 @@ Creating a new object within the storage that a const, complete object with static, thread, or automatic storage duration occupies, or within the storage that such a const object used to occupy before -its lifetime ended, results in undefined behavior. +its lifetime ended, results in undefined behavior\ubdef{creating.within.const.complete.obj}. \begin{example} \begin{codeblock} struct B { @@ -4257,7 +4261,7 @@ then the value produced by that operator is erroneous. Except in the following cases, if an indeterminate value is produced by an evaluation, -the behavior is undefined, and +the behavior is undefined\ubdef{basic.indet.value}, and if an erroneous value is produced by an evaluation, the behavior is erroneous and the result of the evaluation is that erroneous value: @@ -4544,7 +4548,7 @@ does not satisfy the semantic constraints specified in~\ref{basic.stc.dynamic.allocation} and~\ref{basic.stc.dynamic.deallocation}, -the behavior is undefined. +the behavior is undefined\ubdef{basic.stc.alloc.dealloc.constraint}. \indextext{storage duration!dynamic|)} @@ -4584,7 +4588,7 @@ \tcode{p0} represents the address of a block of storage disjoint from the storage for any other object accessible to the caller. The effect of indirecting through a pointer -returned from a request for zero size is undefined. +returned from a request for zero size is undefined\ubdef{basic.stc.alloc.zero.dereference}. \begin{footnote} The intent is to have \tcode{\keyword{operator} \keyword{new}()} implementable by @@ -4707,7 +4711,7 @@ signature. \pnum -If a deallocation function terminates by throwing an exception, the behavior is undefined. +If a deallocation function terminates by throwing an exception, the behavior is undefined\ubdef{basic.stc.alloc.dealloc.throw}. The value of the first argument supplied to a deallocation function may be a null pointer value; if so, and if the deallocation function is one supplied in the standard library, the call has no effect. @@ -6087,7 +6091,7 @@ $P$ is not valid in the context of $E$, then the behavior is undefined if $E$ is an indirection\iref{expr.unary.op} or -an invocation of a deallocation function\iref{basic.stc.dynamic.deallocation}, +an invocation of a deallocation function\iref{basic.stc.dynamic.deallocation}\ubdef{basic.compound.invalid.pointer}, and \impldef{invalid pointer value in the context of an evaluation} otherwise. \begin{footnote} Some implementations might define that @@ -6607,7 +6611,7 @@ The value computations of the operands of an operator are sequenced before the value computation of the result of the operator. -The behavior is undefined if +The behavior is undefined\iref{intro.multithread}\ubdef{intro.execution.unsequenced.modification} if \begin{itemize} \item \indextext{side effects}% @@ -7015,7 +7019,7 @@ and neither happens before the other, except for the special case for signal handlers described below. Any such data race results in undefined -behavior. +behavior\ubdef{intro.races.data}. \begin{note} It can be shown that programs that correctly use mutexes and \tcode{memory_order::seq_cst} operations to prevent all data races and use no @@ -7074,8 +7078,8 @@ \rSec3[intro.progress]{Forward progress} \pnum -The implementation may assume that any thread will eventually do one of the -following: +The implementation may assume\ubdef{intro.progress.stops} that any thread +will eventually do one of the following: \begin{itemize} \item terminate, \item invoke the function \tcode{std::this_thread::yield}\iref{thread.thread.this}, @@ -7379,7 +7383,7 @@ objects with automatic storage duration\iref{class.dtor}. If \tcode{std::exit} is invoked during the destruction of an object with static or thread storage duration, the program has undefined -behavior. +behavior\ubdef{basic.start.main.exit.during.destruction}. \pnum \indextext{termination!program}% @@ -7716,7 +7720,7 @@ \pnum If a function contains a block variable of static or thread storage duration that has been destroyed and the function is called during the destruction of an object with static or -thread storage duration, the program has undefined behavior if the flow of control +thread storage duration, the program has undefined behavior\ubdef{basic.start.term.use.after.destruction} if the flow of control passes through the definition of the previously destroyed block variable. \begin{note} Likewise, the behavior is undefined @@ -7744,7 +7748,7 @@ handlers\iref{support.runtime} that does not happen before\iref{intro.multithread} completion of destruction of objects with static storage duration and execution of \tcode{std::atexit} registered functions\iref{support.start.term}, the program has -undefined behavior. +undefined behavior \begin{note} If there is a use of an object with static storage duration that does not happen before the object's destruction, the program has undefined @@ -7793,7 +7797,7 @@ An invocation of the macro \tcode{va_start}\iref{cstdarg.syn} shall not be a subexpression of the predicate of a contract assertion, -no diagnostic required. +no diagnostic required\ifndrdef{basic.contract.vastart.contract.predicate}. \pnum \begin{note} @@ -8199,6 +8203,6 @@ If the contract-violation handler is not replaceable, a declaration of a replacement function for the contract-violation handler -is ill-formed, no diagnostic required. +is ill-formed, no diagnostic required.\ifndrdef{basic.contract.handler.replacing.nonreplaceable} \indextext{contract assertion|)} diff --git a/source/classes.tex b/source/classes.tex index 6cd80b2796..f6849fadd5 100644 --- a/source/classes.tex +++ b/source/classes.tex @@ -2335,7 +2335,7 @@ \pnum Once a destructor is invoked for an object, the object's lifetime ends; -the behavior is undefined if the destructor is invoked +the behavior is undefined\ubdef{class.dtor.no.longer.exists} if the destructor is invoked for an object whose lifetime has ended\iref{basic.life}. \begin{example} If the destructor for an object with automatic storage duration is explicitly invoked, @@ -4006,7 +4006,7 @@ \indextext{definition!virtual function}% A virtual function declared in a class shall be defined, or declared pure\iref{class.abstract} in that class, or both; no diagnostic is -required\iref{basic.def.odr}. +required\iref{basic.def.odr}\ifndrdef{class.virtual.pure.or.defined}. \indextext{friend!\tcode{virtual} and}% \pnum @@ -4240,7 +4240,7 @@ \indextext{virtual function call!undefined pure}% the effect of making a virtual call\iref{class.virtual} to a pure virtual function directly or indirectly for the object being created (or -destroyed) from such a constructor (or destructor) is undefined.% +destroyed) from such a constructor (or destructor) is undefined\ubdef{class.abstract.pure.virtual}.% \indextext{derived class|)} \rSec1[class.access]{Member access control}% @@ -5525,7 +5525,7 @@ The target constructor is selected by overload resolution. Once the target constructor returns, the body of the delegating constructor is executed. If a constructor delegates to itself directly or indirectly, -the program is ill-formed, no diagnostic required. +the program is ill-formed, no diagnostic required\ifndrdef{class.base.init.delegate.itself}. \begin{example} \begin{codeblock} struct C { @@ -5840,7 +5840,7 @@ \item a postcondition assertion of a destructor\iref{dcl.contract.func}, \end{itemize} -the program has undefined behavior. +the program has undefined behavior\ubdef{class.base.init.mem.fun}. \begin{example} \begin{codeblock} class A { @@ -6037,9 +6037,10 @@ \indextext{destruction!member access}% For an object with a non-trivial constructor, referring to any non-static member or base class of the object before the constructor begins execution results in -undefined behavior. For an object with a non-trivial destructor, referring to +undefined behavior\ubdef{class.cdtor.before.ctor}. +For an object with a non-trivial destructor, referring to any non-static member or base class of the object after the destructor finishes -execution results in undefined behavior. +execution results in undefined behavior\ubdef{class.cdtor.after.dtor}. \begin{example} \begin{codeblock} struct X { int i; }; @@ -6124,7 +6125,7 @@ indirectly derive from \tcode{B} shall have started and the destruction of these classes shall not have -completed, otherwise the conversion results in undefined behavior. +completed, otherwise the conversion results in undefined behavior\ubdef{class.cdtor.convert.pointer}. To form a pointer to (or access the value of) a direct non-static member of an object \tcode{obj}, @@ -6132,7 +6133,7 @@ \tcode{obj} shall have started and its destruction shall not have completed, otherwise the computation of the pointer value (or accessing the member -value) results in undefined behavior. +value) results in undefined behavior\ubdef{class.cdtor.form.pointer}. \begin{example} \begin{codeblock} struct A { }; @@ -6176,7 +6177,7 @@ and the object expression refers to the complete object of \tcode{x} or one of that object's base class subobjects but not \tcode{x} or one of its base class subobjects, the behavior -is undefined. +is undefined\ubdef{class.cdtor.virtual.not.x}. \begin{example} \begin{codeblock} struct V { @@ -6233,7 +6234,7 @@ \tcode{typeid} refers to the object under construction or destruction and the static type of the operand is neither the constructor or destructor's class nor one of its -bases, the behavior is undefined. +bases, the behavior is undefined\ubdef{class.cdtor.typeid}. \pnum \indextext{construction!dynamic cast and}% @@ -6258,7 +6259,7 @@ the operand is not a pointer to or object of the constructor or destructor's own class or one of its bases, the \keyword{dynamic_cast} -results in undefined behavior. +results in undefined behavior\ubdef{class.cdtor.dynamic.cast}. \begin{example} \begin{codeblock} struct V { diff --git a/source/declarations.tex b/source/declarations.tex index 627360a226..88830bfea2 100644 --- a/source/declarations.tex +++ b/source/declarations.tex @@ -1065,7 +1065,7 @@ If the specifier is applied to any declaration of a variable, it shall be applied to the initializing declaration. No diagnostic is required if no \keyword{constinit} declaration -is reachable at the point of the initializing declaration. +is reachable at the point of the initializing declaration\ifndrdef{dcl.constinit.specifier.not.reachable}. \pnum If a variable declared with the \keyword{constinit} specifier has @@ -1140,7 +1140,7 @@ is declared inline in one definition domain, an inline declaration of it shall be reachable from the end of every definition domain in which it is declared; -no diagnostic is required. +no diagnostic is required\ifndrdef{dcl.inline.missing.on.definition}. \begin{note} A call to an inline function or a use of an inline variable can be encountered before its definition becomes reachable in a translation unit. @@ -1297,7 +1297,7 @@ \indextext{const object!undefined change to}% Any attempt to modify\iref{expr.assign,expr.post.incr,expr.pre.incr} a const object\iref{basic.type.qualifier} during its -lifetime\iref{basic.life} results in undefined behavior. +lifetime\iref{basic.life} results in undefined behavior\ubdef{dcl.type.cv.modify.const.obj}. \begin{example} \begin{codeblock} const int ci = 3; // cv-qualified (initialized as required) @@ -1341,7 +1341,7 @@ \impldef{semantics of an access through a volatile glvalue}. If an attempt is made to access an object defined with a volatile-qualified type through the use of a non-volatile glvalue, -the behavior is undefined. +the behavior is undefined\ubdef{dcl.type.cv.access.volatile}. \pnum \indextext{type specifier!\idxcode{volatile}}% @@ -3286,11 +3286,11 @@ the converted initializer is a glvalue whose type is not call-compatible\iref{expr.call} with the type of the function's definition -results in undefined behavior. +results in undefined behavior\ubdef{dcl.ref.incompatible.function}. Attempting to bind a reference to an object where the converted initializer is a glvalue through which the object is not type-accessible\iref{basic.lval} -results in undefined behavior. +results in undefined behavior\ubdef{dcl.ref.incompatible.type}. \begin{note} \indextext{reference!null}% The object designated by such a glvalue can be @@ -3304,7 +3304,7 @@ \end{note} The behavior of an evaluation of a reference\iref{expr.prim.id, expr.ref} that does not happen after\iref{intro.races} the initialization of the reference -is undefined. +is undefined\ubdef{dcl.ref.uninitialized.reference}. \begin{example} \begin{codeblock} int &f(int&); @@ -4376,7 +4376,8 @@ \end{example} For a given inline function defined in different translation units, the accumulated sets of default arguments at the end of the -translation units shall be the same; no diagnostic is required. +translation units shall be the same; +no diagnostic is required\ifndrdef{dcl.fct.default.inline.same.defaults}. If a friend declaration $D$ specifies a default argument expression, that declaration shall be a definition and there shall be no other declaration of the function or function template @@ -4681,7 +4682,8 @@ a declaration $F_2$ is a first declaration of \tcode{f} in another translation unit, $F_1$ and $F_2$ shall specify the same -\grammarterm{function-contract-specifier-seq}, no diagnostic required. +\grammarterm{function-contract-specifier-seq}, +no diagnostic required\ifndrdef{dcl.contract.func.mismatched.contract.specifiers}. \pnum A \grammarterm{function-contract-specifier-seq} $S_1$ @@ -7399,7 +7401,7 @@ The evaluation that invoked a resumption member function is called the \defnx{resumer}{coroutine!resumer}. Invoking a resumption member function for a coroutine -that is not suspended results in undefined behavior. +that is not suspended results in undefined behavior\ubdef{dcl.fct.def.coroutine.resume.not.suspended}. \pnum An implementation may need to allocate additional storage for a coroutine. @@ -7493,7 +7495,7 @@ The storage for the coroutine state is released by calling a non-array deallocation function\iref{basic.stc.dynamic.deallocation}. If \tcode{destroy} is called for a coroutine that is not suspended, the -program has undefined behavior. +program has undefined behavior\ubdef{dcl.fct.def.coroutine.destroy.not.suspended}. \pnum The deallocation function's name is looked up by searching for it in the scope of the promise type. @@ -7590,7 +7592,7 @@ shall be such that it would be valid as a redeclaration of the declaration in that header; \end{itemize} -no diagnostic is required. +no diagnostic is required\ifndrdef{dcl.fct.def.replace.bad.replacement}. \begin{note} The one-definition rule\iref{basic.def.odr} applies to the definitions of a replaceable function @@ -9311,7 +9313,7 @@ \pnum If two declarations of an entity give it different language linkages, the program is ill-formed; no diagnostic is required if neither declaration -is reachable from the other. +is reachable from the other\ifndrdef{dcl.link.mismatched.language.linkage}. \indextext{consistency!linkage specification}% A redeclaration of an entity without a linkage specification inherits the language linkage of the entity and (if applicable) its type. @@ -9669,7 +9671,7 @@ \grammarterm{alignment-specifier}{}, every defining declaration of that entity shall specify an equivalent alignment. -No diagnostic is required if declarations of an entity have +No diagnostic is required\ifndrdef{dcl.align.diff.translation.units} if declarations of an entity have different \grammarterm{alignment-specifier}{s} in different translation units. \begin{example} @@ -9723,7 +9725,7 @@ at the point where the assumption appears, the assumption has no effect. Otherwise, -evaluation of the assumption has runtime-undefined behavior. +evaluation of the assumption has runtime-undefined behavior\ubdef{dcl.attr.assume.false}. \pnum \begin{note} @@ -9899,7 +9901,7 @@ in one translation unit and the same function is declared without the \tcode{indeterminate} attribute on the same parameter in its first declaration in another translation unit, -the program is ill-formed, no diagnostic required. +the program is ill-formed, no diagnostic required\ifndrdef{dcl.attr.indet.mismatched.declarations}. \pnum \begin{note} @@ -10155,12 +10157,12 @@ specify the \tcode{noreturn} attribute if any declaration of that function specifies the \tcode{noreturn} attribute. If a function is declared with the \tcode{noreturn} attribute in one translation unit and the same function is declared without the \tcode{noreturn} attribute in another -translation unit, the program is ill-formed, no diagnostic required. +translation unit, the program is ill-formed, no diagnostic required\ifndrdef{dcl.attr.noreturn.trans.unit.mismatch}. \pnum If a function \tcode{f} is invoked where \tcode{f} was previously declared with the \tcode{noreturn} attribute and that invocation eventually returns, -the behavior is runtime-undefined. +the behavior is runtime-undefined\ubdef{dcl.attr.noreturn.eventually.returns}. \begin{note} The function can terminate by throwing an exception. diff --git a/source/exceptions.tex b/source/exceptions.tex index df3b639dc0..34e3d5ee26 100644 --- a/source/exceptions.tex +++ b/source/exceptions.tex @@ -690,7 +690,7 @@ Referring to any non-static member or base class of an object in the handler for a \grammarterm{function-try-block} -of a constructor or destructor for that object results in undefined behavior. +of a constructor or destructor for that object results in undefined behavior\ubdef{except.handle.handler.ctor.dtor}. \pnum Exceptions thrown in destructors of objects with static storage duration or in diff --git a/source/expressions.tex b/source/expressions.tex index 72f77d87bb..a33badd5f2 100644 --- a/source/expressions.tex +++ b/source/expressions.tex @@ -64,7 +64,7 @@ \indextext{zero!remainder undefined}% If during the evaluation of an expression, the result is not mathematically defined or not in the range of representable values for -its type, the behavior is undefined. +its type, the behavior is undefined\ubdef{expr.expr.eval}. \begin{note} \indextext{overflow}% Treatment of division by zero, forming a remainder using a zero divisor, @@ -317,7 +317,7 @@ If a program attempts to access\iref{defns.access} the stored value of an object through a glvalue through which it is not type-accessible, -the behavior is undefined. +the behavior is undefined\ubdef{expr.basic.lvalue.strict.aliasing.violation}. \begin{footnote} The intent of this list is to specify those circumstances in which an object can or cannot be aliased. @@ -326,7 +326,7 @@ a defaulted copy/move constructor or copy/move assignment operator for a union of type \tcode{U} with a glvalue argument that does not denote an object of type \cv{}~\tcode{U} within its lifetime, -the behavior is undefined. +the behavior is undefined\ubdef{expr.basic.lvalue.union.initialization}. \begin{note} In C, an entire object of structure type can be accessed, e.g., using assignment. By contrast, \Cpp{} has no notion of accessing an object of class type @@ -345,7 +345,7 @@ If a pointer to $X$ would be valid in the context of the evaluation of the expression\iref{basic.fundamental}, the result designates $X$; -otherwise, the behavior is undefined. +otherwise, the behavior is undefined\ubdef{expr.type.reference.lifetime}. \begin{note} Before the lifetime of the reference has started or after it has ended, the behavior is undefined (see~\ref{basic.life}). @@ -686,7 +686,7 @@ \item Otherwise, if the bits in the value representation of the object to which the glvalue refers -are not valid for the object's type, the behavior is undefined. +are not valid for the object's type, the behavior is undefined\ubdef{conv.lval.valid.representation}. \begin{example} \begin{codeblock} bool f() { @@ -989,7 +989,7 @@ that exact representation. If the source value is between two adjacent destination values, the result of the conversion is an \impldef{result of inexact floating-point conversion} choice of either of those values. -Otherwise, the behavior is undefined. +Otherwise, the behavior is undefined\ubdef{conv.double.out.of.range}. \pnum The conversions allowed as floating-point promotions are excluded from @@ -1003,7 +1003,7 @@ integer type. The conversion truncates; that is, the fractional part is discarded. \indextext{value!undefined unrepresentable integral}% -The behavior is undefined if the truncated value cannot be represented +The behavior is undefined\ubdef{conv.fpint.float.not.represented} if the truncated value cannot be represented in the destination type. \begin{note} If the destination type is \keyword{bool}, see~\ref{conv.bool}. @@ -1024,7 +1024,9 @@ exactly as a value of the floating-point type. \end{note} If the value being converted is -outside the range of values that can be represented, the behavior is undefined. If the +outside the range of values that can be represented, +the behavior is undefined\ubdef{conv.fpint.int.not.represented}. +If the source type is \keyword{bool}, the value \keyword{false} is converted to zero and the value \keyword{true} is converted to one. @@ -1077,7 +1079,7 @@ that is within its lifetime or within its period of construction or destruction\iref{class.cdtor}, -the behavior is undefined. +the behavior is undefined\ubdef{conv.ptr.virtual.base}. Otherwise, the result is a pointer to the base class subobject of the derived class object. @@ -1111,7 +1113,8 @@ \tcode{D}, a program that necessitates this conversion is ill-formed. If class \tcode{D} does not contain the original member and is not a base class of the class containing the original member, -the behavior is undefined. Otherwise, +the behavior is undefined\ubdef{conv.member.missing.member}. +Otherwise, the result of the conversion refers to the same member as the pointer to member before the conversion took place, but it refers to the base class member as if it were a member of the derived class. The result refers to @@ -3320,7 +3323,7 @@ \end{note} If the substitution of template arguments into a \grammarterm{requirement} would always result in a substitution failure, the program is ill-formed; -no diagnostic required. +no diagnostic required\ifndrdef{expr.prim.req.always.sub.fail}. \begin{example} \begin{codeblock} template concept C = @@ -3859,7 +3862,7 @@ expression whose function type is not call-compatible with the type of the called function's -definition results in undefined behavior. +definition results in undefined behavior\ubdef{expr.call.different.type}. \begin{note} This requirement allows the case when the expression has the type of a @@ -4391,7 +4394,7 @@ or direct base class relationship and the result of \tcode{E1} is an object whose type is not similar\iref{conv.qual} to the type of \tcode{E1}, -the behavior is undefined. +the behavior is undefined\ubdef{expr.ref.member.not.similar}. \begin{example} \begin{codeblock} struct A { int i; }; @@ -4520,14 +4523,14 @@ that is within its lifetime or within its period of construction or destruction\iref{class.cdtor}, -the behavior is undefined. +the behavior is undefined\ubdef{expr.dynamic.cast.pointer.lifetime}. If \tcode{v} is a glvalue of type \tcode{U} and \tcode{v} does not refer to an object whose type is similar to \tcode{U} and that is within its lifetime or within its period of construction or destruction, -the behavior is undefined. +the behavior is undefined\ubdef{expr.dynamic.cast.glvalue.lifetime}. \pnum If \tcode{T} is ``pointer to \cv{} \keyword{void}'', then the result @@ -4732,7 +4735,7 @@ type ``\cvqual{cv1} \tcode{B}''. If the object of type ``\cvqual{cv1} \tcode{B}'' is actually a base class subobject of an object of type \tcode{D}, the result refers to the enclosing object of type -\tcode{D}. Otherwise, the behavior is undefined. +\tcode{D}. Otherwise, the behavior is undefined\ubdef{expr.static.cast.base.class}. \begin{example} \begin{codeblock} struct B { }; @@ -4832,7 +4835,7 @@ the value is unchanged if the original value is within the range of the enumeration values\iref{dcl.enum}, and -otherwise, the behavior is undefined. +otherwise, the behavior is undefined\ubdef{expr.static.cast.enum.outside.range}. A value of floating-point type can also be explicitly converted to an enumeration type. The resulting value is the same as converting the original value to the underlying type of the enumeration\iref{conv.fpint}, and subsequently to @@ -4847,7 +4850,7 @@ the result of the conversion is an \impldef{result of inexact floating-point conversion} choice of either of those values. -Otherwise, the behavior is undefined. +Otherwise, the behavior is undefined\ubdef{expr.static.cast.fp.outside.range}. \pnum \indextext{cast!base class}% @@ -4868,7 +4871,7 @@ ``pointer to \cvqual{cv1} \tcode{B}'' points to a \tcode{B} that is actually a base class subobject of an object of type \tcode{D}, the resulting pointer points to the enclosing object of type \tcode{D}. Otherwise, the -behavior is undefined. +behavior is undefined\ubdef{expr.static.cast.downcast.wrong.derived.type}. \pnum \indextext{cast!pointer-to-member}% @@ -4891,7 +4894,7 @@ member pointer value of the destination type. If class \tcode{B} contains the original member, or is a base class of the class containing the original member, the resulting pointer to member points -to the original member. Otherwise, the behavior is undefined. +to the original member. Otherwise, the behavior is undefined\ubdef{expr.static.cast.does.not.contain.original.member}. \begin{note} Although class \tcode{B} need not contain the original member, the dynamic type of the object with which indirection through the pointer @@ -5269,7 +5272,8 @@ The operator yields an lvalue of type \tcode{T}. If the operand points to an object or function, the result denotes that object or function; -otherwise, the behavior is undefined except as specified in \ref{expr.typeid}. +otherwise, the behavior is undefined except as specified in \ref{expr.typeid} +\ubdef{expr.unary.dereference}. \begin{note} Indirection through a pointer to an out-of-lifetime object is valid\iref{basic.life}. \end{note} @@ -6285,7 +6289,7 @@ \end{note} If the allocation function is a non-allocating form\iref{new.delete.placement} that returns null, -the behavior is undefined. +the behavior is undefined\ubdef{expr.new.non.allocating.null}. Otherwise, if the allocation function returns null, initialization shall not be done, the deallocation function shall not be called, and the value of @@ -6527,7 +6531,7 @@ that resulted from a previous non-array \grammarterm{new-expression}, or a pointer to a base class subobject of an object created by such a \grammarterm{new-expression}. -If not, the behavior is undefined. +If not, the behavior is undefined\ubdef{expr.delete.mismatch}. \indextext{array!\idxcode{delete}}% In an array delete expression, the value of the operand of \keyword{delete} may be a null pointer value or a pointer value that resulted from @@ -6539,7 +6543,7 @@ element of the array created by that \grammarterm{new-expression}. Zero-length arrays do not have a first element. \end{footnote} -If not, the behavior is undefined. +If not, the behavior is undefined\ubdef{expr.delete.array.mismatch}. \begin{note} This means that the syntax of the \grammarterm{delete-expression} must match the type of the object allocated by \keyword{new}, not the syntax of the @@ -6560,9 +6564,9 @@ is not a destroying operator delete, the static type shall be a base class of the dynamic type of the object to be deleted and the static type shall -have a virtual destructor or the behavior is undefined. In an array delete +have a virtual destructor or the behavior is undefined\ubdef{expr.delete.dynamic.type.differ}. In an array delete expression, if the dynamic type of the object to be deleted is not similar to -its static type, the behavior is undefined. +its static type, the behavior is undefined\ubdef{expr.delete.dynamic.array.dynamic.type.differ}. \pnum \indextext{type!incomplete}% @@ -7127,7 +7131,7 @@ whose type is not similar to the type of \tcode{E1}, or whose most derived object does not contain the member to which -\tcode{E2} refers, the behavior is undefined. +\tcode{E2} refers, the behavior is undefined\ubdef{expr.mptr.oper.not.contain.member}. The expression \tcode{E1} is sequenced before the expression \tcode{E2}. \pnum @@ -7179,7 +7183,7 @@ operand is an lvalue and an xvalue otherwise. The result of a \tcode{.*} expression whose second operand is a pointer to a member function is a prvalue. If the second operand is the null -member pointer value\iref{conv.mem}, the behavior is undefined. +member pointer value\iref{conv.mem}, the behavior is undefined\ubdef{expr.mptr.oper.member.func.null}. \rSec2[expr.mul]{Multiplicative operators}% \indextext{expression!multiplicative operators}% @@ -7220,7 +7224,7 @@ expression by the second. \indextext{zero!undefined division by}% If the second operand of \tcode{/} or \tcode{\%} is zero, the behavior is -undefined. +undefined\ubdef{expr.mul.div.by.zero}. For integral operands, the \tcode{/} operator yields the algebraic quotient with any fractional part discarded; \begin{footnote} @@ -7228,7 +7232,7 @@ \end{footnote} if the quotient \tcode{a/b} is representable in the type of the result, \tcode{(a/b)*b + a\%b} is equal to \tcode{a}; otherwise, the behavior -of both \tcode{a/b} and \tcode{a\%b} is undefined. +of both \tcode{a/b} and \tcode{a\%b} is undefined\ubdef{expr.mul.representable.type.result}. \rSec2[expr.add]{Additive operators}% \indextext{expression!additive operators}% @@ -7307,7 +7311,7 @@ and the expression \tcode{P - J} points to the (possibly-hypothetical) array element $i - j$ of \tcode{x} if $0 \le i - j \le n$. -\item Otherwise, the behavior is undefined. +\item Otherwise, the behavior is undefined\ubdef{expr.add.out.of.bounds}. \end{itemize} \begin{note} Adding a value other than $0$ or $1$ @@ -7333,13 +7337,13 @@ of type \tcode{std::ptrdiff_t}, the behavior is undefined\iref{expr.pre}. \end{note} -\item Otherwise, the behavior is undefined. +\item Otherwise, the behavior is undefined\ubdef{expr.add.sub.diff.pointers}. \end{itemize} \pnum For addition or subtraction, if the expressions \tcode{P} or \tcode{Q} have type ``pointer to \cv{}~\tcode{T}'', where \tcode{T} and the array element type -are not similar\iref{conv.qual}, the behavior is undefined. +are not similar\iref{conv.qual}, the behavior is undefined\ubdef{expr.add.not.similar}. \begin{example} \begin{codeblock} int arr[5] = {1, 2, 3, 4, 5}; @@ -7376,7 +7380,7 @@ promotions are performed. The type of the result is that of the promoted left operand. \indextext{left shift!undefined}% -The behavior is undefined if the right operand is negative, or greater +The behavior is undefined\ubdef{expr.shift.neg.and.width} if the right operand is negative, or greater than or equal to the width of the promoted left operand. \pnum @@ -8287,7 +8291,7 @@ If the value being stored in an object is read via another object that overlaps in any way the storage of the first object, then the overlap shall be exact and the two objects shall have the same type, otherwise the behavior is -undefined. +undefined\ubdef{expr.assign.overlap}. \begin{note} This restriction applies to the relationship between the left and right sides of the assignment operation; it is not a diff --git a/source/ifndr.tex b/source/ifndr.tex new file mode 100644 index 0000000000..740a85ef7f --- /dev/null +++ b/source/ifndr.tex @@ -0,0 +1,1061 @@ +%!TEX root = std.tex +\infannex{ifndr}{Ill-formed, no diagnostic required} + +\rSec1[ifndr.general]{General} + +This Annex documents +rules for which no diagnostic is required, +called out in +\ref{intro} through \ref{\lastcorechapter} +using the following phrases: +\begin{itemize} +\item no diagnostic is required +\item no diagnostic required +\item a diagnostic is required only if +\end{itemize} +Each entry contains +a title, +a cross-reference, +a summary of the circumstances, +and code examples. +The code examples are not intended +to exhaustively cover all possible ways of invoking that case. + +\rSec1[ifndr.lex]{\ref{lex}: Lexical conventions} + +\ifndrdescription{lex.name.reserved} + +\pnum +Using an identifier reserved for use by \Cpp{} +is ill-formed, no diagnostic required. + +\pnum +\begin{example} +\begin{codeblock} +int _z; // IFNDR, \tcode{\_z} is reserved because it starts with \tcode{\_} at global scope + +int main() { + int __x; // IFNDR, \tcode{\_\_x} is reserved because it starts with \tcode{\_\_} + int _Y; // IFNDR, \tcode{\_Y} is reserved because it starts with \tcode{\_} followed by a capital letter + int x__y; // IFNDR, \tcode{x\_\_y} is reserved because it contains \tcode{\_\_} +} +\end{codeblock} +\end{example} + +\rSec1[ifndr.basic]{\ref{basic}: Basics} + +\ifndrdescription{basic.link.consistent.types} + +\pnum +Having multiple declarations of the same entity +with different kinds +when those declarations are not reachable from one another +is ill-formed, no diagnostic required. + +\pnum +\begin{example} +\begin{codeblocktu}{Module interface of \tcode{M}} +void g(); // \#1 +void h(); // \#2 +template int j; // \#3 +\end{codeblocktu} +\begin{codeblocktu}{Module interface of \tcode{N}} +int g(); // same entity as \#1, different type +namespace h {} // same entity as \#2, not both namespaces +template int j; // same entity as \#3, non-equivalent template heads +\end{codeblocktu} +\begin{codeblocktu}{Other translation unit} +import M; +import N; + // IFNDR due to the mismatched pairs above +\end{codeblocktu} +\end{example} + +\ifndrdescription{basic.def.odr.minimum.one.def} + +\pnum +Not having a definition +for a function or variable +that is odr-used from a non-discarded statement\iref{stmt.if} +is ill-formed, no diagnostic required. + +\pnum +\begin{example} +\begin{codeblock} +auto f() { + struct A {}; + return A{}; +} +decltype(f()) g(); +auto x = g(); // IFNDR, function \tcode{g} is used but not defined in this translation unit, and cannot + // be defined in any other translation unit because its type does not have linkage +\end{codeblock} +\end{example} + +\ifndrdescription{basic.def.odr.injected.match} + +\pnum +Defining a definable item \tcode{D} +with an injected declaration\iref{expr.const.reflect} +in one translation unit and +a definition in a different translation unit +when \tcode{D} is not attached to a named module +or neither definition is reachable from the other +is ill-formed, no diagnostic required. + +\pnum +\begin{example} +\begin{codeblocktu}{Translation unit \#1} +struct S; +consteval { std::meta::define_aggregate(^^S, {}); } // \#1 +\end{codeblocktu} +\begin{codeblocktu}{Translation unit \#2} +struct S {}; // IFNDR, definition here, injected declaration at \#1 +\end{codeblocktu} +\end{example} + +\ifndrdescription{basic.def.odr.maximum.one.def} + +\pnum +If there are definitions +in different translation units +of a non-inline non-templated function or variable +that are not attached to a named module +or are not reachable from one another, +the program is ill-formed, no diagnostic required. + +\pnum +\begin{example} +\begin{codeblocktu}{Translation unit \#1} +export module M:A; // module partition +void f() {} // \#1 +void g() {} // \#2 +\end{codeblocktu} +\begin{codeblocktu}{Translation unit \#2} +export module M:B; // module partition +void f() {} // IFNDR, \#1 not reachable +\end{codeblocktu} +\begin{codeblocktu}{Translation unit \#3} +export module M; // primary module interface unit +export import :A; +void g(); // error: \#2 is reachable +\end{codeblocktu} +\end{example} + +\ifndrdescription{basic.def.odr.definition.matches} + +\pnum +If there are definitions +in different translation units +of a definable item \tcode{D} +where +\begin{itemize} +\item $D$ is not defined by an injected declaration\iref{expr.const.reflect}, +\item $D$ is not an inline or templated function or variable, and +\item $D$ is not attached to a named module or the declarations are not reachable from one another, +\end{itemize} +that do not satisfy the matching rules described in \ref{basic.def.odr}, +the program is ill-formed, no diagnostic required. + +\pnum +\begin{example} +\begin{codeblocktu}{Translation unit \#1} +inline void f() {} // \#1 +inline void g() {} // \#2 +inline void h() {[]{}();} // \#3 +namespace { int i = 0; } +inline void j() {++i;} // \#4 +\end{codeblocktu} +\begin{codeblocktu}{Translation unit \#2} +inline void f() {} // OK, same as \#1 +inline void g() {;} // IFNDR, different sequence of tokens than \#2 +inline void h() {[]{}();} // IFNDR, closure has different type than \#3 +namespace { int i = 0; } +inline void j() {++i; } // IFNDR, \tcode{i} refers to different entity than in \#4 +\end{codeblocktu} +\end{example} + +\ifndrdescription{basic.def.odr.unnamed.enum.same.type} + +\pnum +Having multiple unnamed enumeration definitions +in the same scope +that have the same first enumerator name +and do not have typedef names for linkage purposes\iref{dcl.enum} +that are not the same enumeration +is ill-formed, no diagnostic required. + +\pnum +\begin{example} +\begin{codeblocktu}{Source file \tcode{"a.h"}} +enum { a }; +\end{codeblocktu} +\begin{codeblocktu}{Source file \tcode{"b.h"}} +enum { a, b }; +\end{codeblocktu} +\begin{codeblocktu}{Source file \tcode{"main.cpp"}} +import "a.h"; +import "b.h"; +auto n = decltype(a)::b; // IFNDR, more than one unnamed enum definition reachable at + // this point but their types are not the same +\end{codeblocktu} +\end{example} + +\ifndrdescription{basic.contract.vastart.contract.predicate} + +\pnum +The use of \tcode{va_start}\iref{cstdarg.syn} +within the predicate of a contract assertion +is ill-formed, no diagnostic required; + +\pnum +\begin{example} +\begin{codeblock} +void f(...) +{ + va_list args; + contract_assert((va_start(const_cast(args)), true)) // IFNDR +} +\end{codeblock} +\end{example} + +\ifndrdescription{basic.contract.handler.replacing.nonreplaceable} + +\pnum +On platforms where +the contract-violation handler +is not replaceable\iref{dcl.fct.def.replace} +a function declaration which could be such a replacement function +is ill-formed, no diagnostic required. + +\pnum +\begin{example} +\begin{codeblock} +#include +void handle_contract_violation(const std::contracts::contract_violation& violation); + // IFNDR, if contract-violation handler is not replaceable +\end{codeblock} +\end{example} + +\ifndrdescription{class.member.lookup.name.refers.diff.decl} + +\pnum +A name $N$ used in a class $S$ +referring to a different declaration +when resolved in its context +than when re-evaluated in the completed scope of $S$ +is ill-formed, no diagnostic required. + +\pnum +\begin{example} +\begin{codeblock} +struct foo {}; + +struct bar { + foo *m_foo; + + foo *foo() { + return m_foo; + } // IFNDR, \tcode{foo} now refers to member function \tcode{foo()} while previously referred to \tcode{struct} \tcode{foo} +}; +\end{codeblock} +\end{example} +\begin{example} +\begin{codeblock} +struct B { + static int f(); +}; + +struct D : public B { + using B::f; + int g(decltype(f()) x) { + return 0; + } // IFNDR, \tcode{decltype(f())} will refer to \tcode{B::f()} here but if + // moved to the end of \tcode{D} it would refer to \tcode{D::f()} + static float f(); +}; + +int main() { + D d; + + return d.g(0); +} +\end{codeblock} +\end{example} + +\rSec1[ifndr.expr]{\ref{expr}: Expressions} + +\ifndrdescription{expr.prim.req.always.sub.fail} + +\pnum +If the substitution of template arguments into a \grammarterm{requirement} +would always result in a substitution failure, the program is ill-formed; no diagnostic required. + +\pnum +\begin{example} +\begin{codeblock} +template concept C = requires { + new int[-(int)sizeof(T)]; // IFNDR, the size of the allocation is required to be greater + // than zero but can never be +}; +\end{codeblock} +\end{example} + +\rSec1[ifndr.stmt]{\ref{stmt}: Statements} + +\ifndrdescription{stmt.ambig.bound.diff.parse} + +\pnum +If, during +parsing, a name in a template parameter is bound differently than it would be bound during a trial parse, +the program is ill-formed. No diagnostic is required. + +\pnum +\begin{example} +\begin{codeblock} +template struct A { const static int a = 20; }; + +template <> struct A<100> { using a = char; }; + +const int x = 10; + +int main() { + using T = const int; + T(x) + (100), (y)(A::a); // IFNDR, during trial parse the template parameter \tcode{x} is bound to + // the global \tcode{x} later during parsing the template parameter \tcode{x} + // is bound to the local \tcode{x} declared on the same line +} +\end{codeblock} +\end{example} + +\rSec1[ifndr.dcl]{\ref{dcl}: Declarations} + +\ifndrdescription{dcl.constinit.specifier.not.reachable} + +\pnum +If the initializing declaration +of a variable without the \tcode{constinit} specifier +has the \tcode{constinit} specifier +applied to declarations that are not reachable from +that initializing declaration, +the program is ill-formed, no diagnostic required. + +\pnum +\begin{example} +\begin{codeblocktu}{Translation unit \#1} +int x = 5; // initializing declaration of \tcode{x} +\end{codeblocktu} +\begin{codeblocktu}{Translation unit \#2} +extern constinit int x; // IFNDR, not reachable from initializing declaration of \tcode{x} +\end{codeblocktu} +\end{example} + +\ifndrdescription{dcl.inline.missing.on.definition} + +\pnum +If a function or variable +with external or module linkage +is declared inline +but there is no inline declaration +reachable from the end of some definition domain +the program is ill-formed, no diagnostic required. + +\pnum +\begin{example} +\begin{codeblocktu}{Translation unit \#1} +inline int f(); +\end{codeblocktu} +\begin{codeblocktu}{Translation unit \#2} +int f() { return 17; } + // IFNDR, end of definition domain but no inline declaration of \tcode{f} is reachable. +\end{codeblocktu} +\end{example} + +\ifndrdescription{dcl.fct.default.inline.same.defaults} + +\pnum +If the accumulated set of default arguments +for a given inline function +with definitions in multiple translation units +is different at the end +of different translation units, +the program is ill-formed, no diagnostic required. + +\pnum +\begin{example} +\begin{codeblocktu}{Translation unit \#1} +inline int f(int x, int y = 2); +inline int f(int x = 1, int y); + // IFNDR, default arguments of \tcode{f} are \tcode{1} and \tcode{2} +\end{codeblocktu} +\begin{codeblocktu}{Translation unit \#2} +inline int f(int x = 3, int y = 4); + // IFNDR, default arguments of \tcode{f} are \tcode{3} and \tcode{4} +\end{codeblocktu} +\end{example} + +\ifndrdescription{dcl.contract.func.mismatched.contract.specifiers} + +\pnum +If two different first declarations of a function +(which must therefore not be reachable from one another) +do not have equivalent function contract specifiers +the program is ill-formed, no diagnostic required. + +\pnum +\begin{example} +\begin{codeblocktu}{Translation unit \#1} +int f(int x) pre(x >= 0); // IFNDR, \tcode{pre} present, not present in + // the other first declaration of \tcode{f} +int g(int x) pre(x == 0); // IFNDR, \tcode{pre} differs from the other first declaration of \tcode{g} +int h(int x) pre(x <= 0); // OK, \tcode{pre} equivalent to \tcode{pre} on the other first declaration of \tcode{h} +\end{codeblocktu} +\begin{codeblocktu}{Translation unit \#2} +int f(int x); // IFNDR, \tcode{pre} not present, present in + // the other first declaration of \tcode{f} +int g(int x) pre(x != 0); // IFNDR, \tcode{pre} differs from the other first declaration of \tcode{g} +int h(int y) pre(y <= 0); // OK, \tcode{pre} equivalent to \tcode{pre} on the other first declaration of \tcode{h} +\end{codeblocktu} +\end{example} + +\ifndrdescription{dcl.fct.def.replace.bad.replacement} + +\pnum +A declaration of a replaceable function +that is inline, +not attached to the global module, +does not have \Cpp{} language linkage, +does not have the required return type, +or is not a valid redeclaration of the +corresponding declaration in a standard library header (if there is one) +then the program is ill-formed, no diagnostic required. + +\pnum +\begin{example} +\begin{codeblock} +extern "C" // IFNDR, wrong language linkage +inline // IFNDR, inline +int // IFNDR, wrong return type +handle_contract_violation(const std::contracts::contract_violation&) {} + +void* operator new(decltype(sizeof(0))) noexcept; // IFNDR, mismatched exception specification to + // declaration in \tcode{} +\end{codeblock} +\end{example} + +\ifndrdescription{dcl.link.mismatched.language.linkage} + +\pnum +If two declarations of an entity +do not have the same language linkage +and neither is reachable from the other +the program is ill-formed, no diagnostic required. + +\pnum +\begin{example} +\begin{codeblocktu}{Translation unit \#1} +extern "C" { void f(); } +\end{codeblocktu} +\begin{codeblocktu}{Translation unit \#2} +extern "C++" { void f(); } // IFNDR, different language linkage +\end{codeblocktu} +\end{example} + +\ifndrdescription{dcl.align.diff.translation.units} + +\pnum +No diagnostic is required if declarations of an entity have different \grammarterm{alignment-specifier}s in different +translation units. + +\pnum +\begin{example} +\begin{codeblocktu}{Translation unit \#1} +struct S { int x; } s, *p = &s; +\end{codeblocktu} +\begin{codeblocktu}{Translation unit \#2} +struct alignas(16) S; // IFNDR, definition of \tcode{S} lacks alignment +extern S* p; +\end{codeblocktu} +\end{example} + +\ifndrdescription{dcl.attr.indet.mismatched.declarations} + +\pnum +If two first declarations of a function +declare a function parameter with +mismatched uses of the \tcode{indeterminate} attribute, +the program is ill-formed, no diagnostic required. + +\pnum +\begin{example} +\begin{codeblocktu}{Translation unit \#1} +int h(int x [[indeterminate]]); // IFNDR, mismatched \tcode{[[indeterminate]]} to other first declaration of \tcode{h} +\end{codeblocktu} +\begin{codeblocktu}{Translation unit \#2} +int h(int x); // IFNDR, mismatched \tcode{[[indeterminate]]} to other first declaration of \tcode{h} +\end{codeblocktu} +\end{example} + +\ifndrdescription{dcl.attr.noreturn.trans.unit.mismatch} + +\pnum +No diagnostic is required if a function is declared +in one translation unit with the \tcode{noreturn} attribute +but has declarations in other translation units +without the attribute. + +\pnum +\begin{example} +\begin{codeblocktu}{Translation unit \#1} +[[noreturn]] void f() {} +\end{codeblocktu} +\begin{codeblocktu}{Translation unit \#2} +void f(); // IFNDR, declared without \tcode{noreturn} +\end{codeblocktu} +\end{example} + +\rSec1[ifndr.module]{\ref{module}: Modules} + +\ifndrdescription{module.unit.reserved.identifiers} + +\pnum +Specifying a \grammarterm{module-name} +beginning with an identifier +consisting of \tcode{std} followed by zero or more digits, +or containing a reserved identifier\iref{lex.token} +in a \grammarterm{module-declaration} +is ill-formed, no diagnostic required. + +\pnum +\begin{example} +\begin{codeblock} +module std; // IFNDR, \tcode{std} is not allowed at the beginning +module module; // IFNDR, \tcode{module} is a reserved identifier +module std0; // IFNDR, \tcode{std} followed by digits is not allowed at the beginning +export module _Test; // IFNDR, \tcode{_Test} is a reserved identifier +export module te__st; // IFNDR, \tcode{te__st} is a reserved identifier +\end{codeblock} +\end{example} + +\ifndrdescription{module.unit.named.module.no.partition} + +\pnum +Having multiple primary module interface units +for a named module +is ill-formed, no diagnostic required. + +\pnum +\begin{example} +\begin{codeblock} +module A; +export import :Internals; // IFNDR, module partition not allowed +\end{codeblock} +\end{example} + +\ifndrdescription{module.unit.unexported.module.partition} + +\pnum +If a module partition of a module +that is a module interface unit +but is not directly or indirectly exported +by the primary module interface unit\iref{module.import}, +the program is ill-formed, no diagnostic required. + +\pnum +\begin{example} +\begin{codeblocktu}{Translation unit \#1} +export module M; // primary module interface unit +export import :A; +\end{codeblocktu} +\begin{codeblocktu}{Translation unit \#2} +export module M:A; // OK, directly exported by \tcode{M} +export import :B; +\end{codeblocktu} +\begin{codeblocktu}{Translation unit \#3} +export module M:B; // OK, indirectly exported by \tcode{M} +\end{codeblocktu} +\begin{codeblocktu}{Translation unit \#4} +export module M:C; // IFNDR, not directly or indirectly exported by \tcode{M} +\end{codeblocktu} +\end{example} + +\ifndrdescription{module.private.frag.other.module.units} + +\pnum +If a module has a private module fragment +and there is another module unit of that module, +the program is ill-formed, no diagnostic required. + +\pnum +\begin{example} +\begin{codeblocktu}{Translation unit \#1} +export module M; +module :private; // private module fragment +\end{codeblocktu} +\begin{codeblocktu}{Translation unit \#2} +module M:A; // IFNDR, partition of \tcode{M} with private module fragment +\end{codeblocktu} +\end{example} + +\rSec1[ifndr.class]{\ref{class}: Classes} + +\ifndrdescription{class.base.init.delegate.itself} + +\pnum +If a constructor delegates to itself directly or indirectly, +the program is ill-formed, no diagnostic required + +\pnum +\begin{example} +\begin{codeblock} +struct C { + C( int ) { } // \#1: non-delegating constructor + C(): C(42) { } // \#2: delegates to \#1 + C( char c ) : C(42.0) { } // \#3: IFNDR due to recursion with \#4 + C( double d ) : C('a') { } // \#4: IFNDR due to recursion with \#3 +}; +\end{codeblock} +\end{example} + +\ifndrdescription{class.virtual.pure.or.defined} + +\pnum +If a virtual function that is not pure +has no definition, +the program is ill-formed, no diagnostic required. + +\pnum +\begin{example} +\begin{codeblock} +class A { + virtual void f(); +}; + +int main() { + A a; // IFNDR, virtual function that is not pure but has no definition +} +\end{codeblock} +\end{example} + +\rSec1[ifndr.over]{\ref{over}: Overloading} + +\ifndrdescription{over.literal.reserved} + +\pnum +Some literal suffix identifiers are +reserved for future standardization. A declaration whose literal-operator-id uses such a literal +suffix identifier is ill-formed, no diagnostic required. + +\pnum +\begin{example} +\begin{codeblock} +float operator ""E(const char*); // IFNDR, reserved literal suffix +double operator"" _Bq(long double); // IFNDR, uses the reserved identifier \tcode{_Bq} +\end{codeblock} +\end{example} + +\rSec1[ifndr.temp]{\ref{temp}: Templates} + +\ifndrdescription{temp.pre.reach.def} + +\pnum +A definition of a function template, +member function of a class template, +variable template, +or static data member of a class template +that is not reachable from +the end of every definition domain\iref{basic.def.odr} +in which it is implicitly instantiated\iref{temp.inst} +and whose corresponding specialization +is not explicitly instantiated\iref{temp.explicit} in some translation unit +is ill-formed, no diagnostic required. + +\pnum +\begin{example} +\begin{codeblocktu}{Source file \tcode{"a.h"}} +template +void f(); +\end{codeblocktu} +\begin{codeblocktu}{Source file \tcode{"a.cpp"}} +#include "a.h" +int main() { + f(); // IFNDR, function template implicitly instantiated but not reachable definition +} +\end{codeblocktu} +\end{example} + +\ifndrdescription{temp.arg.template.sat.constraints} + +\pnum +Any partial specializations\iref{temp.spec.partial} +associated with the primary template are considered when a specialization +based on the template template-parameter is instantiated. +If a specialization is not reachable +from the point of instantiation, +and it would have been selected had it been reachable, +the program is ill-formed, no diagnostic required. + +\pnum +\begin{example} +\begin{codeblock} +template struct A { + int x; +}; + +template class V> struct C { + V y; + V z; +}; + +C c; + +// IFNDR, specialization is not reachable from point of instantiation above and it would have +// been selected if it had +template struct A { + long x; +}; +\end{codeblock} +\end{example} + +\ifndrdescription{temp.constr.atomic.equiv.but.not.equiv} + +\pnum +If the validity or meaning of the program +depends on whether two atomic constraints are equivalent, +and they are functionally equivalent but not equivalent, +the program is ill-formed, no diagnostic required. + +\pnum +\begin{example} +\begin{codeblock} +template concept Add1 = true; +template void f2() +requires Add1<2 * N>; +template int f2() +requires Add1 && true; +void h2() { +f2<0>(); // IFNDR, requires determination of subsumption between atomic constraints + // that are functionally equivalent but not equivalent +} +\end{codeblock} +\end{example} + +\ifndrdescription{temp.constr.atomic.sat.result.diff} + +\pnum +If, at different points in the program, +the satisfaction result is different +for identical atomic constraints and template arguments, +the program is ill-formed, no diagnostic required. + +\pnum +\begin{example} +\begin{codeblock} +template +concept Complete = sizeof(T) == sizeof(T); + +struct A; +static_assert(!Complete); // \#1 +struct A {}; +static_assert(Complete); // IFNDR, satisfaction result differs from point \#1 +\end{codeblock} +\end{example} + +\ifndrdescription{temp.constr.normal.invalid} + +\pnum +If during constraint normalization +any such substitution results in an invalid type or expression, +the program is ill-formed; no diagnostic is required + +\pnum +\begin{example} +\begin{codeblock} +template concept A = T::value || true; +template concept B = A; +template concept C = B; // IFNDR, it would form the invalid type \tcode{V\&*} in the + // parameter mapping +\end{codeblock} +\end{example} + +\ifndrdescription{temp.spec.partial.general.partial.reachable} + +\pnum +If a partial specialization +is not reachable from a use of a template specialization +that would make use of that partial specialization +as the result of an implicit or explicit instantiation, +the program is ill-formed, no diagnostic required. + +\pnum +\begin{example} +\begin{codeblock} +template class X{ +public: + void foo(){}; +}; + +template class X; // IFNDR, explicit instantiation and partial specialization is not reachable + +template class X{ +public: + void baz(); +}; +\end{codeblock} +\end{example} + +\ifndrdescription{temp.over.link.equiv.not.equiv} + +\pnum +If the validity or meaning of the program +depends on whether two constructs are equivalent, +and they are functionally equivalent but not equivalent, +the program is ill-formed, no diagnostic required. + +\pnum +\begin{example} +\begin{codeblock} +template +struct A{}; + +// IFNDR, the following declarations are functionally equivalent but not equivalent +template void f(A, A); +template void f(A, A); +\end{codeblock} +\end{example} + +\ifndrdescription{temp.res.general.default.but.not.found} + +\pnum +If the validity or meaning of the program +would be changed by considering +a default argument or default template argument +introduced in a declaration that is reachable +from the point of instantiation of a specialization\iref{temp.point} +but is not found by lookup for the specialization, +the program is ill-formed, no diagnostic required. + +\pnum +\begin{example} +\begin{codeblock} +void f(long); // \#1 +void f(int, int); // \#2 +template void g(T t) { f(t); } +void f(int, int = 0); // \#3 +void h() { g(0); } // IFNDR, selects \#3 here but selects \#1 using lookup for \tcode{g} +\end{codeblock} +\end{example} + +\ifndrdescription{temp.point.diff.pt.diff.meaning} + +\pnum +A specialization for a class template has +at most one point of instantiation within a translation unit. +A specialization for any template +may have points of instantiation in multiple translation units. +If two different points of instantiation +give a template specialization +different meanings according to the one-definition rule\iref{basic.def.odr}, +the program is ill-formed, no diagnostic required. + +\pnum +\begin{example} +\begin{codeblocktu}{Source file \tcode{"a.h"}} +#include + +template +struct is_complete : std::false_type {}; + +template +struct is_complete> : std::true_type {}; +\end{codeblocktu} +\begin{codeblocktu}{Source file \tcode{"a.cpp"}} +#include "a.h" +struct X; +static_assert(!is_complete::value); +\end{codeblocktu} +\begin{codeblocktu}{Source file \tcode{"b.cpp"}} +#include "a.h" +struct X { }; +static_assert(is_complete::value); +\end{codeblocktu} +\end{example} + +\ifndrdescription{temp.dep.candidate.different.lookup.different} + +\pnum +If considering all function declarations +with external linkage +in the associated namespaces in all translations +would make a dependent call\iref{temp.dep} ill-formed +or find a better match, +the program is ill-formed, no diagnostic required. + +\pnum +\begin{example} +\begin{codeblocktu}{Translation unit \#1} +namespace A { + struct S {}; + void f(S&, long x, int y); // \#3 + void g(S&, int x); // \#4 +} +\end{codeblocktu} + +\begin{codeblocktu}{Translation unit \#2} +namespace A { + struct S {}; + void f(S&, int x, long y); // \#5 + void g(S&, long x); // \#6 +} +template +void h(T& t) +{ + f(t, 1, 1); // Selects \#5 in \tcode{h}, would be ambiguous call if all declarations were considered. + g(t, 1); // Selects \#6 in \tcode{h}, would select \#4 if all declarations were considered. +} +\end{codeblocktu} +\end{example} + +\ifndrdescription{temp.explicit.decl.implicit.inst} + +\pnum +If an entity that is the subject of +an explicit instantiation declaration +and that is also used +in a way that would otherwise cause an implicit +instantiation\iref{temp.inst} +in the translation unit +is not the subject of +an explicit instantiation definition somewhere in the program +the program is ill-formed, no diagnostic required. + +\pnum +\begin{example} +\begin{codeblock} +// Explicit instantiation declaration +extern template class std::vector; + +int main() { + std::cout << std::vector().size(); // IFNDR, implicit instantiation but no explicit + // instantiation definition +} +\end{codeblock} +\end{example} + +\ifndrdescription{temp.expl.spec.unreachable.declaration} + +\pnum +If an implicit instantiation of a template would occur +and there is an unreachable explicit specialization +that would have matched, +the program is ill-formed, no diagnostic required. + +\pnum +\begin{example} +\begin{codeblocktu}{Source file \tcode{"a.h"}} +template struct S {}; +\end{codeblocktu} +\begin{codeblocktu}{Translation unit \#2} +#include "a.h" +template <> struct S { int oops; }; // \#1 +\end{codeblocktu} +\begin{codeblocktu}{Translation unit \#3} +#include "a.h" +S s; // IFNDR, \#1 is not reachable but would have matched +\end{codeblocktu} +\end{example} + +\ifndrdescription{temp.expl.spec.missing.definition} + +\pnum +If an explicit specialization of a template is +declared but there is no definition provided +for that specialization, +the program is ill-formed, no diagnostic required. + +\pnum +\begin{example} +\begin{codeblock} +template int f(T&&) { return 0; } +template <> int f(int&&); +int j = f(1); // IFNDR, odr-use of \tcode{f} with no definition +\end{codeblock} +\end{example} + +\ifndrdescription{temp.deduct.general.diff.order} + +\pnum +If substitution +into different declarations +of the same function template +would cause template instantiations to occur +in a different order or not at all, +the program is ill-formed; no diagnostic required. + +\pnum +\begin{example} +\begin{codeblock} +template struct A { using X = typename T::X; }; +template typename T::X h(typename A::X); // \#1 +template auto h(typename A::X) -> typename T::X; // redeclaration \#2 +template void h(...) { } + +void x() { + h(0); // Substituting into \#1 forms an invalid type from \tcode{T::X} and does + // not attempt to instantiate \tcode{A}. + // Substituting into \#2 instantiates \tcode{A}, which is ill-formed. +} +\end{codeblock} +\end{example} + +\rSec1[ifndr.cpp]{\ref{cpp}: Preprocessing directives} + +\ifndrdescription{cpp.cond.defined.after.macro} + +\pnum +If the expansion of a macro produces the preprocessing token \tcode{defined} +the program is ill-formed, no diagnostic required. + +\pnum +\begin{example} +\begin{codeblock} +#define A defined +#if A // IFNDR, \tcode{defined} is generated by macro replacement in controlling expression +#endif +\end{codeblock} +\end{example} + +\ifndrdescription{cpp.cond.defined.malformed} + +\pnum +If the \tcode{defined} unary operator is used when it +does not match +one of the specified grammatical forms, +the program is ill-formed, no diagnostic required. + +\pnum +\begin{example} +\begin{codeblock} +#define A +#define B A) +#if defined ( B // IFNDR, unary operator \tcode{defined} did not match valid form before replacement +#endif +\end{codeblock} +\end{example} + +\ifndrdescription{cpp.include.malformed.headername} + +\pnum +If the \grammarterm{header-name-tokens} after +an \tcode{include} directive +cannot be formed into a \grammarterm{header-name} +(with imp\-lementation-defined treatment of whitespace), +the program is ill-formed, no diagnostic required. + +\pnum +\begin{example} +\begin{codeblock} +#include `` // IFNDR, does not match one of the two allowable forms +\end{codeblock} +\end{example} diff --git a/source/lex.tex b/source/lex.tex index bfd55be51d..399c5df5b9 100644 --- a/source/lex.tex +++ b/source/lex.tex @@ -999,7 +999,7 @@ appearing as a \grammarterm{token} or \grammarterm{preprocessing-token} are reserved for use by \Cpp{} implementations and shall -not be used otherwise; no diagnostic is required. +not be used otherwise; no diagnostic is required \ifndrdef{lex.name.reserved}. \begin{itemize} \item Each identifier that contains a double underscore diff --git a/source/macros.tex b/source/macros.tex index c15173a266..4a7c866ba7 100644 --- a/source/macros.tex +++ b/source/macros.tex @@ -801,3 +801,45 @@ } \newcommand{\defncontext}[1]{\textlangle#1\textrangle} \newnoteenvironment{defnote}{Note \arabic{defnote} to entry}{end note} + +%%---------------------------------- +%% UB and IFNDR sections for annexes +%%---------------------------------- +%% Undefined behavior and ill-formed, no diagnostic required +% Use in main body of text to mark undefined behavior +\newcommand{\ubdef}[1]{\nolinebreak[3] (\label{ubx:#1}\ref{ub:#1})} + +% Use in Annex for sections describing each individual IFNDR +\newcommand{\ubdescription}[1]{% +\let\oldcontentsline\addcontentsline% +\let\addcontentsline\nocontentsline% +\ifcase\value{SectionDepth} + \let\s=\section + \or\let\s=\subsection + \or\let\s=\subsubsection + \or\let\s=\paragraph + \or\let\s=\subparagraph + \fi% +\s[#1]{\hfill[ub:#1]}\vspace{-.3\onelineskip}\label{ub:#1} % +\textbf{Specified in:}\space\ref{ubx:#1}% +\let\addcontentsline\oldcontentsline% +\vspace{-\parskip}% +} + +% Use in main body of text to mark IFNDR +\newcommand{\ifndrdef}[1]{\nolinebreak[3] (\label{ifndrx:#1}\ref{ifndr:#1})} +\newcommand{\ifndrdescription}[1]{% +\let\oldcontentsline\addcontentsline% +\let\addcontentsline\nocontentsline% +\ifcase\value{SectionDepth} + \let\s=\section + \or\let\s=\subsection + \or\let\s=\subsubsection + \or\let\s=\paragraph + \or\let\s=\subparagraph + \fi% +\s[#1]{\hfill[ifndr:#1]}\vspace{-.3\onelineskip}\label{ifndr:#1} % +\textbf{Specified in:}\space\ref{ifndrx:#1}% +\let\addcontentsline\oldcontentsline% +\vspace{-\parskip}% +} diff --git a/source/modules.tex b/source/modules.tex index 4db5625672..9ecef29d77 100644 --- a/source/modules.tex +++ b/source/modules.tex @@ -39,7 +39,7 @@ consisting of \tcode{std} followed by zero or more \grammarterm{digit}{s} or containing a reserved identifier\iref{lex.name} are reserved and shall not be specified in a \grammarterm{module-declaration}; -no diagnostic is required. +no diagnostic is required\ifndrdef{module.unit.reserved.identifiers}. If any \grammarterm{identifier} in a reserved \grammarterm{module-name} is a reserved identifier, the module name is reserved for use by \Cpp{} implementations; @@ -54,7 +54,7 @@ A named module shall contain exactly one module interface unit with no \grammarterm{module-partition}, known as the \defn{primary module interface unit} of the module; -no diagnostic is required. +no diagnostic is required\ifndrdef{module.unit.named.module.no.partition}. \pnum A \defn{module partition} is @@ -66,7 +66,8 @@ that are module interface units shall be directly or indirectly exported by the primary module interface unit\iref{module.import}. -No diagnostic is required for a violation of these rules. +No diagnostic is required +for a violation of these rules\ifndrdef{module.unit.unexported.module.partition}. \begin{note} Module partitions can be imported only by other module units in the same module. @@ -810,7 +811,7 @@ in a primary module interface unit\iref{module.unit}. A module unit with a \grammarterm{private-module-fragment} shall be the only module unit of its module; -no diagnostic is required. +no diagnostic is required\ifndrdef{module.private.frag.other.module.units}. \pnum \begin{note} diff --git a/source/overloading.tex b/source/overloading.tex index 52883f84dd..0a0bfbd5fb 100644 --- a/source/overloading.tex +++ b/source/overloading.tex @@ -4168,7 +4168,7 @@ The first form of \grammarterm{literal-operator-id} is deprecated\iref{depr.lit}. Some literal suffix identifiers are reserved for future standardization; see~\ref{usrlit.suffix}. A declaration whose \grammarterm{literal-operator-id} uses -such a literal suffix identifier is ill-formed, no diagnostic required. +such a literal suffix identifier is ill-formed, no diagnostic required\ifndrdef{over.literal.reserved}. \pnum A declaration whose \grammarterm{declarator-id} is a diff --git a/source/preprocessor.tex b/source/preprocessor.tex index 0bfb7f194f..c05a798b76 100644 --- a/source/preprocessor.tex +++ b/source/preprocessor.tex @@ -527,12 +527,12 @@ as a macro\iref{cpp.replace.general}, the program is ill-formed. If the preprocessing token \tcode{defined} -is generated as a result of this replacement process +is generated as a result of this replacement process\ifndrdef{cpp.cond.defined.after.macro} or use of the \tcode{defined} unary operator does not match one of the two specified forms prior to macro replacement, -the program is ill-formed, no diagnostic required. +the program is ill-formed, no diagnostic required\ifndrdef{cpp.cond.defined.malformed}. \pnum After all replacements due to macro expansion and @@ -757,7 +757,8 @@ is \impldef{treatment of whitespace when processing a \tcode{\#include} directive}. If the attempt succeeds, the directive with the so-formed \grammarterm{header-name} is processed as specified for the previous form. -Otherwise, the program is ill-formed, no diagnostic required. +Otherwise, the program is +ill-formed, no diagnostic required\ifndrdef{cpp.include.malformed.headername}. \begin{note} Adjacent \grammarterm{string-literal}s are not concatenated into a single \grammarterm{string-literal} diff --git a/source/statements.tex b/source/statements.tex index fa6944b172..3742c06e8a 100644 --- a/source/statements.tex +++ b/source/statements.tex @@ -1130,7 +1130,7 @@ equivalent to a \tcode{return} with no operand. Otherwise, flowing off the end of a function that is neither \tcode{main}\iref{basic.start.main} nor a coroutine\iref{dcl.fct.def.coroutine} -results in undefined behavior. +results in undefined behavior\ubdef{stmt.return.flow.off}. \pnum The copy-initialization of the result of the call is sequenced before the @@ -1226,7 +1226,7 @@ flowing off the end of a coroutine's \grammarterm{function-body} is equivalent to a \keyword{co_return} with no operand; otherwise flowing off the end of a coroutine's \grammarterm{function-body} -results in undefined behavior. +results in undefined behavior\ubdef{stmt.return.coroutine.flow.off}. \rSec2[stmt.goto]{The \keyword{goto} statement}% \indextext{statement!\idxcode{goto}} @@ -1366,7 +1366,7 @@ \end{note} If control re-enters the declaration recursively while -the variable is being initialized, the behavior is undefined. +the variable is being initialized, the behavior is undefined\ubdef{stmt.dcl.local.static.init.recursive}. \begin{example} \begin{codeblock} int foo(int i) { @@ -1458,7 +1458,7 @@ that a name in a template argument is bound to (part of) the declaration being parsed, the program is ill-formed. -No diagnostic is required. +No diagnostic is required\ifndrdef{stmt.ambig.bound.diff.parse}. \begin{example} \begin{codeblock} struct T1 { diff --git a/source/std.tex b/source/std.tex index ab1d082396..7180e68e55 100644 --- a/source/std.tex +++ b/source/std.tex @@ -166,6 +166,8 @@ \include{limits} \include{compatibility} \include{future} +\include{ub} +\include{ifndr} \include{uax31} %%-------------------------------------------------- diff --git a/source/templates.tex b/source/templates.tex index 369755465d..de6fd2c460 100644 --- a/source/templates.tex +++ b/source/templates.tex @@ -249,7 +249,7 @@ shall be reachable from the end of every definition domain\iref{basic.def.odr} in which it is implicitly instantiated\iref{temp.inst} unless the corresponding specialization is explicitly instantiated\iref{temp.explicit} -in some translation unit; no diagnostic is required. +in some translation unit; no diagnostic is required\ifndrdef{temp.pre.reach.def}. \rSec1[temp.param]{Template parameters} @@ -1514,7 +1514,7 @@ specialization based on the template template parameter is instantiated. If a specialization is not reachable from the point of instantiation, and it would have been selected had it been reachable, the program is ill-formed, -no diagnostic required. +no diagnostic required\ifndrdef{temp.arg.template.sat.constraints}. \begin{example} \begin{codeblock} template class A { // primary template @@ -1871,7 +1871,7 @@ if the validity or meaning of the program depends on whether two constructs are equivalent, and they are functionally equivalent but not equivalent, -the program is ill-formed, no diagnostic required. +the program is ill-formed, no diagnostic required\ifndrdef{temp.constr.atomic.equiv.but.not.equiv}. \begin{example} \begin{codeblock} template void f2() @@ -1902,7 +1902,7 @@ results in \tcode{true}. If, at different points in the program, the satisfaction result is different for identical atomic constraints and template arguments, -the program is ill-formed, no diagnostic required. +the program is ill-formed, no diagnostic required\ifndrdef{temp.constr.atomic.sat.result.diff}. \begin{example} \begin{codeblock} template concept C = @@ -2255,7 +2255,7 @@ after substituting in \tcode{E$_i$} the respective $i^\text{th}$ concept argument of each \tcode{P$_k$}. If any such substitution results in an invalid type or expression, - the program is ill-formed; no diagnostic is required. + the program is ill-formed; no diagnostic is required\ifndrdef{temp.constr.normal.invalid}. \item Otherwise, the normal form of \tcode{F} is @@ -3739,7 +3739,7 @@ that template. A partial specialization shall be reachable from any use of a template specialization that would make use of the partial specialization as the result of -an implicit or explicit instantiation; no diagnostic is required. +an implicit or explicit instantiation; no diagnostic is required\ifndrdef{temp.spec.partial.general.partial.reachable}. \pnum Two partial specialization declarations declare the same entity @@ -4349,7 +4349,7 @@ If the validity or meaning of the program depends on whether two constructs are equivalent, and they are functionally equivalent but not equivalent, the program is ill-formed, -no diagnostic required. +no diagnostic required\ifndrdef{temp.over.link.equiv.not.equiv}. Furthermore, if two declarations $A$ and $B$ of function templates \begin{itemize} \item @@ -4915,7 +4915,7 @@ introduced in a declaration that is reachable from the point of instantiation of a specialization\iref{temp.point} but is not found by lookup for the specialization, -the program is ill-formed, no diagnostic required. +the program is ill-formed, no diagnostic required\ifndrdef{temp.res.general.default.but.not.found}. \begin{bnf} \nontermdef{typename-specifier}\br @@ -6261,7 +6261,7 @@ translation units. If two different points of instantiation give a template specialization different meanings according to the one-definition rule\iref{basic.def.odr}, -the program is ill-formed, no diagnostic required. +the program is ill-formed, no diagnostic required\ifndrdef{temp.point.diff.pt.diff.meaning}. \pnum For the \grammarterm{compound-statement} @@ -6281,7 +6281,7 @@ introduced in the associated namespaces in all translation units, not just considering those declarations found in the template definition and template instantiation contexts\iref{basic.lookup.argdep}, -then the program is ill-formed, no diagnostic required. +then the program is ill-formed, no diagnostic required\ifndrdef{temp.dep.candidate.different.lookup.different}. \pnum \begin{example} @@ -6939,7 +6939,7 @@ There is an \impldef{maximum depth of recursive template instantiations} quantity that specifies the limit on the total depth of recursive instantiations\iref{implimits}, which could involve more than one template. -The result of an infinite recursion in instantiation is undefined. +The result of an infinite recursion in instantiation is undefined\ubdef{temp.inst.inf.recursion}. \begin{example} \begin{codeblock} template class X { @@ -7168,7 +7168,7 @@ in a way that would otherwise cause an implicit instantiation\iref{temp.inst} in the translation unit shall be the subject of an explicit instantiation definition somewhere in the -program; otherwise the program is ill-formed, no diagnostic required. +program; otherwise the program is ill-formed, no diagnostic required\ifndrdef{temp.explicit.decl.implicit.inst}. \begin{note} This rule does apply to inline functions even though an explicit instantiation declaration of such an entity has no other normative @@ -7370,11 +7370,11 @@ every use of that specialization that would cause an implicit instantiation to take place, in every translation unit in which such a use occurs; -no diagnostic is required. +no diagnostic is required\ifndrdef{temp.expl.spec.unreachable.declaration}. If the program does not provide a definition for an explicit specialization and either the specialization is used in a way that would cause an implicit instantiation to take place or the member is a virtual member function, -the program is ill-formed, no diagnostic required. +the program is ill-formed, no diagnostic required\ifndrdef{temp.expl.spec.missing.definition}. An implicit instantiation is never generated for an explicit specialization that is declared but not defined. \begin{example} @@ -8011,7 +8011,7 @@ a condition that causes deduction to fail is encountered. If substitution into different declarations of the same function template would cause template instantiations to occur in a different order or not at all, -the program is ill-formed; no diagnostic required. +the program is ill-formed; no diagnostic required\ifndrdef{temp.deduct.general.diff.order}. \begin{note} The equivalent substitution in exception specifications\iref{except.spec} diff --git a/source/ub.tex b/source/ub.tex new file mode 100644 index 0000000000..4f82faf686 --- /dev/null +++ b/source/ub.tex @@ -0,0 +1,1976 @@ +%!TEX root = std.tex +\infannex{ub}{Core undefined behavior} + +\rSec1[ub.general]{General} + +This Annex documents +undefined behavior explicitly called out in +\ref{intro} through \ref{\lastcorechapter} +using the following phrases: +\begin{itemize} +\item the behavior of the program is undefined +\item has undefined behavior +\item results in undefined behavior +\item the behavior is undefined +\item have undefined behavior +\item is undefined +\item result has undefined behavior +\end{itemize} +Undefined behavior that is implicit is not covered by this annex. +Each entry contains +a title, +a cross-reference, +a summary of the circumstances, +and code examples. +The code examples are not intended +to exhaustively cover all possible ways of invoking that case. + +\rSec1[ub.basic]{\ref{basic}: Basics} + +\ubdescription{intro.object.implicit.create} + +\pnum +For each +operation that is specified as implicitly creating objects, that operation implicitly creates and starts the +lifetime of zero or more objects of implicit-lifetime types\iref{basic.types} in its specified region of storage if doing so +would result in the program having defined behavior. If no such set of objects would give the program defined +behavior, the behavior of the program is undefined. + +\pnum +\begin{example} +\begin{codeblock} +void f() +{ + void *p = malloc(sizeof(int) + sizeof(float)); + *reinterpret_cast(p) = 0; + *reinterpret_cast(p) = 0.0f; // undefined behavior, cannot create + // both \tcode{int} and \tcode{float} in same place +} +\end{codeblock} +\end{example} + +\ubdescription{intro.object.implicit.pointer} + +\pnum +After implicitly creating objects within a specified region of storage, +some operations are described as producing a pointer to a +suitable created object\iref{basic.types}. +These operations select one of the implicitly-created objects +whose address is the address of the start of the region of storage, +and produce a pointer value that points to that object, +if that value would result in the program having defined behavior. +If no such pointer value would give the program defined behavior, +the behavior of the program is undefined. + +\pnum +\begin{example} +\begin{codeblock} +#include +struct X { + int a, b; + ~X() = delete; // deleted destructor makes \tcode{X} a non-implicit-lifetime class +}; + +X* make_x() { + // The call to \tcode{std::malloc} cannot implicitly create an object of type \tcode{X} + // because \tcode{X} is not an implicit-lifetime class. + X* p = (X*)std::malloc(sizeof(struct X)); + p->a = 1; // undefined behavior, no set of objects give us defined behavior + return p; +} +\end{codeblock} +\end{example} +\begin{example} +\begin{codeblock} +#include + +struct X { + int a; +}; + +struct Y { + int x; +}; + +Y* make_y() { + X* p1 = (X*)std::malloc(sizeof(struct X)); // Object \#1 + p1->a = 1; + Y* p2 = (Y*)p1; + p2->x = 2; // undefined behavior, \tcode{p2} points to an in-lifetime object of type \tcode{X}, + // which is not similar to \tcode{Y}\iref{expr.ref}. + return p2; +} +\end{codeblock} +\end{example} + +\ubdescription{basic.align.object.alignment} + +\pnum +All instances of a type must be created in storage that meets the alignment +requirement of that type. + +\pnum +\begin{example} +\begin{codeblock} +struct alignas(4) S {}; + +void make_misaligned() +{ + alignas(S) char s[sizeof(S) + 1]; + new (&s+1) S(); // undefined behavior, \tcode{\&s+1} will yield a pointer to + // a \tcode{char} which is $1$ byte away from an address with + // an alignment of $4$ and so cannot have an alignment of $4$. +} +\end{codeblock} +\end{example} + +\ubdescription{lifetime.outside.pointer.delete} + +\pnum +For a pointer pointing to an object outside of its lifetime, behavior is +undefined if the object will be or was of a class type with a non-trivial destructor +and the pointer is used as the operand of a \grammarterm{delete-expression}. + +\pnum +\begin{example} +\begin{codeblock} +struct S { + float f = 0; + ~S() {} +}; + +void f() { + S* p = new S; + p->~S(); + delete p; // undefined behavior, operand of \tcode{delete}, lifetime has ended and \tcode{S} + // has a non-trivial destructor +} +\end{codeblock} +\end{example} + +\ubdescription{lifetime.outside.pointer.member} + +\pnum +For a pointer pointing to an object outside of its lifetime, behavior is +undefined if the pointer is used to access a non-static data member or call a +non-static member function of the object. + +\pnum +\begin{example} +\begin{codeblock} +struct S { + float f = 0; +}; + +float f() { + S s; + S* p = &s; + s.~S(); + return p->f; // undefined behavior, accessing non-static data member after + // end of lifetime +} +\end{codeblock} +\end{example} + +\ubdescription{lifetime.outside.pointer.virtual} + +\pnum +For a pointer pointing to an object outside of its lifetime, behavior is +undefined if the pointer is implicitly converted\iref{conv.ptr} to a pointer +to a virtual base class (or base class of a virtual base class). + +\pnum +\begin{example} +\begin{codeblock} +struct B {}; +struct D : virtual B {}; +void f() { + D d; + D* p = &d; + d.~D(); + B* b = p; // undefined behavior +} +\end{codeblock} +\end{example} + +\ubdescription{lifetime.outside.pointer.dynamic.cast} + +\pnum +For a pointer pointing to an object outside of its lifetime, behavior is +undefined if the pointer is used as the operand of a +\tcode{dynamic_cast}\iref{expr.dynamic.cast}. + +\pnum +\begin{example} +\begin{codeblock} +struct B { virtual ~B() = default; }; +struct D : B {}; +void f() +{ + D d; + B* bp = &d; + d.~D(); + D* dp = dynamic_cast(bp); // undefined behavior +} +\end{codeblock} +\end{example} + +\ubdescription{lifetime.outside.glvalue.access} + +\pnum +Behavior is undefined if a glvalue referring to an object outside of its +lifetime is used to access the object. + +\pnum +\begin{example} +\begin{codeblock} +void f() { + int x = int{10}; + using T = int; + x.~T(); + int y = x; // undefined behavior, glvalue used to access the + // object after the lifetime has ended +} +\end{codeblock} +\end{example} + +\ubdescription{lifetime.outside.glvalue.member} + +\pnum +Behavior is undefined if a glvalue referring to an object outside of its +lifetime is used to call a non-static member function of the object. + +\pnum +\begin{example} +\begin{codeblock} +struct A { + void f() {} +}; + +void f() { + A a; + a.~A(); + a.f(); // undefined behavior, glvalue used to access a non-static member + // function after the lifetime has ended +} +\end{codeblock} +\end{example} + +\ubdescription{lifetime.outside.glvalue.virtual} + +\pnum +Behavior is undefined if a glvalue referring to an object outside of its +lifetime is bound to a reference to a virtual base class. + +\pnum +\begin{example} +\begin{codeblock} +struct B {}; +struct D : virtual B { +}; + +void f() { + D d; + d.~D(); + B& b = d; // undefined behavior +} +\end{codeblock} +\end{example} + +\ubdescription{lifetime.outside.glvalue.dynamic.cast} + +\pnum +Behavior is undefined if a glvalue referring to an object outside of its +lifetime is used as the operand of a +\keyword{dynamic_cast} or as the operand of \keyword{typeid}. + +\pnum +\begin{example} +\begin{codeblock} +struct B { virtual ~B(); }; +struct D : virtual B {}; + +void f() { + D d; + B& br = d; + d.~D(); + D& dr = dynamic_cast(br); // undefined behavior +} +\end{codeblock} +\end{example} + +\ubdescription{original.type.implicit.destructor} + +\pnum +The behavior is undefined if +a non-trivial implicit destructor call +occurs when the type of the object inhabiting the associated storage +is not the original type associated with that storage. + +\pnum +\begin{example} +\begin{codeblock} +class T {}; + +struct B { + ~B(); +}; + +void h() { + B b; + new (&b) T; +} // undefined behavior at block exit +\end{codeblock} +\end{example} + +\ubdescription{creating.within.const.complete.obj} + +\pnum +Creating a new object within the storage that a const complete object with static, thread, or automatic +storage duration occupies, or within the storage that such a const object used to occupy before its lifetime +ended, results in undefined behavior + +\pnum +\begin{example} +\begin{codeblock} +struct B { + B(); + ~B(); +}; + +const B b; + +void h() { + b.~B(); + new (const_cast(&b)) const B; // undefined behavior +} +\end{codeblock} +\end{example} + +\ubdescription{basic.indet.value} + +\pnum +When the result of an evaluation is +an indeterminate value +(but not just an erroneous value) +the behavior is undefined. + +\pnum +\begin{example} +\begin{codeblock} +void g() { + int x [[ indeterminate ]]; + int y = x; // undefined behavior +} +\end{codeblock} +\end{example} + +\ubdescription{basic.stc.alloc.dealloc.constraint} + +\pnum +If the behavior of an allocation or deallocation function does not satisfy the semantic constraints +specified +in~\ref{basic.stc.dynamic.allocation} and~\ref{basic.stc.dynamic.deallocation}. +the behavior is undefined. + +\pnum +\begin{example} +\begin{codeblock} +#include +void* operator new(std::size_t sz) { + if (sz == 0) + return nullptr; // undefined behavior, should return non-null pointer + + return std::malloc(1); // undefined behavior, if successful should return allocation with + // length in bytes is at least as large as the requested size +} +void operator delete(void* ptr) noexcept { + throw 0; // undefined behavior, terminates by throwing an exception +} +\end{codeblock} +\end{example} + +\ubdescription{basic.stc.alloc.dealloc.throw} + +\pnum +If a call to a deallocation function +terminates by throwing an exception +the behavior is undefined. +\pnum +\begin{example} +\begin{codeblock} +struct X { + void operator delete(void*) noexcept(false) { throw "oops"; } +}; +void f() +{ + X* x = new X(); + delete x; // undefined behavior +} +\end{codeblock} +\end{example} + +\ubdescription{basic.stc.alloc.zero.dereference} + +\pnum +The pointer returned when invoking an allocation function with a size of zero +cannot be dereferenced. + +\pnum +\begin{example} +\begin{codeblock} +void test() +{ + char* c = static_cast(operator new(0z)); + c[0] = 'X'; // undefined behavior +} +\end{codeblock} +\end{example} + +\ubdescription{basic.compound.invalid.pointer} + +\pnum +Indirection or +the invocation of a deallocation function +with a pointer value referencing storage +that has been freed +has undefined behavior. +(Most other uses of such a pointer have +implementation-defined behavior.) + +\pnum +\begin{example} +\begin{codeblock} +void f() +{ + int *x = new int{5}; + delete x; + int y = *x; // undefined behavior + delete x; // undefined behavior +} +\end{codeblock} +\end{example} + +\ubdescription{intro.execution.unsequenced.modification} + +\pnum +If a side effect on a +memory location\iref{intro.memory} is unsequenced relative to either another side effect on the same memory location or +a value computation using the value of any object in the same memory location, and they are not potentially +concurrent\iref{intro.multithread}, the behavior is undefined. + +\pnum +\begin{example} +\begin{codeblock} +void g(int i) { + i = 7, i++, i++; // \tcode{i} becomes 9 + + i = i++ + 1; // the value of \tcode{i} is incremented + i = i++ + i; // undefined behavior + i = i + 1; // the value of \tcode{i} is incremented +} +\end{codeblock} +\end{example} + +\ubdescription{intro.races.data} + +\pnum +The execution of a program contains a data race if it contains two potentially concurrent conflicting actions, +at least one of which is not atomic, and neither happens before the other, except for the special case for +signal handlers described in~\ref{intro.races}. Any such data race results in undefined behavior. + +\pnum +\begin{example} +\begin{codeblock} +int count = 0; +auto f = [&] { count++; }; +std::thread t1{f}, t2{f}, t3{f}; +// undefined behavior \tcode{t1}, \tcode{t2} and \tcode{t3} have a data race on access of variable \tcode{count} +\end{codeblock} +\end{example} + +\ubdescription{intro.progress.stops} + +\pnum +The behavior is undefined if a thread of execution that has not terminated stops +making execution steps. + +\pnum +\begin{example} +\begin{codeblock} +bool stop() { return false; } + +void busy_wait_thread() { + while (!stop()); // undefined behavior, thread makes no progress but the loop +} // is not trivial because \tcode{stop()} is not a constant expression + +int main() { + std::thread t(busy_wait_thread); + t.join(); +} +\end{codeblock} +\end{example} + +\ubdescription{basic.start.main.exit.during.destruction} + +\pnum +If \tcode{std::exit} is called to +end a program during the destruction of an object with static or thread storage duration, the program has +undefined behavior. + +\pnum +\begin{example} +\begin{codeblock} +#include + +struct Exiter { + ~Exiter() { std::exit(0); } +}; + +Exiter ex; + +int main() {} +// undefined behavior when destructor of static variable \tcode{ex} is called it will call \tcode{std::exit} +\end{codeblock} +\end{example} + +\ubdescription{basic.start.term.use.after.destruction} + +\pnum +If a function contains a block-scope object of static or thread storage duration that has been destroyed and the +function is called during the destruction of an object with static or thread storage duration, the program has +undefined behavior if the flow of control passes through the definition of the previously destroyed block-scope +object. Likewise, the behavior is undefined if the block-scope object is used indirectly (i.e., through a pointer) +after its destruction. + +\pnum +\begin{example} +\begin{codeblock} +struct A {}; +void f() { + static A a; +} + +struct B { + B() { f(); } +}; +struct C { + ~C() { f(); } +}; + +C c; +B b; // call to \tcode{f()} in constructor begins lifetime of \tcode{a} + +int main() {} +// undefined behavior, static objects are destructed in reverse order, in this case \tcode{a} then \tcode{b} and +// finally \tcode{c}. When the destructor of \tcode{c} is called, it calls \tcode{f()} which passes through the definition of +// previously destroyed block-scope object +\end{codeblock} +\end{example} + +\rSec1[ub.expr]{\ref{expr}: Expressions} + +\ubdescription{expr.expr.eval} + +\pnum +If during the evaluation of an expression, the result is not mathematically defined or not in the range of +representable values for its type, the behavior is undefined. + +\pnum +\begin{example} +\begin{codeblock} +#include +int main() { + // Assuming $32$-bit \tcode{int} the range of values are: $-2,147,483,648$ to $2,147,483,647$ + int x1 = std::numeric_limits::max() + 1; + // undefined behavior, $2,147,483,647 + 1$ is not representable as an int + int x2 = std::numeric_limits::min() / -1; + // undefined behavior, $-2,147,483,648 / -1$ is not representable as an int +} +\end{codeblock} +\end{example} + +\ubdescription{expr.basic.lvalue.strict.aliasing.violation} + +\pnum +If a program attempts to access\iref{defns.access} the stored value of an object +whose dynamic type is $T$ through a glvalue whose type is not +similar\iref{conv.qual} to $T$ (or its corresponding signed or unsigned types) +the behavior is undefined. + +\pnum +\begin{example} +\begin{codeblock} +int foo(float* f, int* i) { + *i = 1; + *f = 0.f; // undefined behavior, glvalue is not similar to \tcode{int} + + return *i; +} + +int main() { + int x = 0; + + x = foo(reinterpret_cast(&x), &x); +} +\end{codeblock} +\end{example} + +\ubdescription{expr.basic.lvalue.union.initialization} + +\pnum +If a program invokes a defaulted copy/move constructor or +defaulted copy/move assignment +operator of a union with an argument that is not an object of a similar type +within its lifetime, the behavior is undefined. + +\pnum +\begin{example} +\begin{codeblock} +union U { int x; }; +void f() +{ + char u[sizeof(U)]; + U o = reinterpret_cast(u); // undefined behavior +} +\end{codeblock} +\end{example} + +\ubdescription{expr.type.reference.lifetime} + +\pnum +Evaluating a reference when an equivalent use of a pointer denoting the same object +would be invalid has undefined behavior. + +\pnum +\begin{example} +\begin{codeblock} +void g() +{ + int* ip = new int(5); + int& i = *ip; + delete ip; + i; // undefined behavior +} +\end{codeblock} +\end{example} + +\ubdescription{conv.lval.valid.representation} + +\pnum +Performing an +lvalue-to-rvalue conversion +on an object whose +value representation +is not valid for its type +has undefined behavior. + +\pnum +\begin{example} +\begin{codeblock} +bool f() { + bool b = true; + char c = 42; + memcpy(&b, &c, 1); + return b; // undefined behavior if \tcode{42} is not a valid value representation for \keyword{bool} +} +\end{codeblock} +\end{example} + +\ubdescription{conv.double.out.of.range} + +\pnum +Converting a floating point value to a type that cannot represent the value is undefined behavior. + +\pnum +\begin{example} +\begin{codeblock} +#include + +int main() { + // Assuming 32-bit int, 32-bit float and 64-bit double + double d2 = std::numeric_limits::max(); + float f = d2; // undefined behavior on systems where the range of representable values + // of \tcode{float} is \tcode{[-max,+max]}; on systems where the range of representable + // values are \tcode{[-inf,+inf]} this would not be undefined behavior + int i = d2; // undefined behavior, the max value of \tcode{double} is not representable as \tcode{int} +} +\end{codeblock} +\end{example} + +\ubdescription{conv.fpint.int.not.represented} + +\pnum +When converting a value of integer or unscoped enumeration type to a +floating-point type, if the value is not representable in the destination type +it is undefined behavior. + +\pnum +\begin{example} +\begin{codeblock} +int main() { + unsigned long long x2 = -1; + float f = x2; // undefined behavior on systems where \tcode{f} does not include + // a representation for infinity and the maximum value for \tcode{float} + // is smaller than the maximum value for \tcode{unsigned long long}. +} +\end{codeblock} +\end{example} + +\ubdescription{conv.fpint.float.not.represented} + +\pnum +When converting a floating-point value to an integer type, if +the value is not representable in the destination type it is undefined behavior. + +\pnum +\begin{example} +\begin{codeblock} +#include + +int main() { + // Assuming $32$-bit \tcode{int} the range of values are: $-2,147,483,648$ to + // $2,147,483,647$ Assuming $32$-bit \tcode{float} and $64$-bit \tcode{double} + double d = (double)std::numeric_limits::max() + 1; + int x1 = d; // undefined behavior $2,147,483,647 + 1$ is not representable as \tcode{int} +} +\end{codeblock} +\end{example} + +\ubdescription{conv.ptr.virtual.base} + +\pnum +Converting +a pointer to a derived class \tcode{D} +to +a pointer to a virtual base class \tcode{B} +that does not point to +a valid object +within its lifetime +has undefined behavior. + +\pnum +\begin{example} +\begin{codeblock} +struct B {}; +struct D : virtual B {}; +void f() +{ + D ds[1]; + B* b = &ds[1]; // undefined behavior +} +\end{codeblock} +\end{example} + +\ubdescription{conv.member.missing.member} + +\pnum +The conversion of +a pointer to a member of a base class +to a pointer to member of a derived class +that could not contain that member +has undefined behavior. + +\pnum +\begin{example} +\begin{codeblock} +struct B {}; +struct D1 : B { int d1; }; +struct D2 : B {}; +void f() +{ + int (D1::*pd1) = &D1::d1; + int (B::*pb) = static_cast(pd1); + int (D2::*pd2) = pb; // undefined behavior +} +\end{codeblock} +\end{example} + +\ubdescription{expr.call.different.type} + +\pnum +Calling a function through an expression whose +function type is not call-compatible with +the function type of the called +function's definition results in undefined behavior. + +\pnum +\begin{example} +\begin{codeblock} +using f_float = int (*)(float); +using f_int = int (*)(int); + +int f1(float) { return 10; } + +int f2(int) { return 20; } + +int main() { + return reinterpret_cast(f1)(20); // undefined behavior, the function type of the expression + // is different from the called functions definition +} +\end{codeblock} +\end{example} + +\ubdescription{expr.ref.member.not.similar} + +\pnum +In a class member access \tcode{E1.E2}, +where \tcode{E2} is a non-static member, +the behavior is undefined +if \tcode{E1} refers to an object +whose type is not similar +to the type of the expression \tcode{E1}. + +\pnum +\begin{example} +\begin{codeblock} +struct A { int i; }; +struct B { int j; }; +A a; +int x = reinterpret_cast(a).j; // undefined behavior +\end{codeblock} +\end{example} + +\ubdescription{expr.dynamic.cast.pointer.lifetime} + +\pnum +Evaluating a \keyword{dynamic_cast} on a non-null pointer that points to +an object (of polymorphic type) of the wrong type or to an object +not within its lifetime has undefined behavior. + +\pnum +\begin{example} +\begin{codeblock} +struct B { virtual ~B(); }; +void f() { + B bs[1]; + B* dp = dynamic_cast(bs+1); // undefined behavior +} +\end{codeblock} +\end{example} + +\ubdescription{expr.dynamic.cast.glvalue.lifetime} + +\pnum +Evaluating a \keyword{dynamic_cast} on a reference that +denotes an object (of polymorphic type) of the wrong type or an object +not within its lifetime has undefined behavior. + +\pnum +\begin{example} +\begin{codeblock} +struct B { virtual ~B(); }; +void f() { + B bs[1]; + B& dr = dynamic_cast(bs[1]); // undefined behavior +} +\end{codeblock} +\end{example} + +\ubdescription{expr.static.cast.base.class} + +\pnum +A glvalue of type \tcode{B} +can be cast to the type ``reference to \tcode{D}'' +if \tcode{B} is a base class of \tcode{D}, +otherwise the behavior is undefined. + +\pnum +\begin{example} +\begin{codeblock} +struct B {}; +struct D1 : B {}; +struct D2 : B {}; + +void f() { + D1 d; + B &b = d; + static_cast(b); // undefined behavior, base class object of type \tcode{D1} not \tcode{D2} +} +\end{codeblock} +\end{example} + +\ubdescription{expr.static.cast.enum.outside.range} + +\pnum +If the enumeration type does not have a fixed underlying +type, the value is unchanged if the original value is within the range of the enumeration values\iref{dcl.enum}, and +otherwise, the behavior is undefined. + +\pnum +\begin{example} +\begin{codeblock} +enum A { e1 = 1, e2 }; + +void f() { + enum A a = static_cast(4); // undefined behavior, $4$ is not within the range of enumeration values +} +\end{codeblock} +\end{example} + +\ubdescription{expr.static.cast.fp.outside.range} + +\pnum +An explicit conversion of a +floating-point value that is outside the range of the +target type has undefined behavior. + +\pnum +\begin{example} +If \tcode{float} does not adhere to \IsoFloatUndated{} +and cannot represent positive infinity, +a sufficiently large \tcode{double} value will be +outside the (finite) range of \tcode{float}. +\begin{codeblock} +void f() { + double d = FLT_MAX; + d *= 16; + float f = static_cast(d); // undefined behavior. +} +\end{codeblock} +\end{example} + +\ubdescription{expr.static.cast.downcast.wrong.derived.type} + +\pnum +Casting from a pointer to a base class to a pointer to a derived class +when there is no enclosing object of that derived class at the +specified location has undefined behavior. + +\pnum +\begin{example} +\begin{codeblock} +struct B {}; +struct D1 : B {}; +struct D2 : B {}; + +void f() { + B *bp = new D1; + static_cast(bp); // undefined behavior, base class object of type \tcode{D1} not \tcode{D2} +} +\end{codeblock} +\end{example} + +\ubdescription{expr.static.cast.does.not.contain.original.member} + +\pnum +A +pointer to member of derived class D +can be cast to +a pointer to member of base class B +(with certain restrictions on cv qualifiers) +as long as B contains the original member, +or is a base or derived class of the class +containing the original member; +otherwise the behavior is undefined. + +\pnum +\begin{example} +\begin{codeblock} +struct A { + char c; +}; + +struct B { + int i; +}; + +struct D : A, B {}; + +int main() { + char D::*p1 = &D::c; + char B::*p2 = static_cast(p1); // undefined behavior, \tcode{B} does not contain the original member \tcode{c} +} +\end{codeblock} +\end{example} + +\ubdescription{expr.unary.dereference} + +\pnum +Dereferencing a pointer that does not point to an object or function +has undefined behavior. + +\pnum +\begin{example} +\begin{codeblock} +int f() +{ + int *p = nullptr; + return *p; // undefined behavior +} +\end{codeblock} +\end{example} + +\ubdescription{expr.new.non.allocating.null} + +\pnum +If the allocation +function is a non-allocating form\iref{new.delete.placement} that returns null, the behavior is undefined. + +\pnum +\begin{example} +\begin{codeblock} +#include +[[nodiscard]] void* operator new(std::size_t size, void* ptr) noexcept { + return nullptr; // undefined behavior, should return non-null pointer +} +\end{codeblock} +\end{example} +\begin{example} +\begin{codeblock} +#include + +struct A { + int x; +}; + +int main() { + char *p = nullptr; + A *a = new (p) A; // undefined behavior, non-allocating new returning nullptr +} +\end{codeblock} +\end{example} + +\ubdescription{expr.delete.mismatch} + +\pnum +Using array delete on the result of a single object new expression is undefined behavior. + +\pnum +\begin{example} +\begin{codeblock} +int* x = new int; +delete[] x; // undefined behavior, allocated using single object new expression +\end{codeblock} +\end{example} + +\ubdescription{expr.delete.array.mismatch} + +\pnum +Using single object delete on the result of an array new expression is undefined behavior. + +\pnum +\begin{example} +\begin{codeblock} +int* x = new int[10]; +delete x; // undefined behavior, allocated using array new expression +\end{codeblock} +\end{example} + +\ubdescription{expr.delete.dynamic.type.differ} + +\pnum +If the static type of the object to be deleted is different from its dynamic +type and the selected deallocation function is not a destroying operator delete, the static type +shall be a base class of the dynamic type of the object to be deleted and the static type shall have a virtual +destructor or the behavior is undefined. + +\pnum +\begin{example} +\begin{codeblock} +struct B { + int a; +}; + +struct D : public B { + int b; +}; + +void f() { + B* b = new D; + delete b; // undefined behavior, no virtual destructor +} +\end{codeblock} +\end{example} + +\ubdescription{expr.delete.dynamic.array.dynamic.type.differ} + +\pnum +In an array delete expression, if the dynamic type of the object to be deleted differs from its static type, the behavior is undefined. + +\pnum +\begin{example} +\begin{codeblock} +struct B { + virtual ~B(); + void operator delete[](void*, std::size_t); +}; + +struct D : B { + void operator delete[](void*, std::size_t); +}; + +void f(int i) { + D* dp = new D[i]; + delete[] dp; // uses \tcode{D::operator delete[](void*, std::size_t)} + B* bp = new D[i]; + delete[] bp; // undefined behavior +} +\end{codeblock} +\end{example} + +\ubdescription{expr.mptr.oper.not.contain.member} + +\pnum +Abbreviating \grammarterm{pm-expression}.*\grammarterm{cast-expression} as \tcode{E1.*E2}, \tcode{E1} is called the object expression. If the dynamic type +of \tcode{E1} does not contain the member to which \tcode{E2} refers, the behavior is undefined. + +\pnum +\begin{example} +\begin{codeblock} +struct B {}; +struct D : B { + int x; +}; + +void f() { + B *b = new B; + D *d = static_cast(b); + int D::*p = &D::x; + (*d).*p = 1; // undefined behavior, dynamic type \tcode{B} does not contain \tcode{x} +} +\end{codeblock} +\end{example} + +\ubdescription{expr.mptr.oper.member.func.null} + +\pnum +If the second operand in a \tcode{.*} expression is the null +member pointer value\iref{conv.mem}, the behavior is undefined. + +\pnum +\begin{example} +\begin{codeblock} +struct S { + int f(); +}; + +void f() { + S cs; + int (S::*pm)() = nullptr; + (cs.*pm)(); // undefined behavior, the second operand is null +} +\end{codeblock} +\end{example} + +\ubdescription{expr.mul.div.by.zero} + +\pnum +Division by zero is undefined behavior. + +\pnum +\begin{example} +\begin{codeblock} +int main() { + int x = 1 / 0; // undefined behavior, division by zero + double d = 1.0 / 0.0; // undefined behavior on systems where the range of + // representable values of \tcode{double} is \tcode{[-max,+max]}, on systems where + // representable values are \tcode{[-inf,+inf]} this would not be undefined behavior +} +\end{codeblock} +\end{example} + +\ubdescription{expr.mul.representable.type.result} + +\pnum +If the +quotient \tcode{a/b} +is representable in the type of the result, +\tcode{(a/b)*b + a\%b} +is equal to \tcode{a}; +otherwise, the behavior of both \tcode{a/b} and \tcode{a\%b} is undefined. + +\pnum +\begin{example} +\begin{codeblock} +#include + +int main() { + int x = std::numeric_limits::min() / -1; + // Assuming \tcode{LP64} $-2,147,483,648$ which when divided by $-1$ + // gives us $2,147,483,648$ which is not representable by \tcode{int} +} +\end{codeblock} +\end{example} + +\ubdescription{expr.add.out.of.bounds} + +\pnum +Creating an out of bounds pointer is undefined behavior. + +\pnum +\begin{example} +\begin{codeblock} +static const int arrs[10]{}; + +int main() { + const int *y = arrs + 11; // undefined behavior, creating an out of bounds pointer +} +\end{codeblock} +\end{example} + +\begin{example} +\begin{codeblock} +static const int arrs[10][10]{}; + +int main() { + const int(*y)[10] = arrs + 11; // undefined behavior, creating an out of bounds pointer. + // We can't treat arrs as-if it was a pointer to $100$ \tcode{int}, +} +\end{codeblock} +\end{example} + +\ubdescription{expr.add.sub.diff.pointers} + +\pnum +Subtracting pointers that are not part of the same array is undefined behavior. + +\pnum +\begin{example} +\begin{codeblock} +#include +void f() { + int x[2]; + int y[2]; + int* p1 = x; + int* p2 = y; + std::ptrdiff_t off = p1 - p2; // undefined behavior, \tcode{p1} and \tcode{p2} point to different arrays +} +\end{codeblock} +\end{example} + +\ubdescription{expr.add.not.similar} + +\pnum +For addition or subtraction of two expressions P and Q, +if P or Q have type ``pointer to cv T'', where T and the array +element type are not similar\iref{conv.rval}, the behavior is undefined. + +\pnum +\begin{example} +\begin{codeblock} +struct S { + int i; +}; + +struct T : S { + double d; +}; + +void f(const S* s, std::size_t count) { + for (const S* end = s + count; s != end; ++s) { + @\commentellip@ + } +} + +int main() { + T test[5]; + f(test, 5); +} +\end{codeblock} +\end{example} + +\ubdescription{expr.shift.neg.and.width} + +\pnum +Shifting by a negative amount or equal or greater than the bit-width of a type is undefined behavior. + +\pnum +\begin{example} +\begin{codeblock} +int y = 1 << -1; // undefined behavior, shift is negative + +static_assert(sizeof(int) == 4 && CHAR_BIT == 8); +int y1 = 1 << 32; // undefined behavior, shift is equal to the bit width of \tcode{int} +int y2 = 1 >> 32; // undefined behavior, shift is equal to the bit width of \tcode{int} +\end{codeblock} +\end{example} + +\ubdescription{expr.assign.overlap} + +\pnum +Overlap in the storage between the source and destination may result in undefined behavior. + +\pnum +\begin{example} +\begin{codeblock} +int x = 1; +char* c = reinterpret_cast(&x); +x = *c; // undefined behavior, source overlaps storage of destination +\end{codeblock} +\end{example} + +\rSec1[ub.stmt]{\ref{stmt}: Statements} + +\ubdescription{stmt.return.flow.off} + +\pnum +Flowing off the end of a function other +than main or a coroutine results in undefined behavior if the return type +is not \cv{}~\keyword{void}. + +\pnum +\begin{example} +\begin{codeblock} +int f(int x) { + if (x) + return 1; + // undefined behavior if \tcode{x} is \tcode{0} +} + +void b() { + int x = f(0); // undefined behavior, using \tcode{0} as an argument will cause \tcode{f(...)} to flow + // off the end without a \tcode{return} statement +} +\end{codeblock} +\end{example} + +\ubdescription{stmt.return.coroutine.flow.off} + +\pnum +Flowing off the end of a coroutine function body +that does not return void +has undefined behavior. + +\pnum +\begin{example} +\begin{codeblock} +#include +#include +#include +#include + +class resumable { + public: + struct promise_type; + using coro_handle = std::coroutine_handle; + resumable(coro_handle handle) : handle_(handle) { assert(handle); } + resumable(resumable&) = delete; + resumable(resumable&&) = delete; + bool resume() { + if (not handle_.done()) + handle_.resume(); + return not handle_.done(); + } + ~resumable() { handle_.destroy(); } + const char* return_val(); + + private: + coro_handle handle_; +}; + +struct resumable::promise_type { + using coro_handle = std::coroutine_handle; + const char* string_; + auto get_return_object() { return coro_handle::from_promise(*this); } + auto initial_suspend() { return std::suspend_always(); } + auto final_suspend() noexcept { return std::suspend_always(); } + void unhandled_exception() { std::terminate(); } + void return_value(const char* string) { string_ = string; } +}; + +const char* resumable::return_val() { + return handle_.promise().string_; +} + +resumable foo() { + std::cout << "Hello" << std::endl; + co_await std::suspend_always(); + // undefined behavior, falling off the end of coroutine that does not return \tcode{void} +} + +int main() { + resumable res = foo(); + while (res.resume()) + ; + std::cout << res.return_val() << std::endl; +} +\end{codeblock} +\end{example} + +\ubdescription{stmt.dcl.local.static.init.recursive} + +\pnum +If control re-enters the declaration recursively while the +variable is being initialized, the behavior is undefined. + +\pnum +\begin{example} +\begin{codeblock} +int foo(int i) { + static int s = foo(2 * i); // undefined behavior, recursive call + return i + 1; +} +\end{codeblock} +\end{example} + +\rSec1[ub.dcl]{\ref{dcl}: Declarations} + +\ubdescription{dcl.type.cv.modify.const.obj} + +\pnum +Any attempt to modify a const object during its lifetime results in +undefined behavior. + +\pnum +\begin{example} +\begin{codeblock} +const int* ciq = new const int(3); // initialized as required +int* iq = const_cast(ciq); // cast required +*iq = 4; // undefined behavior, modifies a \tcode{const} object +\end{codeblock} +\end{example} + +\ubdescription{dcl.type.cv.access.volatile} + +\pnum +If an attempt is made to +access an object defined with a volatile-qualified type through the use of a non-volatile glvalue, the behavior +is undefined + +\pnum +\begin{example} +\begin{codeblock} +volatile int x = 0; +int& y = const_cast(x); +std::cout << y; // undefined behavior, accessing volatile through non-volatile glvalue +\end{codeblock} +\end{example} + +\ubdescription{dcl.ref.incompatible.function} + +\pnum +Initializing a reference to a function +with a value that is a function +that is not call-compatible\iref{expr.call} +with the type of the reference +has undefined behavior. + +\pnum +\begin{example} +\begin{codeblock} +void f(float x); +void (&g)(int) = reinterpret_cast(f); // undefined behavior +\end{codeblock} +\end{example} + +\ubdescription{dcl.ref.incompatible.type} + +\pnum +Initializing a reference to an object +with a value that is not +type-accessible\iref{basic.lval} through +the type of the reference +has undefined behavior. + +\pnum +\begin{example} +\begin{codeblock} +float g; +int& i = reinterpret_cast(g); // undefined behavior +\end{codeblock} +\end{example} + +\ubdescription{dcl.ref.uninitialized.reference} + +\pnum +Evaluating a reference +prior to initializing that +reference has undefined behavior. + +\pnum +\begin{example} +\begin{codeblock} +extern int &ir1; +int i2 = ir1; // undefined behavior, \tcode{ir1} not yet initialized +int i3 = 17; +int &ir1 = i3; +\end{codeblock} +\end{example} + +\ubdescription{dcl.fct.def.coroutine.resume.not.suspended} + +\pnum +Invoking a resumption member function for a coroutine that is not suspended results in undefined behavior. + +\pnum +\begin{example} +\begin{codeblock} +#include + +struct minig { + struct promise_type { + int val; + minig get_return_object() { return {*this}; } + constexpr suspend_always initial_suspend() noexcept { return {}; } + constexpr suspend_always final_suspend() noexcept { return {}; } + constexpr void return_void() noexcept {} + [[noreturn]] void unhandled_exception() noexcept { throw; } + suspend_always yield_value(int v) noexcept { + val = v; + return {}; + } + }; + using HDL = coroutine_handle; + HDL coro; + minig(promise_type& p) : coro(HDL::from_promise(p)) {} + ~minig() { coro.destroy(); } + bool move_next() { + coro.resume(); + return !coro.done(); + } + int current_value() { return coro.promise().val; } +}; + +static minig f(int n) { + for (int i = 0; i < n; ++i) + co_yield i; +} + +int main() { + auto g = f(10); + int sum = 0; + while (g.move_next()) + sum += g.current_value(); + + g.move_next(); // undefined behavior, will call \tcode{coro.resume()} but \tcode{final_suspend} + // has already returned, even though it returned \tcode{suspend_always} + + return sum; +} +\end{codeblock} +\end{example} + +\ubdescription{dcl.fct.def.coroutine.destroy.not.suspended} + +\pnum +Invoking destroy() on a coroutine that is not suspended is undefined behavior. + +\pnum +\begin{example} +\begin{codeblock} +#include + +struct minig { + struct promise_type { + int val; + minig get_return_object() { return {*this}; } + constexpr suspend_always initial_suspend() noexcept { return {}; } + constexpr suspend_always final_suspend() noexcept { return {}; } + constexpr void return_void() noexcept {} + [[noreturn]] void unhandled_exception() { throw; } + suspend_always yield_value(int v) noexcept { + val = v; + return {}; + } + }; + using HDL = coroutine_handle; + HDL coro; + minig(promise_type& p) : coro(HDL::from_promise(p)) {} + ~minig() { coro.destroy(); } + bool move_next() { + coro.resume(); + return !coro.done(); + } + int current_value() { + // this coroutine is not suspended therefore a call to destroy is undefined behavior + coro.destroy(); + return coro.promise().val; + } +}; + +static minig f(int n) { + for (int i = 0; i < n; ++i) + co_yield i; +} + +int main() { + auto g = f(10); + int sum = 0; + while (g.move_next()) + sum += g.current_value(); + + return sum; +} +\end{codeblock} +\end{example} + +\ubdescription{dcl.attr.assume.false} + +\pnum +If an assumption expression would not evaluate to true at the point where it +appears the behavior is undefined. + +\pnum +\begin{example} +\begin{codeblock} +int g(int x) { + [[assume(x >= 0)]]; + return x/32; +} + +int f() { + return g(-10); // undefined behavior, assumption in \tcode{g} is \tcode{false} +} +\end{codeblock} +\end{example} + +\ubdescription{dcl.attr.noreturn.eventually.returns} + +\pnum +If a function f is called where f was previously declared with the \tcode{noreturn} attribute and f eventually returns, +the behavior is undefined. + +\pnum +\begin{example} +\begin{codeblock} +[[noreturn]] void f(int i) { + if (i > 0) + throw "positive"; +} + +int main() { + f(0); // undefined behavior, returns from a \tcode{[[noreturn]]} function +} +\end{codeblock} +\end{example} + +\rSec1[ub.class]{\ref{class}: Classes} + +\ubdescription{class.dtor.no.longer.exists} + +\pnum +Once a destructor is invoked for an object, +the object's lifetime has ended; +the behavior is undefined if the +destructor is invoked for an object whose lifetime has ended. + +\pnum +\begin{example} +\begin{codeblock} +struct A { + ~A() {} +}; + +int main() { + A a; + a.~A(); +} // undefined behavior, lifetime of \tcode{a} already ended before implicit destructor +\end{codeblock} +\end{example} + +\ubdescription{class.abstract.pure.virtual} + +\pnum +Calling a pure virtual function from a constructor or destructor in an abstract class is undefined behavior. + +\pnum +\begin{example} +\begin{codeblock} +struct B { + virtual void f() = 0; + B() { + f(); // undefined behavior, \tcode{f} is pure virtual and we are calling from the constructor + } +}; + +struct D : B { + void f() override; +}; +\end{codeblock} +\end{example} + +\ubdescription{class.base.init.mem.fun} + +\pnum +It is undefined behavior to call a member function before all the \grammarterm{mem-initializer}s for base classes have completed. + +\pnum +\begin{example} +\begin{codeblock} +class A { +public: + A(int); +}; + +class B : public A { + int j; + +public: + int f(); + B() + : A(f()), // undefined behavior, calls member function but base \tcode{A} not yet initialized + j(f()) {} // defined, bases are all initialized +}; + +class C { +public: + C(int); +}; + +class D : public B, C { + int i; + +public: + D() + : C(f()), // undefined behavior, calls member function but base \tcode{C} not yet initialized + i(f()) {} // defined, bases are all initialized +}; +\end{codeblock} +\end{example} + +\ubdescription{class.cdtor.before.ctor} + +\pnum +For an object with a non-trivial constructor, referring to any non-static member or base class of the object +before the constructor begins execution results in undefined behavior. + +\pnum +\begin{example} +\begin{codeblock} +struct W { + int j; +}; +struct X : public virtual W {}; +struct Y { + int *p; + X x; + Y() : p(&x.j) { // undefined behavior, \tcode{x} is not yet constructed + } +}; +\end{codeblock} +\end{example} + +\ubdescription{class.cdtor.after.dtor} + +\pnum +For an object with a non-trivial destructor, +referring to any non-static member or base class of the object +after the destructor finishes execution +has undefined behavior. + +\pnum +\begin{example} +\begin{codeblock} +struct X { + int i; + ~X(); // non-trivial +}; +X& g() +{ + static X x; + return x; +} +void f() +{ + X* px = &g(); + px->~X(); + int j = px->i; // undefined behavior +} +\end{codeblock} +\end{example} + +\ubdescription{class.cdtor.convert.pointer} + +\pnum +When converting a pointer to a base class of an object, +construction must have started and destruction must not have finished otherwise +this is undefined behavior. + +\pnum +\begin{example} +\begin{codeblock} +struct A { }; +struct B : virtual A { }; +struct C : B { }; +struct D : virtual A { D(A*); }; +struct X { X(A*); }; + +struct E : C, D, X { + E() : D(this), // undefined behavior, upcast from \tcode{E*} to \tcode{A*} might use path \tcode{E*} $\rightarrow$ \tcode{D*} $\rightarrow$ \tcode{A*} + // but \tcode{D} is not constructed + + // ``\tcode{D((C*)this)}\!'' would be defined, \tcode{E*} $\rightarrow$ \tcode{C*} is defined because \tcode{E()} has started, + // and \tcode{C*} $\rightarrow$ \tcode{A*} is defined because \tcode{C} is fully constructed + + X(this) {} // defined, upon construction of \tcode{X}, \tcode{C/B/D/A} sublattice is fully constructed +}; +\end{codeblock} +\end{example} + +\ubdescription{class.cdtor.form.pointer} + +\pnum +When forming a pointer to +a direct non-static member of a class, +construction must have started +and destruction must not have finished +otherwise the behavior is undefined. + +\pnum +\begin{example} +\begin{codeblock} +struct A { + int i = 0; +}; +struct B { + int *p; + A a; + B() : p(&a.i) {} // undefined behavior +}; +\end{codeblock} +\end{example} + +\ubdescription{class.cdtor.virtual.not.x} + +\pnum +When a virtual function is called directly or indirectly from a constructor or from a destructor, +including during the construction or destruction of the class's non-static data members, and the object to +which the call applies is the object (call it \tcode{x}) under construction or destruction, the function called is the +final overrider in the constructor's or destructor's class and not one overriding it in a more-derived class. +If the virtual function call uses an explicit class member access\iref{expr.ref} and the object expression refers +to the complete object of \tcode{x} or one of that object's base class subobjects but not \tcode{x} or one of its base class +subobjects, the behavior is undefined. + +\pnum +\begin{example} +\begin{codeblock} +struct V { + virtual void f(); + virtual void g(); +}; + +struct A : virtual V { + virtual void f(); +}; + +struct B : virtual V { + virtual void g(); + B(V *, A *); +}; + +struct D : A, B { + virtual void f(); + virtual void g(); + D() : B((A *)this, this) {} +}; + +B::B(V *v, A *a) { + f(); // calls \tcode{V::f}, not \tcode{A::f} + g(); // calls \tcode{B::g}, not \tcode{D::g} + v->g(); // \tcode{v} is base of \tcode{B}, the call is defined, calls \tcode{B::g} + a->f(); // undefined behavior, \tcode{a}'s type not a base of \tcode{B} +} +\end{codeblock} +\end{example} + +\ubdescription{class.cdtor.typeid} + +\pnum +If the operand of \tcode{typeid} refers to +the object under construction or destruction and the static type of the operand is neither the constructor or +destructor's class nor one of its bases, the behavior is undefined. + +\pnum +\begin{example} +\begin{codeblock} +struct V { + virtual void f(); +}; + +struct A : virtual V {}; +struct B : virtual V { + B(V *, A *); +}; + +struct D : A, B { + D() : B((A *)this, this) {} +}; + +B::B(V *v, A *a) { + typeid(*this); // \tcode{std::type_info} for \tcode{B} + typeid(*v); // defined, \tcode{*v} has type \tcode{V}, a base of \tcode{B} yields \tcode{std::type_info} for \tcode{B} + typeid(*a); // undefined behavior, type \tcode{A} not a base of \tcode{B} +} +\end{codeblock} +\end{example} + +\ubdescription{class.cdtor.dynamic.cast} + +\pnum +If the operand of the +\tcode{dynamic_cast} refers to the object under construction or destruction and the static type of the operand is +not a pointer to or object of the constructor or destructor's own class or one of its bases, the \tcode{dynamic_cast} +results in undefined behavior. + +\pnum +\begin{example} +\begin{codeblock} +struct V { + virtual void f(); +}; + +struct A : virtual V {}; +struct B : virtual V { + B(V *, A *); +}; + +struct D : A, B { + D() : B((A *)this, this) {} +}; + +B::B(V *v, A *a) { + dynamic_cast(v); // defined, \tcode{v} of type \tcode{V*}, \tcode{V} base of \tcode{B} results in \tcode{B*} + dynamic_cast(a); // undefined behavior, \tcode{a} has type \tcode{A*}, \tcode{A} not a base of \tcode{B} +} +\end{codeblock} +\end{example} + +\rSec1[ub.temp]{\ref{temp}: Templates} + +\ubdescription{temp.inst.inf.recursion} + +\pnum +The result of an infinite recursion in template instantiation is undefined. + +\pnum +\begin{example} +\begin{codeblock} +template +class X { + X *p; // OK + X a; // implicit instantiation of \tcode{X} requires + // the implicit instantiation of \tcode{X} which requires + // the implicit instantiation of \tcode{X} which \ldots +}; + +int main() { + X x; // undefined behavior, instantiation will kick off infinite recursion +} +\end{codeblock} +\end{example} + +\rSec1[ub.except]{\ref{except}: Exception handling} + +\ubdescription{except.handle.handler.ctor.dtor} + +\pnum +Referring to any non-static member or base class of an object in the handler for a \grammarterm{function-try-block} of a +constructor or destructor for that object results in undefined behavior. + +\pnum +\begin{example} +\begin{codeblock} +#include + +struct A { + A() try : x(0 ? 1 : throw 1) { + } catch (int) { + std::cout << "y: " << y << std::endl; // undefined behavior, referring to non-static member y in + // the handler of function-try-block + } + int x; + int y = 42; +}; +\end{codeblock} +\end{example} diff --git a/tools/check-output.sh b/tools/check-output.sh index 8b23f65141..280fc1d423 100755 --- a/tools/check-output.sh +++ b/tools/check-output.sh @@ -36,7 +36,7 @@ rm -f tmp.txt # Find bad labels grep newlabel `ls *.aux | grep -v std.aux` | awk -F '{' '{ print $2 }' | - sed 's/}//g' | sed 's/^tab://;s/fig://;s/eq://;s/idx.*\..//' | + sed 's/}//g' | sed 's/^tab://;s/fig://;s/eq://;s/ub://;s/ubx://;s/ifndr://;s/ifndrx://;s/idx.*\..//' | grep -v '^[a-z.0-9]*$' | sed 's/^\(.*\)$/bad label \1/' | fail || failed=1 diff --git a/tools/check-source.sh b/tools/check-source.sh index ffd7c94d47..b473511598 100755 --- a/tools/check-source.sh +++ b/tools/check-source.sh @@ -242,7 +242,7 @@ done | fail 'hanging paragraph' || failed=1 # Subclauses without siblings -for f in $texfiles; do +for f in `ls $texfiles | grep -v ub.tex | grep -v ifndr.tex`; do sed -n '/^\\rSec/{=;p;}' $f | # prefix output with filename and line sed '/^[0-9]\+$/{N;s/\n/:/;}' | sed "s/.*/$f:&/" | From b47434089863fe787588525f8b8fa47ae2c6b3a8 Mon Sep 17 00:00:00 2001 From: jberne4 Date: Mon, 22 Jun 2026 11:00:21 -0400 Subject: [PATCH 2/3] fixup: minor changes and reordering --- source/ub.tex | 57 +++++++++++++++++++++++++-------------------------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/source/ub.tex b/source/ub.tex index 4f82faf686..3cefed08b7 100644 --- a/source/ub.tex +++ b/source/ub.tex @@ -698,41 +698,41 @@ \end{codeblock} \end{example} -\ubdescription{conv.fpint.int.not.represented} +\ubdescription{conv.fpint.float.not.represented} \pnum -When converting a value of integer or unscoped enumeration type to a -floating-point type, if the value is not representable in the destination type -it is undefined behavior. +When converting a floating-point value to an integer type, if +the value is not representable in the destination type it is undefined behavior. \pnum \begin{example} \begin{codeblock} +#include + int main() { - unsigned long long x2 = -1; - float f = x2; // undefined behavior on systems where \tcode{f} does not include - // a representation for infinity and the maximum value for \tcode{float} - // is smaller than the maximum value for \tcode{unsigned long long}. + // Assuming $32$-bit \tcode{int} the range of values are: $-2,147,483,648$ to + // $2,147,483,647$ Assuming $32$-bit \tcode{float} and $64$-bit \tcode{double} + double d = (double)std::numeric_limits::max() + 1; + int x1 = d; // undefined behavior $2,147,483,647 + 1$ is not representable as \tcode{int} } \end{codeblock} \end{example} -\ubdescription{conv.fpint.float.not.represented} +\ubdescription{conv.fpint.int.not.represented} \pnum -When converting a floating-point value to an integer type, if -the value is not representable in the destination type it is undefined behavior. +When converting a value of integer or unscoped enumeration type to a +floating-point type, if the value is not representable in the destination type +it is undefined behavior. \pnum \begin{example} \begin{codeblock} -#include - int main() { - // Assuming $32$-bit \tcode{int} the range of values are: $-2,147,483,648$ to - // $2,147,483,647$ Assuming $32$-bit \tcode{float} and $64$-bit \tcode{double} - double d = (double)std::numeric_limits::max() + 1; - int x1 = d; // undefined behavior $2,147,483,647 + 1$ is not representable as \tcode{int} + unsigned long long x2 = -1; + float f = x2; // undefined behavior on systems where \tcode{f} does not include + // a representation for infinity and the maximum value for \tcode{float} + // is smaller than the maximum value for \tcode{unsigned long long}. } \end{codeblock} \end{example} @@ -853,7 +853,9 @@ \pnum Evaluating a \keyword{dynamic_cast} on a reference that -denotes an object (of polymorphic type) of the wrong type or an object +denotes an object (of polymorphic type) of +a type that is not similar +or an object not within its lifetime has undefined behavior. \pnum @@ -1242,23 +1244,19 @@ \ubdescription{expr.add.not.similar} \pnum -For addition or subtraction of two expressions P and Q, -if P or Q have type ``pointer to cv T'', where T and the array +For addition or subtraction of two expressions $P$ and $Q$, +if $P$ or $Q$ have type ``pointer to cv T'', where T and the array element type are not similar\iref{conv.rval}, the behavior is undefined. \pnum \begin{example} \begin{codeblock} -struct S { - int i; -}; - -struct T : S { - double d; -}; +struct S { int i; }; +struct T : S { double d; }; void f(const S* s, std::size_t count) { for (const S* end = s + count; s != end; ++s) { + // undefined behavior, \tcode{T} is not similar to \tcode{S} but \tcode{s} points to a \tcode{T[]} @\commentellip@ } } @@ -1289,7 +1287,7 @@ \ubdescription{expr.assign.overlap} \pnum -Overlap in the storage between the source and destination may result in undefined behavior. +Overlap in the storage between the source and destination can result in undefined behavior. \pnum \begin{example} @@ -1683,7 +1681,8 @@ \ubdescription{class.base.init.mem.fun} \pnum -It is undefined behavior to call a member function before all the \grammarterm{mem-initializer}s for base classes have completed. +It is undefined behavior to call a member function +before all the \grammarterm{mem-initializer}s for base classes have completed. \pnum \begin{example} From 3e05f5279b72035ffadf5a941cf8ea842c3869d0 Mon Sep 17 00:00:00 2001 From: jberne4 Date: Mon, 22 Jun 2026 11:42:42 -0400 Subject: [PATCH 3/3] fixup: formatting fixes --- source/ifndr.tex | 6 +++--- source/ub.tex | 31 +++++++++++++++++-------------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/source/ifndr.tex b/source/ifndr.tex index 740a85ef7f..fd6bef6295 100644 --- a/source/ifndr.tex +++ b/source/ifndr.tex @@ -150,9 +150,9 @@ of a definable item \tcode{D} where \begin{itemize} -\item $D$ is not defined by an injected declaration\iref{expr.const.reflect}, -\item $D$ is not an inline or templated function or variable, and -\item $D$ is not attached to a named module or the declarations are not reachable from one another, +\item \tcode{D} is not defined by an injected declaration\iref{expr.const.reflect}, +\item \tcode{D} is not an inline or templated function or variable, and +\item \tcode{D} is not attached to a named module or the declarations are not reachable from one another, \end{itemize} that do not satisfy the matching rules described in \ref{basic.def.odr}, the program is ill-formed, no diagnostic required. diff --git a/source/ub.tex b/source/ub.tex index 3cefed08b7..8ac62236b0 100644 --- a/source/ub.tex +++ b/source/ub.tex @@ -581,7 +581,7 @@ \begin{codeblock} #include int main() { - // Assuming $32$-bit \tcode{int} the range of values are: $-2,147,483,648$ to $2,147,483,647$ + // Assuming $32$-bit \tcode{int}, the range of values is: $-2,147,483,648$ to $2,147,483,647$ int x1 = std::numeric_limits::max() + 1; // undefined behavior, $2,147,483,647 + 1$ is not representable as an int int x2 = std::numeric_limits::min() / -1; @@ -692,7 +692,7 @@ double d2 = std::numeric_limits::max(); float f = d2; // undefined behavior on systems where the range of representable values // of \tcode{float} is \tcode{[-max,+max]}; on systems where the range of representable - // values are \tcode{[-inf,+inf]} this would not be undefined behavior + // values is \tcode{[-inf,+inf]} this would not be undefined behavior int i = d2; // undefined behavior, the max value of \tcode{double} is not representable as \tcode{int} } \end{codeblock} @@ -710,7 +710,7 @@ #include int main() { - // Assuming $32$-bit \tcode{int} the range of values are: $-2,147,483,648$ to + // Assuming $32$-bit \tcode{int}, the range of values is: $-2,147,483,648$ to // $2,147,483,647$ Assuming $32$-bit \tcode{float} and $64$-bit \tcode{double} double d = (double)std::numeric_limits::max() + 1; int x1 = d; // undefined behavior $2,147,483,647 + 1$ is not representable as \tcode{int} @@ -854,7 +854,7 @@ \pnum Evaluating a \keyword{dynamic_cast} on a reference that denotes an object (of polymorphic type) of -a type that is not similar +a type that is not similar to the referenced type or an object not within its lifetime has undefined behavior. @@ -957,11 +957,11 @@ \pnum A -pointer to member of derived class D +pointer to member of derived class \tcode{D} can be cast to -a pointer to member of base class B -(with certain restrictions on cv qualifiers) -as long as B contains the original member, +a pointer to member of base class \tcode{B} +(with certain restrictions on \cv{} qualifiers) +as long as \tcode{B} contains the original member, or is a base or derived class of the class containing the original member; otherwise the behavior is undefined. @@ -1167,7 +1167,7 @@ int x = 1 / 0; // undefined behavior, division by zero double d = 1.0 / 0.0; // undefined behavior on systems where the range of // representable values of \tcode{double} is \tcode{[-max,+max]}, on systems where - // representable values are \tcode{[-inf,+inf]} this would not be undefined behavior + // representable values is \tcode{[-inf,+inf]} this would not be undefined behavior } \end{codeblock} \end{example} @@ -1245,7 +1245,7 @@ \pnum For addition or subtraction of two expressions $P$ and $Q$, -if $P$ or $Q$ have type ``pointer to cv T'', where T and the array +if $P$ or $Q$ have type ``pointer to \cv{}~\tcode{T}'', where \tcode{T} and the array element type are not similar\iref{conv.rval}, the behavior is undefined. \pnum @@ -1256,7 +1256,7 @@ void f(const S* s, std::size_t count) { for (const S* end = s + count; s != end; ++s) { - // undefined behavior, \tcode{T} is not similar to \tcode{S} but \tcode{s} points to a \tcode{T[]} + // undefined behavior, \tcode{T} is not similar to \tcode{S} but \tcode{s} points to a \tcode{T[]} @\commentellip@ } } @@ -1304,7 +1304,7 @@ \pnum Flowing off the end of a function other -than main or a coroutine results in undefined behavior if the return type +than \tcode{main} or a coroutine results in undefined behavior if the return type is not \cv{}~\keyword{void}. \pnum @@ -1542,7 +1542,7 @@ \ubdescription{dcl.fct.def.coroutine.destroy.not.suspended} \pnum -Invoking destroy() on a coroutine that is not suspended is undefined behavior. +Invoking \tcode{destroy()} on a coroutine that is not suspended is undefined behavior. \pnum \begin{example} @@ -1616,7 +1616,10 @@ \ubdescription{dcl.attr.noreturn.eventually.returns} \pnum -If a function f is called where f was previously declared with the \tcode{noreturn} attribute and f eventually returns, +If a function \tcode{f} is called +where \tcode{f} was previously declared +with the \tcode{noreturn} attribute and +\tcode{f} eventually returns, the behavior is undefined. \pnum