Skip to content

Conversation

@carlosgalvezp
Copy link
Contributor

…-literal

Fixes #133618

@llvmbot
Copy link
Member

llvmbot commented Mar 30, 2025

@llvm/pr-subscribers-clang-tools-extra

Author: Carlos Galvez (carlosgalvezp)

Changes

…-literal

Fixes #133618


Full diff: https://github.com/llvm/llvm-project/pull/133636.diff

3 Files Affected:

  • (modified) clang-tools-extra/clang-tidy/modernize/RawStringLiteralCheck.cpp (+6-5)
  • (modified) clang-tools-extra/docs/ReleaseNotes.rst (+4)
  • (modified) clang-tools-extra/test/clang-tidy/checkers/modernize/raw-string-literal.cpp (+2-1)
diff --git a/clang-tools-extra/clang-tidy/modernize/RawStringLiteralCheck.cpp b/clang-tools-extra/clang-tidy/modernize/RawStringLiteralCheck.cpp
index 24674a407cb36..1be0d6cc23935 100644
--- a/clang-tools-extra/clang-tidy/modernize/RawStringLiteralCheck.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/RawStringLiteralCheck.cpp
@@ -58,7 +58,7 @@ bool containsEscapedCharacters(const MatchFinder::MatchResult &Result,
       *Result.SourceManager, Result.Context->getLangOpts());
   StringRef Text = Lexer::getSourceText(CharRange, *Result.SourceManager,
                                         Result.Context->getLangOpts());
-  if (Text.empty() || isRawStringLiteral(Text))
+  if (Text.empty() || !Text.contains('"') || isRawStringLiteral(Text))
     return false;
 
   return containsEscapes(Text, R"('\"?x01)");
