Skip to content

Conversation

@WalterBright
Copy link
Member

No description provided.

@WalterBright WalterBright added the dip1000 memory safety with scope, ref, return label Dec 9, 2025
@dlang-bot
Copy link
Contributor

Thanks for your pull request, @WalterBright!

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

@WalterBright
Copy link
Member Author

m_lock.performLocked!({ m_streamWSize[sid] = newWin; });

  | ^
  | ../.dub/packages/vibe-http/1.3.1/vibe-http/source/vibe/http/internal/http2/exchange.d(84,9): Error: returning scope variable res is not allowed in a @safe function

Anyone know anything about this code?

@WalterBright
Copy link
Member Author

@dkorpel may need to enable this with an edition

@dkorpel
Copy link
Contributor

dkorpel commented Dec 9, 2025

may need to enable this with an edition

If you guard the code with if (sc.hasEdition(Edition.v2024)) it will only take effect from that edition onwards.

@dkorpel
Copy link
Contributor

dkorpel commented Dec 9, 2025

Anyone know anything about this code?

https://github.com/vibe-d/vibe-http/blob/94e4d1fe6c5eace1de38456a79040c5e94b422e7/source/vibe/http/internal/http2/exchange.d#L55

It looks like it incorrectly marks the allocator parameter as scope instead of return scope in a @safe function, which should be an error but it passes because it's not compiled with -preview=dip1000. This PR restricts returning scope variables even without dip1000, so it now breaks.

if (va && !(vaIsFirstRef && v.isReturn()) && va.enclosesLifetimeOf(v))
{
if (sc.setUnsafeDIP1000(gag, ae.loc, "assigning address of variable `%s` to `%s` with longer lifetime", v, va))
if (setUnsafe(&sc, gag, ae.loc, "assigning address of variable `%s` to `%s` with longer lifetime", v, va))
Copy link
Contributor

Choose a reason for hiding this comment

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

This is the wrong place to change. This still enables escaping stack allocated slices through other means than return, and if you were to perform this change consistently you are basically just enabling DIP1000 by default.

Without dip1000, slicing a static array a[] should be treated the same as taking the address of a local &a as far as @safe is concerned, and taking the address of locals is prevented by the checkAddressVar function called from AddrExp::semantic. The same could be done with SliceExp::semantic:

--- a/compiler/src/dmd/expressionsem.d
+++ b/compiler/src/dmd/expressionsem.d
@@ -10954,6 +10954,17 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor
         }
         else if (t1b.ty == Tsarray)
         {
+            if (auto ve = exp.e1.isVarExp())
+            {
+                if (auto vd = ve.var.isVarDeclaration())
+                {
+                    if (!checkAddressVar(sc, exp, vd))
+                    {
+                        result = ErrorExp.get();
+                        return;
+                    }
+                }
+            }
         }
         else if (t1b.ty == Ttuple)
         {
@@ -17649,7 +17660,7 @@ Expression modifiableLvalue(Expression _this, Scope* sc, Expression eorig = null

Of course this is a massive breaking change so it needs to be guarded by an edition.

Copy link
Contributor

Choose a reason for hiding this comment

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

BTW DIP1000 is currently enabled for the 2024 edition, so it catches escaping slices anyway.

Copy link
Member Author

Choose a reason for hiding this comment

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

I appreciate your ideas here. But I have a bit of a different take. As you explained, I put the check on the return. (I also put it on an assignment to a global.) Returning a pointer to the stack is always an error (detected or not). That's why there is value in not requiring dip1000 to check it. But taking a slice of a stack array is legitimate (I use it often as a temporary buffer.

The compiler already attaches scope to any pointer or slice of a stack variable. Making use of that does not require any annotations nor inference of function interfaces, which were the problems with dip1000.

Returning a pointer to the stack is always a bug in the code, and I have no qualms about checking for this without adding the noted difficulties of dip1000. Yes, this doesn't detect everything dip1000 does, but I don't see a downside to adding these checks without requiring dip1000.

I'd like to see what more we can do without the problems of dip1000.

I was a bit nervous about what failures we'd encounter with this PR, but it seems that it is only detecting overlooked bugs in existing code. Which is good news! But in the spirit of not breaking existing code, putting it behind an edition is reasonable.

Copy link
Contributor

Choose a reason for hiding this comment

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

But taking a slice of a stack array is legitimate

Which you can always do in @system or @trusted code. You said before that @safe is not just a warning / linting system, it's supposed to be robust. Are we watering it down now, intentionally leaving holes in it?

Copy link
Member Author

Choose a reason for hiding this comment

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

We want to move to @safe by default. This PR increases safety by default, and the only code it breaks is code that is broken already. It does not carry with it the difficulties that dip1000 has. dip1000 makes @safe safer, but has turned out to be too much of a burden for programmers. I aim to make it safe without those difficulties. This is a good step in that direction.

Copy link
Contributor

Choose a reason for hiding this comment

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

Also don't overlook what Nick said:

BTW DIP1000 is currently enabled for the 2024 edition, so it catches escaping slices anyway.

If I currently revert this PR's code changes, this PR's test cases still pass.

Copy link
Member Author

Choose a reason for hiding this comment

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

this PR breaks this code for example:

Yes, that is correct. This is why it is only enabled with the 2024 edition. While the code sample is not wrong, it is bad form. I expect more of this to happen with "safe by default". (The false error can be corrected using DFA, but we're not there yet.)

Copy link
Member Author

Choose a reason for hiding this comment

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

If I currently revert this PR's code changes, this PR's test cases still pass.

If you have dip1000 turned on, yes.

Copy link
Contributor

Choose a reason for hiding this comment

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

If you have dip1000 turned on, yes.

Or you are using >= 2024 edition:
https://github.com/dlang/dmd/blob/master/compiler/src/dmd/dscope.d#L606

Copy link
Contributor

Choose a reason for hiding this comment

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

Your justification for this incomplete fix instead of the simple, obvious, and robust fix was that this way, "the only code it breaks is code that is broken already" and it doesn't add "the noted difficulties of dip1000".

We've now established that, in fact, it does break valid code with the exact same confusing "Error: returning scope variable" message. This is the worst of both worlds: you don't get the safety of DIP1000, but you do get the notorious DIP1000 error messages about scope variables which the programmer didn't actually mark as scope.

I don't approve of a PR which is justified by false statements.

@WalterBright
Copy link
Member Author

@dkorpel I added the edition check, thanks!

@WalterBright WalterBright force-pushed the issue20508-2 branch 2 times, most recently from ca99e47 to 06f66c1 Compare December 10, 2025 09:23
@WalterBright
Copy link
Member Author

This is ready to go.

@dkorpel dkorpel added the Review:Needs Spec PR A PR updating the language specification needs to be submitted to dlang.org label Dec 10, 2025
@WalterBright
Copy link
Member Author

@dkorpel spec pr dlang/dlang.org#4356

@WalterBright
Copy link
Member Author

@dkorpel spec PR has been merged

@dkorpel
Copy link
Contributor

dkorpel commented Jan 6, 2026

That doesn't change anything about #22207 (comment) though

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

Labels

dip1000 memory safety with scope, ref, return Review:Needs Spec PR A PR updating the language specification needs to be submitted to dlang.org

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants