This is an archive of the discontinued LLVM Phabricator instance.

[MinGW] Reject explicit hidden visibility applied to dllexport and hidden/protected applied to dllimport
ClosedPublic

Authored by MaskRay on Sep 3 2022, 10:17 PM.

Details

Summary

Hidden visibility is incompatible with dllexport.
Hidden and protected visibilities are incompatible with dllimport.
(PlayStation uses dllexport protected.)

When an explicit visibility attribute applies on a dllexport/dllimport
declaration, report a Frontend error (Sema does not compute visibility).

Diff Detail

Event Timeline

MaskRay created this revision.Sep 3 2022, 10:17 PM
Herald added a project: Restricted Project. · View Herald TranscriptSep 3 2022, 10:17 PM
Herald added a subscriber: StephenFan. · View Herald Transcript
MaskRay requested review of this revision.Sep 3 2022, 10:17 PM
Herald added a project: Restricted Project. · View Herald TranscriptSep 3 2022, 10:17 PM
Herald added a subscriber: cfe-commits. · View Herald Transcript
mstorsjo accepted this revision.Sep 5 2022, 12:41 AM

LGTM, thanks!

clang/lib/CodeGen/CodeGenModule.cpp
1104

I was wondering if we're changing behaviour in some other case since this is now checked before the dllexport/dllimport, but I think it shouldn't make any difference, since we already error out (elsewhere) on dllexport+static today.

This revision is now accepted and ready to land.Sep 5 2022, 12:41 AM
MaskRay edited the summary of this revision. (Show Details)Sep 5 2022, 10:14 AM
bd1976llvm added a subscriber: bd1976llvm.EditedSep 7 2022, 11:57 AM

This approach doesn't account for the implementation of -fvisibility-global-new-delete-hidden. The following case now fails after this change:

>type new_del.cpp

namespace std {
  typedef __typeof__(sizeof(int)) size_t;
  struct nothrow_t {};
  struct align_val_t {};
}
__declspec(dllexport) void operator delete(void* ptr) throw() {return;}


>clang.exe --target=x86_64-pc-windows-gnu new_del.cpp -fvisibility-global-new-delete-hidden -c
new_del.cpp:8:28: error: non-default visibility cannot be applied to 'dllexport' declaration
__declspec(dllexport) void operator delete(void* ptr) throw() {return;}
                           ^
1 error generated.

This approach doesn't account for the implementation of -fvisibility-global-new-delete-hidden. The following case now fails after this change:

>type new_del.cpp

namespace std {
  typedef __typeof__(sizeof(int)) size_t;
  struct nothrow_t {};
  struct align_val_t {};
}
__declspec(dllexport) void operator delete(void* ptr) throw() {return;}


>clang.exe --target=x86_64-pc-windows-gnu new_del.cpp -fvisibility-global-new-delete-hidden -c
new_del.cpp:8:28: error: non-default visibility cannot be applied to 'dllexport' declaration
__declspec(dllexport) void operator delete(void* ptr) throw() {return;}
                           ^
1 error generated.

