105 lines · cpp
1//===----------------------------------------------------------------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include "UnintendedCharOstreamOutputCheck.h"10#include "../utils/Matchers.h"11#include "../utils/OptionsUtils.h"12#include "clang/AST/Type.h"13#include "clang/ASTMatchers/ASTMatchFinder.h"14#include "clang/ASTMatchers/ASTMatchers.h"15#include "clang/Basic/Diagnostic.h"16#include "clang/Tooling/FixIt.h"17 18using namespace clang::ast_matchers;19 20namespace clang::tidy::bugprone {21 22namespace {23 24// check if the type is unsigned char or signed char25AST_MATCHER(Type, isNumericChar) {26 return Node.isSpecificBuiltinType(BuiltinType::SChar) ||27 Node.isSpecificBuiltinType(BuiltinType::UChar);28}29 30// check if the type is char31AST_MATCHER(Type, isChar) {32 return Node.isSpecificBuiltinType(BuiltinType::Char_S) ||33 Node.isSpecificBuiltinType(BuiltinType::Char_U);34}35 36} // namespace37 38UnintendedCharOstreamOutputCheck::UnintendedCharOstreamOutputCheck(39 StringRef Name, ClangTidyContext *Context)40 : ClangTidyCheck(Name, Context),41 AllowedTypes(utils::options::parseStringList(42 Options.get("AllowedTypes", "unsigned char;signed char"))),43 CastTypeName(Options.get("CastTypeName")) {}44void UnintendedCharOstreamOutputCheck::storeOptions(45 ClangTidyOptions::OptionMap &Opts) {46 Options.store(Opts, "AllowedTypes",47 utils::options::serializeStringList(AllowedTypes));48 if (CastTypeName.has_value())49 Options.store(Opts, "CastTypeName", CastTypeName.value());50}51 52void UnintendedCharOstreamOutputCheck::registerMatchers(MatchFinder *Finder) {53 auto BasicOstream =54 cxxRecordDecl(hasName("::std::basic_ostream"),55 // only basic_ostream<char, Traits> has overload operator<<56 // with char / unsigned char / signed char57 classTemplateSpecializationDecl(58 hasTemplateArgument(0, refersToType(isChar()))));59 auto IsDeclRefExprFromAllowedTypes = declRefExpr(to(varDecl(60 hasType(matchers::matchesAnyListedTypeName(AllowedTypes, false)))));61 auto IsExplicitCastExprFromAllowedTypes = explicitCastExpr(hasDestinationType(62 matchers::matchesAnyListedTypeName(AllowedTypes, false)));63 Finder->addMatcher(64 cxxOperatorCallExpr(65 hasOverloadedOperatorName("<<"),66 hasLHS(hasType(hasUnqualifiedDesugaredType(67 recordType(hasDeclaration(cxxRecordDecl(68 anyOf(BasicOstream, isDerivedFrom(BasicOstream)))))))),69 hasRHS(expr(hasType(hasUnqualifiedDesugaredType(isNumericChar())),70 unless(ignoringParenImpCasts(71 anyOf(IsDeclRefExprFromAllowedTypes,72 IsExplicitCastExprFromAllowedTypes))))))73 .bind("x"),74 this);75}76 77void UnintendedCharOstreamOutputCheck::check(78 const MatchFinder::MatchResult &Result) {79 const auto *Call = Result.Nodes.getNodeAs<CXXOperatorCallExpr>("x");80 const Expr *Value = Call->getArg(1);81 const SourceRange SourceRange = Value->getSourceRange();82 83 const DiagnosticBuilder Builder =84 diag(Call->getOperatorLoc(),85 "%0 passed to 'operator<<' outputs as character instead of integer. "86 "cast to 'unsigned int' to print numeric value or cast to 'char' to "87 "print as character")88 << Value->getType() << SourceRange;89 90 const QualType T = Value->getType();91 const Type *UnqualifiedDesugaredType = T->getUnqualifiedDesugaredType();92 93 const llvm::StringRef CastType = CastTypeName.value_or(94 UnqualifiedDesugaredType->isSpecificBuiltinType(BuiltinType::SChar)95 ? "int"96 : "unsigned int");97 98 Builder << FixItHint::CreateReplacement(99 SourceRange, ("static_cast<" + CastType + ">(" +100 tooling::fixit::getText(*Value, *Result.Context) + ")")101 .str());102}103 104} // namespace clang::tidy::bugprone105