blob: 7250e4ccb8c699b710bf4192261768a11eedcc63 [file] [log] [blame]
//===--- UnintendedCharOstreamOutputCheck.cpp - clang-tidy ----------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "UnintendedCharOstreamOutputCheck.h"
#include "clang/AST/Type.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Tooling/FixIt.h"
using namespace clang::ast_matchers;
namespace clang::tidy::bugprone {
namespace {
// check if the type is unsigned char or signed char
AST_MATCHER(Type, isNumericChar) {
return Node.isSpecificBuiltinType(BuiltinType::SChar) ||
Node.isSpecificBuiltinType(BuiltinType::UChar);
}
// check if the type is char
AST_MATCHER(Type, isChar) {
return Node.isSpecificBuiltinType(BuiltinType::Char_S) ||
Node.isSpecificBuiltinType(BuiltinType::Char_U);
}
} // namespace
UnintendedCharOstreamOutputCheck::UnintendedCharOstreamOutputCheck(
StringRef Name, ClangTidyContext *Context)
: ClangTidyCheck(Name, Context), CastTypeName(Options.get("CastTypeName")) {
}
void UnintendedCharOstreamOutputCheck::storeOptions(
ClangTidyOptions::OptionMap &Opts) {
if (CastTypeName.has_value())
Options.store(Opts, "CastTypeName", CastTypeName.value());
}
void UnintendedCharOstreamOutputCheck::registerMatchers(MatchFinder *Finder) {
auto BasicOstream =
cxxRecordDecl(hasName("::std::basic_ostream"),
// only basic_ostream<char, Traits> has overload operator<<
// with char / unsigned char / signed char
classTemplateSpecializationDecl(
hasTemplateArgument(0, refersToType(isChar()))));
Finder->addMatcher(
cxxOperatorCallExpr(
hasOverloadedOperatorName("<<"),
hasLHS(hasType(hasUnqualifiedDesugaredType(
recordType(hasDeclaration(cxxRecordDecl(
anyOf(BasicOstream, isDerivedFrom(BasicOstream)))))))),
hasRHS(hasType(hasUnqualifiedDesugaredType(isNumericChar()))))
.bind("x"),
this);
}
void UnintendedCharOstreamOutputCheck::check(
const MatchFinder::MatchResult &Result) {
const auto *Call = Result.Nodes.getNodeAs<CXXOperatorCallExpr>("x");
const Expr *Value = Call->getArg(1);
const SourceRange SourceRange = Value->getSourceRange();
DiagnosticBuilder Builder =
diag(Call->getOperatorLoc(),
"%0 passed to 'operator<<' outputs as character instead of integer. "
"cast to 'unsigned int' to print numeric value or cast to 'char' to "
"print as character")
<< Value->getType() << SourceRange;
QualType T = Value->getType();
const Type *UnqualifiedDesugaredType = T->getUnqualifiedDesugaredType();
llvm::StringRef CastType = CastTypeName.value_or(
UnqualifiedDesugaredType->isSpecificBuiltinType(BuiltinType::SChar)
? "int"
: "unsigned int");
Builder << FixItHint::CreateReplacement(
SourceRange, ("static_cast<" + CastType + ">(" +
tooling::fixit::getText(*Value, *Result.Context) + ")")
.str());
}
} // namespace clang::tidy::bugprone