diff --git a/clang/include/clang/Analysis/FlowSensitive/Logger.h b/clang/include/clang/Analysis/FlowSensitive/Logger.h --- a/clang/include/clang/Analysis/FlowSensitive/Logger.h +++ b/clang/include/clang/Analysis/FlowSensitive/Logger.h @@ -31,6 +31,10 @@ /// A logger that simply writes messages to the specified ostream in real /// time. static std::unique_ptr textual(llvm::raw_ostream &); + /// A logger that builds an HTML UI to inspect the analysis results. + /// Each function's analysis is written to a stream obtained from the factory. + static std::unique_ptr + html(std::function()>); virtual ~Logger() = default; diff --git a/clang/lib/Analysis/FlowSensitive/CMakeLists.txt b/clang/lib/Analysis/FlowSensitive/CMakeLists.txt --- a/clang/lib/Analysis/FlowSensitive/CMakeLists.txt +++ b/clang/lib/Analysis/FlowSensitive/CMakeLists.txt @@ -2,6 +2,7 @@ ControlFlowContext.cpp DataflowAnalysisContext.cpp DataflowEnvironment.cpp + HTMLLogger.cpp Logger.cpp Transfer.cpp TypeErasedDataflowAnalysis.cpp @@ -16,3 +17,14 @@ ) add_subdirectory(Models) + +add_custom_command(OUTPUT HTMLLogger.inc + COMMAND "${Python3_EXECUTABLE}" ${CLANG_SOURCE_DIR}/utils/bundle_resources.py + ${CMAKE_CURRENT_BINARY_DIR}/HTMLLogger.inc + HTMLLogger.css HTMLLogger.js + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMENT "Bundling HTMLLogger resources" + DEPENDS ${CLANG_SOURCE_DIR}/utils/bundle_resources.py HTMLLogger.css HTMLLogger.js + VERBATIM) +add_custom_target(clangAnalysisFlowSensitiveResources DEPENDS HTMLLogger.inc) +add_dependencies(clangAnalysisFlowSensitive clangAnalysisFlowSensitiveResources) \ No newline at end of file diff --git a/clang/lib/Analysis/FlowSensitive/DataflowAnalysisContext.cpp b/clang/lib/Analysis/FlowSensitive/DataflowAnalysisContext.cpp --- a/clang/lib/Analysis/FlowSensitive/DataflowAnalysisContext.cpp +++ b/clang/lib/Analysis/FlowSensitive/DataflowAnalysisContext.cpp @@ -20,14 +20,17 @@ #include "llvm/ADT/SetOperations.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/Debug.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Path.h" #include #include #include -static llvm::cl::opt - DataflowLog("dataflow-log", llvm::cl::Hidden, llvm::cl::ValueOptional, - llvm::cl::desc("Emit log of dataflow analysis. With no arg, " - "writes textual log to stderr.")); +static llvm::cl::opt DataflowLog( + "dataflow-log", llvm::cl::Hidden, llvm::cl::ValueOptional, + llvm::cl::desc("Emit log of dataflow analysis. With no arg, writes textual " + "log to stderr. With an arg, writes HTML logs under the " + "specified directory (one per analyzed function).")); namespace clang { namespace dataflow { @@ -382,6 +385,34 @@ return nullptr; } +std::unique_ptr flagLogger() { + if (DataflowLog.empty()) + return Logger::textual(llvm::errs()); + + llvm::StringRef Dir = DataflowLog; + if (auto EC = llvm::sys::fs::create_directories(Dir)) + llvm::errs() << "Failed to create log dir: " << EC.message() << "\n"; + // Separate analyses will create loggers writing to the same directory. + // Share a counter so they don't all overwrite each other's 0.html. + // (Don't share a logger, it's not threadsafe). + static std::atomic Counter = {0}; + auto StreamFactory = + [Dir(Dir.str())]() mutable -> std::unique_ptr { + llvm::SmallString<256> File(Dir); + llvm::sys::path::append(File, + std::to_string(Counter.fetch_add(1)) + ".html"); + std::error_code EC; + auto OS = std::make_unique(File, EC); + if (EC) { + llvm::errs() << "Failed to create log " << File << ": " << EC.message() + << "\n"; + return std::make_unique(); + } + return OS; + }; + return Logger::html(std::move(StreamFactory)); +} + DataflowAnalysisContext::DataflowAnalysisContext(std::unique_ptr S, Options Opts) : S(std::move(S)), TrueVal(createAtomicBoolValue()), @@ -392,7 +423,7 @@ // based tools. if (Opts.Log == nullptr) { if (DataflowLog.getNumOccurrences() > 0) { - LogOwner = Logger::textual(llvm::errs()); + LogOwner = flagLogger(); this->Opts.Log = LogOwner.get(); // FIXME: if the flag is given a value, write an HTML log to a file. } else { diff --git a/clang/lib/Analysis/FlowSensitive/HTMLLogger.cpp b/clang/lib/Analysis/FlowSensitive/HTMLLogger.cpp new file mode 100644 --- /dev/null +++ b/clang/lib/Analysis/FlowSensitive/HTMLLogger.cpp @@ -0,0 +1,486 @@ +//===-- HTMLLogger.cpp ----------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file implements the HTML logger. Given a directory dir/, we write +// dir/0.html for the first analysis, etc. +// These files contain a visualization that allows inspecting the CFG and the +// state of the analysis at each point. +// Static assets (HTMLLogger.js, HTMLLogger.css) and SVG graphs etc are embedded +// so each output file is self-contained. +// +// VIEWS +// +// The timeline and function view are always shown. These allow selecting basic +// blocks, statements within them, and processing iterations (BBs are visited +// multiple times when e.g. loops are involved). +// These are written directly into the HTML body. +// +// There are also listings of particular basic blocks, and dumps of the state +// at particular analysis points (i.e. BB2 iteration 3 statement 2). +// These are only shown when the relevant BB/analysis point is *selected*. +// These are defined inside