Skip to content

Commit 93aabff

Browse files
authored
Merge pull request swiftlang#64006 from rintaro/macros-plugin-errhandling
[Macros] Improve error handling for executable macro plugins
2 parents 0d28950 + a6e0191 commit 93aabff

File tree

5 files changed

+185
-51
lines changed

5 files changed

+185
-51
lines changed

lib/AST/CASTBridging.cpp

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -653,15 +653,26 @@ bool Plugin_sendMessage(PluginHandle handle, const BridgedData data) {
653653
auto *plugin = static_cast<LoadedExecutablePlugin *>(handle);
654654
StringRef message(data.baseAddress, data.size);
655655
auto error = plugin->sendMessage(message);
656-
bool hadError = bool(error);
657-
llvm::consumeError(std::move(error));
658-
return hadError;
656+
if (error) {
657+
// FIXME: Pass the error message back to the caller.
658+
llvm::consumeError(std::move(error));
659+
// llvm::handleAllErrors(std::move(error), [](const llvm::ErrorInfoBase &err) {
660+
// llvm::errs() << err.message() << "\n";
661+
// });
662+
return true;
663+
}
664+
return false;
659665
}
660666

661667
bool Plugin_waitForNextMessage(PluginHandle handle, BridgedData *out) {
662668
auto *plugin = static_cast<LoadedExecutablePlugin *>(handle);
663669
auto result = plugin->waitForNextMessage();
664670
if (!result) {
671+
// FIXME: Pass the error message back to the caller.
672+
llvm::consumeError(result.takeError());
673+
// llvm::handleAllErrors(result.takeError(), [](const llvm::ErrorInfoBase &err) {
674+
// llvm::errs() << err.message() << "\n";
675+
// });
665676
return true;
666677
}
667678
auto &message = result.get();

lib/AST/PluginRegistry.cpp

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
#include "swift/AST/PluginRegistry.h"
1414

15+
#include "swift/Basic/Defer.h"
1516
#include "swift/Basic/LLVM.h"
1617
#include "swift/Basic/Program.h"
1718
#include "swift/Basic/Sandbox.h"
@@ -21,6 +22,7 @@
2122
#include "llvm/Support/raw_ostream.h"
2223
#include "llvm/Config/config.h"
2324

25+
#include <signal.h>
2426

2527
#if defined(_WIN32)
2628
#define WIN32_LEAN_AND_MEAN
@@ -119,7 +121,6 @@ LoadedExecutablePlugin::LoadedExecutablePlugin(
119121
outputFileDescriptor(outputFileDescriptor) {}
120122

121123
LoadedExecutablePlugin::~LoadedExecutablePlugin() {
122-
// Close the pipes.
123124
close(inputFileDescriptor);
124125
close(outputFileDescriptor);
125126

@@ -131,10 +132,18 @@ ssize_t LoadedExecutablePlugin::read(void *buf, size_t nbyte) const {
131132
ssize_t bytesToRead = nbyte;
132133
void *ptr = buf;
133134

135+
#if defined(SIGPIPE)
136+
/// Ignore SIGPIPE while reading.
137+
auto *old_handler = signal(SIGPIPE, SIG_IGN);
138+
SWIFT_DEFER { signal(SIGPIPE, old_handler); };
139+
#endif
140+
134141
while (bytesToRead > 0) {
135142
ssize_t readingSize = std::min(ssize_t(INT32_MAX), bytesToRead);
136143
ssize_t readSize = ::read(inputFileDescriptor, ptr, readingSize);
137-
if (readSize == 0) {
144+
if (readSize <= 0) {
145+
// 0: EOF (the plugin exited?), -1: error (e.g. broken pipe.)
146+
// FIXME: Mark the plugin 'stale' and relaunch later.
138147
break;
139148
}
140149
ptr = static_cast<char *>(ptr) + readSize;
@@ -148,10 +157,18 @@ ssize_t LoadedExecutablePlugin::write(const void *buf, size_t nbyte) const {
148157
ssize_t bytesToWrite = nbyte;
149158
const void *ptr = buf;
150159

160+
#if defined(SIGPIPE)
161+
/// Ignore SIGPIPE while writing.
162+
auto *old_handler = signal(SIGPIPE, SIG_IGN);
163+
SWIFT_DEFER { signal(SIGPIPE, old_handler); };
164+
#endif
165+
151166
while (bytesToWrite > 0) {
152167
ssize_t writingSize = std::min(ssize_t(INT32_MAX), bytesToWrite);
153168
ssize_t writtenSize = ::write(outputFileDescriptor, ptr, writingSize);
154-
if (writtenSize == 0) {
169+
if (writtenSize <= 0) {
170+
// -1: error (e.g. broken pipe,)
171+
// FIXME: Mark the plugin 'stale' and relaunch later.
155172
break;
156173
}
157174
ptr = static_cast<const char *>(ptr) + writtenSize;
@@ -170,12 +187,17 @@ llvm::Error LoadedExecutablePlugin::sendMessage(llvm::StringRef message) const {
170187
uint64_t header = llvm::support::endian::byte_swap(
171188
uint64_t(size), llvm::support::endianness::little);
172189
writtenSize = write(&header, sizeof(header));
173-
assert(writtenSize == sizeof(header) &&
174-
"failed to write plugin message header");
190+
if (writtenSize != sizeof(header)) {
191+
return llvm::createStringError(llvm::inconvertibleErrorCode(),
192+
"failed to write plugin message header");
193+
}
175194

176195
// Write message.
177196
writtenSize = write(data, size);
178-
assert(writtenSize == ssize_t(size) && "failed to write plugin message data");
197+
if (writtenSize != ssize_t(size)) {
198+
return llvm::createStringError(llvm::inconvertibleErrorCode(),
199+
"failed to write plugin message data");
200+
}
179201

180202
return llvm::Error::success();
181203
}
@@ -187,8 +209,10 @@ llvm::Expected<std::string> LoadedExecutablePlugin::waitForNextMessage() const {
187209
uint64_t header;
188210
readSize = read(&header, sizeof(header));
189211

190-
// FIXME: Error handling. Disconnection, etc.
191-
assert(readSize == sizeof(header) && "failed to read plugin message header");
212+
if (readSize != sizeof(header)) {
213+
return llvm::createStringError(llvm::inconvertibleErrorCode(),
214+
"failed to read plugin message header");
215+
}
192216

193217
size_t size = llvm::support::endian::read<uint64_t>(
194218
&header, llvm::support::endianness::little);
@@ -200,6 +224,10 @@ llvm::Expected<std::string> LoadedExecutablePlugin::waitForNextMessage() const {
200224
while (sizeToRead > 0) {
201225
char buffer[4096];
202226
readSize = read(buffer, std::min(sizeof(buffer), sizeToRead));
227+
if (readSize == 0) {
228+
return llvm::createStringError(llvm::inconvertibleErrorCode(),
229+
"failed to read plugin message data");
230+
}
203231
sizeToRead -= readSize;
204232
message.append(buffer, readSize);
205233
}

lib/ASTGen/Sources/ASTGen/Macros.swift

Lines changed: 58 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -254,30 +254,40 @@ func expandFreestandingMacroIPC(
254254

255255
let macro = macroPtr.assumingMemoryBound(to: ExportedExecutableMacro.self).pointee
256256

257+
// Send the message.
257258
let message = HostToPluginMessage.expandFreestandingMacro(
258259
macro: .init(moduleName: macro.moduleName, typeName: macro.typeName, name: macroName),
259260
discriminator: discriminator,
260261
syntax: PluginMessage.Syntax(syntax: macroSyntax, in: sourceFilePtr)!)
261-
262262
do {
263263
let result = try macro.plugin.sendMessageAndWait(message)
264-
switch result {
265-
case .expandFreestandingMacroResult(var expandedSource, let diagnostics):
266-
if !diagnostics.isEmpty {
267-
let diagEngine = PluginDiagnosticsEngine(cxxDiagnosticEngine: diagEnginePtr)
268-
diagEngine.add(exportedSourceFile: sourceFilePtr)
269-
270-
for diagnostic in diagnostics {
271-
diagEngine.emit(diagnostic, messageSuffix: " (from macro '\(macroName)')")
272-
}
273-
}
274-
return expandedSource
264+
guard
265+
case .expandFreestandingMacroResult(let expandedSource, let diagnostics) = result
266+
else {
267+
throw PluginError.invalidReponseKind
268+
}
275269

276-
default:
277-
fatalError("unexpected result")
270+
// Process the result.
271+
if !diagnostics.isEmpty {
272+
let diagEngine = PluginDiagnosticsEngine(cxxDiagnosticEngine: diagEnginePtr)
273+
diagEngine.add(exportedSourceFile: sourceFilePtr)
274+
diagEngine.emit(diagnostics, messageSuffix: " (from macro '\(macroName)')")
278275
}
276+
return expandedSource
277+
279278
} catch let error {
280-
fatalError("\(error)")
279+
let srcMgr = SourceManager(cxxDiagnosticEngine: diagEnginePtr)
280+
srcMgr.insert(sourceFilePtr)
281+
srcMgr.diagnose(
282+
diagnostic: .init(
283+
node: macroSyntax,
284+
// FIXME: This is probably a plugin communication error.
285+
// The error might not be relevant as the diagnostic message.
286+
message: ThrownErrorDiagnostic(message: String(describing: error))
287+
),
288+
messageSuffix: " (from macro '\(macroName)')"
289+
)
290+
return nil
281291
}
282292
}
283293

@@ -580,6 +590,7 @@ func expandAttachedMacroIPC(
580590
parentDeclSyntax = nil
581591
}
582592

593+
// Send the message.
583594
let message = HostToPluginMessage.expandAttachedMacro(
584595
macro: .init(moduleName: macro.moduleName, typeName: macro.typeName, name: macroName),
585596
macroRole: macroRole,
@@ -589,30 +600,42 @@ func expandAttachedMacroIPC(
589600
parentDeclSyntax: parentDeclSyntax)
590601
do {
591602
let result = try macro.plugin.sendMessageAndWait(message)
592-
switch result {
593-
case .expandAttachedMacroResult(let expandedSources, let diagnostics):
594-
// Form the result buffer for our caller.
595-
596-
if !diagnostics.isEmpty {
597-
let diagEngine = PluginDiagnosticsEngine(cxxDiagnosticEngine: diagEnginePtr)
598-
diagEngine.add(exportedSourceFile: customAttrSourceFilePtr)
599-
diagEngine.add(exportedSourceFile: declarationSourceFilePtr)
600-
if let parentDeclSourceFilePtr = parentDeclSourceFilePtr {
601-
diagEngine.add(exportedSourceFile: parentDeclSourceFilePtr)
602-
}
603-
604-
for diagnostic in diagnostics {
605-
diagEngine.emit(diagnostic, messageSuffix: " (from macro '\(macroName)')")
606-
}
603+
guard
604+
case .expandAttachedMacroResult(let expandedSources, let diagnostics) = result
605+
else {
606+
throw PluginError.invalidReponseKind
607+
}
607608

609+
// Process the result.
610+
if !diagnostics.isEmpty {
611+
let diagEngine = PluginDiagnosticsEngine(cxxDiagnosticEngine: diagEnginePtr)
612+
diagEngine.add(exportedSourceFile: customAttrSourceFilePtr)
613+
diagEngine.add(exportedSourceFile: declarationSourceFilePtr)
614+
if let parentDeclSourceFilePtr = parentDeclSourceFilePtr {
615+
diagEngine.add(exportedSourceFile: parentDeclSourceFilePtr)
608616
}
609-
return expandedSources
610-
611-
default:
612-
fatalError("unexpected result")
617+
diagEngine.emit(diagnostics, messageSuffix: " (from macro '\(macroName)')")
613618
}
619+
return expandedSources
620+
614621
} catch let error {
615-
fatalError("\(error)")
622+
let srcMgr = SourceManager(cxxDiagnosticEngine: diagEnginePtr)
623+
srcMgr.insert(customAttrSourceFilePtr)
624+
srcMgr.insert(declarationSourceFilePtr)
625+
if let parentDeclSourceFilePtr = parentDeclSourceFilePtr {
626+
srcMgr.insert(parentDeclSourceFilePtr)
627+
}
628+
// FIXME: Need to decide where to diagnose the error:
629+
srcMgr.diagnose(
630+
diagnostic: .init(
631+
node: Syntax(declarationNode),
632+
// FIXME: This is probably a plugin communication error.
633+
// The error might not be relevant as the diagnostic message.
634+
message: ThrownErrorDiagnostic(message: String(describing: error))
635+
),
636+
messageSuffix: " (from macro '\(macroName)')"
637+
)
638+
return nil
616639
}
617640
}
618641

lib/ASTGen/Sources/ASTGen/PluginHost.swift

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@ import CASTBridging
1414
import CBasicBridging
1515
import SwiftSyntax
1616

17-
struct PluginError: Error {}
17+
enum PluginError: Error {
18+
case failedToSendMessage
19+
case failedToReceiveMessage
20+
case invalidReponseKind
21+
}
1822

1923
@_cdecl("swift_ASTGen_initializePlugin")
2024
public func _initializePlugin(
@@ -50,7 +54,7 @@ struct CompilerPlugin {
5054
return Plugin_sendMessage(opaqueHandle, BridgedData(baseAddress: data.baseAddress, size: data.count))
5155
}
5256
if hadError {
53-
throw PluginError()
57+
throw PluginError.failedToSendMessage
5458
}
5559
}
5660

@@ -59,7 +63,7 @@ struct CompilerPlugin {
5963
let hadError = Plugin_waitForNextMessage(opaqueHandle, &result)
6064
defer { BridgedData_free(result) }
6165
guard !hadError else {
62-
throw PluginError()
66+
throw PluginError.failedToReceiveMessage
6367
}
6468
let data = UnsafeBufferPointer(start: result.baseAddress, count: result.size)
6569
// // FIXME: Add -dump-plugin-message option?
@@ -209,19 +213,31 @@ class PluginDiagnosticsEngine {
209213
SwiftDiagnostic_finish(diag)
210214
}
211215

216+
/// Emit diagnostics.
217+
func emit(
218+
_ diagnostics: [PluginMessage.Diagnostic],
219+
messageSuffix: String? = nil
220+
) {
221+
for diagnostic in diagnostics {
222+
self.emit(diagnostic)
223+
}
224+
}
225+
212226
/// Produce the C++ source location for a given position based on a
213227
/// syntax node.
214228
private func cxxSourceLocation(
215229
at offset: Int, in fileName: String
216230
) -> CxxSourceLoc? {
217231
// Find the corresponding exported source file.
218-
guard let exportedSourceFile = exportedSourceFileByName[fileName]
232+
guard
233+
let exportedSourceFile = exportedSourceFileByName[fileName]
219234
else {
220235
return nil
221236
}
222237

223238
// Compute the resulting address.
224-
guard let bufferBaseAddress = exportedSourceFile.pointee.buffer.baseAddress
239+
guard
240+
let bufferBaseAddress = exportedSourceFile.pointee.buffer.baseAddress
225241
else {
226242
return nil
227243
}

test/Macros/macro_plugin_error.swift

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// REQUIRES: OS=macosx
2+
3+
// RUN: %empty-directory(%t)
4+
// RUN: split-file %s %t
5+
6+
// RUN: %clang \
7+
// RUN: -isysroot %sdk \
8+
// RUN: -I %swift_src_root/include \
9+
// RUN: -L %swift-lib-dir -l_swiftMockPlugin \
10+
// RUN: -Wl,-rpath,%swift-lib-dir \
11+
// RUN: -o %t/mock-plugin \
12+
// RUN: %t/plugin.c
13+
14+
// RUN: %swift-target-frontend \
15+
// RUN: -typecheck -verify \
16+
// RUN: -swift-version 5 -enable-experimental-feature Macros \
17+
// RUN: -load-plugin-executable %t/mock-plugin#TestPlugin \
18+
// RUN: -dump-macro-expansions \
19+
// RUN: %t/test.swift
20+
21+
//--- test.swift
22+
@freestanding(expression) macro fooMacro(_: Any) -> String = #externalMacro(module: "TestPlugin", type: "FooMacro")
23+
24+
func test() {
25+
// FIXME: Should be more user friendly message.
26+
27+
let _: String = #fooMacro(1)
28+
// expected-error @-1 {{typeMismatch(swiftASTGen.PluginToHostMessage}}
29+
let _: String = #fooMacro(2)
30+
// expected-error @-1 {{failedToReceiveMessage}}
31+
let _: String = #fooMacro(3)
32+
// expected-error @-1 {{failedToSendMessage}}
33+
//FIXME: ^ This should succeed. Error recovery is not implemented.
34+
}
35+
36+
//--- plugin.c
37+
#include "swift-c/MockPlugin/MockPlugin.h"
38+
39+
MOCK_PLUGIN([
40+
{
41+
"expect": {"getCapability": {}},
42+
"response": {"getCapabilityResult": {"capability": {"protocolVersion": 1}}}
43+
},
44+
{
45+
"expect": {"expandFreestandingMacro": {
46+
"macro": {"moduleName": "TestPlugin", "typeName": "FooMacro"},
47+
"syntax": {"kind": "expression", "source": "#fooMacro(1)"}}},
48+
"response": {"invalidResponse": {}}
49+
},
50+
{
51+
"expect": {"expandFreestandingMacro": {
52+
"macro": {"moduleName": "TestPlugin", "typeName": "FooMacro"},
53+
"syntax": {"kind": "expression", "source": "#fooMacro(3)"}}},
54+
"response": {"expandFreestandingMacroResult": {"expandedSource": "3", "diagnostics": []}}
55+
}
56+
])

0 commit comments

Comments
 (0)