1 /**
2  * Compiler implementation of the
3  * $(LINK2 http://www.dlang.org, D programming language).
4  *
5  * Copyright:   Copyright (c) 1999-2016 by Digital Mars, All Rights Reserved
6  * Authors:     $(LINK2 http://www.digitalmars.com, Walter Bright)
7  * License:     $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
8  * Source:      $(DMDSRC _cond.d)
9  */
10 
11 module ddmd.cond;
12 
13 import core.stdc..string;
14 import ddmd.arraytypes;
15 import ddmd.dmodule;
16 import ddmd.dscope;
17 import ddmd.dsymbol;
18 import ddmd.errors;
19 import ddmd.expression;
20 import ddmd.globals;
21 import ddmd.identifier;
22 import ddmd.mtype;
23 import ddmd.root.outbuffer;
24 import ddmd.root.rootobject;
25 import ddmd.tokens;
26 import ddmd.utils;
27 import ddmd.visitor;
28 import ddmd.id;
29 
30 
31 /***********************************************************
32  */
33 extern (C++) abstract class Condition : RootObject
34 {
35     Loc loc;
36     // 0: not computed yet
37     // 1: include
38     // 2: do not include
39     int inc;
40 
41     final extern (D) this(Loc loc)
42     {
43         this.loc = loc;
44     }
45 
46     abstract Condition syntaxCopy();
47 
48     abstract int include(Scope* sc, ScopeDsymbol sds);
49 
50     DebugCondition isDebugCondition()
51     {
52         return null;
53     }
54 
55     void accept(Visitor v)
56     {
57         v.visit(this);
58     }
59 }
60 
61 /***********************************************************
62  */
63 extern (C++) class DVCondition : Condition
64 {
65     uint level;
66     Identifier ident;
67     Module mod;
68 
69     final extern (D) this(Module mod, uint level, Identifier ident)
70     {
71         super(Loc());
72         this.mod = mod;
73         this.level = level;
74         this.ident = ident;
75     }
76 
77     override final Condition syntaxCopy()
78     {
79         return this; // don't need to copy
80     }
81 
82     override void accept(Visitor v)
83     {
84         v.visit(this);
85     }
86 }
87 
88 /***********************************************************
89  */
90 extern (C++) final class DebugCondition : DVCondition
91 {
92     /**
93      * Set the global debug level
94      *
95      * Only called from the driver
96      *
97      * Params:
98      *   level = Integer literal to set the global version to
99      */
100     static void setGlobalLevel(uint level)
101     {
102         global.params.debuglevel = level;
103     }
104 
105 
106     /**
107      * Add an user-supplied identifier to the list of global debug identifiers
108      *
109      * Can be called from either the driver or a `debug = Ident;` statement.
110      * Unlike version identifier, there isn't any reserved debug identifier
111      * so no validation takes place.
112      *
113      * Params:
114      *   ident = identifier to add
115      */
116     deprecated("Kept for C++ compat - Use the string overload instead")
117     static void addGlobalIdent(const(char)* ident)
118     {
119         addGlobalIdent(ident[0 .. ident.strlen]);
120     }
121 
122     /// Ditto
123     extern(D) static void addGlobalIdent(string ident)
124     {
125         // Overload necessary for string literals
126         addGlobalIdent(cast(const(char)[])ident);
127     }
128 
129 
130     /// Ditto
131     extern(D) static void addGlobalIdent(const(char)[] ident)
132     {
133         if (!global.params.debugids)
134             global.params.debugids = new Strings();
135         global.params.debugids.push(cast(char*)ident);
136     }
137 
138 
139     /**
140      * Instantiate a new `DebugCondition`
141      *
142      * Params:
143      *   mod = Module this node belongs to
144      *   level = Minimum global level this condition needs to pass.
145      *           Only used if `ident` is `null`.
146      *   ident = Identifier required for this condition to pass.
147      *           If `null`, this conditiion will use an integer level.
148      */
149     extern (D) this(Module mod, uint level, Identifier ident)
150     {
151         super(mod, level, ident);
152     }
153 
154     override int include(Scope* sc, ScopeDsymbol sds)
155     {
156         //printf("DebugCondition::include() level = %d, debuglevel = %d\n", level, global.params.debuglevel);
157         if (inc == 0)
158         {
159             inc = 2;
160             bool definedInModule = false;
161             if (ident)
162             {
163                 if (findCondition(mod.debugids, ident))
164                 {
165                     inc = 1;
166                     definedInModule = true;
167                 }
168                 else if (findCondition(global.params.debugids, ident))
169                     inc = 1;
170                 else
171                 {
172                     if (!mod.debugidsNot)
173                         mod.debugidsNot = new Strings();
174                     mod.debugidsNot.push(ident.toChars());
175                 }
176             }
177             else if (level <= global.params.debuglevel || level <= mod.debuglevel)
178                 inc = 1;
179             if (!definedInModule)
180                 printDepsConditional(sc, this, "depsDebug ");
181         }
182         return (inc == 1);
183     }
184 
185     override DebugCondition isDebugCondition()
186     {
187         return this;
188     }
189 
190     override void accept(Visitor v)
191     {
192         v.visit(this);
193     }
194 
195     override const(char)* toChars()
196     {
197         return ident ? ident.toChars() : "debug".ptr;
198     }
199 }
200 
201 /**
202  * Node to represent a version condition
203  *
204  * A version condition is of the form:
205  * ---
206  * version (Identifier)
207  * ---
208  * In user code.
209  * This class also provides means to add version identifier
210  * to the list of global (cross module) identifiers.
211  */
212 extern (C++) final class VersionCondition : DVCondition
213 {
214     /**
215      * Set the global version level
216      *
217      * Only called from the driver
218      *
219      * Params:
220      *   level = Integer literal to set the global version to
221      */
222     static void setGlobalLevel(uint level)
223     {
224         global.params.versionlevel = level;
225     }
226 
227     /**
228      * Check if a given version identifier is reserved.
229      *
230      * Reserved identifier are the one documented below or
231      * those starting with 'D_'.
232      *
233      * Params:
234      *   ident = identifier being checked
235      *
236      * Returns:
237      *   `true` if it is reserved, `false` otherwise
238      */
239     extern(D) private static bool isReserved(const(char)[] ident)
240     {
241         // This list doesn't include "D_*" versions, see the last return
242         static immutable string[] reserved =
243         [
244             "DigitalMars",
245             "GNU",
246             "LDC",
247             "SDC",
248             "Windows",
249             "Win32",
250             "Win64",
251             "linux",
252             "OSX",
253             "iOS",
254             "TVOS",
255             "WatchOS",
256             "FreeBSD",
257             "OpenBSD",
258             "NetBSD",
259             "DragonFlyBSD",
260             "BSD",
261             "Solaris",
262             "Posix",
263             "AIX",
264             "Haiku",
265             "SkyOS",
266             "SysV3",
267             "SysV4",
268             "Hurd",
269             "Android",
270             "PlayStation",
271             "PlayStation4",
272             "Cygwin",
273             "MinGW",
274             "FreeStanding",
275             "X86",
276             "X86_64",
277             "ARM",
278             "ARM_Thumb",
279             "ARM_SoftFloat",
280             "ARM_SoftFP",
281             "ARM_HardFloat",
282             "AArch64",
283             "Epiphany",
284             "PPC",
285             "PPC_SoftFloat",
286             "PPC_HardFloat",
287             "PPC64",
288             "IA64",
289             "MIPS32",
290             "MIPS64",
291             "MIPS_O32",
292             "MIPS_N32",
293             "MIPS_O64",
294             "MIPS_N64",
295             "MIPS_EABI",
296             "MIPS_SoftFloat",
297             "MIPS_HardFloat",
298             "NVPTX",
299             "NVPTX64",
300             "SPARC",
301             "SPARC_V8Plus",
302             "SPARC_SoftFloat",
303             "SPARC_HardFloat",
304             "SPARC64",
305             "S390",
306             "S390X",
307             "SystemZ",
308             "HPPA",
309             "HPPA64",
310             "SH",
311             "SH64",
312             "Alpha",
313             "Alpha_SoftFloat",
314             "Alpha_HardFloat",
315             "LittleEndian",
316             "BigEndian",
317             "ELFv1",
318             "ELFv2",
319             "CRuntime_Bionic",
320             "CRuntime_DigitalMars",
321             "CRuntime_Glibc",
322             "CRuntime_Microsoft",
323             "unittest",
324             "assert",
325             "all",
326             "none"
327         ];
328         foreach (r; reserved)
329         {
330             if (ident == r)
331                 return true;
332         }
333         return (ident.length >= 2 && ident[0 .. 2] == "D_");
334     }
335 
336     /**
337      * Raises an error if a version identifier is reserved.
338      *
339      * Called when setting a version identifier, e.g. `-version=identifier`
340      * parameter to the compiler or `version = Foo` in user code.
341      *
342      * Params:
343      *   loc = Where the identifier is set
344      *   ident = identifier being checked (ident[$] must be '\0')
345      */
346     extern(D) static void checkReserved(Loc loc, const(char)[] ident)
347     {
348         if (isReserved(ident))
349             error(loc, "version identifier '%s' is reserved and cannot be set",
350                   ident.ptr);
351     }
352 
353     /**
354      * Add an user-supplied global identifier to the list
355      *
356      * Only called from the driver for `-version=Ident` parameters.
357      * Will raise an error if the identifier is reserved.
358      *
359      * Params:
360      *   ident = identifier to add
361      */
362     deprecated("Kept for C++ compat - Use the string overload instead")
363     static void addGlobalIdent(const(char)* ident)
364     {
365         addGlobalIdent(ident[0 .. ident.strlen]);
366     }
367 
368     /// Ditto
369     extern(D) static void addGlobalIdent(string ident)
370     {
371         // Overload necessary for string literals
372         addGlobalIdent(cast(const(char)[])ident);
373     }
374 
375 
376     /// Ditto
377     extern(D) static void addGlobalIdent(const(char)[] ident)
378     {
379         checkReserved(Loc(), ident);
380         addPredefinedGlobalIdent(ident);
381     }
382 
383     /**
384      * Add any global identifier to the list, without checking
385      * if it's predefined
386      *
387      * Only called from the driver after platform detection,
388      * and internally.
389      *
390      * Params:
391      *   ident = identifier to add (ident[$] must be '\0')
392      */
393     deprecated("Kept for C++ compat - Use the string overload instead")
394     static void addPredefinedGlobalIdent(const(char)* ident)
395     {
396         addPredefinedGlobalIdent(ident[0 .. ident.strlen]);
397     }
398 
399     /// Ditto
400     extern(D) static void addPredefinedGlobalIdent(string ident)
401     {
402         // Forward: Overload necessary for string literal
403         addPredefinedGlobalIdent(cast(const(char)[])ident);
404     }
405 
406 
407     /// Ditto
408     extern(D) static void addPredefinedGlobalIdent(const(char)[] ident)
409     {
410         if (!global.params.versionids)
411             global.params.versionids = new Strings();
412         global.params.versionids.push(cast(char*)ident);
413     }
414 
415     /**
416      * Instantiate a new `VersionCondition`
417      *
418      * Params:
419      *   mod = Module this node belongs to
420      *   level = Minimum global level this condition needs to pass.
421      *           Only used if `ident` is `null`.
422      *   ident = Identifier required for this condition to pass.
423      *           If `null`, this conditiion will use an integer level.
424      */
425     extern (D) this(Module mod, uint level, Identifier ident)
426     {
427         super(mod, level, ident);
428     }
429 
430     override int include(Scope* sc, ScopeDsymbol sds)
431     {
432         //printf("VersionCondition::include() level = %d, versionlevel = %d\n", level, global.params.versionlevel);
433         //if (ident) printf("\tident = '%s'\n", ident->toChars());
434         if (inc == 0)
435         {
436             inc = 2;
437             bool definedInModule = false;
438             if (ident)
439             {
440                 if (findCondition(mod.versionids, ident))
441                 {
442                     inc = 1;
443                     definedInModule = true;
444                 }
445                 else if (findCondition(global.params.versionids, ident))
446                     inc = 1;
447                 else
448                 {
449                     if (!mod.versionidsNot)
450                         mod.versionidsNot = new Strings();
451                     mod.versionidsNot.push(ident.toChars());
452                 }
453             }
454             else if (level <= global.params.versionlevel || level <= mod.versionlevel)
455                 inc = 1;
456             if (!definedInModule &&
457                 (!ident || (!isReserved(ident.toString()) && ident != Id._unittest && ident != Id._assert)))
458             {
459                 printDepsConditional(sc, this, "depsVersion ");
460             }
461         }
462         return (inc == 1);
463     }
464 
465     override void accept(Visitor v)
466     {
467         v.visit(this);
468     }
469 
470     override const(char)* toChars()
471     {
472         return ident ? ident.toChars() : "version".ptr;
473     }
474 }
475 
476 /***********************************************************
477  */
478 extern (C++) final class StaticIfCondition : Condition
479 {
480     Expression exp;
481     int nest;           // limit circular dependencies
482 
483     extern (D) this(Loc loc, Expression exp)
484     {
485         super(loc);
486         this.exp = exp;
487     }
488 
489     override Condition syntaxCopy()
490     {
491         return new StaticIfCondition(loc, exp.syntaxCopy());
492     }
493 
494     override int include(Scope* sc, ScopeDsymbol sds)
495     {
496         version (none)
497         {
498             printf("StaticIfCondition::include(sc = %p, sds = %p) this=%p inc = %d\n", sc, sds, this, inc);
499             if (sds)
500             {
501                 printf("\ts = '%s', kind = %s\n", sds.toChars(), sds.kind());
502             }
503         }
504         if (inc == 0)
505         {
506             if (exp.op == TOKerror || nest > 100)
507             {
508                 error(loc, (nest > 1000) ? "unresolvable circular static if expression" : "error evaluating static if expression");
509                 goto Lerror;
510             }
511             if (!sc)
512             {
513                 error(loc, "static if conditional cannot be at global scope");
514                 inc = 2;
515                 return 0;
516             }
517             ++nest;
518             sc = sc.push(sc.scopesym);
519             sc.sds = sds; // sds gets any addMember()
520             //sc->speculative = true;       // TODO: static if (is(T U)) { /* U is available */ }
521             sc.flags |= SCOPEcondition;
522             sc = sc.startCTFE();
523             Expression e = exp.semantic(sc);
524             e = resolveProperties(sc, e);
525             sc = sc.endCTFE();
526             sc.pop();
527             --nest;
528             // Prevent repeated condition evaluation.
529             // See: fail_compilation/fail7815.d
530             if (inc != 0)
531                 return (inc == 1);
532             if (!e.type.isBoolean())
533             {
534                 if (e.type.toBasetype() != Type.terror)
535                     exp.error("expression %s of type %s does not have a boolean value", exp.toChars(), e.type.toChars());
536                 goto Lerror;
537             }
538             e = e.ctfeInterpret();
539             if (e.op == TOKerror)
540             {
541                 goto Lerror;
542             }
543             else if (e.isBool(true))
544                 inc = 1;
545             else if (e.isBool(false))
546                 inc = 2;
547             else
548             {
549                 e.error("expression %s is not constant or does not evaluate to a bool", e.toChars());
550                 goto Lerror;
551             }
552         }
553         return (inc == 1);
554     Lerror:
555         if (!global.gag)
556             inc = 2; // so we don't see the error message again
557         return 0;
558     }
559 
560     override void accept(Visitor v)
561     {
562         v.visit(this);
563     }
564 
565     override const(char)* toChars()
566     {
567         return exp ? exp.toChars() : "static if".ptr;
568     }
569 }
570 
571 extern (C++) int findCondition(Strings* ids, Identifier ident)
572 {
573     if (ids)
574     {
575         for (size_t i = 0; i < ids.dim; i++)
576         {
577             const(char)* id = (*ids)[i];
578             if (strcmp(id, ident.toChars()) == 0)
579                 return true;
580         }
581     }
582     return false;
583 }
584 
585 // Helper for printing dependency information
586 private void printDepsConditional(Scope* sc, DVCondition condition, const(char)[] depType)
587 {
588     if (!global.params.moduleDeps || global.params.moduleDepsFile)
589         return;
590     OutBuffer* ob = global.params.moduleDeps;
591     Module imod = sc ? sc.instantiatingModule() : condition.mod;
592     if (!imod)
593         return;
594     ob.writestring(depType);
595     ob.writestring(imod.toPrettyChars());
596     ob.writestring(" (");
597     escapePath(ob, imod.srcfile.toChars());
598     ob.writestring(") : ");
599     if (condition.ident)
600         ob.printf("%s\n", condition.ident.toChars());
601     else
602         ob.printf("%d\n", condition.level);
603 }