//===--- AccessNotes.cpp - Access Notes -------------------------*- C++ -*-===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2014 - 2021 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// // // Implements access notes, which allow certain modifiers or attributes to be // added to the declarations in a module. // //===----------------------------------------------------------------------===// #include "swift/AST/AccessNotes.h" #include "swift/AST/ASTContext.h" #include "swift/AST/Attr.h" #include "swift/AST/Decl.h" #include "swift/AST/DiagnosticsFrontend.h" #include "swift/AST/Module.h" // DeclContext::isModuleScopeContext() #include "swift/Basic/Assertions.h" #include "swift/Parse/ParseDeclName.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/YAMLTraits.h" namespace swift { AccessNoteDeclName::AccessNoteDeclName() : parentNames(), name(), accessorKind(std::nullopt) {} AccessNoteDeclName::AccessNoteDeclName(ASTContext &ctx, StringRef str) { auto parsedName = parseDeclName(str); StringRef first, rest = parsedName.ContextName; while (!rest.empty()) { std::tie(first, rest) = rest.split('.'); parentNames.push_back(ctx.getIdentifier(first)); } if (parsedName.IsGetter) accessorKind = AccessorKind::Get; else if (parsedName.IsSetter) accessorKind = AccessorKind::Set; else accessorKind = std::nullopt; name = parsedName.formDeclName(ctx, /*isSubscript=*/true); } bool AccessNoteDeclName::matches(ValueDecl *VD) const { // The declaration whose name and context we ought to match. Usually `VD`, but // when `VD` is an accessor, it becomes the accessor's storage instead. auto lookupVD = VD; // First, we check if the accessor-ness of `VD` matches the accessor-ness of // the name, and update `lookupVD` if necessary. if (auto accessor = dyn_cast(VD)) { if (!accessorKind || *accessorKind != accessor->getAccessorKind()) return false; lookupVD = accessor->getStorage(); } else if (accessorKind.has_value()) return false; // Check that `name` matches `lookupVD`. if (!lookupVD->getName().matchesRef(name)) return false; // The rest of this checks `parentNames` against the parents of `lookupVD`. ArrayRef remainingContextNames = parentNames; DeclContext *nextContext = lookupVD->getDeclContext(); while (!nextContext->isModuleScopeContext()) { // If we've run out of names without reaching module scope, we've failed. if (remainingContextNames.empty()) return false; Identifier contextName = remainingContextNames.back(); // If the context is not a type (or extension), we can't name VD in an // access note and the match fails; if the name doesn't match, the match // fails too. auto contextType = nextContext->getSelfNominalTypeDecl(); if (!contextType || contextType->getName() != contextName) return false; // Still checking. Move to the parent. remainingContextNames = remainingContextNames.drop_back(); nextContext = contextType->getParent(); } // If the context is module-scoped, we've succeeded if we're out of names, or // failed if we still have some names to go. return remainingContextNames.empty(); } bool AccessNoteDeclName::empty() const { return !name; } void AccessNoteDeclName::print(llvm::raw_ostream &os) const { if (accessorKind) os << getAccessorLabel(*accessorKind) << "ter:"; for (auto parentName : parentNames) os << parentName << '.'; name.print(os, /*skipEmptyArgumentNames=*/false); } void AccessNoteDeclName::dump() const { print(llvm::errs()); llvm::errs() << '\n'; } NullablePtr AccessNotesFile::lookup(ValueDecl *VD) const { assert(VD != nullptr); auto iter = llvm::find_if(Notes, [&](const AccessNote ¬e) -> bool { return note.Name.matches(VD); }); return NullablePtr(iter == Notes.end() ? nullptr : &*iter); } void AccessNotesFile::dump() const { dump(llvm::dbgs()); llvm::dbgs() << "\n"; } void AccessNote::dump() const { dump(llvm::dbgs()); llvm::dbgs() << "\n"; } void AccessNotesFile::dump(llvm::raw_ostream &os) const { os << "(access_notes reason='" << Reason << "'"; for (const auto ¬e : Notes) { os << "\n"; note.dump(os, /*indent=*/2); } os << ")"; } void AccessNote::dump(llvm::raw_ostream &os, int indent) const { os.indent(indent) << "(note name='"; Name.print(os); os << "'"; if (Name.name.getBaseName().isSpecial()) os << " is_special_name"; if (ObjC) os << " objc=" << *ObjC; if (ObjCName) os << " objc_name='" << *ObjCName << "'"; if (Dynamic) os << " dynamic=" << *Dynamic; os << ")"; } } LLVM_YAML_DECLARE_SCALAR_TRAITS(swift::AccessNoteDeclName, QuotingType::Single) LLVM_YAML_DECLARE_SCALAR_TRAITS(swift::ObjCSelector, QuotingType::Single) LLVM_YAML_IS_SEQUENCE_VECTOR(swift::AccessNote) LLVM_YAML_DECLARE_MAPPING_TRAITS(swift::AccessNotesFile) // Not using macro to avoid validation issues. template <> struct llvm::yaml::MappingTraits { static void mapping(IO &IO, swift::AccessNote &Obj); static std::string validate(IO &IO, swift::AccessNote &Obj); }; namespace swift { static void convertToErrorAndJoin(const llvm::SMDiagnostic &diag, void *ctxPtr) { ASTContext &ctx = *(ASTContext*)ctxPtr; SourceLoc loc{diag.getLoc()}; assert(ctx.SourceMgr.isOwning(loc)); switch (diag.getKind()) { case llvm::SourceMgr::DK_Error: ctx.Diags.diagnose(loc, diag::error_in_access_notes_file, diag.getMessage()); break; default: ctx.Diags.diagnose(loc, diag::warning_in_access_notes_file, diag.getMessage()); break; } } std::optional AccessNotesFile::load(ASTContext &ctx, const llvm::MemoryBuffer *buffer) { llvm::yaml::Input yamlIn(llvm::MemoryBufferRef(*buffer), (void *)&ctx, convertToErrorAndJoin, &ctx); yamlIn.setAllowUnknownKeys(true); AccessNotesFile notes; yamlIn >> notes; if (yamlIn.error()) return std::nullopt; return notes; } } namespace llvm { namespace yaml { using AccessNote = swift::AccessNote; using AccessNotesFile = swift::AccessNotesFile; using ASTContext = swift::ASTContext; using AccessNoteDeclName = swift::AccessNoteDeclName; using ObjCSelector = swift::ObjCSelector; void ScalarTraits:: output(const AccessNoteDeclName &name, void *ctxPtr, raw_ostream &os) { name.print(os); } StringRef ScalarTraits:: input(StringRef str, void *ctxPtr, AccessNoteDeclName &name) { auto &ctx = *static_cast(ctxPtr); name = AccessNoteDeclName(ctx, str); return name.empty() ? "invalid declaration name" : ""; } void ScalarTraits::output(const ObjCSelector &selector, void *ctxPtr, raw_ostream &os) { os << selector; } StringRef ScalarTraits::input(StringRef str, void *ctxPtr, ObjCSelector &selector) { auto &ctx = *static_cast(ctxPtr); if (auto sel = ObjCSelector::parse(ctx, str)) { selector = *sel; return ""; } return "invalid selector"; } void MappingTraits::mapping(IO &io, AccessNote ¬e) { io.mapRequired("Name", note.Name); io.mapOptional("ObjC", note.ObjC); io.mapOptional("Dynamic", note.Dynamic); io.mapOptional("ObjCName", note.ObjCName); } std::string MappingTraits::validate(IO &io, AccessNote ¬e) { if (note.ObjCName.has_value()) { if (!note.ObjC) note.ObjC = true; else if (!*note.ObjC) return "cannot have an 'ObjCName' if 'ObjC' is false"; } return ""; } void MappingTraits::mapping(IO &io, AccessNotesFile ¬es) { io.mapRequired("Reason", notes.Reason); io.mapRequired("Notes", notes.Notes); } } }