cppfindreferences.cpp 16.5 KB
Newer Older
Roberto Raggi's avatar
Roberto Raggi committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
**
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://www.qtsoftware.com/contact.
**
**************************************************************************/

#include "cppfindreferences.h"
#include "cppmodelmanager.h"
#include "cpptoolsconstants.h"

#include <texteditor/basetexteditor.h>
#include <find/searchresultwindow.h>
#include <extensionsystem/pluginmanager.h>
#include <utils/filesearch.h>
#include <coreplugin/progressmanager/progressmanager.h>
#include <coreplugin/icore.h>

#include <ASTVisitor.h>
#include <AST.h>
#include <Control.h>
#include <Literals.h>
#include <TranslationUnit.h>
#include <Symbols.h>
47
48
#include <Names.h>
#include <Scope.h>
Roberto Raggi's avatar
Roberto Raggi committed
49
50
51
52

#include <cplusplus/CppDocument.h>
#include <cplusplus/ExpressionUnderCursor.h>
#include <cplusplus/ResolveExpression.h>
53
#include <cplusplus/Overview.h>
Roberto Raggi's avatar
Roberto Raggi committed
54
#include <cplusplus/TypeOfExpression.h>
Roberto Raggi's avatar
Roberto Raggi committed
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69

#include <QtCore/QTime>
#include <QtCore/QtConcurrentRun>
#include <QtCore/QDir>

#include <qtconcurrent/runextensions.h>

using namespace CppTools::Internal;
using namespace CPlusPlus;

namespace {

struct Process: protected ASTVisitor
{
public:
70
71
    Process(Document::Ptr doc, const Snapshot &snapshot,
            QFutureInterface<Core::Utils::FileSearchResult> *future)
Roberto Raggi's avatar
Roberto Raggi committed
72
73
74
75
            : ASTVisitor(doc->control()),
              _future(future),
              _doc(doc),
              _snapshot(snapshot),
76
77
78
79
80
              _source(_doc->source()),
              _sem(doc->control())
    {
        _snapshot.insert(_doc);
    }
Roberto Raggi's avatar
Roberto Raggi committed
81

82
    QList<int> operator()(Symbol *symbol, Identifier *id, AST *ast)
Roberto Raggi's avatar
Roberto Raggi committed
83
    {
84
        _references.clear();
85
        _declSymbol = symbol;
Roberto Raggi's avatar
Roberto Raggi committed
86
87
88
        _id = id;
        _exprDoc = Document::create("<references>");
        accept(ast);
89
        return _references;
Roberto Raggi's avatar
Roberto Raggi committed
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
    }

protected:
    using ASTVisitor::visit;

    QString matchingLine(const Token &tk) const
    {
        const char *beg = _source.constData();
        const char *cp = beg + tk.offset;
        for (; cp != beg - 1; --cp) {
            if (*cp == '\n')
                break;
        }

        ++cp;

        const char *lineEnd = cp + 1;
        for (; *lineEnd; ++lineEnd) {
            if (*lineEnd == '\n')
                break;
        }

        const QString matchingLine = QString::fromUtf8(cp, lineEnd - cp);
        return matchingLine;

    }

