Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions changelog/dmd.nullderefcheck.dd
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
An optional check for null dereference is added

A new check has been implemented that injects code to check a pointer for null before it is dereferenced.

It will be typically be used if you need a backtrace generated (when one is not automatically done), or if you want to catch and handle the Error as part of a scheduler.

This can be enabled by using ``-check=nullderef=on`` by default it is off.
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.


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.

5 changes: 1 addition & 4 deletions compiler/src/dmd/backend/codebuilder.d
Original file line number Diff line number Diff line change
Expand Up @@ -399,10 +399,7 @@ assert(op != BADINS);
@trusted
code* last()
{
// g++ and clang++ complain about offsetof() because of the code::code() constructor.
// return (code *)((char *)pTail - offsetof(code, next));
// So do our own.
return cast(code*)(cast(void*)pTail - (cast(void*)&(*pTail).next - cast(void*)*pTail));
return cast(code*)(cast(void*)pTail - code.next.offsetof);
}

/*************************************
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dmd/backend/drtlsym.d
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ Symbol* getRtlsym(RTLSYM i) @trusted
case RTLSYM.DARRAYP: symbolz(ps,FL.func,FREGSAVED,"_d_arrayboundsp", SFLexit, t); break;
case RTLSYM.DARRAY_SLICEP: symbolz(ps,FL.func,FREGSAVED,"_d_arraybounds_slicep", SFLexit, t); break;
case RTLSYM.DARRAY_INDEXP: symbolz(ps,FL.func,FREGSAVED,"_d_arraybounds_indexp", SFLexit, t); break;
case RTLSYM.DNULLP: symbolz(ps,FL.func,FREGSAVED,"_d_nullpointerp", SFLexit, t); break;
case RTLSYM.DINVARIANT: symbolz(ps,FL.func,FREGSAVED,"_D2rt10invariant_12_d_invariantFC6ObjectZv", 0, tsdlib); break;
case RTLSYM.MEMCPY: symbolz(ps,FL.func,FREGSAVED,"memcpy", 0, t); break;
case RTLSYM.MEMSET8: symbolz(ps,FL.func,FREGSAVED,"memset", 0, t); break;
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dmd/backend/rtlsym.d
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ enum RTLSYM
DARRAYP,
DARRAY_SLICEP,
DARRAY_INDEXP,
DNULLP,
DINVARIANT,
MEMCPY,
MEMSET8,
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dmd/cli.d
Original file line number Diff line number Diff line change
Expand Up @@ -1187,6 +1187,7 @@ struct CLIUsage
=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.

=on Enable all assertion checking
(default for non-release builds)
=off Disable all assertion checking
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dmd/dfa/fast/report.d
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ struct DFAReporter
}
}

void onLoopyLabelLessNullThan(DFAVar* var, ref const(Loc) loc)
void onLoopyLabelLessNullThan(DFAVar* var, ref const Loc loc)
{
if (var !is null && !var.isModellable)
return;
Expand All @@ -88,7 +88,7 @@ struct DFAReporter
* to guarantee a non-null output, this ensures the variable is actually non-null
* when the scope exits.
*/
void onEndOfScope(FuncDeclaration fd, ref Loc loc)
void onEndOfScope(FuncDeclaration fd, ref const Loc loc)
{
// this is where we validate escapes, for a specific location

Expand Down
5 changes: 4 additions & 1 deletion compiler/src/dmd/dfa/fast/statement.d
Original file line number Diff line number Diff line change
Expand Up @@ -603,7 +603,7 @@ final:
// This signals that no code after this point in the current block is reachable.
dfaCommon.currentDFAScope.haveJumped = true;
dfaCommon.currentDFAScope.haveReturned = true;
analyzer.reporter.onEndOfScope(dfaCommon.currentFunction, exp.loc);
analyzer.reporter.onEndOfScope(dfaCommon.currentFunction, st.loc);
break;

case STMT.Compound:
Expand Down Expand Up @@ -895,6 +895,7 @@ final:
dfaCommon.currentDFAScope.inConditional = true;
dfaCommon.setScopeAsLoopyLabel;

if (theCondition !is null)
{
dfaCommon.printStructure((ref OutBuffer ob,
scope PrintPrefixType prefix) => ob.writestring("For condition:\n"));
Expand All @@ -909,6 +910,8 @@ final:

expWalker.seeAssert(lrCondition, theCondition.loc, true);
}
else
dfaCommon.currentDFAScope.isLoopyLabelKnownToHaveRun = true;

dfaCommon.printStructure((ref OutBuffer ob,
scope PrintPrefixType prefix) => ob.writestring("For body:\n"));
Expand Down
5 changes: 4 additions & 1 deletion compiler/src/dmd/frontend.h
Original file line number Diff line number Diff line change
Expand Up @@ -8211,6 +8211,7 @@ struct Param final
CHECKENABLE useIn;
CHECKENABLE useOut;
CHECKENABLE useArrayBounds;
CHECKENABLE useNullCheck;
CHECKENABLE useAssert;
CHECKENABLE useSwitchError;
CHECKENABLE boundscheck;
Expand Down Expand Up @@ -8294,6 +8295,7 @@ struct Param final
useIn((CHECKENABLE)0u),
useOut((CHECKENABLE)0u),
useArrayBounds((CHECKENABLE)0u),
useNullCheck((CHECKENABLE)0u),
useAssert((CHECKENABLE)0u),
useSwitchError((CHECKENABLE)0u),
boundscheck((CHECKENABLE)0u),
Expand Down Expand Up @@ -8334,7 +8336,7 @@ struct Param final
timeTraceFile()
{
}
Param(bool obj, bool readStdin = false, bool multiobj = false, bool trace = false, bool tracegc = false, bool vcg_ast = false, DiagnosticReporting useDeprecated = (DiagnosticReporting)1u, bool useUnitTests = false, bool useInline = false, bool release = false, bool preservePaths = false, DiagnosticReporting useWarnings = (DiagnosticReporting)2u, bool cov = false, uint8_t covPercent = 0u, bool ctfe_cov = false, bool ignoreUnsupportedPragmas = true, bool useModuleInfo = true, bool useTypeInfo = true, bool useExceptions = true, bool useGC = true, bool betterC = false, bool addMain = false, bool allInst = false, bool bitfields = true, bool rewriteNoExceptionToSeq = false, CppStdRevision cplusplus = (CppStdRevision)201103u, Help help = Help(), Verbose v = Verbose(), Edition edition = (Edition)2023u, void* editionFiles = nullptr, FeatureState useDIP25 = (FeatureState)2u, FeatureState useDIP1000 = (FeatureState)0u, bool ehnogc = false, bool useDIP1021 = false, FeatureState fieldwise = (FeatureState)0u, bool fixAliasThis = false, FeatureState rvalueRefParam = (FeatureState)0u, FeatureState safer = (FeatureState)0u, FeatureState noSharedAccess = (FeatureState)0u, bool previewIn = false, bool inclusiveInContracts = false, bool shortenedMethods = true, bool fixImmutableConv = false, bool fix16997 = true, FeatureState dtorFields = (FeatureState)0u, FeatureState systemVariables = (FeatureState)0u, bool useFastDFA = false, CHECKENABLE useInvariants = (CHECKENABLE)0u, CHECKENABLE useIn = (CHECKENABLE)0u, CHECKENABLE useOut = (CHECKENABLE)0u, CHECKENABLE useArrayBounds = (CHECKENABLE)0u, CHECKENABLE useAssert = (CHECKENABLE)0u, CHECKENABLE useSwitchError = (CHECKENABLE)0u, CHECKENABLE boundscheck = (CHECKENABLE)0u, CHECKACTION checkAction = (CHECKACTION)0u, CLIIdentifierTable dIdentifierTable = (CLIIdentifierTable)0u, CLIIdentifierTable cIdentifierTable = (CLIIdentifierTable)0u, _d_dynamicArray< const char > argv0 = {}, Array<const char* > modFileAliasStrings = Array<const char* >(), Array<ImportPathInfo > imppath = Array<ImportPathInfo >(), Array<const char* > fileImppath = Array<const char* >(), _d_dynamicArray< const char > objdir = {}, _d_dynamicArray< const char > objname = {}, _d_dynamicArray< const char > libname = {}, Output ddoc = Output(), Output dihdr = Output(), Output cxxhdr = Output(), Output json = Output(), JsonFieldFlags jsonFieldFlags = (JsonFieldFlags)0u, Output makeDeps = Output(), Output mixinOut = Output(), Output moduleDeps = Output(), bool debugEnabled = false, bool run = false, Array<const char* > runargs = Array<const char* >(), Array<const char* > cppswitches = Array<const char* >(), const char* cpp = nullptr, Array<const char* > objfiles = Array<const char* >(), Array<const char* > linkswitches = Array<const char* >(), Array<bool > linkswitchIsForCC = Array<bool >(), Array<const char* > libfiles = Array<const char* >(), Array<const char* > dllfiles = Array<const char* >(), _d_dynamicArray< const char > deffile = {}, _d_dynamicArray< const char > resfile = {}, _d_dynamicArray< const char > exefile = {}, _d_dynamicArray< const char > mapfile = {}, bool fullyQualifiedObjectFiles = false, bool timeTrace = false, uint32_t timeTraceGranularityUs = 500u, const char* timeTraceFile = nullptr) :
Param(bool obj, bool readStdin = false, bool multiobj = false, bool trace = false, bool tracegc = false, bool vcg_ast = false, DiagnosticReporting useDeprecated = (DiagnosticReporting)1u, bool useUnitTests = false, bool useInline = false, bool release = false, bool preservePaths = false, DiagnosticReporting useWarnings = (DiagnosticReporting)2u, bool cov = false, uint8_t covPercent = 0u, bool ctfe_cov = false, bool ignoreUnsupportedPragmas = true, bool useModuleInfo = true, bool useTypeInfo = true, bool useExceptions = true, bool useGC = true, bool betterC = false, bool addMain = false, bool allInst = false, bool bitfields = true, bool rewriteNoExceptionToSeq = false, CppStdRevision cplusplus = (CppStdRevision)201103u, Help help = Help(), Verbose v = Verbose(), Edition edition = (Edition)2023u, void* editionFiles = nullptr, FeatureState useDIP25 = (FeatureState)2u, FeatureState useDIP1000 = (FeatureState)0u, bool ehnogc = false, bool useDIP1021 = false, FeatureState fieldwise = (FeatureState)0u, bool fixAliasThis = false, FeatureState rvalueRefParam = (FeatureState)0u, FeatureState safer = (FeatureState)0u, FeatureState noSharedAccess = (FeatureState)0u, bool previewIn = false, bool inclusiveInContracts = false, bool shortenedMethods = true, bool fixImmutableConv = false, bool fix16997 = true, FeatureState dtorFields = (FeatureState)0u, FeatureState systemVariables = (FeatureState)0u, bool useFastDFA = false, CHECKENABLE useInvariants = (CHECKENABLE)0u, CHECKENABLE useIn = (CHECKENABLE)0u, CHECKENABLE useOut = (CHECKENABLE)0u, CHECKENABLE useArrayBounds = (CHECKENABLE)0u, CHECKENABLE useNullCheck = (CHECKENABLE)0u, CHECKENABLE useAssert = (CHECKENABLE)0u, CHECKENABLE useSwitchError = (CHECKENABLE)0u, CHECKENABLE boundscheck = (CHECKENABLE)0u, CHECKACTION checkAction = (CHECKACTION)0u, CLIIdentifierTable dIdentifierTable = (CLIIdentifierTable)0u, CLIIdentifierTable cIdentifierTable = (CLIIdentifierTable)0u, _d_dynamicArray< const char > argv0 = {}, Array<const char* > modFileAliasStrings = Array<const char* >(), Array<ImportPathInfo > imppath = Array<ImportPathInfo >(), Array<const char* > fileImppath = Array<const char* >(), _d_dynamicArray< const char > objdir = {}, _d_dynamicArray< const char > objname = {}, _d_dynamicArray< const char > libname = {}, Output ddoc = Output(), Output dihdr = Output(), Output cxxhdr = Output(), Output json = Output(), JsonFieldFlags jsonFieldFlags = (JsonFieldFlags)0u, Output makeDeps = Output(), Output mixinOut = Output(), Output moduleDeps = Output(), bool debugEnabled = false, bool run = false, Array<const char* > runargs = Array<const char* >(), Array<const char* > cppswitches = Array<const char* >(), const char* cpp = nullptr, Array<const char* > objfiles = Array<const char* >(), Array<const char* > linkswitches = Array<const char* >(), Array<bool > linkswitchIsForCC = Array<bool >(), Array<const char* > libfiles = Array<const char* >(), Array<const char* > dllfiles = Array<const char* >(), _d_dynamicArray< const char > deffile = {}, _d_dynamicArray< const char > resfile = {}, _d_dynamicArray< const char > exefile = {}, _d_dynamicArray< const char > mapfile = {}, bool fullyQualifiedObjectFiles = false, bool timeTrace = false, uint32_t timeTraceGranularityUs = 500u, const char* timeTraceFile = nullptr) :
obj(obj),
readStdin(readStdin),
multiobj(multiobj),
Expand Down Expand Up @@ -8386,6 +8388,7 @@ struct Param final
useIn(useIn),
useOut(useOut),
useArrayBounds(useArrayBounds),
useNullCheck(useNullCheck),
useAssert(useAssert),
useSwitchError(useSwitchError),
boundscheck(boundscheck),
Expand Down
35 changes: 31 additions & 4 deletions compiler/src/dmd/funcsem.d
Original file line number Diff line number Diff line change
Expand Up @@ -4282,16 +4282,43 @@ bool arrayBoundsCheck(FuncDeclaration fd)
case CHECKENABLE.on:
return true;
case CHECKENABLE.safeonly:
if (fd)
{
Type t = fd.type;
if (t.isTypeFunction() && t.isTypeFunction().trust == TRUST.safe)
return true;
}
return false;
case CHECKENABLE._default:
assert(0);
}
}

