blob: 937d35408afd35d32a2649c292220635c89667cf [file] [log] [blame]
Gabor Horvath717b51c2015-08-21 00:18:28 +00001//=== ObjCGenericsChecker.cpp - Path sensitive checker for Generics *- C++ -*=//
2//
3// The LLVM Compiler Infrastructure
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===----------------------------------------------------------------------===//
9//
10// This checker tries to find type errors that the compiler is not able to catch
11// due to the implicit conversions that were introduced for backward
12// compatibility.
13//
14//===----------------------------------------------------------------------===//
15
16#include "ClangSACheckers.h"
17#include "clang/AST/ParentMap.h"
18#include "clang/AST/RecursiveASTVisitor.h"
19#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
20#include "clang/StaticAnalyzer/Core/Checker.h"
21#include "clang/StaticAnalyzer/Core/CheckerManager.h"
22#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
23#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
24#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
25
26using namespace clang;
27using namespace ento;
28
29// ProgramState trait - a map from symbol to its specialized type.
30REGISTER_MAP_WITH_PROGRAMSTATE(TypeParamMap, SymbolRef,
31 const ObjCObjectPointerType *)
32
33namespace {
34class ObjCGenericsChecker
35 : public Checker<check::DeadSymbols, check::PreObjCMessage,
36 check::PostObjCMessage, check::PostStmt<CastExpr>> {
37public:
38 ProgramStateRef checkPointerEscape(ProgramStateRef State,
39 const InvalidatedSymbols &Escaped,
40 const CallEvent *Call,
41 PointerEscapeKind Kind) const;
42
43 void checkPreObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const;
44 void checkPostObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const;
45 void checkPostStmt(const CastExpr *CE, CheckerContext &C) const;
46 void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const;
47
48private:
49 mutable std::unique_ptr<BugType> BT;
50 void initBugType() const {
51 if (!BT)
52 BT.reset(
53 new BugType(this, "Generics", categories::CoreFoundationObjectiveC));
54 }
55
56 class GenericsBugVisitor : public BugReporterVisitorImpl<GenericsBugVisitor> {
57 public:
58 GenericsBugVisitor(SymbolRef S) : Sym(S) {}
59 ~GenericsBugVisitor() override {}
60
61 void Profile(llvm::FoldingSetNodeID &ID) const override {
62 static int X = 0;
63 ID.AddPointer(&X);
64 ID.AddPointer(Sym);
65 }
66
67 PathDiagnosticPiece *VisitNode(const ExplodedNode *N,
68 const ExplodedNode *PrevN,
69 BugReporterContext &BRC,
70 BugReport &BR) override;
71
72 private:
73 // The tracked symbol.
74 SymbolRef Sym;
75 };
76
77 void reportBug(const ObjCObjectPointerType *From,
78 const ObjCObjectPointerType *To, ExplodedNode *N,
79 SymbolRef Sym, CheckerContext &C,
80 const Stmt *ReportedNode = nullptr) const {
81 initBugType();
82 SmallString<64> Buf;
83 llvm::raw_svector_ostream OS(Buf);
84 OS << "Incompatible pointer types assigning to '";
85 QualType::print(To, Qualifiers(), OS, C.getLangOpts(), llvm::Twine());
86 OS << "' from '";
87 QualType::print(From, Qualifiers(), OS, C.getLangOpts(), llvm::Twine());
88 OS << "'";
89 std::unique_ptr<BugReport> R(new BugReport(*BT, OS.str(), N));
90 R->markInteresting(Sym);
91 R->addVisitor(llvm::make_unique<GenericsBugVisitor>(Sym));
92 if (ReportedNode)
93 R->addRange(ReportedNode->getSourceRange());
94 C.emitReport(std::move(R));
95 }
96};
97} // end anonymous namespace
98
99PathDiagnosticPiece *ObjCGenericsChecker::GenericsBugVisitor::VisitNode(
100 const ExplodedNode *N, const ExplodedNode *PrevN, BugReporterContext &BRC,
101 BugReport &BR) {
102 ProgramStateRef state = N->getState();
103 ProgramStateRef statePrev = PrevN->getState();
104
105 const ObjCObjectPointerType *const *TrackedType =
106 state->get<TypeParamMap>(Sym);
107 const ObjCObjectPointerType *const *TrackedTypePrev =
108 statePrev->get<TypeParamMap>(Sym);
109 if (!TrackedType)
110 return nullptr;
111
112 if (TrackedTypePrev && *TrackedTypePrev == *TrackedType)
113 return nullptr;
114
115 // Retrieve the associated statement.
116 const Stmt *S = nullptr;
117 ProgramPoint ProgLoc = N->getLocation();
118 if (Optional<StmtPoint> SP = ProgLoc.getAs<StmtPoint>()) {
119 S = SP->getStmt();
120 }
121
122 if (!S)
123 return nullptr;
124
125 const LangOptions &LangOpts = BRC.getASTContext().getLangOpts();
126
127 SmallString<64> Buf;
128 llvm::raw_svector_ostream OS(Buf);
129 OS << "Type '";
130 QualType::print(*TrackedType, Qualifiers(), OS, LangOpts, llvm::Twine());
131 OS << "' is infered from ";
132
133 if (const auto *ExplicitCast = dyn_cast<ExplicitCastExpr>(S)) {
134 OS << "explicit cast (from '";
135 QualType::print(ExplicitCast->getSubExpr()->getType().getTypePtr(),
136 Qualifiers(), OS, LangOpts, llvm::Twine());
137 OS << "' to '";
138 QualType::print(ExplicitCast->getType().getTypePtr(), Qualifiers(), OS,
139 LangOpts, llvm::Twine());
140 OS << "')";
141 } else if (const auto *ImplicitCast = dyn_cast<ImplicitCastExpr>(S)) {
142 OS << "implicit cast (from '";
143 QualType::print(ImplicitCast->getSubExpr()->getType().getTypePtr(),
144 Qualifiers(), OS, LangOpts, llvm::Twine());
145 OS << "' to '";
146 QualType::print(ImplicitCast->getType().getTypePtr(), Qualifiers(), OS,
147 LangOpts, llvm::Twine());
148 OS << "')";
149 } else {
150 OS << "this context";
151 }
152
153 // Generate the extra diagnostic.
154 PathDiagnosticLocation Pos(S, BRC.getSourceManager(),
155 N->getLocationContext());
156 return new PathDiagnosticEventPiece(Pos, OS.str(), true, nullptr);
157}
158
159void ObjCGenericsChecker::checkDeadSymbols(SymbolReaper &SR,
160 CheckerContext &C) const {
161 if (!SR.hasDeadSymbols())
162 return;
163
164 ProgramStateRef State = C.getState();
165 TypeParamMapTy TyParMap = State->get<TypeParamMap>();
166 for (TypeParamMapTy::iterator I = TyParMap.begin(), E = TyParMap.end();
167 I != E; ++I) {
168 if (SR.isDead(I->first)) {
169 State = State->remove<TypeParamMap>(I->first);
170 }
171 }
172}
173
174static const ObjCObjectPointerType *getMostInformativeDerivedClassImpl(
175 const ObjCObjectPointerType *From, const ObjCObjectPointerType *To,
176 const ObjCObjectPointerType *MostInformativeCandidate, ASTContext &C) {
177 // Checking if from and to are the same classes modulo specialization.
178 if (From->getInterfaceDecl()->getCanonicalDecl() ==
179 To->getInterfaceDecl()->getCanonicalDecl()) {
180 if (To->isSpecialized()) {
181 assert(MostInformativeCandidate->isSpecialized());
182 return MostInformativeCandidate;
183 }
184 return From;
185 }
186 const auto *SuperOfTo =
187 To->getObjectType()->getSuperClassType()->getAs<ObjCObjectType>();
188 assert(SuperOfTo);
189 QualType SuperPtrOfToQual =
190 C.getObjCObjectPointerType(QualType(SuperOfTo, 0));
191 const auto *SuperPtrOfTo = SuperPtrOfToQual->getAs<ObjCObjectPointerType>();
192 if (To->isUnspecialized())
193 return getMostInformativeDerivedClassImpl(From, SuperPtrOfTo, SuperPtrOfTo,
194 C);
195 else
196 return getMostInformativeDerivedClassImpl(From, SuperPtrOfTo,
197 MostInformativeCandidate, C);
198}
199
200/// Get the most derived class if From that do not loose information about type
201/// parameters. To has to be a subclass of From. From has to be specialized.
202static const ObjCObjectPointerType *
203getMostInformativeDerivedClass(const ObjCObjectPointerType *From,
204 const ObjCObjectPointerType *To, ASTContext &C) {
205 return getMostInformativeDerivedClassImpl(From, To, To, C);
206}
207
208static bool storeWhenMoreInformative(ProgramStateRef &State, SymbolRef Sym,
209 const ObjCObjectPointerType *const *Old,
210 const ObjCObjectPointerType *New,
211 ASTContext &C) {
212 if (!Old || C.canAssignObjCInterfaces(*Old, New)) {
213 State = State->set<TypeParamMap>(Sym, New);
214 return true;
215 }
216 return false;
217}
218
219void ObjCGenericsChecker::checkPostStmt(const CastExpr *CE,
220 CheckerContext &C) const {
221 if (CE->getCastKind() != CK_BitCast)
222 return;
223
224 QualType OriginType = CE->getSubExpr()->getType();
225 QualType DestType = CE->getType();
226
227 const auto *OrigObjectPtrType = OriginType->getAs<ObjCObjectPointerType>();
228 const auto *DestObjectPtrType = DestType->getAs<ObjCObjectPointerType>();
229
230 if (!OrigObjectPtrType || !DestObjectPtrType)
231 return;
232
233 ASTContext &ASTCtxt = C.getASTContext();
234
235 // This checker detects the subtyping relationships using the assignment
236 // rules. In order to be able to do this the kindofness must be stripped
237 // first. The checker treats every type as kindof type anyways: when the
238 // tracked type is the subtype of the static type it tries to look up the
239 // methods in the tracked type first.
240 OrigObjectPtrType = OrigObjectPtrType->stripObjCKindOfTypeAndQuals(ASTCtxt);
241 DestObjectPtrType = DestObjectPtrType->stripObjCKindOfTypeAndQuals(ASTCtxt);
242
243 const ObjCObjectType *OrigObjectType = OrigObjectPtrType->getObjectType();
244 const ObjCObjectType *DestObjectType = DestObjectPtrType->getObjectType();
245
246 if (OrigObjectType->isUnspecialized() && DestObjectType->isUnspecialized())
247 return;
248
249 ProgramStateRef State = C.getState();
250 SymbolRef Sym = State->getSVal(CE, C.getLocationContext()).getAsSymbol();
251 if (!Sym)
252 return;
253
254 // Check which assignments are legal.
255 bool OrigToDest =
256 ASTCtxt.canAssignObjCInterfaces(DestObjectPtrType, OrigObjectPtrType);
257 bool DestToOrig =
258 ASTCtxt.canAssignObjCInterfaces(OrigObjectPtrType, DestObjectPtrType);
259 const ObjCObjectPointerType *const *TrackedType =
260 State->get<TypeParamMap>(Sym);
261
262 // If OrigObjectType could convert to DestObjectType, this could be an
263 // implicit cast. Do not treat that cast as explicit in that case.
264 if (isa<ExplicitCastExpr>(CE) && !OrigToDest) {
265 if (DestToOrig) {
266 // Trust explicit downcasts.
267 // However a downcast may also lose information. E. g.:
268 // MutableMap<T, U> : Map
269 // The downcast to MutableMap loses the information about the types of the
270 // Map (due to the type parameters are not being forwarded to Map), and in
271 // general there is no way to recover that information from the
272 // declaration. In order to have to most information, lets find the most
273 // derived type that has all the type parameters forwarded.
274 const ObjCObjectPointerType *WithMostInfo =
275 getMostInformativeDerivedClass(OrigObjectPtrType, DestObjectPtrType,
276 C.getASTContext());
277 if (storeWhenMoreInformative(State, Sym, TrackedType, WithMostInfo,
278 ASTCtxt))
279 C.addTransition(State);
280 return;
281 }
282 // Mismatched types. If the DestType specialized, store it. Forget the
283 // tracked type otherwise.
284 if (DestObjectPtrType->isSpecialized()) {
285 State = State->set<TypeParamMap>(Sym, DestObjectPtrType);
286 C.addTransition(State);
287 } else if (TrackedType) {
288 State = State->remove<TypeParamMap>(Sym);
289 C.addTransition(State);
290 }
291 return;
292 }
293
294 // Handle implicit casts and explicit upcasts.
295
296 if (DestObjectType->isUnspecialized()) {
297 assert(OrigObjectType->isSpecialized());
298 // In case we already have some type information for this symbol from a
299 // Specialized -> Specialized conversion, do not record the OrigType,
300 // because it might contain less type information than the tracked type.
301 if (!TrackedType) {
302 State = State->set<TypeParamMap>(Sym, OrigObjectPtrType);
303 C.addTransition(State);
304 }
305 return;
306 }
307
308 // The destination type is specialized.
309
310 // The tracked type should be the sub or super class of the static destination
311 // type. When an (implicit) upcast or a downcast happens according to static
312 // types, and there is no subtyping relationship between the tracked and the
313 // static destination types, it indicates an error.
314 if (TrackedType &&
315 !ASTCtxt.canAssignObjCInterfaces(DestObjectPtrType, *TrackedType) &&
316 !ASTCtxt.canAssignObjCInterfaces(*TrackedType, DestObjectPtrType)) {
317 static CheckerProgramPointTag IllegalConv(this, "IllegalConversion");
318 ExplodedNode *N = C.addTransition(State, C.getPredecessor(), &IllegalConv);
319 reportBug(*TrackedType, DestObjectPtrType, N, Sym, C);
320 return;
321 }
322
323 if (OrigToDest && !DestToOrig) {
324 // When upcast happens, store the type with the most information about the
325 // type parameters.
326 const ObjCObjectPointerType *WithMostInfo = getMostInformativeDerivedClass(
327 DestObjectPtrType, OrigObjectPtrType, ASTCtxt);
328 if (storeWhenMoreInformative(State, Sym, TrackedType, WithMostInfo,
329 ASTCtxt))
330 C.addTransition(State);
331 return;
332 }
333
334 // Downcast happens.
335
336 // Trust tracked type on unspecialized value -> specialized implicit
337 // downcasts.
338 if (storeWhenMoreInformative(State, Sym, TrackedType, DestObjectPtrType,
339 ASTCtxt)) {
340 C.addTransition(State);
341 }
342}
343
344static const Expr *stripCastsAndSugar(const Expr *E) {
345 E = E->IgnoreParenImpCasts();
346 if (const PseudoObjectExpr *POE = dyn_cast<PseudoObjectExpr>(E))
347 E = POE->getSyntacticForm()->IgnoreParenImpCasts();
348 if (const OpaqueValueExpr *OVE = dyn_cast<OpaqueValueExpr>(E))
349 E = OVE->getSourceExpr()->IgnoreParenImpCasts();
350 return E;
351}
352
353// This callback is used to infer the types for Class variables. This info is
354// used later to validate messages that sent to classes. Class variables are
355// initialized with by invoking the 'class' method on a class.
356void ObjCGenericsChecker::checkPostObjCMessage(const ObjCMethodCall &M,
357 CheckerContext &C) const {
358 const ObjCMessageExpr *MessageExpr = M.getOriginExpr();
359
360 SymbolRef Sym = M.getReturnValue().getAsSymbol();
361 if (!Sym)
362 return;
363
364 Selector Sel = MessageExpr->getSelector();
365 // We are only interested in cases where the class method is invoked on a
366 // class. This method is provided by the runtime and available on all classes.
367 if (MessageExpr->getReceiverKind() != ObjCMessageExpr::Class ||
368 Sel.getAsString() != "class")
369 return;
370
371 QualType ReceiverType = MessageExpr->getClassReceiver();
372 const auto *ReceiverClassType = ReceiverType->getAs<ObjCObjectType>();
373 QualType ReceiverClassPointerType =
374 C.getASTContext().getObjCObjectPointerType(
375 QualType(ReceiverClassType, 0));
376
377 if (!ReceiverClassType->isSpecialized())
378 return;
379 const auto *InferredType =
380 ReceiverClassPointerType->getAs<ObjCObjectPointerType>();
381 assert(InferredType);
382
383 ProgramStateRef State = C.getState();
384 State = State->set<TypeParamMap>(Sym, InferredType);
385 C.addTransition(State);
386}
387
388static bool isObjCTypeParamDependent(QualType Type) {
389 // It is illegal to typedef parameterized types inside an interface. Therfore
390 // an
391 // Objective-C type can only be dependent on a type parameter when the type
392 // parameter structurally present in the type itself.
393 class IsObjCTypeParamDependentTypeVisitor
394 : public RecursiveASTVisitor<IsObjCTypeParamDependentTypeVisitor> {
395 public:
396 IsObjCTypeParamDependentTypeVisitor() : Result(false) {}
397 bool VisitTypedefType(const TypedefType *Type) {
398 if (isa<ObjCTypeParamDecl>(Type->getDecl())) {
399 Result = true;
400 return false;
401 }
402 return true;
403 }
404 bool getResult() { return Result; }
405
406 private:
407 bool Result;
408 };
409
410 IsObjCTypeParamDependentTypeVisitor Visitor;
411 Visitor.TraverseType(Type);
412 return Visitor.getResult();
413}
414
415// A method might not be available in the interface indicated by the static
416// type. However it might be available in the tracked type. In order to properly
417// substitute the type parameters we need the declaration context of the method.
418// The more specialized the enclosing class of the method is, the more likely
419// that the parameter substitution will be successful.
420static const ObjCMethodDecl *
421findMethodDecl(const ObjCMessageExpr *MessageExpr,
422 const ObjCObjectPointerType *TrackedType, ASTContext &ASTCtxt) {
423 const ObjCMethodDecl *Method = nullptr;
424
425 QualType ReceiverType = MessageExpr->getReceiverType();
426 const auto *ReceiverObjectPtrType =
427 ReceiverType->getAs<ObjCObjectPointerType>();
428
429 // Do this "devirtualization" on instance and class methods only. Trust the
430 // static type on super and super class calls.
431 if (MessageExpr->getReceiverKind() == ObjCMessageExpr::Instance ||
432 MessageExpr->getReceiverKind() == ObjCMessageExpr::Class) {
433 // When the receiver type is id, Class, or some super class of the tracked
434 // type, look up the method in the tracked type, not in the receiver type.
435 // This way we preserve more information.
436 if (ReceiverType->isObjCIdType() || ReceiverType->isObjCClassType() ||
437 ASTCtxt.canAssignObjCInterfaces(ReceiverObjectPtrType, TrackedType)) {
438 const ObjCInterfaceDecl *InterfaceDecl = TrackedType->getInterfaceDecl();
439 // The method might not be found.
440 Selector Sel = MessageExpr->getSelector();
441 Method = InterfaceDecl->lookupInstanceMethod(Sel);
442 if (!Method)
443 Method = InterfaceDecl->lookupClassMethod(Sel);
444 }
445 }
446
447 // Fallback to statick method lookup when the one based on the tracked type
448 // failed.
449 return Method ? Method : MessageExpr->getMethodDecl();
450}
451
452// When the receiver has a tracked type, use that type to validate the
453// argumments of the message expression and the return value.
454void ObjCGenericsChecker::checkPreObjCMessage(const ObjCMethodCall &M,
455 CheckerContext &C) const {
456 ProgramStateRef State = C.getState();
457 SymbolRef Sym = M.getReceiverSVal().getAsSymbol();
458 if (!Sym)
459 return;
460
461 const ObjCObjectPointerType *const *TrackedType =
462 State->get<TypeParamMap>(Sym);
463 if (!TrackedType)
464 return;
465
466 // Get the type arguments from tracked type and substitute type arguments
467 // before do the semantic check.
468
469 ASTContext &ASTCtxt = C.getASTContext();
470 const ObjCMessageExpr *MessageExpr = M.getOriginExpr();
471 const ObjCMethodDecl *Method =
472 findMethodDecl(MessageExpr, *TrackedType, ASTCtxt);
473
474 // It is possible to call non-existent methods in Obj-C.
475 if (!Method)
476 return;
477
478 Optional<ArrayRef<QualType>> TypeArgs =
479 (*TrackedType)->getObjCSubstitutions(Method->getDeclContext());
480 // This case might happen when there is an unspecialized override of a
481 // specialized method.
482 if (!TypeArgs)
483 return;
484
485 for (unsigned i = 0; i < Method->param_size(); i++) {
486 const Expr *Arg = MessageExpr->getArg(i);
487 const ParmVarDecl *Param = Method->parameters()[i];
488
489 QualType OrigParamType = Param->getType();
490 if (!isObjCTypeParamDependent(OrigParamType))
491 continue;
492
493 QualType ParamType = OrigParamType.substObjCTypeArgs(
494 ASTCtxt, *TypeArgs, ObjCSubstitutionContext::Parameter);
495 // Check if it can be assigned
496 const auto *ParamObjectPtrType = ParamType->getAs<ObjCObjectPointerType>();
497 const auto *ArgObjectPtrType =
498 stripCastsAndSugar(Arg)->getType()->getAs<ObjCObjectPointerType>();
499 if (!ParamObjectPtrType || !ArgObjectPtrType)
500 continue;
501
502 // Check if we have more concrete tracked type that is not a super type of
503 // the static argument type.
504 SVal ArgSVal = M.getArgSVal(i);
505 SymbolRef ArgSym = ArgSVal.getAsSymbol();
506 if (ArgSym) {
507 const ObjCObjectPointerType *const *TrackedArgType =
508 State->get<TypeParamMap>(ArgSym);
509 if (TrackedArgType &&
510 ASTCtxt.canAssignObjCInterfaces(ArgObjectPtrType, *TrackedArgType)) {
511 ArgObjectPtrType = *TrackedArgType;
512 }
513 }
514
515 // Warn when argument is incompatible with the parameter.
516 if (!ASTCtxt.canAssignObjCInterfaces(ParamObjectPtrType,
517 ArgObjectPtrType)) {
518 static CheckerProgramPointTag Tag(this, "ArgTypeMismatch");
519 ExplodedNode *N = C.addTransition(State, C.getPredecessor(), &Tag);
520 reportBug(ArgObjectPtrType, ParamObjectPtrType, N, Sym, C, Arg);
521 return;
522 }
523 }
524 QualType StaticResultType = Method->getReturnType();
525 // Check whether the result type was a type parameter.
526 bool IsDeclaredAsInstanceType =
527 StaticResultType == ASTCtxt.getObjCInstanceType();
528 if (!isObjCTypeParamDependent(StaticResultType) && !IsDeclaredAsInstanceType)
529 return;
530
531 QualType ResultType = Method->getReturnType().substObjCTypeArgs(
532 ASTCtxt, *TypeArgs, ObjCSubstitutionContext::Result);
533 if (IsDeclaredAsInstanceType)
534 ResultType = QualType(*TrackedType, 0);
535
536 const Stmt *Parent =
537 C.getCurrentAnalysisDeclContext()->getParentMap().getParent(MessageExpr);
538 if (M.getMessageKind() != OCM_Message) {
539 // Properties and subscripts are not direct parents.
540 Parent =
541 C.getCurrentAnalysisDeclContext()->getParentMap().getParent(Parent);
542 }
543
544 const auto *ImplicitCast = dyn_cast_or_null<ImplicitCastExpr>(Parent);
545 if (!ImplicitCast || ImplicitCast->getCastKind() != CK_BitCast)
546 return;
547
548 const auto *ExprTypeAboveCast =
549 ImplicitCast->getType()->getAs<ObjCObjectPointerType>();
550 const auto *ResultPtrType = ResultType->getAs<ObjCObjectPointerType>();
551
552 if (!ExprTypeAboveCast || !ResultPtrType)
553 return;
554
555 // Only warn on unrelated types to avoid too many false positives on
556 // downcasts.
557 if (!ASTCtxt.canAssignObjCInterfaces(ExprTypeAboveCast, ResultPtrType) &&
558 !ASTCtxt.canAssignObjCInterfaces(ResultPtrType, ExprTypeAboveCast)) {
559 static CheckerProgramPointTag Tag(this, "ReturnTypeMismatch");
560 ExplodedNode *N = C.addTransition(State, C.getPredecessor(), &Tag);
561 reportBug(ResultPtrType, ExprTypeAboveCast, N, Sym, C);
562 return;
563 }
564}
565
566/// Register checker.
567void ento::registerObjCGenericsChecker(CheckerManager &mgr) {
568 mgr.registerChecker<ObjCGenericsChecker>();
569}