llvm-project/clang-tools-extra/clang-tidy/bugprone/UnintendedCharOstreamOutputCheck.cpp
Congcong Cai 06814834a6
[clang-tidy] treat unsigned char and signed char as char type by default in bugprone-unintended-char-ostream-output (#134870)
Add `AllowedTypes` options to support custom defined char like type.
treat `unsigned char` and `signed char` as char like type by default.
The allowed types only effect when the var decl or explicit cast to this
non-canonical type names.

Fixed: #133425
2025-04-13 12:09:50 +08:00

105 lines
4.1 KiB
C++

//===--- 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 "../utils/Matchers.h"
#include "../utils/OptionsUtils.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),
AllowedTypes(utils::options::parseStringList(
Options.get("AllowedTypes", "unsigned char;signed char"))),
CastTypeName(Options.get("CastTypeName")) {}
void UnintendedCharOstreamOutputCheck::storeOptions(
ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, "AllowedTypes",
utils::options::serializeStringList(AllowedTypes));
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()))));
auto IsDeclRefExprFromAllowedTypes = declRefExpr(to(varDecl(
hasType(matchers::matchesAnyListedTypeName(AllowedTypes, false)))));
auto IsExplicitCastExprFromAllowedTypes = explicitCastExpr(hasDestinationType(
matchers::matchesAnyListedTypeName(AllowedTypes, false)));
Finder->addMatcher(
cxxOperatorCallExpr(
hasOverloadedOperatorName("<<"),
hasLHS(hasType(hasUnqualifiedDesugaredType(
recordType(hasDeclaration(cxxRecordDecl(
anyOf(BasicOstream, isDerivedFrom(BasicOstream)))))))),
hasRHS(expr(hasType(hasUnqualifiedDesugaredType(isNumericChar())),
unless(ignoringParenImpCasts(
anyOf(IsDeclRefExprFromAllowedTypes,
IsExplicitCastExprFromAllowedTypes))))))
.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