From 3c685a578a44063bb027787426d2c8b7202a7979 Mon Sep 17 00:00:00 2001
From: Marc Chevrier <marc.chevrier@gmail.com>
Date: Sat, 1 Jul 2023 14:20:15 +0200
Subject: LINK_OPTIONS: ensure correct dollar escaping

Ensure correct escaping for Ninja and Makefile generators.

Fixes: #25049
---
 Source/cmGlobalGenerator.h                                   |  2 ++
 Source/cmGlobalNinjaGenerator.cxx                            | 12 ++++++------
 Source/cmGlobalNinjaGenerator.h                              |  4 ++--
 Source/cmLocalGenerator.cxx                                  | 11 +++++++++++
 Source/cmMakefileTargetGenerator.cxx                         |  6 ++++++
 Source/cmNinjaNormalTargetGenerator.cxx                      |  4 ----
 .../LINK_OPTIONS-dollar-option-check.cmake                   |  4 ++++
 .../LINK_OPTIONS-dollar-option-result.txt                    |  1 +
 Tests/RunCMake/target_link_options/LINK_OPTIONS.cmake        |  4 ++++
 Tests/RunCMake/target_link_options/RunCMakeTest.cmake        |  2 +-
 10 files changed, 37 insertions(+), 13 deletions(-)
 create mode 100644 Tests/RunCMake/target_link_options/LINK_OPTIONS-dollar-option-check.cmake
 create mode 100644 Tests/RunCMake/target_link_options/LINK_OPTIONS-dollar-option-result.txt

diff --git a/Source/cmGlobalGenerator.h b/Source/cmGlobalGenerator.h
index bc59514..735c4ba 100644
--- a/Source/cmGlobalGenerator.h
+++ b/Source/cmGlobalGenerator.h
@@ -637,6 +637,8 @@ public:
   };
   StripCommandStyle GetStripCommandStyle(std::string const& strip);
 
+  virtual std::string& EncodeLiteral(std::string& lit) { return lit; }
+
 protected:
   // for a project collect all its targets by following depend
   // information, and also collect all the targets
diff --git a/Source/cmGlobalNinjaGenerator.cxx b/Source/cmGlobalNinjaGenerator.cxx
index e405909..71084cc 100644
--- a/Source/cmGlobalNinjaGenerator.cxx
+++ b/Source/cmGlobalNinjaGenerator.cxx
@@ -181,14 +181,13 @@ std::string cmGlobalNinjaGenerator::EncodeRuleName(std::string const& name)
   return encoded;
 }
 
-std::string cmGlobalNinjaGenerator::EncodeLiteral(const std::string& lit)
+std::string cmGlobalNinjaGenerator::GetEncodedLiteral(const std::string& lit)
 {
   std::string result = lit;
-  EncodeLiteralInplace(result);
-  return result;
+  return this->EncodeLiteral(result);
 }
 
-void cmGlobalNinjaGenerator::EncodeLiteralInplace(std::string& lit)
+std::string& cmGlobalNinjaGenerator::EncodeLiteral(std::string& lit)
 {
   cmSystemTools::ReplaceString(lit, "$", "$$");
   cmSystemTools::ReplaceString(lit, "\n", "$\n");
@@ -196,6 +195,7 @@ void cmGlobalNinjaGenerator::EncodeLiteralInplace(std::string& lit)
     cmSystemTools::ReplaceString(lit, cmStrCat('$', this->GetCMakeCFGIntDir()),
                                  this->GetCMakeCFGIntDir());
   }
+  return lit;
 }
 
 std::string cmGlobalNinjaGenerator::EncodePath(const std::string& path)
@@ -207,7 +207,7 @@ std::string cmGlobalNinjaGenerator::EncodePath(const std::string& path)
   else
     std::replace(result.begin(), result.end(), '/', '\\');
 #endif
-  this->EncodeLiteralInplace(result);
+  this->EncodeLiteral(result);
   cmSystemTools::ReplaceString(result, " ", "$ ");
   cmSystemTools::ReplaceString(result, ":", "$:");
   return result;
@@ -394,7 +394,7 @@ void cmGlobalNinjaGenerator::WriteCustomCommandBuild(
 #endif
       vars["COMMAND"] = std::move(cmd);
     }
-    vars["DESC"] = this->EncodeLiteral(description);
+    vars["DESC"] = this->GetEncodedLiteral(description);
     if (restat) {
       vars["restat"] = "1";
     }
diff --git a/Source/cmGlobalNinjaGenerator.h b/Source/cmGlobalNinjaGenerator.h
index 95d64e3..4b026eb 100644
--- a/Source/cmGlobalNinjaGenerator.h
+++ b/Source/cmGlobalNinjaGenerator.h
@@ -77,8 +77,8 @@ public:
   static void WriteDivider(std::ostream& os);
 
   static std::string EncodeRuleName(std::string const& name);
-  std::string EncodeLiteral(const std::string& lit);
-  void EncodeLiteralInplace(std::string& lit);
+  std::string& EncodeLiteral(std::string& lit) override;
+  std::string GetEncodedLiteral(const std::string& lit);
   std::string EncodePath(const std::string& path);
 
   std::unique_ptr<cmLinkLineComputer> CreateLinkLineComputer(
diff --git a/Source/cmLocalGenerator.cxx b/Source/cmLocalGenerator.cxx
index 7ad4023..cf1eb96 100644
--- a/Source/cmLocalGenerator.cxx
+++ b/Source/cmLocalGenerator.cxx
@@ -1431,11 +1431,14 @@ void cmLocalGenerator::GetDeviceLinkFlags(
   }
 
   this->AddVisibilityPresetFlags(linkFlags, target, "CUDA");
+  this->GetGlobalGenerator()->EncodeLiteral(linkFlags);
 
   std::vector<std::string> linkOpts;
   target->GetLinkOptions(linkOpts, config, "CUDA");
+  this->SetLinkScriptShell(this->GetGlobalGenerator()->GetUseLinkScript());
   // LINK_OPTIONS are escaped.
   this->AppendCompileOptions(linkFlags, linkOpts);
+  this->SetLinkScriptShell(false);
 }
 
 void cmLocalGenerator::GetTargetFlags(
@@ -1501,13 +1504,17 @@ void cmLocalGenerator::GetTargetFlags(
       }
 
       if (!sharedLibFlags.empty()) {
+        this->GetGlobalGenerator()->EncodeLiteral(sharedLibFlags);
         linkFlags.emplace_back(std::move(sharedLibFlags));
       }
 
       std::vector<BT<std::string>> linkOpts =
         target->GetLinkOptions(config, linkLanguage);
+      this->SetLinkScriptShell(this->GetGlobalGenerator()->GetUseLinkScript());
       // LINK_OPTIONS are escaped.
       this->AppendCompileOptions(linkFlags, linkOpts);
+      this->SetLinkScriptShell(false);
+
       if (pcli) {
         this->OutputLinkLibraries(pcli, linkLineComputer, linkLibs,
                                   frameworkPath, linkPath);
@@ -1581,13 +1588,16 @@ void cmLocalGenerator::GetTargetFlags(
       }
 
       if (!exeFlags.empty()) {
+        this->GetGlobalGenerator()->EncodeLiteral(exeFlags);
         linkFlags.emplace_back(std::move(exeFlags));
       }
 
       std::vector<BT<std::string>> linkOpts =
         target->GetLinkOptions(config, linkLanguage);
+      this->SetLinkScriptShell(this->GetGlobalGenerator()->GetUseLinkScript());
       // LINK_OPTIONS are escaped.
       this->AppendCompileOptions(linkFlags, linkOpts);
+      this->SetLinkScriptShell(false);
     } break;
     default:
       break;
@@ -1603,6 +1613,7 @@ void cmLocalGenerator::GetTargetFlags(
                                    config);
 
   if (!extraLinkFlags.empty()) {
+    this->GetGlobalGenerator()->EncodeLiteral(extraLinkFlags);
     linkFlags.emplace_back(std::move(extraLinkFlags));
   }
 }
