Skip to content

[clang] Fix-it hint for ++this -> ++*this when deref is modifiable #94159

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 13, 2024

Conversation

Rajveer100
Copy link
Member

Resolves #93066

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Jun 2, 2024
@llvmbot
Copy link
Member

llvmbot commented Jun 2, 2024

@llvm/pr-subscribers-clang

Author: Rajveer Singh Bharadwaj (Rajveer100)

Changes

Resolves #93066


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

3 Files Affected:

  • (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+3)
  • (modified) clang/lib/Sema/SemaExpr.cpp (+13-2)
  • (added) clang/test/Sema/debug-93066.cpp (+14)
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 270b0a1e01307..0f5445296e45f 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -8777,6 +8777,9 @@ def err_typecheck_incomplete_type_not_modifiable_lvalue : Error<
 def err_typecheck_lvalue_casts_not_supported : Error<
   "assignment to cast is illegal, lvalue casts are not supported">;
 
+def note_typecheck_expression_not_modifiable_lvalue : Note<
+  "dereference the pointer to modify">; 
+
 def err_typecheck_duplicate_vector_components_not_mlvalue : Error<
   "vector is not assignable (contains duplicate components)">;
 def err_block_decl_ref_not_modifiable_lvalue : Error<
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index ff9c5ead36dcf..39e7962fcb2a6 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -13587,10 +13587,21 @@ static bool CheckForModifiableLvalue(Expr *E, SourceLocation Loc, Sema &S) {
   SourceRange Assign;
   if (Loc != OrigLoc)
     Assign = SourceRange(OrigLoc, OrigLoc);
-  if (NeedType)
+  if (NeedType) {
     S.Diag(Loc, DiagID) << E->getType() << E->getSourceRange() << Assign;
-  else
+  } else {
+    ExprResult Deref;
+    unsigned FixitDiagID = 0;
+    {
+      Sema::TentativeAnalysisScope Trap(S);
+      Deref = S.ActOnUnaryOp(S.getCurScope(), Loc, tok::star, E);
+    }
     S.Diag(Loc, DiagID) << E->getSourceRange() << Assign;
+    if (Deref.isUsable() && Deref.get()->isModifiableLvalue(S.Context, &Loc) == Expr::MLV_Valid) {
+      FixitDiagID = diag::note_typecheck_expression_not_modifiable_lvalue;
+      S.Diag(Loc, FixitDiagID) << E->getSourceRange() << Assign;
+    }
+  }
   return true;
 }
 
diff --git a/clang/test/Sema/debug-93066.cpp b/clang/test/Sema/debug-93066.cpp
new file mode 100644
index 0000000000000..7abedcd0d4d80
--- /dev/null
+++ b/clang/test/Sema/debug-93066.cpp
@@ -0,0 +1,14 @@
+// RUN: %clang_cc1 -std=c++23 -fsyntax-only -verify %s
+
+struct S {
+  void f() {
+    ++this;
+    // expected-error@-1 {{expression is not assignable}}
+    // expected-note@-2 {{dereference the pointer to modify}}
+  }
+
+  void g() const {
+    ++this;
+    // expected-error@-1 {{expression is not assignable}}
+  }
+};

@Rajveer100
Copy link
Member Author

cc @Sirraide

@Rajveer100 Rajveer100 force-pushed the branch-for-issue-93066 branch 2 times, most recently from e637dc8 to 95ce40b Compare June 2, 2024 13:12
Copy link

github-actions bot commented Jun 2, 2024

✅ With the latest revision this PR passed the C/C++ code formatter.

Copy link
Member

@Sirraide Sirraide left a comment

Choose a reason for hiding this comment

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

In addition to what I’ve pointed out below, there are a few more things to be done:

  • This needs a release note (in clang/docs/ReleaseNotes.rst, probably under the ‘Improvements to Clang’s Diagnostics’ section).
  • I find it hard to believe that the test you added is the only thing affected by this; it’s very likely that there are other tests that are failing now because you also need to add the diagnostic there; please run the Clang test suite locally (e.g. ninja check-clang if you’re using Ninja as your generator) and fix any tests that this is probably breaking.
  • Some more tests, including tests involving templates, would also be a good idea.

@Sirraide Sirraide requested review from AaronBallman and cor3ntin June 3, 2024 04:08
@Rajveer100
Copy link
Member Author

Tests which fail currently:

Clang :: Sema/exprs.c
Clang :: Sema/va_arg_x86_32.c
Clang :: SemaObjCXX/sel-address.mm

Also, what do you think about the fix-it hint wording for errors such as assignment to cast is illegal, lvalue casts are not supported?

