File indexing completed on 2024-05-12 05:41:01
0001 /* 0002 SPDX-FileCopyrightText: 2019 Jean-Michaƫl Celerier <jeanmichael.celerier@gmail.com> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "qproperty-type-mismatch.h" 0008 #include "AccessSpecifierManager.h" 0009 #include "ClazyContext.h" 0010 #include "TypeUtils.h" 0011 0012 #include <clang/AST/Decl.h> 0013 #include <clang/AST/DeclCXX.h> 0014 #include <clang/AST/Expr.h> 0015 #include <clang/AST/ExprCXX.h> 0016 #include <clang/AST/Stmt.h> 0017 #include <clang/AST/Type.h> 0018 #include <clang/Basic/IdentifierTable.h> 0019 #include <clang/Basic/LLVM.h> 0020 #include <clang/Basic/SourceManager.h> 0021 #include <clang/Lex/Token.h> 0022 #include <llvm/ADT/ArrayRef.h> 0023 #include <llvm/ADT/StringRef.h> 0024 #include <llvm/Support/Casting.h> 0025 0026 #include <algorithm> 0027 #include <cctype> 0028 #include <string_view> 0029 0030 namespace clang 0031 { 0032 class Decl; 0033 class MacroInfo; 0034 } // namespace clang 0035 0036 using namespace clang; 0037 0038 QPropertyTypeMismatch::QPropertyTypeMismatch(const std::string &name, ClazyContext *context) 0039 : CheckBase(name, context) 0040 { 0041 enablePreProcessorCallbacks(); 0042 context->enableVisitallTypeDefs(); 0043 } 0044 0045 void QPropertyTypeMismatch::VisitDecl(clang::Decl *decl) 0046 { 0047 if (auto *method = dyn_cast<CXXMethodDecl>(decl)) { 0048 VisitMethod(*method); 0049 } else if (auto *field = dyn_cast<FieldDecl>(decl)) { 0050 VisitField(*field); 0051 } else if (auto *typedefdecl = dyn_cast<TypedefNameDecl>(decl)) { 0052 VisitTypedef(typedefdecl); 0053 } 0054 } 0055 0056 void QPropertyTypeMismatch::VisitMethod(const clang::CXXMethodDecl &method) 0057 { 0058 if (method.isThisDeclarationADefinition() && !method.hasInlineBody()) { 0059 return; 0060 } 0061 0062 const auto &theClass = method.getParent(); 0063 const auto &classRange = theClass->getSourceRange(); 0064 const auto &methodName = method.getNameInfo().getName().getAsString(); 0065 0066 for (const auto &prop : m_qproperties) { 0067 if (classRange.getBegin() < prop.loc && prop.loc < classRange.getEnd()) { 0068 checkMethodAgainstProperty(prop, method, methodName); 0069 } 0070 } 0071 } 0072 0073 void QPropertyTypeMismatch::VisitField(const FieldDecl &field) 0074 { 0075 const auto &theClass = field.getParent(); 0076 const auto &classRange = theClass->getSourceRange(); 0077 const auto &methodName = field.getName().str(); 0078 0079 for (const auto &prop : m_qproperties) { 0080 if (classRange.getBegin() < prop.loc && prop.loc < classRange.getEnd()) { 0081 checkFieldAgainstProperty(prop, field, methodName); 0082 } 0083 } 0084 } 0085 0086 void QPropertyTypeMismatch::VisitTypedef(const clang::TypedefNameDecl *td) 0087 { 0088 // Since when processing Q_PROPERTY we're at the pre-processor stage we don't have access 0089 // to the Qualtypes, so catch any typedefs here 0090 QualType underlyingType = td->getUnderlyingType(); 0091 m_typedefMap[td->getQualifiedNameAsString()] = underlyingType; 0092 m_typedefMap[td->getNameAsString()] = underlyingType; // It might be written unqualified in the Q_PROPERTY 0093 0094 // FIXME: All the above is a bit flaky, as we don't know the actual namespace when the type is written without namespace in Q_PROPERTY 0095 // Proper solution would be to process the .moc instead of doing text manipulation with the macros we receive 0096 } 0097 0098 std::string QPropertyTypeMismatch::cleanupType(QualType type, bool unscoped) const 0099 { 0100 type = type.getNonReferenceType().getCanonicalType().getUnqualifiedType(); 0101 0102 PrintingPolicy po(lo()); 0103 po.SuppressTagKeyword = true; 0104 po.SuppressScope = unscoped; 0105 0106 std::string str = type.getAsString(po); 0107 str.erase(std::remove_if(str.begin(), 0108 str.end(), 0109 [](char c) { 0110 return std::isspace(c); 0111 }), 0112 str.end()); 0113 0114 return str; 0115 } 0116 0117 void QPropertyTypeMismatch::checkMethodAgainstProperty(const Property &prop, const CXXMethodDecl &method, const std::string &methodName) 0118 { 0119 auto error_begin = [&] { 0120 return "Q_PROPERTY '" + prop.name + "' of type '" + prop.type + "' is mismatched with "; 0121 }; 0122 0123 if (prop.read == methodName) { 0124 std::string retTypeStr; 0125 if (!typesMatch(prop.type, method.getReturnType(), retTypeStr)) { 0126 emitWarning(&method, error_begin() + "method '" + methodName + "' of return type '" + retTypeStr + "'"); 0127 } 0128 } else if (prop.write == methodName) { 0129 switch (method.getNumParams()) { 0130 case 0: 0131 emitWarning(&method, error_begin() + "method '" + methodName + "' with no parameters"); 0132 break; 0133 case 1: { 0134 std::string parmTypeStr; 0135 if (!typesMatch(prop.type, method.getParamDecl(0)->getType(), parmTypeStr)) { 0136 emitWarning(&method, error_begin() + "method '" + methodName + "' with parameter of type '" + parmTypeStr + "'"); 0137 } 0138 break; 0139 } 0140 default: 0141 // Commented out: Too verbose and it's not a bug, maybe wrap with an option for the purists 0142 // emitWarning(&method, error_begin() + "method '" + methodName + "' with too many parameters"); 0143 break; 0144 } 0145 } else if (prop.notify == methodName) { 0146 switch (method.getNumParams()) { 0147 case 0: 0148 break; 0149 case 2: { 0150 /* 0151 // Commented out: Too verbose and it's not a bug, maybe wrap with an option for the purists 0152 auto param1TypeStr = cleanupType(method.getParamDecl(1)->getType()); 0153 if(param1TypeStr.find("QPrivateSignal") == std::string::npos) 0154 { 0155 emitWarning(&method, error_begin() + "signal '" + methodName + "' with too many parameters" + param1TypeStr); 0156 break; 0157 } 0158 0159 // We want to check the first parameter too : 0160 [[fallthrough]];*/ 0161 } 0162 case 1: { 0163 std::string param0TypeStr; 0164 if (!typesMatch(prop.type, method.getParamDecl(0)->getType(), param0TypeStr)) { 0165 const bool isPrivateSignal = param0TypeStr.find("QPrivateSignal") != std::string::npos; 0166 if (!isPrivateSignal) { 0167 emitWarning(&method, error_begin() + "signal '" + methodName + "' with parameter of type '" + param0TypeStr + "'"); 0168 } 0169 } 0170 break; 0171 } 0172 default: { 0173 break; 0174 } 0175 } 0176 } 0177 } 0178 0179 void QPropertyTypeMismatch::checkFieldAgainstProperty(const Property &prop, const FieldDecl &field, const std::string &fieldName) 0180 { 0181 if (prop.member && prop.name == fieldName) { 0182 std::string typeStr; 0183 if (!typesMatch(prop.type, field.getType(), typeStr)) { 0184 emitWarning(&field, 0185 "Q_PROPERTY '" + prop.name + "' of type '" + prop.type + "' is mismatched with member '" + fieldName + "' of type '" + typeStr + "'"); 0186 } 0187 } 0188 } 0189 0190 bool QPropertyTypeMismatch::typesMatch(const std::string &type1, QualType type2Qt, std::string &type2Cleaned) const 0191 { 0192 type2Cleaned = cleanupType(type2Qt); 0193 if (type1 == type2Cleaned) { 0194 return true; 0195 } 0196 0197 // Maybe it's a typedef 0198 auto it = m_typedefMap.find(type1); 0199 if (it != m_typedefMap.cend()) { 0200 return it->second == type2Qt || cleanupType(it->second) == type2Cleaned; 0201 } 0202 0203 // Maybe the difference is just the scope, if yes then don't warn. We already have a check for complaining about lack of scope 0204 type2Cleaned = cleanupType(type2Qt, /*unscopped=*/true); 0205 return type1 == type2Cleaned; 0206 } 0207 0208 void QPropertyTypeMismatch::VisitMacroExpands(const clang::Token &MacroNameTok, const clang::SourceRange &range, const MacroInfo *) 0209 { 0210 IdentifierInfo *ii = MacroNameTok.getIdentifierInfo(); 0211 if (!ii) { 0212 return; 0213 } 0214 0215 constexpr llvm::StringLiteral q_property{"Q_PROPERTY"}; 0216 0217 if (ii->getName() != q_property) { 0218 return; 0219 } 0220 0221 const CharSourceRange crange = Lexer::getAsCharRange(range, sm(), lo()); 0222 0223 llvm::StringRef text = Lexer::getSourceText(crange, sm(), lo()); 0224 0225 const auto openingLParen = std::find_if_not(text.begin() + q_property.size(), text.end(), [](char c) { 0226 return std::isspace(c); 0227 }); 0228 if (*openingLParen != '(') { 0229 emitWarning(range.getBegin(), "Could not parse argument of the Q_PROPERTY macro"); 0230 return; 0231 } 0232 0233 const std::size_t contentBegin = openingLParen - text.begin() + 1; 0234 std::size_t contentLength = text.size() - contentBegin; 0235 if (!text.empty() && text.back() == ')') { 0236 --contentLength; 0237 } 0238 text = text.substr(contentBegin, contentLength); 0239 0240 std::vector<std::string_view> split = clazy::splitStringBySpaces(text); 0241 if (split.size() < 2) { 0242 return; 0243 } 0244 0245 Property p; 0246 p.loc = range.getBegin(); 0247 0248 std::size_t splitIndex = 0; 0249 using namespace std::string_view_literals; 0250 0251 // Handle type (type string and any following modifiers) 0252 const auto isModifier = [](std::string_view str) { 0253 return str == "*"sv || str == "&"sv; 0254 }; 0255 0256 for (; isModifier(split[splitIndex]) || p.type.empty(); ++splitIndex) { 0257 p.type += split[splitIndex]; 0258 } 0259 0260 // Handle name 0261 p.name = split[splitIndex]; 0262 0263 std::size_t actualNameStartPos = 0; 0264 // FIXME: This is getting hairy, better use regexps 0265 for (unsigned int i = 0; i < p.name.size(); ++i) { 0266 if (p.name[i] == '*' || p.name[i] == '&') { 0267 p.type += p.name[i]; 0268 ++actualNameStartPos; 0269 } else { 0270 break; 0271 } 0272 } 0273 0274 if (actualNameStartPos) { 0275 p.name.erase(0, actualNameStartPos); 0276 } 0277 0278 // Handle Q_PROPERTY functions 0279 enum { None, Read, Write, Notify } next = None; 0280 0281 for (std::string_view &token : split) { 0282 switch (next) { 0283 case None: { 0284 if (token == "READ"sv) { 0285 next = Read; 0286 continue; 0287 } else if (token == "WRITE"sv) { 0288 next = Write; 0289 continue; 0290 } else if (token == "NOTIFY"sv) { 0291 next = Notify; 0292 continue; 0293 } else if (token == "MEMBER"sv) { 0294 p.member = true; 0295 break; 0296 } 0297 break; 0298 } 0299 case Read: 0300 p.read = token; 0301 break; 0302 case Write: 0303 p.write = token; 0304 break; 0305 case Notify: 0306 p.notify = token; 0307 break; 0308 } 0309 0310 next = None; 0311 } 0312 0313 m_qproperties.push_back(std::move(p)); 0314 }