void DetachingTemporary::VisitStmt()

in src/checks/level1/detaching-temporary.cpp [81:179]


void DetachingTemporary::VisitStmt(clang::Stmt *stm)
{
    auto *callExpr = dyn_cast<CallExpr>(stm);
    if (!callExpr) {
        return;
    }

    // For a chain like getList().first(), returns {first(), getList()}
    std::vector<CallExpr *> callExprs = Utils::callListForChain(callExpr); // callExpr would be first()
    if (callExprs.size() < 2) {
        return;
    }

    CallExpr *firstCallToBeEvaluated = callExprs.at(callExprs.size() - 1); // This is the call to getList()
    FunctionDecl *firstFunc = firstCallToBeEvaluated->getDirectCallee();
    if (!firstFunc) {
        return;
    }

    QualType qt = firstFunc->getReturnType();
    const Type *firstFuncReturnType = qt.getTypePtrOrNull();
    if (!firstFuncReturnType) {
        return;
    }

    if (firstFuncReturnType->isReferenceType() || firstFuncReturnType->isPointerType()) {
        return;
    }

    if (qt.isConstQualified()) {
        return; // const doesn't detach
    }

    auto *firstMethod = dyn_cast<CXXMethodDecl>(firstFunc);
    if (isAllowedChainedMethod(clazy::qualifiedMethodName(firstFunc))) {
        return;
    }

    if (firstMethod && isAllowedChainedClass(firstMethod->getParent()->getNameAsString())) {
        return;
    }

    // Check if this is a QGlobalStatic
    if (firstMethod && clazy::name(firstMethod->getParent()) == "QGlobalStatic") {
        return;
    }

    CallExpr *secondCallToBeEvaluated = callExprs.at(callExprs.size() - 2); // This is the call to first()
    FunctionDecl *detachingFunc = secondCallToBeEvaluated->getDirectCallee();
    auto *detachingMethod = detachingFunc ? dyn_cast<CXXMethodDecl>(detachingFunc) : nullptr;
    const Type *detachingMethodReturnType = detachingMethod ? detachingMethod->getReturnType().getTypePtrOrNull() : nullptr;
    if (!detachingMethod || !detachingMethodReturnType) {
        return;
    }

    // Check if it's one of the implicit shared classes
    const CXXRecordDecl *classDecl = detachingMethod->getParent();
    StringRef className = clazy::name(classDecl);

    const std::unordered_map<std::string, std::vector<StringRef>> &methodsByType = clazy::detachingMethods();
    auto it = methodsByType.find(static_cast<std::string>(className));
    auto it2 = m_writeMethodsByType.find(className);

    std::vector<StringRef> allowedFunctions;
    std::vector<StringRef> allowedWriteFunctions;
    if (it != methodsByType.end()) {
        allowedFunctions = it->second;
    }

    if (it2 != m_writeMethodsByType.end()) {
        allowedWriteFunctions = it2->second;
    }

    // Check if it's one of the detaching methods
    StringRef functionName = clazy::name(detachingMethod);

    std::string error;

    const bool isReadFunction = clazy::contains(allowedFunctions, functionName);
    const bool isWriteFunction = clazy::contains(allowedWriteFunctions, functionName);

    if (isReadFunction || isWriteFunction) {
        bool returnTypeIsIterator = false;
        const CXXRecordDecl *returnRecord = detachingMethodReturnType->getAsCXXRecordDecl();
        if (returnRecord) {
            returnTypeIsIterator = clazy::name(returnRecord) == "iterator";
        }

        if (isWriteFunction && (detachingMethodReturnType->isVoidType() || returnTypeIsIterator)) {
            error = std::string("Modifying temporary container is pointless and it also detaches");
        } else {
            error = std::string("Don't call ") + clazy::qualifiedMethodName(detachingMethod) + std::string("() on temporary");
        }
    }

    if (!error.empty()) {
        emitWarning(stm->getBeginLoc(), error);
    }
}