vclwidgets.cxx 34.1 KB
Newer Older
1 2 3 4 5 6 7 8 9
/* -*- 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/.
 */

10 11
#ifndef LO_CLANG_SHARED_PLUGINS

12
#include <memory>
13
#include <string>
14
#include <iostream>
15 16

#include "plugin.hxx"
17
#include "check.hxx"
18 19 20 21 22 23 24 25 26 27 28 29 30
#include "clang/AST/CXXInheritance.h"

// 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:
31
    public loplugin::FilteringPlugin<VCLWidgets>
32 33
{
public:
34
    explicit VCLWidgets(loplugin::InstantiationData const & data): FilteringPlugin(data)
35
    {}
36 37 38

    virtual void run() override { TraverseDecl(compiler.getASTContext().getTranslationUnitDecl()); }

39
    bool shouldVisitTemplateInstantiations () const { return true; }
40

41
    bool VisitVarDecl(const VarDecl *);
42 43 44 45
    bool VisitFieldDecl(const FieldDecl *);
    bool VisitParmVarDecl(const ParmVarDecl *);
    bool VisitFunctionDecl(const FunctionDecl *);
    bool VisitCXXDestructorDecl(const CXXDestructorDecl *);
46
    bool VisitCXXDeleteExpr(const CXXDeleteExpr *);
47
    bool VisitCallExpr(const CallExpr *);
48 49 50
    bool VisitDeclRefExpr(const DeclRefExpr *);
    bool VisitCXXConstructExpr(const CXXConstructExpr *);
    bool VisitBinaryOperator(const BinaryOperator *);
51
private:
52
    void checkAssignmentForVclPtrToRawConversion(const SourceLocation& sourceLoc, const clang::Type* lhsType, const Expr* rhs);
53
    bool isDisposeCallingSuperclassDispose(const CXXMethodDecl* pMethodDecl);
54
    bool mbCheckingMemcpy = false;
55 56
};

57 58
#define BASE_REF_COUNTED_CLASS "VclReferenceBase"

59
bool BaseCheckNotWindowSubclass(const CXXRecordDecl *BaseDefinition) {
60 61
    return !loplugin::DeclCheck(BaseDefinition).Class(BASE_REF_COUNTED_CLASS)
        .GlobalNamespace();
62 63
}

64
bool isDerivedFromVclReferenceBase(const CXXRecordDecl *decl) {
65 66
    if (!decl)
        return false;
67 68 69
    if (loplugin::DeclCheck(decl).Class(BASE_REF_COUNTED_CLASS)
        .GlobalNamespace())
    {
70
        return true;
71
    }
72 73 74
    if (!decl->hasDefinition()) {
        return false;
    }
75 76 77
    if (// not sure what hasAnyDependentBases() does,
        // but it avoids classes we don't want, e.g. WeakAggComponentImplHelper1
        !decl->hasAnyDependentBases() &&
78
        !decl->forallBases(BaseCheckNotWindowSubclass, true)) {
79
        return true;
80 81 82 83
    }
    return false;
}

84
bool containsVclReferenceBaseSubclass(const clang::Type* pType0);
85

86
bool containsVclReferenceBaseSubclass(const QualType& qType) {
87 88 89 90 91 92 93
    auto check = loplugin::TypeCheck(qType);
    if (check.Class("ScopedVclPtr").GlobalNamespace()
        || check.Class("ScopedVclPtrInstance").GlobalNamespace()
        || check.Class("VclPtr").GlobalNamespace()
        || check.Class("VclPtrInstance").GlobalNamespace())
    {
        return false;
94
    }
95
    return containsVclReferenceBaseSubclass(qType.getTypePtr());
96 97
}

