Skip to content

Fix issue 22671 - Allow GC allocations in if (__ctfe) blocks in @nogc functions#22680

Open
divyansharma001 wants to merge 2 commits intodlang:masterfrom
divyansharma001:fix-22671-nogc-ctfe-alloc
Open

Fix issue 22671 - Allow GC allocations in if (__ctfe) blocks in @nogc functions#22680
divyansharma001 wants to merge 2 commits intodlang:masterfrom
divyansharma001:fix-22671-nogc-ctfe-alloc

Conversation

@divyansharma001
Copy link

Problem

@nogc functions that allocate memory via the GC inside an if (__ctfe) block
were incorrectly rejected with a @nogc violation:

@nogc int[] makeArray()
{
    if (__ctfe)
        return new int[](10); // Error: allocating with `new` causes a GC allocation in `@nogc` function
    return null;
}

Since if (__ctfe) code only ever runs at compile time (CTFE), and GC allocation
is the only way to allocate memory during CTFE, this should be allowed.

Root Cause

semantic3.d has a post-pass loop over all ReturnStatements collected in
fd.returns. It calls checkGC(sc2) using the function-level scope sc2,
which does not have ctfeBlock = true — even if the return statement was
inside an if (__ctfe) block. This caused the GC check to fire incorrectly.

The analogous ExpressionStatement path (e.g. if (__ctfe) { new Foo(); })
was already correct, because those checkGC calls happen during statement
semantics with a scope that does have ctfeBlock = true.

Fix

Added bool inCtfeBlock to ReturnStatement (following the same pattern
already used by GotoStatement and LabelStatement).

  • statement.d: Added inCtfeBlock field to ReturnStatement
  • statementsem.d: Set rs.inCtfeBlock = sc.ctfeBlock at the top of
    visitReturn(), capturing the scope state at the time the return statement
    is semantically analyzed
  • semantic3.d: In the fd.returns post-pass loop, save/restore
    sc2.ctfeBlock and temporarily set it to true when rs.inCtfeBlock is
    set, before calling checkGC

Test

compiler/test/compilable/fix22671.d covers:

  • return new ClassName() inside if (__ctfe) in a @nogc function
  • new int[]() array allocation inside if (__ctfe) in a @nogc void function
  • if (__ctfe) ... else ... form
  • if (!__ctfe) ... else ... form (which the compiler rewrites internally)

Verified that @nogc still correctly rejects GC allocations outside
if (__ctfe) blocks.

Fixes #22671

Copilot AI review requested due to automatic review settings March 2, 2026 08:32
@dlang-bot
Copy link
Contributor

Thanks for your pull request and interest in making D better, @divyansharma001! 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.

⚠️⚠️⚠️ Warnings ⚠️⚠️⚠️

  • In preparation for migrating from Bugzilla to GitHub Issues, the issue reference syntax has changed. Please add the word "Bugzilla" to issue references. For example, Fix Bugzilla Issue 12345 or Fix Bugzilla 12345.(Reminder: the edit needs to be done in the Git commit message, not the GitHub pull request.)

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#22680"

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes a false-positive @nogc violation when a @nogc function performs GC allocations inside if (__ctfe) blocks, by ensuring the GC check is aware the return came from a CTFE-only branch.

Changes:

  • Add ReturnStatement.inCtfeBlock to record whether a return was semantically analyzed under ctfeBlock.
  • Populate inCtfeBlock during statement semantic analysis and use it in semantic3.d when running the return-statement post-pass GC check.
  • Add a compilable regression test and a pending changelog entry for the behavior change.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
compiler/test/compilable/fix22671.d Regression coverage for GC allocations inside if (__ctfe) in @nogc functions (including else and !__ctfe rewrite).
compiler/src/dmd/statementsem.d Captures sc.ctfeBlock onto ReturnStatement during semantic analysis.
compiler/src/dmd/statement.d Adds inCtfeBlock field to ReturnStatement.
compiler/src/dmd/semantic3.d Temporarily enables ctfeBlock during ReturnStatement post-pass GC checking when applicable.
changelog/dmd.nogc-ctfe-alloc.dd Documents the @nogc + if (__ctfe) GC-allocation acceptance change.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 1311 to +1312
FuncDeclaration fesFunc; // nested function for foreach it is in
bool inCtfeBlock; /// set if return is inside an `if (__ctfe)` block
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

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

ReturnStatement gained a new data member (inCtfeBlock). The corresponding C++ headers (compiler/src/dmd/statement.h and compiler/src/dmd/frontend.h) still define ReturnStatement without this field, so they’re now out of sync with the D AST definition. Please regenerate/update those headers (dtoh output) to keep the D/C++ AST layouts consistent.

Copilot uses AI. Check for mistakes.
@thewilsonator
Copy link
Contributor

See the copilot suggestions for the fix for the circleCI failure.

// https://issues.dlang.org/show_bug.cgi?id=22671
// GC allocations inside `if (__ctfe)` blocks are always allowed
const prevCtfeBlock = sc2.ctfeBlock;
if (rs.inCtfeBlock)
Copy link
Member

Choose a reason for hiding this comment

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

No need to modfiy sc2.ctfeBlock, checkGC returns exp anyway if set, so

if (!rs.inCtfeBlock) exp = exp.checkGC(sc2);

should be good enough.

@@ -0,0 +1,51 @@
// https://issues.dlang.org/show_bug.cgi?id=22671
Copy link
Contributor

Choose a reason for hiding this comment

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

This link points to bugzilla which is dead

if (tret.ty == Terror)
{
// https://issues.dlang.org/show_bug.cgi?id=13702
// https://issues.dlang.org/show_bug.cgi?id=22671
Copy link
Contributor

Choose a reason for hiding this comment

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

Ditto (dead link)

@divyansharma001 divyansharma001 requested a review from ibuclaw as a code owner March 3, 2026 15:52
@adamdruppe
Copy link
Contributor

Compare and contrast to OpenD's fix for this issue:

opendlang/opend@712ca3a

@rainers
Copy link
Member

rainers commented Mar 3, 2026

Compare and contrast to OpenD's fix for this issue:

opendlang/opend@712ca3a

Indeed this looks simpler, I was expecting something like that in the first place, too. But AFAICT evaluating GC usage of the return statement at function level doesn't provide the scope adapted by the if(__ctfe) blocks, so the information needs to be saved somewhere.

Doesn't your patch deal with calls of non-@nogc functions inside the if(__ctfe) blocks instead? If it works nonetheless, I'd be an favor of a similar change.

@adamdruppe
Copy link
Contributor

But AFAICT evaluating GC usage of the return statement at function level doesn't provide the scope adapted by the if(__ctfe) blocks, so the information needs to be saved somewhere.

It's always possible I just did it wrong in some subtle way (wouldn't be the first time!), but I downloaded the test file in this PR and stock opend built it successfully:

$ dmd-master -o- fix22671.d
fix22671.d(11): Error: allocating with `new` causes a GC allocation in `@nogc`
unction `bar`
        return new Foo();  // allowed - only runs at compile time
               ^
$ ldc2 -o- fix22671.d
fix22671.d(11): Error: cannot use `new` in `@nogc` function `fix22671.bar`

$ opend -o- fix22671.d
# no error

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.

GC alloc inside __ctfe blocks in @nogc functions?

7 participants