Skip to content

Commit a57fb4d

Browse files
committed
[AST] Add minimal JSON support to ASTDumper.
This only takes the existing AST information and writes it as JSON instead of S-expressions. Since many of these fields are stringified, they're not ideal for the kind of analysis clients of the JSON format would want to do. A future commit will update these values to use a more structured representation.
1 parent d14f3c9 commit a57fb4d

File tree

9 files changed

+638
-2
lines changed

9 files changed

+638
-2
lines changed

include/swift/AST/DiagnosticsFrontend.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -603,5 +603,8 @@ GROUPED_WARNING(command_line_conflicts_with_strict_safety,Unsafe,none,
603603
"'%0' is not memory-safe and should not be combined with "
604604
"strict memory safety checking", (StringRef))
605605

606+
ERROR(zlib_not_supported,none,
607+
"this compiler was not built with zlib compression support enabled", ())
608+
606609
#define UNDEFINE_DIAGNOSTIC_MACROS
607610
#include "DefineDiagnosticMacros.h"

include/swift/AST/SourceFile.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -686,6 +686,9 @@ class SourceFile final : public FileUnit {
686686
SWIFT_DEBUG_DUMP;
687687
void dump(raw_ostream &os, bool parseIfNeeded = false) const;
688688

689+
/// Dumps this source file's AST in JSON format to the given output stream.
690+
void dumpJSON(raw_ostream &os) const;
691+
689692
/// Pretty-print the contents of this source file.
690693
///
691694
/// \param Printer The AST printer used for printing the contents.

include/swift/Frontend/FrontendOptions.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,16 @@ class FrontendOptions {
433433
/// -dump-scope-maps.
434434
SmallVector<std::pair<unsigned, unsigned>, 2> DumpScopeMapLocations;
435435

436+
/// The possible output formats supported for dumping ASTs.
437+
enum class ASTFormat {
438+
Default, ///< S-expressions for debugging
439+
JSON, ///< Structured JSON for analysis
440+
JSONZlib, ///< Like JSON, but zlib-compressed
441+
};
442+
443+
/// The output format generated by the `-dump-ast` flag.
444+
ASTFormat DumpASTFormat = ASTFormat::Default;
445+
436446
/// Determines whether the static or shared resource folder is used.
437447
/// When set to `true`, the default resource folder will be set to
438448
/// '.../lib/swift', otherwise '.../lib/swift_static'.

include/swift/Option/Options.td

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1350,6 +1350,10 @@ def emit_parse : Flag<["-"], "emit-parse">, Alias<dump_parse>,
13501350
def dump_ast : Flag<["-"], "dump-ast">,
13511351
HelpText<"Parse and type-check input file(s) and dump AST(s)">, ModeOpt,
13521352
Flags<[FrontendOption, NoInteractiveOption, DoesNotAffectIncrementalBuild]>;
1353+
def dump_ast_format : Separate<["-"], "dump-ast-format">,
1354+
HelpText<"Desired format for -dump-ast output ('default', 'json', or 'json-zlib')">,
1355+
MetaVarName<"<format>">,
1356+
Flags<[FrontendOption, NoInteractiveOption, DoesNotAffectIncrementalBuild]>;
13531357
def emit_ast : Flag<["-"], "emit-ast">, Alias<dump_ast>,
13541358
Flags<[FrontendOption, NoInteractiveOption, DoesNotAffectIncrementalBuild]>;
13551359
def dump_scope_maps : Separate<["-"], "dump-scope-maps">,

lib/AST/ASTDumper.cpp

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,16 @@
3232
#include "swift/Basic/Defer.h"
3333
#include "swift/Basic/QuotedString.h"
3434
#include "swift/Basic/STLExtras.h"
35+
#include "swift/Basic/SourceLoc.h"
36+
#include "swift/Basic/SourceManager.h"
3537
#include "swift/Basic/StringExtras.h"
3638
#include "clang/AST/Type.h"
3739
#include "llvm/ADT/APFloat.h"
3840
#include "llvm/ADT/SmallString.h"
3941
#include "llvm/ADT/StringExtras.h"
4042
#include "llvm/Support/ErrorHandling.h"
4143
#include "llvm/Support/FileSystem.h"
44+
#include "llvm/Support/JSON.h"
4245
#include "llvm/Support/Process.h"
4346
#include "llvm/Support/SaveAndRestore.h"
4447
#include "llvm/Support/raw_ostream.h"
@@ -768,6 +771,111 @@ namespace {
768771
bool isParsable() const override { return false; }
769772
};
770773

774+
/// Implements JSON formatted output for `-ast-dump`.
775+
class JSONWriter : public PrintWriterBase {
776+
llvm::json::OStream OS;
777+
std::vector<bool> InObjectStack;
778+
779+
public:
780+
JSONWriter(raw_ostream &os, unsigned indent = 0) : OS(os, indent) {}
781+
782+
void printRecArbitrary(std::function<void(Label)> body,
783+
Label label) override {
784+
// The label is ignored if we're not printing inside an object (meaning
785+
// we must be in an array).
786+
if (InObjectStack.back()) {
787+
OS.attributeBegin(label.text());
788+
body(Label::optional(""));
789+
OS.attributeEnd();
790+
} else {
791+
body(label);
792+
}
793+
}
794+
795+
void printRecRange(std::function<void(Label)> body, Label label) override {
796+
printListArbitrary([&]{ body(label); }, label);
797+
}
798+
799+
void printListArbitrary(std::function<void()> body, Label label) override {
800+
OS.attributeBegin(label.text());
801+
OS.arrayBegin();
802+
InObjectStack.push_back(false);
803+
body();
804+
InObjectStack.pop_back();
805+
OS.arrayEnd();
806+
OS.attributeEnd();
807+
}
808+
809+
void printHead(StringRef name, TerminalColor color, Label label) override {
810+
OS.objectBegin();
811+
InObjectStack.push_back(true);
812+
OS.attribute(label.empty() ? "_kind" : label.text(), name);
813+
}
814+
815+
void printFoot() override {
816+
InObjectStack.pop_back();
817+
OS.objectEnd();
818+
}
819+
820+
void printFieldRaw(std::function<void(llvm::raw_ostream &)> body,
821+
Label label, TerminalColor color) override {
822+
std::string value;
823+
llvm::raw_string_ostream SOS(value);
824+
body(SOS);
825+
// The label is ignored if we're not printing inside an object (meaning
826+
// we must be in an array).
827+
if (InObjectStack.back()) {
828+
OS.attribute(label.text(), value);
829+
} else {
830+
OS.value(value);
831+
}
832+
}
833+
834+
void printFieldQuotedRaw(std::function<void(llvm::raw_ostream &)> body,
835+
Label label, TerminalColor color) override {
836+
// No need to do special quoting for complex values; the JSON output
837+
// stream will do this for us.
838+
printFieldRaw(body, label, color);
839+
}
840+
841+
void printFlagRaw(std::function<void(llvm::raw_ostream &)> body,
842+
TerminalColor color) override {
843+
std::string flag;
844+
llvm::raw_string_ostream SOS(flag);
845+
body(SOS);
846+
OS.attribute(flag, true);
847+
}
848+
849+
void printSourceLoc(const SourceLoc L, const ASTContext *Ctx,
850+
Label label) override {
851+
// For compactness, we only print source ranges in JSON, since they
852+
// provide a superset of this information.
853+
}
854+
855+
void printSourceRange(const SourceRange R,
856+
const ASTContext *Ctx) override {
857+
OS.attributeBegin("range");
858+
OS.objectBegin();
859+
860+
SourceManager &srcMgr = Ctx->SourceMgr;
861+
unsigned startBufferID = srcMgr.findBufferContainingLoc(R.Start);
862+
unsigned startOffset = srcMgr.getLocOffsetInBuffer(R.Start,
863+
startBufferID);
864+
OS.attribute("start", startOffset);
865+
866+
unsigned endBufferID = srcMgr.findBufferContainingLoc(R.End);
867+
unsigned endOffset = srcMgr.getLocOffsetInBuffer(R.End, endBufferID);
868+
OS.attribute("end", endOffset);
869+
870+
OS.objectEnd();
871+
OS.attributeEnd();
872+
}
873+
874+
bool hasNonStandardOutput() const override { return true; }
875+
876+
bool isParsable() const override { return true; }
877+
};
878+
771879
/// PrintBase - Base type for recursive structured dumps of AST nodes.
772880
///
773881
/// Please keep direct I/O, especially of structural elements like
@@ -2237,6 +2345,11 @@ void SourceFile::dump(llvm::raw_ostream &OS, bool parseIfNeeded) const {
22372345
llvm::errs() << '\n';
22382346
}
22392347

2348+
void SourceFile::dumpJSON(llvm::raw_ostream &OS) const {
2349+
JSONWriter writer(OS, /*indent*/ 2);
2350+
PrintDecl(writer, /*parseIfNeeded*/ true).visitSourceFile(*this);
2351+
}
2352+
22402353
void Pattern::dump() const {
22412354
dump(llvm::errs());
22422355
}

lib/Driver/ToolChains.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,7 @@ void ToolChain::addCommonFrontendArgs(const OutputInfo &OI,
352352
inputArgs.AddLastArg(arguments, options::OPT_compiler_assertions);
353353
inputArgs.AddLastArg(arguments, options::OPT_load_pass_plugin_EQ);
354354
inputArgs.AddAllArgs(arguments, options::OPT_module_alias);
355+
inputArgs.AddLastArg(arguments, options::OPT_dump_ast_format);
355356

356357
// Pass on any build config options
357358
inputArgs.AddAllArgs(arguments, options::OPT_D);

lib/Frontend/ArgsToFrontendOptionsConverter.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include "llvm/Option/Arg.h"
3131
#include "llvm/Option/ArgList.h"
3232
#include "llvm/Option/Option.h"
33+
#include "llvm/Support/Compression.h"
3334
#include "llvm/Support/Process.h"
3435
#include "llvm/Support/FileSystem.h"
3536
#include "llvm/Support/LineIterator.h"
@@ -204,6 +205,21 @@ bool ArgsToFrontendOptionsConverter::convert(
204205

205206
computeDumpScopeMapLocations();
206207

208+
// Ensure that the compiler was built with zlib support if it was the
209+
// requested AST format.
210+
if (const Arg *A = Args.getLastArg(OPT_dump_ast_format)) {
211+
Opts.DumpASTFormat =
212+
llvm::StringSwitch<FrontendOptions::ASTFormat>(A->getValue())
213+
.Case("json", FrontendOptions::ASTFormat::JSON)
214+
.Case("json-zlib", FrontendOptions::ASTFormat::JSONZlib)
215+
.Default(FrontendOptions::ASTFormat::Default);
216+
if (Opts.DumpASTFormat == FrontendOptions::ASTFormat::JSONZlib &&
217+
!llvm::compression::zlib::isAvailable()) {
218+
Diags.diagnose(SourceLoc(), diag::zlib_not_supported);
219+
return true;
220+
}
221+
}
222+
207223
std::optional<FrontendInputsAndOutputs> inputsAndOutputs =
208224
ArgsToFrontendInputsConverter(Diags, Args).convert(buffers);
209225

lib/FrontendTool/FrontendTool.cpp

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@
8282
#include "llvm/IRReader/IRReader.h"
8383
#include "llvm/Option/OptTable.h"
8484
#include "llvm/Option/Option.h"
85+
#include "llvm/Support/Compression.h"
8586
#include "llvm/Support/Error.h"
8687
#include "llvm/Support/ErrorHandling.h"
8788
#include "llvm/Support/FileSystem.h"
@@ -463,6 +464,28 @@ getPrimaryOrMainSourceFile(const CompilerInstance &Instance) {
463464
/// Dumps the AST of all available primary source files. If corresponding output
464465
/// files were specified, use them; otherwise, dump the AST to stdout.
465466
static bool dumpAST(CompilerInstance &Instance) {
467+
const FrontendOptions &opts = Instance.getInvocation().getFrontendOptions();
468+
auto dumpAST = [&](SourceFile *SF, raw_ostream &out) {
469+
switch (opts.DumpASTFormat) {
470+
case FrontendOptions::ASTFormat::Default:
471+
SF->dump(out, /*parseIfNeeded*/ true);
472+
break;
473+
case FrontendOptions::ASTFormat::JSON:
474+
SF->dumpJSON(out);
475+
break;
476+
case FrontendOptions::ASTFormat::JSONZlib:
477+
std::string jsonText;
478+
llvm::raw_string_ostream jsonTextStream(jsonText);
479+
SF->dumpJSON(jsonTextStream);
480+
481+
SmallVector<uint8_t, 0> compressed;
482+
llvm::compression::zlib::compress(llvm::arrayRefFromStringRef(jsonText),
483+
compressed);
484+
out << llvm::toStringRef(compressed);
485+
break;
486+
}
487+
};
488+
466489
auto primaryFiles = Instance.getPrimarySourceFiles();
467490
if (!primaryFiles.empty()) {
468491
for (SourceFile *sourceFile: primaryFiles) {
@@ -471,7 +494,7 @@ static bool dumpAST(CompilerInstance &Instance) {
471494
if (withOutputPath(Instance.getASTContext().Diags,
472495
Instance.getOutputBackend(), OutputFilename,
473496
[&](raw_ostream &out) -> bool {
474-
sourceFile->dump(out, /*parseIfNeeded*/ true);
497+
dumpAST(sourceFile, out);
475498
return false;
476499
}))
477500
return true;
@@ -480,7 +503,7 @@ static bool dumpAST(CompilerInstance &Instance) {
480503
// Some invocations don't have primary files. In that case, we default to
481504
// looking for the main file and dumping it to `stdout`.
482505
auto &SF = getPrimaryOrMainSourceFile(Instance);
483-
SF.dump(llvm::outs(), /*parseIfNeeded*/ true);
506+
dumpAST(&SF, llvm::outs());
484507
}
485508
return Instance.getASTContext().hadError();
486509
}

0 commit comments

Comments
 (0)