/* This file is part of KDevelop Copyright 2007-2009 David Nolden Copyright 2011 Milian Wolff 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 "test_parser.h" #include #define private public #include "ast.h" #undef private #include "parser.h" #include "rpp/preprocessor.h" #include "tokens.h" #include "parsesession.h" #include "commentformatter.h" #include "testconfig.h" #include #include #include #include #include #include #include #include QString preprocess(const QString& contents) { rpp::Preprocessor preprocessor; rpp::pp pp(&preprocessor); QByteArray qba = stringFromContents(pp.processFile("anonymous", contents.toUtf8())); if(pp.problems().empty()) return QString::fromUtf8(qba); else return "*ERROR*"; } TestParser::TestParser() : lastSession(nullptr) { } void TestParser::initTestCase() { KDevelop::AutoTestShell::init(QStringList() << "kdevcppsupport"); KDevelop::TestCore* core = new KDevelop::TestCore(); core->initialize(KDevelop::Core::NoUi); } void TestParser::cleanupTestCase() { KDevelop::TestCore::shutdown(); } void TestParser::testSymbolTable() { NameTable table; table.findOrInsert("Ideal::MainWindow", sizeof("Ideal::MainWindow")); table.findOrInsert("QMainWindow", sizeof("QMainWindow")); table.findOrInsert("KXmlGuiWindow", sizeof("KXmlGuiWindow")); QCOMPARE(table.count(), size_t(3)); const NameSymbol *s = table.findOrInsert("QMainWindow", sizeof("QMainWindow")); QCOMPARE(QString(s->data), QString("QMainWindow")); QCOMPARE(table.count(), size_t(3)); } ///@todo reenable // void TestParser::testControlContexts() // { // Control control; // const NameSymbol *n1 = control.findOrInsertName("a", 1); // int *type1 = new int(1); // don't care much about types // control.declare(n1, (Type*)type1); // // control.pushContext(); // int *type2 = new int(2); // const NameSymbol *n2 = control.findOrInsertName("b", 1); // control.declare(n2, (Type*)type2); // // QCOMPARE(control.lookupType(n1), (Type*)type1); // QCOMPARE(control.lookupType(n2), (Type*)type2); // // control.popContext(); // QCOMPARE(control.lookupType(n1), (Type*)type1); // QCOMPARE(control.lookupType(n2), (Type*)0); // } void TestParser::testTokenTable() { QCOMPARE(token_name(Token_EOF), "eof"); QCOMPARE(token_name('a'), "a"); QCOMPARE(token_name(Token_delete), "delete"); } ///@todo reenable // void TestParser::testLexer() // { // QByteArray code("#include "); // TokenStream token_stream; // LocationTable location_table; // LocationTable line_table; // Control control; // // Lexer lexer(token_stream, location_table, line_table, &control); // lexer.tokenize(code, code.size()+1); // QCOMPARE(control.problem(0).message(), QString("expected end of line")); // // QByteArray code2("class Foo { int foo() {} }; "); // lexer.tokenize(code2, code2.size()+1); // QCOMPARE(control.problemCount(), 1); //we still have the old problem in the list // } void TestParser::testParser() { QByteArray clazz("struct A { int i; A() : i(5) { } virtual void test() = 0; };"); TranslationUnitAST* ast = parse(clazz); dump(ast); QVERIFY(control.problems().isEmpty()); QVERIFY(ast != 0); QVERIFY(ast->declarations != 0); } void TestParser::testTemplateArguments() { QByteArray templatetest("template struct SeriesAdder{ enum { value = N + SeriesAdder< 0 >::value }; };"); TranslationUnitAST* ast = parse(templatetest); QVERIFY(control.problems().isEmpty()); QVERIFY(ast != 0); QVERIFY(ast->declarations != 0); QVERIFY(control.problems().isEmpty()); } void TestParser::testTemplatedDTor() { // see also: https://bugs.kde.org/show_bug.cgi?id=253618 QByteArray templatetest("template struct A{ ~A(); };"); TranslationUnitAST* ast = parse(templatetest); QVERIFY(ast != 0); QVERIFY(ast->declarations != 0); QVERIFY(control.problems().isEmpty()); } void TestParser::testManyComparisons() { //Should not crash { QByteArray clazz("void TestParser::test() { if(val < f && val < val1 && val < val2 && val < val3 ){ } }"); TranslationUnitAST* ast = parse(clazz); dump(ast); QVERIFY(control.problems().isEmpty()); QVERIFY(ast != 0); QVERIFY(ast->declarations != 0); } { QByteArray clazz("void TestParser::test() { if(val < f && val < val1 && val < val2 && val < val3 && val < val4 && val < val5 && val < val6 && val < val7 && val < val8 && val < val9 && val < val10 && val < val11 && val < val12 && val < val13 && val < val14 && val < val15 && val < val16 && val < val17 && val < val18 && val < val19 && val < val20 && val < val21 && val < val22 && val < val23 && val < val24 && val < val25 && val < val26){ } }"); TranslationUnitAST* ast = parse(clazz); dump(ast); QVERIFY(control.problems().isEmpty()); QVERIFY(ast != 0); QVERIFY(ast->declarations != 0); } } void TestParser::testParserFail() { QByteArray stuff("foo bar !!! nothing that really looks like valid c++ code"); TranslationUnitAST *ast = parse(stuff); QVERIFY(ast->declarations == 0); QVERIFY(control.problems().count() > 3); } void TestParser::testPartialParseFail() { { QByteArray method("struct C { Something invalid is here };"); TranslationUnitAST* ast = parse(method); QVERIFY(ast != 0); QVERIFY(hasKind(ast, AST::Kind_ClassSpecifier)); } { QByteArray method("void TestParser::test() { Something invalid is here };"); TranslationUnitAST* ast = parse(method); QVERIFY(ast != 0); QVERIFY(hasKind(ast, AST::Kind_FunctionDefinition)); } { QByteArray method("void TestParser::test() { {Something invalid is here };"); TranslationUnitAST* ast = parse(method); QVERIFY(ast != 0); QVERIFY(hasKind(ast, AST::Kind_FunctionDefinition)); QVERIFY(ast->hadMissingCompoundTokens); } { QByteArray method("void TestParser::test() { case:{};"); TranslationUnitAST* ast = parse(method); QVERIFY(ast != 0); QVERIFY(hasKind(ast, AST::Kind_FunctionDefinition)); QVERIFY(ast->hadMissingCompoundTokens); } } void TestParser::testParseMethod() { QByteArray method("void TestParser::A::test() { }"); TranslationUnitAST* ast = parse(method); dump(ast); QVERIFY(control.problems().isEmpty()); QVERIFY(ast != 0); QVERIFY(hasKind(ast, AST::Kind_FunctionDefinition)); } void TestParser::testForStatements() { QByteArray method("void TestParser::A::t() { for (int i = 0; i < 10; i++) { ; }}"); TranslationUnitAST* ast = parse(method); QVERIFY(control.problems().isEmpty()); QVERIFY(ast != 0); QVERIFY(hasKind(ast, AST::Kind_ForStatement)); QVERIFY(hasKind(ast, AST::Kind_Condition)); QVERIFY(hasKind(ast, AST::Kind_IncrDecrExpression)); QVERIFY(hasKind(ast, AST::Kind_SimpleDeclaration)); QByteArray emptyFor("void TestParser::A::t() { for (;;) { } }"); ast = parse(emptyFor); QVERIFY(ast != 0); QVERIFY(hasKind(ast, AST::Kind_ForStatement)); QVERIFY(!hasKind(ast, AST::Kind_Condition)); QVERIFY(!hasKind(ast, AST::Kind_SimpleDeclaration)); } void TestParser::testIfStatements() { QByteArray method("void TestParser::A::t() { if (1 < 2) { } }"); TranslationUnitAST* ast = parse(method); QVERIFY(control.problems().isEmpty()); QVERIFY(hasKind(ast, AST::Kind_Condition)); QVERIFY(hasKind(ast, AST::Kind_BinaryExpression)); } void TestParser::testComments() { QByteArray method("//TranslationUnitComment\n" "//Hello\n" "int A; //behind\n" " /*between*/\n" " /*Hello2*/\n" " class B{}; //behind\n" "//Hello3\n" " //beforeTest\n" "void TestParser::test(); //testBehind"); TranslationUnitAST* ast = parse(method); QVERIFY(control.problems().isEmpty()); CommentFormatter formatter; QCOMPARE(formatter.formatComment(ast->comments, lastSession), QByteArray("TranslationUnitComment")); //The comments were merged const ListNode* it = ast->declarations; QVERIFY(it); QCOMPARE(it->count(), 3); it = it->next; QVERIFY(it); QVERIFY(it->element); QCOMPARE(formatter.formatComment(it->element->comments, lastSession), QByteArray("Hello\n(behind)")); it = it->next; QVERIFY(it); QVERIFY(it->element); QCOMPARE(formatter.formatComment(it->element->comments, lastSession), QByteArray("between\nHello2\n(behind)")); it = it->next; QVERIFY(it); QVERIFY(it->element); QCOMPARE(formatter.formatComment(it->element->comments, lastSession), QByteArray("Hello3\nbeforeTest\n(testBehind)")); } void TestParser::testComments2() { CommentFormatter formatter; QByteArray method("enum Enum\n {//enumerator1Comment\nenumerator1, //enumerator1BehindComment\n /*enumerator2Comment*/ enumerator2 /*enumerator2BehindComment*/};"); TranslationUnitAST* ast = parse(method); QVERIFY(control.problems().isEmpty()); const ListNode* it = ast->declarations; QVERIFY(it); it = it->next; QVERIFY(it); const SimpleDeclarationAST* simpleDecl = static_cast(it->element); QVERIFY(simpleDecl); const EnumSpecifierAST* enumSpec = (const EnumSpecifierAST*)simpleDecl->type_specifier; QVERIFY(enumSpec); const ListNode *enumerator = enumSpec->enumerators; QVERIFY(enumerator); enumerator = enumerator->next; QVERIFY(enumerator); QCOMPARE(formatter.formatComment(enumerator->element->comments, lastSession), QByteArray("enumerator1Comment\n(enumerator1BehindComment)")); enumerator = enumerator->next; QVERIFY(enumerator); QCOMPARE(formatter.formatComment(enumerator->element->comments, lastSession), QByteArray("enumerator2Comment\n(enumerator2BehindComment)")); } void TestParser::testComments3() { CommentFormatter formatter; QByteArray method("class Class{\n//Comment\n int val;};"); TranslationUnitAST* ast = parse(method); QVERIFY(control.problems().isEmpty()); const ListNode* it = ast->declarations; QVERIFY(it); it = it->next; QVERIFY(it); const SimpleDeclarationAST* simpleDecl = static_cast(it->element); QVERIFY(simpleDecl); const ClassSpecifierAST* classSpec = (const ClassSpecifierAST*)simpleDecl->type_specifier; QVERIFY(classSpec); const ListNode *members = classSpec->member_specs; QVERIFY(members); members = members->next; QVERIFY(members); QCOMPARE(formatter.formatComment(members->element->comments, lastSession), QByteArray("Comment")); } void TestParser::testComments4() { CommentFormatter formatter; QByteArray method("//TranslationUnitComment\n//Comment\ntemplate class Class{};"); TranslationUnitAST* ast = parse(method); const ListNode* it = ast->declarations; QVERIFY(it); it = it->next; QVERIFY(it); const TemplateDeclarationAST* templDecl = static_cast(it->element); QVERIFY(templDecl); QVERIFY(templDecl->declaration); //QCOMPARE(formatter.formatComment(templDecl->declaration->comments, lastSession), QString("Comment")); } void TestParser::testComments5() { CommentFormatter formatter; QByteArray method("//TranslationUnitComment\n //FIXME comment\n //this is TODO\n /* TODO: comment */\n int i; // another TODO \n // Just a simple comment\nint j;\n int main(void) {\n // TODO COMMENT\n}\n// Non-ascii TODO 例えば\n"); int initial_size = control.problems().size(); // Remember existing number of problems TranslationUnitAST* ast = parse(method); const ListNode* it = ast->declarations; QVERIFY(it); it = it->next; QVERIFY(it); QCOMPARE(formatter.formatComment(it->element->comments, lastSession), QByteArray("FIXME comment\nthis is TODO\n TODO: comment\n(another TODO)")); it = it->next; QVERIFY(it); QCOMPARE(formatter.formatComment(it->element->comments, lastSession), QByteArray("Just a simple comment")); QList problem_list = control.problems(); QCOMPARE(problem_list.size(), initial_size + 6); // 6 to-dos KDevelop::ProblemPointer problem = problem_list[initial_size]; QCOMPARE(problem->description(), QString("FIXME comment")); QCOMPARE(problem->source(), KDevelop::ProblemData::ToDo); QCOMPARE(problem->finalLocation().start, KDevelop::SimpleCursor(1, 4)); QCOMPARE(problem->finalLocation().end, KDevelop::SimpleCursor(1, 17)); problem = problem_list[initial_size + 1]; QCOMPARE(problem->description(), QString("this is TODO")); QCOMPARE(problem->source(), KDevelop::ProblemData::ToDo); QCOMPARE(problem->finalLocation().start, KDevelop::SimpleCursor(2, 3)); QCOMPARE(problem->finalLocation().end, KDevelop::SimpleCursor(2, 15)); problem = problem_list[initial_size + 2]; QCOMPARE(problem->description(), QString("TODO: comment")); QCOMPARE(problem->source(), KDevelop::ProblemData::ToDo); QCOMPARE(problem->finalLocation().start, KDevelop::SimpleCursor(3, 4)); QCOMPARE(problem->finalLocation().end, KDevelop::SimpleCursor(3, 17)); problem = problem_list[initial_size + 3]; QCOMPARE(problem->description(), QString("another TODO")); QCOMPARE(problem->source(), KDevelop::ProblemData::ToDo); QCOMPARE(problem->finalLocation().start, KDevelop::SimpleCursor(4, 13)); QCOMPARE(problem->finalLocation().end, KDevelop::SimpleCursor(4, 25)); problem = problem_list[initial_size + 4]; QCOMPARE(problem->description(), QString("TODO COMMENT")); QCOMPARE(problem->source(), KDevelop::ProblemData::ToDo); QCOMPARE(problem->finalLocation().start, KDevelop::SimpleCursor(8, 4)); QCOMPARE(problem->finalLocation().end, KDevelop::SimpleCursor(8, 16)); problem = problem_list[initial_size + 5]; QCOMPARE(problem->description(), QString::fromUtf8("Non-ascii TODO 例えば")); QCOMPARE(problem->source(), KDevelop::ProblemData::ToDo); QCOMPARE(problem->finalLocation().start, KDevelop::SimpleCursor(10, 3)); QCOMPARE(problem->finalLocation().end, KDevelop::SimpleCursor(10, 27)); } void TestParser::testComments6() { QByteArray module("//TranslationUnitComment\n/**\n * foo\n **/\nint i;\n"); TranslationUnitAST* ast = parse(module); const ListNode* it = ast->declarations; QVERIFY(it); it = it->next; QVERIFY(it); QCOMPARE(CommentFormatter().formatComment(it->element->comments, lastSession), QByteArray("foo")); } void TestParser::testComments7() { QByteArray module("//TranslationUnitComment\n\n//Foo\\\nbar\nint i;\n"); TranslationUnitAST* ast = parse(module); const ListNode* it = ast->declarations; QVERIFY(control.problems().isEmpty()); QVERIFY(it); it = it->next; QVERIFY(it); QCOMPARE(QString::fromUtf8(CommentFormatter().formatComment(it->element->comments, lastSession)), QString("Foo bar")); } void TestParser::testEscapedNewline_data() { QTest::addColumn("module"); QTest::newRow("simple") << QByteArray("\\\nint i;\n"); QTest::newRow("after-decl") << QByteArray("int a;\\\nint b;\n"); QTest::newRow("after-decl-white") << QByteArray("int a; \\\nint b;\n"); QTest::newRow("member-initializer") << QByteArray("struct s {\n int a, b;\n s():\\\n a(0),\\\n b(0)\n {}\n};\n"); } void TestParser::testEscapedNewline() { QFETCH(QByteArray, module); TranslationUnitAST* ast = parse(module); Q_UNUSED(ast); QVERIFY(control.problems().isEmpty()); } void TestParser::testPreprocessor() { QCOMPARE(preprocess("#define TEST (1L<<10)\nTEST").trimmed(), QString("(1L<<10)")); QCOMPARE(preprocess("#define SELF OTHER\n#define OTHER SELF\nSELF").trimmed(), QString("SELF")); QCOMPARE(preprocess("#define TEST //Comment\nTEST 1").trimmed(), QString("1")); //Comments are not included in macros QCOMPARE(preprocess("#define TEST /*Comment\n*/\nTEST 1").trimmed(), QString("1")); //Comments are not included in macros QCOMPARE(preprocess("#define TEST_URL \"http://foobar.com\"\nTEST_URL").trimmed(), QString("\"http://foobar.com\"")); QCOMPARE(preprocess("#define TEST_STR \"//\\\"//\"\nTEST_STR").trimmed(), QString("\"//\\\"//\"")); QCOMPARE(preprocess("#if ~1\n#define NUMBER 10\n#else\n#define NUMBER 20\n#endif\nNUMBER").trimmed(), QString("10")); QCOMPARE(preprocess("#define MACRO(a, b) ab\nMACRO\n(aa, bb)").trimmed(), QString("ab")); QCOMPARE(preprocess("#define MACRO(a, b) ab\nMACRO(aa,\n bb)").trimmed(), QString("ab")); QCOMPARE(preprocess("#if 0x1\n #define NUMBER 10\n#else\n#define NUMBER 20\n#endif\nNUMBER\n").trimmed(), QString("10")); QCOMPARE(preprocess("#define AA BB\n#define BB CC\nAA\n").trimmed(), QString("CC")); QCOMPARE(preprocess("#define bla(x) bla(x)\n#define foo(x) foo##x\n#define choose(x) x(a) x(b)\nchoose(bla)\n").replace(QRegExp("[\n\t ]+"), ""), QString("bla(a)bla(b)")); QCOMPARE(preprocess("#define bla(x) bla(x)\n#define foo(x) foo##x\n#define choose(x) x(a) x(b)\nchoose(foo)\n").replace(QRegExp("[\n\t ]+"), " ").trimmed(), QString("fooa foob")); QCOMPARE(preprocess("#define XX YY\n#define YY(x) z x\nXX(x)\n").replace(QRegExp("[\n\t ]+"), " ").trimmed(), QString("z x")); QCOMPARE(preprocess("#define PP(x) Q##x\n#define QQ 12\nPP(Q)\n").trimmed(), QString("12")); QCOMPARE(preprocess("#define MM(x) NN\n#define OO(NN) MM(NN)\nOO(2)\n").trimmed(), QString("NN")); QCOMPARE(preprocess("#define OOO(x) x x x\n#define OOOO(x) O##x(2)\nOOOO(OO)\n").replace(QRegExp("[\n\t ]+"), " ").trimmed(), QString("2 2 2")); QCOMPARE(preprocess("#define OOO(x) x x x\n#define OOOO(x) O##x(2)\nOOOO(OOO)\n").replace(QRegExp("[\n\t ]+"), ""), QString("OOOO(2)")); QCOMPARE(preprocess("#if 1\n #define N 10\n#else\n#define N 20\n#endif\nN\n").trimmed(), QString("10")); QCOMPARE(preprocess("#if 1u\n #define N 10\n#else\n#define N 20\n#endif\nN\n").trimmed(), QString("10")); QCOMPARE(preprocess("#if 1l\n #define N 10\n#else\n#define N 20\n#endif\nN\n").trimmed(), QString("10")); QCOMPARE(preprocess("#if 1lu\n #define N 10\n#else\n#define N 20\n#endif\nN\n").trimmed(), QString("10")); QCOMPARE(preprocess("#if 1ul\n #define N 10\n#else\n#define N 20\n#endif\nN\n").trimmed(), QString("10")); QCOMPARE(preprocess("#if 1ll\n #define N 10\n#else\n#define N 20\n#endif\nN\n").trimmed(), QString("10")); QCOMPARE(preprocess("#if 1llu\n #define N 10\n#else\n#define N 20\n#endif\nN\n").trimmed(), QString("10")); QCOMPARE(preprocess("#if ~0ull == 0u + ~0u\n 10\n #endif\n").trimmed(), QString("10")); QCOMPARE(preprocess("#if 1Ul\n 10\n #endif\n").trimmed(), QString("10")); QCOMPARE(preprocess("#if 1Lu\n 10\n #endif\n").trimmed(), QString("10")); QCOMPARE(preprocess("#if 1LlU\n 10\n #endif\n").trimmed(), QString("10")); QCOMPARE(preprocess("#if 1_u\n #endif\n").trimmed(), QString("*ERROR*")); QCOMPARE(preprocess("#if 1u_\n #endif\n").trimmed(), QString("*ERROR*")); QCOMPARE(preprocess("#if 1uu\n #endif\n").trimmed(), QString("*ERROR*")); QCOMPARE(preprocess("#if 1lll\n #endif\n").trimmed(), QString("*ERROR*")); QCOMPARE(preprocess("#if 1lul\n #endif\n").trimmed(), QString("*ERROR*")); QCOMPARE(preprocess("#if (2147483647L + 10L) > 0\n 10\n #endif\n").trimmed(), QString("10")); QCOMPARE(preprocess("#ifdef\n"), QString("*ERROR*")); // From windows headers: // #define __$drv_unit_user_code FOO // ... // #define __allowed(p) __$allowed_##p // #define __inout __allowed(on_parameter) // Make sure '__inout' resolves to '__$allowed_on_parameter' and not to '__$drv_unit_user_code FOO $allowed_on_parameter' // because '$' is not recognized as valid char of a macro name macro char. // '$' is accepted by some compilers, see http://stackoverflow.com/a/369524 QCOMPARE(preprocess("#define __$a a\n#define __$b b\n#define FOO __$a\nFOO").trimmed(), QString("a")); QEXPECT_FAIL("", "Backslash incorrectly handled", Continue); QCOMPARE(preprocess("bla \\\n#define foobar oc\nfoobar\n").replace(QRegExp("[\n\t ]+"), " ").trimmed(), QString("bla #define foobar oc foobar")); QEXPECT_FAIL("", "Backslash incorrectly handled", Continue); QCOMPARE(preprocess("#define foo(x) foo##x\n#define bla fo\\\no(2)\n").trimmed(), QString("foo2")); QEXPECT_FAIL("", "Empty expansions incorrectly handled", Continue); QCOMPARE(preprocess("#define foo(x) foo##x\n#define _A\n\n#define CALL(X, Y) X _A (Y)\nCALL(foo, 13)\n").replace(QRegExp("[\n\t ]+"), ""), QString("foo(13)")); QEXPECT_FAIL("", "Variadic arguments cannot be left empty (GCC extension)", Continue); QCOMPARE(preprocess("#define NC(x,y...) x y\nNC(kde,ve)\nNC(lop)").replace(QRegExp("[\n\t ]+"), ""), QString("kdevelop")); QEXPECT_FAIL("", "No problems reported for missmatching macro-parameter-lists", Continue); QCOMPARE(preprocess("#define bla(x,y)\nbla(1,2,3)\n"), QString("*ERROR*")); QEXPECT_FAIL("", "No problems reported for missmatching macro-parameter-lists", Continue); QCOMPARE(preprocess("#define PUT_BETWEEN(x,y) x y x\nPUT_BETWEEN(pair, c)\n"), QString("*ERROR*")); QEXPECT_FAIL("", "No problems reported for macro-redefinition", Continue); QCOMPARE(preprocess("#define A B\n#define A C\n"), QString("*ERROR*")); QCOMPARE(preprocess("#define NEXT_\\\nLINE SUCCESS\nNEXT_LINE").replace(QRegExp("[\n\t ]+"), ""), QString("SUCCESS")); QCOMPARE(preprocess("#define NEXT_LINE\\\n(x,y) fun(x,y)\nNEXT_LINE(x,y)").replace(QRegExp("[\n\t ]+"), ""), QString("fun(x,y)")); } void TestParser::testPreprocessorStringify() { QCOMPARE(preprocess("#define STR(s) #s\n#define MACRO string\nSTR(MACRO)").trimmed(), QString("\"MACRO\"")); QCOMPARE(preprocess("#define STR(s) #s\n#define XSTR(s) STR(s)\n#define MACRO string\nXSTR(MACRO)").simplified(), QString("\"string\"")); QEXPECT_FAIL("", "# incorrectly handled", Continue); QCOMPARE(preprocess("#define CONCAT(x,y) x ## y\n#define test #CONCAT(1,2)\ntest\n").trimmed(), QString("#12")); } void TestParser::testStringConcatenation() { QCOMPARE(preprocess("Hello##You"), QString("HelloYou")); QCOMPARE(preprocess("#define CONCAT(Var1, Var2) Var1##Var2\nCONCAT(var1, )").trimmed(), QString("var1")); QCOMPARE(preprocess("#define CONCAT(Var1, Var2) Var1 ## Var2\nCONCAT(, var2)").trimmed(), QString("var2")); QCOMPARE(preprocess("#define CONCAT(Var1, Var2) Var1##Var2 Var2##Var1\nCONCAT( Hello , You )").simplified(), QString("\nHelloYou YouHello").simplified()); QCOMPARE(preprocess("#define GLUE(a, b) a ## b\n#define HIGHLOW hello\nGLUE(HIGH, LOW)").trimmed(), QString("hello")); QCOMPARE(preprocess("#define GLUE(a, b) a ## b\n#define HIGHLOW hello\n#define LOW LOW world\nGLUE(HIGH, LOW)").trimmed(), QString("hello")); QCOMPARE(preprocess("#define GLUE(a, b) a ##b\n#define XGLUE(a, b) GLUE(a, b)\n#define HIGHLOW hello\n#define LOW LOW world\nXGLUE(HIGH, LOW)").simplified(), QString("hello world")); // TODO: simplified -> trimmed QCOMPARE(preprocess("#define GLUE(a, b, c) k ## l ## m\nGLUE(a, b, c)").trimmed(), QString("klm")); QCOMPARE(preprocess("#define foo(x) foo##x\nint foo\n(13)\n").replace(QRegExp("[\n\t ]+"), " ").trimmed(), QString("int foo13")); } void TestParser::testEmptyInclude() { // testcase for https://bugs.kde.org/show_bug.cgi?id=258972 rpp::Preprocessor preprocessor; rpp::pp pp(&preprocessor); pp.processFile("anonymous", QByteArray("#include\n\nint main(){\n ;\n}\n")); QCOMPARE(pp.problems().size(), 1); qDebug() << pp.problems().first()->description(); QCOMPARE(pp.problems().first()->finalLocation().start, KDevelop::SimpleCursor(0, 8)); QCOMPARE(pp.problems().first()->finalLocation().end, KDevelop::SimpleCursor(0, 8)); } void TestParser::testCondition() { QByteArray method("bool i = (small < big || big > small);"); TranslationUnitAST* ast = parse(method); dumper.dump(ast, lastSession->token_stream); ///@todo make this work, it should yield something like TranslationUnit -> SimpleDeclaration -> InitDeclarator -> BinaryExpression } void TestParser::testNonTemplateDeclaration() { /*{ QByteArray templateMethod("template class a {}; int main() { const int b = 1; const int c = 2; a d; }"); TranslationUnitAST* ast = parse(templateMethod); dumper.dump(ast, lastSession->token_stream); }*/ //int a, b, c, d; bool e; QByteArray declaration("void TestParser::expression() { if (a < b || c > d) {} }"); TranslationUnitAST* ast = parse(declaration); dumper.dump(ast, lastSession->token_stream); } void TestParser::testInitListTrailingComma() { //see bug https://bugs.kde.org/show_bug.cgi?id=233328 QByteArray code("const int foo [] = {1,};"); TranslationUnitAST* ast = parse(code); dump(ast); QVERIFY(control.problems().isEmpty()); QCOMPARE(ast->declarations->count(), 1); SimpleDeclarationAST* simpleDecl = reinterpret_cast(ast->declarations->at(0)->element); QVERIFY(simpleDecl); QCOMPARE(simpleDecl->init_declarators->count(), 1); } void TestParser::testAsmVolatile() { //see bug https://bugs.kde.org/show_bug.cgi?id=238772 QByteArray code("__asm__ __volatile__ (\"cld; rep; \" \"stosq\" : \"=c\"" " (__d0), \"=D\" (__d1) : \"a\" (0), \"0\" (sizeof (fd_set) / sizeof (__fd_mask))," " \"1\"" " (&((&rfds)->__fds_bits)[0]) : \"memory\");"); TranslationUnitAST* ast = parse(code); dumper.dump(ast, lastSession->token_stream); QCOMPARE(ast->declarations->count(), 1); AsmDefinitionAST* asmDecl = reinterpret_cast(ast->declarations->at(0)->element); QVERIFY(asmDecl); QCOMPARE(asmDecl->cv->count(), 1); QVERIFY(lastSession->token_stream->kind(asmDecl->cv->at(0)->element) == Token_volatile); } void TestParser::testIncrIdentifier() { //see bug https://bugs.kde.org/show_bug.cgi?id=238772 QByteArray code("void TestParser::incr();"); TranslationUnitAST* ast = parse(code); dumper.dump(ast, lastSession->token_stream); QCOMPARE(ast->declarations->count(), 1); FunctionDefinitionAST* funcDecl = reinterpret_cast(ast->declarations->at(0)->element); QVERIFY(funcDecl); } void TestParser::testParseFile() { QFile file(TEST_FILE); QVERIFY(file.open(QFile::ReadOnly)); QByteArray contents = file.readAll(); file.close(); TranslationUnitAST* ast =parse(contents); QVERIFY(ast != 0); QVERIFY(ast->declarations != 0); } void TestParser::testQProperty_data() { QTest::addColumn("code"); QTest::addColumn("hasMember"); QTest::addColumn("hasGetterMethod"); QTest::addColumn("hasSetterMethod"); QTest::addColumn("hasResetterMethod"); QTest::addColumn("hasNotifierMethod"); QTest::addColumn("hasDesignableMethod"); QTest::addColumn("hasScriptableMethod"); QTest::newRow("member") << QByteArray("class Class{\n__qt_property__(bool myProp MEMBER m_prop)\n};") << true << false << false << false << false << false << false; QTest::newRow("read") << QByteArray("class Class{\n__qt_property__(bool myProp READ prop)\n};") << false << true << false << false << false << false << false; QTest::newRow("write") << QByteArray("class Class{\n__qt_property__(bool myProp READ prop WRITE prop)\n};") << false << true << true << false << false << false << false; QTest::newRow("reset") << QByteArray("class Class{\n__qt_property__(bool myProp READ prop RESET prop)\n};") << false << true << false << true << false << false << false; QTest::newRow("notify") << QByteArray("class Class{\n__qt_property__(bool myProp READ prop NOTIFY prop)\n};") << false << true << false << false << true << false << false; QTest::newRow("designable_method") << QByteArray("class Class{\n__qt_property__(bool myProp READ prop DESIGNABLE prop)\n};") << false << true << false << false << false << true << false; QTest::newRow("scriptable_method") << QByteArray("class Class{\n__qt_property__(bool myProp READ prop SCRIPTABLE prop)\n};") << false << true << false << false << false << false << true; QTest::newRow("revision") << QByteArray("class Class{\n__qt_property__(bool myProp READ prop REVISION 1)\n};") << false << true << false << false << false << false << false; QTest::newRow("designable_bool") << QByteArray("class Class{\n__qt_property__(bool myProp READ prop DESIGNABLE false)\n};") << false << true << false << false << false << false << false; QTest::newRow("scriptable_bool") << QByteArray("class Class{\n__qt_property__(bool myProp READ prop SCRIPTABLE false)\n};") << false << true << false << false << false << false << false; QTest::newRow("stored") << QByteArray("class Class{\n__qt_property__(bool myProp READ prop STORED false)\n};") << false << true << false << false << false << false << false; QTest::newRow("user") << QByteArray("class Class{\n__qt_property__(bool myProp READ prop USER true)\n};") << false << true << false << false << false << false << false; QTest::newRow("constant") << QByteArray("class Class{\n__qt_property__(bool myProp READ prop CONSTANT)\n};") << false << true << false << false << false << false << false; QTest::newRow("final") << QByteArray("class Class{\n__qt_property__(bool myProp READ prop FINAL)\n};") << false << true << false << false << false << false << false; } void TestParser::testQProperty() { QFETCH(QByteArray, code); QFETCH(bool, hasMember); QFETCH(bool, hasGetterMethod); QFETCH(bool, hasSetterMethod); QFETCH(bool, hasResetterMethod); QFETCH(bool, hasNotifierMethod); QFETCH(bool, hasDesignableMethod); QFETCH(bool, hasScriptableMethod); TranslationUnitAST* ast = parse(code); QVERIFY(ast != 0); QVERIFY(hasKind(ast, AST::Kind_QPropertyDeclaration)); QPropertyDeclarationAST* propAst = static_cast (getAST(ast, AST::Kind_QPropertyDeclaration)); QVERIFY((propAst->member != 0) == hasMember); QVERIFY((propAst->getter != 0) == hasGetterMethod); QVERIFY((propAst->setter != 0) == hasSetterMethod); QVERIFY((propAst->resetter != 0) == hasResetterMethod); QVERIFY((propAst->notifier != 0) == hasNotifierMethod); QVERIFY((propAst->designableMethod != 0) == hasDesignableMethod); QVERIFY((propAst->scriptableMethod != 0) == hasScriptableMethod); } void TestParser::testDesignatedInitializers() { TranslationUnitAST* ast; InitializerListAST* listAst; //DumpTree dumper; ast = parse("\nA a = {" "\n .b = {" "\n .a = 10," "\n }," "\n .x = 10," "\n .y = SOME_CONST," "\n .z = 10," "\n};"); QVERIFY(ast != 0); QVERIFY(control.problems().isEmpty()); QCOMPARE(ast->declarations->count(), 1); QVERIFY(hasKind(ast, AST::Kind_InitializerList)); listAst = static_cast(getAST(ast, AST::Kind_InitializerList)); QVERIFY(hasKind(listAst, AST::Kind_ClassMemberAccess)); //dumper.dump(ast, lastSession->token_stream); ast = parse("\nint ia[10][5] = {" "\n [1] = 10," "\n [2][B] = SOME_CONST," "\n};"); QVERIFY(ast != 0); QVERIFY(control.problems().isEmpty()); QCOMPARE(ast->declarations->count(), 1); QVERIFY(hasKind(ast, AST::Kind_InitializerList)); listAst = static_cast(getAST(ast, AST::Kind_InitializerList)); QVERIFY(hasKind(listAst, AST::Kind_SubscriptExpression)); //dumper.dump(ast, lastSession->token_stream); } void TestParser::testCommentAfterFunctionCall() { //this is ambigous TranslationUnitAST* ast = parse("void TestParser::setView() {\n" " setView(m_view); //\n" "}\n"); QVERIFY(ast != 0); DumpTree dumper; dumper.dump(ast, lastSession->token_stream); QCOMPARE(ast->declarations->count(), 1); QVERIFY(hasKind(ast, AST::Kind_FunctionDefinition)); FunctionDefinitionAST* funcAst = static_cast(getAST(ast, AST::Kind_FunctionDefinition)); QVERIFY(hasKind(funcAst, AST::Kind_ExpressionOrDeclarationStatement)); QVERIFY(hasKind(funcAst, AST::Kind_FunctionCall)); QVERIFY(hasKind(funcAst, AST::Kind_InitDeclarator)); ExpressionOrDeclarationStatementAST* ambAst = static_cast(getAST(funcAst, AST::Kind_ExpressionOrDeclarationStatement)); QVERIFY(ambAst); } void TestParser::testPtrToMemberAst() { TranslationUnitAST* ast = parse("\nstruct AA {" "\n int j;" "\n};" "\nstruct BB{" "\n int AA::* pj;" "\n};" "\nvoid TestParser::f(){" "\n int AA::* BB::* ppj=&BB::pj;" "\n}" ); QVERIFY(ast!=0); QCOMPARE(ast->declarations->count(), 3); QVERIFY(hasKind(ast,AST::Kind_PtrToMember)); FunctionDefinitionAST* f_ast=static_cast(getAST(ast,AST::Kind_FunctionDefinition)); QVERIFY(hasKind(f_ast,AST::Kind_PtrToMember)); DeclaratorAST* d_ast=static_cast(getAST(f_ast->function_body,AST::Kind_Declarator)); QCOMPARE(d_ast-> ptr_ops->count(),2); } void TestParser::testSwitchStatement() { int problemCount = control.problems().count(); QByteArray switchTest("int main() { switch(0); }"); parse(switchTest); QCOMPARE(control.problems().count(), problemCount); QByteArray switchTest2("int main() { switch (0) case 0: if (true) ; else return 1; }"); parse(switchTest2); QCOMPARE(control.problems().count(), problemCount); QByteArray switchTest3("int main() { switch (0) { case 0: if (true) ; else return 1; } }"); parse(switchTest3); QCOMPARE(control.problems().count(), problemCount); QByteArray switchTest4("int main() { switch (0) while(true) return false; }"); parse(switchTest4); QCOMPARE(control.problems().count(), problemCount); QByteArray switchTest5("int main() { switch (0) { case 0: return 0; } }"); parse(switchTest5); QCOMPARE(control.problems().count(), problemCount); } void TestParser::testNamedOperators_data() { QTest::addColumn("code"); QTest::newRow("xor") << "int i = 1 xor 2;"; QTest::newRow("bitand") << "int i = 1 bitand 2;"; QTest::newRow("bitor") << "int i = 1 bitor 2;"; QTest::newRow("or") << "int i = 1 or 2;"; QTest::newRow("and") << "int i = 1 and 2;"; QTest::newRow("compl") << "int i = compl 2;"; QTest::newRow("not") << "int i = not 2;"; QTest::newRow("xor_eq") << "int i = 1; i xor_eq 2;"; QTest::newRow("and_eq") << "int i = 1; i and_eq 2;"; QTest::newRow("or_eq") << "int i = 1; i or_eq 2;"; QTest::newRow("not_eq") << "int i = 1 not_eq 2;"; } void TestParser::testNamedOperators() { QFETCH(QString, code); code = "int main() { " + code + " }\n"; parse(code.toLocal8Bit()); QVERIFY(control.problems().isEmpty()); } void TestParser::testOperators_data() { QTest::addColumn("code"); QTest::newRow("xor") << "int i = 1 ^ 2;"; QTest::newRow("bitand") << "int i = 1 & 2;"; QTest::newRow("bitor") << "int i = 1 | 2;"; QTest::newRow("or") << "int i = 1 || 2;"; QTest::newRow("and") << "int i = 1 && 2;"; QTest::newRow("compl") << "int i = ~2;"; QTest::newRow("not") << "int i = !2;"; QTest::newRow("xor_eq") << "int i = 1; i ^= 2;"; QTest::newRow("and_eq") << "int i = 1; i &= 2;"; QTest::newRow("or_eq") << "int i = 1; i |= 2;"; QTest::newRow("not_eq") << "int i = 1 != 2;"; } void TestParser::testOperators() { QFETCH(QString, code); code = "int main() { " + code + " }\n"; parse(code.toLocal8Bit()); QVERIFY(control.problems().isEmpty()); } void TestParser::testTypeID_data() { QTest::addColumn("code"); QTest::newRow("typeid-type") << "typeid(int);"; QTest::newRow("typeid-value") << "typeid(5);"; QTest::newRow("typeid-var") << "float f; typeid(f);"; QTest::newRow("type_info-name") << "typeid(1).name();"; QTest::newRow("type_info-name-stream") << "cout << typeid(1).name() << endl;"; QTest::newRow("type_info-op") << "bool b = typeid(1) == typeid(int);"; } void TestParser::testTypeID() { QFETCH(QString, code); code = "int main() { " + code + " }\n"; TranslationUnitAST* ast = parse(code.toLocal8Bit()); dumper.dump(ast, lastSession->token_stream); QVERIFY(control.problems().isEmpty()); } void TestParser::testRegister() { // see also: http://bugsfiles.kde.org/attachment.cgi?id=61647 QString code = "void foo() { register int i; int register j; }\n"; TranslationUnitAST* ast = parse(code.toLocal8Bit()); dumper.dump(ast, lastSession->token_stream); QVERIFY(control.problems().isEmpty()); } void TestParser::inlineTemplate() { QByteArray code = "template inline void a() {}\n"; TranslationUnitAST* ast = parse(code); dumper.dump(ast, lastSession->token_stream); QVERIFY(control.problems().isEmpty()); } void TestParser::testMultiByteCStrings() { // 0 1 2 3 4 // 01234567890123456789012345678 90 1234567890123456789 QByteArray code = "int main() { const char* a = \"ä\"; a = 0; }\n"; TranslationUnitAST* ast = parse(code); dumper.dump(ast, lastSession->token_stream); QVERIFY(control.problems().isEmpty()); AST* str = getAST(ast, AST::Kind_StringLiteral); QVERIFY(str); QCOMPARE(lastSession->stringForNode(str, true), QString::fromUtf8("\"ä\"")); Token token = lastSession->token_stream->token(str->start_token); QEXPECT_FAIL("", "the wide ä-char takes two indizes in a QByteArray, which breaks our lexer", Abort); QCOMPARE(token.size, 3u); QCOMPARE(lastSession->token_stream->symbolLength(token), 3u); Token endToken = lastSession->token_stream->token(str->end_token); rpp::Anchor pos = lastSession->positionAt(endToken.position); // should end just before the semicolon QVERIFY(pos == KDevelop::CursorInRevision(0, 32)); } void TestParser::testMultiByteComments() { // 0 1 2 3 4 // 01234567890123456789012345678901234567890123456789 QByteArray code = "int a = 1;/* ä */int b = 0;"; TranslationUnitAST* ast = parse(code); dumper.dump(ast, lastSession->token_stream); QVERIFY(control.problems().isEmpty()); AST* b = lastSession->topAstNode()->declarations->toBack()->element; Token token = lastSession->token_stream->token(b->start_token); rpp::Anchor pos = lastSession->positionAt(token.position); // should start just after the comment QEXPECT_FAIL("", "the wide ä-char takes two indizes in a QByteArray, which breaks our lexer", Abort); QVERIFY(pos == KDevelop::CursorInRevision(0, 17)); } void TestParser::testTernaryEmptyExpression() { // see also: https://bugs.kde.org/show_bug.cgi?id=292357 // mostly GCC compatibility // 0 1 2 3 4 // 01234567890123456789012345678901234567890123456789 QByteArray code = "int a = false ?: 0;"; TranslationUnitAST* ast = parse(code); dumper.dump(ast, lastSession->token_stream); QCOMPARE(control.problems().count(), 1); QCOMPARE(control.problems().first()->severity(), KDevelop::ProblemData::Warning); QVERIFY(ast); } TranslationUnitAST* TestParser::parse(const QByteArray& unit) { control = Control(); // Clear the problems Parser parser(&control); lastSession = new ParseSession(); rpp::Preprocessor preprocessor; rpp::pp pp(&preprocessor); lastSession->setContentsAndGenerateLocationTable(pp.processFile("anonymous", unit)); return parser.parse(lastSession); } void TestParser::dump(AST* node) { dumper.dump(node, lastSession->token_stream); if (!control.problems().isEmpty()) { foreach(const KDevelop::ProblemPointer&p, control.problems()) { qDebug() << p->description() << p->explanation() << p->finalLocation().textRange(); } } } struct HasKindVisitor : protected DefaultVisitor { AST::NODE_KIND kind; AST* ast; int num; HasKindVisitor(AST::NODE_KIND kind, int num = 0) : kind(kind), ast(0), num(num) { } bool hasKind() const { return ast != 0; } void visit(AST* node) { if (!ast && node) { if (node->kind == kind && --num < 0) { ast = node; } else { DefaultVisitor::visit(node); } } } }; bool TestParser::hasKind(AST* ast, AST::NODE_KIND kind) { HasKindVisitor visitor(kind); visitor.visit(ast); return visitor.hasKind(); } AST* TestParser::getAST(AST* ast, AST::NODE_KIND kind, int num) { HasKindVisitor visitor(kind, num); visitor.visit(ast); return visitor.ast; } #include "moc_test_parser.cpp" QTEST_MAIN(TestParser)