root/trunk/docsrc/warnings.dd

Revision 2040, 9.5 kB (checked in by walter, 2 years ago)

typography

  • Property svn:eol-style set to native
Line 
1 Ddoc
2
3 $(D_S Warnings,
4
5     $(P Depending on one's point of view, warnings are either a symptom
6     of broken language design or a useful $(SINGLEQUOTE lint) like tool to analyze
7     code and look for potential trouble spots.
8     Most of the time, those trouble spots will be legitimate code
9     intended to be that way. More rarely, it may indicate an
10     unintentional error on the part of the programmer.
11     )
12
13     $(P Warnings are not a defined part of the D Programming Language.
14     They exist at the discretion of the compiler vendor, and
15     will most likely vary from vendor to vendor.
16     All constructs for which an implementation may generate a warning
17     message are legal D code.
18     )
19
20     $(P These are the warnings generated by the Digital Mars D compiler
21     when the $(B -w) switch is thrown.
22     Most have generated some spirited debate as to whether it should
23     be a warning, an error, and what the correct way to write D code
24     in these situations should be. Since no consensus emerged,
25     they appear as optional warnings, with alternatives on how to
26     write correct code.
27     )
28
29 <h3>warning - implicit conversion of expression $(I expr) of type $(I type) to $(I type) can cause loss of data</h3>
30
31     $(P D follows the C and C++ rules regarding default integral promotion
32     in expressions. This is done for both compatibility reasons
33     and because modern CPUs are designed to execute such semantics
34     efficiently. These rules also include implicit narrowing rules,
35     where the result of such can be cast back into a smaller type,
36     perhaps losing significant bits in the process:
37     )
38
39 ---
40 byte a,b,c;
41 ...
42 a = b + c;
43 ---
44
45     $(P The $(I b) and $(I c) are both promoted to type $(B int) using
46     the default integral promotion rules. The result of the add is then
47     also $(B int). To be assigned to $(I a), that $(B int) gets
48     implicitly converted to a $(B byte).
49     )
50
51     $(P Whether it is a bug or not in the program depends on if the values
52     for $(I b) and $(I c) can possibly cause an overflow, and
53     if that overflow matters or not to the algorithm.
54     The warning can be resolved by:
55     )
56
57     $(OL
58     $(LI Inserting a cast:
59 ---
60 a = cast(byte)(b + c);
61 ---
62     This eliminates the warning, but since casting is a blunt instrument
63     that bypasses type checking, this can mask another bug that could
64     be introduced if the types of $(I a), $(I b) or $(I c)
65     change in the future, or if their types are set by a template
66     instantiation. (In generic code, the $(TT cast(byte)) would
67     probably be $(TT cast(typeof(a)))).
68     )
69
70     $(LI Changing the type of $(I a) to $(B int).
71     This is generally a better solution, but of course may not work
72     if it must be a smaller type, which leads us to:
73     )
74
75     $(LI Doing a runtime check for overflow:
76 ---
77 byte a,b,c;
78 int tmp;
79 ...
80 tmp = b + c;
81 assert(cast(byte)tmp == tmp);
82 a = cast(byte)tmp;
83 ---
84     This ensures that no significant bits get lost.
85     )
86     )
87
88     $(P Some proposed language solutions are:)
89
90     $(OL
91     $(LI Having the compiler automatically insert the equivalent of
92     the option 3 runtime check.
93     )
94
95     $(LI Add a new construct,
96     $(TT $(B implicit_cast)($(I type))$(I expression))
97     that is a restricted form of the general cast, and will only
98     work where implicit casts would normally be allowed.
99     )
100
101     $(LI Implement the $(B implicit_cast) as a template.
102     )
103
104     )
105
106 <h3>warning - array 'length' hides other 'length' name in outer scope</h3>
107
108     $(P Inside the [ ] of an array indexing expression or slice, the
109     variable $(I length) gets defined and set to be the length
110     of that array. The scope of the $(I length) is from the opening
111     $(SINGLEQUOTE [) to the closing $(SINGLEQUOTE ]).
112     )
113
114     $(P If $(I length) was declared as a variable name
115     in an outer scope, that version may have been intended for
116     use between the [ ]. For example:
117     )
118
119 ---
120 char[10] a;
121 int length = 4;
122 ...
123 return a[length - 1];   // returns a[9], not a[3]
124 ---
125
126     $(P The warning can be resolved by:)
127
128     $(OL
129     $(LI Renaming the outer $(I length) to another name.
130     )
131
132     $(LI Replacing the use of $(I length) within the [ ]
133     with $(I a.length).
134     )
135
136     $(LI If $(I length) is at global or class scope, and that was the
137     one intended to be used, use $(I .length) or $(I this.length)
138     to disambiguate.
139     )
140     )
141
142     $(P Some proposed language solutions are:)
143
144     $(OL
145     $(LI Make the $(I length) a special symbol or a keyword instead
146     of an implicitly declared variable.
147     )
148     )
149
150 <h3>warning - no return at end of function</h3>
151
152     $(P Consider the following:)
153
154 ---
155 int foo(int k, Collection c)
156 {
157     foreach (int x; c)
158     {
159     if (x == k)
160         return x + 1;
161     }
162 }
163 ---
164
165     $(P and assume that the nature of the algorithm is that $(I x) will
166     always be found in $(I c), so the $(B foreach) never falls
167     through the bottom of the code.
168     There is no $(B return) at the close of the function, because
169     that point is not reachable and any return statement would
170     be dead code.
171     This is perfectly legitimate code.
172     The D compiler, to help with ensuring the robustness of such
173     code, will ensure it is correct by automatically inserting
174     an $(I assert(0);) statement at the close of the function.
175     Then, if there's an error in the assumption that the $(B foreach) will
176     never fall through, it will be caught at runtime with
177     an obvious assertion failure, which is better than the code just
178     falling off the end causing erratic, arbitrary failures.
179     )
180
181     $(P D's behavior on this, however, is not common to other languages,
182     and some programmers will be acutely uncomfortable with relying
183     on this, or they wish to see the error at compile time rather
184     than runtime. They wish to see something explicit expressed in the
185     source code that the $(B foreach) cannot fall through.
186     Hence the warning.
187     )
188
189     $(P The warning can be resolved by:
190     )
191
192     $(OL
193     $(LI Putting a return statement at the close of the function,
194     returning some arbitrary value (after all, it will never be
195     executed):
196
197 ---
198 int foo(int k, Collection c)
199 {
200     foreach (int x; c)
201     {
202     if (x == k)
203         return x + 1;
204     }
205     return 0;   // suppress warning about no return statement
206 }
207 ---
208
209     While doing this is surprisingly common, and happens when
210     the programmer is inexperienced or in a hurry, it is a
211     spectacularly bad solution.
212     The trouble happens when the $(B foreach) does fall through
213     due to a bug, then instead of the bug being detected, now
214     the function returns an unexpected value, which may cause other
215     problems or go undetected.
216     There's another issue with the maintenance programmer who
217     sees this, and upon analyzing the code's behavior, wonders why
218     there's a return statement that will never be executed.
219     (This can be resolved with comments, but comments are always
220     missing, out of date, or just plain wrong.)
221     Dead code is always a confusing problem to maintenance programming,
222     so deliberately inserting it is a bad idea.
223     )
224
225     $(LI A better solution is to make explicit what the D language
226     does implicitly - put an assert there:
227
228 ---
229 int foo(int k, Collection c)
230 {
231     foreach (int x; c)
232     {
233     if (x == k)
234         return x + 1;
235     }
236     assert(0);
237 }
238 ---
239
240     Now, if the $(B foreach) does fall through, the error will be
241     detected. Furthermore, it is self-documenting.
242     )
243
244     $(LI Another alternative is to do a $(B throw) with a custom
245     error class and a more user friendly message:
246
247 ---
248 int foo(int k, Collection c)
249 {
250     foreach (int x; c)
251     {
252     if (x == k)
253         return x + 1;
254     }
255     $(B throw) new MyErrorClass("fell off the end of foo()");
256 }
257 ---
258     )
259     )
260
261 <h3>warning - switch statement has no default</h3>
262
263     $(P Similar to the no return statement warning is the no default
264     in a switch warning. Consider:
265     )
266
267 ---
268 switch (i)
269 {
270     case 1: dothis(); break;
271     case 2: dothat(); break;
272 }
273 ---
274
275     $(P According to the D language semantics, this means the only possible
276     values for $(I i) are 1 and 2. Any other values represent a bug
277     in the program. To detect this bug, the compiler implements the
278     switch as if it were written:
279     )
280
281 ---
282 switch (i)
283 {
284     case 1: dothis(); break;
285     case 2: dothat(); break;
286     $(B default: throw new SwitchError();)
287 }
288 ---
289
290     $(P This is quite different from C and C++ behavior, which is as if
291     the switch were written:
292     )
293
294 ---
295 switch (i)
296 {
297     case 1: dothis(); break;
298     case 2: dothat(); break;
299     $(B default: break;)
300 }
301 ---
302
303     $(P The potential for bugs with that is high, as it is a common error
304     to accidentally omit a case, or to add a new value in one part of
305     the program and overlook adding a case for it in another part.
306     Although D will catch this error at runtime by throwing
307     an instance of $(TT std.switcherr.SwitchError), some prefer at least
308     a warning so such errors can be caught at compile time.
309     )
310
311     $(P The warning can be resolved by:
312     )
313
314     $(OL
315     $(LI Inserting an explict default of the form:
316
317 ---
318 switch (i)
319 {
320     case 1: dothis(); break;
321     case 2: dothat(); break;
322     $(B default: assert(0);)
323 }
324 ---
325     )
326
327     $(LI As in the missing return, a default with a throw of a custom
328     error class can be used.
329     )
330     )
331
332 <h3>warning - statement is not reachable</h3>
333
334     $(P Consider the following code:)
335
336 ---
337 int foo(int i)
338 {
339     return i;
340     $(B return i + 1;)
341 }
342 ---
343
344     $(P The second return statement is not reachable, i.e. it is dead
345     code. While dead code is poor style in released code, it can
346     legitimately
347     happen a lot when rapidly trying to isolate down a bug or experiment
348     with different bits of code.
349     )
350
351     $(P The warning can be resolved by:
352     )
353
354     $(OL
355     $(LI Commenting out the dead code with /+ ... +/ comments:
356 ---
357 int foo(int i)
358 {
359     return i;
360     /+
361     return i + 1;
362      +/
363 }
364 ---
365     )
366
367     $(LI Putting the dead code inside a $(TT version(none)) conditional:
368 ---
369 int foo(int i)
370 {
371     return i;
372     $(B version (none))
373     {
374     return i + 1;
375     }
376 }
377 ---
378     )
379
380     $(LI Only compile with warnings enabled when doing release builds.
381     )
382     )
383
384 )
385
386 Macros:
387     TITLE=Warnings
388     WIKI=Warnings
Note: See TracBrowser for help on using the browser.