Skip to content

Commit bf13139

Browse files
committed
Allow unknown keys in access notes
LLVM’s YAML support wants to show a hard error if an unknown string appears in an access note. Instead, emit a remark but load the parts of the access note we do understand to allow for future expansion of access notes.
1 parent 899d1a4 commit bf13139

File tree

6 files changed

+87
-8
lines changed

6 files changed

+87
-8
lines changed

include/swift/AST/AccessNotes.h

+7
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
#include "llvm/Support/Error.h"
2525
#include "llvm/Support/MemoryBuffer.h"
2626
#include "llvm/Support/raw_ostream.h"
27+
#include <set>
28+
#include <string>
2729
#include <vector>
2830

2931
namespace swift {
@@ -63,6 +65,11 @@ class AccessNotes {
6365
std::string reason;
6466
std::vector<AccessNote> notes;
6567

68+
/// Contains keys which were present in the JSON file but were not recognized
69+
/// by the compiler. Has no functional effect, but can be used for error
70+
/// handling.
71+
std::set<std::string> unknownKeys;
72+
6673
static llvm::Expected<AccessNotes> load(ASTContext &ctx,
6774
llvm::MemoryBuffer *buffer);
6875

include/swift/AST/DiagnosticsFrontend.def

+4
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,10 @@ WARNING(invalid_access_notes_file,none,
409409
"access notes at '%0' will be ignored due to an error while loading "
410410
"them: %1",
411411
(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))
415+
412416

413417
#define UNDEFINE_DIAGNOSTIC_MACROS
414418
#include "DefineDiagnosticMacros.h"

lib/AST/AccessNotes.cpp

+46-6
Original file line numberDiff line numberDiff line change
@@ -191,11 +191,17 @@ convertToErrorAndJoin(const llvm::SMDiagnostic &diag, void *Context) {
191191
errors = llvm::joinErrors(std::move(errors), std::move(newError));
192192
}
193193

194+
struct AccessNoteYAMLContext {
195+
ASTContext &ctx;
196+
std::set<std::string> unknownKeys;
197+
};
198+
194199
llvm::Expected<AccessNotes>
195200
AccessNotes::load(ASTContext &ctx, llvm::MemoryBuffer *buffer) {
196201
llvm::Error errors = llvm::Error::success();
202+
AccessNoteYAMLContext yamlCtx = { ctx, {} };
197203

198-
llvm::yaml::Input yamlIn(buffer->getBuffer(), (void *)&ctx,
204+
llvm::yaml::Input yamlIn(buffer->getBuffer(), (void *)&yamlCtx,
199205
convertToErrorAndJoin, &errors);
200206

201207
AccessNotes notes;
@@ -204,6 +210,8 @@ AccessNotes::load(ASTContext &ctx, llvm::MemoryBuffer *buffer) {
204210
if (errors)
205211
return llvm::Expected<AccessNotes>(std::move(errors));
206212

213+
notes.unknownKeys = std::move(yamlCtx.unknownKeys);
214+
207215
return notes;
208216
}
209217
}
@@ -214,7 +222,7 @@ namespace yaml {
214222

215223
using AccessNote = swift::AccessNote;
216224
using AccessNotes = swift::AccessNotes;
217-
using ASTContext = swift::ASTContext;
225+
using AccessNoteYAMLContext = swift::AccessNoteYAMLContext;
218226
using AccessNoteDeclName = swift::AccessNoteDeclName;
219227
using ObjCSelector = swift::ObjCSelector;
220228

@@ -225,8 +233,9 @@ output(const AccessNoteDeclName &name, void *ctxPtr, raw_ostream &os) {
225233

226234
StringRef ScalarTraits<AccessNoteDeclName>::
227235
input(StringRef str, void *ctxPtr, AccessNoteDeclName &name) {
228-
ASTContext &ctx = *static_cast<ASTContext *>(ctxPtr);
229-
name = AccessNoteDeclName(ctx, str);
236+
auto &yamlCtx = *static_cast<swift::AccessNoteYAMLContext *>(ctxPtr);
237+
238+
name = AccessNoteDeclName(yamlCtx.ctx, str);
230239
return name.empty() ? "invalid declaration name" : "";
231240
}
232241

@@ -237,17 +246,46 @@ void ScalarTraits<ObjCSelector>::output(const ObjCSelector &selector,
237246

238247
StringRef ScalarTraits<ObjCSelector>::input(StringRef str, void *ctxPtr,
239248
ObjCSelector &selector) {
240-
ASTContext &ctx = *static_cast<ASTContext *>(ctxPtr);
249+
auto &yamlCtx = *static_cast<swift::AccessNoteYAMLContext *>(ctxPtr);
241250

242-
if (auto sel = ObjCSelector::parse(ctx, str)) {
251+
if (auto sel = ObjCSelector::parse(yamlCtx.ctx, str)) {
243252
selector = *sel;
244253
return "";
245254
}
246255

247256
return "invalid selector";
248257
}
249258

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

268306
void MappingTraits<AccessNotes>::mapping(IO &io, AccessNotes &notes) {
307+
diagnoseUnknownKeys(io, { "Reason", "Notes" });
308+
269309
io.mapRequired("Reason", notes.reason);
270310
io.mapRequired("Notes", notes.notes);
271311
}

lib/Frontend/Frontend.cpp

+19-2
Original file line numberDiff line numberDiff line change
@@ -957,8 +957,25 @@ ModuleDecl *CompilerInstance::getMainModule() const {
957957
return AccessNotes::load(*Context, buffer.get());
958958
});
959959

960-
if (expectedAccessNotes)
961-
MainModule->getAccessNotes() = std::move(expectedAccessNotes).get();
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+
}
962979
else
963980
llvm::handleAllErrors(expectedAccessNotes.takeError(),
964981
[&](const llvm::ErrorInfoBase &error) {

test/Sema/Inputs/extra.accessnotes

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Reason: Access notes containing future, unknown syntax
2+
CorinthianLeather: 'rich'
3+
Notes:
4+
- Name: 'fn()'
5+
CorinthianLeather: 'rich'

test/Sema/access-notes-invalid.swift

+6
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,9 @@
33

44
// RUN: %target-swift-frontend -typecheck -primary-file %s -access-notes-path %S/Inputs/bad.accessnotes 2>&1 | %FileCheck --check-prefix=CHECK-BAD %s
55
// 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{{$}}
6+
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 %s <%t.txt
9+
// RUN: %FileCheck --check-prefix=NEGATIVE-EXTRA %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

0 commit comments

Comments
 (0)