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

MinGW-clang: Converting constructor of class with no-unique-address members overwrites last byte with zeroes #64077

Closed
crtrott opened this issue Jul 24, 2023 · 12 comments
Labels
clang Clang issues not falling into any other category platform:windows

Comments

@crtrott
Copy link
Contributor

crtrott commented Jul 24, 2023

While implementing mdspan I ran into an issue during MinGW CI. I reproduced this standalone on GodBolt: https://godbolt.org/z/KK8aj5bs7

The related change revision is found here: https://reviews.llvm.org/D154367

Shortish version, you have something like this:

template<class T, class M, class A>
class mdspan {
    public:
  mdspan() = default;
  mdspan(const mdspan&) = default;
  mdspan(T* ptr_, M map_, A acc_):ptr(ptr_),map(map_),acc(acc_) {}
  template<class OT, class OM, class OA>
  mdspan(const mdspan<OT, OM, OA>& other):ptr(other.ptr), map(other.map), acc(other.acc) {}

  [[no_unique_address]] T* ptr;
  [[no_unique_address]] M map;
  [[no_unique_address]] A acc;
};

If you now do mdspan<const T, M2, A2>(mdspan<T, M1, A1>); where M1, A1 and A2 are empty, but M2 is not, and A1->A2 happens via a conversion operator in A1, not a converting constructor in A2 the last byte of ptr gets zeroed out.

@crtrott crtrott added clang Clang issues not falling into any other category platform:windows labels Jul 24, 2023
@crtrott crtrott changed the title MinGW-clang: Converting constructor of class with no-unqiue-address members overwrites last byte with zeroes MinGW-clang: Converting constructor of class with no-unique-address members overwrites last byte with zeroes Jul 24, 2023
@cor3ntin
Copy link
Contributor

FIY, this works

[[no_unique_address]] M map;
[[no_unique_address]] A acc;
T* ptr;``

And is probably more what you wanted to express, although that does not mean there isn't a bug

@AaronBallman
Copy link
Collaborator

I'm a bit confused...

T* certainly requires space to store it, so if M also requires space to store it, then the T* and M fields cannot share their storage; they cannot be potentially overlapping subobjects, right?

@crtrott
Copy link
Contributor Author

crtrott commented Jul 24, 2023

Couple things: a) in mdspan ptr is not necessarily a T* it can very well be a class, and in fact it COULD be an empty class if you are naughty enough to exploit every possible way you can exploit customization points in mdspan. For example: accessor could be a a file system access thing, and ptr could be a handle containing a compile time string defining the file - not that I believe anyone should do that, but its possible.

And yeah the failing case on the left hand side T* and M should NOT be overlapping since M is not empty. The one I am constructing from however had an empty M.

@crtrott
Copy link
Contributor Author

crtrott commented Jul 24, 2023

Oh and reordering the arguments here, may very well fix my test case, but it may break another one where I test screwy things the other way around: i.e. I make M have a single size_t member every time (i.e. dextents<size_t, 1>), have an accessor with a member on the left, and an empty accessor in the original thing. It will just shift the problem to another corner case.

@mordante mordante added this to the LLVM 17.0.X Release milestone Jul 25, 2023
@tru
Copy link
Collaborator

tru commented Jul 31, 2023

Is this still something we need to fix for 17.x? I think I saw some other fixes for mdspan.

@mstorsjo
Copy link
Member

Is this still something we need to fix for 17.x? I think I saw some other fixes for mdspan.

I believe so. The thing that was merged was the general implementation (which was merged slightly after the 17.x branch was made, and then backported) - it has some XFAIL for this particular case in mingw configs, which AFAIK still are unresolved.

@crtrott
Copy link
Contributor Author

crtrott commented Aug 4, 2023

Yeah this issue is still relevant. Note that the godbolt reproducer above does not require mdspan. It's just that it surfaces in mdspan testing, when I test legal misuse of our customization points (i.e. I test user provided implementations of customization points, which are idiosyncratic naughty, but legal).

@crtrott
Copy link
Contributor Author

crtrott commented Aug 4, 2023

I commented on the other issue, but basically the godbolt example fails also on PowerPC with Clang 14, and presumably with clang 17 (which I am still in the process of compiling on my power platform). (Minor note: had to remove the concept and span include which were unnecessary anyway).

@mstorsjo
Copy link
Member

mstorsjo commented Aug 5, 2023

This sounds a lot like #64253 - see the very minimal testcase in the comment at #64253 (comment).