    void reportResult(unsigned tokenIndex)
    {
        const Token &tk = tokenAt(tokenIndex);
        const QString lineText = matchingLine(tk);

        unsigned line, col;
        getTokenStartPosition(tokenIndex, &line, &col);

        if (col)
            --col;  // adjust the column position.

128
        const int len = tk.f.length;
Roberto Raggi's avatar
Roberto Raggi committed
129

130
131
132
133
134
        if (_future)
            _future->reportResult(Core::Utils::FileSearchResult(QDir::toNativeSeparators(_doc->fileName()),
                                                                line, lineText, col, len));

        _references.append(tokenIndex);
Roberto Raggi's avatar
Roberto Raggi committed
135
136
    }

137
    bool checkCandidates(const QList<Symbol *> &candidates) const
Roberto Raggi's avatar
Roberto Raggi committed
138
    {
Roberto Raggi's avatar
Roberto Raggi committed
139
140
141
142
143
144
145
146
147
148
149
        if (Symbol *canonicalSymbol = LookupContext::canonicalSymbol(candidates)) {
#if 0
            qDebug() << "*** canonical symbol:" << canonicalSymbol->fileName()
                    << canonicalSymbol->line() << canonicalSymbol->column()
                    << "candidates:" << candidates.size();
#endif

            return isDeclSymbol(canonicalSymbol);
        }

        return false;
Roberto Raggi's avatar
Roberto Raggi committed
150
151
    }

152
    bool isDeclSymbol(Symbol *symbol) const
Roberto Raggi's avatar
Roberto Raggi committed
153
    {
154
155
        if (! symbol)
            return false;
Roberto Raggi's avatar
Roberto Raggi committed
156

157
158
159
160
161
162
163
164
165
        else if (symbol == _declSymbol)
            return true;

        else if (symbol->line() == _declSymbol->line() && symbol->column() == _declSymbol->column()) {
            if (! qstrcmp(symbol->fileName(), _declSymbol->fileName()))
                return true;
        }

        return false;
Roberto Raggi's avatar
Roberto Raggi committed
166
167
    }

168
    LookupContext currentContext(AST *ast) const
Roberto Raggi's avatar
Roberto Raggi committed
169
    {
170
171
172
173
        unsigned line, column;
        getTokenStartPosition(ast->firstToken(), &line, &column);
        Symbol *lastVisibleSymbol = _doc->findSymbolAt(line, column);
        return LookupContext(lastVisibleSymbol, _exprDoc, _doc, _snapshot);
Roberto Raggi's avatar
Roberto Raggi committed
174
175
    }

Roberto Raggi's avatar
Roberto Raggi committed
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
    void ensureNameIsValid(NameAST *ast)
    {
        if (ast && ! ast->name)
            ast->name = _sem.check(ast, /*scope = */ 0);
    }

    virtual bool visit(MemInitializerAST *ast)
    {
        if (ast->name && ast->name->asSimpleName() != 0) {
            ensureNameIsValid(ast->name);

            SimpleNameAST *simple = ast->name->asSimpleName();
            if (identifier(simple->identifier_token) == _id) {
                LookupContext context = currentContext(ast);
                const QList<Symbol *> candidates = context.resolve(simple->name);
                if (checkCandidates(candidates))
                    reportResult(simple->identifier_token);
            }
        }
        accept(ast->expression);
        return false;
    }

Roberto Raggi's avatar
Roberto Raggi committed
199
200
201
202
203
204
    virtual bool visit(PostfixExpressionAST *ast)
    {
        _postfixExpressionStack.append(ast);
        return true;
    }

Roberto Raggi's avatar
Roberto Raggi committed
205
    virtual void endVisit(PostfixExpressionAST *)
Roberto Raggi's avatar
Roberto Raggi committed
206
207
208
209
210
211
    {
        _postfixExpressionStack.removeLast();
    }

