bool StrictIterators::handleImplicitCast()

in src/checks/level0/strict-iterators.cpp [63:153]


bool StrictIterators::handleImplicitCast(ImplicitCastExpr *implicitCast)
{
    if (!implicitCast) {
        return false;
    }

    const std::string nameTo = clazy::simpleTypeName(implicitCast->getType(), m_context->ci.getLangOpts());

    const QualType typeTo = implicitCast->getType();
    CXXRecordDecl *recordTo = clazy::parentRecordForTypedef(typeTo);
    if (recordTo && !clazy::isQtCOWIterableClass(recordTo)) {
        return false;
    }

    recordTo = clazy::typeAsRecord(typeTo);
    if (recordTo && !clazy::isQtCOWIterator(recordTo)) {
        return false;
    }

    assert(implicitCast->getSubExpr());

    if (isMemberVariable(implicitCast->getSubExpr())) {
        // Comparing a const_iterator against a member QVector<T>::iterator won't detach the container
        return false;
    }

    QualType typeFrom = implicitCast->getSubExpr()->getType();
    CXXRecordDecl *recordFrom = clazy::parentRecordForTypedef(typeFrom);
    if (recordFrom && !clazy::isQtCOWIterableClass(recordFrom)) {
        return false;
    }

    // const_iterator might be a typedef to pointer, like const T *, instead of a class, so just check for const qualification in that case
    if (!(clazy::pointeeQualType(typeTo).isConstQualified() || clazy::endsWith(nameTo, "const_iterator"))) {
        return false;
    }

    // Allow conversions for mutating member functions of Qt container classes
    if (implicitCast->getCastKind() == CK_ConstructorConversion) {
        if (auto *memberCall = dyn_cast_or_null<CXXMemberCallExpr>(m_context->parentMap->getParent(implicitCast))) {
            auto memberFunctionDecl = memberCall->getMethodDecl();
            if (auto *parentClass = memberFunctionDecl->getParent()) {
                static const std::vector<std::string> allow = {
                    "QMap<>::insert",
                    "QMap<>::erase",
                    "QHash<>::erase",
                    "QMultiHash<>::erase",
                    "QList<>::emplace",
                    "QList<>::erase",
                    "QList<>::insert",
                    "QVarLengthArray<>::emplace",
                    "QVarLengthArray<>::erase",
                    "QVarLengthArray<>::insert",
                    "QSet<>::erase",
                    "QSet<>::insert",
                    "QMultiMap<>::erase",
                    "QMultiMap<>::insert",
                };

                const auto qualifiedName = parentClass->getNameAsString() + "<>::" + memberFunctionDecl->getNameAsString();
                if (clazy::contains(allow, qualifiedName)) {
                    return false;
                }
            }
        }

        emitWarning(implicitCast, "Mixing iterators with const_iterators");
        return true;
    }

    // TODO: some util function to get the name of a nested class
    const bool nameToIsIterator = nameTo == "iterator" || clazy::endsWith(nameTo, "::iterator");
    if (nameToIsIterator) {
        return false;
    }

    const std::string nameFrom = clazy::simpleTypeName(typeFrom, m_context->ci.getLangOpts());
    const bool nameFromIsIterator = nameFrom == "iterator" || clazy::endsWith(nameFrom, "::iterator");
    if (!nameFromIsIterator) {
        return false;
    }

    auto *p = m_context->parentMap->getParent(implicitCast);
    if (isa<CXXOperatorCallExpr>(p)) {
        return false;
    }

    emitWarning(implicitCast, "Mixing iterators with const_iterators");

    return true;
}