/* This file is part of KDevelop Copyright 2010 Aleix Pol Gonzalez This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "codeanalysistest.h" #include #include #include #include #include QTEST_MAIN(CodeAnalysisTest) using namespace KDevelop; using namespace Cpp; CodeAnalysisTest::CodeAnalysisTest(QObject* parent): QObject(parent) { initShell(); } void CodeAnalysisTest::initTestCase() {} void CodeAnalysisTest::cleanupTestCase() {} void CodeAnalysisTest::testUseReadWrite() { QFETCH(QString, code); QFETCH(QVariantList, modFlags); LockedTopDUContext top = parse(code.toUtf8(), DumpNone, 0, true); DataAccessRepository* repo = &m_modifications; // foreach(DataAccess* f, repo->modifications()) { // qDebug() << "lalala" << f->flags(); // } QCOMPARE(repo->modifications().count(), modFlags.size()); int i=0; foreach(const QVariant& f, modFlags) { // qDebug() << "flags" << repo->modifications().at(i)->flags() << f; KDevelop::DataAccess::DataAccessFlags p(f.toUInt()); DataAccess* da=repo->modifications().at(i); QCOMPARE(da->isRead(), bool(p&DataAccess::Read)); QCOMPARE(da->isWrite(), bool(p&DataAccess::Write)); DUContext* ctx=top->findContextAt(da->pos()); int u=ctx->findUseAt(da->pos()); Declaration* d=ctx->findDeclarationAt(da->pos()); QVERIFY(u>=0 || d); i++; } } void CodeAnalysisTest::testUseReadWrite_data() { QTest::addColumn("code"); QTest::addColumn("modFlags"); QTest::newRow("def") << "int a; void main() { a=3; }" << (QVariantList() << uint(DataAccess::Write)); QTest::newRow("int==") << "int a; int main() { a==3; }" << (QVariantList() << uint(DataAccess::Read)); QTest::newRow("callr") << "int f(int e, int a) { f(a,e); }" << (QVariantList() << uint(DataAccess::Read) << uint(DataAccess::Read) << uint(DataAccess::Read)); QTest::newRow("callrw") << "int p,w; int f(int&, int) { f(p,w); }" << (QVariantList() << uint(DataAccess::Write|DataAccess::Read) << uint(DataAccess::Read) << uint(DataAccess::Read)); QTest::newRow("initandcopy") << "void f() { int a=1, b; a=b; }" << (QVariantList() << uint(DataAccess::Write) << uint(DataAccess::Write) << uint(DataAccess::Read)); QTest::newRow("oper==") << "class C { bool operator==(const C&) const; }; bool f(const C& e) { return e==e; }" << (QVariantList() << uint(DataAccess::Read) << uint(DataAccess::Read)); QTest::newRow("++oper") << "struct C { bool operator++(); }; C e; bool f() { ++e; }" << (QVariantList() << uint(DataAccess::Write|DataAccess::Read)); QTest::newRow("oper++") << "struct C { bool operator++(int); }; C e; bool f() { e++; }" << (QVariantList() << uint(DataAccess::Write|DataAccess::Read)); QTest::newRow("int++") << "void f() { int e; e++; }" << (QVariantList() << uint(DataAccess::Write|DataAccess::Read)); QTest::newRow("member-dot") << "struct C { C boh(); }; C e; bool f() { e.boh().boh().boh(); }" << (QVariantList() << uint(DataAccess::Write|DataAccess::Read) << uint(DataAccess::Call|DataAccess::Read|DataAccess::Write) << uint(DataAccess::Call|DataAccess::Read|DataAccess::Write) << uint(DataAccess::Call|DataAccess::Read|DataAccess::Write)); QTest::newRow("member-arrow") << "struct C { C boh(); }; C* e; bool f() { e->boh(); }" << (QVariantList() << uint(DataAccess::Read) << uint(DataAccess::Call|DataAccess::Read)); QTest::newRow("forloop") << "int i; bool f() { for(i=0; i<33; ++i) {} }" << (QVariantList() << uint(DataAccess::Write) << uint(DataAccess::Read) << uint(DataAccess::Read|DataAccess::Write)); QTest::newRow("if-nocond") << "int i; bool f() { if(i) {} }" << (QVariantList() << uint(DataAccess::Read)); QTest::newRow("if-unary") << "int i; bool f() { if(!i) {} }" << (QVariantList() << uint(DataAccess::Read)); QTest::newRow("ternary-op") << "int i; bool f() { return i ? i : i; }" //TODO: do the same when ref parameter << (QVariantList() << uint(DataAccess::Read) << uint(DataAccess::Read) << uint(DataAccess::Read)); QTest::newRow("initializer") << "struct C {int i,j; C() : i(0),j(i) {} };" << (QVariantList() << uint(DataAccess::Write) << uint(DataAccess::Write) << uint(DataAccess::Read)); QTest::newRow("new-delete") << "int *a,b; void f() { a = new int(b); delete a; }" << (QVariantList() << uint(DataAccess::Write) << uint(DataAccess::Read) << uint(DataAccess::Read)); QTest::newRow("local new-delete") << "int b; void f() { int *a = new int(b); delete a; }" << (QVariantList() << uint(DataAccess::Write) << uint(DataAccess::Read) << uint(DataAccess::Read)); QTest::newRow("new-define") << "void f() { int b=2, *a = new int(b); }" << (QVariantList() << uint(DataAccess::Write) << uint(DataAccess::Write) << uint(DataAccess::Read)); QTest::newRow("return") << "int a; int f() { return a; }" << (QVariantList() << uint(DataAccess::Read)); QTest::newRow("init") << "int f() { int a=3; }" << (QVariantList() << uint(DataAccess::Write)); QTest::newRow("init2") << "int f() { int a=3; int b=a; }" << (QVariantList() << uint(DataAccess::Write) << uint(DataAccess::Write) << uint(DataAccess::Read)); QTest::newRow("switch") << "int f(int a) { switch(a) { case 3: break;} }" << (QVariantList() << uint(DataAccess::Read)); QTest::newRow("constructor") << "class C { C(int,int&); }; void f(int a) { int b; C* c=new C(a,b); }" << (QVariantList() << uint(DataAccess::Write) << uint(DataAccess::Read) << uint(DataAccess::Read|DataAccess::Write)); QTest::newRow("empty constructor") << "class C { C(); }; void f() { new C; }" << QVariantList(); QTest::newRow("function call, different parameter count") << "void f(int) { f(3,4); }" << (QVariantList() << uint(DataAccess::Read)); QTest::newRow("method call, different parameter count") << "class C { void f(int,int&); }; void f(int x) { C c; c.f(1,x); }" << (QVariantList() << uint(DataAccess::Read|DataAccess::Write) << uint(DataAccess::Read|DataAccess::Write) << uint(DataAccess::Call|DataAccess::Read|DataAccess::Write)); QTest::newRow("casts") << "class C { C m(); }; class D : public C {}; void f(D* a) { static_cast(a)->m(); }" << (QVariantList() << uint(DataAccess::Read) << uint(DataAccess::Read) << uint(DataAccess::Call|DataAccess::Read)); QTest::newRow("ptr") << "void f() { int* a=new int; (*a)=3; }" << (QVariantList() << uint(DataAccess::Write) << uint(DataAccess::Read)); } static void walkNodesRecursively(ControlFlowNode* node, QSet& visited) { if(!visited.contains(node)) { visited.insert(node); if(node->next()) walkNodesRecursively(node->next(), visited); if(node->alternative()) walkNodesRecursively(node->alternative(), visited); } } class ControlFlowToDot { public: ControlFlowToDot(QTextStream* dev, const QByteArray& sources) : m_dev(dev), m_sources(sources) {} static QString escapeQuotes(const QString& _s) { QString s(_s); s.replace('\"', "\\\""); return s;} bool exportGraph(const QByteArray&, const ControlFlowGraph* graph) { bool r = true; int i=0; *m_dev << "digraph G {\n"; *m_dev << " label = \""+escapeQuotes(m_sources)+"\";\n"; QList n=graph->rootNodes(); for(QList::const_iterator it=n.constBegin(), itEnd=n.constEnd(); it!=itEnd; ++it, ++i) { *m_dev << " subgraph cluster_" << i << " {\n\tcolor=black;\n"; Declaration* d=declarationForNode(graph, *it); if(d) *m_dev << "\tlabel=\""+d->toString()+"\"\n"; r &= exportNode(*it); *m_dev << " }\n"; } QVector< ControlFlowNode* > deadNodes = graph->deadNodes(); if(!deadNodes.isEmpty()) { *m_dev << " subgraph cluster_"<< i <<" {\n"; *m_dev << "\tlabel = \"Dead Nodes\";\n"; foreach(ControlFlowNode* node, deadNodes) { *m_dev << '\t' << nodesName(node) << " [color=green]\n\n"; } *m_dev << " }\n"; } *m_dev << "}\n"; return r; } Declaration* declarationForNode(const ControlFlowGraph* graph, const ControlFlowNode* node) const { foreach(Declaration* d, graph->declarations()) { if(graph->nodeForDeclaration(d)==node) return d; } return 0; } private: bool exportNode(const ControlFlowNode* node) { if(!node) return false; QHash< const ControlFlowNode*, QString >::iterator initialIt = m_names.find(node); bool isfirst = m_names.isEmpty(); if(initialIt==m_names.end()) { initialIt = m_names.insert(node, nodesName(node)); } else return true; QString name = initialIt.value(); if(isfirst) *m_dev << '\t' << name << " [shape=doubleoctagon]\n\n"; if(!node->next() && !node->alternative()) *m_dev << '\t' << name << " [color=red]\n\n"; if(exportNode(node->next())) *m_dev << '\t' << name << " -> " << m_names.value(node->next()) << " [color=blue];\n"; if(exportNode(node->alternative())) *m_dev << '\t' << name << " -> " << m_names.value(node->alternative()) << " [color=red];\n"; return true; } QString nodesName(const ControlFlowNode* node) const { RangeInRevision range = node->nodeRange(); if(!node->next() && !node->alternative()) return "Exit"; else if(range.isEmpty()) { static int uniqueId=0; return QString("dummy_%1").arg(uniqueId++); } else { int a=cursorToPos(range.start), b=cursorToPos(range.end); QString ret=m_sources.mid(a, b-a); if(node->type()==ControlFlowNode::Conditional) { RangeInRevision crange = node->conditionRange(); int ca=cursorToPos(crange.start), cb=cursorToPos(crange.end); ret += " - condition: "+m_sources.mid(ca, cb-ca); } return "\""+ret+"\""; } } int cursorToPos(const CursorInRevision& cursor) const { int ret=0; int line=cursor.line, col=cursor.column; for(; line>0 && ret>0; line--) ret = m_sources.indexOf('\n', ret); if(ret<0) return -1; return ret + col; } QTextStream* m_dev; QHash m_names; QByteArray m_sources; }; void CodeAnalysisTest::testControlFlowCreation() { QFETCH(QString, code); QFETCH(int, nodeCount); LockedTopDUContext top = parse(code.toUtf8(), DumpNone); ControlFlowGraph* graph = &m_ctlflowGraph; QList n=graph->rootNodes(); QList::const_iterator it=n.constBegin(), itEnd=n.constEnd(); QSet visited; int entries=0; for(; it!=itEnd; ++it, ++entries) walkNodesRecursively(*it, visited); {//Graph exporting QFile file(QString(QTest::currentDataTag()).replace(' ', '_')+".dot"); QVERIFY(file.open(QFile::WriteOnly)); QTextStream st(&file); ControlFlowToDot exporter(&st, code.toUtf8()); exporter.exportGraph(QTest::currentDataTag(), graph); } QCOMPARE(visited.size(), nodeCount); foreach(ControlFlowNode* n, visited) { RangeInRevision crange=n->conditionRange(); QVERIFY(!crange.isValid() || crange.end>=crange.start); } } void CodeAnalysisTest::testControlFlowCreation_data() { QTest::addColumn("code"); QTest::addColumn("nodeCount"); QTest::newRow("empty") << "void f() {}" << 2; QTest::newRow("sequence") << "int f(int a) { return a+1; }" << 2; QTest::newRow("conditional") << "int f(int a) { if(a) a+=1; return a+3; }" << 4; QTest::newRow("conditional_else") << "int f(int a) { if(a) a+=1; else a-=1; return a+3; }" << 5; QTest::newRow("different exits") << "int f(int a) { if(a) return a; else return b; }" << 4; QTest::newRow("conditional_inlined") << "int f(int a) { a=a?a+1 : a-1; return a+3; }" << 5; QTest::newRow("while") << "int f(int q) { while(q) {q--} return q; }" << 5; QTest::newRow("for") << "int f(int a) { for(int i=0; i20) break; } f(666); }" << 7; QTest::newRow("loopconti") << "void f(int i) { while(i) { if(i>20) continue; } f(666); }" << 7; QTest::newRow("goto") << "void f(int i) { f(0); tag: f(1); if(i) goto tag; f(2); f(1); }" << 5; QTest::newRow("goto2") << "void f(int i) { f(0); goto tag; f(1); if(i) f(3); tag: f(2); }" << 3; QTest::newRow("outside") << "enum {Result = 2 ? 1 : 3 };" << 4; QTest::newRow("class") << "struct Peu { public: Peu() { int x = 3?4:2; } int thing(int x) { return x ? x+1 : x-1; } };" << 10; QTest::newRow("lecture") << "int f(int x) { int i; if(x==0) i=3; else i=4; return i; }" << 5; }