diff --git a/src/checks/manuallevel/qproperty-type-mismatch.cpp b/src/checks/manuallevel/qproperty-type-mismatch.cpp index c0c1c34..06fd209 100644 --- a/src/checks/manuallevel/qproperty-type-mismatch.cpp +++ b/src/checks/manuallevel/qproperty-type-mismatch.cpp @@ -1,270 +1,284 @@ /* This file is part of the clazy static checker. Copyright (C) 2019 Jean-Michaël Celerier This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "qproperty-type-mismatch.h" #include "HierarchyUtils.h" #include "TypeUtils.h" #include "ClazyContext.h" #include "AccessSpecifierManager.h" #include "SourceCompatibilityHelpers.h" #include "StringUtils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace clang { class Decl; class MacroInfo; } // namespace clang using namespace clang; using namespace std; QPropertyTypeMismatch::QPropertyTypeMismatch(const std::string &name, ClazyContext *context) : CheckBase(name, context) { enablePreProcessorCallbacks(); } void QPropertyTypeMismatch::VisitDecl(clang::Decl *decl) { if (auto method = dyn_cast(decl)) VisitMethod(*method); else if (auto field = dyn_cast(decl)) VisitField(*field); + else if (auto typedefdecl = dyn_cast(decl)) + VisitTypedef(*typedefdecl); } void QPropertyTypeMismatch::VisitMethod(const clang::CXXMethodDecl & method) { if (method.isThisDeclarationADefinition() && !method.hasInlineBody()) return; const auto& theClass = method.getParent(); const auto& classRange = theClass->getSourceRange(); const auto& methodName = method.getNameInfo().getName().getAsString(); for(const auto& prop : m_qproperties) { if(classRange.getBegin() < prop.loc && prop.loc < classRange.getEnd()) { checkMethodAgainstProperty(prop, method, methodName); } } } void QPropertyTypeMismatch::VisitField(const FieldDecl & field) { const auto& theClass = field.getParent(); const auto& classRange = theClass->getSourceRange(); const auto& methodName = field.getName().str(); for(const auto& prop : m_qproperties) { if(classRange.getBegin() < prop.loc && prop.loc < classRange.getEnd()) { checkFieldAgainstProperty(prop, field, methodName); } } } -std::string QPropertyTypeMismatch::cleanupType(QualType type, bool unscoped) +void QPropertyTypeMismatch::VisitTypedef(const TypedefDecl &td) +{ + // Since when processing Q_PROPERTY we're at the pre-processor stage we don't have access + // to the Qualtypes, so catch any typedefs here + QualType underlyingType = td.getUnderlyingType(); + m_typedefMap[td.getNameAsString()] = underlyingType; +} + +std::string QPropertyTypeMismatch::cleanupType(QualType type, bool unscoped) const { type = type.getNonReferenceType().getCanonicalType().getUnqualifiedType(); PrintingPolicy po(lo()); po.SuppressTagKeyword = true; po.SuppressScope = unscoped; std::string str = type.getAsString(po); str.erase(std::remove_if(str.begin(), str.end(), [] (char c) { return std::isspace(c); }), str.end()); return str; } void QPropertyTypeMismatch::checkMethodAgainstProperty (const Property& prop, const CXXMethodDecl& method, const std::string& methodName){ auto error_begin = [&] { return "Q_PROPERTY '" + prop.name + "' of type '" + prop.type + "' is mismatched with "; }; if (prop.read == methodName) { - auto retTypeStr = cleanupType(method.getReturnType()); - if (prop.type != retTypeStr) { - // Maybe the difference is just the scope, if yes then don't warn. We already have a check for complaining about lack of scope - auto retTypeStrUnscopped = cleanupType(method.getReturnType(), /*unscopped=*/ true); - if (prop.type != retTypeStrUnscopped) - emitWarning(&method, error_begin() + "method '" + methodName + "' of return type '"+ retTypeStr +"'"); + std::string retTypeStr; + if (!typesMatch(prop.type, method.getReturnType(), retTypeStr)) { + emitWarning(&method, error_begin() + "method '" + methodName + "' of return type '"+ retTypeStr +"'"); } } else if(prop.write == methodName) { switch(method.getNumParams()) { case 0: emitWarning(&method, error_begin() + "method '" + methodName + "' with no parameters"); break; case 1: { - auto parmTypeStr = cleanupType(method.getParamDecl(0)->getType()); - if(prop.type != parmTypeStr) - { + std::string parmTypeStr; + if (!typesMatch(prop.type, method.getParamDecl(0)->getType(), parmTypeStr)) emitWarning(&method, error_begin() + "method '" + methodName + "' with parameter of type '"+ parmTypeStr +"'"); - } break; } default: emitWarning(&method, error_begin() + "method '" + methodName + "' with too many parameters"); break; } } else if(prop.notify == methodName) { switch(method.getNumParams()) { case 0: break; case 2: { auto param1TypeStr = cleanupType(method.getParamDecl(1)->getType()); if(param1TypeStr.find("QPrivateSignal") == std::string::npos) { emitWarning(&method, error_begin() + "signal '" + methodName + "' with too many parameters" + param1TypeStr); break; } // We want to check the first parameter too : [[fallthrough]]; } case 1: { - auto param0TypeStr = cleanupType(method.getParamDecl(0)->getType()); - if(prop.type != param0TypeStr) - { + std::string param0TypeStr; + if (!typesMatch(prop.type, method.getParamDecl(0)->getType(), param0TypeStr)) emitWarning(&method, error_begin() + "signal '" + methodName + "' with parameter of type '"+ param0TypeStr +"'"); - } break; } default: { emitWarning(&method, error_begin() + "signal '" + methodName + "' with too many parameters"); break; } } } } void QPropertyTypeMismatch::checkFieldAgainstProperty (const Property& prop, const FieldDecl& field, const std::string& fieldName) { - if(prop.member && prop.name == fieldName) - { - auto typeStr = cleanupType(field.getType()); - if(prop.type != typeStr) - { + if (prop.member && prop.name == fieldName) { + std::string typeStr; + if (!typesMatch(prop.type, field.getType(), typeStr)) emitWarning(&field, "Q_PROPERTY '" + prop.name + "' of type '" + prop.type + "' is mismatched with member '" + fieldName + "' of type '"+ typeStr +"'"); - } } } +bool QPropertyTypeMismatch::typesMatch(const string &type1, QualType type2Qt, std::string &cleaned) const +{ + cleaned = cleanupType(type2Qt); + if (type1 == cleaned) + return true; + + // Maybe the difference is just the scope, if yes then don't warn. We already have a check for complaining about lack of scope + cleaned = cleanupType(type2Qt, /*unscopped=*/ true); + if (type1 == cleaned) + return true; + + return false; +} + void QPropertyTypeMismatch::VisitMacroExpands(const clang::Token &MacroNameTok, const clang::SourceRange &range, const MacroInfo *) { IdentifierInfo *ii = MacroNameTok.getIdentifierInfo(); if(!ii) return; if(ii->getName() != "Q_PROPERTY") return; CharSourceRange crange = Lexer::getAsCharRange(range, sm(), lo()); string text = Lexer::getSourceText(crange, sm(), lo()); std::vector split = clazy::splitString(text, ' '); if(split.size() < 2) return; Property p; p.loc = range.getBegin(); // Handle type clazy::rtrim(split[0]); p.type = split[0]; if(p.type.find("Q_PROPERTY(") == 0) p.type = p.type.substr(11); // Handle name clazy::rtrim(split[1]); p.name = split[1]; // Handle Q_PROPERTY functions enum { None, Read, Write, Notify } next = None; for (std::string &token : split) { clazy::rtrim(/*by-ref*/token); switch(next) { case None: { if (token == "READ") { next = Read; continue; } else if (token == "WRITE") { next = Write; continue; } else if (token == "NOTIFY") { next = Notify; continue; } else if (token == "MEMBER") { p.member = true; break; } break; } case Read: p.read = token; break; case Write: p.write = token; break; case Notify: p.notify = token; break; } next = None; } m_qproperties.push_back(std::move(p)); } diff --git a/src/checks/manuallevel/qproperty-type-mismatch.h b/src/checks/manuallevel/qproperty-type-mismatch.h index ce80900..078376d 100644 --- a/src/checks/manuallevel/qproperty-type-mismatch.h +++ b/src/checks/manuallevel/qproperty-type-mismatch.h @@ -1,73 +1,78 @@ /* This file is part of the clazy static checker. Copyright (C) 2019 Jean-Michaël Celerier This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef CLAZY_QPROPERTY_TYPE_MISMATCH_H #define CLAZY_QPROPERTY_TYPE_MISMATCH_H #include "checkbase.h" #include #include #include +#include class ClazyContext; namespace clang { class CXXMethodDecl; class FieldDecl; class Decl; class MacroInfo; class Token; } // namespace clang /** * See README-qproperty-type-mismatch.md for more info. */ class QPropertyTypeMismatch : public CheckBase { public: explicit QPropertyTypeMismatch(const std::string &name, ClazyContext *context); void VisitDecl(clang::Decl *) override; private: void VisitMethod(const clang::CXXMethodDecl &); void VisitField(const clang::FieldDecl &); + void VisitTypedef(const clang::TypedefDecl &); void VisitMacroExpands(const clang::Token &MacroNameTok, const clang::SourceRange &range, const clang::MacroInfo *minfo = nullptr) override; struct Property { clang::SourceLocation loc; bool member{}; std::string name; std::string type; std::string read; std::string write; std::string notify; }; std::vector m_qproperties; - std::string cleanupType(clang::QualType type, bool unscoped = false); + std::string cleanupType(clang::QualType type, bool unscoped = false) const; void checkMethodAgainstProperty(const Property &prop, const clang::CXXMethodDecl &method, const std::string &methodName); void checkFieldAgainstProperty(const Property &prop, const clang::FieldDecl &method, const std::string &methodName); + + bool typesMatch(const std::string &type1, clang::QualType type2Qt, std::string &cleaned) const; + std::unordered_map m_typedefMap; }; #endif