Implicitly discovered module maps that reference files outside their module directory cause order dependent behavior when using implicitly discovered module maps. This adds an off by default diagnostic about these cases with the long term goal of removing import order dependent behavior. Module maps found via `-fmodule-map-file=` are not a problem because they are all loaded at the start of translation. Assisted-by: claude-opus-4.6
1244 lines
34 KiB
C++
1244 lines
34 KiB
C++
//===- ModuleMapFile.cpp - ------------------------------------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
///
|
|
/// \file
|
|
/// This file handles parsing of modulemap files into a simple AST.
|
|
///
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "clang/Lex/ModuleMapFile.h"
|
|
#include "clang/Basic/Diagnostic.h"
|
|
#include "clang/Basic/LangOptions.h"
|
|
#include "clang/Basic/Module.h"
|
|
#include "clang/Basic/SourceManager.h"
|
|
#include "clang/Lex/LexDiagnostic.h"
|
|
#include "clang/Lex/Lexer.h"
|
|
#include "clang/Lex/ModuleMap.h"
|
|
#include "llvm/ADT/STLExtras.h"
|
|
#include <optional>
|
|
|
|
using namespace clang;
|
|
using namespace modulemap;
|
|
|
|
namespace {
|
|
struct MMToken {
|
|
enum TokenKind {
|
|
Comma,
|
|
ConfigMacros,
|
|
Conflict,
|
|
EndOfFile,
|
|
HeaderKeyword,
|
|
Identifier,
|
|
Exclaim,
|
|
ExcludeKeyword,
|
|
ExplicitKeyword,
|
|
ExportKeyword,
|
|
ExportAsKeyword,
|
|
ExternKeyword,
|
|
FrameworkKeyword,
|
|
LinkKeyword,
|
|
ModuleKeyword,
|
|
Period,
|
|
PrivateKeyword,
|
|
UmbrellaKeyword,
|
|
UseKeyword,
|
|
RequiresKeyword,
|
|
Star,
|
|
StringLiteral,
|
|
IntegerLiteral,
|
|
TextualKeyword,
|
|
LBrace,
|
|
RBrace,
|
|
LSquare,
|
|
RSquare
|
|
} Kind;
|
|
|
|
SourceLocation::UIntTy Location;
|
|
unsigned StringLength;
|
|
union {
|
|
// If Kind != IntegerLiteral.
|
|
const char *StringData;
|
|
|
|
// If Kind == IntegerLiteral.
|
|
uint64_t IntegerValue;
|
|
};
|
|
|
|
void clear() {
|
|
Kind = EndOfFile;
|
|
Location = 0;
|
|
StringLength = 0;
|
|
StringData = nullptr;
|
|
}
|
|
|
|
bool is(TokenKind K) const { return Kind == K; }
|
|
|
|
SourceLocation getLocation() const {
|
|
return SourceLocation::getFromRawEncoding(Location);
|
|
}
|
|
|
|
uint64_t getInteger() const {
|
|
return Kind == IntegerLiteral ? IntegerValue : 0;
|
|
}
|
|
|
|
StringRef getString() const {
|
|
return Kind == IntegerLiteral ? StringRef()
|
|
: StringRef(StringData, StringLength);
|
|
}
|
|
};
|
|
|
|
struct ModuleMapFileParser {
|
|
// External context
|
|
Lexer &L;
|
|
DiagnosticsEngine &Diags;
|
|
|
|
/// Parsed representation of the module map file
|
|
ModuleMapFile MMF{};
|
|
|
|
bool HadError = false;
|
|
|
|
/// The current token.
|
|
MMToken Tok{};
|
|
|
|
bool parseTopLevelDecls();
|
|
std::optional<ModuleDecl> parseModuleDecl(bool TopLevel);
|
|
std::optional<ExternModuleDecl> parseExternModuleDecl();
|
|
std::optional<ConfigMacrosDecl> parseConfigMacrosDecl();
|
|
std::optional<ConflictDecl> parseConflictDecl();
|
|
std::optional<ExportDecl> parseExportDecl();
|
|
std::optional<ExportAsDecl> parseExportAsDecl();
|
|
std::optional<UseDecl> parseUseDecl();
|
|
std::optional<RequiresDecl> parseRequiresDecl();
|
|
std::optional<HeaderDecl> parseHeaderDecl(MMToken::TokenKind LeadingToken,
|
|
SourceLocation LeadingLoc);
|
|
std::optional<ExcludeDecl> parseExcludeDecl(clang::SourceLocation LeadingLoc);
|
|
std::optional<UmbrellaDirDecl>
|
|
parseUmbrellaDirDecl(SourceLocation UmbrellaLoc);
|
|
std::optional<LinkDecl> parseLinkDecl();
|
|
|
|
SourceLocation consumeToken();
|
|
void skipUntil(MMToken::TokenKind K);
|
|
bool parseModuleId(ModuleId &Id);
|
|
bool parseOptionalAttributes(ModuleAttributes &Attrs);
|
|
|
|
SourceLocation getLocation() const { return Tok.getLocation(); };
|
|
};
|
|
|
|
std::string formatModuleId(const ModuleId &Id) {
|
|
std::string result;
|
|
{
|
|
llvm::raw_string_ostream OS(result);
|
|
|
|
for (unsigned I = 0, N = Id.size(); I != N; ++I) {
|
|
if (I)
|
|
OS << ".";
|
|
OS << Id[I].first;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
} // end anonymous namespace
|
|
|
|
std::optional<ModuleMapFile>
|
|
modulemap::parseModuleMap(FileID ID, clang::DirectoryEntryRef Dir,
|
|
SourceManager &SM, DiagnosticsEngine &Diags,
|
|
bool IsSystem, bool ImplicitlyDiscovered,
|
|
unsigned *Offset) {
|
|
std::optional<llvm::MemoryBufferRef> Buffer = SM.getBufferOrNone(ID);
|
|
LangOptions LOpts;
|
|
LOpts.LangStd = clang::LangStandard::lang_c99;
|
|
Lexer L(SM.getLocForStartOfFile(ID), LOpts, Buffer->getBufferStart(),
|
|
Buffer->getBufferStart() + (Offset ? *Offset : 0),
|
|
Buffer->getBufferEnd());
|
|
SourceLocation Start = L.getSourceLocation();
|
|
|
|
ModuleMapFileParser Parser{L, Diags};
|
|
bool Failed = Parser.parseTopLevelDecls();
|
|
|
|
if (Offset) {
|
|
auto Loc = SM.getDecomposedLoc(Parser.getLocation());
|
|
assert(Loc.first == ID && "stopped in a different file?");
|
|
*Offset = Loc.second;
|
|
}
|
|
|
|
if (Failed)
|
|
return std::nullopt;
|
|
Parser.MMF.ID = ID;
|
|
Parser.MMF.Dir = Dir;
|
|
Parser.MMF.Start = Start;
|
|
Parser.MMF.IsSystem = IsSystem;
|
|
Parser.MMF.ImplicitlyDiscovered = ImplicitlyDiscovered;
|
|
return std::move(Parser.MMF);
|
|
}
|
|
|
|
bool ModuleMapFileParser::parseTopLevelDecls() {
|
|
Tok.clear();
|
|
consumeToken();
|
|
do {
|
|
switch (Tok.Kind) {
|
|
case MMToken::EndOfFile:
|
|
return HadError;
|
|
case MMToken::ExternKeyword: {
|
|
std::optional<ExternModuleDecl> EMD = parseExternModuleDecl();
|
|
if (EMD)
|
|
MMF.Decls.push_back(std::move(*EMD));
|
|
break;
|
|
}
|
|
case MMToken::ExplicitKeyword:
|
|
case MMToken::ModuleKeyword:
|
|
case MMToken::FrameworkKeyword: {
|
|
std::optional<ModuleDecl> MD = parseModuleDecl(true);
|
|
if (MD)
|
|
MMF.Decls.push_back(std::move(*MD));
|
|
break;
|
|
}
|
|
case MMToken::Comma:
|
|
case MMToken::ConfigMacros:
|
|
case MMToken::Conflict:
|
|
case MMToken::Exclaim:
|
|
case MMToken::ExcludeKeyword:
|
|
case MMToken::ExportKeyword:
|
|
case MMToken::ExportAsKeyword:
|
|
case MMToken::HeaderKeyword:
|
|
case MMToken::Identifier:
|
|
case MMToken::LBrace:
|
|
case MMToken::LinkKeyword:
|
|
case MMToken::LSquare:
|
|
case MMToken::Period:
|
|
case MMToken::PrivateKeyword:
|
|
case MMToken::RBrace:
|
|
case MMToken::RSquare:
|
|
case MMToken::RequiresKeyword:
|
|
case MMToken::Star:
|
|
case MMToken::StringLiteral:
|
|
case MMToken::IntegerLiteral:
|
|
case MMToken::TextualKeyword:
|
|
case MMToken::UmbrellaKeyword:
|
|
case MMToken::UseKeyword:
|
|
Diags.Report(Tok.getLocation(), diag::err_mmap_expected_module);
|
|
HadError = true;
|
|
consumeToken();
|
|
break;
|
|
}
|
|
} while (true);
|
|
}
|
|
|
|
/// Parse a module declaration.
|
|
///
|
|
/// module-declaration:
|
|
/// 'extern' 'module' module-id string-literal
|
|
/// 'explicit'[opt] 'framework'[opt] 'module' module-id attributes[opt]
|
|
/// { module-member* }
|
|
///
|
|
/// module-member:
|
|
/// requires-declaration
|
|
/// header-declaration
|
|
/// submodule-declaration
|
|
/// export-declaration
|
|
/// export-as-declaration
|
|
/// link-declaration
|
|
///
|
|
/// submodule-declaration:
|
|
/// module-declaration
|
|
/// inferred-submodule-declaration
|
|
std::optional<ModuleDecl> ModuleMapFileParser::parseModuleDecl(bool TopLevel) {
|
|
assert(Tok.is(MMToken::ExplicitKeyword) || Tok.is(MMToken::ModuleKeyword) ||
|
|
Tok.is(MMToken::FrameworkKeyword));
|
|
|
|
ModuleDecl MDecl;
|
|
|
|
SourceLocation ExplicitLoc;
|
|
MDecl.Explicit = false;
|
|
MDecl.Framework = false;
|
|
|
|
// Parse 'explicit' keyword, if present.
|
|
if (Tok.is(MMToken::ExplicitKeyword)) {
|
|
MDecl.Location = ExplicitLoc = consumeToken();
|
|
MDecl.Explicit = true;
|
|
}
|
|
|
|
// Parse 'framework' keyword, if present.
|
|
if (Tok.is(MMToken::FrameworkKeyword)) {
|
|
SourceLocation FrameworkLoc = consumeToken();
|
|
if (!MDecl.Location.isValid())
|
|
MDecl.Location = FrameworkLoc;
|
|
MDecl.Framework = true;
|
|
}
|
|
|
|
// Parse 'module' keyword.
|
|
if (!Tok.is(MMToken::ModuleKeyword)) {
|
|
Diags.Report(Tok.getLocation(), diag::err_mmap_expected_module);
|
|
consumeToken();
|
|
HadError = true;
|
|
return std::nullopt;
|
|
}
|
|
SourceLocation ModuleLoc = consumeToken();
|
|
if (!MDecl.Location.isValid())
|
|
MDecl.Location = ModuleLoc; // 'module' keyword
|
|
|
|
// If we have a wildcard for the module name, this is an inferred submodule.
|
|
// We treat it as a normal module at this point.
|
|
if (Tok.is(MMToken::Star)) {
|
|
SourceLocation StarLoc = consumeToken();
|
|
MDecl.Id.push_back({"*", StarLoc});
|
|
if (TopLevel && !MDecl.Framework) {
|
|
Diags.Report(StarLoc, diag::err_mmap_top_level_inferred_submodule);
|
|
HadError = true;
|
|
return std::nullopt;
|
|
}
|
|
} else {
|
|
// Parse the module name.
|
|
if (parseModuleId(MDecl.Id)) {
|
|
HadError = true;
|
|
return std::nullopt;
|
|
}
|
|
if (!TopLevel) {
|
|
if (MDecl.Id.size() > 1) {
|
|
Diags.Report(MDecl.Id.front().second,
|
|
diag::err_mmap_nested_submodule_id)
|
|
<< SourceRange(MDecl.Id.front().second, MDecl.Id.back().second);
|
|
|
|
HadError = true;
|
|
}
|
|
} else if (MDecl.Id.size() == 1 && MDecl.Explicit) {
|
|
// Top-level modules can't be explicit.
|
|
Diags.Report(ExplicitLoc, diag::err_mmap_explicit_top_level);
|
|
MDecl.Explicit = false;
|
|
HadError = true;
|
|
}
|
|
}
|
|
|
|
// Parse the optional attribute list.
|
|
if (parseOptionalAttributes(MDecl.Attrs))
|
|
return std::nullopt;
|
|
|
|
// Parse the opening brace.
|
|
if (!Tok.is(MMToken::LBrace)) {
|
|
Diags.Report(Tok.getLocation(), diag::err_mmap_expected_lbrace)
|
|
<< MDecl.Id.back().first;
|
|
HadError = true;
|
|
return std::nullopt;
|
|
}
|
|
SourceLocation LBraceLoc = consumeToken();
|
|
|
|
bool Done = false;
|
|
do {
|
|
std::optional<Decl> SubDecl;
|
|
switch (Tok.Kind) {
|
|
case MMToken::EndOfFile:
|
|
case MMToken::RBrace:
|
|
Done = true;
|
|
break;
|
|
|
|
case MMToken::ConfigMacros:
|
|
// Only top-level modules can have configuration macros.
|
|
if (!TopLevel)
|
|
Diags.Report(Tok.getLocation(), diag::err_mmap_config_macro_submodule);
|
|
SubDecl = parseConfigMacrosDecl();
|
|
break;
|
|
|
|
case MMToken::Conflict:
|
|
SubDecl = parseConflictDecl();
|
|
break;
|
|
|
|
case MMToken::ExternKeyword:
|
|
SubDecl = parseExternModuleDecl();
|
|
break;
|
|
|
|
case MMToken::ExplicitKeyword:
|
|
case MMToken::FrameworkKeyword:
|
|
case MMToken::ModuleKeyword:
|
|
SubDecl = parseModuleDecl(false);
|
|
break;
|
|
|
|
case MMToken::ExportKeyword:
|
|
SubDecl = parseExportDecl();
|
|
break;
|
|
|
|
case MMToken::ExportAsKeyword:
|
|
if (!TopLevel) {
|
|
Diags.Report(Tok.getLocation(), diag::err_mmap_submodule_export_as);
|
|
parseExportAsDecl();
|
|
} else
|
|
SubDecl = parseExportAsDecl();
|
|
break;
|
|
|
|
case MMToken::UseKeyword:
|
|
SubDecl = parseUseDecl();
|
|
break;
|
|
|
|
case MMToken::RequiresKeyword:
|
|
SubDecl = parseRequiresDecl();
|
|
break;
|
|
|
|
case MMToken::TextualKeyword:
|
|
SubDecl = parseHeaderDecl(MMToken::TextualKeyword, consumeToken());
|
|
break;
|
|
|
|
case MMToken::UmbrellaKeyword: {
|
|
SourceLocation UmbrellaLoc = consumeToken();
|
|
if (Tok.is(MMToken::HeaderKeyword))
|
|
SubDecl = parseHeaderDecl(MMToken::UmbrellaKeyword, UmbrellaLoc);
|
|
else
|
|
SubDecl = parseUmbrellaDirDecl(UmbrellaLoc);
|
|
break;
|
|
}
|
|
|
|
case MMToken::ExcludeKeyword: {
|
|
SourceLocation ExcludeLoc = consumeToken();
|
|
if (Tok.is(MMToken::HeaderKeyword))
|
|
SubDecl = parseHeaderDecl(MMToken::ExcludeKeyword, ExcludeLoc);
|
|
else
|
|
SubDecl = parseExcludeDecl(ExcludeLoc);
|
|
break;
|
|
}
|
|
|
|
case MMToken::PrivateKeyword:
|
|
SubDecl = parseHeaderDecl(MMToken::PrivateKeyword, consumeToken());
|
|
break;
|
|
|
|
case MMToken::HeaderKeyword:
|
|
SubDecl = parseHeaderDecl(MMToken::HeaderKeyword, consumeToken());
|
|
break;
|
|
|
|
case MMToken::LinkKeyword:
|
|
SubDecl = parseLinkDecl();
|
|
break;
|
|
|
|
default:
|
|
Diags.Report(Tok.getLocation(), diag::err_mmap_expected_member);
|
|
consumeToken();
|
|
break;
|
|
}
|
|
if (SubDecl)
|
|
MDecl.Decls.push_back(std::move(*SubDecl));
|
|
} while (!Done);
|
|
|
|
if (Tok.is(MMToken::RBrace))
|
|
consumeToken();
|
|
else {
|
|
Diags.Report(Tok.getLocation(), diag::err_mmap_expected_rbrace);
|
|
Diags.Report(LBraceLoc, diag::note_mmap_lbrace_match);
|
|
HadError = true;
|
|
}
|
|
return std::move(MDecl);
|
|
}
|
|
|
|
std::optional<ExternModuleDecl> ModuleMapFileParser::parseExternModuleDecl() {
|
|
assert(Tok.is(MMToken::ExternKeyword));
|
|
ExternModuleDecl EMD;
|
|
EMD.Location = consumeToken(); // 'extern' keyword
|
|
|
|
// Parse 'module' keyword.
|
|
if (!Tok.is(MMToken::ModuleKeyword)) {
|
|
Diags.Report(Tok.getLocation(), diag::err_mmap_expected_module);
|
|
consumeToken();
|
|
HadError = true;
|
|
return std::nullopt;
|
|
}
|
|
consumeToken(); // 'module' keyword
|
|
|
|
// Parse the module name.
|
|
if (parseModuleId(EMD.Id)) {
|
|
HadError = true;
|
|
return std::nullopt;
|
|
}
|
|
|
|
// Parse the referenced module map file name.
|
|
if (!Tok.is(MMToken::StringLiteral)) {
|
|
Diags.Report(Tok.getLocation(), diag::err_mmap_expected_mmap_file);
|
|
HadError = true;
|
|
return std::nullopt;
|
|
}
|
|
EMD.Path = Tok.getString();
|
|
consumeToken(); // filename
|
|
|
|
return std::move(EMD);
|
|
}
|
|
|
|
/// Parse a configuration macro declaration.
|
|
///
|
|
/// module-declaration:
|
|
/// 'config_macros' attributes[opt] config-macro-list?
|
|
///
|
|
/// config-macro-list:
|
|
/// identifier (',' identifier)?
|
|
std::optional<ConfigMacrosDecl> ModuleMapFileParser::parseConfigMacrosDecl() {
|
|
assert(Tok.is(MMToken::ConfigMacros));
|
|
ConfigMacrosDecl CMDecl;
|
|
CMDecl.Location = consumeToken();
|
|
|
|
// Parse the optional attributes.
|
|
ModuleAttributes Attrs;
|
|
if (parseOptionalAttributes(Attrs))
|
|
return std::nullopt;
|
|
|
|
CMDecl.Exhaustive = Attrs.IsExhaustive;
|
|
|
|
// If we don't have an identifier, we're done.
|
|
// FIXME: Support macros with the same name as a keyword here.
|
|
if (!Tok.is(MMToken::Identifier))
|
|
return std::nullopt;
|
|
|
|
// Consume the first identifier.
|
|
CMDecl.Macros.push_back(Tok.getString());
|
|
consumeToken();
|
|
|
|
do {
|
|
// If there's a comma, consume it.
|
|
if (!Tok.is(MMToken::Comma))
|
|
break;
|
|
consumeToken();
|
|
|
|
// We expect to see a macro name here.
|
|
// FIXME: Support macros with the same name as a keyword here.
|
|
if (!Tok.is(MMToken::Identifier)) {
|
|
Diags.Report(Tok.getLocation(), diag::err_mmap_expected_config_macro);
|
|
return std::nullopt;
|
|
}
|
|
|
|
// Consume the macro name.
|
|
CMDecl.Macros.push_back(Tok.getString());
|
|
consumeToken();
|
|
} while (true);
|
|
return std::move(CMDecl);
|
|
}
|
|
|
|
/// Parse a conflict declaration.
|
|
///
|
|
/// module-declaration:
|
|
/// 'conflict' module-id ',' string-literal
|
|
std::optional<ConflictDecl> ModuleMapFileParser::parseConflictDecl() {
|
|
assert(Tok.is(MMToken::Conflict));
|
|
ConflictDecl CD;
|
|
CD.Location = consumeToken();
|
|
|
|
// Parse the module-id.
|
|
if (parseModuleId(CD.Id))
|
|
return std::nullopt;
|
|
|
|
// Parse the ','.
|
|
if (!Tok.is(MMToken::Comma)) {
|
|
Diags.Report(Tok.getLocation(), diag::err_mmap_expected_conflicts_comma)
|
|
<< SourceRange(CD.Location);
|
|
return std::nullopt;
|
|
}
|
|
consumeToken();
|
|
|
|
// Parse the message.
|
|
if (!Tok.is(MMToken::StringLiteral)) {
|
|
Diags.Report(Tok.getLocation(), diag::err_mmap_expected_conflicts_message)
|
|
<< formatModuleId(CD.Id);
|
|
return std::nullopt;
|
|
}
|
|
CD.Message = Tok.getString();
|
|
consumeToken();
|
|
return std::move(CD);
|
|
}
|
|
|
|
/// Parse a module export declaration.
|
|
///
|
|
/// export-declaration:
|
|
/// 'export' wildcard-module-id
|
|
///
|
|
/// wildcard-module-id:
|
|
/// identifier
|
|
/// '*'
|
|
/// identifier '.' wildcard-module-id
|
|
std::optional<ExportDecl> ModuleMapFileParser::parseExportDecl() {
|
|
assert(Tok.is(MMToken::ExportKeyword));
|
|
ExportDecl ED;
|
|
ED.Location = consumeToken();
|
|
|
|
// Parse the module-id with an optional wildcard at the end.
|
|
ED.Wildcard = false;
|
|
do {
|
|
// FIXME: Support string-literal module names here.
|
|
if (Tok.is(MMToken::Identifier)) {
|
|
ED.Id.push_back(
|
|
std::make_pair(std::string(Tok.getString()), Tok.getLocation()));
|
|
consumeToken();
|
|
|
|
if (Tok.is(MMToken::Period)) {
|
|
consumeToken();
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if (Tok.is(MMToken::Star)) {
|
|
ED.Wildcard = true;
|
|
consumeToken();
|
|
break;
|
|
}
|
|
|
|
Diags.Report(Tok.getLocation(), diag::err_mmap_module_id);
|
|
HadError = true;
|
|
return std::nullopt;
|
|
} while (true);
|
|
|
|
return std::move(ED);
|
|
}
|
|
|
|
/// Parse a module export_as declaration.
|
|
///
|
|
/// export-as-declaration:
|
|
/// 'export_as' identifier
|
|
std::optional<ExportAsDecl> ModuleMapFileParser::parseExportAsDecl() {
|
|
assert(Tok.is(MMToken::ExportAsKeyword));
|
|
ExportAsDecl EAD;
|
|
EAD.Location = consumeToken();
|
|
|
|
if (!Tok.is(MMToken::Identifier)) {
|
|
Diags.Report(Tok.getLocation(), diag::err_mmap_module_id);
|
|
HadError = true;
|
|
return std::nullopt;
|
|
}
|
|
|
|
if (parseModuleId(EAD.Id))
|
|
return std::nullopt;
|
|
if (EAD.Id.size() > 1)
|
|
Diags.Report(EAD.Id[1].second, diag::err_mmap_qualified_export_as);
|
|
return std::move(EAD);
|
|
}
|
|
|
|
/// Parse a module use declaration.
|
|
///
|
|
/// use-declaration:
|
|
/// 'use' wildcard-module-id
|
|
std::optional<UseDecl> ModuleMapFileParser::parseUseDecl() {
|
|
assert(Tok.is(MMToken::UseKeyword));
|
|
UseDecl UD;
|
|
UD.Location = consumeToken();
|
|
if (parseModuleId(UD.Id))
|
|
return std::nullopt;
|
|
return std::move(UD);
|
|
}
|
|
|
|
/// Parse a requires declaration.
|
|
///
|
|
/// requires-declaration:
|
|
/// 'requires' feature-list
|
|
///
|
|
/// feature-list:
|
|
/// feature ',' feature-list
|
|
/// feature
|
|
///
|
|
/// feature:
|
|
/// '!'[opt] identifier
|
|
std::optional<RequiresDecl> ModuleMapFileParser::parseRequiresDecl() {
|
|
assert(Tok.is(MMToken::RequiresKeyword));
|
|
RequiresDecl RD;
|
|
RD.Location = consumeToken();
|
|
|
|
// Parse the feature-list.
|
|
do {
|
|
bool RequiredState = true;
|
|
if (Tok.is(MMToken::Exclaim)) {
|
|
RequiredState = false;
|
|
consumeToken();
|
|
}
|
|
|
|
if (!Tok.is(MMToken::Identifier)) {
|
|
Diags.Report(Tok.getLocation(), diag::err_mmap_expected_feature);
|
|
HadError = true;
|
|
return std::nullopt;
|
|
}
|
|
|
|
// Consume the feature name.
|
|
RequiresFeature RF;
|
|
RF.Feature = Tok.getString();
|
|
RF.Location = consumeToken();
|
|
RF.RequiredState = RequiredState;
|
|
|
|
RD.Features.push_back(std::move(RF));
|
|
|
|
if (!Tok.is(MMToken::Comma))
|
|
break;
|
|
|
|
// Consume the comma.
|
|
consumeToken();
|
|
} while (true);
|
|
return std::move(RD);
|
|
}
|
|
|
|
/// Parse a header declaration.
|
|
///
|
|
/// header-declaration:
|
|
/// 'textual'[opt] 'header' string-literal
|
|
/// 'private' 'textual'[opt] 'header' string-literal
|
|
/// 'exclude' 'header' string-literal
|
|
/// 'umbrella' 'header' string-literal
|
|
std::optional<HeaderDecl>
|
|
ModuleMapFileParser::parseHeaderDecl(MMToken::TokenKind LeadingToken,
|
|
clang::SourceLocation LeadingLoc) {
|
|
HeaderDecl HD;
|
|
HD.Private = false;
|
|
HD.Excluded = false;
|
|
HD.Textual = false;
|
|
// We've already consumed the first token.
|
|
HD.Location = LeadingLoc;
|
|
|
|
if (LeadingToken == MMToken::PrivateKeyword) {
|
|
HD.Private = true;
|
|
// 'private' may optionally be followed by 'textual'.
|
|
if (Tok.is(MMToken::TextualKeyword)) {
|
|
HD.Textual = true;
|
|
LeadingToken = Tok.Kind;
|
|
consumeToken();
|
|
}
|
|
} else if (LeadingToken == MMToken::ExcludeKeyword)
|
|
HD.Excluded = true;
|
|
else if (LeadingToken == MMToken::TextualKeyword)
|
|
HD.Textual = true;
|
|
|
|
if (LeadingToken != MMToken::HeaderKeyword) {
|
|
if (!Tok.is(MMToken::HeaderKeyword)) {
|
|
Diags.Report(Tok.getLocation(), diag::err_mmap_expected_header)
|
|
<< (LeadingToken == MMToken::PrivateKeyword ? "private"
|
|
: LeadingToken == MMToken::ExcludeKeyword ? "exclude"
|
|
: LeadingToken == MMToken::TextualKeyword ? "textual"
|
|
: "umbrella");
|
|
return std::nullopt;
|
|
}
|
|
consumeToken();
|
|
}
|
|
|
|
// Parse the header name.
|
|
if (!Tok.is(MMToken::StringLiteral)) {
|
|
Diags.Report(Tok.getLocation(), diag::err_mmap_expected_header) << "header";
|
|
HadError = true;
|
|
return std::nullopt;
|
|
}
|
|
HD.Path = Tok.getString();
|
|
HD.PathLoc = consumeToken();
|
|
HD.Umbrella = LeadingToken == MMToken::UmbrellaKeyword;
|
|
|
|
// If we were given stat information, parse it so we can skip looking for
|
|
// the file.
|
|
if (Tok.is(MMToken::LBrace)) {
|
|
SourceLocation LBraceLoc = consumeToken();
|
|
|
|
while (!Tok.is(MMToken::RBrace) && !Tok.is(MMToken::EndOfFile)) {
|
|
enum Attribute { Size, ModTime, Unknown };
|
|
StringRef Str = Tok.getString();
|
|
SourceLocation Loc = consumeToken();
|
|
switch (llvm::StringSwitch<Attribute>(Str)
|
|
.Case("size", Size)
|
|
.Case("mtime", ModTime)
|
|
.Default(Unknown)) {
|
|
case Size:
|
|
if (HD.Size)
|
|
Diags.Report(Loc, diag::err_mmap_duplicate_header_attribute) << Str;
|
|
if (!Tok.is(MMToken::IntegerLiteral)) {
|
|
Diags.Report(Tok.getLocation(),
|
|
diag::err_mmap_invalid_header_attribute_value)
|
|
<< Str;
|
|
skipUntil(MMToken::RBrace);
|
|
break;
|
|
}
|
|
HD.Size = Tok.getInteger();
|
|
consumeToken();
|
|
break;
|
|
|
|
case ModTime:
|
|
if (HD.MTime)
|
|
Diags.Report(Loc, diag::err_mmap_duplicate_header_attribute) << Str;
|
|
if (!Tok.is(MMToken::IntegerLiteral)) {
|
|
Diags.Report(Tok.getLocation(),
|
|
diag::err_mmap_invalid_header_attribute_value)
|
|
<< Str;
|
|
skipUntil(MMToken::RBrace);
|
|
break;
|
|
}
|
|
HD.MTime = Tok.getInteger();
|
|
consumeToken();
|
|
break;
|
|
|
|
case Unknown:
|
|
Diags.Report(Loc, diag::err_mmap_expected_header_attribute);
|
|
skipUntil(MMToken::RBrace);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (Tok.is(MMToken::RBrace))
|
|
consumeToken();
|
|
else {
|
|
Diags.Report(Tok.getLocation(), diag::err_mmap_expected_rbrace);
|
|
Diags.Report(LBraceLoc, diag::note_mmap_lbrace_match);
|
|
HadError = true;
|
|
}
|
|
}
|
|
return std::move(HD);
|
|
}
|
|
|
|
/// Parse an exclude declaration.
|
|
///
|
|
/// exclude-declaration:
|
|
/// 'exclude' identifier
|
|
std::optional<ExcludeDecl>
|
|
ModuleMapFileParser::parseExcludeDecl(clang::SourceLocation LeadingLoc) {
|
|
// FIXME: Support string-literal module names here.
|
|
if (!Tok.is(MMToken::Identifier)) {
|
|
Diags.Report(Tok.getLocation(), diag::err_mmap_missing_exclude_name);
|
|
HadError = true;
|
|
return std::nullopt;
|
|
}
|
|
|
|
ExcludeDecl ED;
|
|
ED.Location = LeadingLoc;
|
|
ED.Module = Tok.getString();
|
|
consumeToken();
|
|
return std::move(ED);
|
|
}
|
|
|
|
/// Parse an umbrella directory declaration.
|
|
///
|
|
/// umbrella-dir-declaration:
|
|
/// umbrella string-literal
|
|
std::optional<UmbrellaDirDecl>
|
|
ModuleMapFileParser::parseUmbrellaDirDecl(clang::SourceLocation UmbrellaLoc) {
|
|
UmbrellaDirDecl UDD;
|
|
UDD.Location = UmbrellaLoc;
|
|
// Parse the directory name.
|
|
if (!Tok.is(MMToken::StringLiteral)) {
|
|
Diags.Report(Tok.getLocation(), diag::err_mmap_expected_header)
|
|
<< "umbrella";
|
|
HadError = true;
|
|
return std::nullopt;
|
|
}
|
|
|
|
UDD.Path = Tok.getString();
|
|
consumeToken();
|
|
return std::move(UDD);
|
|
}
|
|
|
|
/// Parse a link declaration.
|
|
///
|
|
/// module-declaration:
|
|
/// 'link' 'framework'[opt] string-literal
|
|
std::optional<LinkDecl> ModuleMapFileParser::parseLinkDecl() {
|
|
assert(Tok.is(MMToken::LinkKeyword));
|
|
LinkDecl LD;
|
|
LD.Location = consumeToken();
|
|
|
|
// Parse the optional 'framework' keyword.
|
|
LD.Framework = false;
|
|
if (Tok.is(MMToken::FrameworkKeyword)) {
|
|
consumeToken();
|
|
LD.Framework = true;
|
|
}
|
|
|
|
// Parse the library name
|
|
if (!Tok.is(MMToken::StringLiteral)) {
|
|
Diags.Report(Tok.getLocation(), diag::err_mmap_expected_library_name)
|
|
<< LD.Framework << SourceRange(LD.Location);
|
|
HadError = true;
|
|
return std::nullopt;
|
|
}
|
|
|
|
LD.Library = Tok.getString();
|
|
consumeToken();
|
|
return std::move(LD);
|
|
}
|
|
|
|
SourceLocation ModuleMapFileParser::consumeToken() {
|
|
SourceLocation Result = Tok.getLocation();
|
|
|
|
retry:
|
|
Tok.clear();
|
|
Token LToken;
|
|
L.LexFromRawLexer(LToken);
|
|
Tok.Location = LToken.getLocation().getRawEncoding();
|
|
switch (LToken.getKind()) {
|
|
case tok::raw_identifier: {
|
|
StringRef RI = LToken.getRawIdentifier();
|
|
Tok.StringData = RI.data();
|
|
Tok.StringLength = RI.size();
|
|
Tok.Kind = llvm::StringSwitch<MMToken::TokenKind>(RI)
|
|
.Case("config_macros", MMToken::ConfigMacros)
|
|
.Case("conflict", MMToken::Conflict)
|
|
.Case("exclude", MMToken::ExcludeKeyword)
|
|
.Case("explicit", MMToken::ExplicitKeyword)
|
|
.Case("export", MMToken::ExportKeyword)
|
|
.Case("export_as", MMToken::ExportAsKeyword)
|
|
.Case("extern", MMToken::ExternKeyword)
|
|
.Case("framework", MMToken::FrameworkKeyword)
|
|
.Case("header", MMToken::HeaderKeyword)
|
|
.Case("link", MMToken::LinkKeyword)
|
|
.Case("module", MMToken::ModuleKeyword)
|
|
.Case("private", MMToken::PrivateKeyword)
|
|
.Case("requires", MMToken::RequiresKeyword)
|
|
.Case("textual", MMToken::TextualKeyword)
|
|
.Case("umbrella", MMToken::UmbrellaKeyword)
|
|
.Case("use", MMToken::UseKeyword)
|
|
.Default(MMToken::Identifier);
|
|
break;
|
|
}
|
|
|
|
case tok::comma:
|
|
Tok.Kind = MMToken::Comma;
|
|
break;
|
|
|
|
case tok::eof:
|
|
Tok.Kind = MMToken::EndOfFile;
|
|
break;
|
|
|
|
case tok::l_brace:
|
|
Tok.Kind = MMToken::LBrace;
|
|
break;
|
|
|
|
case tok::l_square:
|
|
Tok.Kind = MMToken::LSquare;
|
|
break;
|
|
|
|
case tok::period:
|
|
Tok.Kind = MMToken::Period;
|
|
break;
|
|
|
|
case tok::r_brace:
|
|
Tok.Kind = MMToken::RBrace;
|
|
break;
|
|
|
|
case tok::r_square:
|
|
Tok.Kind = MMToken::RSquare;
|
|
break;
|
|
|
|
case tok::star:
|
|
Tok.Kind = MMToken::Star;
|
|
break;
|
|
|
|
case tok::exclaim:
|
|
Tok.Kind = MMToken::Exclaim;
|
|
break;
|
|
|
|
case tok::string_literal: {
|
|
if (LToken.hasUDSuffix()) {
|
|
Diags.Report(LToken.getLocation(), diag::err_invalid_string_udl);
|
|
HadError = true;
|
|
goto retry;
|
|
}
|
|
|
|
// Form the token.
|
|
Tok.Kind = MMToken::StringLiteral;
|
|
Tok.StringData = LToken.getLiteralData() + 1;
|
|
Tok.StringLength = LToken.getLength() - 2;
|
|
break;
|
|
}
|
|
|
|
case tok::numeric_constant: {
|
|
// We don't support any suffixes or other complications.
|
|
uint64_t Value;
|
|
if (StringRef(LToken.getLiteralData(), LToken.getLength())
|
|
.getAsInteger(0, Value)) {
|
|
Diags.Report(Tok.getLocation(), diag::err_mmap_unknown_token);
|
|
HadError = true;
|
|
goto retry;
|
|
}
|
|
|
|
Tok.Kind = MMToken::IntegerLiteral;
|
|
Tok.IntegerValue = Value;
|
|
break;
|
|
}
|
|
|
|
case tok::comment:
|
|
goto retry;
|
|
|
|
case tok::hash:
|
|
// A module map can be terminated prematurely by
|
|
// #pragma clang module contents
|
|
// When building the module, we'll treat the rest of the file as the
|
|
// contents of the module.
|
|
{
|
|
auto NextIsIdent = [&](StringRef Str) -> bool {
|
|
L.LexFromRawLexer(LToken);
|
|
return !LToken.isAtStartOfLine() && LToken.is(tok::raw_identifier) &&
|
|
LToken.getRawIdentifier() == Str;
|
|
};
|
|
if (NextIsIdent("pragma") && NextIsIdent("clang") &&
|
|
NextIsIdent("module") && NextIsIdent("contents")) {
|
|
Tok.Kind = MMToken::EndOfFile;
|
|
break;
|
|
}
|
|
}
|
|
[[fallthrough]];
|
|
|
|
default:
|
|
Diags.Report(Tok.getLocation(), diag::err_mmap_unknown_token);
|
|
HadError = true;
|
|
goto retry;
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
void ModuleMapFileParser::skipUntil(MMToken::TokenKind K) {
|
|
unsigned braceDepth = 0;
|
|
unsigned squareDepth = 0;
|
|
do {
|
|
switch (Tok.Kind) {
|
|
case MMToken::EndOfFile:
|
|
return;
|
|
|
|
case MMToken::LBrace:
|
|
if (Tok.is(K) && braceDepth == 0 && squareDepth == 0)
|
|
return;
|
|
|
|
++braceDepth;
|
|
break;
|
|
|
|
case MMToken::LSquare:
|
|
if (Tok.is(K) && braceDepth == 0 && squareDepth == 0)
|
|
return;
|
|
|
|
++squareDepth;
|
|
break;
|
|
|
|
case MMToken::RBrace:
|
|
if (braceDepth > 0)
|
|
--braceDepth;
|
|
else if (Tok.is(K))
|
|
return;
|
|
break;
|
|
|
|
case MMToken::RSquare:
|
|
if (squareDepth > 0)
|
|
--squareDepth;
|
|
else if (Tok.is(K))
|
|
return;
|
|
break;
|
|
|
|
default:
|
|
if (braceDepth == 0 && squareDepth == 0 && Tok.is(K))
|
|
return;
|
|
break;
|
|
}
|
|
|
|
consumeToken();
|
|
} while (true);
|
|
}
|
|
|
|
/// Parse a module-id.
|
|
///
|
|
/// module-id:
|
|
/// identifier
|
|
/// identifier '.' module-id
|
|
///
|
|
/// \returns true if an error occurred, false otherwise.
|
|
bool ModuleMapFileParser::parseModuleId(ModuleId &Id) {
|
|
Id.clear();
|
|
do {
|
|
if (Tok.is(MMToken::Identifier) || Tok.is(MMToken::StringLiteral)) {
|
|
Id.push_back(
|
|
std::make_pair(std::string(Tok.getString()), Tok.getLocation()));
|
|
consumeToken();
|
|
} else {
|
|
Diags.Report(Tok.getLocation(), diag::err_mmap_expected_module_name);
|
|
return true;
|
|
}
|
|
|
|
if (!Tok.is(MMToken::Period))
|
|
break;
|
|
|
|
consumeToken();
|
|
} while (true);
|
|
|
|
return false;
|
|
}
|
|
|
|
/// Parse optional attributes.
|
|
///
|
|
/// attributes:
|
|
/// attribute attributes
|
|
/// attribute
|
|
///
|
|
/// attribute:
|
|
/// [ identifier ]
|
|
///
|
|
/// \param Attrs Will be filled in with the parsed attributes.
|
|
///
|
|
/// \returns true if an error occurred, false otherwise.
|
|
bool ModuleMapFileParser::parseOptionalAttributes(ModuleAttributes &Attrs) {
|
|
bool Error = false;
|
|
|
|
while (Tok.is(MMToken::LSquare)) {
|
|
// Consume the '['.
|
|
SourceLocation LSquareLoc = consumeToken();
|
|
|
|
// Check whether we have an attribute name here.
|
|
if (!Tok.is(MMToken::Identifier)) {
|
|
Diags.Report(Tok.getLocation(), diag::err_mmap_expected_attribute);
|
|
skipUntil(MMToken::RSquare);
|
|
if (Tok.is(MMToken::RSquare))
|
|
consumeToken();
|
|
Error = true;
|
|
}
|
|
|
|
/// Enumerates the known attributes.
|
|
enum AttributeKind {
|
|
/// An unknown attribute.
|
|
AT_unknown,
|
|
|
|
/// The 'system' attribute.
|
|
AT_system,
|
|
|
|
/// The 'extern_c' attribute.
|
|
AT_extern_c,
|
|
|
|
/// The 'exhaustive' attribute.
|
|
AT_exhaustive,
|
|
|
|
/// The 'no_undeclared_includes' attribute.
|
|
AT_no_undeclared_includes
|
|
};
|
|
|
|
// Decode the attribute name.
|
|
AttributeKind Attribute =
|
|
llvm::StringSwitch<AttributeKind>(Tok.getString())
|
|
.Case("exhaustive", AT_exhaustive)
|
|
.Case("extern_c", AT_extern_c)
|
|
.Case("no_undeclared_includes", AT_no_undeclared_includes)
|
|
.Case("system", AT_system)
|
|
.Default(AT_unknown);
|
|
switch (Attribute) {
|
|
case AT_unknown:
|
|
Diags.Report(Tok.getLocation(), diag::warn_mmap_unknown_attribute)
|
|
<< Tok.getString();
|
|
break;
|
|
|
|
case AT_system:
|
|
Attrs.IsSystem = true;
|
|
break;
|
|
|
|
case AT_extern_c:
|
|
Attrs.IsExternC = true;
|
|
break;
|
|
|
|
case AT_exhaustive:
|
|
Attrs.IsExhaustive = true;
|
|
break;
|
|
|
|
case AT_no_undeclared_includes:
|
|
Attrs.NoUndeclaredIncludes = true;
|
|
break;
|
|
}
|
|
consumeToken();
|
|
|
|
// Consume the ']'.
|
|
if (!Tok.is(MMToken::RSquare)) {
|
|
Diags.Report(Tok.getLocation(), diag::err_mmap_expected_rsquare);
|
|
Diags.Report(LSquareLoc, diag::note_mmap_lsquare_match);
|
|
skipUntil(MMToken::RSquare);
|
|
Error = true;
|
|
}
|
|
|
|
if (Tok.is(MMToken::RSquare))
|
|
consumeToken();
|
|
}
|
|
|
|
if (Error)
|
|
HadError = true;
|
|
|
|
return Error;
|
|
}
|
|
|
|
static void dumpModule(const ModuleDecl &MD, llvm::raw_ostream &out, int depth);
|
|
|
|
static void dumpExternModule(const ExternModuleDecl &EMD,
|
|
llvm::raw_ostream &out, int depth) {
|
|
out.indent(depth * 2);
|
|
out << "extern module " << formatModuleId(EMD.Id) << " \"" << EMD.Path
|
|
<< "\"\n";
|
|
}
|
|
|
|
static void dumpDecls(ArrayRef<Decl> Decls, llvm::raw_ostream &out, int depth) {
|
|
for (const auto &Decl : Decls) {
|
|
std::visit(llvm::makeVisitor(
|
|
[&](const RequiresDecl &RD) {
|
|
out.indent(depth * 2);
|
|
out << "requires\n";
|
|
},
|
|
[&](const HeaderDecl &HD) {
|
|
out.indent(depth * 2);
|
|
if (HD.Private)
|
|
out << "private ";
|
|
if (HD.Textual)
|
|
out << "textual ";
|
|
if (HD.Excluded)
|
|
out << "excluded ";
|
|
if (HD.Umbrella)
|
|
out << "umbrella ";
|
|
out << "header \"" << HD.Path << "\"\n";
|
|
},
|
|
[&](const UmbrellaDirDecl &UDD) {
|
|
out.indent(depth * 2);
|
|
out << "umbrella\n";
|
|
},
|
|
[&](const ModuleDecl &MD) { dumpModule(MD, out, depth); },
|
|
[&](const ExcludeDecl &ED) {
|
|
out.indent(depth * 2);
|
|
out << "exclude " << ED.Module << "\n";
|
|
},
|
|
[&](const ExportDecl &ED) {
|
|
out.indent(depth * 2);
|
|
out << "export "
|
|
<< (ED.Wildcard ? "*" : formatModuleId(ED.Id)) << "\n";
|
|
},
|
|
[&](const ExportAsDecl &EAD) {
|
|
out.indent(depth * 2);
|
|
out << "export as\n";
|
|
},
|
|
[&](const ExternModuleDecl &EMD) {
|
|
dumpExternModule(EMD, out, depth);
|
|
},
|
|
[&](const UseDecl &UD) {
|
|
out.indent(depth * 2);
|
|
out << "use\n";
|
|
},
|
|
[&](const LinkDecl &LD) {
|
|
out.indent(depth * 2);
|
|
out << "link\n";
|
|
},
|
|
[&](const ConfigMacrosDecl &CMD) {
|
|
out.indent(depth * 2);
|
|
out << "config_macros ";
|
|
if (CMD.Exhaustive)
|
|
out << "[exhaustive] ";
|
|
for (auto Macro : CMD.Macros) {
|
|
out << Macro << " ";
|
|
}
|
|
out << "\n";
|
|
},
|
|
[&](const ConflictDecl &CD) {
|
|
out.indent(depth * 2);
|
|
out << "conflicts\n";
|
|
}),
|
|
Decl);
|
|
}
|
|
}
|
|
|
|
static void dumpModule(const ModuleDecl &MD, llvm::raw_ostream &out,
|
|
int depth) {
|
|
out.indent(depth * 2);
|
|
out << "module " << formatModuleId(MD.Id) << "\n";
|
|
dumpDecls(MD.Decls, out, depth + 1);
|
|
}
|
|
|
|
void ModuleMapFile::dump(llvm::raw_ostream &out) const {
|
|
for (const auto &Decl : Decls) {
|
|
std::visit(
|
|
llvm::makeVisitor([&](const ModuleDecl &MD) { dumpModule(MD, out, 0); },
|
|
[&](const ExternModuleDecl &EMD) {
|
|
dumpExternModule(EMD, out, 0);
|
|
}),
|
|
Decl);
|
|
}
|
|
}
|