diff --git a/clang-tools-extra/clang-tidy/BSCompatibility/BSCompatibilityTidyModule.cpp b/clang-tools-extra/clang-tidy/BSCompatibility/BSCompatibilityTidyModule.cpp new file mode 100644 index 0000000000000000000000000000000000000000..cd1ee52dd5037e274ff451b8a1bb95d0c1956a02 --- /dev/null +++ b/clang-tools-extra/clang-tidy/BSCompatibility/BSCompatibilityTidyModule.cpp @@ -0,0 +1,51 @@ +//===--- BSCompatibilityTidyModule.cpp - clang-tidy --------------------------===// +// +// +//===----------------------------------------------------------------------===// + +#include "../ClangTidy.h" +#include "../ClangTidyModule.h" +#include "../ClangTidyModuleRegistry.h" +#include "../cppcoreguidelines/NarrowingConversionsCheck.h" +#include "DependentTemplateKeywordCheck.h" +#include "ForbiddenBuiltinExitCheck.h" +#include "MoveExplicitInstantiationAfterDefsCheck.h" +#include "NonVoidFunctionReturnVoidCheck.h" +#include "RedundantDefaultTemplateArgCheck.h" +#include "ThreadStorageUnifyCheck.h" +#include "UnsequencedFunctionParameterCheck.h" + +namespace clang::tidy { +namespace BSCompatibility { + +class BSCompatibilityModule : public ClangTidyModule { +public: + void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { + CheckFactories.registerCheck( + "BSCompatibility-forbidden-builtin-exit"); + CheckFactories.registerCheck( + "BSCompatibility-dependent-template-keyword"); + CheckFactories.registerCheck( + "BSCompatibility-move-explicit-instantiation-after-defs"); + CheckFactories.registerCheck( + "BSCompatibility-non-void-function-return-void"); + CheckFactories.registerCheck( + "BSCompatibility-redundant-default-template-arg"); + CheckFactories.registerCheck( + "BSCompatibility-thread-storage-unify"); + CheckFactories.registerCheck( + "BSCompatibility-unsequenced-function-parameter"); + } +}; + +} // namespace BSCompatibility + +// Register the BSCompatibilityModuleRegistry using this statically initsialized variable. +static ClangTidyModuleRegistry::Add + X("BSCompatibility-module", "Adds checks for BiSheng compatibility code constructs."); + +// This anchor is used to force the linker to link in the generated object file +// and thus register the BSCompatibilityModule. +volatile int BSCompatibilityModuleAnchorSource = 0; + +} // namespace clang::tidy diff --git a/clang-tools-extra/clang-tidy/BSCompatibility/CMakeLists.txt b/clang-tools-extra/clang-tidy/BSCompatibility/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..7810421b97920c2eb242bf7bd7b3404e030b215f --- /dev/null +++ b/clang-tools-extra/clang-tidy/BSCompatibility/CMakeLists.txt @@ -0,0 +1,36 @@ +set(LLVM_LINK_COMPONENTS + support + FrontendOpenMP + ) + +add_clang_library(clangTidyBSCompatibilityModule + BSCompatibilityTidyModule.cpp + ForbiddenBuiltinExitCheck.cpp + DependentTemplateKeywordCheck.cpp + MoveExplicitInstantiationAfterDefsCheck.cpp + NonVoidFunctionReturnVoidCheck.cpp + RedundantDefaultTemplateArgCheck.cpp + ThreadStorageUnifyCheck.cpp + UnsequencedFunctionParameterCheck.cpp + + LINK_LIBS + clangTidy + clangTidyCppCoreGuidelinesModule + clangTidyUtils + + DEPENDS + omp_gen + ) + +clang_target_link_libraries(clangTidyBSCompatibilityModule + PRIVATE + clangAnalysis + clangAnalysisFlowSensitive + clangAnalysisFlowSensitiveModels + clangAST + clangASTMatchers + clangBasic + clangLex + clangTooling + clangTransformer + ) diff --git a/clang-tools-extra/clang-tidy/BSCompatibility/DependentTemplateKeywordCheck.cpp b/clang-tools-extra/clang-tidy/BSCompatibility/DependentTemplateKeywordCheck.cpp new file mode 100644 index 0000000000000000000000000000000000000000..135b5f1b04bb5539381fbe2c3bf6f6b79086a8b5 --- /dev/null +++ b/clang-tools-extra/clang-tidy/BSCompatibility/DependentTemplateKeywordCheck.cpp @@ -0,0 +1,107 @@ +//===--- DependentTemplateKeywordCheck.cpp - clang-tidy -------------------===// +// +// 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 "DependentTemplateKeywordCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/ExprCXX.h" +#include "clang/AST/ParentMapContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Basic/TokenKinds.h" +#include "clang/Lex/Lexer.h" + +using namespace clang; +using namespace clang::ast_matchers; +using namespace clang::tidy; + +static clang::SourceLocation getTemplateInsertLocAfterToken( + clang::SourceLocation OpLoc, clang::tok::TokenKind TokKind, + const clang::SourceManager &SM, const clang::LangOptions &LangOpts) { + return clang::Lexer::findLocationAfterToken(OpLoc, TokKind, SM, LangOpts, + /*SkipTrailingWhitespace=*/true); +} + +namespace clang::tidy::BSCompatibility { + +void DependentTemplateKeywordCheck::registerMatchers( + ast_matchers::MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + Finder->addMatcher(expr().bind("maybeExpr"), this); +} + +static bool isUsedAsCallee(const Expr *E, ASTContext &Context) { + const auto &Parents = Context.getParents(*E); + if (Parents.empty()) + return false; + + const Stmt *ParentStmt = Parents[0].get(); + if (!ParentStmt) + return false; + + if (const auto *Call = dyn_cast(ParentStmt)) { + const Expr *Callee = Call->getCallee()->IgnoreParenImpCasts(); + const Expr *Self = E->IgnoreParenImpCasts(); + return Callee == Self; + } + + return false; +} + +void DependentTemplateKeywordCheck::check( + const MatchFinder::MatchResult &Result) { + ASTContext &Ctx = *Result.Context; + const SourceManager &SM = *Result.SourceManager; + + const auto *ExprNode = Result.Nodes.getNodeAs("maybeExpr"); + if (!ExprNode) + return; + + // a->foo() or a.foo() + if (const auto *CDSME = dyn_cast(ExprNode)) { + if (!CDSME->hasExplicitTemplateArgs() || (!isUsedAsCallee(CDSME, Ctx)) || + CDSME->getTemplateKeywordLoc().isValid()) + return; + + SourceLocation InsertLoc = getTemplateInsertLocAfterToken( + CDSME->getOperatorLoc(), CDSME->isArrow() ? tok::arrow : tok::period, + SM, Ctx.getLangOpts()); + + if (InsertLoc.isInvalid()) + return; + + diag(CDSME->getBeginLoc(), "missing 'template' keyword before dependent " + "template member function call") + << FixItHint::CreateInsertion(InsertLoc, "template "); + return; + } + + // A::foo() + if (const auto *DSDR = dyn_cast(ExprNode)) { + if (!DSDR->hasExplicitTemplateArgs() || (!isUsedAsCallee(DSDR, Ctx)) || + DSDR->getTemplateKeywordLoc().isValid()) + return; + + // find the last "::" location + SourceLocation ColonColonLoc = DSDR->getQualifierLoc().getEndLoc(); + if (ColonColonLoc.isInvalid()) + return; + + SourceLocation InsertLoc = getTemplateInsertLocAfterToken( + ColonColonLoc, tok::coloncolon, SM, Ctx.getLangOpts()); + + if (InsertLoc.isInvalid()) + return; + + diag(DSDR->getNameInfo().getLoc(), + "missing 'template' keyword before dependent template function") + << FixItHint::CreateInsertion(InsertLoc, "template "); + return; + } +} +} // namespace clang::tidy::BSCompatibility \ No newline at end of file diff --git a/clang-tools-extra/clang-tidy/BSCompatibility/DependentTemplateKeywordCheck.h b/clang-tools-extra/clang-tidy/BSCompatibility/DependentTemplateKeywordCheck.h new file mode 100644 index 0000000000000000000000000000000000000000..78f481a9f370b97a72eef47a1f52fd9147ef0d16 --- /dev/null +++ b/clang-tools-extra/clang-tidy/BSCompatibility/DependentTemplateKeywordCheck.h @@ -0,0 +1,32 @@ +//===--- DependentTemplateKeywordCheck.h - clang-tidy -----------*- 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_CLANG_TOOLS_EXTRA_CLANG_TIDY_BSCOMPATIBILITY_DEPENDENTTEMPLATEKEYWORDCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BSCOMPATIBILITY_DEPENDENTTEMPLATEKEYWORDCHECK_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::BSCompatibility { +// Detect if template is missing when calling a dependent template function +// and add template keyword if so + +class DependentTemplateKeywordCheck : public ClangTidyCheck { +public: + DependentTemplateKeywordCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + // generate the unique variable name + std::string generateTempVarName(SourceLocation Loc, ASTContext *Context); +}; + +} // namespace clang::tidy::BSCompatibility + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BSCOMPATIBILITY_DEPENDENTTEMPLATEKEYWORDCHECK_H \ No newline at end of file diff --git a/clang-tools-extra/clang-tidy/BSCompatibility/ForbiddenBuiltinExitCheck.cpp b/clang-tools-extra/clang-tidy/BSCompatibility/ForbiddenBuiltinExitCheck.cpp new file mode 100644 index 0000000000000000000000000000000000000000..56092bda674b6ab96060fe45ec3880c902c707dc --- /dev/null +++ b/clang-tools-extra/clang-tidy/BSCompatibility/ForbiddenBuiltinExitCheck.cpp @@ -0,0 +1,74 @@ +//===--- ForbiddenBuiltinExitCheck.cpp - clang-tidy -----------------------===// +// +// 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 "ForbiddenBuiltinExitCheck.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Basic/LangOptions.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Lex/Lexer.h" +#include "clang/Lex/Preprocessor.h" +#include "clang/Lex/Token.h" +#include "llvm/ADT/StringRef.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::BSCompatibility { +void ForbiddenBuiltinExitCheck::registerMatchers(MatchFinder *Finder) {} + +void ForbiddenBuiltinExitCheck::check(const MatchFinder::MatchResult &Result) {} + +void ForbiddenBuiltinExitCheck::registerPPCallbacks( + const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) { + class ForbiddenBuiltinExitPPCallback : public PPCallbacks { + public: + ForbiddenBuiltinExitPPCallback(ClangTidyCheck &Check, + const SourceManager &SM, Preprocessor &PP) + : Check(Check), SM(SM), PP(PP) {} + + void FileChanged(SourceLocation Loc, FileChangeReason Reason, + SrcMgr::CharacteristicKind, FileID PrevFID) override { + if (Reason != EnterFile) + return; + + FileID FID = SM.getFileID(Loc); + bool Invalid = false; + StringRef Code = SM.getBufferData(FID, &Invalid); + if (Invalid) + return; + + LangOptions LangOpts; + Lexer Lex(SM.getLocForStartOfFile(FID), LangOpts, Code.begin(), + Code.begin(), Code.end()); + + Token Tok; + while (!Lex.LexFromRawLexer(Tok)) { + if (Tok.is(tok::raw_identifier)) { + IdentifierInfo &II = + PP.getIdentifierTable().get(Tok.getRawIdentifier()); + Tok.setIdentifierInfo(&II); + Tok.setKind(II.getTokenID()); + + if (II.getName() == "__builtin_exit") { + Check.diag(Tok.getLocation(), "__builtin_exit is not supported by " + "Clang; you may use std::exit()"); + } + } + } + } + + private: + ClangTidyCheck &Check; + const SourceManager &SM; + Preprocessor &PP; + }; + + PP->addPPCallbacks( + std::make_unique(*this, SM, *PP)); +} + +} // namespace clang::tidy::BSCompatibility \ No newline at end of file diff --git a/clang-tools-extra/clang-tidy/BSCompatibility/ForbiddenBuiltinExitCheck.h b/clang-tools-extra/clang-tidy/BSCompatibility/ForbiddenBuiltinExitCheck.h new file mode 100644 index 0000000000000000000000000000000000000000..e49fc7b21b3acaccb62fa1f4bfb0a38a2ff4572f --- /dev/null +++ b/clang-tools-extra/clang-tidy/BSCompatibility/ForbiddenBuiltinExitCheck.h @@ -0,0 +1,28 @@ +//===--- ForbiddenBuiltinExitCheck.h - clang-tidy ---------------*- 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_CLANG_TOOLS_EXTRA_CLANG_TIDY_BSCOMPATIBILITY_FORBIDDENBUILTINEXITCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BSCOMPATIBILITY_FORBIDDENBUILTINEXITCHECK_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::BSCompatibility { + +// Detect the use of __builtin_exit and propose warning. +class ForbiddenBuiltinExitCheck : public ClangTidyCheck { +public: + using ClangTidyCheck::ClangTidyCheck; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, + Preprocessor *ModuleExpanderPP) override; +}; + +} // namespace clang::tidy::BSCompatibility + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BSCOMPATIBILITY_FORBIDDENBUILTINEXITCHECK_H \ No newline at end of file diff --git a/clang-tools-extra/clang-tidy/BSCompatibility/MoveExplicitInstantiationAfterDefsCheck.cpp b/clang-tools-extra/clang-tidy/BSCompatibility/MoveExplicitInstantiationAfterDefsCheck.cpp new file mode 100644 index 0000000000000000000000000000000000000000..984421a0cb5a9815372c752696e3f39ff3d0fc5c --- /dev/null +++ b/clang-tools-extra/clang-tidy/BSCompatibility/MoveExplicitInstantiationAfterDefsCheck.cpp @@ -0,0 +1,241 @@ +//===--- MoveExplicitInstantiationAfterDefsCheck.cpp - clang-tidy ---------===// +// +// 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 "MoveExplicitInstantiationAfterDefsCheck.h" + +#include "clang/AST/ASTContext.h" +#include "clang/AST/Decl.h" +#include "clang/AST/DeclCXX.h" +#include "clang/AST/DeclTemplate.h" +#include "clang/AST/PrettyPrinter.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Basic/TokenKinds.h" +#include "clang/Lex/Lexer.h" +#include "llvm/ADT/Hashing.h" + +using namespace clang; +using namespace clang::ast_matchers; +using namespace clang::tidy; + +namespace clang::tidy::BSCompatibility { + +namespace { + +static SourceLocation endOfToken(const SourceManager &SM, const LangOptions &LO, + SourceLocation Loc) { + if (!Loc.isValid()) + return Loc; + SourceLocation End = Lexer::getLocForEndOfToken(Loc, 0, SM, LO); + return End.isValid() ? End : Loc; +} + +// Primary key = primary class template's canonical decl + printable name. +static const NamedDecl * +getPrimaryFromClassSpec(const ClassTemplateSpecializationDecl *Spec) { + if (!Spec) + return nullptr; + if (const auto *CTD = Spec->getSpecializedTemplate()) + return llvm::cast(CTD->getTemplatedDecl()->getCanonicalDecl()); + return nullptr; +} + +static const NamedDecl *getPrimaryFromMethod(const CXXMethodDecl *MD) { + if (!MD) + return nullptr; + const auto *RD = MD->getParent(); + if (const auto *CTD = RD->getDescribedClassTemplate()) + return llvm::cast(CTD->getTemplatedDecl()->getCanonicalDecl()); + if (const auto *Spec = dyn_cast(RD)) + if (const auto *CTD2 = Spec->getSpecializedTemplate()) + return llvm::cast( + CTD2->getTemplatedDecl()->getCanonicalDecl()); + return nullptr; +} + +} // namespace + +MoveExplicitInstantiationAfterDefsCheck::Key +MoveExplicitInstantiationAfterDefsCheck::buildKey( + const Decl *D, const MatchFinder::MatchResult &R) const { + const NamedDecl *Primary = nullptr; + + if (const auto *Spec = dyn_cast(D)) { + Primary = getPrimaryFromClassSpec(Spec); + } else if (const auto *FD = dyn_cast(D)) { + if (const auto *MD = dyn_cast(FD)) + Primary = getPrimaryFromMethod(MD); + } else if (const auto *ND = dyn_cast(D)) { + Primary = llvm::cast(ND->getCanonicalDecl()); + } + + // Stable printable signature: fully qualified primary template name. + std::string Sig; + if (const auto *N = dyn_cast_or_null(Primary)) { + PrintingPolicy PP(R.Context->getLangOpts()); + PP.SuppressTagKeyword = true; + PP.FullyQualifiedName = true; + llvm::raw_string_ostream OS(Sig); + N->printQualifiedName(OS); + OS.flush(); + } + Key K; + K.Primary = Primary; + K.Mangle = std::move(Sig); + return K; +} + +void MoveExplicitInstantiationAfterDefsCheck::registerMatchers(MatchFinder *F) { + // 1) Class template explicit instantiation (we'll filter to definition-form). + F->addMatcher(classTemplateSpecializationDecl().bind("inst_cls"), this); + // 2) Out-of-line member function definitions (filter by FD->isOutOfLine()). + F->addMatcher(functionDecl(isDefinition()).bind("def"), this); +} + +void MoveExplicitInstantiationAfterDefsCheck::check( + const MatchFinder::MatchResult &R) { + if (!Ctx) + Ctx = R.Context; + const SourceManager &SM = *R.SourceManager; + const LangOptions &LO = R.Context->getLangOpts(); + + // (A) Capture explicit instantiation definition. + if (const auto *C = + R.Nodes.getNodeAs("inst_cls")) { + if (!C->getBeginLoc().isValid() || SM.isInSystemHeader(C->getLocation()) || + // not explicit instantiation definition + C->getSpecializationKind() != TSK_ExplicitInstantiationDefinition) + return; + + Key K = buildKey(C, R); + auto &I = Map[K]; + if (!I.InstDecl || + SM.isBeforeInTranslationUnit(C->getBeginLoc(), I.InstBegin)) { + I.InstDecl = C; + I.InstBegin = C->getBeginLoc(); + I.InstEndToken = endOfToken(SM, LO, C->getEndLoc()); + } + return; + } + + // (B) Capture out-of-line member definitions of class templates. + if (const auto *FD = R.Nodes.getNodeAs("def")) { + if (!FD->getBeginLoc().isValid() || + SM.isInSystemHeader(FD->getLocation()) || !FD->isOutOfLine()) + return; + + const auto *MD = dyn_cast(FD); + if (!MD) + return; + + // Only class templates (primary or specialization). + if (!getPrimaryFromMethod(MD)) + return; + + Key K = buildKey(FD, R); + auto &I = Map[K]; + + SourceLocation End = endOfToken(SM, LO, FD->getEndLoc()); + if (!I.LastDefEndToken.isValid() || + SM.isBeforeInTranslationUnit(I.LastDefEndToken, End)) + I.LastDefEndToken = End; + return; + } +} + +void MoveExplicitInstantiationAfterDefsCheck::onEndOfTranslationUnit() { + if (Map.empty() || !Ctx) + return; + + const SourceManager &SM = Ctx->getSourceManager(); + const LangOptions &LO = Ctx->getLangOpts(); + + for (const auto &E : Map) { + const Info &I = E.second; + if (!I.InstBegin.isValid() || !I.InstEndToken.isValid() || + !I.LastDefEndToken.isValid() || !I.InstDecl) + continue; + + // Already after the last definition, then leave it alone + if (SM.isBeforeInTranslationUnit(I.LastDefEndToken, I.InstBegin)) + continue; + + // Calculate the deletion range from the beginning of the + // entire line to after the semicolon + SourceLocation B = I.InstBegin; + SourceLocation EOT = I.InstEndToken; + + // Beginning of line (deleting the entire line is more stable) + SourceLocation SpellB = SM.getSpellingLoc(B); + FileID FID = SM.getFileID(SpellB); + unsigned Line = SM.getSpellingLineNumber(SpellB); + SourceLocation LineBegin = SM.translateLineCol(FID, Line, 1); + if (LineBegin.isInvalid()) + LineBegin = B; + + // Find a position after the trailing semicolon + SourceLocation AfterSemi = Lexer::findLocationAfterToken( + EOT, tok::semi, SM, LO, /*SkipTrailingWhitespaceAndNewLine=*/true); + + // Backward scanning: Look forward at most 16 tokens until a ';' + // is encountered + if (!AfterSemi.isValid()) { + SourceLocation Scan = EOT; + for (int i = 0; i < 16 && Scan.isValid(); ++i) { + Token T; + SourceLocation Beg = SM.getSpellingLoc(Scan); + if (Lexer::getRawToken(Beg, T, SM, LO)) + break; + if (T.is(tok::semi)) { + AfterSemi = T.getEndLoc().isValid() ? T.getEndLoc() : EOT; + break; + } + Scan = T.getEndLoc(); + } + } + + CharSourceRange Rng = CharSourceRange::getCharRange(LineBegin, AfterSemi); + llvm::StringRef InstText = Lexer::getSourceText(Rng, SM, LO).ltrim(); + + // Only move the lines that look like "template ..." + if (!InstText.starts_with("template")) + continue; + + // Construct the inserted text, making sure to end it with ';' + std::string Insert; + Insert.push_back('\n'); + Insert.append(InstText.data(), InstText.size()); + + if (!llvm::StringRef(Insert).rtrim().ends_with(";")) + Insert.push_back(';'); + Insert.push_back('\n'); + + SourceLocation InstBeginFile = SM.getFileLoc(I.InstBegin); + SourceLocation InsertAnchor = + Lexer::getLocForEndOfToken(SM.getFileLoc(I.LastDefEndToken), 0, SM, LO); + + if (!SM.isWrittenInMainFile(InsertAnchor)) + InsertAnchor = SM.getFileLoc(I.LastDefEndToken); + + // make fixes + { + auto D = + diag(InsertAnchor, + "explicit instantiation should appear after the out-of-line " + "member definition(s) in this translation unit"); + D << FixItHint::CreateRemoval(Rng); + + D << FixItHint::CreateInsertion(InsertAnchor, Insert); + } + // make notes + diag(InstBeginFile, "suggest to remove this explicit instantiation here", + DiagnosticIDs::Note); + diag(InsertAnchor, "suggest to insert the explicit instantiation here", + DiagnosticIDs::Note); + } +} +} // namespace clang::tidy::BSCompatibility \ No newline at end of file diff --git a/clang-tools-extra/clang-tidy/BSCompatibility/MoveExplicitInstantiationAfterDefsCheck.h b/clang-tools-extra/clang-tidy/BSCompatibility/MoveExplicitInstantiationAfterDefsCheck.h new file mode 100644 index 0000000000000000000000000000000000000000..a52f4e1d869f1fd391710b1d998e2ea43f2355db --- /dev/null +++ b/clang-tools-extra/clang-tidy/BSCompatibility/MoveExplicitInstantiationAfterDefsCheck.h @@ -0,0 +1,81 @@ +//===--- MoveExplicitInstantiationAfterDefsCheck.h - clang-tidy -*- 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_CLANG_TOOLS_EXTRA_CLANG_TIDY_BSCOMPATIBILITY_MOVEEXPLICITINSTANTIATIONAFTERDEFSCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BSCOMPATIBILITY_MOVEEXPLICITINSTANTIATIONAFTERDEFSCHECK_H + +#include "../ClangTidyCheck.h" + +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "llvm/ADT/DenseMap.h" +#include + +namespace clang { +namespace tidy { +namespace BSCompatibility { + +class MoveExplicitInstantiationAfterDefsCheck : public ClangTidyCheck { +public: + using ClangTidyCheck::ClangTidyCheck; + + // Register matchers for: + // - class/var/function template explicit instantiations, and + // - out-of-line function/method definitions. + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + + // Collect matches and record their source locations for later rewrite. + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + + // At TU end, decide whether any explicit instantiation must be moved + // and emit FixIts (remove + re-insert after the last definition). + void onEndOfTranslationUnit() override; + + struct Key { + const NamedDecl *Primary = nullptr; + std::string Mangle; + bool operator==(const Key &O) const { + return Primary == O.Primary && Mangle == O.Mangle; + } + }; + + struct KeyInfo { + static inline Key getEmptyKey() { + return {reinterpret_cast(1), ""}; + } + static inline Key getTombstoneKey() { + return {reinterpret_cast(2), ""}; + } + static unsigned getHashValue(const Key &K) { + return llvm::hash_combine(K.Primary, K.Mangle); + } + static bool isEqual(const Key &A, const Key &B) { return A == B; } + }; + + struct Info { + const Decl *InstDecl = nullptr; // the decl to move + SourceLocation InstBegin; // begin of explicit instantiation + SourceLocation InstEndToken; // end-of-token + SourceLocation + LastDefEndToken; // end-of-token of the last related out-of-line def + }; + + // Build the Key for a given declaration (instantiation or definition). + Key buildKey(const Decl *D, + const ast_matchers::MatchFinder::MatchResult &R) const; + + llvm::DenseMap Map; + // Cache ASTContext pointer because older tidy bases don't + // expose getASTContext(). + clang::ASTContext *Ctx = nullptr; +}; + +} // namespace BSCompatibility +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BSCOMPATIBILITY_MOVEEXPLICITINSTANTIATIONAFTERDEFSCHECK_H \ No newline at end of file diff --git a/clang-tools-extra/clang-tidy/BSCompatibility/NonVoidFunctionReturnVoidCheck.cpp b/clang-tools-extra/clang-tidy/BSCompatibility/NonVoidFunctionReturnVoidCheck.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5063253707ae852351170d80dda210702fb3ad2a --- /dev/null +++ b/clang-tools-extra/clang-tidy/BSCompatibility/NonVoidFunctionReturnVoidCheck.cpp @@ -0,0 +1,33 @@ +//===--- NonVoidFunctionReturnVoidCheck.cpp - clang-tidy ------------------===// +// +// 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 "NonVoidFunctionReturnVoidCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::BSCompatibility { + +void NonVoidFunctionReturnVoidCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher(functionDecl(unless(isMain()), + isDefinition(), + unless(returns(asString("void"))), + unless(hasDescendant(returnStmt()))) + .bind("x"), this); +} + +void NonVoidFunctionReturnVoidCheck::check(const MatchFinder::MatchResult &Result) { + const auto *MatchedDecl = Result.Nodes.getNodeAs("x"); + diag(MatchedDecl->getEndLoc(), "Non-void function does not return a val.") + << MatchedDecl->getNameInfo().getSourceRange() + << FixItHint::CreateReplacement(MatchedDecl->getReturnTypeSourceRange(), "void") + << FixItHint::CreateInsertion(MatchedDecl->getEndLoc(), "return something;"); +} + +} // namespace clang::tidy::BSCompatibility diff --git a/clang-tools-extra/clang-tidy/BSCompatibility/NonVoidFunctionReturnVoidCheck.h b/clang-tools-extra/clang-tidy/BSCompatibility/NonVoidFunctionReturnVoidCheck.h new file mode 100644 index 0000000000000000000000000000000000000000..33138776aac9cd33423c7f9c07009019b48e6fb3 --- /dev/null +++ b/clang-tools-extra/clang-tidy/BSCompatibility/NonVoidFunctionReturnVoidCheck.h @@ -0,0 +1,27 @@ +//===--- NonVoidFunctionReturnVoidCheck.h - clang-tidy ----------*- 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_CLANG_TOOLS_EXTRA_CLANG_TIDY_BSCOMPATIBILITY_NONVOIDFUNCTIONRETURNVOIDCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BSCOMPATIBILITY_NONVOIDFUNCTIONRETURNVOIDCHECK_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::BSCompatibility { + +// Check if a non-void function hasn't return statement. +class NonVoidFunctionReturnVoidCheck : public ClangTidyCheck { +public: + NonVoidFunctionReturnVoidCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace clang::tidy::BSCompatibility + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BSCOMPATIBILITY_NONVOIDFUNCTIONRETURNVOIDCHECK_H diff --git a/clang-tools-extra/clang-tidy/BSCompatibility/RedundantDefaultTemplateArgCheck.cpp b/clang-tools-extra/clang-tidy/BSCompatibility/RedundantDefaultTemplateArgCheck.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c2c73443ed8069e89069385048c34b5bf0868e36 --- /dev/null +++ b/clang-tools-extra/clang-tidy/BSCompatibility/RedundantDefaultTemplateArgCheck.cpp @@ -0,0 +1,92 @@ +//===--- RedundantDefaultTemplateArgCheck.cpp - clang-tidy ----------------===// +// +// 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 "RedundantDefaultTemplateArgCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::BSCompatibility { + +void RedundantDefaultTemplateArgCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher(functionTemplateDecl().bind("funcTmpl"), this); +} + +void RedundantDefaultTemplateArgCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *FuncTmpl = + Result.Nodes.getNodeAs("funcTmpl"); + if (!FuncTmpl || + Result.SourceManager->isInSystemHeader(FuncTmpl->getLocation())) + return; + + auto &Recorded = FirstDefaultMap[FuncTmpl->getCanonicalDecl()]; + bool IsFirstSeen = !Recorded; + for (unsigned i = 0; i < FuncTmpl->getTemplateParameters()->size(); ++i) { + const auto *Param = dyn_cast( + FuncTmpl->getTemplateParameters()->getParam(i)); + if (!Param || !Param->hasDefaultArgument()) + continue; + if (IsFirstSeen) { + Recorded = true; + continue; + } + TypeSourceInfo *DefaultArgInfo = Param->getDefaultArgumentInfo(); + if (!DefaultArgInfo) + continue; + TemplateArgument DefaultArg(DefaultArgInfo->getType()); + TemplateArgumentLoc DefaultArgLoc(DefaultArg, DefaultArgInfo); + if (DefaultArgLoc.getArgument().isNull()) + continue; + SourceRange DefaultRange = DefaultArgLoc.getSourceRange(); + if (!DefaultRange.isValid()) + continue; + SourceManager &SM = *Result.SourceManager; + LangOptions LangOpts = Result.Context->getLangOpts(); + + SourceLocation EndLoc = DefaultRange.getEnd(); + SourceLocation StartLoc = DefaultRange.getBegin(); + + // search forward for `=` + SourceLocation EqualLoc = StartLoc; + Token Tok; + SourceLocation SearchLoc = StartLoc; + + while (SearchLoc.isValid() && SM.isWrittenInSameFile(SearchLoc, StartLoc)) { + SearchLoc = SearchLoc.getLocWithOffset(-1); + if (Lexer::getRawToken(SearchLoc, Tok, SM, LangOpts, true)) + break; + if (Tok.is(tok::equal)) { + EqualLoc = Tok.getLocation(); + break; + } + } + + // scan forward for blank space + SourceLocation RemovalBegin = EqualLoc; + while (RemovalBegin.getRawEncoding() > + Param->getLocation().getRawEncoding()) { + char C = *SM.getCharacterData(RemovalBegin.getLocWithOffset(-1)); + if (C != ' ' && C != '\t') + break; + RemovalBegin = RemovalBegin.getLocWithOffset(-1); + } + + // remove from RemovalBegin to end of DefaultRange + CharSourceRange RemovalRange = CharSourceRange::getCharRange( + RemovalBegin, Lexer::getLocForEndOfToken(EndLoc, 0, SM, LangOpts)); + + diag(Param->getLocation(), "default template argument redefined; only the " + "first declaration should specify it") + << FixItHint::CreateRemoval(RemovalRange); + } +} + +} // namespace clang::tidy::BSCompatibility \ No newline at end of file diff --git a/clang-tools-extra/clang-tidy/BSCompatibility/RedundantDefaultTemplateArgCheck.h b/clang-tools-extra/clang-tidy/BSCompatibility/RedundantDefaultTemplateArgCheck.h new file mode 100644 index 0000000000000000000000000000000000000000..fff2afabef2113e0fa256de6c0f7b2a5e4fe497d --- /dev/null +++ b/clang-tools-extra/clang-tidy/BSCompatibility/RedundantDefaultTemplateArgCheck.h @@ -0,0 +1,30 @@ +//===--- RedundantDefaultTemplateArgCheck.h - clang-tidy --------*- 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_CLANG_TOOLS_EXTRA_CLANG_TIDY_BSCOMPATIBILITY_REDUNDANTDEFAULTTEMPLATEARGCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BSCOMPATIBILITY_REDUNDANTDEFAULTTEMPLATEARGCHECK_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::BSCompatibility { + +// Detect redundant default template arguments for template function. +class RedundantDefaultTemplateArgCheck : public ClangTidyCheck { +public: + RedundantDefaultTemplateArgCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + llvm::DenseMap FirstDefaultMap; +}; + +} // namespace clang::tidy::BSCompatibility + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BSCOMPATIBILITY_REDUNDANTDEFAULTTEMPLATEARGCHECK_H \ No newline at end of file diff --git a/clang-tools-extra/clang-tidy/BSCompatibility/ThreadStorageUnifyCheck.cpp b/clang-tools-extra/clang-tidy/BSCompatibility/ThreadStorageUnifyCheck.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a79b67c3a59585d82b3a37658239d12db2e2b873 --- /dev/null +++ b/clang-tools-extra/clang-tidy/BSCompatibility/ThreadStorageUnifyCheck.cpp @@ -0,0 +1,269 @@ +//===--- ThreadStorageUnifyCheck.cpp - clang-tidy -------------------------===// +// +// 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 "ThreadStorageUnifyCheck.h" + +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Lex/Lexer.h" +#include "llvm/ADT/SmallVector.h" + +using namespace clang; +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace BSCompatibility { + +void ThreadStorageUnifyCheck::registerMatchers(MatchFinder *Finder) { + // Broadly matches variable declarations stored in global storage; + // whether it is TLS is filtered in check() + Finder->addMatcher(varDecl(hasGlobalStorage(), hasThreadStorageDuration(), + unless(isExpansionInSystemHeader())) + .bind("vardecl"), + this); +} + +void ThreadStorageUnifyCheck::findTLSKeywordToken( + const VarDecl *VD, const SourceManager &SM, const LangOptions &LangOpts, + bool &HasThreadLocal, bool &HasGNUThread, SourceLocation &TokLoc, + bool &UnderMacro) { + HasThreadLocal = false; + HasGNUThread = false; + TokLoc = VD->getBeginLoc(); + UnderMacro = VD->getBeginLoc().isMacroID(); + + // Replacement in macros is riskier: diagnostics are still allowed, but the + // token position is not returned for fix-it + if (UnderMacro) + return; + + SourceLocation Begin = VD->getBeginLoc(); + SourceLocation Name = VD->getLocation(); + if (Begin.isInvalid() || Name.isInvalid()) + return; + + CharSourceRange Range = CharSourceRange::getCharRange(Begin, Name); + if (Range.isInvalid()) + return; + + StringRef Text = Lexer::getSourceText(Range, SM, LangOpts); + + // Directly search for the literal value to locate the + // starting position of the keyword + size_t pos = Text.find("thread_local"); + if (pos != StringRef::npos) { + HasThreadLocal = true; + TokLoc = Begin.getLocWithOffset(static_cast(pos)); + return; + } + + pos = Text.find("__thread"); + if (pos != StringRef::npos) { + HasGNUThread = true; + TokLoc = Begin.getLocWithOffset(static_cast(pos)); + return; + } +} + +ThreadStorageUnifyCheck::TargetKind +ThreadStorageUnifyCheck::decideTarget(ArrayRef Infos, bool InC, + const SourceManager &SM) { + if (InC) + return TargetKind::TL___thread; + + // Whenever non-extern (or defined) occurs, unify to __thread (GNU caliber) + for (const auto &I : Infos) { + if (!I.IsExtern || I.IsDefinition) + return TargetKind::TL___thread; + } + + // All are extern: the actual keyword written in the + // "last extern declaration in the source order" shall prevail + const DeclInfo *Last = nullptr; + for (const auto &I : Infos) { + if (!I.IsExtern) + continue; + if (!Last || SM.isBeforeInTranslationUnit(Last->VD->getBeginLoc(), + I.VD->getBeginLoc())) + Last = &I; + } + if (!Last) + return TargetKind::Unknown; + + if (Last->SpelledGNUThread) + return TargetKind::TL___thread; + if (Last->SpelledThreadLocal) + return TargetKind::TL_thread_local; + + // Fallback strategy: prefer __thread when unrecognizable (closer to GNU) + return TargetKind::TL___thread; +} + +static StringRef reasonToText(ThreadStorageUnifyCheck::UnifyReason R, + ThreadStorageUnifyCheck::TargetKind TK) { + switch (R) { + case ThreadStorageUnifyCheck::UnifyReason::NonExternOrDefinition: + return (TK == ThreadStorageUnifyCheck::TargetKind::TL___thread) + ? "unify to GNU '__thread' for non-extern or defined " + "thread-local variable" + : "unify to 'thread_local' for non-extern or defined " + "thread-local variable"; + case ThreadStorageUnifyCheck::UnifyReason::LastExtern: + return (TK == ThreadStorageUnifyCheck::TargetKind::TL___thread) + ? "unify to GNU '__thread' to match the last extern declaration" + : "unify to 'thread_local' to match the last extern declaration"; + case ThreadStorageUnifyCheck::UnifyReason::MatchRedecls: + return (TK == ThreadStorageUnifyCheck::TargetKind::TL___thread) + ? "unify to GNU '__thread' to match redeclarations" + : "unify to 'thread_local' to match redeclarations"; + default: + return (TK == ThreadStorageUnifyCheck::TargetKind::TL___thread) + ? "unify to GNU '__thread'" + : "unify to 'thread_local'"; + } +} + +void ThreadStorageUnifyCheck::applyFixes(ArrayRef Infos, + TargetKind TK, + const SourceManager & /*SM*/, + UnifyReason Reason, + DiagnosticBuilder *AttachTo) { + StringRef Want = + (TK == TargetKind::TL___thread) ? "__thread" : "thread_local"; + StringRef ReasonText = reasonToText(Reason, TK); + + for (const auto &I : Infos) { + if (I.SpelledUnderMacro) + continue; // no fix in macro + + auto needReplace = + (I.SpelledThreadLocal && TK == TargetKind::TL___thread) || + (I.SpelledGNUThread && TK == TargetKind::TL_thread_local); + if (!needReplace) + continue; + + // Calculate replacement interval + const int Len = + I.SpelledThreadLocal ? 12 : 8; // "thread_local" / "__thread" + auto SR = CharSourceRange::getCharRange(I.TLTokenLoc, + I.TLTokenLoc.getLocWithOffset(Len)); + auto Hint = FixItHint::CreateReplacement(SR, Want); + + if (AttachTo) { + // Non-mixed use scenarios: attach the fix to + // the "existing summary" warning + (*AttachTo) << Hint; + } else { + // Maintaining old behavior in mixed scenarios + diag(I.TLTokenLoc, ReasonText) << Hint; + } + } +} + +void ThreadStorageUnifyCheck::check(const MatchFinder::MatchResult &Result) { + const auto *VD = Result.Nodes.getNodeAs("vardecl"); + if (!VD) + return; + + // Only execute once on the specification declaration to avoid duplication + const VarDecl *Canon = VD->getCanonicalDecl(); + if (VD != Canon) + return; + + const SourceManager &SM = *Result.SourceManager; + const LangOptions &LO = Result.Context->getLangOpts(); + + llvm::SmallVector Infos; + for (const VarDecl *D : Canon->redecls()) { + DeclInfo I; + I.VD = D; + I.IsExtern = (D->getStorageClass() == SC_Extern); + I.IsDefinition = D->isThisDeclarationADefinition(); + findTLSKeywordToken(D, SM, LO, I.SpelledThreadLocal, I.SpelledGNUThread, + I.TLTokenLoc, I.SpelledUnderMacro); + Infos.push_back(I); + } + if (Infos.empty()) + return; + + const bool InC = !LO.CPlusPlus; + TargetKind TK = decideTarget(Infos, InC, SM); + if (TK == TargetKind::Unknown) + return; + + // Determine if already consistent with the target spelling + bool AllOk = true; + bool SeenTL = false, SeenGNU = false; + bool HasNonExternOrDef = false; + for (const auto &I : Infos) { + SeenTL |= I.SpelledThreadLocal; + SeenGNU |= I.SpelledGNUThread; + HasNonExternOrDef |= (!I.IsExtern || I.IsDefinition); + if (TK == TargetKind::TL___thread && !I.SpelledGNUThread) { + AllOk = false; + } + if (TK == TargetKind::TL_thread_local && !I.SpelledThreadLocal) { + AllOk = false; + } + } + if (AllOk) + return; + + // Decide reason for unification (affects wording) + UnifyReason Reason = UnifyReason::Unknown; + if (SeenTL && SeenGNU) { + // Mixed spellings across redeclarations. + // If non-extern/def exists and target is __thread, make that explicit. + if (HasNonExternOrDef && TK == TargetKind::TL___thread) + Reason = UnifyReason::NonExternOrDefinition; + else + Reason = (TK == TargetKind::TL_thread_local) ? UnifyReason::LastExtern + : UnifyReason::MatchRedecls; + + // 1) summary + if (TK == TargetKind::TL___thread) { + diag(Infos.front().VD->getLocation(), + "mixed use of '__thread' and 'thread_local' for the same variable; " + "unifying to GNU '__thread'"); + } else { + diag(Infos.front().VD->getLocation(), + "mixed use of '__thread' and 'thread_local' for the same variable; " + "unifying to 'thread_local' per the last extern declaration"); + } + // 2) Send unify everywhere + applyFixes(Infos, TK, SM, Reason, /*AttachTo=*/nullptr); + } else { + // Not mixed, but still needs change (e.g. single non-extern 'thread_local') + if (HasNonExternOrDef && TK == TargetKind::TL___thread) + Reason = UnifyReason::NonExternOrDefinition; + else + Reason = UnifyReason::LastExtern; // safe default for extern-only cases + + // Non-mixed: only one summary is created and all the FixIts + // above are attached to it. + StringRef Head = reasonToText(Reason, TK); + const DeclInfo *Anchor = nullptr; + for (const auto &I : Infos) { + if ((TK == TargetKind::TL___thread && I.SpelledThreadLocal) || + (TK == TargetKind::TL_thread_local && I.SpelledGNUThread)) { + Anchor = &I; + break; + } + } + if (!Anchor) + Anchor = &Infos.front(); + + auto DB = diag(Anchor->VD->getLocation(), Head); + applyFixes(Infos, TK, SM, Reason, /*AttachTo=*/&DB); + } +} + +} // namespace BSCompatibility +} // namespace tidy +} // namespace clang \ No newline at end of file diff --git a/clang-tools-extra/clang-tidy/BSCompatibility/ThreadStorageUnifyCheck.h b/clang-tools-extra/clang-tidy/BSCompatibility/ThreadStorageUnifyCheck.h new file mode 100644 index 0000000000000000000000000000000000000000..797f82a6c0f823959e24e3070b6267aef247e7f9 --- /dev/null +++ b/clang-tools-extra/clang-tidy/BSCompatibility/ThreadStorageUnifyCheck.h @@ -0,0 +1,63 @@ +//===--- ThreadStorageUnifyCheck.h - clang-tidy -----------------*- 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_CLANG_TOOLS_EXTRA_CLANG_TIDY_BSCOMPATIBILITY_THREAD_STORAGE_UNIFY_CHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BSCOMPATIBILITY_THREAD_STORAGE_UNIFY_CHECK_H + +#include "../ClangTidyCheck.h" + +namespace clang { + +class DiagnosticBuilder; +namespace tidy { +namespace BSCompatibility { + +class ThreadStorageUnifyCheck : public ClangTidyCheck { +public: + ThreadStorageUnifyCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + + enum class TargetKind { Unknown, TL_thread_local, TL___thread }; + struct DeclInfo { + const VarDecl *VD = nullptr; + bool IsExtern = false; + bool IsDefinition = false; + bool SpelledThreadLocal = false; + bool SpelledUnderMacro = false; + SourceLocation TLTokenLoc; + bool SpelledGNUThread = false; + }; + + // the reason to distinct the warning message + enum class UnifyReason { + MatchRedecls, + LastExtern, + NonExternOrDefinition, + Unknown + }; + + static void findTLSKeywordToken(const VarDecl *VD, const SourceManager &SM, + const LangOptions &LangOpts, + bool &HasThreadLocal, bool &HasGNUThread, + SourceLocation &TokLoc, bool &UnderMacro); + + static TargetKind decideTarget(ArrayRef Infos, bool InC, + const SourceManager &SM); + + void applyFixes(ArrayRef Infos, TargetKind TK, + const SourceManager &SM, UnifyReason Reason, + DiagnosticBuilder *AttachTo = nullptr); +}; + +} // namespace BSCompatibility +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BSCOMPATIBILITY_THREADSTORAGEUNIFYCHECK_H \ No newline at end of file diff --git a/clang-tools-extra/clang-tidy/BSCompatibility/UnsequencedFunctionParameterCheck.cpp b/clang-tools-extra/clang-tidy/BSCompatibility/UnsequencedFunctionParameterCheck.cpp new file mode 100644 index 0000000000000000000000000000000000000000..de8c9e2fdada8a2c581645f7c299146fcb058346 --- /dev/null +++ b/clang-tools-extra/clang-tidy/BSCompatibility/UnsequencedFunctionParameterCheck.cpp @@ -0,0 +1,165 @@ +//===--- NonVoidFunctionReturnVoidCheck.cpp - clang-tidy ------------------===// +// +// 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 "UnsequencedFunctionParameterCheck.h" +#include "clang/AST/AST.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Expr.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::BSCompatibility { + +void UnsequencedFunctionParameterCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher(callExpr(hasAnyArgument(callExpr())).bind("callee"), this); +} + +void UnsequencedFunctionParameterCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *Callee = Result.Nodes.getNodeAs("callee"); + if (!Callee || Callee->getNumArgs() < 2) + return; + + ASTContext *Context = Result.Context; + SourceManager &SM = *Result.SourceManager; + const LangOptions &LangOpts = Context->getLangOpts(); + + std::vector CallArgs; + std::vector FixIts; + + for (size_t i = 0; i < Callee->getNumArgs(); ++i) { + if (const auto *CallArg = dyn_cast(Callee->getArg(i))) { + CallArgs.push_back(CallArg); + } + } + + if (CallArgs.size() < 2) + return; + + std::string TempDecls = "\n"; + std::vector TempVarNames; + int TempCounter = 0; + + // Get the location for variable insertions. + SourceLocation InsertLoc = findSafeInsertionPoint(Callee, SM, *Context); + + // Create insertions. + for (const CallExpr *Call : CallArgs) { + std::string VarName = (Twine("__temp_") + Twine(GlobalTempCounter) + + Twine("_") + Twine(TempCounter++)) + .str(); + TempVarNames.push_back(VarName); + + QualType ReturnType = Call->getType(); + std::string TypeStr = ReturnType.getAsString(); + + SourceRange CallRange = Call->getSourceRange(); + std::string CallText = + Lexer::getSourceText(CharSourceRange::getTokenRange(CallRange), SM, + LangOpts) + .str(); + + TempDecls += "\t" + TypeStr + " " + VarName + " = " + CallText + ";\n"; + } + + if (!TempDecls.empty()) { + FixIts.push_back(FixItHint::CreateInsertion(InsertLoc, TempDecls)); + } + + // Create replacements. + TempCounter = 0; + GlobalTempCounter++; + for (size_t i = 0; i < Callee->getNumArgs(); ++i) { + if (isa(Callee->getArg(i))) { + const Expr *Arg = Callee->getArg(i); + FixIts.push_back(FixItHint::CreateReplacement( + Arg->getSourceRange(), TempVarNames[TempCounter++])); + } + } + + // Print diaginfo. + auto Diag = + diag(Callee->getBeginLoc(), "Function calls as arguments are unsequenced " + "and may cause dependency issues"); + + for (const FixItHint &Fix : FixIts) { + Diag << Fix; + } +} + +SourceLocation UnsequencedFunctionParameterCheck::findSafeInsertionPoint( + const CallExpr *Callee, SourceManager &SM, ASTContext &Context) { + if (!Callee) return SourceLocation(); + + SourceLocation CalleeStart = Callee->getBeginLoc(); + if (CalleeStart.isInvalid()) return SourceLocation(); + + FileID FID = SM.getFileID(CalleeStart); + SourceLocation FileStart = SM.getLocForStartOfFile(FID); + + // Get source code before Callee. + const char *CalleePtr = SM.getCharacterData(CalleeStart); + const char *FilePtr = SM.getCharacterData(FileStart); + if (!CalleePtr || !FilePtr) return SourceLocation(); + + // Find the position after last ";", "{" or "}". + const char *SearchPtr = CalleePtr - 1; + while (SearchPtr >= FilePtr) { + if (*SearchPtr == ';' || *SearchPtr == '{' || *SearchPtr == '}') { + break; + } + SearchPtr--; + } + SourceLocation FoundLoc; + if (SearchPtr >= FilePtr) { + FoundLoc = FileStart.getLocWithOffset(SearchPtr - FilePtr); + } else { + FoundLoc = FileStart; + } + const char *AfterSemiBrace = SearchPtr + 1; + while (*AfterSemiBrace && isspace(*AfterSemiBrace)) { + AfterSemiBrace++; + } + + SourceLocation LastStatementEnd = + FileStart.getLocWithOffset(SearchPtr - FilePtr); + SourceLocation StatementStart = + FileStart.getLocWithOffset(AfterSemiBrace - FilePtr); + // Check if last statment ends in the same line as this statment start. + if (SM.getSpellingLineNumber(LastStatementEnd) != + SM.getSpellingLineNumber(StatementStart)) + return findPreviousLineEnd(StatementStart, SM); + else + return StatementStart; +} + +SourceLocation +UnsequencedFunctionParameterCheck::findPreviousLineEnd(SourceLocation Loc, + SourceManager &SM) { + if (Loc.isInvalid()) + return Loc; + + unsigned Line = SM.getSpellingLineNumber(Loc); + if (Line <= 1) { + return SM.getLocForStartOfFile(SM.getFileID(Loc)); + } + + FileID FID = SM.getFileID(Loc); + SourceLocation PreviousLine = SM.translateLineCol(FID, Line - 1, 1); + SourceLocation CurrentLine = SM.translateLineCol(FID, Line, 1); + if (PreviousLine.isInvalid() || CurrentLine.isInvalid()) { + return Loc; + } + + SourceLocation Ret = CurrentLine.getLocWithOffset(-1); + return Ret.isValid() ? Ret : Loc; +} + +} // namespace clang::tidy::BSCompatibility diff --git a/clang-tools-extra/clang-tidy/BSCompatibility/UnsequencedFunctionParameterCheck.h b/clang-tools-extra/clang-tidy/BSCompatibility/UnsequencedFunctionParameterCheck.h new file mode 100644 index 0000000000000000000000000000000000000000..9ab9e4135cc2e602affa2266673f35a3b8dfe2ba --- /dev/null +++ b/clang-tools-extra/clang-tidy/BSCompatibility/UnsequencedFunctionParameterCheck.h @@ -0,0 +1,32 @@ +//===--- UnsequencedFunctionParameterCheck.h - clang-tidy -------*- 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_CLANG_TOOLS_EXTRA_CLANG_TIDY_BSCOMPATIBILITY_UNSEQUENCEDFUNCTIONPARAMETERCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BSCOMPATIBILITY_UNSEQUENCEDFUNCTIONPARAMETERCHECK_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::BSCompatibility { + +/// Detect multiple function parameters and provides suggestions for extracting +/// parameters from the function call. +class UnsequencedFunctionParameterCheck : public ClangTidyCheck { +public: + UnsequencedFunctionParameterCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + int GlobalTempCounter = 0; + SourceLocation findSafeInsertionPoint(const CallExpr *Callee, + SourceManager &SM, ASTContext &Context); + SourceLocation findPreviousLineEnd(SourceLocation Loc, SourceManager &SM); +}; + +} // namespace clang::tidy::BSCompatibility + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BSCOMPATIBILITY_UNSEQUENCEDFUNCTIONPARAMETERCHECK_H diff --git a/clang-tools-extra/clang-tidy/CMakeLists.txt b/clang-tools-extra/clang-tidy/CMakeLists.txt index 12dabc34421fd9ebef9b79c3d1e3adf906d030fe..37c3dfd97d0729434bc1482437979aeef5c5ed99 100644 --- a/clang-tools-extra/clang-tidy/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/CMakeLists.txt @@ -55,6 +55,7 @@ add_subdirectory(abseil) add_subdirectory(altera) add_subdirectory(boost) add_subdirectory(bugprone) +add_subdirectory(BSCompatibility) add_subdirectory(cert) add_subdirectory(concurrency) add_subdirectory(cppcoreguidelines) @@ -82,6 +83,7 @@ set(ALL_CLANG_TIDY_CHECKS clangTidyAlteraModule clangTidyBoostModule clangTidyBugproneModule + clangTidyBSCompatibilityModule clangTidyCERTModule clangTidyConcurrencyModule clangTidyCppCoreGuidelinesModule diff --git a/clang-tools-extra/clang-tidy/ClangTidy.cpp b/clang-tools-extra/clang-tidy/ClangTidy.cpp index bdead368195e3324a08c93b089714ebb78ab8cc2..9c2ec653f8b761ecc8e3cc1baf6fbd6bb3ca5bfa 100644 --- a/clang-tools-extra/clang-tidy/ClangTidy.cpp +++ b/clang-tools-extra/clang-tidy/ClangTidy.cpp @@ -629,6 +629,25 @@ void exportReplacements(const llvm::StringRef MainFilePath, YAML << TUD; } +void exportDetails(const llvm::StringRef MainFilePath, + const std::vector &Errors, + raw_ostream &OS) { + TranslationUnitDiagnostics TUD; + TUD.MainSourceFile = std::string(MainFilePath); + for (auto &Error : Errors) { + tooling::Diagnostic Diag = Error; + if (Error.IsWarningAsError) + Diag.DiagLevel = tooling::Diagnostic::Error; + Diag.Message.isDetail = true; + for (auto &range : Diag.Message.Ranges) + range.isDetail = true; + TUD.Diagnostics.insert(TUD.Diagnostics.end(), Diag); + } + + yaml::Output YAML(OS); + YAML << TUD; +} + NamesAndOptions getAllChecksAndOptions(bool AllowEnablingAnalyzerAlphaCheckers) { NamesAndOptions Result; diff --git a/clang-tools-extra/clang-tidy/ClangTidy.h b/clang-tools-extra/clang-tidy/ClangTidy.h index 51d9e226c7946557cfb19bdfff560d2fcbff3c67..08103156c19ca52a7464a357b453735123594b74 100644 --- a/clang-tools-extra/clang-tidy/ClangTidy.h +++ b/clang-tools-extra/clang-tidy/ClangTidy.h @@ -119,6 +119,12 @@ void exportReplacements(StringRef MainFilePath, const std::vector &Errors, raw_ostream &OS); +/// Serializes Details into YAML and writes them to the specified +/// output stream. +void exportDetails(StringRef MainFilePath, + const std::vector &Errors, + raw_ostream &OS); + } // end namespace tidy } // end namespace clang diff --git a/clang-tools-extra/clang-tidy/ClangTidyForceLinker.h b/clang-tools-extra/clang-tidy/ClangTidyForceLinker.h index adde9136ff1dd5de54a3f7eb976cf2d2494d344b..b7ced289410d41a0b0c2d9dc74380721964969e7 100644 --- a/clang-tools-extra/clang-tidy/ClangTidyForceLinker.h +++ b/clang-tools-extra/clang-tidy/ClangTidyForceLinker.h @@ -39,6 +39,11 @@ extern volatile int BugproneModuleAnchorSource; static int LLVM_ATTRIBUTE_UNUSED BugproneModuleAnchorDestination = BugproneModuleAnchorSource; +// This anchor is used to force the linker to link the BSCompatibilityModule. +extern volatile int BSCompatibilityModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED BSCompatibilityModuleAnchorDestination = + BSCompatibilityModuleAnchorSource; + // This anchor is used to force the linker to link the CERTModule. extern volatile int CERTModuleAnchorSource; static int LLVM_ATTRIBUTE_UNUSED CERTModuleAnchorDestination = diff --git a/clang-tools-extra/clang-tidy/tool/CMakeLists.txt b/clang-tools-extra/clang-tidy/tool/CMakeLists.txt index 3ce552872015e054230a4b4f8972fe8dd5c77dc4..43886d57ca5a267a9c7f012a5db9c68d94a2f149 100644 --- a/clang-tools-extra/clang-tidy/tool/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/tool/CMakeLists.txt @@ -67,3 +67,8 @@ install(PROGRAMS run-clang-tidy.py DESTINATION "${CMAKE_INSTALL_BINDIR}" COMPONENT clang-tidy RENAME run-clang-tidy) +if(LLVM_BSPUB_COMMON) + install(PROGRAMS clang-tidy-stats.py + DESTINATION "${CMAKE_INSTALL_DATADIR}/clang" + COMPONENT clang-tidy) +endif() diff --git a/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp b/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp index 74340e1b06cb001331c42753598bcefd25d6e1fe..a7264bb126d03920fe11354b938fe765eeabefac 100644 --- a/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp +++ b/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp @@ -271,6 +271,12 @@ code with clang-apply-replacements. cl::value_desc("filename"), cl::cat(ClangTidyCategory)); +static cl::opt ExportDetails("export-details", desc(R"( +YAML file to store the details of errors. +)"), + cl::value_desc("filename"), + cl::cat(ClangTidyCategory)); + static cl::opt Quiet("quiet", desc(R"( Run clang-tidy in quiet mode. This suppresses printing statistics about ignored warnings and @@ -689,6 +695,16 @@ int clangTidyMain(int argc, const char **argv) { exportReplacements(FilePath.str(), Errors, OS); } + if (!ExportDetails.empty() && !Errors.empty()) { + std::error_code EC; + llvm::raw_fd_ostream OS(ExportDetails, EC, llvm::sys::fs::OF_None); + if (EC) { + llvm::errs() << "Error opening output file: " << EC.message() << '\n'; + return 1; + } + exportDetails(FilePath.str(), Errors, OS); + } + if (!Quiet) { printStats(Context.getStats()); if (DisableFixes && Behaviour != FB_NoFix) diff --git a/clang-tools-extra/clang-tidy/tool/clang-tidy-stats.py b/clang-tools-extra/clang-tidy/tool/clang-tidy-stats.py new file mode 100755 index 0000000000000000000000000000000000000000..f1e2e7f11659f8ed0f99f3337964943d3f756908 --- /dev/null +++ b/clang-tools-extra/clang-tidy/tool/clang-tidy-stats.py @@ -0,0 +1,275 @@ +#!/usr/bin/env python3 + +import argparse +import os +import subprocess +import sys +import tempfile +import yaml +import pandas as pd + + +class DiagTranslator(object): + def __init__(self, diag): + self.FixesMaxWidth = 0 + self.Level = diag["Level"] + self.DiagnosticName = diag["DiagnosticName"] + self.FilePath = diag["DiagnosticMessage"]["FilePath"] + self.Line = diag["DiagnosticMessage"]["FileLine"] + self.Col = diag["DiagnosticMessage"]["FileCol"] + self.Message = diag["DiagnosticMessage"]["Message"] + self.Fixes = None + self.getFixes(diag["DiagnosticMessage"]["Replacements"]) + if "Ranges" in diag["DiagnosticMessage"]: + ranges = diag["DiagnosticMessage"]["Ranges"] + else: + ranges = [] + self.Detail = None + self.DetailsMaxWidth = 0 + self.mergeDetail(diag["DiagnosticMessage"]["WholeText"], ranges, diag["DiagnosticMessage"]["MainLine"]) + + def mergeDetail(self, text, ranges, mainline): + text = text.rstrip().split("\n") + base = dict() + baseline = self.Line - mainline + for index, value in enumerate(text): + base[baseline + index + 1] = value + point = " " * (self.Col - 1) + "^" + point = point.ljust(len(base[self.Line])) + points = dict() + points[self.Line] = point + + for Range in ranges: + Range["WholeText"] = Range["WholeText"].rstrip().split("\n") + baseline = Range["FileLine"] - Range["MainLine"] + for i, value in enumerate(Range["WholeText"]): + index = baseline + i + 1 + if index not in base: + base[index] = value + if Range["MainLine"] in base: + if Range["FileLine"] not in points: + points[Range["FileLine"]] = " " * len(base[Range["FileLine"]]) + points[Range["FileLine"]] = (points[Range["FileLine"]][:Range["FileCol"] - 1] + + "~" * Range["Length"] + + points[Range["FileLine"]][Range["FileCol"] + Range["Length"] - 1:]) + else: + point = " " * (Range["FileCol"] - 1) + "~" * Range["Length"] + point = point.ljust(len(base[Range["FileLine"]])) + points[Range["FileLine"]] = point + + Detail = "" + for_i = sorted(base.keys()) + align = len(str(for_i[-1])) + 1 + last = 0 + DetailsMaxWidth = 0 + for index in for_i: + if not (last == 0 or index == last + 1): + Detail += " " + "-" * (DetailsMaxWidth - 3) + " \n" + last = index + lineNo = str(index) + baseText = " " * (align - len(lineNo)) + lineNo + " | " + base[index] + "\n" + Detail += baseText + DetailsMaxWidth = max(DetailsMaxWidth, len(baseText)) + if index in points: + Detail += " " * align + " | " + points[index] + "\n" + + self.DetailsMaxWidth = DetailsMaxWidth + self.Detail = "\n" + Detail + + def getFixes(self, replacements): + fixes = "" + FixesMaxWidth = 0 + for replacement in replacements: + fix = ReplacementTranslator(replacement) + fileText = "\'" + fix.WholeLineText + "\' in line " + str(fix.FileLine) + " of " + fix.FilePath + if fix.Type == "insert": + text = "Insert \'" + fix.ReplacementText + "\' to " + fixes += text + fileText + "\n" + " " * (len(text) + fix.FileCol) + "^\n" + elif fix.Type == "remove": + text = "Remove \'" + fix.OriginalText + "\' from " + fixes += text + fileText + "\n" + " " * (len(text) + fix.FileCol) + "~" * fix.Length + "\n" + else: + text = "Replace \'" + fix.OriginalText + "\' with \'" + fix.ReplacementText + "\' from " + fixes += text + fileText + "\n" + " " * (len(text) + fix.FileCol) + "~" * fix.Length + "\n" + FixesMaxWidth = max(FixesMaxWidth, len(text + fileText)) + + self.FixesMaxWidth = FixesMaxWidth + self.Fixes = fixes + + +class ReplacementTranslator(object): + def __init__(self, replacement): + self.OriginalText = replacement["OriginalText"] + self.ReplacementText = replacement["ReplacementText"] + self.WholeLineText = replacement["WholeLineText"].rstrip() + self.Length = replacement["Length"] + self.FileLine = replacement["FileLine"] + self.FileCol = replacement["FileCol"] + self.FilePath = replacement["FilePath"] + if self.Length == 0: + self.Type = "insert" + elif self.ReplacementText == '': + self.Type = "remove" + else: + self.Type = "replace" + + +def stats(yaml_file, output_file): + try: + with open(yaml_file, encoding='utf-8') as infile: + Diagnostics = yaml.safe_load(infile) + Diagnostics = Diagnostics["Diagnostics"] + except FileNotFoundError as e: + print("File \"" + yaml_file + "\" not found") + return + except TypeError as e: + print("No errors to be collected.\n") + return + + details = [] + stats = {} + total_stats = {"Error": 0, "Warning": 0, "Remark": 0} + FixesMaxWidth = 0 + DetailsMaxWidth = 0 + + for diag in Diagnostics: + diagTrans = DiagTranslator(diag) + FixesMaxWidth = max(FixesMaxWidth, diagTrans.FixesMaxWidth) + DetailsMaxWidth = max(DetailsMaxWidth, diagTrans.DetailsMaxWidth) + + total_stats[diagTrans.Level] += 1 + if diagTrans.FilePath in stats: + if diagTrans.Level in stats[diagTrans.FilePath]: + stats[diagTrans.FilePath][diagTrans.Level] += 1 + else: + stats[diagTrans.FilePath][diagTrans.Level] = 1 + else: + stats[diagTrans.FilePath] = {"Error": 0, "Warning": 0, "Remark": 0} + stats[diagTrans.FilePath][diagTrans.Level] += 1 + + details.append({"Level": diagTrans.Level, "DiagnosticName": diagTrans.DiagnosticName, + "FilePath": diagTrans.FilePath, "Line": diagTrans.Line, "Col": diagTrans.Col, + "Message": diagTrans.Message, + "Detail": diagTrans.Detail, "Fixes": diagTrans.Fixes}) + + statsExcel = [["TotalHandle:", "TotalError", "TotalWarning"], + [total_stats["Error"] + total_stats["Warning"], + total_stats["Error"], total_stats["Warning"]], + [], ["FileName", "Error", "Warning"]] + for key, value in stats.items(): + statsExcel.append([key, value["Error"], value["Warning"]]) + + with pd.ExcelWriter(output_file, engine='xlsxwriter') as writer: + df = pd.DataFrame(details) + df.to_excel(writer, sheet_name='Details', index=False) + df = pd.DataFrame(statsExcel) + df.to_excel(writer, sheet_name='Stats', index=False, header=False) + + wb = writer.book + wrap = wb.add_format({'text_wrap': True, 'align': 'left', 'valign': 'vcenter'}) + alignCenter = wb.add_format({'align': 'center', 'valign': 'vcenter'}) + fileName = wb.add_format({'bold': True, 'align': 'center', 'valign': 'vcenter'}) + allStyle = wb.add_format({'border': 1}) + errorColor = wb.add_format({'bg_color': '#C00000'}) + warningColor = wb.add_format({'bg_color': '#FFFF00'}) + titleColor = wb.add_format({'bg_color': '#00B0F0'}) + bgColor = wb.add_format({'bg_color': '#EBF1DE'}) + + ws1 = writer.sheets['Details'] + ws1.set_column('A:A', 8, alignCenter) + ws1.set_column('B:C', 30, wrap) + ws1.set_column('D:E', 5, alignCenter) + ws1.set_column('F:F', 30, wrap) + if DetailsMaxWidth < 8: + DetailsMaxWidth = 8 + if FixesMaxWidth < 8: + FixesMaxWidth = 8 + ws1.set_column('G:G', DetailsMaxWidth + 2, wrap) + ws1.set_column('H:H', FixesMaxWidth + 2, wrap) + ws1.conditional_format('A1:H%d' % (len(details) + 1), {'type': 'formula', 'criteria': '=TRUE()', 'format': allStyle}) + ws1.conditional_format('A1:H1', {'type': 'no_blanks', 'format': titleColor}) + ws1.conditional_format('A2:H%d' % (len(details) + 1), {'type': 'formula', 'criteria': '=TRUE()', 'format': bgColor}) + + ws2 = writer.sheets['Stats'] + ws2.set_column('A:A', df.iloc[:, 0].astype(str).apply(lambda x: len(x)).max() + 5, fileName) + ws2.set_column('B:C', 15, alignCenter) + ws2.conditional_format('B1', {'type': 'no_blanks', 'format': errorColor}) + ws2.conditional_format('B4', {'type': 'no_blanks', 'format': errorColor}) + ws2.conditional_format('C1', {'type': 'no_blanks', 'format': warningColor}) + ws2.conditional_format('C4', {'type': 'no_blanks', 'format': warningColor}) + print("Done! output to " + output_file + "\n") + + +def main(): + parser = argparse.ArgumentParser( + description="Run clang-tidy and " + "output diagnostics stats. " + ) + parser.add_argument( + "-clang-tidy-binary", + metavar="PATH", + default="clang-tidy", + help="path to clang-tidy binary", + ) + parser.add_argument( + "-export-stats", + metavar="FILE", + dest="export_stats", + default="stats.xlsx", + help="Create an excel file to store all report and stats.", + ) + parser.add_argument( + "-export-stats-input", + metavar="FILE", + dest="export_stats_input", + default="fix.yaml", + help="Get the yaml file which from export-details.", + ) + + clang_tidy_args = [] + argv = sys.argv[1:] + if "--args" in argv: + clang_tidy_args.extend(argv[: argv.index("--args")]) + argv = argv[argv.index("--args") + 1:] + else: + clang_tidy_args = argv + argv = [] + + args = parser.parse_args(argv) + + if not clang_tidy_args: + stats(args.export_stats_input, args.export_stats) + return + + yaml_file = None + for arg in clang_tidy_args: + if arg.find("-export-details=") != -1: + yaml_file = arg[arg.find("-export-details=") + len("-export-details="):] + break + + command = [args.clang_tidy_binary] + if not yaml_file: + (handle, yaml_file) = tempfile.mkstemp(suffix=".yaml") + os.close(handle) + command.append("--export-details=" + yaml_file) + command.extend(clang_tidy_args) + + try: + proc = subprocess.Popen( + command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True + ) + stdout, stderr = proc.communicate() + + sys.stdout.write(stdout + "\n") + sys.stdout.flush() + if stderr: + sys.stderr.write(stderr + "\n") + sys.stderr.flush() + except Exception as e: + sys.stderr.write("Failed: " + str(e) + ": ".join(command) + "\n") + + stats(yaml_file, args.export_stats) + + +if __name__ == "__main__": + main() diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst index 236226e8b73a941077ab1a2cc845a246611ffe73..3aed580fc755c2a793a327c3555405ac02243718 100644 --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -117,6 +117,43 @@ Improvements to clang-tidy New checks ^^^^^^^^^^ +- New :doc:`BSCompatibility-dependent-template-keyword + ` check. + + Detect if template is missing when calling a dependent template function + and add template keyword if so. + +- New :doc:`BSCompatibility-forbidden-builtin-exit + ` check. + + Detect the use of __builtin_exit and propose warning. + +- New :doc:`BSCompatibility-move-explicit-instantiation-after-defs + ` check. + + Detect the explicit instantiation before any defs and move it after the last def. + +- New :doc:`BSCompatibility-non-void-function-return-void + ` check. + + Check if a non-void function hasn't return statement. + +- New :doc:`BSCompatibility-redundant-default-template-arg + ` check. + + Detect redundant default template arguments for template function. + +- New :doc:`BSCompatibility-thread-storage-unify + ` check. + + detect the mixed use of __thread and thread_local. + +- New :doc:`BSCompatibility-unsequenced-function-parameter + ` check. + + Detect multiple function parameters and provides suggestions for extracting + parameters from the function call. + - New :doc:`bugprone-empty-catch ` check. diff --git a/clang-tools-extra/docs/clang-tidy/checks/BSCompatibility/dependent-template-keyword.rst b/clang-tools-extra/docs/clang-tidy/checks/BSCompatibility/dependent-template-keyword.rst new file mode 100644 index 0000000000000000000000000000000000000000..ece1e118835d11fb17a0ac55b1a2fe975583f607 --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/BSCompatibility/dependent-template-keyword.rst @@ -0,0 +1,27 @@ +.. title:: clang-tidy - BSCompatibility-dependent-template-keyword + +BSCompatibility-dependent-template-keyword +========================================== + +When calling a dependent template function, keyword template is +needed. e.g. + +class A { +public: + template void As(); + static A *FromWebContents(); + A *FromWebContents2(); +}; +template class B : A { + void FromWebContents() { + auto guest = A::FromWebContents(); + guest ? guest->As() : nullptr; + auto guest2 = A::FromWebContents2(); + guest2 ? guest2->As() : nullptr; + } +}; + +clang will report error unless adding a template before guest2, +while g++ dont have this issue. +this check detect every ways to call function,through .,->,:: and will +add template before them. \ No newline at end of file diff --git a/clang-tools-extra/docs/clang-tidy/checks/BSCompatibility/forbidden-builtin-exit.rst b/clang-tools-extra/docs/clang-tidy/checks/BSCompatibility/forbidden-builtin-exit.rst new file mode 100644 index 0000000000000000000000000000000000000000..a272f8aa3843795e79a6e7d8e38d0afbbfa4329e --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/BSCompatibility/forbidden-builtin-exit.rst @@ -0,0 +1,17 @@ +.. title:: clang-tidy - BSCompatibility-forbidden-builtin-exit + +BSCompatibility-forbidden-builtin-exit +====================================== + +Detect the use of __builtin_exit, which is not supported by clang.and +propose warning. +e.g. +void catchEx() { + __builtin_exit(0); + try { + } catch (int) { + } +} + +int main() { __builtin_exit(1); } +warning:__builtin_exit is not supported by Clang; you may use std::exit() \ No newline at end of file diff --git a/clang-tools-extra/docs/clang-tidy/checks/BSCompatibility/move-explicit-instantiation-after-defs.rst b/clang-tools-extra/docs/clang-tidy/checks/BSCompatibility/move-explicit-instantiation-after-defs.rst new file mode 100644 index 0000000000000000000000000000000000000000..10577299fba23f760b898e1fbe101fa8b334b435 --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/BSCompatibility/move-explicit-instantiation-after-defs.rst @@ -0,0 +1,19 @@ +.. title:: clang-tidy - BSCompatibility-move-explicit-instantiation-after-defs + +BSCompatibility-move-explicit-instantiation-after-defs +====================================================== + +detect the explicit instantiation before defs and fix it. +e.g. +template class Wrapper { + public: + Wrapper() = default; + Wrapper(const T &t) : x(t) {} + void print(); + private: + T x; +}; +template class Wrapper; // will be moved +template void Wrapper::print() { +} +// will be move to here diff --git a/clang-tools-extra/docs/clang-tidy/checks/BSCompatibility/non-void-function-return-void.rst b/clang-tools-extra/docs/clang-tidy/checks/BSCompatibility/non-void-function-return-void.rst new file mode 100644 index 0000000000000000000000000000000000000000..10d7ec667ca92df376d4d14bb54d0459e95f2408 --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/BSCompatibility/non-void-function-return-void.rst @@ -0,0 +1,11 @@ +.. title:: clang-tidy - BSCompatibility-non-void-function-return-void + +BSCompatibility-non-void-function-return-void +============================================= + +This check detects the issue that a nonvoid function return void. +e.g. +int f(){} +// CHECK-MESSAGES: :[[@LINE-1]]:9: warning: Non-void function does not return a val. [BSCompatibility-non-void-function-return-void] + +A warning will be proposed. \ No newline at end of file diff --git a/clang-tools-extra/docs/clang-tidy/checks/BSCompatibility/redundant-default-template-arg.rst b/clang-tools-extra/docs/clang-tidy/checks/BSCompatibility/redundant-default-template-arg.rst new file mode 100644 index 0000000000000000000000000000000000000000..4b827da1c9bd3825a45d07336a6c27cf661d15fc --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/BSCompatibility/redundant-default-template-arg.rst @@ -0,0 +1,16 @@ +.. title:: clang-tidy - BSCompatibility-redundant-default-template-arg + +BSCompatibility-redundant-default-template-arg +============================================== + +when there are redundant default template arguments for template function, +clang will raise an error while gcc will accept it, using the first default arg. +This check detect the issue and will fix it by gcc rules. + +e.g. +template void printSize(void); + +template +void printSize(); + +delete the "= double" \ No newline at end of file diff --git a/clang-tools-extra/docs/clang-tidy/checks/BSCompatibility/thread-storage-unify.rst b/clang-tools-extra/docs/clang-tidy/checks/BSCompatibility/thread-storage-unify.rst new file mode 100644 index 0000000000000000000000000000000000000000..857d26dd422573659ebaf66ab0b29337c3f61fc8 --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/BSCompatibility/thread-storage-unify.rst @@ -0,0 +1,10 @@ +.. title:: clang-tidy - BSCompatibility-thread-storage-unify + +BSCompatibility-thread-storage-unify +==================================== + +detect the mixed use of __thread and thread_local and fix it. +e.g. +extern thread_local long test;// FIXES: extern __thread long test; + +__thread long test; diff --git a/clang-tools-extra/docs/clang-tidy/checks/BSCompatibility/unsequenced-function-parameter.rst b/clang-tools-extra/docs/clang-tidy/checks/BSCompatibility/unsequenced-function-parameter.rst new file mode 100644 index 0000000000000000000000000000000000000000..60bcd5ce95f3220766547ed568502f6c8462aa30 --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/BSCompatibility/unsequenced-function-parameter.rst @@ -0,0 +1,50 @@ +.. title:: clang-tidy - BSCompatibility-unsequenced-function-parameter + +BSCompatibility-unsequenced-function-parameter +============================================== + +Function calls as arguments are unsequenced and may cause dependency +issues. e.g. + +```c +int global_value = 0; + +int f1() { + global_value += 10; + std::cout << "f1 called, global_value=" << global_value << std::endl; + return 1; +} + +int f2() { + global_value *= 2; + std::cout << "f2 called, global_value=" << global_value << std::endl; + return 2; +} + +int add(int a, int b) { + return b; +} + +int main() { + int b = add(f1(), f2()); + return 0; +} +``` +in this case, the results of gcc and clang differ. + +``` +clang++ test.cpp && ./a.out +f1 called, global_value=10 +f2 called, global_value=20 + +g++ test.cpp && ./a.out # on x86 +f2 called, global_value=0 +f1 called, global_value=10 +``` + +According to the C++ standard, the order of execution of function +parameters is not guaranteed, and multiple function parameters with +side effects result in undefined behavior. + +This check detect multiple function parameters and provides +suggestions for extracting parameters from the function call. diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst index af147db71eb726d9efb26c65de9ac776066e586e..1c5a1a9db1b2f7a5ea3619f79e9bfdae3366cc69 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/list.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -12,6 +12,7 @@ Clang-Tidy Checks android/* boost/* bugprone/* + BSCompatibility/* cert/* clang-analyzer/* concurrency/* @@ -75,6 +76,13 @@ Clang-Tidy Checks `android-cloexec-pipe2 `_, "Yes" `android-cloexec-socket `_, "Yes" `android-comparison-in-temp-failure-retry `_, + `BSCompatibility-dependent-template-keyword `, "Yes" + `BSCompatibility-forbidden-builtin-exit `, "Yes" + `BSCompatibility-move-explicit-instantiation-after-defs `, "Yes" + `BSCompatibility-non-void-function-return-void `_, "Yes" + `BSCompatibility-redundant-default-template-arg `, "Yes" + `BSCompatibility-thread-storage-unify `, "Yes" + `BSCompatibility-unsequenced-function-parameter `, "Yes" `boost-use-to-string `_, "Yes" `bugprone-argument-comment `_, "Yes" `bugprone-assert-side-effect `_, diff --git a/clang-tools-extra/docs/clang-tidy/index.rst b/clang-tools-extra/docs/clang-tidy/index.rst index 41fde5064b8eee7b25ba2888526bb7d3d2d93539..2960a7d63082adc812ea23dfb1526a82227faa23 100644 --- a/clang-tools-extra/docs/clang-tidy/index.rst +++ b/clang-tools-extra/docs/clang-tidy/index.rst @@ -60,6 +60,7 @@ Name prefix Description ``abseil-`` Checks related to Abseil library. ``altera-`` Checks related to OpenCL programming for FPGAs. ``android-`` Checks related to Android. +``BSCompatibility-`` Checks related to compatibility between BiSheng and gcc ``boost-`` Checks related to Boost library. ``bugprone-`` Checks that target bug-prone code constructs. ``cert-`` Checks related to CERT Secure Coding Guidelines. diff --git a/clang-tools-extra/test/clang-tidy/checkers/BSCompatibility/dependent-template-keyword.cpp b/clang-tools-extra/test/clang-tidy/checkers/BSCompatibility/dependent-template-keyword.cpp new file mode 100644 index 0000000000000000000000000000000000000000..fe1d28b7947c9ebb4e808b3c2c98bf4e07b200af --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/BSCompatibility/dependent-template-keyword.cpp @@ -0,0 +1,51 @@ +// RUN: %check_clang_tidy %s BSCompatibility-dependent-template-keyword %t -- -fix-errors + + +class A { +public: + template void As(); + void As(); + static A *FromWebContents(); + A *FromWebContents2(); +}; + +template class AA { +public: + template void As(); + void As(); +}; + +class AAA { +public: + template static void As(); + void As(); +}; + +template void Aas(); + +template class B : A { + void foo() { + AA *aa = new AA; + + aa->As(); + // CHECK-MESSAGE: :[[@LINE-1]]:9: error: use 'template' keyword to treat 'As' as a dependent template name [clang-diagnostic-error] + // CHECK-FIXES: aa->template As(); + aa->As(); + // CHECK-MESSAGE: :[[@LINE-1]]:9: error: use 'template' keyword to treat 'As' as a dependent template name [clang-diagnostic-error] + // CHECK-FIXES: (*aa).template As(); + aa->template As(); + (*aa).As(); + (*aa).As(); + // CHECK-MESSAGE: :[[@LINE-1]]:11: error: use 'template' keyword to treat 'As' as a dependent template name [clang-diagnostic-error] + // CHECK-FIXES: (*aa).template As(); + (*aa).template As(); + auto guest = A::FromWebContents(); + guest->As(); + guest->template As(); + auto guest2 = A::FromWebContents2(); + guest2->template As(); + guest2->As(); + // CHECK-MESSAGE: :[[@LINE-1]]:13: error: use 'template' keyword to treat 'As' as a dependent template name [clang-diagnostic-error] + // CHECK-FIXES: guest2->template As(); + } +}; \ No newline at end of file diff --git a/clang-tools-extra/test/clang-tidy/checkers/BSCompatibility/forbidden-builtin-exit.cpp b/clang-tools-extra/test/clang-tidy/checkers/BSCompatibility/forbidden-builtin-exit.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2f1f6f1953accfafa90ef2dd3f0b9d8bcc3e0ced --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/BSCompatibility/forbidden-builtin-exit.cpp @@ -0,0 +1,16 @@ +// RUN: %check_clang_tidy %s BSCompatibility-forbidden-builtin-exit %t -- -fix-errors + +void catchEx() { + __builtin_exit(0); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: __builtin_exit is not supported by Clang; you may use std::exit() [BSCompatibility-forbidden-builtin-exit] + // CHECK-MESSAGES: :[[@LINE-2]]:3: error: use of undeclared identifier '__builtin_exit' [clang-diagnostic-error] + try { + } catch (int) { + } +} + +int main() { + __builtin_exit(1); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: __builtin_exit is not supported by Clang; you may use std::exit() [BSCompatibility-forbidden-builtin-exit] + // CHECK-MESSAGES: :[[@LINE-2]]:3: error: use of undeclared identifier '__builtin_exit' [clang-diagnostic-error] + } \ No newline at end of file diff --git a/clang-tools-extra/test/clang-tidy/checkers/BSCompatibility/move-explicit-instantiation-after-defs.cpp b/clang-tools-extra/test/clang-tidy/checkers/BSCompatibility/move-explicit-instantiation-after-defs.cpp new file mode 100644 index 0000000000000000000000000000000000000000..955fc8db6050a27bcfe547366dfa631d69eead65 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/BSCompatibility/move-explicit-instantiation-after-defs.cpp @@ -0,0 +1,29 @@ +// RUN: %check_clang_tidy %s BSCompatibility-move-explicit-instantiation-after-defs %t -- -fix-errors + + +// CHECK-MESSAGES: warning: explicit instantiation should appear after the out-of-line member definition(s) in this translation unit [BSCompatibility-move-explicit-instantiation-after-defs] + +template class Wrapper { + public: + Wrapper() = default; + Wrapper(const T &t) : x(t) {} + void print(); + void print2(); + void print3(); + private: + T x; +}; + +template void Wrapper::print2() +{} + +template class Wrapper; +// CHECK-MESSAGES: note: suggest to remove this explicit instantiation here +template void Wrapper::print() { +} + +template void Wrapper::print3(){ +} + +// CHECK-MESSAGES: note: suggest to insert the explicit instantiation here +// CHECK-FIXES: template class Wrapper; diff --git a/clang-tools-extra/test/clang-tidy/checkers/BSCompatibility/non-void-function-return-void.cpp b/clang-tools-extra/test/clang-tidy/checkers/BSCompatibility/non-void-function-return-void.cpp new file mode 100644 index 0000000000000000000000000000000000000000..072822172f846119e7daf31976d29bf35e1ae6a4 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/BSCompatibility/non-void-function-return-void.cpp @@ -0,0 +1,11 @@ +// RUN: %check_clang_tidy %s BSCompatibility-non-void-function-return-void %t -- -fix-errors + +// triggers the check here. +int f(){} +// CHECK-MESSAGES: :[[@LINE-1]]:9: warning: Non-void function does not return a val. [BSCompatibility-non-void-function-return-void] +// CHECK-FIXES: void f(){return something;} + +// doesn't trigger the check here. +void f1(); +int f2(){ return 0; } +int main(){} diff --git a/clang-tools-extra/test/clang-tidy/checkers/BSCompatibility/redundant-default-template-arg.cpp b/clang-tools-extra/test/clang-tidy/checkers/BSCompatibility/redundant-default-template-arg.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2d4fabfc59b325a4de98d56ba36e63d716c98aff --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/BSCompatibility/redundant-default-template-arg.cpp @@ -0,0 +1,38 @@ +// RUN: %check_clang_tidy %s BSCompatibility-redundant-default-template-arg %t -- -fix-errors +// the first declearation will be remained +template void printSize(void); + +template +// CHECK-MESSAGES: :[[@LINE-1]]:20: warning: default template argument redefined; only the first declaration should specify it [BSCompatibility-redundant-default-template-arg] +// CHECK-MESSAGES: :[[@LINE-2]]:24: error: template parameter redefines default argument [clang-diagnostic-error] +//CHECK-FIXES: template +void printSize(); + +template +// CHECK-MESSAGES: :[[@LINE-1]]:20: warning: default template argument redefined; only the first declaration should specify it [BSCompatibility-redundant-default-template-arg] +// CHECK-MESSAGES: :[[@LINE-2]]:24: error: template parameter redefines default argument [clang-diagnostic-error] +//CHECK-FIXES: template +void printSize() { + T a = 3.15; +} + +template int retSize(T x); + +template int retSize(T x); +// CHECK-MESSAGES: :[[@LINE-1]]:20: warning: default template argument redefined; only the first declaration should specify it [BSCompatibility-redundant-default-template-arg] +// CHECK-MESSAGES: :[[@LINE-2]]:24: error: template parameter redefines default argument [clang-diagnostic-error] +// CHECK-FIXES: template int retSize(T x); + +template +// CHECK-MESSAGES: :[[@LINE-1]]:20: warning: default template argument redefined; only the first declaration should specify it [BSCompatibility-redundant-default-template-arg] +// CHECK-MESSAGES: :[[@LINE-2]]:24: error: template parameter redefines default argument [clang-diagnostic-error] +// CHECK-FIXES: template +int retSize(T x) +{ + +} + +int main() { + printSize<>(); + return 0; +} \ No newline at end of file diff --git a/clang-tools-extra/test/clang-tidy/checkers/BSCompatibility/thread-storage-unify.cpp b/clang-tools-extra/test/clang-tidy/checkers/BSCompatibility/thread-storage-unify.cpp new file mode 100644 index 0000000000000000000000000000000000000000..85fc3d6ac03faed575c6eac77a7d31188f6410f4 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/BSCompatibility/thread-storage-unify.cpp @@ -0,0 +1,29 @@ +// RUN: %check_clang_tidy %s BSCompatibility-thread-storage-unify %t -- -fix-errors + +extern thread_local long test; +// CHECK-MESSAGES: warning: unify to GNU '__thread' to match redeclarations [BSCompatibility-thread-storage-unify] +// CHECK-MESSAGES: warning: mixed use of '__thread' and 'thread_local' for the same variable; unifying to GNU '__thread' [BSCompatibility-thread-storage-unify] +// CHECK-FIXES: extern __thread long test; +extern __thread long test; +// CHECK-MESSAGES: error: thread-local declaration of 'test' with static initialization follows declaration with dynamic initialization [clang-diagnostic-error] + + +extern __thread int b; +// CHECK-MESSAGES: warning: unify to 'thread_local' to match the last extern declaration [BSCompatibility-thread-storage-unify] +// CHECK-MESSAGES: warning: mixed use of '__thread' and 'thread_local' for the same variable; unifying to 'thread_local' per the last extern declaration [BSCompatibility-thread-storage-unify] +// CHECK-FIXES: extern thread_local int b; +extern thread_local int b; +// CHECK-MESSAGES: error: thread-local declaration of 'b' with dynamic initialization follows declaration with static initialization [clang-diagnostic-error] + + +extern __thread int c; +// CHECK-MESSAGES: warning: mixed use of '__thread' and 'thread_local' for the same variable; unifying to GNU '__thread' [BSCompatibility-thread-storage-unify] + +thread_local int c; +// CHECK-MESSAGES: warning: unify to GNU '__thread' for non-extern or defined thread-local variable [BSCompatibility-thread-storage-unify] +// CHECK-MESSAGES: error: thread-local declaration of 'c' with dynamic initialization follows declaration with static initialization [clang-diagnostic-error] +// CHECK_FIXES: __thread int c; + +thread_local int d; +// CHECK-MESSAGES: warning: unify to GNU '__thread' for non-extern or defined thread-local variable [BSCompatibility-thread-storage-unify] +// CHECK-FIXES: __thread int d; \ No newline at end of file diff --git a/clang-tools-extra/test/clang-tidy/checkers/BSCompatibility/unsequenced-function-parameter.cpp b/clang-tools-extra/test/clang-tidy/checkers/BSCompatibility/unsequenced-function-parameter.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2e1303cbe112f77f2905b64f0a969b1fe7b7b7bc --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/BSCompatibility/unsequenced-function-parameter.cpp @@ -0,0 +1,33 @@ +// RUN: %check_clang_tidy %s BSCompatibility-unsequenced-function-parameter %t -- -fix-errors + +int foo() {return 1;} +double foo2() {return 1.0;} +int barbar(int a, int b, double c) {return 1;} + +double bar(double a, double b) {return 2.0;} + +#define foofoo(first, second) + +int main() { + foofoo(foo(), foo()); // Ignore the macro func. + barbar(foo(), foo(), 1.0); +// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: Function calls as arguments are unsequenced and may cause dependency issues [BSCompatibility-unsequenced-function-parameter] +// CHECK-FIXES: int __temp_0_0 = foo(); +// CHECK-FIXES: int __temp_0_1 = foo(); +// CHECK-FIXES: barbar(__temp_0_0, __temp_0_1, 1.0); + barbar(foo(), foo(), bar(foo2(), foo2())); +// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: Function calls as arguments are unsequenced and may cause dependency issues [BSCompatibility-unsequenced-function-parameter] +// CHECK-FIXES: int __temp_1_0 = foo(); +// CHECK-FIXES: int __temp_1_1 = foo(); +// CHECK-FIXES: double __temp_1_2 = bar(foo2(), foo2()); +// CHECK-FIXES: barbar(__temp_1_0, __temp_1_1, __temp_1_2); +// CHECK-MESSAGES: :[[@LINE-6]]:26: warning: Function calls as arguments are unsequenced and may cause dependency issues [BSCompatibility-unsequenced-function-parameter] + +// bar(foo(), foo()) is not fixed during first check +} + +// CHECK-NOT: instantiatedFunction +// CHECK-NOT: classTemplateMethod +// CHECK-NOT: usedFunction +// CHECK-NOT: declaredButNotDefined +// CHECK-NOT: systemTemplateFunction \ No newline at end of file diff --git a/clang/include/clang/Tooling/Core/Diagnostic.h b/clang/include/clang/Tooling/Core/Diagnostic.h index 4553380bcf00eb6124ff75631b4a98388f9776f1..282d5fc6d07b93d268f4a39776c9fec11aabf61e 100644 --- a/clang/include/clang/Tooling/Core/Diagnostic.h +++ b/clang/include/clang/Tooling/Core/Diagnostic.h @@ -34,7 +34,15 @@ struct FileByteRange { std::string FilePath; unsigned FileOffset; + unsigned FileLine; + unsigned FileCol; unsigned Length; + std::string Text; + std::string WholeText; + unsigned MainLine; + + /// If output with details. + bool isDetail = false; }; /// Represents the diagnostic message with the error message associated @@ -54,6 +62,10 @@ struct DiagnosticMessage { std::string Message; std::string FilePath; unsigned FileOffset; + unsigned FileLine; + unsigned FileCol; + std::string WholeText; + unsigned MainLine; /// Fixes for this diagnostic, grouped by file path. llvm::StringMap Fix; @@ -61,6 +73,9 @@ struct DiagnosticMessage { /// Extra source ranges associated with the note, in addition to the location /// of the Message itself. llvm::SmallVector Ranges; + + /// If output with details. + bool isDetail = false; }; /// Represents the diagnostic with the level of severity and possible diff --git a/clang/include/clang/Tooling/Core/Replacement.h b/clang/include/clang/Tooling/Core/Replacement.h index f9452111e147f179b49a53aa2286b186f2132882..6bc996ab8b2aa471bd6d020d9a17c28ae8de785b 100644 --- a/clang/include/clang/Tooling/Core/Replacement.h +++ b/clang/include/clang/Tooling/Core/Replacement.h @@ -120,7 +120,12 @@ public: StringRef getFilePath() const { return FilePath; } unsigned getOffset() const { return ReplacementRange.getOffset(); } unsigned getLength() const { return ReplacementRange.getLength(); } + unsigned getFileLine() const { return FileLine; } + unsigned getFileCol() const { return FileCol; } StringRef getReplacementText() const { return ReplacementText; } + StringRef getOriginalText() const { return OriginalText; } + StringRef getWholeLineText() const { return WholeLineText; } + bool isDetail() const { return Detail;} /// @} /// Applies the replacement on the Rewriter. @@ -129,6 +134,8 @@ public: /// Returns a human readable string representation. std::string toString() const; + void setDetail(bool flag) { Detail = flag; } + private: void setFromSourceLocation(const SourceManager &Sources, SourceLocation Start, unsigned Length, StringRef ReplacementText); @@ -139,7 +146,14 @@ private: std::string FilePath; Range ReplacementRange; + unsigned int FileLine; + unsigned int FileCol; std::string ReplacementText; + std::string OriginalText; + std::string WholeLineText; + + /// If output with details. + bool Detail = false; }; enum class replacement_error { diff --git a/clang/include/clang/Tooling/DiagnosticsYaml.h b/clang/include/clang/Tooling/DiagnosticsYaml.h index 88f81e1f629990ca0e17a31c19c57ad79e7de3d6..23fa67100b9da9bf86d3d380ce150317c89df9fc 100644 --- a/clang/include/clang/Tooling/DiagnosticsYaml.h +++ b/clang/include/clang/Tooling/DiagnosticsYaml.h @@ -32,6 +32,13 @@ template <> struct MappingTraits { Io.mapRequired("FilePath", R.FilePath); Io.mapRequired("FileOffset", R.FileOffset); Io.mapRequired("Length", R.Length); + if (R.isDetail) { + Io.mapOptional("FileLine", R.FileLine); + Io.mapOptional("FileCol", R.FileCol); + Io.mapOptional("Text", R.Text); + Io.mapOptional("WholeText", R.WholeText); + Io.mapOptional("MainLine", R.MainLine); + } } }; @@ -40,10 +47,20 @@ template <> struct MappingTraits { Io.mapRequired("Message", M.Message); Io.mapOptional("FilePath", M.FilePath); Io.mapOptional("FileOffset", M.FileOffset); + if (M.isDetail) { + Io.mapOptional("FileLine", M.FileLine); + Io.mapOptional("FileCol", M.FileCol); + Io.mapOptional("WholeText", M.WholeText); + Io.mapOptional("MainLine", M.MainLine); + } std::vector Fixes; for (auto &Replacements : M.Fix) { llvm::append_range(Fixes, Replacements.second); } + for (auto &Fix : Fixes) { + if (M.isDetail) + Fix.setDetail(true); + } Io.mapRequired("Replacements", Fixes); for (auto &Fix : Fixes) { llvm::Error Err = M.Fix[Fix.getFilePath()].add(Fix); diff --git a/clang/include/clang/Tooling/ReplacementsYaml.h b/clang/include/clang/Tooling/ReplacementsYaml.h index 838f87fd197852ab552fb8aff6fe6e7a759257f2..223a81e56f4a0042253895d02f70cf247c9f8b46 100644 --- a/clang/include/clang/Tooling/ReplacementsYaml.h +++ b/clang/include/clang/Tooling/ReplacementsYaml.h @@ -34,7 +34,10 @@ template <> struct MappingTraits { NormalizedReplacement(const IO &, const clang::tooling::Replacement &R) : FilePath(R.getFilePath()), Offset(R.getOffset()), - Length(R.getLength()), ReplacementText(R.getReplacementText()) {} + FileLine(R.getFileLine()), FileCol(R.getFileCol()), + Length(R.getLength()), ReplacementText(R.getReplacementText()), + OriginalText(R.getOriginalText()), WholeLineText(R.getWholeLineText()), + isDetail(R.isDetail()) {} clang::tooling::Replacement denormalize(const IO &) { return clang::tooling::Replacement(FilePath, Offset, Length, @@ -43,8 +46,13 @@ template <> struct MappingTraits { std::string FilePath; unsigned int Offset; + unsigned int FileLine; + unsigned int FileCol; unsigned int Length; std::string ReplacementText; + std::string OriginalText; + std::string WholeLineText; + bool isDetail; }; static void mapping(IO &Io, clang::tooling::Replacement &R) { @@ -52,8 +60,16 @@ template <> struct MappingTraits { Keys(Io, R); Io.mapRequired("FilePath", Keys->FilePath); Io.mapRequired("Offset", Keys->Offset); + if (Keys->isDetail) { + Io.mapOptional("FileLine", Keys->FileLine); + Io.mapOptional("FileCol", Keys->FileCol); + } Io.mapRequired("Length", Keys->Length); Io.mapRequired("ReplacementText", Keys->ReplacementText); + if (Keys->isDetail) { + Io.mapOptional("OriginalText", Keys->OriginalText); + Io.mapOptional("WholeLineText", Keys->WholeLineText); + } } }; diff --git a/clang/lib/Frontend/TextDiagnostic.cpp b/clang/lib/Frontend/TextDiagnostic.cpp index 1b58261b22a2653a6a2d8542c42551c7d5445218..56546a4a4f1b30eb4a667876feb0f99d7d2dc73f 100644 --- a/clang/lib/Frontend/TextDiagnostic.cpp +++ b/clang/lib/Frontend/TextDiagnostic.cpp @@ -997,6 +997,7 @@ static std::string buildFixItInsertionLine(FileID FID, const SourceManager &SM, const DiagnosticOptions *DiagOpts) { std::string FixItInsertionLine; + std::string FixItInsertionMultiLine; if (Hints.empty() || !DiagOpts->ShowFixits) return FixItInsertionLine; unsigned PrevHintEndCol = 0; @@ -1006,12 +1007,16 @@ static std::string buildFixItInsertionLine(FileID FID, continue; // We have an insertion hint. Determine whether the inserted - // code contains no newlines and is on the same line as the caret. + // code is on the same line as the caret. std::pair HintLocInfo = SM.getDecomposedExpansionLoc(H.RemoveRange.getBegin()); if (FID == HintLocInfo.first && - LineNo == SM.getLineNumber(HintLocInfo.first, HintLocInfo.second) && - StringRef(H.CodeToInsert).find_first_of("\n\r") == StringRef::npos) { + LineNo == SM.getLineNumber(HintLocInfo.first, HintLocInfo.second)) { + // If contains newlines, insert to the end. + if (StringRef(H.CodeToInsert).find_first_of("\n\r") != StringRef::npos) { + llvm::copy(H.CodeToInsert, std::back_inserter(FixItInsertionMultiLine)); + continue; + } // Insert the new code into the line just below the code // that the user wrote. // Note: When modifying this function, be very careful about what is a @@ -1051,6 +1056,9 @@ static std::string buildFixItInsertionLine(FileID FID, expandTabs(FixItInsertionLine, DiagOpts->TabStop); + if (!FixItInsertionMultiLine.empty()) + FixItInsertionLine += '\n' + FixItInsertionMultiLine; + return FixItInsertionLine; } @@ -1259,6 +1267,11 @@ void TextDiagnostic::emitSnippetAndCaret( OS.resetColor(); } + auto split = StringRef(FixItInsertionLine).find_first_of("\n\r"); + std::string FixItInsertionMultiLine = + FixItInsertionLine.substr(split + 1, FixItInsertionLine.size()); + FixItInsertionLine = FixItInsertionLine.substr(0, split); + if (!FixItInsertionLine.empty()) { indentForLineNumbers(); if (DiagOpts->ShowColors) @@ -1270,6 +1283,26 @@ void TextDiagnostic::emitSnippetAndCaret( if (DiagOpts->ShowColors) OS.resetColor(); } + + if (split != StringRef::npos) { + unsigned start = 0; + auto end = StringRef(FixItInsertionMultiLine).find_first_of("\n\r"); + while (end != StringRef::npos) { + indentForLineNumbers(); + if (DiagOpts->ShowColors) + // Print fixit line in color + OS.changeColor(fixitColor, false); + if (DiagOpts->ShowSourceRanges) + OS << ' '; + OS << FixItInsertionMultiLine.substr(start, end - start + 1); + start = end + 1; + end = StringRef(FixItInsertionMultiLine).find_first_of("\n\r", start); + if (DiagOpts->ShowColors) + OS.resetColor(); + } + indentForLineNumbers(); + OS << '\n'; + } } // Print out any parseable fixit information requested by the options. diff --git a/clang/lib/Tooling/Core/Diagnostic.cpp b/clang/lib/Tooling/Core/Diagnostic.cpp index fb3358024692dbd49c6c6e1cdd154d6ba03d19ed..9318de181b133458f9006077aeb7417130b35f20 100644 --- a/clang/lib/Tooling/Core/Diagnostic.cpp +++ b/clang/lib/Tooling/Core/Diagnostic.cpp @@ -31,8 +31,26 @@ DiagnosticMessage::DiagnosticMessage(llvm::StringRef Message, // Don't store offset in the scratch space. It doesn't tell anything to the // user. Moreover, it depends on the history of macro expansions and thus // prevents deduplication of warnings in headers. - if (!FilePath.empty()) + if (!FilePath.empty()) { FileOffset = Sources.getFileOffset(Loc); + FileID FID = Sources.getFileID(Loc); + FileLine = Sources.getLineNumber(FID, FileOffset); + FileCol = Sources.getColumnNumber(FID, FileOffset); + SourceLocation start; + if (FileLine < 4) { + start = Sources.getLocForStartOfFile(FID); + MainLine = FileLine; + } else { + start = Sources.translateLineCol(FID, FileLine - 3, 1); + MainLine = 4; + } + SourceLocation end = Sources.translateLineCol(FID, FileLine + 4, 1); + if (end.isInvalid()) + end = Sources.getLocForEndOfFile(FID); + unsigned len = Sources.getFileOffset(end) - Sources.getFileOffset(start); + const char* ptr = Sources.getCharacterData(start); + WholeText = std::string(ptr, len); + } } FileByteRange::FileByteRange( @@ -41,7 +59,28 @@ FileByteRange::FileByteRange( FilePath = std::string(Sources.getFilename(Range.getBegin())); if (!FilePath.empty()) { FileOffset = Sources.getFileOffset(Range.getBegin()); + SourceLocation Start = Range.getBegin(); + FileOffset = Sources.getFileOffset(Start); + FileID FID = Sources.getFileID(Start); + FileLine = Sources.getLineNumber(FID, FileOffset); + FileCol = Sources.getColumnNumber(FID, FileOffset); Length = Sources.getFileOffset(Range.getEnd()) - FileOffset; + const char* TextPtr = Sources.getCharacterData(Start); + Text = std::string(TextPtr, Length); + SourceLocation start; + if (FileLine < 4) { + start = Sources.getLocForStartOfFile(FID); + MainLine = FileLine; + } else { + start = Sources.translateLineCol(FID, FileLine - 3, 1); + MainLine = 4; + } + SourceLocation end = Sources.translateLineCol(FID, FileLine + 4, 1); + if (end.isInvalid()) + end = Sources.getLocForEndOfFile(FID); + unsigned len = Sources.getFileOffset(end) - Sources.getFileOffset(start); + const char* ptr = Sources.getCharacterData(start); + WholeText = std::string(ptr, len); } } diff --git a/clang/lib/Tooling/Core/Replacement.cpp b/clang/lib/Tooling/Core/Replacement.cpp index 020ad08a65e7ec79c3499afd5adab24091243a8c..c5f45389c1195f9f97ddfe7b480e7b3e23cdee02 100644 --- a/clang/lib/Tooling/Core/Replacement.cpp +++ b/clang/lib/Tooling/Core/Replacement.cpp @@ -120,12 +120,26 @@ bool operator==(const Replacement &LHS, const Replacement &RHS) { void Replacement::setFromSourceLocation(const SourceManager &Sources, SourceLocation Start, unsigned Length, StringRef ReplacementText) { - const std::pair DecomposedLocation = - Sources.getDecomposedLoc(Start); - const FileEntry *Entry = Sources.getFileEntryForID(DecomposedLocation.first); + auto [FID, offset] = Sources.getDecomposedLoc(Start); + OptionalFileEntryRef Entry = Sources.getFileEntryRefForID(FID); this->FilePath = std::string(Entry ? Entry->getName() : InvalidLocation); - this->ReplacementRange = Range(DecomposedLocation.second, Length); + this->ReplacementRange = Range(offset, Length); this->ReplacementText = std::string(ReplacementText); + const char* OriginalPtr = Sources.getCharacterData(Start); + this->OriginalText = std::string(OriginalPtr, Length); + this->FileLine = Sources.getLineNumber(FID, offset); + this->FileCol = Sources.getColumnNumber(FID, offset); + SourceLocation start = Sources.translateLineCol(FID, this->FileLine, 1); + SourceLocation end = Sources.translateLineCol(FID, this->FileLine + 1, 1); + if (end.isInvalid()) + end = Sources.getLocForEndOfFile(FID); + if (Sources.getFileOffset(end) < Sources.getFileOffset(start)) { + this->WholeLineText = "// New line in the end of file."; + return; + } + unsigned len = Sources.getFileOffset(end) - Sources.getFileOffset(start); + const char* LinePtr = Sources.getCharacterData(start); + this->WholeLineText = std::string(LinePtr, len); } // FIXME: This should go into the Lexer, but we need to figure out how