aaron.ballman created this revision. aaron.ballman added reviewers: rsmith, riccibruno, steveire, dblaikie. Herald added a subscriber: mgorny.
This is a work in progress patch that adds the ability to specify an AST dump format on the command line. By default, we continue to dump the AST with its usual tree view, but users can now optionally pass `-ast-dump=json` to dump to a machine-readable JSON format that makes it easier for third parties to consume the Clang AST in some fashion. The patch can currently handle dumping a fair amount of declaration information, some statements, and very few expressions. I got it to the point where it was showing useful output in roughly the correct format, but I wanted to get community feedback for continuing the implementation. Once the current approach gains consensus, my plan is to commit the WIP and then do subsequent commits with post-commit review to finish the implementation (unless the changes are somehow interesting enough to warrant pre-commit review, of course). The hybrid approach of using some LLVM JSON functionality and some streaming functionality is purposeful for performance reasons (collecting the entire AST into memory in a second form means ~2x the memory usage for the AST, which can be prohibitive for large compilation units). Testing this functionality with FileCheck is quite verbose, so if someone has suggestions for a better way to test the JSON output, I'd be happy to consider it. https://reviews.llvm.org/D60910 Files: include/clang/AST/ASTDumperUtils.h include/clang/AST/DeclBase.h include/clang/AST/JSONNodeDumper.h include/clang/Driver/CC1Options.td include/clang/Frontend/ASTConsumers.h include/clang/Frontend/FrontendOptions.h lib/AST/ASTDumper.cpp lib/AST/CMakeLists.txt lib/AST/JSONNodeDumper.cpp lib/Frontend/ASTConsumers.cpp lib/Frontend/CompilerInvocation.cpp lib/Frontend/FrontendActions.cpp test/AST/ast-dump-enum-json.cpp test/AST/ast-dump-if-json.cpp tools/clang-check/ClangCheck.cpp tools/clang-import-test/clang-import-test.cpp
Index: tools/clang-import-test/clang-import-test.cpp =================================================================== --- tools/clang-import-test/clang-import-test.cpp +++ tools/clang-import-test/clang-import-test.cpp @@ -316,8 +316,9 @@ auto &CG = *static_cast<CodeGenerator *>(ASTConsumers.back().get()); if (ShouldDumpAST) - ASTConsumers.push_back(CreateASTDumper(nullptr /*Dump to stdout.*/, - "", true, false, false)); + ASTConsumers.push_back( + CreateASTDumper(nullptr /*Dump to stdout.*/, "", true, false, false, + clang::FrontendOptions::AOF_Default)); CI.getDiagnosticClient().BeginSourceFile( CI.getCompilerInstance().getLangOpts(), Index: tools/clang-check/ClangCheck.cpp =================================================================== --- tools/clang-check/ClangCheck.cpp +++ tools/clang-check/ClangCheck.cpp @@ -134,11 +134,11 @@ if (ASTList) return clang::CreateASTDeclNodeLister(); if (ASTDump) - return clang::CreateASTDumper(nullptr /*Dump to stdout.*/, - ASTDumpFilter, + return clang::CreateASTDumper(nullptr /*Dump to stdout.*/, ASTDumpFilter, /*DumpDecls=*/true, /*Deserialize=*/false, - /*DumpLookups=*/false); + /*DumpLookups=*/false, + clang::FrontendOptions::AOF_Default); if (ASTPrint) return clang::CreateASTPrinter(nullptr, ASTDumpFilter); return llvm::make_unique<clang::ASTConsumer>(); Index: test/AST/ast-dump-if-json.cpp =================================================================== --- test/AST/ast-dump-if-json.cpp +++ test/AST/ast-dump-if-json.cpp @@ -0,0 +1,360 @@ +// RUN: %clang_cc1 -triple x86_64-pc-linux -std=c++17 -ast-dump=json %s | FileCheck %s + +void func(int val) { + if (val) + ; + +// CHECK: "kind": "IfStmt", +// CHECK-NEXT: "range": {"begin":{"col":3,"file":"{{.*}}","line":4},"end":{"col":5,"file":"{{.*}}","line":5}}, +// CHECK-NEXT: "inner": [ +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "ImplicitCastExpr", +// CHECK-NEXT: "range": {"begin":{"col":7,"file":"{{.*}}","line":4},"end":{"col":7,"file":"{{.*}}","line":4}}, +// CHECK-NEXT: "type": {"qualType":"bool"}, +// CHECK-NEXT: "valueCategory": "rvalue", +// CHECK-NEXT: "inner": [ +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "ImplicitCastExpr", +// CHECK-NEXT: "range": {"begin":{"col":7,"file":"{{.*}}","line":4},"end":{"col":7,"file":"{{.*}}","line":4}}, +// CHECK-NEXT: "type": {"qualType":"int"}, +// CHECK-NEXT: "valueCategory": "rvalue", +// CHECK-NEXT: "inner": [ +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "DeclRefExpr", +// CHECK-NEXT: "range": {"begin":{"col":7,"file":"{{.*}}","line":4},"end":{"col":7,"file":"{{.*}}","line":4}}, +// CHECK-NEXT: "type": {"qualType":"int"}, +// CHECK-NEXT: "valueCategory": "lvalue" +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "NullStmt", +// CHECK-NEXT: "range": {"begin":{"col":5,"file":"{{.*}}","line":5},"end":{"col":5,"file":"{{.*}}","line":5}} +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: }, + + if (val) + ; + else + ; + +// CHECK: "kind": "IfStmt", +// CHECK-NEXT: "range": {"begin":{"col":3,"file":"{{.*}}","line":43},"end":{"col":5,"file":"{{.*}}","line":46}}, +// CHECK-NEXT: "hasElse": true, +// CHECK-NEXT: "inner": [ +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "ImplicitCastExpr", +// CHECK-NEXT: "range": {"begin":{"col":7,"file":"{{.*}}","line":43},"end":{"col":7,"file":"{{.*}}","line":43}}, +// CHECK-NEXT: "type": {"qualType":"bool"}, +// CHECK-NEXT: "valueCategory": "rvalue", +// CHECK-NEXT: "inner": [ +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "ImplicitCastExpr", +// CHECK-NEXT: "range": {"begin":{"col":7,"file":"{{.*}}","line":43},"end":{"col":7,"file":"{{.*}}","line":43}}, +// CHECK-NEXT: "type": {"qualType":"int"}, +// CHECK-NEXT: "valueCategory": "rvalue", +// CHECK-NEXT: "inner": [ +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "DeclRefExpr", +// CHECK-NEXT: "range": {"begin":{"col":7,"file":"{{.*}}","line":43},"end":{"col":7,"file":"{{.*}}","line":43}}, +// CHECK-NEXT: "type": {"qualType":"int"}, +// CHECK-NEXT: "valueCategory": "lvalue" +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "NullStmt", +// CHECK-NEXT: "range": {"begin":{"col":5,"file":"{{.*}}","line":44},"end":{"col":5,"file":"{{.*}}","line":44}} +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "NullStmt", +// CHECK-NEXT: "range": {"begin":{"col":5,"file":"{{.*}}","line":46},"end":{"col":5,"file":"{{.*}}","line":46}} +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: }, + + if (val) + ; + else if (val) + ; + else + ; + +// CHECK: "kind": "IfStmt", +// CHECK-NEXT: "range": {"begin":{"col":3,"file":"{{.*}}","line":90},"end":{"col":5,"file":"{{.*}}","line":95}}, +// CHECK-NEXT: "hasElse": true, +// CHECK-NEXT: "inner": [ +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "ImplicitCastExpr", +// CHECK-NEXT: "range": {"begin":{"col":7,"file":"{{.*}}","line":90},"end":{"col":7,"file":"{{.*}}","line":90}}, +// CHECK-NEXT: "type": {"qualType":"bool"}, +// CHECK-NEXT: "valueCategory": "rvalue", +// CHECK-NEXT: "inner": [ +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "ImplicitCastExpr", +// CHECK-NEXT: "range": {"begin":{"col":7,"file":"{{.*}}","line":90},"end":{"col":7,"file":"{{.*}}","line":90}}, +// CHECK-NEXT: "type": {"qualType":"int"}, +// CHECK-NEXT: "valueCategory": "rvalue", +// CHECK-NEXT: "inner": [ +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "DeclRefExpr", +// CHECK-NEXT: "range": {"begin":{"col":7,"file":"{{.*}}","line":90},"end":{"col":7,"file":"{{.*}}","line":90}}, +// CHECK-NEXT: "type": {"qualType":"int"}, +// CHECK-NEXT: "valueCategory": "lvalue" +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "NullStmt", +// CHECK-NEXT: "range": {"begin":{"col":5,"file":"{{.*}}","line":91},"end":{"col":5,"file":"{{.*}}","line":91}} +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "IfStmt", +// CHECK-NEXT: "range": {"begin":{"col":8,"file":"{{.*}}","line":92},"end":{"col":5,"file":"{{.*}}","line":95}}, +// CHECK-NEXT: "hasElse": true, +// CHECK-NEXT: "inner": [ +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "ImplicitCastExpr", +// CHECK-NEXT: "range": {"begin":{"col":12,"file":"{{.*}}","line":92},"end":{"col":12,"file":"{{.*}}","line":92}}, +// CHECK-NEXT: "type": {"qualType":"bool"}, +// CHECK-NEXT: "valueCategory": "rvalue", +// CHECK-NEXT: "inner": [ +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "ImplicitCastExpr", +// CHECK-NEXT: "range": {"begin":{"col":12,"file":"{{.*}}","line":92},"end":{"col":12,"file":"{{.*}}","line":92}}, +// CHECK-NEXT: "type": {"qualType":"int"}, +// CHECK-NEXT: "valueCategory": "rvalue", +// CHECK-NEXT: "inner": [ +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "DeclRefExpr", +// CHECK-NEXT: "range": {"begin":{"col":12,"file":"{{.*}}","line":92},"end":{"col":12,"file":"{{.*}}","line":92}}, +// CHECK-NEXT: "type": {"qualType":"int"}, +// CHECK-NEXT: "valueCategory": "lvalue" +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "NullStmt", +// CHECK-NEXT: "range": {"begin":{"col":5,"file":"{{.*}}","line":93},"end":{"col":5,"file":"{{.*}}","line":93}} +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "NullStmt", +// CHECK-NEXT: "range": {"begin":{"col":5,"file":"{{.*}}","line":95},"end":{"col":5,"file":"{{.*}}","line":95}} +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: }, + + if constexpr(10 == 10) + ; + +// CHECK: "kind": "IfStmt", +// CHECK-NEXT: "range": {"begin":{"col":3,"file":"{{.*}}","line":177},"end":{"col":5,"file":"{{.*}}","line":178}}, +// CHECK-NEXT: "isConstexpr": true, +// CHECK-NEXT: "inner": [ +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "ConstantExpr", +// CHECK-NEXT: "range": {"begin":{"col":16,"file":"{{.*}}","line":177},"end":{"col":22,"file":"{{.*}}","line":177}}, +// CHECK-NEXT: "type": {"qualType":"bool"}, +// CHECK-NEXT: "valueCategory": "rvalue", +// CHECK-NEXT: "inner": [ +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "BinaryOperator", +// CHECK-NEXT: "range": {"begin":{"col":16,"file":"{{.*}}","line":177},"end":{"col":22,"file":"{{.*}}","line":177}}, +// CHECK-NEXT: "type": {"qualType":"bool"}, +// CHECK-NEXT: "valueCategory": "rvalue", +// CHECK-NEXT: "opcode": "==", +// CHECK-NEXT: "inner": [ +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "IntegerLiteral", +// CHECK-NEXT: "range": {"begin":{"col":16,"file":"{{.*}}","line":177},"end":{"col":16,"file":"{{.*}}","line":177}}, +// CHECK-NEXT: "type": {"qualType":"int"}, +// CHECK-NEXT: "valueCategory": "rvalue", +// CHECK-NEXT: "value": "10" +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "IntegerLiteral", +// CHECK-NEXT: "range": {"begin":{"col":22,"file":"{{.*}}","line":177},"end":{"col":22,"file":"{{.*}}","line":177}}, +// CHECK-NEXT: "type": {"qualType":"int"}, +// CHECK-NEXT: "valueCategory": "rvalue", +// CHECK-NEXT: "value": "10" +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "NullStmt", +// CHECK-NEXT: "range": {"begin":{"col":5,"file":"{{.*}}","line":178},"end":{"col":5,"file":"{{.*}}","line":178}} +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: }, + + if (int i = 12) + ; + +// CHECK: "kind": "IfStmt", +// CHECK-NEXT: "range": {"begin":{"col":3,"file":"{{.*}}","line":227},"end":{"col":5,"file":"{{.*}}","line":228}}, +// CHECK-NEXT: "hasVar": true, +// CHECK-NEXT: "inner": [ +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "DeclStmt", +// CHECK-NEXT: "range": {"begin":{"col":7,"file":"{{.*}}","line":227},"end":{"col":15,"file":"{{.*}}","line":227}}, +// CHECK-NEXT: "inner": [ +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "VarDecl", +// CHECK-NEXT: "loc": {"col":11,"file":"{{.*}}","line":227}, +// CHECK-NEXT: "range": {"begin":{"col":7,"file":"{{.*}}","line":227},"end":{"col":15,"file":"{{.*}}","line":227}}, +// CHECK-NEXT: "isUsed": true, +// CHECK-NEXT: "name": "i", +// CHECK-NEXT: "type": {"qualType":"int"}, +// CHECK-NEXT: "init": "c", +// CHECK-NEXT: "inner": [ +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "IntegerLiteral", +// CHECK-NEXT: "range": {"begin":{"col":15,"file":"{{.*}}","line":227},"end":{"col":15,"file":"{{.*}}","line":227}}, +// CHECK-NEXT: "type": {"qualType":"int"}, +// CHECK-NEXT: "valueCategory": "rvalue", +// CHECK-NEXT: "value": "12" +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "ImplicitCastExpr", +// CHECK-NEXT: "range": {"begin":{"col":11,"file":"{{.*}}","line":227},"end":{"col":11,"file":"{{.*}}","line":227}}, +// CHECK-NEXT: "type": {"qualType":"bool"}, +// CHECK-NEXT: "valueCategory": "rvalue", +// CHECK-NEXT: "inner": [ +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "ImplicitCastExpr", +// CHECK-NEXT: "range": {"begin":{"col":11,"file":"{{.*}}","line":227},"end":{"col":11,"file":"{{.*}}","line":227}}, +// CHECK-NEXT: "type": {"qualType":"int"}, +// CHECK-NEXT: "valueCategory": "rvalue", +// CHECK-NEXT: "inner": [ +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "DeclRefExpr", +// CHECK-NEXT: "range": {"begin":{"col":11,"file":"{{.*}}","line":227},"end":{"col":11,"file":"{{.*}}","line":227}}, +// CHECK-NEXT: "type": {"qualType":"int"}, +// CHECK-NEXT: "valueCategory": "lvalue" +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "NullStmt", +// CHECK-NEXT: "range": {"begin":{"col":5,"file":"{{.*}}","line":228},"end":{"col":5,"file":"{{.*}}","line":228}} +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: }, + + if (int i = 12; i) + ; + +// CHECK: "kind": "IfStmt", +// CHECK-NEXT: "range": {"begin":{"col":3,"file":"{{.*}}","line":294},"end":{"col":5,"file":"{{.*}}","line":295}}, +// CHECK-NEXT: "hasInit": true, +// CHECK-NEXT: "inner": [ +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "DeclStmt", +// CHECK-NEXT: "range": {"begin":{"col":7,"file":"{{.*}}","line":294},"end":{"col":17,"file":"{{.*}}","line":294}}, +// CHECK-NEXT: "inner": [ +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "VarDecl", +// CHECK-NEXT: "loc": {"col":11,"file":"{{.*}}","line":294}, +// CHECK-NEXT: "range": {"begin":{"col":7,"file":"{{.*}}","line":294},"end":{"col":15,"file":"{{.*}}","line":294}}, +// CHECK-NEXT: "isUsed": true, +// CHECK-NEXT: "name": "i", +// CHECK-NEXT: "type": {"qualType":"int"}, +// CHECK-NEXT: "init": "c", +// CHECK-NEXT: "inner": [ +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "IntegerLiteral", +// CHECK-NEXT: "range": {"begin":{"col":15,"file":"{{.*}}","line":294},"end":{"col":15,"file":"{{.*}}","line":294}}, +// CHECK-NEXT: "type": {"qualType":"int"}, +// CHECK-NEXT: "valueCategory": "rvalue", +// CHECK-NEXT: "value": "12" +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "ImplicitCastExpr", +// CHECK-NEXT: "range": {"begin":{"col":19,"file":"{{.*}}","line":294},"end":{"col":19,"file":"{{.*}}","line":294}}, +// CHECK-NEXT: "type": {"qualType":"bool"}, +// CHECK-NEXT: "valueCategory": "rvalue", +// CHECK-NEXT: "inner": [ +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "ImplicitCastExpr", +// CHECK-NEXT: "range": {"begin":{"col":19,"file":"{{.*}}","line":294},"end":{"col":19,"file":"{{.*}}","line":294}}, +// CHECK-NEXT: "type": {"qualType":"int"}, +// CHECK-NEXT: "valueCategory": "rvalue", +// CHECK-NEXT: "inner": [ +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "DeclRefExpr", +// CHECK-NEXT: "range": {"begin":{"col":19,"file":"{{.*}}","line":294},"end":{"col":19,"file":"{{.*}}","line":294}}, +// CHECK-NEXT: "type": {"qualType":"int"}, +// CHECK-NEXT: "valueCategory": "lvalue" +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "NullStmt", +// CHECK-NEXT: "range": {"begin":{"col":5,"file":"{{.*}}","line":295},"end":{"col":5,"file":"{{.*}}","line":295}} +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: } +} Index: test/AST/ast-dump-enum-json.cpp =================================================================== --- test/AST/ast-dump-enum-json.cpp +++ test/AST/ast-dump-enum-json.cpp @@ -0,0 +1,182 @@ +// RUN: %clang_cc1 -triple x86_64-pc-linux -std=c++17 -ast-dump=json %s | FileCheck %s + +enum { + One, + Two +}; + +// CHECK: "kind": "EnumDecl", +// CHECK-NEXT: "loc": {"col":1,"file":"{{.*}}","line":3}, +// CHECK-NEXT: "range": {"begin":{"col":1,"file":"{{.*}}","line":3},"end":{"col":1,"file":"{{.*}}","line":6}}, +// CHECK-NEXT: "inner": [ +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "EnumConstantDecl", +// CHECK-NEXT: "loc": {"col":3,"file":"{{.*}}","line":4}, +// CHECK-NEXT: "range": {"begin":{"col":3,"file":"{{.*}}","line":4},"end":{"col":3,"file":"{{.*}}","line":4}}, +// CHECK-NEXT: "name": "One", +// CHECK-NEXT: "type": {"qualType":"(anonymous enum at {{.*}}:3:1)"} +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "EnumConstantDecl", +// CHECK-NEXT: "loc": {"col":3,"file":"{{.*}}","line":5}, +// CHECK-NEXT: "range": {"begin":{"col":3,"file":"{{.*}}","line":5},"end":{"col":3,"file":"{{.*}}","line":5}}, +// CHECK-NEXT: "name": "Two", +// CHECK-NEXT: "type": {"qualType":"(anonymous enum at {{.*}}:3:1)"} +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: }, + +enum E { + Three, + Four +}; + +// CHECK: "kind": "EnumDecl", +// CHECK-NEXT: "loc": {"col":6,"file":"{{.*}}","line":31}, +// CHECK-NEXT: "range": {"begin":{"col":1,"file":"{{.*}}","line":31},"end":{"col":1,"file":"{{.*}}","line":34}}, +// CHECK-NEXT: "name": "E", +// CHECK-NEXT: "inner": [ +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "EnumConstantDecl", +// CHECK-NEXT: "loc": {"col":3,"file":"{{.*}}","line":32}, +// CHECK-NEXT: "range": {"begin":{"col":3,"file":"{{.*}}","line":32},"end":{"col":3,"file":"{{.*}}","line":32}}, +// CHECK-NEXT: "name": "Three", +// CHECK-NEXT: "type": {"qualType":"E"} +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "EnumConstantDecl", +// CHECK-NEXT: "loc": {"col":3,"file":"{{.*}}","line":33}, +// CHECK-NEXT: "range": {"begin":{"col":3,"file":"{{.*}}","line":33},"end":{"col":3,"file":"{{.*}}","line":33}}, +// CHECK-NEXT: "name": "Four", +// CHECK-NEXT: "type": {"qualType":"E"} +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: }, + +enum F : short { + Five, + Six +}; + +// CHECK: "kind": "EnumDecl", +// CHECK-NEXT: "loc": {"col":6,"file":"{{.*}}","line":60}, +// CHECK-NEXT: "range": {"begin":{"col":1,"file":"{{.*}}","line":60},"end":{"col":1,"file":"{{.*}}","line":63}}, +// CHECK-NEXT: "name": "F", +// CHECK-NEXT: "fixedUnderlyingType": {"qualType":"short"}, +// CHECK-NEXT: "inner": [ +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "EnumConstantDecl", +// CHECK-NEXT: "loc": {"col":3,"file":"{{.*}}","line":61}, +// CHECK-NEXT: "range": {"begin":{"col":3,"file":"{{.*}}","line":61},"end":{"col":3,"file":"{{.*}}","line":61}}, +// CHECK-NEXT: "name": "Five", +// CHECK-NEXT: "type": {"qualType":"F"} +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "EnumConstantDecl", +// CHECK-NEXT: "loc": {"col":3,"file":"{{.*}}","line":62}, +// CHECK-NEXT: "range": {"begin":{"col":3,"file":"{{.*}}","line":62},"end":{"col":3,"file":"{{.*}}","line":62}}, +// CHECK-NEXT: "name": "Six", +// CHECK-NEXT: "type": {"qualType":"F"} +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: }, + +enum struct G { + Seven, + Eight +}; + +// CHECK: "kind": "EnumDecl", +// CHECK-NEXT: "loc": {"col":13,"file":"{{.*}}","line":90}, +// CHECK-NEXT: "range": {"begin":{"col":1,"file":"{{.*}}","line":90},"end":{"col":1,"file":"{{.*}}","line":93}}, +// CHECK-NEXT: "name": "G", +// CHECK-NEXT: "fixedUnderlyingType": {"qualType":"int"}, +// CHECK-NEXT: "scopedEnumTag": "struct", +// CHECK-NEXT: "inner": [ +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "EnumConstantDecl", +// CHECK-NEXT: "loc": {"col":3,"file":"{{.*}}","line":91}, +// CHECK-NEXT: "range": {"begin":{"col":3,"file":"{{.*}}","line":91},"end":{"col":3,"file":"{{.*}}","line":91}}, +// CHECK-NEXT: "name": "Seven", +// CHECK-NEXT: "type": {"qualType":"G"} +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "EnumConstantDecl", +// CHECK-NEXT: "loc": {"col":3,"file":"{{.*}}","line":92}, +// CHECK-NEXT: "range": {"begin":{"col":3,"file":"{{.*}}","line":92},"end":{"col":3,"file":"{{.*}}","line":92}}, +// CHECK-NEXT: "name": "Eight", +// CHECK-NEXT: "type": {"qualType":"G"} +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: }, + +enum class H { + Nine, + Ten +}; + +// CHECK: "kind": "EnumDecl", +// CHECK-NEXT: "loc": {"col":12,"file":"{{.*}}","line":121}, +// CHECK-NEXT: "range": {"begin":{"col":1,"file":"{{.*}}","line":121},"end":{"col":1,"file":"{{.*}}","line":124}}, +// CHECK-NEXT: "name": "H", +// CHECK-NEXT: "fixedUnderlyingType": {"qualType":"int"}, +// CHECK-NEXT: "scopedEnumTag": "class", +// CHECK-NEXT: "inner": [ +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "EnumConstantDecl", +// CHECK-NEXT: "loc": {"col":3,"file":"{{.*}}","line":122}, +// CHECK-NEXT: "range": {"begin":{"col":3,"file":"{{.*}}","line":122},"end":{"col":3,"file":"{{.*}}","line":122}}, +// CHECK-NEXT: "name": "Nine", +// CHECK-NEXT: "type": {"qualType":"H"} +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "EnumConstantDecl", +// CHECK-NEXT: "loc": {"col":3,"file":"{{.*}}","line":123}, +// CHECK-NEXT: "range": {"begin":{"col":3,"file":"{{.*}}","line":123},"end":{"col":3,"file":"{{.*}}","line":123}}, +// CHECK-NEXT: "name": "Ten", +// CHECK-NEXT: "type": {"qualType":"H"} +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: }, + +enum class I : int { + Eleven, + Twelve +}; + +// CHECK: "kind": "EnumDecl", +// CHECK-NEXT: "loc": {"col":12,"file":"{{.*}}","line":152}, +// CHECK-NEXT: "range": {"begin":{"col":1,"file":"{{.*}}","line":152},"end":{"col":1,"file":"{{.*}}","line":155}}, +// CHECK-NEXT: "name": "I", +// CHECK-NEXT: "fixedUnderlyingType": {"qualType":"int"}, +// CHECK-NEXT: "scopedEnumTag": "class", +// CHECK-NEXT: "inner": [ +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "EnumConstantDecl", +// CHECK-NEXT: "loc": {"col":3,"file":"{{.*}}","line":153}, +// CHECK-NEXT: "range": {"begin":{"col":3,"file":"{{.*}}","line":153},"end":{"col":3,"file":"{{.*}}","line":153}}, +// CHECK-NEXT: "name": "Eleven", +// CHECK-NEXT: "type": {"qualType":"I"} +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "EnumConstantDecl", +// CHECK-NEXT: "loc": {"col":3,"file":"{{.*}}","line":154}, +// CHECK-NEXT: "range": {"begin":{"col":3,"file":"{{.*}}","line":154},"end":{"col":3,"file":"{{.*}}","line":154}}, +// CHECK-NEXT: "name": "Twelve", +// CHECK-NEXT: "type": {"qualType":"I"} +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: } +// CHECK-NEXT: ] Index: lib/Frontend/FrontendActions.cpp =================================================================== --- lib/Frontend/FrontendActions.cpp +++ lib/Frontend/FrontendActions.cpp @@ -73,11 +73,10 @@ std::unique_ptr<ASTConsumer> ASTDumpAction::CreateASTConsumer(CompilerInstance &CI, StringRef InFile) { - return CreateASTDumper(nullptr /*Dump to stdout.*/, - CI.getFrontendOpts().ASTDumpFilter, - CI.getFrontendOpts().ASTDumpDecls, - CI.getFrontendOpts().ASTDumpAll, - CI.getFrontendOpts().ASTDumpLookups); + const FrontendOptions &Opts = CI.getFrontendOpts(); + return CreateASTDumper(nullptr /*Dump to stdout.*/, Opts.ASTDumpFilter, + Opts.ASTDumpDecls, Opts.ASTDumpAll, + Opts.ASTDumpLookups, Opts.ASTDumpFormat); } std::unique_ptr<ASTConsumer> Index: lib/Frontend/CompilerInvocation.cpp =================================================================== --- lib/Frontend/CompilerInvocation.cpp +++ lib/Frontend/CompilerInvocation.cpp @@ -1603,6 +1603,22 @@ llvm_unreachable("Invalid option in group!"); case OPT_ast_list: Opts.ProgramAction = frontend::ASTDeclList; break; + case OPT_ast_dump_all_EQ: + case OPT_ast_dump_EQ: { + unsigned Val = llvm::StringSwitch<unsigned>(A->getValue()) + .CaseLower("default", FrontendOptions::AOF_Default) + .CaseLower("json", FrontendOptions::AOF_JSON) + .Default(std::numeric_limits<unsigned>::max()); + + if (Val != std::numeric_limits<unsigned>::max()) + Opts.ASTDumpFormat = static_cast<FrontendOptions::ASTOutputFormat>(Val); + else { + Diags.Report(diag::err_drv_invalid_value) + << A->getAsString(Args) << A->getValue(); + Opts.ASTDumpFormat = FrontendOptions::AOF_Default; + } + LLVM_FALLTHROUGH; + } case OPT_ast_dump: case OPT_ast_dump_all: case OPT_ast_dump_lookups: @@ -1725,8 +1741,8 @@ Opts.FixOnlyWarnings = Args.hasArg(OPT_fix_only_warnings); Opts.FixAndRecompile = Args.hasArg(OPT_fixit_recompile); Opts.FixToTemporaries = Args.hasArg(OPT_fixit_to_temp); - Opts.ASTDumpDecls = Args.hasArg(OPT_ast_dump); - Opts.ASTDumpAll = Args.hasArg(OPT_ast_dump_all); + Opts.ASTDumpDecls = Args.hasArg(OPT_ast_dump, OPT_ast_dump_EQ); + Opts.ASTDumpAll = Args.hasArg(OPT_ast_dump_all, OPT_ast_dump_all_EQ); Opts.ASTDumpFilter = Args.getLastArgValue(OPT_ast_dump_filter); Opts.ASTDumpLookups = Args.hasArg(OPT_ast_dump_lookups); Opts.UseGlobalModuleIndex = !Args.hasArg(OPT_fno_modules_global_index); Index: lib/Frontend/ASTConsumers.cpp =================================================================== --- lib/Frontend/ASTConsumers.cpp +++ lib/Frontend/ASTConsumers.cpp @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------===// #include "clang/Frontend/ASTConsumers.h" +#include "clang/Frontend/FrontendOptions.h" #include "clang/AST/AST.h" #include "clang/AST/ASTConsumer.h" #include "clang/AST/ASTContext.h" @@ -34,10 +35,12 @@ public: enum Kind { DumpFull, Dump, Print, None }; - ASTPrinter(std::unique_ptr<raw_ostream> Out, Kind K, StringRef FilterString, + ASTPrinter(std::unique_ptr<raw_ostream> Out, Kind K, + FrontendOptions::ASTOutputFormat Format, StringRef FilterString, bool DumpLookups = false) : Out(Out ? *Out : llvm::outs()), OwnedOut(std::move(Out)), - OutputKind(K), FilterString(FilterString), DumpLookups(DumpLookups) {} + OutputKind(K), OutputFormat(Format), FilterString(FilterString), + DumpLookups(DumpLookups) {} void HandleTranslationUnit(ASTContext &Context) override { TranslationUnitDecl *D = Context.getTranslationUnitDecl(); @@ -90,7 +93,10 @@ PrintingPolicy Policy(D->getASTContext().getLangOpts()); D->print(Out, Policy, /*Indentation=*/0, /*PrintInstantiation=*/true); } else if (OutputKind != None) - D->dump(Out, OutputKind == DumpFull); + D->dump(Out, OutputKind == DumpFull, + OutputFormat == FrontendOptions::ASTOutputFormat::AOF_JSON + ? ADOF_JSON + : ADOF_Default); } raw_ostream &Out; @@ -99,6 +105,9 @@ /// How to output individual declarations. Kind OutputKind; + /// What format should the output take? + FrontendOptions::ASTOutputFormat OutputFormat; + /// Which declarations or DeclContexts to display. std::string FilterString; @@ -135,20 +144,19 @@ clang::CreateASTPrinter(std::unique_ptr<raw_ostream> Out, StringRef FilterString) { return llvm::make_unique<ASTPrinter>(std::move(Out), ASTPrinter::Print, + FrontendOptions::AOF_Default, FilterString); } std::unique_ptr<ASTConsumer> -clang::CreateASTDumper(std::unique_ptr<raw_ostream> Out, - StringRef FilterString, - bool DumpDecls, - bool Deserialize, - bool DumpLookups) { +clang::CreateASTDumper(std::unique_ptr<raw_ostream> Out, StringRef FilterString, + bool DumpDecls, bool Deserialize, bool DumpLookups, + FrontendOptions::ASTOutputFormat Format) { assert((DumpDecls || Deserialize || DumpLookups) && "nothing to dump"); return llvm::make_unique<ASTPrinter>(std::move(Out), Deserialize ? ASTPrinter::DumpFull : DumpDecls ? ASTPrinter::Dump : - ASTPrinter::None, + ASTPrinter::None, Format, FilterString, DumpLookups); } Index: lib/AST/JSONNodeDumper.cpp =================================================================== --- lib/AST/JSONNodeDumper.cpp +++ lib/AST/JSONNodeDumper.cpp @@ -0,0 +1,593 @@ +#include "clang/AST/JSONNodeDumper.h" +#include "llvm/ADT/StringSwitch.h" + +using namespace clang; + +void JSONNodeDumper::addPreviousDeclaration(const Decl *D) { + switch (D->getKind()) { +#define DECL(DERIVED, BASE) \ + case Decl::DERIVED: \ + return writePreviousDeclImpl(cast<DERIVED##Decl>(D)); +#define ABSTRACT_DECL(DECL) +#include "clang/AST/DeclNodes.inc" +#undef ABSTRACT_DECL +#undef DECL + } + llvm_unreachable("Decl that isn't part of DeclNodes.inc!"); +} + + +void JSONNodeDumper::Visit(const Attr *A) { + const char *AttrName = nullptr; + switch (A->getKind()) { +#define ATTR(X) \ + case attr::X: \ + AttrName = #X##"Attr"; \ + break; +#include "clang/Basic/AttrList.inc" +#undef ATTR + } + writeKVPair("id", createPointerRepresentation(A)); + writeKVPair("kind", AttrName); + writeKVPair("range", createSourceRange(A->getRange())); + writeKVPairOnlyIfTrue("inherited", A->isInherited()); + writeKVPairOnlyIfTrue("implicit", A->isImplicit()); + + // FIXME: it would be useful for us to output the spelling kind as well as + // the actual spelling. This would allow us to distinguish between the + // various attribute syntaxes, but we don't currently track that information + // within the AST. + writeKVPair("spelling", A->getSpelling()); + + InnerAttrVisitor::Visit(A); +} + +void JSONNodeDumper::Visit(const Stmt *S) { + if (!S) + return; + + writeKVPair("id", createPointerRepresentation(S)); + writeKVPair("kind", S->getStmtClassName()); + writeKVPair("range", createSourceRange(S->getSourceRange())); + + if (const auto *E = dyn_cast<Expr>(S)) { + writeKVPair("type", createQualType(E->getType())); + const char *Category = nullptr; + switch (E->getValueKind()) { + case VK_LValue: Category = "lvalue"; break; + case VK_XValue: Category = "xvalue"; break; + case VK_RValue: Category = "rvalue"; break; + } + writeKVPair("valueCategory", Category); + } + InnerStmtVisitor::Visit(S); +} + +void JSONNodeDumper::Visit(const Type *T) { + writeKVPair("id", createPointerRepresentation(T)); + writeKVPair("kind", (llvm::Twine(T->getTypeClassName()) + "Type").str()); + writeKVPair("type", createQualType(QualType(T, 0), /*Desugar*/ false)); + writeKVPairOnlyIfTrue("isDependent", T->isDependentType()); + writeKVPairOnlyIfTrue("isInstantiationDependent", + T->isInstantiationDependentType()); + writeKVPairOnlyIfTrue("isVariablyModified", T->isVariablyModifiedType()); + writeKVPairOnlyIfTrue("containsUnexpandedPack", + T->containsUnexpandedParameterPack()); + writeKVPairOnlyIfTrue("isImported", T->isFromAST()); + InnerTypeVisitor::Visit(T); +} + +void JSONNodeDumper::Visit(QualType T) { + writeKVPair("id", createPointerRepresentation(T.getAsOpaquePtr())); + writeKVPair("type", createQualType(T)); + writeKVPair("qualifiers", T.split().Quals.getAsString()); +} + +void JSONNodeDumper::Visit(const Decl *D) { + writeKVPair("id", createPointerRepresentation(D)); + writeKVPair("kind", (llvm::Twine(D->getDeclKindName()) + "Decl").str()); + writeKVPair("loc", createSourceLocation(D->getLocation())); + writeKVPair("range", createSourceRange(D->getSourceRange())); + writeKVPairOnlyIfTrue("isImplicit", D->isImplicit()); + writeKVPairOnlyIfTrue("isInvalid", D->isInvalidDecl()); + + if (D->isUsed()) + writeKVPair("isUsed", true); + else if (D->isThisDeclarationReferenced()) + writeKVPair("isReferenced", true); + + if (const auto *ND = dyn_cast<NamedDecl>(D)) + writeKVPairOnlyIfTrue("isHidden", ND->isHidden()); + + if (D->getLexicalDeclContext() != D->getDeclContext()) + writeKVPair("parentDeclContext", createPointerRepresentation(D->getDeclContext())); + + addPreviousDeclaration(D); + InnerDeclVisitor::Visit(D); +} + +void JSONNodeDumper::Visit(const comments::Comment *C, + const comments::FullComment *FC) {} +void JSONNodeDumper::Visit(const TemplateArgument &TA, SourceRange R, + const Decl *From, StringRef Label) {} +void JSONNodeDumper::Visit(const CXXCtorInitializer *Init) {} +void JSONNodeDumper::Visit(const OMPClause *C) {} +void JSONNodeDumper::Visit(const BlockDecl::Capture &C) {} +void JSONNodeDumper::Visit(const GenericSelectionExpr::ConstAssociation &A) {} + +void JSONNodeDumper::writeToOutput(llvm::json::Object Obj) { + OS << Indentation << llvm::formatv("{0:2}", llvm::json::Value(std::move(Obj))); +} + +void JSONNodeDumper::writeKVPair(StringRef Key, llvm::json::Value Value) { + if (!FirstChildField) + OS << ",\n"; + OS << Indentation << "\"" << Key << "\": " << Value; + FirstChildField = false; +} + +llvm::json::Object JSONNodeDumper::createSourceLocation(SourceLocation Loc) { + SourceLocation Spelling = SM.getSpellingLoc(Loc); + PresumedLoc Presumed = SM.getPresumedLoc(Spelling); + + if (Presumed.isInvalid()) + return llvm::json::Object{}; + + return llvm::json::Object{{"file", Presumed.getFilename()}, + {"line", Presumed.getLine()}, + {"col", Presumed.getColumn()}}; +} + +llvm::json::Object JSONNodeDumper::createSourceRange(SourceRange R) { + return llvm::json::Object{{"begin", createSourceLocation(R.getBegin())}, + {"end", createSourceLocation(R.getEnd())}}; +} + +std::string JSONNodeDumper::createPointerRepresentation(const void *Ptr) { + // Because JSON stores integer values as signed 64-bit integers, trying to + // represent them as such makes for very ugly pointer values in the resulting + // output. Instead, we convert the value to hex and treat it as a string. + return "0x" + llvm::utohexstr(reinterpret_cast<uint64_t>(Ptr), true); +} + +llvm::json::Object JSONNodeDumper::createQualType(QualType QT, bool Desugar) { + SplitQualType SQT = QT.split(); + llvm::json::Object Ret{{"qualType", QualType::getAsString(SQT, PrintPolicy)}}; + + if (Desugar && !QT.isNull()) { + SplitQualType DSQT = QT.getSplitDesugaredType(); + if (DSQT != SQT) + Ret["desugaredQualType"] = QualType::getAsString(DSQT, PrintPolicy); + } + return Ret; +} + +llvm::json::Object JSONNodeDumper::createBareDeclRef(const Decl *D) { + llvm::json::Object Ret{ + {"id", createPointerRepresentation(D)}, + {"kind", (llvm::Twine(D->getDeclKindName()) + "Decl").str()}}; + if (const auto *ND = dyn_cast<NamedDecl>(D)) + Ret["name"] = ND->getDeclName().getAsString(); + if (const auto *VD = dyn_cast<ValueDecl>(D)) + Ret["type"] = createQualType(VD->getType()); + return Ret; +} + +#define FIELD2(Name, Flag) if (RD->Flag()) Ret[Name] = true +#define FIELD1(Flag) FIELD2(#Flag, Flag) + +static llvm::json::Object +createDefaultConstructorDefinitionData(const CXXRecordDecl *RD) { + llvm::json::Object Ret; + + FIELD2("exists", hasDefaultConstructor); + FIELD2("trivial", hasTrivialDefaultConstructor); + FIELD2("nonTrivial", hasNonTrivialDefaultConstructor); + FIELD2("userProvided", hasUserProvidedDefaultConstructor); + FIELD2("isConstexpr", hasConstexprDefaultConstructor); + FIELD2("needsImplicit", needsImplicitDefaultConstructor); + FIELD2("defaultedIsConstexpr", defaultedDefaultConstructorIsConstexpr); + + return Ret; +} + +static llvm::json::Object +createCopyConstructorDefinitionData(const CXXRecordDecl *RD) { + llvm::json::Object Ret; + + FIELD2("simple", hasSimpleCopyConstructor); + FIELD2("trivial", hasTrivialCopyConstructor); + FIELD2("nonTrivial", hasNonTrivialCopyConstructor); + FIELD2("userDeclared", hasUserDeclaredCopyConstructor); + FIELD2("hasConstParam", hasCopyConstructorWithConstParam); + FIELD2("implicitHasConstParam", implicitCopyConstructorHasConstParam); + FIELD2("needsImplicit", needsImplicitCopyConstructor); + FIELD2("needsOverloadResolution", needsOverloadResolutionForCopyConstructor); + if (!RD->needsOverloadResolutionForCopyConstructor()) + FIELD2("defaultedIsDeleted", defaultedCopyConstructorIsDeleted); + + return Ret; +} + +static llvm::json::Object +createMoveConstructorDefinitionData(const CXXRecordDecl *RD) { + llvm::json::Object Ret; + + FIELD2("exists", hasMoveConstructor); + FIELD2("simple", hasSimpleMoveConstructor); + FIELD2("trivial", hasTrivialMoveConstructor); + FIELD2("nonTrivial", hasNonTrivialMoveConstructor); + FIELD2("userDeclared", hasUserDeclaredMoveConstructor); + FIELD2("needsImplicit", needsImplicitMoveConstructor); + FIELD2("needsOverloadResolution", needsOverloadResolutionForMoveConstructor); + if (!RD->needsOverloadResolutionForMoveConstructor()) + FIELD2("defaultedIsDeleted", defaultedMoveConstructorIsDeleted); + + return Ret; +} + +static llvm::json::Object +createCopyAssignmentDefinitionData(const CXXRecordDecl *RD) { + llvm::json::Object Ret; + + FIELD2("trivial", hasTrivialCopyAssignment); + FIELD2("nonTrivial", hasNonTrivialCopyAssignment); + FIELD2("hasConstParam", hasCopyAssignmentWithConstParam); + FIELD2("implicitHasConstParam", implicitCopyAssignmentHasConstParam); + FIELD2("userDeclared", hasUserDeclaredCopyAssignment); + FIELD2("needsImplicit", needsImplicitCopyAssignment); + FIELD2("needsOverloadResolution", needsOverloadResolutionForCopyAssignment); + + return Ret; +} + +static llvm::json::Object +createMoveAssignmentDefinitionData(const CXXRecordDecl *RD) { + llvm::json::Object Ret; + + FIELD2("exists", hasMoveAssignment); + FIELD2("simple", hasSimpleMoveAssignment); + FIELD2("trivial", hasTrivialMoveAssignment); + FIELD2("nonTrivial", hasNonTrivialMoveAssignment); + FIELD2("userDeclared", hasUserDeclaredMoveAssignment); + FIELD2("needsImplicit", needsImplicitMoveAssignment); + FIELD2("needsOverloadResolution", needsOverloadResolutionForMoveAssignment); + + return Ret; +} + +static llvm::json::Object +createDestructorDefinitionData(const CXXRecordDecl *RD) { + llvm::json::Object Ret; + + FIELD2("simple", hasSimpleDestructor); + FIELD2("irrelevant", hasIrrelevantDestructor); + FIELD2("trivial", hasTrivialDestructor); + FIELD2("nonTrivial", hasNonTrivialDestructor); + FIELD2("userDeclared", hasUserDeclaredDestructor); + FIELD2("needsImplicit", needsImplicitDestructor); + FIELD2("needsOverloadResolution", needsOverloadResolutionForDestructor); + if (!RD->needsOverloadResolutionForDestructor()) + FIELD2("defaultedIsDeleted", defaultedDestructorIsDeleted); + + return Ret; +} + +llvm::json::Object +JSONNodeDumper::createCXXRecordDefinitionData(const CXXRecordDecl *RD) { + llvm::json::Object Ret; + + // This data is common to all C++ classes. + FIELD1(isGenericLambda); + FIELD1(isLambda); + FIELD1(isEmpty); + FIELD1(isAggregate); + FIELD1(isStandardLayout); + FIELD1(isTriviallyCopyable); + FIELD1(isPOD); + FIELD1(isTrivial); + FIELD1(isPolymorphic); + FIELD1(isAbstract); + FIELD1(isLiteral); + FIELD1(canPassInRegisters); + FIELD1(hasUserDeclaredConstructor); + FIELD1(hasConstexprNonCopyMoveConstructor); + FIELD1(hasMutableFields); + FIELD1(hasVariantMembers); + FIELD2("canConstDefaultInit", allowConstDefaultInit); + + Ret["defaultCtor"] = createDefaultConstructorDefinitionData(RD); + Ret["copyCtor"] = createCopyConstructorDefinitionData(RD); + Ret["moveCtor"] = createMoveConstructorDefinitionData(RD); + Ret["copyAssign"] = createCopyAssignmentDefinitionData(RD); + Ret["moveAssign"] = createMoveAssignmentDefinitionData(RD); + Ret["dtor"] = createDestructorDefinitionData(RD); + + return Ret; +} + +#undef FIELD1 +#undef FIELD2 + +std::string JSONNodeDumper::createAccessSpecifier(AccessSpecifier AS) { + switch (AS) { + case AS_none: return "none"; + case AS_private: return "private"; + case AS_protected: return "protected"; + case AS_public: return "public"; + } + llvm_unreachable("Unknown access specifier"); +} + +llvm::json::Object +JSONNodeDumper::createCXXBaseSpecifier(const CXXBaseSpecifier &BS) { + llvm::json::Object Ret; + + Ret["type"] = createQualType(BS.getType()); + Ret["access"] = createAccessSpecifier(BS.getAccessSpecifier()); + Ret["writtenAccess"] = + createAccessSpecifier(BS.getAccessSpecifierAsWritten()); + if (BS.isVirtual()) + Ret["isVirtual"] = true; + if (BS.isPackExpansion()) + Ret["isPackExpansion"] = true; + + return Ret; +} + +void JSONNodeDumper::VisitTypedefType(const TypedefType *TT) { + writeKVPair("decl", createBareDeclRef(TT->getDecl())); +} + +void JSONNodeDumper::VisitFunctionType(const FunctionType *T) { + FunctionType::ExtInfo E = T->getExtInfo(); + writeKVPairOnlyIfTrue("noreturn", E.getNoReturn()); + writeKVPairOnlyIfTrue("producesResult", E.getProducesResult()); + if (E.getHasRegParm()) + writeKVPair("regParm", E.getRegParm()); + writeKVPair("cc", FunctionType::getNameForCallConv(E.getCC())); +} + +void JSONNodeDumper::VisitFunctionProtoType(const FunctionProtoType *T) { + FunctionProtoType::ExtProtoInfo E = T->getExtProtoInfo(); + writeKVPairOnlyIfTrue("trailingReturn", E.HasTrailingReturn); + writeKVPairOnlyIfTrue("const", T->isConst()); + writeKVPairOnlyIfTrue("volatile", T->isVolatile()); + writeKVPairOnlyIfTrue("restrict", T->isRestrict()); + writeKVPairOnlyIfTrue("variadic", E.Variadic); + switch (E.RefQualifier) { + case RQ_LValue: writeKVPair("refQualifier", "&"); break; + case RQ_RValue: writeKVPair("refQualifier", "&&"); break; + case RQ_None: break; + } + VisitFunctionType(T); +} + +void JSONNodeDumper::VisitNamedDecl(const NamedDecl *ND) { + if (ND && ND->getDeclName()) + writeKVPair("name", ND->getNameAsString()); +} + +void JSONNodeDumper::VisitTypedefDecl(const TypedefDecl *TD) { + VisitNamedDecl(TD); + writeKVPair("type", createQualType(TD->getUnderlyingType())); +} + +void JSONNodeDumper::VisitTypeAliasDecl(const TypeAliasDecl *TAD) { + VisitNamedDecl(TAD); + writeKVPair("type", createQualType(TAD->getUnderlyingType())); +} + +void JSONNodeDumper::VisitNamespaceDecl(const NamespaceDecl *ND) { + VisitNamedDecl(ND); + writeKVPairOnlyIfTrue("isInline", ND->isInline()); + if (!ND->isOriginalNamespace()) + writeKVPair("originalNamespace", + createBareDeclRef(ND->getOriginalNamespace())); +} + +void JSONNodeDumper::VisitUsingDirectiveDecl(const UsingDirectiveDecl *UDD) { + writeKVPair("nominatedNamespace", + createBareDeclRef(UDD->getNominatedNamespace())); +} + +void JSONNodeDumper::VisitNamespaceAliasDecl(const NamespaceAliasDecl *NAD) { + VisitNamedDecl(NAD); + writeKVPair("aliasedNamespace", + createBareDeclRef(NAD->getAliasedNamespace())); +} + +void JSONNodeDumper::VisitUsingDecl(const UsingDecl *UD) { + std::string Name; + if (const NestedNameSpecifier *NNS = UD->getQualifier()) { + llvm::raw_string_ostream SOS(Name); + NNS->print(SOS, UD->getASTContext().getPrintingPolicy()); + } + Name += UD->getNameAsString(); + writeKVPair("name", Name); +} + +void JSONNodeDumper::VisitUsingShadowDecl(const UsingShadowDecl *USD) { + writeKVPair("target", createBareDeclRef(USD->getTargetDecl())); +} + +void JSONNodeDumper::VisitVarDecl(const VarDecl *VD) { + VisitNamedDecl(VD); + writeKVPair("type", createQualType(VD->getType())); + + StorageClass SC = VD->getStorageClass(); + if (SC != SC_None) + writeKVPair("storageClass", VarDecl::getStorageClassSpecifierString(SC)); + switch (VD->getTLSKind()) { + case VarDecl::TLS_Dynamic: writeKVPair("tls", "dynamic"); break; + case VarDecl::TLS_Static: writeKVPair("tls", "static"); break; + case VarDecl::TLS_None: break; + } + writeKVPairOnlyIfTrue("nrvo", VD->isNRVOVariable()); + writeKVPairOnlyIfTrue("inline", VD->isInline()); + writeKVPairOnlyIfTrue("constexpr", VD->isConstexpr()); + if (VD->hasInit()) { + switch (VD->getInitStyle()) { + case VarDecl::CInit: writeKVPair("init", "c"); break; + case VarDecl::CallInit: writeKVPair("init", "call"); break; + case VarDecl::ListInit: writeKVPair("init", "list"); break; + } + } +} + +void JSONNodeDumper::VisitFunctionDecl(const FunctionDecl *FD) { + VisitNamedDecl(FD); + writeKVPair("type", createQualType(FD->getType())); + StorageClass SC = FD->getStorageClass(); + if (SC != SC_None) + writeKVPair("storageClass", VarDecl::getStorageClassSpecifierString(SC)); + writeKVPairOnlyIfTrue("inline", FD->isInlineSpecified()); + writeKVPairOnlyIfTrue("virtual", FD->isVirtualAsWritten()); + writeKVPairOnlyIfTrue("pure", FD->isPure()); + writeKVPairOnlyIfTrue("explicitlyDeleted", FD->isDeletedAsWritten()); + writeKVPairOnlyIfTrue("constexpr", FD->isConstexpr()); + if (FD->isDefaulted()) + writeKVPair("explicitlyDefaulted", FD->isDeleted() ? "deleted" : "default"); +} + +void JSONNodeDumper::VisitEnumDecl(const EnumDecl *ED) { + VisitNamedDecl(ED); + if (ED->isFixed()) + writeKVPair("fixedUnderlyingType", createQualType(ED->getIntegerType())); + if (ED->isScoped()) + writeKVPair("scopedEnumTag", + ED->isScopedUsingClassTag() ? "class" : "struct"); +} +void JSONNodeDumper::VisitEnumConstantDecl(const EnumConstantDecl *ECD) { + VisitNamedDecl(ECD); + writeKVPair("type", createQualType(ECD->getType())); +} + +void JSONNodeDumper::VisitRecordDecl(const RecordDecl *RD) { + VisitNamedDecl(RD); + writeKVPair("tagUsed", RD->getKindName()); + writeKVPairOnlyIfTrue("completeDefinition", RD->isCompleteDefinition()); +} +void JSONNodeDumper::VisitCXXRecordDecl(const CXXRecordDecl *RD) { + VisitRecordDecl(RD); + + // All other information requires a complete definition. + if (!RD->isCompleteDefinition()) + return; + + writeKVPair("definitionData", createCXXRecordDefinitionData(RD)); + + llvm::json::Array Bases; + for (const auto &Spec : RD->bases()) { + Bases.push_back(createCXXBaseSpecifier(Spec)); + } + if (!Bases.empty()) + writeKVPair("bases", std::move(Bases)); +} + +void JSONNodeDumper::VisitTemplateTypeParmDecl(const TemplateTypeParmDecl *D) { + VisitNamedDecl(D); + writeKVPair("tagUsed", D->wasDeclaredWithTypename() ? "typename" : "class"); + writeKVPair("depth", D->getDepth()); + writeKVPair("index", D->getIndex()); + writeKVPairOnlyIfTrue("isParameterPack", D->isParameterPack()); +} + +void JSONNodeDumper::VisitNonTypeTemplateParmDecl(const NonTypeTemplateParmDecl *D) { + VisitNamedDecl(D); + writeKVPair("type", createQualType(D->getType())); + writeKVPair("depth", D->getDepth()); + writeKVPair("index", D->getIndex()); + writeKVPairOnlyIfTrue("isParameterPack", D->isParameterPack()); +} + +void JSONNodeDumper::VisitTemplateTemplateParmDecl(const TemplateTemplateParmDecl *D) { + VisitNamedDecl(D); + writeKVPair("depth", D->getDepth()); + writeKVPair("index", D->getIndex()); + writeKVPairOnlyIfTrue("isParameterPack", D->isParameterPack()); +} + +void JSONNodeDumper::VisitLinkageSpecDecl(const LinkageSpecDecl *LSD) { + std::string Lang; + switch (LSD->getLanguage()) { + case LinkageSpecDecl::lang_c: Lang = "C"; break; + case LinkageSpecDecl::lang_cxx: Lang = "C++"; break; + } + writeKVPair("language", Lang); + writeKVPairOnlyIfTrue("hasBraces", LSD->hasBraces()); +} + +void JSONNodeDumper::VisitAccessSpecDecl(const AccessSpecDecl *ASD) { + writeKVPair("access", createAccessSpecifier(ASD->getAccess())); +} + +void JSONNodeDumper::VisitFriendDecl(const FriendDecl *FD) { + if (const TypeSourceInfo *T = FD->getFriendType()) + writeKVPair("type", createQualType(T->getType())); +} + +void JSONNodeDumper::VisitUnaryOperator(const UnaryOperator *UO) { + writeKVPair("isPostfix", UO->isPostfix()); + writeKVPair("opcode", UnaryOperator::getOpcodeStr(UO->getOpcode())); + if (!UO->canOverflow()) + writeKVPair("canOverflow", false); +} + +void JSONNodeDumper::VisitBinaryOperator(const BinaryOperator *BO) { + writeKVPair("opcode", BinaryOperator::getOpcodeStr(BO->getOpcode())); +} + +void JSONNodeDumper::VisitMemberExpr(const MemberExpr *ME) { + // Note, we always write this Boolean field because the information it conveys + // is critical to understanding the AST node. + writeKVPair("isArrow", ME->isArrow()); + writeKVPair("referencedMemberDecl", + createPointerRepresentation(ME->getMemberDecl())); +} + +void JSONNodeDumper::VisitIntegerLiteral(const IntegerLiteral *IL) { + writeKVPair("value", IL->getValue().toString( + /*Radix=*/10, IL->getType()->isSignedIntegerType())); +} +void JSONNodeDumper::VisitCharacterLiteral(const CharacterLiteral *CL) { + // FIXME: This should probably print the character literal as a string, + // rather than as a numerical value. + writeKVPair("value", CL->getValue()); +} +void JSONNodeDumper::VisitFixedPointLiteral(const FixedPointLiteral *FPL) { + writeKVPair("value", FPL->getValueAsString(/*Radix=*/10)); +} +void JSONNodeDumper::VisitFloatingLiteral(const FloatingLiteral *FL) { + writeKVPair("value", FL->getValueAsApproximateDouble()); +} +void JSONNodeDumper::VisitStringLiteral(const StringLiteral *SL) { + writeKVPair("value", SL->getString()); +} + +void JSONNodeDumper::VisitIfStmt(const IfStmt *IS) { + writeKVPairOnlyIfTrue("hasInit", IS->hasInitStorage()); + writeKVPairOnlyIfTrue("hasVar", IS->hasVarStorage()); + writeKVPairOnlyIfTrue("hasElse", IS->hasElseStorage()); + writeKVPairOnlyIfTrue("isConstexpr", IS->isConstexpr()); +} + +void JSONNodeDumper::VisitSwitchStmt(const SwitchStmt *SS) { + writeKVPairOnlyIfTrue("hasInit", SS->hasInitStorage()); + writeKVPairOnlyIfTrue("hasVar", SS->hasVarStorage()); +} +void JSONNodeDumper::VisitCaseStmt(const CaseStmt *CS) { + writeKVPairOnlyIfTrue("isGNURange", CS->caseStmtIsGNURange()); +} + +void JSONNodeDumper::VisitLabelStmt(const LabelStmt *LS) { + writeKVPair("name", LS->getName()); + writeKVPair("declId", createPointerRepresentation(LS->getDecl())); +} +void JSONNodeDumper::VisitGotoStmt(const GotoStmt *GS) { + writeKVPair("targetLabelDeclId", createPointerRepresentation(GS->getLabel())); +} + +void JSONNodeDumper::VisitWhileStmt(const WhileStmt *WS) { + writeKVPairOnlyIfTrue("hasVar", WS->hasVarStorage()); +} Index: lib/AST/CMakeLists.txt =================================================================== --- lib/AST/CMakeLists.txt +++ lib/AST/CMakeLists.txt @@ -44,6 +44,7 @@ InheritViz.cpp ItaniumCXXABI.cpp ItaniumMangle.cpp + JSONNodeDumper.cpp Mangle.cpp MicrosoftCXXABI.cpp MicrosoftMangle.cpp Index: lib/AST/ASTDumper.cpp =================================================================== --- lib/AST/ASTDumper.cpp +++ lib/AST/ASTDumper.cpp @@ -14,6 +14,7 @@ #include "clang/AST/ASTContext.h" #include "clang/AST/ASTNodeTraverser.h" #include "clang/AST/DeclLookups.h" +#include "clang/AST/JSONNodeDumper.h" #include "clang/AST/TextNodeDumper.h" #include "clang/Basic/Builtins.h" #include "clang/Basic/Module.h" @@ -222,13 +223,21 @@ LLVM_DUMP_METHOD void Decl::dump() const { dump(llvm::errs()); } -LLVM_DUMP_METHOD void Decl::dump(raw_ostream &OS, bool Deserialize) const { +LLVM_DUMP_METHOD void Decl::dump(raw_ostream &OS, bool Deserialize, + ASTDumpOutputFormat Format) const { const ASTContext &Ctx = getASTContext(); const SourceManager &SM = Ctx.getSourceManager(); - ASTDumper P(OS, &Ctx.getCommentCommandTraits(), &SM, - SM.getDiagnostics().getShowColors(), Ctx.getPrintingPolicy()); - P.setDeserialize(Deserialize); - P.Visit(this); + + if (ADOF_JSON == Format) { + JSONDumper P(OS, SM, Ctx.getPrintingPolicy()); + (void)Deserialize; // FIXME? + P.Visit(this); + } else { + ASTDumper P(OS, &Ctx.getCommentCommandTraits(), &SM, + SM.getDiagnostics().getShowColors(), Ctx.getPrintingPolicy()); + P.setDeserialize(Deserialize); + P.Visit(this); + } } LLVM_DUMP_METHOD void Decl::dumpColor() const { Index: include/clang/Frontend/FrontendOptions.h =================================================================== --- include/clang/Frontend/FrontendOptions.h +++ include/clang/Frontend/FrontendOptions.h @@ -307,6 +307,12 @@ CodeCompleteOptions CodeCompleteOpts; + /// Specifies the output format of the AST. + enum ASTOutputFormat { + AOF_Default, + AOF_JSON, + } ASTDumpFormat = AOF_Default; + enum { ARCMT_None, ARCMT_Check, Index: include/clang/Frontend/ASTConsumers.h =================================================================== --- include/clang/Frontend/ASTConsumers.h +++ include/clang/Frontend/ASTConsumers.h @@ -14,6 +14,7 @@ #define LLVM_CLANG_FRONTEND_ASTCONSUMERS_H #include "clang/Basic/LLVM.h" +#include "clang/Frontend/FrontendOptions.h" #include <memory> namespace clang { @@ -35,10 +36,10 @@ // AST dumper: dumps the raw AST in human-readable form to the given output // stream, or stdout if OS is nullptr. -std::unique_ptr<ASTConsumer> CreateASTDumper(std::unique_ptr<raw_ostream> OS, - StringRef FilterString, - bool DumpDecls, bool Deserialize, - bool DumpLookups); +std::unique_ptr<ASTConsumer> +CreateASTDumper(std::unique_ptr<raw_ostream> OS, StringRef FilterString, + bool DumpDecls, bool Deserialize, bool DumpLookups, + FrontendOptions::ASTOutputFormat Format); // AST Decl node lister: prints qualified names of all filterable AST Decl // nodes. Index: include/clang/Driver/CC1Options.td =================================================================== --- include/clang/Driver/CC1Options.td +++ include/clang/Driver/CC1Options.td @@ -553,8 +553,14 @@ HelpText<"Build ASTs and print the list of declaration node qualified names">; def ast_dump : Flag<["-"], "ast-dump">, HelpText<"Build ASTs and then debug dump them">; +def ast_dump_EQ : Joined<["-"], "ast-dump=">, + HelpText<"Build ASTs and then debug dump them in the specified format. " + "Supported formats include: default, json">; def ast_dump_all : Flag<["-"], "ast-dump-all">, HelpText<"Build ASTs and then debug dump them, forcing deserialization">; +def ast_dump_all_EQ : Joined<["-"], "ast-dump-all=">, + HelpText<"Build ASTs and then debug dump them in the specified format, " + "forcing deserialization. Supported formats include: default, json">; def templight_dump : Flag<["-"], "templight-dump">, HelpText<"Dump templight information to stdout">; def ast_dump_lookups : Flag<["-"], "ast-dump-lookups">, Index: include/clang/AST/JSONNodeDumper.h =================================================================== --- include/clang/AST/JSONNodeDumper.h +++ include/clang/AST/JSONNodeDumper.h @@ -0,0 +1,255 @@ +//===--- JSONNodeDumper.h - Printing of AST nodes to JSON -----------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file implements AST dumping of components of individual AST nodes to +// a JSON. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_AST_JSONNODEDUMPER_H +#define LLVM_CLANG_AST_JSONNODEDUMPER_H + +#include "clang/AST/ASTContext.h" +#include "clang/AST/ASTNodeTraverser.h" +#include "clang/AST/ASTDumperUtils.h" +#include "clang/AST/AttrVisitor.h" +#include "clang/AST/CommentCommandTraits.h" +#include "clang/AST/CommentVisitor.h" +#include "clang/AST/ExprCXX.h" +#include "llvm/Support/JSON.h" + +namespace clang { + +class NodeStreamer { + raw_ostream &OS; + + /// Pending[i] is an action to dump an entity at level i. + llvm::SmallVector<std::function<void(bool IsLastChild)>, 32> Pending; + + /// Indicates whether we're at the top level. + bool TopLevel = true; + + /// Indicates if we're handling the first child after entering a new depth. + bool FirstChild = true; + +protected: + + /// Used for determining intra-object comma placement; inter-object comma + /// placement is determined by FirstChild. + bool FirstChildField = true; + + /// The current indentation level. + std::string Indentation; + +public: + /// Add a child of the current node. Calls DoAddChild without arguments + template <typename Fn> void AddChild(Fn DoAddChild) { + return AddChild("", DoAddChild); + } + + /// Add a child of the current node with an optional label. + /// Calls DoAddChild without arguments. + template <typename Fn> void AddChild(StringRef Label, Fn DoAddChild) { + // If we're at the top level, there's nothing interesting to do; just + // run the dumper. + if (TopLevel) { + TopLevel = false; + OS << Indentation << "{\n"; + FirstChildField = true; + Indentation += " "; + DoAddChild(); + + while (!Pending.empty()) { + Pending.back()(true); + Pending.pop_back(); + } + Indentation.clear(); + OS << "\n" << Indentation << "}\n"; + TopLevel = true; + return; + } + + bool WasFirstChild = FirstChild; + // We need to capture an owning-string in the lambda because the lambda + // is invoked in a deferred manner. + auto DumpWithIndent = [this, DoAddChild, WasFirstChild](bool IsLastChild) { + if (WasFirstChild) { + if (!FirstChildField) + OS << ",\n"; + OS << Indentation << "\"inner\": [\n"; + } + + FirstChild = true; + unsigned Depth = Pending.size(); + OS << Indentation << "{\n"; + + Indentation += " "; + + FirstChildField = true; + DoAddChild(); + + // If any children are left, they're the last at their nesting level. + // Dump those ones out now. + while (Depth < Pending.size()) { + Pending.back()(true); + this->Pending.pop_back(); + } + + // Restore the old prefix. + this->Indentation.resize(Indentation.size() - 2); + + OS << "\n" << Indentation << "}"; + + if (!IsLastChild) + OS << ",\n"; + else + OS << "\n" << Indentation << "]"; + }; + + if (FirstChild) { + Pending.push_back(std::move(DumpWithIndent)); + } else { + Pending.back()(false); + Pending.back() = std::move(DumpWithIndent); + } + FirstChild = false; + } + + NodeStreamer(raw_ostream &OS) : OS(OS) {} +}; + +// Dumps AST nodes in JSON format. Note that we cannot serialize the AST into +// JSON and then dump to a file because the AST is an ordered collection of +// nodes but our JSON objects are modelled as an unordered container. We can +// use JSON facilities for constructing the terminal fields in a node because +// the field order is unimportant there, but we cannot use those same facilities +// for dumping the entire tree because AST nodes could get out of order in ways +// that alter the meaning of the AST. +class JSONNodeDumper + : public ConstAttrVisitor<JSONNodeDumper>, + public ConstStmtVisitor<JSONNodeDumper>, + public TypeVisitor<JSONNodeDumper>, + public ConstDeclVisitor<JSONNodeDumper>, + public NodeStreamer { + raw_ostream &OS; + const SourceManager &SM; + PrintingPolicy PrintPolicy; + + using InnerAttrVisitor = ConstAttrVisitor<JSONNodeDumper>; + using InnerStmtVisitor = ConstStmtVisitor<JSONNodeDumper>; + using InnerTypeVisitor = TypeVisitor<JSONNodeDumper>; + using InnerDeclVisitor = ConstDeclVisitor<JSONNodeDumper>; + + void writeToOutput(llvm::json::Object Obj); + void writeKVPair(StringRef Key, llvm::json::Value Value); + void writeKVPairOnlyIfTrue(StringRef Key, bool Value) { + if (Value) + writeKVPair(Key, true); + } + + llvm::json::Object createSourceLocation(SourceLocation Loc); + llvm::json::Object createSourceRange(SourceRange R); + std::string createPointerRepresentation(const void *Ptr); + llvm::json::Object createQualType(QualType QT, bool Desugar = true); + llvm::json::Object createBareDeclRef(const Decl *D); + llvm::json::Object createCXXRecordDefinitionData(const CXXRecordDecl *RD); + llvm::json::Object createCXXBaseSpecifier(const CXXBaseSpecifier &BS); + std::string createAccessSpecifier(AccessSpecifier AS); + + void writePreviousDeclImpl(...) {} + + template <typename T> void writePreviousDeclImpl(const Mergeable<T> *D) { + const T *First = D->getFirstDecl(); + if (First != D) + writeKVPair("firstRedecl", createPointerRepresentation(First)); + } + + template <typename T> void writePreviousDeclImpl(const Redeclarable<T> *D) { + const T *Prev = D->getPreviousDecl(); + if (Prev) + writeKVPair("previousDecl", createPointerRepresentation(Prev)); + } + void addPreviousDeclaration(const Decl *D); + +public: + JSONNodeDumper(raw_ostream &OS, const SourceManager &SrcMgr, + const PrintingPolicy &PrintPolicy) + : NodeStreamer(OS), OS(OS), SM(SrcMgr), PrintPolicy(PrintPolicy) {} + + void Visit(const Attr *A); + void Visit(const Stmt *Node); + void Visit(const Type *T); + void Visit(QualType T); + void Visit(const Decl *D); + + void Visit(const comments::Comment *C, const comments::FullComment *FC); + void Visit(const TemplateArgument &TA, SourceRange R = {}, + const Decl *From = nullptr, StringRef Label = {}); + void Visit(const CXXCtorInitializer *Init); + void Visit(const OMPClause *C); + void Visit(const BlockDecl::Capture &C); + void Visit(const GenericSelectionExpr::ConstAssociation &A); + + void VisitTypedefType(const TypedefType *TT); + void VisitFunctionType(const FunctionType *T); + void VisitFunctionProtoType(const FunctionProtoType *T); + + void VisitNamedDecl(const NamedDecl *ND); + void VisitTypedefDecl(const TypedefDecl *TD); + void VisitTypeAliasDecl(const TypeAliasDecl *TAD); + void VisitNamespaceDecl(const NamespaceDecl *ND); + void VisitUsingDirectiveDecl(const UsingDirectiveDecl *UDD); + void VisitNamespaceAliasDecl(const NamespaceAliasDecl *NAD); + void VisitUsingDecl(const UsingDecl *UD); + void VisitUsingShadowDecl(const UsingShadowDecl *USD); + void VisitVarDecl(const VarDecl *VD); + void VisitFunctionDecl(const FunctionDecl *FD); + void VisitEnumDecl(const EnumDecl *ED); + void VisitEnumConstantDecl(const EnumConstantDecl *ECD); + void VisitRecordDecl(const RecordDecl *RD); + void VisitCXXRecordDecl(const CXXRecordDecl *RD); + void VisitTemplateTypeParmDecl(const TemplateTypeParmDecl *D); + void VisitNonTypeTemplateParmDecl(const NonTypeTemplateParmDecl *D); + void VisitTemplateTemplateParmDecl(const TemplateTemplateParmDecl *D); + void VisitLinkageSpecDecl(const LinkageSpecDecl *LSD); + void VisitAccessSpecDecl(const AccessSpecDecl *ASD); + void VisitFriendDecl(const FriendDecl *FD); + + void VisitUnaryOperator(const UnaryOperator *UO); + void VisitBinaryOperator(const BinaryOperator *BO); + void VisitMemberExpr(const MemberExpr *ME); + + void VisitIntegerLiteral(const IntegerLiteral *IL); + void VisitCharacterLiteral(const CharacterLiteral *CL); + void VisitFixedPointLiteral(const FixedPointLiteral *FPL); + void VisitFloatingLiteral(const FloatingLiteral *FL); + void VisitStringLiteral(const StringLiteral *SL); + + void VisitIfStmt(const IfStmt *IS); + void VisitSwitchStmt(const SwitchStmt *SS); + void VisitCaseStmt(const CaseStmt *CS); + void VisitLabelStmt(const LabelStmt *LS); + void VisitGotoStmt(const GotoStmt *GS); + void VisitWhileStmt(const WhileStmt *WS); +}; + +class JSONDumper : public ASTNodeTraverser<JSONDumper, JSONNodeDumper> { + JSONNodeDumper NodeDumper; + +public: + JSONDumper(raw_ostream &OS, const SourceManager &SrcMgr, + const PrintingPolicy &PrintPolicy) + : NodeDumper(OS, SrcMgr, PrintPolicy) {} + + JSONNodeDumper &doGetNodeDelegate() { return NodeDumper; } +}; + +} // namespace clang + +#endif // LLVM_CLANG_AST_JSONNODEDUMPER_H Index: include/clang/AST/DeclBase.h =================================================================== --- include/clang/AST/DeclBase.h +++ include/clang/AST/DeclBase.h @@ -13,6 +13,7 @@ #ifndef LLVM_CLANG_AST_DECLBASE_H #define LLVM_CLANG_AST_DECLBASE_H +#include "clang/AST/ASTDumperUtils.h" #include "clang/AST/AttrIterator.h" #include "clang/AST/DeclarationName.h" #include "clang/Basic/IdentifierTable.h" @@ -1138,7 +1139,8 @@ // Same as dump(), but forces color printing. void dumpColor() const; - void dump(raw_ostream &Out, bool Deserialize = false) const; + void dump(raw_ostream &Out, bool Deserialize = false, + ASTDumpOutputFormat OutputFormat = ADOF_Default) const; /// \return Unique reproducible object identifier int64_t getID() const; Index: include/clang/AST/ASTDumperUtils.h =================================================================== --- include/clang/AST/ASTDumperUtils.h +++ include/clang/AST/ASTDumperUtils.h @@ -17,6 +17,14 @@ namespace clang { +/// Used to specify the format for printing AST dump information. The values +/// in this enumeration should match the ones in +/// clang::FrontendOptions::ASTOutputFormat. +enum ASTDumpOutputFormat { + ADOF_Default, + ADOF_JSON +}; + // Colors used for various parts of the AST dump // Do not use bold yellow for any text. It is hard to read on white screens.
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits