Skip to content
Closed
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
4 changes: 4 additions & 0 deletions changelog/dip1006.dd
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Allow to disable only specific run-time checks

The $(LINK2 $(TT -release), https://dlang.org/dmd.html#switch-release) switch now accepts optional arguments to only disable some run-time checks, e.g. $(TT -release=in,out,invariant) disables in-/out-contracts and invariants, but still leaves assertions enabled.
Also see $(LINK2 DIP1006, https://github.com/dlang/DIPs/blob/master/DIPs/DIP1006.md).
15 changes: 9 additions & 6 deletions src/dmd/cli.d
Original file line number Diff line number Diff line change
Expand Up @@ -509,12 +509,15 @@ dmd -cov -unittest myprog.d
to the file $(TT profilegc.log) upon program termination.)
)`,
),
Option("release",
"compile release version",
`Compile release version, which means not emitting run-time
checks for contracts and asserts. Array bounds checking is not
done for system and trusted functions, and assertion failures
are undefined behaviour.`
Option("release[=<assert,in,out,invariant>]",
"disable run-time checks",
`Disable in-/out-contracts, invariants, and assertions.
It is possible to disable only some run-time checks by specifying them
as comma separated arguments (e.g. $(TT -release=assert,invariant)).
By default $(TT $(SWLINK -release)) will disable all run-time checks.
Any $(TT $(SWLINK -release)) switch furthermore sets the default of
$(SWLINK -boundscheck) to safeonly.
$(P Note that $(SWLINK -unittest) (re-)enables assertions in unittests and function bodies.)`
),
Option("run <srcfile>",
"compile, link, and run the program srcfile",
Expand Down
2 changes: 1 addition & 1 deletion src/dmd/globals.d
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ struct Param
bool useUnitTests; // generate unittest code
bool useInline = false; // inline expand functions
bool useDIP25; // implement http://wiki.dlang.org/DIP25
bool release; // build release version
bool optimizeLinking; // use optimized linker settings
bool preservePaths; // true means don't strip path from source file
// 0: disable warnings
// 1: warnings as errors
Expand Down
4 changes: 2 additions & 2 deletions src/dmd/link.d
Original file line number Diff line number Diff line change
Expand Up @@ -224,8 +224,8 @@ public int runLINK()
{
cmdbuf.writeByte(' ');
cmdbuf.writestring("/DEBUG");
// in release mode we need to reactivate /OPT:REF after /DEBUG
if (global.params.release)
// in release mode we reactivate /OPT:REF after /DEBUG to still minimize binary size
if (global.params.linkRelease)
cmdbuf.writestring(" /OPT:REF");
}
if (global.params.dll)
Expand Down
135 changes: 113 additions & 22 deletions src/dmd/mars.d
Original file line number Diff line number Diff line change
Expand Up @@ -396,37 +396,23 @@ private int tryMain(size_t argc, const(char)** argv)
global.params.mscrtlib = vsopt.defaultRuntimeLibrary(global.params.is64bit);
}
}
if (global.params.release)
{
global.params.useInvariants = false;
global.params.useIn = false;
global.params.useOut = false;

if (global.params.useArrayBounds == CHECKENABLE._default)
global.params.useArrayBounds = CHECKENABLE.safeonly;

if (global.params.useAssert == CHECKENABLE._default)
global.params.useAssert = CHECKENABLE.off;

if (global.params.useSwitchError == CHECKENABLE._default)
global.params.useSwitchError = CHECKENABLE.off;
}
if (global.params.betterC)
{
global.params.checkAction = CHECKACTION.C;
global.params.useModuleInfo = false;
global.params.useTypeInfo = false;
global.params.useExceptions = false;
}
// -release -unittest enables assertions in unittests AND function bodies
if (global.params.useUnitTests)
global.params.useAssert = CHECKENABLE.on;

if (global.params.useArrayBounds == CHECKENABLE._default)
global.params.useArrayBounds = CHECKENABLE.on;

if (global.params.useAssert == CHECKENABLE._default)
global.params.useAssert = CHECKENABLE.on;

if (global.params.useArrayBounds == CHECKENABLE._default)
global.params.useArrayBounds = CHECKENABLE.on;

if (global.params.useSwitchError == CHECKENABLE._default)
global.params.useSwitchError = CHECKENABLE.on;

Expand Down Expand Up @@ -1503,9 +1489,9 @@ private bool parseCommandLine(const ref Strings arguments, const size_t argc, re
{
bool errors;

void error(const(char)* format, const(char*) arg = null)
void error(Args...)(const(char)* format, Args args)
{
dmd.errors.error(Loc.initial, format, arg);
dmd.errors.error(Loc.initial, format, args);
errors = true;
}

Expand Down Expand Up @@ -2005,8 +1991,66 @@ private bool parseCommandLine(const ref Strings arguments, const size_t argc, re
{
// Ignore
}
else if (arg == "-release") // https://dlang.org/dmd.html#switch-release
params.release = true;
else if (startsWith(p + 1, "release")) // https://dlang.org/dmd.html#switch-release
{
// Parse:
// -release[=assert[,in,out,invariant]]
if (p["-release".length] == '=')
{
if (arg.length == "-release=".length)
goto Lnoarg;

auto tail = arg["-release=".length .. $];
while (true)
{
auto delim = strchr(tail.ptr, ',');

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I thought we were not supporting CLI with comma ? After #7863

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm not familiar with the approved CLI interface for DIP1006, whether commas were approved. If commas were approved then we should establish a convention on when and when not to support commas, and then use a common implementation for comma support. If it were approved, I would think it would also apply to options like -i and -version...but this is all moot if commas are not approved

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I picked commas because it allows to override previous occurences of that argument, sometimes useful when using other peoples projects and Makefiles. Less of an argument for switches like include paths that are expected to be additive.

auto check = tail[0 .. delim ? delim - tail.ptr : $];
switch (check)
{
case "assert":
// can be overridden by -unittest
if (params.useAssert == CHECKENABLE._default)
params.useAssert = CHECKENABLE.off;
break;
case "in":
params.useIn = false;
break;
case "out":
params.useOut = false;
break;
case "invariant":
params.useInvariants = false;
break;
default:
error("unrecognized argument '%.*s' for -release=", cast(int)check.length, check.ptr);
break;
}
if (check.length == tail.length)
break;
tail = tail[check.length + ",".length .. $];
}
}
else if (arg.length > "-release".length)
goto Lerror;
else // -release
{
if (params.useAssert == CHECKENABLE._default)
params.useAssert = CHECKENABLE.off;
params.useIn = false;
params.useOut = false;
params.useInvariants = false;
}

// for additional linker flags
params.optimizeLinking = true;

if (params.useArrayBounds == CHECKENABLE._default)
params.useArrayBounds = CHECKENABLE.safeonly;

// switch without default is now a compile-time error anyhow
if (params.useSwitchError == CHECKENABLE._default)
params.useSwitchError = CHECKENABLE.off;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Do I understand correctly that those 2 get affected even if I just provide -release=invariant ? That sounds odd.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Switch fallthrough errors are dead, they're a compile-time error nowadays.

}
else if (arg == "-betterC") // https://dlang.org/dmd.html#switch-betterC
params.betterC = true;
else if (arg == "-noboundscheck") // https://dlang.org/dmd.html#switch-noboundscheck
Expand Down Expand Up @@ -2245,6 +2289,53 @@ private bool parseCommandLine(const ref Strings arguments, const size_t argc, re
return errors;
}

unittest
{
global.gag = 1;
immutable errorsave = global.errors;
scope (exit) { global.gag = 0; global.errors = errorsave; }

static bool test(const(char*)[] args, out Param params, out Strings files)
{
Strings sargs;
sargs.setDim(args.length + 1);
sargs[0] = "dmd";
foreach (i, ref sarg; sargs[1 .. sargs.dim])
sarg = args[i];
return !parseCommandLine(sargs, sargs.dim, params, files);
}

Param params;
Strings files;
assert(test(["-release"], params, files));
assert(params.optimizeLinking);
assert(params.useAssert == CHECKENABLE.off && !params.useIn && !params.useOut && !params.useInvariants);
assert(params.useArrayBounds == CHECKENABLE.safeonly && params.useSwitchError == CHECKENABLE.off);
assert(test(["-release=assert,in,out,invariant"], params, files));
assert(params.optimizeLinking);
assert(params.useAssert == CHECKENABLE.off && !params.useIn && !params.useOut && !params.useInvariants);
assert(params.useArrayBounds == CHECKENABLE.safeonly && params.useSwitchError == CHECKENABLE.off);
assert(test(["-release=in"], params, files));
assert(params.optimizeLinking);
assert(params.useAssert == CHECKENABLE._default);
assert(!params.useIn && params.useOut && params.useInvariants);
assert(params.useArrayBounds == CHECKENABLE.safeonly && params.useSwitchError == CHECKENABLE.off);
assert(test(["-release=in,out"], params, files));
assert(params.optimizeLinking);
assert(params.useAssert == CHECKENABLE._default && !params.useIn && !params.useOut && params.useInvariants);
assert(params.useArrayBounds == CHECKENABLE.safeonly && params.useSwitchError == CHECKENABLE.off);
assert(test(["-release=in,out,assert,invariant"], params, files));
assert(params.optimizeLinking);
assert(params.useAssert == CHECKENABLE.off && !params.useIn && !params.useOut && !params.useInvariants);
assert(params.useArrayBounds == CHECKENABLE.safeonly && params.useSwitchError == CHECKENABLE.off);

assert(!test(["-release="], params, files));
assert(!test(["-release=in,"], params, files));
assert(!test(["-release=foo"], params, files));
assert(!test(["-release=assert,in,put"], params, files));
assert(!test(["-release=assert;in"], params, files));
assert(!test(["-release:in,out"], params, files));
}

private __gshared bool includeImports = false;
// array of module patterns used to include/exclude imported modules
Expand Down
38 changes: 38 additions & 0 deletions test/runnable/test_dip1006.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// REQUIRED_ARGS: -release=in,out,invariant
// PERMUTE_ARGS:
class C
{
int foo(int a)
in { assert(a != 0); } // skipped
out(res) { assert(res != 0); } // skipped
body
{
return a;
}

invariant // skipped
{
assert(false);
}

void bar(int a)
{
assert(a != 0); // triggered
}
}

void main()
{
import core.exception : AssertError;

auto c = new C;
c.foo(0);

bool catched;
try
c.bar(0);
catch (AssertError e)
catched = true;
if (!catched)
assert(0);
}
32 changes: 32 additions & 0 deletions test/runnable/test_dip1006b.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// REQUIRED_ARGS: -release=in,invariant
// PERMUTE_ARGS:
class C
{
int foo(int a)
in { assert(a != 0); } // skipped
out(res) { assert(res != 0, "out"); } // triggered
body
{
return a;
}

invariant // skipped
{
assert(false);
}
}

void main()
{
import core.exception : AssertError;

auto c = new C;
bool catched;
try
c.foo(0);
catch (AssertError e)
catched = e.msg == "out";

if (!catched)
assert(0);
}