Jan Svoboda 6e86ee2c23
[clang][modules] Stop uniquing implicit modules via FileEntry (#185765)
This PR changes how `ModuleManager` deduplicates module files.

Previously, `ModuleManager` used `FileEntry` for assigning unique
identity to module files. This works fine for explicitly-built modules
because they don't change during the lifetime of a single Clang
instance. For implicitly-built modules however, there are two issues:
1. The `FileEntry` objects are deduplicated by `FileManager` based on
the inode number. Some file systems reuse inode numbers of previously
removed files. Because implicitly-built module files are rapidly removed
and created, this deduplication breaks and compilations may fail
spuriously when inode numbers are recycled during the lifetime of a
single Clang instance.
2. The first thing `ModuleManager` does when loading a module file is
consulting the `FileManager` and checking the file size and modification
time match the expectation of the importer. This is done even when such
module file already lives in the `InMemoryModuleCache`. This introduces
racy behavior into the mechanism that explicitly tries to solve race
conditions, and may lead into spurious compilation failures.

This PR identifies implicitly-built module files by a pair of
`DirectoryEntry` of the module cache path and the path suffix
`<context-hash>/<module-name>-<module-map-path-hash>.pcm`. This gives us
canonicalization of the user-provided module cache path without turning
to `FileEntry` for the PCM file. The path suffix is Clang-generated and
is already canonical.

Some tests needed to be updated because the module cache path directory
was also used as an include directory. This PR relies on not caching the
non-existence of the module cache directory in the `FileManager`. When
other parts of Clang are trying to look up the same path and cache its
non-existence, things break. This is probably very specific to some of
our tests and not how users are setting up their compilations.
2026-03-18 09:47:51 -07:00

730 lines
23 KiB
C++

//===- Module.cpp - Describe a module -------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// This file defines the Module class, which describes a module in the source
// code.
//
//===----------------------------------------------------------------------===//
#include "clang/Basic/Module.h"
#include "clang/Basic/CharInfo.h"
#include "clang/Basic/FileManager.h"
#include "clang/Basic/LangOptions.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Basic/TargetInfo.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/StringSwitch.h"
#include "llvm/Support/Compiler.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/raw_ostream.h"
#include <cassert>
#include <functional>
#include <string>
#include <utility>
#include <vector>
using namespace clang;
std::optional<ModuleFileKey>
ModuleFileName::makeKey(FileManager &FileMgr) const {
if (ImplicitModuleSuffixLength) {
StringRef ModuleCachePath =
StringRef(Path).drop_back(ImplicitModuleSuffixLength);
StringRef ImplicitModuleSuffix =
StringRef(Path).take_back(ImplicitModuleSuffixLength);
if (auto ModuleCache = FileMgr.getOptionalDirectoryRef(
ModuleCachePath, /*CacheFailure=*/false))
return ModuleFileKey(*ModuleCache, ImplicitModuleSuffix);
} else {
if (auto ModuleFile = FileMgr.getOptionalFileRef(Path, /*OpenFile=*/true,
/*CacheFailure=*/false))
return ModuleFileKey(*ModuleFile);
}
return std::nullopt;
}
Module::Module(ModuleConstructorTag, StringRef Name,
SourceLocation DefinitionLoc, Module *Parent, bool IsFramework,
bool IsExplicit, unsigned VisibilityID)
: Name(Name), DefinitionLoc(DefinitionLoc), Parent(Parent),
VisibilityID(VisibilityID), IsUnimportable(false),
HasIncompatibleModuleFile(false), IsAvailable(true),
IsFromModuleFile(false), IsFramework(IsFramework), IsExplicit(IsExplicit),
IsSystem(false), IsExternC(false), IsInferred(false),
InferSubmodules(false), InferExplicitSubmodules(false),
InferExportWildcard(false), ConfigMacrosExhaustive(false),
NoUndeclaredIncludes(false), ModuleMapIsPrivate(false),
NamedModuleHasInit(true), NameVisibility(Hidden) {
if (Parent) {
IsAvailable = Parent->isAvailable();
IsUnimportable = Parent->isUnimportable();
IsSystem = Parent->IsSystem;
IsExternC = Parent->IsExternC;
NoUndeclaredIncludes = Parent->NoUndeclaredIncludes;
ModuleMapIsPrivate = Parent->ModuleMapIsPrivate;
Parent->SubModules.push_back(this);
}
}
Module::~Module() = default;
static bool isPlatformEnvironment(const TargetInfo &Target, StringRef Feature) {
StringRef Platform = Target.getPlatformName();
StringRef Env = Target.getTriple().getEnvironmentName();
// Attempt to match platform and environment.
if (Platform == Feature || Target.getTriple().getOSName() == Feature ||
Env == Feature)
return true;
auto CmpPlatformEnv = [](StringRef LHS, StringRef RHS) {
auto Pos = LHS.find('-');
if (Pos == StringRef::npos)
return false;
SmallString<128> NewLHS = LHS.slice(0, Pos);
NewLHS += LHS.slice(Pos+1, LHS.size());
return NewLHS == RHS;
};
SmallString<128> PlatformEnv = Target.getTriple().getOSAndEnvironmentName();
// Darwin has different but equivalent variants for simulators, example:
// 1. x86_64-apple-ios-simulator
// 2. x86_64-apple-iossimulator
// where both are valid examples of the same platform+environment but in the
// variant (2) the simulator is hardcoded as part of the platform name. Both
// forms above should match for "iossimulator" requirement.
if (Target.getTriple().isOSDarwin() && PlatformEnv.ends_with("simulator"))
return PlatformEnv == Feature || CmpPlatformEnv(PlatformEnv, Feature);
return PlatformEnv == Feature;
}
/// Determine whether a translation unit built using the current
/// language options has the given feature.
static bool hasFeature(StringRef Feature, const LangOptions &LangOpts,
const TargetInfo &Target) {
bool HasFeature = llvm::StringSwitch<bool>(Feature)
.Case("altivec", LangOpts.AltiVec)
.Case("blocks", LangOpts.Blocks)
.Case("coroutines", LangOpts.Coroutines)
.Case("cplusplus", LangOpts.CPlusPlus)
.Case("cplusplus11", LangOpts.CPlusPlus11)
.Case("cplusplus14", LangOpts.CPlusPlus14)
.Case("cplusplus17", LangOpts.CPlusPlus17)
.Case("cplusplus20", LangOpts.CPlusPlus20)
.Case("cplusplus23", LangOpts.CPlusPlus23)
.Case("cplusplus26", LangOpts.CPlusPlus26)
.Case("c99", LangOpts.C99)
.Case("c11", LangOpts.C11)
.Case("c17", LangOpts.C17)
.Case("c23", LangOpts.C23)
.Case("freestanding", LangOpts.Freestanding)
.Case("gnuinlineasm", LangOpts.GNUAsm)
.Case("objc", LangOpts.ObjC)
.Case("objc_arc", LangOpts.ObjCAutoRefCount)
.Case("opencl", LangOpts.OpenCL)
.Case("tls", Target.isTLSSupported())
.Case("zvector", LangOpts.ZVector)
.Default(Target.hasFeature(Feature) ||
isPlatformEnvironment(Target, Feature));
if (!HasFeature)
HasFeature = llvm::is_contained(LangOpts.ModuleFeatures, Feature);
return HasFeature;
}
bool Module::isUnimportable(const LangOptions &LangOpts,
const TargetInfo &Target, Requirement &Req,
Module *&ShadowingModule) const {
if (!IsUnimportable)
return false;
for (const Module *Current = this; Current; Current = Current->Parent) {
if (Current->ShadowingModule) {
ShadowingModule = Current->ShadowingModule;
return true;
}
for (unsigned I = 0, N = Current->Requirements.size(); I != N; ++I) {
if (hasFeature(Current->Requirements[I].FeatureName, LangOpts, Target) !=
Current->Requirements[I].RequiredState) {
Req = Current->Requirements[I];
return true;
}
}
}
llvm_unreachable("could not find a reason why module is unimportable");
}
// The -fmodule-name option tells the compiler to textually include headers in
// the specified module, meaning Clang won't build the specified module. This
// is useful in a number of situations, for instance, when building a library
// that vends a module map, one might want to avoid hitting intermediate build
// products containing the module map or avoid finding the system installed
// modulemap for that library.
bool Module::isForBuilding(const LangOptions &LangOpts) const {
StringRef TopLevelName = getTopLevelModuleName();
StringRef CurrentModule = LangOpts.CurrentModule;
// When building the implementation of framework Foo, we want to make sure
// that Foo *and* Foo_Private are textually included and no modules are built
// for either.
if (!LangOpts.isCompilingModule() && getTopLevelModule()->IsFramework &&
CurrentModule == LangOpts.ModuleName &&
!CurrentModule.ends_with("_Private") &&
TopLevelName.ends_with("_Private"))
TopLevelName = TopLevelName.drop_back(8);
return TopLevelName == CurrentModule;
}
bool Module::isAvailable(const LangOptions &LangOpts, const TargetInfo &Target,
Requirement &Req,
UnresolvedHeaderDirective &MissingHeader,
Module *&ShadowingModule) const {
if (IsAvailable)
return true;
if (isUnimportable(LangOpts, Target, Req, ShadowingModule))
return false;
// FIXME: All missing headers are listed on the top-level module. Should we
// just look there?
for (const Module *Current = this; Current; Current = Current->Parent) {
if (!Current->MissingHeaders.empty()) {
MissingHeader = Current->MissingHeaders.front();
return false;
}
}
llvm_unreachable("could not find a reason why module is unavailable");
}
bool Module::isSubModuleOf(const Module *Other) const {
for (auto *Parent = this; Parent; Parent = Parent->Parent) {
if (Parent == Other)
return true;
}
return false;
}
const Module *Module::getTopLevelModule() const {
const Module *Result = this;
while (Result->Parent)
Result = Result->Parent;
return Result;
}
static StringRef getModuleNameFromComponent(
const std::pair<std::string, SourceLocation> &IdComponent) {
return IdComponent.first;
}
static StringRef getModuleNameFromComponent(StringRef R) { return R; }
template<typename InputIter>
static void printModuleId(raw_ostream &OS, InputIter Begin, InputIter End,
bool AllowStringLiterals = true) {
for (InputIter It = Begin; It != End; ++It) {
if (It != Begin)
OS << ".";
StringRef Name = getModuleNameFromComponent(*It);
if (!AllowStringLiterals || isValidAsciiIdentifier(Name))
OS << Name;
else {
OS << '"';
OS.write_escaped(Name);
OS << '"';
}
}
}
template<typename Container>
static void printModuleId(raw_ostream &OS, const Container &C) {
return printModuleId(OS, C.begin(), C.end());
}
std::string Module::getFullModuleName(bool AllowStringLiterals) const {
SmallVector<StringRef, 2> Names;
// Build up the set of module names (from innermost to outermost).
for (const Module *M = this; M; M = M->Parent)
Names.push_back(M->Name);
std::string Result;
llvm::raw_string_ostream Out(Result);
printModuleId(Out, Names.rbegin(), Names.rend(), AllowStringLiterals);
return Result;
}
bool Module::fullModuleNameIs(ArrayRef<StringRef> nameParts) const {
for (const Module *M = this; M; M = M->Parent) {
if (nameParts.empty() || M->Name != nameParts.back())
return false;
nameParts = nameParts.drop_back();
}
return nameParts.empty();
}
OptionalDirectoryEntryRef Module::getEffectiveUmbrellaDir() const {
if (const auto *Hdr = std::get_if<FileEntryRef>(&Umbrella))
return Hdr->getDir();
if (const auto *Dir = std::get_if<DirectoryEntryRef>(&Umbrella))
return *Dir;
return std::nullopt;
}
void Module::addTopHeader(FileEntryRef File) {
assert(File);
TopHeaders.insert(File);
}
ArrayRef<FileEntryRef> Module::getTopHeaders(FileManager &FileMgr) {
if (!TopHeaderNames.empty()) {
for (StringRef TopHeaderName : TopHeaderNames)
if (auto FE = FileMgr.getOptionalFileRef(TopHeaderName))
TopHeaders.insert(*FE);
TopHeaderNames.clear();
}
return llvm::ArrayRef(TopHeaders.begin(), TopHeaders.end());
}
bool Module::directlyUses(const Module *Requested) {
auto *Top = getTopLevelModule();
// A top-level module implicitly uses itself.
if (Requested->isSubModuleOf(Top))
return true;
for (auto *Use : Top->DirectUses)
if (Requested->isSubModuleOf(Use))
return true;
// Anyone is allowed to use our builtin stddef.h and its accompanying modules.
if (Requested->fullModuleNameIs({"_Builtin_stddef", "max_align_t"}) ||
Requested->fullModuleNameIs({"_Builtin_stddef_wint_t"}))
return true;
// Darwin is allowed is to use our builtin 'ptrauth.h' and its accompanying
// module.
if (!Requested->Parent && Requested->Name == "ptrauth")
return true;
if (NoUndeclaredIncludes)
UndeclaredUses.insert(Requested);
return false;
}
void Module::addRequirement(StringRef Feature, bool RequiredState,
const LangOptions &LangOpts,
const TargetInfo &Target) {
Requirements.push_back(Requirement{std::string(Feature), RequiredState});
// If this feature is currently available, we're done.
if (hasFeature(Feature, LangOpts, Target) == RequiredState)
return;
markUnavailable(/*Unimportable*/true);
}
void Module::markUnavailable(bool Unimportable) {
auto needUpdate = [Unimportable](Module *M) {
return M->IsAvailable || (!M->IsUnimportable && Unimportable);
};
if (!needUpdate(this))
return;
SmallVector<Module *, 2> Stack;
Stack.push_back(this);
while (!Stack.empty()) {
Module *Current = Stack.pop_back_val();
if (!needUpdate(Current))
continue;
Current->IsAvailable = false;
Current->IsUnimportable |= Unimportable;
for (auto *Submodule : Current->submodules()) {
if (needUpdate(Submodule))
Stack.push_back(Submodule);
}
}
}
Module *Module::findSubmodule(StringRef Name) const {
// Add new submodules into the index.
for (unsigned I = SubModuleIndex.size(), E = SubModules.size(); I != E; ++I)
SubModuleIndex[SubModules[I]->Name] = I;
if (auto It = SubModuleIndex.find(Name); It != SubModuleIndex.end())
return SubModules[It->second];
return nullptr;
}
Module *Module::getGlobalModuleFragment() const {
assert(isNamedModuleUnit() && "We should only query the global module "
"fragment from the C++20 Named modules");
for (auto *SubModule : SubModules)
if (SubModule->isExplicitGlobalModule())
return SubModule;
return nullptr;
}
Module *Module::getPrivateModuleFragment() const {
assert(isNamedModuleUnit() && "We should only query the private module "
"fragment from the C++20 Named modules");
for (auto *SubModule : SubModules)
if (SubModule->isPrivateModule())
return SubModule;
return nullptr;
}
void Module::getExportedModules(SmallVectorImpl<Module *> &Exported) const {
// All non-explicit submodules are exported.
for (std::vector<Module *>::const_iterator I = SubModules.begin(),
E = SubModules.end();
I != E; ++I) {
Module *Mod = *I;
if (!Mod->IsExplicit)
Exported.push_back(Mod);
}
// Find re-exported modules by filtering the list of imported modules.
bool AnyWildcard = false;
bool UnrestrictedWildcard = false;
SmallVector<Module *, 4> WildcardRestrictions;
for (unsigned I = 0, N = Exports.size(); I != N; ++I) {
Module *Mod = Exports[I].getPointer();
if (!Exports[I].getInt()) {
// Export a named module directly; no wildcards involved.
Exported.push_back(Mod);
continue;
}
// Wildcard export: export all of the imported modules that match
// the given pattern.
AnyWildcard = true;
if (UnrestrictedWildcard)
continue;
if (Module *Restriction = Exports[I].getPointer())
WildcardRestrictions.push_back(Restriction);
else {
WildcardRestrictions.clear();
UnrestrictedWildcard = true;
}
}
// If there were any wildcards, push any imported modules that were
// re-exported by the wildcard restriction.
if (!AnyWildcard)
return;
for (unsigned I = 0, N = Imports.size(); I != N; ++I) {
Module *Mod = Imports[I];
bool Acceptable = UnrestrictedWildcard;
if (!Acceptable) {
// Check whether this module meets one of the restrictions.
for (unsigned R = 0, NR = WildcardRestrictions.size(); R != NR; ++R) {
Module *Restriction = WildcardRestrictions[R];
if (Mod == Restriction || Mod->isSubModuleOf(Restriction)) {
Acceptable = true;
break;
}
}
}
if (!Acceptable)
continue;
Exported.push_back(Mod);
}
}
void Module::buildVisibleModulesCache() const {
assert(VisibleModulesCache.empty() && "cache does not need building");
// This module is visible to itself.
VisibleModulesCache.insert(this);
// Every imported module is visible.
SmallVector<Module *, 16> Stack(Imports.begin(), Imports.end());
while (!Stack.empty()) {
Module *CurrModule = Stack.pop_back_val();
// Every module transitively exported by an imported module is visible.
if (VisibleModulesCache.insert(CurrModule).second)
CurrModule->getExportedModules(Stack);
}
}
void Module::print(raw_ostream &OS, unsigned Indent, bool Dump) const {
OS.indent(Indent);
if (IsFramework)
OS << "framework ";
if (IsExplicit)
OS << "explicit ";
OS << "module ";
printModuleId(OS, &Name, &Name + 1);
if (IsSystem || IsExternC) {
OS.indent(Indent + 2);
if (IsSystem)
OS << " [system]";
if (IsExternC)
OS << " [extern_c]";
}
OS << " {\n";
if (!Requirements.empty()) {
OS.indent(Indent + 2);
OS << "requires ";
for (unsigned I = 0, N = Requirements.size(); I != N; ++I) {
if (I)
OS << ", ";
if (!Requirements[I].RequiredState)
OS << "!";
OS << Requirements[I].FeatureName;
}
OS << "\n";
}
if (std::optional<Header> H = getUmbrellaHeaderAsWritten()) {
OS.indent(Indent + 2);
OS << "umbrella header \"";
OS.write_escaped(H->NameAsWritten);
OS << "\"\n";
} else if (std::optional<DirectoryName> D = getUmbrellaDirAsWritten()) {
OS.indent(Indent + 2);
OS << "umbrella \"";
OS.write_escaped(D->NameAsWritten);
OS << "\"\n";
}
if (!ConfigMacros.empty() || ConfigMacrosExhaustive) {
OS.indent(Indent + 2);
OS << "config_macros ";
if (ConfigMacrosExhaustive)
OS << "[exhaustive]";
for (unsigned I = 0, N = ConfigMacros.size(); I != N; ++I) {
if (I)
OS << ", ";
OS << ConfigMacros[I];
}
OS << "\n";
}
struct {
StringRef Prefix;
HeaderKind Kind;
} Kinds[] = {{"", HK_Normal},
{"textual ", HK_Textual},
{"private ", HK_Private},
{"private textual ", HK_PrivateTextual},
{"exclude ", HK_Excluded}};
for (auto &K : Kinds) {
assert(&K == &Kinds[K.Kind] && "kinds in wrong order");
for (auto &H : getHeaders(K.Kind)) {
OS.indent(Indent + 2);
OS << K.Prefix << "header \"";
OS.write_escaped(H.NameAsWritten);
OS << "\" { size " << H.Entry.getSize()
<< " mtime " << H.Entry.getModificationTime() << " }\n";
}
}
for (auto *Unresolved : {&UnresolvedHeaders, &MissingHeaders}) {
for (auto &U : *Unresolved) {
OS.indent(Indent + 2);
OS << Kinds[U.Kind].Prefix << "header \"";
OS.write_escaped(U.FileName);
OS << "\"";
if (U.Size || U.ModTime) {
OS << " {";
if (U.Size)
OS << " size " << *U.Size;
if (U.ModTime)
OS << " mtime " << *U.ModTime;
OS << " }";
}
OS << "\n";
}
}
if (!ExportAsModule.empty()) {
OS.indent(Indent + 2);
OS << "export_as" << ExportAsModule << "\n";
}
for (auto *Submodule : submodules())
// Print inferred subframework modules so that we don't need to re-infer
// them (requires expensive directory iteration + stat calls) when we build
// the module. Regular inferred submodules are OK, as we need to look at all
// those header files anyway.
if (!Submodule->IsInferred || Submodule->IsFramework)
Submodule->print(OS, Indent + 2, Dump);
for (unsigned I = 0, N = Exports.size(); I != N; ++I) {
OS.indent(Indent + 2);
OS << "export ";
if (Module *Restriction = Exports[I].getPointer()) {
OS << Restriction->getFullModuleName(true);
if (Exports[I].getInt())
OS << ".*";
} else {
OS << "*";
}
OS << "\n";
}
for (unsigned I = 0, N = UnresolvedExports.size(); I != N; ++I) {
OS.indent(Indent + 2);
OS << "export ";
printModuleId(OS, UnresolvedExports[I].Id);
if (UnresolvedExports[I].Wildcard)
OS << (UnresolvedExports[I].Id.empty() ? "*" : ".*");
OS << "\n";
}
if (Dump) {
for (Module *M : Imports) {
OS.indent(Indent + 2);
llvm::errs() << "import " << M->getFullModuleName() << "\n";
}
}
for (unsigned I = 0, N = DirectUses.size(); I != N; ++I) {
OS.indent(Indent + 2);
OS << "use ";
OS << DirectUses[I]->getFullModuleName(true);
OS << "\n";
}
for (unsigned I = 0, N = UnresolvedDirectUses.size(); I != N; ++I) {
OS.indent(Indent + 2);
OS << "use ";
printModuleId(OS, UnresolvedDirectUses[I]);
OS << "\n";
}
for (unsigned I = 0, N = LinkLibraries.size(); I != N; ++I) {
OS.indent(Indent + 2);
OS << "link ";
if (LinkLibraries[I].IsFramework)
OS << "framework ";
OS << "\"";
OS.write_escaped(LinkLibraries[I].Library);
OS << "\"";
}
for (unsigned I = 0, N = UnresolvedConflicts.size(); I != N; ++I) {
OS.indent(Indent + 2);
OS << "conflict ";
printModuleId(OS, UnresolvedConflicts[I].Id);
OS << ", \"";
OS.write_escaped(UnresolvedConflicts[I].Message);
OS << "\"\n";
}
for (unsigned I = 0, N = Conflicts.size(); I != N; ++I) {
OS.indent(Indent + 2);
OS << "conflict ";
OS << Conflicts[I].Other->getFullModuleName(true);
OS << ", \"";
OS.write_escaped(Conflicts[I].Message);
OS << "\"\n";
}
if (InferSubmodules) {
OS.indent(Indent + 2);
if (InferExplicitSubmodules)
OS << "explicit ";
OS << "module * {\n";
if (InferExportWildcard) {
OS.indent(Indent + 4);
OS << "export *\n";
}
OS.indent(Indent + 2);
OS << "}\n";
}
OS.indent(Indent);
OS << "}\n";
}
LLVM_DUMP_METHOD void Module::dump() const {
print(llvm::errs(), 0, true);
}
void VisibleModuleSet::setVisible(Module *M, SourceLocation Loc,
bool IncludeExports, VisibleCallback Vis,
ConflictCallback Cb) {
// We can't import a global module fragment so the location can be invalid.
assert((M->isGlobalModule() || Loc.isValid()) &&
"setVisible expects a valid import location");
if (isVisible(M))
return;
++Generation;
struct Visiting {
Module *M;
Visiting *ExportedBy;
};
std::function<void(Visiting)> VisitModule = [&](Visiting V) {
// Nothing to do for a module that's already visible.
unsigned ID = V.M->getVisibilityID();
if (ImportLocs.size() <= ID)
ImportLocs.resize(ID + 1);
else if (ImportLocs[ID].isValid())
return;
ImportLocs[ID] = Loc;
Vis(V.M);
// Make any exported modules visible.
if (IncludeExports) {
SmallVector<Module *, 16> Exports;
V.M->getExportedModules(Exports);
for (Module *E : Exports) {
// Don't import non-importable modules.
if (!E->isUnimportable())
VisitModule({E, &V});
}
}
for (auto &C : V.M->Conflicts) {
if (isVisible(C.Other)) {
llvm::SmallVector<Module*, 8> Path;
for (Visiting *I = &V; I; I = I->ExportedBy)
Path.push_back(I->M);
Cb(Path, C.Other, C.Message);
}
}
};
VisitModule({M, nullptr});
}