Skip to content
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

Fix: Prevent ICE on final switch forward referencing its enum #21001

Open
wants to merge 5 commits into
base: master
Choose a base branch
from

Conversation

abulgit
Copy link
Contributor

@abulgit abulgit commented Mar 15, 2025

Closes: #20867
Fix Details-

  1. First I Added a check to detect when a final switch statement is used within an enum's own definition
  2. Another secondary check to detect incomplete enum member values during the missing members check

It now properly detects and reports the circular reference with the error message: "cannot use final switch on enum %s while it is being defined".

Testing-
I added a testcase to verify the fix: fail_compilation/fix20867.d

This test includes:
Test Case 1: The exact scenario from the GitHub issue
Test Case 2: A variation with multiple enum members
Test Case 3: A regular use of final switch (should compile normally)
Test Case 4: A nested circular reference case

All test cases Work Fine.

@dlang-bot
Copy link
Contributor

Thanks for your pull request and interest in making D better, @abulgit! We are looking forward to reviewing it, and you should be hearing from a maintainer soon.
Please verify that your PR follows this checklist:

  • My PR is fully covered with tests (you can see the coverage diff by visiting the details link of the codecov check)
  • My PR is as minimal as possible (smaller, focused PRs are easier to review than big ones)
  • I have provided a detailed rationale explaining my changes
  • New or modified functions have Ddoc comments (with Params: and Returns:)

Please see CONTRIBUTING.md for more information.


If you have addressed all reviews or aren't sure how to proceed, don't hesitate to ping us with a simple comment.

Bugzilla references

Your PR doesn't reference any Bugzilla issue.

If your PR contains non-trivial changes, please reference a Bugzilla issue or create a manual changelog.

Testing this PR locally

If you don't have a local development environment setup, you can use Digger to test this PR:

dub run digger -- build "master + dmd#21001"

@dkorpel
Copy link
Contributor

dkorpel commented Mar 15, 2025

Props for tackling this bug, forward references can be tricky. 👍

Your code for inferring whether an enum is incomplete looks functional, but there's a simpler way to check the semantic state of symbols that the rest of the compiler uses: the semanticRun variable in Dsymbol stores the last completed pass, so this should work better:

if (ed.semanticRun < PASS.semanticdone)
    error(...);

For the tests, please minimize the amount of added test code, since the test suite is already so large.

  1. The presence of multiple enum members doesn't change any code paths
  2. Regular final switch is already tested somewhere else
  3. The nested case makes sense for testing your code that walks up the scopes, but it will be redundant with the simpler check.

So just keep test 1.

@abulgit
Copy link
Contributor Author

abulgit commented Mar 15, 2025

Thank you for the review and suggestions! I've updated the PR

  1. I simplified the enum incompleteness check using semanticRun < PASS.semanticdone as suggested.

  2. And reduced the test file to only include the original case from the issue. and that makes sense since the test suite is already so big, and keeps getting bigger.