98
bool containsVclReferenceBaseSubclass(const clang::Type* pType0) {
99 100
    if (!pType0)
        return false;
101
    const clang::Type* pType = pType0->getUnqualifiedDesugaredType();
102 103
    if (!pType)
        return false;
104 105 106 107
    const CXXRecordDecl* pRecordDecl = pType->getAsCXXRecordDecl();
    if (pRecordDecl) {
        const ClassTemplateSpecializationDecl* pTemplate = dyn_cast<ClassTemplateSpecializationDecl>(pRecordDecl);
        if (pTemplate) {
108 109
            auto check = loplugin::DeclCheck(pTemplate);
            if (check.Class("VclStatusListener").GlobalNamespace()) {
110 111
                return false;
            }
112
            bool link = bool(check.Class("Link").GlobalNamespace());
113 114 115
            for(unsigned i=0; i<pTemplate->getTemplateArgs().size(); ++i) {
                const TemplateArgument& rArg = pTemplate->getTemplateArgs()[i];
                if (rArg.getKind() == TemplateArgument::ArgKind::Type &&
116
                    containsVclReferenceBaseSubclass(rArg.getAsType()))
117
                {
118 119 120 121 122
                    // OK for first template argument of tools/link.hxx Link
                    // to be a Window-derived pointer:
                    if (!link || i != 0) {
                        return true;
                    }
123 124 125 126
                }
            }
        }
    }
127 128
    if (pType->isPointerType()) {
        QualType pointeeType = pType->getPointeeType();
129
        return containsVclReferenceBaseSubclass(pointeeType);
130
    } else if (pType->isArrayType()) {
131
        const clang::ArrayType* pArrayType = dyn_cast<clang::ArrayType>(pType);
132
        QualType elementType = pArrayType->getElementType();
133
        return containsVclReferenceBaseSubclass(elementType);
134
    } else {
135
        return isDerivedFromVclReferenceBase(pRecordDecl);
136
    }
137 138
}

139 140 141
bool VCLWidgets::VisitCXXDestructorDecl(const CXXDestructorDecl* pCXXDestructorDecl)
{
    if (ignoreLocation(pCXXDestructorDecl)) {
142 143
        return true;
    }
144
    if (!pCXXDestructorDecl->isThisDeclarationADefinition()) {
145
        return true;
146 147
    }
    const CXXRecordDecl * pRecordDecl = pCXXDestructorDecl->getParent();
148
    // ignore
149 150 151
    if (loplugin::DeclCheck(pRecordDecl).Class(BASE_REF_COUNTED_CLASS)
        .GlobalNamespace())
    {
152 153
        return true;
    }
154 155
    // check if this class is derived from VclReferenceBase
    if (!isDerivedFromVclReferenceBase(pRecordDecl)) {
156 157
        return true;
    }
158
    // check if we have any VclPtr<> fields
159
    bool bFoundVclPtrField = false;
160 161 162
    for(auto fieldDecl = pRecordDecl->field_begin();
        fieldDecl != pRecordDecl->field_end(); ++fieldDecl)
    {
163 164
        const RecordType *pFieldRecordType = fieldDecl->getType()->getAs<RecordType>();
        if (pFieldRecordType) {
165 166 167
            if (loplugin::DeclCheck(pFieldRecordType->getDecl())
                .Class("VclPtr").GlobalNamespace())
            {
168
               bFoundVclPtrField = true;
169 170 171
               break;
            }
       }
172
    }
173
    // check if there is a dispose() method
174
    bool bFoundDispose = false;
175 176 177
    for(auto methodDecl = pRecordDecl->method_begin();
        methodDecl != pRecordDecl->method_end(); ++methodDecl)
    {
178 179 180
        if (methodDecl->isInstance() && methodDecl->param_size()==0
            && loplugin::DeclCheck(*methodDecl).Function("dispose"))
        {
181
           bFoundDispose = true;
182 183 184
           break;
        }
    }
185
    const CompoundStmt *pCompoundStatement = dyn_cast_or_null<CompoundStmt>(pCXXDestructorDecl->getBody());
186
    // having an empty body and no dispose() method is fine
187
    if (!bFoundVclPtrField && !bFoundDispose && (!pCompoundStatement || pCompoundStatement->size() == 0)) {
188 189
        return true;
    }
190
    if (bFoundVclPtrField && (!pCompoundStatement || pCompoundStatement->size() == 0)) {
191 192
        report(
            DiagnosticsEngine::Warning,
193
            BASE_REF_COUNTED_CLASS " subclass with VclPtr field must call disposeOnce() from its destructor",
194
            compat::getBeginLoc(pCXXDestructorDecl))
195
          << pCXXDestructorDecl->getSourceRange();
196 197
        return true;
    }
198 199
    // Check that the destructor for a BASE_REF_COUNTED_CLASS subclass either
    // only calls disposeOnce() or, if !bFoundVclPtrField, does nothing at all:
200
    bool bOk = false;
201 202 203
    if (pCompoundStatement) {
        bool bFoundDisposeOnce = false;
        int nNumExtraStatements = 0;
204 205
        for (auto i = pCompoundStatement->body_begin();
             i != pCompoundStatement->body_end(); ++i)
206
        {
207 208 209 210 211
            //TODO: The below erroneously also skips past entire statements like
            //
            //  assert(true), ...;
            //
            auto skip = false;
212
            for (auto loc = compat::getBeginLoc(*i);
213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
                 compiler.getSourceManager().isMacroBodyExpansion(loc);
                 loc = compiler.getSourceManager().getImmediateMacroCallerLoc(
                     loc))
            {
                auto const name = Lexer::getImmediateMacroName(
                    loc, compiler.getSourceManager(), compiler.getLangOpts());
                if (name == "SAL_DEBUG" || name == "assert") {
                    skip = true;
                    break;
                }
            }
            if (skip) {
                continue;
            }
            if (auto const pCallExpr = dyn_cast<CXXMemberCallExpr>(*i)) {
228 229 230 231
                if( const FunctionDecl* func = pCallExpr->getDirectCallee()) {
                    if( func->getNumParams() == 0 && func->getIdentifier() != NULL
                        && ( func->getName() == "disposeOnce" )) {
                        bFoundDisposeOnce = true;
232
                        continue;
233
                    }
234 235
                }
            }
236
            nNumExtraStatements++;
237
        }
238 239
        bOk = (bFoundDisposeOnce || !bFoundVclPtrField)
            && nNumExtraStatements == 0;
240
    }
241
    if (!bOk) {
242
        SourceLocation spellingLocation = compiler.getSourceManager().getSpellingLoc(
243
                              compat::getBeginLoc(pCXXDestructorDecl));
244
        StringRef filename = getFileNameOfSpellingLoc(spellingLocation);
245 246
        if (   !(loplugin::isSamePathname(filename, SRCDIR "/vcl/source/window/window.cxx"))
            && !(loplugin::isSamePathname(filename, SRCDIR "/vcl/source/gdi/virdev.cxx"))
247 248
            && !(loplugin::isSamePathname(filename, SRCDIR "/vcl/qa/cppunit/lifecycle.cxx"))
            && !(loplugin::isSamePathname(filename, SRCDIR "/sfx2/source/dialog/tabdlg.cxx")) )
249 250 251
        {
            report(
                DiagnosticsEngine::Warning,
252
                BASE_REF_COUNTED_CLASS " subclass should have nothing in its destructor but a call to disposeOnce()",
253
                compat::getBeginLoc(pCXXDestructorDecl))
254 255
              << pCXXDestructorDecl->getSourceRange();
        }
256 257 258 259
    }
    return true;
}

