Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Undefined behavior when accessing inactive union members #57033

Open
safocl opened this issue Feb 13, 2025 · 0 comments
Open

Undefined behavior when accessing inactive union members #57033

safocl opened this issue Feb 13, 2025 · 0 comments

Comments

@safocl
Copy link

safocl commented Feb 13, 2025

Version

main

Platform

any

Subsystem

trace_event

What steps will reproduce the bug?

Standard C++ (Working Draft):

[defns.undefined]

3.65[defns.undefined]undefined behavior
behavior for which this document imposes no requirements
[Note 1: Undefined behavior may be expected when this document omits any explicit definition of behavior or when a program uses an incorrect construct or invalid data. Permissible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message ([defns.diagnostic])), to terminating a translation or execution (with the issuance of a diagnostic message). Many incorrect program constructs do not engender undefined behavior; they are required to be diagnosed. Evaluation of a constant expression ([expr.const]) never exhibits behavior explicitly specified as undefined in [intro] through [cpp]. — end note]

[defns.undefined.runtime]

3.50[defns.undefined.runtime]runtime-undefined behavior
behavior that is undefined except when it occurs during constant evaluation
[Note 1: During constant evaluation,
it is implementation-defined whether runtime-undefined behavior results in the expression being deemed non-constant (as specified in [expr.const]) and
runtime-undefined behavior has no other effect.
— end note]

How often does it reproduce? Is there a required condition?

Standard C++ (Working Draft):

[defns.undefined]

3.65[defns.undefined]undefined behavior
behavior for which this document imposes no requirements
[Note 1: Undefined behavior may be expected when this document omits any explicit definition of behavior or when a program uses an incorrect construct or invalid data. Permissible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message ([defns.diagnostic])), to terminating a translation or execution (with the issuance of a diagnostic message). Many incorrect program constructs do not engender undefined behavior; they are required to be diagnosed. Evaluation of a constant expression ([expr.const]) never exhibits behavior explicitly specified as undefined in [intro] through [cpp]. — end note]

[defns.undefined.runtime]

3.50[defns.undefined.runtime]runtime-undefined behavior
behavior that is undefined except when it occurs during constant evaluation
[Note 1: During constant evaluation,
it is implementation-defined whether runtime-undefined behavior results in the expression being deemed non-constant (as specified in [expr.const]) and
runtime-undefined behavior has no other effect.
— end note]

What is the expected behavior? Why is that the expected behavior?

Correct program without UB.

What do you see instead?

Standard C++ (Working Draft):

[defns.undefined]

3.65[defns.undefined]undefined behavior
behavior for which this document imposes no requirements
[Note 1: Undefined behavior may be expected when this document omits any explicit definition of behavior or when a program uses an incorrect construct or invalid data. Permissible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message ([defns.diagnostic])), to terminating a translation or execution (with the issuance of a diagnostic message). Many incorrect program constructs do not engender undefined behavior; they are required to be diagnosed. Evaluation of a constant expression ([expr.const]) never exhibits behavior explicitly specified as undefined in [intro] through [cpp]. — end note]

[defns.undefined.runtime]

3.50[defns.undefined.runtime]runtime-undefined behavior
behavior that is undefined except when it occurs during constant evaluation
[Note 1: During constant evaluation,
it is implementation-defined whether runtime-undefined behavior results in the expression being deemed non-constant (as specified in [expr.const]) and
runtime-undefined behavior has no other effect.
— end note]

Additional information

Read from the member of the union that wasn't most recently written is undefined behavior.

*value = type_value.as_uint; \

// Simple union to store various types as uint64_t.
union TraceValueUnion {
  bool as_bool;
  uint64_t as_uint;
  int64_t as_int;
  double as_double;
  const void* as_pointer;
  const char* as_string;
};
  static inline void SetTraceValue(actual_type arg, unsigned char* type,    \
                                   uint64_t* value) {                       \
    TraceValueUnion type_value;                                             \
    type_value.union_member = arg;                                          \
    *type = value_type_id;                                                  \
    *value = type_value.as_uint;                                            \ 
                                 //  Access to TraceValueUnion.as_uint, 
                                 //  but either as_double or as_pointer or as_string is active.
  }

Standard C++ (Working Draft):

[basic.life]

except that if the object is a union member or subobject thereof, its lifetime only begins if that union member is the initialized member in the union ([dcl.init.aggr], [class.base.init]), or as described in [class.union], [class.copy.ctor], and [class.copy.assign], and except as described in [allocator.members]. The lifetime of an object o of type T ends when:
-- if T is a non-class type, the object is destroyed, or
-- if T is a class type, the destructor call starts, or
-- the storage which the object occupies is released, or is reused by an object that is not nested within o ([intro.object]).

[class.union#general-2]

In a union, a non-static data member is active if its name refers to an object whose lifetime has begun and has not ended ([basic.life]). At most one of the non-static data members of an object of union type can be active at any time, that is, the value of at most one of the non-static data members can be stored in a union at any time.

[basic.life#8]

Similarly, before the lifetime of an object has started but after the storage which the object will occupy has been allocated or, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, any glvalue that refers to the original object may be used but only in limited ways. For an object under construction or destruction, see [class.cdtor]. Otherwise, such a glvalue refers to allocated storage ([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
-- the glvalue is used to access the object, or
-- the glvalue is used to call a non-static member function of the object, or
-- the glvalue is bound to a reference to a virtual base class ([dcl.init.ref]), or
-- the glvalue is used as the operand of a dynamic_cast ([expr.dynamic.cast]) or as the operand of typeid.

[expr.post#expr.ref-2.sentence-1]

For the first option (dot), [...] if the id-expression names a non-static data member, the first expression shall be a glvalue.

https://en.cppreference.com/w/cpp/language/union :

It is undefined behavior to read from the member of the union that wasn't most recently written.

#include <cstdint>
#include <iostream>
 
union S
{
    std::int32_t n;     // occupies 4 bytes
    std::uint16_t s[2]; // occupies 4 bytes
    std::uint8_t c;     // occupies 1 byte
};                      // the whole union occupies 4 bytes
 
int main()
{
    S s = {0x12345678}; // initializes the first member, s.n is now the active member
    // At this point, reading from s.s or s.c is undefined behavior,
    // but most compilers define it.
    std::cout << std::hex << "s.n = " << s.n << '\n';
 
    s.s[0] = 0x0011; // s.s is now the active member
    // At this point, reading from s.n or s.c is undefined behavior,
    // but most compilers define it.
    std::cout << "s.c is now " << +s.c << '\n' // 11 or 00, depending on platform
              << "s.n is now " << s.n << '\n'; // 12340011 or 00115678
}

[basic.life#8.1] violated.

https://godbolt.org/z/r81ePcqoY

Infos:
https://gitlab.com/libeigen/eigen/-/issues/2898
arduino/ArduinoCore-API#225
llvm/llvm-project#102342 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant