Skip to content

[C] Handle comma operator for implicit int->enum conversions #138752

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

Conversation

AaronBallman
Copy link
Collaborator

In C++, the type of an enumerator is the type of the enumeration, whereas in C, the type of the enumerator is 'int'. The type of a comma operator is the type of the right-hand operand, which means you can get an implicit conversion with this code in C but not in C++:

  enum E { Zero };
  enum E foo() {
    return ((void)0, Zero);
  }

We were previously incorrectly diagnosing this code as being incompatible with C++ because the type of the paren expression would be 'int' there, whereas in C++ the type is 'E'.

So now we handle the comma operator with special logic when analyzing implicit conversions in C. When analyzing the left-hand operand of a comma operator, we do not need to check for that operand causing an implicit conversion for the entire comma expression. So we only check for that case with the right-hand operand.

This addresses a concern brought up post-commit:
#137658 (comment)

In C++, the type of an enumerator is the type of the enumeration,
whereas in C, the type of the enumerator is 'int'. The type of a comma
operator is the type of the right-hand operand, which means you can get
an implicit conversion with this code in C but not in C++:

  enum E { Zero };
  enum E foo() {
    return ((void)0, Zero);
  }

We were previously incorrectly diagnosing this code as being
incompatible with C++ because the type of the paren expression would be
'int' there, whereas in C++ the type is 'E'.

So now we handle the comma operator with special logic when analyzing
implicit conversions in C. When analyzing the left-hand operand of a
comma operator, we do not need to check for that operand causing an
implicit conversion for the entire comma expression. So we only check
for that case with the right-hand operand.

This addresses a concern brought up post-commit:
llvm#137658 (comment)
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels May 6, 2025
@llvmbot
Copy link
Member

llvmbot commented May 6, 2025

@llvm/pr-subscribers-clang

Author: Aaron Ballman (AaronBallman)

Changes

In C++, the type of an enumerator is the type of the enumeration, whereas in C, the type of the enumerator is 'int'. The type of a comma operator is the type of the right-hand operand, which means you can get an implicit conversion with this code in C but not in C++:

  enum E { Zero };
  enum E foo() {
    return ((void)0, Zero);
  }

We were previously incorrectly diagnosing this code as being incompatible with C++ because the type of the paren expression would be 'int' there, whereas in C++ the type is 'E'.

So now we handle the comma operator with special logic when analyzing implicit conversions in C. When analyzing the left-hand operand of a comma operator, we do not need to check for that operand causing an implicit conversion for the entire comma expression. So we only check for that case with the right-hand operand.

This addresses a concern brought up post-commit:
#137658 (comment)


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

3 Files Affected:

  • (modified) clang/lib/Sema/SemaChecking.cpp (+27-1)
  • (modified) clang/test/Sema/implicit-cast.c (+6-1)
  • (modified) clang/test/Sema/implicit-int-enum-conversion.c (+22)
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index 7f45533713bae..5f7769f9758a6 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -11647,6 +11647,29 @@ static void DiagnoseFloatingImpCast(Sema &S, Expr *E, QualType T,
   }
 }
 
+static void CheckCommaOperand(Sema &S, Expr *E, QualType T, SourceLocation CC,
+                              bool Check) {
+  E = E->IgnoreParenImpCasts();
+  AnalyzeImplicitConversions(S, E, CC);
+
+  if (Check && E->getType() != T)
+    S.CheckImplicitConversion(E, T, CC);
+}
+
+/// Analyze the given comma operator. The basic idea behind the analysis is to
+/// analyze the left and right operands slightly differently. The left operand
+/// needs to check whether the operand itself has an implicit conversion, but
+/// not whether the left operand induces an implicit conversion for the entire
+/// comma expression itself. This is similar to how CheckConditionalOperand
+/// behaves; it's as-if the correct operand were directly used for the implicit
+/// conversion check.
+static void AnalyzeCommaOperator(Sema &S, BinaryOperator *E, QualType T) {
+  assert(E->isCommaOp() && "Must be a comma operator");
+
+  CheckCommaOperand(S, E->getLHS(), T, E->getOperatorLoc(), false);
+  CheckCommaOperand(S, E->getRHS(), T, E->getOperatorLoc(), true);
+}
+
 /// Analyze the given compound assignment for the possible losing of
 /// floating-point precision.
 static void AnalyzeCompoundAssignment(Sema &S, BinaryOperator *E) {
@@ -12464,7 +12487,7 @@ static void AnalyzeImplicitConversions(
           << OrigE->getSourceRange() << T->isBooleanType()
           << FixItHint::CreateReplacement(UO->getBeginLoc(), "!");
 
-  if (const auto *BO = dyn_cast<BinaryOperator>(SourceExpr))
+  if (auto *BO = dyn_cast<BinaryOperator>(SourceExpr))
     if ((BO->getOpcode() == BO_And || BO->getOpcode() == BO_Or) &&
         BO->getLHS()->isKnownToHaveBooleanValue() &&
         BO->getRHS()->isKnownToHaveBooleanValue() &&
@@ -12490,6 +12513,9 @@ static void AnalyzeImplicitConversions(
                    (BO->getOpcode() == BO_And ? "&&" : "||"));
         S.Diag(BO->getBeginLoc(), diag::note_cast_operand_to_int);
       }
+    } else if (BO->isCommaOp() && !S.getLangOpts().CPlusPlus) {
+      AnalyzeCommaOperator(S, BO, T);
+      return;
     }
 
   // For conditional operators, we analyze the arguments as if they
diff --git a/clang/test/Sema/implicit-cast.c b/clang/test/Sema/implicit-cast.c
index 088b1958d9b85..4700b7d37a855 100644
--- a/clang/test/Sema/implicit-cast.c
+++ b/clang/test/Sema/implicit-cast.c
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -fsyntax-only %s
+// RUN: %clang_cc1 -fsyntax-only -verify %s
 
 static char *test1(int cf) {
   return cf ? "abc" : 0;
@@ -6,3 +6,8 @@ static char *test1(int cf) {
 static char *test2(int cf) {
   return cf ? 0 : "abc";
 }
+
+int baz(void) {
+  int f;
+  return ((void)0, f = 1.4f); // expected-warning {{implicit conversion from 'float' to 'int' changes value from 1.4 to 1}}
+}
diff --git a/clang/test/Sema/implicit-int-enum-conversion.c b/clang/test/Sema/implicit-int-enum-conversion.c
index 13afb5d297aba..36717f36dd083 100644
--- a/clang/test/Sema/implicit-int-enum-conversion.c
+++ b/clang/test/Sema/implicit-int-enum-conversion.c
@@ -50,3 +50,25 @@ enum E1 quux(void) {
   return E2_Zero;       // expected-warning {{implicit conversion from enumeration type 'enum E2' to different enumeration type 'enum E1'}} \
                            cxx-error {{cannot initialize return object of type 'enum E1' with an rvalue of type 'E2'}}
 }
+
+enum E1 comma1(void) {
+  return ((void)0, E1_One);
+}
+
+enum E1 comma2(void) {
+  enum E1 x;
+  return
+    (x = 12,  // expected-warning {{implicit conversion from 'int' to enumeration type 'enum E1' is invalid in C++}} \
+                 cxx-error {{assigning to 'enum E1' from incompatible type 'int'}}
+    E1_One);
+}
+
+enum E1 comma3(void) {
+  enum E1 x;
+  return ((void)0, foo()); // Okay, no conversion in C++
+}
+
+enum E1 comma4(void) {
+  return ((void)1, 2); // expected-warning {{implicit conversion from 'int' to enumeration type 'enum E1' is invalid in C++}} \
+                          cxx-error {{cannot initialize return object of type 'enum E1' with an rvalue of type 'int'}}
+}

* Renamed a parameter to be more clear
* Inlined a simple function body into its sole call site
@AaronBallman AaronBallman requested a review from erichkeane May 7, 2025 10:50
Copy link
Collaborator

@erichkeane erichkeane left a comment

Choose a reason for hiding this comment

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

1 nit, else LGTM.

@AaronBallman AaronBallman merged commit b59ab70 into llvm:main May 7, 2025
6 of 9 checks passed
@AaronBallman AaronBallman deleted the aballman-implicit-enum-conv-fix branch May 7, 2025 14:05
@mikaelholmen
Copy link
Collaborator

Hi @AaronBallman

I see that if I build clang with ASAN with this patch and run the testcase
clang/test/C/C99/n590.c
it crashes and I see this

AddressSanitizer:DEADLYSIGNAL
=================================================================
==2063954==ERROR: AddressSanitizer: SEGV on unknown address 0xb5c8001f7e52 (pc 0x7fe2bd0e8baf bp 0x7fe2bd718370 sp 0x7fe2bd7181a0 T0)
==2063954==The signal is caused by a WRITE memory access.
    #0 0x7fe2bd0e8baf in raise (/lib64/libpthread.so.0+0x12baf) (BuildId: 1962602ac5dc3011b6d697b38b05ddc244197114)
    #1 0x5610e162f93e in SignalHandler(int, siginfo_t*, void*) /repo/llvm/build-all-bbisdk-asan/../lib/Support/Unix/Signals.inc
    #2 0x7fe2bd0e8d0f  (/lib64/libpthread.so.0+0x12d0f) (BuildId: 1962602ac5dc3011b6d697b38b05ddc244197114)
    #3 0x5610e888912a in AnalyzeImplicitConversions(clang::Sema&, clang::Expr*, clang::SourceLocation, bool) /repo/llvm/build-all-bbisdk-asan/../../clang/lib/Sema/SemaChecking.cpp:12638
    #4 0x5610e888a70b in CheckCommaOperand /repo/llvm/build-all-bbisdk-asan/../../clang/lib/Sema/SemaChecking.cpp:11653:3
    #5 0x5610e888a70b in AnalyzeImplicitConversions /repo/llvm/build-all-bbisdk-asan/../../clang/lib/Sema/SemaChecking.cpp:12510:7
    #6 0x5610e888a70b in AnalyzeImplicitConversions(clang::Sema&, clang::Expr*, clang::SourceLocation, bool) /repo/llvm/build-all-bbisdk-asan/../../clang/lib/Sema/SemaChecking.cpp:12642:5
    [...]
    #730 0x5610e888a70b in CheckCommaOperand /repo/llvm/build-all-bbisdk-asan/../../clang/lib/Sema/SemaChecking.cpp:11653:3
    #731 0x5610e888a70b in AnalyzeImplicitConversions /repo/llvm/build-all-bbisdk-asan/../../clang/lib/Sema/SemaChecking.cpp:12510:7
    #732 0x5610e888a70b in AnalyzeImplicitConversions(clang::Sema&, clang::Expr*, clang::SourceLocation, bool) /repo/llvm/build-all-bbisdk-asan/../../clang/lib/Sema/SemaChecking.cpp:12642:5

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV (/lib64/libpthread.so.0+0x12baf) (BuildId: 1962602ac5dc3011b6d697b38b05ddc244197114) in raise
==2063954==ABORTING

@AaronBallman
Copy link
Collaborator Author

AaronBallman commented May 12, 2025

Hi @AaronBallman

I see that if I build clang with ASAN with this patch and run the testcase clang/test/C/C99/n590.c it crashes and I see this

AddressSanitizer:DEADLYSIGNAL
=================================================================
==2063954==ERROR: AddressSanitizer: SEGV on unknown address 0xb5c8001f7e52 (pc 0x7fe2bd0e8baf bp 0x7fe2bd718370 sp 0x7fe2bd7181a0 T0)
==2063954==The signal is caused by a WRITE memory access.
    #0 0x7fe2bd0e8baf in raise (/lib64/libpthread.so.0+0x12baf) (BuildId: 1962602ac5dc3011b6d697b38b05ddc244197114)
    #1 0x5610e162f93e in SignalHandler(int, siginfo_t*, void*) /repo/llvm/build-all-bbisdk-asan/../lib/Support/Unix/Signals.inc
    #2 0x7fe2bd0e8d0f  (/lib64/libpthread.so.0+0x12d0f) (BuildId: 1962602ac5dc3011b6d697b38b05ddc244197114)
    #3 0x5610e888912a in AnalyzeImplicitConversions(clang::Sema&, clang::Expr*, clang::SourceLocation, bool) /repo/llvm/build-all-bbisdk-asan/../../clang/lib/Sema/SemaChecking.cpp:12638
    #4 0x5610e888a70b in CheckCommaOperand /repo/llvm/build-all-bbisdk-asan/../../clang/lib/Sema/SemaChecking.cpp:11653:3
    #5 0x5610e888a70b in AnalyzeImplicitConversions /repo/llvm/build-all-bbisdk-asan/../../clang/lib/Sema/SemaChecking.cpp:12510:7
    #6 0x5610e888a70b in AnalyzeImplicitConversions(clang::Sema&, clang::Expr*, clang::SourceLocation, bool) /repo/llvm/build-all-bbisdk-asan/../../clang/lib/Sema/SemaChecking.cpp:12642:5
    [...]
    #730 0x5610e888a70b in CheckCommaOperand /repo/llvm/build-all-bbisdk-asan/../../clang/lib/Sema/SemaChecking.cpp:11653:3
    #731 0x5610e888a70b in AnalyzeImplicitConversions /repo/llvm/build-all-bbisdk-asan/../../clang/lib/Sema/SemaChecking.cpp:12510:7
    #732 0x5610e888a70b in AnalyzeImplicitConversions(clang::Sema&, clang::Expr*, clang::SourceLocation, bool) /repo/llvm/build-all-bbisdk-asan/../../clang/lib/Sema/SemaChecking.cpp:12642:5

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV (/lib64/libpthread.so.0+0x12baf) (BuildId: 1962602ac5dc3011b6d697b38b05ddc244197114) in raise
==2063954==ABORTING

Thank you for letting me know!

I'm trying to reproduce the issue on Windows with MSVC + ASAN and I'm not getting any failures. The stack trace looks valid, but also implies that E = E->IgnoreParenImpCasts(); is somehow resulting in an invalid pointer being passed to AnalyzeImplicitConversions() which is a surprise; I would expect E-> to be an issue if there was an invalid pointer involved with the changes in this PR. I may need a bit of help on this one if I can't reproduce locally.

@erichkeane
Copy link
Collaborator

I've run it as well this way:

 /local/home/ekeane/llvm-project/build/bin/clang -cc1 -internal-isystem /local/home/ekeane/llvm-project/build/lib/clang/21/include -nostdsysteminc -verify -Wno-unused -I /local/home/ekeane/llvm-project/clang/test/C/C99/Inputs /local/home/ekeane/llvm-project/clang/test/C/C99/n590.c -fsanitize=address

However, I'm not reproducing any problems with that. @mikaelholmen : Are there any other repro steps we have to do to enable this?

@mikaelholmen
Copy link
Collaborator

@AaronBallman @erichkeane
Apparently we used a clang version from July 2023 to compile clang when the testcase failed.

I made another try with a later build now and then I don't see the failure anymore so perhaps something has been fixed. Or it just went hiding but then I suppose it will popup at some point again.

Sorry if I sent you off on a wild goose chase. :(

@AaronBallman
Copy link
Collaborator Author

@AaronBallman @erichkeane Apparently we used a clang version from July 2023 to compile clang when the testcase failed.

I made another try with a later build now and then I don't see the failure anymore so perhaps something has been fixed. Or it just went hiding but then I suppose it will popup at some point again.

Sorry if I sent you off on a wild goose chase. :(

No worries, I'm glad there's not an issue after all. :-)

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.

4 participants