260 261 262 263 264 265 266 267
bool VCLWidgets::VisitBinaryOperator(const BinaryOperator * binaryOperator)
{
    if (ignoreLocation(binaryOperator)) {
        return true;
    }
    if ( !binaryOperator->isAssignmentOp() ) {
        return true;
    }
268
    SourceLocation spellingLocation = compiler.getSourceManager().getSpellingLoc(
269
                          compat::getBeginLoc(binaryOperator));
270
    checkAssignmentForVclPtrToRawConversion(spellingLocation, binaryOperator->getLHS()->getType().getTypePtr(), binaryOperator->getRHS());
271 272 273 274 275
    return true;
}

// 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.
276
void VCLWidgets::checkAssignmentForVclPtrToRawConversion(const SourceLocation& spellingLocation, const clang::Type* lhsType, const Expr* rhs)
277
{
278
    if (!lhsType || !isa<clang::PointerType>(lhsType)) {
279 280 281 282 283
        return;
    }
    if (!rhs) {
        return;
    }
284
    StringRef filename = getFileNameOfSpellingLoc(spellingLocation);
285
    if (loplugin::isSamePathname(filename, SRCDIR "/include/rtl/ref.hxx")) {
286
        return;
287 288 289 290 291
    }
    const CXXRecordDecl* pointeeClass = lhsType->getPointeeType()->getAsCXXRecordDecl();
    if (!isDerivedFromVclReferenceBase(pointeeClass)) {
        return;
    }
292 293 294

    // if we have T* on the LHS and VclPtr<T> on the RHS, we expect to see either
    // an ImplicitCastExpr
Andrea Gelmini's avatar
Andrea Gelmini committed
295
    // or an ExprWithCleanups and then an ImplicitCastExpr
296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313
    if (auto implicitCastExpr = dyn_cast<ImplicitCastExpr>(rhs)) {
        if (implicitCastExpr->getCastKind() != CK_UserDefinedConversion) {
            return;
        }
        rhs = rhs->IgnoreCasts();
    } else if (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)) {
314 315
        return;
    }
