[Macros] Add option to disable sandbox for exectuable plugins

`-disable-sandbox` to disable sandboxing when invoking subprocess from
from the frontend. Since `sandbox(7)` in macOS doesn't support nested
sandbox, complation used to fail when the parent build process is sandboxed.
This commit is contained in:
Rintaro Ishizaki
2023-11-17 13:42:16 -08:00
parent df30c12d46
commit 0c9f099d88
9 changed files with 116 additions and 11 deletions

View File

@@ -44,6 +44,7 @@ private:
ASTContext &Ctx;
DependencyTracker *DepTracker;
const bool disableSandbox;
/// Map a module name to an plugin entry that provides the module.
llvm::Optional<llvm::DenseMap<swift::Identifier, PluginEntry>> PluginMap;
@@ -52,8 +53,9 @@ private:
llvm::DenseMap<swift::Identifier, PluginEntry> &getPluginMap();
public:
PluginLoader(ASTContext &Ctx, DependencyTracker *DepTracker)
: Ctx(Ctx), DepTracker(DepTracker) {}
PluginLoader(ASTContext &Ctx, DependencyTracker *DepTracker,
bool disableSandbox = false)
: Ctx(Ctx), DepTracker(DepTracker), disableSandbox(disableSandbox) {}
void setRegistry(PluginRegistry *newValue);
PluginRegistry *getRegistry();

View File

@@ -89,6 +89,9 @@ class LoadedExecutablePlugin {
/// Callbacks to be called when the connection is restored.
llvm::SmallVector<std::function<void(void)> *, 0> onReconnect;
/// Disable sandbox.
bool disableSandbox = false;
/// Flag to dump plugin messagings.
bool dumpMessaging = false;
@@ -99,9 +102,11 @@ class LoadedExecutablePlugin {
public:
LoadedExecutablePlugin(llvm::StringRef ExecutablePath,
llvm::sys::TimePoint<> LastModificationTime)
llvm::sys::TimePoint<> LastModificationTime,
bool disableSandbox)
: ExecutablePath(ExecutablePath),
LastModificationTime(LastModificationTime){};
LastModificationTime(LastModificationTime),
disableSandbox(disableSandbox){};
~LoadedExecutablePlugin();
/// The last modification time of 'ExecutablePath' when this object is
@@ -181,7 +186,7 @@ public:
/// Load an executable plugin specified by \p path .
/// If \p path plugin is already loaded, this returns the cached object.
llvm::Expected<LoadedExecutablePlugin *>
loadExecutablePlugin(llvm::StringRef path);
loadExecutablePlugin(llvm::StringRef path, bool disableSandbox);
};
} // namespace swift

View File

