Skip to content

Conversation

@rikkimax
Copy link
Contributor

@rikkimax rikkimax commented Oct 28, 2025

Approved work: https://forum.dlang.org/post/mwffryejcmqyolmweaps@forum.dlang.org

Currently turned on to see what the CI does.

Will request Walter to review once I'm happy with CI.

Heavily inspired by OpenD with thanks to @adamdruppe.

EDIT: Special thanks to @limepoutine for helping to get both function pointers and delegate function pointers to be null checked!

@dlang-bot
Copy link
Contributor

Thanks for your pull request and interest in making D better, @rikkimax! 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#22040"

@rikkimax rikkimax force-pushed the nullderef-barrier branch 5 times, most recently from 1ca48e7 to c0d2c45 Compare October 28, 2025 10:23
@rikkimax
Copy link
Contributor Author

Fun fact, lazy parameters can have a null object. Oh well we just can't check for null on the object of a delegate prior to a call.

@rikkimax rikkimax force-pushed the nullderef-barrier branch 13 times, most recently from 86f1e3b to 05957b2 Compare October 28, 2025 13:46
@nordlow
Copy link
Contributor

nordlow commented Oct 28, 2025

So a NullPointerError is unrecoverable, right?

@rikkimax
Copy link
Contributor Author

So I take it a NullPointerError unrecoverable, right?

It is an Error. Something has to die in response, like all Error's.

@nordlow
Copy link
Contributor

nordlow commented Oct 28, 2025

Is the plan to have -check=nullderef=on be on my default or optionally in debug mode or something else?

@adamdruppe
Copy link
Contributor

There's no technical reason why you can't recover from a NullPointerError in general, though you may choose not to as it can indicate you don't understand program state.

@rikkimax
Copy link
Contributor Author

Will using -check=nullderef=on affect performance?

Possibly.

I intend to wire up the fast DFA engine to elide the checks. But can't do it until what does exist is proven to work.

@adamdruppe
Copy link
Contributor

The performance impact is very small. The cpu's branch predictor handles it well and ldc's code generator is decent at removing dead checks and arranging the code blocks in a cache-friendly manner. (dmd's isn't so good but ldc is known to optimize better than dmd in many ways)

@nordlow
Copy link
Contributor

nordlow commented Oct 29, 2025

So a single unittest in libdparse at

https://buildkite.com/dlang/dmd/builds/43787/steps/canvas?sid=019a2b1c-9d02-4993-b9df-dfb58a18783d

fails buildkite/dmd.

The libdparse/test/fail_files/killer2.d contains

unittest
{
version (53056)[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
union {}
Klse
gosh ÿ� 'A+ SPL : tape];
		}
	}
}

. Could the CI failure be caused by the compiler existing with error code other than what the tests expects? I also wonder how these seemingly random test cases where deduced...

@rikkimax
Copy link
Contributor Author

I'm aware, but the problem will be in the compiled libdparse, and without compiling it locally I won't be figuring out which emitted hook did it.

As far as I know this could be related to XMM intrinsics, and since those are more of a known problem they're up first.

@rikkimax
Copy link
Contributor Author

CI has done the correct, thing.

Anything else you need @WalterBright before I turn it off and take it out of draft?

This can be enabled by using ``-check=nullderef=on``
What happens may be customized by the ``-checkaction`` switch and by setting a new handler in ``core.exception``.

Due to issues in dmd's backend, not all pointer dereferences are guaranteed to get a check.
Copy link
Member

Choose a reason for hiding this comment

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

What does that mean?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Disabling of intrinsics and their arguments.

I don't particularly want to explain this in a changelog as it won't impact everyone, but it does need acknowledgment that dmd specifically can't check all pointer dereferences.

I can disable the intrinsic check in a follow-up PR for you to look into.

Copy link
Member

Choose a reason for hiding this comment

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

What do the intrinsics have to do with null pointer checking?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ideally nothing.

If I remove the counter check the backend errors when intrinsics are used.

A new check is implemented for when a null dereference occurs.

This can be enabled by using ``-check=nullderef=on``
What happens may be customized by the ``-checkaction`` switch and by setting a new handler in ``core.exception``.
Copy link
Member

Choose a reason for hiding this comment

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

No mention of what the default behavior is.

Copy link
Member

Choose a reason for hiding this comment

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

