diff --git a/include/swift/AST/ASTContext.h b/include/swift/AST/ASTContext.h index d260270e6d929..58e59bbb6c3ca 100644 --- a/include/swift/AST/ASTContext.h +++ b/include/swift/AST/ASTContext.h @@ -1461,12 +1461,21 @@ class ASTContext final { /// The declared interface type of Builtin.TheTupleType. BuiltinTupleType *getBuiltinTupleType(); - /// Finds the address of the given symbol. If `libraryHandleHint` is non-null, - /// search within the library. - void *getAddressOfSymbol(const char *name, void *libraryHandleHint = nullptr); - Type getNamedSwiftType(ModuleDecl *module, StringRef name); + /// Lookup a library plugin that can handle \p moduleName and return the path + /// to it. + /// The path is valid within the VFS, use `FS.getRealPath()` for the + /// underlying path. + Optional lookupLibraryPluginByModuleName(Identifier moduleName); + + /// Load the specified dylib plugin path resolving the path with the + /// current VFS. If it fails to load the plugin, a diagnostic is emitted, and + /// returns a nullptr. + /// NOTE: This method is idempotent. If the plugin is already loaded, the same + /// instance is simply returned. + void *loadLibraryPlugin(StringRef path); + /// Lookup an executable plugin that is declared to handle \p moduleName /// module by '-load-plugin-executable'. /// The path is valid within the VFS, use `FS.getRealPath()` for the @@ -1494,6 +1503,8 @@ class ASTContext final { /// This should be called before any plugin is loaded. void setPluginRegistry(PluginRegistry *newValue); + const llvm::StringSet<> &getLoadedPluginLibraryPaths() const; + private: friend Decl; @@ -1506,7 +1517,7 @@ class ASTContext final { Optional getBriefComment(const Decl *D); void setBriefComment(const Decl *D, StringRef Comment); - void loadCompilerPlugins(); + void createModuleToExecutablePluginMap(); friend TypeBase; friend ArchetypeType; diff --git a/include/swift/AST/CASTBridging.h b/include/swift/AST/CASTBridging.h index 07643be909fcc..e361cb35c0a1c 100644 --- a/include/swift/AST/CASTBridging.h +++ b/include/swift/AST/CASTBridging.h @@ -307,6 +307,9 @@ void Plugin_lock(PluginHandle handle); /// Unlock the plugin. void Plugin_unlock(PluginHandle handle); +/// Launch the plugin if it's not running. +_Bool Plugin_spawnIfNeeded(PluginHandle handle); + /// Sends the message to the plugin, returns true if there was an error. /// Clients should receive the response by \c Plugin_waitForNextMessage . _Bool Plugin_sendMessage(PluginHandle handle, const BridgedData data); diff --git a/include/swift/AST/DiagnosticsFrontend.def b/include/swift/AST/DiagnosticsFrontend.def index afb50fcda13c9..9947e8bea57b5 100644 --- a/include/swift/AST/DiagnosticsFrontend.def +++ b/include/swift/AST/DiagnosticsFrontend.def @@ -128,6 +128,9 @@ ERROR(error_invalid_source_location_str,none, ERROR(error_no_source_location_scope_map,none, "-dump-scope-maps argument must be 'expanded' or a list of " "source locations", ()) +ERROR(error_load_plugin_executable,none, + "invalid value '%0' in '-load-plugin-executable'; " + "make sure to use format '#'", (StringRef)) NOTE(note_valid_swift_versions, none, "valid arguments to '-swift-version' are %0", (StringRef)) diff --git a/include/swift/AST/PluginRegistry.h b/include/swift/AST/PluginRegistry.h index 9fb91517fbd8a..ee38e6294fd16 100644 --- a/include/swift/AST/PluginRegistry.h +++ b/include/swift/AST/PluginRegistry.h @@ -22,36 +22,82 @@ namespace swift { +/// Represent a "resolved" exectuable plugin. +/// +/// Plugin clients usually deal with this object to communicate with the actual +/// plugin implementation. +/// This object has a file path of the plugin executable, and is responsible to +/// launch it and manages the process. When the plugin process crashes, this +/// should automatically relaunch the process so the clients can keep using this +/// object as the interface. class LoadedExecutablePlugin { - const llvm::sys::procid_t pid; + + /// Represents the current process of the executable plugin. + struct PluginProcess { + const llvm::sys::procid_t pid; + const int inputFileDescriptor; + const int outputFileDescriptor; + bool isStale = false; + + PluginProcess(llvm::sys::procid_t pid, int inputFileDescriptor, + int outputFileDescriptor); + + ~PluginProcess(); + + ssize_t write(const void *buf, size_t nbyte) const; + ssize_t read(void *buf, size_t nbyte) const; + }; + + /// Launched current process. + std::unique_ptr Process; + + /// Path to the plugin executable. + const std::string ExecutablePath; + + /// Last modification time of the `ExecutablePath` when this is initialized. const llvm::sys::TimePoint<> LastModificationTime; - const int inputFileDescriptor; - const int outputFileDescriptor; /// Opaque value of the protocol capability of the pluugin. This is a /// value from ASTGen. const void *capability = nullptr; + /// Callbacks to be called when the connection is restored. + llvm::SmallVector *, 0> onReconnect; + + /// Flag to dump plugin messagings. + bool dumpMessaging = false; + /// Cleanup function to call ASTGen. std::function cleanup; std::mutex mtx; - ssize_t write(const void *buf, size_t nbyte) const; - ssize_t read(void *buf, size_t nbyte) const; - public: - LoadedExecutablePlugin(llvm::sys::procid_t pid, - llvm::sys::TimePoint<> LastModificationTime, - int inputFileDescriptor, int outputFileDescriptor); + LoadedExecutablePlugin(llvm::StringRef ExecutablePath, + llvm::sys::TimePoint<> LastModificationTime) + : ExecutablePath(ExecutablePath), + LastModificationTime(LastModificationTime){}; ~LoadedExecutablePlugin(); + + /// The last modification time of 'ExecutablePath' when this object is + /// created. llvm::sys::TimePoint<> getLastModificationTime() const { return LastModificationTime; } + /// Indicates that the current process is usable. + bool isAlive() const { return Process != nullptr && !Process->isStale; } + + /// Mark the current process "stale". + void setStale() const { Process->isStale = true; } + void lock() { mtx.lock(); } void unlock() { mtx.unlock(); } + // Launch the plugin if it's not already running, or it's stale. Return an + // error if it's fails to execute it. + llvm::Error spawnIfNeeded(); + /// Send a message to the plugin. llvm::Error sendMessage(llvm::StringRef message) const; @@ -63,10 +109,23 @@ class LoadedExecutablePlugin { this->cleanup = cleanup; } - llvm::sys::procid_t getPid() { return pid; } + /// Add "on reconnect" callback. + /// These callbacks are called when `spawnIfNeeded()` relaunched the plugin. + void addOnReconnect(std::function *fn) { + onReconnect.push_back(fn); + } + + /// Remove "on reconnect" callback. + void removeOnReconnect(std::function *fn) { + llvm::erase_value(onReconnect, fn); + } + + llvm::sys::procid_t getPid() { return Process->pid; } const void *getCapability() { return capability; }; void setCapability(const void *newValue) { capability = newValue; }; + + void setDumpMessaging(bool flag) { dumpMessaging = flag; } }; class PluginRegistry { @@ -77,14 +136,22 @@ class PluginRegistry { llvm::StringMap> LoadedPluginExecutables; + /// Flag to dump plugin messagings. + bool dumpMessaging = false; + + std::mutex mtx; + public: + PluginRegistry(); + + /// Load a dynamic link library specified by \p path. + /// If \p path plugin is already loaded, this returns the cached object. llvm::Expected loadLibraryPlugin(llvm::StringRef path); + + /// Load an executable plugin specified by \p path . + /// If \p path plugin is already loaded, this returns the cached object. llvm::Expected loadExecutablePlugin(llvm::StringRef path); - - const llvm::StringMap &getLoadedLibraryPlugins() const { - return LoadedPluginLibraries; - } }; } // namespace swift diff --git a/include/swift/AST/SearchPathOptions.h b/include/swift/AST/SearchPathOptions.h index f060f6a396169..74bc7d175af11 100644 --- a/include/swift/AST/SearchPathOptions.h +++ b/include/swift/AST/SearchPathOptions.h @@ -174,6 +174,12 @@ class ModuleSearchPathLookup { llvm::vfs::FileSystem *FS, bool IsOSDarwin); }; +/// Pair of a plugin path and the module name that the plugin provides. +struct PluginExecutablePathAndModuleNames { + std::string ExecutablePath; + std::vector ModuleNames; +}; + /// Pair of a plugin search path and the corresponding plugin server executable /// path. struct ExternalPluginSearchPathAndServerPath { @@ -242,8 +248,7 @@ class SearchPathOptions { std::vector CompilerPluginLibraryPaths; /// Compiler plugin executable paths and providing module names. - /// Format: '#' - std::vector CompilerPluginExecutablePaths; + std::vector CompilerPluginExecutablePaths; /// Add a single import search path. Must only be called from /// \c ASTContext::addSearchPath. @@ -361,12 +366,13 @@ class SearchPathOptions { } void setCompilerPluginExecutablePaths( - std::vector NewCompilerPluginExecutablePaths) { - CompilerPluginExecutablePaths = NewCompilerPluginExecutablePaths; + std::vector &&newValue) { + CompilerPluginExecutablePaths = std::move(newValue); Lookup.searchPathsDidChange(); } - ArrayRef getCompilerPluginExecutablePaths() const { + ArrayRef + getCompilerPluginExecutablePaths() const { return CompilerPluginExecutablePaths; } diff --git a/include/swift/IDETool/CompileInstance.h b/include/swift/IDETool/CompileInstance.h index 4c39a9b5e52a1..ba0c9fe2f9a66 100644 --- a/include/swift/IDETool/CompileInstance.h +++ b/include/swift/IDETool/CompileInstance.h @@ -25,6 +25,7 @@ namespace swift { class CompilerInstance; class DiagnosticConsumer; +class PluginRegistry; namespace ide { @@ -32,6 +33,7 @@ namespace ide { class CompileInstance { const std::string &RuntimeResourcePath; const std::string &DiagnosticDocumentationPath; + const std::shared_ptr Plugins; struct Options { unsigned MaxASTReuseCount = 100; @@ -66,10 +68,11 @@ class CompileInstance { public: CompileInstance(const std::string &RuntimeResourcePath, - const std::string &DiagnosticDocumentationPath) + const std::string &DiagnosticDocumentationPath, + std::shared_ptr Plugins = nullptr) : RuntimeResourcePath(RuntimeResourcePath), DiagnosticDocumentationPath(DiagnosticDocumentationPath), - CachedCIInvalidated(false), CachedReuseCount(0) {} + Plugins(Plugins), CachedCIInvalidated(false), CachedReuseCount(0) {} /// NOTE: \p Args is only used for checking the equaity of the invocation. /// Since this function assumes that it is already normalized, exact the same diff --git a/include/swift/IDETool/IDEInspectionInstance.h b/include/swift/IDETool/IDEInspectionInstance.h index de7c078def77b..00fae7514fc24 100644 --- a/include/swift/IDETool/IDEInspectionInstance.h +++ b/include/swift/IDETool/IDEInspectionInstance.h @@ -35,6 +35,7 @@ namespace swift { class CompilerInstance; class CompilerInvocation; class DiagnosticConsumer; +class PluginRegistry; namespace ide { @@ -96,6 +97,8 @@ class IDEInspectionInstance { std::mutex mtx; + std::shared_ptr Plugins; + std::shared_ptr CachedCI; llvm::hash_code CachedArgHash; llvm::sys::TimePoint<> DependencyCheckedTimestamp; @@ -167,7 +170,8 @@ class IDEInspectionInstance { Callback); public: - IDEInspectionInstance() : CachedCIShouldBeInvalidated(false) {} + IDEInspectionInstance(std::shared_ptr Plugins = nullptr) + : Plugins(Plugins), CachedCIShouldBeInvalidated(false) {} // Mark the cached compiler instance "should be invalidated". In the next // completion, new compiler instance will be used. (Thread safe.) diff --git a/lib/AST/ASTContext.cpp b/lib/AST/ASTContext.cpp index cd98397277c63..b1fbdc78862b2 100644 --- a/lib/AST/ASTContext.cpp +++ b/lib/AST/ASTContext.cpp @@ -528,12 +528,14 @@ struct ASTContext::Implementation { /// NOTE: Do not reference this directly. Use ASTContext::getPluginRegistry(). PluginRegistry *Plugins = nullptr; - /// Cache of loaded symbols. - llvm::StringMap LoadedSymbols; + /// `Plugins` storage if this ASTContext owns it. + std::unique_ptr OwnedPluginRegistry = nullptr; /// Map a module name to an executable plugin path that provides the module. llvm::DenseMap ExecutablePluginPaths; + llvm::StringSet<> LoadedPluginLibraryPaths; + /// The permanent arena. Arena Permanent; @@ -704,8 +706,7 @@ ASTContext::ASTContext( registerAccessRequestFunctions(evaluator); registerNameLookupRequestFunctions(evaluator); - // FIXME: Delay this so the client e.g. SourceKit can inject plugin registry. - loadCompilerPlugins(); + createModuleToExecutablePluginMap(); } ASTContext::~ASTContext() { @@ -6272,75 +6273,24 @@ PluginRegistry *ASTContext::getPluginRegistry() const { // Create a new one if it hasn't been set. if (!registry) { registry = new PluginRegistry(); - const_cast(this)->addCleanup([registry]{ - delete registry; - }); + getImpl().OwnedPluginRegistry.reset(registry); } assert(registry != nullptr); return registry; } -void ASTContext::loadCompilerPlugins() { - auto fs = this->SourceMgr.getFileSystem(); - for (auto &path : SearchPathOpts.getCompilerPluginLibraryPaths()) { - SmallString<128> resolvedPath; - if (auto err = fs->getRealPath(path, resolvedPath)) { - Diags.diagnose(SourceLoc(), diag::compiler_plugin_not_loaded, path, - err.message()); - continue; - } - auto loaded = getPluginRegistry()->loadLibraryPlugin(resolvedPath); - if (!loaded) { - Diags.diagnose(SourceLoc(), diag::compiler_plugin_not_loaded, path, - llvm::toString(loaded.takeError())); - } - } - +void ASTContext::createModuleToExecutablePluginMap() { for (auto &arg : SearchPathOpts.getCompilerPluginExecutablePaths()) { - // 'arg' is '#' where the module names are - // comma separated. - // Create a moduleName -> pluginPath mapping. - StringRef path; - StringRef modulesStr; - std::tie(path, modulesStr) = StringRef(arg).rsplit('#'); - SmallVector modules; - modulesStr.split(modules, ','); - - if (modules.empty() || path.empty()) { - // TODO: Error messsage. - Diags.diagnose(SourceLoc(), diag::compiler_plugin_not_loaded, arg, ""); - } - auto pathStr = AllocateCopy(path); - for (auto moduleName : modules) { + assert(!arg.ExecutablePath.empty() && "empty plugin path"); + auto pathStr = AllocateCopy(arg.ExecutablePath); + for (auto moduleName : arg.ModuleNames) { getImpl().ExecutablePluginPaths[getIdentifier(moduleName)] = pathStr; } } } -void *ASTContext::getAddressOfSymbol(const char *name, - void *libraryHandleHint) { - auto lookup = getImpl().LoadedSymbols.try_emplace(name, nullptr); - void *&address = lookup.first->getValue(); -#if !defined(_WIN32) - if (lookup.second) { - auto *handle = libraryHandleHint ? libraryHandleHint : RTLD_DEFAULT; - address = dlsym(handle, name); - - // If we didn't know where to look, look specifically in each plugin. - if (!address && !libraryHandleHint) { - for (const auto &plugin : getPluginRegistry()->getLoadedLibraryPlugins()) { - address = dlsym(plugin.second, name); - if (address) - break; - } - } - } -#endif - return address; -} - Type ASTContext::getNamedSwiftType(ModuleDecl *module, StringRef name) { if (!module) return Type(); @@ -6376,6 +6326,35 @@ Type ASTContext::getNamedSwiftType(ModuleDecl *module, StringRef name) { return decl->getDeclaredInterfaceType(); } +Optional +ASTContext::lookupLibraryPluginByModuleName(Identifier moduleName) { + auto fs = SourceMgr.getFileSystem(); + + // Look for 'lib${module name}(.dylib|.so)'. + SmallString<64> expectedBasename; + expectedBasename.append("lib"); + expectedBasename.append(moduleName.str()); + expectedBasename.append(LTDL_SHLIB_EXT); + + // Try '-plugin-path'. + for (const auto &searchPath : SearchPathOpts.PluginSearchPaths) { + SmallString<128> fullPath(searchPath); + llvm::sys::path::append(fullPath, expectedBasename); + if (fs->exists(fullPath)) { + return std::string(fullPath); + } + } + + // Try '-load-plugin-library'. + for (const auto &libPath : SearchPathOpts.getCompilerPluginLibraryPaths()) { + if (llvm::sys::path::filename(libPath) == expectedBasename) { + return libPath; + } + } + + return None; +} + Optional ASTContext::lookupExecutablePluginByModuleName(Identifier moduleName) { auto &execPluginPaths = getImpl().ExecutablePluginPaths; @@ -6418,6 +6397,32 @@ LoadedExecutablePlugin *ASTContext::loadExecutablePlugin(StringRef path) { return plugin.get(); } +void *ASTContext::loadLibraryPlugin(StringRef path) { + // Remember the path (even if it fails to load.) + getImpl().LoadedPluginLibraryPaths.insert(path); + + SmallString<128> resolvedPath; + auto fs = this->SourceMgr.getFileSystem(); + if (auto err = fs->getRealPath(path, resolvedPath)) { + Diags.diagnose(SourceLoc(), diag::compiler_plugin_not_loaded, path, + err.message()); + return nullptr; + } + + // Load the plugin. + auto plugin = getPluginRegistry()->loadLibraryPlugin(resolvedPath); + if (!plugin) { + Diags.diagnose(SourceLoc(), diag::compiler_plugin_not_loaded, path, + llvm::toString(plugin.takeError())); + } + + return plugin.get(); +} + +const llvm::StringSet<> &ASTContext::getLoadedPluginLibraryPaths() const { + return getImpl().LoadedPluginLibraryPaths; +} + bool ASTContext::supportsMoveOnlyTypes() const { // currently the only thing holding back whether the types can appear is this. return SILOpts.LexicalLifetimes != LexicalLifetimesOption::Off; diff --git a/lib/AST/CASTBridging.cpp b/lib/AST/CASTBridging.cpp index d7fed9a6c99e1..f92eb505bbf7e 100644 --- a/lib/AST/CASTBridging.cpp +++ b/lib/AST/CASTBridging.cpp @@ -649,6 +649,14 @@ void Plugin_unlock(PluginHandle handle) { plugin->unlock(); } +bool Plugin_spawnIfNeeded(PluginHandle handle) { + auto *plugin = static_cast(handle); + auto error = plugin->spawnIfNeeded(); + bool hadError(error); + llvm::consumeError(std::move(error)); + return hadError; +} + bool Plugin_sendMessage(PluginHandle handle, const BridgedData data) { auto *plugin = static_cast(handle); StringRef message(data.baseAddress, data.size); diff --git a/lib/AST/PluginRegistry.cpp b/lib/AST/PluginRegistry.cpp index d8873a91023df..a9584fb79a8ec 100644 --- a/lib/AST/PluginRegistry.cpp +++ b/lib/AST/PluginRegistry.cpp @@ -38,12 +38,14 @@ #include #endif -extern "C" const void *swift_ASTGen_getCompilerPluginCapability(void *handle); -extern "C" void swift_ASTGen_destroyCompilerPluginCapability(void *value); - using namespace swift; +PluginRegistry::PluginRegistry() { + dumpMessaging = ::getenv("SWIFT_DUMP_PLUGIN_MESSAGING") != nullptr; +} + llvm::Expected PluginRegistry::loadLibraryPlugin(StringRef path) { + std::lock_guard lock(mtx); auto found = LoadedPluginLibraries.find(path); if (found != LoadedPluginLibraries.end()) { // Already loaded. @@ -73,15 +75,17 @@ PluginRegistry::loadExecutablePlugin(StringRef path) { return llvm::errorCodeToError(err); } + std::lock_guard lock(mtx); + // See if the plugin is already loaded. - auto &plugin = LoadedPluginExecutables[path]; - if (plugin) { + auto &storage = LoadedPluginExecutables[path]; + if (storage) { // See if the loaded one is still usable. - if (plugin->getLastModificationTime() == stat.getLastModificationTime()) - return plugin.get(); + if (storage->getLastModificationTime() == stat.getLastModificationTime()) + return storage.get(); // The plugin is updated. Close the previously opened plugin. - plugin = nullptr; + storage = nullptr; } if (!llvm::sys::fs::exists(stat)) { @@ -94,8 +98,37 @@ PluginRegistry::loadExecutablePlugin(StringRef path) { "not executable"); } + auto plugin = std::unique_ptr( + new LoadedExecutablePlugin(path, stat.getLastModificationTime())); + + plugin->setDumpMessaging(dumpMessaging); + + // Launch here to see if it's actually executable, and diagnose (by returning + // an error) if necessary. + if (auto error = plugin->spawnIfNeeded()) { + return std::move(error); + } + + storage = std::move(plugin); + return storage.get(); +} + +llvm::Error LoadedExecutablePlugin::spawnIfNeeded() { + if (Process) { + // See if the loaded one is still usable. + if (!Process->isStale) + return llvm::Error::success(); + + // NOTE: We don't check the mtime here because 'stat(2)' call is too heavy. + // PluginRegistry::loadExecutablePlugin() checks it and replace this object + // itself if the plugin is updated. + + // The plugin is stale. Discard the previously opened process. + Process.reset(); + } + // Create command line arguments. - SmallVector command{path}; + SmallVector command{ExecutablePath}; // Apply sandboxing. llvm::BumpPtrAllocator Allocator; @@ -107,29 +140,36 @@ PluginRegistry::loadExecutablePlugin(StringRef path) { return llvm::errorCodeToError(childInfo.getError()); } - plugin = std::unique_ptr(new LoadedExecutablePlugin( - childInfo->Pid, stat.getLastModificationTime(), - childInfo->ReadFileDescriptor, childInfo->WriteFileDescriptor)); + Process = std::unique_ptr( + new PluginProcess(childInfo->Pid, childInfo->ReadFileDescriptor, + childInfo->WriteFileDescriptor)); + + // Call "on reconnect" callbacks. + for (auto *callback : onReconnect) { + (*callback)(); + } - return plugin.get(); + return llvm::Error::success(); } -LoadedExecutablePlugin::LoadedExecutablePlugin( - llvm::sys::procid_t pid, llvm::sys::TimePoint<> LastModificationTime, - int inputFileDescriptor, int outputFileDescriptor) - : pid(pid), LastModificationTime(LastModificationTime), - inputFileDescriptor(inputFileDescriptor), +LoadedExecutablePlugin::PluginProcess::PluginProcess(llvm::sys::procid_t pid, + int inputFileDescriptor, + int outputFileDescriptor) + : pid(pid), inputFileDescriptor(inputFileDescriptor), outputFileDescriptor(outputFileDescriptor) {} -LoadedExecutablePlugin::~LoadedExecutablePlugin() { +LoadedExecutablePlugin::PluginProcess::~PluginProcess() { close(inputFileDescriptor); close(outputFileDescriptor); +} +LoadedExecutablePlugin::~LoadedExecutablePlugin() { // Let ASTGen to cleanup things. this->cleanup(); } -ssize_t LoadedExecutablePlugin::read(void *buf, size_t nbyte) const { +ssize_t LoadedExecutablePlugin::PluginProcess::read(void *buf, + size_t nbyte) const { ssize_t bytesToRead = nbyte; void *ptr = buf; @@ -154,7 +194,8 @@ ssize_t LoadedExecutablePlugin::read(void *buf, size_t nbyte) const { return nbyte - bytesToRead; } -ssize_t LoadedExecutablePlugin::write(const void *buf, size_t nbyte) const { +ssize_t LoadedExecutablePlugin::PluginProcess::write(const void *buf, + size_t nbyte) const { ssize_t bytesToWrite = nbyte; const void *ptr = buf; @@ -181,21 +222,27 @@ ssize_t LoadedExecutablePlugin::write(const void *buf, size_t nbyte) const { llvm::Error LoadedExecutablePlugin::sendMessage(llvm::StringRef message) const { ssize_t writtenSize = 0; + if (dumpMessaging) { + llvm::dbgs() << "->(plugin:" << Process->pid << ") " << message << "\n"; + } + const char *data = message.data(); size_t size = message.size(); // Write header (message size). uint64_t header = llvm::support::endian::byte_swap( uint64_t(size), llvm::support::endianness::little); - writtenSize = write(&header, sizeof(header)); + writtenSize = Process->write(&header, sizeof(header)); if (writtenSize != sizeof(header)) { + setStale(); return llvm::createStringError(llvm::inconvertibleErrorCode(), "failed to write plugin message header"); } // Write message. - writtenSize = write(data, size); + writtenSize = Process->write(data, size); if (writtenSize != ssize_t(size)) { + setStale(); return llvm::createStringError(llvm::inconvertibleErrorCode(), "failed to write plugin message data"); } @@ -208,9 +255,10 @@ llvm::Expected LoadedExecutablePlugin::waitForNextMessage() const { // Read header (message size). uint64_t header; - readSize = read(&header, sizeof(header)); + readSize = Process->read(&header, sizeof(header)); if (readSize != sizeof(header)) { + setStale(); return llvm::createStringError(llvm::inconvertibleErrorCode(), "failed to read plugin message header"); } @@ -224,8 +272,9 @@ llvm::Expected LoadedExecutablePlugin::waitForNextMessage() const { auto sizeToRead = size; while (sizeToRead > 0) { char buffer[4096]; - readSize = read(buffer, std::min(sizeof(buffer), sizeToRead)); + readSize = Process->read(buffer, std::min(sizeof(buffer), sizeToRead)); if (readSize == 0) { + setStale(); return llvm::createStringError(llvm::inconvertibleErrorCode(), "failed to read plugin message data"); } @@ -233,5 +282,9 @@ llvm::Expected LoadedExecutablePlugin::waitForNextMessage() const { message.append(buffer, readSize); } + if (dumpMessaging) { + llvm::dbgs() << "<-(plugin:" << Process->pid << ") " << message << "\n"; + } + return message; } diff --git a/lib/ASTGen/Sources/ASTGen/PluginHost.swift b/lib/ASTGen/Sources/ASTGen/PluginHost.swift index 1cbd3a53943a0..bc0747f87813f 100644 --- a/lib/ASTGen/Sources/ASTGen/PluginHost.swift +++ b/lib/ASTGen/Sources/ASTGen/PluginHost.swift @@ -16,6 +16,7 @@ import SwiftSyntax import swiftLLVMJSON enum PluginError: Error { + case stalePlugin case failedToSendMessage case failedToReceiveMessage case invalidReponseKind @@ -38,30 +39,37 @@ public func _deinitializePlugin( } /// Load the library plugin in the plugin server. +/// This should be called inside lock. @_cdecl("swift_ASTGen_pluginServerLoadLibraryPlugin") func swift_ASTGen_pluginServerLoadLibraryPlugin( opaqueHandle: UnsafeMutableRawPointer, libraryPath: UnsafePointer, moduleName: UnsafePointer, - cxxDiagnosticEngine: UnsafeMutablePointer + cxxDiagnosticEngine: UnsafeMutablePointer? ) -> Bool { let plugin = CompilerPlugin(opaqueHandle: opaqueHandle) assert(plugin.capability?.features.contains(.loadPluginLibrary) == true) let libraryPath = String(cString: libraryPath) let moduleName = String(cString: moduleName) - let diagEngine = PluginDiagnosticsEngine(cxxDiagnosticEngine: cxxDiagnosticEngine) + + let diagEngine: PluginDiagnosticsEngine? + if let cxxDiagnosticEngine = cxxDiagnosticEngine { + diagEngine = PluginDiagnosticsEngine(cxxDiagnosticEngine: cxxDiagnosticEngine) + } else { + diagEngine = nil + } do { - let result = try plugin.sendMessageAndWait( + let result = try plugin.sendMessageAndWaitWithoutLock( .loadPluginLibrary(libraryPath: libraryPath, moduleName: moduleName) ) guard case .loadPluginLibraryResult(let loaded, let diagnostics) = result else { throw PluginError.invalidReponseKind } - diagEngine.emit(diagnostics); + diagEngine?.emit(diagnostics); return loaded } catch { - diagEngine.diagnose(error: error) + diagEngine?.diagnose(error: error) return false } } @@ -95,10 +103,6 @@ struct CompilerPlugin { private func sendMessage(_ message: HostToPluginMessage) throws { let hadError = try LLVMJSON.encoding(message) { (data) -> Bool in -// // FIXME: Add -dump-plugin-message option? -// data.withMemoryRebound(to: UInt8.self) { buffer in -// print(">> " + String(decoding: buffer, as: UTF8.self)) -// } return Plugin_sendMessage(opaqueHandle, BridgedData(baseAddress: data.baseAddress, size: data.count)) } if hadError { @@ -114,35 +118,34 @@ struct CompilerPlugin { throw PluginError.failedToReceiveMessage } let data = UnsafeBufferPointer(start: result.baseAddress, count: result.size) -// // FIXME: Add -dump-plugin-message option? -// data.withMemoryRebound(to: UInt8.self) { buffer in -// print("<< " + String(decoding: buffer, as: UTF8.self)) -// } return try LLVMJSON.decode(PluginToHostMessage.self, from: data) } + func sendMessageAndWaitWithoutLock(_ message: HostToPluginMessage) throws -> PluginToHostMessage { + try sendMessage(message) + return try waitForNextMessage() + } + func sendMessageAndWait(_ message: HostToPluginMessage) throws -> PluginToHostMessage { try self.withLock { - try sendMessage(message) - return try waitForNextMessage() + guard !Plugin_spawnIfNeeded(opaqueHandle) else { + throw PluginError.stalePlugin + } + return try sendMessageAndWaitWithoutLock(message); } } + /// Initialize the plugin. This should be called inside lock. func initialize() { - // Don't use `sendMessageAndWait` because we want to keep the lock until - // setting the returned value. do { - try self.withLock { - // Get capability. - try self.sendMessage(.getCapability) - let response = try self.waitForNextMessage() - guard case .getCapabilityResult(let capability) = response else { - throw PluginError.invalidReponseKind - } - let ptr = UnsafeMutablePointer.allocate(capacity: 1) - ptr.initialize(to: .init(capability)) - Plugin_setCapability(opaqueHandle, UnsafeRawPointer(ptr)) + // Get capability. + let response = try self.sendMessageAndWaitWithoutLock(.getCapability) + guard case .getCapabilityResult(let capability) = response else { + throw PluginError.invalidReponseKind } + let ptr = UnsafeMutablePointer.allocate(capacity: 1) + ptr.initialize(to: .init(capability)) + Plugin_setCapability(opaqueHandle, UnsafeRawPointer(ptr)) } catch { assertionFailure(String(describing: error)) return diff --git a/lib/Frontend/CompilerInvocation.cpp b/lib/Frontend/CompilerInvocation.cpp index 0f5cc08dcb4a1..f14e7f1e09c08 100644 --- a/lib/Frontend/CompilerInvocation.cpp +++ b/lib/Frontend/CompilerInvocation.cpp @@ -1568,14 +1568,28 @@ static bool ParseSearchPathArgs(SearchPathOptions &Opts, } Opts.setCompilerPluginLibraryPaths(CompilerPluginLibraryPaths); - std::vector CompilerPluginExecutablePaths( + std::vector CompilerPluginExecutablePaths( Opts.getCompilerPluginExecutablePaths()); for (const Arg *A : Args.filtered(OPT_load_plugin_executable)) { - // NOTE: The value has '#' after the path. - // But resolveSearchPath() works as long as the value starts with a path. - CompilerPluginExecutablePaths.push_back(resolveSearchPath(A->getValue())); + // 'A' is '#' where the module names are + // comma separated. + StringRef path; + StringRef modulesStr; + std::tie(path, modulesStr) = StringRef(A->getValue()).rsplit('#'); + std::vector moduleNames; + for (auto name : llvm::split(modulesStr, ',')) { + moduleNames.emplace_back(name); + } + if (path.empty() || moduleNames.empty()) { + Diags.diagnose(SourceLoc(), diag::error_load_plugin_executable, + A->getValue()); + } else { + CompilerPluginExecutablePaths.push_back( + {resolveSearchPath(path), std::move(moduleNames)}); + } } - Opts.setCompilerPluginExecutablePaths(CompilerPluginExecutablePaths); + Opts.setCompilerPluginExecutablePaths( + std::move(CompilerPluginExecutablePaths)); return false; } diff --git a/lib/FrontendTool/LoadedModuleTrace.cpp b/lib/FrontendTool/LoadedModuleTrace.cpp index 650356a8c8b5d..1f64336b1a855 100644 --- a/lib/FrontendTool/LoadedModuleTrace.cpp +++ b/lib/FrontendTool/LoadedModuleTrace.cpp @@ -760,8 +760,7 @@ bool swift::emitLoadedModuleTraceIfNeeded(ModuleDecl *mainModule, } // Add compiler plugin libraries as dependencies. - auto *pluginRegistry = ctxt.getPluginRegistry(); - for (auto &pluginEntry : pluginRegistry->getLoadedLibraryPlugins()) + for (auto &pluginEntry : ctxt.getLoadedPluginLibraryPaths()) depTracker->addDependency(pluginEntry.getKey(), /*IsSystem*/ false); std::vector swiftModules; diff --git a/lib/IDETool/CompileInstance.cpp b/lib/IDETool/CompileInstance.cpp index 6e42fe3c48679..30af4399b7266 100644 --- a/lib/IDETool/CompileInstance.cpp +++ b/lib/IDETool/CompileInstance.cpp @@ -299,6 +299,7 @@ bool CompileInstance::setupCI( assert(Diags.hadAnyError()); return false; } + CI->getASTContext().setPluginRegistry(Plugins.get()); return true; } diff --git a/lib/IDETool/IDEInspectionInstance.cpp b/lib/IDETool/IDEInspectionInstance.cpp index 7085745bc9fdb..41fca55f3ee81 100644 --- a/lib/IDETool/IDEInspectionInstance.cpp +++ b/lib/IDETool/IDEInspectionInstance.cpp @@ -482,6 +482,7 @@ void IDEInspectionInstance::performNewOperation( InstanceSetupError)); return; } + CI->getASTContext().setPluginRegistry(Plugins.get()); CI->getASTContext().CancellationFlag = CancellationFlag; registerIDERequestFunctions(CI->getASTContext().evaluator); diff --git a/lib/Sema/TypeCheckMacros.cpp b/lib/Sema/TypeCheckMacros.cpp index e81144792b634..d9e3337a18baf 100644 --- a/lib/Sema/TypeCheckMacros.cpp +++ b/lib/Sema/TypeCheckMacros.cpp @@ -93,7 +93,11 @@ static void const *lookupMacroTypeMetadataByExternalName( for (auto typeKind : typeKinds) { auto symbolName = Demangle::mangledNameForTypeMetadataAccessor( moduleName, typeName, typeKind); - accessorAddr = ctx.getAddressOfSymbol(symbolName.c_str(), libraryHint); +#if !defined(_WIN32) + /// FIXME: 'PluginRegistry' should vend a wrapper object of the library + /// handle (like llvm::sys::DynamicLibrary) and dlsym should be abstracted. + accessorAddr = dlsym(libraryHint, symbolName.c_str()); +#endif if (accessorAddr) break; } @@ -286,15 +290,16 @@ MacroDefinition MacroDefinitionRequest::evaluate( } /// Load a plugin library based on a module name. -static void *loadLibraryPluginByName(StringRef searchPath, StringRef moduleName, - llvm::vfs::FileSystem &fs, - PluginRegistry *registry) { - SmallString<128> fullPath(searchPath); - llvm::sys::path::append(fullPath, "lib" + moduleName + LTDL_SHLIB_EXT); - if (fs.getRealPath(fullPath, fullPath)) +static void *loadLibraryPluginByName(ASTContext &ctx, Identifier moduleName) { + std::string libraryPath; + if (auto found = ctx.lookupLibraryPluginByModuleName(moduleName)) { + libraryPath = *found; + } else { return nullptr; - auto loadResult = registry->loadLibraryPlugin(fullPath); - return loadResult ? *loadResult : nullptr; + } + + // Load the plugin. + return ctx.loadLibraryPlugin(libraryPath); } static LoadedExecutablePlugin * @@ -319,6 +324,11 @@ loadExecutablePluginByName(ASTContext &ctx, Identifier moduleName) { if (!executablePlugin) return nullptr; + // Lock the plugin while initializing. + // Note that'executablePlugn' can be shared between multiple ASTContext. + executablePlugin->lock(); + SWIFT_DEFER { executablePlugin->unlock(); }; + // FIXME: Ideally this should be done right after invoking the plugin. // But plugin loading is in libAST and it can't link ASTGen symbols. if (!executablePlugin->isInitialized()) { @@ -338,11 +348,30 @@ loadExecutablePluginByName(ASTContext &ctx, Identifier moduleName) { if (fs->getRealPath(libraryPath, resolvedLibraryPath)) { return nullptr; } + std::string resolvedLibraryPathStr(resolvedLibraryPath); + std::string moduleNameStr(moduleName.str()); + bool loaded = swift_ASTGen_pluginServerLoadLibraryPlugin( - executablePlugin, resolvedLibraryPath.c_str(), moduleName.str().data(), + executablePlugin, resolvedLibraryPathStr.c_str(), moduleNameStr.c_str(), &ctx.Diags); if (!loaded) return nullptr; + + // Set a callback to load the library again on reconnections. + auto *callback = new std::function( + [executablePlugin, resolvedLibraryPathStr, moduleNameStr]() { + (void)swift_ASTGen_pluginServerLoadLibraryPlugin( + executablePlugin, resolvedLibraryPathStr.c_str(), + moduleNameStr.c_str(), + /*diags=*/nullptr); + }); + executablePlugin->addOnReconnect(callback); + + // Remove the callback and deallocate it when this ASTContext is destructed. + ctx.addCleanup([executablePlugin, callback]() { + executablePlugin->removeOnReconnect(callback); + delete callback; + }); #endif } @@ -356,12 +385,10 @@ CompilerPluginLoadRequest::evaluate(Evaluator &evaluator, ASTContext *ctx, auto &searchPathOpts = ctx->SearchPathOpts; auto *registry = ctx->getPluginRegistry(); - // First, check '-plugin-path' paths. - for (const auto &path : searchPathOpts.PluginSearchPaths) { - if (auto found = - loadLibraryPluginByName(path, moduleName.str(), *fs, registry)) - return LoadedCompilerPlugin::inProcess(found); - } + // Check dynamic link library plugins. + // i.e. '-plugin-path', and '-load-plugin-library'. + if (auto found = loadLibraryPluginByName(*ctx, moduleName)) + return LoadedCompilerPlugin::inProcess(found); // Fall back to executable plugins. // i.e. '-external-plugin-path', and '-load-plugin-executable'. @@ -424,17 +451,13 @@ ExternalMacroDefinitionRequest::evaluate(Evaluator &evaluator, ASTContext *ctx, CompilerPluginLoadRequest loadRequest{ctx, moduleName}; LoadedCompilerPlugin loaded = evaluateOrDefault(evaluator, loadRequest, nullptr); + if (auto loadedLibrary = loaded.getAsInProcessPlugin()) { if (auto inProcess = resolveInProcessMacro( *ctx, moduleName, typeName, loadedLibrary)) return *inProcess; } - // Try to resolve in-process. - if (auto inProcess = resolveInProcessMacro(*ctx, moduleName, typeName)) - return *inProcess; - - // Try executable plugins. if (auto *executablePlugin = loaded.getAsExecutablePlugin()) { if (auto executableMacro = resolveExecutableMacro(*ctx, executablePlugin, moduleName, typeName)) { diff --git a/test/Macros/Inputs/evil_macro_definitions.swift b/test/Macros/Inputs/evil_macro_definitions.swift new file mode 100644 index 0000000000000..7d84519af336a --- /dev/null +++ b/test/Macros/Inputs/evil_macro_definitions.swift @@ -0,0 +1,17 @@ +import SwiftDiagnostics +import SwiftOperators +import SwiftSyntax +import SwiftSyntaxBuilder +import SwiftSyntaxMacros + + +public struct CrashingMacro: ExpressionMacro { + public static func expansion( + of macro: some FreestandingMacroExpansionSyntax, + in context: some MacroExpansionContext + ) -> ExprSyntax { + let arg: UInt = UInt(macro.argumentList.first!.expression.description)! + let zero: UInt = 0 + return "\(raw: zero - arg).description" + } +} diff --git a/test/Macros/macro_plugin_basic.swift b/test/Macros/macro_plugin_basic.swift index 81078aee2d74c..b8516e9661cc8 100644 --- a/test/Macros/macro_plugin_basic.swift +++ b/test/Macros/macro_plugin_basic.swift @@ -11,16 +11,23 @@ // RUN: -o %t/mock-plugin \ // RUN: %t/plugin.c -// RUN: %swift-target-frontend \ +// RUN: SWIFT_DUMP_PLUGIN_MESSAGING=1 %swift-target-frontend \ // RUN: -typecheck -verify \ // RUN: -swift-version 5 \ // RUN: -load-plugin-executable %t/mock-plugin#TestPlugin \ -// RUN: -dump-macro-expansions \ +// RUN: -module-name MyApp \ // RUN: %t/test.swift \ // RUN: 2>&1 | tee %t/macro-expansions.txt // RUN: %FileCheck -strict-whitespace %s < %t/macro-expansions.txt +// CHECK: ->(plugin:[[#PID:]]) {"getCapability":{}} +// CHECK: <-(plugin:[[#PID]]) {"getCapabilityResult":{"capability":{"protocolVersion":1}}} +// CHECK: ->(plugin:[[#PID]]) {"expandFreestandingMacro":{"discriminator":"$s{{.+}}","macro":{"moduleName":"TestPlugin","name":"testString","typeName":"TestStringMacro"},"syntax":{"kind":"expression","location":{"column":19,"fileID":"MyApp/test.swift","fileName":"BUILD_DIR{{.+}}test.swift","line":5,"offset":301},"source":"#testString(123)"}}} +// CHECK: <-(plugin:[[#PID]]) {"expandFreestandingMacroResult":{"diagnostics":[],"expandedSource":"\"123\"\n + \"foo \""}} +// CHECK: ->(plugin:[[#PID]]) {"expandFreestandingMacro":{"discriminator":"$s{{.+}}","macro":{"moduleName":"TestPlugin","name":"testStringWithError","typeName":"TestStringWithErrorMacro"},"syntax":{"kind":"expression","location":{"column":19,"fileID":"MyApp/test.swift","fileName":"BUILD_DIR{{.+}}test.swift","line":6,"offset":336},"source":"#testStringWithError(321)"}}} +// CHECK: <-(plugin:[[#PID]]) {"expandFreestandingMacroResult":{"diagnostics":[{"fixIts":[],"highlights":[],"message":"message from plugin","notes":[],"position":{"fileName":"BUILD_DIR{{.*}}test.swift","offset":336},"severity":"error"}],"expandedSource":"\"bar\""}} + //--- test.swift @freestanding(expression) macro testString(_: Any) -> String = #externalMacro(module: "TestPlugin", type: "TestStringMacro") @freestanding(expression) macro testStringWithError(_: Any) -> String = #externalMacro(module: "TestPlugin", type: "TestStringWithErrorMacro") @@ -31,15 +38,6 @@ func test() { // expected-error @-1 {{message from plugin}} } -// CHECK: ------------------------------ -// CHECK-NEXT: {{^}}"123" -// CHECK-NEXT: {{^}} + "foo " -// CHECK-NEXT: ------------------------------ - -// CHECK: ------------------------------ -// CHECK-NEXT: {{^}}"bar" -// CHECK-NEXT: ------------------------------ - //--- plugin.c #include "swift-c/MockPlugin/MockPlugin.h" diff --git a/test/Macros/macro_plugin_error.swift b/test/Macros/macro_plugin_error.swift index fa884f54aeb81..902c5ef7ae6f4 100644 --- a/test/Macros/macro_plugin_error.swift +++ b/test/Macros/macro_plugin_error.swift @@ -11,12 +11,25 @@ // RUN: -o %t/mock-plugin \ // RUN: %t/plugin.c -// RUN: %swift-target-frontend \ +// RUN: SWIFT_DUMP_PLUGIN_MESSAGING=1 %swift-target-frontend \ // RUN: -typecheck -verify \ // RUN: -swift-version 5 -enable-experimental-feature Macros \ // RUN: -load-plugin-executable %t/mock-plugin#TestPlugin \ -// RUN: -dump-macro-expansions \ -// RUN: %t/test.swift +// RUN: -module-name MyApp \ +// RUN: %t/test.swift \ +// RUN: 2>&1 | tee %t/macro-expansions.txt + +// RUN: %FileCheck -strict-whitespace %s < %t/macro-expansions.txt + +// CHECK: ->(plugin:[[#PID1:]]) {"getCapability":{}} +// CHECK-NEXT: <-(plugin:[[#PID1]]) {"getCapabilityResult":{"capability":{"protocolVersion":1}}} +// CHECK-NEXT: ->(plugin:[[#PID1]]) {"expandFreestandingMacro":{"discriminator":"$s{{.+}}","macro":{"moduleName":"TestPlugin","name":"fooMacro","typeName":"FooMacro"},"syntax":{"kind":"expression","location":{"column":19,"fileID":"MyApp/test.swift","fileName":"BUILD_DIR{{.+}}test.swift","line":6,"offset":200},"source":"#fooMacro(1)"}}} +// CHECK-NEXT: <-(plugin:[[#PID1]]) {"invalidResponse":{}} +// CHECK-NEXT: ->(plugin:[[#PID1]]) {"expandFreestandingMacro":{"discriminator":"$s{{.+}}","macro":{"moduleName":"TestPlugin","name":"fooMacro","typeName":"FooMacro"},"syntax":{"kind":"expression","location":{"column":19,"fileID":"MyApp/test.swift","fileName":"BUILD_DIR{{.+}}test.swift","line":8,"offset":304},"source":"#fooMacro(2)"}}} +// ^ This messages causes the mock plugin exit because there's no matching expected message. + +// CHECK: ->(plugin:[[#PID2:]]) {"expandFreestandingMacro":{"discriminator":"$s{{.+}}","macro":{"moduleName":"TestPlugin","name":"fooMacro","typeName":"FooMacro"},"syntax":{"kind":"expression","location":{"column":19,"fileID":"MyApp/test.swift","fileName":"BUILD_DIR{{.+}}test.swift","line":10,"offset":386},"source":"#fooMacro(3)"}}} +// CHECK-NEXT: <-(plugin:[[#PID2:]]) {"expandFreestandingMacroResult":{"diagnostics":[],"expandedSource":"3.description"}} //--- test.swift @freestanding(expression) macro fooMacro(_: Any) -> String = #externalMacro(module: "TestPlugin", type: "FooMacro") @@ -29,8 +42,7 @@ func test() { let _: String = #fooMacro(2) // expected-error @-1 {{failedToReceiveMessage}} let _: String = #fooMacro(3) - // expected-error @-1 {{failedToSendMessage}} - //FIXME: ^ This should succeed. Error recovery is not implemented. + // ^ This should succeed. } //--- plugin.c @@ -51,6 +63,6 @@ MOCK_PLUGIN([ "expect": {"expandFreestandingMacro": { "macro": {"moduleName": "TestPlugin", "typeName": "FooMacro"}, "syntax": {"kind": "expression", "source": "#fooMacro(3)"}}}, - "response": {"expandFreestandingMacroResult": {"expandedSource": "3", "diagnostics": []}} + "response": {"expandFreestandingMacroResult": {"expandedSource": "3.description", "diagnostics": []}} } ]) diff --git a/test/Macros/macro_plugin_server.swift b/test/Macros/macro_plugin_server.swift index bed9378c4e300..301550e154579 100644 --- a/test/Macros/macro_plugin_server.swift +++ b/test/Macros/macro_plugin_server.swift @@ -15,25 +15,55 @@ // RUN: %S/Inputs/syntax_macro_definitions.swift \ // RUN: -g -no-toolchain-stdlib-rpath -// RUN: %swift-target-frontend \ -// RUN: -typecheck -verify \ +// RUN: %target-build-swift \ // RUN: -swift-version 5 \ +// RUN: -I %swift-host-lib-dir \ +// RUN: -L %swift-host-lib-dir \ +// RUN: -emit-library \ +// RUN: -o %t/plugins/%target-library-name(EvilMacros) \ +// RUN: -module-name=EvilMacros \ +// RUN: %S/Inputs/evil_macro_definitions.swift \ +// RUN: -g -no-toolchain-stdlib-rpath + +// RUN: SWIFT_DUMP_PLUGIN_MESSAGING=1 %swift-target-frontend \ +// RUN: -typecheck -verify \ +// RUN: -swift-version 5 -enable-experimental-feature Macros \ // RUN: -external-plugin-path %t/plugins#%swift-plugin-server \ -// RUN: -dump-macro-expansions \ +// RUN: -module-name MyApp \ // RUN: %s \ // RUN: 2>&1 | tee %t/macro-expansions.txt // RUN: %FileCheck -strict-whitespace %s < %t/macro-expansions.txt +// CHECK: ->(plugin:[[#PID1:]]) {"getCapability":{}} +// CHECK-NEXT: <-(plugin:[[#PID1]]) {"getCapabilityResult":{"capability":{"features":["load-plugin-library"],"protocolVersion":4}}} +// CHECK-NEXT: ->(plugin:[[#PID1]]) {"loadPluginLibrary":{"libraryPath":"BUILD_DIR{{.*}}plugins/libMacroDefinition.dylib","moduleName":"MacroDefinition"}} +// CHECK-NEXT: <-(plugin:[[#PID1]]) {"loadPluginLibraryResult":{"diagnostics":[],"loaded":true}} +// CHECK-NEXT: ->(plugin:[[#PID1]]) {"loadPluginLibrary":{"libraryPath":"BUILD_DIR{{.*}}plugins/libEvilMacros.dylib","moduleName":"EvilMacros"}} +// CHECK-NEXT: <-(plugin:[[#PID1]]) {"loadPluginLibraryResult":{"diagnostics":[],"loaded":true}} +// CHECK-NEXT: ->(plugin:[[#PID1]]) {"expandFreestandingMacro":{"discriminator":"${{.*}}","macro":{"moduleName":"MacroDefinition","name":"stringify","typeName":"StringifyMacro"},"syntax":{"kind":"expression","location":{{{.+}}},"source":"#stringify(a + b)"}}} +// CHECK-NEXT: <-(plugin:[[#PID1]]) {"expandFreestandingMacroResult":{"diagnostics":[],"expandedSource":"(a + b, \"a + b\")"}} +// CHECK-NEXT: ->(plugin:[[#PID1]]) {"expandFreestandingMacro":{"discriminator":"${{.*}}","macro":{"moduleName":"EvilMacros","name":"evil","typeName":"CrashingMacro"},"syntax":{"kind":"expression","location":{{{.+}}},"source":"#evil(42)"}}} +// ^ This crashes the plugin server. + +// CHECK-NEXT: ->(plugin:[[#PID2:]]) {"loadPluginLibrary":{"libraryPath":"BUILD_DIR{{.*}}plugins/libMacroDefinition.dylib","moduleName":"MacroDefinition"}} +// CHECK-NEXT: <-(plugin:[[#PID2]]) {"loadPluginLibraryResult":{"diagnostics":[],"loaded":true}} +// CHECK-NEXT: ->(plugin:[[#PID2]]) {"loadPluginLibrary":{"libraryPath":"BUILD_DIR{{.*}}plugins/libEvilMacros.dylib","moduleName":"EvilMacros"}} +// CHECK-NEXT: <-(plugin:[[#PID2]]) {"loadPluginLibraryResult":{"diagnostics":[],"loaded":true}} +// CHECK-NEXT: ->(plugin:[[#PID2]]) {"expandFreestandingMacro":{"discriminator":"${{.*}}","macro":{"moduleName":"MacroDefinition","name":"stringify","typeName":"StringifyMacro"},"syntax":{"kind":"expression","location":{{{.+}}},"source":"#stringify(b + a)"}}} +// CHECK-NEXT: <-(plugin:[[#PID2]]) {"expandFreestandingMacroResult":{"diagnostics":[],"expandedSource":"(b + a, \"b + a\")"}} @freestanding(expression) macro stringify(_ value: T) -> (T, String) = #externalMacro(module: "MacroDefinition", type: "StringifyMacro") +@freestanding(expression) macro evil(_ value: Int) -> String = #externalMacro(module: "EvilMacros", type: "CrashingMacro") func testStringify(a: Int, b: Int) { - let s: String = #stringify(a + b).1 - print(s) -} + let s1: String = #stringify(a + b).1 + print(s1) -// CHECK: {{^}}------------------------------ -// CHECK-NEXT: {{^}}(a + b, "a + b") -// CHECK-NEXT: {{^}}------------------------------ + // expected-error @+1 {{failedToReceiveMessage (from macro 'evil')}} + let s2: String = #evil(42) + print(s2) + let s3: String = #stringify(b + a).1 + print(s3) +} diff --git a/tools/SourceKit/lib/SwiftLang/SwiftASTManager.cpp b/tools/SourceKit/lib/SwiftLang/SwiftASTManager.cpp index b5b3edfe34cfe..ef667aa9c54aa 100644 --- a/tools/SourceKit/lib/SwiftLang/SwiftASTManager.cpp +++ b/tools/SourceKit/lib/SwiftLang/SwiftASTManager.cpp @@ -553,10 +553,12 @@ struct SwiftASTManager::Implementation { std::shared_ptr EditorDocs, std::shared_ptr Config, std::shared_ptr Stats, - std::shared_ptr ReqTracker, StringRef SwiftExecutablePath, + std::shared_ptr ReqTracker, + std::shared_ptr Plugins, StringRef SwiftExecutablePath, StringRef RuntimeResourcePath, StringRef DiagnosticDocumentationPath) : EditorDocs(EditorDocs), Config(Config), Stats(Stats), - ReqTracker(ReqTracker), SwiftExecutablePath(SwiftExecutablePath), + ReqTracker(ReqTracker), Plugins(Plugins), + SwiftExecutablePath(SwiftExecutablePath), RuntimeResourcePath(RuntimeResourcePath), DiagnosticDocumentationPath(DiagnosticDocumentationPath), SessionTimestamp(llvm::sys::toTimeT(std::chrono::system_clock::now())) { @@ -566,6 +568,7 @@ struct SwiftASTManager::Implementation { std::shared_ptr Config; std::shared_ptr Stats; std::shared_ptr ReqTracker; + std::shared_ptr Plugins; /// The path of the swift-frontend executable. /// Used to find clang relative to it. std::string SwiftExecutablePath; @@ -638,9 +641,10 @@ SwiftASTManager::SwiftASTManager( std::shared_ptr EditorDocs, std::shared_ptr Config, std::shared_ptr Stats, - std::shared_ptr ReqTracker, StringRef SwiftExecutablePath, + std::shared_ptr ReqTracker, + std::shared_ptr Plugins, StringRef SwiftExecutablePath, StringRef RuntimeResourcePath, StringRef DiagnosticDocumentationPath) - : Impl(*new Implementation(EditorDocs, Config, Stats, ReqTracker, + : Impl(*new Implementation(EditorDocs, Config, Stats, ReqTracker, Plugins, SwiftExecutablePath, RuntimeResourcePath, DiagnosticDocumentationPath)) {} @@ -1073,6 +1077,7 @@ ASTUnitRef ASTBuildOperation::buildASTUnit(std::string &Error) { } return nullptr; } + CompIns.getASTContext().setPluginRegistry(ASTManager->Impl.Plugins.get()); CompIns.getASTContext().CancellationFlag = CancellationFlag; registerIDERequestFunctions(CompIns.getASTContext().evaluator); if (TracedOp.enabled()) { diff --git a/tools/SourceKit/lib/SwiftLang/SwiftASTManager.h b/tools/SourceKit/lib/SwiftLang/SwiftASTManager.h index 154b281fbc772..8eb761f752532 100644 --- a/tools/SourceKit/lib/SwiftLang/SwiftASTManager.h +++ b/tools/SourceKit/lib/SwiftLang/SwiftASTManager.h @@ -95,6 +95,7 @@ namespace swift { class CompilerInstance; class CompilerInvocation; class DiagnosticEngine; + class PluginRegistry; class SourceFile; class SourceManager; } @@ -235,6 +236,7 @@ class SwiftASTManager : public std::enable_shared_from_this { std::shared_ptr Config, std::shared_ptr Stats, std::shared_ptr ReqTracker, + std::shared_ptr Plugins, StringRef SwiftExecutablePath, StringRef RuntimeResourcePath, StringRef DiagnosticDocumentationPath); diff --git a/tools/SourceKit/lib/SwiftLang/SwiftCompile.cpp b/tools/SourceKit/lib/SwiftLang/SwiftCompile.cpp index 0bf275568941c..a261d6e5057ed 100644 --- a/tools/SourceKit/lib/SwiftLang/SwiftCompile.cpp +++ b/tools/SourceKit/lib/SwiftLang/SwiftCompile.cpp @@ -31,7 +31,9 @@ compile::SessionManager::getSession(StringRef name) { } bool inserted = false; - std::tie(i, inserted) = sessions.try_emplace(name, std::make_shared(RuntimeResourcePath, DiagnosticDocumentationPath)); + std::tie(i, inserted) = sessions.try_emplace( + name, std::make_shared( + RuntimeResourcePath, DiagnosticDocumentationPath, Plugins)); assert(inserted); return i->second; } @@ -141,10 +143,10 @@ void SwiftLangSupport::performCompile( CancellationFlag->store(true, std::memory_order_relaxed); }); - CompileManager.performCompileAsync(Name, Args, std::move(fileSystem), - CancellationFlag, Receiver); + CompileManager->performCompileAsync(Name, Args, std::move(fileSystem), + CancellationFlag, Receiver); } void SwiftLangSupport::closeCompile(StringRef Name) { - CompileManager.clearSession(Name); + CompileManager->clearSession(Name); } diff --git a/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.cpp b/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.cpp index 53167f53c8d92..77ff2110e4916 100644 --- a/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.cpp +++ b/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.cpp @@ -276,8 +276,7 @@ static void configureIDEInspectionInstance( SwiftLangSupport::SwiftLangSupport(SourceKit::Context &SKCtx) : NotificationCtr(SKCtx.getNotificationCenter()), SwiftExecutablePath(SKCtx.getSwiftExecutablePath()), - ReqTracker(SKCtx.getRequestTracker()), CCCache(new SwiftCompletionCache), - CompileManager(RuntimeResourcePath, DiagnosticDocumentationPath) { + ReqTracker(SKCtx.getRequestTracker()), CCCache(new SwiftCompletionCache) { llvm::SmallString<128> LibPath(SKCtx.getRuntimeLibPath()); llvm::sys::path::append(LibPath, "swift"); RuntimeResourcePath = std::string(LibPath.str()); @@ -285,15 +284,21 @@ SwiftLangSupport::SwiftLangSupport(SourceKit::Context &SKCtx) Stats = std::make_shared(); EditorDocuments = std::make_shared(); + + std::shared_ptr Plugins = std::make_shared(); + ASTMgr = std::make_shared( EditorDocuments, SKCtx.getGlobalConfiguration(), Stats, ReqTracker, - SwiftExecutablePath, RuntimeResourcePath, DiagnosticDocumentationPath); - - IDEInspectionInst = std::make_shared(); + Plugins, SwiftExecutablePath, RuntimeResourcePath, + DiagnosticDocumentationPath); + IDEInspectionInst = std::make_shared(Plugins); configureIDEInspectionInstance(IDEInspectionInst, SKCtx.getGlobalConfiguration()); + CompileManager = std::make_shared( + RuntimeResourcePath, DiagnosticDocumentationPath, Plugins); + // By default, just use the in-memory cache. CCCache->inMemory = std::make_unique(); diff --git a/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.h b/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.h index 40968bdc159bf..6ac85d1c2e387 100644 --- a/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.h +++ b/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.h @@ -293,8 +293,9 @@ class Session { public: Session(const std::string &RuntimeResourcePath, - const std::string &DiagnosticDocumentationPath) - : Compiler(RuntimeResourcePath, DiagnosticDocumentationPath) {} + const std::string &DiagnosticDocumentationPath, + std::shared_ptr Plugins) + : Compiler(RuntimeResourcePath, DiagnosticDocumentationPath, Plugins) {} bool performCompile(llvm::ArrayRef Args, @@ -308,6 +309,7 @@ class Session { class SessionManager { const std::string &RuntimeResourcePath; const std::string &DiagnosticDocumentationPath; + const std::shared_ptr Plugins; llvm::StringMap> sessions; WorkQueue compileQueue{WorkQueue::Dequeuing::Concurrent, @@ -316,9 +318,11 @@ class SessionManager { public: SessionManager(std::string &RuntimeResourcePath, - std::string &DiagnosticDocumentationPath) + std::string &DiagnosticDocumentationPath, + std::shared_ptr Plugins) : RuntimeResourcePath(RuntimeResourcePath), - DiagnosticDocumentationPath(DiagnosticDocumentationPath) {} + DiagnosticDocumentationPath(DiagnosticDocumentationPath), + Plugins(Plugins) {} std::shared_ptr getSession(StringRef name); @@ -356,7 +360,7 @@ class SwiftLangSupport : public LangSupport { std::shared_ptr Stats; llvm::StringMap> FileSystemProviders; std::shared_ptr IDEInspectionInst; - compile::SessionManager CompileManager; + std::shared_ptr CompileManager; public: explicit SwiftLangSupport(SourceKit::Context &SKCtx); diff --git a/tools/swift-ide-test/swift-ide-test.cpp b/tools/swift-ide-test/swift-ide-test.cpp index ec62ef8b58a95..e7d84d1901341 100644 --- a/tools/swift-ide-test/swift-ide-test.cpp +++ b/tools/swift-ide-test/swift-ide-test.cpp @@ -23,6 +23,7 @@ #include "swift/AST/DiagnosticEngine.h" #include "swift/AST/ImportCache.h" #include "swift/AST/NameLookupRequests.h" +#include "swift/AST/PluginRegistry.h" #include "swift/AST/PrintOptions.h" #include "swift/AST/RawComment.h" #include "swift/AST/USRGeneration.h" @@ -1188,7 +1189,8 @@ static int doTypeContextInfo(const CompilerInvocation &InitInvok, InitInvok, SourceFilename, SecondSourceFileName, CodeCompletionToken, CodeCompletionDiagnostics, [&](CompletionLikeOperationParams Params) -> bool { - IDEInspectionInstance IDEInspectionInst; + IDEInspectionInstance IDEInspectionInst( + std::make_shared()); int ExitCode = 2; IDEInspectionInst.typeContextInfo( Params.Invocation, Params.Args, Params.FileSystem, @@ -1260,7 +1262,8 @@ doConformingMethodList(const CompilerInvocation &InitInvok, InitInvok, SourceFilename, SecondSourceFileName, CodeCompletionToken, CodeCompletionDiagnostics, [&](CompletionLikeOperationParams Params) -> bool { - IDEInspectionInstance IDEInspectionInst; + IDEInspectionInstance IDEInspectionInst( + std::make_shared()); int ExitCode = 2; IDEInspectionInst.conformingMethodList( Params.Invocation, Params.Args, Params.FileSystem, @@ -1410,7 +1413,7 @@ doCodeCompletion(const CompilerInvocation &InitInvok, StringRef SourceFilename, InitInvok, SourceFilename, SecondSourceFileName, CodeCompletionToken, CodeCompletionDiagnostics, [&](CompletionLikeOperationParams Params) -> bool { - IDEInspectionInstance Inst; + IDEInspectionInstance Inst(std::make_shared()); int ExitCode = 2; Inst.codeComplete( Params.Invocation, Params.Args, Params.FileSystem, @@ -1504,7 +1507,7 @@ static int doBatchCodeCompletion(const CompilerInvocation &InitInvok, CompilerInvocation Invocation(InitInvok); auto FileSystem = llvm::vfs::getRealFileSystem(); - IDEInspectionInstance IDEInspectionInst; + IDEInspectionInstance IDEInspectionInst(std::make_shared()); std::unique_ptr OnDiskCache; if (!options::CompletionCachePath.empty())