@@ -415,6 +415,9 @@ public:
/// are present at LTO time.
bool HermeticSealAtLink = false;
/// Disable using the sandbox when executing subprocesses.
bool DisableSandbox = false;
/// The different modes for validating TBD against the LLVM IR.
enum class TBDValidationMode {
Default, ///< Do the default validation for the current platform.

View File

@@ -1948,4 +1948,9 @@ def load_plugin_executable:
"of module names where the macro types are declared">,
MetaVarName<"<path>#<module-names>">;
def disable_sandbox:
Flag<["-"], "disable-sandbox">,
Flags<[FrontendOption, DoesNotAffectIncrementalBuild]>,
HelpText<"Disable using the sandbox when executing subprocesses">;
include "FrontendOptions.td"

View File

@@ -199,7 +199,8 @@ PluginLoader::loadExecutablePlugin(StringRef path) {
DepTracker->addDependency(resolvedPath, /*IsSystem=*/false);
// Load the plugin.
auto plugin = getRegistry()->loadExecutablePlugin(resolvedPath);
auto plugin =
getRegistry()->loadExecutablePlugin(resolvedPath, disableSandbox);
if (!plugin) {
resolvedPath.push_back(0);
return llvm::handleErrors(

View File

@@ -84,7 +84,7 @@ void *LoadedLibraryPlugin::getAddressOfSymbol(const char *symbolName) {
}
llvm::Expected<LoadedExecutablePlugin *>
PluginRegistry::loadExecutablePlugin(StringRef path) {
PluginRegistry::loadExecutablePlugin(StringRef path, bool disableSandbox) {
llvm::sys::fs::file_status stat;
if (auto err = llvm::sys::fs::status(path, stat)) {
return llvm::errorCodeToError(err);
@@ -114,7 +114,7 @@ PluginRegistry::loadExecutablePlugin(StringRef path) {
}
auto plugin = std::make_unique<LoadedExecutablePlugin>(
path, stat.getLastModificationTime());
path, stat.getLastModificationTime(), disableSandbox);
plugin->setDumpMessaging(dumpMessaging);
@@ -147,7 +147,9 @@ llvm::Error LoadedExecutablePlugin::spawnIfNeeded() {
// Apply sandboxing.
llvm::BumpPtrAllocator Allocator;
Sandbox::apply(command, Allocator);
if (!disableSandbox) {
Sandbox::apply(command, Allocator);
}
// Launch.
auto childInfo = ExecuteWithPipe(command[0], command);

View File

@@ -395,6 +395,8 @@ bool ArgsToFrontendOptionsConverter::convert(
Opts.UseCASBackend = Args.hasArg(OPT_cas_backend);
Opts.EmitCASIDFile = Args.hasArg(OPT_cas_emit_casid_file);
Opts.DisableSandbox = Args.hasArg(OPT_disable_sandbox);
return false;
}

View File

@@ -812,8 +812,9 @@ bool CompilerInstance::setUpModuleLoaders() {
bool CompilerInstance::setUpPluginLoader() {
/// FIXME: If Invocation has 'PluginRegistry', we can set it. But should we?
auto loader =
std::make_unique<PluginLoader>(*Context, getDependencyTracker());
auto loader = std::make_unique<PluginLoader>(
*Context, getDependencyTracker(),
Invocation.getFrontendOptions().DisableSandbox);
Context->setPluginLoader(std::move(loader));
return false;
}

View File

@@ -0,0 +1,84 @@
// REQUIRES: swift_swift_parser
// sandbox-exec is only avaiable in Darwin
// REQUIRES: OS=macosx
// RUN: %empty-directory(%t)
// RUN: %empty-directory(%t/plugins)
// RUN: split-file %s %t
//== Build the plugins
// RUN: %host-build-swift \
// RUN: -swift-version 5 \
// RUN: -emit-library \
// RUN: -o %t/plugins/%target-library-name(MacroDefinition) \
// RUN: -module-name=MacroDefinition \
// RUN: %t/MacroDefinition.swift \
// RUN: -g -no-toolchain-stdlib-rpath
// RUN: %swift-build-cxx-plugin -o %t/mock-plugin %t/TestPlugin.c
//== Nested sandbox. Expected to fail because sandbox-exec doesn't support nested sandboxing.
// RUN: not sandbox-exec -p '(version 1)(allow default)' \
// RUN: %swift-target-frontend \
// RUN: -typecheck -verify \
// RUN: -swift-version 5 \
// RUN: -external-plugin-path %t/plugins#%swift-plugin-server \
// RUN: -load-plugin-executable %t/mock-plugin#TestPlugin \
// RUN: -module-name MyApp \
// RUN: %t/test.swift
//== Avoid nested sandbox by -disable-sandbox
// RUN: sandbox-exec -p '(version 1)(allow default)' \
// RUN: %swift-target-frontend \
// RUN: -disable-sandbox \
// RUN: -typecheck -verify \
// RUN: -swift-version 5 \
// RUN: -external-plugin-path %t/plugins#%swift-plugin-server \
// RUN: -load-plugin-executable %t/mock-plugin#TestPlugin \
// RUN: -module-name MyApp \
// RUN: %t/test.swift
//--- MacroDefinition.swift
import SwiftSyntax
import SwiftSyntaxBuilder
import SwiftSyntaxMacros
public struct StringifyMacro: ExpressionMacro {
public static func expansion(
of macro: some FreestandingMacroExpansionSyntax,
in context: some MacroExpansionContext
) -> ExprSyntax {
guard let argument = macro.arguments.first?.expression else {
fatalError("boom")
}
return "(\(argument), \(StringLiteralExprSyntax(content: argument.description)))"
}
}
//--- TestPlugin.c
#include "swift-c/MockPlugin/MockPlugin.h"
MOCK_PLUGIN([
{
"expect": {"getCapability": {}},
"response": {"getCapabilityResult": {"capability": {"protocolVersion": 1}}}
},
{
"expect": {"expandFreestandingMacro": {
"macro": {"moduleName": "TestPlugin", "typeName": "TestStringMacro"}}},
"response": {"expandMacroResult": {"expandedSource": "\"test string\"", "diagnostics": []}}
}
])
//--- test.swift
@freestanding(expression) macro stringify<T>(_ value: T) -> (T, String) = #externalMacro(module: "MacroDefinition", type: "StringifyMacro")
@freestanding(expression) macro testString() -> String = #externalMacro(module: "TestPlugin", type: "TestStringMacro")
func test() {
let _: String = #stringify(42).1
let _: String = #testString
}