648 lines · cpp
1//= UnixAPIChecker.h - Checks preconditions for various Unix APIs --*- C++ -*-//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// This defines UnixAPIChecker, which is an assortment of checks on calls10// to various, widely used UNIX/Posix functions.11//12//===----------------------------------------------------------------------===//13 14#include "clang/Basic/TargetInfo.h"15#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"16#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"17#include "clang/StaticAnalyzer/Core/BugReporter/CommonBugCategories.h"18#include "clang/StaticAnalyzer/Core/Checker.h"19#include "clang/StaticAnalyzer/Core/CheckerManager.h"20#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"21#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"22#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h"23#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicExtent.h"24#include "llvm/ADT/STLExtras.h"25#include "llvm/ADT/StringExtras.h"26#include "llvm/Support/raw_ostream.h"27#include <optional>28 29using namespace clang;30using namespace ento;31 32enum class OpenVariant {33 /// The standard open() call:34 /// int open(const char *path, int oflag, ...);35 Open,36 37 /// The variant taking a directory file descriptor and a relative path:38 /// int openat(int fd, const char *path, int oflag, ...);39 OpenAt40};41 42static std::optional<int> getCreateFlagValue(const ASTContext &Ctx,43 const Preprocessor &PP) {44 std::optional<int> MacroVal = tryExpandAsInteger("O_CREAT", PP);45 if (MacroVal.has_value())46 return MacroVal;47 48 // If we failed, fall-back to known values.49 if (Ctx.getTargetInfo().getTriple().getVendor() == llvm::Triple::Apple)50 return {0x0200};51 return MacroVal;52}53 54namespace {55 56class UnixAPIMisuseChecker : public Checker<check::PreCall> {57 const BugType BT_open{this, "Improper use of 'open'", categories::UnixAPI};58 const BugType BT_getline{this, "Improper use of getdelim",59 categories::UnixAPI};60 const BugType BT_pthreadOnce{this, "Improper use of 'pthread_once'",61 categories::UnixAPI};62 const BugType BT_ArgumentNull{this, "NULL pointer", categories::UnixAPI};63 const std::optional<int> Val_O_CREAT;64 65 ProgramStateRef66 EnsurePtrNotNull(SVal PtrVal, const Expr *PtrExpr, CheckerContext &C,67 ProgramStateRef State, const StringRef PtrDescr,68 std::optional<std::reference_wrapper<const BugType>> BT =69 std::nullopt) const;70 71 ProgramStateRef EnsureGetdelimBufferAndSizeCorrect(72 SVal LinePtrPtrSVal, SVal SizePtrSVal, const Expr *LinePtrPtrExpr,73 const Expr *SizePtrExpr, CheckerContext &C, ProgramStateRef State) const;74 75public:76 UnixAPIMisuseChecker(const ASTContext &Ctx, const Preprocessor &PP)77 : Val_O_CREAT(getCreateFlagValue(Ctx, PP)) {}78 79 void checkASTDecl(const TranslationUnitDecl *TU, AnalysisManager &Mgr,80 BugReporter &BR) const;81 82 void checkPreCall(const CallEvent &Call, CheckerContext &C) const;83 84 void CheckOpen(CheckerContext &C, const CallEvent &Call) const;85 void CheckOpenAt(CheckerContext &C, const CallEvent &Call) const;86 void CheckGetDelimOrGetline(CheckerContext &C, const CallEvent &Call) const;87 void CheckPthreadOnce(CheckerContext &C, const CallEvent &Call) const;88 89 void CheckOpenVariant(CheckerContext &C, const CallEvent &Call,90 OpenVariant Variant) const;91 92 void ReportOpenBug(CheckerContext &C, ProgramStateRef State, const char *Msg,93 SourceRange SR) const;94};95 96class UnixAPIPortabilityChecker : public Checker< check::PreStmt<CallExpr> > {97public:98 void checkPreStmt(const CallExpr *CE, CheckerContext &C) const;99 100private:101 const BugType BT_mallocZero{102 this, "Undefined allocation of 0 bytes (CERT MEM04-C; CWE-131)",103 categories::UnixAPI};104 105 void CheckCallocZero(CheckerContext &C, const CallExpr *CE) const;106 void CheckMallocZero(CheckerContext &C, const CallExpr *CE) const;107 void CheckReallocZero(CheckerContext &C, const CallExpr *CE) const;108 void CheckReallocfZero(CheckerContext &C, const CallExpr *CE) const;109 void CheckAllocaZero(CheckerContext &C, const CallExpr *CE) const;110 void CheckAllocaWithAlignZero(CheckerContext &C, const CallExpr *CE) const;111 void CheckVallocZero(CheckerContext &C, const CallExpr *CE) const;112 113 bool ReportZeroByteAllocation(CheckerContext &C,114 ProgramStateRef falseState,115 const Expr *arg,116 const char *fn_name) const;117 void BasicAllocationCheck(CheckerContext &C,118 const CallExpr *CE,119 const unsigned numArgs,120 const unsigned sizeArg,121 const char *fn) const;122};123 124} // end anonymous namespace125 126ProgramStateRef UnixAPIMisuseChecker::EnsurePtrNotNull(127 SVal PtrVal, const Expr *PtrExpr, CheckerContext &C, ProgramStateRef State,128 const StringRef PtrDescr,129 std::optional<std::reference_wrapper<const BugType>> BT) const {130 const auto Ptr = PtrVal.getAs<DefinedSVal>();131 if (!Ptr || !PtrExpr->getType()->isPointerType())132 return State;133 134 const auto [PtrNotNull, PtrNull] = State->assume(*Ptr);135 if (!PtrNotNull && PtrNull) {136 if (ExplodedNode *N = C.generateErrorNode(PtrNull)) {137 auto R = std::make_unique<PathSensitiveBugReport>(138 BT.value_or(std::cref(BT_ArgumentNull)),139 (PtrDescr + " pointer might be NULL.").str(), N);140 bugreporter::trackExpressionValue(N, PtrExpr, *R);141 C.emitReport(std::move(R));142 }143 return nullptr;144 }145 146 return PtrNotNull;147}148 149//===----------------------------------------------------------------------===//150// "open" (man 2 open)151//===----------------------------------------------------------------------===/152 153void UnixAPIMisuseChecker::checkPreCall(const CallEvent &Call,154 CheckerContext &C) const {155 const FunctionDecl *FD = dyn_cast_if_present<FunctionDecl>(Call.getDecl());156 if (!FD || FD->getKind() != Decl::Function)157 return;158 159 // Don't treat functions in namespaces with the same name a Unix function160 // as a call to the Unix function.161 const DeclContext *NamespaceCtx = FD->getEnclosingNamespaceContext();162 if (isa_and_nonnull<NamespaceDecl>(NamespaceCtx))163 return;164 165 StringRef FName = C.getCalleeName(FD);166 if (FName.empty())167 return;168 169 if (FName == "open")170 CheckOpen(C, Call);171 172 else if (FName == "openat")173 CheckOpenAt(C, Call);174 175 else if (FName == "pthread_once")176 CheckPthreadOnce(C, Call);177 178 else if (is_contained({"getdelim", "getline"}, FName))179 CheckGetDelimOrGetline(C, Call);180}181void UnixAPIMisuseChecker::ReportOpenBug(CheckerContext &C,182 ProgramStateRef State,183 const char *Msg,184 SourceRange SR) const {185 ExplodedNode *N = C.generateErrorNode(State);186 if (!N)187 return;188 189 auto Report = std::make_unique<PathSensitiveBugReport>(BT_open, Msg, N);190 Report->addRange(SR);191 C.emitReport(std::move(Report));192}193 194void UnixAPIMisuseChecker::CheckOpen(CheckerContext &C,195 const CallEvent &Call) const {196 CheckOpenVariant(C, Call, OpenVariant::Open);197}198 199void UnixAPIMisuseChecker::CheckOpenAt(CheckerContext &C,200 const CallEvent &Call) const {201 CheckOpenVariant(C, Call, OpenVariant::OpenAt);202}203 204void UnixAPIMisuseChecker::CheckOpenVariant(CheckerContext &C,205 const CallEvent &Call,206 OpenVariant Variant) const {207 // The index of the argument taking the flags open flags (O_RDONLY,208 // O_WRONLY, O_CREAT, etc.),209 unsigned int FlagsArgIndex;210 const char *VariantName;211 switch (Variant) {212 case OpenVariant::Open:213 FlagsArgIndex = 1;214 VariantName = "open";215 break;216 case OpenVariant::OpenAt:217 FlagsArgIndex = 2;218 VariantName = "openat";219 break;220 };221 222 // All calls should at least provide arguments up to the 'flags' parameter.223 unsigned int MinArgCount = FlagsArgIndex + 1;224 225 // The frontend should issue a warning for this case. Just return.226 if (Call.getNumArgs() < MinArgCount)227 return;228 229 // If the flags has O_CREAT set then open/openat() require an additional230 // argument specifying the file mode (permission bits) for the created file.231 unsigned int CreateModeArgIndex = FlagsArgIndex + 1;232 233 // The create mode argument should be the last argument.234 unsigned int MaxArgCount = CreateModeArgIndex + 1;235 236 ProgramStateRef state = C.getState();237 if (Call.getNumArgs() == MaxArgCount) {238 const Expr *Arg = Call.getArgExpr(CreateModeArgIndex);239 QualType QT = Arg->getType();240 if (!QT->isIntegerType()) {241 SmallString<256> SBuf;242 llvm::raw_svector_ostream OS(SBuf);243 OS << "The " << CreateModeArgIndex + 1244 << llvm::getOrdinalSuffix(CreateModeArgIndex + 1)245 << " argument to '" << VariantName << "' is not an integer";246 247 ReportOpenBug(C, state,248 SBuf.c_str(),249 Arg->getSourceRange());250 return;251 }252 } else if (Call.getNumArgs() > MaxArgCount) {253 SmallString<256> SBuf;254 llvm::raw_svector_ostream OS(SBuf);255 OS << "Call to '" << VariantName << "' with more than " << MaxArgCount256 << " arguments";257 258 ReportOpenBug(C, state, SBuf.c_str(),259 Call.getArgExpr(MaxArgCount)->getSourceRange());260 return;261 }262 263 if (!Val_O_CREAT.has_value()) {264 return;265 }266 267 // Now check if oflags has O_CREAT set.268 const Expr *oflagsEx = Call.getArgExpr(FlagsArgIndex);269 const SVal V = Call.getArgSVal(FlagsArgIndex);270 if (!isa<NonLoc>(V)) {271 // The case where 'V' can be a location can only be due to a bad header,272 // so in this case bail out.273 return;274 }275 NonLoc oflags = V.castAs<NonLoc>();276 NonLoc ocreateFlag = C.getSValBuilder()277 .makeIntVal(Val_O_CREAT.value(), oflagsEx->getType())278 .castAs<NonLoc>();279 SVal maskedFlagsUC = C.getSValBuilder().evalBinOpNN(state, BO_And,280 oflags, ocreateFlag,281 oflagsEx->getType());282 if (maskedFlagsUC.isUnknownOrUndef())283 return;284 DefinedSVal maskedFlags = maskedFlagsUC.castAs<DefinedSVal>();285 286 // Check if maskedFlags is non-zero.287 ProgramStateRef trueState, falseState;288 std::tie(trueState, falseState) = state->assume(maskedFlags);289 290 // Only emit an error if the value of 'maskedFlags' is properly291 // constrained;292 if (!(trueState && !falseState))293 return;294 295 if (Call.getNumArgs() < MaxArgCount) {296 SmallString<256> SBuf;297 llvm::raw_svector_ostream OS(SBuf);298 OS << "Call to '" << VariantName << "' requires a "299 << CreateModeArgIndex + 1300 << llvm::getOrdinalSuffix(CreateModeArgIndex + 1)301 << " argument when the 'O_CREAT' flag is set";302 ReportOpenBug(C, trueState,303 SBuf.c_str(),304 oflagsEx->getSourceRange());305 }306}307 308//===----------------------------------------------------------------------===//309// getdelim and getline310//===----------------------------------------------------------------------===//311 312ProgramStateRef UnixAPIMisuseChecker::EnsureGetdelimBufferAndSizeCorrect(313 SVal LinePtrPtrSVal, SVal SizePtrSVal, const Expr *LinePtrPtrExpr,314 const Expr *SizePtrExpr, CheckerContext &C, ProgramStateRef State) const {315 static constexpr llvm::StringLiteral SizeGreaterThanBufferSize =316 "The buffer from the first argument is smaller than the size "317 "specified by the second parameter";318 static constexpr llvm::StringLiteral SizeUndef =319 "The buffer from the first argument is not NULL, but the size specified "320 "by the second parameter is undefined.";321 322 auto EmitBugReport = [this, &C, SizePtrExpr, LinePtrPtrExpr](323 ProgramStateRef BugState, StringRef ErrMsg) {324 if (ExplodedNode *N = C.generateErrorNode(BugState)) {325 auto R = std::make_unique<PathSensitiveBugReport>(BT_getline, ErrMsg, N);326 bugreporter::trackExpressionValue(N, SizePtrExpr, *R);327 bugreporter::trackExpressionValue(N, LinePtrPtrExpr, *R);328 C.emitReport(std::move(R));329 }330 };331 332 // We have a pointer to a pointer to the buffer, and a pointer to the size.333 // We want what they point at.334 const auto LinePtrValOpt = getPointeeVal(LinePtrPtrSVal, State);335 if (!LinePtrValOpt)336 return nullptr;337 338 const auto LinePtrSVal = LinePtrValOpt->getAs<DefinedSVal>();339 const auto NSVal = getPointeeVal(SizePtrSVal, State);340 if (!LinePtrSVal || !NSVal || NSVal->isUnknown())341 return nullptr;342 343 assert(LinePtrPtrExpr && SizePtrExpr);344 345 const auto [LinePtrNotNull, LinePtrNull] = State->assume(*LinePtrSVal);346 if (LinePtrNotNull && !LinePtrNull) {347 // If `*lineptr` is not null, but `*n` is undefined, there is UB.348 if (NSVal->isUndef()) {349 EmitBugReport(LinePtrNotNull, SizeUndef);350 return nullptr;351 }352 353 // If it is defined, and known, its size must be less than or equal to354 // the buffer size.355 auto NDefSVal = NSVal->getAs<DefinedSVal>();356 if (!NDefSVal)357 return LinePtrNotNull;358 359 auto &SVB = C.getSValBuilder();360 361 const MemRegion *LinePtrRegion = LinePtrSVal->getAsRegion();362 if (!LinePtrRegion)363 return LinePtrNotNull;364 365 auto LineBufSize = getDynamicExtent(LinePtrNotNull, LinePtrRegion, SVB);366 auto LineBufSizeGtN = SVB.evalBinOp(LinePtrNotNull, BO_GE, LineBufSize,367 *NDefSVal, SVB.getConditionType())368 .getAs<DefinedOrUnknownSVal>();369 if (!LineBufSizeGtN)370 return LinePtrNotNull;371 if (auto LineBufSizeOk = LinePtrNotNull->assume(*LineBufSizeGtN, true))372 return LineBufSizeOk;373 374 EmitBugReport(LinePtrNotNull, SizeGreaterThanBufferSize);375 return nullptr;376 }377 return State;378}379 380void UnixAPIMisuseChecker::CheckGetDelimOrGetline(CheckerContext &C,381 const CallEvent &Call) const {382 if (Call.getNumArgs() < 2)383 return;384 385 ProgramStateRef State = C.getState();386 387 // The parameter `n` must not be NULL.388 SVal SizePtrSval = Call.getArgSVal(1);389 State = EnsurePtrNotNull(SizePtrSval, Call.getArgExpr(1), C, State, "Size");390 if (!State)391 return;392 393 // The parameter `lineptr` must not be NULL.394 SVal LinePtrPtrSVal = Call.getArgSVal(0);395 State =396 EnsurePtrNotNull(LinePtrPtrSVal, Call.getArgExpr(0), C, State, "Line");397 if (!State)398 return;399 400 State = EnsureGetdelimBufferAndSizeCorrect(LinePtrPtrSVal, SizePtrSval,401 Call.getArgExpr(0),402 Call.getArgExpr(1), C, State);403 if (!State)404 return;405 406 C.addTransition(State);407}408 409//===----------------------------------------------------------------------===//410// pthread_once411//===----------------------------------------------------------------------===//412 413void UnixAPIMisuseChecker::CheckPthreadOnce(CheckerContext &C,414 const CallEvent &Call) const {415 416 // This is similar to 'CheckDispatchOnce' in the MacOSXAPIChecker.417 // They can possibly be refactored.418 419 if (Call.getNumArgs() < 1)420 return;421 422 // Check if the first argument is stack allocated. If so, issue a warning423 // because that's likely to be bad news.424 ProgramStateRef state = C.getState();425 const MemRegion *R = Call.getArgSVal(0).getAsRegion();426 if (!R || !R->hasMemorySpace<StackSpaceRegion>(state))427 return;428 429 ExplodedNode *N = C.generateErrorNode(state);430 if (!N)431 return;432 433 SmallString<256> S;434 llvm::raw_svector_ostream os(S);435 os << "Call to 'pthread_once' uses";436 if (const VarRegion *VR = dyn_cast<VarRegion>(R))437 os << " the local variable '" << VR->getDecl()->getName() << '\'';438 else439 os << " stack allocated memory";440 os << " for the \"control\" value. Using such transient memory for "441 "the control value is potentially dangerous.";442 if (isa<VarRegion>(R) && R->hasMemorySpace<StackLocalsSpaceRegion>(state))443 os << " Perhaps you intended to declare the variable as 'static'?";444 445 auto report =446 std::make_unique<PathSensitiveBugReport>(BT_pthreadOnce, os.str(), N);447 report->addRange(Call.getArgExpr(0)->getSourceRange());448 C.emitReport(std::move(report));449}450 451//===----------------------------------------------------------------------===//452// "calloc", "malloc", "realloc", "reallocf", "alloca" and "valloc"453// with allocation size 0454//===----------------------------------------------------------------------===//455 456// FIXME: Eventually these should be rolled into the MallocChecker, but right now457// they're more basic and valuable for widespread use.458 459// Returns true if we try to do a zero byte allocation, false otherwise.460// Fills in trueState and falseState.461static bool IsZeroByteAllocation(ProgramStateRef state,462 const SVal argVal,463 ProgramStateRef *trueState,464 ProgramStateRef *falseState) {465 std::tie(*trueState, *falseState) =466 state->assume(argVal.castAs<DefinedSVal>());467 468 return (*falseState && !*trueState);469}470 471// Generates an error report, indicating that the function whose name is given472// will perform a zero byte allocation.473// Returns false if an error occurred, true otherwise.474bool UnixAPIPortabilityChecker::ReportZeroByteAllocation(475 CheckerContext &C,476 ProgramStateRef falseState,477 const Expr *arg,478 const char *fn_name) const {479 ExplodedNode *N = C.generateErrorNode(falseState);480 if (!N)481 return false;482 483 SmallString<256> S;484 llvm::raw_svector_ostream os(S);485 os << "Call to '" << fn_name << "' has an allocation size of 0 bytes";486 auto report =487 std::make_unique<PathSensitiveBugReport>(BT_mallocZero, os.str(), N);488 489 report->addRange(arg->getSourceRange());490 bugreporter::trackExpressionValue(N, arg, *report);491 C.emitReport(std::move(report));492 493 return true;494}495 496// Does a basic check for 0-sized allocations suitable for most of the below497// functions (modulo "calloc")498void UnixAPIPortabilityChecker::BasicAllocationCheck(CheckerContext &C,499 const CallExpr *CE,500 const unsigned numArgs,501 const unsigned sizeArg,502 const char *fn) const {503 // Check for the correct number of arguments.504 if (CE->getNumArgs() != numArgs)505 return;506 507 // Check if the allocation size is 0.508 ProgramStateRef state = C.getState();509 ProgramStateRef trueState = nullptr, falseState = nullptr;510 const Expr *arg = CE->getArg(sizeArg);511 SVal argVal = C.getSVal(arg);512 513 if (argVal.isUnknownOrUndef())514 return;515 516 // Is the value perfectly constrained to zero?517 if (IsZeroByteAllocation(state, argVal, &trueState, &falseState)) {518 (void) ReportZeroByteAllocation(C, falseState, arg, fn);519 return;520 }521 // Assume the value is non-zero going forward.522 assert(trueState);523 if (trueState != state)524 C.addTransition(trueState);525}526 527void UnixAPIPortabilityChecker::CheckCallocZero(CheckerContext &C,528 const CallExpr *CE) const {529 unsigned int nArgs = CE->getNumArgs();530 if (nArgs != 2)531 return;532 533 ProgramStateRef state = C.getState();534 ProgramStateRef trueState = nullptr, falseState = nullptr;535 536 unsigned int i;537 for (i = 0; i < nArgs; i++) {538 const Expr *arg = CE->getArg(i);539 SVal argVal = C.getSVal(arg);540 if (argVal.isUnknownOrUndef()) {541 if (i == 0)542 continue;543 return;544 }545 546 if (IsZeroByteAllocation(state, argVal, &trueState, &falseState)) {547 if (ReportZeroByteAllocation(C, falseState, arg, "calloc"))548 return;549 if (i == 0)550 continue;551 return;552 }553 }554 555 // Assume the value is non-zero going forward.556 assert(trueState);557 if (trueState != state)558 C.addTransition(trueState);559}560 561void UnixAPIPortabilityChecker::CheckMallocZero(CheckerContext &C,562 const CallExpr *CE) const {563 BasicAllocationCheck(C, CE, 1, 0, "malloc");564}565 566void UnixAPIPortabilityChecker::CheckReallocZero(CheckerContext &C,567 const CallExpr *CE) const {568 BasicAllocationCheck(C, CE, 2, 1, "realloc");569}570 571void UnixAPIPortabilityChecker::CheckReallocfZero(CheckerContext &C,572 const CallExpr *CE) const {573 BasicAllocationCheck(C, CE, 2, 1, "reallocf");574}575 576void UnixAPIPortabilityChecker::CheckAllocaZero(CheckerContext &C,577 const CallExpr *CE) const {578 BasicAllocationCheck(C, CE, 1, 0, "alloca");579}580 581void UnixAPIPortabilityChecker::CheckAllocaWithAlignZero(582 CheckerContext &C,583 const CallExpr *CE) const {584 BasicAllocationCheck(C, CE, 2, 0, "__builtin_alloca_with_align");585}586 587void UnixAPIPortabilityChecker::CheckVallocZero(CheckerContext &C,588 const CallExpr *CE) const {589 BasicAllocationCheck(C, CE, 1, 0, "valloc");590}591 592void UnixAPIPortabilityChecker::checkPreStmt(const CallExpr *CE,593 CheckerContext &C) const {594 const FunctionDecl *FD = C.getCalleeDecl(CE);595 if (!FD || FD->getKind() != Decl::Function)596 return;597 598 // Don't treat functions in namespaces with the same name a Unix function599 // as a call to the Unix function.600 const DeclContext *NamespaceCtx = FD->getEnclosingNamespaceContext();601 if (isa_and_nonnull<NamespaceDecl>(NamespaceCtx))602 return;603 604 StringRef FName = C.getCalleeName(FD);605 if (FName.empty())606 return;607 608 if (FName == "calloc")609 CheckCallocZero(C, CE);610 611 else if (FName == "malloc")612 CheckMallocZero(C, CE);613 614 else if (FName == "realloc")615 CheckReallocZero(C, CE);616 617 else if (FName == "reallocf")618 CheckReallocfZero(C, CE);619 620 else if (FName == "alloca" || FName == "__builtin_alloca")621 CheckAllocaZero(C, CE);622 623 else if (FName == "__builtin_alloca_with_align")624 CheckAllocaWithAlignZero(C, CE);625 626 else if (FName == "valloc")627 CheckVallocZero(C, CE);628}629 630//===----------------------------------------------------------------------===//631// Registration.632//===----------------------------------------------------------------------===//633 634void ento::registerUnixAPIMisuseChecker(CheckerManager &Mgr) {635 Mgr.registerChecker<UnixAPIMisuseChecker>(Mgr.getASTContext(),636 Mgr.getPreprocessor());637}638bool ento::shouldRegisterUnixAPIMisuseChecker(const CheckerManager &Mgr) {639 return true;640}641 642void ento::registerUnixAPIPortabilityChecker(CheckerManager &Mgr) {643 Mgr.registerChecker<UnixAPIPortabilityChecker>();644}645bool ento::shouldRegisterUnixAPIPortabilityChecker(const CheckerManager &Mgr) {646 return true;647}648