@mstorsjo
Copy link
Member

mstorsjo commented Aug 7, 2023

See https://reviews.llvm.org/D157332 for a potential fix for this case.

mstorsjo added a commit that referenced this issue Aug 15, 2023
An empty struct is handled as a struct with a dummy i8, on all targets.

Most targets treat an empty struct return value as essentially
void - but some don't. (Currently, at least x86_64-windows-* and
powerpc64le-* don't treat it as void.)

When intializing a struct with such a no_unique_address member,
make sure we don't write the dummy i8 into the struct where there's
no space allocated for it.

Previously it would clobber the actual valid data of the struct.

Fixes #64253, and
possibly #64077
and #64427 as well.

We should omit the store for any empty record (not only ones
declared with no_unique_address); we can have a situation where a
class doesn't have the no_unique_address attribute, but is embedded
in an outer struct with the no_unique_address attribute - like this:

    struct S {};
    S f();
    struct S2 : public S { S2();};
    S2::S2() : S(f()) {}
    struct S3 { int x; [[no_unique_address]] S2 y; S3(); };
    S3::S3() : x(1), y() {}

Here, the problematic store (which this patch omits) is in
the constructor of S2. In the case of S3, S2 has no valid storage
and aliases x - thus the constructor of S2 should omit the dummy
store.

Differential Revision: https://reviews.llvm.org/D157332
llvmbot pushed a commit to llvm/llvm-project-release-prs that referenced this issue Aug 15, 2023
An empty struct is handled as a struct with a dummy i8, on all targets.

Most targets treat an empty struct return value as essentially
void - but some don't. (Currently, at least x86_64-windows-* and
powerpc64le-* don't treat it as void.)

When intializing a struct with such a no_unique_address member,
make sure we don't write the dummy i8 into the struct where there's
no space allocated for it.

Previously it would clobber the actual valid data of the struct.

Fixes llvm/llvm-project#64253, and
possibly llvm/llvm-project#64077
and llvm/llvm-project#64427 as well.

We should omit the store for any empty record (not only ones
declared with no_unique_address); we can have a situation where a
class doesn't have the no_unique_address attribute, but is embedded
in an outer struct with the no_unique_address attribute - like this:

    struct S {};
    S f();
    struct S2 : public S { S2();};
    S2::S2() : S(f()) {}
    struct S3 { int x; [[no_unique_address]] S2 y; S3(); };
    S3::S3() : x(1), y() {}

Here, the problematic store (which this patch omits) is in
the constructor of S2. In the case of S3, S2 has no valid storage
and aliases x - thus the constructor of S2 should omit the dummy
store.

Differential Revision: https://reviews.llvm.org/D157332

(cherry picked from commit d60c3d0)
tru pushed a commit to llvm/llvm-project-release-prs that referenced this issue Aug 16, 2023
An empty struct is handled as a struct with a dummy i8, on all targets.

Most targets treat an empty struct return value as essentially
void - but some don't. (Currently, at least x86_64-windows-* and
powerpc64le-* don't treat it as void.)

When intializing a struct with such a no_unique_address member,
make sure we don't write the dummy i8 into the struct where there's
no space allocated for it.

Previously it would clobber the actual valid data of the struct.

Fixes llvm/llvm-project#64253, and
possibly llvm/llvm-project#64077
and llvm/llvm-project#64427 as well.

We should omit the store for any empty record (not only ones
declared with no_unique_address); we can have a situation where a
class doesn't have the no_unique_address attribute, but is embedded
in an outer struct with the no_unique_address attribute - like this:

    struct S {};
    S f();
    struct S2 : public S { S2();};
    S2::S2() : S(f()) {}
    struct S3 { int x; [[no_unique_address]] S2 y; S3(); };
    S3::S3() : x(1), y() {}

Here, the problematic store (which this patch omits) is in
the constructor of S2. In the case of S3, S2 has no valid storage
and aliases x - thus the constructor of S2 should omit the dummy
store.

Differential Revision: https://reviews.llvm.org/D157332

(cherry picked from commit d60c3d0)
@tru
Copy link
Collaborator

tru commented Aug 21, 2023

can this one be closed now @mstorsjo ?

@mstorsjo
Copy link
Member

can this one be closed now @mstorsjo ?

Yes, afaik this should have been fixed by 4d502f1.

