#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h"
#include "clang/Lex/Lexer.h"
#include "llvm/ADT/APSInt.h"
using namespace clang;
using namespace ento;
using namespace ast_matchers;
namespace {
class NumberObjectConversionChecker : public Checker<check::ASTCodeBody> {
public:
bool Pedantic;
void checkASTCodeBody(const Decl *D, AnalysisManager &AM,
BugReporter &BR) const;
};
class Callback : public MatchFinder::MatchCallback {
const NumberObjectConversionChecker *C;
BugReporter &BR;
AnalysisDeclContext *ADC;
public:
Callback(const NumberObjectConversionChecker *C,
BugReporter &BR, AnalysisDeclContext *ADC)
: C(C), BR(BR), ADC(ADC) {}
void run(const MatchFinder::MatchResult &Result) override;
};
}
void Callback::run(const MatchFinder::MatchResult &Result) {
bool IsPedanticMatch =
(Result.Nodes.getNodeAs<Stmt>("pedantic") != nullptr);
if (IsPedanticMatch && !C->Pedantic)
return;
ASTContext &ACtx = ADC->getASTContext();
if (const Expr *CheckIfNull =
Result.Nodes.getNodeAs<Expr>("check_if_null")) {
bool MacroIndicatesWeShouldSkipTheCheck = false;
SourceLocation Loc = CheckIfNull->getBeginLoc();
if (Loc.isMacroID()) {
StringRef MacroName = Lexer::getImmediateMacroName(
Loc, ACtx.getSourceManager(), ACtx.getLangOpts());
if (MacroName == "NULL" || MacroName == "nil")
return;
if (MacroName == "YES" || MacroName == "NO")
MacroIndicatesWeShouldSkipTheCheck = true;
}
if (!MacroIndicatesWeShouldSkipTheCheck) {
Expr::EvalResult EVResult;
if (CheckIfNull->IgnoreParenCasts()->EvaluateAsInt(
EVResult, ACtx, Expr::SE_AllowSideEffects)) {
llvm::APSInt Result = EVResult.Val.getInt();
if (Result == 0) {
if (!C->Pedantic)
return;
IsPedanticMatch = true;
}
}
}
}
const Stmt *Conv = Result.Nodes.getNodeAs<Stmt>("conv");
assert(Conv);
const Expr *ConvertedCObject = Result.Nodes.getNodeAs<Expr>("c_object");
const Expr *ConvertedCppObject = Result.Nodes.getNodeAs<Expr>("cpp_object");
const Expr *ConvertedObjCObject = Result.Nodes.getNodeAs<Expr>("objc_object");
bool IsCpp = (ConvertedCppObject != nullptr);
bool IsObjC = (ConvertedObjCObject != nullptr);
const Expr *Obj = IsObjC ? ConvertedObjCObject
: IsCpp ? ConvertedCppObject
: ConvertedCObject;
assert(Obj);
bool IsComparison =
(Result.Nodes.getNodeAs<Stmt>("comparison") != nullptr);
bool IsOSNumber =
(Result.Nodes.getNodeAs<Decl>("osnumber") != nullptr);
bool IsInteger =
(Result.Nodes.getNodeAs<QualType>("int_type") != nullptr);
bool IsObjCBool =
(Result.Nodes.getNodeAs<QualType>("objc_bool_type") != nullptr);
bool IsCppBool =
(Result.Nodes.getNodeAs<QualType>("cpp_bool_type") != nullptr);
llvm::SmallString<64> Msg;
llvm::raw_svector_ostream OS(Msg);
QualType ObjT = Obj->getType().getUnqualifiedType();
if (IsCpp) {
assert(ObjT.getCanonicalType()->isPointerType());
ObjT = ACtx.getPointerType(
ObjT->getPointeeType().getCanonicalType().getUnqualifiedType());
}
if (IsComparison)
OS << "Comparing ";
else
OS << "Converting ";
OS << "a pointer value of type '" << ObjT << "' to a ";
std::string EuphemismForPlain = "primitive";
std::string SuggestedApi = IsObjC ? (IsInteger ? "" : "-boolValue")
: IsCpp ? (IsOSNumber ? "" : "getValue()")
: "CFNumberGetValue()";
if (SuggestedApi.empty()) {
SuggestedApi =
"a method on '" + ObjT.getAsString() + "' to get the scalar value";
EuphemismForPlain = "scalar";
}
if (IsInteger)
OS << EuphemismForPlain << " integer value";
else if (IsObjCBool)
OS << EuphemismForPlain << " BOOL value";
else if (IsCppBool)
OS << EuphemismForPlain << " bool value";
else OS << EuphemismForPlain << " boolean value";
if (IsPedanticMatch)
OS << "; instead, either compare the pointer to "
<< (IsObjC ? "nil" : IsCpp ? "nullptr" : "NULL") << " or ";
else
OS << "; did you mean to ";
if (IsComparison)
OS << "compare the result of calling " << SuggestedApi;
else
OS << "call " << SuggestedApi;
if (!IsPedanticMatch)
OS << "?";
BR.EmitBasicReport(
ADC->getDecl(), C, "Suspicious number object conversion", "Logic error",
OS.str(),
PathDiagnosticLocation::createBegin(Obj, BR.getSourceManager(), ADC),
Conv->getSourceRange());
}
void NumberObjectConversionChecker::checkASTCodeBody(const Decl *D,
AnalysisManager &AM,
BugReporter &BR) const {
auto CSuspiciousNumberObjectExprM =
expr(ignoringParenImpCasts(
expr(hasType(
typedefType(hasDeclaration(anyOf(
typedefDecl(hasName("CFNumberRef")),
typedefDecl(hasName("CFBooleanRef")))))))
.bind("c_object")));
auto CppSuspiciousNumberObjectExprM =
expr(ignoringParenImpCasts(
expr(hasType(hasCanonicalType(
pointerType(pointee(hasCanonicalType(
recordType(hasDeclaration(
anyOf(
cxxRecordDecl(hasName("OSBoolean")),
cxxRecordDecl(hasName("OSNumber"))
.bind("osnumber"))))))))))
.bind("cpp_object")));
auto ObjCSuspiciousNumberObjectExprM =
expr(ignoringParenImpCasts(
expr(hasType(hasCanonicalType(
objcObjectPointerType(pointee(
qualType(hasCanonicalType(
qualType(hasDeclaration(
objcInterfaceDecl(hasName("NSNumber")))))))))))
.bind("objc_object")));
auto SuspiciousNumberObjectExprM = anyOf(
CSuspiciousNumberObjectExprM,
CppSuspiciousNumberObjectExprM,
ObjCSuspiciousNumberObjectExprM);
auto AnotherSuspiciousNumberObjectExprM =
expr(anyOf(
equalsBoundNode("c_object"),
equalsBoundNode("objc_object"),
equalsBoundNode("cpp_object")));
auto ObjCSuspiciousScalarBooleanTypeM =
qualType(typedefType(hasDeclaration(
typedefDecl(hasName("BOOL"))))).bind("objc_bool_type");
auto SuspiciousScalarBooleanTypeM =
qualType(anyOf(qualType(booleanType()).bind("cpp_bool_type"),
ObjCSuspiciousScalarBooleanTypeM));
auto SuspiciousScalarNumberTypeM =
qualType(hasCanonicalType(isInteger()),
unless(typedefType(hasDeclaration(
typedefDecl(matchesName("^::u?intptr_t$"))))))
.bind("int_type");
auto SuspiciousScalarTypeM =
qualType(anyOf(SuspiciousScalarBooleanTypeM,
SuspiciousScalarNumberTypeM));
auto SuspiciousScalarExprM =
expr(ignoringParenImpCasts(expr(hasType(SuspiciousScalarTypeM))));
auto ConversionThroughAssignmentM =
binaryOperator(allOf(hasOperatorName("="),
hasLHS(SuspiciousScalarExprM),
hasRHS(SuspiciousNumberObjectExprM)));
auto ConversionThroughBranchingM =
ifStmt(allOf(
hasCondition(SuspiciousNumberObjectExprM),
unless(hasConditionVariableStatement(declStmt())
))).bind("pedantic");
auto ConversionThroughCallM =
callExpr(hasAnyArgument(allOf(hasType(SuspiciousScalarTypeM),
ignoringParenImpCasts(
SuspiciousNumberObjectExprM))));
auto ConversionThroughEquivalenceM =
binaryOperator(allOf(anyOf(hasOperatorName("=="), hasOperatorName("!=")),
hasEitherOperand(SuspiciousNumberObjectExprM),
hasEitherOperand(SuspiciousScalarExprM
.bind("check_if_null"))))
.bind("comparison");
auto ConversionThroughComparisonM =
binaryOperator(allOf(anyOf(hasOperatorName(">="), hasOperatorName(">"),
hasOperatorName("<="), hasOperatorName("<")),
hasEitherOperand(SuspiciousNumberObjectExprM),
hasEitherOperand(SuspiciousScalarExprM)))
.bind("comparison");
auto ConversionThroughConditionalOperatorM =
conditionalOperator(allOf(
hasCondition(SuspiciousNumberObjectExprM),
unless(hasTrueExpression(
hasDescendant(AnotherSuspiciousNumberObjectExprM))),
unless(hasFalseExpression(
hasDescendant(AnotherSuspiciousNumberObjectExprM)))))
.bind("pedantic");
auto ConversionThroughExclamationMarkM =
unaryOperator(allOf(hasOperatorName("!"),
has(expr(SuspiciousNumberObjectExprM))))
.bind("pedantic");
auto ConversionThroughExplicitBooleanCastM =
explicitCastExpr(allOf(hasType(SuspiciousScalarBooleanTypeM),
has(expr(SuspiciousNumberObjectExprM))));
auto ConversionThroughExplicitNumberCastM =
explicitCastExpr(allOf(hasType(SuspiciousScalarNumberTypeM),
has(expr(SuspiciousNumberObjectExprM))));
auto ConversionThroughInitializerM =
declStmt(hasSingleDecl(
varDecl(hasType(SuspiciousScalarTypeM),
hasInitializer(SuspiciousNumberObjectExprM))));
auto FinalM = stmt(anyOf(ConversionThroughAssignmentM,
ConversionThroughBranchingM,
ConversionThroughCallM,
ConversionThroughComparisonM,
ConversionThroughConditionalOperatorM,
ConversionThroughEquivalenceM,
ConversionThroughExclamationMarkM,
ConversionThroughExplicitBooleanCastM,
ConversionThroughExplicitNumberCastM,
ConversionThroughInitializerM)).bind("conv");
MatchFinder F;
Callback CB(this, BR, AM.getAnalysisDeclContext(D));
F.addMatcher(traverse(TK_AsIs, stmt(forEachDescendant(FinalM))), &CB);
F.match(*D->getBody(), AM.getASTContext());
}
void ento::registerNumberObjectConversionChecker(CheckerManager &Mgr) {
NumberObjectConversionChecker *Chk =
Mgr.registerChecker<NumberObjectConversionChecker>();
Chk->Pedantic =
Mgr.getAnalyzerOptions().getCheckerBooleanOption(Chk, "Pedantic");
}
bool ento::shouldRegisterNumberObjectConversionChecker(const CheckerManager &mgr) {
return true;
}