316
    if (isa<CXXThisExpr>(rhs)) {
317 318
        return;
    }
319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346

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

347 348
    report(
        DiagnosticsEngine::Warning,
349
        "assigning a returned-by-value VclPtr<T> 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",
350 351 352
         rhs->getSourceRange().getBegin())
        << rhs->getSourceRange();
}
353

354 355 356 357
bool VCLWidgets::VisitVarDecl(const VarDecl * pVarDecl) {
    if (ignoreLocation(pVarDecl)) {
        return true;
    }
358
    if (isa<ParmVarDecl>(pVarDecl)) {
359 360
        return true;
    }
361
    SourceLocation spellingLocation = compiler.getSourceManager().getSpellingLoc(
362
                          compat::getBeginLoc(pVarDecl));
363
    if (pVarDecl->getInit()) {
364
        checkAssignmentForVclPtrToRawConversion(spellingLocation, pVarDecl->getType().getTypePtr(), pVarDecl->getInit());
365
    }
366
    StringRef aFileName = getFileNameOfSpellingLoc(spellingLocation);
367
    if (loplugin::isSamePathname(aFileName, SRCDIR "/include/vcl/vclptr.hxx"))
368
        return true;
369
    if (loplugin::isSamePathname(aFileName, SRCDIR "/vcl/source/window/layout.cxx"))
370 371 372 373
        return true;
    // whitelist the valid things that can contain pointers.
    // It is containing stuff like std::unique_ptr we get worried
    if (pVarDecl->getType()->isArrayType()) {
374 375
        return true;
    }
376 377 378 379 380 381 382 383
    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
384
        || tc.Class("OMultiInstanceAutoRegistration").Namespace("compmodule").GlobalNamespace())
385
    {
386 387
        return true;
    }
388 389
    // 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
390 391 392 393 394
    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())
    {
395 396
        return true;
    }
397
    // 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
Andrea Gelmini's avatar
Andrea Gelmini committed
398
    // it's an ElaboratedType (whatever that is)
399 400 401 402 403
    if (s.find("pair") != std::string::npos) {
        return true;
    }

    if (containsVclReferenceBaseSubclass(pVarDecl->getType())) {
404 405
        report(
            DiagnosticsEngine::Warning,
406
            BASE_REF_COUNTED_CLASS " subclass %0 should be wrapped in VclPtr",
407
            pVarDecl->getLocation())
408 409
            << pVarDecl->getType() << pVarDecl->getSourceRange();
        return true;
410
    }
411 412 413
    return true;
}

