Changeset View
Changeset View
Standalone View
Standalone View
src/checks/level1/qproperty-type-mismatch.cpp
- This file was added.
1 | /* | ||||
---|---|---|---|---|---|
2 | This file is part of the clazy static checker. | ||||
3 | | ||||
4 | Copyright (C) 2019 Jean-Michaël Celerier <jeanmichael.celerier@gmail.com> | ||||
5 | | ||||
6 | This library is free software; you can redistribute it and/or | ||||
7 | modify it under the terms of the GNU Library General Public | ||||
8 | License as published by the Free Software Foundation; either | ||||
9 | version 2 of the License, or (at your option) any later version. | ||||
10 | | ||||
11 | This library is distributed in the hope that it will be useful, | ||||
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||||
14 | Library General Public License for more details. | ||||
15 | | ||||
16 | You should have received a copy of the GNU Library General Public License | ||||
17 | along with this library; see the file COPYING.LIB. If not, write to | ||||
18 | the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, | ||||
19 | Boston, MA 02110-1301, USA. | ||||
20 | */ | ||||
21 | | ||||
22 | #include "qproperty-type-mismatch.h" | ||||
23 | #include "HierarchyUtils.h" | ||||
24 | #include "TypeUtils.h" | ||||
25 | #include "ClazyContext.h" | ||||
26 | #include "AccessSpecifierManager.h" | ||||
27 | #include "SourceCompatibilityHelpers.h" | ||||
28 | #include "StringUtils.h" | ||||
29 | | ||||
30 | #include <clang/AST/Decl.h> | ||||
31 | #include <clang/AST/DeclCXX.h> | ||||
32 | #include <clang/AST/Expr.h> | ||||
33 | #include <clang/AST/ExprCXX.h> | ||||
34 | #include <clang/AST/Stmt.h> | ||||
35 | #include <clang/AST/Type.h> | ||||
36 | #include <clang/Basic/IdentifierTable.h> | ||||
37 | #include <clang/Basic/LLVM.h> | ||||
38 | #include <clang/Basic/SourceManager.h> | ||||
39 | #include <clang/Lex/Token.h> | ||||
40 | #include <llvm/ADT/ArrayRef.h> | ||||
41 | #include <llvm/ADT/StringRef.h> | ||||
42 | #include <llvm/Support/Casting.h> | ||||
43 | #include <algorithm> | ||||
44 | #include <cctype> | ||||
45 | | ||||
46 | namespace clang { | ||||
47 | class Decl; | ||||
48 | class MacroInfo; | ||||
49 | } // namespace clang | ||||
50 | | ||||
51 | using namespace clang; | ||||
52 | using namespace std; | ||||
53 | | ||||
54 | | ||||
55 | QPropertyTypeMismatch::QPropertyTypeMismatch(const std::string &name, ClazyContext *context) | ||||
56 | : CheckBase(name, context) | ||||
57 | { | ||||
58 | enablePreProcessorCallbacks(); | ||||
59 | } | ||||
60 | | ||||
61 | void QPropertyTypeMismatch::VisitDecl(clang::Decl *decl) | ||||
62 | { | ||||
63 | if (auto method = dyn_cast<CXXMethodDecl>(decl)) | ||||
64 | VisitMethod(*method); | ||||
65 | else if (auto field = dyn_cast<FieldDecl>(decl)) | ||||
66 | VisitField(*field); | ||||
67 | } | ||||
68 | | ||||
69 | void QPropertyTypeMismatch::VisitMethod(const clang::CXXMethodDecl & method) | ||||
70 | { | ||||
71 | if (method.isThisDeclarationADefinition() && !method.hasInlineBody()) | ||||
72 | return; | ||||
73 | | ||||
74 | const auto& theClass = method.getParent(); | ||||
75 | const auto& classRange = theClass->getSourceRange(); | ||||
76 | const auto& methodName = method.getNameInfo().getName().getAsString(); | ||||
77 | | ||||
78 | for(const auto& prop : m_qproperties) | ||||
79 | { | ||||
80 | if(classRange.getBegin() < prop.loc && prop.loc < classRange.getEnd()) | ||||
81 | { | ||||
82 | checkMethodAgainstProperty(prop, method, methodName); | ||||
83 | } | ||||
84 | } | ||||
85 | } | ||||
86 | | ||||
87 | void QPropertyTypeMismatch::VisitField(const FieldDecl & field) | ||||
88 | { | ||||
89 | const auto& theClass = field.getParent(); | ||||
90 | const auto& classRange = theClass->getSourceRange(); | ||||
91 | const auto& methodName = field.getName().str(); | ||||
92 | | ||||
93 | for(const auto& prop : m_qproperties) | ||||
94 | { | ||||
95 | if(classRange.getBegin() < prop.loc && prop.loc < classRange.getEnd()) | ||||
96 | { | ||||
97 | checkFieldAgainstProperty(prop, field, methodName); | ||||
98 | } | ||||
99 | } | ||||
100 | } | ||||
101 | | ||||
102 | std::string QPropertyTypeMismatch::cleanupType(QualType type) { | ||||
103 | type = type.getNonReferenceType().getCanonicalType().getUnqualifiedType(); | ||||
104 | //type.removeLocalCVRQualifiers(Qualifiers::CVRMask); | ||||
105 | | ||||
106 | std::string str = type.getAsString(); | ||||
107 | if(str.compare(0, 6, "class ") == 0) | ||||
108 | str = str.substr(6); | ||||
109 | else if(str.compare(0, 7, "struct ") == 0) | ||||
110 | str = str.substr(7); | ||||
111 | | ||||
112 | str.erase(std::remove_if(str.begin(), str.end(), [] (char c) { return std::isspace(c); }), str.end()); | ||||
113 | | ||||
114 | return str; | ||||
115 | } | ||||
116 | | ||||
117 | void QPropertyTypeMismatch::checkMethodAgainstProperty (const Property& prop, const CXXMethodDecl& method, const std::string& methodName){ | ||||
118 | | ||||
119 | auto error_begin = [&] { return "Q_PROPERTY '" + prop.name + "' of type '" + prop.type + "' is mismatched with "; }; | ||||
120 | | ||||
121 | if(prop.read == methodName) | ||||
122 | { | ||||
123 | auto retTypeStr = cleanupType(method.getReturnType()); | ||||
124 | if(prop.type != retTypeStr) | ||||
125 | { | ||||
126 | emitWarning(&method, error_begin() + "method '" + methodName + "' of return type '"+ retTypeStr +"'"); | ||||
127 | } | ||||
128 | } | ||||
129 | else if(prop.write == methodName) | ||||
130 | { | ||||
131 | switch(method.getNumParams()) | ||||
132 | { | ||||
133 | case 0: | ||||
134 | emitWarning(&method, error_begin() + "method '" + methodName + "' with no parameters"); | ||||
135 | break; | ||||
136 | case 1: | ||||
137 | { | ||||
138 | auto parmTypeStr = cleanupType(method.getParamDecl(0)->getType()); | ||||
139 | if(prop.type != parmTypeStr) | ||||
140 | { | ||||
141 | emitWarning(&method, error_begin() + "method '" + methodName + "' with parameter of type '"+ parmTypeStr +"'"); | ||||
142 | } | ||||
143 | break; | ||||
144 | } | ||||
145 | default: | ||||
146 | emitWarning(&method, error_begin() + "method '" + methodName + "' with too many parameters"); | ||||
147 | break; | ||||
148 | } | ||||
149 | } | ||||
150 | else if(prop.notify == methodName) | ||||
151 | { | ||||
152 | switch(method.getNumParams()) | ||||
153 | { | ||||
154 | case 0: | ||||
155 | // Should this case be ok ? | ||||
156 | // I don't think it is good practice to have signals of a property without | ||||
157 | // the property value in parameter, but afaik it's valid in Qt. | ||||
158 | emitWarning(&method, "Q_PROPERTY '" + prop.name + "' of type '" + prop.type + "' is mismatched with signal '" + methodName + "' with no parameters"); | ||||
159 | break; | ||||
160 | case 2: | ||||
161 | { | ||||
162 | auto param1TypeStr = cleanupType(method.getParamDecl(1)->getType()); | ||||
163 | if(param1TypeStr.find("QPrivateSignal") == std::string::npos) | ||||
164 | { | ||||
165 | emitWarning(&method, error_begin() + "signal '" + methodName + "' with too many parameters" + param1TypeStr); | ||||
166 | break; | ||||
167 | } | ||||
168 | | ||||
169 | // We want to check the first parameter too : | ||||
170 | [[fallthrough]]; | ||||
171 | } | ||||
172 | case 1: | ||||
173 | { | ||||
174 | auto param0TypeStr = cleanupType(method.getParamDecl(0)->getType()); | ||||
175 | if(prop.type != param0TypeStr) | ||||
176 | { | ||||
177 | emitWarning(&method, error_begin() + "signal '" + methodName + "' with parameter of type '"+ param0TypeStr +"'"); | ||||
178 | } | ||||
179 | break; | ||||
180 | } | ||||
181 | default: | ||||
182 | { | ||||
183 | emitWarning(&method, error_begin() + "signal '" + methodName + "' with too many parameters"); | ||||
184 | break; | ||||
185 | } | ||||
186 | } | ||||
187 | } | ||||
188 | } | ||||
189 | | ||||
190 | void QPropertyTypeMismatch::checkFieldAgainstProperty (const Property& prop, const FieldDecl& field, const std::string& fieldName) | ||||
191 | { | ||||
192 | if(prop.member && prop.name == fieldName) | ||||
193 | { | ||||
194 | auto typeStr = cleanupType(field.getType()); | ||||
195 | if(prop.type != typeStr) | ||||
196 | { | ||||
197 | emitWarning(&field, "Q_PROPERTY '" + prop.name + "' of type '" + prop.type + "' is mismatched with member '" + fieldName + "' of type '"+ typeStr +"'"); | ||||
198 | } | ||||
199 | } | ||||
200 | } | ||||
201 | | ||||
202 | void QPropertyTypeMismatch::VisitMacroExpands(const clang::Token &MacroNameTok, const clang::SourceRange &range, const MacroInfo *) | ||||
203 | { | ||||
204 | IdentifierInfo *ii = MacroNameTok.getIdentifierInfo(); | ||||
205 | if(!ii) | ||||
206 | return; | ||||
207 | if(ii->getName() != "Q_PROPERTY") | ||||
208 | return; | ||||
209 | | ||||
210 | CharSourceRange crange = Lexer::getAsCharRange(range, sm(), lo()); | ||||
211 | | ||||
212 | string text = Lexer::getSourceText(crange, sm(), lo()); | ||||
213 | std::vector<std::string> split = clazy::splitString(text, ' '); | ||||
214 | if(split.size() < 2) | ||||
215 | return; | ||||
216 | | ||||
217 | Property p; | ||||
218 | p.loc = range.getBegin(); | ||||
219 | | ||||
220 | // Handle type | ||||
221 | clazy::rtrim(split[0]); | ||||
222 | p.type = split[0]; | ||||
223 | if(p.type.find("Q_PROPERTY(") == 0) | ||||
224 | p.type = p.type.substr(11); | ||||
225 | | ||||
226 | // Handle name | ||||
227 | clazy::rtrim(split[1]); | ||||
228 | p.name = split[1]; | ||||
229 | | ||||
230 | // Handle Q_PROPERTY functions | ||||
231 | enum { | ||||
232 | None, Read, Write, Notify | ||||
233 | } next = None; | ||||
234 | | ||||
235 | for (std::string &token : split) { | ||||
236 | clazy::rtrim(/*by-ref*/token); | ||||
237 | switch(next) | ||||
238 | { | ||||
239 | case None: | ||||
240 | { | ||||
241 | if (token == "READ") { | ||||
242 | next = Read; | ||||
243 | continue; | ||||
244 | } | ||||
245 | else if (token == "WRITE") { | ||||
246 | next = Write; | ||||
247 | continue; | ||||
248 | } | ||||
249 | else if (token == "NOTIFY") { | ||||
250 | next = Notify; | ||||
251 | continue; | ||||
252 | } | ||||
253 | else if (token == "MEMBER") { | ||||
254 | p.member = true; | ||||
255 | break; | ||||
256 | } | ||||
257 | break; | ||||
258 | } | ||||
259 | case Read: | ||||
260 | p.read = token; | ||||
261 | break; | ||||
262 | case Write: | ||||
263 | p.write = token; | ||||
264 | break; | ||||
265 | case Notify: | ||||
266 | p.notify = token; | ||||
267 | break; | ||||
268 | } | ||||
269 | | ||||
270 | next = None; | ||||
271 | } | ||||
272 | | ||||
273 | m_qproperties.push_back(std::move(p)); | ||||
274 | } | ||||
275 | |