Skip to content

Commit bad68d6

Browse files
committed
implement DIP1006 - more selective control over run-time checks
- allow to disable only some run-time checks e.g. with -release=in,out,invariant - cli was proposed in DIP1006 review discussion https://forum.dlang.org/post/rsafosvkhxddkxptaziy@forum.dlang.org but hasn't yet been added to https://github.com/dlang/DIPs/blob/master/DIPs/DIP1006.md
1 parent d91b678 commit bad68d6

7 files changed

Lines changed: 199 additions & 31 deletions

File tree

changelog/dip1006.dd

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
allow to disable only specific run-time checks
2+
3+
The $(LINK2 $(TT -release), https://dlang.org/dmd-linux.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.
4+
Also see $(LINK2 DIP1006, https://github.com/dlang/DIPs/blob/master/DIPs/DIP1006.md).

src/dmd/cli.d

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -508,12 +508,15 @@ dmd -cov -unittest myprog.d
508508
to the file $(TT profilegc.log) upon program termination.)
509509
)`,
510510
),
511-
Option("release",
512-
"compile release version",
513-
`Compile release version, which means not emitting run-time
514-
checks for contracts and asserts. Array bounds checking is not
515-
done for system and trusted functions, and assertion failures
516-
are undefined behaviour.`
511+
Option("release[=<assert,in,out,invariant>]",
512+
"disable run-time checks",
513+
`Disable in-/out-contracts, invariants, and assertions.
514+
It is possible to disable only some run-time checks by specifying them
515+
as comma separated arguments (e.g. $(TT -release=assert,invariant)).
516+
By default $(TT $(SWLINK -release)) will disable all run-time checks.
517+
Any $(TT $(SWLINK -release)) switch furthermore sets the default of
518+
$(SWLINK -boundscheck) to safeonly.
519+
$(P Note that $(SWLINK -unittest) (re-)enables assertions in unittests and function bodies.)`
517520
),
518521
Option("run <srcfile>",
519522
"compile, link, and run the program srcfile",

src/dmd/globals.d

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ struct Param
127127
bool useUnitTests; // generate unittest code
128128
bool useInline = false; // inline expand functions
129129
bool useDIP25; // implement http://wiki.dlang.org/DIP25
130-
bool release; // build release version
130+
bool linkRelease; // use optimized linker settings
131131
bool preservePaths; // true means don't strip path from source file
132132
// 0: disable warnings
133133
// 1: warnings as errors

src/dmd/link.d

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -224,8 +224,8 @@ public int runLINK()
224224
{
225225
cmdbuf.writeByte(' ');
226226
cmdbuf.writestring("/DEBUG");
227-
// in release mode we need to reactivate /OPT:REF after /DEBUG
228-
if (global.params.release)
227+
// in release mode we reactivate /OPT:REF after /DEBUG to still minimize binary size
228+
if (global.params.linkRelease)
229229
cmdbuf.writestring(" /OPT:REF");
230230
}
231231
if (global.params.dll)

src/dmd/mars.d

Lines changed: 113 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -393,37 +393,23 @@ private int tryMain(size_t argc, const(char)** argv)
393393
global.params.mscrtlib = vsopt.defaultRuntimeLibrary(global.params.is64bit);
394394
}
395395
}
396-
if (global.params.release)
397-
{
398-
global.params.useInvariants = false;
399-
global.params.useIn = false;
400-
global.params.useOut = false;
401-
402-
if (global.params.useArrayBounds == CHECKENABLE._default)
403-
global.params.useArrayBounds = CHECKENABLE.safeonly;
404-
405-
if (global.params.useAssert == CHECKENABLE._default)
406-
global.params.useAssert = CHECKENABLE.off;
407-
408-
if (global.params.useSwitchError == CHECKENABLE._default)
409-
global.params.useSwitchError = CHECKENABLE.off;
410-
}
411396
if (global.params.betterC)
412397
{
413398
global.params.checkAction = CHECKACTION.C;
414399
global.params.useModuleInfo = false;
415400
global.params.useTypeInfo = false;
416401
global.params.useExceptions = false;
417402
}
403+
// -release -unittest enables assertions in unittests AND function bodies
418404
if (global.params.useUnitTests)
419405
global.params.useAssert = CHECKENABLE.on;
420406

421-
if (global.params.useArrayBounds == CHECKENABLE._default)
422-
global.params.useArrayBounds = CHECKENABLE.on;
423-
424407
if (global.params.useAssert == CHECKENABLE._default)
425408
global.params.useAssert = CHECKENABLE.on;
426409

410+
if (global.params.useArrayBounds == CHECKENABLE._default)
411+
global.params.useArrayBounds = CHECKENABLE.on;
412+
427413
if (global.params.useSwitchError == CHECKENABLE._default)
428414
global.params.useSwitchError = CHECKENABLE.on;
429415

@@ -1488,9 +1474,9 @@ private bool parseCommandLine(const ref Strings arguments, const size_t argc, re
14881474
{
14891475
bool errors;
14901476

1491-
void error(const(char)* format, const(char*) arg = null)
1477+
void error(Args...)(const(char)* format, Args args)
14921478
{
1493-
dmd.errors.error(Loc.initial, format, arg);
1479+
dmd.errors.error(Loc.initial, format, args);
14941480
errors = true;
14951481
}
14961482

@@ -1992,8 +1978,66 @@ private bool parseCommandLine(const ref Strings arguments, const size_t argc, re
19921978
{
19931979
// Ignore
19941980
}
1995-
else if (arg == "-release") // https://dlang.org/dmd.html#switch-release
1996-
params.release = true;
1981+
else if (startsWith(p + 1, "release")) // https://dlang.org/dmd.html#switch-release
1982+
{
1983+
// Parse:
1984+
// -release[=assert[,in,out,invariant]]
1985+
if (p["-release".length] == '=')
1986+
{
1987+
if (arg.length == "-release=".length)
1988+
goto Lnoarg;
1989+
1990+
auto tail = arg["-release=".length .. $];
1991+
while (true)
1992+
{
1993+
auto delim = strchr(tail.ptr, ',');
1994+
auto check = tail[0 .. delim ? delim - tail.ptr : $];
1995+
switch (check)
1996+
{
1997+
case "assert":
1998+
// can be overridden by -unittest
1999+
if (params.useAssert == CHECKENABLE._default)
2000+
params.useAssert = CHECKENABLE.off;
2001+
break;
2002+
case "in":
2003+
params.useIn = false;
2004+
break;
2005+
case "out":
2006+
params.useOut = false;
2007+
break;
2008+
case "invariant":
2009+
params.useInvariants = false;
2010+
break;
2011+
default:
2012+
error("unrecognized argument '%.*s' for -release=", cast(int)check.length, check.ptr);
2013+
break;
2014+
}
2015+
if (check.length == tail.length)
2016+
break;
2017+
tail = tail[check.length + ",".length .. $];
2018+
}
2019+
}
2020+
else if (arg.length > "-release".length)
2021+
goto Lerror;
2022+
else // -release
2023+
{
2024+
if (params.useAssert == CHECKENABLE._default)
2025+
params.useAssert = CHECKENABLE.off;
2026+
params.useIn = false;
2027+
params.useOut = false;
2028+
params.useInvariants = false;
2029+
}
2030+
2031+
// for additional linker flags
2032+
params.linkRelease = true;
2033+
2034+
if (params.useArrayBounds == CHECKENABLE._default)
2035+
params.useArrayBounds = CHECKENABLE.safeonly;
2036+
2037+
// switch without default is now a compile-time error anyhow
2038+
if (params.useSwitchError == CHECKENABLE._default)
2039+
params.useSwitchError = CHECKENABLE.off;
2040+
}
19972041
else if (arg == "-betterC") // https://dlang.org/dmd.html#switch-betterC
19982042
params.betterC = true;
19992043
else if (arg == "-noboundscheck") // https://dlang.org/dmd.html#switch-noboundscheck
@@ -2232,6 +2276,53 @@ private bool parseCommandLine(const ref Strings arguments, const size_t argc, re
22322276
return errors;
22332277
}
22342278

2279+
unittest
2280+
{
2281+
global.gag = 1;
2282+
immutable errorsave = global.errors;
2283+
scope (exit) { global.gag = 0; global.errors = errorsave; }
2284+
2285+
static bool test(const(char*)[] args, out Param params, out Strings files)
2286+
{
2287+
Strings sargs;
2288+
sargs.setDim(args.length + 1);
2289+
sargs[0] = "dmd";
2290+
foreach (i, ref sarg; sargs[1 .. sargs.dim])
2291+
sarg = args[i];
2292+
return !parseCommandLine(sargs, sargs.dim, params, files);
2293+
}
2294+
2295+
Param params;
2296+
Strings files;
2297+
assert(test(["-release"], params, files));
2298+
assert(params.release);
2299+
assert(params.useAssert == CHECKENABLE.off && !params.useIn && !params.useOut && !params.useInvariants);
2300+
assert(params.useArrayBounds == CHECKENABLE.safeonly && params.useSwitchError == CHECKENABLE.off);
2301+
assert(test(["-release=assert,in,out,invariant"], params, files));
2302+
assert(params.release);
2303+
assert(params.useAssert == CHECKENABLE.off && !params.useIn && !params.useOut && !params.useInvariants);
2304+
assert(params.useArrayBounds == CHECKENABLE.safeonly && params.useSwitchError == CHECKENABLE.off);
2305+
assert(test(["-release=in"], params, files));
2306+
assert(params.release);
2307+
assert(params.useAssert == CHECKENABLE._default);
2308+
assert(!params.useIn && params.useOut && params.useInvariants);
2309+
assert(params.useArrayBounds == CHECKENABLE.safeonly && params.useSwitchError == CHECKENABLE.off);
2310+
assert(test(["-release=in,out"], params, files));
2311+
assert(params.release);
2312+
assert(params.useAssert == CHECKENABLE._default && !params.useIn && !params.useOut && params.useInvariants);
2313+
assert(params.useArrayBounds == CHECKENABLE.safeonly && params.useSwitchError == CHECKENABLE.off);
2314+
assert(test(["-release=in,out,assert,invariant"], params, files));
2315+
assert(params.release);
2316+
assert(params.useAssert == CHECKENABLE.off && !params.useIn && !params.useOut && !params.useInvariants);
2317+
assert(params.useArrayBounds == CHECKENABLE.safeonly && params.useSwitchError == CHECKENABLE.off);
2318+
2319+
assert(!test(["-release="], params, files));
2320+
assert(!test(["-release=in,"], params, files));
2321+
assert(!test(["-release=foo"], params, files));
2322+
assert(!test(["-release=assert,in,put"], params, files));
2323+
assert(!test(["-release=assert;in"], params, files));
2324+
assert(!test(["-release:in,out"], params, files));
2325+
}
22352326

22362327
private __gshared bool includeImports = false;
22372328
// array of module patterns used to include/exclude imported modules

test/runnable/test_dip1006.d

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// REQUIRED_ARGS: -release=in,out,invariant
2+
// PERMUTE_ARGS:
3+
class C
4+
{
5+
int foo(int a)
6+
in { assert(a != 0); } // skipped
7+
out(res) { assert(res != 0); } // skipped
8+
body
9+
{
10+
return a;
11+
}
12+
13+
invariant // skipped
14+
{
15+
assert(false);
16+
}
17+
18+
void bar(int a)
19+
{
20+
assert(a != 0); // triggered
21+
}
22+
}
23+
24+
void main()
25+
{
26+
import core.exception : AssertError;
27+
28+
auto c = new C;
29+
c.foo(0);
30+
31+
bool catched;
32+
try
33+
c.bar(0);
34+
catch (AssertError e)
35+
catched = true;
36+
if (!catched)
37+
assert(0);
38+
}

test/runnable/test_dip1006b.d

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// REQUIRED_ARGS: -release=in,invariant
2+
// PERMUTE_ARGS:
3+
class C
4+
{
5+
int foo(int a)
6+
in { assert(a != 0); } // skipped
7+
out(res) { assert(res != 0, "out"); } // triggered
8+
body
9+
{
10+
return a;
11+
}
12+
13+
invariant // skipped
14+
{
15+
assert(false);
16+
}
17+
}
18+
19+
void main()
20+
{
21+
import core.exception : AssertError;
22+
23+
auto c = new C;
24+
bool catched;
25+
try
26+
c.foo(0);
27+
catch (AssertError e)
28+
catched = e.msg == "out";
29+
30+
if (!catched)
31+
assert(0);
32+
}

0 commit comments

Comments
 (0)