/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
// Final goal: Checker for VCL widget references. Makes sure that VCL Window subclasses are properly referenced counted and dispose()'ed. // // But at the moment it just finds subclasses of Window which are not heap-allocated // // TODO do I need to check for local and static variables, too ? // TODO when we have a dispose() method, verify that the dispose() methods releases all of the Window references // TODO when we have a dispose() method, verify that it calls the super-class dispose() method at some point.
namespace {
class VCLWidgets: public loplugin::FilteringPlugin<VCLWidgets>
{ public: explicit VCLWidgets(loplugin::InstantiationData const & data): FilteringPlugin(data)
{}
bool isDerivedFromVclReferenceBase(const CXXRecordDecl *decl) { if (!decl) returnfalse; if (loplugin::DeclCheck(decl).Class(BASE_REF_COUNTED_CLASS)
.GlobalNamespace())
{ returntrue;
} if (!decl->hasDefinition()) { returnfalse;
} if (// not sure what hasAnyDependentBases() does, // but it avoids classes we don't want, e.g. WeakAggComponentImplHelper1
!decl->hasAnyDependentBases() &&
!decl->forallBases(BaseCheckNotWindowSubclass)) { returntrue;
} returnfalse;
}
// Look for places where we are accidentally assigning a returned-by-value VclPtr<T> to a T*, which generally // ends up in a use-after-free. void VCLWidgets::checkAssignmentForVclPtrToRawConversion(const SourceLocation& spellingLocation, const clang::Type* lhsType, const Expr* rhs)
{ if (!lhsType || !isa<clang::PointerType>(lhsType)) { return;
} if (!rhs) { return;
}
StringRef filename = getFilenameOfLocation(spellingLocation); if (loplugin::isSamePathname(filename, SRCDIR "/include/rtl/ref.hxx")) { return;
} const CXXRecordDecl* pointeeClass = lhsType->getPointeeType()->getAsCXXRecordDecl(); if (!isDerivedFromVclReferenceBase(pointeeClass)) { return;
}
// if we have T* on the LHS and VclPtr<T> on the RHS, we expect to see either // an ImplicitCastExpr // or an ExprWithCleanups and then an ImplicitCastExpr if (auto implicitCastExpr = dyn_cast<ImplicitCastExpr>(rhs)) { if (implicitCastExpr->getCastKind() != CK_UserDefinedConversion) { return;
}
rhs = rhs->IgnoreCasts();
} elseif (auto exprWithCleanups = dyn_cast<ExprWithCleanups>(rhs)) { if (auto implicitCastExpr = dyn_cast<ImplicitCastExpr>(exprWithCleanups->getSubExpr())) { if (implicitCastExpr->getCastKind() != CK_UserDefinedConversion) { return;
}
rhs = exprWithCleanups->IgnoreCasts();
} else { return;
}
} else { return;
} if (isa<CXXNullPtrLiteralExpr>(rhs)) { return;
} if (isa<CXXThisExpr>(rhs)) { return;
}
// ignore assignments from a member field to a local variable, to avoid unnecessary refcounting traffic if (auto callExpr = dyn_cast<CXXMemberCallExpr>(rhs)) { if (auto calleeMemberExpr = dyn_cast<MemberExpr>(callExpr->getCallee())) { if ((calleeMemberExpr = dyn_cast<MemberExpr>(calleeMemberExpr->getBase()->IgnoreImpCasts()))) { if (isa<FieldDecl>(calleeMemberExpr->getMemberDecl())) { return;
}
}
}
}
// ignore assignments from a local variable to a local variable, to avoid unnecessary refcounting traffic if (auto callExpr = dyn_cast<CXXMemberCallExpr>(rhs)) { if (auto calleeMemberExpr = dyn_cast<MemberExpr>(callExpr->getCallee())) { if (auto declRefExpr = dyn_cast<DeclRefExpr>(calleeMemberExpr->getBase()->IgnoreImpCasts())) { if (isa<VarDecl>(declRefExpr->getDecl())) { return;
}
}
}
} if (auto declRefExpr = dyn_cast<DeclRefExpr>(rhs->IgnoreImpCasts())) { if (isa<VarDecl>(declRefExpr->getDecl())) { return;
}
}
report(
DiagnosticsEngine::Warning, "assigning a returned-by-value VclPtr to a T* variable is dodgy, should be assigned to a VclPtr. If you know that the RHS does not return a newly created T, then add a '.get()' to the RHS",
rhs->getSourceRange().getBegin())
<< rhs->getSourceRange();
}
bool VCLWidgets::VisitVarDecl(const VarDecl * pVarDecl) { if (ignoreLocation(pVarDecl)) { returntrue;
} if (isa<ParmVarDecl>(pVarDecl)) { returntrue;
}
SourceLocation spellingLocation = compiler.getSourceManager().getSpellingLoc(
pVarDecl->getBeginLoc()); if (pVarDecl->getInit()) {
checkAssignmentForVclPtrToRawConversion(spellingLocation, pVarDecl->getType().getTypePtr(), pVarDecl->getInit());
}
StringRef aFileName = getFilenameOfLocation(spellingLocation); if (loplugin::isSamePathname(aFileName, SRCDIR "/include/vcl/vclptr.hxx")) returntrue; if (loplugin::isSamePathname(aFileName, SRCDIR "/vcl/source/window/layout.cxx")) returntrue; // allowlist the valid things that can contain pointers. // It is containing stuff like std::unique_ptr we get worried if (pVarDecl->getType()->isArrayType()) { returntrue;
} auto tc = loplugin::TypeCheck(pVarDecl->getType()); if (tc.Pointer()
|| tc.Class("map").StdNamespace()
|| tc.Class("multimap").StdNamespace()
|| tc.Class("vector").StdNamespace()
|| tc.Class("list").StdNamespace()
|| tc.Class("mem_fun1_t").StdNamespace() // registration template thing, doesn't actually allocate anything we need to care about
|| tc.Class("OMultiInstanceAutoRegistration").Namespace("compmodule").GlobalNamespace())
{ returntrue;
} // Apparently I should be doing some kind of lookup for a partial specialisations of std::iterator_traits<T> to see if an // object is an iterator, but that sounds like too much work auto t = pVarDecl->getType().getDesugaredType(compiler.getASTContext());
std::string s = t.getAsString(); if (s.find("iterator") != std::string::npos
|| loplugin::TypeCheck(t).Class("__wrap_iter").StdNamespace())
{ returntrue;
} // std::pair seems to show up in whacky ways in clang's AST. Sometimes it's a class, sometimes it's a typedef, and sometimes // it's an ElaboratedType (whatever that is) if (s.find("pair") != std::string::npos) { returntrue;
}
if (containsVclReferenceBaseSubclass(pVarDecl->getType())) {
report(
DiagnosticsEngine::Warning,
BASE_REF_COUNTED_CLASS " subclass %0 should be wrapped in VclPtr",
pVarDecl->getLocation())
<< pVarDecl->getType() << pVarDecl->getSourceRange(); returntrue;
} returntrue;
}
bool VCLWidgets::VisitFieldDecl(const FieldDecl * fieldDecl) { if (ignoreLocation(fieldDecl)) { returntrue;
}
StringRef aFileName = getFilenameOfLocation(
compiler.getSourceManager().getSpellingLoc(fieldDecl->getBeginLoc())); if (loplugin::isSamePathname(aFileName, SRCDIR "/include/vcl/vclptr.hxx")) returntrue; if (loplugin::isSamePathname(aFileName, SRCDIR "/include/rtl/ref.hxx")) returntrue; if (loplugin::isSamePathname(aFileName, SRCDIR "/include/o3tl/enumarray.hxx")) returntrue; if (loplugin::isSamePathname(aFileName, SRCDIR "/vcl/source/window/layout.cxx")) returntrue; if (fieldDecl->isBitField()) { returntrue;
} const CXXRecordDecl *pParentRecordDecl = isa<RecordDecl>(fieldDecl->getDeclContext()) ? dyn_cast<CXXRecordDecl>(fieldDecl->getParent()) : nullptr; if (loplugin::DeclCheck(pParentRecordDecl).Class("VclPtr")
.GlobalNamespace())
{ returntrue;
} if (containsVclReferenceBaseSubclass(fieldDecl->getType())) { // have to ignore this for now, nasty reverse dependency from tools->vcl auto check = loplugin::DeclCheck(pParentRecordDecl); if (!(check.Struct("ImplErrorContext").GlobalNamespace()
|| check.Class("ScHFEditPage").GlobalNamespace()))
{
report(
DiagnosticsEngine::Warning,
BASE_REF_COUNTED_CLASS " subclass %0 declared as a pointer member, should be wrapped in VclPtr",
fieldDecl->getLocation())
<< fieldDecl->getType() << fieldDecl->getSourceRange(); if (auto parent = dyn_cast<ClassTemplateSpecializationDecl>(fieldDecl->getParent())) {
report(
DiagnosticsEngine::Note, "template field here",
parent->getPointOfInstantiation());
} returntrue;
}
} const RecordType *recordType = fieldDecl->getType()->getAs<RecordType>(); if (recordType == nullptr) { returntrue;
} const CXXRecordDecl *recordDecl = dyn_cast<CXXRecordDecl>(recordType->getDecl()); if (recordDecl == nullptr) { returntrue;
}
// check if this field is derived fromVclReferenceBase if (isDerivedFromVclReferenceBase(recordDecl)) {
report(
DiagnosticsEngine::Warning,
BASE_REF_COUNTED_CLASS " subclass allocated as a class member, should be allocated via VclPtr",
fieldDecl->getLocation())
<< fieldDecl->getSourceRange();
}
// If this field is a VclPtr field, then the class MUST have a dispose method if (pParentRecordDecl && isDerivedFromVclReferenceBase(pParentRecordDecl)
&& loplugin::DeclCheck(recordDecl).Class("VclPtr").GlobalNamespace())
{ bool bFoundDispose = false; for(auto methodDecl = pParentRecordDecl->method_begin();
methodDecl != pParentRecordDecl->method_end(); ++methodDecl)
{ if (methodDecl->isInstance() && methodDecl->param_size()==0
&& loplugin::DeclCheck(*methodDecl).Function("dispose"))
{
bFoundDispose = true; break;
}
} if (!bFoundDispose) {
report(
DiagnosticsEngine::Warning,
BASE_REF_COUNTED_CLASS " subclass with a VclPtr field MUST override dispose() (and call its superclass dispose() as the last thing it does)",
fieldDecl->getLocation())
<< fieldDecl->getSourceRange();
} if (!pParentRecordDecl->hasUserDeclaredDestructor()) {
report(
DiagnosticsEngine::Warning,
BASE_REF_COUNTED_CLASS " subclass with a VclPtr field MUST have a user-provided destructor (that calls disposeOnce())",
fieldDecl->getLocation())
<< fieldDecl->getSourceRange();
}
}
returntrue;
}
bool VCLWidgets::VisitParmVarDecl(ParmVarDecl const * pvDecl)
{ if (ignoreLocation(pvDecl)) { returntrue;
} // ignore the stuff in the VclPtr template class const CXXMethodDecl *pMethodDecl = dyn_cast<CXXMethodDecl>(pvDecl->getDeclContext()); if (loplugin::DeclCheck(pMethodDecl).MemberFunction().Class("VclPtr")
.GlobalNamespace())
{ returntrue;
} // we exclude this method in VclBuilder because it's so useful to have it like this auto check = loplugin::DeclCheck(pMethodDecl).Function("get"); if (check.Class("VclBuilder").GlobalNamespace()
|| check.Class("VclBuilderContainer").GlobalNamespace())
{ returntrue;
} returntrue;
}
staticvoid findDisposeAndClearStatements(std::set<const FieldDecl*>& aVclPtrFields, const Stmt *pStmt)
{ if (!pStmt) return; if (isa<CompoundStmt>(pStmt)) { const CompoundStmt *pCompoundStatement = dyn_cast<CompoundStmt>(pStmt); for (auto i = pCompoundStatement->body_begin();
i != pCompoundStatement->body_end(); ++i)
{
findDisposeAndClearStatements(aVclPtrFields, *i);
} return;
} if (isa<ForStmt>(pStmt)) {
findDisposeAndClearStatements(aVclPtrFields, dyn_cast<ForStmt>(pStmt)->getBody()); return;
} if (isa<IfStmt>(pStmt)) {
findDisposeAndClearStatements(aVclPtrFields, dyn_cast<IfStmt>(pStmt)->getThen());
findDisposeAndClearStatements(aVclPtrFields, dyn_cast<IfStmt>(pStmt)->getElse()); return;
} if (!isa<CallExpr>(pStmt)) return; const CallExpr *pCallExpr = dyn_cast<CallExpr>(pStmt);
if (!pCallExpr->getDirectCallee()) return; if (!isa<CXXMethodDecl>(pCallExpr->getDirectCallee())) return; auto check = loplugin::DeclCheck(
dyn_cast<CXXMethodDecl>(pCallExpr->getDirectCallee())); // clear() is for containers like std::map<VclPtr<...>, ...> if (!(check.Function("disposeAndClear") || check.Function("reset") || check.Function("clear"))) return;
if (!pCallExpr->getCallee()) return;
if (!isa<MemberExpr>(pCallExpr->getCallee())) return; const MemberExpr *pCalleeMemberExpr = dyn_cast<MemberExpr>(pCallExpr->getCallee());
if (!pCalleeMemberExpr->getBase()) return; const MemberExpr *pCalleeMemberExprBase = dyn_cast<MemberExpr>(pCalleeMemberExpr->getBase()->IgnoreImpCasts()); if (pCalleeMemberExprBase == nullptr) return;
const FieldDecl* xxx = dyn_cast_or_null<FieldDecl>(pCalleeMemberExprBase->getMemberDecl()); if (xxx)
aVclPtrFields.erase(xxx);
}
bool VCLWidgets::VisitFunctionDecl( const FunctionDecl* functionDecl )
{ if (ignoreLocation(functionDecl)) { returntrue;
} // ignore the stuff in the VclPtr template class if (loplugin::DeclCheck(functionDecl).MemberFunction().Class("VclPtr")
.GlobalNamespace())
{ returntrue;
} // ignore the BASE_REF_COUNTED_CLASS::dispose() method if (loplugin::DeclCheck(functionDecl).Function("dispose")
.Class(BASE_REF_COUNTED_CLASS).GlobalNamespace())
{ returntrue;
} const CXXMethodDecl *pMethodDecl = dyn_cast<CXXMethodDecl>(functionDecl); if (functionDecl->hasBody() && pMethodDecl && isDerivedFromVclReferenceBase(pMethodDecl->getParent())) { // check the last thing that the dispose() method does, is to call into the superclass dispose method if (loplugin::DeclCheck(functionDecl).Function("dispose")) { if (!isDisposeCallingSuperclassDispose(pMethodDecl)) { // We specifically have to clear a member variable AFTER calling super::dispose() here, unfortunately if (!loplugin::DeclCheck(pMethodDecl->getParent()).Class("WindowOutputDevice"))
report(
DiagnosticsEngine::Warning,
BASE_REF_COUNTED_CLASS " subclass dispose() function MUST call dispose() of its superclass as the last thing it does",
functionDecl->getBeginLoc())
<< functionDecl->getSourceRange();
}
}
}
// check dispose method to make sure we are actually disposing all of the VclPtr fields // FIXME this is not exhaustive. We should enable shouldVisitTemplateInstantiations and look deeper inside type declarations if (pMethodDecl && pMethodDecl->isInstance() && pMethodDecl->getBody()
&& pMethodDecl->param_size()==0
&& loplugin::DeclCheck(functionDecl).Function("dispose")
&& isDerivedFromVclReferenceBase(pMethodDecl->getParent()) )
{ auto check = loplugin::DeclCheck(functionDecl).MemberFunction(); if (check.Class("VirtualDevice").GlobalNamespace()
|| check.Class("Breadcrumb").GlobalNamespace())
{ returntrue;
}
std::set<const FieldDecl*> aVclPtrFields; for (auto i = pMethodDecl->getParent()->field_begin();
i != pMethodDecl->getParent()->field_end(); ++i)
{ autoconst type = loplugin::TypeCheck((*i)->getType()); if (type.Class("VclPtr").GlobalNamespace()) {
aVclPtrFields.insert(*i);
} elseif (type.Class("vector").StdNamespace()
|| type.Class("map").StdNamespace()
|| type.Class("list").StdNamespace()
|| type.Class("set").StdNamespace())
{ const RecordType* recordType = dyn_cast_or_null<RecordType>((*i)->getType()->getUnqualifiedDesugaredType()); if (recordType) { auto d = dyn_cast<ClassTemplateSpecializationDecl>(recordType->getDecl()); if (d && d->getTemplateArgs().size()>0) { autoconst type = loplugin::TypeCheck(d->getTemplateArgs()[0].getAsType()); if (type.Class("VclPtr").GlobalNamespace()) {
aVclPtrFields.insert(*i);
}
}
}
}
} if (!aVclPtrFields.empty()) {
findDisposeAndClearStatements( aVclPtrFields, pMethodDecl->getBody() ); if (!aVclPtrFields.empty()) { //pMethodDecl->dump();
std::string aMessage = BASE_REF_COUNTED_CLASS " subclass dispose() method does not call disposeAndClear() or reset() on the following field(s): "; for(auto s : aVclPtrFields)
aMessage += ", " + s->getNameAsString();
report(
DiagnosticsEngine::Warning,
aMessage,
functionDecl->getBeginLoc())
<< functionDecl->getSourceRange();
}
}
}
returntrue;
}
bool VCLWidgets::VisitCXXDeleteExpr(const CXXDeleteExpr *pCXXDeleteExpr)
{ if (ignoreLocation(pCXXDeleteExpr)) { returntrue;
} const CXXRecordDecl *pPointee = pCXXDeleteExpr->getArgument()->getType()->getPointeeCXXRecordDecl(); if (pPointee && isDerivedFromVclReferenceBase(pPointee)) {
SourceLocation spellingLocation = compiler.getSourceManager().getSpellingLoc(
pCXXDeleteExpr->getBeginLoc());
StringRef filename = getFilenameOfLocation(spellingLocation); if ( !(loplugin::isSamePathname(filename, SRCDIR "/include/vcl/vclreferencebase.hxx")))
{
report(
DiagnosticsEngine::Warning, "calling delete on instance of " BASE_REF_COUNTED_CLASS " subclass, must rather call disposeAndClear()",
pCXXDeleteExpr->getBeginLoc())
<< pCXXDeleteExpr->getSourceRange();
}
} const ImplicitCastExpr* pImplicitCastExpr = dyn_cast<ImplicitCastExpr>(pCXXDeleteExpr->getArgument()); if (!pImplicitCastExpr) { returntrue;
} if (pImplicitCastExpr->getCastKind() != CK_UserDefinedConversion) { returntrue;
} if (!loplugin::TypeCheck(pImplicitCastExpr->getSubExprAsWritten()->getType()).Class("VclPtr")
.GlobalNamespace())
{ returntrue;
}
report(
DiagnosticsEngine::Warning, "calling delete on instance of VclPtr, must rather call disposeAndClear()",
pCXXDeleteExpr->getBeginLoc())
<< pCXXDeleteExpr->getSourceRange(); returntrue;
}
/** The AST looks like: `-CXXMemberCallExpr 0xb06d8b0 'void' `-MemberExpr 0xb06d868 '<bound member function type>' ->dispose 0x9d34880 `-ImplicitCastExpr 0xb06d8d8 'class SfxTabPage *' <UncheckedDerivedToBase (SfxTabPage)> `-CXXThisExpr 0xb06d850 'class SfxAcceleratorConfigPage *' this
*/ bool VCLWidgets::isDisposeCallingSuperclassDispose(const CXXMethodDecl* pMethodDecl)
{ const CompoundStmt *pCompoundStatement = dyn_cast<CompoundStmt>(pMethodDecl->getBody()); if (!pCompoundStatement) returnfalse; if (pCompoundStatement->size() == 0) returnfalse; // find the last statement const CXXMemberCallExpr *pCallExpr = dyn_cast<CXXMemberCallExpr>(*pCompoundStatement->body_rbegin()); if (!pCallExpr) returnfalse; const MemberExpr *pMemberExpr = dyn_cast<MemberExpr>(pCallExpr->getCallee()); if (!pMemberExpr) returnfalse; if (!loplugin::DeclCheck(pMemberExpr->getMemberDecl()).Function("dispose")) returnfalse; const CXXMethodDecl *pDirectCallee = dyn_cast<CXXMethodDecl>(pCallExpr->getDirectCallee()); if (!pDirectCallee) returnfalse; /* Not working yet. Partially because sometimes the superclass does not a dispose() method, so it gets passed up the chain. Need complex checking for that case. if (pDirectCallee->getParent()->getTypeForDecl() != (*pMethodDecl->getParent()->bases_begin()).getType().getTypePtr()) { report( DiagnosticsEngine::Warning, "dispose() method calling wrong baseclass, calling " + pDirectCallee->getParent()->getQualifiedNameAsString() + " should be calling " + (*pMethodDecl->getParent()->bases_begin()).getType().getAsString(), pCallExpr->getLocStart()) << pCallExpr->getSourceRange(); return false;
}*/ returntrue;
}
bool VCLWidgets::VisitDeclRefExpr(const DeclRefExpr* pDeclRefExpr)
{ if (!mbCheckingMemcpy) { returntrue;
} if (ignoreLocation(pDeclRefExpr)) { returntrue;
}
QualType pType = pDeclRefExpr->getDecl()->getType(); if (pType->isPointerType()) {
pType = pType->getPointeeType();
} if (!containsVclPtr(pType)) { returntrue;
}
report(
DiagnosticsEngine::Warning, "Calling memcpy on a type which contains a VclPtr",
pDeclRefExpr->getExprLoc()); returntrue;
}
bool VCLWidgets::VisitCXXConstructExpr( const CXXConstructExpr* constructExpr )
{ if (ignoreLocation(constructExpr)) { returntrue;
} if (constructExpr->getConstructionKind() != compat::CXXConstructionKind::Complete) { returntrue;
} const CXXConstructorDecl* pConstructorDecl = constructExpr->getConstructor(); const CXXRecordDecl* recordDecl = pConstructorDecl->getParent(); if (isDerivedFromVclReferenceBase(recordDecl)) {
StringRef aFileName = getFilenameOfLocation(
compiler.getSourceManager().getSpellingLoc(constructExpr->getBeginLoc())); if (!loplugin::isSamePathname(aFileName, SRCDIR "/include/vcl/vclptr.hxx")) {
report(
DiagnosticsEngine::Warning, "Calling constructor of a VclReferenceBase-derived type directly; all such creation should go via VclPtr<>::Create",
constructExpr->getExprLoc());
}
} returntrue;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.