void SerializedDiagnosticConsumer::addRangeToRecord(CharSourceRange Range, SourceManager &SM, StringRef Filename, RecordDataImpl &Record) { assert(Range.isValid()); addLocToRecord(Range.getStart(), SM, Filename, Record); addLocToRecord(Range.getEnd(), SM, Filename, Record); }
static void ReplaceCallWithArg(const CallExpr *Call, DiagnosticBuilder &Diag, const SourceManager &SM, const LangOptions &LangOpts) { const Expr *Arg = Call->getArg(0); CharSourceRange BeforeArgumentsRange = Lexer::makeFileCharRange( CharSourceRange::getCharRange(Call->getLocStart(), Arg->getLocStart()), SM, LangOpts); CharSourceRange AfterArgumentsRange = Lexer::makeFileCharRange( CharSourceRange::getCharRange(Call->getLocEnd(), Call->getLocEnd().getLocWithOffset(1)), SM, LangOpts); if (BeforeArgumentsRange.isValid() && AfterArgumentsRange.isValid()) { Diag << FixItHint::CreateRemoval(BeforeArgumentsRange) << FixItHint::CreateRemoval(AfterArgumentsRange); } }
void MoveConstArgCheck::check(const MatchFinder::MatchResult &Result) { const auto *CallMove = Result.Nodes.getNodeAs<CallExpr>("call-move"); const auto *ReceivingExpr = Result.Nodes.getNodeAs<Expr>("receiving-expr"); const Expr *Arg = CallMove->getArg(0); SourceManager &SM = Result.Context->getSourceManager(); CharSourceRange MoveRange = CharSourceRange::getCharRange(CallMove->getSourceRange()); CharSourceRange FileMoveRange = Lexer::makeFileCharRange(MoveRange, SM, getLangOpts()); if (!FileMoveRange.isValid()) return; bool IsConstArg = Arg->getType().isConstQualified(); bool IsTriviallyCopyable = Arg->getType().isTriviallyCopyableType(*Result.Context); if (IsConstArg || IsTriviallyCopyable) { if (const CXXRecordDecl *R = Arg->getType()->getAsCXXRecordDecl()) { // According to [expr.prim.lambda]p3, "whether the closure type is // trivially copyable" property can be changed by the implementation of // the language, so we shouldn't rely on it when issuing diagnostics. if (R->isLambda()) return; // Don't warn when the type is not copyable. for (const auto *Ctor : R->ctors()) { if (Ctor->isCopyConstructor() && Ctor->isDeleted()) return; } } if (!IsConstArg && IsTriviallyCopyable && !CheckTriviallyCopyableMove) return; bool IsVariable = isa<DeclRefExpr>(Arg); const auto *Var = IsVariable ? dyn_cast<DeclRefExpr>(Arg)->getDecl() : nullptr; auto Diag = diag(FileMoveRange.getBegin(), "std::move of the %select{|const }0" "%select{expression|variable %4}1 " "%select{|of the trivially-copyable type %5 }2" "has no effect; remove std::move()" "%select{| or make the variable non-const}3") << IsConstArg << IsVariable << IsTriviallyCopyable << (IsConstArg && IsVariable && !IsTriviallyCopyable) << Var << Arg->getType(); ReplaceCallWithArg(CallMove, Diag, SM, getLangOpts()); } else if (ReceivingExpr) { auto Diag = diag(FileMoveRange.getBegin(), "passing result of std::move() as a const reference " "argument; no move will actually happen"); ReplaceCallWithArg(CallMove, Diag, SM, getLangOpts()); } }
StringRef SourceManager::extractText(CharSourceRange Range, Optional<unsigned> BufferID) const { assert(Range.isValid() && "range should be valid"); if (!BufferID) BufferID = findBufferContainingLoc(Range.getStart()); StringRef Buffer = LLVMSourceMgr.getMemoryBuffer(*BufferID)->getBuffer(); return Buffer.substr(getLocOffsetInBuffer(Range.getStart(), *BufferID), Range.getByteLength()); }
bool FileInstrumentation::redirectInclude(FileID includedFileID, llvm::StringRef newFilePath) { CharSourceRange includeDirectiveRange = utils::getIncludeDirectiveSourceRange(includedFileID, m_sourceFile.getSourceManager(), m_astContext.getLangOpts()); if(includeDirectiveRange.isValid()) { m_rewriter.replace(includeDirectiveRange.getAsRange()) << "\"" << newFilePath << "\""; return true; } return false; }
/// Find the suitable set of lines to show to include a set of ranges. static llvm::Optional<std::pair<unsigned, unsigned>> findLinesForRange(const CharSourceRange &R, FileID FID, const SourceManager &SM) { if (!R.isValid()) return None; SourceLocation Begin = R.getBegin(); SourceLocation End = R.getEnd(); if (SM.getFileID(Begin) != FID || SM.getFileID(End) != FID) return None; return std::make_pair(SM.getExpansionLineNumber(Begin), SM.getExpansionLineNumber(End)); }
void AvoidConstParamsInDecls::check(const MatchFinder::MatchResult &Result) { const auto *Func = Result.Nodes.getNodeAs<FunctionDecl>("func"); const auto *Param = Result.Nodes.getNodeAs<ParmVarDecl>("param"); if (!Param->getType().isLocalConstQualified()) return; auto Diag = diag(Param->getBeginLoc(), "parameter %0 is const-qualified in the function " "declaration; const-qualification of parameters only has an " "effect in function definitions"); if (Param->getName().empty()) { for (unsigned int i = 0; i < Func->getNumParams(); ++i) { if (Param == Func->getParamDecl(i)) { Diag << (i + 1); break; } } } else { Diag << Param; } if (Param->getBeginLoc().isMacroID() != Param->getEndLoc().isMacroID()) { // Do not offer a suggestion if the part of the variable declaration comes // from a macro. return; } CharSourceRange FileRange = Lexer::makeFileCharRange( CharSourceRange::getTokenRange(getTypeRange(*Param)), *Result.SourceManager, getLangOpts()); if (!FileRange.isValid()) return; auto Tok = ConstTok(FileRange, Result); if (!Tok) return; Diag << FixItHint::CreateRemoval( CharSourceRange::getTokenRange(Tok->getLocation(), Tok->getLocation())); }
void MoveConstantArgumentCheck::check(const MatchFinder::MatchResult &Result) { const auto *CallMove = Result.Nodes.getNodeAs<CallExpr>("call-move"); const auto *ReceivingExpr = Result.Nodes.getNodeAs<Expr>("receiving-expr"); const Expr *Arg = CallMove->getArg(0); SourceManager &SM = Result.Context->getSourceManager(); CharSourceRange MoveRange = CharSourceRange::getCharRange(CallMove->getSourceRange()); CharSourceRange FileMoveRange = Lexer::makeFileCharRange(MoveRange, SM, getLangOpts()); if (!FileMoveRange.isValid()) return; bool IsConstArg = Arg->getType().isConstQualified(); bool IsTriviallyCopyable = Arg->getType().isTriviallyCopyableType(*Result.Context); if (IsConstArg || IsTriviallyCopyable) { bool IsVariable = isa<DeclRefExpr>(Arg); const auto *Var = IsVariable ? dyn_cast<DeclRefExpr>(Arg)->getDecl() : nullptr; auto Diag = diag(FileMoveRange.getBegin(), "std::move of the %select{|const }0" "%select{expression|variable %4}1 " "%select{|of the trivially-copyable type %5 }2" "has no effect; remove std::move()" "%select{| or make the variable non-const}3") << IsConstArg << IsVariable << IsTriviallyCopyable << (IsConstArg && IsVariable && !IsTriviallyCopyable) << Var << Arg->getType(); ReplaceCallWithArg(CallMove, Diag, SM, getLangOpts()); } else if (ReceivingExpr) { auto Diag = diag(FileMoveRange.getBegin(), "passing result of std::move() as a const reference " "argument; no move will actually happen"); ReplaceCallWithArg(CallMove, Diag, SM, getLangOpts()); } }
void UseOverride::check(const MatchFinder::MatchResult &Result) { const FunctionDecl *Method = Result.Nodes.getStmtAs<FunctionDecl>("method"); const SourceManager &Sources = *Result.SourceManager; assert(Method != nullptr); if (Method->getInstantiatedFromMemberFunction() != nullptr) Method = Method->getInstantiatedFromMemberFunction(); if (Method->isImplicit() || Method->getLocation().isMacroID() || Method->isOutOfLine()) return; bool HasVirtual = Method->isVirtualAsWritten(); bool HasOverride = Method->getAttr<OverrideAttr>(); bool HasFinal = Method->getAttr<FinalAttr>(); bool OnlyVirtualSpecified = HasVirtual && !HasOverride && !HasFinal; unsigned KeywordCount = HasVirtual + HasOverride + HasFinal; if (!OnlyVirtualSpecified && KeywordCount == 1) return; // Nothing to do. DiagnosticBuilder Diag = diag( Method->getLocation(), OnlyVirtualSpecified ? "Prefer using 'override' or (rarely) 'final' instead of 'virtual'" : "Annotate this function with 'override' or (rarely) 'final'"); CharSourceRange FileRange = Lexer::makeFileCharRange( CharSourceRange::getTokenRange(Method->getSourceRange()), Sources, Result.Context->getLangOpts()); if (!FileRange.isValid()) return; // FIXME: Instead of re-lexing and looking for specific macros such as // 'ABSTRACT', properly store the location of 'virtual' and '= 0' in each // FunctionDecl. SmallVector<Token, 16> Tokens = ParseTokens(FileRange, Result); // Add 'override' on inline declarations that don't already have it. if (!HasFinal && !HasOverride) { SourceLocation InsertLoc; StringRef ReplacementText = "override "; for (Token T : Tokens) { if (T.is(tok::kw___attribute)) { InsertLoc = T.getLocation(); break; } } if (Method->hasAttrs()) { for (const clang::Attr *A : Method->getAttrs()) { if (!A->isImplicit()) { SourceLocation Loc = Sources.getExpansionLoc(A->getRange().getBegin()); if (!InsertLoc.isValid() || Sources.isBeforeInTranslationUnit(Loc, InsertLoc)) InsertLoc = Loc; } } } if (InsertLoc.isInvalid() && Method->doesThisDeclarationHaveABody() && Method->getBody() && !Method->isDefaulted()) InsertLoc = Method->getBody()->getLocStart(); if (!InsertLoc.isValid()) { if (Tokens.size() > 2 && GetText(Tokens.back(), Sources) == "0" && GetText(Tokens[Tokens.size() - 2], Sources) == "=") { InsertLoc = Tokens[Tokens.size() - 2].getLocation(); } else if (GetText(Tokens.back(), Sources) == "ABSTRACT") { InsertLoc = Tokens.back().getLocation(); } } if (!InsertLoc.isValid()) { InsertLoc = FileRange.getEnd(); ReplacementText = " override"; } Diag << FixItHint::CreateInsertion(InsertLoc, ReplacementText); } if (HasFinal && HasOverride) { SourceLocation OverrideLoc = Method->getAttr<OverrideAttr>()->getLocation(); Diag << FixItHint::CreateRemoval( CharSourceRange::getTokenRange(OverrideLoc, OverrideLoc)); } if (Method->isVirtualAsWritten()) { for (Token Tok : Tokens) { if (Tok.is(tok::kw_virtual)) { Diag << FixItHint::CreateRemoval(CharSourceRange::getTokenRange( Tok.getLocation(), Tok.getLocation())); break; } } } }
void UseOverrideCheck::check(const MatchFinder::MatchResult &Result) { const auto *Method = Result.Nodes.getNodeAs<FunctionDecl>("method"); const SourceManager &Sources = *Result.SourceManager; assert(Method != nullptr); if (Method->getInstantiatedFromMemberFunction() != nullptr) Method = Method->getInstantiatedFromMemberFunction(); if (Method->isImplicit() || Method->getLocation().isMacroID() || Method->isOutOfLine()) return; bool HasVirtual = Method->isVirtualAsWritten(); bool HasOverride = Method->getAttr<OverrideAttr>(); bool HasFinal = Method->getAttr<FinalAttr>(); bool OnlyVirtualSpecified = HasVirtual && !HasOverride && !HasFinal; unsigned KeywordCount = HasVirtual + HasOverride + HasFinal; if (!OnlyVirtualSpecified && KeywordCount == 1) return; // Nothing to do. std::string Message; if (OnlyVirtualSpecified) { Message = "prefer using 'override' or (rarely) 'final' instead of 'virtual'"; } else if (KeywordCount == 0) { Message = "annotate this function with 'override' or (rarely) 'final'"; } else { StringRef Redundant = HasVirtual ? (HasOverride && HasFinal ? "'virtual' and 'override' are" : "'virtual' is") : "'override' is"; StringRef Correct = HasFinal ? "'final'" : "'override'"; Message = (llvm::Twine(Redundant) + " redundant since the function is already declared " + Correct) .str(); } DiagnosticBuilder Diag = diag(Method->getLocation(), Message); CharSourceRange FileRange = Lexer::makeFileCharRange( CharSourceRange::getTokenRange(Method->getSourceRange()), Sources, getLangOpts()); if (!FileRange.isValid()) return; // FIXME: Instead of re-lexing and looking for specific macros such as // 'ABSTRACT', properly store the location of 'virtual' and '= 0' in each // FunctionDecl. SmallVector<Token, 16> Tokens = ParseTokens(FileRange, Result); // Add 'override' on inline declarations that don't already have it. if (!HasFinal && !HasOverride) { SourceLocation InsertLoc; StringRef ReplacementText = "override "; SourceLocation MethodLoc = Method->getLocation(); for (Token T : Tokens) { if (T.is(tok::kw___attribute) && !Sources.isBeforeInTranslationUnit(T.getLocation(), MethodLoc)) { InsertLoc = T.getLocation(); break; } } if (Method->hasAttrs()) { for (const clang::Attr *A : Method->getAttrs()) { if (!A->isImplicit() && !A->isInherited()) { SourceLocation Loc = Sources.getExpansionLoc(A->getRange().getBegin()); if ((!InsertLoc.isValid() || Sources.isBeforeInTranslationUnit(Loc, InsertLoc)) && !Sources.isBeforeInTranslationUnit(Loc, MethodLoc)) InsertLoc = Loc; } } } if (InsertLoc.isInvalid() && Method->doesThisDeclarationHaveABody() && Method->getBody() && !Method->isDefaulted()) { // For methods with inline definition, add the override keyword at the // end of the declaration of the function, but prefer to put it on the // same line as the declaration if the beginning brace for the start of // the body falls on the next line. Token LastNonCommentToken; for (Token T : Tokens) { if (!T.is(tok::comment)) { LastNonCommentToken = T; } } InsertLoc = LastNonCommentToken.getEndLoc(); ReplacementText = " override"; } if (!InsertLoc.isValid()) { // For declarations marked with "= 0" or "= [default|delete]", the end // location will point until after those markings. Therefore, the override // keyword shouldn't be inserted at the end, but before the '='. if (Tokens.size() > 2 && (GetText(Tokens.back(), Sources) == "0" || Tokens.back().is(tok::kw_default) || Tokens.back().is(tok::kw_delete)) && GetText(Tokens[Tokens.size() - 2], Sources) == "=") { InsertLoc = Tokens[Tokens.size() - 2].getLocation(); // Check if we need to insert a space. if ((Tokens[Tokens.size() - 2].getFlags() & Token::LeadingSpace) == 0) ReplacementText = " override "; } else if (GetText(Tokens.back(), Sources) == "ABSTRACT") { InsertLoc = Tokens.back().getLocation(); } } if (!InsertLoc.isValid()) { InsertLoc = FileRange.getEnd(); ReplacementText = " override"; } Diag << FixItHint::CreateInsertion(InsertLoc, ReplacementText); } if (HasFinal && HasOverride) { SourceLocation OverrideLoc = Method->getAttr<OverrideAttr>()->getLocation(); Diag << FixItHint::CreateRemoval( CharSourceRange::getTokenRange(OverrideLoc, OverrideLoc)); } if (HasVirtual) { for (Token Tok : Tokens) { if (Tok.is(tok::kw_virtual)) { Diag << FixItHint::CreateRemoval(CharSourceRange::getTokenRange( Tok.getLocation(), Tok.getLocation())); break; } } } }
/// \brief Highlight a SourceRange (with ~'s) for any characters on LineNo. static void highlightRange(const CharSourceRange &R, unsigned LineNo, FileID FID, const SourceColumnMap &map, std::string &CaretLine, const SourceManager &SM, const LangOptions &LangOpts) { if (!R.isValid()) return; SourceLocation Begin = R.getBegin(); SourceLocation End = R.getEnd(); unsigned StartLineNo = SM.getExpansionLineNumber(Begin); if (StartLineNo > LineNo || SM.getFileID(Begin) != FID) return; // No intersection. unsigned EndLineNo = SM.getExpansionLineNumber(End); if (EndLineNo < LineNo || SM.getFileID(End) != FID) return; // No intersection. // Compute the column number of the start. unsigned StartColNo = 0; if (StartLineNo == LineNo) { StartColNo = SM.getExpansionColumnNumber(Begin); if (StartColNo) --StartColNo; // Zero base the col #. } // Compute the column number of the end. unsigned EndColNo = map.getSourceLine().size(); if (EndLineNo == LineNo) { EndColNo = SM.getExpansionColumnNumber(End); if (EndColNo) { --EndColNo; // Zero base the col #. // Add in the length of the token, so that we cover multi-char tokens if // this is a token range. if (R.isTokenRange()) EndColNo += Lexer::MeasureTokenLength(End, SM, LangOpts); } else { EndColNo = CaretLine.size(); } } assert(StartColNo <= EndColNo && "Invalid range!"); // Check that a token range does not highlight only whitespace. if (R.isTokenRange()) { // Pick the first non-whitespace column. while (StartColNo < map.getSourceLine().size() && (map.getSourceLine()[StartColNo] == ' ' || map.getSourceLine()[StartColNo] == '\t')) StartColNo = map.startOfNextColumn(StartColNo); // Pick the last non-whitespace column. if (EndColNo > map.getSourceLine().size()) EndColNo = map.getSourceLine().size(); while (EndColNo-1 && (map.getSourceLine()[EndColNo-1] == ' ' || map.getSourceLine()[EndColNo-1] == '\t')) EndColNo = map.startOfPreviousColumn(EndColNo); // If the start/end passed each other, then we are trying to highlight a // range that just exists in whitespace, which must be some sort of other // bug. assert(StartColNo <= EndColNo && "Trying to highlight whitespace??"); } assert(StartColNo <= map.getSourceLine().size() && "Invalid range!"); assert(EndColNo <= map.getSourceLine().size() && "Invalid range!"); // Fill the range with ~'s. StartColNo = map.byteToContainingColumn(StartColNo); EndColNo = map.byteToContainingColumn(EndColNo); assert(StartColNo <= EndColNo && "Invalid range!"); if (CaretLine.size() < EndColNo) CaretLine.resize(EndColNo,' '); std::fill(CaretLine.begin()+StartColNo,CaretLine.begin()+EndColNo,'~'); }
/// \brief Highlight a SourceRange (with ~'s) for any characters on LineNo. void TextDiagnostic::highlightRange(const CharSourceRange &R, unsigned LineNo, FileID FID, const SourceColumnMap &map, std::string &CaretLine) { if (!R.isValid()) return; SourceLocation Begin = SM.getExpansionLoc(R.getBegin()); SourceLocation End = SM.getExpansionLoc(R.getEnd()); // If the End location and the start location are the same and are a macro // location, then the range was something that came from a macro expansion // or _Pragma. If this is an object-like macro, the best we can do is to // highlight the range. If this is a function-like macro, we'd also like to // highlight the arguments. if (Begin == End && R.getEnd().isMacroID()) End = SM.getExpansionRange(R.getEnd()).second; unsigned StartLineNo = SM.getExpansionLineNumber(Begin); if (StartLineNo > LineNo || SM.getFileID(Begin) != FID) return; // No intersection. unsigned EndLineNo = SM.getExpansionLineNumber(End); if (EndLineNo < LineNo || SM.getFileID(End) != FID) return; // No intersection. // Compute the column number of the start. unsigned StartColNo = 0; if (StartLineNo == LineNo) { StartColNo = SM.getExpansionColumnNumber(Begin); if (StartColNo) --StartColNo; // Zero base the col #. } // Compute the column number of the end. unsigned EndColNo = map.getSourceLine().size(); if (EndLineNo == LineNo) { EndColNo = SM.getExpansionColumnNumber(End); if (EndColNo) { --EndColNo; // Zero base the col #. // Add in the length of the token, so that we cover multi-char tokens if // this is a token range. if (R.isTokenRange()) EndColNo += Lexer::MeasureTokenLength(End, SM, LangOpts); } else { EndColNo = CaretLine.size(); } } assert(StartColNo <= EndColNo && "Invalid range!"); // Check that a token range does not highlight only whitespace. if (R.isTokenRange()) { // Pick the first non-whitespace column. while (StartColNo < map.getSourceLine().size() && (map.getSourceLine()[StartColNo] == ' ' || map.getSourceLine()[StartColNo] == '\t')) ++StartColNo; // Pick the last non-whitespace column. if (EndColNo > map.getSourceLine().size()) EndColNo = map.getSourceLine().size(); while (EndColNo-1 && (map.getSourceLine()[EndColNo-1] == ' ' || map.getSourceLine()[EndColNo-1] == '\t')) --EndColNo; // If the start/end passed each other, then we are trying to highlight a // range that just exists in whitespace, which must be some sort of other // bug. assert(StartColNo <= EndColNo && "Trying to highlight whitespace??"); } assert(StartColNo <= map.getSourceLine().size() && "Invalid range!"); assert(EndColNo <= map.getSourceLine().size() && "Invalid range!"); // Fill the range with ~'s. StartColNo = map.byteToColumn(StartColNo); EndColNo = map.byteToColumn(EndColNo); assert(StartColNo <= EndColNo && "Invalid range!"); if (CaretLine.size() < EndColNo) CaretLine.resize(EndColNo,' '); std::fill(CaretLine.begin()+StartColNo,CaretLine.begin()+EndColNo,'~'); }
/// HighlightRange - Given a SourceRange and a line number, highlight (with ~'s) /// any characters in LineNo that intersect the SourceRange. void TextDiagnosticPrinter::HighlightRange(const CharSourceRange &R, const SourceManager &SM, unsigned LineNo, FileID FID, std::string &CaretLine, const std::string &SourceLine) { assert(CaretLine.size() == SourceLine.size() && "Expect a correspondence between source and caret line!"); if (!R.isValid()) return; SourceLocation Begin = SM.getInstantiationLoc(R.getBegin()); SourceLocation End = SM.getInstantiationLoc(R.getEnd()); // If the End location and the start location are the same and are a macro // location, then the range was something that came from a macro expansion // or _Pragma. If this is an object-like macro, the best we can do is to // highlight the range. If this is a function-like macro, we'd also like to // highlight the arguments. if (Begin == End && R.getEnd().isMacroID()) End = SM.getInstantiationRange(R.getEnd()).second; unsigned StartLineNo = SM.getInstantiationLineNumber(Begin); if (StartLineNo > LineNo || SM.getFileID(Begin) != FID) return; // No intersection. unsigned EndLineNo = SM.getInstantiationLineNumber(End); if (EndLineNo < LineNo || SM.getFileID(End) != FID) return; // No intersection. // Compute the column number of the start. unsigned StartColNo = 0; if (StartLineNo == LineNo) { StartColNo = SM.getInstantiationColumnNumber(Begin); if (StartColNo) --StartColNo; // Zero base the col #. } // Compute the column number of the end. unsigned EndColNo = CaretLine.size(); if (EndLineNo == LineNo) { EndColNo = SM.getInstantiationColumnNumber(End); if (EndColNo) { --EndColNo; // Zero base the col #. // Add in the length of the token, so that we cover multi-char tokens if // this is a token range. if (R.isTokenRange()) EndColNo += Lexer::MeasureTokenLength(End, SM, *LangOpts); } else { EndColNo = CaretLine.size(); } } assert(StartColNo <= EndColNo && "Invalid range!"); // Check that a token range does not highlight only whitespace. if (R.isTokenRange()) { // Pick the first non-whitespace column. while (StartColNo < SourceLine.size() && (SourceLine[StartColNo] == ' ' || SourceLine[StartColNo] == '\t')) ++StartColNo; // Pick the last non-whitespace column. if (EndColNo > SourceLine.size()) EndColNo = SourceLine.size(); while (EndColNo-1 && (SourceLine[EndColNo-1] == ' ' || SourceLine[EndColNo-1] == '\t')) --EndColNo; // If the start/end passed each other, then we are trying to highlight a range // that just exists in whitespace, which must be some sort of other bug. assert(StartColNo <= EndColNo && "Trying to highlight whitespace??"); } // Fill the range with ~'s. for (unsigned i = StartColNo; i < EndColNo; ++i) CaretLine[i] = '~'; }