mirror of
https://github.com/github/codeql.git
synced 2026-05-02 20:25:13 +02:00
Swift: rework file redirection
The hash map mechanism that was already in use for reading swiftmodule files on macOS is now in use also on Linux. The output replacing mechanism has been also reworked so that: * frontend module emission modes have the remapping done directly in the internal frontend options instead of painstakingly modifying input flags (this requires a patch on the swift headers though) * object emission mode is silenced to be just a type checking pass, thus producing no output files * all other passes but some debugging and version related ones become noops The open file read redirection uses a global weak pointer instance to maximize robustness in the face of possibly multi-threaded calls to open happening while `main` is exiting. Possibly overkill, but better safe than sorry.
This commit is contained in:
@@ -9,6 +9,7 @@ swift_cc_binary(
|
||||
]),
|
||||
visibility = ["//swift:__pkg__"],
|
||||
deps = [
|
||||
"//swift/extractor/config",
|
||||
"//swift/extractor/infra",
|
||||
"//swift/extractor/invocation",
|
||||
"//swift/extractor/remapping",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "swift/extractor/SwiftExtractorConfiguration.h"
|
||||
#include "swift/extractor/config/SwiftExtractorConfiguration.h"
|
||||
#include <swift/AST/SourceFile.h>
|
||||
#include <swift/Frontend/Frontend.h>
|
||||
#include <memory>
|
||||
|
||||
@@ -11,11 +11,6 @@ std::optional<TargetFile> createTargetTrapFile(const SwiftExtractorConfiguration
|
||||
for (const auto& opt : configuration.frontendOptions) {
|
||||
*ret << " " << std::quoted(opt) << " \\\n";
|
||||
}
|
||||
*ret << "\n*/\n"
|
||||
"/* swift-frontend-args:\n";
|
||||
for (const auto& opt : configuration.patchedFrontendOptions) {
|
||||
*ret << " " << std::quoted(opt) << " \\\n";
|
||||
}
|
||||
*ret << "\n*/\n";
|
||||
}
|
||||
return ret;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "swift/extractor/infra/file/TargetFile.h"
|
||||
#include "swift/extractor/SwiftExtractorConfiguration.h"
|
||||
#include "swift/extractor/config/SwiftExtractorConfiguration.h"
|
||||
|
||||
namespace codeql {
|
||||
|
||||
|
||||
8
swift/extractor/config/BUILD.bazel
Normal file
8
swift/extractor/config/BUILD.bazel
Normal file
@@ -0,0 +1,8 @@
|
||||
load("//swift:rules.bzl", "swift_cc_library")
|
||||
|
||||
swift_cc_library(
|
||||
name = "config",
|
||||
srcs = glob(["*.cpp"]),
|
||||
hdrs = glob(["*.h"]),
|
||||
visibility = ["//swift:__subpackages__"],
|
||||
)
|
||||
@@ -17,9 +17,7 @@ struct SwiftExtractorConfiguration {
|
||||
std::filesystem::path scratchDir;
|
||||
|
||||
// The original arguments passed to the extractor. Used for debugging.
|
||||
std::vector<std::string> frontendOptions;
|
||||
// The patched arguments passed to the swift::performFrontend/ Used for debugging.
|
||||
std::vector<std::string> patchedFrontendOptions;
|
||||
std::vector<const char*> frontendOptions;
|
||||
|
||||
// A temporary directory that contains TRAP files before they are moved into their final
|
||||
// destination.
|
||||
@@ -12,13 +12,58 @@
|
||||
|
||||
#include "swift/extractor/SwiftExtractor.h"
|
||||
#include "swift/extractor/TargetTrapFile.h"
|
||||
#include "swift/extractor/remapping/SwiftOutputRewrite.h"
|
||||
#include "swift/extractor/remapping/SwiftOpenInterception.h"
|
||||
#include "swift/extractor/remapping/SwiftFileInterception.h"
|
||||
#include "swift/extractor/invocation/SwiftDiagnosticsConsumer.h"
|
||||
#include "swift/extractor/trap/TrapDomain.h"
|
||||
|
||||
using namespace std::string_literals;
|
||||
|
||||
static void lockOutputSwiftModuleTraps(const codeql::SwiftExtractorConfiguration& config,
|
||||
const swift::CompilerInstance& compiler) {
|
||||
std::filesystem::path output = compiler.getInvocation().getOutputFilename();
|
||||
if (output.extension() == ".swiftmodule") {
|
||||
if (auto target = codeql::createTargetTrapFile(config, output)) {
|
||||
*target << "// trap file deliberately empty\n"
|
||||
"// this swiftmodule was created during the build, so its entities must have"
|
||||
" been extracted directly from source files";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void modifyFrontendOptions(swift::FrontendOptions& options) {
|
||||
using Action = swift::FrontendOptions::ActionType;
|
||||
switch (options.RequestedAction) {
|
||||
case Action::EmitModuleOnly:
|
||||
case Action::MergeModules:
|
||||
case Action::CompileModuleFromInterface:
|
||||
// for module emission actions, we redirect the output to our internal artifact storage
|
||||
{
|
||||
swift::SupplementaryOutputPaths paths;
|
||||
paths.ModuleOutputPath =
|
||||
codeql::redirect(options.InputsAndOutputs.getSingleOutputFilename()).string();
|
||||
options.InputsAndOutputs.setMainAndSupplementaryOutputs(std::vector{paths.ModuleOutputPath},
|
||||
std::vector{paths});
|
||||
return;
|
||||
}
|
||||
case Action::EmitObject:
|
||||
// for object emission, we do a type check pass instead, muting output but getting the sema
|
||||
// phase to run in order to extract everything
|
||||
options.RequestedAction = Action::Typecheck;
|
||||
return;
|
||||
case Action::PrintVersion:
|
||||
case Action::DumpAST:
|
||||
case Action::PrintAST:
|
||||
case Action::PrintASTDecl:
|
||||
// these actions are nice to have on the extractor for debugging, so we preserve them. Also,
|
||||
// version printing is used by CI to match up the correct compiler version
|
||||
return;
|
||||
default:
|
||||
// otherwise, do nothing (the closest action to doing nothing is printing the version)
|
||||
options.RequestedAction = Action::PrintVersion;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// This is part of the swiftFrontendTool interface, we hook into the
|
||||
// compilation pipeline and extract files after the Swift frontend performed
|
||||
// semantic analysis
|
||||
@@ -28,8 +73,13 @@ class Observer : public swift::FrontendObserver {
|
||||
codeql::SwiftDiagnosticsConsumer& diagConsumer)
|
||||
: config{config}, diagConsumer{diagConsumer} {}
|
||||
|
||||
void parsedArgs(swift::CompilerInvocation& invocation) override {
|
||||
modifyFrontendOptions(invocation.getFrontendOptions());
|
||||
}
|
||||
|
||||
void configuredCompiler(swift::CompilerInstance& instance) override {
|
||||
instance.addDiagnosticConsumer(&diagConsumer);
|
||||
lockOutputSwiftModuleTraps(config, instance);
|
||||
}
|
||||
|
||||
void performedSemanticAnalysis(swift::CompilerInstance& compiler) override {
|
||||
@@ -48,19 +98,6 @@ static std::string getenv_or(const char* envvar, const std::string& def) {
|
||||
return def;
|
||||
}
|
||||
|
||||
static void lockOutputSwiftModuleTraps(const codeql::SwiftExtractorConfiguration& config,
|
||||
const codeql::PathRemapping& remapping) {
|
||||
for (const auto& [oldPath, newPath] : remapping) {
|
||||
if (oldPath.extension() == ".swiftmodule") {
|
||||
if (auto target = codeql::createTargetTrapFile(config, oldPath)) {
|
||||
*target << "// trap file deliberately empty\n"
|
||||
"// this swiftmodule was created during the build, so its entities must have"
|
||||
" been extracted directly from source files";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool checkRunUnderFilter(int argc, char* const* argv) {
|
||||
auto runUnderFilter = getenv("CODEQL_EXTRACTOR_SWIFT_RUN_UNDER_FILTER");
|
||||
if (runUnderFilter == nullptr) {
|
||||
@@ -106,7 +143,7 @@ static void checkWhetherToRunUnderTool(int argc, char* const* argv) {
|
||||
|
||||
// Creates a target file that should store per-invocation info, e.g. compilation args,
|
||||
// compilations, diagnostics, etc.
|
||||
codeql::TargetFile invocationTargetFile(codeql::SwiftExtractorConfiguration& configuration) {
|
||||
codeql::TargetFile invocationTargetFile(const codeql::SwiftExtractorConfiguration& configuration) {
|
||||
auto timestamp = std::chrono::system_clock::now().time_since_epoch().count();
|
||||
auto filename = std::to_string(timestamp) + '-' + std::to_string(getpid());
|
||||
auto target = std::filesystem::path("invocations") / std::filesystem::path(filename);
|
||||
@@ -118,6 +155,15 @@ codeql::TargetFile invocationTargetFile(codeql::SwiftExtractorConfiguration& con
|
||||
return std::move(maybeFile.value());
|
||||
}
|
||||
|
||||
codeql::SwiftExtractorConfiguration configure(int argc, char** argv) {
|
||||
codeql::SwiftExtractorConfiguration configuration{};
|
||||
configuration.trapDir = getenv_or("CODEQL_EXTRACTOR_SWIFT_TRAP_DIR", ".");
|
||||
configuration.sourceArchiveDir = getenv_or("CODEQL_EXTRACTOR_SWIFT_SOURCE_ARCHIVE_DIR", ".");
|
||||
configuration.scratchDir = getenv_or("CODEQL_EXTRACTOR_SWIFT_SCRATCH_DIR", ".");
|
||||
configuration.frontendOptions.assign(argv + 1, argv + argc);
|
||||
return configuration;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
checkWhetherToRunUnderTool(argc, argv);
|
||||
|
||||
@@ -130,36 +176,16 @@ int main(int argc, char** argv) {
|
||||
PROGRAM_START(argc, argv);
|
||||
INITIALIZE_LLVM();
|
||||
|
||||
codeql::SwiftExtractorConfiguration configuration{};
|
||||
configuration.trapDir = getenv_or("CODEQL_EXTRACTOR_SWIFT_TRAP_DIR", ".");
|
||||
configuration.sourceArchiveDir = getenv_or("CODEQL_EXTRACTOR_SWIFT_SOURCE_ARCHIVE_DIR", ".");
|
||||
configuration.scratchDir = getenv_or("CODEQL_EXTRACTOR_SWIFT_SCRATCH_DIR", ".");
|
||||
const auto configuration = configure(argc, argv);
|
||||
|
||||
codeql::initRemapping(configuration.getTempArtifactDir());
|
||||
|
||||
configuration.frontendOptions.reserve(argc - 1);
|
||||
for (int i = 1; i < argc; i++) {
|
||||
configuration.frontendOptions.push_back(argv[i]);
|
||||
}
|
||||
configuration.patchedFrontendOptions = configuration.frontendOptions;
|
||||
|
||||
auto remapping = codeql::rewriteOutputsInPlace(configuration.getTempArtifactDir(),
|
||||
configuration.patchedFrontendOptions);
|
||||
codeql::ensureDirectoriesForNewPathsExist(remapping);
|
||||
lockOutputSwiftModuleTraps(configuration, remapping);
|
||||
|
||||
std::vector<const char*> args;
|
||||
for (auto& arg : configuration.patchedFrontendOptions) {
|
||||
args.push_back(arg.c_str());
|
||||
}
|
||||
auto openInterception = codeql::setupFileInterception(configuration.getTempArtifactDir());
|
||||
|
||||
auto invocationTrapFile = invocationTargetFile(configuration);
|
||||
codeql::TrapDomain invocationDomain(invocationTrapFile);
|
||||
codeql::SwiftDiagnosticsConsumer diagConsumer(invocationDomain);
|
||||
Observer observer(configuration, diagConsumer);
|
||||
int frontend_rc = swift::performFrontend(args, "swift-extractor", (void*)main, &observer);
|
||||
|
||||
codeql::finalizeRemapping(remapping);
|
||||
int frontend_rc = swift::performFrontend(configuration.frontendOptions, "swift-extractor",
|
||||
(void*)main, &observer);
|
||||
|
||||
return frontend_rc;
|
||||
}
|
||||
|
||||
@@ -2,23 +2,11 @@ load("//swift:rules.bzl", "swift_cc_library")
|
||||
|
||||
swift_cc_library(
|
||||
name = "remapping",
|
||||
srcs = ["SwiftOutputRewrite.cpp"] + select({
|
||||
"@platforms//os:linux": [
|
||||
"SwiftOpenInterception.Linux.cpp",
|
||||
],
|
||||
"@platforms//os:macos": [
|
||||
"SwiftOpenInterception.macOS.cpp",
|
||||
],
|
||||
}),
|
||||
srcs = glob(["*.cpp"]),
|
||||
hdrs = glob(["*.h"]),
|
||||
visibility = ["//swift:__subpackages__"],
|
||||
deps = [
|
||||
"//swift/third_party/swift-llvm-support",
|
||||
"//swift/extractor/infra/file",
|
||||
] + select({
|
||||
"@platforms//os:linux": [],
|
||||
"@platforms//os:macos": [
|
||||
"@fishhook//:fishhook",
|
||||
],
|
||||
}),
|
||||
"//swift/third_party/swift-llvm-support",
|
||||
],
|
||||
)
|
||||
|
||||
160
swift/extractor/remapping/SwiftFileInterception.cpp
Normal file
160
swift/extractor/remapping/SwiftFileInterception.cpp
Normal file
@@ -0,0 +1,160 @@
|
||||
#include "swift/extractor/remapping/SwiftFileInterception.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <filesystem>
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <cassert>
|
||||
|
||||
#include "swift/extractor/infra/file/FileHash.h"
|
||||
#include "swift/extractor/infra/file/FileHash.h"
|
||||
|
||||
#ifdef __APPLE__
|
||||
#define SHARED_LIBC "libc.dylib"
|
||||
#define FILE_CREATION_MODE O_CREAT
|
||||
#else
|
||||
#define SHARED_LIBC "libc.so.6"
|
||||
#define FILE_CREATION_MODE (O_CREAT | O_TMPFILE)
|
||||
#endif
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace {
|
||||
namespace original {
|
||||
|
||||
void* libc() {
|
||||
static auto ret = dlopen(SHARED_LIBC, RTLD_LAZY);
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename Signature>
|
||||
Signature get(const char* name) {
|
||||
return reinterpret_cast<Signature>(dlsym(libc(), name));
|
||||
}
|
||||
|
||||
int open(const char* path, int flags, mode_t mode = 0) {
|
||||
static auto original = get<int (*)(const char*, int, ...)>("open");
|
||||
return original(path, flags, mode);
|
||||
}
|
||||
|
||||
} // namespace original
|
||||
|
||||
bool endsWith(const std::string_view& s, const std::string_view& suffix) {
|
||||
return s.size() >= suffix.size() && s.substr(s.size() - suffix.size()) == suffix;
|
||||
}
|
||||
|
||||
auto& fileInterceptorInstance() {
|
||||
static std::weak_ptr<codeql::FileInterceptor> ret{};
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool mayBeRedirected(const char* path, int flags = O_RDONLY) {
|
||||
return (!fileInterceptorInstance().expired() && (flags & O_ACCMODE) == O_RDONLY &&
|
||||
endsWith(path, ".swiftmodule"));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace codeql {
|
||||
|
||||
class FileInterceptor {
|
||||
public:
|
||||
FileInterceptor(fs::path&& workingDir) : workingDir{std::move(workingDir)} {
|
||||
fs::create_directories(hashesPath());
|
||||
fs::create_directories(storePath());
|
||||
}
|
||||
|
||||
int open(const char* path, int flags, mode_t mode = 0) const {
|
||||
fs::path fsPath{path};
|
||||
assert((flags & O_ACCMODE) == O_RDONLY);
|
||||
errno = 0;
|
||||
// first, try the same path underneath the artifact store
|
||||
if (auto ret = original::open(redirectedPath(path).c_str(), flags);
|
||||
ret >= 0 || errno != ENOENT) {
|
||||
return ret;
|
||||
}
|
||||
errno = 0;
|
||||
// then try to use the hash map
|
||||
if (auto hashed = hashPath(path)) {
|
||||
if (auto ret = original::open(hashed->c_str(), flags); ret >= 0 || errno != ENOENT) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
return original::open(path, flags, mode);
|
||||
}
|
||||
|
||||
fs::path redirect(const fs::path& target) const {
|
||||
assert(mayBeRedirected(target.c_str()));
|
||||
auto ret = redirectedPath(target);
|
||||
fs::create_directories(ret.parent_path());
|
||||
if (auto hashed = hashPath(target)) {
|
||||
std::error_code ec;
|
||||
fs::create_symlink(ret, *hashed, ec);
|
||||
if (ec) {
|
||||
std::cerr << "Cannot remap file " << ret << " -> " << *hashed << ": " << ec.message()
|
||||
<< "\n";
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private:
|
||||
fs::path hashesPath() const { return workingDir / "hashes"; }
|
||||
|
||||
fs::path storePath() const { return workingDir / "store"; }
|
||||
|
||||
fs::path redirectedPath(const fs::path& target) const {
|
||||
return storePath() / target.relative_path();
|
||||
}
|
||||
|
||||
std::optional<fs::path> hashPath(const fs::path& target) const {
|
||||
if (auto fd = original::open(target.c_str(), O_RDONLY | O_CLOEXEC); fd >= 0) {
|
||||
return hashesPath() / hashFile(fd);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
fs::path workingDir;
|
||||
};
|
||||
|
||||
int openReal(const fs::path& path) {
|
||||
return original::open(path.c_str(), O_RDONLY | O_CLOEXEC);
|
||||
}
|
||||
|
||||
fs::path redirect(const fs::path& target) {
|
||||
if (auto interceptor = fileInterceptorInstance().lock()) {
|
||||
return interceptor->redirect(target);
|
||||
} else {
|
||||
return target;
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<FileInterceptor> setupFileInterception(fs::path workginDir) {
|
||||
auto ret = std::make_shared<FileInterceptor>(std::move(workginDir));
|
||||
fileInterceptorInstance() = ret;
|
||||
return ret;
|
||||
}
|
||||
} // namespace codeql
|
||||
|
||||
extern "C" {
|
||||
int open(const char* path, int flags, ...) {
|
||||
mode_t mode = 0;
|
||||
if (flags & FILE_CREATION_MODE) {
|
||||
va_list ap;
|
||||
// mode only applies when creating a file
|
||||
va_start(ap, flags);
|
||||
mode = va_arg(ap, int);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
if (mayBeRedirected(path, flags)) {
|
||||
if (auto interceptor = fileInterceptorInstance().lock()) {
|
||||
return interceptor->open(path, flags, mode);
|
||||
}
|
||||
}
|
||||
return original::open(path, flags, mode);
|
||||
}
|
||||
|
||||
} // namespace codeql
|
||||
19
swift/extractor/remapping/SwiftFileInterception.h
Normal file
19
swift/extractor/remapping/SwiftFileInterception.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
|
||||
#include "swift/extractor/infra/file/PathHash.h"
|
||||
|
||||
namespace codeql {
|
||||
|
||||
int openReal(const std::filesystem::path& path);
|
||||
|
||||
class FileInterceptor;
|
||||
|
||||
std::shared_ptr<FileInterceptor> setupFileInterception(std::filesystem::path workingDir);
|
||||
|
||||
std::filesystem::path redirect(const std::filesystem::path& target);
|
||||
} // namespace codeql
|
||||
@@ -1,9 +0,0 @@
|
||||
#include "swift/extractor/remapping/SwiftOpenInterception.h"
|
||||
|
||||
namespace codeql {
|
||||
// TBD
|
||||
void initRemapping(const std::filesystem::path& dir) {}
|
||||
void finalizeRemapping(
|
||||
const std::unordered_map<std::filesystem::path, std::filesystem::path>& mapping) {}
|
||||
|
||||
} // namespace codeql
|
||||
@@ -1,15 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <filesystem>
|
||||
|
||||
#include "swift/extractor/infra/file/PathHash.h"
|
||||
|
||||
namespace codeql {
|
||||
|
||||
void initRemapping(const std::filesystem::path& dir);
|
||||
void finalizeRemapping(
|
||||
const std::unordered_map<std::filesystem::path, std::filesystem::path>& mapping);
|
||||
|
||||
} // namespace codeql
|
||||
@@ -1,85 +0,0 @@
|
||||
#include "swift/extractor/remapping/SwiftOpenInterception.h"
|
||||
|
||||
#include <fishhook.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <filesystem>
|
||||
|
||||
#include "swift/extractor/infra/file/FileHash.h"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace codeql {
|
||||
|
||||
static fs::path scratchDir;
|
||||
static bool interceptionEnabled = false;
|
||||
|
||||
static int (*original_open)(const char*, int, ...) = nullptr;
|
||||
|
||||
static std::string originalHashFile(const fs::path& filename) {
|
||||
int fd = original_open(filename.c_str(), O_RDONLY);
|
||||
if (fd == -1) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return hashFile(fd);
|
||||
}
|
||||
|
||||
static int codeql_open(const char* path, int oflag, ...) {
|
||||
va_list ap;
|
||||
mode_t mode = 0;
|
||||
if ((oflag & O_CREAT) != 0) {
|
||||
// mode only applies to O_CREAT
|
||||
va_start(ap, oflag);
|
||||
mode = va_arg(ap, int);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
fs::path newPath(path);
|
||||
|
||||
if (interceptionEnabled && fs::exists(newPath)) {
|
||||
// TODO: check file magic instead
|
||||
if (newPath.extension() == ".swiftmodule") {
|
||||
auto hash = originalHashFile(newPath);
|
||||
auto hashed = scratchDir / hash;
|
||||
if (!hash.empty() && fs::exists(hashed)) {
|
||||
newPath = hashed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return original_open(newPath.c_str(), oflag, mode);
|
||||
}
|
||||
|
||||
void finalizeRemapping(
|
||||
const std::unordered_map<std::filesystem::path, std::filesystem::path>& mapping) {
|
||||
for (auto& [original, patched] : mapping) {
|
||||
// TODO: Check file magic instead
|
||||
if (original.extension() != ".swiftmodule") {
|
||||
continue;
|
||||
}
|
||||
auto hash = originalHashFile(original);
|
||||
auto hashed = scratchDir / hash;
|
||||
if (!hash.empty() && fs::exists(patched)) {
|
||||
std::error_code ec;
|
||||
fs::create_symlink(/* target */ patched, /* symlink */ hashed, ec);
|
||||
if (ec) {
|
||||
std::cerr << "Cannot remap file '" << patched << "' -> '" << hashed << "': " << ec.message()
|
||||
<< "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
interceptionEnabled = false;
|
||||
}
|
||||
|
||||
void initRemapping(const std::filesystem::path& dir) {
|
||||
scratchDir = dir;
|
||||
|
||||
struct rebinding binding[] = {
|
||||
{"open", reinterpret_cast<void*>(codeql_open), reinterpret_cast<void**>(&original_open)}};
|
||||
rebind_symbols(binding, 1);
|
||||
interceptionEnabled = true;
|
||||
}
|
||||
|
||||
} // namespace codeql
|
||||
@@ -1,137 +0,0 @@
|
||||
#include "swift/extractor/remapping/SwiftOutputRewrite.h"
|
||||
|
||||
#include <llvm/ADT/SmallString.h>
|
||||
#include <swift/Basic/OutputFileMap.h>
|
||||
#include <swift/Basic/Platform.h>
|
||||
#include <unistd.h>
|
||||
#include <unordered_set>
|
||||
#include <optional>
|
||||
#include <iostream>
|
||||
|
||||
#include "swift/extractor/infra/file/PathHash.h"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace codeql {
|
||||
|
||||
// Creates a copy of the output file map and updates remapping table in place
|
||||
// It does not change the original map file as it is depended upon by the original compiler
|
||||
// Returns path to the newly created output file map on success, or None in a case of failure
|
||||
static std::optional<fs::path> rewriteOutputFileMap(const fs::path& scratchDir,
|
||||
const fs::path& outputFileMapPath,
|
||||
const std::vector<fs::path>& inputs,
|
||||
PathRemapping& remapping) {
|
||||
auto newMapPath = scratchDir / outputFileMapPath.relative_path();
|
||||
|
||||
// TODO: do not assume absolute path for the second parameter
|
||||
auto outputMapOrError = swift::OutputFileMap::loadFromPath(outputFileMapPath.c_str(), "");
|
||||
if (!outputMapOrError) {
|
||||
std::cerr << "Cannot load output map " << outputFileMapPath << "\n";
|
||||
return std::nullopt;
|
||||
}
|
||||
auto oldOutputMap = outputMapOrError.get();
|
||||
swift::OutputFileMap newOutputMap;
|
||||
std::vector<llvm::StringRef> keys;
|
||||
for (auto& key : inputs) {
|
||||
auto oldMap = oldOutputMap.getOutputMapForInput(key.c_str());
|
||||
if (!oldMap) {
|
||||
continue;
|
||||
}
|
||||
keys.push_back(key.c_str());
|
||||
auto& newMap = newOutputMap.getOrCreateOutputMapForInput(key.c_str());
|
||||
newMap.copyFrom(*oldMap);
|
||||
for (auto& entry : newMap) {
|
||||
fs::path oldPath = entry.getSecond();
|
||||
auto newPath = scratchDir / oldPath.relative_path();
|
||||
entry.getSecond() = newPath;
|
||||
remapping[oldPath] = newPath;
|
||||
}
|
||||
}
|
||||
std::error_code ec;
|
||||
fs::create_directories(newMapPath.parent_path(), ec);
|
||||
if (ec) {
|
||||
std::cerr << "Cannot create relocated output map dir " << newMapPath.parent_path() << ": "
|
||||
<< ec.message() << "\n";
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
llvm::raw_fd_ostream fd(newMapPath.c_str(), ec, llvm::sys::fs::OF_None);
|
||||
newOutputMap.write(fd, keys);
|
||||
return newMapPath;
|
||||
}
|
||||
|
||||
PathRemapping rewriteOutputsInPlace(const fs::path& scratchDir, std::vector<std::string>& CLIArgs) {
|
||||
PathRemapping remapping;
|
||||
|
||||
// TODO: handle filelists?
|
||||
const std::unordered_set<std::string> pathRewriteOptions({
|
||||
"-emit-abi-descriptor-path",
|
||||
"-emit-dependencies-path",
|
||||
"-emit-module-path",
|
||||
"-emit-module-doc-path",
|
||||
"-emit-module-source-info-path",
|
||||
"-emit-objc-header-path",
|
||||
"-emit-reference-dependencies-path",
|
||||
"-index-store-path",
|
||||
"-index-unit-output-path",
|
||||
"-module-cache-path",
|
||||
"-o",
|
||||
"-pch-output-dir",
|
||||
"-serialize-diagnostics-path",
|
||||
});
|
||||
|
||||
std::unordered_set<std::string> outputFileMaps(
|
||||
{"-supplementary-output-file-map", "-output-file-map"});
|
||||
|
||||
std::vector<size_t> outputFileMapIndexes;
|
||||
std::vector<fs::path> maybeInput;
|
||||
std::string targetTriple;
|
||||
|
||||
std::vector<fs::path> newLocations;
|
||||
for (size_t i = 0; i < CLIArgs.size(); i++) {
|
||||
if (pathRewriteOptions.count(CLIArgs[i])) {
|
||||
fs::path oldPath = CLIArgs[i + 1];
|
||||
auto newPath = scratchDir / oldPath.relative_path();
|
||||
CLIArgs[++i] = newPath.string();
|
||||
newLocations.push_back(newPath);
|
||||
|
||||
remapping[oldPath] = newPath;
|
||||
} else if (outputFileMaps.count(CLIArgs[i])) {
|
||||
// collect output map indexes for further rewriting and skip the following argument
|
||||
// We don't patch the map in place as we need to collect all the input files first
|
||||
outputFileMapIndexes.push_back(++i);
|
||||
} else if (CLIArgs[i] == "-target") {
|
||||
targetTriple = CLIArgs[++i];
|
||||
} else if (CLIArgs[i][0] != '-') {
|
||||
// TODO: add support for input file lists?
|
||||
// We need to collect input file names to later use them to extract information from the
|
||||
// output file maps.
|
||||
maybeInput.push_back(CLIArgs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto index : outputFileMapIndexes) {
|
||||
auto oldPath = CLIArgs[index];
|
||||
auto maybeNewPath = rewriteOutputFileMap(scratchDir, oldPath, maybeInput, remapping);
|
||||
if (maybeNewPath) {
|
||||
const auto& newPath = maybeNewPath.value();
|
||||
CLIArgs[index] = newPath;
|
||||
remapping[oldPath] = newPath;
|
||||
}
|
||||
}
|
||||
|
||||
return remapping;
|
||||
}
|
||||
|
||||
void ensureDirectoriesForNewPathsExist(const PathRemapping& remapping) {
|
||||
for (auto& [_, newPath] : remapping) {
|
||||
std::error_code ec;
|
||||
fs::create_directories(newPath.parent_path(), ec);
|
||||
if (ec) {
|
||||
std::cerr << "Cannot create redirected directory " << newPath.parent_path() << ": "
|
||||
<< ec.message() << "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace codeql
|
||||
@@ -1,21 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include "swift/extractor/infra/file/PathHash.h"
|
||||
|
||||
namespace codeql {
|
||||
|
||||
using PathRemapping = std::unordered_map<std::filesystem::path, std::filesystem::path>;
|
||||
// Rewrites all the output CLI args to point to a scratch dir instead of the actual locations.
|
||||
// This is needed to ensure that the artifacts produced by the extractor do not collide with the
|
||||
// artifacts produced by the actual Swift compiler.
|
||||
// Returns the map containing remapping oldpath -> newPath.
|
||||
PathRemapping rewriteOutputsInPlace(const std::filesystem::path& scratchDir,
|
||||
std::vector<std::string>& CLIArgs);
|
||||
|
||||
// Create directories for all the redirected new paths as the Swift compiler expects them to exist.
|
||||
void ensureDirectoriesForNewPathsExist(const PathRemapping& remapping);
|
||||
|
||||
} // namespace codeql
|
||||
@@ -0,0 +1 @@
|
||||
func foo() {}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
func bar() {}
|
||||
|
||||
@@ -15,4 +15,4 @@ $FRONTEND -frontend -c -primary-file D.swift -o D.o $SDK
|
||||
$FRONTEND -frontend -c -primary-file E.swift Esup.swift -o E.o $SDK
|
||||
$FRONTEND -frontend -emit-module -primary-file F1.swift F2.swift -module-name F -o F1.swiftmodule $SDK
|
||||
$FRONTEND -frontend -emit-module F1.swift -primary-file F2.swift -module-name F -o F2.swiftmodule $SDK
|
||||
$FRONTEND -merge-modules F1.swiftmodule F2.swiftmodule -o F.swiftmodule $SDK
|
||||
$FRONTEND -frontend -merge-modules F1.swiftmodule F2.swiftmodule -o F.swiftmodule $SDK
|
||||
|
||||
Reference in New Issue
Block a user