414 415 416 417
bool VCLWidgets::VisitFieldDecl(const FieldDecl * fieldDecl) {
    if (ignoreLocation(fieldDecl)) {
        return true;
    }
418
    StringRef aFileName = getFileNameOfSpellingLoc(
419
        compiler.getSourceManager().getSpellingLoc(compat::getBeginLoc(fieldDecl)));
420
    if (loplugin::isSamePathname(aFileName, SRCDIR "/include/vcl/vclptr.hxx"))
421
        return true;
422
    if (loplugin::isSamePathname(aFileName, SRCDIR "/include/rtl/ref.hxx"))
423
        return true;
424
    if (loplugin::isSamePathname(aFileName, SRCDIR "/include/o3tl/enumarray.hxx"))
425
        return true;
426
    if (loplugin::isSamePathname(aFileName, SRCDIR "/vcl/source/window/layout.cxx"))
427
        return true;
428 429 430
    if (fieldDecl->isBitField()) {
        return true;
    }
431
    const CXXRecordDecl *pParentRecordDecl = isa<RecordDecl>(fieldDecl->getDeclContext()) ? dyn_cast<CXXRecordDecl>(fieldDecl->getParent()) : nullptr;
432 433 434
    if (loplugin::DeclCheck(pParentRecordDecl).Class("VclPtr")
        .GlobalNamespace())
    {
435 436 437
        return true;
    }
    if (containsVclReferenceBaseSubclass(fieldDecl->getType())) {
438
        // have to ignore this for now, nasty reverse dependency from tools->vcl
439
        auto check = loplugin::DeclCheck(pParentRecordDecl);
440
        if (!(check.Struct("ImplErrorContext").GlobalNamespace()
441 442
              || check.Class("ScHFEditPage").GlobalNamespace()))
        {
443 444
            report(
                DiagnosticsEngine::Warning,
445
                BASE_REF_COUNTED_CLASS " subclass %0 declared as a pointer member, should be wrapped in VclPtr",
446
                fieldDecl->getLocation())
447
                << fieldDecl->getType() << fieldDecl->getSourceRange();
448 449 450 451 452 453
            if (auto parent = dyn_cast<ClassTemplateSpecializationDecl>(fieldDecl->getParent())) {
                report(
                    DiagnosticsEngine::Note,
                    "template field here",
                    parent->getPointOfInstantiation());
            }
454
            return true;
455
        }
456
    }
457 458 459 460 461 462 463 464
    const RecordType *recordType = fieldDecl->getType()->getAs<RecordType>();
    if (recordType == nullptr) {
        return true;
    }
    const CXXRecordDecl *recordDecl = dyn_cast<CXXRecordDecl>(recordType->getDecl());
    if (recordDecl == nullptr) {
        return true;
    }
465

466 467
    // check if this field is derived fromVclReferenceBase
    if (isDerivedFromVclReferenceBase(recordDecl)) {
468 469
        report(
            DiagnosticsEngine::Warning,
470
            BASE_REF_COUNTED_CLASS " subclass allocated as a class member, should be allocated via VclPtr",
471 472 473 474 475
            fieldDecl->getLocation())
          << fieldDecl->getSourceRange();
    }

    // If this field is a VclPtr field, then the class MUST have a dispose method
476
    if (pParentRecordDecl && isDerivedFromVclReferenceBase(pParentRecordDecl)
477
        && loplugin::DeclCheck(recordDecl).Class("VclPtr").GlobalNamespace())
478
    {
479
        bool bFoundDispose = false;
480 481 482
        for(auto methodDecl = pParentRecordDecl->method_begin();
            methodDecl != pParentRecordDecl->method_end(); ++methodDecl)
        {
483 484 485
            if (methodDecl->isInstance() && methodDecl->param_size()==0
                && loplugin::DeclCheck(*methodDecl).Function("dispose"))
            {
486
               bFoundDispose = true;
487 488 489
               break;
            }
        }
490
        if (!bFoundDispose) {
491 492
            report(
                DiagnosticsEngine::Warning,
493
                BASE_REF_COUNTED_CLASS " subclass with a VclPtr field MUST override dispose() (and call its superclass dispose() as the last thing it does)",
494 495
                fieldDecl->getLocation())
              << fieldDecl->getSourceRange();
496
        }
497 498 499
        if (!pParentRecordDecl->hasUserDeclaredDestructor()) {
            report(
                DiagnosticsEngine::Warning,
500
                BASE_REF_COUNTED_CLASS " subclass with a VclPtr field MUST have a user-provided destructor (that calls disposeOnce())",
501 502 503
                fieldDecl->getLocation())
              << fieldDecl->getSourceRange();
        }
504 505 506 507 508
    }

    return true;
}

509 510
bool VCLWidgets::VisitParmVarDecl(ParmVarDecl const * pvDecl)
{
511 512 513
    if (ignoreLocation(pvDecl)) {
        return true;
    }
514 515
    // ignore the stuff in the VclPtr template class
    const CXXMethodDecl *pMethodDecl = dyn_cast<CXXMethodDecl>(pvDecl->getDeclContext());
516 517 518
    if (loplugin::DeclCheck(pMethodDecl).MemberFunction().Class("VclPtr")
        .GlobalNamespace())
    {
519 520
        return true;
    }
521
    // we exclude this method in VclBuilder because it's so useful to have it like this
522 523 524
    auto check = loplugin::DeclCheck(pMethodDecl).Function("get");
    if (check.Class("VclBuilder").GlobalNamespace()
        || check.Class("VclBuilderContainer").GlobalNamespace())
525 526 527
    {
        return true;
    }
528 529
    return true;
}
530

531 532 533 534 535 536 537