@EugeneZelenko EugeneZelenko removed this from the LLVM 17.0.X Release milestone Aug 21, 2023
razmser pushed a commit to SuduIDE/llvm-project that referenced this issue Oct 2, 2023
An empty struct is handled as a struct with a dummy i8, on all targets.

Most targets treat an empty struct return value as essentially
void - but some don't. (Currently, at least x86_64-windows-* and
powerpc64le-* don't treat it as void.)

When intializing a struct with such a no_unique_address member,
make sure we don't write the dummy i8 into the struct where there's
no space allocated for it.

Previously it would clobber the actual valid data of the struct.

Fixes llvm#64253, and
possibly llvm#64077
and llvm#64427 as well.

We should omit the store for any empty record (not only ones
declared with no_unique_address); we can have a situation where a
class doesn't have the no_unique_address attribute, but is embedded
in an outer struct with the no_unique_address attribute - like this:

    struct S {};
    S f();
    struct S2 : public S { S2();};
    S2::S2() : S(f()) {}
    struct S3 { int x; [[no_unique_address]] S2 y; S3(); };
    S3::S3() : x(1), y() {}

Here, the problematic store (which this patch omits) is in
the constructor of S2. In the case of S3, S2 has no valid storage
and aliases x - thus the constructor of S2 should omit the dummy
store.

Differential Revision: https://reviews.llvm.org/D157332
razmser pushed a commit to SuduIDE/llvm-project that referenced this issue Oct 2, 2023
An empty struct is handled as a struct with a dummy i8, on all targets.

Most targets treat an empty struct return value as essentially
void - but some don't. (Currently, at least x86_64-windows-* and
powerpc64le-* don't treat it as void.)

When intializing a struct with such a no_unique_address member,
make sure we don't write the dummy i8 into the struct where there's
no space allocated for it.

Previously it would clobber the actual valid data of the struct.

Fixes llvm#64253, and
possibly llvm#64077
and llvm#64427 as well.

We should omit the store for any empty record (not only ones
declared with no_unique_address); we can have a situation where a
class doesn't have the no_unique_address attribute, but is embedded
in an outer struct with the no_unique_address attribute - like this:

    struct S {};
    S f();
    struct S2 : public S { S2();};
    S2::S2() : S(f()) {}
    struct S3 { int x; [[no_unique_address]] S2 y; S3(); };
    S3::S3() : x(1), y() {}

Here, the problematic store (which this patch omits) is in
the constructor of S2. In the case of S3, S2 has no valid storage
and aliases x - thus the constructor of S2 should omit the dummy
store.

Differential Revision: https://reviews.llvm.org/D157332
razmser pushed a commit to SuduIDE/llvm-project that referenced this issue Oct 2, 2023
An empty struct is handled as a struct with a dummy i8, on all targets.

Most targets treat an empty struct return value as essentially
void - but some don't. (Currently, at least x86_64-windows-* and
powerpc64le-* don't treat it as void.)

When intializing a struct with such a no_unique_address member,
make sure we don't write the dummy i8 into the struct where there's
no space allocated for it.

Previously it would clobber the actual valid data of the struct.

Fixes llvm#64253, and
possibly llvm#64077
and llvm#64427 as well.

We should omit the store for any empty record (not only ones
declared with no_unique_address); we can have a situation where a
class doesn't have the no_unique_address attribute, but is embedded
in an outer struct with the no_unique_address attribute - like this:

    struct S {};
    S f();
    struct S2 : public S { S2();};
    S2::S2() : S(f()) {}
    struct S3 { int x; [[no_unique_address]] S2 y; S3(); };
    S3::S3() : x(1), y() {}

Here, the problematic store (which this patch omits) is in
the constructor of S2. In the case of S3, S2 has no valid storage
and aliases x - thus the constructor of S2 should omit the dummy
store.

Differential Revision: https://reviews.llvm.org/D157332
razmser pushed a commit to SuduIDE/llvm-project that referenced this issue Oct 3, 2023
An empty struct is handled as a struct with a dummy i8, on all targets.

Most targets treat an empty struct return value as essentially
void - but some don't. (Currently, at least x86_64-windows-* and
powerpc64le-* don't treat it as void.)

When intializing a struct with such a no_unique_address member,
make sure we don't write the dummy i8 into the struct where there's
no space allocated for it.

Previously it would clobber the actual valid data of the struct.

Fixes llvm#64253, and
possibly llvm#64077
and llvm#64427 as well.