/**********************
* Check to see if null dereference checking code has to be generated
*
* Params:
* fd = function for which code is to be generated
* Returns:
* true if do null dereference checking for the given function
*/
bool nullDerefCheck(FuncDeclaration fd)
{
final switch (global.params.useNullCheck)
{
case CHECKENABLE.off:
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

if (fd)
{
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.

Type t = fd.type;
if (t.ty == Tfunction && (cast(TypeFunction)t).trust == TRUST.safe)
if (t.isTypeFunction() && t.isTypeFunction().trust == TRUST.safe)
return true;
}
return false;
}
case CHECKENABLE._default:
assert(0);
case CHECKENABLE._default:
assert(0);
}
}
1 change: 1 addition & 0 deletions compiler/src/dmd/globals.d
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ extern (C++) struct Param
CHECKENABLE useIn = CHECKENABLE._default; // generate precondition checks
CHECKENABLE useOut = CHECKENABLE._default; // generate postcondition checks
CHECKENABLE useArrayBounds = CHECKENABLE._default; // when to generate code for array bounds checks
CHECKENABLE useNullCheck = CHECKENABLE._default; // when to generate code for null dereference checks
CHECKENABLE useAssert = CHECKENABLE._default; // when to generate code for assert()'s
CHECKENABLE useSwitchError = CHECKENABLE._default; // check for switches without a default
CHECKENABLE boundscheck = CHECKENABLE._default; // state of -boundscheck switch
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dmd/globals.h
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ struct Param
CHECKENABLE useIn; // generate precondition checks
CHECKENABLE useOut; // generate postcondition checks
CHECKENABLE useArrayBounds; // when to generate code for array bounds checks
CHECKENABLE useNullCheck; // when to generate code for null dereference checks
CHECKENABLE useAssert; // when to generate code for assert()'s
CHECKENABLE useSwitchError; // check for switches without a default
CHECKENABLE boundscheck; // state of -boundscheck switch
Expand Down
Loading
Loading