1 | //== DynamicTypeChecker.cpp ------------------------------------ -*- 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-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | // |
9 | // This checker looks for cases where the dynamic type of an object is unrelated |
10 | // to its static type. The type information utilized by this check is collected |
11 | // by the DynamicTypePropagation checker. This check does not report any type |
12 | // error for ObjC Generic types, in order to avoid duplicate erros from the |
13 | // ObjC Generics checker. This checker is not supposed to modify the program |
14 | // state, it is just the observer of the type information provided by other |
15 | // checkers. |
16 | // |
17 | //===----------------------------------------------------------------------===// |
18 | |
19 | #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" |
20 | #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" |
21 | #include "clang/StaticAnalyzer/Core/Checker.h" |
22 | #include "clang/StaticAnalyzer/Core/CheckerManager.h" |
23 | #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" |
24 | #include "clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h" |
25 | #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" |
26 | #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" |
27 | |
28 | using namespace clang; |
29 | using namespace ento; |
30 | |
31 | namespace { |
32 | class DynamicTypeChecker : public Checker<check::PostStmt<ImplicitCastExpr>> { |
33 | const BugType BT{this, "Dynamic and static type mismatch" , "Type Error" }; |
34 | |
35 | class DynamicTypeBugVisitor : public BugReporterVisitor { |
36 | public: |
37 | DynamicTypeBugVisitor(const MemRegion *Reg) : Reg(Reg) {} |
38 | |
39 | void Profile(llvm::FoldingSetNodeID &ID) const override { |
40 | static int X = 0; |
41 | ID.AddPointer(Ptr: &X); |
42 | ID.AddPointer(Ptr: Reg); |
43 | } |
44 | |
45 | PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, |
46 | BugReporterContext &BRC, |
47 | PathSensitiveBugReport &BR) override; |
48 | |
49 | private: |
50 | // The tracked region. |
51 | const MemRegion *Reg; |
52 | }; |
53 | |
54 | void reportTypeError(QualType DynamicType, QualType StaticType, |
55 | const MemRegion *Reg, const Stmt *ReportedNode, |
56 | CheckerContext &C) const; |
57 | |
58 | public: |
59 | void checkPostStmt(const ImplicitCastExpr *CE, CheckerContext &C) const; |
60 | }; |
61 | } |
62 | |
63 | void DynamicTypeChecker::reportTypeError(QualType DynamicType, |
64 | QualType StaticType, |
65 | const MemRegion *Reg, |
66 | const Stmt *ReportedNode, |
67 | CheckerContext &C) const { |
68 | SmallString<192> Buf; |
69 | llvm::raw_svector_ostream OS(Buf); |
70 | OS << "Object has a dynamic type '" ; |
71 | QualType::print(ty: DynamicType.getTypePtr(), qs: Qualifiers(), OS, policy: C.getLangOpts(), |
72 | PlaceHolder: llvm::Twine()); |
73 | OS << "' which is incompatible with static type '" ; |
74 | QualType::print(ty: StaticType.getTypePtr(), qs: Qualifiers(), OS, policy: C.getLangOpts(), |
75 | PlaceHolder: llvm::Twine()); |
76 | OS << "'" ; |
77 | auto R = std::make_unique<PathSensitiveBugReport>( |
78 | args: BT, args: OS.str(), args: C.generateNonFatalErrorNode()); |
79 | R->markInteresting(R: Reg); |
80 | R->addVisitor(visitor: std::make_unique<DynamicTypeBugVisitor>(args&: Reg)); |
81 | R->addRange(R: ReportedNode->getSourceRange()); |
82 | C.emitReport(R: std::move(R)); |
83 | } |
84 | |
85 | PathDiagnosticPieceRef DynamicTypeChecker::DynamicTypeBugVisitor::VisitNode( |
86 | const ExplodedNode *N, BugReporterContext &BRC, PathSensitiveBugReport &) { |
87 | ProgramStateRef State = N->getState(); |
88 | ProgramStateRef StatePrev = N->getFirstPred()->getState(); |
89 | |
90 | DynamicTypeInfo TrackedType = getDynamicTypeInfo(State, MR: Reg); |
91 | DynamicTypeInfo TrackedTypePrev = getDynamicTypeInfo(State: StatePrev, MR: Reg); |
92 | if (!TrackedType.isValid()) |
93 | return nullptr; |
94 | |
95 | if (TrackedTypePrev.isValid() && |
96 | TrackedTypePrev.getType() == TrackedType.getType()) |
97 | return nullptr; |
98 | |
99 | // Retrieve the associated statement. |
100 | const Stmt *S = N->getStmtForDiagnostics(); |
101 | if (!S) |
102 | return nullptr; |
103 | |
104 | const LangOptions &LangOpts = BRC.getASTContext().getLangOpts(); |
105 | |
106 | SmallString<256> Buf; |
107 | llvm::raw_svector_ostream OS(Buf); |
108 | OS << "Type '" ; |
109 | QualType::print(ty: TrackedType.getType().getTypePtr(), qs: Qualifiers(), OS, |
110 | policy: LangOpts, PlaceHolder: llvm::Twine()); |
111 | OS << "' is inferred from " ; |
112 | |
113 | if (const auto *ExplicitCast = dyn_cast<ExplicitCastExpr>(Val: S)) { |
114 | OS << "explicit cast (from '" ; |
115 | QualType::print(ExplicitCast->getSubExpr()->getType().getTypePtr(), |
116 | Qualifiers(), OS, LangOpts, llvm::Twine()); |
117 | OS << "' to '" ; |
118 | QualType::print(ExplicitCast->getType().getTypePtr(), Qualifiers(), OS, |
119 | LangOpts, llvm::Twine()); |
120 | OS << "')" ; |
121 | } else if (const auto *ImplicitCast = dyn_cast<ImplicitCastExpr>(Val: S)) { |
122 | OS << "implicit cast (from '" ; |
123 | QualType::print(ImplicitCast->getSubExpr()->getType().getTypePtr(), |
124 | Qualifiers(), OS, LangOpts, llvm::Twine()); |
125 | OS << "' to '" ; |
126 | QualType::print(ImplicitCast->getType().getTypePtr(), Qualifiers(), OS, |
127 | LangOpts, llvm::Twine()); |
128 | OS << "')" ; |
129 | } else { |
130 | OS << "this context" ; |
131 | } |
132 | |
133 | // Generate the extra diagnostic. |
134 | PathDiagnosticLocation Pos(S, BRC.getSourceManager(), |
135 | N->getLocationContext()); |
136 | return std::make_shared<PathDiagnosticEventPiece>(args&: Pos, args: OS.str(), args: true); |
137 | } |
138 | |
139 | static bool hasDefinition(const ObjCObjectPointerType *ObjPtr) { |
140 | const ObjCInterfaceDecl *Decl = ObjPtr->getInterfaceDecl(); |
141 | if (!Decl) |
142 | return false; |
143 | |
144 | return Decl->getDefinition(); |
145 | } |
146 | |
147 | // TODO: consider checking explicit casts? |
148 | void DynamicTypeChecker::checkPostStmt(const ImplicitCastExpr *CE, |
149 | CheckerContext &C) const { |
150 | // TODO: C++ support. |
151 | if (CE->getCastKind() != CK_BitCast) |
152 | return; |
153 | |
154 | const MemRegion *Region = C.getSVal(CE).getAsRegion(); |
155 | if (!Region) |
156 | return; |
157 | |
158 | ProgramStateRef State = C.getState(); |
159 | DynamicTypeInfo DynTypeInfo = getDynamicTypeInfo(State, MR: Region); |
160 | |
161 | if (!DynTypeInfo.isValid()) |
162 | return; |
163 | |
164 | QualType DynType = DynTypeInfo.getType(); |
165 | QualType StaticType = CE->getType(); |
166 | |
167 | const auto *DynObjCType = DynType->getAs<ObjCObjectPointerType>(); |
168 | const auto *StaticObjCType = StaticType->getAs<ObjCObjectPointerType>(); |
169 | |
170 | if (!DynObjCType || !StaticObjCType) |
171 | return; |
172 | |
173 | if (!hasDefinition(DynObjCType) || !hasDefinition(StaticObjCType)) |
174 | return; |
175 | |
176 | ASTContext &ASTCtxt = C.getASTContext(); |
177 | |
178 | // Strip kindeofness to correctly detect subtyping relationships. |
179 | DynObjCType = DynObjCType->stripObjCKindOfTypeAndQuals(ASTCtxt); |
180 | StaticObjCType = StaticObjCType->stripObjCKindOfTypeAndQuals(ASTCtxt); |
181 | |
182 | // Specialized objects are handled by the generics checker. |
183 | if (StaticObjCType->isSpecialized()) |
184 | return; |
185 | |
186 | if (ASTCtxt.canAssignObjCInterfaces(StaticObjCType, DynObjCType)) |
187 | return; |
188 | |
189 | if (DynTypeInfo.canBeASubClass() && |
190 | ASTCtxt.canAssignObjCInterfaces(DynObjCType, StaticObjCType)) |
191 | return; |
192 | |
193 | reportTypeError(DynType, StaticType, Region, CE, C); |
194 | } |
195 | |
196 | void ento::registerDynamicTypeChecker(CheckerManager &mgr) { |
197 | mgr.registerChecker<DynamicTypeChecker>(); |
198 | } |
199 | |
200 | bool ento::shouldRegisterDynamicTypeChecker(const CheckerManager &mgr) { |
201 | return true; |
202 | } |
203 | |