forked from qt-creator/qt-creator
* Update files in src/plugins Change-Id: Ia5d77fad7d19d4bb3498e78661982f68729adb22 Reviewed-by: Tobias Hunger <tobias.hunger@theqtcompany.com>
472 lines
18 KiB
C++
472 lines
18 KiB
C++
/****************************************************************************
|
|
**
|
|
** Copyright (C) 2016 The Qt Company Ltd.
|
|
** Contact: https://www.qt.io/licensing/
|
|
**
|
|
** This file is part of Qt Creator.
|
|
**
|
|
** Commercial License Usage
|
|
** Licensees holding valid commercial Qt licenses may use this file in
|
|
** accordance with the commercial license agreement provided with the
|
|
** Software or, alternatively, in accordance with the terms contained in
|
|
** a written agreement between you and The Qt Company. For licensing terms
|
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
|
** information use the contact form at https://www.qt.io/contact-us.
|
|
**
|
|
** GNU General Public License Usage
|
|
** Alternatively, this file may be used under the terms of the GNU
|
|
** General Public License version 3 as published by the Free Software
|
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
|
** included in the packaging of this file. Please review the following
|
|
** information to ensure the GNU General Public License requirements will
|
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
|
**
|
|
****************************************************************************/
|
|
|
|
#include "cpppointerdeclarationformatter.h"
|
|
|
|
#include <cplusplus/Overview.h>
|
|
|
|
#include <QDebug>
|
|
#include <QTextCursor>
|
|
|
|
#define DEBUG_OUTPUT 0
|
|
|
|
#if DEBUG_OUTPUT
|
|
# include <typeinfo>
|
|
# ifdef __GNUC__
|
|
# include <cxxabi.h>
|
|
# endif
|
|
#endif
|
|
|
|
#define CHECK_RV(cond, err, r) \
|
|
if (!(cond)) { if (DEBUG_OUTPUT) qDebug() << "Discarded:" << (err); return r; }
|
|
#define CHECK_R(cond, err) \
|
|
if (!(cond)) { if (DEBUG_OUTPUT) qDebug() << "Discarded:" << (err); return; }
|
|
#define CHECK_C(cond, err) \
|
|
if (!(cond)) { if (DEBUG_OUTPUT) qDebug() << "Discarded:" << (err); continue; }
|
|
|
|
using namespace CppTools;
|
|
|
|
/*!
|
|
Skips specifiers that are not type relevant and returns the index of the
|
|
first specifier token which is not followed by __attribute__
|
|
((T___ATTRIBUTE__)).
|
|
|
|
This is used to get 'correct' start of the activation range in
|
|
simple declarations.
|
|
|
|
Consider these cases:
|
|
|
|
\list
|
|
\li \c {static char *s = 0;}
|
|
\li \c {typedef char *s cp;}
|
|
\li \c {__attribute__((visibility("default"))) char *f();}
|
|
\endlist
|
|
|
|
For all these cases we want to skip all the specifiers that are not type
|
|
relevant
|
|
(since these are not part of the type and thus are not rewritten).
|
|
|
|
\a list is the specifier list to iterate and \a translationUnit is the
|
|
translation unit.
|
|
\a endToken is the last token to check.
|
|
\a found is an output parameter that must not be 0.
|
|
*/
|
|
static unsigned firstTypeSpecifierWithoutFollowingAttribute(
|
|
SpecifierListAST *list, TranslationUnit *translationUnit, unsigned endToken, bool *found)
|
|
{
|
|
*found = false;
|
|
if (!list || !translationUnit || !endToken)
|
|
return 0;
|
|
|
|
for (SpecifierListAST *it = list; it; it = it->next) {
|
|
SpecifierAST *specifier = it->value;
|
|
CHECK_RV(specifier, "No specifier", 0);
|
|
const unsigned index = specifier->firstToken();
|
|
CHECK_RV(index < endToken, "EndToken reached", 0);
|
|
|
|
const int tokenKind = translationUnit->tokenKind(index);
|
|
switch (tokenKind) {
|
|
case T_VIRTUAL:
|
|
case T_INLINE:
|
|
case T_FRIEND:
|
|
case T_REGISTER:
|
|
case T_STATIC:
|
|
case T_EXTERN:
|
|
case T_MUTABLE:
|
|
case T_TYPEDEF:
|
|
case T_CONSTEXPR:
|
|
case T___ATTRIBUTE__:
|
|
continue;
|
|
default:
|
|
// Check if attributes follow
|
|
for (unsigned i = index; i <= endToken; ++i) {
|
|
const int tokenKind = translationUnit->tokenKind(i);
|
|
if (tokenKind == T___ATTRIBUTE__)
|
|
return 0;
|
|
}
|
|
*found = true;
|
|
return index;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
PointerDeclarationFormatter::PointerDeclarationFormatter(
|
|
const CppRefactoringFilePtr &refactoringFile,
|
|
Overview &overview,
|
|
CursorHandling cursorHandling)
|
|
: ASTVisitor(refactoringFile->cppDocument()->translationUnit())
|
|
, m_cppRefactoringFile(refactoringFile)
|
|
, m_overview(overview)
|
|
, m_cursorHandling(cursorHandling)
|
|
{}
|
|
|
|
/*!
|
|
Handle
|
|
(1) Simple declarations like in "char *s, *t, *int foo();"
|
|
(2) Return types of function declarations.
|
|
*/
|
|
bool PointerDeclarationFormatter::visit(SimpleDeclarationAST *ast)
|
|
{
|
|
CHECK_RV(ast, "Invalid AST", true);
|
|
printCandidate(ast);
|
|
|
|
const unsigned tokenKind = tokenAt(ast->firstToken()).kind();
|
|
const bool astIsOk = tokenKind != T_CLASS && tokenKind != T_STRUCT && tokenKind != T_ENUM;
|
|
CHECK_RV(astIsOk, "Nothing to do for class/struct/enum", true);
|
|
|
|
DeclaratorListAST *declaratorList = ast->declarator_list;
|
|
CHECK_RV(declaratorList, "No declarator list", true);
|
|
DeclaratorAST *firstDeclarator = declaratorList->value;
|
|
CHECK_RV(firstDeclarator, "No declarator", true);
|
|
CHECK_RV(ast->symbols, "No Symbols", true);
|
|
CHECK_RV(ast->symbols->value, "No Symbol", true);
|
|
|
|
List<Symbol *> *sit = ast->symbols;
|
|
DeclaratorListAST *dit = declaratorList;
|
|
for (; sit && dit; sit = sit->next, dit = dit->next) {
|
|
DeclaratorAST *declarator = dit->value;
|
|
Symbol *symbol = sit->value;
|
|
|
|
const bool isFirstDeclarator = declarator == firstDeclarator;
|
|
|
|
// If were not handling the first declarator, we need to remove
|
|
// characters from the beginning since our rewritten declaration
|
|
// will contain all type specifiers.
|
|
int charactersToRemove = 0;
|
|
if (!isFirstDeclarator) {
|
|
const int startAST = m_cppRefactoringFile->startOf(ast);
|
|
const int startFirstDeclarator = m_cppRefactoringFile->startOf(firstDeclarator);
|
|
CHECK_RV(startAST < startFirstDeclarator, "No specifier", true);
|
|
charactersToRemove = startFirstDeclarator - startAST;
|
|
}
|
|
|
|
// Specify activation range
|
|
int lastActivationToken = 0;
|
|
TokenRange range;
|
|
// (2) Handle function declaration's return type
|
|
if (symbol->type()->asFunctionType()) {
|
|
PostfixDeclaratorListAST *pfDeclaratorList = declarator->postfix_declarator_list;
|
|
CHECK_RV(pfDeclaratorList, "No postfix declarator list", true);
|
|
PostfixDeclaratorAST *pfDeclarator = pfDeclaratorList->value;
|
|
CHECK_RV(pfDeclarator, "No postfix declarator", true);
|
|
FunctionDeclaratorAST *functionDeclarator = pfDeclarator->asFunctionDeclarator();
|
|
CHECK_RV(functionDeclarator, "No function declarator", true);
|
|
// End the activation range before the '(' token.
|
|
lastActivationToken = functionDeclarator->lparen_token - 1;
|
|
|
|
SpecifierListAST *specifierList = isFirstDeclarator
|
|
? ast->decl_specifier_list
|
|
: declarator->attribute_list;
|
|
|
|
unsigned firstActivationToken = 0;
|
|
bool foundBegin = false;
|
|
firstActivationToken = firstTypeSpecifierWithoutFollowingAttribute(
|
|
specifierList,
|
|
m_cppRefactoringFile->cppDocument()->translationUnit(),
|
|
lastActivationToken,
|
|
&foundBegin);
|
|
if (!foundBegin) {
|
|
CHECK_RV(!isFirstDeclarator, "Declaration without attributes not supported", true);
|
|
firstActivationToken = declarator->firstToken();
|
|
}
|
|
|
|
range.start = firstActivationToken;
|
|
|
|
// (1) Handle 'normal' declarations.
|
|
} else {
|
|
if (isFirstDeclarator) {
|
|
bool foundBegin = false;
|
|
unsigned firstActivationToken = firstTypeSpecifierWithoutFollowingAttribute(
|
|
ast->decl_specifier_list,
|
|
m_cppRefactoringFile->cppDocument()->translationUnit(),
|
|
declarator->firstToken(),
|
|
&foundBegin);
|
|
CHECK_RV(foundBegin, "Declaration without attributes not supported", true);
|
|
range.start = firstActivationToken;
|
|
} else {
|
|
range.start = declarator->firstToken();
|
|
}
|
|
lastActivationToken = declarator->equal_token
|
|
? declarator->equal_token - 1
|
|
: declarator->lastToken() - 1;
|
|
}
|
|
|
|
range.end = lastActivationToken;
|
|
|
|
checkAndRewrite(declarator, symbol, range, charactersToRemove);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*! Handle return types of function definitions */
|
|
bool PointerDeclarationFormatter::visit(FunctionDefinitionAST *ast)
|
|
{
|
|
CHECK_RV(ast, "Invalid AST", true);
|
|
printCandidate(ast);
|
|
|
|
DeclaratorAST *declarator = ast->declarator;
|
|
CHECK_RV(declarator, "No declarator", true);
|
|
CHECK_RV(declarator->ptr_operator_list, "No Pointer or references", true);
|
|
Symbol *symbol = ast->symbol;
|
|
|
|
PostfixDeclaratorListAST *pfDeclaratorList = declarator->postfix_declarator_list;
|
|
CHECK_RV(pfDeclaratorList, "No postfix declarator list", true);
|
|
PostfixDeclaratorAST *pfDeclarator = pfDeclaratorList->value;
|
|
CHECK_RV(pfDeclarator, "No postfix declarator", true);
|
|
FunctionDeclaratorAST *functionDeclarator = pfDeclarator->asFunctionDeclarator();
|
|
CHECK_RV(functionDeclarator, "No function declarator", true);
|
|
|
|
// Specify activation range
|
|
bool foundBegin = false;
|
|
const unsigned lastActivationToken = functionDeclarator->lparen_token - 1;
|
|
const unsigned firstActivationToken = firstTypeSpecifierWithoutFollowingAttribute(
|
|
ast->decl_specifier_list,
|
|
m_cppRefactoringFile->cppDocument()->translationUnit(),
|
|
lastActivationToken,
|
|
&foundBegin);
|
|
CHECK_RV(foundBegin, "Declaration without attributes not supported", true);
|
|
TokenRange range(firstActivationToken, lastActivationToken);
|
|
|
|
checkAndRewrite(declarator, symbol, range);
|
|
return true;
|
|
}
|
|
|
|
/*! Handle parameters in function declarations and definitions */
|
|
bool PointerDeclarationFormatter::visit(ParameterDeclarationAST *ast)
|
|
{
|
|
CHECK_RV(ast, "Invalid AST", true);
|
|
printCandidate(ast);
|
|
|
|
DeclaratorAST *declarator = ast->declarator;
|
|
CHECK_RV(declarator, "No declarator", true);
|
|
CHECK_RV(declarator->ptr_operator_list, "No Pointer or references", true);
|
|
Symbol *symbol = ast->symbol;
|
|
|
|
// Specify activation range
|
|
const int lastActivationToken = ast->equal_token
|
|
? ast->equal_token - 1
|
|
: ast->lastToken() - 1;
|
|
TokenRange range(ast->firstToken(), lastActivationToken);
|
|
|
|
checkAndRewrite(declarator, symbol, range);
|
|
return true;
|
|
}
|
|
|
|
/*! Handle declaration in foreach statement */
|
|
bool PointerDeclarationFormatter::visit(ForeachStatementAST *ast)
|
|
{
|
|
CHECK_RV(ast, "Invalid AST", true);
|
|
printCandidate(ast);
|
|
|
|
DeclaratorAST *declarator = ast->declarator;
|
|
CHECK_RV(declarator, "No declarator", true);
|
|
CHECK_RV(declarator->ptr_operator_list, "No Pointer or references", true);
|
|
CHECK_RV(ast->type_specifier_list, "No type specifier", true);
|
|
SpecifierAST *firstSpecifier = ast->type_specifier_list->value;
|
|
CHECK_RV(firstSpecifier, "No first type specifier", true);
|
|
CHECK_RV(ast->symbol, "No symbol", true);
|
|
Symbol *symbol = ast->symbol->memberAt(0);
|
|
|
|
// Specify activation range
|
|
const int lastActivationToken = declarator->equal_token
|
|
? declarator->equal_token - 1
|
|
: declarator->lastToken() - 1;
|
|
TokenRange range(firstSpecifier->firstToken(), lastActivationToken);
|
|
|
|
checkAndRewrite(declarator, symbol, range);
|
|
return true;
|
|
}
|
|
|
|
bool PointerDeclarationFormatter::visit(IfStatementAST *ast)
|
|
{
|
|
CHECK_RV(ast, "Invalid AST", true);
|
|
printCandidate(ast);
|
|
processIfWhileForStatement(ast->condition, ast->symbol);
|
|
return true;
|
|
}
|
|
|
|
bool PointerDeclarationFormatter::visit(WhileStatementAST *ast)
|
|
{
|
|
CHECK_RV(ast, "Invalid AST", true);
|
|
printCandidate(ast);
|
|
processIfWhileForStatement(ast->condition, ast->symbol);
|
|
return true;
|
|
}
|
|
|
|
bool PointerDeclarationFormatter::visit(ForStatementAST *ast)
|
|
{
|
|
CHECK_RV(ast, "Invalid AST", true);
|
|
printCandidate(ast);
|
|
processIfWhileForStatement(ast->condition, ast->symbol);
|
|
return true;
|
|
}
|
|
|
|
/*! Handle declaration in if, while and for statements */
|
|
void PointerDeclarationFormatter::processIfWhileForStatement(ExpressionAST *expression,
|
|
Symbol *statementSymbol)
|
|
{
|
|
CHECK_R(expression, "No expression");
|
|
CHECK_R(statementSymbol, "No symbol");
|
|
|
|
ConditionAST *condition = expression->asCondition();
|
|
CHECK_R(condition, "No condition");
|
|
DeclaratorAST *declarator = condition->declarator;
|
|
CHECK_R(declarator, "No declarator");
|
|
CHECK_R(declarator->ptr_operator_list, "No Pointer or references");
|
|
CHECK_R(declarator->equal_token, "No equal token");
|
|
Block *block = statementSymbol->asBlock();
|
|
CHECK_R(block, "No block");
|
|
CHECK_R(block->memberCount() > 0, "No block members");
|
|
|
|
// Get the right symbol
|
|
//
|
|
// This is especially important for e.g.
|
|
//
|
|
// for (char *s = 0; char *t = 0;) {}
|
|
//
|
|
// The declaration for 's' will be handled in visit(SimpleDeclarationAST *ast),
|
|
// so handle declaration for 't' here.
|
|
Scope::iterator it = block->memberEnd() - 1;
|
|
Symbol *symbol = *it;
|
|
if (symbol && symbol->asScope()) { // True if there is a "{ ... }" following.
|
|
--it;
|
|
symbol = *it;
|
|
}
|
|
|
|
// Specify activation range
|
|
TokenRange range(condition->firstToken(), declarator->equal_token - 1);
|
|
|
|
checkAndRewrite(declarator, symbol, range);
|
|
}
|
|
|
|
/*!
|
|
Performs some further checks and rewrites the type and name of \a symbol
|
|
into the substitution range in the file specified by \a tokenRange.
|
|
*/
|
|
void PointerDeclarationFormatter::checkAndRewrite(DeclaratorAST *declarator,
|
|
Symbol *symbol,
|
|
TokenRange tokenRange,
|
|
unsigned charactersToRemove)
|
|
{
|
|
CHECK_R(tokenRange.end > 0, "TokenRange invalid1");
|
|
CHECK_R(tokenRange.start < tokenRange.end, "TokenRange invalid2");
|
|
CHECK_R(symbol, "No symbol");
|
|
|
|
// Check for expanded tokens
|
|
for (unsigned token = tokenRange.start; token <= tokenRange.end; ++token)
|
|
CHECK_R(!tokenAt(token).expanded(), "Token is expanded");
|
|
|
|
Utils::ChangeSet::Range range(m_cppRefactoringFile->startOf(tokenRange.start),
|
|
m_cppRefactoringFile->endOf(tokenRange.end));
|
|
|
|
CHECK_R(range.start >= 0 && range.end > 0, "ChangeRange invalid1");
|
|
CHECK_R(range.start < range.end, "ChangeRange invalid2");
|
|
|
|
// Check range with respect to cursor position / selection
|
|
if (m_cursorHandling == RespectCursor) {
|
|
const QTextCursor cursor = m_cppRefactoringFile->cursor();
|
|
if (cursor.hasSelection()) {
|
|
CHECK_R(cursor.selectionStart() <= range.start, "Change not in selection range");
|
|
CHECK_R(range.end <= cursor.selectionEnd(), "Change not in selection range");
|
|
} else {
|
|
CHECK_R(range.start <= cursor.selectionStart(), "Cursor before activation range");
|
|
CHECK_R(cursor.selectionEnd() <= range.end, "Cursor after activation range");
|
|
}
|
|
}
|
|
|
|
FullySpecifiedType type = symbol->type();
|
|
if (Function *function = type->asFunctionType())
|
|
type = function->returnType();
|
|
|
|
// Check if pointers or references are involved
|
|
const QString originalDeclaration = m_cppRefactoringFile->textOf(range);
|
|
CHECK_R(originalDeclaration.contains(QLatin1Char('&'))
|
|
|| originalDeclaration.contains(QLatin1Char('*')), "No pointer or references");
|
|
|
|
// Does the rewritten declaration (part) differs from the original source (part)?
|
|
QString rewrittenDeclaration;
|
|
const Name *name = symbol->name();
|
|
if (name) {
|
|
if (name->isOperatorNameId()
|
|
|| (name->isQualifiedNameId()
|
|
&& name->asQualifiedNameId()->name()->isOperatorNameId())) {
|
|
const QString operatorText = m_cppRefactoringFile->textOf(declarator->core_declarator);
|
|
m_overview.includeWhiteSpaceInOperatorName = operatorText.contains(QLatin1Char(' '));
|
|
}
|
|
}
|
|
rewrittenDeclaration = m_overview.prettyType(type, name);
|
|
rewrittenDeclaration.remove(0, charactersToRemove);
|
|
|
|
CHECK_R(originalDeclaration != rewrittenDeclaration, "Rewritten is same as original");
|
|
CHECK_R(rewrittenDeclaration.contains(QLatin1Char('&'))
|
|
|| rewrittenDeclaration.contains(QLatin1Char('*')),
|
|
"No pointer or references in rewritten declaration");
|
|
|
|
if (DEBUG_OUTPUT) {
|
|
qDebug("==> Rewritten: \"%s\" --> \"%s\"", originalDeclaration.toUtf8().constData(),
|
|
rewrittenDeclaration.toUtf8().constData());
|
|
}
|
|
|
|
// Creating the replacement in the changeset may fail due to operations
|
|
// in the changeset that overlap with the current range.
|
|
//
|
|
// Consider this case:
|
|
//
|
|
// void (*foo)(char * s) = 0;
|
|
//
|
|
// First visit(SimpleDeclarationAST *ast) will be called. It creates a
|
|
// replacement that also includes the parameter.
|
|
// Next visit(ParameterDeclarationAST *ast) is called with the
|
|
// original source. It tries to create an replacement operation
|
|
// at this position and fails due to overlapping ranges (the
|
|
// simple declaration range includes parameter declaration range).
|
|
Utils::ChangeSet change(m_changeSet);
|
|
if (change.replace(range, rewrittenDeclaration))
|
|
m_changeSet = change;
|
|
else if (DEBUG_OUTPUT)
|
|
qDebug() << "Replacement operation failed";
|
|
}
|
|
|
|
void PointerDeclarationFormatter::printCandidate(AST *ast)
|
|
{
|
|
#if DEBUG_OUTPUT
|
|
QString tokens;
|
|
for (unsigned token = ast->firstToken(); token < ast->lastToken(); token++)
|
|
tokens += QString::fromLatin1(tokenAt(token).spell()) + QLatin1Char(' ');
|
|
|
|
# ifdef __GNUC__
|
|
QByteArray name = abi::__cxa_demangle(typeid(*ast).name(), 0, 0, 0) + 11;
|
|
name.truncate(name.length() - 3);
|
|
# else
|
|
QByteArray name = typeid(*ast).name();
|
|
# endif
|
|
qDebug("--> Candidate: %s: %s", name.constData(), qPrintable(tokens));
|
|
#else
|
|
Q_UNUSED(ast)
|
|
#endif // DEBUG_OUTPUT
|
|
}
|