/**************************************************************************** ** ** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** 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 Digia. For licensing terms and ** conditions see http://qt.digia.com/licensing. For further information ** use the contact form at http://qt.digia.com/contact-us. ** ** 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. ** ** In addition, as a special exception, Digia gives you certain additional ** rights. These rights are described in the Digia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cplusplus-tools-utils.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __GNUC__ # include #endif // For isatty(), _isatty() #if defined(Q_OS_WIN) # include #else # include #endif bool tty_for_stdin() { #if defined(Q_OS_WIN) return _isatty(_fileno(stdin)); #else return isatty(fileno(stdin)); #endif } using namespace CPlusPlus; class ASTDump: protected ASTVisitor { public: ASTDump(TranslationUnit *unit) : ASTVisitor(unit) {} void operator()(AST *ast) { QByteArray basename = translationUnit()->fileName(); basename.append(".ast.dot"); out.open(basename.constData()); out << "digraph AST { ordering=out;" << std::endl; // std::cout << "rankdir = \"LR\";" << std::endl; generateTokens(); accept(ast); typedef QPair Pair; foreach (const Pair &conn, _connections) out << conn.first.constData() << " -> " << conn.second.constData() << std::endl; alignTerminals(); out << "}" << std::endl; out.close(); } // the following file can be generated by using: // cplusplus-update-frontend #include "dumpers.inc" protected: void alignTerminals() { out<<"{ rank=same;" << std::endl; foreach (const QByteArray &terminalShape, _terminalShapes) { out << " " << std::string(terminalShape) << ";" << std::endl; } out<<"}"<tokenCount(); ++token) { if (translationUnit()->tokenKind(token) == T_EOF_SYMBOL) break; QByteArray t; t.append(terminalId(token)); t.append(" [shape=rect label = \""); t.append(spell(token)); t.append("\"]"); if (token > 1) { t.append("; "); t.append(terminalId(token - 1)); t.append(" -> "); t.append(terminalId(token)); t.append(" [arrowhead=\"vee\" color=\"transparent\"]"); } _terminalShapes.append(t); } } virtual void nonterminal(AST *ast) { accept(ast); } virtual void node(AST *ast) { out << _id[ast].constData() << " [label=\"" << name(ast).constData() << "\"];" << std::endl; } virtual bool preVisit(AST *ast) { static int count = 1; const QByteArray id = 'n' + QByteArray::number(count++); _id[ast] = id; if (! _stack.isEmpty()) _connections.append(qMakePair(_id[_stack.last()], id)); _stack.append(ast); node(ast); return true; } virtual void postVisit(AST *) { _stack.removeLast(); } private: QHash _id; QList > _connections; QList _stack; QList _terminalShapes; std::ofstream out; }; class SymbolDump: protected SymbolVisitor { public: SymbolDump(TranslationUnit *unit) : translationUnit(unit) { o.setShowArgumentNames(true); o.setShowFunctionSignatures(true); o.setShowReturnTypes(true); } void operator()(Symbol *s) { QByteArray basename = translationUnit->fileName(); basename.append(".symbols.dot"); out.open(basename.constData()); out << "digraph Symbols { ordering=out;" << std::endl; // std::cout << "rankdir = \"LR\";" << std::endl; accept(s); for (int i = 0; i < _connections.size(); ++i) { QPair connection = _connections.at(i); QByteArray from = _id.value(connection.first); if (from.isEmpty()) from = name(connection.first); QByteArray to = _id.value(connection.second); if (to.isEmpty()) to = name(connection.second); out << from.constData() << " -> " << to.constData() << ";" << std::endl; } out << "}" << std::endl; out.close(); } protected: QByteArray name(Symbol *s) { #ifdef __GNUC__ QByteArray result = abi::__cxa_demangle(typeid(*s).name(), 0, 0, 0) + 11; #else QByteArray result = typeid(*s).name(); #endif if (s->identifier()) { result.append("\\nid: "); result.append(s->identifier()->chars()); } if (s->isDeprecated()) result.append("\\n(deprecated)"); return result; } virtual bool preVisit(Symbol *s) { static int count = 0; QByteArray nodeId("s"); nodeId.append(QByteArray::number(++count)); _id[s] = nodeId; if (!_stack.isEmpty()) _connections.append(qMakePair(_stack.last(), s)); _stack.append(s); return true; } virtual void postVisit(Symbol *) { _stack.removeLast(); } virtual void simpleNode(Symbol *symbol) { out << _id[symbol].constData() << " [label=\"" << name(symbol).constData() << "\"];" << std::endl; } virtual bool visit(Class *symbol) { const char *id = _id.value(symbol).constData(); out << id << " [label=\""; if (symbol->isClass()) { out << "class"; } else if (symbol->isStruct()) { out << "struct"; } else if (symbol->isUnion()) { out << "union"; } else { out << "UNKNOWN"; } out << "\\nid: "; if (symbol->identifier()) { out << symbol->identifier()->chars(); } else { out << "NO ID"; } if (symbol->isDeprecated()) out << "\\n(deprecated)"; out << "\"];" << std::endl; return true; } virtual bool visit(UsingNamespaceDirective *symbol) { simpleNode(symbol); return true; } virtual bool visit(UsingDeclaration *symbol) { simpleNode(symbol); return true; } virtual bool visit(Declaration *symbol) { out << _id[symbol].constData() << " [label=\""; out << "Declaration\\n"; out << qPrintable(o(symbol->name())); out << ": "; out << qPrintable(o(symbol->type())); if (symbol->isDeprecated()) out << "\\n(deprecated)"; if (Function *funTy = symbol->type()->asFunctionType()) { if (funTy->isPureVirtual()) out << "\\n(pure virtual)"; else if (funTy->isVirtual()) out << "\\n(virtual)"; if (funTy->isSignal()) out << "\\n(signal)"; if (funTy->isSlot()) out << "\\n(slot)"; if (funTy->isInvokable()) out << "\\n(invokable)"; } out << "\"];" << std::endl; return true; } virtual bool visit(Argument *symbol) { simpleNode(symbol); return true; } virtual bool visit(TypenameArgument *symbol) { simpleNode(symbol); return true; } virtual bool visit(BaseClass *symbol) { out << _id[symbol].constData() << " [label=\"BaseClass\\n"; out << qPrintable(o(symbol->name())); if (symbol->isDeprecated()) out << "\\n(deprecated)"; out << "\"];" << std::endl; return true; } virtual bool visit(Enum *symbol) { simpleNode(symbol); return true; } virtual bool visit(Function *symbol) { simpleNode(symbol); return true; } virtual bool visit(Namespace *symbol) { simpleNode(symbol); return true; } virtual bool visit(Block *symbol) { simpleNode(symbol); return true; } virtual bool visit(ForwardClassDeclaration *symbol) { simpleNode(symbol); return true; } virtual bool visit(ObjCBaseClass *symbol) { simpleNode(symbol); return true; } virtual bool visit(ObjCBaseProtocol *symbol) { simpleNode(symbol); return true; } virtual bool visit(ObjCClass *symbol) { simpleNode(symbol); return true; } virtual bool visit(ObjCForwardClassDeclaration *symbol) { simpleNode(symbol); return true; } virtual bool visit(ObjCProtocol *symbol) { simpleNode(symbol); return true; } virtual bool visit(ObjCForwardProtocolDeclaration *symbol) { simpleNode(symbol); return true; } virtual bool visit(ObjCMethod *symbol) { simpleNode(symbol); return true; } virtual bool visit(ObjCPropertyDeclaration *symbol) { simpleNode(symbol); return true; } private: TranslationUnit *translationUnit; QHash _id; QList >_connections; QList _stack; std::ofstream out; Overview o; }; void createImageFromDot(const QString &inputFile, const QString &outputFile, bool verbose) { const QString command = CplusplusToolsUtils::portableExecutableName(QLatin1String("dot")); const QStringList arguments = QStringList() << QLatin1String("-Tpng") << QLatin1String("-o") << outputFile << inputFile; CplusplusToolsUtils::executeCommand(command, arguments, QString(), verbose); } const char PATH_STDIN_FILE[] = "_stdincontents.cpp"; QString example() { return #if defined(Q_OS_WIN) QString::fromLatin1("> echo int foo() {} | %1 && %2.ast.png") #elif defined(Q_OS_MAC) QString::fromLatin1("$ echo \"int foo() {}\" | ./%1 && open %2.ast.png") #else QString::fromLatin1("$ echo \"int foo() {}\" | ./%1 && xdg-open %2.ast.png") #endif .arg(QFileInfo(qApp->arguments().at(0)).fileName(), PATH_STDIN_FILE); } void printUsage() { std::cout << "Usage: " << qPrintable(QFileInfo(qApp->arguments().at(0)).fileName()) << " [-v] ...\n\n"; std::cout << qPrintable(QString::fromLatin1( "Visualize AST and symbol hierarchy of given C++ files by generating png image files\n" "in the same directory as the input files. Print paths to generated image files.\n" "\n" "Standard input is also read. The resulting files starts with \"%1\"\n" "and are created in the current working directory. To show the AST for simple snippets\n" "you might want to execute:\n" "\n" " %2\n" "\n" "Prerequisites:\n" " 1) Make sure to have 'dot' from graphviz locatable by PATH.\n" " 2) Make sure to have an up to date dumpers file by using 'cplusplus-update-frontend'.\n" ).arg(PATH_STDIN_FILE, example())); } int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); QStringList args = app.arguments(); args.removeFirst(); bool optionVerbose = false; // Data from stdin? if (!tty_for_stdin()) { QFile file("_stdincontents.cpp"); if (! file.open(QFile::WriteOnly)) { std::cerr << "Error: Cannot open file for writing\"" << qPrintable(file.fileName()) << "\"" << std::endl; exit(EXIT_FAILURE); } file.write(QTextStream(stdin).readAll().toLocal8Bit()); file.close(); args.append(file.fileName()); } // Process options & arguments if (args.contains("-v")) { optionVerbose = true; args.removeOne("-v"); } const bool helpRequested = args.contains("-h") || args.contains("-help"); if (args.isEmpty() || helpRequested) { printUsage(); return helpRequested ? EXIT_SUCCESS : EXIT_FAILURE; } // Process files const QStringList files = args; foreach (const QString &fileName, files) { if (! QFile::exists(fileName)) { std::cerr << "Error: File \"" << qPrintable(fileName) << "\" does not exist." << std::endl; exit(EXIT_FAILURE); } // Run the preprocessor const QString fileNamePreprocessed = fileName + QLatin1String(".preprocessed"); CplusplusToolsUtils::SystemPreprocessor preprocessor(optionVerbose); preprocessor.preprocessFile(fileName, fileNamePreprocessed); // Convert to dot QFile file(fileNamePreprocessed); if (! file.open(QFile::ReadOnly)) { std::cerr << "Error: Could not open file \"" << qPrintable(fileNamePreprocessed) << "\"" << std::endl; exit(EXIT_FAILURE); } const QByteArray source = file.readAll(); file.close(); Document::Ptr doc = Document::create(fileName); doc->control()->setDiagnosticClient(0); doc->setUtf8Source(source); doc->parse(); doc->check(); ASTDump dump(doc->translationUnit()); dump(doc->translationUnit()->ast()); SymbolDump dump2(doc->translationUnit()); dump2(doc->globalNamespace()); // Create images typedef QPair Pair; QList inputOutputFiles; inputOutputFiles.append(qMakePair(QString(fileName + QLatin1String(".ast.dot")), QString(fileName + QLatin1String(".ast.png")))); inputOutputFiles.append(qMakePair(QString(fileName + QLatin1String(".symbols.dot")), QString(fileName + QLatin1String(".symbols.png")))); foreach (const Pair &pair, inputOutputFiles) { createImageFromDot(pair.first, pair.second, optionVerbose); std::cout << qPrintable(QDir::toNativeSeparators(pair.second)) << std::endl; } } return EXIT_SUCCESS; }