static void findDisposeAndClearStatements(std::set<const FieldDecl*>& aVclPtrFields, const Stmt *pStmt)
{
    if (!pStmt)
        return;
    if (isa<CompoundStmt>(pStmt)) {
        const CompoundStmt *pCompoundStatement = dyn_cast<CompoundStmt>(pStmt);
538 539 540 541
        for (auto i = pCompoundStatement->body_begin();
             i != pCompoundStatement->body_end(); ++i)
        {
            findDisposeAndClearStatements(aVclPtrFields, *i);
542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558
        }
        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;
559 560 561
    auto check = loplugin::DeclCheck(
        dyn_cast<CXXMethodDecl>(pCallExpr->getDirectCallee()));
    if (!(check.Function("disposeAndClear") || check.Function("clear")))
562 563 564 565 566 567 568 569
            return;

    if (!pCallExpr->getCallee()) return;

    if (!isa<MemberExpr>(pCallExpr->getCallee())) return;
    const MemberExpr *pCalleeMemberExpr = dyn_cast<MemberExpr>(pCallExpr->getCallee());

    if (!pCalleeMemberExpr->getBase()) return;
570 571
    const MemberExpr *pCalleeMemberExprBase = dyn_cast<MemberExpr>(pCalleeMemberExpr->getBase()->IgnoreImpCasts());
    if (pCalleeMemberExprBase == nullptr) return;
572 573 574 575 576 577 578

    const FieldDecl* xxx = dyn_cast_or_null<FieldDecl>(pCalleeMemberExprBase->getMemberDecl());
    if (xxx)
        aVclPtrFields.erase(xxx);
}


579 580 581 582 583
bool VCLWidgets::VisitFunctionDecl( const FunctionDecl* functionDecl )
{
    if (ignoreLocation(functionDecl)) {
        return true;
    }
584
    // ignore the stuff in the VclPtr template class
585 586 587
    if (loplugin::DeclCheck(functionDecl).MemberFunction().Class("VclPtr")
        .GlobalNamespace())
    {
588 589
        return true;
    }
590
    // ignore the BASE_REF_COUNTED_CLASS::dispose() method
591 592 593
    if (loplugin::DeclCheck(functionDecl).Function("dispose")
        .Class(BASE_REF_COUNTED_CLASS).GlobalNamespace())
    {
594 595
        return true;
    }
596
    const CXXMethodDecl *pMethodDecl = dyn_cast<CXXMethodDecl>(functionDecl);
597
    if (functionDecl->hasBody() && pMethodDecl && isDerivedFromVclReferenceBase(pMethodDecl->getParent())) {
598
        // check the last thing that the dispose() method does, is to call into the superclass dispose method
599
        if (loplugin::DeclCheck(functionDecl).Function("dispose")) {
600 601 602
            if (!isDisposeCallingSuperclassDispose(pMethodDecl)) {
                report(
                    DiagnosticsEngine::Warning,
603
                    BASE_REF_COUNTED_CLASS " subclass dispose() function MUST call dispose() of its superclass as the last thing it does",
604
                    compat::getBeginLoc(functionDecl))
605
                  << functionDecl->getSourceRange();
606 607 608
           }
        }
    }
609

610 611 612 613
    // 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
614
        && loplugin::DeclCheck(functionDecl).Function("dispose")
615
        && isDerivedFromVclReferenceBase(pMethodDecl->getParent()) )
616
    {
617 618 619 620
        auto check = loplugin::DeclCheck(functionDecl).MemberFunction();
        if (check.Class("VirtualDevice").GlobalNamespace()
            || check.Class("Breadcrumb").GlobalNamespace())
        {
621
            return true;
622
        }
623 624

        std::set<const FieldDecl*> aVclPtrFields;
625 626 627 628
        for (auto i = pMethodDecl->getParent()->field_begin();
             i != pMethodDecl->getParent()->field_end(); ++i)
        {
            auto const type = loplugin::TypeCheck((*i)->getType());
629
            if (type.Class("VclPtr").GlobalNamespace()) {
630
               aVclPtrFields.insert(*i);
631 632 633 634 635
            } else if (type.Class("vector").StdNamespace()
                       || type.Class("map").StdNamespace()
                       || type.Class("list").StdNamespace()
                       || type.Class("set").StdNamespace())
            {
636
                const RecordType* recordType = dyn_cast_or_null<RecordType>((*i)->getType()->getUnqualifiedDesugaredType());
637 638 639 640 641
                if (recordType) {
                    auto d = dyn_cast<ClassTemplateSpecializationDecl>(recordType->getDecl());
                    if (d && d->getTemplateArgs().size()>0) {
                        auto const type = loplugin::TypeCheck(d->getTemplateArgs()[0].getAsType());
                        if (type.Class("VclPtr").GlobalNamespace()) {
642
                            aVclPtrFields.insert(*i);
643 644 645 646 647 648 649 650 651 652 653 654 655 656 657
                        }
                    }
                }
            }
        }
        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 clear() on the following field(s): ";
                for(auto s : aVclPtrFields)
                    aMessage += ", " + s->getNameAsString();
                report(
                    DiagnosticsEngine::Warning,
                    aMessage,
658
                    compat::getBeginLoc(functionDecl))
659 660 661 662 663
                  << functionDecl->getSourceRange();
           }
       }
    }

