Internally these would treat the cast same as a normal conversion from int[7] to int[], which allows code at CTFE to erroneously succeed where it would raise a SEGV at run-time.
1 |
/**
|
|
2 |
* Lazily evaluate static conditions for `static if`, `static assert` and template constraints.
|
|
3 |
*
|
|
4 |
* Copyright: Copyright (C) 1999-2020 by The D Language Foundation, All Rights Reserved
|
|
5 |
* Authors: $(LINK2 http://www.digitalmars.com, Walter Bright)
|
|
6 |
* License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
|
|
7 |
* Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/staticcond.d, _staticcond.d)
|
|
8 |
* Documentation: https://dlang.org/phobos/dmd_staticcond.html
|
|
9 |
* Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/staticcond.d
|
|
10 |
*/
|
|
11 |
|
|
12 |
module dmd.staticcond; |
|
13 |
|
|
14 |
import dmd.arraytypes; |
|
15 |
import dmd.dmodule; |
|
16 |
import dmd.dscope; |
|
17 |
import dmd.dsymbol; |
|
18 |
import dmd.errors; |
|
19 |
import dmd.expression; |
|
20 |
import dmd.expressionsem; |
|
21 |
import dmd.globals; |
|
22 |
import dmd.identifier; |
|
23 |
import dmd.mtype; |
|
24 |
import dmd.root.array; |
|
25 |
import dmd.root.outbuffer; |
|
26 |
import dmd.tokens; |
|
27 |
|
|
28 |
|
|
29 |
|
|
30 |
/********************************************
|
|
31 |
* Semantically analyze and then evaluate a static condition at compile time.
|
|
32 |
* This is special because short circuit operators &&, || and ?: at the top
|
|
33 |
* level are not semantically analyzed if the result of the expression is not
|
|
34 |
* necessary.
|
|
35 |
* Params:
|
|
36 |
* sc = instantiating scope
|
|
37 |
* original = original expression, for error messages
|
|
38 |
* e = resulting expression
|
|
39 |
* errors = set to `true` if errors occurred
|
|
40 |
* negatives = array to store negative clauses
|
|
41 |
* Returns:
|
|
42 |
* true if evaluates to true
|
|
43 |
*/
|
|
44 |
bool evalStaticCondition(Scope* sc, Expression original, Expression e, out bool errors, Expressions* negatives = null) |
|
45 |
{
|
|
46 | 1 |
if (negatives) |
47 | 1 |
negatives.setDim(0); |
48 |
|
|
49 |
bool impl(Expression e) |
|
50 |
{
|
|
51 | 1 |
if (e.op == TOK.not) |
52 |
{
|
|
53 | 1 |
NotExp ne = cast(NotExp)e; |
54 | 1 |
return !impl(ne.e1); |
55 |
}
|
|
56 |
|
|
57 | 1 |
if (e.op == TOK.andAnd || e.op == TOK.orOr) |
58 |
{
|
|
59 | 1 |
LogicalExp aae = cast(LogicalExp)e; |
60 | 1 |
bool result = impl(aae.e1); |
61 | 1 |
if (errors) |
62 | 1 |
return false; |
63 | 1 |
if (e.op == TOK.andAnd) |
64 |
{
|
|
65 | 1 |
if (!result) |
66 | 1 |
return false; |
67 |
}
|
|
68 |
else
|
|
69 |
{
|
|
70 | 1 |
if (result) |
71 | 1 |
return true; |
72 |
}
|
|
73 | 1 |
result = impl(aae.e2); |
74 | 1 |
return !errors && result; |
75 |
}
|
|
76 |
|
|
77 | 1 |
if (e.op == TOK.question) |
78 |
{
|
|
79 | 1 |
CondExp ce = cast(CondExp)e; |
80 | 1 |
bool result = impl(ce.econd); |
81 | 1 |
if (errors) |
82 |
return false; |
|
83 | 1 |
Expression leg = result ? ce.e1 : ce.e2; |
84 | 1 |
result = impl(leg); |
85 | 1 |
return !errors && result; |
86 |
}
|
|
87 |
|
|
88 | 1 |
Expression before = e; |
89 | 1 |
const uint nerrors = global.errors; |
90 |
|
|
91 | 1 |
sc = sc.startCTFE(); |
92 | 1 |
sc.flags |= SCOPE.condition; |
93 |
|
|
94 | 1 |
e = e.expressionSemantic(sc); |
95 | 1 |
e = resolveProperties(sc, e); |
96 | 1 |
e = e.toBoolean(sc); |
97 |
|
|
98 | 1 |
sc = sc.endCTFE(); |
99 | 1 |
e = e.optimize(WANTvalue); |
100 |
|
|
101 | 1 |
if (nerrors != global.errors || |
102 | 1 |
e.op == TOK.error || |
103 | 1 |
e.type.toBasetype() == Type.terror) |
104 |
{
|
|
105 | 1 |
errors = true; |
106 | 1 |
return false; |
107 |
}
|
|
108 |
|
|
109 | 1 |
e = e.ctfeInterpret(); |
110 |
|
|
111 | 1 |
if (e.isBool(true)) |
112 | 1 |
return true; |
113 | 1 |
else if (e.isBool(false)) |
114 |
{
|
|
115 | 1 |
if (negatives) |
116 | 1 |
negatives.push(before); |
117 | 1 |
return false; |
118 |
}
|
|
119 |
|
|
120 | 1 |
e.error("expression `%s` is not constant", e.toChars()); |
121 | 1 |
errors = true; |
122 | 1 |
return false; |
123 |
}
|
|
124 | 1 |
return impl(e); |
125 |
}
|
|
126 |
|
|
127 |
/********************************************
|
|
128 |
* Format a static condition as a tree-like structure, marking failed and
|
|
129 |
* bypassed expressions.
|
|
130 |
* Params:
|
|
131 |
* original = original expression
|
|
132 |
* instantiated = instantiated expression
|
|
133 |
* negatives = array with negative clauses from `instantiated` expression
|
|
134 |
* full = controls whether it shows the full output or only failed parts
|
|
135 |
* itemCount = returns the number of written clauses
|
|
136 |
* Returns:
|
|
137 |
* formatted string or `null` if the expressions were `null`, or if the
|
|
138 |
* instantiated expression is not based on the original one
|
|
139 |
*/
|
|
140 |
const(char)* visualizeStaticCondition(Expression original, Expression instantiated, |
|
141 |
const Expression[] negatives, bool full, ref uint itemCount) |
|
142 |
{
|
|
143 | 1 |
if (!original || !instantiated || original.loc !is instantiated.loc) |
144 | 1 |
return null; |
145 |
|
|
146 | 1 |
OutBuffer buf; |
147 |
|
|
148 | 1 |
if (full) |
149 | 1 |
itemCount = visualizeFull(original, instantiated, negatives, buf); |
150 |
else
|
|
151 | 1 |
itemCount = visualizeShort(original, instantiated, negatives, buf); |
152 |
|
|
153 | 1 |
return buf.extractChars(); |
154 |
}
|
|
155 |
|
|
156 |
private uint visualizeFull(Expression original, Expression instantiated, |
|
157 |
const Expression[] negatives, ref OutBuffer buf) |
|
158 |
{
|
|
159 |
// tree-like structure; traverse and format simultaneously
|
|
160 | 1 |
uint count; |
161 | 1 |
uint indent; |
162 |
|
|
163 |
static void printOr(uint indent, ref OutBuffer buf) |
|
164 |
{
|
|
165 |
buf.reserve(indent * 4 + 8); |
|
166 |
foreach (i; 0 .. indent) |
|
167 |
buf.writestring(" "); |
|
168 |
buf.writestring(" or:\n"); |
|
169 |
}
|
|
170 |
|
|
171 |
// returns true if satisfied
|
|
172 |
bool impl(Expression orig, Expression e, bool inverted, bool orOperand, bool unreached) |
|
173 |
{
|
|
174 | 1 |
TOK op = orig.op; |
175 |
|
|
176 |
// lower all 'not' to the bottom
|
|
177 |
// !(A && B) -> !A || !B
|
|
178 |
// !(A || B) -> !A && !B
|
|
179 | 1 |
if (inverted) |
180 |
{
|
|
181 |
if (op == TOK.andAnd) |
|
182 |
op = TOK.orOr; |
|
183 |
else if (op == TOK.orOr) |
|
184 |
op = TOK.andAnd; |
|
185 |
}
|
|
186 |
|
|
187 | 1 |
if (op == TOK.not) |
188 |
{
|
|
189 |
NotExp no = cast(NotExp)orig; |
|
190 |
NotExp ne = cast(NotExp)e; |
|
191 |
assert(ne); |
|
192 |
return impl(no.e1, ne.e1, !inverted, orOperand, unreached); |
|
193 |
}
|
|
194 | 1 |
else if (op == TOK.andAnd) |
195 |
{
|
|
196 |
BinExp bo = cast(BinExp)orig; |
|
197 |
BinExp be = cast(BinExp)e; |
|
198 |
assert(be); |
|
199 |
const r1 = impl(bo.e1, be.e1, inverted, false, unreached); |
|
200 |
const r2 = impl(bo.e2, be.e2, inverted, false, unreached || !r1); |
|
201 |
return r1 && r2; |
|
202 |
}
|
|
203 | 1 |
else if (op == TOK.orOr) |
204 |
{
|
|
205 |
if (!orOperand) // do not indent A || B || C twice |
|
206 |
indent++; |
|
207 |
BinExp bo = cast(BinExp)orig; |
|
208 |
BinExp be = cast(BinExp)e; |
|
209 |
assert(be); |
|
210 |
const r1 = impl(bo.e1, be.e1, inverted, true, unreached); |
|
211 |
printOr(indent, buf); |
|
212 |
const r2 = impl(bo.e2, be.e2, inverted, true, unreached); |
|
213 |
if (!orOperand) |
|
214 |
indent--; |
|
215 |
return r1 || r2; |
|
216 |
}
|
|
217 | 1 |
else if (op == TOK.question) |
218 |
{
|
|
219 |
CondExp co = cast(CondExp)orig; |
|
220 |
CondExp ce = cast(CondExp)e; |
|
221 |
assert(ce); |
|
222 |
if (!inverted) |
|
223 |
{
|
|
224 |
// rewrite (A ? B : C) as (A && B || !A && C)
|
|
225 |
if (!orOperand) |
|
226 |
indent++; |
|
227 |
const r1 = impl(co.econd, ce.econd, inverted, false, unreached); |
|
228 |
const r2 = impl(co.e1, ce.e1, inverted, false, unreached || !r1); |
|
229 |
printOr(indent, buf); |
|
230 |
const r3 = impl(co.econd, ce.econd, !inverted, false, unreached); |
|
231 |
const r4 = impl(co.e2, ce.e2, inverted, false, unreached || !r3); |
|
232 |
if (!orOperand) |
|
233 |
indent--; |
|
234 |
return r1 && r2 || r3 && r4; |
|
235 |
}
|
|
236 |
else
|
|
237 |
{
|
|
238 |
// rewrite !(A ? B : C) as (!A || !B) && (A || !C)
|
|
239 |
if (!orOperand) |
|
240 |
indent++; |
|
241 |
const r1 = impl(co.econd, ce.econd, inverted, false, unreached); |
|
242 |
printOr(indent, buf); |
|
243 |
const r2 = impl(co.e1, ce.e1, inverted, false, unreached); |
|
244 |
const r12 = r1 || r2; |
|
245 |
const r3 = impl(co.econd, ce.econd, !inverted, false, unreached || !r12); |
|
246 |
printOr(indent, buf); |
|
247 |
const r4 = impl(co.e2, ce.e2, inverted, false, unreached || !r12); |
|
248 |
if (!orOperand) |
|
249 |
indent--; |
|
250 |
return (r1 || r2) && (r3 || r4); |
|
251 |
}
|
|
252 |
}
|
|
253 |
else // 'primitive' expression |
|
254 |
{
|
|
255 | 1 |
buf.reserve(indent * 4 + 4); |
256 | 1 |
foreach (i; 0 .. indent) |
257 |
buf.writestring(" "); |
|
258 |
|
|
259 |
// find its value; it may be not computed, if there was a short circuit,
|
|
260 |
// but we handle this case with `unreached` flag
|
|
261 | 1 |
bool value = true; |
262 | 1 |
if (!unreached) |
263 |
{
|
|
264 | 1 |
foreach (fe; negatives) |
265 |
{
|
|
266 | 1 |
if (fe is e) |
267 |
{
|
|
268 | 1 |
value = false; |
269 | 1 |
break; |
270 |
}
|
|
271 |
}
|
|
272 |
}
|
|
273 |
// write the marks first
|
|
274 | 1 |
const satisfied = inverted ? !value : value; |
275 | 1 |
if (!satisfied && !unreached) |
276 | 1 |
buf.writestring(" > "); |
277 |
else if (unreached) |
|
278 |
buf.writestring(" - "); |
|
279 |
else
|
|
280 |
buf.writestring(" "); |
|
281 |
// then the expression itself
|
|
282 | 1 |
if (inverted) |
283 |
buf.writeByte('!'); |
|
284 | 1 |
buf.writestring(orig.toChars); |
285 | 1 |
buf.writenl(); |
286 | 1 |
count++; |
287 | 1 |
return satisfied; |
288 |
}
|
|
289 |
}
|
|
290 |
|
|
291 | 1 |
impl(original, instantiated, false, true, false); |
292 | 1 |
return count; |
293 |
}
|
|
294 |
|
|
295 |
private uint visualizeShort(Expression original, Expression instantiated, |
|
296 |
const Expression[] negatives, ref OutBuffer buf) |
|
297 |
{
|
|
298 |
// simple list; somewhat similar to long version, so no comments
|
|
299 |
// one difference is that it needs to hold items to display in a stack
|
|
300 |
|
|
301 |
static struct Item |
|
302 |
{
|
|
303 |
Expression orig; |
|
304 |
bool inverted; |
|
305 |
}
|
|
306 |
|
|
307 | 1 |
Array!Item stack; |
308 |
|
|
309 |
bool impl(Expression orig, Expression e, bool inverted) |
|
310 |
{
|
|
311 | 1 |
TOK op = orig.op; |
312 |
|
|
313 | 1 |
if (inverted) |
314 |
{
|
|
315 | 1 |
if (op == TOK.andAnd) |
316 | 1 |
op = TOK.orOr; |
317 | 1 |
else if (op == TOK.orOr) |
318 | 1 |
op = TOK.andAnd; |
319 |
}
|
|
320 |
|
|
321 | 1 |
if (op == TOK.not) |
322 |
{
|
|
323 | 1 |
NotExp no = cast(NotExp)orig; |
324 | 1 |
NotExp ne = cast(NotExp)e; |
325 | 1 |
assert(ne); |
326 | 1 |
return impl(no.e1, ne.e1, !inverted); |
327 |
}
|
|
328 | 1 |
else if (op == TOK.andAnd) |
329 |
{
|
|
330 | 1 |
BinExp bo = cast(BinExp)orig; |
331 | 1 |
BinExp be = cast(BinExp)e; |
332 | 1 |
assert(be); |
333 | 1 |
bool r = impl(bo.e1, be.e1, inverted); |
334 | 1 |
r = r && impl(bo.e2, be.e2, inverted); |
335 | 1 |
return r; |
336 |
}
|
|
337 | 1 |
else if (op == TOK.orOr) |
338 |
{
|
|
339 | 1 |
BinExp bo = cast(BinExp)orig; |
340 | 1 |
BinExp be = cast(BinExp)e; |
341 | 1 |
assert(be); |
342 | 1 |
const lbefore = stack.length; |
343 | 1 |
bool r = impl(bo.e1, be.e1, inverted); |
344 | 1 |
r = r || impl(bo.e2, be.e2, inverted); |
345 | 1 |
if (r) |
346 | 1 |
stack.setDim(lbefore); // purge added positive items |
347 | 1 |
return r; |
348 |
}
|
|
349 | 1 |
else if (op == TOK.question) |
350 |
{
|
|
351 | 1 |
CondExp co = cast(CondExp)orig; |
352 | 1 |
CondExp ce = cast(CondExp)e; |
353 | 1 |
assert(ce); |
354 | 1 |
if (!inverted) |
355 |
{
|
|
356 | 1 |
const lbefore = stack.length; |
357 | 1 |
bool a = impl(co.econd, ce.econd, inverted); |
358 | 1 |
a = a && impl(co.e1, ce.e1, inverted); |
359 | 1 |
bool b; |
360 | 1 |
if (!a) |
361 |
{
|
|
362 | 1 |
b = impl(co.econd, ce.econd, !inverted); |
363 | 1 |
b = b && impl(co.e2, ce.e2, inverted); |
364 |
}
|
|
365 | 1 |
const r = a || b; |
366 | 1 |
if (r) |
367 |
stack.setDim(lbefore); |
|
368 | 1 |
return r; |
369 |
}
|
|
370 |
else
|
|
371 |
{
|
|
372 | 1 |
bool a; |
373 |
{
|
|
374 | 1 |
const lbefore = stack.length; |
375 | 1 |
a = impl(co.econd, ce.econd, inverted); |
376 | 1 |
a = a || impl(co.e1, ce.e1, inverted); |
377 | 1 |
if (a) |
378 | 1 |
stack.setDim(lbefore); |
379 |
}
|
|
380 | 1 |
bool b; |
381 | 1 |
if (a) |
382 |
{
|
|
383 | 1 |
const lbefore = stack.length; |
384 | 1 |
b = impl(co.econd, ce.econd, !inverted); |
385 | 1 |
b = b || impl(co.e2, ce.e2, inverted); |
386 | 1 |
if (b) |
387 | 1 |
stack.setDim(lbefore); |
388 |
}
|
|
389 | 1 |
return a && b; |
390 |
}
|
|
391 |
}
|
|
392 |
else // 'primitive' expression |
|
393 |
{
|
|
394 | 1 |
bool value = true; |
395 | 1 |
foreach (fe; negatives) |
396 |
{
|
|
397 | 1 |
if (fe is e) |
398 |
{
|
|
399 | 1 |
value = false; |
400 | 1 |
break; |
401 |
}
|
|
402 |
}
|
|
403 | 1 |
const satisfied = inverted ? !value : value; |
404 | 1 |
if (!satisfied) |
405 | 1 |
stack.push(Item(orig, inverted)); |
406 | 1 |
return satisfied; |
407 |
}
|
|
408 |
}
|
|
409 |
|
|
410 | 1 |
impl(original, instantiated, false); |
411 |
|
|
412 | 1 |
foreach (i; 0 .. stack.length) |
413 |
{
|
|
414 |
// write the expression only
|
|
415 | 1 |
buf.writestring(" "); |
416 | 1 |
if (stack[i].inverted) |
417 | 1 |
buf.writeByte('!'); |
418 | 1 |
buf.writestring(stack[i].orig.toChars); |
419 |
// here with no trailing newline
|
|
420 | 1 |
if (i + 1 < stack.length) |
421 | 1 |
buf.writenl(); |
422 |
}
|
|
423 | 1 |
return cast(uint)stack.length; |
424 |
}
|
Read our documentation on viewing source code .