Could add a brief discussion of why one would want this feature.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

@rikkimax rikkimax force-pushed the nullderef-barrier branch 2 times, most recently from 7e31fd7 to 773864e Compare December 11, 2025 13:44
@rikkimax
Copy link
Contributor Author

I've turned this off now, will need confirmation that @WalterBright doesn't want any more changes.

@rikkimax rikkimax marked this pull request as ready for review December 11, 2025 13:46
@rikkimax rikkimax requested a review from ibuclaw as a code owner December 11, 2025 13:46
@WalterBright
Copy link
Member

@rikkimax can you please resolve the conflicts? Thanks!

@rikkimax
Copy link
Contributor Author

rikkimax commented Jan 9, 2026

@rikkimax can you please resolve the conflicts? Thanks!

Can do later on, is your feedback handled?

@WalterBright
Copy link
Member

Approved work: https://forum.dlang.org/post/mwffryejcmqyolmweaps@forum.dlang.org

What is the relationship between this PR and the opening comment?

@rikkimax
Copy link
Contributor Author

Approved work: https://forum.dlang.org/post/mwffryejcmqyolmweaps@forum.dlang.org

What is the relationship between this PR and the opening comment?

Remember the error handling meeting we had for exceptions?

This came up and was approved work.

Null deref read barrier aka null check. CLI switch, throw Error with a hook function. Noted that this is useful for platforms like web assembly and debugging CI's where no stack trace is present.

=invariant[=[on|off]] Class/struct invariants
=out[=[on|off]] Out contracts
=switch[=[on|off]] Final switch failure checking
=nullderef[=[on|off]] Null dereference error
Copy link
Member

Choose a reason for hiding this comment

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

=safeonly ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

None of the others support it. I'm matching them.

* when the scope exits.
*/
void onEndOfScope(FuncDeclaration fd, ref Loc loc)
void onEndOfScope(FuncDeclaration fd, ref const(Loc) loc)
Copy link
Member

Choose a reason for hiding this comment

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

ref const Loc loc will suffice

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Applying to elsewhere in module where I've done it.

case CHECKENABLE.on:
return true;
case CHECKENABLE.safeonly:
{
Copy link
Member

Choose a reason for hiding this comment

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

unnecessary { }

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 prefer adding the extra scope to make it clearer. Removing.

ec = el_same(ethis);
ethis = el_una(target.isX86_64 || target.isAArch64 ? OP128_64 : OP64_32, TYnptr, ethis); // get this
ec = array_toPtr(t, ec); // get funcptr

Copy link
Member

Choose a reason for hiding this comment

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

why removed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not removed, slightly moved around.

https://github.com/dlang/dmd/pull/22040/files#diff-54719c55eec4a6cfe3b7b7f4b5289d0b5a0b9d41e8589a023bba4d68921f8be0R5505

This was the cleanest I could come up with, which resulted in the least memory allocations.

params.useInvariants = CHECKENABLE.off;
params.useOut = CHECKENABLE.off;
params.useSwitchError = CHECKENABLE.off;
params.useNullCheck = CHECKENABLE.off;
Copy link
Member

Choose a reason for hiding this comment

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

what happened to =safeonly ?

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 have no idea, I don't remove such things.

Copy link
Member

Choose a reason for hiding this comment

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

the nullcheck has three settings - on|off|safeonly

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There is nothing to enable safeonly for -check.

static bool check(const(char)[] checkarg, string name, ref CHECKENABLE ce)

I haven't got an opinion about it being added. But right now, it doesn't exist for any of the checks.

return false;
case CHECKENABLE.on:
return true;
case CHECKENABLE.safeonly:
Copy link
Member

Choose a reason for hiding this comment

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

Here is where the null dereference check is enabled for safeonly. But there is no way to set it to safeonly.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Indeed. Seems to be an unused enum member for all the checks.

Copy link
Member

Choose a reason for hiding this comment

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

Do you think it would be reasonable to add safeonly for the null check?

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 think safeonly should be readded for all of the check's in one go.

A separate PR.

It's probably just

needs an extra else if, then update all the comments/docs. As well as test cases.

Copy link
Member

Choose a reason for hiding this comment

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

fair 'nuff

@thewilsonator thewilsonator merged commit 2fd2eba into dlang:master Jan 21, 2026
54 of 55 checks passed
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.

8 participants