17330f729Sjoerg // MacOSXAPIChecker.h - Checks proper use of various MacOS X APIs --*- C++ -*-//
27330f729Sjoerg //
37330f729Sjoerg // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
47330f729Sjoerg // See https://llvm.org/LICENSE.txt for license information.
57330f729Sjoerg // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
67330f729Sjoerg //
77330f729Sjoerg //===----------------------------------------------------------------------===//
87330f729Sjoerg //
97330f729Sjoerg // This defines MacOSXAPIChecker, which is an assortment of checks on calls
107330f729Sjoerg // to various, widely used Apple APIs.
117330f729Sjoerg //
127330f729Sjoerg // FIXME: What's currently in BasicObjCFoundationChecks.cpp should be migrated
137330f729Sjoerg // to here, using the new Checker interface.
147330f729Sjoerg //
157330f729Sjoerg //===----------------------------------------------------------------------===//
167330f729Sjoerg
17*e038c9c4Sjoerg #include "clang/AST/Attr.h"
187330f729Sjoerg #include "clang/Basic/TargetInfo.h"
19*e038c9c4Sjoerg #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
207330f729Sjoerg #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
217330f729Sjoerg #include "clang/StaticAnalyzer/Core/Checker.h"
227330f729Sjoerg #include "clang/StaticAnalyzer/Core/CheckerManager.h"
237330f729Sjoerg #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
247330f729Sjoerg #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
257330f729Sjoerg #include "llvm/ADT/SmallString.h"
267330f729Sjoerg #include "llvm/ADT/StringSwitch.h"
277330f729Sjoerg #include "llvm/Support/raw_ostream.h"
287330f729Sjoerg
297330f729Sjoerg using namespace clang;
307330f729Sjoerg using namespace ento;
317330f729Sjoerg
327330f729Sjoerg namespace {
337330f729Sjoerg class MacOSXAPIChecker : public Checker< check::PreStmt<CallExpr> > {
347330f729Sjoerg mutable std::unique_ptr<BugType> BT_dispatchOnce;
357330f729Sjoerg
367330f729Sjoerg static const ObjCIvarRegion *getParentIvarRegion(const MemRegion *R);
377330f729Sjoerg
387330f729Sjoerg public:
397330f729Sjoerg void checkPreStmt(const CallExpr *CE, CheckerContext &C) const;
407330f729Sjoerg
417330f729Sjoerg void CheckDispatchOnce(CheckerContext &C, const CallExpr *CE,
427330f729Sjoerg StringRef FName) const;
437330f729Sjoerg
447330f729Sjoerg typedef void (MacOSXAPIChecker::*SubChecker)(CheckerContext &,
457330f729Sjoerg const CallExpr *,
467330f729Sjoerg StringRef FName) const;
477330f729Sjoerg };
487330f729Sjoerg } //end anonymous namespace
497330f729Sjoerg
507330f729Sjoerg //===----------------------------------------------------------------------===//
517330f729Sjoerg // dispatch_once and dispatch_once_f
527330f729Sjoerg //===----------------------------------------------------------------------===//
537330f729Sjoerg
547330f729Sjoerg const ObjCIvarRegion *
getParentIvarRegion(const MemRegion * R)557330f729Sjoerg MacOSXAPIChecker::getParentIvarRegion(const MemRegion *R) {
567330f729Sjoerg const SubRegion *SR = dyn_cast<SubRegion>(R);
577330f729Sjoerg while (SR) {
587330f729Sjoerg if (const ObjCIvarRegion *IR = dyn_cast<ObjCIvarRegion>(SR))
597330f729Sjoerg return IR;
607330f729Sjoerg SR = dyn_cast<SubRegion>(SR->getSuperRegion());
617330f729Sjoerg }
627330f729Sjoerg return nullptr;
637330f729Sjoerg }
647330f729Sjoerg
CheckDispatchOnce(CheckerContext & C,const CallExpr * CE,StringRef FName) const657330f729Sjoerg void MacOSXAPIChecker::CheckDispatchOnce(CheckerContext &C, const CallExpr *CE,
667330f729Sjoerg StringRef FName) const {
677330f729Sjoerg if (CE->getNumArgs() < 1)
687330f729Sjoerg return;
697330f729Sjoerg
707330f729Sjoerg // Check if the first argument is improperly allocated. If so, issue a
717330f729Sjoerg // warning because that's likely to be bad news.
727330f729Sjoerg const MemRegion *R = C.getSVal(CE->getArg(0)).getAsRegion();
737330f729Sjoerg if (!R)
747330f729Sjoerg return;
757330f729Sjoerg
767330f729Sjoerg // Global variables are fine.
777330f729Sjoerg const MemRegion *RB = R->getBaseRegion();
787330f729Sjoerg const MemSpaceRegion *RS = RB->getMemorySpace();
797330f729Sjoerg if (isa<GlobalsSpaceRegion>(RS))
807330f729Sjoerg return;
817330f729Sjoerg
827330f729Sjoerg // Handle _dispatch_once. In some versions of the OS X SDK we have the case
837330f729Sjoerg // that dispatch_once is a macro that wraps a call to _dispatch_once.
847330f729Sjoerg // _dispatch_once is then a function which then calls the real dispatch_once.
857330f729Sjoerg // Users do not care; they just want the warning at the top-level call.
867330f729Sjoerg if (CE->getBeginLoc().isMacroID()) {
877330f729Sjoerg StringRef TrimmedFName = FName.ltrim('_');
887330f729Sjoerg if (TrimmedFName != FName)
897330f729Sjoerg FName = TrimmedFName;
907330f729Sjoerg }
917330f729Sjoerg
927330f729Sjoerg SmallString<256> S;
937330f729Sjoerg llvm::raw_svector_ostream os(S);
947330f729Sjoerg bool SuggestStatic = false;
957330f729Sjoerg os << "Call to '" << FName << "' uses";
967330f729Sjoerg if (const VarRegion *VR = dyn_cast<VarRegion>(RB)) {
977330f729Sjoerg const VarDecl *VD = VR->getDecl();
987330f729Sjoerg // FIXME: These should have correct memory space and thus should be filtered
997330f729Sjoerg // out earlier. This branch only fires when we're looking from a block,
1007330f729Sjoerg // which we analyze as a top-level declaration, onto a static local
1017330f729Sjoerg // in a function that contains the block.
1027330f729Sjoerg if (VD->isStaticLocal())
1037330f729Sjoerg return;
1047330f729Sjoerg // We filtered out globals earlier, so it must be a local variable
1057330f729Sjoerg // or a block variable which is under UnknownSpaceRegion.
1067330f729Sjoerg if (VR != R)
1077330f729Sjoerg os << " memory within";
1087330f729Sjoerg if (VD->hasAttr<BlocksAttr>())
1097330f729Sjoerg os << " the block variable '";
1107330f729Sjoerg else
1117330f729Sjoerg os << " the local variable '";
1127330f729Sjoerg os << VR->getDecl()->getName() << '\'';
1137330f729Sjoerg SuggestStatic = true;
1147330f729Sjoerg } else if (const ObjCIvarRegion *IVR = getParentIvarRegion(R)) {
1157330f729Sjoerg if (IVR != R)
1167330f729Sjoerg os << " memory within";
1177330f729Sjoerg os << " the instance variable '" << IVR->getDecl()->getName() << '\'';
1187330f729Sjoerg } else if (isa<HeapSpaceRegion>(RS)) {
1197330f729Sjoerg os << " heap-allocated memory";
1207330f729Sjoerg } else if (isa<UnknownSpaceRegion>(RS)) {
1217330f729Sjoerg // Presence of an IVar superregion has priority over this branch, because
1227330f729Sjoerg // ObjC objects are on the heap even if the core doesn't realize this.
1237330f729Sjoerg // Presence of a block variable base region has priority over this branch,
1247330f729Sjoerg // because block variables are known to be either on stack or on heap
1257330f729Sjoerg // (might actually move between the two, hence UnknownSpace).
1267330f729Sjoerg return;
1277330f729Sjoerg } else {
1287330f729Sjoerg os << " stack allocated memory";
1297330f729Sjoerg }
1307330f729Sjoerg os << " for the predicate value. Using such transient memory for "
1317330f729Sjoerg "the predicate is potentially dangerous.";
1327330f729Sjoerg if (SuggestStatic)
1337330f729Sjoerg os << " Perhaps you intended to declare the variable as 'static'?";
1347330f729Sjoerg
1357330f729Sjoerg ExplodedNode *N = C.generateErrorNode();
1367330f729Sjoerg if (!N)
1377330f729Sjoerg return;
1387330f729Sjoerg
1397330f729Sjoerg if (!BT_dispatchOnce)
1407330f729Sjoerg BT_dispatchOnce.reset(new BugType(this, "Improper use of 'dispatch_once'",
1417330f729Sjoerg "API Misuse (Apple)"));
1427330f729Sjoerg
1437330f729Sjoerg auto report =
1447330f729Sjoerg std::make_unique<PathSensitiveBugReport>(*BT_dispatchOnce, os.str(), N);
1457330f729Sjoerg report->addRange(CE->getArg(0)->getSourceRange());
1467330f729Sjoerg C.emitReport(std::move(report));
1477330f729Sjoerg }
1487330f729Sjoerg
1497330f729Sjoerg //===----------------------------------------------------------------------===//
1507330f729Sjoerg // Central dispatch function.
1517330f729Sjoerg //===----------------------------------------------------------------------===//
1527330f729Sjoerg
checkPreStmt(const CallExpr * CE,CheckerContext & C) const1537330f729Sjoerg void MacOSXAPIChecker::checkPreStmt(const CallExpr *CE,
1547330f729Sjoerg CheckerContext &C) const {
1557330f729Sjoerg StringRef Name = C.getCalleeName(CE);
1567330f729Sjoerg if (Name.empty())
1577330f729Sjoerg return;
1587330f729Sjoerg
1597330f729Sjoerg SubChecker SC =
1607330f729Sjoerg llvm::StringSwitch<SubChecker>(Name)
1617330f729Sjoerg .Cases("dispatch_once",
1627330f729Sjoerg "_dispatch_once",
1637330f729Sjoerg "dispatch_once_f",
1647330f729Sjoerg &MacOSXAPIChecker::CheckDispatchOnce)
1657330f729Sjoerg .Default(nullptr);
1667330f729Sjoerg
1677330f729Sjoerg if (SC)
1687330f729Sjoerg (this->*SC)(C, CE, Name);
1697330f729Sjoerg }
1707330f729Sjoerg
1717330f729Sjoerg //===----------------------------------------------------------------------===//
1727330f729Sjoerg // Registration.
1737330f729Sjoerg //===----------------------------------------------------------------------===//
1747330f729Sjoerg
registerMacOSXAPIChecker(CheckerManager & mgr)1757330f729Sjoerg void ento::registerMacOSXAPIChecker(CheckerManager &mgr) {
1767330f729Sjoerg mgr.registerChecker<MacOSXAPIChecker>();
1777330f729Sjoerg }
1787330f729Sjoerg
shouldRegisterMacOSXAPIChecker(const CheckerManager & mgr)179*e038c9c4Sjoerg bool ento::shouldRegisterMacOSXAPIChecker(const CheckerManager &mgr) {
1807330f729Sjoerg return true;
1817330f729Sjoerg }
182