@Sirraide
Copy link
Member

Sirraide commented Jun 3, 2024

Also, what do you think about the fix-it hint wording for errors such as assignment to cast is illegal, lvalue casts are not supported?

Don’t remember what that was for off the top of my head, but just the same fix-it should be fine imo

@Rajveer100 Rajveer100 force-pushed the branch-for-issue-93066 branch from 95ce40b to 6dec67c Compare June 4, 2024 14:01
@Rajveer100 Rajveer100 requested a review from Sirraide June 4, 2024 14:01
@Rajveer100 Rajveer100 force-pushed the branch-for-issue-93066 branch from 6dec67c to 5092ffd Compare June 6, 2024 12:55
@Rajveer100
Copy link
Member Author

@Sirraide
Let me know if any further changes are needed. All tests should pass once CI finishes.

@Rajveer100 Rajveer100 force-pushed the branch-for-issue-93066 branch from 5092ffd to e38b387 Compare June 7, 2024 08:54
@Rajveer100 Rajveer100 requested a review from Sirraide June 7, 2024 12:48
Copy link
Member

@Sirraide Sirraide left a comment

Choose a reason for hiding this comment

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

This is already looking better.

(Also, for the record, this is just a me thing and not official policy or anything, but I’d find it easier if you didn’t force-push because then I’d have an easier time telling what’s changed in recent commits; everything is going to get squashed into a single commit on merge anyway, so individual commits to the pr don’t have to be perfect or anything.)

@Rajveer100 Rajveer100 force-pushed the branch-for-issue-93066 branch from e38b387 to 1a9ef88 Compare June 8, 2024 13:44
@Rajveer100
Copy link
Member Author

I have added a FIXME for now regarding the Obj-C part.

The function change is also done.

@Rajveer100 Rajveer100 requested a review from Sirraide June 8, 2024 18:03
@Rajveer100 Rajveer100 force-pushed the branch-for-issue-93066 branch from 1a9ef88 to 3b25887 Compare June 10, 2024 11:52
Copy link
Member

@Sirraide Sirraide left a comment

Choose a reason for hiding this comment

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

The implementation looks fine, but a few more tests involving templates would be nice to see what this does with dependent types, e.g.

template <typename T>
void f(T& t) {
    ++t;
}

void g() {
    int* a;
    int* const b = a;
    const int* const c = a;
    f(a); // Ok.
    f(b); // Error and fixit.
    f(c); // Error, but no fixit.
}

I’d expect an error to be reported at instantiation time only; I doubt that there will be any complications here, but more tests are never a bad idea.

@Rajveer100 Rajveer100 force-pushed the branch-for-issue-93066 branch from 3b25887 to 765176a Compare June 11, 2024 10:58
@Rajveer100 Rajveer100 requested a review from Sirraide June 11, 2024 11:00
Copy link
Member

@Sirraide Sirraide left a comment

Choose a reason for hiding this comment

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

Two last comments, but other than that this LGTM.

@Rajveer100 Rajveer100 force-pushed the branch-for-issue-93066 branch from 765176a to bf11465 Compare June 11, 2024 18:55
@Rajveer100
Copy link
Member Author

I have done the last few changes as well.

Regarding the force-push, I will try to do single commits and later squash them!

@Sirraide
Copy link
Member

I will try to do single commits and later squash them!

You don’t even need to squash them yourself; Github does that automatically because we use ‘squash and merge’.

@Rajveer100
Copy link
Member Author

@Sirraide
Can you land this for me?

@Sirraide
Copy link
Member

@Sirraide Can you land this for me?

Sure; looks like everything is fine.

@Sirraide Sirraide merged commit 9d7299f into llvm:main Jun 13, 2024
8 checks passed
Copy link
Collaborator

@zygoloid zygoloid left a comment

Choose a reason for hiding this comment

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

I have some significant concerns about the approach of this PR. It seems to be producing notes that in a lot of cases suggest doing something that doesn't make sense or won't compile, and tentatively building a *p expression is unsafe.

Can we revert and consider other approaches?

