//===- Utils.cpp - Shared utilities for SSAF tools ------------------------===// // // 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 // //===----------------------------------------------------------------------===// #include "clang/ScalableStaticAnalysisFramework/Tool/Utils.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallVector.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/DynamicLibrary.h" #include "llvm/Support/Error.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/FormatVariadic.h" #include "llvm/Support/Path.h" #include "llvm/Support/Process.h" #include "llvm/Support/WithColor.h" #include "llvm/Support/raw_ostream.h" #include #include #include using namespace clang::ssaf; namespace fs = llvm::sys::fs; namespace path = llvm::sys::path; namespace { //===----------------------------------------------------------------------===// // Error Messages //===----------------------------------------------------------------------===// namespace ErrorMessages { constexpr const char *CannotValidatePath = "failed to validate path '{0}': {1}"; constexpr const char *ExtensionNotSupplied = "Extension not supplied"; constexpr const char *NoFormatForExtension = "No format registered for extension '{0}'"; constexpr const char *PathDoesNotExist = "Path does not exist"; constexpr const char *PathIsNotAFile = "Path is not a file"; constexpr const char *OutputDirectoryMissing = "Parent directory does not exist"; constexpr const char *OutputDirectoryNotWritable = "Parent directory is not writable"; constexpr const char *FileAlreadyExists = "File already exists"; constexpr const char *FailedToLoadPlugin = "failed to load plugin '{0}': {1}"; } // namespace ErrorMessages llvm::StringRef ToolName; llvm::StringRef ToolVersion; void printVersion(llvm::raw_ostream &OS) { OS << ToolName << " " << ToolVersion << "\n"; } // Returns the SerializationFormat registered for \p Extension, or nullptr if // none is registered. Results are cached for the lifetime of the process. // FIXME: This will be revisited after we add support for registering formats // with extensions. SerializationFormat *getFormatForExtension(llvm::StringRef Extension) { // This cache is not thread-safe. SSAF tools are single-threaded CLIs, so // concurrent calls to this function are not expected. // Realistically, we don't expect to encounter more than four registered // formats. static llvm::SmallVector< std::pair>, 4> ExtensionFormatList; // Most recently used format is most likely to be reused again. auto ReversedList = llvm::reverse(ExtensionFormatList); auto It = llvm::find_if(ReversedList, [&](const auto &Entry) { return Entry.first == Extension; }); if (It != ReversedList.end()) { return It->second.get(); } if (!isFormatRegistered(Extension)) { return nullptr; } auto Format = makeFormat(Extension); SerializationFormat *Result = Format.get(); assert(Result && "makeFormat must return non-null for a registered extension"); ExtensionFormatList.emplace_back(Extension, std::move(Format)); return Result; } FormatFile fromPath(llvm::StringRef Path) { llvm::StringRef Extension = path::extension(Path); if (Extension.empty()) { fail(ErrorMessages::CannotValidatePath, Path, ErrorMessages::ExtensionNotSupplied); } Extension = Extension.drop_front(); SerializationFormat *Format = getFormatForExtension(Extension); if (!Format) { std::string BadExtension = llvm::formatv(ErrorMessages::NoFormatForExtension, Extension); fail(ErrorMessages::CannotValidatePath, Path, BadExtension); } return {Path.str(), Format}; } } // namespace llvm::StringRef clang::ssaf::getToolName() { return ToolName; } [[noreturn]] void clang::ssaf::fail(const char *Msg) { llvm::WithColor::error(llvm::errs(), ToolName) << Msg << "\n"; llvm::sys::Process::Exit(1); } [[noreturn]] void clang::ssaf::fail(llvm::Error Err) { std::string Message = llvm::toString(std::move(Err)); clang::ssaf::fail(Message.data()); } void clang::ssaf::loadPlugins(llvm::ArrayRef Paths) { for (const std::string &PluginPath : Paths) { if (!fs::exists(PluginPath)) { fail(ErrorMessages::FailedToLoadPlugin, PluginPath, ErrorMessages::PathDoesNotExist); } std::string ErrMsg; if (llvm::sys::DynamicLibrary::LoadLibraryPermanently(PluginPath.c_str(), &ErrMsg)) { fail(ErrorMessages::FailedToLoadPlugin, PluginPath, ErrMsg); } } } void clang::ssaf::initTool(int argc, const char **argv, llvm::StringRef Version, llvm::cl::OptionCategory &Category, llvm::StringRef ToolHeading) { // path::stem strips the .exe extension on Windows so ToolName is consistent. ToolName = path::stem(argv[0]); // Set tool version for the version printer. ToolVersion = Version; // Hide options unrelated to the tool from --help output. llvm::cl::HideUnrelatedOptions(Category); // Register a custom version printer for the --version flag. llvm::cl::SetVersionPrinter(printVersion); // Parse command-line arguments and exit with an error if they are invalid. std::string Overview = (ToolHeading + "\n").str(); llvm::cl::ParseCommandLineOptions(argc, argv, Overview); } clang::ssaf::FormatFile clang::ssaf::FormatFile::fromInputPath(llvm::StringRef Path) { if (!fs::exists(Path)) { fail(ErrorMessages::CannotValidatePath, Path, ErrorMessages::PathDoesNotExist); } if (!fs::is_regular_file(Path)) { fail(ErrorMessages::CannotValidatePath, Path, ErrorMessages::PathIsNotAFile); } return fromPath(Path); } clang::ssaf::FormatFile clang::ssaf::FormatFile::fromOutputPath(llvm::StringRef Path) { if (fs::exists(Path)) { fail(ErrorMessages::CannotValidatePath, Path, ErrorMessages::FileAlreadyExists); } llvm::StringRef ParentDir = path::parent_path(Path); llvm::StringRef DirToCheck = ParentDir.empty() ? "." : ParentDir; if (!fs::exists(DirToCheck)) { fail(ErrorMessages::CannotValidatePath, Path, ErrorMessages::OutputDirectoryMissing); } if (fs::access(DirToCheck, fs::AccessMode::Write)) { fail(ErrorMessages::CannotValidatePath, Path, ErrorMessages::OutputDirectoryNotWritable); } return fromPath(Path); }