664 665 666
    return true;
}

667 668 669 670 671
bool VCLWidgets::VisitCXXDeleteExpr(const CXXDeleteExpr *pCXXDeleteExpr)
{
    if (ignoreLocation(pCXXDeleteExpr)) {
        return true;
    }
672
    const CXXRecordDecl *pPointee = pCXXDeleteExpr->getArgument()->getType()->getPointeeCXXRecordDecl();
673
    if (pPointee && isDerivedFromVclReferenceBase(pPointee)) {
674
        SourceLocation spellingLocation = compiler.getSourceManager().getSpellingLoc(
675
                              compat::getBeginLoc(pCXXDeleteExpr));
676
        StringRef filename = getFileNameOfSpellingLoc(spellingLocation);
677
        if ( !(loplugin::isSamePathname(filename, SRCDIR "/include/vcl/vclreferencebase.hxx")))
678 679 680
        {
            report(
                DiagnosticsEngine::Warning,
681
                "calling delete on instance of " BASE_REF_COUNTED_CLASS " subclass, must rather call disposeAndClear()",
682
                compat::getBeginLoc(pCXXDeleteExpr))
683 684
              << pCXXDeleteExpr->getSourceRange();
        }
685
    }
686 687 688 689 690 691 692 693 694 695
    const ImplicitCastExpr* pImplicitCastExpr = dyn_cast<ImplicitCastExpr>(pCXXDeleteExpr->getArgument());
    if (!pImplicitCastExpr) {
        return true;
    }
    if (pImplicitCastExpr->getCastKind() != CK_UserDefinedConversion) {
        return true;
    }
    report(
        DiagnosticsEngine::Warning,
        "calling delete on instance of VclPtr, must rather call disposeAndClear()",
696
        compat::getBeginLoc(pCXXDeleteExpr))
697 698 699 700 701
     << pCXXDeleteExpr->getSourceRange();
    return true;
}


702 703 704 705 706 707 708 709 710 711 712 713
/**
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) return false;
714
    if (pCompoundStatement->size() == 0) return false;
715 716 717 718 719
    // find the last statement
    const CXXMemberCallExpr *pCallExpr = dyn_cast<CXXMemberCallExpr>(*pCompoundStatement->body_rbegin());
    if (!pCallExpr) return false;
    const MemberExpr *pMemberExpr = dyn_cast<MemberExpr>(pCallExpr->getCallee());
    if (!pMemberExpr) return false;
720
    if (!loplugin::DeclCheck(pMemberExpr->getMemberDecl()).Function("dispose")) return false;
721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736
    const CXXMethodDecl *pDirectCallee = dyn_cast<CXXMethodDecl>(pCallExpr->getDirectCallee());
    if (!pDirectCallee) return false;
/* 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;
    }*/
    return true;
}

737
bool containsVclPtr(const clang::Type* pType0);
738 739

bool containsVclPtr(const QualType& qType) {
740 741 742 743 744 745 746
    auto check = loplugin::TypeCheck(qType);
    if (check.Class("ScopedVclPtr").GlobalNamespace()
        || check.Class("ScopedVclPtrInstance").GlobalNamespace()
        || check.Class("VclPtr").GlobalNamespace()
        || check.Class("VclPtrInstance").GlobalNamespace())
    {
        return true;
747 748 749 750
    }
    return containsVclPtr(qType.getTypePtr());
}

