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);
}
}