Genex: Diagnose cyclic link information

A generator expression such as `$<TARGET_RUNTIME_DLLS:tgt>` reachable
from the link closure of `tgt` causes a nested re-entry into
`GetLinkInformation`. This computation runs over a partially computed
link implementation and leads to bogus generation errors or a crash.

Reserve the cache entry before computing, and error out when the
computation is re-entered.
This commit is contained in:
Tyler Yankee committed 2026-10-29 15:41:22 -04:00
1 parent cda028d08c
commit 2ac0d35159
6 files changed
+43 -2

No files matched your search

+1
View File
@@ -1302,6 +1302,7 @@ private:
using cmTargetLinkInformationMap =
std::map<std::string, std::unique_ptr<cmComputeLinkInformation>>;
mutable cmTargetLinkInformationMap LinkInformation;
mutable std::set<std::string> LinkInformationInProgress;
void CheckPropertyCompatibility(cmComputeLinkInformation& info,
std::string const& config) const;
+17 -2
View File
@@ -347,7 +347,21 @@ cmComputeLinkInformation* cmGeneratorTarget::GetLinkInformation(
// Lookup any existing information for this configuration.
std::string key(cmSystemTools::UpperCase(config));
auto i = this->LinkInformation.find(key);
if (i == this->LinkInformation.end()) {
if (i != this->LinkInformation.end()) {
if (cm::contains(this->LinkInformationInProgress, key)) {
this->GlobalGenerator->GetCMakeInstance()->IssueMessage(
MessageType::FATAL_ERROR,
cmStrCat("The link dependencies of \"", this->GetName(),
"\" use a generator expression that depends on the link "
"dependencies themselves."),
this->GetBacktrace());
}
} else {
// Reserve the entry so that a recursive lookup while computing the
// information is diagnosed instead of re-entering the computation.
i = this->LinkInformation.emplace(key, nullptr).first;
this->LinkInformationInProgress.insert(key);
// Compute information for this configuration.
auto info = cm::make_unique<cmComputeLinkInformation>(this, config);
if (info && !info->Compute()) {
@@ -355,7 +369,8 @@ cmComputeLinkInformation* cmGeneratorTarget::GetLinkInformation(
}
// Store the information for this configuration.
i = this->LinkInformation.emplace(key, std::move(info)).first;
this->LinkInformationInProgress.erase(key);
i->second = std::move(info);
if (i->second) {
this->CheckPropertyCompatibility(*i->second, config);
@@ -5,3 +5,4 @@ run_cmake(static)
run_cmake(target_link_libraries)
run_cmake(target_link_libraries-cycle1)
run_cmake(target_link_libraries-cycle2)
run_cmake(target_link_libraries-cycle3)
@@ -0,0 +1,5 @@
CMake Error at target_link_libraries-cycle3\.cmake:[0-9]+ \(add_library\):
The link dependencies of "lib1" use a generator expression that depends on
the link dependencies themselves\.
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\)
@@ -0,0 +1,18 @@
enable_language(C)
# lib1 -> lib2 -> static -> iface -> $<TARGET_RUNTIME_DLLS:lib1>
add_library(lib1 SHARED lib1.c)
add_library(lib2 SHARED lib2.c)
add_library(static STATIC static.c)
add_library(iface INTERFACE)
set_target_properties(iface PROPERTIES
COMPATIBLE_INTERFACE_BOOL FOO
INTERFACE_FOO ON
)
target_link_libraries(iface INTERFACE $<TARGET_RUNTIME_DLLS:lib1>)
target_link_libraries(static PUBLIC iface)
target_link_libraries(lib2 PUBLIC static)
target_link_libraries(lib1 PRIVATE lib2)
# Evaluate the link interface before the sources of lib1 are computed.
file(GENERATE OUTPUT "out-$<TARGET_PROPERTY:lib1,FOO>.txt" CONTENT "")