diff --git a/source/basic.tex b/source/basic.tex index e35bfe841b..66e9786a68 100644 --- a/source/basic.tex +++ b/source/basic.tex @@ -3504,7 +3504,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. @@ -3748,19 +3748,19 @@ 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\ubdef{lifetime.outside.pointer} 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 implicitly converted\iref{conv.ptr} to a pointer - to a virtual base class, or + to a virtual base class\ubdef{lifetime.outside.pointer.virtual}, or \item the pointer is used as the operand of a - \keyword{static_cast}\iref{expr.static.cast}, except when the conversion + \keyword{static_cast}\iref{expr.static.cast}\ubdef{lifetime.outside.pointer.static.cast}, except when the conversion is to pointer to \cv{}~\keyword{void}, or to pointer to \cv{}~\keyword{void} and subsequently to pointer to \cv{}~\keyword{char}, @@ -3768,7 +3768,7 @@ \cv{}~\tcode{std::byte}\iref{cstddef.syn}, 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} @@ -3811,14 +3811,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\ubdef{lifetime.outside.glvalue} 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} diff --git a/source/classes.tex b/source/classes.tex index 0e8bf61c1c..3e28757e0e 100644 --- a/source/classes.tex +++ b/source/classes.tex @@ -6123,7 +6123,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}, @@ -6131,7 +6131,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\ubdef{class.cdtor.convert.or.form.pointer}. +value) results in undefined behavior\ubdef{class.cdtor.form.pointer}. \begin{example} \begin{codeblock} struct A { }; diff --git a/source/expressions.tex b/source/expressions.tex index 15b5270059..711ea76401 100644 --- a/source/expressions.tex +++ b/source/expressions.tex @@ -1019,7 +1019,7 @@ \end{note} If the value being converted is outside the range of values that can be represented, the behavior is undefined. -% \ubdef{conv.fpint.int.not.represented} +\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. @@ -4199,14 +4199,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.\ubdef{expr.dynamic.cast.lifetime} +the behavior is undefined.\ubdef{expr.dynamic.cast.glvalue.lifetime} \pnum If \tcode{T} is ``pointer to \cv{} \keyword{void}'', then the result @@ -6156,7 +6156,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 diff --git a/source/ub.tex b/source/ub.tex index fa3e3f42a3..988cf8e43c 100644 --- a/source/ub.tex +++ b/source/ub.tex @@ -23,6 +23,32 @@ 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)); // undefined behavior, cannot create + // both int and float in same place + int& i = *reinterpret_cast(p); + float& f = *reinterpret_cast(p); +} +\end{codeblock} +\end{example} + + +\pnum +\ubxref{intro.object.implicit.pointer} +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} @@ -87,32 +113,10 @@ \rSec2[ub.basic.life]{Object lifetime} \pnum -\ubxref{lifetime.outside.pointer} \\ -The behavior of some uses of a pointer -pointing to an object outside its lifetime -are not defined: -\begin{itemize} -\item - 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}, -\item - the pointer is used to access a non-static data member or call a - non-static member function of the object, or -\item - the pointer is implicitly converted\iref{conv.ptr} to a pointer - to a virtual base class, or -\item - the pointer is used as the operand of a - \tcode{static_cast}\iref{expr.static.cast}, except when the conversion - is to pointer to \cv{}~\tcode{void}, or to pointer to \cv{}~\tcode{void} - and subsequently to pointer to - \cv{}~\tcode{char}, - \cv{}~\tcode{unsigned char}, or - \cv{}~\tcode{std::byte}\iref{cstddef.syn}, or -\item - the pointer is used as the operand of a - \tcode{dynamic_cast}\iref{expr.dynamic.cast}. -\end{itemize} +\ubxref{lifetime.outside.pointer.delete} +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} @@ -123,78 +127,184 @@ }; float f() { - S s; - S* p1 = &s; - S* p2 = new S; - s.~S(); - p2->~S(); - delete p2; // undefined behavior, operand of delete, lifetime has ended and \tcode{S} - // has a non-trivial destructor - return p1->f; // Undefined behavior, accessing non-static data member after - // end of lifetime + S* p = new S; + p->~S(); + delete p; // undefined behavior, operand of delete, lifetime has ended and \tcode{S} + // has a non-trivial destructor } \end{codeblock} \end{example} + +\pnum +\ubxref{lifetime.outside.pointer.member} +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} -#include -#include - -struct B { - virtual void f(); - void mutate(); - virtual ~B(); +struct S { + float f = 0; }; -struct D1 : B { - void f(); -}; -struct D2 : B { - void f(); -}; +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} + +\pnum +\ubxref{lifetime.outside.pointer.virtual} +For a pointer pointing to an object outside of its lifetime, behavior is +undefined if pointer is implicitly converted\iref{conv.ptr} to a pointer +to a virtual base class. -void B::mutate() { - new (this) D2; // reuses storage and ends the lifetime of *this - f(); // undefined behavior - B *b = this; // OK, this points to valid memory +\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} -void g() { - void* p = std::malloc(sizeof(D1) + sizeof(D2)); - B* pb = new (p) D1; - pb->mutate(); - *pb; // OK, pb points to valid memory - void* q = pb; // OK, pb points to valid memory - pb->f(); // undefined behavior, lifetime of \tcode{*pb} has ended +\pnum +\ubxref{lifetime.outside.pointer.static.cast} +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{static_cast}, except when the conversion +is to pointer to \cv{}~\tcode{void}, or to pointer to \cv{}~\tcode{void} +and subsequently to pointer to +\cv{}~\tcode{char}, +\cv{}~\tcode{unsigned char}, or +\cv{}~\tcode{std::byte}\iref{cstddef.syn}. + +\pnum +\begin{example} +\begin{codeblock} +struct B {}; +struct D : B {}; +void f() +{ + D d; + B* bp = &d; + d.~D(); + void* vp = bp; // OK + D* dp = static_cast(bp); // undefined behavior } \end{codeblock} \end{example} \pnum -\ubxref{lifetime.outside.glvalue} \\ -The behavior of some uses of a glvalue -that refers to an object outside its lifetime -are not defined. +\ubxref{lifetime.outside.pointer.dynamic.cast} +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 A{void f(){} }; +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} +\pnum +\ubxref{lifetime.outside.glvalue.access} +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}; - A a; using T = int; x.~T(); - a.~A(); - a.f(); // undefined behavior, glvalue used to access a - // non-static member function after the lifetime has ended int y = x; // undefined behavior, glvalue used to access the // object after the lifetime has ended } \end{codeblock} \end{example} +\pnum +\ubxref{lifetime.outside.glvalue.member} +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} + +\pnum +\ubxref{lifetime.outside.glvalue.virtual} +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} + +\pnum +\ubxref{lifetime.outside.glvalue.dynamic.cast} +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} \pnum \ubxref{original.type.implicit.destructor} \\ @@ -388,7 +498,7 @@ int count = 0; auto f = [&] { count++; }; std::thread t1{f}, t2{f}, t3{f}; -// Undefined behavior t1, t2 and t3 have a data race on access of variable count +// undefined behavior t1, t2 and t3 have a data race on access of variable count \end{codeblock} \end{example} @@ -602,8 +712,7 @@ \rSec2[ub.conv.fpint]{Floating-integral conversions} \pnum -\ubxref{conv.fpint.float.not.represented} \\ -%\ubxref{conv.fpint.int.not.represented} \\ % in same section +\ubxref{conv.fpint.int.not.represented} \\ When converting a floating-point value to an integer type and vice versa if the value is not representable in the destination type it is undefined behavior. @@ -617,15 +726,28 @@ // 2,147,483,647 Assuming 32-bit float and 64-bit double double d = (double)std::numeric_limits::max() + 1; int x1 = d; // undefined behavior 2,147,483,647 + 1 is not representable as int - - __uint128_t x2 = -1; - float f = x2; // undefined behavior on systems where the range of - // representable values of float is [-max,+max] on system where - // represetable values are [-inf,+inf] this would not be UB } \end{codeblock} \end{example} +\pnum +\ubxref{conv.fpint.float.not.represented} \\ +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() { + __uint128_t x2 = -1; + float f = x2; // undefined behavior on systems where the range of + // representable values of float is [-max,+max] on system where + // represetable values are [-inf,+inf] this would not be UB + } +\end{codeblock} +\end{example} + \rSec2[ub.conv.ptr]{Pointer conversions} \pnum @@ -725,10 +847,29 @@ \rSec2[ub.expr.dynamic.cast]{Dynamic cast} + \pnum -\ubxref{expr.dynamic.cast.lifetime} \\ -Evaluating a \keyword{dynamic_cast} on a non-null pointer or reference that -denotes an object (of polymorphic type) of the wrong type or that is +\ubxref{expr.dynamic.cast.pointer.lifetime} \\ +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} + + +\pnum +\ubxref{expr.dynamic.cast.glvalue.lifetime} \\ +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 @@ -737,7 +878,6 @@ struct B { virtual ~B(); }; void f() { B bs[1]; - B* dp = dynamic_cast(bs+1); // undefined behavior B& dr = dynamic_cast(bs[1]); // undefined behavior } \end{codeblock} @@ -906,13 +1046,25 @@ \pnum \ubxref{expr.delete.mismatch} \\ -Using array delete on the result of a single object new expression and vice versa is undefined behavior. +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 +delete[] x; // undefined behavior, allocated using single object new expression +\end{codeblock} +\end{example} + +\pnum +\ubxref{expr.delete.array.mismatch} \\ +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} @@ -1681,34 +1833,51 @@ \end{codeblock} \end{example} - \pnum -\ubxref{class.cdtor.convert.or.form.pointer} \\ -When converting a pointer to a base class of an object or forming a pointer to a direct non-static -member of a class, construction must have started and destruction must not have finished otherwise +\ubxref{class.cdtor.convert.pointer} \\ +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 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 }; -struct X { - X(A *); +\end{codeblock} +\end{example} + +\pnum +\ubxref{class.cdtor.form.pointer} \\ +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 E : C, D, X { - E() - : D(this), // undefined: upcast from \tcode{E*} to \tcode{A*} might use path E->D->A - // but \tcode{D} is not constructed - // \tcode{D((C*)this)} would be defined: E->C is defined because - // \tcode{E()} has started, and C->A is defined because \tcode{C} is fully - // constructed - X(this) // well-defined: upon construction of \tcode{X}, C/B/D/A sublattice is fully constructed - {} +struct B { + int *p; + A a; + B() : p(&a.i) {} // undefined behavior }; \end{codeblock} \end{example} @@ -1860,7 +2029,7 @@ 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 + std::cout << "y: " << y << std::endl; // undefined behavior, referring to non-static member y in // the handler of function-try-block } int x; @@ -1963,7 +2132,7 @@ #define cat(x, y) x##y void f() { - cat(/, /) // Undefined behavior // is not a valid preprocessing token + cat(/, /) // undefined behavior // is not a valid preprocessing token } \end{codeblock} \end{example}