    virtual bool visit(MemberAccessAST *ast)
    {
Roberto Raggi's avatar
Roberto Raggi committed
212
213
214
215
        if (ast->member_name) {
            if (SimpleNameAST *simple = ast->member_name->asSimpleName()) {
                if (identifier(simple->identifier_token) == _id) {
                    Q_ASSERT(! _postfixExpressionStack.isEmpty());
Roberto Raggi's avatar
Roberto Raggi committed
216

Roberto Raggi's avatar
Roberto Raggi committed
217
218
                    checkExpression(_postfixExpressionStack.last()->firstToken(),
                                    simple->identifier_token);
Roberto Raggi's avatar
Roberto Raggi committed
219

Roberto Raggi's avatar
Roberto Raggi committed
220
221
222
223
                    return false;
                }
            }
        }
Roberto Raggi's avatar
Roberto Raggi committed
224

Roberto Raggi's avatar
Roberto Raggi committed
225
226
        return true;
    }
Roberto Raggi's avatar
Roberto Raggi committed
227

Roberto Raggi's avatar
Roberto Raggi committed
228
229
230
231
    void checkExpression(unsigned startToken, unsigned endToken)
    {
        const unsigned begin = tokenAt(startToken).begin();
        const unsigned end = tokenAt(endToken).end();
Roberto Raggi's avatar
Roberto Raggi committed
232

Roberto Raggi's avatar
Roberto Raggi committed
233
234
        const QString expression = _source.mid(begin, end - begin);
        // qDebug() << "*** expression:" << expression;
Roberto Raggi's avatar
Roberto Raggi committed
235

Roberto Raggi's avatar
Roberto Raggi committed
236
237
        TypeOfExpression typeofExpression;
        typeofExpression.setSnapshot(_snapshot);
Roberto Raggi's avatar
Roberto Raggi committed
238

Roberto Raggi's avatar
Roberto Raggi committed
239
240
241
        unsigned line, column;
        getTokenStartPosition(startToken, &line, &column);
        Symbol *lastVisibleSymbol = _doc->findSymbolAt(line, column);
Roberto Raggi's avatar
Roberto Raggi committed
242

Roberto Raggi's avatar
Roberto Raggi committed
243
244
        const QList<TypeOfExpression::Result> results =
                typeofExpression(expression, _doc, lastVisibleSymbol,
245
                                 TypeOfExpression::Preprocess);
Roberto Raggi's avatar
Roberto Raggi committed
246

Roberto Raggi's avatar
Roberto Raggi committed
247
        QList<Symbol *> candidates;
Roberto Raggi's avatar
Roberto Raggi committed
248

Roberto Raggi's avatar
Roberto Raggi committed
249
250
251
        foreach (TypeOfExpression::Result r, results) {
            FullySpecifiedType ty = r.first;
            Symbol *lastVisibleSymbol = r.second;
Roberto Raggi's avatar
Roberto Raggi committed
252

Roberto Raggi's avatar
Roberto Raggi committed
253
            candidates.append(lastVisibleSymbol);
Roberto Raggi's avatar
Roberto Raggi committed
254
255
        }

Roberto Raggi's avatar
Roberto Raggi committed
256
257
        if (checkCandidates(candidates))
            reportResult(endToken);
Roberto Raggi's avatar
Roberto Raggi committed
258
259
    }

Roberto Raggi's avatar
Roberto Raggi committed
260
261
    virtual bool visit(QualifiedNameAST *ast)
    {
Roberto Raggi's avatar
Roberto Raggi committed
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
        for (NestedNameSpecifierAST *nested_name_specifier = ast->nested_name_specifier;
             nested_name_specifier; nested_name_specifier = nested_name_specifier->next) {

            if (NameAST *class_or_namespace_name = nested_name_specifier->class_or_namespace_name) {
                SimpleNameAST *simple_name = class_or_namespace_name->asSimpleName();

                TemplateIdAST *template_id = 0;
                if (! simple_name) {
                    template_id = class_or_namespace_name->asTemplateId();

                    if (template_id) {
                        for (TemplateArgumentListAST *template_arguments = template_id->template_arguments;
                             template_arguments; template_arguments = template_arguments->next) {
                            accept(template_arguments->template_argument);
                        }
                    }
                }

                if (simple_name || template_id) {
                    const unsigned identifier_token = simple_name
                               ? simple_name->identifier_token
                               : template_id->identifier_token;

                    if (identifier(identifier_token) == _id)
                        checkExpression(ast->firstToken(), identifier_token);
                }
            }
        }

        if (ast->unqualified_name) {
            SimpleNameAST *simple_name = ast->unqualified_name->asSimpleName();

            TemplateIdAST *template_id = 0;
            if (! simple_name) {
                template_id = ast->unqualified_name->asTemplateId();

                if (template_id) {
                    for (TemplateArgumentListAST *template_arguments = template_id->template_arguments;
                         template_arguments; template_arguments = template_arguments->next) {
                        accept(template_arguments->template_argument);
                    }
                }
            }

            if (simple_name || template_id) {
                const unsigned identifier_token = simple_name
                        ? simple_name->identifier_token
                        : template_id->identifier_token;

                if (identifier(identifier_token) == _id)
                    checkExpression(ast->firstToken(), identifier_token);
            }
314
315
        }

Roberto Raggi's avatar
Roberto Raggi committed
316
317
318
319
320
321
322
        return false;
    }

