Skip to content

Commit 8d0040f

Browse files
committed
Improve access note error handling
The LLVM rebranch added an “AllowUnknownKeys” setting to llvm::yaml::Input, which lets us rip out a lot of marginal code and encourages a broader rework of access note error diagnosis: • Access note warnings and errors are now diagnosed as they’re found during YAML parsing • They now have proper SourceLocs inside the .accessnotes file • They’re now tested using -verify-additional-file instead of FileCheck • A lot of gross duct tape is now gone
1 parent d8c3463 commit 8d0040f

File tree

7 files changed

+71
-134
lines changed

7 files changed

+71
-134
lines changed

include/swift/AST/AccessNotes.h

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -94,15 +94,10 @@ class AccessNotesFile {
9494
/// Access notes to apply to the module.
9595
std::vector<AccessNote> Notes;
9696

97-
/// Contains keys which were present in the JSON file but were not recognized
98-
/// by the compiler. Has no functional effect, but can be used for error
99-
/// handling.
100-
std::set<std::string> unknownKeys;
101-
102-
/// Load the access notes from \p buffer, or return an error if they cannot
103-
/// be loaded.
104-
static llvm::Expected<AccessNotesFile> load(ASTContext &ctx,
105-
llvm::MemoryBuffer *buffer);
97+
/// Load the access notes from \p buffer, or \c None if they cannot be loaded.
98+
/// Diagnoses any parsing issues with the access notes file.
99+
static llvm::Optional<AccessNotesFile>
100+
load(ASTContext &ctx, const llvm::MemoryBuffer *buffer);
106101

107102
/// Look up the access note in this file, if any, which applies to \p VD.
108103
NullablePtr<const AccessNote> lookup(ValueDecl *VD) const;

include/swift/AST/DiagnosticsFrontend.def

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -405,13 +405,15 @@ WARNING(module_incompatible_with_skip_function_bodies,none,
405405
"-experimental-skip-*-function-bodies* flags; they have "
406406
"been automatically disabled", (StringRef))
407407

408-
WARNING(invalid_access_notes_file,none,
409-
"access notes at '%0' will be ignored due to an error while loading "
410-
"them: %1",
408+
WARNING(access_notes_file_io_error,none,
409+
"ignored access notes file at '%0' because it cannot be read: %1",
411410
(StringRef, StringRef))
412-
REMARK(unknown_keys_in_access_notes_file,none,
413-
"compiler ignored unknown key%s0 '%1' in access notes at '%2'",
414-
(bool, StringRef, StringRef))
411+
WARNING(error_in_access_notes_file,none,
412+
"ignored access notes file because it cannot be parsed: %0",
413+
(StringRef))
414+
REMARK(warning_in_access_notes_file,none,
415+
"ignored invalid content in access notes file: %0",
416+
(StringRef))
415417

416418

417419
#define UNDEFINE_DIAGNOSTIC_MACROS

lib/AST/AccessNotes.cpp

Lines changed: 30 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "swift/AST/Attr.h"
2020
#include "swift/AST/Decl.h"
2121
#include "swift/AST/Module.h" // DeclContext::isModuleScopeContext()
22+
#include "swift/AST/DiagnosticsFrontend.h"
2223
#include "swift/Parse/Parser.h"
2324
#include "llvm/ADT/STLExtras.h"
2425
#include "llvm/ADT/StringRef.h"
@@ -183,36 +184,36 @@ template <> struct llvm::yaml::MappingTraits<swift::AccessNote> {
183184
namespace swift {
184185

185186
static void
186-
convertToErrorAndJoin(const llvm::SMDiagnostic &diag, void *Context) {
187-
auto newError = llvm::createStringError(std::errc::invalid_argument,
188-
"%s at line %d, column %d",
189-
diag.getMessage().bytes_begin(),
190-
diag.getLineNo(), diag.getColumnNo());
191-
192-
auto &errors = *(llvm::Error*)Context;
193-
errors = llvm::joinErrors(std::move(errors), std::move(newError));
187+
convertToErrorAndJoin(const llvm::SMDiagnostic &diag, void *ctxPtr) {
188+
ASTContext &ctx = *(ASTContext*)ctxPtr;
189+
190+
SourceLoc loc{diag.getLoc()};
191+
assert(ctx.SourceMgr.isOwning(loc));
192+
193+
switch (diag.getKind()) {
194+
case llvm::SourceMgr::DK_Error:
195+
ctx.Diags.diagnose(loc, diag::error_in_access_notes_file,
196+
diag.getMessage());
197+
break;
198+
199+
default:
200+
ctx.Diags.diagnose(loc, diag::warning_in_access_notes_file,
201+
diag.getMessage());
202+
break;
203+
}
194204
}
195205

196-
struct AccessNoteYAMLContext {
197-
ASTContext &ctx;
198-
std::set<std::string> unknownKeys;
199-
};
200-
201-
llvm::Expected<AccessNotesFile>
202-
AccessNotesFile::load(ASTContext &ctx, llvm::MemoryBuffer *buffer) {
203-
llvm::Error errors = llvm::Error::success();
204-
AccessNoteYAMLContext yamlCtx = { ctx, {} };
205-
206-
llvm::yaml::Input yamlIn(buffer->getBuffer(), (void *)&yamlCtx,
207-
convertToErrorAndJoin, &errors);
206+
llvm::Optional<AccessNotesFile>
207+
AccessNotesFile::load(ASTContext &ctx, const llvm::MemoryBuffer *buffer) {
208+
llvm::yaml::Input yamlIn(llvm::MemoryBufferRef(*buffer), (void *)&ctx,
209+
convertToErrorAndJoin, &ctx);
210+
yamlIn.setAllowUnknownKeys(true);
208211

209212
AccessNotesFile notes;
210213
yamlIn >> notes;
211214

212-
if (errors)
213-
return llvm::Expected<AccessNotesFile>(std::move(errors));
214-
215-
notes.unknownKeys = std::move(yamlCtx.unknownKeys);
215+
if (yamlIn.error())
216+
return None;
216217

217218
return notes;
218219
}
@@ -224,7 +225,7 @@ namespace yaml {
224225

225226
using AccessNote = swift::AccessNote;
226227
using AccessNotesFile = swift::AccessNotesFile;
227-
using AccessNoteYAMLContext = swift::AccessNoteYAMLContext;
228+
using ASTContext = swift::ASTContext;
228229
using AccessNoteDeclName = swift::AccessNoteDeclName;
229230
using ObjCSelector = swift::ObjCSelector;
230231

@@ -235,9 +236,9 @@ output(const AccessNoteDeclName &name, void *ctxPtr, raw_ostream &os) {
235236

236237
StringRef ScalarTraits<AccessNoteDeclName>::
237238
input(StringRef str, void *ctxPtr, AccessNoteDeclName &name) {
238-
auto &yamlCtx = *static_cast<swift::AccessNoteYAMLContext *>(ctxPtr);
239+
auto &ctx = *static_cast<swift::ASTContext *>(ctxPtr);
239240

240-
name = AccessNoteDeclName(yamlCtx.ctx, str);
241+
name = AccessNoteDeclName(ctx, str);
241242
return name.empty() ? "invalid declaration name" : "";
242243
}
243244

@@ -248,46 +249,17 @@ void ScalarTraits<ObjCSelector>::output(const ObjCSelector &selector,
248249

249250
StringRef ScalarTraits<ObjCSelector>::input(StringRef str, void *ctxPtr,
250251
ObjCSelector &selector) {
251-
auto &yamlCtx = *static_cast<swift::AccessNoteYAMLContext *>(ctxPtr);
252+
auto &ctx = *static_cast<swift::ASTContext *>(ctxPtr);
252253

253-
if (auto sel = ObjCSelector::parse(yamlCtx.ctx, str)) {
254+
if (auto sel = ObjCSelector::parse(ctx, str)) {
254255
selector = *sel;
255256
return "";
256257
}
257258

258259
return "invalid selector";
259260
}
260261

261-
/// If \p io is outputting, mark all keys in the current mapping as used.
262-
static void diagnoseUnknownKeys(IO &io, ArrayRef<StringRef> expectedKeys) {
263-
if (io.outputting())
264-
return;
265-
266-
auto &yamlCtx = *static_cast<swift::AccessNoteYAMLContext *>(io.getContext());
267-
268-
for (auto key : io.keys()) {
269-
if (is_contained(expectedKeys, key))
270-
continue;
271-
272-
// "Use" this key without actually doing anything with it to suppress the
273-
// error llvm::yaml::Input will otherwise generate.
274-
bool useDefault;
275-
void *saveInfo;
276-
if (io.preflightKey((const char *)key.bytes_begin(), /*required=*/false,
277-
/*sameAsDefault=*/false, useDefault, saveInfo)) {
278-
// FIXME: We should diagnose these with locations, but llvm::yaml::Input
279-
// encapsulates all of the necessary details. Instead, we are currently
280-
// just building a list that the frontend will display.
281-
yamlCtx.unknownKeys.insert(key.str());
282-
283-
io.postflightKey(saveInfo);
284-
}
285-
}
286-
}
287-
288262
void MappingTraits<AccessNote>::mapping(IO &io, AccessNote &note) {
289-
diagnoseUnknownKeys(io, { "Name", "ObjC", "Dynamic", "ObjCName" });
290-
291263
io.mapRequired("Name", note.Name);
292264
io.mapOptional("ObjC", note.ObjC);
293265
io.mapOptional("Dynamic", note.Dynamic);
@@ -306,8 +278,6 @@ std::string MappingTraits<AccessNote>::validate(IO &io, AccessNote &note) {
306278
}
307279

308280
void MappingTraits<AccessNotesFile>::mapping(IO &io, AccessNotesFile &notes) {
309-
diagnoseUnknownKeys(io, { "Reason", "Notes" });
310-
311281
io.mapRequired("Reason", notes.Reason);
312282
io.mapRequired("Notes", notes.Notes);
313283
}

lib/Frontend/Frontend.cpp

Lines changed: 23 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -902,16 +902,6 @@ bool CompilerInstance::createFilesForMainModule(
902902
return false;
903903
}
904904

905-
template<typename ExpectedOut, typename ExpectedIn>
906-
static llvm::Expected<ExpectedOut>
907-
flatMap(llvm::Expected<ExpectedIn> &&in,
908-
llvm::function_ref<llvm::Expected<ExpectedOut>(ExpectedIn &&)> transform) {
909-
if (!in)
910-
return llvm::Expected<ExpectedOut>(in.takeError());
911-
912-
return transform(std::move(in.get()));
913-
}
914-
915905
ModuleDecl *CompilerInstance::getMainModule() const {
916906
if (!MainModule) {
917907
Identifier ID = Context->getIdentifier(Invocation.getModuleName());
@@ -943,46 +933,6 @@ ModuleDecl *CompilerInstance::getMainModule() const {
943933
// into a partial module that failed to load.
944934
MainModule->setFailedToLoad();
945935
}
946-
947-
if (!Invocation.getFrontendOptions().AccessNotesPath.empty()) {
948-
auto accessNotesPath = Invocation.getFrontendOptions().AccessNotesPath;
949-
950-
auto expectedBuffer = llvm::errorOrToExpected(
951-
swift::vfs::getFileOrSTDIN(getFileSystem(), accessNotesPath));
952-
953-
auto expectedAccessNotes =
954-
flatMap<AccessNotesFile, std::unique_ptr<llvm::MemoryBuffer>>(
955-
std::move(expectedBuffer),
956-
[&](auto &&buffer) -> auto {
957-
return AccessNotesFile::load(*Context, buffer.get());
958-
});
959-
960-
if (expectedAccessNotes) {
961-
auto &notes = std::move(expectedAccessNotes).get();
962-
963-
// If there were unknown keys in the file, diagnose that.
964-
if (!notes.unknownKeys.empty()) {
965-
// Create a string like "key1', 'key2', 'key3". The diagnostic itself
966-
// provides the leading and trailing single quotes.
967-
SmallString<64> scratch;
968-
llvm::raw_svector_ostream scratchOS(scratch);
969-
llvm::interleave(notes.unknownKeys, scratchOS, "', '");
970-
971-
Context->Diags.diagnose(SourceLoc(),
972-
diag::unknown_keys_in_access_notes_file,
973-
notes.unknownKeys.size(), scratch,
974-
accessNotesPath);
975-
}
976-
977-
MainModule->getAccessNotes() = std::move(notes);
978-
}
979-
else
980-
llvm::handleAllErrors(expectedAccessNotes.takeError(),
981-
[&](const llvm::ErrorInfoBase &error) {
982-
Context->Diags.diagnose(SourceLoc(), diag::invalid_access_notes_file,
983-
accessNotesPath, error.message());
984-
});
985-
}
986936
}
987937
return MainModule;
988938
}
@@ -996,8 +946,30 @@ void CompilerInstance::setMainModule(ModuleDecl *newMod) {
996946
bool CompilerInstance::performParseAndResolveImportsOnly() {
997947
FrontendStatsTracer tracer(getStatsReporter(), "parse-and-resolve-imports");
998948

999-
// Resolve imports for all the source files.
1000949
auto *mainModule = getMainModule();
950+
951+
// Load access notes.
952+
if (!Invocation.getFrontendOptions().AccessNotesPath.empty()) {
953+
auto accessNotesPath = Invocation.getFrontendOptions().AccessNotesPath;
954+
955+
auto bufferOrError =
956+
swift::vfs::getFileOrSTDIN(getFileSystem(), accessNotesPath);
957+
if (bufferOrError) {
958+
int sourceID =
959+
SourceMgr.addNewSourceBuffer(std::move(bufferOrError.get()));
960+
auto buffer =
961+
SourceMgr.getLLVMSourceMgr().getMemoryBuffer(sourceID);
962+
963+
if (auto accessNotesFile = AccessNotesFile::load(*Context, buffer))
964+
mainModule->getAccessNotes() = *accessNotesFile;
965+
}
966+
else {
967+
Diagnostics.diagnose(SourceLoc(), diag::access_notes_file_io_error,
968+
accessNotesPath, bufferOrError.getError().message());
969+
}
970+
}
971+
972+
// Resolve imports for all the source files.
1001973
for (auto *file : mainModule->getFiles()) {
1002974
if (auto *SF = dyn_cast<SourceFile>(file))
1003975
performImportResolution(*SF);

test/Sema/Inputs/bad.accessnotes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
Colourless green ideas sleep furiously.
2+
# expected-warning@-1 {{ignored access notes file because it cannot be parsed: not a mapping}}

test/Sema/Inputs/extra.accessnotes

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
Reason: Access notes containing future, unknown syntax
22
CorinthianLeather: 'rich'
3+
# expected-remark@-1 {{ignored invalid content in access notes file: unknown key 'CorinthianLeather'}}
34
Notes:
45
- Name: 'fn()'
56
CorinthianLeather: 'rich'
7+
# expected-remark@-1 {{ignored invalid content in access notes file: unknown key 'CorinthianLeather'}}

test/Sema/access-notes-invalid.swift

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,9 @@
11
// RUN: %target-swift-frontend -typecheck -primary-file %s -access-notes-path %S/Inputs/missing.accessnotes 2>&1 | %FileCheck --check-prefix=CHECK-MISSING --enable-windows-compatibility %s
2-
// CHECK-MISSING: <unknown>:0: warning: access notes at 'SOURCE_DIR/test/Sema/Inputs/missing.accessnotes' will be ignored due to an error while loading them: No such file or directory{{$}}
2+
// CHECK-MISSING: <unknown>:0: warning: ignored access notes file at 'SOURCE_DIR/test/Sema/Inputs/missing.accessnotes' because it cannot be read: No such file or directory{{$}}
33

4-
// RUN: %target-swift-frontend -typecheck -primary-file %s -access-notes-path %S/Inputs/bad.accessnotes 2>&1 | %FileCheck --check-prefix=CHECK-BAD --enable-windows-compatibility %s
5-
// CHECK-BAD: <unknown>:0: warning: access notes at 'SOURCE_DIR/test/Sema/Inputs/bad.accessnotes' will be ignored due to an error while loading them: not a mapping at line 1, column 0{{$}}
4+
// RUN: %target-typecheck-verify-swift -access-notes-path %S/Inputs/bad.accessnotes -verify-additional-file %S/Inputs/bad.accessnotes
65

7-
// RUN: %target-swift-frontend -typecheck -primary-file %s -access-notes-path %S/Inputs/extra.accessnotes >%t.txt 2>&1
8-
// RUN: %FileCheck --check-prefix=CHECK-EXTRA --enable-windows-compatibility %s <%t.txt
9-
// RUN: %FileCheck --check-prefix=NEGATIVE-EXTRA --enable-windows-compatibility %s <%t.txt
10-
// CHECK-EXTRA: <unknown>:0: remark: compiler ignored unknown key 'CorinthianLeather' in access notes at 'SOURCE_DIR/test/Sema/Inputs/extra.accessnotes'
11-
// NEGATIVE-EXTRA-NOT: <unknown>:0: warning: access notes at 'SOURCE_DIR/test/Sema/Inputs/extra.accessnotes' will be ignored due to an error while loading them
6+
// RUN: %target-typecheck-verify-swift -access-notes-path %S/Inputs/extra.accessnotes -verify-additional-file %S/Inputs/extra.accessnotes
127

138
// FIXME: Should diagnose multiple access notes for the same decl
149

0 commit comments

Comments
 (0)