Compare commits
230 Commits
llvmorg-8.
...
llvmorg-7.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0d8eb2e54 | ||
|
|
0a9b9e6746 | ||
|
|
5a9000ba83 | ||
|
|
f98e746cba | ||
|
|
c5ae766829 | ||
|
|
fe1944239c | ||
|
|
e725fba53c | ||
|
|
c4c4115791 | ||
|
|
b7096f761a | ||
|
|
9e856fabe0 | ||
|
|
2ea572a8e5 | ||
|
|
a0f6a34313 | ||
|
|
e2bdf494cd | ||
|
|
5d1a670b7c | ||
|
|
9bd05390b3 | ||
|
|
b1c89d22f9 | ||
|
|
d363502dcd | ||
|
|
1d3cb9452f | ||
|
|
5b036d9701 | ||
|
|
4d730d8ff3 | ||
|
|
4a6ae60f26 | ||
|
|
d6ffc0c6ea | ||
|
|
958b37efa2 | ||
|
|
938be89e4e | ||
|
|
3ccd033c4e | ||
|
|
3e4d4dd038 | ||
|
|
1ee9092683 | ||
|
|
9ad6d0609a | ||
|
|
f79f472aa1 | ||
|
|
b948f5fe98 | ||
|
|
e4023f8c63 | ||
|
|
e3cd69e00b | ||
|
|
85e1cc5cd7 | ||
|
|
35aa48a4a4 | ||
|
|
77480c54e9 | ||
|
|
24d841e026 | ||
|
|
9a20188b02 | ||
|
|
92a9b7acc3 | ||
|
|
26d78f3492 | ||
|
|
e89fa36f68 | ||
|
|
447f344357 | ||
|
|
b0d060a930 | ||
|
|
6ea1c152e2 | ||
|
|
dc96c518c1 | ||
|
|
204289afe0 | ||
|
|
bbaf33edcf | ||
|
|
74c32a1179 | ||
|
|
32d269695a | ||
|
|
8beaae2363 | ||
|
|
f0ed63b765 | ||
|
|
ea49dcd184 | ||
|
|
9e981fb6e1 | ||
|
|
1696eef2ac | ||
|
|
ccf7f5115e | ||
|
|
6d5c2a5a85 | ||
|
|
a8b61d167f | ||
|
|
b8dce5ad76 | ||
|
|
8e5d7e5115 | ||
|
|
f2ee78ccdf | ||
|
|
dfebe57343 | ||
|
|
0447cd66a0 | ||
|
|
cddf73ef26 | ||
|
|
80adf40949 | ||
|
|
78605c68f3 | ||
|
|
8c4d80af1f | ||
|
|
00b161b897 | ||
|
|
d18bc8eb29 | ||
|
|
383fa52f0f | ||
|
|
d15c758f14 | ||
|
|
e37bc8295d | ||
|
|
5de718097c | ||
|
|
39b75e9e84 | ||
|
|
78bdb22298 | ||
|
|
f0e9472a47 | ||
|
|
494f9e4c0d | ||
|
|
1db9b3efdf | ||
|
|
3472e2a5a5 | ||
|
|
3065b0a70b | ||
|
|
0e44dc22ff | ||
|
|
89e6185feb | ||
|
|
e0f0bdd4d0 | ||
|
|
69c7f488bf | ||
|
|
0d7b2cb16c | ||
|
|
f464f9b322 | ||
|
|
d2647fa6f2 | ||
|
|
f1c57fe859 | ||
|
|
2359633b04 | ||
|
|
fbe05633b7 | ||
|
|
3f83c2fa74 | ||
|
|
d8c3a2aa7e | ||
|
|
4e3ef6368c | ||
|
|
90699f8b7e | ||
|
|
3d3c7c2e04 | ||
|
|
9f802dded8 | ||
|
|
ddeced087a | ||
|
|
3ec022d029 | ||
|
|
64b6c7b5f9 | ||
|
|
95cde841ed | ||
|
|
8efd177122 | ||
|
|
8f8e7688cc | ||
|
|
3c08d65383 | ||
|
|
db7876c1bc | ||
|
|
9a5180794b | ||
|
|
55aaea2e2e | ||
|
|
612dde51dc | ||
|
|
dc2d638493 | ||
|
|
8f50cb6b5a | ||
|
|
7e8fa65f14 | ||
|
|
a3b989e1f0 | ||
|
|
fd068d62a0 | ||
|
|
97ded321c3 | ||
|
|
7c9d3c67a4 | ||
|
|
1a8a05a1c0 | ||
|
|
21f6f5c987 | ||
|
|
cbd4a17454 | ||
|
|
e89d1857ed | ||
|
|
1a530a1bae | ||
|
|
d2b33338a5 | ||
|
|
623c252055 | ||
|
|
780e7970fe | ||
|
|
7642c02fae | ||
|
|
3b7811f644 | ||
|
|
09ca0cddec | ||
|
|
9fa66d83d5 | ||
|
|
7a72546ff6 | ||
|
|
d8568622e6 | ||
|
|
e23b77ca93 | ||
|
|
9d0c8d3f87 | ||
|
|
5396514e13 | ||
|
|
e71890b66d | ||
|
|
72bbf8c531 | ||
|
|
8a2eba80a7 | ||
|
|
786ae53d2b | ||
|
|
3fd44a109d | ||
|
|
54989e78e4 | ||
|
|
7cea4193d9 | ||
|
|
b90baad8a3 | ||
|
|
42db5f37f1 | ||
|
|
ad568e4f19 | ||
|
|
96148317d3 | ||
|
|
c558aeea0c | ||
|
|
ff0e60f323 | ||
|
|
719997b080 | ||
|
|
ea525abfcb | ||
|
|
25fb1ca68e | ||
|
|
099600fc8b | ||
|
|
ada3e3cdb3 | ||
|
|
4f88bfa0ee | ||
|
|
e4f6c94bd7 | ||
|
|
f273c33c1f | ||
|
|
7b0a6d3246 | ||
|
|
9dc31a87e7 | ||
|
|
8d3b6bcdbf | ||
|
|
b2177de8ba | ||
|
|
5dfcfbf90c | ||
|
|
db1dc2ea56 | ||
|
|
20089ece79 | ||
|
|
4b14e145d4 | ||
|
|
a7a21e64cb | ||
|
|
599b2d122f | ||
|
|
f3f6ca7957 | ||
|
|
7b0c6a2c02 | ||
|
|
8a6427840e | ||
|
|
6141c26486 | ||
|
|
9b38a730d1 | ||
|
|
c662a52399 | ||
|
|
a5e23f0874 | ||
|
|
80a10ad9cb | ||
|
|
371f3503ef | ||
|
|
e5c5c47b70 | ||
|
|
e6a0875aed | ||
|
|
9de4c4fece | ||
|
|
43d7c7dff0 | ||
|
|
3a3b7af45d | ||
|
|
85a67580c5 | ||
|
|
de814ae681 | ||
|
|
6757207d9a | ||
|
|
51c9fed89d | ||
|
|
ace5c2356a | ||
|
|
4d989f7996 | ||
|
|
850d801d57 | ||
|
|
f57a2d5bf8 | ||
|
|
8389196dd9 | ||
|
|
9f482deaa3 | ||
|
|
8732f8f547 | ||
|
|
466ccce944 | ||
|
|
4eb9806913 | ||
|
|
e5aaa0579f | ||
|
|
a6a8a0dc0c | ||
|
|
b6d96c3866 | ||
|
|
dc65afcc36 | ||
|
|
1c21f5b429 | ||
|
|
57c21c9592 | ||
|
|
54bbd10d8d | ||
|
|
d9371841f6 | ||
|
|
7f4ef00eb2 | ||
|
|
cebee05a4d | ||
|
|
9228f08eb5 | ||
|
|
76c03a515a | ||
|
|
babd002ba8 | ||
|
|
3f3701060b | ||
|
|
c7a1b92d79 | ||
|
|
fefd94a85f | ||
|
|
d08e938917 | ||
|
|
bbd95a6951 | ||
|
|
69794107d9 | ||
|
|
6d8abb0718 | ||
|
|
91764583f2 | ||
|
|
8c6b6d1141 | ||
|
|
ceaf95f93d | ||
|
|
23798fa3ae | ||
|
|
e6324b725a | ||
|
|
1cca79b00b | ||
|
|
ce211412ad | ||
|
|
1d910ad0f5 | ||
|
|
f59f1ca9b0 | ||
|
|
3ab9eb5378 | ||
|
|
5006581f1e | ||
|
|
ceb5474679 | ||
|
|
5e3af0e9e3 | ||
|
|
ad32392b8c | ||
|
|
dd449c2432 | ||
|
|
63740db57a | ||
|
|
41c19c9620 | ||
|
|
d32543e590 | ||
|
|
f26bd8777b | ||
|
|
73825c44f4 | ||
|
|
d81a23816a | ||
|
|
d0e85c99da | ||
|
|
67cf759ac3 |
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"conduit_uri" : "https://reviews.llvm.org/"
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
# The LLVM Compiler Infrastructure
|
||||
|
||||
This directory and its subdirectories contain source code for LLVM,
|
||||
a toolkit for the construction of highly optimized compilers,
|
||||
optimizers, and runtime environments.
|
||||
@@ -1,13 +1,10 @@
|
||||
option(CLANGD_BUILD_XPC "Build XPC Support For Clangd." OFF)
|
||||
if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
|
||||
set(CLANGD_BUILD_XPC ON CACHE BOOL "" FORCE)
|
||||
endif ()
|
||||
|
||||
add_subdirectory(clang-apply-replacements)
|
||||
add_subdirectory(clang-reorder-fields)
|
||||
add_subdirectory(modularize)
|
||||
if(CLANG_ENABLE_STATIC_ANALYZER)
|
||||
add_subdirectory(clang-tidy)
|
||||
add_subdirectory(clang-tidy-vs)
|
||||
endif()
|
||||
|
||||
add_subdirectory(change-namespace)
|
||||
add_subdirectory(clang-doc)
|
||||
|
||||
@@ -8,8 +8,8 @@ beautification by scripts. The fields are: name (N), email (E), web-address
|
||||
(W), PGP key ID and fingerprint (P), description (D), and snail-mail address
|
||||
(S).
|
||||
|
||||
N: Aaron Ballman
|
||||
E: aaron@aaronballman.com
|
||||
N: Peter Collingbourne
|
||||
E: peter@pcc.me.uk
|
||||
D: clang-query
|
||||
|
||||
N: Manuel Klimek
|
||||
|
||||
@@ -4,7 +4,7 @@ LLVM Release License
|
||||
University of Illinois/NCSA
|
||||
Open Source License
|
||||
|
||||
Copyright (c) 2007-2019 University of Illinois at Urbana-Champaign.
|
||||
Copyright (c) 2007-2018 University of Illinois at Urbana-Champaign.
|
||||
All rights reserved.
|
||||
|
||||
Developed by:
|
||||
|
||||
@@ -12,7 +12,6 @@ add_clang_library(clangChangeNamespace
|
||||
clangFormat
|
||||
clangFrontend
|
||||
clangLex
|
||||
clangSerialization
|
||||
clangTooling
|
||||
clangToolingCore
|
||||
)
|
||||
|
||||
@@ -7,10 +7,8 @@
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
#include "ChangeNamespace.h"
|
||||
#include "clang/AST/ASTContext.h"
|
||||
#include "clang/Format/Format.h"
|
||||
#include "clang/Lex/Lexer.h"
|
||||
#include "llvm/Support/Casting.h"
|
||||
#include "llvm/Support/ErrorHandling.h"
|
||||
|
||||
using namespace clang::ast_matchers;
|
||||
@@ -48,7 +46,7 @@ SourceLocation startLocationForType(TypeLoc TLoc) {
|
||||
return NestedNameSpecifier.getBeginLoc();
|
||||
TLoc = TLoc.getNextTypeLoc();
|
||||
}
|
||||
return TLoc.getBeginLoc();
|
||||
return TLoc.getLocStart();
|
||||
}
|
||||
|
||||
SourceLocation endLocationForType(TypeLoc TLoc) {
|
||||
@@ -277,7 +275,7 @@ bool isNestedDeclContext(const DeclContext *D, const DeclContext *Context) {
|
||||
// Returns true if \p D is visible at \p Loc with DeclContext \p DeclCtx.
|
||||
bool isDeclVisibleAtLocation(const SourceManager &SM, const Decl *D,
|
||||
const DeclContext *DeclCtx, SourceLocation Loc) {
|
||||
SourceLocation DeclLoc = SM.getSpellingLoc(D->getBeginLoc());
|
||||
SourceLocation DeclLoc = SM.getSpellingLoc(D->getLocStart());
|
||||
Loc = SM.getSpellingLoc(Loc);
|
||||
return SM.isBeforeInTranslationUnit(DeclLoc, Loc) &&
|
||||
(SM.getFileID(DeclLoc) == SM.getFileID(Loc) &&
|
||||
@@ -285,48 +283,23 @@ bool isDeclVisibleAtLocation(const SourceManager &SM, const Decl *D,
|
||||
}
|
||||
|
||||
// Given a qualified symbol name, returns true if the symbol will be
|
||||
// incorrectly qualified without leading "::". For example, a symbol
|
||||
// "nx::ny::Foo" in namespace "na::nx::ny" without leading "::"; a symbol
|
||||
// "util::X" in namespace "na" can potentially conflict with "na::util" (if this
|
||||
// exists).
|
||||
bool conflictInNamespace(const ASTContext &AST, llvm::StringRef QualifiedSymbol,
|
||||
// incorrectly qualified without leading "::".
|
||||
bool conflictInNamespace(llvm::StringRef QualifiedSymbol,
|
||||
llvm::StringRef Namespace) {
|
||||
auto SymbolSplitted = splitSymbolName(QualifiedSymbol.trim(":"));
|
||||
assert(!SymbolSplitted.empty());
|
||||
SymbolSplitted.pop_back(); // We are only interested in namespaces.
|
||||
|
||||
if (SymbolSplitted.size() >= 1 && !Namespace.empty()) {
|
||||
auto SymbolTopNs = SymbolSplitted.front();
|
||||
if (SymbolSplitted.size() > 1 && !Namespace.empty()) {
|
||||
auto NsSplitted = splitSymbolName(Namespace.trim(":"));
|
||||
assert(!NsSplitted.empty());
|
||||
|
||||
auto LookupDecl = [&AST](const Decl &Scope,
|
||||
llvm::StringRef Name) -> const NamedDecl * {
|
||||
const auto *DC = llvm::dyn_cast<DeclContext>(&Scope);
|
||||
if (!DC)
|
||||
return nullptr;
|
||||
auto LookupRes = DC->lookup(DeclarationName(&AST.Idents.get(Name)));
|
||||
if (LookupRes.empty())
|
||||
return nullptr;
|
||||
return LookupRes.front();
|
||||
};
|
||||
// We do not check the outermost namespace since it would not be a
|
||||
// conflict if it equals to the symbol's outermost namespace and the
|
||||
// symbol name would have been shortened.
|
||||
const NamedDecl *Scope =
|
||||
LookupDecl(*AST.getTranslationUnitDecl(), NsSplitted.front());
|
||||
// We do not check the outermost namespace since it would not be a conflict
|
||||
// if it equals to the symbol's outermost namespace and the symbol name
|
||||
// would have been shortened.
|
||||
for (auto I = NsSplitted.begin() + 1, E = NsSplitted.end(); I != E; ++I) {
|
||||
if (*I == SymbolTopNs) // Handles "::ny" in "::nx::ny" case.
|
||||
if (*I == SymbolSplitted.front())
|
||||
return true;
|
||||
// Handles "::util" and "::nx::util" conflicts.
|
||||
if (Scope) {
|
||||
if (LookupDecl(*Scope, SymbolTopNs))
|
||||
return true;
|
||||
Scope = LookupDecl(*Scope, *I);
|
||||
}
|
||||
}
|
||||
if (Scope && LookupDecl(*Scope, SymbolTopNs))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -450,8 +423,8 @@ void ChangeNamespaceTool::registerMatchers(ast_matchers::MatchFinder *Finder) {
|
||||
typeLoc(IsInMovedNs,
|
||||
loc(qualType(hasDeclaration(DeclMatcher.bind("from_decl")))),
|
||||
unless(anyOf(hasParent(typeLoc(loc(qualType(
|
||||
hasDeclaration(DeclMatcher),
|
||||
unless(templateSpecializationType()))))),
|
||||
allOf(hasDeclaration(DeclMatcher),
|
||||
unless(templateSpecializationType())))))),
|
||||
hasParent(nestedNameSpecifierLoc()),
|
||||
hasAncestor(isImplicit()),
|
||||
hasAncestor(UsingShadowDeclInClass),
|
||||
@@ -505,12 +478,13 @@ void ChangeNamespaceTool::registerMatchers(ast_matchers::MatchFinder *Finder) {
|
||||
hasAncestor(namespaceDecl(isAnonymous())),
|
||||
hasAncestor(cxxRecordDecl()))),
|
||||
hasParent(namespaceDecl()));
|
||||
Finder->addMatcher(expr(hasAncestor(decl().bind("dc")), IsInMovedNs,
|
||||
unless(hasAncestor(isImplicit())),
|
||||
anyOf(callExpr(callee(FuncMatcher)).bind("call"),
|
||||
declRefExpr(to(FuncMatcher.bind("func_decl")))
|
||||
.bind("func_ref"))),
|
||||
this);
|
||||
Finder->addMatcher(
|
||||
expr(allOf(hasAncestor(decl().bind("dc")), IsInMovedNs,
|
||||
unless(hasAncestor(isImplicit())),
|
||||
anyOf(callExpr(callee(FuncMatcher)).bind("call"),
|
||||
declRefExpr(to(FuncMatcher.bind("func_decl")))
|
||||
.bind("func_ref")))),
|
||||
this);
|
||||
|
||||
auto GlobalVarMatcher = varDecl(
|
||||
hasGlobalStorage(), hasParent(namespaceDecl()),
|
||||
@@ -660,7 +634,7 @@ static SourceLocation getLocAfterNamespaceLBrace(const NamespaceDecl *NsDecl,
|
||||
const SourceManager &SM,
|
||||
const LangOptions &LangOpts) {
|
||||
std::unique_ptr<Lexer> Lex =
|
||||
getLexerStartingFromLoc(NsDecl->getBeginLoc(), SM, LangOpts);
|
||||
getLexerStartingFromLoc(NsDecl->getLocStart(), SM, LangOpts);
|
||||
assert(Lex.get() &&
|
||||
"Failed to create lexer from the beginning of namespace.");
|
||||
if (!Lex.get())
|
||||
@@ -735,8 +709,8 @@ void ChangeNamespaceTool::moveOldNamespace(
|
||||
void ChangeNamespaceTool::moveClassForwardDeclaration(
|
||||
const ast_matchers::MatchFinder::MatchResult &Result,
|
||||
const NamedDecl *FwdDecl) {
|
||||
SourceLocation Start = FwdDecl->getBeginLoc();
|
||||
SourceLocation End = FwdDecl->getEndLoc();
|
||||
SourceLocation Start = FwdDecl->getLocStart();
|
||||
SourceLocation End = FwdDecl->getLocEnd();
|
||||
const SourceManager &SM = *Result.SourceManager;
|
||||
SourceLocation AfterSemi = Lexer::findLocationAfterToken(
|
||||
End, tok::semi, SM, Result.Context->getLangOpts(),
|
||||
@@ -870,16 +844,15 @@ void ChangeNamespaceTool::replaceQualifiedSymbolInDeclContext(
|
||||
}
|
||||
}
|
||||
}
|
||||
bool Conflict = conflictInNamespace(DeclCtx->getParentASTContext(),
|
||||
ReplaceName, NewNamespace);
|
||||
// If the new nested name in the new namespace is the same as it was in the
|
||||
// old namespace, we don't create replacement unless there can be ambiguity.
|
||||
if ((NestedName == ReplaceName && !Conflict) ||
|
||||
// old namespace, we don't create replacement.
|
||||
if (NestedName == ReplaceName ||
|
||||
(NestedName.startswith("::") && NestedName.drop_front(2) == ReplaceName))
|
||||
return;
|
||||
// If the reference need to be fully-qualified, add a leading "::" unless
|
||||
// NewNamespace is the global namespace.
|
||||
if (ReplaceName == FromDeclName && !NewNamespace.empty() && Conflict)
|
||||
if (ReplaceName == FromDeclName && !NewNamespace.empty() &&
|
||||
conflictInNamespace(ReplaceName, NewNamespace))
|
||||
ReplaceName = "::" + ReplaceName;
|
||||
addReplacementOrDie(Start, End, ReplaceName, *Result.SourceManager,
|
||||
&FileToReplacements);
|
||||
@@ -906,7 +879,7 @@ void ChangeNamespaceTool::fixTypeLoc(
|
||||
if (!llvm::StringRef(D->getQualifiedNameAsString())
|
||||
.startswith(OldNamespace + "::"))
|
||||
return false;
|
||||
auto ExpansionLoc = Result.SourceManager->getExpansionLoc(D->getBeginLoc());
|
||||
auto ExpansionLoc = Result.SourceManager->getExpansionLoc(D->getLocStart());
|
||||
if (ExpansionLoc.isInvalid())
|
||||
return false;
|
||||
llvm::StringRef Filename = Result.SourceManager->getFilename(ExpansionLoc);
|
||||
@@ -937,8 +910,8 @@ void ChangeNamespaceTool::fixTypeLoc(
|
||||
void ChangeNamespaceTool::fixUsingShadowDecl(
|
||||
const ast_matchers::MatchFinder::MatchResult &Result,
|
||||
const UsingDecl *UsingDeclaration) {
|
||||
SourceLocation Start = UsingDeclaration->getBeginLoc();
|
||||
SourceLocation End = UsingDeclaration->getEndLoc();
|
||||
SourceLocation Start = UsingDeclaration->getLocStart();
|
||||
SourceLocation End = UsingDeclaration->getLocEnd();
|
||||
if (Start.isInvalid() || End.isInvalid())
|
||||
return;
|
||||
|
||||
@@ -1016,8 +989,7 @@ void ChangeNamespaceTool::onEndOfTranslationUnit() {
|
||||
// Add replacements referring to the changed code to existing replacements,
|
||||
// which refers to the original code.
|
||||
Replaces = Replaces.merge(NewReplacements);
|
||||
auto Style =
|
||||
format::getStyle(format::DefaultFormatStyle, FilePath, FallbackStyle);
|
||||
auto Style = format::getStyle("file", FilePath, FallbackStyle);
|
||||
if (!Style) {
|
||||
llvm::errs() << llvm::toString(Style.takeError()) << "\n";
|
||||
continue;
|
||||
|
||||
@@ -16,7 +16,6 @@ target_link_libraries(clang-change-namespace
|
||||
clangFormat
|
||||
clangFrontend
|
||||
clangRewrite
|
||||
clangSerialization
|
||||
clangTooling
|
||||
clangToolingCore
|
||||
)
|
||||
|
||||
@@ -125,8 +125,7 @@ std::error_code collectReplacementsFromDirectory(
|
||||
}
|
||||
|
||||
/// \brief Extract replacements from collected TranslationUnitReplacements and
|
||||
/// TranslationUnitDiagnostics and group them per file. Identical replacements
|
||||
/// from diagnostics are deduplicated.
|
||||
/// TranslationUnitDiagnostics and group them per file.
|
||||
///
|
||||
/// \param[in] TUs Collection of all found and deserialized
|
||||
/// TranslationUnitReplacements.
|
||||
@@ -143,20 +142,10 @@ groupReplacements(const TUReplacements &TUs, const TUDiagnostics &TUDs,
|
||||
llvm::DenseMap<const FileEntry *, std::vector<tooling::Replacement>>
|
||||
GroupedReplacements;
|
||||
|
||||
// Deduplicate identical replacements in diagnostics.
|
||||
// FIXME: Find an efficient way to deduplicate on diagnostics level.
|
||||
llvm::DenseMap<const FileEntry *, std::set<tooling::Replacement>>
|
||||
DiagReplacements;
|
||||
|
||||
auto AddToGroup = [&](const tooling::Replacement &R, bool FromDiag) {
|
||||
auto AddToGroup = [&](const tooling::Replacement &R) {
|
||||
// Use the file manager to deduplicate paths. FileEntries are
|
||||
// automatically canonicalized.
|
||||
if (const FileEntry *Entry = SM.getFileManager().getFile(R.getFilePath())) {
|
||||
if (FromDiag) {
|
||||
auto &Replaces = DiagReplacements[Entry];
|
||||
if (!Replaces.insert(R).second)
|
||||
return;
|
||||
}
|
||||
GroupedReplacements[Entry].push_back(R);
|
||||
} else if (Warned.insert(R.getFilePath()).second) {
|
||||
errs() << "Described file '" << R.getFilePath()
|
||||
@@ -166,13 +155,13 @@ groupReplacements(const TUReplacements &TUs, const TUDiagnostics &TUDs,
|
||||
|
||||
for (const auto &TU : TUs)
|
||||
for (const tooling::Replacement &R : TU.Replacements)
|
||||
AddToGroup(R, false);
|
||||
AddToGroup(R);
|
||||
|
||||
for (const auto &TU : TUDs)
|
||||
for (const auto &D : TU.Diagnostics)
|
||||
for (const auto &Fix : D.Fix)
|
||||
for (const tooling::Replacement &R : Fix.second)
|
||||
AddToGroup(R, true);
|
||||
AddToGroup(R);
|
||||
|
||||
// Sort replacements per file to keep consistent behavior when
|
||||
// clang-apply-replacements run on differents machine.
|
||||
|
||||
@@ -97,8 +97,8 @@ int main(int argc, char **argv) {
|
||||
IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs()), DiagOpts.get());
|
||||
|
||||
// Determine a formatting style from options.
|
||||
auto FormatStyleOrError = format::getStyle(FormatStyleOpt, FormatStyleConfig,
|
||||
format::DefaultFallbackStyle);
|
||||
auto FormatStyleOrError =
|
||||
format::getStyle(FormatStyleOpt, FormatStyleConfig, "LLVM");
|
||||
if (!FormatStyleOrError) {
|
||||
llvm::errs() << llvm::toString(FormatStyleOrError.takeError()) << "\n";
|
||||
return 1;
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#include "BitcodeReader.h"
|
||||
#include "llvm/ADT/IndexedMap.h"
|
||||
#include "llvm/ADT/Optional.h"
|
||||
#include "llvm/Support/Error.h"
|
||||
#include "llvm/Support/raw_ostream.h"
|
||||
|
||||
namespace clang {
|
||||
@@ -18,53 +17,49 @@ namespace doc {
|
||||
|
||||
using Record = llvm::SmallVector<uint64_t, 1024>;
|
||||
|
||||
llvm::Error decodeRecord(Record R, llvm::SmallVectorImpl<char> &Field,
|
||||
llvm::StringRef Blob) {
|
||||
bool decodeRecord(Record R, llvm::SmallVectorImpl<char> &Field,
|
||||
llvm::StringRef Blob) {
|
||||
Field.assign(Blob.begin(), Blob.end());
|
||||
return llvm::Error::success();
|
||||
return true;
|
||||
}
|
||||
|
||||
llvm::Error decodeRecord(Record R, SymbolID &Field, llvm::StringRef Blob) {
|
||||
bool decodeRecord(Record R, SymbolID &Field, llvm::StringRef Blob) {
|
||||
if (R[0] != BitCodeConstants::USRHashSize)
|
||||
return llvm::make_error<llvm::StringError>("Incorrect USR size.\n",
|
||||
llvm::inconvertibleErrorCode());
|
||||
return false;
|
||||
|
||||
// First position in the record is the length of the following array, so we
|
||||
// copy the following elements to the field.
|
||||
for (int I = 0, E = R[0]; I < E; ++I)
|
||||
Field[I] = R[I + 1];
|
||||
return llvm::Error::success();
|
||||
return true;
|
||||
}
|
||||
|
||||
llvm::Error decodeRecord(Record R, bool &Field, llvm::StringRef Blob) {
|
||||
bool decodeRecord(Record R, bool &Field, llvm::StringRef Blob) {
|
||||
Field = R[0] != 0;
|
||||
return llvm::Error::success();
|
||||
return true;
|
||||
}
|
||||
|
||||
llvm::Error decodeRecord(Record R, int &Field, llvm::StringRef Blob) {
|
||||
bool decodeRecord(Record R, int &Field, llvm::StringRef Blob) {
|
||||
if (R[0] > INT_MAX)
|
||||
return llvm::make_error<llvm::StringError>("Integer too large to parse.\n",
|
||||
llvm::inconvertibleErrorCode());
|
||||
return false;
|
||||
Field = (int)R[0];
|
||||
return llvm::Error::success();
|
||||
return true;
|
||||
}
|
||||
|
||||
llvm::Error decodeRecord(Record R, AccessSpecifier &Field,
|
||||
llvm::StringRef Blob) {
|
||||
bool decodeRecord(Record R, AccessSpecifier &Field, llvm::StringRef Blob) {
|
||||
switch (R[0]) {
|
||||
case AS_public:
|
||||
case AS_private:
|
||||
case AS_protected:
|
||||
case AS_none:
|
||||
Field = (AccessSpecifier)R[0];
|
||||
return llvm::Error::success();
|
||||
return true;
|
||||
default:
|
||||
return llvm::make_error<llvm::StringError>(
|
||||
"Invalid value for AccessSpecifier.\n", llvm::inconvertibleErrorCode());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
llvm::Error decodeRecord(Record R, TagTypeKind &Field, llvm::StringRef Blob) {
|
||||
bool decodeRecord(Record R, TagTypeKind &Field, llvm::StringRef Blob) {
|
||||
switch (R[0]) {
|
||||
case TTK_Struct:
|
||||
case TTK_Interface:
|
||||
@@ -72,23 +67,21 @@ llvm::Error decodeRecord(Record R, TagTypeKind &Field, llvm::StringRef Blob) {
|
||||
case TTK_Class:
|
||||
case TTK_Enum:
|
||||
Field = (TagTypeKind)R[0];
|
||||
return llvm::Error::success();
|
||||
return true;
|
||||
default:
|
||||
return llvm::make_error<llvm::StringError>(
|
||||
"Invalid value for TagTypeKind.\n", llvm::inconvertibleErrorCode());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
llvm::Error decodeRecord(Record R, llvm::Optional<Location> &Field,
|
||||
llvm::StringRef Blob) {
|
||||
bool decodeRecord(Record R, llvm::Optional<Location> &Field,
|
||||
llvm::StringRef Blob) {
|
||||
if (R[0] > INT_MAX)
|
||||
return llvm::make_error<llvm::StringError>("Integer too large to parse.\n",
|
||||
llvm::inconvertibleErrorCode());
|
||||
return false;
|
||||
Field.emplace((int)R[0], Blob);
|
||||
return llvm::Error::success();
|
||||
return true;
|
||||
}
|
||||
|
||||
llvm::Error decodeRecord(Record R, InfoType &Field, llvm::StringRef Blob) {
|
||||
bool decodeRecord(Record R, InfoType &Field, llvm::StringRef Blob) {
|
||||
switch (auto IT = static_cast<InfoType>(R[0])) {
|
||||
case InfoType::IT_namespace:
|
||||
case InfoType::IT_record:
|
||||
@@ -96,67 +89,58 @@ llvm::Error decodeRecord(Record R, InfoType &Field, llvm::StringRef Blob) {
|
||||
case InfoType::IT_default:
|
||||
case InfoType::IT_enum:
|
||||
Field = IT;
|
||||
return llvm::Error::success();
|
||||
return true;
|
||||
}
|
||||
return llvm::make_error<llvm::StringError>("Invalid value for InfoType.\n",
|
||||
llvm::inconvertibleErrorCode());
|
||||
return false;
|
||||
}
|
||||
|
||||
llvm::Error decodeRecord(Record R, FieldId &Field, llvm::StringRef Blob) {
|
||||
bool decodeRecord(Record R, FieldId &Field, llvm::StringRef Blob) {
|
||||
switch (auto F = static_cast<FieldId>(R[0])) {
|
||||
case FieldId::F_namespace:
|
||||
case FieldId::F_parent:
|
||||
case FieldId::F_vparent:
|
||||
case FieldId::F_type:
|
||||
case FieldId::F_child_namespace:
|
||||
case FieldId::F_child_record:
|
||||
case FieldId::F_default:
|
||||
Field = F;
|
||||
return llvm::Error::success();
|
||||
return true;
|
||||
}
|
||||
return llvm::make_error<llvm::StringError>("Invalid value for FieldId.\n",
|
||||
llvm::inconvertibleErrorCode());
|
||||
return false;
|
||||
}
|
||||
|
||||
llvm::Error decodeRecord(Record R,
|
||||
llvm::SmallVectorImpl<llvm::SmallString<16>> &Field,
|
||||
llvm::StringRef Blob) {
|
||||
bool decodeRecord(Record R, llvm::SmallVectorImpl<llvm::SmallString<16>> &Field,
|
||||
llvm::StringRef Blob) {
|
||||
Field.push_back(Blob);
|
||||
return llvm::Error::success();
|
||||
return true;
|
||||
}
|
||||
|
||||
llvm::Error decodeRecord(Record R, llvm::SmallVectorImpl<Location> &Field,
|
||||
llvm::StringRef Blob) {
|
||||
bool decodeRecord(Record R, llvm::SmallVectorImpl<Location> &Field,
|
||||
llvm::StringRef Blob) {
|
||||
if (R[0] > INT_MAX)
|
||||
return llvm::make_error<llvm::StringError>("Integer too large to parse.\n",
|
||||
llvm::inconvertibleErrorCode());
|
||||
return false;
|
||||
Field.emplace_back((int)R[0], Blob);
|
||||
return llvm::Error::success();
|
||||
return true;
|
||||
}
|
||||
|
||||
llvm::Error parseRecord(Record R, unsigned ID, llvm::StringRef Blob,
|
||||
const unsigned VersionNo) {
|
||||
bool parseRecord(Record R, unsigned ID, llvm::StringRef Blob,
|
||||
const unsigned VersionNo) {
|
||||
if (ID == VERSION && R[0] == VersionNo)
|
||||
return llvm::Error::success();
|
||||
return llvm::make_error<llvm::StringError>(
|
||||
"Mismatched bitcode version number.\n", llvm::inconvertibleErrorCode());
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
llvm::Error parseRecord(Record R, unsigned ID, llvm::StringRef Blob,
|
||||
NamespaceInfo *I) {
|
||||
bool parseRecord(Record R, unsigned ID, llvm::StringRef Blob,
|
||||
NamespaceInfo *I) {
|
||||
switch (ID) {
|
||||
case NAMESPACE_USR:
|
||||
return decodeRecord(R, I->USR, Blob);
|
||||
case NAMESPACE_NAME:
|
||||
return decodeRecord(R, I->Name, Blob);
|
||||
default:
|
||||
return llvm::make_error<llvm::StringError>(
|
||||
"Invalid field for NamespaceInfo.\n", llvm::inconvertibleErrorCode());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
llvm::Error parseRecord(Record R, unsigned ID, llvm::StringRef Blob,
|
||||
RecordInfo *I) {
|
||||
bool parseRecord(Record R, unsigned ID, llvm::StringRef Blob, RecordInfo *I) {
|
||||
switch (ID) {
|
||||
case RECORD_USR:
|
||||
return decodeRecord(R, I->USR, Blob);
|
||||
@@ -169,13 +153,11 @@ llvm::Error parseRecord(Record R, unsigned ID, llvm::StringRef Blob,
|
||||
case RECORD_TAG_TYPE:
|
||||
return decodeRecord(R, I->TagType, Blob);
|
||||
default:
|
||||
return llvm::make_error<llvm::StringError>(
|
||||
"Invalid field for RecordInfo.\n", llvm::inconvertibleErrorCode());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
llvm::Error parseRecord(Record R, unsigned ID, llvm::StringRef Blob,
|
||||
EnumInfo *I) {
|
||||
bool parseRecord(Record R, unsigned ID, llvm::StringRef Blob, EnumInfo *I) {
|
||||
switch (ID) {
|
||||
case ENUM_USR:
|
||||
return decodeRecord(R, I->USR, Blob);
|
||||
@@ -190,13 +172,11 @@ llvm::Error parseRecord(Record R, unsigned ID, llvm::StringRef Blob,
|
||||
case ENUM_SCOPED:
|
||||
return decodeRecord(R, I->Scoped, Blob);
|
||||
default:
|
||||
return llvm::make_error<llvm::StringError>("Invalid field for EnumInfo.\n",
|
||||
llvm::inconvertibleErrorCode());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
llvm::Error parseRecord(Record R, unsigned ID, llvm::StringRef Blob,
|
||||
FunctionInfo *I) {
|
||||
bool parseRecord(Record R, unsigned ID, llvm::StringRef Blob, FunctionInfo *I) {
|
||||
switch (ID) {
|
||||
case FUNCTION_USR:
|
||||
return decodeRecord(R, I->USR, Blob);
|
||||
@@ -211,42 +191,37 @@ llvm::Error parseRecord(Record R, unsigned ID, llvm::StringRef Blob,
|
||||
case FUNCTION_IS_METHOD:
|
||||
return decodeRecord(R, I->IsMethod, Blob);
|
||||
default:
|
||||
return llvm::make_error<llvm::StringError>(
|
||||
"Invalid field for FunctionInfo.\n", llvm::inconvertibleErrorCode());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
llvm::Error parseRecord(Record R, unsigned ID, llvm::StringRef Blob,
|
||||
TypeInfo *I) {
|
||||
return llvm::Error::success();
|
||||
bool parseRecord(Record R, unsigned ID, llvm::StringRef Blob, TypeInfo *I) {
|
||||
return true;
|
||||
}
|
||||
|
||||
llvm::Error parseRecord(Record R, unsigned ID, llvm::StringRef Blob,
|
||||
FieldTypeInfo *I) {
|
||||
bool parseRecord(Record R, unsigned ID, llvm::StringRef Blob,
|
||||
FieldTypeInfo *I) {
|
||||
switch (ID) {
|
||||
case FIELD_TYPE_NAME:
|
||||
return decodeRecord(R, I->Name, Blob);
|
||||
default:
|
||||
return llvm::make_error<llvm::StringError>("Invalid field for TypeInfo.\n",
|
||||
llvm::inconvertibleErrorCode());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
llvm::Error parseRecord(Record R, unsigned ID, llvm::StringRef Blob,
|
||||
MemberTypeInfo *I) {
|
||||
bool parseRecord(Record R, unsigned ID, llvm::StringRef Blob,
|
||||
MemberTypeInfo *I) {
|
||||
switch (ID) {
|
||||
case MEMBER_TYPE_NAME:
|
||||
return decodeRecord(R, I->Name, Blob);
|
||||
case MEMBER_TYPE_ACCESS:
|
||||
return decodeRecord(R, I->Access, Blob);
|
||||
default:
|
||||
return llvm::make_error<llvm::StringError>(
|
||||
"Invalid field for MemberTypeInfo.\n", llvm::inconvertibleErrorCode());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
llvm::Error parseRecord(Record R, unsigned ID, llvm::StringRef Blob,
|
||||
CommentInfo *I) {
|
||||
bool parseRecord(Record R, unsigned ID, llvm::StringRef Blob, CommentInfo *I) {
|
||||
switch (ID) {
|
||||
case COMMENT_KIND:
|
||||
return decodeRecord(R, I->Kind, Blob);
|
||||
@@ -271,13 +246,12 @@ llvm::Error parseRecord(Record R, unsigned ID, llvm::StringRef Blob,
|
||||
case COMMENT_EXPLICIT:
|
||||
return decodeRecord(R, I->Explicit, Blob);
|
||||
default:
|
||||
return llvm::make_error<llvm::StringError>(
|
||||
"Invalid field for CommentInfo.\n", llvm::inconvertibleErrorCode());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
llvm::Error parseRecord(Record R, unsigned ID, llvm::StringRef Blob,
|
||||
Reference *I, FieldId &F) {
|
||||
bool parseRecord(Record R, unsigned ID, llvm::StringRef Blob, Reference *I,
|
||||
FieldId &F) {
|
||||
switch (ID) {
|
||||
case REFERENCE_USR:
|
||||
return decodeRecord(R, I->USR, Blob);
|
||||
@@ -288,214 +262,162 @@ llvm::Error parseRecord(Record R, unsigned ID, llvm::StringRef Blob,
|
||||
case REFERENCE_FIELD:
|
||||
return decodeRecord(R, F, Blob);
|
||||
default:
|
||||
return llvm::make_error<llvm::StringError>("Invalid field for Reference.\n",
|
||||
llvm::inconvertibleErrorCode());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T> llvm::Expected<CommentInfo *> getCommentInfo(T I) {
|
||||
return llvm::make_error<llvm::StringError>(
|
||||
"Invalid type cannot contain CommentInfo.\n",
|
||||
llvm::inconvertibleErrorCode());
|
||||
template <typename T> CommentInfo *getCommentInfo(T I) {
|
||||
llvm::errs() << "Cannot have comment subblock.\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
template <> llvm::Expected<CommentInfo *> getCommentInfo(FunctionInfo *I) {
|
||||
template <> CommentInfo *getCommentInfo(FunctionInfo *I) {
|
||||
I->Description.emplace_back();
|
||||
return &I->Description.back();
|
||||
}
|
||||
|
||||
template <> llvm::Expected<CommentInfo *> getCommentInfo(NamespaceInfo *I) {
|
||||
template <> CommentInfo *getCommentInfo(NamespaceInfo *I) {
|
||||
I->Description.emplace_back();
|
||||
return &I->Description.back();
|
||||
}
|
||||
|
||||
template <> llvm::Expected<CommentInfo *> getCommentInfo(RecordInfo *I) {
|
||||
template <> CommentInfo *getCommentInfo(RecordInfo *I) {
|
||||
I->Description.emplace_back();
|
||||
return &I->Description.back();
|
||||
}
|
||||
|
||||
template <> llvm::Expected<CommentInfo *> getCommentInfo(EnumInfo *I) {
|
||||
template <> CommentInfo *getCommentInfo(EnumInfo *I) {
|
||||
I->Description.emplace_back();
|
||||
return &I->Description.back();
|
||||
}
|
||||
|
||||
template <> llvm::Expected<CommentInfo *> getCommentInfo(CommentInfo *I) {
|
||||
template <> CommentInfo *getCommentInfo(CommentInfo *I) {
|
||||
I->Children.emplace_back(llvm::make_unique<CommentInfo>());
|
||||
return I->Children.back().get();
|
||||
}
|
||||
|
||||
template <>
|
||||
llvm::Expected<CommentInfo *> getCommentInfo(std::unique_ptr<CommentInfo> &I) {
|
||||
template <> CommentInfo *getCommentInfo(std::unique_ptr<CommentInfo> &I) {
|
||||
return getCommentInfo(I.get());
|
||||
}
|
||||
|
||||
template <typename T, typename TTypeInfo>
|
||||
llvm::Error addTypeInfo(T I, TTypeInfo &&TI) {
|
||||
return llvm::make_error<llvm::StringError>(
|
||||
"Invalid type cannot contain TypeInfo.\n",
|
||||
llvm::inconvertibleErrorCode());
|
||||
}
|
||||
|
||||
template <> llvm::Error addTypeInfo(RecordInfo *I, MemberTypeInfo &&T) {
|
||||
I->Members.emplace_back(std::move(T));
|
||||
return llvm::Error::success();
|
||||
}
|
||||
|
||||
template <> llvm::Error addTypeInfo(FunctionInfo *I, TypeInfo &&T) {
|
||||
I->ReturnType = std::move(T);
|
||||
return llvm::Error::success();
|
||||
}
|
||||
|
||||
template <> llvm::Error addTypeInfo(FunctionInfo *I, FieldTypeInfo &&T) {
|
||||
I->Params.emplace_back(std::move(T));
|
||||
return llvm::Error::success();
|
||||
}
|
||||
|
||||
template <typename T> llvm::Error addReference(T I, Reference &&R, FieldId F) {
|
||||
return llvm::make_error<llvm::StringError>(
|
||||
"Invalid type cannot contain Reference\n",
|
||||
llvm::inconvertibleErrorCode());
|
||||
}
|
||||
|
||||
template <> llvm::Error addReference(TypeInfo *I, Reference &&R, FieldId F) {
|
||||
switch (F) {
|
||||
case FieldId::F_type:
|
||||
I->Type = std::move(R);
|
||||
return llvm::Error::success();
|
||||
default:
|
||||
return llvm::make_error<llvm::StringError>(
|
||||
"Invalid type cannot contain Reference.\n",
|
||||
llvm::inconvertibleErrorCode());
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
llvm::Error addReference(FieldTypeInfo *I, Reference &&R, FieldId F) {
|
||||
switch (F) {
|
||||
case FieldId::F_type:
|
||||
I->Type = std::move(R);
|
||||
return llvm::Error::success();
|
||||
default:
|
||||
return llvm::make_error<llvm::StringError>(
|
||||
"Invalid type cannot contain Reference.\n",
|
||||
llvm::inconvertibleErrorCode());
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
llvm::Error addReference(MemberTypeInfo *I, Reference &&R, FieldId F) {
|
||||
switch (F) {
|
||||
case FieldId::F_type:
|
||||
I->Type = std::move(R);
|
||||
return llvm::Error::success();
|
||||
default:
|
||||
return llvm::make_error<llvm::StringError>(
|
||||
"Invalid type cannot contain Reference.\n",
|
||||
llvm::inconvertibleErrorCode());
|
||||
}
|
||||
}
|
||||
|
||||
template <> llvm::Error addReference(EnumInfo *I, Reference &&R, FieldId F) {
|
||||
switch (F) {
|
||||
case FieldId::F_namespace:
|
||||
I->Namespace.emplace_back(std::move(R));
|
||||
return llvm::Error::success();
|
||||
default:
|
||||
return llvm::make_error<llvm::StringError>(
|
||||
"Invalid type cannot contain Reference.\n",
|
||||
llvm::inconvertibleErrorCode());
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
llvm::Error addReference(NamespaceInfo *I, Reference &&R, FieldId F) {
|
||||
switch (F) {
|
||||
case FieldId::F_namespace:
|
||||
I->Namespace.emplace_back(std::move(R));
|
||||
return llvm::Error::success();
|
||||
case FieldId::F_child_namespace:
|
||||
I->ChildNamespaces.emplace_back(std::move(R));
|
||||
return llvm::Error::success();
|
||||
case FieldId::F_child_record:
|
||||
I->ChildRecords.emplace_back(std::move(R));
|
||||
return llvm::Error::success();
|
||||
default:
|
||||
return llvm::make_error<llvm::StringError>(
|
||||
"Invalid type cannot contain Reference.\n",
|
||||
llvm::inconvertibleErrorCode());
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
llvm::Error addReference(FunctionInfo *I, Reference &&R, FieldId F) {
|
||||
switch (F) {
|
||||
case FieldId::F_namespace:
|
||||
I->Namespace.emplace_back(std::move(R));
|
||||
return llvm::Error::success();
|
||||
case FieldId::F_parent:
|
||||
I->Parent = std::move(R);
|
||||
return llvm::Error::success();
|
||||
default:
|
||||
return llvm::make_error<llvm::StringError>(
|
||||
"Invalid type cannot contain Reference.\n",
|
||||
llvm::inconvertibleErrorCode());
|
||||
}
|
||||
}
|
||||
|
||||
template <> llvm::Error addReference(RecordInfo *I, Reference &&R, FieldId F) {
|
||||
switch (F) {
|
||||
case FieldId::F_namespace:
|
||||
I->Namespace.emplace_back(std::move(R));
|
||||
return llvm::Error::success();
|
||||
case FieldId::F_parent:
|
||||
I->Parents.emplace_back(std::move(R));
|
||||
return llvm::Error::success();
|
||||
case FieldId::F_vparent:
|
||||
I->VirtualParents.emplace_back(std::move(R));
|
||||
return llvm::Error::success();
|
||||
case FieldId::F_child_record:
|
||||
I->ChildRecords.emplace_back(std::move(R));
|
||||
return llvm::Error::success();
|
||||
default:
|
||||
return llvm::make_error<llvm::StringError>(
|
||||
"Invalid type cannot contain Reference.\n",
|
||||
llvm::inconvertibleErrorCode());
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, typename ChildInfoType>
|
||||
void addChild(T I, ChildInfoType &&R) {
|
||||
llvm::errs() << "Invalid child type for info.\n";
|
||||
void addTypeInfo(T I, TTypeInfo &&TI) {
|
||||
llvm::errs() << "Invalid type for info.\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
template <> void addChild(NamespaceInfo *I, FunctionInfo &&R) {
|
||||
I->ChildFunctions.emplace_back(std::move(R));
|
||||
template <> void addTypeInfo(RecordInfo *I, MemberTypeInfo &&T) {
|
||||
I->Members.emplace_back(std::move(T));
|
||||
}
|
||||
|
||||
template <> void addChild(NamespaceInfo *I, EnumInfo &&R) {
|
||||
I->ChildEnums.emplace_back(std::move(R));
|
||||
template <> void addTypeInfo(FunctionInfo *I, TypeInfo &&T) {
|
||||
I->ReturnType = std::move(T);
|
||||
}
|
||||
|
||||
template <> void addChild(RecordInfo *I, FunctionInfo &&R) {
|
||||
I->ChildFunctions.emplace_back(std::move(R));
|
||||
template <> void addTypeInfo(FunctionInfo *I, FieldTypeInfo &&T) {
|
||||
I->Params.emplace_back(std::move(T));
|
||||
}
|
||||
|
||||
template <> void addChild(RecordInfo *I, EnumInfo &&R) {
|
||||
I->ChildEnums.emplace_back(std::move(R));
|
||||
template <typename T> void addReference(T I, Reference &&R, FieldId F) {
|
||||
llvm::errs() << "Invalid field type for info.\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
template <> void addReference(TypeInfo *I, Reference &&R, FieldId F) {
|
||||
switch (F) {
|
||||
case FieldId::F_type:
|
||||
I->Type = std::move(R);
|
||||
break;
|
||||
default:
|
||||
llvm::errs() << "Invalid field type for info.\n";
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
template <> void addReference(FieldTypeInfo *I, Reference &&R, FieldId F) {
|
||||
switch (F) {
|
||||
case FieldId::F_type:
|
||||
I->Type = std::move(R);
|
||||
break;
|
||||
default:
|
||||
llvm::errs() << "Invalid field type for info.\n";
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
template <> void addReference(MemberTypeInfo *I, Reference &&R, FieldId F) {
|
||||
switch (F) {
|
||||
case FieldId::F_type:
|
||||
I->Type = std::move(R);
|
||||
break;
|
||||
default:
|
||||
llvm::errs() << "Invalid field type for info.\n";
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
template <> void addReference(EnumInfo *I, Reference &&R, FieldId F) {
|
||||
switch (F) {
|
||||
case FieldId::F_namespace:
|
||||
I->Namespace.emplace_back(std::move(R));
|
||||
break;
|
||||
default:
|
||||
llvm::errs() << "Invalid field type for info.\n";
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
template <> void addReference(NamespaceInfo *I, Reference &&R, FieldId F) {
|
||||
switch (F) {
|
||||
case FieldId::F_namespace:
|
||||
I->Namespace.emplace_back(std::move(R));
|
||||
break;
|
||||
default:
|
||||
llvm::errs() << "Invalid field type for info.\n";
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
template <> void addReference(FunctionInfo *I, Reference &&R, FieldId F) {
|
||||
switch (F) {
|
||||
case FieldId::F_namespace:
|
||||
I->Namespace.emplace_back(std::move(R));
|
||||
break;
|
||||
case FieldId::F_parent:
|
||||
I->Parent = std::move(R);
|
||||
break;
|
||||
default:
|
||||
llvm::errs() << "Invalid field type for info.\n";
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
template <> void addReference(RecordInfo *I, Reference &&R, FieldId F) {
|
||||
switch (F) {
|
||||
case FieldId::F_namespace:
|
||||
I->Namespace.emplace_back(std::move(R));
|
||||
break;
|
||||
case FieldId::F_parent:
|
||||
I->Parents.emplace_back(std::move(R));
|
||||
break;
|
||||
case FieldId::F_vparent:
|
||||
I->VirtualParents.emplace_back(std::move(R));
|
||||
break;
|
||||
default:
|
||||
llvm::errs() << "Invalid field type for info.\n";
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Read records from bitcode into a given info.
|
||||
template <typename T>
|
||||
llvm::Error ClangDocBitcodeReader::readRecord(unsigned ID, T I) {
|
||||
template <typename T> bool ClangDocBitcodeReader::readRecord(unsigned ID, T I) {
|
||||
Record R;
|
||||
llvm::StringRef Blob;
|
||||
unsigned RecID = Stream.readRecord(ID, R, &Blob);
|
||||
return parseRecord(R, RecID, Blob, I);
|
||||
}
|
||||
|
||||
template <>
|
||||
llvm::Error ClangDocBitcodeReader::readRecord(unsigned ID, Reference *I) {
|
||||
template <> bool ClangDocBitcodeReader::readRecord(unsigned ID, Reference *I) {
|
||||
Record R;
|
||||
llvm::StringRef Blob;
|
||||
unsigned RecID = Stream.readRecord(ID, R, &Blob);
|
||||
@@ -503,11 +425,9 @@ llvm::Error ClangDocBitcodeReader::readRecord(unsigned ID, Reference *I) {
|
||||
}
|
||||
|
||||
// Read a block of records into a single info.
|
||||
template <typename T>
|
||||
llvm::Error ClangDocBitcodeReader::readBlock(unsigned ID, T I) {
|
||||
template <typename T> bool ClangDocBitcodeReader::readBlock(unsigned ID, T I) {
|
||||
if (Stream.EnterSubBlock(ID))
|
||||
return llvm::make_error<llvm::StringError>("Unable to enter subblock.\n",
|
||||
llvm::inconvertibleErrorCode());
|
||||
return false;
|
||||
|
||||
while (true) {
|
||||
unsigned BlockOrCode = 0;
|
||||
@@ -515,87 +435,66 @@ llvm::Error ClangDocBitcodeReader::readBlock(unsigned ID, T I) {
|
||||
|
||||
switch (Res) {
|
||||
case Cursor::BadBlock:
|
||||
return llvm::make_error<llvm::StringError>(
|
||||
"Bad block found.\n", llvm::inconvertibleErrorCode());
|
||||
return false;
|
||||
case Cursor::BlockEnd:
|
||||
return llvm::Error::success();
|
||||
return true;
|
||||
case Cursor::BlockBegin:
|
||||
if (auto Err = readSubBlock(BlockOrCode, I)) {
|
||||
if (!Stream.SkipBlock())
|
||||
continue;
|
||||
return Err;
|
||||
}
|
||||
if (readSubBlock(BlockOrCode, I))
|
||||
continue;
|
||||
if (!Stream.SkipBlock())
|
||||
return false;
|
||||
continue;
|
||||
case Cursor::Record:
|
||||
break;
|
||||
}
|
||||
if (auto Err = readRecord(BlockOrCode, I))
|
||||
return Err;
|
||||
if (!readRecord(BlockOrCode, I))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
llvm::Error ClangDocBitcodeReader::readSubBlock(unsigned ID, T I) {
|
||||
bool ClangDocBitcodeReader::readSubBlock(unsigned ID, T I) {
|
||||
switch (ID) {
|
||||
// Blocks can only have Comment, Reference, TypeInfo, FunctionInfo, or
|
||||
// EnumInfo subblocks
|
||||
case BI_COMMENT_BLOCK_ID: {
|
||||
auto Comment = getCommentInfo(I);
|
||||
if (!Comment)
|
||||
return Comment.takeError();
|
||||
if (auto Err = readBlock(ID, Comment.get()))
|
||||
return Err;
|
||||
return llvm::Error::success();
|
||||
}
|
||||
// Blocks can only have Comment, Reference, or TypeInfo subblocks
|
||||
case BI_COMMENT_BLOCK_ID:
|
||||
if (readBlock(ID, getCommentInfo(I)))
|
||||
return true;
|
||||
return false;
|
||||
case BI_TYPE_BLOCK_ID: {
|
||||
TypeInfo TI;
|
||||
if (auto Err = readBlock(ID, &TI))
|
||||
return Err;
|
||||
if (auto Err = addTypeInfo(I, std::move(TI)))
|
||||
return Err;
|
||||
return llvm::Error::success();
|
||||
if (readBlock(ID, &TI)) {
|
||||
addTypeInfo(I, std::move(TI));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case BI_FIELD_TYPE_BLOCK_ID: {
|
||||
FieldTypeInfo TI;
|
||||
if (auto Err = readBlock(ID, &TI))
|
||||
return Err;
|
||||
if (auto Err = addTypeInfo(I, std::move(TI)))
|
||||
return Err;
|
||||
return llvm::Error::success();
|
||||
if (readBlock(ID, &TI)) {
|
||||
addTypeInfo(I, std::move(TI));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case BI_MEMBER_TYPE_BLOCK_ID: {
|
||||
MemberTypeInfo TI;
|
||||
if (auto Err = readBlock(ID, &TI))
|
||||
return Err;
|
||||
if (auto Err = addTypeInfo(I, std::move(TI)))
|
||||
return Err;
|
||||
return llvm::Error::success();
|
||||
if (readBlock(ID, &TI)) {
|
||||
addTypeInfo(I, std::move(TI));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case BI_REFERENCE_BLOCK_ID: {
|
||||
Reference R;
|
||||
if (auto Err = readBlock(ID, &R))
|
||||
return Err;
|
||||
if (auto Err = addReference(I, std::move(R), CurrentReferenceField))
|
||||
return Err;
|
||||
return llvm::Error::success();
|
||||
}
|
||||
case BI_FUNCTION_BLOCK_ID: {
|
||||
FunctionInfo F;
|
||||
if (auto Err = readBlock(ID, &F))
|
||||
return Err;
|
||||
addChild(I, std::move(F));
|
||||
return llvm::Error::success();
|
||||
}
|
||||
case BI_ENUM_BLOCK_ID: {
|
||||
EnumInfo E;
|
||||
if (auto Err = readBlock(ID, &E))
|
||||
return Err;
|
||||
addChild(I, std::move(E));
|
||||
return llvm::Error::success();
|
||||
if (readBlock(ID, &R)) {
|
||||
addReference(I, std::move(R), CurrentReferenceField);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
default:
|
||||
return llvm::make_error<llvm::StringError>("Invalid subblock type.\n",
|
||||
llvm::inconvertibleErrorCode());
|
||||
llvm::errs() << "Invalid subblock type.\n";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -627,41 +526,37 @@ ClangDocBitcodeReader::skipUntilRecordOrBlock(unsigned &BlockOrRecordID) {
|
||||
llvm_unreachable("Premature stream end.");
|
||||
}
|
||||
|
||||
llvm::Error ClangDocBitcodeReader::validateStream() {
|
||||
bool ClangDocBitcodeReader::validateStream() {
|
||||
if (Stream.AtEndOfStream())
|
||||
return llvm::make_error<llvm::StringError>("Premature end of stream.\n",
|
||||
llvm::inconvertibleErrorCode());
|
||||
return false;
|
||||
|
||||
// Sniff for the signature.
|
||||
if (Stream.Read(8) != BitCodeConstants::Signature[0] ||
|
||||
Stream.Read(8) != BitCodeConstants::Signature[1] ||
|
||||
Stream.Read(8) != BitCodeConstants::Signature[2] ||
|
||||
Stream.Read(8) != BitCodeConstants::Signature[3])
|
||||
return llvm::make_error<llvm::StringError>("Invalid bitcode signature.\n",
|
||||
llvm::inconvertibleErrorCode());
|
||||
return llvm::Error::success();
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
llvm::Error ClangDocBitcodeReader::readBlockInfoBlock() {
|
||||
bool ClangDocBitcodeReader::readBlockInfoBlock() {
|
||||
BlockInfo = Stream.ReadBlockInfoBlock();
|
||||
if (!BlockInfo)
|
||||
return llvm::make_error<llvm::StringError>(
|
||||
"Unable to parse BlockInfoBlock.\n", llvm::inconvertibleErrorCode());
|
||||
return false;
|
||||
Stream.setBlockInfo(&*BlockInfo);
|
||||
return llvm::Error::success();
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
llvm::Expected<std::unique_ptr<Info>>
|
||||
ClangDocBitcodeReader::createInfo(unsigned ID) {
|
||||
std::unique_ptr<Info> ClangDocBitcodeReader::createInfo(unsigned ID) {
|
||||
std::unique_ptr<Info> I = llvm::make_unique<T>();
|
||||
if (auto Err = readBlock(ID, static_cast<T *>(I.get())))
|
||||
return std::move(Err);
|
||||
return std::unique_ptr<Info>{std::move(I)};;
|
||||
if (readBlock(ID, static_cast<T *>(I.get())))
|
||||
return I;
|
||||
llvm::errs() << "Error reading from block.\n";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
llvm::Expected<std::unique_ptr<Info>>
|
||||
ClangDocBitcodeReader::readBlockToInfo(unsigned ID) {
|
||||
std::unique_ptr<Info> ClangDocBitcodeReader::readBlockToInfo(unsigned ID) {
|
||||
switch (ID) {
|
||||
case BI_NAMESPACE_BLOCK_ID:
|
||||
return createInfo<NamespaceInfo>(ID);
|
||||
@@ -672,24 +567,23 @@ ClangDocBitcodeReader::readBlockToInfo(unsigned ID) {
|
||||
case BI_FUNCTION_BLOCK_ID:
|
||||
return createInfo<FunctionInfo>(ID);
|
||||
default:
|
||||
return llvm::make_error<llvm::StringError>("Cannot create info.\n",
|
||||
llvm::inconvertibleErrorCode());
|
||||
llvm::errs() << "Error reading from block.\n";
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Entry point
|
||||
llvm::Expected<std::vector<std::unique_ptr<Info>>>
|
||||
ClangDocBitcodeReader::readBitcode() {
|
||||
std::vector<std::unique_ptr<Info>> ClangDocBitcodeReader::readBitcode() {
|
||||
std::vector<std::unique_ptr<Info>> Infos;
|
||||
if (auto Err = validateStream())
|
||||
return std::move(Err);
|
||||
if (!validateStream())
|
||||
return Infos;
|
||||
|
||||
// Read the top level blocks.
|
||||
while (!Stream.AtEndOfStream()) {
|
||||
unsigned Code = Stream.ReadCode();
|
||||
if (Code != llvm::bitc::ENTER_SUBBLOCK)
|
||||
return llvm::make_error<llvm::StringError>(
|
||||
"No blocks in input.\n", llvm::inconvertibleErrorCode());
|
||||
return Infos;
|
||||
|
||||
unsigned ID = Stream.ReadSubBlockID();
|
||||
switch (ID) {
|
||||
// NamedType and Comment blocks should not appear at the top level
|
||||
@@ -698,32 +592,30 @@ ClangDocBitcodeReader::readBitcode() {
|
||||
case BI_MEMBER_TYPE_BLOCK_ID:
|
||||
case BI_COMMENT_BLOCK_ID:
|
||||
case BI_REFERENCE_BLOCK_ID:
|
||||
return llvm::make_error<llvm::StringError>(
|
||||
"Invalid top level block.\n", llvm::inconvertibleErrorCode());
|
||||
llvm::errs() << "Invalid top level block.\n";
|
||||
return Infos;
|
||||
case BI_NAMESPACE_BLOCK_ID:
|
||||
case BI_RECORD_BLOCK_ID:
|
||||
case BI_ENUM_BLOCK_ID:
|
||||
case BI_FUNCTION_BLOCK_ID: {
|
||||
auto InfoOrErr = readBlockToInfo(ID);
|
||||
if (!InfoOrErr)
|
||||
return InfoOrErr.takeError();
|
||||
Infos.emplace_back(std::move(InfoOrErr.get()));
|
||||
continue;
|
||||
}
|
||||
case BI_FUNCTION_BLOCK_ID:
|
||||
if (std::unique_ptr<Info> I = readBlockToInfo(ID)) {
|
||||
Infos.emplace_back(std::move(I));
|
||||
}
|
||||
return Infos;
|
||||
case BI_VERSION_BLOCK_ID:
|
||||
if (auto Err = readBlock(ID, VersionNumber))
|
||||
return std::move(Err);
|
||||
continue;
|
||||
if (readBlock(ID, VersionNumber))
|
||||
continue;
|
||||
return Infos;
|
||||
case llvm::bitc::BLOCKINFO_BLOCK_ID:
|
||||
if (auto Err = readBlockInfoBlock())
|
||||
return std::move(Err);
|
||||
continue;
|
||||
if (readBlockInfoBlock())
|
||||
continue;
|
||||
return Infos;
|
||||
default:
|
||||
if (!Stream.SkipBlock())
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return std::move(Infos);
|
||||
return Infos;
|
||||
}
|
||||
|
||||
} // namespace doc
|
||||
|
||||
@@ -19,10 +19,8 @@
|
||||
#include "BitcodeWriter.h"
|
||||
#include "Representation.h"
|
||||
#include "clang/AST/AST.h"
|
||||
#include "llvm/ADT/Optional.h"
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
#include "llvm/Bitcode/BitstreamReader.h"
|
||||
#include "llvm/Support/Error.h"
|
||||
|
||||
namespace clang {
|
||||
namespace doc {
|
||||
@@ -33,37 +31,36 @@ public:
|
||||
ClangDocBitcodeReader(llvm::BitstreamCursor &Stream) : Stream(Stream) {}
|
||||
|
||||
// Main entry point, calls readBlock to read each block in the given stream.
|
||||
llvm::Expected<std::vector<std::unique_ptr<Info>>> readBitcode();
|
||||
std::vector<std::unique_ptr<Info>> readBitcode();
|
||||
|
||||
private:
|
||||
enum class Cursor { BadBlock = 1, Record, BlockEnd, BlockBegin };
|
||||
|
||||
// Top level parsing
|
||||
llvm::Error validateStream();
|
||||
llvm::Error readVersion();
|
||||
llvm::Error readBlockInfoBlock();
|
||||
bool validateStream();
|
||||
bool readVersion();
|
||||
bool readBlockInfoBlock();
|
||||
|
||||
// Read a block of records into a single Info struct, calls readRecord on each
|
||||
// record found.
|
||||
template <typename T> llvm::Error readBlock(unsigned ID, T I);
|
||||
template <typename T> bool readBlock(unsigned ID, T I);
|
||||
|
||||
// Step through a block of records to find the next data field.
|
||||
template <typename T> llvm::Error readSubBlock(unsigned ID, T I);
|
||||
template <typename T> bool readSubBlock(unsigned ID, T I);
|
||||
|
||||
// Read record data into the given Info data field, calling the appropriate
|
||||
// parseRecord functions to parse and store the data.
|
||||
template <typename T> llvm::Error readRecord(unsigned ID, T I);
|
||||
template <typename T> bool readRecord(unsigned ID, T I);
|
||||
|
||||
// Allocate the relevant type of info and add read data to the object.
|
||||
template <typename T>
|
||||
llvm::Expected<std::unique_ptr<Info>> createInfo(unsigned ID);
|
||||
template <typename T> std::unique_ptr<Info> createInfo(unsigned ID);
|
||||
|
||||
// Helper function to step through blocks to find and dispatch the next record
|
||||
// or block to be read.
|
||||
Cursor skipUntilRecordOrBlock(unsigned &BlockOrRecordID);
|
||||
|
||||
// Helper function to set up the approriate type of Info.
|
||||
llvm::Expected<std::unique_ptr<Info>> readBlockToInfo(unsigned ID);
|
||||
std::unique_ptr<Info> readBlockToInfo(unsigned ID);
|
||||
|
||||
llvm::BitstreamCursor &Stream;
|
||||
Optional<llvm::BitstreamBlockInfo> BlockInfo;
|
||||
|
||||
@@ -309,8 +309,10 @@ void ClangDocBitcodeWriter::emitRecord(const Location &Loc, RecordId ID) {
|
||||
// FIXME: Assert that the line number is of the appropriate size.
|
||||
Record.push_back(Loc.LineNumber);
|
||||
assert(Loc.Filename.size() < (1U << BitCodeConstants::StringLengthSize));
|
||||
Record.push_back(Loc.Filename.size());
|
||||
Stream.EmitRecordWithBlob(Abbrevs.get(ID), Record, Loc.Filename);
|
||||
// Record.push_back(Loc.Filename.size());
|
||||
// Stream.EmitRecordWithBlob(Abbrevs.get(ID), Record, Loc.Filename);
|
||||
Record.push_back(4);
|
||||
Stream.EmitRecordWithBlob(Abbrevs.get(ID), Record, "test");
|
||||
}
|
||||
|
||||
void ClangDocBitcodeWriter::emitRecord(bool Val, RecordId ID) {
|
||||
@@ -432,14 +434,6 @@ void ClangDocBitcodeWriter::emitBlock(const NamespaceInfo &I) {
|
||||
emitBlock(N, FieldId::F_namespace);
|
||||
for (const auto &CI : I.Description)
|
||||
emitBlock(CI);
|
||||
for (const auto &C : I.ChildNamespaces)
|
||||
emitBlock(C, FieldId::F_child_namespace);
|
||||
for (const auto &C : I.ChildRecords)
|
||||
emitBlock(C, FieldId::F_child_record);
|
||||
for (const auto &C : I.ChildFunctions)
|
||||
emitBlock(C);
|
||||
for (const auto &C : I.ChildEnums)
|
||||
emitBlock(C);
|
||||
}
|
||||
|
||||
void ClangDocBitcodeWriter::emitBlock(const EnumInfo &I) {
|
||||
@@ -478,12 +472,6 @@ void ClangDocBitcodeWriter::emitBlock(const RecordInfo &I) {
|
||||
emitBlock(P, FieldId::F_parent);
|
||||
for (const auto &P : I.VirtualParents)
|
||||
emitBlock(P, FieldId::F_vparent);
|
||||
for (const auto &C : I.ChildRecords)
|
||||
emitBlock(C, FieldId::F_child_record);
|
||||
for (const auto &C : I.ChildFunctions)
|
||||
emitBlock(C);
|
||||
for (const auto &C : I.ChildEnums)
|
||||
emitBlock(C);
|
||||
}
|
||||
|
||||
void ClangDocBitcodeWriter::emitBlock(const FunctionInfo &I) {
|
||||
|
||||
@@ -68,10 +68,11 @@ enum BlockId {
|
||||
|
||||
// New Ids need to be added to the enum here, and to the relevant IdNameMap and
|
||||
// initialization list in the implementation file.
|
||||
#define INFORECORDS(X) X##_USR, X##_NAME
|
||||
|
||||
enum RecordId {
|
||||
VERSION = 1,
|
||||
FUNCTION_USR,
|
||||
FUNCTION_NAME,
|
||||
INFORECORDS(FUNCTION),
|
||||
FUNCTION_DEFLOCATION,
|
||||
FUNCTION_LOCATION,
|
||||
FUNCTION_ACCESS,
|
||||
@@ -90,16 +91,13 @@ enum RecordId {
|
||||
FIELD_TYPE_NAME,
|
||||
MEMBER_TYPE_NAME,
|
||||
MEMBER_TYPE_ACCESS,
|
||||
NAMESPACE_USR,
|
||||
NAMESPACE_NAME,
|
||||
ENUM_USR,
|
||||
ENUM_NAME,
|
||||
INFORECORDS(NAMESPACE),
|
||||
INFORECORDS(ENUM),
|
||||
ENUM_DEFLOCATION,
|
||||
ENUM_LOCATION,
|
||||
ENUM_MEMBER,
|
||||
ENUM_SCOPED,
|
||||
RECORD_USR,
|
||||
RECORD_NAME,
|
||||
INFORECORDS(RECORD),
|
||||
RECORD_DEFLOCATION,
|
||||
RECORD_LOCATION,
|
||||
RECORD_TAG_TYPE,
|
||||
@@ -114,16 +112,10 @@ enum RecordId {
|
||||
static constexpr unsigned BlockIdCount = BI_LAST - BI_FIRST;
|
||||
static constexpr unsigned RecordIdCount = RI_LAST - RI_FIRST;
|
||||
|
||||
#undef INFORECORDS
|
||||
|
||||
// Identifiers for differentiating between subblocks
|
||||
enum class FieldId {
|
||||
F_default,
|
||||
F_namespace,
|
||||
F_parent,
|
||||
F_vparent,
|
||||
F_type,
|
||||
F_child_namespace,
|
||||
F_child_record
|
||||
};
|
||||
enum class FieldId { F_default, F_namespace, F_parent, F_vparent, F_type };
|
||||
|
||||
class ClangDocBitcodeWriter {
|
||||
public:
|
||||
|
||||
@@ -10,7 +10,6 @@ add_clang_library(clangDoc
|
||||
ClangDoc.cpp
|
||||
Generators.cpp
|
||||
Mapper.cpp
|
||||
MDGenerator.cpp
|
||||
Representation.cpp
|
||||
Serialize.cpp
|
||||
YAMLGenerator.cpp
|
||||
|
||||
@@ -29,11 +29,8 @@ findGeneratorByName(llvm::StringRef Format) {
|
||||
// This anchor is used to force the linker to link in the generated object file
|
||||
// and thus register the generators.
|
||||
extern volatile int YAMLGeneratorAnchorSource;
|
||||
extern volatile int MDGeneratorAnchorSource;
|
||||
static int LLVM_ATTRIBUTE_UNUSED YAMLGeneratorAnchorDest =
|
||||
YAMLGeneratorAnchorSource;
|
||||
static int LLVM_ATTRIBUTE_UNUSED MDGeneratorAnchorDest =
|
||||
MDGeneratorAnchorSource;
|
||||
|
||||
} // namespace doc
|
||||
} // namespace clang
|
||||
|
||||
@@ -27,7 +27,7 @@ public:
|
||||
virtual ~Generator() = default;
|
||||
|
||||
// Write out the decl info in the specified format.
|
||||
virtual llvm::Error generateDocForInfo(Info *I, llvm::raw_ostream &OS) = 0;
|
||||
virtual bool generateDocForInfo(Info *I, llvm::raw_ostream &OS) = 0;
|
||||
};
|
||||
|
||||
typedef llvm::Registry<Generator> GeneratorRegistry;
|
||||
|
||||
@@ -1,319 +0,0 @@
|
||||
//===-- MDGenerator.cpp - Markdown Generator --------------------*- C++ -*-===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "Generators.h"
|
||||
#include "Representation.h"
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
#include "llvm/Support/FileSystem.h"
|
||||
#include "llvm/Support/Path.h"
|
||||
#include <string>
|
||||
|
||||
using namespace llvm;
|
||||
|
||||
namespace clang {
|
||||
namespace doc {
|
||||
|
||||
// Enum conversion
|
||||
|
||||
std::string getAccess(AccessSpecifier AS) {
|
||||
switch (AS) {
|
||||
case AccessSpecifier::AS_public:
|
||||
return "public";
|
||||
case AccessSpecifier::AS_protected:
|
||||
return "protected";
|
||||
case AccessSpecifier::AS_private:
|
||||
return "private";
|
||||
case AccessSpecifier::AS_none:
|
||||
return {};
|
||||
}
|
||||
llvm_unreachable("Unknown AccessSpecifier");
|
||||
}
|
||||
|
||||
std::string getTagType(TagTypeKind AS) {
|
||||
switch (AS) {
|
||||
case TagTypeKind::TTK_Class:
|
||||
return "class";
|
||||
case TagTypeKind::TTK_Union:
|
||||
return "union";
|
||||
case TagTypeKind::TTK_Interface:
|
||||
return "interface";
|
||||
case TagTypeKind::TTK_Struct:
|
||||
return "struct";
|
||||
case TagTypeKind::TTK_Enum:
|
||||
return "enum";
|
||||
}
|
||||
llvm_unreachable("Unknown TagTypeKind");
|
||||
}
|
||||
|
||||
// Markdown generation
|
||||
|
||||
std::string genItalic(const Twine &Text) { return "*" + Text.str() + "*"; }
|
||||
|
||||
std::string genEmphasis(const Twine &Text) { return "**" + Text.str() + "**"; }
|
||||
|
||||
std::string genLink(const Twine &Text, const Twine &Link) {
|
||||
return "[" + Text.str() + "](" + Link.str() + ")";
|
||||
}
|
||||
|
||||
std::string genReferenceList(const llvm::SmallVectorImpl<Reference> &Refs) {
|
||||
std::string Buffer;
|
||||
llvm::raw_string_ostream Stream(Buffer);
|
||||
bool First = true;
|
||||
for (const auto &R : Refs) {
|
||||
if (!First)
|
||||
Stream << ", ";
|
||||
Stream << R.Name;
|
||||
First = false;
|
||||
}
|
||||
return Stream.str();
|
||||
}
|
||||
|
||||
void writeLine(const Twine &Text, raw_ostream &OS) { OS << Text << "\n\n"; }
|
||||
|
||||
void writeNewLine(raw_ostream &OS) { OS << "\n\n"; }
|
||||
|
||||
void writeHeader(const Twine &Text, unsigned int Num, raw_ostream &OS) {
|
||||
OS << std::string(Num, '#') + " " + Text << "\n\n";
|
||||
}
|
||||
|
||||
void writeFileDefinition(const Location &L, raw_ostream &OS) {
|
||||
OS << genItalic("Defined at line " + std::to_string(L.LineNumber) + " of " +
|
||||
L.Filename)
|
||||
<< "\n\n";
|
||||
}
|
||||
|
||||
void writeDescription(const CommentInfo &I, raw_ostream &OS) {
|
||||
if (I.Kind == "FullComment") {
|
||||
for (const auto &Child : I.Children)
|
||||
writeDescription(*Child, OS);
|
||||
} else if (I.Kind == "ParagraphComment") {
|
||||
for (const auto &Child : I.Children)
|
||||
writeDescription(*Child, OS);
|
||||
writeNewLine(OS);
|
||||
} else if (I.Kind == "BlockCommandComment") {
|
||||
OS << genEmphasis(I.Name);
|
||||
for (const auto &Child : I.Children)
|
||||
writeDescription(*Child, OS);
|
||||
} else if (I.Kind == "InlineCommandComment") {
|
||||
OS << genEmphasis(I.Name) << " " << I.Text;
|
||||
} else if (I.Kind == "ParamCommandComment") {
|
||||
std::string Direction = I.Explicit ? (" " + I.Direction).str() : "";
|
||||
OS << genEmphasis(I.ParamName) << I.Text << Direction << "\n\n";
|
||||
} else if (I.Kind == "TParamCommandComment") {
|
||||
std::string Direction = I.Explicit ? (" " + I.Direction).str() : "";
|
||||
OS << genEmphasis(I.ParamName) << I.Text << Direction << "\n\n";
|
||||
} else if (I.Kind == "VerbatimBlockComment") {
|
||||
for (const auto &Child : I.Children)
|
||||
writeDescription(*Child, OS);
|
||||
} else if (I.Kind == "VerbatimBlockLineComment") {
|
||||
OS << I.Text;
|
||||
writeNewLine(OS);
|
||||
} else if (I.Kind == "VerbatimLineComment") {
|
||||
OS << I.Text;
|
||||
writeNewLine(OS);
|
||||
} else if (I.Kind == "HTMLStartTagComment") {
|
||||
if (I.AttrKeys.size() != I.AttrValues.size())
|
||||
return;
|
||||
std::string Buffer;
|
||||
llvm::raw_string_ostream Attrs(Buffer);
|
||||
for (unsigned Idx = 0; Idx < I.AttrKeys.size(); ++Idx)
|
||||
Attrs << " \"" << I.AttrKeys[Idx] << "=" << I.AttrValues[Idx] << "\"";
|
||||
|
||||
std::string CloseTag = I.SelfClosing ? "/>" : ">";
|
||||
writeLine("<" + I.Name + Attrs.str() + CloseTag, OS);
|
||||
} else if (I.Kind == "HTMLEndTagComment") {
|
||||
writeLine("</" + I.Name + ">", OS);
|
||||
} else if (I.Kind == "TextComment") {
|
||||
OS << I.Text;
|
||||
} else {
|
||||
OS << "Unknown comment kind: " << I.Kind << ".\n\n";
|
||||
}
|
||||
}
|
||||
|
||||
void genMarkdown(const EnumInfo &I, llvm::raw_ostream &OS) {
|
||||
if (I.Scoped)
|
||||
writeLine("| enum class " + I.Name + " |", OS);
|
||||
else
|
||||
writeLine("| enum " + I.Name + " |", OS);
|
||||
writeLine("--", OS);
|
||||
|
||||
std::string Buffer;
|
||||
llvm::raw_string_ostream Members(Buffer);
|
||||
if (!I.Members.empty())
|
||||
for (const auto &N : I.Members)
|
||||
Members << "| " << N << " |\n";
|
||||
writeLine(Members.str(), OS);
|
||||
if (I.DefLoc)
|
||||
writeFileDefinition(I.DefLoc.getValue(), OS);
|
||||
|
||||
for (const auto &C : I.Description)
|
||||
writeDescription(C, OS);
|
||||
}
|
||||
|
||||
void genMarkdown(const FunctionInfo &I, llvm::raw_ostream &OS) {
|
||||
std::string Buffer;
|
||||
llvm::raw_string_ostream Stream(Buffer);
|
||||
bool First = true;
|
||||
for (const auto &N : I.Params) {
|
||||
if (!First)
|
||||
Stream << ", ";
|
||||
Stream << N.Type.Name + " " + N.Name;
|
||||
First = false;
|
||||
}
|
||||
writeHeader(I.Name, 3, OS);
|
||||
std::string Access = getAccess(I.Access);
|
||||
if (Access != "")
|
||||
writeLine(genItalic(Access + " " + I.ReturnType.Type.Name + " " + I.Name +
|
||||
"(" + Stream.str() + ")"),
|
||||
OS);
|
||||
else
|
||||
writeLine(genItalic(I.ReturnType.Type.Name + " " + I.Name + "(" +
|
||||
Stream.str() + ")"),
|
||||
OS);
|
||||
if (I.DefLoc)
|
||||
writeFileDefinition(I.DefLoc.getValue(), OS);
|
||||
|
||||
for (const auto &C : I.Description)
|
||||
writeDescription(C, OS);
|
||||
}
|
||||
|
||||
void genMarkdown(const NamespaceInfo &I, llvm::raw_ostream &OS) {
|
||||
if (I.Name == "")
|
||||
writeHeader("Global Namespace", 1, OS);
|
||||
else
|
||||
writeHeader("namespace " + I.Name, 1, OS);
|
||||
writeNewLine(OS);
|
||||
|
||||
if (!I.Description.empty()) {
|
||||
for (const auto &C : I.Description)
|
||||
writeDescription(C, OS);
|
||||
writeNewLine(OS);
|
||||
}
|
||||
|
||||
if (!I.ChildNamespaces.empty()) {
|
||||
writeHeader("Namespaces", 2, OS);
|
||||
for (const auto &R : I.ChildNamespaces)
|
||||
writeLine(R.Name, OS);
|
||||
writeNewLine(OS);
|
||||
}
|
||||
if (!I.ChildRecords.empty()) {
|
||||
writeHeader("Records", 2, OS);
|
||||
for (const auto &R : I.ChildRecords)
|
||||
writeLine(R.Name, OS);
|
||||
writeNewLine(OS);
|
||||
}
|
||||
if (!I.ChildFunctions.empty()) {
|
||||
writeHeader("Functions", 2, OS);
|
||||
for (const auto &F : I.ChildFunctions)
|
||||
genMarkdown(F, OS);
|
||||
writeNewLine(OS);
|
||||
}
|
||||
if (!I.ChildEnums.empty()) {
|
||||
writeHeader("Enums", 2, OS);
|
||||
for (const auto &E : I.ChildEnums)
|
||||
genMarkdown(E, OS);
|
||||
writeNewLine(OS);
|
||||
}
|
||||
}
|
||||
|
||||
void genMarkdown(const RecordInfo &I, llvm::raw_ostream &OS) {
|
||||
writeHeader(getTagType(I.TagType) + " " + I.Name, 1, OS);
|
||||
if (I.DefLoc)
|
||||
writeFileDefinition(I.DefLoc.getValue(), OS);
|
||||
|
||||
if (!I.Description.empty()) {
|
||||
for (const auto &C : I.Description)
|
||||
writeDescription(C, OS);
|
||||
writeNewLine(OS);
|
||||
}
|
||||
|
||||
std::string Parents = genReferenceList(I.Parents);
|
||||
std::string VParents = genReferenceList(I.VirtualParents);
|
||||
if (!Parents.empty() || !VParents.empty()) {
|
||||
if (Parents.empty())
|
||||
writeLine("Inherits from " + VParents, OS);
|
||||
else if (VParents.empty())
|
||||
writeLine("Inherits from " + Parents, OS);
|
||||
else
|
||||
writeLine("Inherits from " + Parents + ", " + VParents, OS);
|
||||
writeNewLine(OS);
|
||||
}
|
||||
|
||||
if (!I.Members.empty()) {
|
||||
writeHeader("Members", 2, OS);
|
||||
for (const auto Member : I.Members) {
|
||||
std::string Access = getAccess(Member.Access);
|
||||
if (Access != "")
|
||||
writeLine(Access + " " + Member.Type.Name + " " + Member.Name, OS);
|
||||
else
|
||||
writeLine(Member.Type.Name + " " + Member.Name, OS);
|
||||
}
|
||||
writeNewLine(OS);
|
||||
}
|
||||
|
||||
if (!I.ChildRecords.empty()) {
|
||||
writeHeader("Records", 2, OS);
|
||||
for (const auto &R : I.ChildRecords)
|
||||
writeLine(R.Name, OS);
|
||||
writeNewLine(OS);
|
||||
}
|
||||
if (!I.ChildFunctions.empty()) {
|
||||
writeHeader("Functions", 2, OS);
|
||||
for (const auto &F : I.ChildFunctions)
|
||||
genMarkdown(F, OS);
|
||||
writeNewLine(OS);
|
||||
}
|
||||
if (!I.ChildEnums.empty()) {
|
||||
writeHeader("Enums", 2, OS);
|
||||
for (const auto &E : I.ChildEnums)
|
||||
genMarkdown(E, OS);
|
||||
writeNewLine(OS);
|
||||
}
|
||||
}
|
||||
|
||||
/// Generator for Markdown documentation.
|
||||
class MDGenerator : public Generator {
|
||||
public:
|
||||
static const char *Format;
|
||||
|
||||
llvm::Error generateDocForInfo(Info *I, llvm::raw_ostream &OS) override;
|
||||
};
|
||||
|
||||
const char *MDGenerator::Format = "md";
|
||||
|
||||
llvm::Error MDGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS) {
|
||||
switch (I->IT) {
|
||||
case InfoType::IT_namespace:
|
||||
genMarkdown(*static_cast<clang::doc::NamespaceInfo *>(I), OS);
|
||||
break;
|
||||
case InfoType::IT_record:
|
||||
genMarkdown(*static_cast<clang::doc::RecordInfo *>(I), OS);
|
||||
break;
|
||||
case InfoType::IT_enum:
|
||||
genMarkdown(*static_cast<clang::doc::EnumInfo *>(I), OS);
|
||||
break;
|
||||
case InfoType::IT_function:
|
||||
genMarkdown(*static_cast<clang::doc::FunctionInfo *>(I), OS);
|
||||
break;
|
||||
case InfoType::IT_default:
|
||||
return llvm::make_error<llvm::StringError>("Unexpected info type.\n",
|
||||
llvm::inconvertibleErrorCode());
|
||||
}
|
||||
return llvm::Error::success();
|
||||
}
|
||||
|
||||
static GeneratorRegistry::Add<MDGenerator> MD(MDGenerator::Format,
|
||||
"Generator for MD output.");
|
||||
|
||||
// This anchor is used to force the linker to link in the generated object file
|
||||
// and thus register the generator.
|
||||
volatile int MDGeneratorAnchorSource = 0;
|
||||
|
||||
} // namespace doc
|
||||
} // namespace clang
|
||||
@@ -13,7 +13,6 @@
|
||||
#include "clang/AST/Comment.h"
|
||||
#include "clang/Index/USRGeneration.h"
|
||||
#include "llvm/ADT/StringExtras.h"
|
||||
#include "llvm/Support/Error.h"
|
||||
|
||||
using clang::comments::FullComment;
|
||||
|
||||
@@ -29,24 +28,19 @@ template <typename T> bool MapASTVisitor::mapDecl(const T *D) {
|
||||
if (D->getASTContext().getSourceManager().isInSystemHeader(D->getLocation()))
|
||||
return true;
|
||||
|
||||
// Skip function-internal decls.
|
||||
if (D->getParentFunctionOrMethod())
|
||||
return true;
|
||||
|
||||
llvm::SmallString<128> USR;
|
||||
// If there is an error generating a USR for the decl, skip this decl.
|
||||
if (index::generateUSRForDecl(D, USR))
|
||||
return true;
|
||||
|
||||
auto I = serialize::emitInfo(
|
||||
std::string info = serialize::emitInfo(
|
||||
D, getComment(D, D->getASTContext()), getLine(D, D->getASTContext()),
|
||||
getFile(D, D->getASTContext()), CDCtx.PublicOnly);
|
||||
|
||||
// A null in place of I indicates that the serializer is skipping this decl
|
||||
// for some reason (e.g. we're only reporting public decls).
|
||||
if (I)
|
||||
CDCtx.ECtx->reportResult(llvm::toHex(llvm::toStringRef(I->USR)),
|
||||
serialize::serialize(I));
|
||||
if (info != "")
|
||||
CDCtx.ECtx->reportResult(
|
||||
llvm::toHex(llvm::toStringRef(serialize::hashUSR(USR))), info);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -82,13 +76,13 @@ MapASTVisitor::getComment(const NamedDecl *D, const ASTContext &Context) const {
|
||||
|
||||
int MapASTVisitor::getLine(const NamedDecl *D,
|
||||
const ASTContext &Context) const {
|
||||
return Context.getSourceManager().getPresumedLoc(D->getBeginLoc()).getLine();
|
||||
return Context.getSourceManager().getPresumedLoc(D->getLocStart()).getLine();
|
||||
}
|
||||
|
||||
llvm::StringRef MapASTVisitor::getFile(const NamedDecl *D,
|
||||
const ASTContext &Context) const {
|
||||
return Context.getSourceManager()
|
||||
.getPresumedLoc(D->getBeginLoc())
|
||||
.getPresumedLoc(D->getLocStart())
|
||||
.getFilename();
|
||||
}
|
||||
|
||||
|
||||
@@ -26,70 +26,17 @@
|
||||
namespace clang {
|
||||
namespace doc {
|
||||
|
||||
namespace {
|
||||
|
||||
const SymbolID EmptySID = SymbolID();
|
||||
static const SymbolID EmptySID = SymbolID();
|
||||
|
||||
template <typename T>
|
||||
llvm::Expected<std::unique_ptr<Info>>
|
||||
reduce(std::vector<std::unique_ptr<Info>> &Values) {
|
||||
if (Values.empty())
|
||||
return llvm::make_error<llvm::StringError>(" No values to reduce.\n",
|
||||
llvm::inconvertibleErrorCode());
|
||||
std::unique_ptr<Info> Merged = llvm::make_unique<T>(Values[0]->USR);
|
||||
std::unique_ptr<Info> reduce(std::vector<std::unique_ptr<Info>> &Values) {
|
||||
std::unique_ptr<Info> Merged = llvm::make_unique<T>();
|
||||
T *Tmp = static_cast<T *>(Merged.get());
|
||||
for (auto &I : Values)
|
||||
Tmp->merge(std::move(*static_cast<T *>(I.get())));
|
||||
return std::move(Merged);
|
||||
return Merged;
|
||||
}
|
||||
|
||||
// Return the index of the matching child in the vector, or -1 if merge is not
|
||||
// necessary.
|
||||
template <typename T>
|
||||
int getChildIndexIfExists(std::vector<T> &Children, T &ChildToMerge) {
|
||||
for (unsigned long I = 0; I < Children.size(); I++) {
|
||||
if (ChildToMerge.USR == Children[I].USR)
|
||||
return I;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// For References, we don't need to actually merge them, we just don't want
|
||||
// duplicates.
|
||||
void reduceChildren(std::vector<Reference> &Children,
|
||||
std::vector<Reference> &&ChildrenToMerge) {
|
||||
for (auto &ChildToMerge : ChildrenToMerge) {
|
||||
if (getChildIndexIfExists(Children, ChildToMerge) == -1)
|
||||
Children.push_back(std::move(ChildToMerge));
|
||||
}
|
||||
}
|
||||
|
||||
void reduceChildren(std::vector<FunctionInfo> &Children,
|
||||
std::vector<FunctionInfo> &&ChildrenToMerge) {
|
||||
for (auto &ChildToMerge : ChildrenToMerge) {
|
||||
int mergeIdx = getChildIndexIfExists(Children, ChildToMerge);
|
||||
if (mergeIdx == -1) {
|
||||
Children.push_back(std::move(ChildToMerge));
|
||||
continue;
|
||||
}
|
||||
Children[mergeIdx].merge(std::move(ChildToMerge));
|
||||
}
|
||||
}
|
||||
|
||||
void reduceChildren(std::vector<EnumInfo> &Children,
|
||||
std::vector<EnumInfo> &&ChildrenToMerge) {
|
||||
for (auto &ChildToMerge : ChildrenToMerge) {
|
||||
int mergeIdx = getChildIndexIfExists(Children, ChildToMerge);
|
||||
if (mergeIdx == -1) {
|
||||
Children.push_back(std::move(ChildToMerge));
|
||||
continue;
|
||||
}
|
||||
Children[mergeIdx].merge(std::move(ChildToMerge));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Dispatch function.
|
||||
llvm::Expected<std::unique_ptr<Info>>
|
||||
mergeInfos(std::vector<std::unique_ptr<Info>> &Values) {
|
||||
@@ -126,7 +73,7 @@ void Info::mergeBase(Info &&Other) {
|
||||
}
|
||||
|
||||
bool Info::mergeable(const Info &Other) {
|
||||
return IT == Other.IT && USR == Other.USR;
|
||||
return IT == Other.IT && (USR == EmptySID || USR == Other.USR);
|
||||
}
|
||||
|
||||
void SymbolInfo::merge(SymbolInfo &&Other) {
|
||||
@@ -140,11 +87,6 @@ void SymbolInfo::merge(SymbolInfo &&Other) {
|
||||
|
||||
void NamespaceInfo::merge(NamespaceInfo &&Other) {
|
||||
assert(mergeable(Other));
|
||||
// Reduce children if necessary.
|
||||
reduceChildren(ChildNamespaces, std::move(Other.ChildNamespaces));
|
||||
reduceChildren(ChildRecords, std::move(Other.ChildRecords));
|
||||
reduceChildren(ChildFunctions, std::move(Other.ChildFunctions));
|
||||
reduceChildren(ChildEnums, std::move(Other.ChildEnums));
|
||||
mergeBase(std::move(Other));
|
||||
}
|
||||
|
||||
@@ -158,10 +100,6 @@ void RecordInfo::merge(RecordInfo &&Other) {
|
||||
Parents = std::move(Other.Parents);
|
||||
if (VirtualParents.empty())
|
||||
VirtualParents = std::move(Other.VirtualParents);
|
||||
// Reduce children if necessary.
|
||||
reduceChildren(ChildRecords, std::move(Other.ChildRecords));
|
||||
reduceChildren(ChildFunctions, std::move(Other.ChildFunctions));
|
||||
reduceChildren(ChildEnums, std::move(Other.ChildEnums));
|
||||
SymbolInfo::merge(std::move(Other));
|
||||
}
|
||||
|
||||
|
||||
@@ -31,9 +31,6 @@ namespace doc {
|
||||
using SymbolID = std::array<uint8_t, 20>;
|
||||
|
||||
struct Info;
|
||||
struct FunctionInfo;
|
||||
struct EnumInfo;
|
||||
|
||||
enum class InfoType {
|
||||
IT_default,
|
||||
IT_namespace,
|
||||
@@ -48,14 +45,13 @@ struct CommentInfo {
|
||||
CommentInfo(CommentInfo &Other) = delete;
|
||||
CommentInfo(CommentInfo &&Other) = default;
|
||||
|
||||
SmallString<16>
|
||||
Kind; // Kind of comment (FullComment, ParagraphComment, TextComment,
|
||||
// InlineCommandComment, HTMLStartTagComment, HTMLEndTagComment,
|
||||
// BlockCommandComment, ParamCommandComment,
|
||||
// TParamCommandComment, VerbatimBlockComment,
|
||||
// VerbatimBlockLineComment, VerbatimLineComment).
|
||||
SmallString<64> Text; // Text of the comment.
|
||||
SmallString<16> Name; // Name of the comment (for Verbatim and HTML).
|
||||
SmallString<16> Kind; // Kind of comment (TextComment, InlineCommandComment,
|
||||
// HTMLStartTagComment, HTMLEndTagComment,
|
||||
// BlockCommandComment, ParamCommandComment,
|
||||
// TParamCommandComment, VerbatimBlockComment,
|
||||
// VerbatimBlockLineComment, VerbatimLineComment).
|
||||
SmallString<64> Text; // Text of the comment.
|
||||
SmallString<16> Name; // Name of the comment (for Verbatim and HTML).
|
||||
SmallString<8> Direction; // Parameter direction (for (T)ParamCommand).
|
||||
SmallString<16> ParamName; // Parameter name (for (T)ParamCommand).
|
||||
SmallString<16> CloseName; // Closing tag name (for VerbatimBlock).
|
||||
@@ -157,14 +153,9 @@ struct Location {
|
||||
struct Info {
|
||||
Info() = default;
|
||||
Info(InfoType IT) : IT(IT) {}
|
||||
Info(InfoType IT, SymbolID USR) : USR(USR), IT(IT) {}
|
||||
Info(InfoType IT, SymbolID USR, StringRef Name)
|
||||
: USR(USR), IT(IT), Name(Name) {}
|
||||
Info(const Info &Other) = delete;
|
||||
Info(Info &&Other) = default;
|
||||
|
||||
virtual ~Info() = default;
|
||||
|
||||
SymbolID USR =
|
||||
SymbolID(); // Unique identifier for the decl described by this Info.
|
||||
const InfoType IT = InfoType::IT_default; // InfoType of this particular Info.
|
||||
@@ -175,36 +166,18 @@ struct Info {
|
||||
|
||||
void mergeBase(Info &&I);
|
||||
bool mergeable(const Info &Other);
|
||||
|
||||
// Returns a reference to the parent scope (that is, the immediate parent
|
||||
// namespace or class in which this decl resides).
|
||||
llvm::Expected<Reference> getEnclosingScope();
|
||||
};
|
||||
|
||||
// Info for namespaces.
|
||||
struct NamespaceInfo : public Info {
|
||||
NamespaceInfo() : Info(InfoType::IT_namespace) {}
|
||||
NamespaceInfo(SymbolID USR) : Info(InfoType::IT_namespace, USR) {}
|
||||
NamespaceInfo(SymbolID USR, StringRef Name)
|
||||
: Info(InfoType::IT_namespace, USR, Name) {}
|
||||
|
||||
void merge(NamespaceInfo &&I);
|
||||
|
||||
// Namespaces and Records are references because they will be properly
|
||||
// documented in their own info, while the entirety of Functions and Enums are
|
||||
// included here because they should not have separate documentation from
|
||||
// their scope.
|
||||
std::vector<Reference> ChildNamespaces;
|
||||
std::vector<Reference> ChildRecords;
|
||||
std::vector<FunctionInfo> ChildFunctions;
|
||||
std::vector<EnumInfo> ChildEnums;
|
||||
};
|
||||
|
||||
// Info for symbols.
|
||||
struct SymbolInfo : public Info {
|
||||
SymbolInfo(InfoType IT) : Info(IT) {}
|
||||
SymbolInfo(InfoType IT, SymbolID USR) : Info(IT, USR) {}
|
||||
SymbolInfo(InfoType IT, SymbolID USR, StringRef Name) : Info(IT, USR, Name) {}
|
||||
|
||||
void merge(SymbolInfo &&I);
|
||||
|
||||
@@ -216,7 +189,6 @@ struct SymbolInfo : public Info {
|
||||
// Info for functions.
|
||||
struct FunctionInfo : public SymbolInfo {
|
||||
FunctionInfo() : SymbolInfo(InfoType::IT_function) {}
|
||||
FunctionInfo(SymbolID USR) : SymbolInfo(InfoType::IT_function, USR) {}
|
||||
|
||||
void merge(FunctionInfo &&I);
|
||||
|
||||
@@ -233,9 +205,6 @@ struct FunctionInfo : public SymbolInfo {
|
||||
// Info for types.
|
||||
struct RecordInfo : public SymbolInfo {
|
||||
RecordInfo() : SymbolInfo(InfoType::IT_record) {}
|
||||
RecordInfo(SymbolID USR) : SymbolInfo(InfoType::IT_record, USR) {}
|
||||
RecordInfo(SymbolID USR, StringRef Name)
|
||||
: SymbolInfo(InfoType::IT_record, USR, Name) {}
|
||||
|
||||
void merge(RecordInfo &&I);
|
||||
|
||||
@@ -249,21 +218,12 @@ struct RecordInfo : public SymbolInfo {
|
||||
// parents).
|
||||
llvm::SmallVector<Reference, 4>
|
||||
VirtualParents; // List of virtual base/parent records.
|
||||
|
||||
// Records are references because they will be properly
|
||||
// documented in their own info, while the entirety of Functions and Enums are
|
||||
// included here because they should not have separate documentation from
|
||||
// their scope.
|
||||
std::vector<Reference> ChildRecords;
|
||||
std::vector<FunctionInfo> ChildFunctions;
|
||||
std::vector<EnumInfo> ChildEnums;
|
||||
};
|
||||
|
||||
// TODO: Expand to allow for documenting templating.
|
||||
// Info for types.
|
||||
struct EnumInfo : public SymbolInfo {
|
||||
EnumInfo() : SymbolInfo(InfoType::IT_enum) {}
|
||||
EnumInfo(SymbolID USR) : SymbolInfo(InfoType::IT_enum, USR) {}
|
||||
|
||||
void merge(EnumInfo &&I);
|
||||
|
||||
|
||||
@@ -152,21 +152,6 @@ template <typename T> static std::string serialize(T &I) {
|
||||
return Buffer.str().str();
|
||||
}
|
||||
|
||||
std::string serialize(std::unique_ptr<Info> &I) {
|
||||
switch (I->IT) {
|
||||
case InfoType::IT_namespace:
|
||||
return serialize(*static_cast<NamespaceInfo *>(I.get()));
|
||||
case InfoType::IT_record:
|
||||
return serialize(*static_cast<RecordInfo *>(I.get()));
|
||||
case InfoType::IT_enum:
|
||||
return serialize(*static_cast<EnumInfo *>(I.get()));
|
||||
case InfoType::IT_function:
|
||||
return serialize(*static_cast<FunctionInfo *>(I.get()));
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
static void parseFullComment(const FullComment *C, CommentInfo &CI) {
|
||||
ClangDocCommentVisitor Visitor(CI);
|
||||
Visitor.parseComment(C);
|
||||
@@ -244,9 +229,6 @@ static void parseParameters(FunctionInfo &I, const FunctionDecl *D) {
|
||||
}
|
||||
|
||||
static void parseBases(RecordInfo &I, const CXXRecordDecl *D) {
|
||||
// Don't parse bases if this isn't a definition.
|
||||
if (!D->isThisDeclarationADefinition())
|
||||
return;
|
||||
for (const CXXBaseSpecifier &B : D->bases()) {
|
||||
if (B.isVirtual())
|
||||
continue;
|
||||
@@ -324,106 +306,61 @@ static void populateFunctionInfo(FunctionInfo &I, const FunctionDecl *D,
|
||||
parseParameters(I, D);
|
||||
}
|
||||
|
||||
std::unique_ptr<Info> emitInfo(const NamespaceDecl *D, const FullComment *FC,
|
||||
int LineNumber, llvm::StringRef File,
|
||||
bool PublicOnly) {
|
||||
std::string emitInfo(const NamespaceDecl *D, const FullComment *FC,
|
||||
int LineNumber, llvm::StringRef File, bool PublicOnly) {
|
||||
if (PublicOnly && ((D->isAnonymousNamespace()) ||
|
||||
!isPublic(D->getAccess(), D->getLinkageInternal())))
|
||||
return nullptr;
|
||||
auto I = llvm::make_unique<NamespaceInfo>();
|
||||
populateInfo(*I, D, FC);
|
||||
return std::unique_ptr<Info>{std::move(I)};
|
||||
return "";
|
||||
NamespaceInfo I;
|
||||
populateInfo(I, D, FC);
|
||||
return serialize(I);
|
||||
}
|
||||
|
||||
std::unique_ptr<Info> emitInfo(const RecordDecl *D, const FullComment *FC,
|
||||
int LineNumber, llvm::StringRef File,
|
||||
bool PublicOnly) {
|
||||
std::string emitInfo(const RecordDecl *D, const FullComment *FC, int LineNumber,
|
||||
llvm::StringRef File, bool PublicOnly) {
|
||||
if (PublicOnly && !isPublic(D->getAccess(), D->getLinkageInternal()))
|
||||
return nullptr;
|
||||
auto I = llvm::make_unique<RecordInfo>();
|
||||
populateSymbolInfo(*I, D, FC, LineNumber, File);
|
||||
I->TagType = D->getTagKind();
|
||||
parseFields(*I, D, PublicOnly);
|
||||
return "";
|
||||
RecordInfo I;
|
||||
populateSymbolInfo(I, D, FC, LineNumber, File);
|
||||
I.TagType = D->getTagKind();
|
||||
parseFields(I, D, PublicOnly);
|
||||
if (const auto *C = dyn_cast<CXXRecordDecl>(D))
|
||||
parseBases(*I, C);
|
||||
return std::unique_ptr<Info>{std::move(I)};
|
||||
parseBases(I, C);
|
||||
return serialize(I);
|
||||
}
|
||||
|
||||
std::unique_ptr<Info> emitInfo(const FunctionDecl *D, const FullComment *FC,
|
||||
int LineNumber, llvm::StringRef File,
|
||||
bool PublicOnly) {
|
||||
std::string emitInfo(const FunctionDecl *D, const FullComment *FC,
|
||||
int LineNumber, llvm::StringRef File, bool PublicOnly) {
|
||||
if (PublicOnly && !isPublic(D->getAccess(), D->getLinkageInternal()))
|
||||
return nullptr;
|
||||
FunctionInfo Func;
|
||||
populateFunctionInfo(Func, D, FC, LineNumber, File);
|
||||
Func.Access = clang::AccessSpecifier::AS_none;
|
||||
|
||||
// Wrap in enclosing scope
|
||||
auto I = llvm::make_unique<NamespaceInfo>();
|
||||
if (!Func.Namespace.empty())
|
||||
I->USR = Func.Namespace[0].USR;
|
||||
else
|
||||
I->USR = SymbolID();
|
||||
I->ChildFunctions.emplace_back(std::move(Func));
|
||||
return std::unique_ptr<Info>{std::move(I)};
|
||||
return "";
|
||||
FunctionInfo I;
|
||||
populateFunctionInfo(I, D, FC, LineNumber, File);
|
||||
I.Access = clang::AccessSpecifier::AS_none;
|
||||
return serialize(I);
|
||||
}
|
||||
|
||||
std::unique_ptr<Info> emitInfo(const CXXMethodDecl *D, const FullComment *FC,
|
||||
int LineNumber, llvm::StringRef File,
|
||||
bool PublicOnly) {
|
||||
std::string emitInfo(const CXXMethodDecl *D, const FullComment *FC,
|
||||
int LineNumber, llvm::StringRef File, bool PublicOnly) {
|
||||
if (PublicOnly && !isPublic(D->getAccess(), D->getLinkageInternal()))
|
||||
return nullptr;
|
||||
FunctionInfo Func;
|
||||
populateFunctionInfo(Func, D, FC, LineNumber, File);
|
||||
Func.IsMethod = true;
|
||||
|
||||
SymbolID ParentUSR = getUSRForDecl(D->getParent());
|
||||
Func.Parent = Reference{ParentUSR, D->getParent()->getNameAsString(),
|
||||
InfoType::IT_record};
|
||||
Func.Access = D->getAccess();
|
||||
|
||||
// Wrap in enclosing scope
|
||||
auto I = llvm::make_unique<RecordInfo>();
|
||||
I->USR = ParentUSR;
|
||||
I->ChildFunctions.emplace_back(std::move(Func));
|
||||
return std::unique_ptr<Info>{std::move(I)};
|
||||
return "";
|
||||
FunctionInfo I;
|
||||
populateFunctionInfo(I, D, FC, LineNumber, File);
|
||||
I.IsMethod = true;
|
||||
I.Parent = Reference{getUSRForDecl(D->getParent()),
|
||||
D->getParent()->getNameAsString(), InfoType::IT_record};
|
||||
I.Access = D->getAccess();
|
||||
return serialize(I);
|
||||
}
|
||||
|
||||
std::unique_ptr<Info> emitInfo(const EnumDecl *D, const FullComment *FC,
|
||||
int LineNumber, llvm::StringRef File,
|
||||
bool PublicOnly) {
|
||||
std::string emitInfo(const EnumDecl *D, const FullComment *FC, int LineNumber,
|
||||
llvm::StringRef File, bool PublicOnly) {
|
||||
if (PublicOnly && !isPublic(D->getAccess(), D->getLinkageInternal()))
|
||||
return nullptr;
|
||||
EnumInfo Enum;
|
||||
populateSymbolInfo(Enum, D, FC, LineNumber, File);
|
||||
Enum.Scoped = D->isScoped();
|
||||
parseEnumerators(Enum, D);
|
||||
|
||||
// Wrap in enclosing scope
|
||||
if (!Enum.Namespace.empty()) {
|
||||
switch (Enum.Namespace[0].RefType) {
|
||||
case InfoType::IT_namespace: {
|
||||
auto I = llvm::make_unique<NamespaceInfo>();
|
||||
I->USR = Enum.Namespace[0].USR;
|
||||
I->ChildEnums.emplace_back(std::move(Enum));
|
||||
return std::unique_ptr<Info>{std::move(I)};
|
||||
}
|
||||
case InfoType::IT_record: {
|
||||
auto I = llvm::make_unique<RecordInfo>();
|
||||
I->USR = Enum.Namespace[0].USR;
|
||||
I->ChildEnums.emplace_back(std::move(Enum));
|
||||
return std::unique_ptr<Info>{std::move(I)};
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Put in global namespace
|
||||
auto I = llvm::make_unique<NamespaceInfo>();
|
||||
I->USR = SymbolID();
|
||||
I->ChildEnums.emplace_back(std::move(Enum));
|
||||
return std::unique_ptr<Info>{std::move(I)};
|
||||
return "";
|
||||
EnumInfo I;
|
||||
populateSymbolInfo(I, D, FC, LineNumber, File);
|
||||
I.Scoped = D->isScoped();
|
||||
parseEnumerators(I, D);
|
||||
return serialize(I);
|
||||
}
|
||||
|
||||
} // namespace serialize
|
||||
|
||||
@@ -28,16 +28,16 @@ namespace clang {
|
||||
namespace doc {
|
||||
namespace serialize {
|
||||
|
||||
std::unique_ptr<Info> emitInfo(const NamespaceDecl *D, const FullComment *FC,
|
||||
int LineNumber, StringRef File, bool PublicOnly);
|
||||
std::unique_ptr<Info> emitInfo(const RecordDecl *D, const FullComment *FC,
|
||||
int LineNumber, StringRef File, bool PublicOnly);
|
||||
std::unique_ptr<Info> emitInfo(const EnumDecl *D, const FullComment *FC,
|
||||
int LineNumber, StringRef File, bool PublicOnly);
|
||||
std::unique_ptr<Info> emitInfo(const FunctionDecl *D, const FullComment *FC,
|
||||
int LineNumber, StringRef File, bool PublicOnly);
|
||||
std::unique_ptr<Info> emitInfo(const CXXMethodDecl *D, const FullComment *FC,
|
||||
int LineNumber, StringRef File, bool PublicOnly);
|
||||
std::string emitInfo(const NamespaceDecl *D, const FullComment *FC,
|
||||
int LineNumber, StringRef File, bool PublicOnly);
|
||||
std::string emitInfo(const RecordDecl *D, const FullComment *FC, int LineNumber,
|
||||
StringRef File, bool PublicOnly);
|
||||
std::string emitInfo(const EnumDecl *D, const FullComment *FC, int LineNumber,
|
||||
StringRef File, bool PublicOnly);
|
||||
std::string emitInfo(const FunctionDecl *D, const FullComment *FC,
|
||||
int LineNumber, StringRef File, bool PublicOnly);
|
||||
std::string emitInfo(const CXXMethodDecl *D, const FullComment *FC,
|
||||
int LineNumber, StringRef File, bool PublicOnly);
|
||||
|
||||
// Function to hash a given USR value for storage.
|
||||
// As USRs (Unified Symbol Resolution) could be large, especially for functions
|
||||
@@ -46,8 +46,6 @@ std::unique_ptr<Info> emitInfo(const CXXMethodDecl *D, const FullComment *FC,
|
||||
// memory (vs storing USRs directly).
|
||||
SymbolID hashUSR(llvm::StringRef USR);
|
||||
|
||||
std::string serialize(std::unique_ptr<Info> &I);
|
||||
|
||||
} // namespace serialize
|
||||
} // namespace doc
|
||||
} // namespace clang
|
||||
|
||||
@@ -20,8 +20,6 @@ LLVM_YAML_IS_SEQUENCE_VECTOR(MemberTypeInfo)
|
||||
LLVM_YAML_IS_SEQUENCE_VECTOR(Reference)
|
||||
LLVM_YAML_IS_SEQUENCE_VECTOR(Location)
|
||||
LLVM_YAML_IS_SEQUENCE_VECTOR(CommentInfo)
|
||||
LLVM_YAML_IS_SEQUENCE_VECTOR(FunctionInfo)
|
||||
LLVM_YAML_IS_SEQUENCE_VECTOR(EnumInfo)
|
||||
LLVM_YAML_IS_SEQUENCE_VECTOR(std::unique_ptr<CommentInfo>)
|
||||
LLVM_YAML_IS_SEQUENCE_VECTOR(llvm::SmallString<16>)
|
||||
|
||||
@@ -177,14 +175,7 @@ template <> struct MappingTraits<MemberTypeInfo> {
|
||||
};
|
||||
|
||||
template <> struct MappingTraits<NamespaceInfo> {
|
||||
static void mapping(IO &IO, NamespaceInfo &I) {
|
||||
InfoMapping(IO, I);
|
||||
IO.mapOptional("ChildNamespaces", I.ChildNamespaces,
|
||||
std::vector<Reference>());
|
||||
IO.mapOptional("ChildRecords", I.ChildRecords, std::vector<Reference>());
|
||||
IO.mapOptional("ChildFunctions", I.ChildFunctions);
|
||||
IO.mapOptional("ChildEnums", I.ChildEnums);
|
||||
}
|
||||
static void mapping(IO &IO, NamespaceInfo &I) { InfoMapping(IO, I); }
|
||||
};
|
||||
|
||||
template <> struct MappingTraits<RecordInfo> {
|
||||
@@ -195,9 +186,6 @@ template <> struct MappingTraits<RecordInfo> {
|
||||
IO.mapOptional("Parents", I.Parents, llvm::SmallVector<Reference, 4>());
|
||||
IO.mapOptional("VirtualParents", I.VirtualParents,
|
||||
llvm::SmallVector<Reference, 4>());
|
||||
IO.mapOptional("ChildRecords", I.ChildRecords, std::vector<Reference>());
|
||||
IO.mapOptional("ChildFunctions", I.ChildFunctions);
|
||||
IO.mapOptional("ChildEnums", I.ChildEnums);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -242,12 +230,12 @@ class YAMLGenerator : public Generator {
|
||||
public:
|
||||
static const char *Format;
|
||||
|
||||
llvm::Error generateDocForInfo(Info *I, llvm::raw_ostream &OS) override;
|
||||
bool generateDocForInfo(Info *I, llvm::raw_ostream &OS) override;
|
||||
};
|
||||
|
||||
const char *YAMLGenerator::Format = "yaml";
|
||||
|
||||
llvm::Error YAMLGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS) {
|
||||
bool YAMLGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS) {
|
||||
llvm::yaml::Output InfoYAML(OS);
|
||||
switch (I->IT) {
|
||||
case InfoType::IT_namespace:
|
||||
@@ -263,10 +251,10 @@ llvm::Error YAMLGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS) {
|
||||
InfoYAML << *static_cast<clang::doc::FunctionInfo *>(I);
|
||||
break;
|
||||
case InfoType::IT_default:
|
||||
return llvm::make_error<llvm::StringError>("Unexpected info type.\n",
|
||||
llvm::inconvertibleErrorCode());
|
||||
llvm::errs() << "Unexpected info type in index.\n";
|
||||
return true;
|
||||
}
|
||||
return llvm::Error::success();
|
||||
return false;
|
||||
}
|
||||
|
||||
static GeneratorRegistry::Add<YAMLGenerator> YAML(YAMLGenerator::Format,
|
||||
|
||||
200
clang-tools-extra/clang-doc/gen_tests.py
Normal file
200
clang-tools-extra/clang-doc/gen_tests.py
Normal file
@@ -0,0 +1,200 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
#===- gen_tests.py - clang-doc test generator ----------------*- python -*--===#
|
||||
#
|
||||
# The LLVM Compiler Infrastructure
|
||||
#
|
||||
# This file is distributed under the University of Illinois Open Source
|
||||
# License. See LICENSE.TXT for details.
|
||||
#
|
||||
#===------------------------------------------------------------------------===#
|
||||
"""
|
||||
clang-doc test generator
|
||||
==========================
|
||||
|
||||
Generates tests for clang-doc given a certain set of flags, a prefix for the
|
||||
test file, and a given clang-doc binary. Please check emitted tests for
|
||||
accuracy before using.
|
||||
|
||||
To generate all current tests:
|
||||
- Generate mapper tests:
|
||||
gen_tests.py -flag='--dump-mapper' -flag='--doxygen' -prefix mapper
|
||||
|
||||
- Generate reducer tests:
|
||||
gen_tests.py -flag='--dump-intermediate' -flag='--doxygen' -prefix bc
|
||||
|
||||
- Generate yaml tests:
|
||||
gen_tests.py -flag='--format=yaml' -flag='--doxygen' -prefix yaml
|
||||
|
||||
This script was written on/for Linux, and has not been tested on any other
|
||||
platform and so it may not work.
|
||||
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import glob
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
RUN_CLANG_DOC = """
|
||||
// RUN: clang-doc {0} -p %t %t/test.cpp -output=%t/docs
|
||||
"""
|
||||
RUN = """
|
||||
// RUN: {0} %t/{1} | FileCheck %s --check-prefix CHECK-{2}
|
||||
"""
|
||||
|
||||
CHECK = '// CHECK-{0}: '
|
||||
|
||||
CHECK_NEXT = '// CHECK-{0}-NEXT: '
|
||||
|
||||
|
||||
def clear_test_prefix_files(prefix, tests_path):
|
||||
if os.path.isdir(tests_path):
|
||||
for root, dirs, files in os.walk(tests_path):
|
||||
for filename in files:
|
||||
if filename.startswith(prefix):
|
||||
os.remove(os.path.join(root, filename))
|
||||
|
||||
|
||||
def copy_to_test_file(test_case_path, test_cases_path):
|
||||
# Copy file to 'test.cpp' to preserve file-dependent USRs
|
||||
test_file = os.path.join(test_cases_path, 'test.cpp')
|
||||
shutil.copyfile(test_case_path, test_file)
|
||||
return test_file
|
||||
|
||||
|
||||
def run_clang_doc(args, out_dir, test_file):
|
||||
# Run clang-doc.
|
||||
current_cmd = [args.clangdoc]
|
||||
current_cmd.extend(args.flags)
|
||||
current_cmd.append('--output=' + out_dir)
|
||||
current_cmd.append(test_file)
|
||||
print('Running ' + ' '.join(current_cmd))
|
||||
return_code = subprocess.call(current_cmd)
|
||||
if return_code:
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
def get_test_case_code(test_case_path, flags):
|
||||
# Get the test case code
|
||||
code = ''
|
||||
with open(test_case_path, 'r') as code_file:
|
||||
code = code_file.read()
|
||||
|
||||
code += RUN_CLANG_DOC.format(flags)
|
||||
return code
|
||||
|
||||
|
||||
def get_output(root, out_file, case_out_path, flags, checkname, bcanalyzer):
|
||||
output = ''
|
||||
run_cmd = ''
|
||||
if '--dump-mapper' in flags or '--dump-intermediate' in flags:
|
||||
# Run llvm-bcanalyzer
|
||||
output = subprocess.check_output(
|
||||
[bcanalyzer, '--dump',
|
||||
os.path.join(root, out_file)])
|
||||
output = output[:output.find('Summary of ')].rstrip()
|
||||
run_cmd = RUN.format('llvm-bcanalyzer --dump',
|
||||
os.path.join('docs', 'bc', out_file), checkname)
|
||||
else:
|
||||
# Run cat
|
||||
output = subprocess.check_output(['cat', os.path.join(root, out_file)])
|
||||
run_cmd = RUN.format(
|
||||
'cat',
|
||||
os.path.join('docs', os.path.relpath(root, case_out_path),
|
||||
out_file), checkname)
|
||||
|
||||
# Format output.
|
||||
output = output.replace('blob data = \'test\'', 'blob data = \'{{.*}}\'')
|
||||
output = CHECK.format(checkname) + output.rstrip()
|
||||
output = run_cmd + output.replace('\n',
|
||||
'\n' + CHECK_NEXT.format(checkname))
|
||||
|
||||
return output + '\n'
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Generate clang-doc tests.')
|
||||
parser.add_argument(
|
||||
'-flag',
|
||||
action='append',
|
||||
default=[],
|
||||
dest='flags',
|
||||
help='Flags to pass to clang-doc.')
|
||||
parser.add_argument(
|
||||
'-prefix',
|
||||
type=str,
|
||||
default='',
|
||||
dest='prefix',
|
||||
help='Prefix for this test group.')
|
||||
parser.add_argument(
|
||||
'-clang-doc-binary',
|
||||
dest='clangdoc',
|
||||
metavar="PATH",
|
||||
default='clang-doc',
|
||||
help='path to clang-doc binary')
|
||||
parser.add_argument(
|
||||
'-llvm-bcanalyzer-binary',
|
||||
dest='bcanalyzer',
|
||||
metavar="PATH",
|
||||
default='llvm-bcanalyzer',
|
||||
help='path to llvm-bcanalyzer binary')
|
||||
args = parser.parse_args()
|
||||
|
||||
flags = ' '.join(args.flags)
|
||||
|
||||
clang_doc_path = os.path.dirname(__file__)
|
||||
tests_path = os.path.join(clang_doc_path, '..', 'test', 'clang-doc')
|
||||
test_cases_path = os.path.join(tests_path, 'test_cases')
|
||||
|
||||
clear_test_prefix_files(args.prefix, tests_path)
|
||||
|
||||
for test_case_path in glob.glob(os.path.join(test_cases_path, '*')):
|
||||
if test_case_path.endswith(
|
||||
'compile_flags.txt') or test_case_path.endswith(
|
||||
'compile_commands.json'):
|
||||
continue
|
||||
|
||||
# Name of this test case
|
||||
case_name = os.path.basename(test_case_path).split('.')[0]
|
||||
|
||||
test_file = copy_to_test_file(test_case_path, test_cases_path)
|
||||
out_dir = os.path.join(test_cases_path, case_name)
|
||||
|
||||
if run_clang_doc(args, out_dir, test_file):
|
||||
return 1
|
||||
|
||||
# Retrieve output and format as FileCheck tests
|
||||
all_output = ''
|
||||
num_outputs = 0
|
||||
for root, dirs, files in os.walk(out_dir):
|
||||
for out_file in files:
|
||||
# Make the file check the first 3 letters (there's a very small chance
|
||||
# that this will collide, but the fix is to simply change the decl name)
|
||||
usr = os.path.basename(out_file).split('.')
|
||||
# If the usr is less than 2, this isn't one of the test files.
|
||||
if len(usr) < 2:
|
||||
continue
|
||||
all_output += get_output(root, out_file, out_dir, args.flags,
|
||||
num_outputs, args.bcanalyzer)
|
||||
num_outputs += 1
|
||||
|
||||
# Add test case code to test
|
||||
all_output = get_test_case_code(test_case_path,
|
||||
flags) + '\n' + all_output
|
||||
|
||||
# Write to test case file in /test.
|
||||
test_out_path = os.path.join(
|
||||
tests_path, args.prefix + '-' + os.path.basename(test_case_path))
|
||||
with open(test_out_path, 'w+') as o:
|
||||
o.write(all_output)
|
||||
|
||||
# Clean up
|
||||
shutil.rmtree(out_dir)
|
||||
os.remove(test_file)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -31,9 +31,9 @@
|
||||
#include "clang/Frontend/FrontendActions.h"
|
||||
#include "clang/Tooling/CommonOptionsParser.h"
|
||||
#include "clang/Tooling/Execution.h"
|
||||
#include "clang/Tooling/StandaloneExecution.h"
|
||||
#include "clang/Tooling/Tooling.h"
|
||||
#include "llvm/ADT/APFloat.h"
|
||||
#include "llvm/Support/CommandLine.h"
|
||||
#include "llvm/Support/Error.h"
|
||||
#include "llvm/Support/FileSystem.h"
|
||||
#include "llvm/Support/Path.h"
|
||||
@@ -54,39 +54,34 @@ static llvm::cl::opt<std::string>
|
||||
llvm::cl::desc("Directory for outputting generated files."),
|
||||
llvm::cl::init("docs"), llvm::cl::cat(ClangDocCategory));
|
||||
|
||||
static llvm::cl::opt<bool>
|
||||
DumpMapperResult("dump-mapper",
|
||||
llvm::cl::desc("Dump mapper results to bitcode file."),
|
||||
llvm::cl::init(false), llvm::cl::cat(ClangDocCategory));
|
||||
|
||||
static llvm::cl::opt<bool> DumpIntermediateResult(
|
||||
"dump-intermediate",
|
||||
llvm::cl::desc("Dump intermediate results to bitcode file."),
|
||||
llvm::cl::init(false), llvm::cl::cat(ClangDocCategory));
|
||||
|
||||
static llvm::cl::opt<bool>
|
||||
PublicOnly("public", llvm::cl::desc("Document only public declarations."),
|
||||
llvm::cl::init(false), llvm::cl::cat(ClangDocCategory));
|
||||
|
||||
enum OutputFormatTy {
|
||||
yaml,
|
||||
};
|
||||
|
||||
static llvm::cl::opt<OutputFormatTy> FormatEnum(
|
||||
"format", llvm::cl::desc("Format for outputted docs."),
|
||||
llvm::cl::values(clEnumVal(yaml, "Documentation in YAML format.")),
|
||||
llvm::cl::init(yaml), llvm::cl::cat(ClangDocCategory));
|
||||
|
||||
static llvm::cl::opt<bool> DoxygenOnly(
|
||||
"doxygen",
|
||||
llvm::cl::desc("Use only doxygen-style comments to generate docs."),
|
||||
llvm::cl::init(false), llvm::cl::cat(ClangDocCategory));
|
||||
|
||||
enum OutputFormatTy {
|
||||
md,
|
||||
yaml,
|
||||
};
|
||||
|
||||
static llvm::cl::opt<OutputFormatTy>
|
||||
FormatEnum("format", llvm::cl::desc("Format for outputted docs."),
|
||||
llvm::cl::values(clEnumValN(OutputFormatTy::yaml, "yaml",
|
||||
"Documentation in YAML format."),
|
||||
clEnumValN(OutputFormatTy::md, "md",
|
||||
"Documentation in MD format.")),
|
||||
llvm::cl::init(OutputFormatTy::yaml),
|
||||
llvm::cl::cat(ClangDocCategory));
|
||||
|
||||
std::string getFormatString() {
|
||||
switch (FormatEnum) {
|
||||
case OutputFormatTy::yaml:
|
||||
return "yaml";
|
||||
case OutputFormatTy::md:
|
||||
return "md";
|
||||
}
|
||||
llvm_unreachable("Unknown OutputFormatTy");
|
||||
}
|
||||
|
||||
bool CreateDirectory(const Twine &DirName, bool ClearDirectory = false) {
|
||||
std::error_code OK;
|
||||
llvm::SmallString<128> DocsRootPath;
|
||||
@@ -106,24 +101,29 @@ bool CreateDirectory(const Twine &DirName, bool ClearDirectory = false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// A function to extract the appropriate path name for a given info's
|
||||
// documentation. The path returned is a composite of the parent namespaces as
|
||||
// directories plus the decl name as the filename.
|
||||
//
|
||||
// Example: Given the below, the <ext> path for class C will be <
|
||||
// root>/A/B/C.<ext>
|
||||
//
|
||||
// namespace A {
|
||||
// namesapce B {
|
||||
//
|
||||
// class C {};
|
||||
//
|
||||
// }
|
||||
// }
|
||||
bool DumpResultToFile(const Twine &DirName, const Twine &FileName,
|
||||
StringRef Buffer, bool ClearDirectory = false) {
|
||||
std::error_code OK;
|
||||
llvm::SmallString<128> IRRootPath;
|
||||
llvm::sys::path::native(OutDirectory, IRRootPath);
|
||||
llvm::sys::path::append(IRRootPath, DirName);
|
||||
if (CreateDirectory(IRRootPath, ClearDirectory))
|
||||
return true;
|
||||
llvm::sys::path::append(IRRootPath, FileName);
|
||||
std::error_code OutErrorInfo;
|
||||
llvm::raw_fd_ostream OS(IRRootPath, OutErrorInfo, llvm::sys::fs::F_None);
|
||||
if (OutErrorInfo != OK) {
|
||||
llvm::errs() << "Error opening documentation file.\n";
|
||||
return true;
|
||||
}
|
||||
OS << Buffer;
|
||||
OS.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
llvm::Expected<llvm::SmallString<128>>
|
||||
getInfoOutputFile(StringRef Root,
|
||||
llvm::SmallVectorImpl<doc::Reference> &Namespaces,
|
||||
StringRef Name, StringRef Ext) {
|
||||
getPath(StringRef Root, StringRef Ext, StringRef Name,
|
||||
llvm::SmallVectorImpl<doc::Reference> &Namespaces) {
|
||||
std::error_code OK;
|
||||
llvm::SmallString<128> Path;
|
||||
llvm::sys::path::native(Root, Path);
|
||||
@@ -134,41 +134,30 @@ getInfoOutputFile(StringRef Root,
|
||||
return llvm::make_error<llvm::StringError>("Unable to create directory.\n",
|
||||
llvm::inconvertibleErrorCode());
|
||||
|
||||
if (Name.empty())
|
||||
Name = "GlobalNamespace";
|
||||
llvm::sys::path::append(Path, Name + Ext);
|
||||
return Path;
|
||||
}
|
||||
|
||||
// Iterate through tool results and build string map of info vectors from the
|
||||
// encoded bitstreams.
|
||||
bool bitcodeResultsToInfos(
|
||||
tooling::ToolResults &Results,
|
||||
llvm::StringMap<std::vector<std::unique_ptr<doc::Info>>> &Output) {
|
||||
bool Err = false;
|
||||
Results.forEachResult([&](StringRef Key, StringRef Value) {
|
||||
llvm::BitstreamCursor Stream(Value);
|
||||
doc::ClangDocBitcodeReader Reader(Stream);
|
||||
auto Infos = Reader.readBitcode();
|
||||
if (!Infos) {
|
||||
llvm::errs() << toString(Infos.takeError()) << "\n";
|
||||
Err = true;
|
||||
return;
|
||||
}
|
||||
for (auto &I : Infos.get()) {
|
||||
auto R =
|
||||
Output.try_emplace(Key, std::vector<std::unique_ptr<doc::Info>>());
|
||||
R.first->second.emplace_back(std::move(I));
|
||||
}
|
||||
});
|
||||
return Err;
|
||||
std::string getFormatString(OutputFormatTy Ty) {
|
||||
switch (Ty) {
|
||||
case yaml:
|
||||
return "yaml";
|
||||
}
|
||||
llvm_unreachable("Unknown OutputFormatTy");
|
||||
}
|
||||
|
||||
int main(int argc, const char **argv) {
|
||||
llvm::sys::PrintStackTraceOnErrorSignal(argv[0]);
|
||||
std::error_code OK;
|
||||
|
||||
ExecutorName.setInitialValue("all-TUs");
|
||||
// Fail early if an invalid format was provided.
|
||||
std::string Format = getFormatString(FormatEnum);
|
||||
auto G = doc::findGeneratorByName(Format);
|
||||
if (!G) {
|
||||
llvm::errs() << toString(G.takeError()) << "\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto Exec = clang::tooling::createExecutorFromCommandLineArgs(
|
||||
argc, argv, ClangDocCategory);
|
||||
|
||||
@@ -177,15 +166,6 @@ int main(int argc, const char **argv) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Fail early if an invalid format was provided.
|
||||
std::string Format = getFormatString();
|
||||
llvm::outs() << "Emiting docs in " << Format << " format.\n";
|
||||
auto G = doc::findGeneratorByName(Format);
|
||||
if (!G) {
|
||||
llvm::errs() << toString(G.takeError()) << "\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
ArgumentsAdjuster ArgAdjuster;
|
||||
if (!DoxygenOnly)
|
||||
ArgAdjuster = combineAdjusters(
|
||||
@@ -204,27 +184,56 @@ int main(int argc, const char **argv) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (DumpMapperResult) {
|
||||
bool Err = false;
|
||||
Exec->get()->getToolResults()->forEachResult(
|
||||
[&](StringRef Key, StringRef Value) {
|
||||
Err = DumpResultToFile("bc", Key + ".bc", Value);
|
||||
});
|
||||
if (Err)
|
||||
llvm::errs() << "Error dumping map results.\n";
|
||||
return Err;
|
||||
}
|
||||
|
||||
// Collect values into output by key.
|
||||
llvm::outs() << "Collecting infos...\n";
|
||||
llvm::StringMap<std::vector<std::unique_ptr<doc::Info>>> MapOutput;
|
||||
|
||||
// In ToolResults, the Key is the hashed USR and the value is the
|
||||
// bitcode-encoded representation of the Info object.
|
||||
llvm::outs() << "Collecting infos...\n";
|
||||
llvm::StringMap<std::vector<std::unique_ptr<doc::Info>>> USRToInfos;
|
||||
if (bitcodeResultsToInfos(*Exec->get()->getToolResults(), USRToInfos))
|
||||
return 1;
|
||||
Exec->get()->getToolResults()->forEachResult([&](StringRef Key,
|
||||
StringRef Value) {
|
||||
llvm::BitstreamCursor Stream(Value);
|
||||
doc::ClangDocBitcodeReader Reader(Stream);
|
||||
auto Infos = Reader.readBitcode();
|
||||
for (auto &I : Infos) {
|
||||
auto R =
|
||||
MapOutput.try_emplace(Key, std::vector<std::unique_ptr<doc::Info>>());
|
||||
R.first->second.emplace_back(std::move(I));
|
||||
}
|
||||
});
|
||||
|
||||
// First reducing phase (reduce all decls into one info per decl).
|
||||
llvm::outs() << "Reducing " << USRToInfos.size() << " infos...\n";
|
||||
for (auto &Group : USRToInfos) {
|
||||
// Reducing and generation phases
|
||||
llvm::outs() << "Reducing " << MapOutput.size() << " infos...\n";
|
||||
llvm::StringMap<std::unique_ptr<doc::Info>> ReduceOutput;
|
||||
for (auto &Group : MapOutput) {
|
||||
auto Reduced = doc::mergeInfos(Group.getValue());
|
||||
if (!Reduced) {
|
||||
if (!Reduced)
|
||||
llvm::errs() << llvm::toString(Reduced.takeError());
|
||||
|
||||
if (DumpIntermediateResult) {
|
||||
SmallString<4096> Buffer;
|
||||
llvm::BitstreamWriter Stream(Buffer);
|
||||
doc::ClangDocBitcodeWriter Writer(Stream);
|
||||
Writer.dispatchInfoForWrite(Reduced.get().get());
|
||||
if (DumpResultToFile("bc", Group.getKey() + ".bc", Buffer))
|
||||
llvm::errs() << "Error dumping to bitcode.\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create the relevant ostream and emit the documentation for this decl.
|
||||
doc::Info *I = Reduced.get().get();
|
||||
|
||||
auto InfoPath =
|
||||
getInfoOutputFile(OutDirectory, I->Namespace, I->Name, "." + Format);
|
||||
auto InfoPath = getPath(OutDirectory, "." + Format, I->Name, I->Namespace);
|
||||
if (!InfoPath) {
|
||||
llvm::errs() << toString(InfoPath.takeError()) << "\n";
|
||||
continue;
|
||||
@@ -232,12 +241,12 @@ int main(int argc, const char **argv) {
|
||||
std::error_code FileErr;
|
||||
llvm::raw_fd_ostream InfoOS(InfoPath.get(), FileErr, llvm::sys::fs::F_None);
|
||||
if (FileErr != OK) {
|
||||
llvm::errs() << "Error opening info file: " << FileErr.message() << "\n";
|
||||
llvm::errs() << "Error opening index file: " << FileErr.message() << "\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (auto Err = G->get()->generateDocForInfo(I, InfoOS))
|
||||
llvm::errs() << toString(std::move(Err)) << "\n";
|
||||
if (G->get()->generateDocForInfo(I, InfoOS))
|
||||
llvm::errs() << "Unable to generate docs for info.\n";
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -14,7 +14,6 @@ add_clang_library(clangMove
|
||||
clangFormat
|
||||
clangFrontend
|
||||
clangLex
|
||||
clangSerialization
|
||||
clangTooling
|
||||
clangToolingCore
|
||||
)
|
||||
|
||||
@@ -76,7 +76,10 @@ std::string MakeAbsolutePath(StringRef CurrentDir, StringRef Path) {
|
||||
return "";
|
||||
llvm::SmallString<128> InitialDirectory(CurrentDir);
|
||||
llvm::SmallString<128> AbsolutePath(Path);
|
||||
llvm::sys::fs::make_absolute(InitialDirectory, AbsolutePath);
|
||||
if (std::error_code EC =
|
||||
llvm::sys::fs::make_absolute(InitialDirectory, AbsolutePath))
|
||||
llvm::errs() << "Warning: could not make absolute file: '" << EC.message()
|
||||
<< '\n';
|
||||
return CleanPath(std::move(AbsolutePath));
|
||||
}
|
||||
|
||||
@@ -114,7 +117,7 @@ AST_POLYMORPHIC_MATCHER_P(isExpansionInFile,
|
||||
AST_POLYMORPHIC_SUPPORTED_TYPES(Decl, Stmt, TypeLoc),
|
||||
std::string, AbsoluteFilePath) {
|
||||
auto &SourceManager = Finder->getASTContext().getSourceManager();
|
||||
auto ExpansionLoc = SourceManager.getExpansionLoc(Node.getBeginLoc());
|
||||
auto ExpansionLoc = SourceManager.getExpansionLoc(Node.getLocStart());
|
||||
if (ExpansionLoc.isInvalid())
|
||||
return false;
|
||||
auto FileEntry =
|
||||
@@ -125,17 +128,18 @@ AST_POLYMORPHIC_MATCHER_P(isExpansionInFile,
|
||||
AbsoluteFilePath;
|
||||
}
|
||||
|
||||
class FindAllIncludes : public PPCallbacks {
|
||||
class FindAllIncludes : public clang::PPCallbacks {
|
||||
public:
|
||||
explicit FindAllIncludes(SourceManager *SM, ClangMoveTool *const MoveTool)
|
||||
: SM(*SM), MoveTool(MoveTool) {}
|
||||
|
||||
void InclusionDirective(SourceLocation HashLoc, const Token & /*IncludeTok*/,
|
||||
void InclusionDirective(clang::SourceLocation HashLoc,
|
||||
const clang::Token & /*IncludeTok*/,
|
||||
StringRef FileName, bool IsAngled,
|
||||
CharSourceRange FilenameRange,
|
||||
const FileEntry * /*File*/, StringRef SearchPath,
|
||||
StringRef /*RelativePath*/,
|
||||
const Module * /*Imported*/,
|
||||
clang::CharSourceRange FilenameRange,
|
||||
const clang::FileEntry * /*File*/,
|
||||
StringRef SearchPath, StringRef /*RelativePath*/,
|
||||
const clang::Module * /*Imported*/,
|
||||
SrcMgr::CharacteristicKind /*FileType*/) override {
|
||||
if (const auto *FileEntry = SM.getFileEntryForID(SM.getFileID(HashLoc)))
|
||||
MoveTool->addIncludes(FileName, IsAngled, SearchPath,
|
||||
@@ -161,9 +165,9 @@ public:
|
||||
: MoveTool(MoveTool) {}
|
||||
|
||||
void run(const MatchFinder::MatchResult &Result) override {
|
||||
const auto *FD = Result.Nodes.getNodeAs<FunctionDecl>("function");
|
||||
const auto *FD = Result.Nodes.getNodeAs<clang::FunctionDecl>("function");
|
||||
assert(FD);
|
||||
const NamedDecl *D = FD;
|
||||
const clang::NamedDecl *D = FD;
|
||||
if (const auto *FTD = FD->getDescribedFunctionTemplate())
|
||||
D = FTD;
|
||||
MoveDeclFromOldFileToNewFile(MoveTool, D);
|
||||
@@ -179,7 +183,7 @@ public:
|
||||
: MoveTool(MoveTool) {}
|
||||
|
||||
void run(const MatchFinder::MatchResult &Result) override {
|
||||
const auto *VD = Result.Nodes.getNodeAs<VarDecl>("var");
|
||||
const auto *VD = Result.Nodes.getNodeAs<clang::VarDecl>("var");
|
||||
assert(VD);
|
||||
MoveDeclFromOldFileToNewFile(MoveTool, VD);
|
||||
}
|
||||
@@ -194,10 +198,10 @@ public:
|
||||
: MoveTool(MoveTool) {}
|
||||
|
||||
void run(const MatchFinder::MatchResult &Result) override {
|
||||
if (const auto *TD = Result.Nodes.getNodeAs<TypedefDecl>("typedef"))
|
||||
if (const auto *TD = Result.Nodes.getNodeAs<clang::TypedefDecl>("typedef"))
|
||||
MoveDeclFromOldFileToNewFile(MoveTool, TD);
|
||||
else if (const auto *TAD =
|
||||
Result.Nodes.getNodeAs<TypeAliasDecl>("type_alias")) {
|
||||
Result.Nodes.getNodeAs<clang::TypeAliasDecl>("type_alias")) {
|
||||
const NamedDecl * D = TAD;
|
||||
if (const auto * TD = TAD->getDescribedAliasTemplate())
|
||||
D = TD;
|
||||
@@ -215,7 +219,7 @@ public:
|
||||
: MoveTool(MoveTool) {}
|
||||
|
||||
void run(const MatchFinder::MatchResult &Result) override {
|
||||
const auto *ED = Result.Nodes.getNodeAs<EnumDecl>("enum");
|
||||
const auto *ED = Result.Nodes.getNodeAs<clang::EnumDecl>("enum");
|
||||
assert(ED);
|
||||
MoveDeclFromOldFileToNewFile(MoveTool, ED);
|
||||
}
|
||||
@@ -229,19 +233,21 @@ public:
|
||||
explicit ClassDeclarationMatch(ClangMoveTool *MoveTool)
|
||||
: MoveTool(MoveTool) {}
|
||||
void run(const MatchFinder::MatchResult &Result) override {
|
||||
SourceManager *SM = &Result.Context->getSourceManager();
|
||||
if (const auto *CMD = Result.Nodes.getNodeAs<CXXMethodDecl>("class_method"))
|
||||
clang::SourceManager* SM = &Result.Context->getSourceManager();
|
||||
if (const auto *CMD =
|
||||
Result.Nodes.getNodeAs<clang::CXXMethodDecl>("class_method"))
|
||||
MatchClassMethod(CMD, SM);
|
||||
else if (const auto *VD =
|
||||
Result.Nodes.getNodeAs<VarDecl>("class_static_var_decl"))
|
||||
else if (const auto *VD = Result.Nodes.getNodeAs<clang::VarDecl>(
|
||||
"class_static_var_decl"))
|
||||
MatchClassStaticVariable(VD, SM);
|
||||
else if (const auto *CD =
|
||||
Result.Nodes.getNodeAs<CXXRecordDecl>("moved_class"))
|
||||
else if (const auto *CD = Result.Nodes.getNodeAs<clang::CXXRecordDecl>(
|
||||
"moved_class"))
|
||||
MatchClassDeclaration(CD, SM);
|
||||
}
|
||||
|
||||
private:
|
||||
void MatchClassMethod(const CXXMethodDecl *CMD, SourceManager *SM) {
|
||||
void MatchClassMethod(const clang::CXXMethodDecl* CMD,
|
||||
clang::SourceManager* SM) {
|
||||
// Skip inline class methods. isInline() ast matcher doesn't ignore this
|
||||
// case.
|
||||
if (!CMD->isInlined()) {
|
||||
@@ -256,11 +262,13 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
void MatchClassStaticVariable(const NamedDecl *VD, SourceManager *SM) {
|
||||
void MatchClassStaticVariable(const clang::NamedDecl *VD,
|
||||
clang::SourceManager* SM) {
|
||||
MoveDeclFromOldFileToNewFile(MoveTool, VD);
|
||||
}
|
||||
|
||||
void MatchClassDeclaration(const CXXRecordDecl *CD, SourceManager *SM) {
|
||||
void MatchClassDeclaration(const clang::CXXRecordDecl *CD,
|
||||
clang::SourceManager* SM) {
|
||||
// Get class template from its class declaration as UnremovedDecls stores
|
||||
// class template.
|
||||
if (const auto *TC = CD->getDescribedClassTemplate())
|
||||
@@ -277,13 +285,14 @@ private:
|
||||
|
||||
// Expand to get the end location of the line where the EndLoc of the given
|
||||
// Decl.
|
||||
SourceLocation getLocForEndOfDecl(const Decl *D,
|
||||
const LangOptions &LangOpts = LangOptions()) {
|
||||
SourceLocation
|
||||
getLocForEndOfDecl(const clang::Decl *D,
|
||||
const LangOptions &LangOpts = clang::LangOptions()) {
|
||||
const auto &SM = D->getASTContext().getSourceManager();
|
||||
// If the expansion range is a character range, this is the location of
|
||||
// the first character past the end. Otherwise it's the location of the
|
||||
// first character in the final token in the range.
|
||||
auto EndExpansionLoc = SM.getExpansionRange(D->getEndLoc()).getEnd();
|
||||
auto EndExpansionLoc = SM.getExpansionRange(D->getLocEnd()).getEnd();
|
||||
std::pair<FileID, unsigned> LocInfo = SM.getDecomposedLoc(EndExpansionLoc);
|
||||
// Try to load the file buffer.
|
||||
bool InvalidTemp = false;
|
||||
@@ -310,36 +319,39 @@ SourceLocation getLocForEndOfDecl(const Decl *D,
|
||||
}
|
||||
|
||||
// Get full range of a Decl including the comments associated with it.
|
||||
CharSourceRange getFullRange(const Decl *D,
|
||||
const LangOptions &options = LangOptions()) {
|
||||
clang::CharSourceRange
|
||||
getFullRange(const clang::Decl *D,
|
||||
const clang::LangOptions &options = clang::LangOptions()) {
|
||||
const auto &SM = D->getASTContext().getSourceManager();
|
||||
SourceRange Full(SM.getExpansionLoc(D->getBeginLoc()), getLocForEndOfDecl(D));
|
||||
clang::SourceRange Full(SM.getExpansionLoc(D->getLocStart()),
|
||||
getLocForEndOfDecl(D));
|
||||
// Expand to comments that are associated with the Decl.
|
||||
if (const auto *Comment = D->getASTContext().getRawCommentForDeclNoCache(D)) {
|
||||
if (SM.isBeforeInTranslationUnit(Full.getEnd(), Comment->getEndLoc()))
|
||||
Full.setEnd(Comment->getEndLoc());
|
||||
if (SM.isBeforeInTranslationUnit(Full.getEnd(), Comment->getLocEnd()))
|
||||
Full.setEnd(Comment->getLocEnd());
|
||||
// FIXME: Don't delete a preceding comment, if there are no other entities
|
||||
// it could refer to.
|
||||
if (SM.isBeforeInTranslationUnit(Comment->getBeginLoc(), Full.getBegin()))
|
||||
Full.setBegin(Comment->getBeginLoc());
|
||||
if (SM.isBeforeInTranslationUnit(Comment->getLocStart(), Full.getBegin()))
|
||||
Full.setBegin(Comment->getLocStart());
|
||||
}
|
||||
|
||||
return CharSourceRange::getCharRange(Full);
|
||||
return clang::CharSourceRange::getCharRange(Full);
|
||||
}
|
||||
|
||||
std::string getDeclarationSourceText(const Decl *D) {
|
||||
std::string getDeclarationSourceText(const clang::Decl *D) {
|
||||
const auto &SM = D->getASTContext().getSourceManager();
|
||||
llvm::StringRef SourceText =
|
||||
Lexer::getSourceText(getFullRange(D), SM, LangOptions());
|
||||
clang::Lexer::getSourceText(getFullRange(D), SM, clang::LangOptions());
|
||||
return SourceText.str();
|
||||
}
|
||||
|
||||
bool isInHeaderFile(const Decl *D, llvm::StringRef OriginalRunningDirectory,
|
||||
bool isInHeaderFile(const clang::Decl *D,
|
||||
llvm::StringRef OriginalRunningDirectory,
|
||||
llvm::StringRef OldHeader) {
|
||||
const auto &SM = D->getASTContext().getSourceManager();
|
||||
if (OldHeader.empty())
|
||||
return false;
|
||||
auto ExpansionLoc = SM.getExpansionLoc(D->getBeginLoc());
|
||||
auto ExpansionLoc = SM.getExpansionLoc(D->getLocStart());
|
||||
if (ExpansionLoc.isInvalid())
|
||||
return false;
|
||||
|
||||
@@ -351,22 +363,22 @@ bool isInHeaderFile(const Decl *D, llvm::StringRef OriginalRunningDirectory,
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<std::string> getNamespaces(const Decl *D) {
|
||||
std::vector<std::string> getNamespaces(const clang::Decl *D) {
|
||||
std::vector<std::string> Namespaces;
|
||||
for (const auto *Context = D->getDeclContext(); Context;
|
||||
Context = Context->getParent()) {
|
||||
if (llvm::isa<TranslationUnitDecl>(Context) ||
|
||||
llvm::isa<LinkageSpecDecl>(Context))
|
||||
if (llvm::isa<clang::TranslationUnitDecl>(Context) ||
|
||||
llvm::isa<clang::LinkageSpecDecl>(Context))
|
||||
break;
|
||||
|
||||
if (const auto *ND = llvm::dyn_cast<NamespaceDecl>(Context))
|
||||
if (const auto *ND = llvm::dyn_cast<clang::NamespaceDecl>(Context))
|
||||
Namespaces.push_back(ND->getName().str());
|
||||
}
|
||||
std::reverse(Namespaces.begin(), Namespaces.end());
|
||||
return Namespaces;
|
||||
}
|
||||
|
||||
tooling::Replacements
|
||||
clang::tooling::Replacements
|
||||
createInsertedReplacements(const std::vector<std::string> &Includes,
|
||||
const std::vector<const NamedDecl *> &Decls,
|
||||
llvm::StringRef FileName, bool IsHeader = false,
|
||||
@@ -451,7 +463,8 @@ createInsertedReplacements(const std::vector<std::string> &Includes,
|
||||
|
||||
if (IsHeader)
|
||||
NewCode += "\n#endif // " + GuardName + "\n";
|
||||
return tooling::Replacements(tooling::Replacement(FileName, 0, 0, NewCode));
|
||||
return clang::tooling::Replacements(
|
||||
clang::tooling::Replacement(FileName, 0, 0, NewCode));
|
||||
}
|
||||
|
||||
// Return a set of all decls which are used/referenced by the given Decls.
|
||||
@@ -475,8 +488,8 @@ getUsedDecls(const HelperDeclRefGraph *RG,
|
||||
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<ASTConsumer>
|
||||
ClangMoveAction::CreateASTConsumer(CompilerInstance &Compiler,
|
||||
std::unique_ptr<clang::ASTConsumer>
|
||||
ClangMoveAction::CreateASTConsumer(clang::CompilerInstance &Compiler,
|
||||
StringRef /*InFile*/) {
|
||||
Compiler.getPreprocessor().addPPCallbacks(llvm::make_unique<FindAllIncludes>(
|
||||
&Compiler.getSourceManager(), &MoveTool));
|
||||
@@ -545,8 +558,7 @@ void ClangMoveTool::registerMatchers(ast_matchers::MatchFinder *Finder) {
|
||||
// namespace, these decls are always copied to new.h/cc. Those in classes,
|
||||
// functions are covered in other matchers.
|
||||
Finder->addMatcher(namedDecl(anyOf(usingDecl(IsOldCCTopLevelDecl),
|
||||
usingDirectiveDecl(unless(isImplicit()),
|
||||
IsOldCCTopLevelDecl),
|
||||
usingDirectiveDecl(IsOldCCTopLevelDecl),
|
||||
typeAliasDecl(IsOldCCTopLevelDecl)),
|
||||
notInMacro())
|
||||
.bind("using_decl"),
|
||||
@@ -660,10 +672,11 @@ void ClangMoveTool::registerMatchers(ast_matchers::MatchFinder *Finder) {
|
||||
}
|
||||
|
||||
void ClangMoveTool::run(const ast_matchers::MatchFinder::MatchResult &Result) {
|
||||
if (const auto *D = Result.Nodes.getNodeAs<NamedDecl>("decls_in_header")) {
|
||||
if (const auto *D =
|
||||
Result.Nodes.getNodeAs<clang::NamedDecl>("decls_in_header")) {
|
||||
UnremovedDeclsInOldHeader.insert(D);
|
||||
} else if (const auto *FWD =
|
||||
Result.Nodes.getNodeAs<CXXRecordDecl>("fwd_decl")) {
|
||||
Result.Nodes.getNodeAs<clang::CXXRecordDecl>("fwd_decl")) {
|
||||
// Skip all forward declarations which appear after moved class declaration.
|
||||
if (RemovedDecls.empty()) {
|
||||
if (const auto *DCT = FWD->getDescribedClassTemplate())
|
||||
@@ -672,12 +685,13 @@ void ClangMoveTool::run(const ast_matchers::MatchFinder::MatchResult &Result) {
|
||||
MovedDecls.push_back(FWD);
|
||||
}
|
||||
} else if (const auto *ND =
|
||||
Result.Nodes.getNodeAs<NamedDecl>("helper_decls")) {
|
||||
Result.Nodes.getNodeAs<clang::NamedDecl>("helper_decls")) {
|
||||
MovedDecls.push_back(ND);
|
||||
HelperDeclarations.push_back(ND);
|
||||
LLVM_DEBUG(llvm::dbgs() << "Add helper : " << ND->getNameAsString() << " ("
|
||||
<< ND << ")\n");
|
||||
} else if (const auto *UD = Result.Nodes.getNodeAs<NamedDecl>("using_decl")) {
|
||||
} else if (const auto *UD =
|
||||
Result.Nodes.getNodeAs<clang::NamedDecl>("using_decl")) {
|
||||
MovedDecls.push_back(UD);
|
||||
}
|
||||
}
|
||||
@@ -689,7 +703,7 @@ std::string ClangMoveTool::makeAbsolutePath(StringRef Path) {
|
||||
void ClangMoveTool::addIncludes(llvm::StringRef IncludeHeader, bool IsAngled,
|
||||
llvm::StringRef SearchPath,
|
||||
llvm::StringRef FileName,
|
||||
CharSourceRange IncludeFilenameRange,
|
||||
clang::CharSourceRange IncludeFilenameRange,
|
||||
const SourceManager &SM) {
|
||||
SmallVector<char, 128> HeaderWithSearchPath;
|
||||
llvm::sys::path::append(HeaderWithSearchPath, SearchPath, IncludeHeader);
|
||||
@@ -749,8 +763,9 @@ void ClangMoveTool::removeDeclsInOldFiles() {
|
||||
for (const auto *RemovedDecl : RemovedDecls) {
|
||||
const auto &SM = RemovedDecl->getASTContext().getSourceManager();
|
||||
auto Range = getFullRange(RemovedDecl);
|
||||
tooling::Replacement RemoveReplacement(
|
||||
SM, CharSourceRange::getCharRange(Range.getBegin(), Range.getEnd()),
|
||||
clang::tooling::Replacement RemoveReplacement(
|
||||
SM,
|
||||
clang::CharSourceRange::getCharRange(Range.getBegin(), Range.getEnd()),
|
||||
"");
|
||||
std::string FilePath = RemoveReplacement.getFilePath().str();
|
||||
auto Err = Context->FileToReplacements[FilePath].add(RemoveReplacement);
|
||||
@@ -780,8 +795,7 @@ void ClangMoveTool::removeDeclsInOldFiles() {
|
||||
// Ignore replacements for new.h/cc.
|
||||
if (SI == FilePathToFileID.end()) continue;
|
||||
llvm::StringRef Code = SM.getBufferData(SI->second);
|
||||
auto Style = format::getStyle(format::DefaultFormatStyle, FilePath,
|
||||
Context->FallbackStyle);
|
||||
auto Style = format::getStyle("file", FilePath, Context->FallbackStyle);
|
||||
if (!Style) {
|
||||
llvm::errs() << llvm::toString(Style.takeError()) << "\n";
|
||||
continue;
|
||||
@@ -852,18 +866,20 @@ void ClangMoveTool::moveAll(SourceManager &SM, StringRef OldFile,
|
||||
FileID ID = SM.getOrCreateFileID(FE, SrcMgr::C_User);
|
||||
auto Begin = SM.getLocForStartOfFile(ID);
|
||||
auto End = SM.getLocForEndOfFile(ID);
|
||||
tooling::Replacement RemoveAll(SM, CharSourceRange::getCharRange(Begin, End),
|
||||
"");
|
||||
clang::tooling::Replacement RemoveAll (
|
||||
SM, clang::CharSourceRange::getCharRange(Begin, End), "");
|
||||
std::string FilePath = RemoveAll.getFilePath().str();
|
||||
Context->FileToReplacements[FilePath] = tooling::Replacements(RemoveAll);
|
||||
Context->FileToReplacements[FilePath] =
|
||||
clang::tooling::Replacements(RemoveAll);
|
||||
|
||||
StringRef Code = SM.getBufferData(ID);
|
||||
if (!NewFile.empty()) {
|
||||
auto AllCode =
|
||||
tooling::Replacements(tooling::Replacement(NewFile, 0, 0, Code));
|
||||
auto ReplaceOldInclude = [&](CharSourceRange OldHeaderIncludeRange) {
|
||||
AllCode = AllCode.merge(tooling::Replacements(tooling::Replacement(
|
||||
SM, OldHeaderIncludeRange, '"' + Context->Spec.NewHeader + '"')));
|
||||
auto AllCode = clang::tooling::Replacements(
|
||||
clang::tooling::Replacement(NewFile, 0, 0, Code));
|
||||
auto ReplaceOldInclude = [&](clang::CharSourceRange OldHeaderIncludeRange) {
|
||||
AllCode = AllCode.merge(clang::tooling::Replacements(
|
||||
clang::tooling::Replacement(SM, OldHeaderIncludeRange,
|
||||
'"' + Context->Spec.NewHeader + '"')));
|
||||
};
|
||||
// Fix the case where old.h/old.cc includes "old.h", we replace the
|
||||
// `#include "old.h"` with `#include "new.h"`.
|
||||
@@ -881,21 +897,21 @@ void ClangMoveTool::onEndOfTranslationUnit() {
|
||||
assert(Reporter);
|
||||
for (const auto *Decl : UnremovedDeclsInOldHeader) {
|
||||
auto Kind = Decl->getKind();
|
||||
bool Templated = Decl->isTemplated();
|
||||
const std::string QualifiedName = Decl->getQualifiedNameAsString();
|
||||
if (Kind == Decl::Kind::Var)
|
||||
Reporter->reportDeclaration(QualifiedName, "Variable", Templated);
|
||||
Reporter->reportDeclaration(QualifiedName, "Variable");
|
||||
else if (Kind == Decl::Kind::Function ||
|
||||
Kind == Decl::Kind::FunctionTemplate)
|
||||
Reporter->reportDeclaration(QualifiedName, "Function", Templated);
|
||||
Reporter->reportDeclaration(QualifiedName, "Function");
|
||||
else if (Kind == Decl::Kind::ClassTemplate ||
|
||||
Kind == Decl::Kind::CXXRecord)
|
||||
Reporter->reportDeclaration(QualifiedName, "Class", Templated);
|
||||
Reporter->reportDeclaration(QualifiedName, "Class");
|
||||
else if (Kind == Decl::Kind::Enum)
|
||||
Reporter->reportDeclaration(QualifiedName, "Enum", Templated);
|
||||
else if (Kind == Decl::Kind::Typedef || Kind == Decl::Kind::TypeAlias ||
|
||||
Reporter->reportDeclaration(QualifiedName, "Enum");
|
||||
else if (Kind == Decl::Kind::Typedef ||
|
||||
Kind == Decl::Kind::TypeAlias ||
|
||||
Kind == Decl::Kind::TypeAliasTemplate)
|
||||
Reporter->reportDeclaration(QualifiedName, "TypeAlias", Templated);
|
||||
Reporter->reportDeclaration(QualifiedName, "TypeAlias");
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -905,7 +921,7 @@ void ClangMoveTool::onEndOfTranslationUnit() {
|
||||
// Ignore symbols that are not supported when checking if there is unremoved
|
||||
// symbol in old header. This makes sure that we always move old files to new
|
||||
// files when all symbols produced from dump_decls are moved.
|
||||
auto IsSupportedKind = [](const NamedDecl *Decl) {
|
||||
auto IsSupportedKind = [](const clang::NamedDecl *Decl) {
|
||||
switch (Decl->getKind()) {
|
||||
case Decl::Kind::Function:
|
||||
case Decl::Kind::FunctionTemplate:
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
#include "clang/Tooling/Tooling.h"
|
||||
#include "llvm/ADT/SmallPtrSet.h"
|
||||
#include "llvm/ADT/StringMap.h"
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
@@ -32,30 +31,23 @@ public:
|
||||
DeclarationReporter() = default;
|
||||
~DeclarationReporter() = default;
|
||||
|
||||
void reportDeclaration(llvm::StringRef DeclarationName, llvm::StringRef Type,
|
||||
bool Templated) {
|
||||
DeclarationList.emplace_back(DeclarationName, Type, Templated);
|
||||
void reportDeclaration(llvm::StringRef DeclarationName,
|
||||
llvm::StringRef Type) {
|
||||
DeclarationList.emplace_back(DeclarationName, Type);
|
||||
};
|
||||
|
||||
struct Declaration {
|
||||
Declaration(llvm::StringRef QName, llvm::StringRef Kind, bool Templated)
|
||||
: QualifiedName(QName), Kind(Kind), Templated(Templated) {}
|
||||
// A <DeclarationName, DeclarationKind> pair.
|
||||
// The DeclarationName is a fully qualified name for the declaration, like
|
||||
// A::B::Foo. The DeclarationKind is a string represents the kind of the
|
||||
// declaration, currently only "Function" and "Class" are supported.
|
||||
typedef std::pair<std::string, std::string> DeclarationPair;
|
||||
|
||||
friend bool operator==(const Declaration &LHS, const Declaration &RHS) {
|
||||
return std::tie(LHS.QualifiedName, LHS.Kind, LHS.Templated) ==
|
||||
std::tie(RHS.QualifiedName, RHS.Kind, RHS.Templated);
|
||||
}
|
||||
std::string QualifiedName; // E.g. A::B::Foo.
|
||||
std::string Kind; // E.g. Function, Class
|
||||
bool Templated = false; // Whether the declaration is templated.
|
||||
};
|
||||
|
||||
const std::vector<Declaration> getDeclarationList() const {
|
||||
const std::vector<DeclarationPair> getDeclarationList() const {
|
||||
return DeclarationList;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<Declaration> DeclarationList;
|
||||
std::vector<DeclarationPair> DeclarationList;
|
||||
};
|
||||
|
||||
// Specify declarations being moved. It contains all information of the moved
|
||||
|
||||
@@ -13,7 +13,6 @@ target_link_libraries(clang-move
|
||||
clangFrontend
|
||||
clangMove
|
||||
clangRewrite
|
||||
clangSerialization
|
||||
clangTooling
|
||||
clangToolingCore
|
||||
)
|
||||
|
||||
@@ -128,7 +128,7 @@ int main(int argc, const char **argv) {
|
||||
InitialDirectory.str(), Style, DumpDecls};
|
||||
move::DeclarationReporter Reporter;
|
||||
move::ClangMoveActionFactory Factory(&Context, &Reporter);
|
||||
|
||||
|
||||
int CodeStatus = Tool.run(&Factory);
|
||||
if (CodeStatus)
|
||||
return CodeStatus;
|
||||
@@ -138,11 +138,8 @@ int main(int argc, const char **argv) {
|
||||
const auto &Declarations = Reporter.getDeclarationList();
|
||||
for (auto I = Declarations.begin(), E = Declarations.end(); I != E; ++I) {
|
||||
llvm::outs() << " {\n";
|
||||
llvm::outs() << " \"DeclarationName\": \"" << I->QualifiedName
|
||||
<< "\",\n";
|
||||
llvm::outs() << " \"DeclarationType\": \"" << I->Kind << "\",\n";
|
||||
llvm::outs() << " \"Templated\": " << (I->Templated ? "true" : "false")
|
||||
<< "\n";
|
||||
llvm::outs() << " \"DeclarationName\": \"" << I->first << "\",\n";
|
||||
llvm::outs() << " \"DeclarationType\": \"" << I->second << "\"\n";
|
||||
llvm::outs() << " }";
|
||||
// Don't print trailing "," at the end of last element.
|
||||
if (I != std::prev(E))
|
||||
|
||||
@@ -13,7 +13,6 @@ add_clang_library(clangQuery
|
||||
clangBasic
|
||||
clangDynamicASTMatchers
|
||||
clangFrontend
|
||||
clangSerialization
|
||||
)
|
||||
|
||||
add_subdirectory(tool)
|
||||
|
||||
@@ -41,26 +41,12 @@ bool HelpQuery::run(llvm::raw_ostream &OS, QuerySession &QS) const {
|
||||
"as part of other expressions.\n"
|
||||
" set bind-root (true|false) "
|
||||
"Set whether to bind the root matcher to \"root\".\n"
|
||||
" set print-matcher (true|false) "
|
||||
"Set whether to print the current matcher,\n"
|
||||
" set output <feature> "
|
||||
"Set whether to output only <feature> content.\n"
|
||||
" enable output <feature> "
|
||||
"Enable <feature> content non-exclusively.\n"
|
||||
" disable output <feature> "
|
||||
"Disable <feature> content non-exclusively.\n"
|
||||
" quit, q "
|
||||
"Terminates the query session.\n\n"
|
||||
"Several commands accept a <feature> parameter. The available features "
|
||||
"are:\n\n"
|
||||
" print "
|
||||
"Pretty-print bound nodes.\n"
|
||||
" diag "
|
||||
"Diagnostic location for bound nodes.\n"
|
||||
" detailed-ast "
|
||||
"Detailed AST output for bound nodes.\n"
|
||||
" dump "
|
||||
"Detailed AST output for bound nodes (alias of detailed-ast).\n\n";
|
||||
" set output (diag|print|dump) "
|
||||
"Set whether to print bindings as diagnostics,\n"
|
||||
" "
|
||||
"AST pretty prints or AST dumps.\n"
|
||||
" quit "
|
||||
"Terminates the query session.\n\n";
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -100,18 +86,13 @@ bool MatchQuery::run(llvm::raw_ostream &OS, QuerySession &QS) const {
|
||||
}
|
||||
Finder.matchAST(AST->getASTContext());
|
||||
|
||||
if (QS.PrintMatcher) {
|
||||
std::string prefixText = "Matcher: ";
|
||||
OS << "\n " << prefixText << Source << "\n";
|
||||
OS << " " << std::string(prefixText.size() + Source.size(), '=') << '\n';
|
||||
}
|
||||
|
||||
for (auto MI = Matches.begin(), ME = Matches.end(); MI != ME; ++MI) {
|
||||
OS << "\nMatch #" << ++MatchCount << ":\n\n";
|
||||
|
||||
for (auto BI = MI->getMap().begin(), BE = MI->getMap().end(); BI != BE;
|
||||
++BI) {
|
||||
if (QS.DiagOutput) {
|
||||
switch (QS.OutKind) {
|
||||
case OK_Diag: {
|
||||
clang::SourceRange R = BI->second.getSourceRange();
|
||||
if (R.isValid()) {
|
||||
TextDiagnostic TD(OS, AST->getASTContext().getLangOpts(),
|
||||
@@ -121,16 +102,20 @@ bool MatchQuery::run(llvm::raw_ostream &OS, QuerySession &QS) const {
|
||||
DiagnosticsEngine::Note, "\"" + BI->first + "\" binds here",
|
||||
CharSourceRange::getTokenRange(R), None);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (QS.PrintOutput) {
|
||||
case OK_Print: {
|
||||
OS << "Binding for \"" << BI->first << "\":\n";
|
||||
BI->second.print(OS, AST->getASTContext().getPrintingPolicy());
|
||||
OS << "\n";
|
||||
break;
|
||||
}
|
||||
if (QS.DetailedASTOutput) {
|
||||
case OK_Dump: {
|
||||
OS << "Binding for \"" << BI->first << "\":\n";
|
||||
BI->second.dump(OS, AST->getSourceManager());
|
||||
OS << "\n";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_QUERY_QUERY_H
|
||||
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_QUERY_QUERY_H
|
||||
|
||||
#include "QuerySession.h"
|
||||
#include "clang/ASTMatchers/Dynamic/VariantValue.h"
|
||||
#include "llvm/ADT/IntrusiveRefCntPtr.h"
|
||||
#include "llvm/ADT/Optional.h"
|
||||
@@ -19,7 +18,7 @@
|
||||
namespace clang {
|
||||
namespace query {
|
||||
|
||||
enum OutputKind { OK_Diag, OK_Print, OK_DetailedAST };
|
||||
enum OutputKind { OK_Diag, OK_Print, OK_Dump };
|
||||
|
||||
enum QueryKind {
|
||||
QK_Invalid,
|
||||
@@ -29,8 +28,6 @@ enum QueryKind {
|
||||
QK_Match,
|
||||
QK_SetBool,
|
||||
QK_SetOutputKind,
|
||||
QK_EnableOutputKind,
|
||||
QK_DisableOutputKind,
|
||||
QK_Quit
|
||||
};
|
||||
|
||||
@@ -86,15 +83,12 @@ struct QuitQuery : Query {
|
||||
|
||||
/// Query for "match MATCHER".
|
||||
struct MatchQuery : Query {
|
||||
MatchQuery(StringRef Source,
|
||||
const ast_matchers::dynamic::DynTypedMatcher &Matcher)
|
||||
: Query(QK_Match), Matcher(Matcher), Source(Source) {}
|
||||
MatchQuery(const ast_matchers::dynamic::DynTypedMatcher &Matcher)
|
||||
: Query(QK_Match), Matcher(Matcher) {}
|
||||
bool run(llvm::raw_ostream &OS, QuerySession &QS) const override;
|
||||
|
||||
ast_matchers::dynamic::DynTypedMatcher Matcher;
|
||||
|
||||
StringRef Source;
|
||||
|
||||
static bool classof(const Query *Q) { return Q->Kind == QK_Match; }
|
||||
};
|
||||
|
||||
@@ -136,53 +130,6 @@ template <typename T> struct SetQuery : Query {
|
||||
T Value;
|
||||
};
|
||||
|
||||
// Implements the exclusive 'set output dump|diag|print' options.
|
||||
struct SetExclusiveOutputQuery : Query {
|
||||
SetExclusiveOutputQuery(bool QuerySession::*Var)
|
||||
: Query(QK_SetOutputKind), Var(Var) {}
|
||||
bool run(llvm::raw_ostream &OS, QuerySession &QS) const override {
|
||||
QS.DiagOutput = false;
|
||||
QS.DetailedASTOutput = false;
|
||||
QS.PrintOutput = false;
|
||||
QS.*Var = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool classof(const Query *Q) { return Q->Kind == QK_SetOutputKind; }
|
||||
|
||||
bool QuerySession::*Var;
|
||||
};
|
||||
|
||||
// Implements the non-exclusive 'set output dump|diag|print' options.
|
||||
struct SetNonExclusiveOutputQuery : Query {
|
||||
SetNonExclusiveOutputQuery(QueryKind Kind, bool QuerySession::*Var,
|
||||
bool Value)
|
||||
: Query(Kind), Var(Var), Value(Value) {}
|
||||
bool run(llvm::raw_ostream &OS, QuerySession &QS) const override {
|
||||
QS.*Var = Value;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool QuerySession::*Var;
|
||||
bool Value;
|
||||
};
|
||||
|
||||
struct EnableOutputQuery : SetNonExclusiveOutputQuery {
|
||||
EnableOutputQuery(bool QuerySession::*Var)
|
||||
: SetNonExclusiveOutputQuery(QK_EnableOutputKind, Var, true) {}
|
||||
|
||||
static bool classof(const Query *Q) { return Q->Kind == QK_EnableOutputKind; }
|
||||
};
|
||||
|
||||
struct DisableOutputQuery : SetNonExclusiveOutputQuery {
|
||||
DisableOutputQuery(bool QuerySession::*Var)
|
||||
: SetNonExclusiveOutputQuery(QK_DisableOutputKind, Var, false) {}
|
||||
|
||||
static bool classof(const Query *Q) {
|
||||
return Q->Kind == QK_DisableOutputKind;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace query
|
||||
} // namespace clang
|
||||
|
||||
|
||||
@@ -27,22 +27,24 @@ namespace query {
|
||||
// is found before End, return StringRef(). Begin is adjusted to exclude the
|
||||
// lexed region.
|
||||
StringRef QueryParser::lexWord() {
|
||||
Line = Line.ltrim();
|
||||
while (true) {
|
||||
if (Begin == End)
|
||||
return StringRef(Begin, 0);
|
||||
|
||||
if (Line.empty())
|
||||
// Even though the Line is empty, it contains a pointer and
|
||||
// a (zero) length. The pointer is used in the LexOrCompleteWord
|
||||
// code completion.
|
||||
return Line;
|
||||
if (!isWhitespace(*Begin))
|
||||
break;
|
||||
|
||||
if (Line.front() == '#') {
|
||||
Line = {};
|
||||
return StringRef();
|
||||
++Begin;
|
||||
}
|
||||
|
||||
StringRef Word = Line.take_until(isWhitespace);
|
||||
Line = Line.drop_front(Word.size());
|
||||
return Word;
|
||||
const char *WordBegin = Begin;
|
||||
|
||||
while (true) {
|
||||
++Begin;
|
||||
|
||||
if (Begin == End || isWhitespace(*Begin))
|
||||
return StringRef(WordBegin, Begin - WordBegin);
|
||||
}
|
||||
}
|
||||
|
||||
// This is the StringSwitch-alike used by lexOrCompleteWord below. See that
|
||||
@@ -99,36 +101,25 @@ QueryRef QueryParser::parseSetBool(bool QuerySession::*Var) {
|
||||
return new SetQuery<bool>(Var, Value);
|
||||
}
|
||||
|
||||
template <typename QueryType> QueryRef QueryParser::parseSetOutputKind() {
|
||||
QueryRef QueryParser::parseSetOutputKind() {
|
||||
StringRef ValStr;
|
||||
unsigned OutKind = LexOrCompleteWord<unsigned>(this, ValStr)
|
||||
.Case("diag", OK_Diag)
|
||||
.Case("print", OK_Print)
|
||||
.Case("detailed-ast", OK_DetailedAST)
|
||||
.Case("dump", OK_DetailedAST)
|
||||
.Case("dump", OK_Dump)
|
||||
.Default(~0u);
|
||||
if (OutKind == ~0u) {
|
||||
return new InvalidQuery(
|
||||
"expected 'diag', 'print', 'detailed-ast' or 'dump', got '" + ValStr +
|
||||
"'");
|
||||
return new InvalidQuery("expected 'diag', 'print' or 'dump', got '" +
|
||||
ValStr + "'");
|
||||
}
|
||||
|
||||
switch (OutKind) {
|
||||
case OK_DetailedAST:
|
||||
return new QueryType(&QuerySession::DetailedASTOutput);
|
||||
case OK_Diag:
|
||||
return new QueryType(&QuerySession::DiagOutput);
|
||||
case OK_Print:
|
||||
return new QueryType(&QuerySession::PrintOutput);
|
||||
}
|
||||
|
||||
llvm_unreachable("Invalid output kind");
|
||||
return new SetQuery<OutputKind>(&QuerySession::OutKind, OutputKind(OutKind));
|
||||
}
|
||||
|
||||
QueryRef QueryParser::endQuery(QueryRef Q) {
|
||||
const StringRef Extra = Line;
|
||||
const char *Extra = Begin;
|
||||
if (!lexWord().empty())
|
||||
return new InvalidQuery("unexpected extra input: '" + Extra + "'");
|
||||
return new InvalidQuery("unexpected extra input: '" +
|
||||
StringRef(Extra, End - Extra) + "'");
|
||||
return Q;
|
||||
}
|
||||
|
||||
@@ -136,24 +127,16 @@ namespace {
|
||||
|
||||
enum ParsedQueryKind {
|
||||
PQK_Invalid,
|
||||
PQK_Comment,
|
||||
PQK_NoOp,
|
||||
PQK_Help,
|
||||
PQK_Let,
|
||||
PQK_Match,
|
||||
PQK_Set,
|
||||
PQK_Unlet,
|
||||
PQK_Quit,
|
||||
PQK_Enable,
|
||||
PQK_Disable
|
||||
PQK_Quit
|
||||
};
|
||||
|
||||
enum ParsedQueryVariable {
|
||||
PQV_Invalid,
|
||||
PQV_Output,
|
||||
PQV_BindRoot,
|
||||
PQV_PrintMatcher
|
||||
};
|
||||
enum ParsedQueryVariable { PQV_Invalid, PQV_Output, PQV_BindRoot };
|
||||
|
||||
QueryRef makeInvalidQueryFromDiagnostics(const Diagnostics &Diag) {
|
||||
std::string ErrStr;
|
||||
@@ -166,7 +149,8 @@ QueryRef makeInvalidQueryFromDiagnostics(const Diagnostics &Diag) {
|
||||
|
||||
QueryRef QueryParser::completeMatcherExpression() {
|
||||
std::vector<MatcherCompletion> Comps = Parser::completeExpression(
|
||||
Line, CompletionPos - Line.begin(), nullptr, &QS.NamedValues);
|
||||
StringRef(Begin, End - Begin), CompletionPos - Begin, nullptr,
|
||||
&QS.NamedValues);
|
||||
for (auto I = Comps.begin(), E = Comps.end(); I != E; ++I) {
|
||||
Completions.push_back(LineEditor::Completion(I->TypedText, I->MatcherDecl));
|
||||
}
|
||||
@@ -177,22 +161,16 @@ QueryRef QueryParser::doParse() {
|
||||
StringRef CommandStr;
|
||||
ParsedQueryKind QKind = LexOrCompleteWord<ParsedQueryKind>(this, CommandStr)
|
||||
.Case("", PQK_NoOp)
|
||||
.Case("#", PQK_Comment, /*IsCompletion=*/false)
|
||||
.Case("help", PQK_Help)
|
||||
.Case("l", PQK_Let, /*IsCompletion=*/false)
|
||||
.Case("let", PQK_Let)
|
||||
.Case("m", PQK_Match, /*IsCompletion=*/false)
|
||||
.Case("let", PQK_Let)
|
||||
.Case("match", PQK_Match)
|
||||
.Case("q", PQK_Quit, /*IsCompletion=*/false)
|
||||
.Case("quit", PQK_Quit)
|
||||
.Case("set", PQK_Set)
|
||||
.Case("enable", PQK_Enable)
|
||||
.Case("disable", PQK_Disable)
|
||||
.Case("unlet", PQK_Unlet)
|
||||
.Case("quit", PQK_Quit)
|
||||
.Default(PQK_Invalid);
|
||||
|
||||
switch (QKind) {
|
||||
case PQK_Comment:
|
||||
case PQK_NoOp:
|
||||
return new NoOpQuery;
|
||||
|
||||
@@ -213,8 +191,8 @@ QueryRef QueryParser::doParse() {
|
||||
|
||||
Diagnostics Diag;
|
||||
ast_matchers::dynamic::VariantValue Value;
|
||||
if (!Parser::parseExpression(Line, nullptr, &QS.NamedValues, &Value,
|
||||
&Diag)) {
|
||||
if (!Parser::parseExpression(StringRef(Begin, End - Begin), nullptr,
|
||||
&QS.NamedValues, &Value, &Diag)) {
|
||||
return makeInvalidQueryFromDiagnostics(Diag);
|
||||
}
|
||||
|
||||
@@ -226,23 +204,21 @@ QueryRef QueryParser::doParse() {
|
||||
return completeMatcherExpression();
|
||||
|
||||
Diagnostics Diag;
|
||||
auto MatcherSource = Line.trim();
|
||||
Optional<DynTypedMatcher> Matcher = Parser::parseMatcherExpression(
|
||||
MatcherSource, nullptr, &QS.NamedValues, &Diag);
|
||||
StringRef(Begin, End - Begin), nullptr, &QS.NamedValues, &Diag);
|
||||
if (!Matcher) {
|
||||
return makeInvalidQueryFromDiagnostics(Diag);
|
||||
}
|
||||
return new MatchQuery(MatcherSource, *Matcher);
|
||||
return new MatchQuery(*Matcher);
|
||||
}
|
||||
|
||||
case PQK_Set: {
|
||||
StringRef VarStr;
|
||||
ParsedQueryVariable Var =
|
||||
LexOrCompleteWord<ParsedQueryVariable>(this, VarStr)
|
||||
.Case("output", PQV_Output)
|
||||
.Case("bind-root", PQV_BindRoot)
|
||||
.Case("print-matcher", PQV_PrintMatcher)
|
||||
.Default(PQV_Invalid);
|
||||
ParsedQueryVariable Var = LexOrCompleteWord<ParsedQueryVariable>(this,
|
||||
VarStr)
|
||||
.Case("output", PQV_Output)
|
||||
.Case("bind-root", PQV_BindRoot)
|
||||
.Default(PQV_Invalid);
|
||||
if (VarStr.empty())
|
||||
return new InvalidQuery("expected variable name");
|
||||
if (Var == PQV_Invalid)
|
||||
@@ -251,42 +227,17 @@ QueryRef QueryParser::doParse() {
|
||||
QueryRef Q;
|
||||
switch (Var) {
|
||||
case PQV_Output:
|
||||
Q = parseSetOutputKind<SetExclusiveOutputQuery>();
|
||||
Q = parseSetOutputKind();
|
||||
break;
|
||||
case PQV_BindRoot:
|
||||
Q = parseSetBool(&QuerySession::BindRoot);
|
||||
break;
|
||||
case PQV_PrintMatcher:
|
||||
Q = parseSetBool(&QuerySession::PrintMatcher);
|
||||
break;
|
||||
case PQV_Invalid:
|
||||
llvm_unreachable("Invalid query kind");
|
||||
}
|
||||
|
||||
return endQuery(Q);
|
||||
}
|
||||
case PQK_Enable:
|
||||
case PQK_Disable: {
|
||||
StringRef VarStr;
|
||||
ParsedQueryVariable Var =
|
||||
LexOrCompleteWord<ParsedQueryVariable>(this, VarStr)
|
||||
.Case("output", PQV_Output)
|
||||
.Default(PQV_Invalid);
|
||||
if (VarStr.empty())
|
||||
return new InvalidQuery("expected variable name");
|
||||
if (Var == PQV_Invalid)
|
||||
return new InvalidQuery("unknown variable: '" + VarStr + "'");
|
||||
|
||||
QueryRef Q;
|
||||
|
||||
if (QKind == PQK_Enable)
|
||||
Q = parseSetOutputKind<EnableOutputQuery>();
|
||||
else if (QKind == PQK_Disable)
|
||||
Q = parseSetOutputKind<DisableOutputQuery>();
|
||||
else
|
||||
llvm_unreachable("Invalid query kind");
|
||||
return endQuery(Q);
|
||||
}
|
||||
|
||||
case PQK_Unlet: {
|
||||
StringRef Name = lexWord();
|
||||
|
||||
@@ -37,14 +37,14 @@ public:
|
||||
|
||||
private:
|
||||
QueryParser(StringRef Line, const QuerySession &QS)
|
||||
: Line(Line), CompletionPos(nullptr), QS(QS) {}
|
||||
: Begin(Line.begin()), End(Line.end()), CompletionPos(nullptr), QS(QS) {}
|
||||
|
||||
StringRef lexWord();
|
||||
|
||||
template <typename T> struct LexOrCompleteWord;
|
||||
|
||||
QueryRef parseSetBool(bool QuerySession::*Var);
|
||||
template <typename QueryType> QueryRef parseSetOutputKind();
|
||||
QueryRef parseSetOutputKind();
|
||||
QueryRef completeMatcherExpression();
|
||||
|
||||
QueryRef endQuery(QueryRef Q);
|
||||
@@ -55,7 +55,8 @@ private:
|
||||
/// \c InvalidQuery if a parse error occurs.
|
||||
QueryRef doParse();
|
||||
|
||||
StringRef Line;
|
||||
const char *Begin;
|
||||
const char *End;
|
||||
|
||||
const char *CompletionPos;
|
||||
std::vector<llvm::LineEditor::Completion> Completions;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_QUERY_QUERY_SESSION_H
|
||||
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_QUERY_QUERY_SESSION_H
|
||||
|
||||
#include "Query.h"
|
||||
#include "clang/ASTMatchers/Dynamic/VariantValue.h"
|
||||
#include "llvm/ADT/ArrayRef.h"
|
||||
#include "llvm/ADT/StringMap.h"
|
||||
@@ -24,18 +25,11 @@ namespace query {
|
||||
class QuerySession {
|
||||
public:
|
||||
QuerySession(llvm::ArrayRef<std::unique_ptr<ASTUnit>> ASTs)
|
||||
: ASTs(ASTs), PrintOutput(false), DiagOutput(true),
|
||||
DetailedASTOutput(false), BindRoot(true), PrintMatcher(false),
|
||||
Terminate(false) {}
|
||||
: ASTs(ASTs), OutKind(OK_Diag), BindRoot(true), Terminate(false) {}
|
||||
|
||||
llvm::ArrayRef<std::unique_ptr<ASTUnit>> ASTs;
|
||||
|
||||
bool PrintOutput;
|
||||
bool DiagOutput;
|
||||
bool DetailedASTOutput;
|
||||
|
||||
OutputKind OutKind;
|
||||
bool BindRoot;
|
||||
bool PrintMatcher;
|
||||
bool Terminate;
|
||||
llvm::StringMap<ast_matchers::dynamic::VariantValue> NamedValues;
|
||||
};
|
||||
|
||||
@@ -9,7 +9,6 @@ target_link_libraries(clang-query
|
||||
clangDynamicASTMatchers
|
||||
clangFrontend
|
||||
clangQuery
|
||||
clangSerialization
|
||||
clangTooling
|
||||
)
|
||||
|
||||
|
||||
@@ -58,29 +58,6 @@ static cl::list<std::string> CommandFiles("f",
|
||||
cl::value_desc("file"),
|
||||
cl::cat(ClangQueryCategory));
|
||||
|
||||
static cl::opt<std::string> PreloadFile(
|
||||
"preload",
|
||||
cl::desc("Preload commands from file and start interactive mode"),
|
||||
cl::value_desc("file"), cl::cat(ClangQueryCategory));
|
||||
|
||||
bool runCommandsInFile(const char *ExeName, std::string const &FileName,
|
||||
QuerySession &QS) {
|
||||
std::ifstream Input(FileName.c_str());
|
||||
if (!Input.is_open()) {
|
||||
llvm::errs() << ExeName << ": cannot open " << FileName << "\n";
|
||||
return 1;
|
||||
}
|
||||
while (Input.good()) {
|
||||
std::string Line;
|
||||
std::getline(Input, Line);
|
||||
|
||||
QueryRef Q = QueryParser::parse(Line, QS);
|
||||
if (!Q->run(llvm::outs(), QS))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int main(int argc, const char **argv) {
|
||||
llvm::sys::PrintStackTraceOnErrorSignal(argv[0]);
|
||||
|
||||
@@ -91,28 +68,11 @@ int main(int argc, const char **argv) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ((!Commands.empty() || !CommandFiles.empty()) && !PreloadFile.empty()) {
|
||||
llvm::errs() << argv[0]
|
||||
<< ": cannot specify both -c or -f with --preload\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
ClangTool Tool(OptionsParser.getCompilations(),
|
||||
OptionsParser.getSourcePathList());
|
||||
std::vector<std::unique_ptr<ASTUnit>> ASTs;
|
||||
int Status = Tool.buildASTs(ASTs);
|
||||
int ASTStatus = 0;
|
||||
if (Status == 1) {
|
||||
// Building ASTs failed.
|
||||
if (Tool.buildASTs(ASTs) != 0)
|
||||
return 1;
|
||||
} else if (Status == 2) {
|
||||
ASTStatus |= 1;
|
||||
llvm::errs() << "Failed to build AST for some of the files, "
|
||||
<< "results may be incomplete."
|
||||
<< "\n";
|
||||
} else {
|
||||
assert(Status == 0 && "Unexpected status returned");
|
||||
}
|
||||
|
||||
QuerySession QS(ASTs);
|
||||
|
||||
@@ -124,14 +84,21 @@ int main(int argc, const char **argv) {
|
||||
}
|
||||
} else if (!CommandFiles.empty()) {
|
||||
for (auto I = CommandFiles.begin(), E = CommandFiles.end(); I != E; ++I) {
|
||||
if (runCommandsInFile(argv[0], *I, QS))
|
||||
std::ifstream Input(I->c_str());
|
||||
if (!Input.is_open()) {
|
||||
llvm::errs() << argv[0] << ": cannot open " << *I << "\n";
|
||||
return 1;
|
||||
}
|
||||
while (Input.good()) {
|
||||
std::string Line;
|
||||
std::getline(Input, Line);
|
||||
|
||||
QueryRef Q = QueryParser::parse(Line, QS);
|
||||
if (!Q->run(llvm::outs(), QS))
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!PreloadFile.empty()) {
|
||||
if (runCommandsInFile(argv[0], PreloadFile, QS))
|
||||
return 1;
|
||||
}
|
||||
LineEditor LE("clang-query");
|
||||
LE.setListCompleter([&QS](StringRef Line, size_t Pos) {
|
||||
return QueryParser::complete(Line, Pos, QS);
|
||||
@@ -145,5 +112,5 @@ int main(int argc, const char **argv) {
|
||||
}
|
||||
}
|
||||
|
||||
return ASTStatus;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ add_clang_library(clangReorderFields
|
||||
clangBasic
|
||||
clangIndex
|
||||
clangLex
|
||||
clangSerialization
|
||||
clangToolingCore
|
||||
)
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ target_link_libraries(clang-reorder-fields
|
||||
clangFrontend
|
||||
clangReorderFields
|
||||
clangRewrite
|
||||
clangSerialization
|
||||
clangTooling
|
||||
clangToolingCore
|
||||
)
|
||||
|
||||
@@ -21,18 +21,12 @@ add_clang_library(clangTidy
|
||||
clangLex
|
||||
clangRewrite
|
||||
clangSema
|
||||
clangSerialization
|
||||
clangStaticAnalyzerCore
|
||||
clangStaticAnalyzerFrontend
|
||||
clangTooling
|
||||
clangToolingCore
|
||||
)
|
||||
|
||||
if(CLANG_ENABLE_STATIC_ANALYZER)
|
||||
target_link_libraries(clangTidy PRIVATE
|
||||
clangStaticAnalyzerCore
|
||||
clangStaticAnalyzerFrontend
|
||||
)
|
||||
endif()
|
||||
|
||||
add_subdirectory(android)
|
||||
add_subdirectory(abseil)
|
||||
add_subdirectory(boost)
|
||||
@@ -45,9 +39,7 @@ add_subdirectory(hicpp)
|
||||
add_subdirectory(llvm)
|
||||
add_subdirectory(misc)
|
||||
add_subdirectory(modernize)
|
||||
if(CLANG_ENABLE_STATIC_ANALYZER)
|
||||
add_subdirectory(mpi)
|
||||
endif()
|
||||
add_subdirectory(mpi)
|
||||
add_subdirectory(objc)
|
||||
add_subdirectory(performance)
|
||||
add_subdirectory(plugin)
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
#include "clang/AST/ASTContext.h"
|
||||
#include "clang/AST/Decl.h"
|
||||
#include "clang/ASTMatchers/ASTMatchFinder.h"
|
||||
#include "clang/Config/config.h"
|
||||
#include "clang/Format/Format.h"
|
||||
#include "clang/Frontend/ASTConsumers.h"
|
||||
#include "clang/Frontend/CompilerInstance.h"
|
||||
@@ -35,10 +34,8 @@
|
||||
#include "clang/Lex/Preprocessor.h"
|
||||
#include "clang/Rewrite/Frontend/FixItRewriter.h"
|
||||
#include "clang/Rewrite/Frontend/FrontendActions.h"
|
||||
#if CLANG_ENABLE_STATIC_ANALYZER
|
||||
#include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h"
|
||||
#include "clang/StaticAnalyzer/Frontend/AnalysisConsumer.h"
|
||||
#endif // CLANG_ENABLE_STATIC_ANALYZER
|
||||
#include "clang/Tooling/DiagnosticsYaml.h"
|
||||
#include "clang/Tooling/Refactoring.h"
|
||||
#include "clang/Tooling/ReplacementsYaml.h"
|
||||
@@ -59,7 +56,6 @@ namespace clang {
|
||||
namespace tidy {
|
||||
|
||||
namespace {
|
||||
#if CLANG_ENABLE_STATIC_ANALYZER
|
||||
static const char *AnalyzerCheckNamePrefix = "clang-analyzer-";
|
||||
|
||||
class AnalyzerDiagnosticConsumer : public ento::PathDiagnosticConsumer {
|
||||
@@ -91,12 +87,11 @@ public:
|
||||
private:
|
||||
ClangTidyContext &Context;
|
||||
};
|
||||
#endif // CLANG_ENABLE_STATIC_ANALYZER
|
||||
|
||||
class ErrorReporter {
|
||||
public:
|
||||
ErrorReporter(ClangTidyContext &Context, bool ApplyFixes,
|
||||
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> BaseFS)
|
||||
llvm::IntrusiveRefCntPtr<vfs::FileSystem> BaseFS)
|
||||
: Files(FileSystemOptions(), BaseFS), DiagOpts(new DiagnosticOptions()),
|
||||
DiagPrinter(new TextDiagnosticPrinter(llvm::outs(), &*DiagOpts)),
|
||||
Diags(IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs), &*DiagOpts,
|
||||
@@ -127,6 +122,10 @@ public:
|
||||
<< Message.Message << Name;
|
||||
for (const auto &FileAndReplacements : Error.Fix) {
|
||||
for (const auto &Repl : FileAndReplacements.second) {
|
||||
// Retrieve the source range for applicable fixes. Macro definitions
|
||||
// on the command line have locations in a virtual buffer and don't
|
||||
// have valid file paths and are therefore not applicable.
|
||||
SourceRange Range;
|
||||
SourceLocation FixLoc;
|
||||
++TotalFixes;
|
||||
bool CanBeApplied = false;
|
||||
@@ -167,11 +166,7 @@ public:
|
||||
FixLoc = getLocation(FixAbsoluteFilePath, Repl.getOffset());
|
||||
SourceLocation FixEndLoc =
|
||||
FixLoc.getLocWithOffset(Repl.getLength());
|
||||
// Retrieve the source range for applicable fixes. Macro definitions
|
||||
// on the command line have locations in a virtual buffer and don't
|
||||
// have valid file paths and are therefore not applicable.
|
||||
CharSourceRange Range =
|
||||
CharSourceRange::getCharRange(SourceRange(FixLoc, FixEndLoc));
|
||||
Range = SourceRange(FixLoc, FixEndLoc);
|
||||
Diag << FixItHint::CreateReplacement(Range,
|
||||
Repl.getReplacementText());
|
||||
}
|
||||
@@ -301,7 +296,6 @@ ClangTidyASTConsumerFactory::ClangTidyASTConsumerFactory(
|
||||
}
|
||||
}
|
||||
|
||||
#if CLANG_ENABLE_STATIC_ANALYZER
|
||||
static void setStaticAnalyzerCheckerOpts(const ClangTidyOptions &Opts,
|
||||
AnalyzerOptionsRef AnalyzerOptions) {
|
||||
StringRef AnalyzerPrefix(AnalyzerCheckNamePrefix);
|
||||
@@ -345,7 +339,6 @@ static CheckersList getCheckersControlList(ClangTidyContext &Context,
|
||||
}
|
||||
return List;
|
||||
}
|
||||
#endif // CLANG_ENABLE_STATIC_ANALYZER
|
||||
|
||||
std::unique_ptr<clang::ASTConsumer>
|
||||
ClangTidyASTConsumerFactory::CreateASTConsumer(
|
||||
@@ -387,7 +380,6 @@ ClangTidyASTConsumerFactory::CreateASTConsumer(
|
||||
if (!Checks.empty())
|
||||
Consumers.push_back(Finder->newASTConsumer());
|
||||
|
||||
#if CLANG_ENABLE_STATIC_ANALYZER
|
||||
AnalyzerOptionsRef AnalyzerOptions = Compiler.getAnalyzerOpts();
|
||||
AnalyzerOptions->CheckersControlList =
|
||||
getCheckersControlList(Context, Context.canEnableAnalyzerAlphaCheckers());
|
||||
@@ -403,7 +395,6 @@ ClangTidyASTConsumerFactory::CreateASTConsumer(
|
||||
new AnalyzerDiagnosticConsumer(Context));
|
||||
Consumers.push_back(std::move(AnalysisConsumer));
|
||||
}
|
||||
#endif // CLANG_ENABLE_STATIC_ANALYZER
|
||||
return llvm::make_unique<ClangTidyASTConsumer>(
|
||||
std::move(Consumers), std::move(Profiling), std::move(Finder),
|
||||
std::move(Checks));
|
||||
@@ -416,11 +407,9 @@ std::vector<std::string> ClangTidyASTConsumerFactory::getCheckNames() {
|
||||
CheckNames.push_back(CheckFactory.first);
|
||||
}
|
||||
|
||||
#if CLANG_ENABLE_STATIC_ANALYZER
|
||||
for (const auto &AnalyzerCheck : getCheckersControlList(
|
||||
Context, Context.canEnableAnalyzerAlphaCheckers()))
|
||||
CheckNames.push_back(AnalyzerCheckNamePrefix + AnalyzerCheck.first);
|
||||
#endif // CLANG_ENABLE_STATIC_ANALYZER
|
||||
|
||||
std::sort(CheckNames.begin(), CheckNames.end());
|
||||
return CheckNames;
|
||||
@@ -441,9 +430,7 @@ DiagnosticBuilder ClangTidyCheck::diag(SourceLocation Loc, StringRef Message,
|
||||
}
|
||||
|
||||
void ClangTidyCheck::run(const ast_matchers::MatchFinder::MatchResult &Result) {
|
||||
// For historical reasons, checks don't implement the MatchFinder run()
|
||||
// callback directly. We keep the run()/check() distinction to avoid interface
|
||||
// churn, and to allow us to add cross-cutting logic in the future.
|
||||
Context->setSourceManager(Result.SourceManager);
|
||||
check(Result);
|
||||
}
|
||||
|
||||
@@ -502,12 +489,11 @@ getCheckOptions(const ClangTidyOptions &Options,
|
||||
return Factory.getCheckOptions();
|
||||
}
|
||||
|
||||
std::vector<ClangTidyError>
|
||||
runClangTidy(clang::tidy::ClangTidyContext &Context,
|
||||
const CompilationDatabase &Compilations,
|
||||
ArrayRef<std::string> InputFiles,
|
||||
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> BaseFS,
|
||||
bool EnableCheckProfile, llvm::StringRef StoreCheckProfile) {
|
||||
void runClangTidy(clang::tidy::ClangTidyContext &Context,
|
||||
const CompilationDatabase &Compilations,
|
||||
ArrayRef<std::string> InputFiles,
|
||||
llvm::IntrusiveRefCntPtr<vfs::FileSystem> BaseFS,
|
||||
bool EnableCheckProfile, llvm::StringRef StoreCheckProfile) {
|
||||
ClangTool Tool(Compilations, InputFiles,
|
||||
std::make_shared<PCHContainerOperations>(), BaseFS);
|
||||
|
||||
@@ -529,15 +515,29 @@ runClangTidy(clang::tidy::ClangTidyContext &Context,
|
||||
return AdjustedArgs;
|
||||
};
|
||||
|
||||
// Remove plugins arguments.
|
||||
ArgumentsAdjuster PluginArgumentsRemover =
|
||||
[](const CommandLineArguments &Args, StringRef Filename) {
|
||||
CommandLineArguments AdjustedArgs;
|
||||
for (size_t I = 0, E = Args.size(); I < E; ++I) {
|
||||
if (I + 4 < Args.size() && Args[I] == "-Xclang" &&
|
||||
(Args[I + 1] == "-load" || Args[I + 1] == "-add-plugin" ||
|
||||
StringRef(Args[I + 1]).startswith("-plugin-arg-")) &&
|
||||
Args[I + 2] == "-Xclang") {
|
||||
I += 3;
|
||||
} else
|
||||
AdjustedArgs.push_back(Args[I]);
|
||||
}
|
||||
return AdjustedArgs;
|
||||
};
|
||||
|
||||
Tool.appendArgumentsAdjuster(PerFileExtraArgumentsInserter);
|
||||
Tool.appendArgumentsAdjuster(getStripPluginsAdjuster());
|
||||
Tool.appendArgumentsAdjuster(PluginArgumentsRemover);
|
||||
Context.setEnableProfiling(EnableCheckProfile);
|
||||
Context.setProfileStoragePrefix(StoreCheckProfile);
|
||||
|
||||
ClangTidyDiagnosticConsumer DiagConsumer(Context);
|
||||
DiagnosticsEngine DE(new DiagnosticIDs(), new DiagnosticOptions(),
|
||||
&DiagConsumer, /*ShouldOwnClient=*/false);
|
||||
Context.setDiagnosticsEngine(&DE);
|
||||
|
||||
Tool.setDiagnosticConsumer(&DiagConsumer);
|
||||
|
||||
class ActionFactory : public FrontendActionFactory {
|
||||
@@ -575,21 +575,19 @@ runClangTidy(clang::tidy::ClangTidyContext &Context,
|
||||
|
||||
ActionFactory Factory(Context);
|
||||
Tool.run(&Factory);
|
||||
return DiagConsumer.take();
|
||||
}
|
||||
|
||||
void handleErrors(llvm::ArrayRef<ClangTidyError> Errors,
|
||||
ClangTidyContext &Context, bool Fix,
|
||||
void handleErrors(ClangTidyContext &Context, bool Fix,
|
||||
unsigned &WarningsAsErrorsCount,
|
||||
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> BaseFS) {
|
||||
llvm::IntrusiveRefCntPtr<vfs::FileSystem> BaseFS) {
|
||||
ErrorReporter Reporter(Context, Fix, BaseFS);
|
||||
llvm::vfs::FileSystem &FileSystem =
|
||||
vfs::FileSystem &FileSystem =
|
||||
*Reporter.getSourceManager().getFileManager().getVirtualFileSystem();
|
||||
auto InitialWorkingDir = FileSystem.getCurrentWorkingDirectory();
|
||||
if (!InitialWorkingDir)
|
||||
llvm::report_fatal_error("Cannot get current working path.");
|
||||
|
||||
for (const ClangTidyError &Error : Errors) {
|
||||
for (const ClangTidyError &Error : Context.getErrors()) {
|
||||
if (!Error.BuildDirectory.empty()) {
|
||||
// By default, the working directory of file system is the current
|
||||
// clang-tidy running directory.
|
||||
|
||||
@@ -230,13 +230,12 @@ getCheckOptions(const ClangTidyOptions &Options,
|
||||
/// \param StoreCheckProfile If provided, and EnableCheckProfile is true,
|
||||
/// the profile will not be output to stderr, but will instead be stored
|
||||
/// as a JSON file in the specified directory.
|
||||
std::vector<ClangTidyError>
|
||||
runClangTidy(clang::tidy::ClangTidyContext &Context,
|
||||
const tooling::CompilationDatabase &Compilations,
|
||||
ArrayRef<std::string> InputFiles,
|
||||
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> BaseFS,
|
||||
bool EnableCheckProfile = false,
|
||||
llvm::StringRef StoreCheckProfile = StringRef());
|
||||
void runClangTidy(clang::tidy::ClangTidyContext &Context,
|
||||
const tooling::CompilationDatabase &Compilations,
|
||||
ArrayRef<std::string> InputFiles,
|
||||
llvm::IntrusiveRefCntPtr<vfs::FileSystem> BaseFS,
|
||||
bool EnableCheckProfile = false,
|
||||
llvm::StringRef StoreCheckProfile = StringRef());
|
||||
|
||||
// FIXME: This interface will need to be significantly extended to be useful.
|
||||
// FIXME: Implement confidence levels for displaying/fixing errors.
|
||||
@@ -244,10 +243,9 @@ runClangTidy(clang::tidy::ClangTidyContext &Context,
|
||||
/// \brief Displays the found \p Errors to the users. If \p Fix is true, \p
|
||||
/// Errors containing fixes are automatically applied and reformatted. If no
|
||||
/// clang-format configuration file is found, the given \P FormatStyle is used.
|
||||
void handleErrors(llvm::ArrayRef<ClangTidyError> Errors,
|
||||
ClangTidyContext &Context, bool Fix,
|
||||
void handleErrors(ClangTidyContext &Context, bool Fix,
|
||||
unsigned &WarningsAsErrorsCount,
|
||||
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> BaseFS);
|
||||
llvm::IntrusiveRefCntPtr<vfs::FileSystem> BaseFS);
|
||||
|
||||
/// \brief Serializes replacements into YAML and writes them to the specified
|
||||
/// output stream.
|
||||
|
||||
@@ -199,6 +199,10 @@ DiagnosticBuilder ClangTidyContext::diag(
|
||||
return DiagEngine->Report(Loc, ID);
|
||||
}
|
||||
|
||||
void ClangTidyContext::setDiagnosticsEngine(DiagnosticsEngine *Engine) {
|
||||
DiagEngine = Engine;
|
||||
}
|
||||
|
||||
void ClangTidyContext::setSourceManager(SourceManager *SourceMgr) {
|
||||
DiagEngine->setSourceManager(SourceMgr);
|
||||
}
|
||||
@@ -255,6 +259,11 @@ bool ClangTidyContext::treatAsError(StringRef CheckName) const {
|
||||
return WarningAsErrorFilter->contains(CheckName);
|
||||
}
|
||||
|
||||
/// \brief Store a \c ClangTidyError.
|
||||
void ClangTidyContext::storeError(const ClangTidyError &Error) {
|
||||
Errors.push_back(Error);
|
||||
}
|
||||
|
||||
StringRef ClangTidyContext::getCheckName(unsigned DiagnosticID) const {
|
||||
llvm::DenseMap<unsigned, std::string>::const_iterator I =
|
||||
CheckNamesByDiagnosticID.find(DiagnosticID);
|
||||
@@ -267,7 +276,13 @@ ClangTidyDiagnosticConsumer::ClangTidyDiagnosticConsumer(
|
||||
ClangTidyContext &Ctx, bool RemoveIncompatibleErrors)
|
||||
: Context(Ctx), RemoveIncompatibleErrors(RemoveIncompatibleErrors),
|
||||
LastErrorRelatesToUserCode(false), LastErrorPassesLineFilter(false),
|
||||
LastErrorWasIgnored(false) {}
|
||||
LastErrorWasIgnored(false) {
|
||||
IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions();
|
||||
Diags = llvm::make_unique<DiagnosticsEngine>(
|
||||
IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs), &*DiagOpts, this,
|
||||
/*ShouldOwnClient=*/false);
|
||||
Context.setDiagnosticsEngine(Diags.get());
|
||||
}
|
||||
|
||||
void ClangTidyDiagnosticConsumer::finalizeLastError() {
|
||||
if (!Errors.empty()) {
|
||||
@@ -319,7 +334,7 @@ static bool IsNOLINTFound(StringRef NolintDirectiveText, StringRef Line,
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool LineIsMarkedWithNOLINT(const SourceManager &SM, SourceLocation Loc,
|
||||
static bool LineIsMarkedWithNOLINT(SourceManager &SM, SourceLocation Loc,
|
||||
unsigned DiagID,
|
||||
const ClangTidyContext &Context) {
|
||||
bool Invalid;
|
||||
@@ -365,8 +380,8 @@ static bool LineIsMarkedWithNOLINT(const SourceManager &SM, SourceLocation Loc,
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool LineIsMarkedWithNOLINTinMacro(const SourceManager &SM,
|
||||
SourceLocation Loc, unsigned DiagID,
|
||||
static bool LineIsMarkedWithNOLINTinMacro(SourceManager &SM, SourceLocation Loc,
|
||||
unsigned DiagID,
|
||||
const ClangTidyContext &Context) {
|
||||
while (true) {
|
||||
if (LineIsMarkedWithNOLINT(SM, Loc, DiagID, Context))
|
||||
@@ -385,7 +400,7 @@ void ClangTidyDiagnosticConsumer::HandleDiagnostic(
|
||||
|
||||
if (Info.getLocation().isValid() && DiagLevel != DiagnosticsEngine::Error &&
|
||||
DiagLevel != DiagnosticsEngine::Fatal &&
|
||||
LineIsMarkedWithNOLINTinMacro(Info.getSourceManager(),
|
||||
LineIsMarkedWithNOLINTinMacro(Diags->getSourceManager(),
|
||||
Info.getLocation(), Info.getID(),
|
||||
Context)) {
|
||||
++Context.Stats.ErrorsIgnoredNOLINT;
|
||||
@@ -447,14 +462,14 @@ void ClangTidyDiagnosticConsumer::HandleDiagnostic(
|
||||
Errors.back());
|
||||
SmallString<100> Message;
|
||||
Info.FormatDiagnostic(Message);
|
||||
FullSourceLoc Loc;
|
||||
if (Info.getLocation().isValid() && Info.hasSourceManager())
|
||||
Loc = FullSourceLoc(Info.getLocation(), Info.getSourceManager());
|
||||
FullSourceLoc Loc =
|
||||
(Info.getLocation().isInvalid())
|
||||
? FullSourceLoc()
|
||||
: FullSourceLoc(Info.getLocation(), Info.getSourceManager());
|
||||
Converter.emitDiagnostic(Loc, DiagLevel, Message, Info.getRanges(),
|
||||
Info.getFixItHints());
|
||||
|
||||
if (Info.hasSourceManager())
|
||||
checkFilters(Info.getLocation(), Info.getSourceManager());
|
||||
checkFilters(Info.getLocation());
|
||||
}
|
||||
|
||||
bool ClangTidyDiagnosticConsumer::passesLineFilter(StringRef FileName,
|
||||
@@ -475,8 +490,7 @@ bool ClangTidyDiagnosticConsumer::passesLineFilter(StringRef FileName,
|
||||
return false;
|
||||
}
|
||||
|
||||
void ClangTidyDiagnosticConsumer::checkFilters(SourceLocation Location,
|
||||
const SourceManager &Sources) {
|
||||
void ClangTidyDiagnosticConsumer::checkFilters(SourceLocation Location) {
|
||||
// Invalid location may mean a diagnostic in a command line, don't skip these.
|
||||
if (!Location.isValid()) {
|
||||
LastErrorRelatesToUserCode = true;
|
||||
@@ -484,6 +498,7 @@ void ClangTidyDiagnosticConsumer::checkFilters(SourceLocation Location,
|
||||
return;
|
||||
}
|
||||
|
||||
const SourceManager &Sources = Diags->getSourceManager();
|
||||
if (!*Context.getOptions().SystemHeaders &&
|
||||
Sources.isInSystemHeader(Location))
|
||||
return;
|
||||
@@ -519,7 +534,8 @@ llvm::Regex *ClangTidyDiagnosticConsumer::getHeaderFilter() {
|
||||
return HeaderFilter.get();
|
||||
}
|
||||
|
||||
void ClangTidyDiagnosticConsumer::removeIncompatibleErrors() {
|
||||
void ClangTidyDiagnosticConsumer::removeIncompatibleErrors(
|
||||
SmallVectorImpl<ClangTidyError> &Errors) const {
|
||||
// Each error is modelled as the set of intervals in which it applies
|
||||
// replacements. To detect overlapping replacements, we use a sweep line
|
||||
// algorithm over these sets of intervals.
|
||||
@@ -657,13 +673,18 @@ struct EqualClangTidyError {
|
||||
};
|
||||
} // end anonymous namespace
|
||||
|
||||
std::vector<ClangTidyError> ClangTidyDiagnosticConsumer::take() {
|
||||
// Flushes the internal diagnostics buffer to the ClangTidyContext.
|
||||
void ClangTidyDiagnosticConsumer::finish() {
|
||||
finalizeLastError();
|
||||
|
||||
std::sort(Errors.begin(), Errors.end(), LessClangTidyError());
|
||||
Errors.erase(std::unique(Errors.begin(), Errors.end(), EqualClangTidyError()),
|
||||
Errors.end());
|
||||
|
||||
if (RemoveIncompatibleErrors)
|
||||
removeIncompatibleErrors();
|
||||
return std::move(Errors);
|
||||
removeIncompatibleErrors(Errors);
|
||||
|
||||
for (const ClangTidyError &Error : Errors)
|
||||
Context.storeError(Error);
|
||||
Errors.clear();
|
||||
}
|
||||
|
||||
@@ -102,12 +102,6 @@ public:
|
||||
/// \brief Initializes \c ClangTidyContext instance.
|
||||
ClangTidyContext(std::unique_ptr<ClangTidyOptionsProvider> OptionsProvider,
|
||||
bool AllowEnablingAnalyzerAlphaCheckers = false);
|
||||
/// Sets the DiagnosticsEngine that diag() will emit diagnostics to.
|
||||
// FIXME: this is required initialization, and should be a constructor param.
|
||||
// Fix the context -> diag engine -> consumer -> context initialization cycle.
|
||||
void setDiagnosticsEngine(DiagnosticsEngine *DiagEngine) {
|
||||
this->DiagEngine = DiagEngine;
|
||||
}
|
||||
|
||||
~ClangTidyContext();
|
||||
|
||||
@@ -166,6 +160,12 @@ public:
|
||||
/// counters.
|
||||
const ClangTidyStats &getStats() const { return Stats; }
|
||||
|
||||
/// \brief Returns all collected errors.
|
||||
ArrayRef<ClangTidyError> getErrors() const { return Errors; }
|
||||
|
||||
/// \brief Clears collected errors.
|
||||
void clearErrors() { Errors.clear(); }
|
||||
|
||||
/// \brief Control profile collection in clang-tidy.
|
||||
void setEnableProfiling(bool Profile);
|
||||
bool getEnableProfiling() const { return Profile; }
|
||||
@@ -192,9 +192,18 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
// Writes to Stats.
|
||||
// Calls setDiagnosticsEngine() and storeError().
|
||||
friend class ClangTidyDiagnosticConsumer;
|
||||
friend class ClangTidyPluginAction;
|
||||
|
||||
/// \brief Sets the \c DiagnosticsEngine so that Diagnostics can be generated
|
||||
/// correctly.
|
||||
void setDiagnosticsEngine(DiagnosticsEngine *Engine);
|
||||
|
||||
/// \brief Store an \p Error.
|
||||
void storeError(const ClangTidyError &Error);
|
||||
|
||||
std::vector<ClangTidyError> Errors;
|
||||
DiagnosticsEngine *DiagEngine;
|
||||
std::unique_ptr<ClangTidyOptionsProvider> OptionsProvider;
|
||||
|
||||
@@ -234,12 +243,13 @@ public:
|
||||
void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,
|
||||
const Diagnostic &Info) override;
|
||||
|
||||
// Retrieve the diagnostics that were captured.
|
||||
std::vector<ClangTidyError> take();
|
||||
/// \brief Flushes the internal diagnostics buffer to the ClangTidyContext.
|
||||
void finish() override;
|
||||
|
||||
private:
|
||||
void finalizeLastError();
|
||||
void removeIncompatibleErrors();
|
||||
|
||||
void removeIncompatibleErrors(SmallVectorImpl<ClangTidyError> &Errors) const;
|
||||
|
||||
/// \brief Returns the \c HeaderFilter constructed for the options set in the
|
||||
/// context.
|
||||
@@ -247,12 +257,13 @@ private:
|
||||
|
||||
/// \brief Updates \c LastErrorRelatesToUserCode and LastErrorPassesLineFilter
|
||||
/// according to the diagnostic \p Location.
|
||||
void checkFilters(SourceLocation Location, const SourceManager& Sources);
|
||||
void checkFilters(SourceLocation Location);
|
||||
bool passesLineFilter(StringRef FileName, unsigned LineNumber) const;
|
||||
|
||||
ClangTidyContext &Context;
|
||||
bool RemoveIncompatibleErrors;
|
||||
std::vector<ClangTidyError> Errors;
|
||||
std::unique_ptr<DiagnosticsEngine> Diags;
|
||||
SmallVector<ClangTidyError, 8> Errors;
|
||||
std::unique_ptr<llvm::Regex> HeaderFilter;
|
||||
bool LastErrorRelatesToUserCode;
|
||||
bool LastErrorPassesLineFilter;
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
//===- ClangTidyForceLinker.h - clang-tidy --------------------------------===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYFORCELINKER_H
|
||||
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYFORCELINKER_H
|
||||
|
||||
#include "clang/Config/config.h"
|
||||
#include "llvm/Support/Compiler.h"
|
||||
|
||||
namespace clang {
|
||||
namespace tidy {
|
||||
|
||||
// This anchor is used to force the linker to link the CERTModule.
|
||||
extern volatile int CERTModuleAnchorSource;
|
||||
static int LLVM_ATTRIBUTE_UNUSED CERTModuleAnchorDestination =
|
||||
CERTModuleAnchorSource;
|
||||
|
||||
// This anchor is used to force the linker to link the AbseilModule.
|
||||
extern volatile int AbseilModuleAnchorSource;
|
||||
static int LLVM_ATTRIBUTE_UNUSED AbseilModuleAnchorDestination =
|
||||
AbseilModuleAnchorSource;
|
||||
|
||||
// This anchor is used to force the linker to link the BoostModule.
|
||||
extern volatile int BoostModuleAnchorSource;
|
||||
static int LLVM_ATTRIBUTE_UNUSED BoostModuleAnchorDestination =
|
||||
BoostModuleAnchorSource;
|
||||
|
||||
// This anchor is used to force the linker to link the BugproneModule.
|
||||
extern volatile int BugproneModuleAnchorSource;
|
||||
static int LLVM_ATTRIBUTE_UNUSED BugproneModuleAnchorDestination =
|
||||
BugproneModuleAnchorSource;
|
||||
|
||||
// This anchor is used to force the linker to link the LLVMModule.
|
||||
extern volatile int LLVMModuleAnchorSource;
|
||||
static int LLVM_ATTRIBUTE_UNUSED LLVMModuleAnchorDestination =
|
||||
LLVMModuleAnchorSource;
|
||||
|
||||
// This anchor is used to force the linker to link the CppCoreGuidelinesModule.
|
||||
extern volatile int CppCoreGuidelinesModuleAnchorSource;
|
||||
static int LLVM_ATTRIBUTE_UNUSED CppCoreGuidelinesModuleAnchorDestination =
|
||||
CppCoreGuidelinesModuleAnchorSource;
|
||||
|
||||
// This anchor is used to force the linker to link the FuchsiaModule.
|
||||
extern volatile int FuchsiaModuleAnchorSource;
|
||||
static int LLVM_ATTRIBUTE_UNUSED FuchsiaModuleAnchorDestination =
|
||||
FuchsiaModuleAnchorSource;
|
||||
|
||||
// This anchor is used to force the linker to link the GoogleModule.
|
||||
extern volatile int GoogleModuleAnchorSource;
|
||||
static int LLVM_ATTRIBUTE_UNUSED GoogleModuleAnchorDestination =
|
||||
GoogleModuleAnchorSource;
|
||||
|
||||
// This anchor is used to force the linker to link the AndroidModule.
|
||||
extern volatile int AndroidModuleAnchorSource;
|
||||
static int LLVM_ATTRIBUTE_UNUSED AndroidModuleAnchorDestination =
|
||||
AndroidModuleAnchorSource;
|
||||
|
||||
// This anchor is used to force the linker to link the MiscModule.
|
||||
extern volatile int MiscModuleAnchorSource;
|
||||
static int LLVM_ATTRIBUTE_UNUSED MiscModuleAnchorDestination =
|
||||
MiscModuleAnchorSource;
|
||||
|
||||
// This anchor is used to force the linker to link the ModernizeModule.
|
||||
extern volatile int ModernizeModuleAnchorSource;
|
||||
static int LLVM_ATTRIBUTE_UNUSED ModernizeModuleAnchorDestination =
|
||||
ModernizeModuleAnchorSource;
|
||||
|
||||
#if CLANG_ENABLE_STATIC_ANALYZER
|
||||
// This anchor is used to force the linker to link the MPIModule.
|
||||
extern volatile int MPIModuleAnchorSource;
|
||||
static int LLVM_ATTRIBUTE_UNUSED MPIModuleAnchorDestination =
|
||||
MPIModuleAnchorSource;
|
||||
#endif
|
||||
|
||||
// This anchor is used to force the linker to link the PerformanceModule.
|
||||
extern volatile int PerformanceModuleAnchorSource;
|
||||
static int LLVM_ATTRIBUTE_UNUSED PerformanceModuleAnchorDestination =
|
||||
PerformanceModuleAnchorSource;
|
||||
|
||||
// This anchor is used to force the linker to link the PortabilityModule.
|
||||
extern volatile int PortabilityModuleAnchorSource;
|
||||
static int LLVM_ATTRIBUTE_UNUSED PortabilityModuleAnchorDestination =
|
||||
PortabilityModuleAnchorSource;
|
||||
|
||||
// This anchor is used to force the linker to link the ReadabilityModule.
|
||||
extern volatile int ReadabilityModuleAnchorSource;
|
||||
static int LLVM_ATTRIBUTE_UNUSED ReadabilityModuleAnchorDestination =
|
||||
ReadabilityModuleAnchorSource;
|
||||
|
||||
// This anchor is used to force the linker to link the ObjCModule.
|
||||
extern volatile int ObjCModuleAnchorSource;
|
||||
static int LLVM_ATTRIBUTE_UNUSED ObjCModuleAnchorDestination =
|
||||
ObjCModuleAnchorSource;
|
||||
|
||||
// This anchor is used to force the linker to link the HICPPModule.
|
||||
extern volatile int HICPPModuleAnchorSource;
|
||||
static int LLVM_ATTRIBUTE_UNUSED HICPPModuleAnchorDestination =
|
||||
HICPPModuleAnchorSource;
|
||||
|
||||
// This anchor is used to force the linker to link the ZirconModule.
|
||||
extern volatile int ZirconModuleAnchorSource;
|
||||
static int LLVM_ATTRIBUTE_UNUSED ZirconModuleAnchorDestination =
|
||||
ZirconModuleAnchorSource;
|
||||
|
||||
} // namespace tidy
|
||||
} // namespace clang
|
||||
|
||||
#endif
|
||||
@@ -204,11 +204,11 @@ FileOptionsProvider::FileOptionsProvider(
|
||||
const ClangTidyGlobalOptions &GlobalOptions,
|
||||
const ClangTidyOptions &DefaultOptions,
|
||||
const ClangTidyOptions &OverrideOptions,
|
||||
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS)
|
||||
llvm::IntrusiveRefCntPtr<vfs::FileSystem> VFS)
|
||||
: DefaultOptionsProvider(GlobalOptions, DefaultOptions),
|
||||
OverrideOptions(OverrideOptions), FS(std::move(VFS)) {
|
||||
if (!FS)
|
||||
FS = llvm::vfs::getRealFileSystem();
|
||||
FS = vfs::getRealFileSystem();
|
||||
ConfigHandlers.emplace_back(".clang-tidy", parseConfiguration);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,12 +10,12 @@
|
||||
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYOPTIONS_H
|
||||
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYOPTIONS_H
|
||||
|
||||
#include "llvm/ADT/IntrusiveRefCntPtr.h"
|
||||
#include "llvm/ADT/Optional.h"
|
||||
#include "llvm/ADT/StringMap.h"
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
#include "llvm/ADT/IntrusiveRefCntPtr.h"
|
||||
#include "llvm/Support/ErrorOr.h"
|
||||
#include "llvm/Support/VirtualFileSystem.h"
|
||||
#include "clang/Basic/VirtualFileSystem.h"
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <string>
|
||||
@@ -218,11 +218,10 @@ public:
|
||||
///
|
||||
/// If any of the \param OverrideOptions fields are set, they will override
|
||||
/// whatever options are read from the configuration file.
|
||||
FileOptionsProvider(
|
||||
const ClangTidyGlobalOptions &GlobalOptions,
|
||||
const ClangTidyOptions &DefaultOptions,
|
||||
const ClangTidyOptions &OverrideOptions,
|
||||
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS = nullptr);
|
||||
FileOptionsProvider(const ClangTidyGlobalOptions &GlobalOptions,
|
||||
const ClangTidyOptions &DefaultOptions,
|
||||
const ClangTidyOptions &OverrideOptions,
|
||||
llvm::IntrusiveRefCntPtr<vfs::FileSystem> FS = nullptr);
|
||||
|
||||
/// \brief Initializes the \c FileOptionsProvider instance with a custom set
|
||||
/// of configuration file handlers.
|
||||
@@ -256,7 +255,7 @@ protected:
|
||||
llvm::StringMap<OptionsSource> CachedOptions;
|
||||
ClangTidyOptions OverrideOptions;
|
||||
ConfigFileHandlers ConfigHandlers;
|
||||
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS;
|
||||
llvm::IntrusiveRefCntPtr<vfs::FileSystem> FS;
|
||||
};
|
||||
|
||||
/// \brief Parses LineFilter from JSON and stores it to the \p Options.
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
//===- AbseilMatcher.h - clang-tidy ---------------------------------------===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "clang/AST/ASTContext.h"
|
||||
#include "clang/ASTMatchers/ASTMatchFinder.h"
|
||||
#include <algorithm>
|
||||
|
||||
namespace clang {
|
||||
namespace ast_matchers {
|
||||
|
||||
/// Matches AST nodes that were found within Abseil files.
|
||||
///
|
||||
/// Example matches Y but not X
|
||||
/// (matcher = cxxRecordDecl(isInAbseilFile())
|
||||
/// \code
|
||||
/// #include "absl/strings/internal-file.h"
|
||||
/// class X {};
|
||||
/// \endcode
|
||||
/// absl/strings/internal-file.h:
|
||||
/// \code
|
||||
/// class Y {};
|
||||
/// \endcode
|
||||
///
|
||||
/// Usable as: Matcher<Decl>, Matcher<Stmt>, Matcher<TypeLoc>,
|
||||
/// Matcher<NestedNameSpecifierLoc>
|
||||
AST_POLYMORPHIC_MATCHER(
|
||||
isInAbseilFile, AST_POLYMORPHIC_SUPPORTED_TYPES(Decl, Stmt, TypeLoc,
|
||||
NestedNameSpecifierLoc)) {
|
||||
auto &SourceManager = Finder->getASTContext().getSourceManager();
|
||||
SourceLocation Loc = SourceManager.getSpellingLoc(Node.getBeginLoc());
|
||||
if (Loc.isInvalid())
|
||||
return false;
|
||||
const FileEntry *FileEntry =
|
||||
SourceManager.getFileEntryForID(SourceManager.getFileID(Loc));
|
||||
if (!FileEntry)
|
||||
return false;
|
||||
// Determine whether filepath contains "absl/[absl-library]" substring, where
|
||||
// [absl-library] is AbseilLibraries list entry.
|
||||
StringRef Path = FileEntry->getName();
|
||||
static constexpr llvm::StringLiteral AbslPrefix("absl/");
|
||||
size_t PrefixPosition = Path.find(AbslPrefix);
|
||||
if (PrefixPosition == StringRef::npos)
|
||||
return false;
|
||||
Path = Path.drop_front(PrefixPosition + AbslPrefix.size());
|
||||
static const char *AbseilLibraries[] = {
|
||||
"algorithm", "base", "container", "debugging", "flags",
|
||||
"hash", "iterator", "memory", "meta", "numeric",
|
||||
"random", "strings", "synchronization", "time", "types",
|
||||
"utility"};
|
||||
return std::any_of(
|
||||
std::begin(AbseilLibraries), std::end(AbseilLibraries),
|
||||
[&](const char *Library) { return Path.startswith(Library); });
|
||||
}
|
||||
|
||||
} // namespace ast_matchers
|
||||
} // namespace clang
|
||||
@@ -10,18 +10,7 @@
|
||||
#include "../ClangTidy.h"
|
||||
#include "../ClangTidyModule.h"
|
||||
#include "../ClangTidyModuleRegistry.h"
|
||||
#include "DurationComparisonCheck.h"
|
||||
#include "DurationDivisionCheck.h"
|
||||
#include "DurationFactoryFloatCheck.h"
|
||||
#include "DurationFactoryScaleCheck.h"
|
||||
#include "DurationSubtractionCheck.h"
|
||||
#include "FasterStrsplitDelimiterCheck.h"
|
||||
#include "NoInternalDependenciesCheck.h"
|
||||
#include "NoNamespaceCheck.h"
|
||||
#include "RedundantStrcatCallsCheck.h"
|
||||
#include "StringFindStartswithCheck.h"
|
||||
#include "StrCatAppendCheck.h"
|
||||
#include "UpgradeDurationConversionsCheck.h"
|
||||
|
||||
namespace clang {
|
||||
namespace tidy {
|
||||
@@ -30,29 +19,8 @@ namespace abseil {
|
||||
class AbseilModule : public ClangTidyModule {
|
||||
public:
|
||||
void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override {
|
||||
CheckFactories.registerCheck<DurationComparisonCheck>(
|
||||
"abseil-duration-comparison");
|
||||
CheckFactories.registerCheck<DurationDivisionCheck>(
|
||||
"abseil-duration-division");
|
||||
CheckFactories.registerCheck<DurationFactoryFloatCheck>(
|
||||
"abseil-duration-factory-float");
|
||||
CheckFactories.registerCheck<DurationFactoryScaleCheck>(
|
||||
"abseil-duration-factory-scale");
|
||||
CheckFactories.registerCheck<DurationSubtractionCheck>(
|
||||
"abseil-duration-subtraction");
|
||||
CheckFactories.registerCheck<FasterStrsplitDelimiterCheck>(
|
||||
"abseil-faster-strsplit-delimiter");
|
||||
CheckFactories.registerCheck<NoInternalDependenciesCheck>(
|
||||
"abseil-no-internal-dependencies");
|
||||
CheckFactories.registerCheck<NoNamespaceCheck>("abseil-no-namespace");
|
||||
CheckFactories.registerCheck<RedundantStrcatCallsCheck>(
|
||||
"abseil-redundant-strcat-calls");
|
||||
CheckFactories.registerCheck<StrCatAppendCheck>(
|
||||
"abseil-str-cat-append");
|
||||
CheckFactories.registerCheck<StringFindStartswithCheck>(
|
||||
"abseil-string-find-startswith");
|
||||
CheckFactories.registerCheck<UpgradeDurationConversionsCheck>(
|
||||
"abseil-upgrade-duration-conversions");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -2,19 +2,7 @@ set(LLVM_LINK_COMPONENTS support)
|
||||
|
||||
add_clang_library(clangTidyAbseilModule
|
||||
AbseilTidyModule.cpp
|
||||
DurationComparisonCheck.cpp
|
||||
DurationDivisionCheck.cpp
|
||||
DurationFactoryFloatCheck.cpp
|
||||
DurationFactoryScaleCheck.cpp
|
||||
DurationRewriter.cpp
|
||||
DurationSubtractionCheck.cpp
|
||||
FasterStrsplitDelimiterCheck.cpp
|
||||
NoInternalDependenciesCheck.cpp
|
||||
NoNamespaceCheck.cpp
|
||||
RedundantStrcatCallsCheck.cpp
|
||||
StrCatAppendCheck.cpp
|
||||
StringFindStartswithCheck.cpp
|
||||
UpgradeDurationConversionsCheck.cpp
|
||||
|
||||
LINK_LIBS
|
||||
clangAST
|
||||
@@ -23,5 +11,4 @@ add_clang_library(clangTidyAbseilModule
|
||||
clangLex
|
||||
clangTidy
|
||||
clangTidyUtils
|
||||
clangTooling
|
||||
)
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
//===--- DurationComparisonCheck.cpp - clang-tidy -------------------------===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "DurationComparisonCheck.h"
|
||||
#include "DurationRewriter.h"
|
||||
#include "clang/AST/ASTContext.h"
|
||||
#include "clang/ASTMatchers/ASTMatchFinder.h"
|
||||
#include "clang/Tooling/FixIt.h"
|
||||
|
||||
using namespace clang::ast_matchers;
|
||||
|
||||
namespace clang {
|
||||
namespace tidy {
|
||||
namespace abseil {
|
||||
|
||||
/// Return `true` if `E` is a either: not a macro at all; or an argument to
|
||||
/// one. In the latter case, we should still transform it.
|
||||
static bool IsValidMacro(const MatchFinder::MatchResult &Result,
|
||||
const Expr *E) {
|
||||
if (!E->getBeginLoc().isMacroID())
|
||||
return true;
|
||||
|
||||
SourceLocation Loc = E->getBeginLoc();
|
||||
// We want to get closer towards the initial macro typed into the source only
|
||||
// if the location is being expanded as a macro argument.
|
||||
while (Result.SourceManager->isMacroArgExpansion(Loc)) {
|
||||
// We are calling getImmediateMacroCallerLoc, but note it is essentially
|
||||
// equivalent to calling getImmediateSpellingLoc in this context according
|
||||
// to Clang implementation. We are not calling getImmediateSpellingLoc
|
||||
// because Clang comment says it "should not generally be used by clients."
|
||||
Loc = Result.SourceManager->getImmediateMacroCallerLoc(Loc);
|
||||
}
|
||||
return !Loc.isMacroID();
|
||||
}
|
||||
|
||||
void DurationComparisonCheck::registerMatchers(MatchFinder *Finder) {
|
||||
auto Matcher =
|
||||
binaryOperator(anyOf(hasOperatorName(">"), hasOperatorName(">="),
|
||||
hasOperatorName("=="), hasOperatorName("<="),
|
||||
hasOperatorName("<")),
|
||||
hasEitherOperand(ignoringImpCasts(callExpr(
|
||||
callee(functionDecl(DurationConversionFunction())
|
||||
.bind("function_decl"))))))
|
||||
.bind("binop");
|
||||
|
||||
Finder->addMatcher(Matcher, this);
|
||||
}
|
||||
|
||||
void DurationComparisonCheck::check(const MatchFinder::MatchResult &Result) {
|
||||
const auto *Binop = Result.Nodes.getNodeAs<BinaryOperator>("binop");
|
||||
|
||||
llvm::Optional<DurationScale> Scale = getScaleForInverse(
|
||||
Result.Nodes.getNodeAs<FunctionDecl>("function_decl")->getName());
|
||||
if (!Scale)
|
||||
return;
|
||||
|
||||
// In most cases, we'll only need to rewrite one of the sides, but we also
|
||||
// want to handle the case of rewriting both sides. This is much simpler if
|
||||
// we unconditionally try and rewrite both, and let the rewriter determine
|
||||
// if nothing needs to be done.
|
||||
if (!IsValidMacro(Result, Binop->getLHS()) ||
|
||||
!IsValidMacro(Result, Binop->getRHS()))
|
||||
return;
|
||||
std::string LhsReplacement =
|
||||
rewriteExprFromNumberToDuration(Result, *Scale, Binop->getLHS());
|
||||
std::string RhsReplacement =
|
||||
rewriteExprFromNumberToDuration(Result, *Scale, Binop->getRHS());
|
||||
|
||||
diag(Binop->getBeginLoc(), "perform comparison in the duration domain")
|
||||
<< FixItHint::CreateReplacement(Binop->getSourceRange(),
|
||||
(llvm::Twine(LhsReplacement) + " " +
|
||||
Binop->getOpcodeStr() + " " +
|
||||
RhsReplacement)
|
||||
.str());
|
||||
}
|
||||
|
||||
} // namespace abseil
|
||||
} // namespace tidy
|
||||
} // namespace clang
|
||||
@@ -1,36 +0,0 @@
|
||||
//===--- DurationComparisonCheck.h - clang-tidy -----------------*- C++ -*-===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ABSEIL_DURATIONCOMPARISONCHECK_H
|
||||
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ABSEIL_DURATIONCOMPARISONCHECK_H
|
||||
|
||||
#include "../ClangTidy.h"
|
||||
|
||||
namespace clang {
|
||||
namespace tidy {
|
||||
namespace abseil {
|
||||
|
||||
/// Prefer comparison in the absl::Duration domain instead of the numeric
|
||||
/// domain.
|
||||
///
|
||||
/// For the user-facing documentation see:
|
||||
/// http://clang.llvm.org/extra/clang-tidy/checks/abseil-duration-comparison.html
|
||||
class DurationComparisonCheck : public ClangTidyCheck {
|
||||
public:
|
||||
DurationComparisonCheck(StringRef Name, ClangTidyContext *Context)
|
||||
: ClangTidyCheck(Name, Context) {}
|
||||
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
|
||||
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
|
||||
};
|
||||
|
||||
} // namespace abseil
|
||||
} // namespace tidy
|
||||
} // namespace clang
|
||||
|
||||
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ABSEIL_DURATIONCOMPARISONCHECK_H
|
||||
@@ -1,59 +0,0 @@
|
||||
//===--- DurationDivisionCheck.cpp - clang-tidy----------------------------===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "DurationDivisionCheck.h"
|
||||
#include "clang/AST/ASTContext.h"
|
||||
#include "clang/ASTMatchers/ASTMatchFinder.h"
|
||||
|
||||
namespace clang {
|
||||
namespace tidy {
|
||||
namespace abseil {
|
||||
|
||||
using namespace clang::ast_matchers;
|
||||
|
||||
void DurationDivisionCheck::registerMatchers(MatchFinder *finder) {
|
||||
if (!getLangOpts().CPlusPlus)
|
||||
return;
|
||||
|
||||
const auto DurationExpr =
|
||||
expr(hasType(cxxRecordDecl(hasName("::absl::Duration"))));
|
||||
finder->addMatcher(
|
||||
implicitCastExpr(
|
||||
hasSourceExpression(ignoringParenCasts(
|
||||
cxxOperatorCallExpr(hasOverloadedOperatorName("/"),
|
||||
hasArgument(0, DurationExpr),
|
||||
hasArgument(1, DurationExpr))
|
||||
.bind("OpCall"))),
|
||||
hasImplicitDestinationType(qualType(unless(isInteger()))),
|
||||
unless(hasParent(cxxStaticCastExpr())),
|
||||
unless(hasParent(cStyleCastExpr())),
|
||||
unless(isInTemplateInstantiation())),
|
||||
this);
|
||||
}
|
||||
|
||||
void DurationDivisionCheck::check(const MatchFinder::MatchResult &result) {
|
||||
const auto *OpCall = result.Nodes.getNodeAs<CXXOperatorCallExpr>("OpCall");
|
||||
diag(OpCall->getOperatorLoc(),
|
||||
"operator/ on absl::Duration objects performs integer division; "
|
||||
"did you mean to use FDivDuration()?")
|
||||
<< FixItHint::CreateInsertion(OpCall->getBeginLoc(),
|
||||
"absl::FDivDuration(")
|
||||
<< FixItHint::CreateReplacement(
|
||||
SourceRange(OpCall->getOperatorLoc(), OpCall->getOperatorLoc()),
|
||||
", ")
|
||||
<< FixItHint::CreateInsertion(
|
||||
Lexer::getLocForEndOfToken(
|
||||
result.SourceManager->getSpellingLoc(OpCall->getEndLoc()), 0,
|
||||
*result.SourceManager, result.Context->getLangOpts()),
|
||||
")");
|
||||
}
|
||||
|
||||
} // namespace abseil
|
||||
} // namespace tidy
|
||||
} // namespace clang
|
||||
@@ -1,35 +0,0 @@
|
||||
//===--- DurationDivisionCheck.h - clang-tidy--------------------*- C++ -*-===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ABSEIL_DURATIONDIVISIONCHECK_H_
|
||||
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ABSEIL_DURATIONDIVISIONCHECK_H_
|
||||
|
||||
#include "../ClangTidy.h"
|
||||
|
||||
namespace clang {
|
||||
namespace tidy {
|
||||
namespace abseil {
|
||||
|
||||
// Find potential incorrect uses of integer division of absl::Duration objects.
|
||||
//
|
||||
// For the user-facing documentation see:
|
||||
// http://clang.llvm.org/extra/clang-tidy/checks/abseil-duration-division.html
|
||||
|
||||
class DurationDivisionCheck : public ClangTidyCheck {
|
||||
public:
|
||||
using ClangTidyCheck::ClangTidyCheck;
|
||||
void registerMatchers(ast_matchers::MatchFinder *finder) override;
|
||||
void check(const ast_matchers::MatchFinder::MatchResult &result) override;
|
||||
};
|
||||
|
||||
} // namespace abseil
|
||||
} // namespace tidy
|
||||
} // namespace clang
|
||||
|
||||
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ABSEIL_DURATIONDIVISIONCHECK_H_
|
||||
@@ -1,72 +0,0 @@
|
||||
//===--- DurationFactoryFloatCheck.cpp - clang-tidy -----------------------===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "DurationFactoryFloatCheck.h"
|
||||
#include "DurationRewriter.h"
|
||||
#include "clang/AST/ASTContext.h"
|
||||
#include "clang/ASTMatchers/ASTMatchFinder.h"
|
||||
#include "clang/Tooling/FixIt.h"
|
||||
|
||||
using namespace clang::ast_matchers;
|
||||
|
||||
namespace clang {
|
||||
namespace tidy {
|
||||
namespace abseil {
|
||||
|
||||
// Returns `true` if `Range` is inside a macro definition.
|
||||
static bool InsideMacroDefinition(const MatchFinder::MatchResult &Result,
|
||||
SourceRange Range) {
|
||||
return !clang::Lexer::makeFileCharRange(
|
||||
clang::CharSourceRange::getCharRange(Range),
|
||||
*Result.SourceManager, Result.Context->getLangOpts())
|
||||
.isValid();
|
||||
}
|
||||
|
||||
void DurationFactoryFloatCheck::registerMatchers(MatchFinder *Finder) {
|
||||
Finder->addMatcher(
|
||||
callExpr(callee(functionDecl(DurationFactoryFunction())),
|
||||
hasArgument(0, anyOf(cxxStaticCastExpr(hasDestinationType(
|
||||
realFloatingPointType())),
|
||||
cStyleCastExpr(hasDestinationType(
|
||||
realFloatingPointType())),
|
||||
cxxFunctionalCastExpr(hasDestinationType(
|
||||
realFloatingPointType())),
|
||||
floatLiteral())))
|
||||
.bind("call"),
|
||||
this);
|
||||
}
|
||||
|
||||
void DurationFactoryFloatCheck::check(const MatchFinder::MatchResult &Result) {
|
||||
const auto *MatchedCall = Result.Nodes.getNodeAs<CallExpr>("call");
|
||||
|
||||
// Don't try and replace things inside of macro definitions.
|
||||
if (InsideMacroDefinition(Result, MatchedCall->getSourceRange()))
|
||||
return;
|
||||
|
||||
const Expr *Arg = MatchedCall->getArg(0)->IgnoreImpCasts();
|
||||
// Arguments which are macros are ignored.
|
||||
if (Arg->getBeginLoc().isMacroID())
|
||||
return;
|
||||
|
||||
llvm::Optional<std::string> SimpleArg = stripFloatCast(Result, *Arg);
|
||||
if (!SimpleArg)
|
||||
SimpleArg = stripFloatLiteralFraction(Result, *Arg);
|
||||
|
||||
if (SimpleArg) {
|
||||
diag(MatchedCall->getBeginLoc(),
|
||||
(llvm::Twine("use the integer version of absl::") +
|
||||
MatchedCall->getDirectCallee()->getName())
|
||||
.str())
|
||||
<< FixItHint::CreateReplacement(Arg->getSourceRange(), *SimpleArg);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace abseil
|
||||
} // namespace tidy
|
||||
} // namespace clang
|
||||
@@ -1,38 +0,0 @@
|
||||
//===--- DurationFactoryFloatCheck.h - clang-tidy ---------------*- C++ -*-===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ABSEIL_DURATIONFACTORYFLOATCHECK_H
|
||||
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ABSEIL_DURATIONFACTORYFLOATCHECK_H
|
||||
|
||||
#include "../ClangTidy.h"
|
||||
|
||||
namespace clang {
|
||||
namespace tidy {
|
||||
namespace abseil {
|
||||
|
||||
/// This check finds cases where `Duration` factories are being called with
|
||||
/// floating point arguments, but could be called using integer arguments.
|
||||
/// It handles explicit casts and floating point literals with no fractional
|
||||
/// component.
|
||||
///
|
||||
/// For the user-facing documentation see:
|
||||
/// http://clang.llvm.org/extra/clang-tidy/checks/abseil-duration-factory-float.html
|
||||
class DurationFactoryFloatCheck : public ClangTidyCheck {
|
||||
public:
|
||||
DurationFactoryFloatCheck(StringRef Name, ClangTidyContext *Context)
|
||||
: ClangTidyCheck(Name, Context) {}
|
||||
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
|
||||
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
|
||||
};
|
||||
|
||||
} // namespace abseil
|
||||
} // namespace tidy
|
||||
} // namespace clang
|
||||
|
||||
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ABSEIL_DURATIONFACTORYFLOATCHECK_H
|
||||
@@ -1,235 +0,0 @@
|
||||
//===--- DurationFactoryScaleCheck.cpp - clang-tidy -----------------------===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "DurationFactoryScaleCheck.h"
|
||||
#include "DurationRewriter.h"
|
||||
#include "clang/AST/ASTContext.h"
|
||||
#include "clang/ASTMatchers/ASTMatchFinder.h"
|
||||
#include "clang/Tooling/FixIt.h"
|
||||
|
||||
using namespace clang::ast_matchers;
|
||||
|
||||
namespace clang {
|
||||
namespace tidy {
|
||||
namespace abseil {
|
||||
|
||||
// Given the name of a duration factory function, return the appropriate
|
||||
// `DurationScale` for that factory. If no factory can be found for
|
||||
// `FactoryName`, return `None`.
|
||||
static llvm::Optional<DurationScale>
|
||||
getScaleForFactory(llvm::StringRef FactoryName) {
|
||||
static const std::unordered_map<std::string, DurationScale> ScaleMap(
|
||||
{{"Nanoseconds", DurationScale::Nanoseconds},
|
||||
{"Microseconds", DurationScale::Microseconds},
|
||||
{"Milliseconds", DurationScale::Milliseconds},
|
||||
{"Seconds", DurationScale::Seconds},
|
||||
{"Minutes", DurationScale::Minutes},
|
||||
{"Hours", DurationScale::Hours}});
|
||||
|
||||
auto ScaleIter = ScaleMap.find(FactoryName);
|
||||
if (ScaleIter == ScaleMap.end())
|
||||
return llvm::None;
|
||||
|
||||
return ScaleIter->second;
|
||||
}
|
||||
|
||||
// Given either an integer or float literal, return its value.
|
||||
// One and only one of `IntLit` and `FloatLit` should be provided.
|
||||
static double GetValue(const IntegerLiteral *IntLit,
|
||||
const FloatingLiteral *FloatLit) {
|
||||
if (IntLit)
|
||||
return IntLit->getValue().getLimitedValue();
|
||||
|
||||
assert(FloatLit != nullptr && "Neither IntLit nor FloatLit set");
|
||||
return FloatLit->getValueAsApproximateDouble();
|
||||
}
|
||||
|
||||
// Given the scale of a duration and a `Multiplier`, determine if `Multiplier`
|
||||
// would produce a new scale. If so, return a tuple containing the new scale
|
||||
// and a suitable Multipler for that scale, otherwise `None`.
|
||||
static llvm::Optional<std::tuple<DurationScale, double>>
|
||||
GetNewScaleSingleStep(DurationScale OldScale, double Multiplier) {
|
||||
switch (OldScale) {
|
||||
case DurationScale::Hours:
|
||||
if (Multiplier <= 1.0 / 60.0)
|
||||
return std::make_tuple(DurationScale::Minutes, Multiplier * 60.0);
|
||||
break;
|
||||
|
||||
case DurationScale::Minutes:
|
||||
if (Multiplier >= 60.0)
|
||||
return std::make_tuple(DurationScale::Hours, Multiplier / 60.0);
|
||||
if (Multiplier <= 1.0 / 60.0)
|
||||
return std::make_tuple(DurationScale::Seconds, Multiplier * 60.0);
|
||||
break;
|
||||
|
||||
case DurationScale::Seconds:
|
||||
if (Multiplier >= 60.0)
|
||||
return std::make_tuple(DurationScale::Minutes, Multiplier / 60.0);
|
||||
if (Multiplier <= 1e-3)
|
||||
return std::make_tuple(DurationScale::Milliseconds, Multiplier * 1e3);
|
||||
break;
|
||||
|
||||
case DurationScale::Milliseconds:
|
||||
if (Multiplier >= 1e3)
|
||||
return std::make_tuple(DurationScale::Seconds, Multiplier / 1e3);
|
||||
if (Multiplier <= 1e-3)
|
||||
return std::make_tuple(DurationScale::Microseconds, Multiplier * 1e3);
|
||||
break;
|
||||
|
||||
case DurationScale::Microseconds:
|
||||
if (Multiplier >= 1e3)
|
||||
return std::make_tuple(DurationScale::Milliseconds, Multiplier / 1e3);
|
||||
if (Multiplier <= 1e-3)
|
||||
return std::make_tuple(DurationScale::Nanoseconds, Multiplier * 1e-3);
|
||||
break;
|
||||
|
||||
case DurationScale::Nanoseconds:
|
||||
if (Multiplier >= 1e3)
|
||||
return std::make_tuple(DurationScale::Microseconds, Multiplier / 1e3);
|
||||
break;
|
||||
}
|
||||
|
||||
return llvm::None;
|
||||
}
|
||||
|
||||
// Given the scale of a duration and a `Multiplier`, determine if `Multiplier`
|
||||
// would produce a new scale. If so, return it, otherwise `None`.
|
||||
static llvm::Optional<DurationScale> GetNewScale(DurationScale OldScale,
|
||||
double Multiplier) {
|
||||
while (Multiplier != 1.0) {
|
||||
llvm::Optional<std::tuple<DurationScale, double>> result =
|
||||
GetNewScaleSingleStep(OldScale, Multiplier);
|
||||
if (!result)
|
||||
break;
|
||||
if (std::get<1>(*result) == 1.0)
|
||||
return std::get<0>(*result);
|
||||
Multiplier = std::get<1>(*result);
|
||||
OldScale = std::get<0>(*result);
|
||||
}
|
||||
|
||||
return llvm::None;
|
||||
}
|
||||
|
||||
void DurationFactoryScaleCheck::registerMatchers(MatchFinder *Finder) {
|
||||
Finder->addMatcher(
|
||||
callExpr(
|
||||
callee(functionDecl(DurationFactoryFunction()).bind("call_decl")),
|
||||
hasArgument(
|
||||
0,
|
||||
ignoringImpCasts(anyOf(
|
||||
cxxFunctionalCastExpr(
|
||||
hasDestinationType(
|
||||
anyOf(isInteger(), realFloatingPointType())),
|
||||
hasSourceExpression(initListExpr())),
|
||||
integerLiteral(equals(0)), floatLiteral(equals(0.0)),
|
||||
binaryOperator(hasOperatorName("*"),
|
||||
hasEitherOperand(ignoringImpCasts(
|
||||
anyOf(integerLiteral(), floatLiteral()))))
|
||||
.bind("mult_binop"),
|
||||
binaryOperator(hasOperatorName("/"), hasRHS(floatLiteral()))
|
||||
.bind("div_binop")))))
|
||||
.bind("call"),
|
||||
this);
|
||||
}
|
||||
|
||||
void DurationFactoryScaleCheck::check(const MatchFinder::MatchResult &Result) {
|
||||
const auto *Call = Result.Nodes.getNodeAs<CallExpr>("call");
|
||||
|
||||
// Don't try to replace things inside of macro definitions.
|
||||
if (Call->getExprLoc().isMacroID())
|
||||
return;
|
||||
|
||||
const Expr *Arg = Call->getArg(0)->IgnoreParenImpCasts();
|
||||
// Arguments which are macros are ignored.
|
||||
if (Arg->getBeginLoc().isMacroID())
|
||||
return;
|
||||
|
||||
// We first handle the cases of literal zero (both float and integer).
|
||||
if (IsLiteralZero(Result, *Arg)) {
|
||||
diag(Call->getBeginLoc(),
|
||||
"use ZeroDuration() for zero-length time intervals")
|
||||
<< FixItHint::CreateReplacement(Call->getSourceRange(),
|
||||
"absl::ZeroDuration()");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto *CallDecl = Result.Nodes.getNodeAs<FunctionDecl>("call_decl");
|
||||
llvm::Optional<DurationScale> MaybeScale =
|
||||
getScaleForFactory(CallDecl->getName());
|
||||
if (!MaybeScale)
|
||||
return;
|
||||
|
||||
DurationScale Scale = *MaybeScale;
|
||||
const Expr *Remainder;
|
||||
llvm::Optional<DurationScale> NewScale;
|
||||
|
||||
// We next handle the cases of multiplication and division.
|
||||
if (const auto *MultBinOp =
|
||||
Result.Nodes.getNodeAs<BinaryOperator>("mult_binop")) {
|
||||
// For multiplication, we need to look at both operands, and consider the
|
||||
// cases where a user is multiplying by something such as 1e-3.
|
||||
|
||||
// First check the LHS
|
||||
const auto *IntLit = llvm::dyn_cast<IntegerLiteral>(MultBinOp->getLHS());
|
||||
const auto *FloatLit = llvm::dyn_cast<FloatingLiteral>(MultBinOp->getLHS());
|
||||
if (IntLit || FloatLit) {
|
||||
NewScale = GetNewScale(Scale, GetValue(IntLit, FloatLit));
|
||||
if (NewScale)
|
||||
Remainder = MultBinOp->getRHS();
|
||||
}
|
||||
|
||||
// If we weren't able to scale based on the LHS, check the RHS
|
||||
if (!NewScale) {
|
||||
IntLit = llvm::dyn_cast<IntegerLiteral>(MultBinOp->getRHS());
|
||||
FloatLit = llvm::dyn_cast<FloatingLiteral>(MultBinOp->getRHS());
|
||||
if (IntLit || FloatLit) {
|
||||
NewScale = GetNewScale(Scale, GetValue(IntLit, FloatLit));
|
||||
if (NewScale)
|
||||
Remainder = MultBinOp->getLHS();
|
||||
}
|
||||
}
|
||||
} else if (const auto *DivBinOp =
|
||||
Result.Nodes.getNodeAs<BinaryOperator>("div_binop")) {
|
||||
// We next handle division.
|
||||
// For division, we only check the RHS.
|
||||
const auto *FloatLit = llvm::dyn_cast<FloatingLiteral>(DivBinOp->getRHS());
|
||||
|
||||
llvm::Optional<DurationScale> NewScale =
|
||||
GetNewScale(Scale, 1.0 / FloatLit->getValueAsApproximateDouble());
|
||||
if (NewScale) {
|
||||
const Expr *Remainder = DivBinOp->getLHS();
|
||||
|
||||
// We've found an appropriate scaling factor and the new scale, so output
|
||||
// the relevant fix.
|
||||
diag(Call->getBeginLoc(), "internal duration scaling can be removed")
|
||||
<< FixItHint::CreateReplacement(
|
||||
Call->getSourceRange(),
|
||||
(llvm::Twine(getFactoryForScale(*NewScale)) + "(" +
|
||||
tooling::fixit::getText(*Remainder, *Result.Context) + ")")
|
||||
.str());
|
||||
}
|
||||
}
|
||||
|
||||
if (NewScale) {
|
||||
assert(Remainder && "No remainder found");
|
||||
// We've found an appropriate scaling factor and the new scale, so output
|
||||
// the relevant fix.
|
||||
diag(Call->getBeginLoc(), "internal duration scaling can be removed")
|
||||
<< FixItHint::CreateReplacement(
|
||||
Call->getSourceRange(),
|
||||
(llvm::Twine(getFactoryForScale(*NewScale)) + "(" +
|
||||
tooling::fixit::getText(*Remainder, *Result.Context) + ")")
|
||||
.str());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
} // namespace abseil
|
||||
} // namespace tidy
|
||||
} // namespace clang
|
||||
@@ -1,38 +0,0 @@
|
||||
//===--- DurationFactoryScaleCheck.h - clang-tidy ---------------*- C++ -*-===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ABSEIL_DURATIONFACTORYSCALECHECK_H
|
||||
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ABSEIL_DURATIONFACTORYSCALECHECK_H
|
||||
|
||||
#include "../ClangTidy.h"
|
||||
|
||||
namespace clang {
|
||||
namespace tidy {
|
||||
namespace abseil {
|
||||
|
||||
/// This check finds cases where the incorrect `Duration` factory function is
|
||||
/// being used by looking for scaling constants inside the factory argument
|
||||
/// and suggesting a more appropriate factory. It also looks for the special
|
||||
/// case of zero and suggests `ZeroDuration()`.
|
||||
///
|
||||
/// For the user-facing documentation see:
|
||||
/// http://clang.llvm.org/extra/clang-tidy/checks/abseil-duration-factory-scale.html
|
||||
class DurationFactoryScaleCheck : public ClangTidyCheck {
|
||||
public:
|
||||
DurationFactoryScaleCheck(StringRef Name, ClangTidyContext *Context)
|
||||
: ClangTidyCheck(Name, Context) {}
|
||||
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
|
||||
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
|
||||
};
|
||||
|
||||
} // namespace abseil
|
||||
} // namespace tidy
|
||||
} // namespace clang
|
||||
|
||||
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ABSEIL_DURATIONFACTORYSCALECHECK_H
|
||||
@@ -1,221 +0,0 @@
|
||||
//===--- DurationRewriter.cpp - clang-tidy --------------------------------===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "DurationRewriter.h"
|
||||
#include "clang/Tooling/FixIt.h"
|
||||
#include "llvm/ADT/IndexedMap.h"
|
||||
|
||||
using namespace clang::ast_matchers;
|
||||
|
||||
namespace clang {
|
||||
namespace tidy {
|
||||
namespace abseil {
|
||||
|
||||
struct DurationScale2IndexFunctor {
|
||||
using argument_type = DurationScale;
|
||||
unsigned operator()(DurationScale Scale) const {
|
||||
return static_cast<unsigned>(Scale);
|
||||
}
|
||||
};
|
||||
|
||||
/// Returns an integer if the fractional part of a `FloatingLiteral` is `0`.
|
||||
static llvm::Optional<llvm::APSInt>
|
||||
truncateIfIntegral(const FloatingLiteral &FloatLiteral) {
|
||||
double Value = FloatLiteral.getValueAsApproximateDouble();
|
||||
if (std::fmod(Value, 1) == 0) {
|
||||
if (Value >= static_cast<double>(1u << 31))
|
||||
return llvm::None;
|
||||
|
||||
return llvm::APSInt::get(static_cast<int64_t>(Value));
|
||||
}
|
||||
return llvm::None;
|
||||
}
|
||||
|
||||
const std::pair<llvm::StringRef, llvm::StringRef> &
|
||||
getInverseForScale(DurationScale Scale) {
|
||||
static const llvm::IndexedMap<std::pair<llvm::StringRef, llvm::StringRef>,
|
||||
DurationScale2IndexFunctor>
|
||||
InverseMap = []() {
|
||||
// TODO: Revisit the immediately invoked lamba technique when
|
||||
// IndexedMap gets an initializer list constructor.
|
||||
llvm::IndexedMap<std::pair<llvm::StringRef, llvm::StringRef>,
|
||||
DurationScale2IndexFunctor>
|
||||
InverseMap;
|
||||
InverseMap.resize(6);
|
||||
InverseMap[DurationScale::Hours] =
|
||||
std::make_pair("::absl::ToDoubleHours", "::absl::ToInt64Hours");
|
||||
InverseMap[DurationScale::Minutes] =
|
||||
std::make_pair("::absl::ToDoubleMinutes", "::absl::ToInt64Minutes");
|
||||
InverseMap[DurationScale::Seconds] =
|
||||
std::make_pair("::absl::ToDoubleSeconds", "::absl::ToInt64Seconds");
|
||||
InverseMap[DurationScale::Milliseconds] = std::make_pair(
|
||||
"::absl::ToDoubleMilliseconds", "::absl::ToInt64Milliseconds");
|
||||
InverseMap[DurationScale::Microseconds] = std::make_pair(
|
||||
"::absl::ToDoubleMicroseconds", "::absl::ToInt64Microseconds");
|
||||
InverseMap[DurationScale::Nanoseconds] = std::make_pair(
|
||||
"::absl::ToDoubleNanoseconds", "::absl::ToInt64Nanoseconds");
|
||||
return InverseMap;
|
||||
}();
|
||||
|
||||
return InverseMap[Scale];
|
||||
}
|
||||
|
||||
/// If `Node` is a call to the inverse of `Scale`, return that inverse's
|
||||
/// argument, otherwise None.
|
||||
static llvm::Optional<std::string>
|
||||
rewriteInverseDurationCall(const MatchFinder::MatchResult &Result,
|
||||
DurationScale Scale, const Expr &Node) {
|
||||
const std::pair<llvm::StringRef, llvm::StringRef> &InverseFunctions =
|
||||
getInverseForScale(Scale);
|
||||
if (const auto *MaybeCallArg = selectFirst<const Expr>(
|
||||
"e",
|
||||
match(callExpr(callee(functionDecl(hasAnyName(
|
||||
InverseFunctions.first, InverseFunctions.second))),
|
||||
hasArgument(0, expr().bind("e"))),
|
||||
Node, *Result.Context))) {
|
||||
return tooling::fixit::getText(*MaybeCallArg, *Result.Context).str();
|
||||
}
|
||||
|
||||
return llvm::None;
|
||||
}
|
||||
|
||||
/// Returns the factory function name for a given `Scale`.
|
||||
llvm::StringRef getFactoryForScale(DurationScale Scale) {
|
||||
switch (Scale) {
|
||||
case DurationScale::Hours:
|
||||
return "absl::Hours";
|
||||
case DurationScale::Minutes:
|
||||
return "absl::Minutes";
|
||||
case DurationScale::Seconds:
|
||||
return "absl::Seconds";
|
||||
case DurationScale::Milliseconds:
|
||||
return "absl::Milliseconds";
|
||||
case DurationScale::Microseconds:
|
||||
return "absl::Microseconds";
|
||||
case DurationScale::Nanoseconds:
|
||||
return "absl::Nanoseconds";
|
||||
}
|
||||
llvm_unreachable("unknown scaling factor");
|
||||
}
|
||||
|
||||
/// Returns `true` if `Node` is a value which evaluates to a literal `0`.
|
||||
bool IsLiteralZero(const MatchFinder::MatchResult &Result, const Expr &Node) {
|
||||
auto ZeroMatcher =
|
||||
anyOf(integerLiteral(equals(0)), floatLiteral(equals(0.0)));
|
||||
|
||||
// Check to see if we're using a zero directly.
|
||||
if (selectFirst<const clang::Expr>(
|
||||
"val", match(expr(ignoringImpCasts(ZeroMatcher)).bind("val"), Node,
|
||||
*Result.Context)) != nullptr)
|
||||
return true;
|
||||
|
||||
// Now check to see if we're using a functional cast with a scalar
|
||||
// initializer expression, e.g. `int{0}`.
|
||||
if (selectFirst<const clang::Expr>(
|
||||
"val", match(cxxFunctionalCastExpr(
|
||||
hasDestinationType(
|
||||
anyOf(isInteger(), realFloatingPointType())),
|
||||
hasSourceExpression(initListExpr(
|
||||
hasInit(0, ignoringParenImpCasts(ZeroMatcher)))))
|
||||
.bind("val"),
|
||||
Node, *Result.Context)) != nullptr)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
llvm::Optional<std::string>
|
||||
stripFloatCast(const ast_matchers::MatchFinder::MatchResult &Result,
|
||||
const Expr &Node) {
|
||||
if (const Expr *MaybeCastArg = selectFirst<const Expr>(
|
||||
"cast_arg",
|
||||
match(expr(anyOf(cxxStaticCastExpr(
|
||||
hasDestinationType(realFloatingPointType()),
|
||||
hasSourceExpression(expr().bind("cast_arg"))),
|
||||
cStyleCastExpr(
|
||||
hasDestinationType(realFloatingPointType()),
|
||||
hasSourceExpression(expr().bind("cast_arg"))),
|
||||
cxxFunctionalCastExpr(
|
||||
hasDestinationType(realFloatingPointType()),
|
||||
hasSourceExpression(expr().bind("cast_arg"))))),
|
||||
Node, *Result.Context)))
|
||||
return tooling::fixit::getText(*MaybeCastArg, *Result.Context).str();
|
||||
|
||||
return llvm::None;
|
||||
}
|
||||
|
||||
llvm::Optional<std::string>
|
||||
stripFloatLiteralFraction(const MatchFinder::MatchResult &Result,
|
||||
const Expr &Node) {
|
||||
if (const auto *LitFloat = llvm::dyn_cast<FloatingLiteral>(&Node))
|
||||
// Attempt to simplify a `Duration` factory call with a literal argument.
|
||||
if (llvm::Optional<llvm::APSInt> IntValue = truncateIfIntegral(*LitFloat))
|
||||
return IntValue->toString(/*radix=*/10);
|
||||
|
||||
return llvm::None;
|
||||
}
|
||||
|
||||
std::string simplifyDurationFactoryArg(const MatchFinder::MatchResult &Result,
|
||||
const Expr &Node) {
|
||||
// Check for an explicit cast to `float` or `double`.
|
||||
if (llvm::Optional<std::string> MaybeArg = stripFloatCast(Result, Node))
|
||||
return *MaybeArg;
|
||||
|
||||
// Check for floats without fractional components.
|
||||
if (llvm::Optional<std::string> MaybeArg =
|
||||
stripFloatLiteralFraction(Result, Node))
|
||||
return *MaybeArg;
|
||||
|
||||
// We couldn't simplify any further, so return the argument text.
|
||||
return tooling::fixit::getText(Node, *Result.Context).str();
|
||||
}
|
||||
|
||||
llvm::Optional<DurationScale> getScaleForInverse(llvm::StringRef Name) {
|
||||
static const llvm::StringMap<DurationScale> ScaleMap(
|
||||
{{"ToDoubleHours", DurationScale::Hours},
|
||||
{"ToInt64Hours", DurationScale::Hours},
|
||||
{"ToDoubleMinutes", DurationScale::Minutes},
|
||||
{"ToInt64Minutes", DurationScale::Minutes},
|
||||
{"ToDoubleSeconds", DurationScale::Seconds},
|
||||
{"ToInt64Seconds", DurationScale::Seconds},
|
||||
{"ToDoubleMilliseconds", DurationScale::Milliseconds},
|
||||
{"ToInt64Milliseconds", DurationScale::Milliseconds},
|
||||
{"ToDoubleMicroseconds", DurationScale::Microseconds},
|
||||
{"ToInt64Microseconds", DurationScale::Microseconds},
|
||||
{"ToDoubleNanoseconds", DurationScale::Nanoseconds},
|
||||
{"ToInt64Nanoseconds", DurationScale::Nanoseconds}});
|
||||
|
||||
auto ScaleIter = ScaleMap.find(std::string(Name));
|
||||
if (ScaleIter == ScaleMap.end())
|
||||
return llvm::None;
|
||||
|
||||
return ScaleIter->second;
|
||||
}
|
||||
|
||||
std::string rewriteExprFromNumberToDuration(
|
||||
const ast_matchers::MatchFinder::MatchResult &Result, DurationScale Scale,
|
||||
const Expr *Node) {
|
||||
const Expr &RootNode = *Node->IgnoreParenImpCasts();
|
||||
|
||||
// First check to see if we can undo a complimentary function call.
|
||||
if (llvm::Optional<std::string> MaybeRewrite =
|
||||
rewriteInverseDurationCall(Result, Scale, RootNode))
|
||||
return *MaybeRewrite;
|
||||
|
||||
if (IsLiteralZero(Result, RootNode))
|
||||
return std::string("absl::ZeroDuration()");
|
||||
|
||||
return (llvm::Twine(getFactoryForScale(Scale)) + "(" +
|
||||
simplifyDurationFactoryArg(Result, RootNode) + ")")
|
||||
.str();
|
||||
}
|
||||
|
||||
} // namespace abseil
|
||||
} // namespace tidy
|
||||
} // namespace clang
|
||||
@@ -1,102 +0,0 @@
|
||||
//===--- DurationRewriter.h - clang-tidy ------------------------*- C++ -*-===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ABSEIL_DURATIONREWRITER_H
|
||||
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ABSEIL_DURATIONREWRITER_H
|
||||
|
||||
#include "clang/ASTMatchers/ASTMatchFinder.h"
|
||||
#include "clang/ASTMatchers/ASTMatchers.h"
|
||||
#include <cinttypes>
|
||||
|
||||
namespace clang {
|
||||
namespace tidy {
|
||||
namespace abseil {
|
||||
|
||||
/// Duration factory and conversion scales
|
||||
enum class DurationScale : std::uint8_t {
|
||||
Hours = 0,
|
||||
Minutes,
|
||||
Seconds,
|
||||
Milliseconds,
|
||||
Microseconds,
|
||||
Nanoseconds,
|
||||
};
|
||||
|
||||
/// Given a `Scale`, return the appropriate factory function call for
|
||||
/// constructing a `Duration` for that scale.
|
||||
llvm::StringRef getFactoryForScale(DurationScale Scale);
|
||||
|
||||
// Determine if `Node` represents a literal floating point or integral zero.
|
||||
bool IsLiteralZero(const ast_matchers::MatchFinder::MatchResult &Result,
|
||||
const Expr &Node);
|
||||
|
||||
/// Possibly strip a floating point cast expression.
|
||||
///
|
||||
/// If `Node` represents an explicit cast to a floating point type, return
|
||||
/// the textual context of the cast argument, otherwise `None`.
|
||||
llvm::Optional<std::string>
|
||||
stripFloatCast(const ast_matchers::MatchFinder::MatchResult &Result,
|
||||
const Expr &Node);
|
||||
|
||||
/// Possibly remove the fractional part of a floating point literal.
|
||||
///
|
||||
/// If `Node` represents a floating point literal with a zero fractional part,
|
||||
/// return the textual context of the integral part, otherwise `None`.
|
||||
llvm::Optional<std::string>
|
||||
stripFloatLiteralFraction(const ast_matchers::MatchFinder::MatchResult &Result,
|
||||
const Expr &Node);
|
||||
|
||||
/// Possibly further simplify a duration factory function's argument, without
|
||||
/// changing the scale of the factory function. Return that simplification or
|
||||
/// the text of the argument if no simplification is possible.
|
||||
std::string
|
||||
simplifyDurationFactoryArg(const ast_matchers::MatchFinder::MatchResult &Result,
|
||||
const Expr &Node);
|
||||
|
||||
/// Given the name of an inverse Duration function (e.g., `ToDoubleSeconds`),
|
||||
/// return its `DurationScale`, or `None` if a match is not found.
|
||||
llvm::Optional<DurationScale> getScaleForInverse(llvm::StringRef Name);
|
||||
|
||||
/// Given a `Scale` return the fully qualified inverse functions for it.
|
||||
/// The first returned value is the inverse for `double`, and the second
|
||||
/// returned value is the inverse for `int64`.
|
||||
const std::pair<llvm::StringRef, llvm::StringRef> &
|
||||
getInverseForScale(DurationScale Scale);
|
||||
|
||||
/// Assuming `Node` has type `double` or `int` representing a time interval of
|
||||
/// `Scale`, return the expression to make it a suitable `Duration`.
|
||||
std::string rewriteExprFromNumberToDuration(
|
||||
const ast_matchers::MatchFinder::MatchResult &Result, DurationScale Scale,
|
||||
const Expr *Node);
|
||||
|
||||
AST_MATCHER_FUNCTION(ast_matchers::internal::Matcher<FunctionDecl>,
|
||||
DurationConversionFunction) {
|
||||
using namespace clang::ast_matchers;
|
||||
return functionDecl(
|
||||
hasAnyName("::absl::ToDoubleHours", "::absl::ToDoubleMinutes",
|
||||
"::absl::ToDoubleSeconds", "::absl::ToDoubleMilliseconds",
|
||||
"::absl::ToDoubleMicroseconds", "::absl::ToDoubleNanoseconds",
|
||||
"::absl::ToInt64Hours", "::absl::ToInt64Minutes",
|
||||
"::absl::ToInt64Seconds", "::absl::ToInt64Milliseconds",
|
||||
"::absl::ToInt64Microseconds", "::absl::ToInt64Nanoseconds"));
|
||||
}
|
||||
|
||||
AST_MATCHER_FUNCTION(ast_matchers::internal::Matcher<FunctionDecl>,
|
||||
DurationFactoryFunction) {
|
||||
using namespace clang::ast_matchers;
|
||||
return functionDecl(hasAnyName("::absl::Nanoseconds", "::absl::Microseconds",
|
||||
"::absl::Milliseconds", "::absl::Seconds",
|
||||
"::absl::Minutes", "::absl::Hours"));
|
||||
}
|
||||
|
||||
} // namespace abseil
|
||||
} // namespace tidy
|
||||
} // namespace clang
|
||||
|
||||
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ABSEIL_DURATIONCOMPARISONCHECK_H
|
||||
@@ -1,61 +0,0 @@
|
||||
//===--- DurationSubtractionCheck.cpp - clang-tidy ------------------------===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "DurationSubtractionCheck.h"
|
||||
#include "DurationRewriter.h"
|
||||
#include "clang/AST/ASTContext.h"
|
||||
#include "clang/ASTMatchers/ASTMatchFinder.h"
|
||||
#include "clang/Tooling/FixIt.h"
|
||||
|
||||
using namespace clang::ast_matchers;
|
||||
|
||||
namespace clang {
|
||||
namespace tidy {
|
||||
namespace abseil {
|
||||
|
||||
void DurationSubtractionCheck::registerMatchers(MatchFinder *Finder) {
|
||||
Finder->addMatcher(
|
||||
binaryOperator(
|
||||
hasOperatorName("-"),
|
||||
hasLHS(callExpr(callee(functionDecl(DurationConversionFunction())
|
||||
.bind("function_decl")),
|
||||
hasArgument(0, expr().bind("lhs_arg")))))
|
||||
.bind("binop"),
|
||||
this);
|
||||
}
|
||||
|
||||
void DurationSubtractionCheck::check(const MatchFinder::MatchResult &Result) {
|
||||
const auto *Binop = Result.Nodes.getNodeAs<BinaryOperator>("binop");
|
||||
const auto *FuncDecl = Result.Nodes.getNodeAs<FunctionDecl>("function_decl");
|
||||
|
||||
// Don't try to replace things inside of macro definitions.
|
||||
if (Binop->getExprLoc().isMacroID() || Binop->getExprLoc().isInvalid())
|
||||
return;
|
||||
|
||||
llvm::Optional<DurationScale> Scale = getScaleForInverse(FuncDecl->getName());
|
||||
if (!Scale)
|
||||
return;
|
||||
|
||||
std::string RhsReplacement =
|
||||
rewriteExprFromNumberToDuration(Result, *Scale, Binop->getRHS());
|
||||
|
||||
const Expr *LhsArg = Result.Nodes.getNodeAs<Expr>("lhs_arg");
|
||||
|
||||
diag(Binop->getBeginLoc(), "perform subtraction in the duration domain")
|
||||
<< FixItHint::CreateReplacement(
|
||||
Binop->getSourceRange(),
|
||||
(llvm::Twine("absl::") + FuncDecl->getName() + "(" +
|
||||
tooling::fixit::getText(*LhsArg, *Result.Context) + " - " +
|
||||
RhsReplacement + ")")
|
||||
.str());
|
||||
}
|
||||
|
||||
} // namespace abseil
|
||||
} // namespace tidy
|
||||
} // namespace clang
|
||||
@@ -1,36 +0,0 @@
|
||||
//===--- DurationSubtractionCheck.h - clang-tidy ----------------*- C++ -*-===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ABSEIL_DURATIONSUBTRACTIONCHECK_H
|
||||
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ABSEIL_DURATIONSUBTRACTIONCHECK_H
|
||||
|
||||
#include "../ClangTidy.h"
|
||||
|
||||
namespace clang {
|
||||
namespace tidy {
|
||||
namespace abseil {
|
||||
|
||||
/// Checks for cases where subtraction should be performed in the
|
||||
/// `absl::Duration` domain.
|
||||
///
|
||||
/// For the user-facing documentation see:
|
||||
/// http://clang.llvm.org/extra/clang-tidy/checks/abseil-duration-subtraction.html
|
||||
class DurationSubtractionCheck : public ClangTidyCheck {
|
||||
public:
|
||||
DurationSubtractionCheck(StringRef Name, ClangTidyContext *Context)
|
||||
: ClangTidyCheck(Name, Context) {}
|
||||
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
|
||||
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
|
||||
};
|
||||
|
||||
} // namespace abseil
|
||||
} // namespace tidy
|
||||
} // namespace clang
|
||||
|
||||
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ABSEIL_DURATIONSUBTRACTIONCHECK_H
|
||||
@@ -1,131 +0,0 @@
|
||||
//===--- FasterStrsplitDelimiterCheck.cpp - clang-tidy---------------------===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "FasterStrsplitDelimiterCheck.h"
|
||||
#include "clang/AST/ASTContext.h"
|
||||
#include "clang/ASTMatchers/ASTMatchFinder.h"
|
||||
|
||||
using namespace clang::ast_matchers;
|
||||
|
||||
namespace clang {
|
||||
namespace tidy {
|
||||
namespace abseil {
|
||||
|
||||
namespace {
|
||||
|
||||
AST_MATCHER(StringLiteral, lengthIsOne) { return Node.getLength() == 1; }
|
||||
|
||||
::internal::Matcher<Expr>
|
||||
constructExprWithArg(llvm::StringRef ClassName,
|
||||
const ::internal::Matcher<Expr> &Arg) {
|
||||
auto ConstrExpr = cxxConstructExpr(hasType(recordDecl(hasName(ClassName))),
|
||||
hasArgument(0, ignoringParenCasts(Arg)));
|
||||
|
||||
return anyOf(ConstrExpr, cxxBindTemporaryExpr(has(ConstrExpr)));
|
||||
}
|
||||
|
||||
::internal::Matcher<Expr>
|
||||
copyConstructExprWithArg(llvm::StringRef ClassName,
|
||||
const ::internal::Matcher<Expr> &Arg) {
|
||||
return constructExprWithArg(ClassName, constructExprWithArg(ClassName, Arg));
|
||||
}
|
||||
|
||||
llvm::Optional<std::string> makeCharacterLiteral(const StringLiteral *Literal) {
|
||||
std::string Result;
|
||||
{
|
||||
llvm::raw_string_ostream Stream(Result);
|
||||
Literal->outputString(Stream);
|
||||
}
|
||||
|
||||
// Special case: If the string contains a single quote, we just need to return
|
||||
// a character of the single quote. This is a special case because we need to
|
||||
// escape it in the character literal.
|
||||
if (Result == R"("'")")
|
||||
return std::string(R"('\'')");
|
||||
|
||||
assert(Result.size() == 3 || (Result.size() == 4 && Result.substr(0, 2) == "\"\\"));
|
||||
|
||||
// Now replace the " with '.
|
||||
auto Pos = Result.find_first_of('"');
|
||||
if (Pos == Result.npos)
|
||||
return llvm::None;
|
||||
Result[Pos] = '\'';
|
||||
Pos = Result.find_last_of('"');
|
||||
if (Pos == Result.npos)
|
||||
return llvm::None;
|
||||
Result[Pos] = '\'';
|
||||
return Result;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
void FasterStrsplitDelimiterCheck::registerMatchers(MatchFinder *Finder) {
|
||||
if (!getLangOpts().CPlusPlus)
|
||||
return;
|
||||
|
||||
// Binds to one character string literals.
|
||||
const auto SingleChar =
|
||||
expr(ignoringParenCasts(stringLiteral(lengthIsOne()).bind("Literal")));
|
||||
|
||||
// Binds to a string_view (either absl or std) that was passed by value and
|
||||
// contructed from string literal.
|
||||
auto StringViewArg =
|
||||
copyConstructExprWithArg("::absl::string_view", SingleChar);
|
||||
|
||||
auto ByAnyCharArg =
|
||||
expr(copyConstructExprWithArg("::absl::ByAnyChar", StringViewArg))
|
||||
.bind("ByAnyChar");
|
||||
|
||||
// Find uses of absl::StrSplit(..., "x") and absl::StrSplit(...,
|
||||
// absl::ByAnyChar("x")) to transform them into absl::StrSplit(..., 'x').
|
||||
Finder->addMatcher(callExpr(callee(functionDecl(hasName("::absl::StrSplit"))),
|
||||
hasArgument(1, anyOf(ByAnyCharArg, SingleChar)),
|
||||
unless(isInTemplateInstantiation()))
|
||||
.bind("StrSplit"),
|
||||
this);
|
||||
|
||||
// Find uses of absl::MaxSplits("x", N) and
|
||||
// absl::MaxSplits(absl::ByAnyChar("x"), N) to transform them into
|
||||
// absl::MaxSplits('x', N).
|
||||
Finder->addMatcher(
|
||||
callExpr(
|
||||
callee(functionDecl(hasName("::absl::MaxSplits"))),
|
||||
hasArgument(0, anyOf(ByAnyCharArg, ignoringParenCasts(SingleChar))),
|
||||
unless(isInTemplateInstantiation())),
|
||||
this);
|
||||
}
|
||||
|
||||
void FasterStrsplitDelimiterCheck::check(
|
||||
const MatchFinder::MatchResult &Result) {
|
||||
const auto *Literal = Result.Nodes.getNodeAs<StringLiteral>("Literal");
|
||||
|
||||
if (Literal->getBeginLoc().isMacroID() || Literal->getEndLoc().isMacroID())
|
||||
return;
|
||||
|
||||
llvm::Optional<std::string> Replacement = makeCharacterLiteral(Literal);
|
||||
if (!Replacement)
|
||||
return;
|
||||
SourceRange Range = Literal->getSourceRange();
|
||||
|
||||
if (const auto *ByAnyChar = Result.Nodes.getNodeAs<Expr>("ByAnyChar"))
|
||||
Range = ByAnyChar->getSourceRange();
|
||||
|
||||
diag(
|
||||
Literal->getBeginLoc(),
|
||||
"%select{absl::StrSplit()|absl::MaxSplits()}0 called with a string "
|
||||
"literal "
|
||||
"consisting of a single character; consider using the character overload")
|
||||
<< (Result.Nodes.getNodeAs<CallExpr>("StrSplit") ? 0 : 1)
|
||||
<< FixItHint::CreateReplacement(CharSourceRange::getTokenRange(Range),
|
||||
*Replacement);
|
||||
}
|
||||
|
||||
} // namespace abseil
|
||||
} // namespace tidy
|
||||
} // namespace clang
|
||||
@@ -1,36 +0,0 @@
|
||||
//===--- FasterStrsplitDelimiterCheck.h - clang-tidy-------------*- C++ -*-===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ABSEIL_FASTERSTRSPLITDELIMITERCHECK_H
|
||||
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ABSEIL_FASTERSTRSPLITDELIMITERCHECK_H
|
||||
|
||||
#include "../ClangTidy.h"
|
||||
|
||||
namespace clang {
|
||||
namespace tidy {
|
||||
namespace abseil {
|
||||
|
||||
/// Finds instances of absl::StrSplit() or absl::MaxSplits() where the delimiter
|
||||
/// is a single character string literal and replaces it with a character.
|
||||
///
|
||||
/// For the user-facing documentation see:
|
||||
/// http://clang.llvm.org/extra/clang-tidy/checks/abseil-faster-strsplit-delimiter.html
|
||||
class FasterStrsplitDelimiterCheck : public ClangTidyCheck {
|
||||
public:
|
||||
FasterStrsplitDelimiterCheck(StringRef Name, ClangTidyContext *Context)
|
||||
: ClangTidyCheck(Name, Context) {}
|
||||
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
|
||||
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
|
||||
};
|
||||
|
||||
} // namespace abseil
|
||||
} // namespace tidy
|
||||
} // namespace clang
|
||||
|
||||
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ABSEIL_FASTERSTRSPLITDELIMITERCHECK_H
|
||||
@@ -1,48 +0,0 @@
|
||||
//===--- NoInternalDependenciesCheck.cpp - clang-tidy----------------------===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "NoInternalDependenciesCheck.h"
|
||||
#include "AbseilMatcher.h"
|
||||
#include "clang/AST/ASTContext.h"
|
||||
#include "clang/ASTMatchers/ASTMatchFinder.h"
|
||||
|
||||
using namespace clang::ast_matchers;
|
||||
|
||||
namespace clang {
|
||||
namespace tidy {
|
||||
namespace abseil {
|
||||
|
||||
void NoInternalDependenciesCheck::registerMatchers(MatchFinder *Finder) {
|
||||
if (!getLangOpts().CPlusPlus)
|
||||
return;
|
||||
|
||||
// TODO: refactor matcher to be configurable or just match on any internal
|
||||
// access from outside the enclosing namespace.
|
||||
|
||||
Finder->addMatcher(
|
||||
nestedNameSpecifierLoc(loc(specifiesNamespace(namespaceDecl(
|
||||
matchesName("internal"),
|
||||
hasParent(namespaceDecl(hasName("absl")))))),
|
||||
unless(isInAbseilFile()))
|
||||
.bind("InternalDep"),
|
||||
this);
|
||||
}
|
||||
|
||||
void NoInternalDependenciesCheck::check(const MatchFinder::MatchResult &Result) {
|
||||
const auto *InternalDependency =
|
||||
Result.Nodes.getNodeAs<NestedNameSpecifierLoc>("InternalDep");
|
||||
|
||||
diag(InternalDependency->getBeginLoc(),
|
||||
"do not reference any 'internal' namespaces; those implementation "
|
||||
"details are reserved to Abseil");
|
||||
}
|
||||
|
||||
} // namespace abseil
|
||||
} // namespace tidy
|
||||
} // namespace clang
|
||||
@@ -1,36 +0,0 @@
|
||||
//===--- NoInternalDependenciesCheck.h - clang-tidy----------------------*- C++ -*-===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ABSEIL_NOINTERNALDEPSCHECK_H
|
||||
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ABSEIL_NOINTERNALDEPSCHECK_H
|
||||
|
||||
#include "../ClangTidy.h"
|
||||
|
||||
namespace clang {
|
||||
namespace tidy {
|
||||
namespace abseil {
|
||||
|
||||
/// Finds instances where the user depends on internal details and warns them
|
||||
/// against doing so.
|
||||
///
|
||||
/// For the user-facing documentation see:
|
||||
/// http://clang.llvm.org/extra/clang-tidy/checks/abseil-no-internal-dependencies.html
|
||||
class NoInternalDependenciesCheck : public ClangTidyCheck {
|
||||
public:
|
||||
NoInternalDependenciesCheck(StringRef Name, ClangTidyContext *Context)
|
||||
: ClangTidyCheck(Name, Context) {}
|
||||
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
|
||||
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
|
||||
};
|
||||
|
||||
} // namespace abseil
|
||||
} // namespace tidy
|
||||
} // namespace clang
|
||||
|
||||
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ABSEIL_NOINTERNALDEPSCHECK_H
|
||||
@@ -1,42 +0,0 @@
|
||||
//===--- NoNamespaceCheck.cpp - clang-tidy---------------------------------===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "NoNamespaceCheck.h"
|
||||
#include "AbseilMatcher.h"
|
||||
#include "clang/AST/ASTContext.h"
|
||||
#include "clang/ASTMatchers/ASTMatchFinder.h"
|
||||
|
||||
using namespace clang::ast_matchers;
|
||||
|
||||
namespace clang {
|
||||
namespace tidy {
|
||||
namespace abseil {
|
||||
|
||||
void NoNamespaceCheck::registerMatchers(MatchFinder *Finder) {
|
||||
if (!getLangOpts().CPlusPlus)
|
||||
return;
|
||||
|
||||
Finder->addMatcher(
|
||||
namespaceDecl(hasName("::absl"), unless(isInAbseilFile()))
|
||||
.bind("abslNamespace"),
|
||||
this);
|
||||
}
|
||||
|
||||
void NoNamespaceCheck::check(const MatchFinder::MatchResult &Result) {
|
||||
const auto *abslNamespaceDecl =
|
||||
Result.Nodes.getNodeAs<NamespaceDecl>("abslNamespace");
|
||||
|
||||
diag(abslNamespaceDecl->getLocation(),
|
||||
"namespace 'absl' is reserved for implementation of the Abseil library "
|
||||
"and should not be opened in user code");
|
||||
}
|
||||
|
||||
} // namespace abseil
|
||||
} // namespace tidy
|
||||
} // namespace clang
|
||||
@@ -1,36 +0,0 @@
|
||||
//===--- NoNamespaceCheck.h - clang-tidy-------------------------*- C++ -*-===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ABSEIL_NONAMESPACECHECK_H
|
||||
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ABSEIL_NONAMESPACECHECK_H
|
||||
|
||||
#include "../ClangTidy.h"
|
||||
|
||||
namespace clang {
|
||||
namespace tidy {
|
||||
namespace abseil {
|
||||
|
||||
/// This check ensures users don't open namespace absl, as that violates
|
||||
/// Abseil's compatibility guidelines.
|
||||
///
|
||||
/// For the user-facing documentation see:
|
||||
/// http://clang.llvm.org/extra/clang-tidy/checks/abseil-no-namespace.html
|
||||
class NoNamespaceCheck : public ClangTidyCheck {
|
||||
public:
|
||||
NoNamespaceCheck(StringRef Name, ClangTidyContext *Context)
|
||||
: ClangTidyCheck(Name, Context) {}
|
||||
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
|
||||
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
|
||||
};
|
||||
|
||||
} // namespace abseil
|
||||
} // namespace tidy
|
||||
} // namespace clang
|
||||
|
||||
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ABSEIL_NONAMESPACECHECK_H
|
||||
@@ -1,140 +0,0 @@
|
||||
//===--- RedundantStrcatCallsCheck.cpp - clang-tidy------------------------===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "RedundantStrcatCallsCheck.h"
|
||||
#include "clang/AST/ASTContext.h"
|
||||
#include "clang/ASTMatchers/ASTMatchFinder.h"
|
||||
|
||||
using namespace clang::ast_matchers;
|
||||
|
||||
namespace clang {
|
||||
namespace tidy {
|
||||
namespace abseil {
|
||||
|
||||
// TODO: Features to add to the check:
|
||||
// - Make it work if num_args > 26.
|
||||
// - Remove empty literal string arguments.
|
||||
// - Collapse consecutive literal string arguments into one (remove the ,).
|
||||
// - Replace StrCat(a + b) -> StrCat(a, b) if a or b are strings.
|
||||
// - Make it work in macros if the outer and inner StrCats are both in the
|
||||
// argument.
|
||||
|
||||
void RedundantStrcatCallsCheck::registerMatchers(MatchFinder* Finder) {
|
||||
if (!getLangOpts().CPlusPlus)
|
||||
return;
|
||||
const auto CallToStrcat =
|
||||
callExpr(callee(functionDecl(hasName("::absl::StrCat"))));
|
||||
const auto CallToStrappend =
|
||||
callExpr(callee(functionDecl(hasName("::absl::StrAppend"))));
|
||||
// Do not match StrCat() calls that are descendants of other StrCat calls.
|
||||
// Those are handled on the ancestor call.
|
||||
const auto CallToEither = callExpr(
|
||||
callee(functionDecl(hasAnyName("::absl::StrCat", "::absl::StrAppend"))));
|
||||
Finder->addMatcher(
|
||||
callExpr(CallToStrcat, unless(hasAncestor(CallToEither))).bind("StrCat"),
|
||||
this);
|
||||
Finder->addMatcher(CallToStrappend.bind("StrAppend"), this);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
struct StrCatCheckResult {
|
||||
int NumCalls = 0;
|
||||
std::vector<FixItHint> Hints;
|
||||
};
|
||||
|
||||
void RemoveCallLeaveArgs(const CallExpr* Call, StrCatCheckResult* CheckResult) {
|
||||
// Remove 'Foo('
|
||||
CheckResult->Hints.push_back(
|
||||
FixItHint::CreateRemoval(CharSourceRange::getCharRange(
|
||||
Call->getBeginLoc(), Call->getArg(0)->getBeginLoc())));
|
||||
// Remove the ')'
|
||||
CheckResult->Hints.push_back(
|
||||
FixItHint::CreateRemoval(CharSourceRange::getCharRange(
|
||||
Call->getRParenLoc(), Call->getEndLoc().getLocWithOffset(1))));
|
||||
}
|
||||
|
||||
const clang::CallExpr* ProcessArgument(const Expr* Arg,
|
||||
const MatchFinder::MatchResult& Result,
|
||||
StrCatCheckResult* CheckResult) {
|
||||
const auto IsAlphanum = hasDeclaration(cxxMethodDecl(hasName("AlphaNum")));
|
||||
static const auto* const Strcat = new auto(hasName("::absl::StrCat"));
|
||||
const auto IsStrcat = cxxBindTemporaryExpr(
|
||||
has(callExpr(callee(functionDecl(*Strcat))).bind("StrCat")));
|
||||
if (const auto* SubStrcatCall = selectFirst<const CallExpr>(
|
||||
"StrCat",
|
||||
match(stmt(anyOf(
|
||||
cxxConstructExpr(IsAlphanum, hasArgument(0, IsStrcat)),
|
||||
IsStrcat)),
|
||||
*Arg->IgnoreParenImpCasts(), *Result.Context))) {
|
||||
RemoveCallLeaveArgs(SubStrcatCall, CheckResult);
|
||||
return SubStrcatCall;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
StrCatCheckResult ProcessCall(const CallExpr* RootCall, bool IsAppend,
|
||||
const MatchFinder::MatchResult& Result) {
|
||||
StrCatCheckResult CheckResult;
|
||||
std::deque<const CallExpr*> CallsToProcess = {RootCall};
|
||||
|
||||
while (!CallsToProcess.empty()) {
|
||||
++CheckResult.NumCalls;
|
||||
|
||||
const CallExpr* CallExpr = CallsToProcess.front();
|
||||
CallsToProcess.pop_front();
|
||||
|
||||
int StartArg = CallExpr == RootCall && IsAppend;
|
||||
for (const auto *Arg : CallExpr->arguments()) {
|
||||
if (StartArg-- > 0)
|
||||
continue;
|
||||
if (const clang::CallExpr* Sub =
|
||||
ProcessArgument(Arg, Result, &CheckResult)) {
|
||||
CallsToProcess.push_back(Sub);
|
||||
}
|
||||
}
|
||||
}
|
||||
return CheckResult;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void RedundantStrcatCallsCheck::check(const MatchFinder::MatchResult& Result) {
|
||||
bool IsAppend;
|
||||
|
||||
const CallExpr* RootCall;
|
||||
if ((RootCall = Result.Nodes.getNodeAs<CallExpr>("StrCat")))
|
||||
IsAppend = false;
|
||||
else if ((RootCall = Result.Nodes.getNodeAs<CallExpr>("StrAppend")))
|
||||
IsAppend = true;
|
||||
else
|
||||
return;
|
||||
|
||||
if (RootCall->getBeginLoc().isMacroID()) {
|
||||
// Ignore calls within macros.
|
||||
// In many cases the outer StrCat part of the macro and the inner StrCat is
|
||||
// a macro argument. Removing the inner StrCat() converts one macro
|
||||
// argument into many.
|
||||
return;
|
||||
}
|
||||
|
||||
const StrCatCheckResult CheckResult =
|
||||
ProcessCall(RootCall, IsAppend, Result);
|
||||
if (CheckResult.NumCalls == 1) {
|
||||
// Just one call, so nothing to fix.
|
||||
return;
|
||||
}
|
||||
|
||||
diag(RootCall->getBeginLoc(),
|
||||
"multiple calls to 'absl::StrCat' can be flattened into a single call")
|
||||
<< CheckResult.Hints;
|
||||
}
|
||||
|
||||
} // namespace abseil
|
||||
} // namespace tidy
|
||||
} // namespace clang
|
||||
@@ -1,39 +0,0 @@
|
||||
//===--- RedundantStrcatCallsCheck.h - clang-tidy----------------*- C++ -*-===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ABSEIL_REDUNDANTSTRCATCALLSCHECK_H
|
||||
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ABSEIL_REDUNDANTSTRCATCALLSCHECK_H
|
||||
|
||||
#include "../ClangTidy.h"
|
||||
|
||||
namespace clang {
|
||||
namespace tidy {
|
||||
namespace abseil {
|
||||
|
||||
/// Flags redundant calls to absl::StrCat when the result is being passed to
|
||||
/// another call of absl::StrCat/absl::StrAppend. Also suggests a fix to
|
||||
/// collapse the calls.
|
||||
/// Example:
|
||||
/// StrCat(1, StrCat(2, 3)) ==> StrCat(1, 2, 3)
|
||||
///
|
||||
/// For the user-facing documentation see:
|
||||
/// http://clang.llvm.org/extra/clang-tidy/checks/abseil-redundant-strcat-calls.html
|
||||
class RedundantStrcatCallsCheck : public ClangTidyCheck {
|
||||
public:
|
||||
RedundantStrcatCallsCheck(StringRef Name, ClangTidyContext *Context)
|
||||
: ClangTidyCheck(Name, Context) {}
|
||||
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
|
||||
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
|
||||
};
|
||||
|
||||
} // namespace abseil
|
||||
} // namespace tidy
|
||||
} // namespace clang
|
||||
|
||||
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ABSEIL_REDUNDANTSTRCATCALLSCHECK_H
|
||||
@@ -1,102 +0,0 @@
|
||||
//===--- StrCatAppendCheck.cpp - clang-tidy--------------------------------===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "StrCatAppendCheck.h"
|
||||
#include "clang/AST/ASTContext.h"
|
||||
#include "clang/ASTMatchers/ASTMatchFinder.h"
|
||||
|
||||
using namespace clang::ast_matchers;
|
||||
|
||||
namespace clang {
|
||||
namespace tidy {
|
||||
namespace abseil {
|
||||
|
||||
namespace {
|
||||
// Skips any combination of temporary materialization, temporary binding and
|
||||
// implicit casting.
|
||||
AST_MATCHER_P(Stmt, IgnoringTemporaries, ast_matchers::internal::Matcher<Stmt>,
|
||||
InnerMatcher) {
|
||||
const Stmt *E = &Node;
|
||||
while (true) {
|
||||
if (const auto *MTE = dyn_cast<MaterializeTemporaryExpr>(E))
|
||||
E = MTE->getTemporary();
|
||||
if (const auto *BTE = dyn_cast<CXXBindTemporaryExpr>(E))
|
||||
E = BTE->getSubExpr();
|
||||
if (const auto *ICE = dyn_cast<ImplicitCastExpr>(E))
|
||||
E = ICE->getSubExpr();
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
return InnerMatcher.matches(*E, Finder, Builder);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// TODO: str += StrCat(...)
|
||||
// str.append(StrCat(...))
|
||||
|
||||
void StrCatAppendCheck::registerMatchers(MatchFinder *Finder) {
|
||||
if (!getLangOpts().CPlusPlus)
|
||||
return;
|
||||
const auto StrCat = functionDecl(hasName("::absl::StrCat"));
|
||||
// The arguments of absl::StrCat are implicitly converted to AlphaNum. This
|
||||
// matches to the arguments because of that behavior.
|
||||
const auto AlphaNum = IgnoringTemporaries(cxxConstructExpr(
|
||||
argumentCountIs(1), hasType(cxxRecordDecl(hasName("::absl::AlphaNum"))),
|
||||
hasArgument(0, ignoringImpCasts(declRefExpr(to(equalsBoundNode("LHS")),
|
||||
expr().bind("Arg0"))))));
|
||||
|
||||
const auto HasAnotherReferenceToLhs =
|
||||
callExpr(hasAnyArgument(expr(hasDescendant(declRefExpr(
|
||||
to(equalsBoundNode("LHS")), unless(equalsBoundNode("Arg0")))))));
|
||||
|
||||
// Now look for calls to operator= with an object on the LHS and a call to
|
||||
// StrCat on the RHS. The first argument of the StrCat call should be the same
|
||||
// as the LHS. Ignore calls from template instantiations.
|
||||
Finder->addMatcher(
|
||||
cxxOperatorCallExpr(
|
||||
unless(isInTemplateInstantiation()), hasOverloadedOperatorName("="),
|
||||
hasArgument(0, declRefExpr(to(decl().bind("LHS")))),
|
||||
hasArgument(1, IgnoringTemporaries(
|
||||
callExpr(callee(StrCat), hasArgument(0, AlphaNum),
|
||||
unless(HasAnotherReferenceToLhs))
|
||||
.bind("Call"))))
|
||||
.bind("Op"),
|
||||
this);
|
||||
}
|
||||
|
||||
void StrCatAppendCheck::check(const MatchFinder::MatchResult &Result) {
|
||||
const auto *Op = Result.Nodes.getNodeAs<CXXOperatorCallExpr>("Op");
|
||||
const auto *Call = Result.Nodes.getNodeAs<CallExpr>("Call");
|
||||
assert(Op != nullptr && Call != nullptr && "Matcher does not work as expected");
|
||||
|
||||
// Handles the case 'x = absl::StrCat(x)', which has no effect.
|
||||
if (Call->getNumArgs() == 1) {
|
||||
diag(Op->getBeginLoc(), "call to 'absl::StrCat' has no effect");
|
||||
return;
|
||||
}
|
||||
|
||||
// Emit a warning and emit fixits to go from
|
||||
// x = absl::StrCat(x, ...)
|
||||
// to
|
||||
// absl::StrAppend(&x, ...)
|
||||
diag(Op->getBeginLoc(),
|
||||
"call 'absl::StrAppend' instead of 'absl::StrCat' when appending to a "
|
||||
"string to avoid a performance penalty")
|
||||
<< FixItHint::CreateReplacement(
|
||||
CharSourceRange::getTokenRange(Op->getBeginLoc(),
|
||||
Call->getCallee()->getEndLoc()),
|
||||
"absl::StrAppend")
|
||||
<< FixItHint::CreateInsertion(Call->getArg(0)->getBeginLoc(), "&");
|
||||
}
|
||||
|
||||
} // namespace abseil
|
||||
} // namespace tidy
|
||||
} // namespace clang
|
||||
@@ -1,36 +0,0 @@
|
||||
//===--- StrCatAppendCheck.h - clang-tidy------------------------*- C++ -*-===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ABSEIL_STRCATAPPENDCHECK_H
|
||||
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ABSEIL_STRCATAPPENDCHECK_H
|
||||
|
||||
#include "../ClangTidy.h"
|
||||
|
||||
namespace clang {
|
||||
namespace tidy {
|
||||
namespace abseil {
|
||||
|
||||
/// Flags uses of absl::StrCat to append to a string. Suggests absl::StrAppend
|
||||
/// should be used instead.
|
||||
///
|
||||
/// For the user-facing documentation see:
|
||||
/// http://clang.llvm.org/extra/clang-tidy/checks/abseil-str-cat-append.html
|
||||
class StrCatAppendCheck : public ClangTidyCheck {
|
||||
public:
|
||||
StrCatAppendCheck(StringRef Name, ClangTidyContext *Context)
|
||||
: ClangTidyCheck(Name, Context) {}
|
||||
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
|
||||
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
|
||||
};
|
||||
|
||||
} // namespace abseil
|
||||
} // namespace tidy
|
||||
} // namespace clang
|
||||
|
||||
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ABSEIL_STRCATAPPENDCHECK_H
|
||||
@@ -71,7 +71,7 @@ void StringFindStartswithCheck::check(const MatchFinder::MatchResult &Result) {
|
||||
->getImplicitObjectArgument();
|
||||
assert(Haystack != nullptr);
|
||||
|
||||
if (ComparisonExpr->getBeginLoc().isMacroID())
|
||||
if (ComparisonExpr->getLocStart().isMacroID())
|
||||
return;
|
||||
|
||||
// Get the source code blocks (as characters) for both the string object
|
||||
@@ -94,7 +94,7 @@ void StringFindStartswithCheck::check(const MatchFinder::MatchResult &Result) {
|
||||
|
||||
// Create the warning message and a FixIt hint replacing the original expr.
|
||||
auto Diagnostic =
|
||||
diag(ComparisonExpr->getBeginLoc(),
|
||||
diag(ComparisonExpr->getLocStart(),
|
||||
(StringRef("use ") + StartswithStr + " instead of find() " +
|
||||
ComparisonExpr->getOpcodeStr() + " 0")
|
||||
.str());
|
||||
@@ -107,7 +107,7 @@ void StringFindStartswithCheck::check(const MatchFinder::MatchResult &Result) {
|
||||
// Create a preprocessor #include FixIt hint (CreateIncludeInsertion checks
|
||||
// whether this already exists).
|
||||
auto IncludeHint = IncludeInserter->CreateIncludeInsertion(
|
||||
Source.getFileID(ComparisonExpr->getBeginLoc()), AbseilStringsMatchHeader,
|
||||
Source.getFileID(ComparisonExpr->getLocStart()), AbseilStringsMatchHeader,
|
||||
false);
|
||||
if (IncludeHint) {
|
||||
Diagnostic << *IncludeHint;
|
||||
|
||||
@@ -1,156 +0,0 @@
|
||||
//===--- UpgradeDurationConversionsCheck.cpp - clang-tidy -----------------===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "UpgradeDurationConversionsCheck.h"
|
||||
#include "DurationRewriter.h"
|
||||
#include "clang/AST/ASTContext.h"
|
||||
#include "clang/ASTMatchers/ASTMatchFinder.h"
|
||||
|
||||
using namespace clang::ast_matchers;
|
||||
|
||||
namespace clang {
|
||||
namespace tidy {
|
||||
namespace abseil {
|
||||
|
||||
void UpgradeDurationConversionsCheck::registerMatchers(MatchFinder *Finder) {
|
||||
if (!getLangOpts().CPlusPlus)
|
||||
return;
|
||||
|
||||
// For the arithmetic calls, we match only the uses of the templated operators
|
||||
// where the template parameter is not a built-in type. This means the
|
||||
// instantiation makes use of an available user defined conversion to
|
||||
// `int64_t`.
|
||||
//
|
||||
// The implementation of these templates will be updated to fail SFINAE for
|
||||
// non-integral types. We match them to suggest an explicit cast.
|
||||
|
||||
// Match expressions like `a *= b` and `a /= b` where `a` has type
|
||||
// `absl::Duration` and `b` is not of a built-in type.
|
||||
Finder->addMatcher(
|
||||
cxxOperatorCallExpr(
|
||||
argumentCountIs(2),
|
||||
hasArgument(
|
||||
0, expr(hasType(cxxRecordDecl(hasName("::absl::Duration"))))),
|
||||
hasArgument(1, expr().bind("arg")),
|
||||
callee(functionDecl(
|
||||
hasParent(functionTemplateDecl()),
|
||||
unless(hasTemplateArgument(0, refersToType(builtinType()))),
|
||||
hasAnyName("operator*=", "operator/=")))),
|
||||
this);
|
||||
|
||||
// Match expressions like `a.operator*=(b)` and `a.operator/=(b)` where `a`
|
||||
// has type `absl::Duration` and `b` is not of a built-in type.
|
||||
Finder->addMatcher(
|
||||
cxxMemberCallExpr(
|
||||
callee(cxxMethodDecl(
|
||||
ofClass(cxxRecordDecl(hasName("::absl::Duration"))),
|
||||
hasParent(functionTemplateDecl()),
|
||||
unless(hasTemplateArgument(0, refersToType(builtinType()))),
|
||||
hasAnyName("operator*=", "operator/="))),
|
||||
argumentCountIs(1), hasArgument(0, expr().bind("arg"))),
|
||||
this);
|
||||
|
||||
// Match expressions like `a * b`, `a / b`, `operator*(a, b)`, and
|
||||
// `operator/(a, b)` where `a` has type `absl::Duration` and `b` is not of a
|
||||
// built-in type.
|
||||
Finder->addMatcher(
|
||||
callExpr(callee(functionDecl(
|
||||
hasParent(functionTemplateDecl()),
|
||||
unless(hasTemplateArgument(0, refersToType(builtinType()))),
|
||||
hasAnyName("::absl::operator*", "::absl::operator/"))),
|
||||
argumentCountIs(2),
|
||||
hasArgument(0, expr(hasType(
|
||||
cxxRecordDecl(hasName("::absl::Duration"))))),
|
||||
hasArgument(1, expr().bind("arg"))),
|
||||
this);
|
||||
|
||||
// Match expressions like `a * b` and `operator*(a, b)` where `a` is not of a
|
||||
// built-in type and `b` has type `absl::Duration`.
|
||||
Finder->addMatcher(
|
||||
callExpr(callee(functionDecl(
|
||||
hasParent(functionTemplateDecl()),
|
||||
unless(hasTemplateArgument(0, refersToType(builtinType()))),
|
||||
hasName("::absl::operator*"))),
|
||||
argumentCountIs(2), hasArgument(0, expr().bind("arg")),
|
||||
hasArgument(1, expr(hasType(cxxRecordDecl(
|
||||
hasName("::absl::Duration")))))),
|
||||
this);
|
||||
|
||||
// For the factory functions, we match only the non-templated overloads that
|
||||
// take an `int64_t` parameter. Within these calls, we care about implicit
|
||||
// casts through a user defined conversion to `int64_t`.
|
||||
//
|
||||
// The factory functions will be updated to be templated and SFINAE on whether
|
||||
// the template parameter is an integral type. This complements the already
|
||||
// existing templated overloads that only accept floating point types.
|
||||
|
||||
// Match calls like:
|
||||
// `absl::Nanoseconds(x)`
|
||||
// `absl::Microseconds(x)`
|
||||
// `absl::Milliseconds(x)`
|
||||
// `absl::Seconds(x)`
|
||||
// `absl::Minutes(x)`
|
||||
// `absl::Hours(x)`
|
||||
// where `x` is not of a built-in type.
|
||||
Finder->addMatcher(
|
||||
implicitCastExpr(
|
||||
anyOf(hasCastKind(CK_UserDefinedConversion),
|
||||
has(implicitCastExpr(hasCastKind(CK_UserDefinedConversion)))),
|
||||
hasParent(callExpr(
|
||||
callee(functionDecl(DurationFactoryFunction(),
|
||||
unless(hasParent(functionTemplateDecl())))),
|
||||
hasArgument(0, expr().bind("arg"))))),
|
||||
this);
|
||||
}
|
||||
|
||||
void UpgradeDurationConversionsCheck::check(
|
||||
const MatchFinder::MatchResult &Result) {
|
||||
const llvm::StringRef Message =
|
||||
"implicit conversion to 'int64_t' is deprecated in this context; use an "
|
||||
"explicit cast instead";
|
||||
|
||||
const auto *ArgExpr = Result.Nodes.getNodeAs<Expr>("arg");
|
||||
SourceLocation Loc = ArgExpr->getBeginLoc();
|
||||
|
||||
if (!match(isInTemplateInstantiation(), *ArgExpr, *Result.Context).empty()) {
|
||||
if (MatchedTemplateLocations.count(Loc.getRawEncoding()) == 0) {
|
||||
// For each location matched in a template instantiation, we check if the
|
||||
// location can also be found in `MatchedTemplateLocations`. If it is not
|
||||
// found, that means the expression did not create a match without the
|
||||
// instantiation and depends on template parameters. A manual fix is
|
||||
// probably required so we provide only a warning.
|
||||
diag(Loc, Message);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// We gather source locations from template matches not in template
|
||||
// instantiations for future matches.
|
||||
internal::Matcher<Stmt> IsInsideTemplate =
|
||||
hasAncestor(decl(anyOf(classTemplateDecl(), functionTemplateDecl())));
|
||||
if (!match(IsInsideTemplate, *ArgExpr, *Result.Context).empty())
|
||||
MatchedTemplateLocations.insert(Loc.getRawEncoding());
|
||||
|
||||
DiagnosticBuilder Diag = diag(Loc, Message);
|
||||
CharSourceRange SourceRange = Lexer::makeFileCharRange(
|
||||
CharSourceRange::getTokenRange(ArgExpr->getSourceRange()),
|
||||
*Result.SourceManager, Result.Context->getLangOpts());
|
||||
if (SourceRange.isInvalid())
|
||||
// An invalid source range likely means we are inside a macro body. A manual
|
||||
// fix is likely needed so we do not create a fix-it hint.
|
||||
return;
|
||||
|
||||
Diag << FixItHint::CreateInsertion(SourceRange.getBegin(),
|
||||
"static_cast<int64_t>(")
|
||||
<< FixItHint::CreateInsertion(SourceRange.getEnd(), ")");
|
||||
}
|
||||
|
||||
} // namespace abseil
|
||||
} // namespace tidy
|
||||
} // namespace clang
|
||||
@@ -1,40 +0,0 @@
|
||||
//===--- UpgradeDurationConversionsCheck.h - clang-tidy ---------*- C++ -*-===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ABSEIL_UPGRADEDURATIONCONVERSIONSCHECK_H
|
||||
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ABSEIL_UPGRADEDURATIONCONVERSIONSCHECK_H
|
||||
|
||||
#include "../ClangTidy.h"
|
||||
|
||||
#include <unordered_set>
|
||||
|
||||
namespace clang {
|
||||
namespace tidy {
|
||||
namespace abseil {
|
||||
|
||||
/// Finds deprecated uses of `absl::Duration` arithmetic operators and factories.
|
||||
///
|
||||
/// For the user-facing documentation see:
|
||||
/// http://clang.llvm.org/extra/clang-tidy/checks/abseil-upgrade-duration-conversions.html
|
||||
class UpgradeDurationConversionsCheck : public ClangTidyCheck {
|
||||
public:
|
||||
UpgradeDurationConversionsCheck(StringRef Name, ClangTidyContext *Context)
|
||||
: ClangTidyCheck(Name, Context) {}
|
||||
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
|
||||
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
|
||||
|
||||
private:
|
||||
std::unordered_set<unsigned> MatchedTemplateLocations;
|
||||
};
|
||||
|
||||
} // namespace abseil
|
||||
} // namespace tidy
|
||||
} // namespace clang
|
||||
|
||||
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ABSEIL_UPGRADEDURATIONCONVERSIONSCHECK_H
|
||||
@@ -56,8 +56,8 @@ def write_header(module_path, module, check_name, check_name_camel):
|
||||
+ check_name_camel.upper() + '_H')
|
||||
f.write('//===--- ')
|
||||
f.write(os.path.basename(filename))
|
||||
f.write(' - clang-tidy ')
|
||||
f.write('-' * max(0, 42 - len(os.path.basename(filename))))
|
||||
f.write(' - clang-tidy')
|
||||
f.write('-' * max(0, 43 - len(os.path.basename(filename))))
|
||||
f.write('*- C++ -*-===//')
|
||||
f.write("""
|
||||
//
|
||||
@@ -107,8 +107,8 @@ def write_implementation(module_path, module, check_name_camel):
|
||||
with open(filename, 'w') as f:
|
||||
f.write('//===--- ')
|
||||
f.write(os.path.basename(filename))
|
||||
f.write(' - clang-tidy ')
|
||||
f.write('-' * max(0, 51 - len(os.path.basename(filename))))
|
||||
f.write(' - clang-tidy')
|
||||
f.write('-' * max(0, 52 - len(os.path.basename(filename))))
|
||||
f.write('-===//')
|
||||
f.write("""
|
||||
//
|
||||
@@ -200,47 +200,23 @@ def add_release_notes(module_path, module, check_name):
|
||||
with open(filename, 'r') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
lineMatcher = re.compile('Improvements to clang-tidy')
|
||||
nextSectionMatcher = re.compile('Improvements to include-fixer')
|
||||
checkerMatcher = re.compile('- New :doc:`(.*)')
|
||||
|
||||
print('Updating %s...' % filename)
|
||||
with open(filename, 'w') as f:
|
||||
note_added = False
|
||||
header_found = False
|
||||
next_header_found = False
|
||||
add_note_here = False
|
||||
|
||||
for line in lines:
|
||||
if not note_added:
|
||||
match = lineMatcher.match(line)
|
||||
match_next = nextSectionMatcher.match(line)
|
||||
match_checker = checkerMatcher.match(line)
|
||||
if match_checker:
|
||||
last_checker = match_checker.group(1)
|
||||
if last_checker > check_name_dashes:
|
||||
add_note_here = True
|
||||
|
||||
if match_next:
|
||||
next_header_found = True
|
||||
add_note_here = True
|
||||
|
||||
match = re.search('Improvements to clang-tidy', line)
|
||||
if match:
|
||||
header_found = True
|
||||
f.write(line)
|
||||
continue
|
||||
|
||||
if line.startswith('----'):
|
||||
f.write(line)
|
||||
continue
|
||||
|
||||
if header_found and add_note_here:
|
||||
elif header_found:
|
||||
if not line.startswith('----'):
|
||||
f.write("""- New :doc:`%s
|
||||
f.write("""
|
||||
- New :doc:`%s
|
||||
<clang-tidy/checks/%s>` check.
|
||||
|
||||
FIXME: add release notes.
|
||||
|
||||
""" % (check_name_dashes, check_name_dashes))
|
||||
note_added = True
|
||||
|
||||
|
||||
@@ -37,16 +37,16 @@ public:
|
||||
CheckFactories.registerCheck<CloexecAccept4Check>("android-cloexec-accept4");
|
||||
CheckFactories.registerCheck<CloexecAcceptCheck>("android-cloexec-accept");
|
||||
CheckFactories.registerCheck<CloexecCreatCheck>("android-cloexec-creat");
|
||||
CheckFactories.registerCheck<CloexecDupCheck>("android-cloexec-dup");
|
||||
CheckFactories.registerCheck<CloexecEpollCreate1Check>(
|
||||
"android-cloexec-epoll-create1");
|
||||
CheckFactories.registerCheck<CloexecEpollCreateCheck>(
|
||||
"android-cloexec-epoll-create");
|
||||
CheckFactories.registerCheck<CloexecDupCheck>("android-cloexec-dup");
|
||||
CheckFactories.registerCheck<CloexecFopenCheck>("android-cloexec-fopen");
|
||||
CheckFactories.registerCheck<CloexecInotifyInit1Check>(
|
||||
"android-cloexec-inotify-init1");
|
||||
CheckFactories.registerCheck<CloexecInotifyInitCheck>(
|
||||
"android-cloexec-inotify-init");
|
||||
CheckFactories.registerCheck<CloexecInotifyInit1Check>(
|
||||
"android-cloexec-inotify-init1");
|
||||
CheckFactories.registerCheck<CloexecMemfdCreateCheck>(
|
||||
"android-cloexec-memfd-create");
|
||||
CheckFactories.registerCheck<CloexecOpenCheck>("android-cloexec-open");
|
||||
|
||||
@@ -6,9 +6,9 @@ add_clang_library(clangTidyAndroidModule
|
||||
CloexecAcceptCheck.cpp
|
||||
CloexecCheck.cpp
|
||||
CloexecCreatCheck.cpp
|
||||
CloexecDupCheck.cpp
|
||||
CloexecEpollCreate1Check.cpp
|
||||
CloexecEpollCreateCheck.cpp
|
||||
CloexecDupCheck.cpp
|
||||
CloexecFopenCheck.cpp
|
||||
CloexecInotifyInit1Check.cpp
|
||||
CloexecInotifyInitCheck.cpp
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace {
|
||||
// end of the string. Else, add <Mode>.
|
||||
std::string buildFixMsgForStringFlag(const Expr *Arg, const SourceManager &SM,
|
||||
const LangOptions &LangOpts, char Mode) {
|
||||
if (Arg->getBeginLoc().isMacroID())
|
||||
if (Arg->getLocStart().isMacroID())
|
||||
return (Lexer::getSourceText(
|
||||
CharSourceRange::getTokenRange(Arg->getSourceRange()), SM,
|
||||
LangOpts) +
|
||||
@@ -64,7 +64,7 @@ void CloexecCheck::insertMacroFlag(const MatchFinder::MatchResult &Result,
|
||||
return;
|
||||
|
||||
SourceLocation EndLoc =
|
||||
Lexer::getLocForEndOfToken(SM.getFileLoc(FlagArg->getEndLoc()), 0, SM,
|
||||
Lexer::getLocForEndOfToken(SM.getFileLoc(FlagArg->getLocEnd()), 0, SM,
|
||||
Result.Context->getLangOpts());
|
||||
|
||||
diag(EndLoc, "%0 should use %1 where possible")
|
||||
@@ -75,7 +75,7 @@ void CloexecCheck::insertMacroFlag(const MatchFinder::MatchResult &Result,
|
||||
void CloexecCheck::replaceFunc(const MatchFinder::MatchResult &Result,
|
||||
StringRef WarningMsg, StringRef FixMsg) {
|
||||
const auto *MatchedCall = Result.Nodes.getNodeAs<CallExpr>(FuncBindingStr);
|
||||
diag(MatchedCall->getBeginLoc(), WarningMsg)
|
||||
diag(MatchedCall->getLocStart(), WarningMsg)
|
||||
<< FixItHint::CreateReplacement(MatchedCall->getSourceRange(), FixMsg);
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ void CloexecCheck::insertStringFlag(
|
||||
const std::string &ReplacementText = buildFixMsgForStringFlag(
|
||||
ModeArg, *Result.SourceManager, Result.Context->getLangOpts(), Mode);
|
||||
|
||||
diag(ModeArg->getBeginLoc(), "use %0 mode '%1' to set O_CLOEXEC")
|
||||
diag(ModeArg->getLocStart(), "use %0 mode '%1' to set O_CLOEXEC")
|
||||
<< FD << std::string(1, Mode)
|
||||
<< FixItHint::CreateReplacement(ModeArg->getSourceRange(),
|
||||
ReplacementText);
|
||||
|
||||
@@ -21,15 +21,15 @@ namespace android {
|
||||
|
||||
namespace {
|
||||
AST_MATCHER(BinaryOperator, isRHSATempFailureRetryArg) {
|
||||
if (!Node.getBeginLoc().isMacroID())
|
||||
if (!Node.getLocStart().isMacroID())
|
||||
return false;
|
||||
|
||||
const SourceManager &SM = Finder->getASTContext().getSourceManager();
|
||||
if (!SM.isMacroArgExpansion(Node.getRHS()->IgnoreParenCasts()->getBeginLoc()))
|
||||
if (!SM.isMacroArgExpansion(Node.getRHS()->IgnoreParenCasts()->getLocStart()))
|
||||
return false;
|
||||
|
||||
const LangOptions &Opts = Finder->getASTContext().getLangOpts();
|
||||
SourceLocation LocStart = Node.getBeginLoc();
|
||||
SourceLocation LocStart = Node.getLocStart();
|
||||
while (LocStart.isMacroID()) {
|
||||
SourceLocation Invocation = SM.getImmediateMacroCallerLoc(LocStart);
|
||||
Token Tok;
|
||||
|
||||
@@ -56,7 +56,7 @@ void UseToStringCheck::check(const MatchFinder::MatchResult &Result) {
|
||||
else
|
||||
return;
|
||||
|
||||
auto Loc = Call->getBeginLoc();
|
||||
auto Loc = Call->getLocStart();
|
||||
auto Diag =
|
||||
diag(Loc, "use std::to_%0 instead of boost::lexical_cast<std::%0>")
|
||||
<< StringType;
|
||||
@@ -65,8 +65,8 @@ void UseToStringCheck::check(const MatchFinder::MatchResult &Result) {
|
||||
return;
|
||||
|
||||
Diag << FixItHint::CreateReplacement(
|
||||
CharSourceRange::getCharRange(Call->getBeginLoc(),
|
||||
Call->getArg(0)->getBeginLoc()),
|
||||
CharSourceRange::getCharRange(Call->getLocStart(),
|
||||
Call->getArg(0)->getLocStart()),
|
||||
(llvm::Twine("std::to_") + StringType + "(").str());
|
||||
}
|
||||
|
||||
|
||||
@@ -91,9 +91,8 @@ static std::vector<std::pair<SourceLocation, StringRef>>
|
||||
getCommentsBeforeLoc(ASTContext *Ctx, SourceLocation Loc) {
|
||||
std::vector<std::pair<SourceLocation, StringRef>> Comments;
|
||||
while (Loc.isValid()) {
|
||||
clang::Token Tok = utils::lexer::getPreviousToken(
|
||||
Loc, Ctx->getSourceManager(), Ctx->getLangOpts(),
|
||||
/*SkipComments=*/false);
|
||||
clang::Token Tok =
|
||||
utils::lexer::getPreviousToken(*Ctx, Loc, /*SkipComments=*/false);
|
||||
if (Tok.isNot(tok::comment))
|
||||
break;
|
||||
Loc = Tok.getLocation();
|
||||
@@ -243,8 +242,8 @@ void ArgumentCommentCheck::checkCallArgs(ASTContext *Ctx,
|
||||
}
|
||||
|
||||
CharSourceRange BeforeArgument =
|
||||
makeFileCharRange(ArgBeginLoc, Args[I]->getBeginLoc());
|
||||
ArgBeginLoc = Args[I]->getEndLoc();
|
||||
makeFileCharRange(ArgBeginLoc, Args[I]->getLocStart());
|
||||
ArgBeginLoc = Args[I]->getLocEnd();
|
||||
|
||||
std::vector<std::pair<SourceLocation, StringRef>> Comments;
|
||||
if (BeforeArgument.isValid()) {
|
||||
@@ -252,7 +251,7 @@ void ArgumentCommentCheck::checkCallArgs(ASTContext *Ctx,
|
||||
} else {
|
||||
// Fall back to parsing back from the start of the argument.
|
||||
CharSourceRange ArgsRange = makeFileCharRange(
|
||||
Args[I]->getBeginLoc(), Args[NumArgs - 1]->getEndLoc());
|
||||
Args[I]->getLocStart(), Args[NumArgs - 1]->getLocEnd());
|
||||
Comments = getCommentsBeforeLoc(Ctx, ArgsRange.getBegin());
|
||||
}
|
||||
|
||||
@@ -288,7 +287,7 @@ void ArgumentCommentCheck::check(const MatchFinder::MatchResult &Result) {
|
||||
if (!Callee)
|
||||
return;
|
||||
|
||||
checkCallArgs(Result.Context, Callee, Call->getCallee()->getEndLoc(),
|
||||
checkCallArgs(Result.Context, Callee, Call->getCallee()->getLocEnd(),
|
||||
llvm::makeArrayRef(Call->getArgs(), Call->getNumArgs()));
|
||||
} else {
|
||||
const auto *Construct = cast<CXXConstructExpr>(E);
|
||||
|
||||
@@ -102,7 +102,7 @@ void AssertSideEffectCheck::registerMatchers(MatchFinder *Finder) {
|
||||
void AssertSideEffectCheck::check(const MatchFinder::MatchResult &Result) {
|
||||
const SourceManager &SM = *Result.SourceManager;
|
||||
const LangOptions LangOpts = getLangOpts();
|
||||
SourceLocation Loc = Result.Nodes.getNodeAs<Stmt>("condStmt")->getBeginLoc();
|
||||
SourceLocation Loc = Result.Nodes.getNodeAs<Stmt>("condStmt")->getLocStart();
|
||||
|
||||
StringRef AssertMacroName;
|
||||
while (Loc.isValid() && Loc.isMacroID()) {
|
||||
|
||||
@@ -20,11 +20,11 @@ void BoolPointerImplicitConversionCheck::registerMatchers(MatchFinder *Finder) {
|
||||
// condition. Filter negations.
|
||||
Finder->addMatcher(
|
||||
ifStmt(hasCondition(findAll(implicitCastExpr(
|
||||
unless(hasParent(unaryOperator(hasOperatorName("!")))),
|
||||
hasSourceExpression(
|
||||
expr(hasType(pointerType(pointee(booleanType()))),
|
||||
ignoringParenImpCasts(declRefExpr().bind("expr")))),
|
||||
hasCastKind(CK_PointerToBoolean)))),
|
||||
allOf(unless(hasParent(unaryOperator(hasOperatorName("!")))),
|
||||
hasSourceExpression(expr(
|
||||
hasType(pointerType(pointee(booleanType()))),
|
||||
ignoringParenImpCasts(declRefExpr().bind("expr")))),
|
||||
hasCastKind(CK_PointerToBoolean))))),
|
||||
unless(isInTemplateInstantiation()))
|
||||
.bind("if"),
|
||||
this);
|
||||
@@ -36,7 +36,7 @@ void BoolPointerImplicitConversionCheck::check(
|
||||
auto *Var = Result.Nodes.getNodeAs<DeclRefExpr>("expr");
|
||||
|
||||
// Ignore macros.
|
||||
if (Var->getBeginLoc().isMacroID())
|
||||
if (Var->getLocStart().isMacroID())
|
||||
return;
|
||||
|
||||
// Only allow variable accesses for now, no function calls or member exprs.
|
||||
@@ -63,9 +63,9 @@ void BoolPointerImplicitConversionCheck::check(
|
||||
.empty())
|
||||
return;
|
||||
|
||||
diag(Var->getBeginLoc(), "dubious check of 'bool *' against 'nullptr', did "
|
||||
diag(Var->getLocStart(), "dubious check of 'bool *' against 'nullptr', did "
|
||||
"you mean to dereference it?")
|
||||
<< FixItHint::CreateInsertion(Var->getBeginLoc(), "*");
|
||||
<< FixItHint::CreateInsertion(Var->getLocStart(), "*");
|
||||
}
|
||||
|
||||
} // namespace bugprone
|
||||
|
||||
@@ -44,7 +44,6 @@
|
||||
#include "SwappedArgumentsCheck.h"
|
||||
#include "TerminatingContinueCheck.h"
|
||||
#include "ThrowKeywordMissingCheck.h"
|
||||
#include "TooSmallLoopVariableCheck.h"
|
||||
#include "UndefinedMemoryManipulationCheck.h"
|
||||
#include "UndelegatedConstructorCheck.h"
|
||||
#include "UnusedRaiiCheck.h"
|
||||
@@ -97,8 +96,6 @@ public:
|
||||
"bugprone-move-forwarding-reference");
|
||||
CheckFactories.registerCheck<MultipleStatementMacroCheck>(
|
||||
"bugprone-multiple-statement-macro");
|
||||
CheckFactories.registerCheck<TooSmallLoopVariableCheck>(
|
||||
"bugprone-too-small-loop-variable");
|
||||
CheckFactories.registerCheck<cppcoreguidelines::NarrowingConversionsCheck>(
|
||||
"bugprone-narrowing-conversions");
|
||||
CheckFactories.registerCheck<ParentVirtualCallCheck>(
|
||||
|
||||
@@ -35,7 +35,6 @@ add_clang_library(clangTidyBugproneModule
|
||||
SwappedArgumentsCheck.cpp
|
||||
TerminatingContinueCheck.cpp
|
||||
ThrowKeywordMissingCheck.cpp
|
||||
TooSmallLoopVariableCheck.cpp
|
||||
UndefinedMemoryManipulationCheck.cpp
|
||||
UndelegatedConstructorCheck.cpp
|
||||
UnusedRaiiCheck.cpp
|
||||
|
||||
@@ -81,7 +81,7 @@ void CopyConstructorInitCheck::check(const MatchFinder::MatchResult &Result) {
|
||||
if (CtorInitIsWritten) {
|
||||
if (!ParamName.empty())
|
||||
SafeFixIts.push_back(
|
||||
FixItHint::CreateInsertion(CExpr->getEndLoc(), ParamName));
|
||||
FixItHint::CreateInsertion(CExpr->getLocEnd(), ParamName));
|
||||
} else {
|
||||
if (Init->getSourceLocation().isMacroID() ||
|
||||
Ctor->getLocation().isMacroID() || ShouldNotDoFixit)
|
||||
@@ -104,7 +104,7 @@ void CopyConstructorInitCheck::check(const MatchFinder::MatchResult &Result) {
|
||||
SourceLocation FixItLoc;
|
||||
// There is no initialization list in this constructor.
|
||||
if (!HasWrittenInitializer) {
|
||||
FixItLoc = Ctor->getBody()->getBeginLoc();
|
||||
FixItLoc = Ctor->getBody()->getLocStart();
|
||||
FixItMsg = " : " + FixItMsg;
|
||||
} else {
|
||||
// We apply the missing ctors at the beginning of the initialization list.
|
||||
|
||||
@@ -178,7 +178,7 @@ void DanglingHandleCheck::registerMatchers(MatchFinder *Finder) {
|
||||
|
||||
void DanglingHandleCheck::check(const MatchFinder::MatchResult &Result) {
|
||||
auto *Handle = Result.Nodes.getNodeAs<CXXRecordDecl>("handle");
|
||||
diag(Result.Nodes.getNodeAs<Stmt>("bad_stmt")->getBeginLoc(),
|
||||
diag(Result.Nodes.getNodeAs<Stmt>("bad_stmt")->getLocStart(),
|
||||
"%0 outlives its value")
|
||||
<< Handle->getQualifiedNameAsString();
|
||||
}
|
||||
|
||||
@@ -186,16 +186,13 @@ void ExceptionEscapeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
|
||||
}
|
||||
|
||||
void ExceptionEscapeCheck::registerMatchers(MatchFinder *Finder) {
|
||||
if (!getLangOpts().CPlusPlus || !getLangOpts().CXXExceptions)
|
||||
return;
|
||||
|
||||
Finder->addMatcher(
|
||||
functionDecl(anyOf(isNoThrow(), cxxDestructorDecl(),
|
||||
cxxConstructorDecl(isMoveConstructor()),
|
||||
cxxMethodDecl(isMoveAssignmentOperator()),
|
||||
hasName("main"), hasName("swap"),
|
||||
isEnabled(FunctionsThatShouldNotThrow)),
|
||||
throws(unless(isIgnored(IgnoredExceptions))))
|
||||
functionDecl(allOf(anyOf(isNoThrow(), cxxDestructorDecl(),
|
||||
cxxConstructorDecl(isMoveConstructor()),
|
||||
cxxMethodDecl(isMoveAssignmentOperator()),
|
||||
hasName("main"), hasName("swap"),
|
||||
isEnabled(FunctionsThatShouldNotThrow)),
|
||||
throws(unless(isIgnored(IgnoredExceptions)))))
|
||||
.bind("thrower"),
|
||||
this);
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ void InaccurateEraseCheck::check(const MatchFinder::MatchResult &Result) {
|
||||
Result.Nodes.getNodeAs<CXXMemberCallExpr>("erase");
|
||||
const auto *EndExpr =
|
||||
Result.Nodes.getNodeAs<CXXMemberCallExpr>("end");
|
||||
const SourceLocation Loc = MemberCall->getBeginLoc();
|
||||
const SourceLocation Loc = MemberCall->getLocStart();
|
||||
|
||||
FixItHint Hint;
|
||||
|
||||
@@ -67,7 +67,7 @@ void InaccurateEraseCheck::check(const MatchFinder::MatchResult &Result) {
|
||||
CharSourceRange::getTokenRange(EndExpr->getSourceRange()),
|
||||
*Result.SourceManager, getLangOpts());
|
||||
const SourceLocation EndLoc = Lexer::getLocForEndOfToken(
|
||||
AlgCall->getEndLoc(), 0, *Result.SourceManager, getLangOpts());
|
||||
AlgCall->getLocEnd(), 0, *Result.SourceManager, getLangOpts());
|
||||
Hint = FixItHint::CreateInsertion(EndLoc, ", " + ReplacementText);
|
||||
}
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ void IncorrectRoundingsCheck::registerMatchers(MatchFinder *MatchFinder) {
|
||||
|
||||
void IncorrectRoundingsCheck::check(const MatchFinder::MatchResult &Result) {
|
||||
const auto *CastExpr = Result.Nodes.getNodeAs<ImplicitCastExpr>("CastExpr");
|
||||
diag(CastExpr->getBeginLoc(),
|
||||
diag(CastExpr->getLocStart(),
|
||||
"casting (double + 0.5) to integer leads to incorrect rounding; "
|
||||
"consider using lround (#include <cmath>) instead");
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ void IntegerDivisionCheck::registerMatchers(MatchFinder *Finder) {
|
||||
|
||||
void IntegerDivisionCheck::check(const MatchFinder::MatchResult &Result) {
|
||||
const auto *IntDiv = Result.Nodes.getNodeAs<BinaryOperator>("IntDiv");
|
||||
diag(IntDiv->getBeginLoc(), "result of integer division used in a floating "
|
||||
diag(IntDiv->getLocStart(), "result of integer division used in a floating "
|
||||
"point context; possible loss of precision");
|
||||
}
|
||||
|
||||
|
||||
@@ -73,8 +73,8 @@ void LambdaFunctionNameCheck::registerPPCallbacks(CompilerInstance &Compiler) {
|
||||
|
||||
void LambdaFunctionNameCheck::check(const MatchFinder::MatchResult &Result) {
|
||||
const auto *E = Result.Nodes.getNodeAs<PredefinedExpr>("E");
|
||||
if (E->getIdentKind() != PredefinedExpr::Func &&
|
||||
E->getIdentKind() != PredefinedExpr::Function) {
|
||||
if (E->getIdentType() != PredefinedExpr::Func &&
|
||||
E->getIdentType() != PredefinedExpr::Function) {
|
||||
// We don't care about other PredefinedExprs.
|
||||
return;
|
||||
}
|
||||
@@ -91,7 +91,7 @@ void LambdaFunctionNameCheck::check(const MatchFinder::MatchResult &Result) {
|
||||
"inside a lambda, '%0' expands to the name of the function call "
|
||||
"operator; consider capturing the name of the enclosing function "
|
||||
"explicitly")
|
||||
<< PredefinedExpr::getIdentKindName(E->getIdentKind());
|
||||
<< PredefinedExpr::getIdentTypeName(E->getIdentType());
|
||||
}
|
||||
|
||||
} // namespace bugprone
|
||||
|
||||
@@ -29,17 +29,17 @@ void MisplacedOperatorInStrlenInAllocCheck::registerMatchers(
|
||||
const auto BadUse =
|
||||
callExpr(callee(StrLenFunc),
|
||||
hasAnyArgument(ignoringImpCasts(
|
||||
binaryOperator(
|
||||
hasOperatorName("+"),
|
||||
hasRHS(ignoringParenImpCasts(integerLiteral(equals(1)))))
|
||||
binaryOperator(allOf(hasOperatorName("+"),
|
||||
hasRHS(ignoringParenImpCasts(
|
||||
integerLiteral(equals(1))))))
|
||||
.bind("BinOp"))))
|
||||
.bind("StrLen");
|
||||
|
||||
const auto BadArg = anyOf(
|
||||
allOf(unless(binaryOperator(
|
||||
allOf(hasDescendant(BadUse),
|
||||
unless(binaryOperator(allOf(
|
||||
hasOperatorName("+"), hasLHS(BadUse),
|
||||
hasRHS(ignoringParenImpCasts(integerLiteral(equals(1)))))),
|
||||
hasDescendant(BadUse)),
|
||||
hasRHS(ignoringParenImpCasts(integerLiteral(equals(1)))))))),
|
||||
BadUse);
|
||||
|
||||
const auto Alloc0Func =
|
||||
@@ -102,10 +102,9 @@ void MisplacedOperatorInStrlenInAllocCheck::check(
|
||||
StrLen->getSourceRange(),
|
||||
(StrLenBegin + LHSText + StrLenEnd + " + " + RHSText).str());
|
||||
|
||||
diag(Alloc->getBeginLoc(),
|
||||
diag(Alloc->getLocStart(),
|
||||
"addition operator is applied to the argument of %0 instead of its "
|
||||
"result")
|
||||
<< StrLen->getDirectCallee()->getName() << Hint;
|
||||
"result") << StrLen->getDirectCallee()->getName() << Hint;
|
||||
}
|
||||
|
||||
} // namespace bugprone
|
||||
|
||||
@@ -65,16 +65,16 @@ static unsigned getMaxCalculationWidth(const ASTContext &Context,
|
||||
if (Bop->getOpcode() == BO_Add)
|
||||
return std::max(LHSWidth, RHSWidth) + 1;
|
||||
if (Bop->getOpcode() == BO_Rem) {
|
||||
Expr::EvalResult Result;
|
||||
if (Bop->getRHS()->EvaluateAsInt(Result, Context))
|
||||
return Result.Val.getInt().getActiveBits();
|
||||
llvm::APSInt Val;
|
||||
if (Bop->getRHS()->EvaluateAsInt(Val, Context))
|
||||
return Val.getActiveBits();
|
||||
} else if (Bop->getOpcode() == BO_Shl) {
|
||||
Expr::EvalResult Result;
|
||||
if (Bop->getRHS()->EvaluateAsInt(Result, Context)) {
|
||||
llvm::APSInt Bits;
|
||||
if (Bop->getRHS()->EvaluateAsInt(Bits, Context)) {
|
||||
// We don't handle negative values and large values well. It is assumed
|
||||
// that compiler warnings are written for such values so the user will
|
||||
// fix that.
|
||||
return LHSWidth + Result.Val.getInt().getExtValue();
|
||||
return LHSWidth + Bits.getExtValue();
|
||||
}
|
||||
|
||||
// Unknown bitcount, assume there is truncation.
|
||||
@@ -185,11 +185,11 @@ void MisplacedWideningCastCheck::check(const MatchFinder::MatchResult &Result) {
|
||||
const auto *Cast = Result.Nodes.getNodeAs<CastExpr>("Cast");
|
||||
if (!CheckImplicitCasts && isa<ImplicitCastExpr>(Cast))
|
||||
return;
|
||||
if (Cast->getBeginLoc().isMacroID())
|
||||
if (Cast->getLocStart().isMacroID())
|
||||
return;
|
||||
|
||||
const auto *Calc = Result.Nodes.getNodeAs<Expr>("Calc");
|
||||
if (Calc->getBeginLoc().isMacroID())
|
||||
if (Calc->getLocStart().isMacroID())
|
||||
return;
|
||||
|
||||
if (Cast->isTypeDependent() || Cast->isValueDependent() ||
|
||||
@@ -213,9 +213,8 @@ void MisplacedWideningCastCheck::check(const MatchFinder::MatchResult &Result) {
|
||||
dyn_cast<BuiltinType>(CastType->getUnqualifiedDesugaredType());
|
||||
const auto *CalcBuiltinType =
|
||||
dyn_cast<BuiltinType>(CalcType->getUnqualifiedDesugaredType());
|
||||
if (!CastBuiltinType || !CalcBuiltinType)
|
||||
return;
|
||||
if (!isFirstWider(CastBuiltinType->getKind(), CalcBuiltinType->getKind()))
|
||||
if (CastBuiltinType && CalcBuiltinType &&
|
||||
!isFirstWider(CastBuiltinType->getKind(), CalcBuiltinType->getKind()))
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -224,7 +223,7 @@ void MisplacedWideningCastCheck::check(const MatchFinder::MatchResult &Result) {
|
||||
if (Context.getIntWidth(CalcType) >= getMaxCalculationWidth(Context, Calc))
|
||||
return;
|
||||
|
||||
diag(Cast->getBeginLoc(), "either cast from %0 to %1 is ineffective, or "
|
||||
diag(Cast->getLocStart(), "either cast from %0 to %1 is ineffective, or "
|
||||
"there is loss of precision before the conversion")
|
||||
<< CalcType << CastType;
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ static void replaceMoveWithForward(const UnresolvedLookupExpr *Callee,
|
||||
|
||||
CharSourceRange CallRange =
|
||||
Lexer::makeFileCharRange(CharSourceRange::getTokenRange(
|
||||
Callee->getBeginLoc(), Callee->getEndLoc()),
|
||||
Callee->getLocStart(), Callee->getLocEnd()),
|
||||
SM, LangOpts);
|
||||
|
||||
if (CallRange.isValid()) {
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace bugprone {
|
||||
|
||||
namespace {
|
||||
|
||||
AST_MATCHER(Expr, isInMacro) { return Node.getBeginLoc().isMacroID(); }
|
||||
AST_MATCHER(Expr, isInMacro) { return Node.getLocStart().isMacroID(); }
|
||||
|
||||
/// \brief Find the next statement after `S`.
|
||||
const Stmt *nextStmt(const MatchFinder::MatchResult &Result, const Stmt *S) {
|
||||
@@ -73,13 +73,13 @@ void MultipleStatementMacroCheck::check(
|
||||
if (!Next)
|
||||
return;
|
||||
|
||||
SourceLocation OuterLoc = Outer->getBeginLoc();
|
||||
SourceLocation OuterLoc = Outer->getLocStart();
|
||||
if (Result.Nodes.getNodeAs<Stmt>("else"))
|
||||
OuterLoc = cast<IfStmt>(Outer)->getElseLoc();
|
||||
|
||||
auto InnerRanges = getExpansionRanges(Inner->getBeginLoc(), Result);
|
||||
auto InnerRanges = getExpansionRanges(Inner->getLocStart(), Result);
|
||||
auto OuterRanges = getExpansionRanges(OuterLoc, Result);
|
||||
auto NextRanges = getExpansionRanges(Next->getBeginLoc(), Result);
|
||||
auto NextRanges = getExpansionRanges(Next->getLocStart(), Result);
|
||||
|
||||
// Remove all the common ranges, starting from the top (the last ones in the
|
||||
// list).
|
||||
|
||||
@@ -50,7 +50,9 @@ static BasesVector getParentsByGrandParent(const CXXRecordDecl &GrandParent,
|
||||
// TypePtr is the nearest base class to ThisClass between ThisClass and
|
||||
// GrandParent, where MemberDecl is overridden. TypePtr is the class the
|
||||
// check proposes to fix to.
|
||||
const Type *TypePtr = ActualMemberDecl->getThisType().getTypePtr();
|
||||
const Type *TypePtr =
|
||||
ActualMemberDecl->getThisType(ActualMemberDecl->getASTContext())
|
||||
.getTypePtr();
|
||||
const CXXRecordDecl *RecordDeclType = TypePtr->getPointeeCXXRecordDecl();
|
||||
assert(RecordDeclType && "TypePtr is not a pointer to CXXRecordDecl!");
|
||||
if (RecordDeclType->getCanonicalDecl()->isDerivedFrom(&GrandParent))
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user