    virtual bool visit(SimpleNameAST *ast)
    {
        Identifier *id = identifier(ast->identifier_token);
        if (id == _id) {
323
324
325
            LookupContext context = currentContext(ast);
            const QList<Symbol *> candidates = context.resolve(ast->name);
            if (checkCandidates(candidates))
Roberto Raggi's avatar
Roberto Raggi committed
326
                reportResult(ast->identifier_token);
327
328
329
        }

        return false;
Roberto Raggi's avatar
Roberto Raggi committed
330
331
    }

Roberto Raggi's avatar
Roberto Raggi committed
332
    virtual bool visit(DestructorNameAST *ast)
Roberto Raggi's avatar
Roberto Raggi committed
333
334
335
    {
        Identifier *id = identifier(ast->identifier_token);
        if (id == _id) {
336
337
338
339
            LookupContext context = currentContext(ast);
            const QList<Symbol *> candidates = context.resolve(ast->name);
            if (checkCandidates(candidates))
                reportResult(ast->identifier_token);
Roberto Raggi's avatar
Roberto Raggi committed
340
341
342
343
344
345
346
347
        }

        return false;
    }

    virtual bool visit(TemplateIdAST *ast)
    {
        Identifier *id = identifier(ast->identifier_token);
348
349
350
351
352
353
        if (id == _id) {
            LookupContext context = currentContext(ast);
            const QList<Symbol *> candidates = context.resolve(ast->name);
            if (checkCandidates(candidates))
                reportResult(ast->identifier_token);
        }
Roberto Raggi's avatar
Roberto Raggi committed
354

355
        return false;
Roberto Raggi's avatar
Roberto Raggi committed
356
357
358
    }

private:
359
    QFutureInterface<Core::Utils::FileSearchResult> *_future;
360
361
    Identifier *_id; // ### remove me
    Symbol *_declSymbol;
Roberto Raggi's avatar
Roberto Raggi committed
362
363
364
365
    Document::Ptr _doc;
    Snapshot _snapshot;
    QByteArray _source;
    Document::Ptr _exprDoc;
366
    Semantic _sem;
Roberto Raggi's avatar
Roberto Raggi committed
367
    QList<PostfixExpressionAST *> _postfixExpressionStack;
Roberto Raggi's avatar
Roberto Raggi committed
368
    QList<QualifiedNameAST *> _qualifiedNameStack;
369
    QList<int> _references;
Roberto Raggi's avatar
Roberto Raggi committed
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
};

} // end of anonymous namespace

CppFindReferences::CppFindReferences(CppModelManager *modelManager)
    : _modelManager(modelManager),
      _resultWindow(ExtensionSystem::PluginManager::instance()->getObject<Find::SearchResultWindow>())
{
    m_watcher.setPendingResultsLimit(1);
    connect(&m_watcher, SIGNAL(resultReadyAt(int)), this, SLOT(displayResult(int)));
    connect(&m_watcher, SIGNAL(finished()), this, SLOT(searchFinished()));
}

CppFindReferences::~CppFindReferences()
{
}

387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
QList<int> CppFindReferences::references(Symbol *symbol,
                                         Document::Ptr doc,
                                         const Snapshot& snapshot) const
{
    Identifier *id = 0;
    if (Identifier *symbolId = symbol->identifier())
        id = doc->control()->findIdentifier(symbolId->chars(), symbolId->size());

    QList<int> references;

    if (! id)
        return references;

    TranslationUnit *translationUnit = doc->translationUnit();
    Q_ASSERT(translationUnit != 0);

    Process process(doc, snapshot, /*future = */ 0);
    references = process(symbol, id, translationUnit->ast());

    return references;
}

Roberto Raggi's avatar
Roberto Raggi committed
409
static void find_helper(QFutureInterface<Core::Utils::FileSearchResult> &future,
410
                        const QMap<QString, QString> wl,
411
412
                        Snapshot snapshot,
                        Symbol *symbol)
