//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "clang-tidy/ClangTidyCheck.h" #include "clang-tidy/ClangTidyModuleRegistry.h" #include "hide_from_abi.hpp" namespace { AST_MATCHER(clang::ClassTemplateDecl, hasFullSpecializations) { return !Node.specializations().empty(); } AST_MATCHER(clang::CXXRecordDecl, isTrivial) { return Node.isTrivial(); } } // namespace namespace libcpp { hide_from_abi::hide_from_abi(llvm::StringRef name, clang::tidy::ClangTidyContext* context) : clang::tidy::ClangTidyCheck(name, context) {} void hide_from_abi::registerMatchers(clang::ast_matchers::MatchFinder* finder) { using namespace clang::ast_matchers; auto has_hide_from_abi_attr = anyOf(hasAttr(clang::attr::Visibility), hasAttr(clang::attr::AbiTag)); finder->addMatcher( functionDecl( unless(anyOf( // These functions can't be marked `[[gnu::always_inline]]` for various reasons, // so we can't mark them `_LIBCPP_HIDE_FROM_ABI`. These functions are ignored in // all namespaces. Checking the qualified name is a lot harder and these names // should result in very few (if any) false-negatives. This is also just a // temporary work-around until we can mark functions as HIDE_FROM_ABI without // having to add `[[gnu::always_inline]]` with GCC. hasAnyName("__introsort", "__inplace_merge", "__libcpp_snprintf_l", "__libcpp_asprintf_l", "__libcpp_sscanf_l", "__tree_sub_invariant", "__stable_sort_move", "__stable_sort", "__stable_partition", "__lock_first", "__stable_partition_impl"), has_hide_from_abi_attr, cxxMethodDecl(), // We have explicitly instantiated classes and some of their methods don't have these attributes isDeleted(), isConsteval())), isDefinition()) .bind("hide_from_abi_on_free_function"), this); auto on_trivial = allOf( unless(isImplicit()), isDefaulted(), unless(ofClass(hasAncestor(classTemplateDecl()))), ofClass(isTrivial())); // TODO: find a better way to check for explicit instantiations. Currently, every template that has a full // specialization is ignored. For example, vector is ignored because we instantiate vector // in discrete_distribution. finder->addMatcher( cxxMethodDecl( unless(anyOf( has_hide_from_abi_attr, isDeleted(), isImplicit(), hasAncestor(cxxRecordDecl(isLambda())), ofClass(anyOf(hasAncestor(classTemplateDecl(hasFullSpecializations())), hasAnyName("basic_filebuf", "basic_ifstream", "basic_ofstream", // These are in the dylib in ABIv2 // TODO: fix the matcher to catch `sentry` instantiation. "sentry"))), isConsteval(), hasParent(classTemplateSpecializationDecl()), on_trivial)), isDefinition()) .bind("hide_from_abi_on_member_function"), this); finder->addMatcher( cxxMethodDecl(has_hide_from_abi_attr, on_trivial).bind("hide_from_abi_on_defaulted_smf_in_trivial_class"), this); } void hide_from_abi::check(const clang::ast_matchers::MatchFinder::MatchResult& result) { if (const auto* call = result.Nodes.getNodeAs("hide_from_abi_on_free_function"); call != nullptr) { diag(call->getBeginLoc(), "_LIBCPP_HIDE_FROM_ABI is missing"); } // The rest gets ignored in C++03 because it is subtly different in some cases. // e.g. we change the definition of default constructors in some cases // TODO: check whether we can remove thse differences if (!result.Context->getLangOpts().CPlusPlus11) return; if (const auto* call = result.Nodes.getNodeAs("hide_from_abi_on_member_function"); call != nullptr) { diag(call->getLocation(), "_LIBCPP_HIDE_FROM_ABI or _LIBCPP_HIDE_FROM_ABI_VIRTUAL is missing"); } if (const auto* call = result.Nodes.getNodeAs("hide_from_abi_on_defaulted_smf_in_trivial_class"); call != nullptr) { diag(call->getLocation(), "_LIBCPP_HIDE_FROM_ABI should not be used for special member functions in trivial classes"); } } } // namespace libcpp