1 | /* Implementation of -Wmisleading-indentation |
2 | Copyright (C) 2015-2023 Free Software Foundation, Inc. |
3 | |
4 | This file is part of GCC. |
5 | |
6 | GCC is free software; you can redistribute it and/or modify it under |
7 | the terms of the GNU General Public License as published by the Free |
8 | Software Foundation; either version 3, or (at your option) any later |
9 | version. |
10 | |
11 | GCC is distributed in the hope that it will be useful, but WITHOUT ANY |
12 | WARRANTY; without even the implied warranty of MERCHANTABILITY or |
13 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
14 | for more details. |
15 | |
16 | You should have received a copy of the GNU General Public License |
17 | along with GCC; see the file COPYING3. If not see |
18 | <http://www.gnu.org/licenses/>. */ |
19 | |
20 | #include "config.h" |
21 | #include "system.h" |
22 | #include "coretypes.h" |
23 | #include "tm.h" |
24 | #include "c-common.h" |
25 | #include "c-indentation.h" |
26 | #include "selftest.h" |
27 | #include "diagnostic.h" |
28 | |
29 | /* Round up VIS_COLUMN to nearest tab stop. */ |
30 | |
31 | static unsigned int |
32 | next_tab_stop (unsigned int vis_column, unsigned int tab_width) |
33 | { |
34 | vis_column = ((vis_column + tab_width) / tab_width) * tab_width; |
35 | return vis_column; |
36 | } |
37 | |
38 | /* Convert libcpp's notion of a column (a 1-based char count) to |
39 | the "visual column" (0-based column, respecting tabs), by reading the |
40 | relevant line. |
41 | |
42 | Returns true if a conversion was possible, writing the result to OUT, |
43 | otherwise returns false. If FIRST_NWS is not NULL, then write to it |
44 | the visual column corresponding to the first non-whitespace character |
45 | on the line (up to or before EXPLOC). */ |
46 | |
47 | static bool |
48 | get_visual_column (expanded_location exploc, |
49 | unsigned int *out, |
50 | unsigned int *first_nws, |
51 | unsigned int tab_width) |
52 | { |
53 | char_span line = location_get_source_line (file_path: exploc.file, line: exploc.line); |
54 | if (!line) |
55 | return false; |
56 | if ((size_t)exploc.column > line.length ()) |
57 | return false; |
58 | unsigned int vis_column = 0; |
59 | for (int i = 1; i < exploc.column; i++) |
60 | { |
61 | unsigned char ch = line[i - 1]; |
62 | |
63 | if (first_nws != NULL && !ISSPACE (ch)) |
64 | { |
65 | *first_nws = vis_column; |
66 | first_nws = NULL; |
67 | } |
68 | |
69 | if (ch == '\t') |
70 | vis_column = next_tab_stop (vis_column, tab_width); |
71 | else |
72 | vis_column++; |
73 | } |
74 | |
75 | if (first_nws != NULL) |
76 | *first_nws = vis_column; |
77 | |
78 | *out = vis_column; |
79 | return true; |
80 | } |
81 | |
82 | /* Attempt to determine the first non-whitespace character in line LINE_NUM |
83 | of source line FILE. |
84 | |
85 | If this is possible, return true and write its "visual column" to |
86 | *FIRST_NWS. |
87 | Otherwise, return false, leaving *FIRST_NWS untouched. */ |
88 | |
89 | static bool |
90 | get_first_nws_vis_column (const char *file, int line_num, |
91 | unsigned int *first_nws, |
92 | unsigned int tab_width) |
93 | { |
94 | gcc_assert (first_nws); |
95 | |
96 | char_span line = location_get_source_line (file_path: file, line: line_num); |
97 | if (!line) |
98 | return false; |
99 | unsigned int vis_column = 0; |
100 | for (size_t i = 1; i < line.length (); i++) |
101 | { |
102 | unsigned char ch = line[i - 1]; |
103 | |
104 | if (!ISSPACE (ch)) |
105 | { |
106 | *first_nws = vis_column; |
107 | return true; |
108 | } |
109 | |
110 | if (ch == '\t') |
111 | vis_column = next_tab_stop (vis_column, tab_width); |
112 | else |
113 | vis_column++; |
114 | } |
115 | |
116 | /* No non-whitespace characters found. */ |
117 | return false; |
118 | } |
119 | |
120 | /* Determine if there is an unindent/outdent between |
121 | BODY_EXPLOC and NEXT_STMT_EXPLOC, to ensure that we don't |
122 | issue a warning for cases like the following: |
123 | |
124 | (1) Preprocessor logic |
125 | |
126 | if (flagA) |
127 | foo (); |
128 | ^ BODY_EXPLOC |
129 | #if SOME_CONDITION_THAT_DOES_NOT_HOLD |
130 | if (flagB) |
131 | #endif |
132 | bar (); |
133 | ^ NEXT_STMT_EXPLOC |
134 | |
135 | "bar ();" is visually aligned below "foo ();" and |
136 | is (as far as the parser sees) the next token, but |
137 | this isn't misleading to a human reader. |
138 | |
139 | (2) Empty macro with bad indentation |
140 | |
141 | In the following, the |
142 | "if (i > 0)" |
143 | is poorly indented, and ought to be on the same column as |
144 | "engine_ref_debug(e, 0, -1)" |
145 | However, it is not misleadingly indented, due to the presence |
146 | of that macro. |
147 | |
148 | #define engine_ref_debug(X, Y, Z) |
149 | |
150 | if (locked) |
151 | i = foo (0); |
152 | else |
153 | i = foo (1); |
154 | engine_ref_debug(e, 0, -1) |
155 | if (i > 0) |
156 | return 1; |
157 | |
158 | Return true if such an unindent/outdent is detected. */ |
159 | |
160 | static bool |
161 | detect_intervening_unindent (const char *file, |
162 | int body_line, |
163 | int next_stmt_line, |
164 | unsigned int vis_column, |
165 | unsigned int tab_width) |
166 | { |
167 | gcc_assert (file); |
168 | gcc_assert (next_stmt_line > body_line); |
169 | |
170 | for (int line = body_line + 1; line < next_stmt_line; line++) |
171 | { |
172 | unsigned int line_vis_column; |
173 | if (get_first_nws_vis_column (file, line_num: line, first_nws: &line_vis_column, tab_width)) |
174 | if (line_vis_column < vis_column) |
175 | return true; |
176 | } |
177 | |
178 | /* Not found. */ |
179 | return false; |
180 | } |
181 | |
182 | |
183 | /* Helper function for warn_for_misleading_indentation; see |
184 | description of that function below. */ |
185 | |
186 | static bool |
187 | should_warn_for_misleading_indentation (const token_indent_info &guard_tinfo, |
188 | const token_indent_info &body_tinfo, |
189 | const token_indent_info &next_tinfo) |
190 | { |
191 | /* Don't attempt to compare indentation if #line or # 44 "file"-style |
192 | directives are present, suggesting generated code. |
193 | |
194 | All bets are off if these are present: the file that the #line |
195 | directive could have an entirely different coding layout to C/C++ |
196 | (e.g. .md files). |
197 | |
198 | To determine if a #line is present, in theory we could look for a |
199 | map with reason == LC_RENAME_VERBATIM. However, if there has |
200 | subsequently been a long line requiring a column number larger than |
201 | that representable by the original LC_RENAME_VERBATIM map, then |
202 | we'll have a map with reason LC_RENAME. |
203 | Rather than attempting to search all of the maps for a |
204 | LC_RENAME_VERBATIM, instead we have libcpp set a flag whenever one |
205 | is seen, and we check for the flag here. |
206 | */ |
207 | if (line_table->seen_line_directive) |
208 | return false; |
209 | |
210 | /* We can't usefully warn about do-while and switch statements since the |
211 | bodies of these statements are always explicitly delimited at both ends, |
212 | so control flow is quite obvious. */ |
213 | if (guard_tinfo.keyword == RID_DO |
214 | || guard_tinfo.keyword == RID_SWITCH) |
215 | return false; |
216 | |
217 | /* If the token following the body is a close brace or an "else" |
218 | then while indentation may be sloppy, there is not much ambiguity |
219 | about control flow, e.g. |
220 | |
221 | if (foo) <- GUARD |
222 | bar (); <- BODY |
223 | else baz (); <- NEXT |
224 | |
225 | { |
226 | while (foo) <- GUARD |
227 | bar (); <- BODY |
228 | } <- NEXT |
229 | baz (); |
230 | */ |
231 | enum cpp_ttype next_tok_type = next_tinfo.type; |
232 | if (next_tok_type == CPP_CLOSE_BRACE |
233 | || next_tinfo.keyword == RID_ELSE) |
234 | return false; |
235 | |
236 | /* Likewise, if the body of the guard is a compound statement then control |
237 | flow is quite visually explicit regardless of the code's possibly poor |
238 | indentation, e.g. |
239 | |
240 | while (foo) <- GUARD |
241 | { <- BODY |
242 | bar (); |
243 | } |
244 | baz (); <- NEXT |
245 | |
246 | Things only get muddy when the body of the guard does not have |
247 | braces, e.g. |
248 | |
249 | if (foo) <- GUARD |
250 | bar (); <- BODY |
251 | baz (); <- NEXT |
252 | */ |
253 | enum cpp_ttype body_type = body_tinfo.type; |
254 | if (body_type == CPP_OPEN_BRACE) |
255 | return false; |
256 | |
257 | /* Don't warn here about spurious semicolons. */ |
258 | if (next_tok_type == CPP_SEMICOLON) |
259 | return false; |
260 | |
261 | location_t guard_loc = guard_tinfo.location; |
262 | location_t body_loc = body_tinfo.location; |
263 | location_t next_stmt_loc = next_tinfo.location; |
264 | |
265 | /* Resolve each token location to the respective macro expansion |
266 | point that produced the token. */ |
267 | if (linemap_location_from_macro_expansion_p (line_table, guard_loc)) |
268 | guard_loc = linemap_resolve_location (line_table, loc: guard_loc, |
269 | lrk: LRK_MACRO_EXPANSION_POINT, NULL); |
270 | if (linemap_location_from_macro_expansion_p (line_table, body_loc)) |
271 | body_loc = linemap_resolve_location (line_table, loc: body_loc, |
272 | lrk: LRK_MACRO_EXPANSION_POINT, NULL); |
273 | if (linemap_location_from_macro_expansion_p (line_table, next_stmt_loc)) |
274 | next_stmt_loc = linemap_resolve_location (line_table, loc: next_stmt_loc, |
275 | lrk: LRK_MACRO_EXPANSION_POINT, NULL); |
276 | |
277 | /* When all three tokens are produced from a single macro expansion, we |
278 | instead consider their loci inside that macro's definition. */ |
279 | if (guard_loc == body_loc && body_loc == next_stmt_loc) |
280 | { |
281 | const line_map *guard_body_common_map |
282 | = first_map_in_common (set: line_table, |
283 | loc0: guard_tinfo.location, loc1: body_tinfo.location, |
284 | res_loc0: &guard_loc, res_loc1: &body_loc); |
285 | const line_map *body_next_common_map |
286 | = first_map_in_common (set: line_table, |
287 | loc0: body_tinfo.location, loc1: next_tinfo.location, |
288 | res_loc0: &body_loc, res_loc1: &next_stmt_loc); |
289 | |
290 | /* Punt on complicated nesting of macros. */ |
291 | if (guard_body_common_map != body_next_common_map) |
292 | return false; |
293 | |
294 | guard_loc = linemap_resolve_location (line_table, loc: guard_loc, |
295 | lrk: LRK_MACRO_DEFINITION_LOCATION, NULL); |
296 | body_loc = linemap_resolve_location (line_table, loc: body_loc, |
297 | lrk: LRK_MACRO_DEFINITION_LOCATION, NULL); |
298 | next_stmt_loc = linemap_resolve_location (line_table, loc: next_stmt_loc, |
299 | lrk: LRK_MACRO_DEFINITION_LOCATION, |
300 | NULL); |
301 | } |
302 | |
303 | expanded_location body_exploc = expand_location (body_loc); |
304 | expanded_location next_stmt_exploc = expand_location (next_stmt_loc); |
305 | expanded_location guard_exploc = expand_location (guard_loc); |
306 | |
307 | /* PR c++/68819: if the column number is zero, we presumably |
308 | had a location_t > LINE_MAP_MAX_LOCATION_WITH_COLS, and so |
309 | we have no column information. */ |
310 | if (!guard_exploc.column || !body_exploc.column || !next_stmt_exploc.column) |
311 | { |
312 | static bool issued_note = false; |
313 | if (!issued_note) |
314 | { |
315 | /* Notify the user the first time this happens. */ |
316 | issued_note = true; |
317 | inform (guard_loc, |
318 | "%<-Wmisleading-indentation%> is disabled from this point" |
319 | " onwards, since column-tracking was disabled due to" |
320 | " the size of the code/headers" ); |
321 | if (!flag_large_source_files) |
322 | inform (guard_loc, |
323 | "adding %<-flarge-source-files%> will allow for more" |
324 | " column-tracking support, at the expense of compilation" |
325 | " time and memory" ); |
326 | } |
327 | return false; |
328 | } |
329 | |
330 | /* Give up if the loci are not all distinct. */ |
331 | if (guard_loc == body_loc || body_loc == next_stmt_loc) |
332 | return false; |
333 | |
334 | const unsigned int tab_width = global_dc->m_tabstop; |
335 | |
336 | /* They must be in the same file. */ |
337 | if (next_stmt_exploc.file != body_exploc.file) |
338 | return false; |
339 | |
340 | /* If NEXT_STMT_LOC and BODY_LOC are on the same line, consider |
341 | the location of the guard. |
342 | |
343 | Cases where we want to issue a warning: |
344 | |
345 | if (flag) |
346 | foo (); bar (); |
347 | ^ WARN HERE |
348 | |
349 | if (flag) foo (); bar (); |
350 | ^ WARN HERE |
351 | |
352 | |
353 | if (flag) ; { |
354 | ^ WARN HERE |
355 | |
356 | if (flag) |
357 | ; { |
358 | ^ WARN HERE |
359 | |
360 | Cases where we don't want to issue a warning: |
361 | |
362 | various_code (); if (flag) foo (); bar (); more_code (); |
363 | ^ DON'T WARN HERE. */ |
364 | if (next_stmt_exploc.line == body_exploc.line) |
365 | { |
366 | if (guard_exploc.file != body_exploc.file) |
367 | return true; |
368 | if (guard_exploc.line < body_exploc.line) |
369 | /* The guard is on a line before a line that contains both |
370 | the body and the next stmt. */ |
371 | return true; |
372 | else if (guard_exploc.line == body_exploc.line) |
373 | { |
374 | /* They're all on the same line. */ |
375 | gcc_assert (guard_exploc.file == next_stmt_exploc.file); |
376 | gcc_assert (guard_exploc.line == next_stmt_exploc.line); |
377 | unsigned int guard_vis_column; |
378 | unsigned int guard_line_first_nws; |
379 | if (!get_visual_column (exploc: guard_exploc, |
380 | out: &guard_vis_column, |
381 | first_nws: &guard_line_first_nws, tab_width)) |
382 | return false; |
383 | /* Heuristic: only warn if the guard is the first thing |
384 | on its line. */ |
385 | if (guard_vis_column == guard_line_first_nws) |
386 | return true; |
387 | } |
388 | } |
389 | |
390 | /* If NEXT_STMT_LOC is on a line after BODY_LOC, consider |
391 | their relative locations, and of the guard. |
392 | |
393 | Cases where we want to issue a warning: |
394 | if (flag) |
395 | foo (); |
396 | bar (); |
397 | ^ WARN HERE |
398 | |
399 | Cases where we don't want to issue a warning: |
400 | if (flag) |
401 | foo (); |
402 | bar (); |
403 | ^ DON'T WARN HERE (autogenerated code?) |
404 | |
405 | if (flagA) |
406 | foo (); |
407 | #if SOME_CONDITION_THAT_DOES_NOT_HOLD |
408 | if (flagB) |
409 | #endif |
410 | bar (); |
411 | ^ DON'T WARN HERE |
412 | |
413 | if (flag) |
414 | ; |
415 | foo (); |
416 | ^ DON'T WARN HERE |
417 | |
418 | #define emit |
419 | if (flag) |
420 | foo (); |
421 | emit bar (); |
422 | ^ DON'T WARN HERE |
423 | |
424 | */ |
425 | if (next_stmt_exploc.line > body_exploc.line) |
426 | { |
427 | /* Determine if GUARD_LOC and NEXT_STMT_LOC are aligned on the same |
428 | "visual column"... */ |
429 | unsigned int next_stmt_vis_column; |
430 | unsigned int next_stmt_line_first_nws; |
431 | unsigned int body_vis_column; |
432 | unsigned int body_line_first_nws; |
433 | unsigned int guard_vis_column; |
434 | unsigned int guard_line_first_nws; |
435 | /* If we can't determine it, don't issue a warning. This is sometimes |
436 | the case for input files containing #line directives, and these |
437 | are often for autogenerated sources (e.g. from .md files), where |
438 | it's not clear that it's meaningful to look at indentation. */ |
439 | if (!get_visual_column (exploc: next_stmt_exploc, |
440 | out: &next_stmt_vis_column, |
441 | first_nws: &next_stmt_line_first_nws, tab_width)) |
442 | return false; |
443 | if (!get_visual_column (exploc: body_exploc, |
444 | out: &body_vis_column, |
445 | first_nws: &body_line_first_nws, tab_width)) |
446 | return false; |
447 | if (!get_visual_column (exploc: guard_exploc, |
448 | out: &guard_vis_column, |
449 | first_nws: &guard_line_first_nws, tab_width)) |
450 | return false; |
451 | |
452 | /* If the line where the next stmt starts has non-whitespace |
453 | on it before the stmt, then don't warn: |
454 | #define emit |
455 | if (flag) |
456 | foo (); |
457 | emit bar (); |
458 | ^ DON'T WARN HERE |
459 | (PR c/69122). */ |
460 | if (next_stmt_line_first_nws < next_stmt_vis_column) |
461 | return false; |
462 | |
463 | if ((body_type != CPP_SEMICOLON |
464 | && next_stmt_vis_column == body_vis_column) |
465 | /* As a special case handle the case where the body is a semicolon |
466 | that may be hidden by a preceding comment, e.g. */ |
467 | |
468 | // if (p) |
469 | // /* blah */; |
470 | // foo (1); |
471 | |
472 | /* by looking instead at the column of the first non-whitespace |
473 | character on the body line. */ |
474 | || (body_type == CPP_SEMICOLON |
475 | && body_exploc.line > guard_exploc.line |
476 | && body_line_first_nws != body_vis_column |
477 | && next_stmt_vis_column > guard_line_first_nws)) |
478 | { |
479 | /* Don't warn if they are aligned on the same column |
480 | as the guard itself (suggesting autogenerated code that doesn't |
481 | bother indenting at all). |
482 | For "else" clauses, we consider the column of the first |
483 | non-whitespace character on the guard line instead of the column |
484 | of the actual guard token itself because it is more sensible. |
485 | Consider: |
486 | |
487 | if (p) { |
488 | foo (1); |
489 | } else // GUARD |
490 | foo (2); // BODY |
491 | foo (3); // NEXT |
492 | |
493 | and: |
494 | |
495 | if (p) |
496 | foo (1); |
497 | } else // GUARD |
498 | foo (2); // BODY |
499 | foo (3); // NEXT |
500 | |
501 | If we just used the column of the "else" token, we would warn on |
502 | the first example and not warn on the second. But we want the |
503 | exact opposite to happen: to not warn on the first example (which |
504 | is probably autogenerated) and to warn on the second (whose |
505 | indentation is misleading). Using the column of the first |
506 | non-whitespace character on the guard line makes that |
507 | happen. */ |
508 | unsigned int guard_column = (guard_tinfo.keyword == RID_ELSE |
509 | ? guard_line_first_nws |
510 | : guard_vis_column); |
511 | if (guard_column == body_vis_column) |
512 | return false; |
513 | |
514 | /* We may have something like: |
515 | |
516 | if (p) |
517 | { |
518 | foo (1); |
519 | } else // GUARD |
520 | foo (2); // BODY |
521 | foo (3); // NEXT |
522 | |
523 | in which case the columns are not aligned but the code is not |
524 | misleadingly indented. If the column of the body isn't indented |
525 | more than the guard line then don't warn. */ |
526 | if (body_vis_column <= guard_line_first_nws) |
527 | return false; |
528 | |
529 | /* Don't warn if there is an unindent between the two statements. */ |
530 | int vis_column = MIN (next_stmt_vis_column, body_vis_column); |
531 | if (detect_intervening_unindent (file: body_exploc.file, body_line: body_exploc.line, |
532 | next_stmt_line: next_stmt_exploc.line, |
533 | vis_column, tab_width)) |
534 | return false; |
535 | |
536 | /* Otherwise, they are visually aligned: issue a warning. */ |
537 | return true; |
538 | } |
539 | |
540 | /* Also issue a warning for code having the form: |
541 | |
542 | if (flag); |
543 | foo (); |
544 | |
545 | while (flag); |
546 | { |
547 | ... |
548 | } |
549 | |
550 | for (...); |
551 | { |
552 | ... |
553 | } |
554 | |
555 | if (flag) |
556 | ; |
557 | else if (flag); |
558 | foo (); |
559 | |
560 | where the semicolon at the end of each guard is most likely spurious. |
561 | |
562 | But do not warn on: |
563 | |
564 | for (..); |
565 | foo (); |
566 | |
567 | where the next statement is aligned with the guard. |
568 | */ |
569 | if (body_type == CPP_SEMICOLON) |
570 | { |
571 | if (body_exploc.line == guard_exploc.line) |
572 | { |
573 | if (next_stmt_vis_column > guard_line_first_nws |
574 | || (next_tok_type == CPP_OPEN_BRACE |
575 | && next_stmt_vis_column == guard_line_first_nws)) |
576 | return true; |
577 | } |
578 | } |
579 | } |
580 | |
581 | return false; |
582 | } |
583 | |
584 | /* Return the string identifier corresponding to the given guard token. */ |
585 | |
586 | const char * |
587 | guard_tinfo_to_string (enum rid keyword) |
588 | { |
589 | switch (keyword) |
590 | { |
591 | case RID_FOR: |
592 | return "for" ; |
593 | case RID_ELSE: |
594 | return "else" ; |
595 | case RID_IF: |
596 | return "if" ; |
597 | case RID_WHILE: |
598 | return "while" ; |
599 | case RID_DO: |
600 | return "do" ; |
601 | case RID_SWITCH: |
602 | return "switch" ; |
603 | default: |
604 | gcc_unreachable (); |
605 | } |
606 | } |
607 | |
608 | /* Called by the C/C++ frontends when we have a guarding statement at |
609 | GUARD_LOC containing a statement at BODY_LOC, where the block wasn't |
610 | written using braces, like this: |
611 | |
612 | if (flag) |
613 | foo (); |
614 | |
615 | along with the location of the next token, at NEXT_STMT_LOC, |
616 | so that we can detect followup statements that are within |
617 | the same "visual block" as the guarded statement, but which |
618 | aren't logically grouped within the guarding statement, such |
619 | as: |
620 | |
621 | GUARD_LOC |
622 | | |
623 | V |
624 | if (flag) |
625 | foo (); <- BODY_LOC |
626 | bar (); <- NEXT_STMT_LOC |
627 | |
628 | In the above, "bar ();" isn't guarded by the "if", but |
629 | is indented to misleadingly suggest that it is in the same |
630 | block as "foo ();". |
631 | |
632 | GUARD_KIND identifies the kind of clause e.g. "if", "else" etc. */ |
633 | |
634 | void |
635 | warn_for_misleading_indentation (const token_indent_info &guard_tinfo, |
636 | const token_indent_info &body_tinfo, |
637 | const token_indent_info &next_tinfo) |
638 | { |
639 | /* Early reject for the case where -Wmisleading-indentation is disabled, |
640 | to avoid doing work only to have the warning suppressed inside the |
641 | diagnostic machinery. */ |
642 | if (!warn_misleading_indentation) |
643 | return; |
644 | |
645 | if (should_warn_for_misleading_indentation (guard_tinfo, |
646 | body_tinfo, |
647 | next_tinfo)) |
648 | { |
649 | auto_diagnostic_group d; |
650 | if (warning_at (guard_tinfo.location, OPT_Wmisleading_indentation, |
651 | "this %qs clause does not guard..." , |
652 | guard_tinfo_to_string (keyword: guard_tinfo.keyword))) |
653 | inform (next_tinfo.location, |
654 | "...this statement, but the latter is misleadingly indented" |
655 | " as if it were guarded by the %qs" , |
656 | guard_tinfo_to_string (keyword: guard_tinfo.keyword)); |
657 | } |
658 | } |
659 | |
660 | #if CHECKING_P |
661 | |
662 | namespace selftest { |
663 | |
664 | /* Verify that next_tab_stop works as expected. */ |
665 | |
666 | static void |
667 | test_next_tab_stop () |
668 | { |
669 | const unsigned int tab_width = 8; |
670 | |
671 | ASSERT_EQ (next_tab_stop (0, tab_width), 8); |
672 | ASSERT_EQ (next_tab_stop (1, tab_width), 8); |
673 | ASSERT_EQ (next_tab_stop (7, tab_width), 8); |
674 | |
675 | ASSERT_EQ (next_tab_stop (8, tab_width), 16); |
676 | ASSERT_EQ (next_tab_stop (9, tab_width), 16); |
677 | ASSERT_EQ (next_tab_stop (15, tab_width), 16); |
678 | |
679 | ASSERT_EQ (next_tab_stop (16, tab_width), 24); |
680 | ASSERT_EQ (next_tab_stop (17, tab_width), 24); |
681 | ASSERT_EQ (next_tab_stop (23, tab_width), 24); |
682 | } |
683 | |
684 | /* Verify that the given call to get_visual_column succeeds, with |
685 | the given results. */ |
686 | |
687 | static void |
688 | assert_get_visual_column_succeeds (const location &loc, |
689 | const char *file, int line, int column, |
690 | const unsigned int tab_width, |
691 | unsigned int expected_visual_column, |
692 | unsigned int expected_first_nws) |
693 | { |
694 | expanded_location exploc; |
695 | exploc.file = file; |
696 | exploc.line = line; |
697 | exploc.column = column; |
698 | exploc.data = NULL; |
699 | exploc.sysp = false; |
700 | unsigned int actual_visual_column; |
701 | unsigned int actual_first_nws; |
702 | bool result = get_visual_column (exploc, |
703 | out: &actual_visual_column, |
704 | first_nws: &actual_first_nws, tab_width); |
705 | ASSERT_TRUE_AT (loc, result); |
706 | ASSERT_EQ_AT (loc, actual_visual_column, expected_visual_column); |
707 | ASSERT_EQ_AT (loc, actual_first_nws, expected_first_nws); |
708 | } |
709 | |
710 | /* Verify that the given call to get_visual_column succeeds, with |
711 | the given results. */ |
712 | |
713 | #define ASSERT_GET_VISUAL_COLUMN_SUCCEEDS(FILENAME, LINE, COLUMN, \ |
714 | TAB_WIDTH, \ |
715 | EXPECTED_VISUAL_COLUMN, \ |
716 | EXPECTED_FIRST_NWS) \ |
717 | SELFTEST_BEGIN_STMT \ |
718 | assert_get_visual_column_succeeds (SELFTEST_LOCATION, \ |
719 | FILENAME, LINE, COLUMN, \ |
720 | TAB_WIDTH, \ |
721 | EXPECTED_VISUAL_COLUMN, \ |
722 | EXPECTED_FIRST_NWS); \ |
723 | SELFTEST_END_STMT |
724 | |
725 | /* Verify that the given call to get_visual_column fails gracefully. */ |
726 | |
727 | static void |
728 | assert_get_visual_column_fails (const location &loc, |
729 | const char *file, int line, int column, |
730 | const unsigned int tab_width) |
731 | { |
732 | expanded_location exploc; |
733 | exploc.file = file; |
734 | exploc.line = line; |
735 | exploc.column = column; |
736 | exploc.data = NULL; |
737 | exploc.sysp = false; |
738 | unsigned int actual_visual_column; |
739 | unsigned int actual_first_nws; |
740 | bool result = get_visual_column (exploc, |
741 | out: &actual_visual_column, |
742 | first_nws: &actual_first_nws, tab_width); |
743 | ASSERT_FALSE_AT (loc, result); |
744 | } |
745 | |
746 | /* Verify that the given call to get_visual_column fails gracefully. */ |
747 | |
748 | #define ASSERT_GET_VISUAL_COLUMN_FAILS(FILENAME, LINE, COLUMN, \ |
749 | TAB_WIDTH) \ |
750 | SELFTEST_BEGIN_STMT \ |
751 | assert_get_visual_column_fails (SELFTEST_LOCATION, \ |
752 | FILENAME, LINE, COLUMN, \ |
753 | TAB_WIDTH); \ |
754 | SELFTEST_END_STMT |
755 | |
756 | /* Verify that get_visual_column works as expected. */ |
757 | |
758 | static void |
759 | test_get_visual_column () |
760 | { |
761 | /* Create a tempfile with a mixture of tabs and spaces. |
762 | |
763 | Both lines have either a space or a tab, then " line N", |
764 | for 8 characters in total. |
765 | |
766 | 1-based "columns" (w.r.t. to line 1): |
767 | .....................0000000001111. |
768 | .....................1234567890123. */ |
769 | const char *content = (" line 1\n" |
770 | "\t line 2\n" ); |
771 | line_table_test ltt; |
772 | temp_source_file tmp (SELFTEST_LOCATION, ".txt" , content); |
773 | |
774 | const unsigned int tab_width = 8; |
775 | const char *file = tmp.get_filename (); |
776 | |
777 | /* Line 1 (space-based indentation). */ |
778 | { |
779 | const int line = 1; |
780 | ASSERT_GET_VISUAL_COLUMN_SUCCEEDS (file, line, 1, tab_width, 0, 0); |
781 | ASSERT_GET_VISUAL_COLUMN_SUCCEEDS (file, line, 2, tab_width, 1, 1); |
782 | ASSERT_GET_VISUAL_COLUMN_SUCCEEDS (file, line, 3, tab_width, 2, 2); |
783 | /* first_nws should have stopped increasing. */ |
784 | ASSERT_GET_VISUAL_COLUMN_SUCCEEDS (file, line, 4, tab_width, 3, 2); |
785 | /* Verify the end-of-line boundary. */ |
786 | ASSERT_GET_VISUAL_COLUMN_SUCCEEDS (file, line, 8, tab_width, 7, 2); |
787 | ASSERT_GET_VISUAL_COLUMN_FAILS (file, line, 9, tab_width); |
788 | } |
789 | |
790 | /* Line 2 (tab-based indentation). */ |
791 | { |
792 | const int line = 2; |
793 | ASSERT_GET_VISUAL_COLUMN_SUCCEEDS (file, line, 1, tab_width, 0, 0); |
794 | ASSERT_GET_VISUAL_COLUMN_SUCCEEDS (file, line, 2, tab_width, 8, 8); |
795 | ASSERT_GET_VISUAL_COLUMN_SUCCEEDS (file, line, 3, tab_width, 9, 9); |
796 | /* first_nws should have stopped increasing. */ |
797 | ASSERT_GET_VISUAL_COLUMN_SUCCEEDS (file, line, 4, tab_width, 10, 9); |
798 | /* Verify the end-of-line boundary. */ |
799 | ASSERT_GET_VISUAL_COLUMN_SUCCEEDS (file, line, 8, tab_width, 14, 9); |
800 | ASSERT_GET_VISUAL_COLUMN_FAILS (file, line, 9, tab_width); |
801 | } |
802 | } |
803 | |
804 | /* Run all of the selftests within this file. */ |
805 | |
806 | void |
807 | c_indentation_cc_tests () |
808 | { |
809 | test_next_tab_stop (); |
810 | test_get_visual_column (); |
811 | } |
812 | |
813 | } // namespace selftest |
814 | |
815 | #endif /* CHECKING_P */ |
816 | |