Comment on lines +13279 to +13282
{
Sema::TentativeAnalysisScope Trap(S);
Deref = S.ActOnUnaryOp(S.getCurScope(), Loc, tok::star, TE);
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

It's not really feasible to use a TentativeAnalysisScope for this. Here are three reasons:

  1. If ActOnUnaryOp produces an "immediate context" diagnostic (the common case), such as a warning, then the note you produce below will be attached to that diagnostic instead of to the original diagnostic, so your note will be suppressed.
  2. If ActOnUnaryOp triggers template instantiation, then TentativeAnalysisScope will not suppress diagnostics outside of the immediate context, potentially leading to additional spurious diagnostics being produced.
  3. If this function is used to attach a note to a warning / extension diagnostic rather than an error (which doesn't appear to be the case right now, but it seems plausible that someone would make such a change to CheckForModifiableLValue in the future), then the diagnostics produced by (2) would lead to rejecting a valid program.

Checking for an overloaded operator* seems overly complicated here, so I think the right thing to test here is just whether the expression has pointer type, and the pointee type is an object type (not void or a function type).

struct S {
void f() {
++this; // expected-error {{expression is not assignable}}
// expected-note@-1 {{add '*' to dereference it}}
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is a bad diagnostic change: adding the * doesn't help.

Copy link
Member

Choose a reason for hiding this comment

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

That’s true. Looking at this again, I’m candidly a bit confused that this seemed fine to me at the time...

}

void g() const {
++this; // expected-error {{expression is not assignable}}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Likewise.

@@ -65,8 +65,10 @@ void test4(void) {

void test5(int *X, float *P) {
(float*)X = P; // expected-error {{assignment to cast is illegal, lvalue casts are not supported}}
// expected-note@-1 {{add '*' to dereference it}}
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is a diagnostic regression.

#define FOO ((float*) X)
FOO = P; // expected-error {{assignment to cast is illegal, lvalue casts are not supported}}
// expected-note@-1 {{add '*' to dereference it}}
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is a diagnostic regression.

Comment on lines +13286 to +13289
S.Diag(E->getBeginLoc(),
diag::note_typecheck_add_deref_star_not_modifiable_lvalue)
<< E->getSourceRange()
<< FixItHint::CreateInsertion(E->getBeginLoc(), "*");
Copy link
Collaborator

Choose a reason for hiding this comment

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

We shouldn't be suggesting adding a * unless that would actually make sense in context. Eg, given:

const int *const p;
const int *const q;
p = q;

... we should not suggest a dereference -- the problem is clearly that p is const, and adding a * doesn't help.

I suspect we simply don't have enough information to do this from within CheckForModifiableLvalue.

Copy link
Member

Choose a reason for hiding this comment

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

I suspect we simply don't have enough information to do this from within CheckForModifiableLvalue.

Yeah, my idea was that by trying to build a * expression we might be able to figure out whether that makes sense in the context, whereas just looking for e.g. pointer types would cause too many false positives (at least that’s what I thought), but it might just be that you end up with too many false positives either way...

@Sirraide
Copy link
Member

Can we revert and consider other approaches?

Yeah, a lot of the points you’ve brought up make sense, and if you’re saying it’s a bad idea then I’ll trust you and revert this for now so we can think about this some more...

Also, I suppose part of the issue for me at least was that I wasn’t 100% sure about this approach either, but I also didn’t want to create even more work for other people than they already have by asking someone else to review this too... I’ll be a bit more careful with this in the future.

I also marked this as a good first issue because I thought it shouldn’t be that complicated to do this, but it seems I was wrong (this is also why I usually don’t like using that label...)

@Sirraide
Copy link
Member

Checking for an overloaded operator* seems overly complicated here, so I think the right thing to test here is just whether the expression has pointer type, and the pointee type is an object type (not void or a function type).

That’s also what I was going for initially, but I thought that doing just that might lead to too many false positives...

That said, considering all the issues that using TentativeAnalysisScope seems to come with, I suppose it’s not too surprising that it’s used very sparingly (most of the uses are apparently in Open MP–related code)

@zygoloid
Copy link
Collaborator

Perhaps we can start by supplying CheckForModifiableLvalue with extra information about why we're doing the check. The current diagnostic we get for incrementing a non-modifiable lvalue:

<source>:2:3: error: cannot assign to variable 'n' with const-qualified type 'const int'
    2 |   ++n;
      |   ^ ~

... is really bad -- this isn't an assignment, it's an increment. An enum of possible reasons why we're doing the check would let us significantly improve the diagnostic here.

@Sirraide
Copy link
Member

Having more context would probably help yeah. We should be able to improve that a bit even if the fix-it isn’t viable...

Sirraide added a commit that referenced this pull request Jun 18, 2024
…odifiable" (#95833)

Reverts #94159

@zygoloid had some concerns about the implementation of this, which make
sense to me, so I’d like to revert this for now so we can have more time
to think about how to best approach this (if at all...)
@Rajveer100
Copy link
Member Author

@Sirraide @zygoloid
Can I be of any help to improve this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Clang] Add a fix-it hint for ++this -> ++*this
5 participants