| //===- 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/ScalableStaticAnalysis/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 <cassert> | |
| #include <memory> | |
| #include <string> | |
| 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}"; | |
| constexpr const char *InvalidTargetTriple = | |
| "invalid {0} '{1}': unrecognized architecture"; | |
| } // 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<std::string, std::unique_ptr<SerializationFormat>>, 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<std::string> 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); | |
| } | |
| } | |
| } | |
| llvm::Triple clang::ssaf::parseTargetTripleOrFail(llvm::StringRef FlagName, | |
| llvm::StringRef Value) { | |
| assert(!Value.empty() && | |
| "parseTargetTripleOrFail: triple value cannot be empty"); | |
| // Normalize so the components are moved to their proper places. | |
| llvm::Triple T(llvm::Triple::normalize(Value)); | |
| // Only the architecture is validated. Validating vendor or OS rejects real | |
| // targets like x86_64-unknown-linux-gnu. A misspelled vendor or OS is instead | |
| // caught as a triple mismatch during linking or library creation. | |
| if (T.getArch() == llvm::Triple::UnknownArch) { | |
| fail(ErrorMessages::InvalidTargetTriple, FlagName, Value); | |
| } | |
| return T; | |
| } | |
| 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); | |
| } |