We should omit the store for any empty record (not only ones
declared with no_unique_address); we can have a situation where a
class doesn't have the no_unique_address attribute, but is embedded
in an outer struct with the no_unique_address attribute - like this:

    struct S {};
    S f();
    struct S2 : public S { S2();};
    S2::S2() : S(f()) {}
    struct S3 { int x; [[no_unique_address]] S2 y; S3(); };
    S3::S3() : x(1), y() {}

Here, the problematic store (which this patch omits) is in
the constructor of S2. In the case of S3, S2 has no valid storage
and aliases x - thus the constructor of S2 should omit the dummy
store.

Differential Revision: https://reviews.llvm.org/D157332
razmser pushed a commit to SuduIDE/llvm-project that referenced this issue Oct 3, 2023
An empty struct is handled as a struct with a dummy i8, on all targets.

Most targets treat an empty struct return value as essentially
void - but some don't. (Currently, at least x86_64-windows-* and
powerpc64le-* don't treat it as void.)

When intializing a struct with such a no_unique_address member,
make sure we don't write the dummy i8 into the struct where there's
no space allocated for it.

Previously it would clobber the actual valid data of the struct.

Fixes llvm#64253, and
possibly llvm#64077
and llvm#64427 as well.

We should omit the store for any empty record (not only ones
declared with no_unique_address); we can have a situation where a
class doesn't have the no_unique_address attribute, but is embedded
in an outer struct with the no_unique_address attribute - like this:

    struct S {};
    S f();
    struct S2 : public S { S2();};
    S2::S2() : S(f()) {}
    struct S3 { int x; [[no_unique_address]] S2 y; S3(); };
    S3::S3() : x(1), y() {}

Here, the problematic store (which this patch omits) is in
the constructor of S2. In the case of S3, S2 has no valid storage
and aliases x - thus the constructor of S2 should omit the dummy
store.

Differential Revision: https://reviews.llvm.org/D157332
razmser pushed a commit to SuduIDE/llvm-project that referenced this issue Oct 6, 2023
An empty struct is handled as a struct with a dummy i8, on all targets.

Most targets treat an empty struct return value as essentially
void - but some don't. (Currently, at least x86_64-windows-* and
powerpc64le-* don't treat it as void.)

When intializing a struct with such a no_unique_address member,
make sure we don't write the dummy i8 into the struct where there's
no space allocated for it.

Previously it would clobber the actual valid data of the struct.

Fixes llvm#64253, and
possibly llvm#64077
and llvm#64427 as well.

We should omit the store for any empty record (not only ones
declared with no_unique_address); we can have a situation where a
class doesn't have the no_unique_address attribute, but is embedded
in an outer struct with the no_unique_address attribute - like this:

    struct S {};
    S f();
    struct S2 : public S { S2();};
    S2::S2() : S(f()) {}
    struct S3 { int x; [[no_unique_address]] S2 y; S3(); };
    S3::S3() : x(1), y() {}

Here, the problematic store (which this patch omits) is in
the constructor of S2. In the case of S3, S2 has no valid storage
and aliases x - thus the constructor of S2 should omit the dummy
store.

Differential Revision: https://reviews.llvm.org/D157332
razmser pushed a commit to SuduIDE/llvm-project that referenced this issue Oct 11, 2023
An empty struct is handled as a struct with a dummy i8, on all targets.

Most targets treat an empty struct return value as essentially
void - but some don't. (Currently, at least x86_64-windows-* and
powerpc64le-* don't treat it as void.)

When intializing a struct with such a no_unique_address member,
make sure we don't write the dummy i8 into the struct where there's
no space allocated for it.

Previously it would clobber the actual valid data of the struct.

Fixes llvm#64253, and
possibly llvm#64077
and llvm#64427 as well.

We should omit the store for any empty record (not only ones
declared with no_unique_address); we can have a situation where a
class doesn't have the no_unique_address attribute, but is embedded
in an outer struct with the no_unique_address attribute - like this:

    struct S {};
    S f();
    struct S2 : public S { S2();};
    S2::S2() : S(f()) {}
    struct S3 { int x; [[no_unique_address]] S2 y; S3(); };
    S3::S3() : x(1), y() {}

Here, the problematic store (which this patch omits) is in
the constructor of S2. In the case of S3, S2 has no valid storage
and aliases x - thus the constructor of S2 should omit the dummy
store.

Differential Revision: https://reviews.llvm.org/D157332
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang Clang issues not falling into any other category platform:windows
Projects
Archived in project
Development

No branches or pull requests

7 participants