From a glance, -fvisibility-global-new-delete-hidden should make the visibility implicit (so won't trigger this error) but don't seem to do so? I'll investigate later.

clang/lib/CodeGen/CodeGenModule.cpp
1104

Confirmed. We have diagnostics like:

a.c:1:35: error: 'f' must have external linkage when declared 'dllexport'
static __declspec(dllexport) void f() {}
                                  ^
a.c:2:35: error: 'g' must have external linkage when declared 'dllimport'
static __declspec(dllimport) void g() {}
                                  ^

This approach doesn't account for the implementation of -fvisibility-global-new-delete-hidden. The following case now fails after this change:

>type new_del.cpp

namespace std {
  typedef __typeof__(sizeof(int)) size_t;
  struct nothrow_t {};
  struct align_val_t {};
}
__declspec(dllexport) void operator delete(void* ptr) throw() {return;}


>clang.exe --target=x86_64-pc-windows-gnu new_del.cpp -fvisibility-global-new-delete-hidden -c
new_del.cpp:8:28: error: non-default visibility cannot be applied to 'dllexport' declaration
__declspec(dllexport) void operator delete(void* ptr) throw() {return;}
                           ^
1 error generated.

From a glance, -fvisibility-global-new-delete-hidden should make the visibility implicit (so won't trigger this error) but don't seem to do so? I'll investigate later.

-fvisibility-global-new-delete-hidden is implemented by adding VisibilityAttr to declarations.
I think -fvisibility-global-new-delete-hidden triggering the error is fine. The alternative, adding a rule that __declspec(dllexport) void operator delete does not get hidden visibility, seems ad-hoc to me.

I think the only needed change is to allow dllexport protected, but then we probably need two diagnostics. Perhaps diag::err_hidden_visiblity_dllexport and diag::err_non_default_visibility_dllimport?

if (LV.isVisibilityExplicit()) {
  // Reject explicit hidden visibility on dllexport and hidden/protected
  // visibility on dllimport.
  if (GV->hasDLLExportStorageClass() &&
      LV.getVisibility() == HiddenVisibility)
    getDiags().Report(D->getLocation(),
                      diag::err_hidden_visibility_dllexport);
  if (GV->hasDLLImportStorageClass() &&
      LV.getVisibility() != DefaultVisibility)
    getDiags().Report(D->getLocation(),
                      diag::err_non_default_visibility_dllimport);
}

From a glance, -fvisibility-global-new-delete-hidden should make the visibility implicit (so won't trigger this error) but don't seem to do so? I'll investigate later.

-fvisibility-global-new-delete-hidden is implemented by adding VisibilityAttr to declarations.
I think -fvisibility-global-new-delete-hidden triggering the error is fine. The alternative, adding a rule that __declspec(dllexport) void operator delete does not get hidden visibility, seems ad-hoc to me.

I'm not sure why this visibility option (-fvisibility-global-new-delete-hidden) is implemented differently to the others (e.g. -fvisibility=hidden)? __declspec(dllexport) void operator delete does not get hidden visibility, might be difficult to implement; but OTOH, the dllstorageclass overrides the visibility set by a visibility option for the other visibility options (e.g. -fvisibility=hidden) and it would be nice to have consistent behaviour for all the visibility options. Would be great to get other peoples opinions on whether an error here would be a problem?

I think the only needed change is to allow dllexport protected, but then we probably need two diagnostics. Perhaps diag::err_hidden_visiblity_dllexport and diag::err_non_default_visibility_dllimport?

SGTM!

From a glance, -fvisibility-global-new-delete-hidden should make the visibility implicit (so won't trigger this error) but don't seem to do so? I'll investigate later.

-fvisibility-global-new-delete-hidden is implemented by adding VisibilityAttr to declarations.
I think -fvisibility-global-new-delete-hidden triggering the error is fine. The alternative, adding a rule that __declspec(dllexport) void operator delete does not get hidden visibility, seems ad-hoc to me.

I'm not sure why this visibility option (-fvisibility-global-new-delete-hidden) is implemented differently to the others (e.g. -fvisibility=hidden)? __declspec(dllexport) void operator delete does not get hidden visibility, might be difficult to implement; but OTOH, the dllstorageclass overrides the visibility set by a visibility option for the other visibility options (e.g. -fvisibility=hidden) and it would be nice to have consistent behaviour for all the visibility options. Would be great to get other peoples opinions on whether an error here would be a problem?

First, does COFF use -fvisibility-global-new-delete-hidden? The impl of -fvisibility-global-new-delete-hidden is very different from -fvisibility. -fvisibility changed visibility is considered !isVisibilityExplicit while -fvisibility-global-new-delete-hidden's is isVisibilityExplicit.

dllspec and visibility are extensions for different object file formats and here we are mixing them together.
Personally I don't favor defining too many "let A override B" designs. Adding if (GV->hasDLLExportStorageClass() || GV->hasDLLImportStorageClass()) is mostly only due to compatibility consideration...

I think the only needed change is to allow dllexport protected, but then we probably need two diagnostics. Perhaps diag::err_hidden_visiblity_dllexport and diag::err_non_default_visibility_dllimport?

SGTM!

MaskRay reopened this revision.Sep 7 2022, 7:39 PM
This revision is now accepted and ready to land.Sep 7 2022, 7:39 PM
MaskRay updated this revision to Diff 458627.Sep 7 2022, 7:42 PM
MaskRay retitled this revision from [MinGW] Reject explicit non-default visibility applied to dllexport/dllimport declaration to [MinGW] Reject explicit hidden visibility applied to dllexport and hidden/protected applied to dllimport.
MaskRay edited the summary of this revision. (Show Details)

.

From a glance, -fvisibility-global-new-delete-hidden should make the visibility implicit (so won't trigger this error) but don't seem to do so? I'll investigate later.

-fvisibility-global-new-delete-hidden is implemented by adding VisibilityAttr to declarations.
I think -fvisibility-global-new-delete-hidden triggering the error is fine. The alternative, adding a rule that __declspec(dllexport) void operator delete does not get hidden visibility, seems ad-hoc to me.

I'm not sure why this visibility option (-fvisibility-global-new-delete-hidden) is implemented differently to the others (e.g. -fvisibility=hidden)? __declspec(dllexport) void operator delete does not get hidden visibility, might be difficult to implement; but OTOH, the dllstorageclass overrides the visibility set by a visibility option for the other visibility options (e.g. -fvisibility=hidden) and it would be nice to have consistent behaviour for all the visibility options. Would be great to get other peoples opinions on whether an error here would be a problem?

First, does COFF use -fvisibility-global-new-delete-hidden? The impl of -fvisibility-global-new-delete-hidden is very different from -fvisibility. -fvisibility changed visibility is considered !isVisibilityExplicit while -fvisibility-global-new-delete-hidden's is isVisibilityExplicit.

dllspec and visibility are extensions for different object file formats and here we are mixing them together.
Personally I don't favor defining too many "let A override B" designs. Adding if (GV->hasDLLExportStorageClass() || GV->hasDLLImportStorageClass()) is mostly only due to compatibility consideration...

Sorry for the slow reply. -fvisibility-global-new-delete-hidden was implemented here: https://reviews.llvm.org/D53787, it is Clang only (it is not in GCC) and, as you pointed out, the changed visibility is deliberately isVisibilityExplicit. We are happy to accept the change in behaviour as we don't think it will impact customers.

I think the only needed change is to allow dllexport protected, but then we probably need two diagnostics. Perhaps diag::err_hidden_visiblity_dllexport and diag::err_non_default_visibility_dllimport?

SGTM!

LGTM. Thanks for your patience.

clang/lib/CodeGen/CodeGenModule.cpp
1116

Opinion: I don't think these comments are needed as the code is clear. I think it would be better to have a single comment at the top of this block e.g. "Reject incompatible dlllstorageclass and visibility annotations" or just remove them.

MaskRay marked an inline comment as done.Sep 12 2022, 3:51 PM
MaskRay added inline comments.
clang/lib/CodeGen/CodeGenModule.cpp
1116

Will change. Thanks!

This revision was automatically updated to reflect the committed changes.
MaskRay marked an inline comment as done.

Whilst reviewing the impact of these dllimport/export restrictions I noticed that LLVM IR does not reject dllimport/export on local linkage globals. I have put up a PR for adding that restriction: D134784 (https://reviews.llvm.org/D134784).