Skip to content

Conversation

@SherAndrei
Copy link

Without these changes a clash appears between a Tag, which is bound to enclosing match, and a Tag, which is associated with first Case of applyFirst in rewriteDescendands.

We fix this by making sure that associated Tags are unique and deterministic as they are intend to be.

@github-actions
Copy link

Thank you for submitting a Pull Request (PR) to the LLVM Project!

This PR will be automatically labeled and the relevant teams will be notified.

If you wish to, you can add reviewers by using the "Reviewers" section on this page.

If this is not working for you, it is probably because you do not have write permissions for the repository. In which case you can instead tag reviewers by name in a comment by using @ followed by their GitHub username.

If you have received no comments on your PR for a week, you can request a review by "ping"ing the PR by adding a comment “Ping”. The common courtesy "ping" rate is once a week. Please remember that you are asking for valuable time from other developers.

If you have further questions, they may be answered by the LLVM GitHub User Guide.

You can also ask questions in a comment on this PR, on the LLVM Discord or on the forums.

@llvmbot llvmbot added the clang Clang issues not falling into any other category label Nov 26, 2024
@llvmbot
Copy link
Member

llvmbot commented Nov 26, 2024

@llvm/pr-subscribers-clang

Author: None (SherAndrei)

Changes

Without these changes a clash appears between a Tag, which is bound to enclosing match, and a Tag, which is associated with first Case of applyFirst in rewriteDescendands.

We fix this by making sure that associated Tags are unique and deterministic as they are intend to be.


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

2 Files Affected:

  • (modified) clang/lib/Tooling/Transformer/RewriteRule.cpp (+4-2)
  • (modified) clang/unittests/Tooling/TransformerTest.cpp (+43)