diff --git a/Source/cmMakefileTargetGenerator.cxx b/Source/cmMakefileTargetGenerator.cxx
index 1dd48b3..5e3bf61 100644
--- a/Source/cmMakefileTargetGenerator.cxx
+++ b/Source/cmMakefileTargetGenerator.cxx
@@ -126,8 +126,11 @@ void cmMakefileTargetGenerator::GetDeviceLinkFlags(
   std::vector<std::string> linkOpts;
   this->GeneratorTarget->GetLinkOptions(linkOpts, this->GetConfigName(),
                                         linkLanguage);
+  this->LocalGenerator->SetLinkScriptShell(
+    this->GlobalGenerator->GetUseLinkScript());
   // LINK_OPTIONS are escaped.
   this->LocalGenerator->AppendCompileOptions(linkFlags, linkOpts);
+  this->LocalGenerator->SetLinkScriptShell(false);
 }
 
 void cmMakefileTargetGenerator::GetTargetLinkFlags(
@@ -144,8 +147,11 @@ void cmMakefileTargetGenerator::GetTargetLinkFlags(
   std::vector<std::string> opts;
   this->GeneratorTarget->GetLinkOptions(opts, this->GetConfigName(),
                                         linkLanguage);
+  this->LocalGenerator->SetLinkScriptShell(
+    this->GlobalGenerator->GetUseLinkScript());
   // LINK_OPTIONS are escaped.
   this->LocalGenerator->AppendCompileOptions(flags, opts);
+  this->LocalGenerator->SetLinkScriptShell(false);
 
   this->LocalGenerator->AppendPositionIndependentLinkerFlags(
     flags, this->GeneratorTarget, this->GetConfigName(), linkLanguage);
diff --git a/Source/cmNinjaNormalTargetGenerator.cxx b/Source/cmNinjaNormalTargetGenerator.cxx
index 063ca6b..a5280fb 100644
--- a/Source/cmNinjaNormalTargetGenerator.cxx
+++ b/Source/cmNinjaNormalTargetGenerator.cxx
@@ -952,8 +952,6 @@ void cmNinjaNormalTargetGenerator::WriteNvidiaDeviceLinkStatement(
 
   this->addPoolNinjaVariable("JOB_POOL_LINK", genTarget, vars);
 
-  vars["LINK_FLAGS"] = globalGen->EncodeLiteral(vars["LINK_FLAGS"]);
-
   vars["MANIFESTS"] = this->GetManifests(config);
 
   vars["LINK_PATH"] = frameworkPath + linkPath;
@@ -1271,8 +1269,6 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement(
     vars["LINK_FLAGS"], this->GetGeneratorTarget(),
     this->TargetLinkLanguage(config));
 
-  vars["LINK_FLAGS"] = globalGen->EncodeLiteral(vars["LINK_FLAGS"]);
-
   vars["MANIFESTS"] = this->GetManifests(config);
   vars["AIX_EXPORTS"] = this->GetAIXExports(config);
 
diff --git a/Tests/RunCMake/target_link_options/LINK_OPTIONS-dollar-option-check.cmake b/Tests/RunCMake/target_link_options/LINK_OPTIONS-dollar-option-check.cmake
new file mode 100644
index 0000000..0f897fe
--- /dev/null
+++ b/Tests/RunCMake/target_link_options/LINK_OPTIONS-dollar-option-check.cmake
@@ -0,0 +1,4 @@
+
+if (NOT actual_stdout MATCHES "BADFLAG_\\$dollar")
+  set (RunCMake_TEST_FAILED "Not found expected 'BADFLAG_$dollar'.")
+endif()
diff --git a/Tests/RunCMake/target_link_options/LINK_OPTIONS-dollar-option-result.txt b/Tests/RunCMake/target_link_options/LINK_OPTIONS-dollar-option-result.txt
new file mode 100644
index 0000000..8d98f9d
--- /dev/null
+++ b/Tests/RunCMake/target_link_options/LINK_OPTIONS-dollar-option-result.txt
@@ -0,0 +1 @@
+.*
diff --git a/Tests/RunCMake/target_link_options/LINK_OPTIONS.cmake b/Tests/RunCMake/target_link_options/LINK_OPTIONS.cmake
index bb04841..879151b 100644
--- a/Tests/RunCMake/target_link_options/LINK_OPTIONS.cmake
+++ b/Tests/RunCMake/target_link_options/LINK_OPTIONS.cmake
@@ -53,3 +53,7 @@ target_link_options(LinkOptions_mod PRIVATE $<$<CONFIG:Release>:${pre}BADFLAG_RE
 # executable with generator expression
 add_executable(LinkOptions_exe LinkOptionsExe.c)
 target_link_options(LinkOptions_exe PRIVATE $<$<CONFIG:Release>:${pre}BADFLAG_RELEASE${obj}>)
+
+# executable with dollar character
+add_executable(LinkOptions_dollar_exe LinkOptionsExe.c)
+target_link_options(LinkOptions_dollar_exe PRIVATE "${pre}BADFLAG_$dollar${obj}")
diff --git a/Tests/RunCMake/target_link_options/RunCMakeTest.cmake b/Tests/RunCMake/target_link_options/RunCMakeTest.cmake
index 1a29ecf..ff0c5a8 100644
--- a/Tests/RunCMake/target_link_options/RunCMakeTest.cmake
+++ b/Tests/RunCMake/target_link_options/RunCMakeTest.cmake
@@ -30,7 +30,7 @@ if (NOT CMAKE_C_COMPILER_ID STREQUAL "Intel")
   run_cmake_target(LINK_OPTIONS shared LinkOptions_shared --config Release)
   run_cmake_target(LINK_OPTIONS mod LinkOptions_mod --config Release)
   run_cmake_target(LINK_OPTIONS exe LinkOptions_exe --config Release)
-
+  run_cmake_target(LINK_OPTIONS dollar-option LinkOptions_dollar_exe --config Release)
 
   run_cmake(genex_LINK_LANGUAGE)
 
-- 
cgit v0.12