Comment on lines 1967 to 1975
for (Scope* scx = sc; scx; scx = scx.enclosing)
{
if (scx.scopesym && scx.scopesym.isEnumDeclaration() && scx.scopesym == ed)
{
error(ss.loc, "cannot use `final switch` on enum `%s` while it is being defined", ed.toChars());
sc.pop();
return setError();
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This check still needs to be removed

@@ -1959,8 +1959,33 @@ Statement statementSemanticVisit(Statement s, Scope* sc)
ed = ds.isEnumDeclaration(); // typedef'ed enum
if (!ed && te && ((ds = te.toDsymbol(sc)) !is null))
ed = ds.isEnumDeclaration();

// Circular references
if (ed)
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
if (ed)
if (ed && ed.semanticRun < PASS.semanticdone)

if (ed.semanticRun < PASS.semanticdone)
{
error(ss.loc, "cannot use `final switch` on enum `%s` while it is being defined", ed.toChars());
sc.pop();
Copy link
Contributor

Choose a reason for hiding this comment

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

Why did you add this line?

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 kept both checks thinking they might catch different cases.

Copy link
Contributor

Choose a reason for hiding this comment

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

Nevermind this comment, I was asking about sc.pop() specifically, but I see now that a scope is pushed before and every return path needs to pop it.

if (ed && ss.cases.length < ed.members.length)
{
// Remove the old check for incomplete enum members
Copy link
Contributor

Choose a reason for hiding this comment

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

This looks like an AI instruction accidentally left in here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

no! I remove the previous enum members checks that's why I added that line.

Copy link
Contributor

Choose a reason for hiding this comment

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

But the comment doesn't make sense without the context of this Pull Request, and only adds confusion when someone reads it later.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

if (!ed && te && ((ds = te.toDsymbol(sc)) !is null))
                ed = ds.isEnumDeclaration();
            
            if (ed && ed.semanticRun < PASS.semanticdone)
            {
                error(ss.loc, "cannot use `final switch` on enum `%s` while it is being defined", ed.toChars());
                sc.pop();
                return setError();
            }
            
            if (ed && ss.cases.length < ed.members.length)
            {

did u mean this, remove all the other checks only ed && ed.semanticRun < PASS.semanticdone

Copy link
Contributor Author

Choose a reason for hiding this comment

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

But the comment doesn't make sense without the context of this Pull Request, and only adds confusion when someone reads it later.

I wrote it for my own, mistakenlyforgot to remove it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Okay, I understand your point that the in-progress enum declarations should be done with the semanticRun state. So my question is -
You mentioned the semanticRun check should be sufficient. but how the enum's semantic state works.
Also, could you suggest specific debugging or maybe some instrumentation I could add to better understand this problem
Because I wanna understand this whole thing.

Copy link
Contributor

Choose a reason for hiding this comment

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

You could print the value of ed.semanticRun at the time of the crash, and then locate where it was last set with a debugger / debug print statements.

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 tried adding debug points and print statements to trace how semanticRun interacts with enum member initializers, but things are getting so overwhelming for me.

If you or someone else solve this, I’d love to see the final implementation to better understand how this compiler tracks enum in these edge cases. A concrete example would help me learn how to handle similar issues in the future.
Thanks for your guidance!

Copy link
Contributor

Choose a reason for hiding this comment

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

I found that at the time of the crash, em.semanticRun == PASS.semanticdone which got set here:

ed.semanticRun = PASS.semanticdone;

I tried moving that to after members have run semantic, but that breaks things, so I think we need to use semantic2 here. For structs, semantic2 does analysis of members, and for variables, semantic2 does analysis of initializers. I think we can extend this distinction to enums: semantic(1) can mean 'enum base type is determined', while semantic2 can mean 'all member values have been computed'. So after moving the enum member analysis to semantic2, the final switch check can verify we're at PASS.semantic2done and that should work.

If you want to give that a try, you can do so, but I can also take care of it. In that case, just close this PR and I'll open my own when I have time.

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, I will give it a try.

@abulgit abulgit requested a review from dkorpel March 16, 2025 18:10
@abulgit
Copy link
Contributor Author

abulgit commented Mar 18, 2025

I don't understand why this failed
It looks like solving an issue gives a new one.

src/core/time.d(360): Error: cannot use `final switch` on enum `ClockType` while it is being defined
            with(ClockType) final switch (clockType)
                            ^
src/core/time.d(2055): Error: template instance `core.time.MonoTimeImpl!(ClockType.normal)` error instantiating
alias MonoTime = MonoTimeImpl!(ClockType.normal);
                 ^
src/core/time.d(360): Error: cannot use `final switch` on enum `ClockType` while it is being defined
            with(ClockType) final switch (clockType)
                            ^
src/core/time.d(2055): Error: template instance `core.time.MonoTimeImpl!(ClockType.normal)` error instantiating
alias MonoTime = MonoTimeImpl!(ClockType.normal);
                 ^
make: *** [Makefile:420: ../generated/linux/release/64/libdruntime.a] Error 1
make: *** Waiting for unfinished jobs....
make: *** [Makefile:414: ../generated/linux/release/64/libdruntime.so.a] Error 1
make: Leaving directory '/__w/dmd/dmd/druntime'
Error: Process completed with exit code 2.

@dkorpel
Copy link
Contributor

dkorpel commented Mar 18, 2025

Yes, that's why solving semantic analysis bugs is so hard! In this case, I think semantic2 doesn't get run. It's best to take small steps, so I'd start by simply setting the semantic2done flag at the end of the current enumSemantic function and see if that breaks anything.

I'd only really extract / move the code as a follow-up refactoring PR.

@abulgit
Copy link
Contributor Author

abulgit commented Mar 19, 2025

Yes, that's why solving semantic analysis bugs is so hard! In this case, I think semantic2 doesn't get run. It's best to take small steps, so I'd start by simply setting the semantic2done flag at the end of the current enumSemantic function and see if that breaks anything.

I'd only really extract / move the code as a follow-up refactoring PR.

Yes, I Debugged the code and Tried so many different approaches, It took so many hrs and finally, this worked.
can u please review this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

ICE on final switch forward referencing its enum
3 participants