cmGeneratorTarget: Avoid recursion in GetOutputName method

Since support for generator expressions was added to OUTPUT_NAME it is
possible for project code to cause recursion in this method by using a
$<TARGET_FILE> genex.  Detect and reject such cases.
This commit is contained in:
Robert Goulet 2015-08-14 20:35:58 +00:00 committed by Brad King
parent a38ea312c0
commit 3c37d2642d
7 changed files with 72 additions and 35 deletions

View File

@ -270,48 +270,71 @@ const char *cmGeneratorTarget::GetProperty(const std::string& prop) const
std::string cmGeneratorTarget::GetOutputName(const std::string& config, std::string cmGeneratorTarget::GetOutputName(const std::string& config,
bool implib) const bool implib) const
{ {
std::vector<std::string> props; // Lookup/compute/cache the output name for this configuration.
std::string type = this->Target->GetOutputTargetType(implib); OutputNameKey key(config, implib);
std::string configUpper = cmSystemTools::UpperCase(config); cmGeneratorTarget::OutputNameMapType::iterator i =
if(!type.empty() && !configUpper.empty()) this->OutputNameMap.find(key);
if(i == this->OutputNameMap.end())
{ {
// <ARCHIVE|LIBRARY|RUNTIME>_OUTPUT_NAME_<CONFIG> // Add empty name in map to detect potential recursion.
props.push_back(type + "_OUTPUT_NAME_" + configUpper); OutputNameMapType::value_type entry(key, "");
} i = this->OutputNameMap.insert(entry).first;
if(!type.empty())
{
// <ARCHIVE|LIBRARY|RUNTIME>_OUTPUT_NAME
props.push_back(type + "_OUTPUT_NAME");
}
if(!configUpper.empty())
{
// OUTPUT_NAME_<CONFIG>
props.push_back("OUTPUT_NAME_" + configUpper);
// <CONFIG>_OUTPUT_NAME
props.push_back(configUpper + "_OUTPUT_NAME");
}
// OUTPUT_NAME
props.push_back("OUTPUT_NAME");
std::string outName; // Compute output name.
for(std::vector<std::string>::const_iterator i = props.begin(); std::vector<std::string> props;
i != props.end(); ++i) std::string type = this->Target->GetOutputTargetType(implib);
{ std::string configUpper = cmSystemTools::UpperCase(config);
if (const char* outNameProp = this->Target->GetProperty(*i)) if(!type.empty() && !configUpper.empty())
{ {
outName = outNameProp; // <ARCHIVE|LIBRARY|RUNTIME>_OUTPUT_NAME_<CONFIG>
break; props.push_back(type + "_OUTPUT_NAME_" + configUpper);
} }
} if(!type.empty())
{
// <ARCHIVE|LIBRARY|RUNTIME>_OUTPUT_NAME
props.push_back(type + "_OUTPUT_NAME");
}
if(!configUpper.empty())
{
// OUTPUT_NAME_<CONFIG>
props.push_back("OUTPUT_NAME_" + configUpper);
// <CONFIG>_OUTPUT_NAME
props.push_back(configUpper + "_OUTPUT_NAME");
}
// OUTPUT_NAME
props.push_back("OUTPUT_NAME");
if (outName.empty()) std::string outName;
for(std::vector<std::string>::const_iterator it = props.begin();
it != props.end(); ++it)
{
if (const char* outNameProp = this->Target->GetProperty(*it))
{
outName = outNameProp;
break;
}
}
if(outName.empty())
{
outName = this->GetName();
}
// Now evaluate genex and update the previously-prepared map entry.
cmGeneratorExpression ge;
cmsys::auto_ptr<cmCompiledGeneratorExpression> cge = ge.Parse(outName);
i->second = cge->Evaluate(this->Makefile, config);
}
else if(i->second.empty())
{ {
outName = this->GetName(); // An empty map entry indicates we have been called recursively
// from the above block.
this->Makefile->GetCMakeInstance()->IssueMessage(
cmake::FATAL_ERROR,
"Target '" + this->GetName() + "' OUTPUT_NAME depends on itself.",
this->Target->GetBacktrace());
} }
return i->second;
cmGeneratorExpression ge;
cmsys::auto_ptr<cmCompiledGeneratorExpression> cge = ge.Parse(outName);
return cge->Evaluate(this->Makefile, config);
} }
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------

View File

@ -375,6 +375,10 @@ private:
}; };
mutable std::map<std::string, LinkImplClosure> LinkImplClosureMap; mutable std::map<std::string, LinkImplClosure> LinkImplClosureMap;
typedef std::pair<std::string, bool> OutputNameKey;
typedef std::map<OutputNameKey, std::string> OutputNameMapType;
mutable OutputNameMapType OutputNameMap;
public: public:
std::vector<cmTarget const*> const& std::vector<cmTarget const*> const&
GetLinkImplementationClosure(const std::string& config) const; GetLinkImplementationClosure(const std::string& config) const;

View File

@ -0,0 +1,4 @@
CMake Error at OUTPUT_NAME-recursion.cmake:[0-9]+ \(add_executable\):
Target 'empty1' OUTPUT_NAME depends on itself.
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)

View File

@ -0,0 +1,3 @@
enable_language(C)
add_executable(empty1 empty.c)
set_property(TARGET empty1 PROPERTY OUTPUT_NAME $<TARGET_FILE_NAME:empty1>)

View File

@ -26,6 +26,7 @@ run_cmake(COMPILE_LANGUAGE-add_library)
run_cmake(COMPILE_LANGUAGE-add_test) run_cmake(COMPILE_LANGUAGE-add_test)
run_cmake(COMPILE_LANGUAGE-unknown-lang) run_cmake(COMPILE_LANGUAGE-unknown-lang)
run_cmake(TARGET_FILE-recursion) run_cmake(TARGET_FILE-recursion)
run_cmake(OUTPUT_NAME-recursion)
run_cmake(ImportedTarget-TARGET_PDB_FILE) run_cmake(ImportedTarget-TARGET_PDB_FILE)
if(LINKER_SUPPORTS_PDB) if(LINKER_SUPPORTS_PDB)

View File

@ -1,3 +1,4 @@
enable_language(C) enable_language(C)
add_executable(empty1 empty.c) add_executable(empty1 empty.c)
set_property(TARGET empty1 PROPERTY OUTPUT_NAME $<TARGET_FILE_NAME:empty1>)
set_property(TARGET empty1 PROPERTY RUNTIME_OUTPUT_DIRECTORY $<TARGET_FILE_DIR:empty1>) set_property(TARGET empty1 PROPERTY RUNTIME_OUTPUT_DIRECTORY $<TARGET_FILE_DIR:empty1>)