| 1 | //===----------------------------------------------------------------------===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | |
| 9 | #include "clang-tidy/ClangTidyCheck.h" |
| 10 | #include "clang-tidy/ClangTidyModuleRegistry.h" |
| 11 | |
| 12 | #include "hide_from_abi.hpp" |
| 13 | |
| 14 | namespace { |
| 15 | AST_MATCHER(clang::ClassTemplateDecl, hasFullSpecializations) { return !Node.specializations().empty(); } |
| 16 | AST_MATCHER(clang::CXXRecordDecl, isTrivial) { return Node.isTrivial(); } |
| 17 | } // namespace |
| 18 | |
| 19 | namespace libcpp { |
| 20 | hide_from_abi::hide_from_abi(llvm::StringRef name, clang::tidy::ClangTidyContext* context) |
| 21 | : clang::tidy::ClangTidyCheck(name, context) {} |
| 22 | |
| 23 | void hide_from_abi::registerMatchers(clang::ast_matchers::MatchFinder* finder) { |
| 24 | using namespace clang::ast_matchers; |
| 25 | |
| 26 | auto has_hide_from_abi_attr = anyOf(hasAttr(clang::attr::Visibility), hasAttr(clang::attr::AbiTag)); |
| 27 | |
| 28 | finder->addMatcher( |
| 29 | functionDecl( |
| 30 | unless(anyOf( |
| 31 | // These functions can't be marked `[[gnu::always_inline]]` for various reasons, |
| 32 | // so we can't mark them `_LIBCPP_HIDE_FROM_ABI`. These functions are ignored in |
| 33 | // all namespaces. Checking the qualified name is a lot harder and these names |
| 34 | // should result in very few (if any) false-negatives. This is also just a |
| 35 | // temporary work-around until we can mark functions as HIDE_FROM_ABI without |
| 36 | // having to add `[[gnu::always_inline]]` with GCC. |
| 37 | hasAnyName("__introsort" , |
| 38 | "__inplace_merge" , |
| 39 | "__libcpp_snprintf_l" , |
| 40 | "__libcpp_asprintf_l" , |
| 41 | "__libcpp_sscanf_l" , |
| 42 | "__tree_sub_invariant" , |
| 43 | "__stable_sort_move" , |
| 44 | "__stable_sort" , |
| 45 | "__stable_partition" , |
| 46 | "__lock_first" , |
| 47 | "__stable_partition_impl" ), |
| 48 | has_hide_from_abi_attr, |
| 49 | cxxMethodDecl(), // We have explicitly instantiated classes and some of their methods don't have these attributes |
| 50 | isDeleted(), |
| 51 | isConsteval())), |
| 52 | isDefinition()) |
| 53 | .bind("hide_from_abi_on_free_function" ), |
| 54 | this); |
| 55 | |
| 56 | auto on_trivial = allOf( |
| 57 | unless(isImplicit()), isDefaulted(), unless(ofClass(hasAncestor(classTemplateDecl()))), ofClass(isTrivial())); |
| 58 | |
| 59 | // TODO: find a better way to check for explicit instantiations. Currently, every template that has a full |
| 60 | // specialization is ignored. For example, vector is ignored because we instantiate vector<double> |
| 61 | // in discrete_distribution. |
| 62 | finder->addMatcher( |
| 63 | cxxMethodDecl( |
| 64 | unless(anyOf( |
| 65 | has_hide_from_abi_attr, |
| 66 | isDeleted(), |
| 67 | isImplicit(), |
| 68 | hasAncestor(cxxRecordDecl(isLambda())), |
| 69 | ofClass(anyOf(hasAncestor(classTemplateDecl(hasFullSpecializations())), |
| 70 | hasAnyName("basic_filebuf" , |
| 71 | "basic_ifstream" , |
| 72 | "basic_ofstream" , // These are in the dylib in ABIv2 |
| 73 | // TODO: fix the matcher to catch `sentry` instantiation. |
| 74 | "sentry" ))), |
| 75 | isConsteval(), |
| 76 | hasParent(classTemplateSpecializationDecl()), |
| 77 | on_trivial)), |
| 78 | isDefinition()) |
| 79 | .bind("hide_from_abi_on_member_function" ), |
| 80 | this); |
| 81 | |
| 82 | finder->addMatcher( |
| 83 | cxxMethodDecl(has_hide_from_abi_attr, on_trivial).bind("hide_from_abi_on_defaulted_smf_in_trivial_class" ), this); |
| 84 | } |
| 85 | |
| 86 | void hide_from_abi::check(const clang::ast_matchers::MatchFinder::MatchResult& result) { |
| 87 | if (const auto* call = result.Nodes.getNodeAs<clang::FunctionDecl>("hide_from_abi_on_free_function" ); |
| 88 | call != nullptr) { |
| 89 | diag(call->getBeginLoc(), "_LIBCPP_HIDE_FROM_ABI is missing" ); |
| 90 | } |
| 91 | |
| 92 | // The rest gets ignored in C++03 because it is subtly different in some cases. |
| 93 | // e.g. we change the definition of default constructors in some cases |
| 94 | // TODO: check whether we can remove thse differences |
| 95 | if (!result.Context->getLangOpts().CPlusPlus11) |
| 96 | return; |
| 97 | |
| 98 | if (const auto* call = result.Nodes.getNodeAs<clang::CXXMethodDecl>("hide_from_abi_on_member_function" ); |
| 99 | call != nullptr) { |
| 100 | diag(call->getLocation(), "_LIBCPP_HIDE_FROM_ABI or _LIBCPP_HIDE_FROM_ABI_VIRTUAL is missing" ); |
| 101 | } |
| 102 | |
| 103 | if (const auto* call = |
| 104 | result.Nodes.getNodeAs<clang::CXXMethodDecl>("hide_from_abi_on_defaulted_smf_in_trivial_class" ); |
| 105 | call != nullptr) { |
| 106 | diag(call->getLocation(), |
| 107 | "_LIBCPP_HIDE_FROM_ABI should not be used for special member functions in trivial classes" ); |
| 108 | } |
| 109 | } |
| 110 | } // namespace libcpp |
| 111 | |