Index: clang/include/clang/Basic/DiagnosticSemaKinds.td =================================================================== --- clang/include/clang/Basic/DiagnosticSemaKinds.td +++ clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -4684,6 +4684,10 @@ def warn_static_local_in_extern_inline : Warning< "non-constant static local variable in inline function may be different " "in different files">, InGroup; +def warn_static_local_in_inline_member_of_exported_class : Warning< + "static %0 used in an inline function of " + "exported class isn't exported due to -fno-dllexport-inlines">, + InGroup; def note_convert_inline_to_static : Note< "use 'static' to give inline function %0 internal linkage">; Index: clang/include/clang/Basic/LangOptions.h =================================================================== --- clang/include/clang/Basic/LangOptions.h +++ clang/include/clang/Basic/LangOptions.h @@ -214,6 +214,9 @@ /// input is a header file (i.e. -x c-header). bool IsHeaderFile = false; + /// If set, dllexported classes dllexport their inline methods. + bool DllexportInlines = true; + LangOptions(); // Define accessors/mutators for language options of enumeration type. @@ -270,7 +273,7 @@ /// Floating point control options class FPOptions { public: - FPOptions() : fp_contract(LangOptions::FPC_Off), + FPOptions() : fp_contract(LangOptions::FPC_Off), fenv_access(LangOptions::FEA_Off) {} // Used for serializing. @@ -316,7 +319,7 @@ unsigned getInt() const { return fp_contract | (fenv_access << 2); } private: - /// Adjust BinaryOperator::FPFeatures to match the total bit-field size + /// Adjust BinaryOperator::FPFeatures to match the total bit-field size /// of these two. unsigned fp_contract : 2; unsigned fenv_access : 1; Index: clang/include/clang/Basic/LangOptions.def =================================================================== --- clang/include/clang/Basic/LangOptions.def +++ clang/include/clang/Basic/LangOptions.def @@ -114,6 +114,7 @@ LANGOPT(GNUKeywords , 1, 1, "GNU keywords") BENIGN_LANGOPT(ImplicitInt, 1, !C99 && !CPlusPlus, "C89 implicit 'int'") LANGOPT(Digraphs , 1, 0, "digraphs") +LANGOPT(DllexportInlines , 1, 1, "If dllexport a class should dllexport inline methods in the Microsoft ABI") BENIGN_LANGOPT(HexFloats , 1, C99, "C99 hexadecimal float constants") LANGOPT(CXXOperatorNames , 1, 0, "C++ operator name keywords") LANGOPT(AppleKext , 1, 0, "Apple kext support") Index: clang/include/clang/Driver/CC1Options.td =================================================================== --- clang/include/clang/Driver/CC1Options.td +++ clang/include/clang/Driver/CC1Options.td @@ -692,6 +692,8 @@ HelpText<"Don't use a const qualified type for string literals in C and ObjC">; def fno_bitfield_type_align : Flag<["-"], "fno-bitfield-type-align">, HelpText<"Ignore bit-field types when aligning structures">; +def fno_dllexport_inlines : Flag<["-"], "fno-dllexport-inlines">, + HelpText<"Don't export inline functions">; def ffake_address_space_map : Flag<["-"], "ffake-address-space-map">, HelpText<"Use a fake address space map; OpenCL testing purposes only">; def faddress_space_map_mangling_EQ : Joined<["-"], "faddress-space-map-mangling=">, MetaVarName<"">, Index: clang/include/clang/Driver/CLCompatOptions.td =================================================================== --- clang/include/clang/Driver/CLCompatOptions.td +++ clang/include/clang/Driver/CLCompatOptions.td @@ -301,6 +301,8 @@ MetaVarName<"">; def _SLASH_Y_ : CLFlag<"Y-">, HelpText<"Disable precompiled headers, overrides /Yc and /Yu">; +def _SLASH_Zc_dllexportInlines : CLFlag<"Zc:dllexportInlines">; +def _SLASH_Zc_dllexportInlines_ : CLFlag<"Zc:dllexportInlines-">; def _SLASH_Fp : CLJoined<"Fp">, HelpText<"Set pch filename (with /Yc and /Yu)">, MetaVarName<"">; Index: clang/lib/Driver/ToolChains/Clang.cpp =================================================================== --- clang/lib/Driver/ToolChains/Clang.cpp +++ clang/lib/Driver/ToolChains/Clang.cpp @@ -5238,6 +5238,11 @@ if (VolatileOptionID == options::OPT__SLASH_volatile_ms) CmdArgs.push_back("-fms-volatile"); + if (Args.hasFlag(options::OPT__SLASH_Zc_dllexportInlines_, + options::OPT__SLASH_Zc_dllexportInlines, + false)) + CmdArgs.push_back("-fno-dllexport-inlines"); + Arg *MostGeneralArg = Args.getLastArg(options::OPT__SLASH_vmg); Arg *BestCaseArg = Args.getLastArg(options::OPT__SLASH_vmb); if (MostGeneralArg && BestCaseArg) Index: clang/lib/Frontend/CompilerInvocation.cpp =================================================================== --- clang/lib/Frontend/CompilerInvocation.cpp +++ clang/lib/Frontend/CompilerInvocation.cpp @@ -2153,6 +2153,9 @@ } } + if (Args.hasArg(OPT_fno_dllexport_inlines)) + Opts.DllexportInlines = false; + if (const Arg *A = Args.getLastArg(OPT_fcf_protection_EQ)) { StringRef Name = A->getValue(); if (Name == "full" || Name == "branch") { Index: clang/lib/Sema/SemaDeclCXX.cpp =================================================================== --- clang/lib/Sema/SemaDeclCXX.cpp +++ clang/lib/Sema/SemaDeclCXX.cpp @@ -5485,6 +5485,38 @@ } } +namespace { + +// StaticLocalVarChecker is used to warn if static local variables are used in +// inline functions of exported class with fno-dllexport-inlines +class StaticLocalVarChecker final : public ConstStmtVisitor { + private: + Sema &SemaRef; + + public: + StaticLocalVarChecker(Sema &S) : SemaRef(S) {} + void VisitDeclStmt(const DeclStmt *E) { + for (const Decl *D : E->decls()) { + if (const VarDecl *VD = dyn_cast(D)) { + if (VD->isStaticLocal() && !VD->checkInitIsICE()) { + SemaRef.Diag(VD->getLocation(), + diag::warn_static_local_in_inline_member_of_exported_class) + << VD; + } + } + } + } + + void VisitStmt(const Stmt *S) { + for (const Stmt *Child : S->children()) { + if (Child) { + Visit(Child); + } + } + } +}; +} // namespace + static void ReferenceDllExportedMembers(Sema &S, CXXRecordDecl *Class) { Attr *ClassAttr = getDLLAttr(Class); if (!ClassAttr) @@ -5512,6 +5544,26 @@ if (!MD) continue; + // XXX: need to skip inlines here + // or not needed because they no longer get dllexport attribs. hmm, best place here + // or checkClassLevelDLLAttribute? + if (MD->isInlined() && !getDLLAttr(MD)) { + const Stmt *Body = nullptr; + StaticLocalVarChecker Checker(S); + + if (const FunctionDecl *Definition = MD->getDefinition()) { + Body = Definition->getBody(); + + if (S.Context.getTargetInfo().getCXXABI().isMicrosoft() && + !S.getLangOpts().DllexportInlines && + Class->getTemplateSpecializationKind() != TSK_ExplicitInstantiationDeclaration && + Class->getTemplateSpecializationKind() != TSK_ExplicitInstantiationDefinition && + Body) { + Checker.Visit(Body); + } + } + } + if (Member->getAttr()) { if (MD->isUserProvided()) { // Instantiate non-default class member functions ... @@ -5686,6 +5738,17 @@ !Context.getTargetInfo().getTriple().isWindowsItaniumEnvironment()) continue; + if (Context.getTargetInfo().getCXXABI().isMicrosoft() && + !getLangOpts().DllexportInlines && + Class->getTemplateSpecializationKind() != TSK_ExplicitInstantiationDeclaration && + Class->getTemplateSpecializationKind() != TSK_ExplicitInstantiationDefinition && + + // To check static local var later. + MD->isDefined()) { + // Warn later if MD has static local variable. + continue; + } + // MSVC versions before 2015 don't export the move assignment operators // and move constructor, so don't attempt to import/export them if // we have a definition. Index: clang/test/CodeGenCXX/dllexport-no-inline.cpp =================================================================== --- /dev/null +++ clang/test/CodeGenCXX/dllexport-no-inline.cpp @@ -0,0 +1,131 @@ +// RUN: %clang_cc1 %s -fms-extensions -triple x86_64-windows-msvc \ +// RUN: -fno-dllexport-inlines -emit-llvm -O0 -o - -verify -DNOEXPORT_INLINE | \ +// RUN: FileCheck --check-prefix=DEFAULT --check-prefix=NOINLINE %s + +// RUN: %clang_cc1 %s -fms-extensions -triple x86_64-windows-msvc \ +// RUN: -emit-llvm -O0 -o - | \ +// RUN: FileCheck --check-prefix=DEFAULT --check-prefix=INLINE %s + + +// Function + +// DEFAULT-DAG: define dso_local dllexport void @"?NormalFunction@@YAXXZ"() +void __declspec(dllexport) NormalFunction() {} + + +// DEFAULT-DAG: define weak_odr dso_local dllexport void @"?AlwaysInlineFunction@@ +__forceinline void __declspec(dllexport) AlwaysInlineFunction() {} + + +// Class member function + +class __declspec(dllexport) NoTemplateExportedClass { + // DEFAULT-NOT: NoTemplateExportedClass@NoTemplateExportedClass@@ + NoTemplateExportedClass() = default; + + // NOINLINE-NOT: InclassDefFunc@NoTemplateExportedClass + // INLINE-DAG: define weak_odr dso_local dllexport void @"?InclassDefFunc@NoTemplateExportedClass@@AEAAXXZ" + void InclassDefFunc() {} + + int f(); + + // FIX: make this DEFAULT-DAG. + // INLINE-DAG: define weak_odr dso_local dllexport i32 @"?InclassDefFuncWithStaticVariable@NoTemplateExportedClass@@AEAAHXZ" + int InclassDefFuncWithStaticVariable() { +#ifdef NOEXPORT_INLINE + // expected-warning@+2 {{static 'static_variable' used in an inline function of exported class isn't exported due to -fno-dllexport-inlines}} +#endif + static int static_variable = f(); + + static const int static_const_variable = 1; // expected-no-warning + constexpr int static_constexpr_variable = 2; // expected-no-warning + static const int static_const_array[] = {1, 2, 3}; // expected-no-warning + + return ++static_variable + static_const_variable + static_constexpr_variable + + static_const_array[0]; + } + + // DEFAULT-NOT: InlineOutclassDefFuncWihtoutDefinition + __forceinline void InlineOutclassDefFuncWihtoutDefinition(); + + // FIX: Do not export this when -ms-no-dllexport-inline. + // DEFAULT-DAG: define weak_odr dso_local dllexport void @"?InlineOutclassDefFunc@NoTemplateExportedClass@@AEAAXXZ" + __forceinline void InlineOutclassDefFunc(); + + // FIX: Do not export this when -ms-no-dllexport-inline is given and warn for local static var. + // DEFAULT-DAG: define weak_odr dso_local dllexport i32 @"?InlineOutclassDefFuncWithStaticVariable@NoTemplateExportedClass@@AEAAHXZ" + __forceinline int InlineOutclassDefFuncWithStaticVariable(); + + // DEFAULT-DAG: define dso_local dllexport void @"?OutclassDefFunc@NoTemplateExportedClass@@AEAAXXZ" + void OutclassDefFunc(); +}; + +void NoTemplateExportedClass::OutclassDefFunc() {} + +__forceinline void NoTemplateExportedClass::InlineOutclassDefFunc() {} + +__forceinline int NoTemplateExportedClass::InlineOutclassDefFuncWithStaticVariable() { + static int static_variable = 0; + return ++static_variable; +} + +template +class __declspec(dllexport) TemplateExportedClass { + void InclassDefFunc() {} + void OutclassDefFunc(); + + T templateValue; +}; + +// DEFAULT-NOT: define dso_local dllexport void @"?OutclassDefFunc@NoTemplateExportedClass@@ +template void TemplateExportedClass::OutclassDefFunc() {} + +class A11{}; +class B22{}; + +// DEFAULT-DAG: define weak_odr dso_local dllexport void @"?InclassDefFunc@?$TemplateExportedClass@VA11@@ +// DEFAULT-DAG: define weak_odr dso_local dllexport void @"?OutclassDefFunc@?$TemplateExportedClass@VA11@@ +template class __declspec(dllexport) TemplateExportedClass; + +// DEFAULT-DAG: define weak_odr dso_local dllexport void @"?InclassDefFunc@?$TemplateExportedClass@VB22@@ +// DEFAULT-DAG: define weak_odr dso_local dllexport void @"?OutclassDefFunc@?$TemplateExportedClass@VB22@@ +template class TemplateExportedClass; + + +template +class TemplateNoExportedClass { + void InclassDefFunc() {} + void OutclassDefFunc(); +}; + +template void TemplateNoExportedClass::OutclassDefFunc() {} + +// DEFAULT-DAG: define weak_odr dso_local dllexport void @"?InclassDefFunc@?$TemplateNoExportedClass@VA11@@ +// DEFAULT-DAG: define weak_odr dso_local dllexport void @"?OutclassDefFunc@?$TemplateNoExportedClass@VA11@@ +template class __declspec(dllexport) TemplateNoExportedClass; + +// DEFAULT-DAG: define weak_odr dso_local void @"?InclassDefFunc@?$TemplateNoExportedClass@VB22@@ +// DEFAULT-DAG: define weak_odr dso_local void @"?OutclassDefFunc@?$TemplateNoExportedClass@VB22@@ +template class TemplateNoExportedClass; + + +class __declspec(dllimport) ImportedClass { +public: + class ImportedInnerClass { + public: + void OutClassDefFunc(); + }; + + // INLINE-DAG: declare dllimport void @"?InClassDefFunc@ImportedClass@ + // NOINLINE-NOT: dllimport .* InClassDefFunc@ImportedClass@ + void InClassDefFunc() { + i.OutClassDefFunc(); + } + ImportedInnerClass i; +}; + +void InClassDefFuncUser() { + // This is necessary for declare statement of ImportedClass::InClassDefFunc(). + ImportedClass c; + c.InClassDefFunc(); +}