751
bool containsVclPtr(const clang::Type* pType0) {
752 753
    if (!pType0)
        return false;
754
    const clang::Type* pType = pType0->getUnqualifiedDesugaredType();
755 756 757 758 759
    if (!pType)
        return false;
    if (pType->isPointerType()) {
        return false;
    } else if (pType->isArrayType()) {
760
        const clang::ArrayType* pArrayType = dyn_cast<clang::ArrayType>(pType);
761 762 763 764 765 766
        QualType elementType = pArrayType->getElementType();
        return containsVclPtr(elementType);
    } else {
        const CXXRecordDecl* pRecordDecl = pType->getAsCXXRecordDecl();
        if (pRecordDecl)
        {
767 768 769 770 771
            auto check = loplugin::DeclCheck(pRecordDecl);
            if (check.Class("ScopedVclPtr").GlobalNamespace()
                || check.Class("ScopedVclPtrInstance").GlobalNamespace()
                || check.Class("VclPtr").GlobalNamespace()
                || check.Class("VclPtrInstance").GlobalNamespace())
772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839
            {
                return true;
            }
            for(auto fieldDecl = pRecordDecl->field_begin();
                fieldDecl != pRecordDecl->field_end(); ++fieldDecl)
            {
                const RecordType *pFieldRecordType = fieldDecl->getType()->getAs<RecordType>();
                if (pFieldRecordType && containsVclPtr(pFieldRecordType)) {
                    return true;
                }
            }
            for(auto baseSpecifier = pRecordDecl->bases_begin();
                baseSpecifier != pRecordDecl->bases_end(); ++baseSpecifier)
            {
                const RecordType *pFieldRecordType = baseSpecifier->getType()->getAs<RecordType>();
                if (pFieldRecordType && containsVclPtr(pFieldRecordType)) {
                    return true;
                }
            }
        }
    }
    return false;
}

bool VCLWidgets::VisitCallExpr(const CallExpr* pCallExpr)
{
    if (ignoreLocation(pCallExpr)) {
        return true;
    }
    FunctionDecl const * fdecl = pCallExpr->getDirectCallee();
    if (fdecl == nullptr) {
        return true;
    }
    std::string qname { fdecl->getQualifiedNameAsString() };
    if (qname.find("memcpy") == std::string::npos
         && qname.find("bcopy") == std::string::npos
         && qname.find("memmove") == std::string::npos
         && qname.find("rtl_copy") == std::string::npos) {
        return true;
    }
    mbCheckingMemcpy = true;
    Stmt * pStmt = const_cast<Stmt*>(static_cast<const Stmt*>(pCallExpr->getArg(0)));
    TraverseStmt(pStmt);
    mbCheckingMemcpy = false;
    return true;
}

bool VCLWidgets::VisitDeclRefExpr(const DeclRefExpr* pDeclRefExpr)
{
    if (!mbCheckingMemcpy) {
        return true;
    }
    if (ignoreLocation(pDeclRefExpr)) {
        return true;
    }
    QualType pType = pDeclRefExpr->getDecl()->getType();
    if (pType->isPointerType()) {
        pType = pType->getPointeeType();
    }
    if (!containsVclPtr(pType)) {
        return true;
    }
    report(
        DiagnosticsEngine::Warning,
        "Calling memcpy on a type which contains a VclPtr",
        pDeclRefExpr->getExprLoc());
    return true;
}
840

841 842 843 844 845 846 847 848 849 850
bool VCLWidgets::VisitCXXConstructExpr( const CXXConstructExpr* constructExpr )
{
    if (ignoreLocation(constructExpr)) {
        return true;
    }
    if (constructExpr->getConstructionKind() != CXXConstructExpr::CK_Complete) {
        return true;
    }
    const CXXConstructorDecl* pConstructorDecl = constructExpr->getConstructor();
    const CXXRecordDecl* recordDecl = pConstructorDecl->getParent();
851
    if (isDerivedFromVclReferenceBase(recordDecl)) {
852
        StringRef aFileName = getFileNameOfSpellingLoc(
853
            compiler.getSourceManager().getSpellingLoc(compat::getBeginLoc(constructExpr)));
854
        if (!loplugin::isSamePathname(aFileName, SRCDIR "/include/vcl/vclptr.hxx")) {
855 856 857 858 859
            report(
                DiagnosticsEngine::Warning,
                "Calling constructor of a VclReferenceBase-derived type directly; all such creation should go via VclPtr<>::Create",
                constructExpr->getExprLoc());
        }
860 861 862
    }
    return true;
}
863

864
loplugin::Plugin::Registration< VCLWidgets > vclwidgets("vclwidgets");
865 866 867

}

868 869
#endif // LO_CLANG_SHARED_PLUGINS

870
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */