diff --git a/libc/utils/CMakeLists.txt b/libc/utils/CMakeLists.txt --- a/libc/utils/CMakeLists.txt +++ b/libc/utils/CMakeLists.txt @@ -1,5 +1,6 @@ add_subdirectory(CPP) add_subdirectory(HdrGen) +add_subdirectory(LibcTidy) add_subdirectory(MPFRWrapper) add_subdirectory(testutils) add_subdirectory(UnitTest) diff --git a/libc/utils/HdrGen/PublicAPICommand.h b/libc/utils/HdrGen/PublicAPICommand.h --- a/libc/utils/HdrGen/PublicAPICommand.h +++ b/libc/utils/HdrGen/PublicAPICommand.h @@ -12,6 +12,7 @@ #include "Command.h" #include "llvm/ADT/StringRef.h" +#include "llvm/TableGen/Record.h" #include #include @@ -38,4 +39,15 @@ } // namespace llvm_libc +inline bool isa(llvm::Record *Def, llvm::Record *TypeClass) { + llvm::RecordRecTy *RecordType = Def->getType(); + llvm::ArrayRef Classes = RecordType->getClasses(); + // We want exact types. That is, we don't want the classes listed in + // spec.td to be subclassed. Hence, we do not want the record |Def| + // to be of more than one class type. + if (Classes.size() != 1) + return false; + return Classes[0] == TypeClass; +} + #endif // LLVM_LIBC_UTILS_HDRGEN_PUBLICAPICOMMAND_H diff --git a/libc/utils/HdrGen/PublicAPICommand.cpp b/libc/utils/HdrGen/PublicAPICommand.cpp --- a/libc/utils/HdrGen/PublicAPICommand.cpp +++ b/libc/utils/HdrGen/PublicAPICommand.cpp @@ -12,7 +12,6 @@ #include "llvm/ADT/StringRef.h" #include "llvm/Support/SourceMgr.h" #include "llvm/TableGen/Error.h" -#include "llvm/TableGen/Record.h" static const char NamedTypeClassName[] = "NamedType"; static const char PtrTypeClassName[] = "PtrType"; @@ -23,17 +22,6 @@ static const char StandardSpecClassName[] = "StandardSpec"; static const char PublicAPIClassName[] = "PublicAPI"; -static bool isa(llvm::Record *Def, llvm::Record *TypeClass) { - llvm::RecordRecTy *RecordType = Def->getType(); - llvm::ArrayRef Classes = RecordType->getClasses(); - // We want exact types. That is, we don't want the classes listed in - // spec.td to be subclassed. Hence, we do not want the record |Def| - // to be of more than one class type.. - if (Classes.size() != 1) - return false; - return Classes[0] == TypeClass; -} - // Text blocks for macro definitions and type decls can be indented to // suit the surrounding tablegen listing. We need to dedent such blocks // before writing them out. diff --git a/libc/utils/LibcTidy/CMakeLists.txt b/libc/utils/LibcTidy/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/libc/utils/LibcTidy/CMakeLists.txt @@ -0,0 +1,18 @@ +set(LLVM_LINK_COMPONENTS support TableGen) +include(${PROJECT_SOURCE_DIR}/../clang/cmake/modules/AddClang.cmake) + +include_directories(${PROJECT_BINARY_DIR}/tools/clang/include/) +include_directories(${PROJECT_SOURCE_DIR}/../clang/include/) + +add_clang_executable(libc-tidy + LibcTidyTool.cpp + Main.cpp + TableGenUtil.cpp) + +target_link_libraries(libc-tidy + PRIVATE + clangTooling + clangBasic + clangASTMatchers) + +add_subdirectory(tests) diff --git a/libc/utils/LibcTidy/EntrypointNameCheck.h b/libc/utils/LibcTidy/EntrypointNameCheck.h new file mode 100644 --- /dev/null +++ b/libc/utils/LibcTidy/EntrypointNameCheck.h @@ -0,0 +1,72 @@ +//===-- EntrypointNameCheck.h -----------------------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIBC_UTILS_LIBCTIDY_ENTRYPOINTNAMECHECK_H +#define LLVM_LIBC_UTILS_LIBCTIDY_ENTRYPOINTNAMECHECK_H + +#include "clang/Lex/MacroArgs.h" +#include "clang/Lex/PPCallbacks.h" + +#include "LibcTidyTool.h" + +using namespace clang; +using namespace clang::ast_matchers; + +class EntrypointPPCallback : public PPCallbacks { +public: + EntrypointPPCallback(LibcTidyCheck &check, MatchFinder &finder) + : check(check), finder(finder) {} + + void MacroExpands(const Token ¯oNameTok, const MacroDefinition &, + SourceRange, const MacroArgs *args) override { + if (macroNameTok.getIdentifierInfo()->getName() != "LLVM_LIBC_ENTRYPOINT") + return; + + // Get the name of the function passed to macro and register a matcher. + StringRef Name = args->getUnexpArgument(0)->getIdentifierInfo()->getName(); + finder.addMatcher(functionDecl(hasName(Name)).bind("libc_entry"), &check); + } + +private: + LibcTidyCheck ✓ + MatchFinder &finder; +}; + +class EntrypointNameCheck : public LibcTidyCheck { +public: + EntrypointNameCheck(const llvm::StringSet<> &publicFunctions) + : publicFunctions(publicFunctions) {} + + void registerPPCallbacks(Preprocessor &PP, MatchFinder &finder) override { + PP.addPPCallbacks(std::make_unique(*this, finder)); + } + + void run(const MatchFinder::MatchResult &result) override { + const auto *funcDecl = result.Nodes.getNodeAs("libc_entry"); + SourceLocation loc = funcDecl->getLocation(); + + // Ignore declarations from header files. + if (!result.SourceManager->isInMainFile(loc)) + return; + + StringRef funcName = funcDecl->getName(); + if (publicFunctions.count(funcName) != 0) + return; + + DiagnosticsEngine &DE = result.Context->getDiagnostics(); + const unsigned ID = + DE.getCustomDiagID(DiagnosticsEngine::Error, + "Entrypoint %0 is not a public libc function."); + DE.Report(loc, ID).AddString(funcName); + } + +private: + const llvm::StringSet<> &publicFunctions; +}; + +#endif // LLVM_LIBC_UTILS_LIBCTIDY_ENTRYPOINTNAMECHECK_H diff --git a/libc/utils/LibcTidy/LibcTidyTool.h b/libc/utils/LibcTidy/LibcTidyTool.h new file mode 100644 --- /dev/null +++ b/libc/utils/LibcTidy/LibcTidyTool.h @@ -0,0 +1,38 @@ +//===-- LibcTidyTool.h ------------------------------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIBC_UTILS_LIBCTIDY_TOOL_H +#define LLVM_LIBC_UTILS_LIBCTIDY_TOOL_H + +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Frontend/FrontendActions.h" +#include "clang/Tooling/Tooling.h" + +#include "llvm/ADT/StringSet.h" + +class LibcTidyCheck : public clang::ast_matchers::MatchFinder::MatchCallback { +public: + virtual void registerPPCallbacks(clang::Preprocessor &PP, + clang::ast_matchers::MatchFinder &finder){}; + virtual void registerMatchers(clang::ast_matchers::MatchFinder &finder){}; +}; + +class LibcTidyActionFactory : public clang::tooling::FrontendActionFactory { +public: + LibcTidyActionFactory(clang::ast_matchers::MatchFinder &finder, + std::vector> &checks) + : finder(finder), checks(checks) {} + + std::unique_ptr create() override; + +private: + clang::ast_matchers::MatchFinder &finder; + std::vector> &checks; +}; + +#endif // LLVM_LIBC_UTILS_LIBCTIDY_TOOL_H diff --git a/libc/utils/LibcTidy/LibcTidyTool.cpp b/libc/utils/LibcTidy/LibcTidyTool.cpp new file mode 100644 --- /dev/null +++ b/libc/utils/LibcTidy/LibcTidyTool.cpp @@ -0,0 +1,42 @@ +//===-- LibcTidyTool.cpp --------------------------------------------------===// +// +// 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/ASTMatchers/ASTMatchers.h" +#include "clang/Frontend/CompilerInstance.h" + +#include "llvm/Support/Debug.h" +#include "llvm/Support/raw_ostream.h" + +#include "LibcTidyTool.h" + +using namespace clang; +using namespace clang::ast_matchers; + +class LibcTidyAction : public ASTFrontendAction { +public: + LibcTidyAction(MatchFinder &finder, + std::vector> &checks) + : finder(finder), checks(checks) {} + + std::unique_ptr CreateASTConsumer(CompilerInstance &compiler, + llvm::StringRef) override { + Preprocessor &PP = compiler.getPreprocessor(); + for (auto &check : checks) + check->registerPPCallbacks(PP, finder); + + return finder.newASTConsumer(); + } + +private: + MatchFinder &finder; + std::vector> &checks; +}; + +std::unique_ptr LibcTidyActionFactory::create() { + return std::make_unique(finder, checks); +} diff --git a/libc/utils/LibcTidy/Main.cpp b/libc/utils/LibcTidy/Main.cpp new file mode 100644 --- /dev/null +++ b/libc/utils/LibcTidy/Main.cpp @@ -0,0 +1,71 @@ +//===-- Main.cpp ----------------------------------------------------------===// +// +// 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/Tooling/CommonOptionsParser.h" +#include "clang/Tooling/Tooling.h" + +#include "llvm/Support/CommandLine.h" +#include "llvm/TableGen/Main.h" + +#include "EntrypointNameCheck.h" +#include "LibcTidyTool.h" +#include "TableGenUtil.h" + +using namespace clang; + +static llvm::cl::OptionCategory LibCTidyCategory("libc-tidy options"); + +static llvm::cl::opt TableGenFilePathOption( + "tablegen-filepath", + llvm::cl::desc( + "The filepath to the '.td' file containing libc public api functions"), + llvm::cl::Required, llvm::cl::cat(LibCTidyCategory)); + +static llvm::cl::opt TableGenIncludeDirOption( + "tablegen-includedir", + llvm::cl::desc("The include dir to pass to tablegen."), llvm::cl::Required, + llvm::cl::cat(LibCTidyCategory)); + +llvm::StringSet<> publicFunctions; + +bool TidyTableGenMain(llvm::raw_ostream &OS, llvm::RecordKeeper &records) { + publicFunctions = getAllLibcFunctions(records); + return false; +} + +int main(int argc, const char **argv) { + // Both clang tooling and TableGen expect positional arguments. So instead we + // remove and manually provide the arguments below for TableGenMain. + llvm::cl::Option *tblGenFileOpt = + llvm::cl::TopLevelSubCommand->PositionalOpts.pop_back_val(); + llvm::cl::Option *tblGenIncludeOpt = + llvm::cl::TopLevelSubCommand->OptionsMap["I"]; + + tooling::CommonOptionsParser OptionsParser(argc, argv, LibCTidyCategory); + tooling::ClangTool Tool(OptionsParser.getCompilations(), + OptionsParser.getSourcePathList()); + + // Manually pass our option values to TableGenMain's options. + llvm::cl::ProvidePositionalOption(tblGenFileOpt, TableGenFilePathOption, 0); + llvm::cl::ProvidePositionalOption(tblGenIncludeOpt, TableGenIncludeDirOption, + 0); + + TableGenMain(argv[0], &TidyTableGenMain); + + MatchFinder finder; + + // Checks + std::vector> checks; + checks.emplace_back(std::make_unique(publicFunctions)); + + for (auto &check : checks) + check->registerMatchers(finder); + + LibcTidyActionFactory factory(finder, checks); + return Tool.run(&factory); +} diff --git a/libc/utils/LibcTidy/TableGenUtil.h b/libc/utils/LibcTidy/TableGenUtil.h new file mode 100644 --- /dev/null +++ b/libc/utils/LibcTidy/TableGenUtil.h @@ -0,0 +1,18 @@ +//===-- TableGenUtil.h ------------------------------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIBC_UTILS_LIBCTIDY_TABLEGENUTIL_H +#define LLVM_LIBC_UTILS_LIBCTIDY_TABLEGENUTIL_H + +#include "llvm/ADT/StringSet.h" +#include "llvm/Support/Error.h" +#include "llvm/TableGen/Record.h" + +llvm::StringSet<> getAllLibcFunctions(llvm::RecordKeeper &records); + +#endif // LLVM_LIBC_UTILS_LIBCTIDY_TABLEGENUTIL_H diff --git a/libc/utils/LibcTidy/TableGenUtil.cpp b/libc/utils/LibcTidy/TableGenUtil.cpp new file mode 100644 --- /dev/null +++ b/libc/utils/LibcTidy/TableGenUtil.cpp @@ -0,0 +1,26 @@ +//===-- TableGenUtil.cpp --------------------------------------------------===// +// +// 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 "TableGenUtil.h" +#include "../HdrGen/PublicAPICommand.h" + +llvm::StringSet<> getAllLibcFunctions(llvm::RecordKeeper &records) { + llvm::StringSet<> funcNames; + + llvm::Record *publicAPIClass = records.getClass("PublicAPI"); + const auto &defsMap = records.getDefs(); + for (const auto &pair : defsMap) { + llvm::Record *def = pair.second.get(); + if (!isa(def, publicAPIClass)) + continue; + + auto functionNameList = def->getValueAsListOfStrings("Functions"); + funcNames.insert(functionNameList.begin(), functionNameList.end()); + } + return funcNames; +} diff --git a/libc/utils/LibcTidy/tests/.clang-format b/libc/utils/LibcTidy/tests/.clang-format new file mode 100644 --- /dev/null +++ b/libc/utils/LibcTidy/tests/.clang-format @@ -0,0 +1,2 @@ +BasedOnStyle: LLVM +ColumnLimit: 0 diff --git a/libc/utils/LibcTidy/tests/CMakeLists.txt b/libc/utils/LibcTidy/tests/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/libc/utils/LibcTidy/tests/CMakeLists.txt @@ -0,0 +1,8 @@ +add_lit_testsuite(check-libc-tidy "Libc-Tidy Regression Tests." + ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS libc-tidy FileCheck not + EXCLUDE_FROM_CHECK_ALL) + +configure_lit_site_cfg( + ${CMAKE_CURRENT_SOURCE_DIR}/lit.site.cfg.in + ${CMAKE_CURRENT_BINARY_DIR}/lit.site.cfg) diff --git a/libc/utils/LibcTidy/tests/EntrypointNameTest.cpp b/libc/utils/LibcTidy/tests/EntrypointNameTest.cpp new file mode 100644 --- /dev/null +++ b/libc/utils/LibcTidy/tests/EntrypointNameTest.cpp @@ -0,0 +1,21 @@ +// RUN: not libc-tidy %s --tablegen-filepath "%S/mock_api.td" \ +// RUN: --tablegen-includedir "%S/../../../" -- 2>&1 | FileCheck %s + +#define LLVM_LIBC_ENTRYPOINT(name) name +#define SOMETHING_ELSE(name) name + +namespace __llvm_libc { + +// Entrypoint is valid if the function is a public libc function. +void LLVM_LIBC_ENTRYPOINT(libc_api_func)(char *param) {} +// CHECK-NOT: error: Entrypoint libc_api_func is not a public libc function. + +// Functions defined with different macros are ignored. +void SOMETHING_ELSE(different_macro)(char *param) {} +// CHECK-NOT: error: Entrypoint different_macro is not a public libc function. + +// Entrypoint is invalid if the function is not a public libc function. +void LLVM_LIBC_ENTRYPOINT(non_api_func)(char *param) {} +// CHECK: :[[@LINE-1]]:27: error: Entrypoint non_api_func is not a public libc function. + +} // namespace __llvm_libc diff --git a/libc/utils/LibcTidy/tests/lit.site.cfg.in b/libc/utils/LibcTidy/tests/lit.site.cfg.in new file mode 100644 --- /dev/null +++ b/libc/utils/LibcTidy/tests/lit.site.cfg.in @@ -0,0 +1,8 @@ +import os +import lit.formats + +config.name = 'libc-tidy' +config.test_format = lit.formats.ShTest() +config.test_source_root = "@CMAKE_CURRENT_SOURCE_DIR@" +config.suffixes = ['.cpp'] +config.environment['PATH'] = "@LLVM_TOOLS_DIR@" diff --git a/libc/utils/LibcTidy/tests/mock_api.td b/libc/utils/LibcTidy/tests/mock_api.td new file mode 100644 --- /dev/null +++ b/libc/utils/LibcTidy/tests/mock_api.td @@ -0,0 +1,7 @@ +include "config/public_api.td" + +def FakeAPI : PublicAPI<"fakeapi.h"> { + let Functions = [ + "libc_api_func", + ]; +}