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 }