Roberto Raggi's avatar
Roberto Raggi committed
413
414
415
{
    QTime tm;
    tm.start();
416

417
418
419
    Identifier *symbolId = symbol->identifier();
    Q_ASSERT(symbolId != 0);

Roberto Raggi's avatar
Cleanup    
Roberto Raggi committed
420
    const QString sourceFile = QString::fromUtf8(symbol->fileName(), symbol->fileNameLength());
421

Roberto Raggi's avatar
Cleanup    
Roberto Raggi committed
422
423
    QStringList files(sourceFile);
    files += snapshot.dependsOn(sourceFile);
Roberto Raggi's avatar
Roberto Raggi committed
424
425
426
427
428
    qDebug() << "done in:" << tm.elapsed() << "number of files to parse:" << files.size();

    future.setProgressRange(0, files.size());

    for (int i = 0; i < files.size(); ++i) {
Roberto Raggi's avatar
Cleanup    
Roberto Raggi committed
429
430
        const QString &fileName = files.at(i);
        future.setProgressValueAndText(i, QFileInfo(fileName).fileName());
431

Roberto Raggi's avatar
Cleanup    
Roberto Raggi committed
432
        Document::Ptr previousDoc = snapshot.value(fileName);
433
434
        if (previousDoc) {
            Control *control = previousDoc->control();
Roberto Raggi's avatar
Roberto Raggi committed
435
            previousDoc->control();
436
437
438
439
440
            Identifier *id = control->findIdentifier(symbolId->chars(), symbolId->size());
            if (! id)
                continue; // skip this document, it's not using symbolId.
        }

441
442
443
444
445
446
447
448
449
450
451
452
453
454
        QByteArray source;

        if (wl.contains(fileName))
            source = snapshot.preprocessedCode(wl.value(fileName), fileName);
        else {
            QFile file(fileName);
            if (! file.open(QFile::ReadOnly))
                continue;

            const QString contents = QTextStream(&file).readAll(); // ### FIXME
            source = snapshot.preprocessedCode(contents, fileName);
        }

        Document::Ptr doc = snapshot.documentFromSource(source, fileName);
455
        doc->tokenize();
Roberto Raggi's avatar
Roberto Raggi committed
456
457

        Control *control = doc->control();
458
        if (Identifier *id = control->findIdentifier(symbolId->chars(), symbolId->size())) {
459
460
            doc->check();
            TranslationUnit *unit = doc->translationUnit();
461
            Process process(doc, snapshot, &future);
462
            process(symbol, id, unit->ast());
463
        }
Roberto Raggi's avatar
Roberto Raggi committed
464
    }
465

Roberto Raggi's avatar
Roberto Raggi committed
466
467
468
    future.setProgressValue(files.size());
}

469
void CppFindReferences::findAll(Symbol *symbol)
Roberto Raggi's avatar
Roberto Raggi committed
470
471
{
    _resultWindow->clearContents();
Roberto Raggi's avatar
Roberto Raggi committed
472
    _resultWindow->setShowReplaceUI(true);
Roberto Raggi's avatar
Roberto Raggi committed
473
474
    _resultWindow->popup(true);

475
476
477
    const Snapshot snapshot = _modelManager->snapshot();
    const QMap<QString, QString> wl = _modelManager->buildWorkingCopyList();

Roberto Raggi's avatar
Roberto Raggi committed
478
479
    Core::ProgressManager *progressManager = Core::ICore::instance()->progressManager();

480
    QFuture<Core::Utils::FileSearchResult> result = QtConcurrent::run(&find_helper, wl, snapshot, symbol);
Roberto Raggi's avatar
Roberto Raggi committed
481
482
483
    m_watcher.setFuture(result);

    Core::FutureProgress *progress = progressManager->addTask(result, tr("Searching..."),
484
                                                              CppTools::Constants::TASK_SEARCH,
Roberto Raggi's avatar
Roberto Raggi committed
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
                                                              Core::ProgressManager::CloseOnSuccess);

    connect(progress, SIGNAL(clicked()), _resultWindow, SLOT(popup()));
}

void CppFindReferences::displayResult(int index)
{
    Core::Utils::FileSearchResult result = m_watcher.future().resultAt(index);
    Find::ResultWindowItem *item = _resultWindow->addResult(result.fileName,
                                                            result.lineNumber,
                                                            result.matchingLine,
                                                            result.matchStart,
                                                            result.matchLength);
    if (item)
        connect(item, SIGNAL(activated(const QString&,int,int)),
                this, SLOT(openEditor(const QString&,int,int)));
}

void CppFindReferences::searchFinished()
{
    emit changed();
}

void CppFindReferences::openEditor(const QString &fileName, int line, int column)
{
    TextEditor::BaseTextEditor::openEditorAt(fileName, line, column);
}