@@ -156,14 +156,14 @@ static bool compareStringLength(StringRef Replacement,
                                 const SourceManager &SM,
                                 const LangOptions &LangOpts) {
   return Replacement.size() <=
-         Lexer::MeasureTokenLength(Literal->getBeginLoc(), SM, LangOpts);
+         Lexer::MeasureTokenLength(SM.getSpellingLoc(Literal->getBeginLoc()), SM, LangOpts);
 }
 
 void RawStringLiteralCheck::check(const MatchFinder::MatchResult &Result) {
   const auto *Literal = Result.Nodes.getNodeAs<StringLiteral>("lit");
-  if (Literal->getBeginLoc().isMacroID())
-    return;
   const SourceManager &SM = *Result.SourceManager;
+  if (SM.getSpellingLoc(Literal->getBeginLoc()).isMacroID())
+    return;
   const LangOptions &LangOpts = getLangOpts();
   if (containsEscapedCharacters(Result, Literal, DisallowedChars)) {
     const std::string Replacement =
@@ -172,7 +172,8 @@ void RawStringLiteralCheck::check(const MatchFinder::MatchResult &Result) {
         compareStringLength(Replacement, Literal, SM, LangOpts)) {
       diag(Literal->getBeginLoc(),
            "escaped string literal can be written as a raw string literal")
-          << FixItHint::CreateReplacement(Literal->getSourceRange(),
+          << FixItHint::CreateReplacement(SourceRange(SM.getSpellingLoc(Literal->getBeginLoc()),
+                                                      SM.getSpellingLoc(Literal->getEndLoc())),
                                           Replacement);
     }
   }
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 6cb8d572d3a78..c7c5ac75986be 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -181,6 +181,10 @@ Changes in existing checks
   <clang-tidy/checks/modernize/use-std-numbers>` check to support math
   functions of different precisions.
 
+- Improved :doc:`modernize-raw-string-literal
+  <clang-tidy/checks/modernize/raw-string-literal>` check to detect string
+  literals passed into macros.
+
 - Improved :doc:`performance-move-const-arg
   <clang-tidy/checks/performance/move-const-arg>` check by fixing false
   negatives on ternary operators calling ``std::move``.
diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/raw-string-literal.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/raw-string-literal.cpp
index 5856b8882574a..fc4e966e92e07 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/modernize/raw-string-literal.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/raw-string-literal.cpp
@@ -105,7 +105,8 @@ char const *const StringizedMacroArgument = HAT(foo\\bar);
 
 #define SUBST(lit_) lit_
 char const *const MacroArgument = SUBST("foo\\bar");
-// FIXME: We should be able to replace this string literal macro argument
+// CHECK-MESSAGES: :[[@LINE-1]]:41: warning: {{.*}} can be written as a raw string literal
+// CHECK-FIXES: {{^}}char const *const MacroArgument = SUBST(R"(foo\bar)");{{$}}
 
 template <typename T>
 void fn(char const *const Arg) {

@llvmbot
Copy link
Member

llvmbot commented Mar 30, 2025

@llvm/pr-subscribers-clang-tidy

Author: Carlos Galvez (carlosgalvezp)

Changes

…-literal

Fixes #133618


Full diff: https://github.com/llvm/llvm-project/pull/133636.diff

3 Files Affected:

  • (modified) clang-tools-extra/clang-tidy/modernize/RawStringLiteralCheck.cpp (+6-5)
  • (modified) clang-tools-extra/docs/ReleaseNotes.rst (+4)
  • (modified) clang-tools-extra/test/clang-tidy/checkers/modernize/raw-string-literal.cpp (+2-1)
diff --git a/clang-tools-extra/clang-tidy/modernize/RawStringLiteralCheck.cpp b/clang-tools-extra/clang-tidy/modernize/RawStringLiteralCheck.cpp
index 24674a407cb36..1be0d6cc23935 100644
--- a/clang-tools-extra/clang-tidy/modernize/RawStringLiteralCheck.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/RawStringLiteralCheck.cpp
@@ -58,7 +58,7 @@ bool containsEscapedCharacters(const MatchFinder::MatchResult &Result,
       *Result.SourceManager, Result.Context->getLangOpts());
   StringRef Text = Lexer::getSourceText(CharRange, *Result.SourceManager,
                                         Result.Context->getLangOpts());
-  if (Text.empty() || isRawStringLiteral(Text))
+  if (Text.empty() || !Text.contains('"') || isRawStringLiteral(Text))
     return false;
 
   return containsEscapes(Text, R"('\"?x01)");
@@ -156,14 +156,14 @@ static bool compareStringLength(StringRef Replacement,
                                 const SourceManager &SM,
                                 const LangOptions &LangOpts) {
   return Replacement.size() <=
-         Lexer::MeasureTokenLength(Literal->getBeginLoc(), SM, LangOpts);
+         Lexer::MeasureTokenLength(SM.getSpellingLoc(Literal->getBeginLoc()), SM, LangOpts);
 }
 
 void RawStringLiteralCheck::check(const MatchFinder::MatchResult &Result) {
   const auto *Literal = Result.Nodes.getNodeAs<StringLiteral>("lit");
-  if (Literal->getBeginLoc().isMacroID())
-    return;
   const SourceManager &SM = *Result.SourceManager;
+  if (SM.getSpellingLoc(Literal->getBeginLoc()).isMacroID())
+    return;
   const LangOptions &LangOpts = getLangOpts();
   if (containsEscapedCharacters(Result, Literal, DisallowedChars)) {
     const std::string Replacement =
@@ -172,7 +172,8 @@ void RawStringLiteralCheck::check(const MatchFinder::MatchResult &Result) {
         compareStringLength(Replacement, Literal, SM, LangOpts)) {
       diag(Literal->getBeginLoc(),
            "escaped string literal can be written as a raw string literal")
-          << FixItHint::CreateReplacement(Literal->getSourceRange(),
+          << FixItHint::CreateReplacement(SourceRange(SM.getSpellingLoc(Literal->getBeginLoc()),
+                                                      SM.getSpellingLoc(Literal->getEndLoc())),
                                           Replacement);
     }
   }
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 6cb8d572d3a78..c7c5ac75986be 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -181,6 +181,10 @@ Changes in existing checks
   <clang-tidy/checks/modernize/use-std-numbers>` check to support math
   functions of different precisions.
 
+- Improved :doc:`modernize-raw-string-literal
+  <clang-tidy/checks/modernize/raw-string-literal>` check to detect string
+  literals passed into macros.
+
 - Improved :doc:`performance-move-const-arg
   <clang-tidy/checks/performance/move-const-arg>` check by fixing false
   negatives on ternary operators calling ``std::move``.
diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/raw-string-literal.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/raw-string-literal.cpp
index 5856b8882574a..fc4e966e92e07 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/modernize/raw-string-literal.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/raw-string-literal.cpp
@@ -105,7 +105,8 @@ char const *const StringizedMacroArgument = HAT(foo\\bar);
 
 #define SUBST(lit_) lit_
 char const *const MacroArgument = SUBST("foo\\bar");
-// FIXME: We should be able to replace this string literal macro argument
+// CHECK-MESSAGES: :[[@LINE-1]]:41: warning: {{.*}} can be written as a raw string literal
+// CHECK-FIXES: {{^}}char const *const MacroArgument = SUBST(R"(foo\bar)");{{$}}
 
 template <typename T>
 void fn(char const *const Arg) {

@github-actions
Copy link

⚠️ C/C++ code formatter, clang-format found issues in your code. ⚠️

You can test this locally with the following command:
git-clang-format --diff HEAD~1 HEAD --extensions cpp -- clang-tools-extra/clang-tidy/modernize/RawStringLiteralCheck.cpp clang-tools-extra/test/clang-tidy/checkers/modernize/raw-string-literal.cpp
View the diff from clang-format here.
diff --git a/clang-tools-extra/clang-tidy/modernize/RawStringLiteralCheck.cpp b/clang-tools-extra/clang-tidy/modernize/RawStringLiteralCheck.cpp
index 1be0d6cc2..b7fad9193 100644
--- a/clang-tools-extra/clang-tidy/modernize/RawStringLiteralCheck.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/RawStringLiteralCheck.cpp
@@ -156,7 +156,8 @@ static bool compareStringLength(StringRef Replacement,
                                 const SourceManager &SM,
                                 const LangOptions &LangOpts) {
   return Replacement.size() <=
-         Lexer::MeasureTokenLength(SM.getSpellingLoc(Literal->getBeginLoc()), SM, LangOpts);
+         Lexer::MeasureTokenLength(SM.getSpellingLoc(Literal->getBeginLoc()),
+                                   SM, LangOpts);
 }
 
 void RawStringLiteralCheck::check(const MatchFinder::MatchResult &Result) {
@@ -172,9 +173,10 @@ void RawStringLiteralCheck::check(const MatchFinder::MatchResult &Result) {
         compareStringLength(Replacement, Literal, SM, LangOpts)) {
       diag(Literal->getBeginLoc(),
            "escaped string literal can be written as a raw string literal")
-          << FixItHint::CreateReplacement(SourceRange(SM.getSpellingLoc(Literal->getBeginLoc()),
-                                                      SM.getSpellingLoc(Literal->getEndLoc())),
-                                          Replacement);
+          << FixItHint::CreateReplacement(
+                 SourceRange(SM.getSpellingLoc(Literal->getBeginLoc()),
+                             SM.getSpellingLoc(Literal->getEndLoc())),
+                 Replacement);
     }
   }
 }

<clang-tidy/checks/modernize/use-std-numbers>` check to support math
functions of different precisions.

- Improved :doc:`modernize-raw-string-literal
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please keep alphabetical order (by check name) in this list.

#define HAT(rabbit_) #rabbit_ "foo\\bar"
char const *const StringizedMacroArgument = HAT(foo\\bar);

#define SUBST(lit_) lit_
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about concat string in macro?
e.g.

#define SUBST(a,b) a#b
char const *const MacroArgument = SUBST("foo\\bar","cd");

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, this doesn't work. It wants to create a replacement "R\"(foo\\bar\\baz\\bazz\\foo\"cd\")\"" which is non sense.

Most likely macros should be handled in a PPCallback instead, thus it's better to drop this PR and implement this feature there instead.

@carlosgalvezp
Copy link
Contributor Author

This approach does not scale for macros, it should rather be implemented in a PPCallback instead.

StringRef Text = Lexer::getSourceText(CharRange, *Result.SourceManager,
Result.Context->getLangOpts());
if (Text.empty() || isRawStringLiteral(Text))
if (Text.empty() || !Text.contains('"') || isRawStringLiteral(Text))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this necessary?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It picked up on this:

#define TRICK(arg_) #arg_
char const *const MacroBody = TRICK(foo\\bar);

Since foo\\bar does not have quotes, the assertion inside isRawStringLiteral failed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So... shouldn't the check include something that says this is only when the text is a macro argument to #, or am I missing something?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand the question. Do you mean that we should update the user-facing documentation of the check? Or that we should modify the implementation?

Copy link
Contributor

@LegalizeAdulthood LegalizeAdulthood Apr 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean this is a blanket-wide check to see if the text contains a double quote character ("), but isn't contextualized to the specific case that matters -- when the text is supplied as a macro argument to the preprocessor string-ize operator #. I don't see how adding this condition applies only to that situation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that won't work. Do note this PR is abandoned :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I just wanted to understand the change as written to make sure I wasn't missing something :). FYI, I am the one who contributed this check and a weakness of several early checks I wrote is that they don't play well with macros and this was one of them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[clang-tidy][false-negative] modernize-raw-string-literal does not check function-like macro parameters

5 participants