[flang-commits] [flang] [flang] Safer hermetic module file reading (PR #121002)
Peter Klausler via flang-commits
flang-commits at lists.llvm.org
Tue Jan 7 08:01:19 PST 2025
https://github.com/klausler updated https://github.com/llvm/llvm-project/pull/121002
>From 661c42578ead837f713092b77473a0acc50b9f14 Mon Sep 17 00:00:00 2001
From: Peter Klausler <pklausler at nvidia.com>
Date: Mon, 23 Dec 2024 13:18:05 -0800
Subject: [PATCH] [flang] Safer hermetic module file reading
When a hermetic module file is read, use a new scope to hold its
dependent modules so that they don't conflict with any modules in
the global scope.
---
flang/docs/ModFiles.md | 10 ++
flang/include/flang/Common/Fortran-features.h | 3 +-
flang/include/flang/Evaluate/type.h | 2 +
flang/include/flang/Semantics/semantics.h | 7 +
flang/lib/Evaluate/type.cpp | 34 +++--
flang/lib/Semantics/mod-file.cpp | 19 +++
flang/lib/Semantics/resolve-names.cpp | 129 +++++++++++++++++-
flang/test/Semantics/modfile71.F90 | 81 +++++++++++
8 files changed, 272 insertions(+), 13 deletions(-)
create mode 100644 flang/test/Semantics/modfile71.F90
diff --git a/flang/docs/ModFiles.md b/flang/docs/ModFiles.md
index 7463454c8563a2..a4c2395d308fb2 100644
--- a/flang/docs/ModFiles.md
+++ b/flang/docs/ModFiles.md
@@ -164,3 +164,13 @@ a diagnostic but we still wouldn't have line numbers.
To provide line numbers and character positions or source lines as the user
wrote them we would have to save some amount of provenance information in the
module file as well.
+
+## Hermetic modules files
+
+Top-level module files for libraries can be build with `-fhermetic-module-files`.
+This option causes these module files to contain copies of all of the non-intrinsic
+modules on which they depend, so that non-top-level local modules and the
+modules of dependent libraries need not also be packaged with the library.
+When the compiler reads a hermetic module file, the copies of the dependent
+modules are read into their own scope, and will not conflict with other modules
+of the same name that client code might `USE`.
diff --git a/flang/include/flang/Common/Fortran-features.h b/flang/include/flang/Common/Fortran-features.h
index 44f88009f8f2c2..9549e8bfbbef0b 100644
--- a/flang/include/flang/Common/Fortran-features.h
+++ b/flang/include/flang/Common/Fortran-features.h
@@ -73,7 +73,8 @@ ENUM_CLASS(UsageWarning, Portability, PointerToUndefinable,
PreviousScalarUse, RedeclaredInaccessibleComponent, ImplicitShared,
IndexVarRedefinition, IncompatibleImplicitInterfaces, BadTypeForTarget,
VectorSubscriptFinalization, UndefinedFunctionResult, UselessIomsg,
- MismatchingDummyProcedure, SubscriptedEmptyArray, UnsignedLiteralTruncation)
+ MismatchingDummyProcedure, SubscriptedEmptyArray, UnsignedLiteralTruncation,
+ CompatibleDeclarationsFromDistinctModules)
using LanguageFeatures = EnumSet<LanguageFeature, LanguageFeature_enumSize>;
using UsageWarnings = EnumSet<UsageWarning, UsageWarning_enumSize>;
diff --git a/flang/include/flang/Evaluate/type.h b/flang/include/flang/Evaluate/type.h
index 1f9296ac4fea75..a4afe49d6077a6 100644
--- a/flang/include/flang/Evaluate/type.h
+++ b/flang/include/flang/Evaluate/type.h
@@ -510,6 +510,8 @@ bool AreSameDerivedType(
const semantics::DerivedTypeSpec &, const semantics::DerivedTypeSpec &);
bool AreSameDerivedTypeIgnoringTypeParameters(
const semantics::DerivedTypeSpec &, const semantics::DerivedTypeSpec &);
+bool AreSameDerivedTypeIgnoringSequence(
+ const semantics::DerivedTypeSpec &, const semantics::DerivedTypeSpec &);
// For generating "[extern] template class", &c. boilerplate
#define EXPAND_FOR_EACH_INTEGER_KIND(M, P, S) \
diff --git a/flang/include/flang/Semantics/semantics.h b/flang/include/flang/Semantics/semantics.h
index c981d86fbd94cb..821ce021b32264 100644
--- a/flang/include/flang/Semantics/semantics.h
+++ b/flang/include/flang/Semantics/semantics.h
@@ -110,6 +110,12 @@ class SemanticsContext {
}
Scope &globalScope() { return globalScope_; }
Scope &intrinsicModulesScope() { return intrinsicModulesScope_; }
+ Scope *currentHermeticModuleFileScope() {
+ return currentHermeticModuleFileScope_;
+ }
+ void set_currentHermeticModuleFileScope(Scope *scope) {
+ currentHermeticModuleFileScope_ = scope;
+ }
parser::Messages &messages() { return messages_; }
evaluate::FoldingContext &foldingContext() { return foldingContext_; }
parser::AllCookedSources &allCookedSources() { return allCookedSources_; }
@@ -313,6 +319,7 @@ class SemanticsContext {
evaluate::TargetCharacteristics targetCharacteristics_;
Scope globalScope_;
Scope &intrinsicModulesScope_;
+ Scope *currentHermeticModuleFileScope_{nullptr};
ScopeIndex scopeIndex_;
parser::Messages messages_;
evaluate::FoldingContext foldingContext_;
diff --git a/flang/lib/Evaluate/type.cpp b/flang/lib/Evaluate/type.cpp
index 0c2784d9cbe301..c8f75f91ed9c64 100644
--- a/flang/lib/Evaluate/type.cpp
+++ b/flang/lib/Evaluate/type.cpp
@@ -293,11 +293,13 @@ using SetOfDerivedTypePairs =
static bool AreSameDerivedType(const semantics::DerivedTypeSpec &,
const semantics::DerivedTypeSpec &, bool ignoreTypeParameterValues,
- bool ignoreLenParameters, SetOfDerivedTypePairs &inProgress);
+ bool ignoreLenParameters, bool ignoreSequence,
+ SetOfDerivedTypePairs &inProgress);
// F2023 7.5.3.2
static bool AreSameComponent(const semantics::Symbol &x,
- const semantics::Symbol &y, SetOfDerivedTypePairs &inProgress) {
+ const semantics::Symbol &y, bool ignoreSequence,
+ SetOfDerivedTypePairs &inProgress) {
if (x.attrs() != y.attrs()) {
return false;
}
@@ -325,7 +327,8 @@ static bool AreSameComponent(const semantics::Symbol &x,
!yType->IsUnlimitedPolymorphic() ||
(!xType->IsUnlimitedPolymorphic() &&
!AreSameDerivedType(xType->GetDerivedTypeSpec(),
- yType->GetDerivedTypeSpec(), false, false, inProgress))) {
+ yType->GetDerivedTypeSpec(), false, false, ignoreSequence,
+ inProgress))) {
return false;
}
} else if (!xType->IsTkLenCompatibleWith(*yType)) {
@@ -449,7 +452,8 @@ static bool AreTypeParamCompatible(const semantics::DerivedTypeSpec &x,
// F2023 7.5.3.2
static bool AreSameDerivedType(const semantics::DerivedTypeSpec &x,
const semantics::DerivedTypeSpec &y, bool ignoreTypeParameterValues,
- bool ignoreLenParameters, SetOfDerivedTypePairs &inProgress) {
+ bool ignoreLenParameters, bool ignoreSequence,
+ SetOfDerivedTypePairs &inProgress) {
if (&x == &y) {
return true;
}
@@ -472,7 +476,12 @@ static bool AreSameDerivedType(const semantics::DerivedTypeSpec &x,
inProgress.insert(thisQuery);
const auto &xDetails{xSymbol.get<semantics::DerivedTypeDetails>()};
const auto &yDetails{ySymbol.get<semantics::DerivedTypeDetails>()};
- if (!(xDetails.sequence() && yDetails.sequence()) &&
+ if (xDetails.sequence() != yDetails.sequence() ||
+ xSymbol.attrs().test(semantics::Attr::BIND_C) !=
+ ySymbol.attrs().test(semantics::Attr::BIND_C)) {
+ return false;
+ }
+ if (!ignoreSequence && !(xDetails.sequence() && yDetails.sequence()) &&
!(xSymbol.attrs().test(semantics::Attr::BIND_C) &&
ySymbol.attrs().test(semantics::Attr::BIND_C))) {
// PGI does not enforce this requirement; all other Fortran
@@ -493,7 +502,8 @@ static bool AreSameDerivedType(const semantics::DerivedTypeSpec &x,
const auto yLookup{ySymbol.scope()->find(*yComponentName)};
if (xLookup == xSymbol.scope()->end() ||
yLookup == ySymbol.scope()->end() ||
- !AreSameComponent(*xLookup->second, *yLookup->second, inProgress)) {
+ !AreSameComponent(
+ *xLookup->second, *yLookup->second, ignoreSequence, inProgress)) {
return false;
}
}
@@ -503,13 +513,19 @@ static bool AreSameDerivedType(const semantics::DerivedTypeSpec &x,
bool AreSameDerivedType(
const semantics::DerivedTypeSpec &x, const semantics::DerivedTypeSpec &y) {
SetOfDerivedTypePairs inProgress;
- return AreSameDerivedType(x, y, false, false, inProgress);
+ return AreSameDerivedType(x, y, false, false, false, inProgress);
}
bool AreSameDerivedTypeIgnoringTypeParameters(
const semantics::DerivedTypeSpec &x, const semantics::DerivedTypeSpec &y) {
SetOfDerivedTypePairs inProgress;
- return AreSameDerivedType(x, y, true, true, inProgress);
+ return AreSameDerivedType(x, y, true, true, false, inProgress);
+}
+
+bool AreSameDerivedTypeIgnoringSequence(
+ const semantics::DerivedTypeSpec &x, const semantics::DerivedTypeSpec &y) {
+ SetOfDerivedTypePairs inProgress;
+ return AreSameDerivedType(x, y, false, false, true, inProgress);
}
static bool AreSameDerivedType(
@@ -536,7 +552,7 @@ static bool AreCompatibleDerivedTypes(const semantics::DerivedTypeSpec *x,
} else {
SetOfDerivedTypePairs inProgress;
if (AreSameDerivedType(*x, *y, ignoreTypeParameterValues,
- ignoreLenTypeParameters, inProgress)) {
+ ignoreLenTypeParameters, false, inProgress)) {
return true;
} else {
return isPolymorphic &&
diff --git a/flang/lib/Semantics/mod-file.cpp b/flang/lib/Semantics/mod-file.cpp
index c0065045ebee0b..fe3a65e784e0a7 100644
--- a/flang/lib/Semantics/mod-file.cpp
+++ b/flang/lib/Semantics/mod-file.cpp
@@ -1366,6 +1366,12 @@ Scope *ModFileReader::Read(SourceName name, std::optional<bool> isIntrinsic,
name.ToString(), isIntrinsic.value_or(false))};
if (!isIntrinsic.value_or(false) && !ancestor) {
// Already present in the symbol table as a usable non-intrinsic module?
+ if (Scope * hermeticScope{context_.currentHermeticModuleFileScope()}) {
+ auto it{hermeticScope->find(name)};
+ if (it != hermeticScope->end()) {
+ return it->second->scope();
+ }
+ }
auto it{context_.globalScope().find(name)};
if (it != context_.globalScope().end()) {
Scope *scope{it->second->scope()};
@@ -1543,9 +1549,22 @@ Scope *ModFileReader::Read(SourceName name, std::optional<bool> isIntrinsic,
// Process declarations from the module file
auto wasModuleFileName{context_.foldingContext().moduleFileName()};
context_.foldingContext().set_moduleFileName(name);
+ // Are there multiple modules in the module file due to it having been
+ // created under -fhermetic-module-files? If so, process them first in
+ // their own nested scope that will be visible only to USE statements
+ // within the module file.
+ if (parseTree.v.size() > 1) {
+ parser::Program hermeticModules{std::move(parseTree.v)};
+ parseTree.v.emplace_back(std::move(hermeticModules.v.front()));
+ hermeticModules.v.pop_front();
+ Scope &hermeticScope{topScope.MakeScope(Scope::Kind::Global)};
+ context_.set_currentHermeticModuleFileScope(&hermeticScope);
+ ResolveNames(context_, hermeticModules, hermeticScope);
+ }
GetModuleDependences(context_.moduleDependences(), sourceFile->content());
ResolveNames(context_, parseTree, topScope);
context_.foldingContext().set_moduleFileName(wasModuleFileName);
+ context_.set_currentHermeticModuleFileScope(nullptr);
if (!moduleSymbol) {
// Submodule symbols' storage are owned by their parents' scopes,
// but their names are not in their parents' dictionaries -- we
diff --git a/flang/lib/Semantics/resolve-names.cpp b/flang/lib/Semantics/resolve-names.cpp
index 122c0a2ebb646a..86589867de077b 100644
--- a/flang/lib/Semantics/resolve-names.cpp
+++ b/flang/lib/Semantics/resolve-names.cpp
@@ -2548,9 +2548,11 @@ void ScopeHandler::PopScope() {
ConvertToObjectEntity(*pair.second);
}
funcResultStack_.Pop();
- // If popping back into a global scope, pop back to the main global scope.
- SetScope(currScope_->parent().IsGlobal() ? context().globalScope()
- : currScope_->parent());
+ // If popping back into a global scope, pop back to the top scope.
+ Scope *hermetic{context().currentHermeticModuleFileScope()};
+ SetScope(currScope_->parent().IsGlobal()
+ ? (hermetic ? *hermetic : context().globalScope())
+ : currScope_->parent());
}
void ScopeHandler::SetScope(Scope &scope) {
currScope_ = &scope;
@@ -3179,6 +3181,111 @@ static bool ConvertToUseError(
}
}
+// Two ultimate symbols are distinct, but they have the same name and come
+// from modules with the same name. At link time, their mangled names
+// would conflict, so they had better resolve to the same definition.
+// Check whether the two ultimate symbols have compatible definitions.
+// Returns true if no further processing is required in DoAddUse().
+static bool CheckCompatibleDistinctUltimates(SemanticsContext &context,
+ SourceName location, SourceName localName, const Symbol &localSymbol,
+ const Symbol &localUltimate, const Symbol &useUltimate) {
+ bool bad{false};
+ if (localUltimate.has<GenericDetails>()) {
+ if (useUltimate.has<GenericDetails>() ||
+ useUltimate.has<SubprogramDetails>() ||
+ useUltimate.has<DerivedTypeDetails>()) {
+ return false; // can try to merge them
+ } else {
+ bad = true;
+ }
+ } else if (useUltimate.has<GenericDetails>()) {
+ if (localUltimate.has<SubprogramDetails>() ||
+ localUltimate.has<DerivedTypeDetails>()) {
+ return false; // can try to merge them
+ } else {
+ bad = true;
+ }
+ } else if (localUltimate.has<SubprogramDetails>()) {
+ if (useUltimate.has<SubprogramDetails>()) {
+ auto localCharacteristics{
+ evaluate::characteristics::Procedure::Characterize(
+ localUltimate, context.foldingContext())};
+ auto useCharacteristics{
+ evaluate::characteristics::Procedure::Characterize(
+ useUltimate, context.foldingContext())};
+ if ((localCharacteristics &&
+ (!useCharacteristics ||
+ *localCharacteristics != *useCharacteristics)) ||
+ (!localCharacteristics && useCharacteristics)) {
+ bad = true;
+ }
+ } else {
+ bad = true;
+ }
+ } else if (useUltimate.has<SubprogramDetails>()) {
+ bad = true;
+ } else if (const auto *localObject{
+ localUltimate.detailsIf<ObjectEntityDetails>()}) {
+ if (const auto *useObject{useUltimate.detailsIf<ObjectEntityDetails>()}) {
+ auto localType{evaluate::DynamicType::From(localUltimate)};
+ auto useType{evaluate::DynamicType::From(useUltimate)};
+ if (localUltimate.size() != useUltimate.size() ||
+ (localType &&
+ (!useType || !localType->IsTkLenCompatibleWith(*useType) ||
+ !useType->IsTkLenCompatibleWith(*localType))) ||
+ (!localType && useType)) {
+ bad = true;
+ } else if (IsNamedConstant(localUltimate)) {
+ bad = !IsNamedConstant(useUltimate) ||
+ !(*localObject->init() == *useObject->init());
+ } else {
+ bad = IsNamedConstant(useUltimate);
+ }
+ } else {
+ bad = true;
+ }
+ } else if (useUltimate.has<ObjectEntityDetails>()) {
+ bad = true;
+ } else if (IsProcedurePointer(localUltimate)) {
+ bad = !IsProcedurePointer(useUltimate);
+ } else if (IsProcedurePointer(useUltimate)) {
+ bad = true;
+ } else if (localUltimate.has<DerivedTypeDetails>()) {
+ bad = !(useUltimate.has<DerivedTypeDetails>() &&
+ evaluate::AreSameDerivedTypeIgnoringSequence(
+ DerivedTypeSpec{localUltimate.name(), localUltimate},
+ DerivedTypeSpec{useUltimate.name(), useUltimate}));
+ } else if (useUltimate.has<DerivedTypeDetails>()) {
+ bad = true;
+ } else if (localUltimate.has<NamelistDetails>() &&
+ useUltimate.has<NamelistDetails>()) {
+ } else if (localUltimate.has<CommonBlockDetails>() &&
+ useUltimate.has<CommonBlockDetails>()) {
+ } else {
+ bad = true;
+ }
+ if (bad) {
+ context
+ .Say(location,
+ "'%s' use-associated from '%s' in module '%s' is incompatible with '%s' from another module"_err_en_US,
+ localName, useUltimate.name(),
+ useUltimate.owner().GetName().value(), localUltimate.name())
+ .Attach(useUltimate.name(), "First declaration"_en_US)
+ .Attach(localUltimate.name(), "Other declaration"_en_US);
+ return true;
+ }
+ if (auto *msg{context.Warn(
+ common::UsageWarning::CompatibleDeclarationsFromDistinctModules,
+ location,
+ "'%s' is use-associated from '%s' in two distinct instances of module '%s'"_warn_en_US,
+ localName, localUltimate.name(),
+ localUltimate.owner().GetName().value())}) {
+ msg->Attach(localUltimate.name(), "Previous declaration"_en_US)
+ .Attach(useUltimate.name(), "Later declaration"_en_US);
+ }
+ return true;
+}
+
void ModuleVisitor::DoAddUse(SourceName location, SourceName localName,
Symbol &originalLocal, const Symbol &useSymbol) {
Symbol *localSymbol{&originalLocal};
@@ -3220,6 +3327,16 @@ void ModuleVisitor::DoAddUse(SourceName location, SourceName localName,
return;
}
+ if (localUltimate.name() == useUltimate.name() &&
+ localUltimate.owner().IsModule() && useUltimate.owner().IsModule() &&
+ localUltimate.owner().GetName() &&
+ localUltimate.owner().GetName() == useUltimate.owner().GetName()) {
+ if (CheckCompatibleDistinctUltimates(context(), location, localName,
+ *localSymbol, localUltimate, useUltimate)) {
+ return;
+ }
+ }
+
// There are many possible combinations of symbol types that could arrive
// with the same (local) name vie USE association from distinct modules.
// Fortran allows a generic interface to share its name with a derived type,
@@ -9375,6 +9492,12 @@ template <typename A> std::set<SourceName> GetUses(const A &x) {
}
bool ResolveNamesVisitor::Pre(const parser::Program &x) {
+ if (Scope * hermetic{context().currentHermeticModuleFileScope()}) {
+ // Processing either the dependent modules or first module of a
+ // hermetic module file; ensure that the hermetic module scope has
+ // its implicit rules map entry.
+ ImplicitRulesVisitor::BeginScope(*hermetic);
+ }
std::map<SourceName, const parser::ProgramUnit *> modules;
std::set<SourceName> uses;
bool disordered{false};
diff --git a/flang/test/Semantics/modfile71.F90 b/flang/test/Semantics/modfile71.F90
new file mode 100644
index 00000000000000..0b40593de76578
--- /dev/null
+++ b/flang/test/Semantics/modfile71.F90
@@ -0,0 +1,81 @@
+!RUN: %flang_fc1 -fsyntax-only -fhermetic-module-files -DSTEP=1 %s
+!RUN: %flang_fc1 -fsyntax-only -DSTEP=2 %s
+!RUN: not %flang_fc1 -fsyntax-only -pedantic %s 2>&1 | FileCheck %s
+
+! Tests that a module captured in a hermetic module file is compatible when
+! USE'd with a module of the same name USE'd directly.
+
+#if STEP == 1
+module modfile71a
+ ! not errors
+ integer, parameter :: good_named_const = 123
+ integer :: good_var = 1
+ type :: good_derived
+ integer component
+ end type
+ procedure(), pointer :: good_proc_ptr
+ generic :: gen => bad_subroutine
+ ! errors
+ integer, parameter :: bad_named_const = 123
+ integer :: bad_var = 1
+ type :: bad_derived
+ integer component
+ end type
+ procedure(), pointer :: bad_proc_ptr
+ contains
+ subroutine good_subroutine
+ end
+ subroutine bad_subroutine(x)
+ integer x
+ end
+end
+
+module modfile71b
+ use modfile71a ! capture hermetically
+end
+
+#elif STEP == 2
+module modfile71a
+ ! not errors
+ integer, parameter :: good_named_const = 123
+ integer :: good_var = 1
+ type :: good_derived
+ integer component
+ end type
+ procedure(), pointer :: good_proc_ptr
+ generic :: gen => bad_subroutine
+ ! errors
+ integer, parameter :: bad_named_const = 666
+ real :: bad_var = 1.
+ type :: bad_derived
+ real component
+ end type
+ real, pointer :: bad_proc_ptr
+ contains
+ subroutine good_subroutine
+ end
+ subroutine bad_subroutine(x)
+ real x
+ end
+end
+
+#else
+
+!CHECK: error: 'bad_derived' use-associated from 'bad_derived' in module 'modfile71a' is incompatible with 'bad_derived' from another module
+!CHECK: error: 'bad_named_const' use-associated from 'bad_named_const' in module 'modfile71a' is incompatible with 'bad_named_const' from another module
+!CHECK: error: 'bad_proc_ptr' use-associated from 'bad_proc_ptr' in module 'modfile71a' is incompatible with 'bad_proc_ptr' from another module
+!CHECK: error: 'bad_subroutine' use-associated from 'bad_subroutine' in module 'modfile71a' is incompatible with 'bad_subroutine' from another module
+!CHECK: error: 'bad_var' use-associated from 'bad_var' in module 'modfile71a' is incompatible with 'bad_var' from another module
+!CHECK: warning: 'good_derived' is use-associated from 'good_derived' in two distinct instances of module 'modfile71a'
+!CHECK: warning: 'good_named_const' is use-associated from 'good_named_const' in two distinct instances of module 'modfile71a'
+!CHECK: warning: 'good_proc_ptr' is use-associated from 'good_proc_ptr' in two distinct instances of module 'modfile71a'
+!CHECK: warning: 'good_subroutine' is use-associated from 'good_subroutine' in two distinct instances of module 'modfile71a'
+!CHECK: warning: 'good_var' is use-associated from 'good_var' in two distinct instances of module 'modfile71a'
+!CHECK-NOT: error:
+!CHECK-NOT: warning:
+
+program main
+ use modfile71a
+ use modfile71b
+end
+#endif
More information about the flang-commits
mailing list