//=== ObjCGenericsChecker.cpp - Path sensitive checker for Generics *- C++ -*=// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// // // This checker tries to find type errors that the compiler is not able to catch // due to the implicit conversions that were introduced for backward // compatibility. // //===----------------------------------------------------------------------===// #include "ClangSACheckers.h" #include "clang/AST/ParentMap.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" using namespace clang; using namespace ento; // ProgramState trait - a map from symbol to its specialized type. REGISTER_MAP_WITH_PROGRAMSTATE(TypeParamMap, SymbolRef, const ObjCObjectPointerType *) namespace { class ObjCGenericsChecker : public Checker> { public: void checkPreObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const; void checkPostObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const; void checkPostStmt(const CastExpr *CE, CheckerContext &C) const; void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const; private: mutable std::unique_ptr ObjCGenericsBugType; void initBugType() const { if (!ObjCGenericsBugType) ObjCGenericsBugType.reset( new BugType(this, "Generics", categories::CoreFoundationObjectiveC)); } class GenericsBugVisitor : public BugReporterVisitorImpl { public: GenericsBugVisitor(SymbolRef S) : Sym(S) {} void Profile(llvm::FoldingSetNodeID &ID) const override { static int X = 0; ID.AddPointer(&X); ID.AddPointer(Sym); } PathDiagnosticPiece *VisitNode(const ExplodedNode *N, const ExplodedNode *PrevN, BugReporterContext &BRC, BugReport &BR) override; private: // The tracked symbol. SymbolRef Sym; }; void reportGenericsBug(const ObjCObjectPointerType *From, const ObjCObjectPointerType *To, ExplodedNode *N, SymbolRef Sym, CheckerContext &C, const Stmt *ReportedNode = nullptr) const; void checkReturnType(const ObjCMessageExpr *MessageExpr, const ObjCObjectPointerType *TrackedType, SymbolRef Sym, const ObjCMethodDecl *Method, ArrayRef TypeArgs, bool SubscriptOrProperty, CheckerContext &C) const; }; } // end anonymous namespace void ObjCGenericsChecker::reportGenericsBug(const ObjCObjectPointerType *From, const ObjCObjectPointerType *To, ExplodedNode *N, SymbolRef Sym, CheckerContext &C, const Stmt *ReportedNode) const { initBugType(); SmallString<192> Buf; llvm::raw_svector_ostream OS(Buf); OS << "Conversion from value of type '"; QualType::print(From, Qualifiers(), OS, C.getLangOpts(), llvm::Twine()); OS << "' to incompatible type '"; QualType::print(To, Qualifiers(), OS, C.getLangOpts(), llvm::Twine()); OS << "'"; std::unique_ptr R( new BugReport(*ObjCGenericsBugType, OS.str(), N)); R->markInteresting(Sym); R->addVisitor(llvm::make_unique(Sym)); if (ReportedNode) R->addRange(ReportedNode->getSourceRange()); C.emitReport(std::move(R)); } PathDiagnosticPiece *ObjCGenericsChecker::GenericsBugVisitor::VisitNode( const ExplodedNode *N, const ExplodedNode *PrevN, BugReporterContext &BRC, BugReport &BR) { ProgramStateRef state = N->getState(); ProgramStateRef statePrev = PrevN->getState(); const ObjCObjectPointerType *const *TrackedType = state->get(Sym); const ObjCObjectPointerType *const *TrackedTypePrev = statePrev->get(Sym); if (!TrackedType) return nullptr; if (TrackedTypePrev && *TrackedTypePrev == *TrackedType) return nullptr; // Retrieve the associated statement. const Stmt *S = nullptr; ProgramPoint ProgLoc = N->getLocation(); if (Optional SP = ProgLoc.getAs()) { S = SP->getStmt(); } if (!S) return nullptr; const LangOptions &LangOpts = BRC.getASTContext().getLangOpts(); SmallString<256> Buf; llvm::raw_svector_ostream OS(Buf); OS << "Type '"; QualType::print(*TrackedType, Qualifiers(), OS, LangOpts, llvm::Twine()); OS << "' is inferred from "; if (const auto *ExplicitCast = dyn_cast(S)) { OS << "explicit cast (from '"; QualType::print(ExplicitCast->getSubExpr()->getType().getTypePtr(), Qualifiers(), OS, LangOpts, llvm::Twine()); OS << "' to '"; QualType::print(ExplicitCast->getType().getTypePtr(), Qualifiers(), OS, LangOpts, llvm::Twine()); OS << "')"; } else if (const auto *ImplicitCast = dyn_cast(S)) { OS << "implicit cast (from '"; QualType::print(ImplicitCast->getSubExpr()->getType().getTypePtr(), Qualifiers(), OS, LangOpts, llvm::Twine()); OS << "' to '"; QualType::print(ImplicitCast->getType().getTypePtr(), Qualifiers(), OS, LangOpts, llvm::Twine()); OS << "')"; } else { OS << "this context"; } // Generate the extra diagnostic. PathDiagnosticLocation Pos(S, BRC.getSourceManager(), N->getLocationContext()); return new PathDiagnosticEventPiece(Pos, OS.str(), true, nullptr); } /// Clean up the states stored by the checker. void ObjCGenericsChecker::checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const { if (!SR.hasDeadSymbols()) return; ProgramStateRef State = C.getState(); TypeParamMapTy TyParMap = State->get(); for (TypeParamMapTy::iterator I = TyParMap.begin(), E = TyParMap.end(); I != E; ++I) { if (SR.isDead(I->first)) { State = State->remove(I->first); } } } static const ObjCObjectPointerType *getMostInformativeDerivedClassImpl( const ObjCObjectPointerType *From, const ObjCObjectPointerType *To, const ObjCObjectPointerType *MostInformativeCandidate, ASTContext &C) { // Checking if from and to are the same classes modulo specialization. if (From->getInterfaceDecl()->getCanonicalDecl() == To->getInterfaceDecl()->getCanonicalDecl()) { if (To->isSpecialized()) { assert(MostInformativeCandidate->isSpecialized()); return MostInformativeCandidate; } return From; } const auto *SuperOfTo = To->getObjectType()->getSuperClassType()->getAs(); assert(SuperOfTo); QualType SuperPtrOfToQual = C.getObjCObjectPointerType(QualType(SuperOfTo, 0)); const auto *SuperPtrOfTo = SuperPtrOfToQual->getAs(); if (To->isUnspecialized()) return getMostInformativeDerivedClassImpl(From, SuperPtrOfTo, SuperPtrOfTo, C); else return getMostInformativeDerivedClassImpl(From, SuperPtrOfTo, MostInformativeCandidate, C); } /// A downcast may loose specialization information. E. g.: /// MutableMap : Map /// The downcast to MutableMap looses the information about the types of the /// Map (due to the type parameters are not being forwarded to Map), and in /// general there is no way to recover that information from the /// declaration. In order to have to most information, lets find the most /// derived type that has all the type parameters forwarded. /// /// Get the a subclass of \p From (which has a lower bound \p To) that do not /// loose information about type parameters. \p To has to be a subclass of /// \p From. From has to be specialized. static const ObjCObjectPointerType * getMostInformativeDerivedClass(const ObjCObjectPointerType *From, const ObjCObjectPointerType *To, ASTContext &C) { return getMostInformativeDerivedClassImpl(From, To, To, C); } /// Inputs: /// \param StaticLowerBound Static lower bound for a symbol. The dynamic lower /// bound might be the subclass of this type. /// \param StaticUpperBound A static upper bound for a symbol. /// \p StaticLowerBound expected to be the subclass of \p StaticUpperBound. /// \param Current The type that was inferred for a symbol in a previous /// context. Might be null when this is the first time that inference happens. /// Precondition: /// \p StaticLowerBound or \p StaticUpperBound is specialized. If \p Current /// is not null, it is specialized. /// Possible cases: /// (1) The \p Current is null and \p StaticLowerBound <: \p StaticUpperBound /// (2) \p StaticLowerBound <: \p Current <: \p StaticUpperBound /// (3) \p Current <: \p StaticLowerBound <: \p StaticUpperBound /// (4) \p StaticLowerBound <: \p StaticUpperBound <: \p Current /// Effect: /// Use getMostInformativeDerivedClass with the upper and lower bound of the /// set {\p StaticLowerBound, \p Current, \p StaticUpperBound}. The computed /// lower bound must be specialized. If the result differs from \p Current or /// \p Current is null, store the result. static bool storeWhenMoreInformative(ProgramStateRef &State, SymbolRef Sym, const ObjCObjectPointerType *const *Current, const ObjCObjectPointerType *StaticLowerBound, const ObjCObjectPointerType *StaticUpperBound, ASTContext &C) { // Precondition assert(StaticUpperBound->isSpecialized() || StaticLowerBound->isSpecialized()); assert(!Current || (*Current)->isSpecialized()); // Case (1) if (!Current) { if (StaticUpperBound->isUnspecialized()) { State = State->set(Sym, StaticLowerBound); return true; } // Upper bound is specialized. const ObjCObjectPointerType *WithMostInfo = getMostInformativeDerivedClass(StaticUpperBound, StaticLowerBound, C); State = State->set(Sym, WithMostInfo); return true; } // Case (3) if (C.canAssignObjCInterfaces(StaticLowerBound, *Current)) { return false; } // Case (4) if (C.canAssignObjCInterfaces(*Current, StaticUpperBound)) { // The type arguments might not be forwarded at any point of inheritance. const ObjCObjectPointerType *WithMostInfo = getMostInformativeDerivedClass(*Current, StaticUpperBound, C); WithMostInfo = getMostInformativeDerivedClass(WithMostInfo, StaticLowerBound, C); if (WithMostInfo == *Current) return false; State = State->set(Sym, WithMostInfo); return true; } // Case (2) const ObjCObjectPointerType *WithMostInfo = getMostInformativeDerivedClass(*Current, StaticLowerBound, C); if (WithMostInfo != *Current) { State = State->set(Sym, WithMostInfo); return true; } return false; } /// Type inference based on static type information that is available for the /// cast and the tracked type information for the given symbol. When the tracked /// symbol and the destination type of the cast are unrelated, report an error. void ObjCGenericsChecker::checkPostStmt(const CastExpr *CE, CheckerContext &C) const { if (CE->getCastKind() != CK_BitCast) return; QualType OriginType = CE->getSubExpr()->getType(); QualType DestType = CE->getType(); const auto *OrigObjectPtrType = OriginType->getAs(); const auto *DestObjectPtrType = DestType->getAs(); if (!OrigObjectPtrType || !DestObjectPtrType) return; ASTContext &ASTCtxt = C.getASTContext(); // This checker detects the subtyping relationships using the assignment // rules. In order to be able to do this the kindofness must be stripped // first. The checker treats every type as kindof type anyways: when the // tracked type is the subtype of the static type it tries to look up the // methods in the tracked type first. OrigObjectPtrType = OrigObjectPtrType->stripObjCKindOfTypeAndQuals(ASTCtxt); DestObjectPtrType = DestObjectPtrType->stripObjCKindOfTypeAndQuals(ASTCtxt); // TODO: erase tracked information when there is a cast to unrelated type // and everything is unspecialized statically. if (OrigObjectPtrType->isUnspecialized() && DestObjectPtrType->isUnspecialized()) return; ProgramStateRef State = C.getState(); SymbolRef Sym = State->getSVal(CE, C.getLocationContext()).getAsSymbol(); if (!Sym) return; // Check which assignments are legal. bool OrigToDest = ASTCtxt.canAssignObjCInterfaces(DestObjectPtrType, OrigObjectPtrType); bool DestToOrig = ASTCtxt.canAssignObjCInterfaces(OrigObjectPtrType, DestObjectPtrType); const ObjCObjectPointerType *const *TrackedType = State->get(Sym); // Downcasts and upcasts handled in an uniform way regardless of being // explicit. Explicit casts however can happen between mismatched types. if (isa(CE) && !OrigToDest && !DestToOrig) { // Mismatched types. If the DestType specialized, store it. Forget the // tracked type otherwise. if (DestObjectPtrType->isSpecialized()) { State = State->set(Sym, DestObjectPtrType); C.addTransition(State); } else if (TrackedType) { State = State->remove(Sym); C.addTransition(State); } return; } // The tracked type should be the sub or super class of the static destination // type. When an (implicit) upcast or a downcast happens according to static // types, and there is no subtyping relationship between the tracked and the // static destination types, it indicates an error. if (TrackedType && !ASTCtxt.canAssignObjCInterfaces(DestObjectPtrType, *TrackedType) && !ASTCtxt.canAssignObjCInterfaces(*TrackedType, DestObjectPtrType)) { static CheckerProgramPointTag IllegalConv(this, "IllegalConversion"); ExplodedNode *N = C.addTransition(State, &IllegalConv); reportGenericsBug(*TrackedType, DestObjectPtrType, N, Sym, C); return; } // Handle downcasts and upcasts. const ObjCObjectPointerType *LowerBound = DestObjectPtrType; const ObjCObjectPointerType *UpperBound = OrigObjectPtrType; if (OrigToDest && !DestToOrig) std::swap(LowerBound, UpperBound); // The id type is not a real bound. Eliminate it. LowerBound = LowerBound->isObjCIdType() ? UpperBound : LowerBound; UpperBound = UpperBound->isObjCIdType() ? LowerBound : UpperBound; if (storeWhenMoreInformative(State, Sym, TrackedType, LowerBound, UpperBound, ASTCtxt)) { C.addTransition(State); } } static const Expr *stripCastsAndSugar(const Expr *E) { E = E->IgnoreParenImpCasts(); if (const PseudoObjectExpr *POE = dyn_cast(E)) E = POE->getSyntacticForm()->IgnoreParenImpCasts(); if (const OpaqueValueExpr *OVE = dyn_cast(E)) E = OVE->getSourceExpr()->IgnoreParenImpCasts(); return E; } /// This callback is used to infer the types for Class variables. This info is /// used later to validate messages that sent to classes. Class variables are /// initialized with by invoking the 'class' method on a class. void ObjCGenericsChecker::checkPostObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const { const ObjCMessageExpr *MessageExpr = M.getOriginExpr(); SymbolRef Sym = M.getReturnValue().getAsSymbol(); if (!Sym) return; Selector Sel = MessageExpr->getSelector(); // We are only interested in cases where the class method is invoked on a // class. This method is provided by the runtime and available on all classes. if (MessageExpr->getReceiverKind() != ObjCMessageExpr::Class || Sel.getAsString() != "class") return; QualType ReceiverType = MessageExpr->getClassReceiver(); const auto *ReceiverClassType = ReceiverType->getAs(); QualType ReceiverClassPointerType = C.getASTContext().getObjCObjectPointerType( QualType(ReceiverClassType, 0)); if (!ReceiverClassType->isSpecialized()) return; const auto *InferredType = ReceiverClassPointerType->getAs(); assert(InferredType); ProgramStateRef State = C.getState(); State = State->set(Sym, InferredType); C.addTransition(State); } static bool isObjCTypeParamDependent(QualType Type) { // It is illegal to typedef parameterized types inside an interface. Therfore // an Objective-C type can only be dependent on a type parameter when the type // parameter structurally present in the type itself. class IsObjCTypeParamDependentTypeVisitor : public RecursiveASTVisitor { public: IsObjCTypeParamDependentTypeVisitor() : Result(false) {} bool VisitTypedefType(const TypedefType *Type) { if (isa(Type->getDecl())) { Result = true; return false; } return true; } bool Result; }; IsObjCTypeParamDependentTypeVisitor Visitor; Visitor.TraverseType(Type); return Visitor.Result; } /// A method might not be available in the interface indicated by the static /// type. However it might be available in the tracked type. In order to /// properly substitute the type parameters we need the declaration context of /// the method. The more specialized the enclosing class of the method is, the /// more likely that the parameter substitution will be successful. static const ObjCMethodDecl * findMethodDecl(const ObjCMessageExpr *MessageExpr, const ObjCObjectPointerType *TrackedType, ASTContext &ASTCtxt) { const ObjCMethodDecl *Method = nullptr; QualType ReceiverType = MessageExpr->getReceiverType(); const auto *ReceiverObjectPtrType = ReceiverType->getAs(); // Do this "devirtualization" on instance and class methods only. Trust the // static type on super and super class calls. if (MessageExpr->getReceiverKind() == ObjCMessageExpr::Instance || MessageExpr->getReceiverKind() == ObjCMessageExpr::Class) { // When the receiver type is id, Class, or some super class of the tracked // type, look up the method in the tracked type, not in the receiver type. // This way we preserve more information. if (ReceiverType->isObjCIdType() || ReceiverType->isObjCClassType() || ASTCtxt.canAssignObjCInterfaces(ReceiverObjectPtrType, TrackedType)) { const ObjCInterfaceDecl *InterfaceDecl = TrackedType->getInterfaceDecl(); // The method might not be found. Selector Sel = MessageExpr->getSelector(); Method = InterfaceDecl->lookupInstanceMethod(Sel); if (!Method) Method = InterfaceDecl->lookupClassMethod(Sel); } } // Fallback to statick method lookup when the one based on the tracked type // failed. return Method ? Method : MessageExpr->getMethodDecl(); } /// Validate that the return type of a message expression is used correctly. void ObjCGenericsChecker::checkReturnType( const ObjCMessageExpr *MessageExpr, const ObjCObjectPointerType *TrackedType, SymbolRef Sym, const ObjCMethodDecl *Method, ArrayRef TypeArgs, bool SubscriptOrProperty, CheckerContext &C) const { QualType StaticResultType = Method->getReturnType(); ASTContext &ASTCtxt = C.getASTContext(); // Check whether the result type was a type parameter. bool IsDeclaredAsInstanceType = StaticResultType == ASTCtxt.getObjCInstanceType(); if (!isObjCTypeParamDependent(StaticResultType) && !IsDeclaredAsInstanceType) return; QualType ResultType = Method->getReturnType().substObjCTypeArgs( ASTCtxt, TypeArgs, ObjCSubstitutionContext::Result); if (IsDeclaredAsInstanceType) ResultType = QualType(TrackedType, 0); const Stmt *Parent = C.getCurrentAnalysisDeclContext()->getParentMap().getParent(MessageExpr); if (SubscriptOrProperty) { // Properties and subscripts are not direct parents. Parent = C.getCurrentAnalysisDeclContext()->getParentMap().getParent(Parent); } const auto *ImplicitCast = dyn_cast_or_null(Parent); if (!ImplicitCast || ImplicitCast->getCastKind() != CK_BitCast) return; const auto *ExprTypeAboveCast = ImplicitCast->getType()->getAs(); const auto *ResultPtrType = ResultType->getAs(); if (!ExprTypeAboveCast || !ResultPtrType) return; // Only warn on unrelated types to avoid too many false positives on // downcasts. if (!ASTCtxt.canAssignObjCInterfaces(ExprTypeAboveCast, ResultPtrType) && !ASTCtxt.canAssignObjCInterfaces(ResultPtrType, ExprTypeAboveCast)) { static CheckerProgramPointTag Tag(this, "ReturnTypeMismatch"); ExplodedNode *N = C.addTransition(C.getState(), &Tag); reportGenericsBug(ResultPtrType, ExprTypeAboveCast, N, Sym, C); return; } } /// When the receiver has a tracked type, use that type to validate the /// argumments of the message expression and the return value. void ObjCGenericsChecker::checkPreObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const { ProgramStateRef State = C.getState(); SymbolRef Sym = M.getReceiverSVal().getAsSymbol(); if (!Sym) return; const ObjCObjectPointerType *const *TrackedType = State->get(Sym); if (!TrackedType) return; // Get the type arguments from tracked type and substitute type arguments // before do the semantic check. ASTContext &ASTCtxt = C.getASTContext(); const ObjCMessageExpr *MessageExpr = M.getOriginExpr(); const ObjCMethodDecl *Method = findMethodDecl(MessageExpr, *TrackedType, ASTCtxt); // It is possible to call non-existent methods in Obj-C. if (!Method) return; Optional> TypeArgs = (*TrackedType)->getObjCSubstitutions(Method->getDeclContext()); // This case might happen when there is an unspecialized override of a // specialized method. if (!TypeArgs) return; for (unsigned i = 0; i < Method->param_size(); i++) { const Expr *Arg = MessageExpr->getArg(i); const ParmVarDecl *Param = Method->parameters()[i]; QualType OrigParamType = Param->getType(); if (!isObjCTypeParamDependent(OrigParamType)) continue; QualType ParamType = OrigParamType.substObjCTypeArgs( ASTCtxt, *TypeArgs, ObjCSubstitutionContext::Parameter); // Check if it can be assigned const auto *ParamObjectPtrType = ParamType->getAs(); const auto *ArgObjectPtrType = stripCastsAndSugar(Arg)->getType()->getAs(); if (!ParamObjectPtrType || !ArgObjectPtrType) continue; // Check if we have more concrete tracked type that is not a super type of // the static argument type. SVal ArgSVal = M.getArgSVal(i); SymbolRef ArgSym = ArgSVal.getAsSymbol(); if (ArgSym) { const ObjCObjectPointerType *const *TrackedArgType = State->get(ArgSym); if (TrackedArgType && ASTCtxt.canAssignObjCInterfaces(ArgObjectPtrType, *TrackedArgType)) { ArgObjectPtrType = *TrackedArgType; } } // Warn when argument is incompatible with the parameter. if (!ASTCtxt.canAssignObjCInterfaces(ParamObjectPtrType, ArgObjectPtrType)) { static CheckerProgramPointTag Tag(this, "ArgTypeMismatch"); ExplodedNode *N = C.addTransition(State, &Tag); reportGenericsBug(ArgObjectPtrType, ParamObjectPtrType, N, Sym, C, Arg); return; } } checkReturnType(MessageExpr, *TrackedType, Sym, Method, *TypeArgs, M.getMessageKind() != OCM_Message, C); } /// Register checker. void ento::registerObjCGenericsChecker(CheckerManager &mgr) { mgr.registerChecker(); }