diff --git a/clang/lib/Tooling/Transformer/RewriteRule.cpp b/clang/lib/Tooling/Transformer/RewriteRule.cpp
index eefddc34940487..196249260ec8b4 100644
--- a/clang/lib/Tooling/Transformer/RewriteRule.cpp
+++ b/clang/lib/Tooling/Transformer/RewriteRule.cpp
@@ -382,9 +382,10 @@ static std::vector<DynTypedMatcher> taggedMatchers(
   std::vector<DynTypedMatcher> Matchers;
   Matchers.reserve(Cases.size());
   for (const auto &Case : Cases) {
-    std::string Tag = (TagBase + Twine(Case.first)).str();
     // HACK: Many matchers are not bindable, so ensure that tryBind will work.
     DynTypedMatcher BoundMatcher(Case.second.Matcher);
+    const auto [_, ID] = BoundMatcher.getID();
+    std::string Tag = (TagBase + Twine(ID)).str();
     BoundMatcher.setAllowBind(true);
     auto M = *BoundMatcher.tryBind(Tag);
     Matchers.push_back(!M.getTraversalKind()
@@ -469,7 +470,8 @@ size_t transformer::detail::findSelectedCase(const MatchResult &Result,
 
   auto &NodesMap = Result.Nodes.getMap();
   for (size_t i = 0, N = Rule.Cases.size(); i < N; ++i) {
-    std::string Tag = ("Tag" + Twine(i)).str();
+    const auto [_, ID] = Rule.Cases[i].Matcher.getID();
+    std::string Tag = ("Tag" + Twine(ID)).str();
     if (NodesMap.find(Tag) != NodesMap.end())
       return i;
   }
diff --git a/clang/unittests/Tooling/TransformerTest.cpp b/clang/unittests/Tooling/TransformerTest.cpp
index cbd84ab794a49a..0404c81862468f 100644
--- a/clang/unittests/Tooling/TransformerTest.cpp
+++ b/clang/unittests/Tooling/TransformerTest.cpp
@@ -605,6 +605,49 @@ TEST_F(TransformerTest, RewriteDescendantsReferToParentBinding) {
            Input, Expected);
 }
 
+TEST_F(TransformerTest, RewriteDescendantsApplyFirstOrderedRuleUnrelated) {
+  std::string Input = "int f(int x) { int y = x; return x; }";
+  std::string Expected = "int f(int x) { char y = 3; return 3; }";
+  auto IntToChar = makeRule(typeLoc(loc(qualType(isInteger(), builtinType()))),
+                            changeTo(cat("char")));
+  auto InlineX =
+      makeRule(declRefExpr(to(varDecl(hasName("x")))), changeTo(cat("3")));
+  testRule(
+      makeRule(functionDecl(hasName("f"), hasBody(stmt().bind("body"))),
+               rewriteDescendants("body", applyFirst({InlineX, IntToChar}))),
+      Input, Expected);
+}
+
+TEST_F(TransformerTest, RewriteDescendantsApplyFirstOrderedRuleRelated) {
+  std::string Input = "int f(int x) { int y = x; return x; }";
+  std::string Expected = "int f(int x) { int y = 3; return y; }";
+  auto ReturnY = makeRule(
+      traverse(TK_IgnoreUnlessSpelledInSource,
+               declRefExpr(to(varDecl(hasName("x"))), hasParent(returnStmt()))),
+      changeTo(cat("y")));
+  auto InlineX = makeRule(traverse(TK_IgnoreUnlessSpelledInSource,
+                                   declRefExpr(to(varDecl(hasName("x"))))),
+                          changeTo(cat("3")));
+  testRule(makeRule(functionDecl(hasName("f"), hasBody(stmt().bind("body"))),
+                    rewriteDescendants("body", applyFirst({ReturnY, InlineX}))),
+           Input, Expected);
+}
+
+TEST_F(TransformerTest, RewriteDescendantsApplyFirstOrderedRuleRelatedSwapped) {
+  std::string Input = "int f(int x) { int y = x; return x; }";
+  std::string Expected = "int f(int x) { int y = 3; return 3; }";
+  auto ReturnY = makeRule(
+      traverse(TK_IgnoreUnlessSpelledInSource,
+               declRefExpr(to(varDecl(hasName("x"))), hasParent(returnStmt()))),
+      changeTo(cat("y")));
+  auto InlineX = makeRule(traverse(TK_IgnoreUnlessSpelledInSource,
+                                   declRefExpr(to(varDecl(hasName("x"))))),
+                          changeTo(cat("3")));
+  testRule(makeRule(functionDecl(hasName("f"), hasBody(stmt().bind("body"))),
+                    rewriteDescendants("body", applyFirst({InlineX, ReturnY}))),
+           Input, Expected);
+}
+
 TEST_F(TransformerTest, RewriteDescendantsUnboundNode) {
   std::string Input =
       "int f(int x) { int y = x; { int z = x * x; } return x; }";

@SherAndrei
Copy link
Author

SherAndrei commented Nov 26, 2024

@ymand, can you please review?

Without these changes a clash appears between a Tag, which is
bound to enclosing match, and a Tag, which is associated with first
Case of applyFirst in rewriteDescendands.

We fix this by making sure that associated Tags are unique and
deterministic as they are intend to be.
@SherAndrei SherAndrei force-pushed the rewrite-descendantds-apply-first branch from 9634a78 to 5eea966 Compare December 1, 2024 18:45
@SherAndrei SherAndrei changed the title Allow usage of applyFirst with rewriteDescendants [clang][transformer] Allow usage of applyFirst with rewriteDescendants Dec 1, 2024
@SherAndrei
Copy link
Author

@ymand, could you please take a look?

@SherAndrei
Copy link
Author

@5chmidti, @carlosgalvezp, @PiotrZSL, can anyone please review suggested changes or maybe help find who can additionally review them? The author of the code under question -- @ymand, is absent for over a week

@ymand
Copy link
Collaborator

ymand commented Dec 10, 2024

@ymand, could you please take a look?

Yes -- sorry, missed the notification. I'll try to review within 24h.

@ymand ymand self-requested a review December 10, 2024 14:37
@ymand
Copy link
Collaborator

ymand commented Dec 13, 2024

Without these changes a clash appears between a Tag, which is bound to enclosing match, and a Tag, which is associated with first Case of applyFirst in rewriteDescendands.

Can you expand on your concern about the clash? Your approach of using the matcher ID seems like an improvement, but I'm not clear on what exactly we're trying to avoid.

@SherAndrei
Copy link
Author

SherAndrei commented Dec 14, 2024

Can you expand on your concern about the clash?

The clash is happening in NodesMap. See,

  1. the matcher which is associated with rewriteDescendants gets "Tag0",
  2. tags for matchers from applyfirst are shifted, they get "Tag1", ...
  3. that is why transformer::detail::findSelectedCase selects incorrect Tag from NodesMap (this also leads to error "ID not bound", for expected bound ID from other matcher)

Have I made anything clearer?
Do you want for me to update the description of the commit?

@ymand
Copy link
Collaborator

ymand commented Dec 18, 2024

Can you expand on your concern about the clash?

The clash is happening in NodesMap. See,

  1. the matcher which is associated with rewriteDescendants gets "Tag0",
  2. tags for matchers from applyfirst are shifted, they get "Tag1", ...
  3. that is why transformer::detail::findSelectedCase selects incorrect Tag from NodesMap (this also leads to error "ID not bound", for expected bound ID from other matcher)

Have I made anything clearer? Do you want for me to update the description of the commit?

Thanks for the explanation (and for your patience). Yes, this would be good to specify in the description. That said, I'm still a little unclear:

  1. Will any of the new tests trigger the bug?
  2. Can you clarify where the matchers associated with the nested rule will be shifted? As far as I can tell, the original match uses Tag0 but happens in a separate context (that is, MatchFinder) than the nested match so I don't see how they are interacting.

@SherAndrei
Copy link
Author

SherAndrei commented Dec 25, 2024

Will any of the new tests trigger the bug?

Yes, two of them do fail if no suggested changes are present. Feel free to reproduce it yourself.

./tools/clang/unittests/Tooling/ToolingTests --gtest_filter=*Transform*RewriteDescendantsApplyFirst*

yields

Note: Google Test filter = *Transform*RewriteDescendantsApplyFirst*
[==========] Running 3 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 3 tests from TransformerTest
[ RUN      ] TransformerTest.RewriteDescendantsApplyFirstOrderedRuleUnrelated
/root/clang-llvm/llvm-project/clang/unittests/Tooling/TransformerTest.cpp:94: Failure
Expected equality of these values:
 format(Expected)
   Which is: "int f(int x) {\n  char y = 3;\n  return 3;\n}"
 format(Actual)
   Which is: "int f(int x) {\n  3 y = 3;\n  return 3;\n}"
With diff:
@@ -1,4 +1,4 @@
int f(int x) {
-  char y = 3;
+  3 y = 3;
  return 3;
}


[  FAILED  ] TransformerTest.RewriteDescendantsApplyFirstOrderedRuleUnrelated (17 ms)
[ RUN      ] TransformerTest.RewriteDescendantsApplyFirstOrderedRuleRelated
/root/clang-llvm/llvm-project/clang/unittests/Tooling/TransformerTest.cpp:94: Failure
Expected equality of these values:
 format(Expected)
   Which is: "int f(int x) {\n  int y = 3;\n  return y;\n}"
 format(Actual)
   Which is: "int f(int x) {\n  int y = y;\n  return y;\n}"
With diff:
@@ -1,4 +1,4 @@
int f(int x) {
-  int y = 3;
+  int y = y;
  return y;
}


[  FAILED  ] TransformerTest.RewriteDescendantsApplyFirstOrderedRuleRelated (14 ms)
[ RUN      ] TransformerTest.RewriteDescendantsApplyFirstOrderedRuleRelatedSwapped
[       OK ] TransformerTest.RewriteDescendantsApplyFirstOrderedRuleRelatedSwapped (13 ms)
[----------] 3 tests from TransformerTest (45 ms total)

[----------] Global test environment tear-down
[==========] 3 tests from 1 test suite ran. (45 ms total)
[  PASSED  ] 1 test.
[  FAILED  ] 2 tests, listed below:
[  FAILED  ] TransformerTest.RewriteDescendantsApplyFirstOrderedRuleUnrelated
[  FAILED  ] TransformerTest.RewriteDescendantsApplyFirstOrderedRuleRelated

2 FAILED TESTS

Can you clarify where the matchers associated with the nested rule will be shifted? As far as I can tell, the original match uses Tag0 but happens in a separate context (that is, MatchFinder) than the nested match so I don't see how they are interacting.

Yes, indeed the original match happens in separate context, but all contexts share one NodesMap,
which values are overriden with a consecutive match ('clash'). Let's consider test
RewriteDescendantsApplyFirstOrderedRuleUnrelated before suggested changes:

  1. Transformer::registerMatchers calls to taggedMatchers: FunctionDecl matcher, we'll call it FD, receives "Tag0",
  2. then ApplyRuleCallback::registerMatchers calls to taggedMatchers: first DeclRefExpr matcher, let's call it DRE1, receives "Tag0", second DeclRefExpr matcher -- DRE2 -- receives "Tag1",
  3. when DRE1 matches, ApplyRuleCallback::run calls to findSelectedCase: it successfully finds "Tag0" which is bound to FD and not to expected DRE1.

Now let's consider same test with suggested changes

  1. Transformer::registerMatchers binds FD to "Tag93825274205568",
  2. ApplyRuleCallback::registerMatchers binds DRE1 to "Tag93825274195136", DRE2 to "Tag93825274196288",
  3. when DRE1 matches, findSelectedCase finds expected DRE1 by searching for "Tag93825274195136"

Sorry for misleading you by using the word 'shifted' here. Nothing is shifted here really. I do struggle to update description of the commit. What do you think about next description of the commit:

[clang][transformer] Allow usage of applyFirst with rewriteDescendants

Previously, `taggedMatchers` could apply same tag to two different matchers (here, for enclosing matcher and for first from `applyFirst`). 
We fix this by making sure that associated Tags are unique and deterministic as they are intended to be.

@ymand
Copy link
Collaborator

ymand commented Dec 30, 2024

Yes, indeed the original match happens in separate context, but all contexts share one NodesMap, which values are overriden with a consecutive match ('clash').

I see -- I'd missed the reuse of the MatchResult. Now that I understand the problem, I don't think that you're suggestion will fix it. The problem is that the IDs of the matchers are unique between different matchers, but not necessarily unique within a rule, because you can trivially reuse a matcher twice within the rule. So, we need a different source of uniqueness.

I don't have a quick fix offhand -- I think we'd probably want to package the ID with the rule case and then use a unique-ID generator (e.g. a global int variable) when we create the cases. But, in the meantime, if you want to unblock your progress, you could combine the position-based ID with the matcher-based ID, which would significantly decrease the likelihood of conflict. But, if you do that, please include a simple test that demonstrates the problem.

thanks!

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

Labels

clang Clang issues not falling into any other category

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants