VirtualBox

source: vbox/trunk/src/bldprogs/scmrw.cpp@ 92185

Last change on this file since 92185 was 92185, checked in by vboxsync, 3 years ago

scm: Check UTF-8 encoding and reject files with bidirectional control codes in them.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 130.0 KB
Line 
1/* $Id: scmrw.cpp 92185 2021-11-02 21:52:35Z vboxsync $ */
2/** @file
3 * IPRT Testcase / Tool - Source Code Massager.
4 */
5
6/*
7 * Copyright (C) 2010-2020 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18
19/*********************************************************************************************************************************
20* Header Files *
21*********************************************************************************************************************************/
22#include <iprt/assert.h>
23#include <iprt/ctype.h>
24#include <iprt/dir.h>
25#include <iprt/env.h>
26#include <iprt/file.h>
27#include <iprt/err.h>
28#include <iprt/getopt.h>
29#include <iprt/initterm.h>
30#include <iprt/mem.h>
31#include <iprt/message.h>
32#include <iprt/param.h>
33#include <iprt/path.h>
34#include <iprt/process.h>
35#include <iprt/stream.h>
36#include <iprt/string.h>
37
38#include "scm.h"
39
40
41/*********************************************************************************************************************************
42* Structures and Typedefs *
43*********************************************************************************************************************************/
44/** License types. */
45typedef enum SCMLICENSETYPE
46{
47 kScmLicenseType_Invalid = 0,
48 kScmLicenseType_OseGpl,
49 kScmLicenseType_OseDualGplCddl,
50 kScmLicenseType_OseCddl,
51 kScmLicenseType_VBoxLgpl,
52 kScmLicenseType_Mit,
53 kScmLicenseType_Confidential
54} SCMLICENSETYPE;
55
56/** A license. */
57typedef struct SCMLICENSETEXT
58{
59 /** The license type. */
60 SCMLICENSETYPE enmType;
61 /** The license option. */
62 SCMLICENSE enmOpt;
63 /** The license text. */
64 const char *psz;
65 /** The license text length. */
66 size_t cch;
67} SCMLICENSETEXT;
68/** Pointer to a license. */
69typedef SCMLICENSETEXT const *PCSCMLICENSETEXT;
70
71/**
72 * Copyright + license rewriter state.
73 */
74typedef struct SCMCOPYRIGHTINFO
75{
76 /** State. */
77 PSCMRWSTATE pState; /**< input */
78 /** The comment style (neede for C/C++). */
79 SCMCOMMENTSTYLE enmCommentStyle; /**< input */
80
81 /** Number of comments we've parsed. */
82 uint32_t cComments;
83
84 /** Copy of the contributed-by line if present. */
85 char *pszContributedBy;
86
87 /** @name Common info
88 * @{ */
89 uint32_t iLineComment;
90 uint32_t cLinesComment; /**< This excludes any external license lines. */
91 /** @} */
92
93 /** @name Copyright info
94 * @{ */
95 uint32_t iLineCopyright;
96 uint32_t uFirstYear;
97 uint32_t uLastYear;
98 bool fWellFormedCopyright;
99 bool fUpToDateCopyright;
100 /** @} */
101
102 /** @name License info
103 * @{ */
104 bool fOpenSource; /**< input */
105 PCSCMLICENSETEXT pExpectedLicense; /**< input */
106 PCSCMLICENSETEXT paLicenses; /**< input */
107 SCMLICENSE enmLicenceOpt; /**< input */
108 uint32_t iLineLicense;
109 uint32_t cLinesLicense;
110 PCSCMLICENSETEXT pCurrentLicense;
111 bool fIsCorrectLicense;
112 bool fWellFormedLicense;
113 bool fExternalLicense;
114 /** @} */
115
116 /** @name LGPL licence notice and disclaimer info
117 * @{ */
118 /** Wheter to check for LGPL license notices and disclaimers. */
119 bool fCheckforLgpl;
120 /** The approximate line we found the (first) LGPL licence notice on. */
121 uint32_t iLineLgplNotice;
122 /** The line number after the LGPL notice comment. */
123 uint32_t iLineAfterLgplComment;
124 /** The LGPL disclaimer line. */
125 uint32_t iLineLgplDisclaimer;
126 /** @} */
127
128} SCMCOPYRIGHTINFO;
129typedef SCMCOPYRIGHTINFO *PSCMCOPYRIGHTINFO;
130
131
132/*********************************************************************************************************************************
133* Global Variables *
134*********************************************************************************************************************************/
135/** --license-ose-gpl */
136static const char g_szVBoxOseGpl[] =
137 "This file is part of VirtualBox Open Source Edition (OSE), as\n"
138 "available from http://www.virtualbox.org. This file is free software;\n"
139 "you can redistribute it and/or modify it under the terms of the GNU\n"
140 "General Public License (GPL) as published by the Free Software\n"
141 "Foundation, in version 2 as it comes in the \"COPYING\" file of the\n"
142 "VirtualBox OSE distribution. VirtualBox OSE is distributed in the\n"
143 "hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.\n";
144
145/** --license-ose-dual */
146static const char g_szVBoxOseDualGplCddl[] =
147 "This file is part of VirtualBox Open Source Edition (OSE), as\n"
148 "available from http://www.virtualbox.org. This file is free software;\n"
149 "you can redistribute it and/or modify it under the terms of the GNU\n"
150 "General Public License (GPL) as published by the Free Software\n"
151 "Foundation, in version 2 as it comes in the \"COPYING\" file of the\n"
152 "VirtualBox OSE distribution. VirtualBox OSE is distributed in the\n"
153 "hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.\n"
154 "\n"
155 "The contents of this file may alternatively be used under the terms\n"
156 "of the Common Development and Distribution License Version 1.0\n"
157 "(CDDL) only, as it comes in the \"COPYING.CDDL\" file of the\n"
158 "VirtualBox OSE distribution, in which case the provisions of the\n"
159 "CDDL are applicable instead of those of the GPL.\n"
160 "\n"
161 "You may elect to license modified versions of this file under the\n"
162 "terms and conditions of either the GPL or the CDDL or both.\n";
163
164/** --license-ose-cddl */
165static const char g_szVBoxOseCddl[] =
166 "This file is part of VirtualBox Open Source Edition (OSE), as\n"
167 "available from http://www.virtualbox.org. This file is free software;\n"
168 "you can redistribute it and/or modify it under the terms of the Common\n"
169 "Development and Distribution License Version 1.0 (CDDL) only, as it\n"
170 "comes in the \"COPYING.CDDL\" file of the VirtualBox OSE distribution.\n"
171 "VirtualBox OSE is distributed in the hope that it will be useful, but\n"
172 "WITHOUT ANY WARRANTY of any kind.\n";
173
174/** --license-lgpl */
175static const char g_szVBoxLgpl[] =
176 "This file is part of a free software library; you can redistribute\n"
177 "it and/or modify it under the terms of the GNU Lesser General\n"
178 "Public License version 2.1 as published by the Free Software\n"
179 "Foundation and shipped in the \"COPYING\" file with this library.\n"
180 "The library is distributed in the hope that it will be useful,\n"
181 "but WITHOUT ANY WARRANTY of any kind.\n"
182 "\n"
183 "Oracle LGPL Disclaimer: For the avoidance of doubt, except that if\n"
184 "any license choice other than GPL or LGPL is available it will\n"
185 "apply instead, Oracle elects to use only the Lesser General Public\n"
186 "License version 2.1 (LGPLv2) at this time for any software where\n"
187 "a choice of LGPL license versions is made available with the\n"
188 "language indicating that LGPLv2 or any later version may be used,\n"
189 "or where a choice of which version of the LGPL is applied is\n"
190 "otherwise unspecified.\n";
191
192/** --license-mit
193 * @note This isn't detectable as VirtualBox or Oracle specific.
194 */
195static const char g_szMit[] =
196 "Permission is hereby granted, free of charge, to any person\n"
197 "obtaining a copy of this software and associated documentation\n"
198 "files (the \"Software\"), to deal in the Software without\n"
199 "restriction, including without limitation the rights to use,\n"
200 "copy, modify, merge, publish, distribute, sublicense, and/or sell\n"
201 "copies of the Software, and to permit persons to whom the\n"
202 "Software is furnished to do so, subject to the following\n"
203 "conditions:\n"
204 "\n"
205 "The above copyright notice and this permission notice shall be\n"
206 "included in all copies or substantial portions of the Software.\n"
207 "\n"
208 "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n"
209 "EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n"
210 "OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n"
211 "NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n"
212 "HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n"
213 "WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n"
214 "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n"
215 "OTHER DEALINGS IN THE SOFTWARE.\n";
216
217/** --license-mit, alternative wording \#1.
218 * @note This differes from g_szMit in "AUTHORS OR COPYRIGHT HOLDERS" is written
219 * "COPYRIGHT HOLDER(S) OR AUTHOR(S)". Its layout is wider, so it is a
220 * couple of lines shorter. */
221static const char g_szMitAlt1[] =
222 "Permission is hereby granted, free of charge, to any person obtaining a\n"
223 "copy of this software and associated documentation files (the \"Software\"),\n"
224 "to deal in the Software without restriction, including without limitation\n"
225 "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n"
226 "and/or sell copies of the Software, and to permit persons to whom the\n"
227 "Software is furnished to do so, subject to the following conditions:\n"
228 "\n"
229 "The above copyright notice and this permission notice shall be included in\n"
230 "all copies or substantial portions of the Software.\n"
231 "\n"
232 "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n"
233 "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n"
234 "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n"
235 "THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR\n"
236 "OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\n"
237 "ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n"
238 "OTHER DEALINGS IN THE SOFTWARE.\n";
239
240/** --license-mit, alternative wording \#2.
241 * @note This differes from g_szMit in that "AUTHORS OR COPYRIGHT HOLDERS" is
242 * replaced with "THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS".
243 * Its layout is wider, so it is a couple of lines shorter. */
244static const char g_szMitAlt2[] =
245 "Permission is hereby granted, free of charge, to any person obtaining a\n"
246 "copy of this software and associated documentation files (the \"Software\"),\n"
247 "to deal in the Software without restriction, including without limitation\n"
248 "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n"
249 "and/or sell copies of the Software, and to permit persons to whom the\n"
250 "Software is furnished to do so, subject to the following conditions:\n"
251 "\n"
252 "The above copyright notice and this permission notice shall be included in\n"
253 "all copies or substantial portions of the Software.\n"
254 "\n"
255 "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n"
256 "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n"
257 "FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n"
258 "THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,\n"
259 "DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n"
260 "OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n"
261 "USE OR OTHER DEALINGS IN THE SOFTWARE.\n";
262
263/** --license-mit, alternative wording \#3.
264 * @note This differes from g_szMitAlt2 in that the second and third sections
265 * have been switch. */
266static const char g_szMitAlt3[] =
267 "Permission is hereby granted, free of charge, to any person obtaining a\n"
268 "copy of this software and associated documentation files (the \"Software\"),\n"
269 "to deal in the Software without restriction, including without limitation\n"
270 "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n"
271 "and/or sell copies of the Software, and to permit persons to whom the\n"
272 "Software is furnished to do so, subject to the following conditions:\n"
273 "\n"
274 "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n"
275 "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n"
276 "FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n"
277 "THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,\n"
278 "DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n"
279 "OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n"
280 "USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
281 "\n"
282 "The above copyright notice and this permission notice shall be included in\n"
283 "all copies or substantial portions of the Software.\n";
284
285/** --license-(based-on)mit, alternative wording \#4.
286 * @note This differs from g_szMitAlt2 in injecting "(including the next
287 * paragraph)". */
288static const char g_szMitAlt4[] =
289 "Permission is hereby granted, free of charge, to any person obtaining a\n"
290 "copy of this software and associated documentation files (the \"Software\"),\n"
291 "to deal in the Software without restriction, including without limitation\n"
292 "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n"
293 "and/or sell copies of the Software, and to permit persons to whom the\n"
294 "Software is furnished to do so, subject to the following conditions:\n"
295 "\n"
296 "The above copyright notice and this permission notice (including the next\n"
297 "paragraph) shall be included in all copies or substantial portions of the\n"
298 "Software.\n"
299 "\n"
300 "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n"
301 "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n"
302 "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n"
303 "THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n"
304 "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n"
305 "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n"
306 "DEALINGS IN THE SOFTWARE.\n";
307
308/** --license-(based-on)mit, alternative wording \#5.
309 * @note This differs from g_szMitAlt3 in using "sub license" instead of
310 * "sublicense" and adding an illogical "(including the next
311 * paragraph)" remark to the final paragraph. (vbox_ttm.c) */
312static const char g_szMitAlt5[] =
313 "Permission is hereby granted, free of charge, to any person obtaining a\n"
314 "copy of this software and associated documentation files (the\n"
315 "\"Software\"), to deal in the Software without restriction, including\n"
316 "without limitation the rights to use, copy, modify, merge, publish,\n"
317 "distribute, sub license, and/or sell copies of the Software, and to\n"
318 "permit persons to whom the Software is furnished to do so, subject to\n"
319 "the following conditions:\n"
320 "\n"
321 "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n"
322 "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n"
323 "FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n"
324 "THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,\n"
325 "DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n"
326 "OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n"
327 "USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
328 "\n"
329 "The above copyright notice and this permission notice (including the\n"
330 "next paragraph) shall be included in all copies or substantial portions\n"
331 "of the Software.\n";
332
333/** Oracle confidential. */
334static const char g_szOracleConfidential[] =
335 "Oracle Corporation confidential\n"
336 "All rights reserved\n";
337
338/** Licenses to detect when --license-mit isn't used. */
339static const SCMLICENSETEXT g_aLicenses[] =
340{
341 { kScmLicenseType_OseGpl, kScmLicense_OseGpl, RT_STR_TUPLE(g_szVBoxOseGpl)},
342 { kScmLicenseType_OseDualGplCddl, kScmLicense_OseDualGplCddl, RT_STR_TUPLE(g_szVBoxOseDualGplCddl) },
343 { kScmLicenseType_OseCddl, kScmLicense_OseCddl, RT_STR_TUPLE(g_szVBoxOseCddl) },
344 { kScmLicenseType_VBoxLgpl, kScmLicense_Lgpl, RT_STR_TUPLE(g_szVBoxLgpl)},
345 { kScmLicenseType_Confidential, kScmLicense_End, RT_STR_TUPLE(g_szOracleConfidential) },
346 { kScmLicenseType_Invalid, kScmLicense_End, NULL, 0 },
347};
348
349/** Licenses to detect when --license-mit or --license-based-on-mit are used. */
350static const SCMLICENSETEXT g_aLicensesWithMit[] =
351{
352 { kScmLicenseType_Mit, kScmLicense_Mit, RT_STR_TUPLE(g_szMit) },
353 { kScmLicenseType_Mit, kScmLicense_Mit, RT_STR_TUPLE(g_szMitAlt1) },
354 { kScmLicenseType_Mit, kScmLicense_Mit, RT_STR_TUPLE(g_szMitAlt2) },
355 { kScmLicenseType_Mit, kScmLicense_Mit, RT_STR_TUPLE(g_szMitAlt3) },
356 { kScmLicenseType_Mit, kScmLicense_Mit, RT_STR_TUPLE(g_szMitAlt4) },
357 { kScmLicenseType_Mit, kScmLicense_Mit, RT_STR_TUPLE(g_szMitAlt5) },
358 { kScmLicenseType_OseGpl, kScmLicense_OseGpl, RT_STR_TUPLE(g_szVBoxOseGpl)},
359 { kScmLicenseType_OseDualGplCddl, kScmLicense_OseDualGplCddl, RT_STR_TUPLE(g_szVBoxOseDualGplCddl) },
360 { kScmLicenseType_VBoxLgpl, kScmLicense_Lgpl, RT_STR_TUPLE(g_szVBoxLgpl)},
361 { kScmLicenseType_Confidential, kScmLicense_End, RT_STR_TUPLE(g_szOracleConfidential) },
362 { kScmLicenseType_Invalid, kScmLicense_End, NULL, 0 },
363};
364
365/** Copyright holder. */
366static const char g_szCopyrightHolder[] = "Oracle Corporation";
367
368/** LGPL disclaimer. */
369static const char g_szLgplDisclaimer[] =
370 "Oracle LGPL Disclaimer: For the avoidance of doubt, except that if any license choice\n"
371 "other than GPL or LGPL is available it will apply instead, Oracle elects to use only\n"
372 "the Lesser General Public License version 2.1 (LGPLv2) at this time for any software where\n"
373 "a choice of LGPL license versions is made available with the language indicating\n"
374 "that LGPLv2 or any later version may be used, or where a choice of which version\n"
375 "of the LGPL is applied is otherwise unspecified.\n";
376
377/** Copyright+license comment start for each SCMCOMMENTSTYLE. */
378static RTSTRTUPLE const g_aCopyrightCommentStart[] =
379{
380 { RT_STR_TUPLE("<invalid> ") },
381 { RT_STR_TUPLE("/*") },
382 { RT_STR_TUPLE("#") },
383 { RT_STR_TUPLE("\"\"\"") },
384 { RT_STR_TUPLE(";") },
385 { RT_STR_TUPLE("REM") },
386 { RT_STR_TUPLE("rem") },
387 { RT_STR_TUPLE("Rem") },
388 { RT_STR_TUPLE("--") },
389 { RT_STR_TUPLE("'") },
390 { RT_STR_TUPLE("<end>") },
391};
392
393/** Copyright+license line prefix for each SCMCOMMENTSTYLE. */
394static RTSTRTUPLE const g_aCopyrightCommentPrefix[] =
395{
396 { RT_STR_TUPLE("<invalid> ") },
397 { RT_STR_TUPLE(" * ") },
398 { RT_STR_TUPLE("# ") },
399 { RT_STR_TUPLE("") },
400 { RT_STR_TUPLE("; ") },
401 { RT_STR_TUPLE("REM ") },
402 { RT_STR_TUPLE("rem ") },
403 { RT_STR_TUPLE("Rem ") },
404 { RT_STR_TUPLE("-- ") },
405 { RT_STR_TUPLE("' ") },
406 { RT_STR_TUPLE("<end>") },
407};
408
409/** Copyright+license empty line for each SCMCOMMENTSTYLE. */
410static RTSTRTUPLE const g_aCopyrightCommentEmpty[] =
411{
412 { RT_STR_TUPLE("<invalid>") },
413 { RT_STR_TUPLE(" *") },
414 { RT_STR_TUPLE("#") },
415 { RT_STR_TUPLE("") },
416 { RT_STR_TUPLE(";") },
417 { RT_STR_TUPLE("REM") },
418 { RT_STR_TUPLE("rem") },
419 { RT_STR_TUPLE("Rem") },
420 { RT_STR_TUPLE("--") },
421 { RT_STR_TUPLE("'") },
422 { RT_STR_TUPLE("<end>") },
423};
424
425/** Copyright+license end of comment for each SCMCOMMENTSTYLE. */
426static RTSTRTUPLE const g_aCopyrightCommentEnd[] =
427{
428 { RT_STR_TUPLE("<invalid> ") },
429 { RT_STR_TUPLE(" */") },
430 { RT_STR_TUPLE("#") },
431 { RT_STR_TUPLE("\"\"\"") },
432 { RT_STR_TUPLE(";") },
433 { RT_STR_TUPLE("REM") },
434 { RT_STR_TUPLE("rem") },
435 { RT_STR_TUPLE("Rem") },
436 { RT_STR_TUPLE("--") },
437 { RT_STR_TUPLE("'") },
438 { RT_STR_TUPLE("<end>") },
439};
440
441
442/**
443 * Figures out the predominant casing of the "REM" keyword in a batch file.
444 *
445 * @returns Predominant comment style.
446 * @param pIn The file to scan. Will be rewound.
447 */
448static SCMCOMMENTSTYLE determinBatchFileCommentStyle(PSCMSTREAM pIn)
449{
450 /*
451 * Figure out whether it's using upper or lower case REM comments before
452 * doing the work.
453 */
454 uint32_t cUpper = 0;
455 uint32_t cLower = 0;
456 uint32_t cCamel = 0;
457 SCMEOL enmEol;
458 size_t cchLine;
459 const char *pchLine;
460 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
461 {
462 while ( cchLine > 2
463 && RT_C_IS_SPACE(*pchLine))
464 {
465 pchLine++;
466 cchLine--;
467 }
468 if ( ( cchLine > 3
469 && RT_C_IS_SPACE(pchLine[2]))
470 || cchLine == 3)
471 {
472 if ( pchLine[0] == 'R'
473 && pchLine[1] == 'E'
474 && pchLine[2] == 'M')
475 cUpper++;
476 else if ( pchLine[0] == 'r'
477 && pchLine[1] == 'e'
478 && pchLine[2] == 'm')
479 cLower++;
480 else if ( pchLine[0] == 'R'
481 && pchLine[1] == 'e'
482 && pchLine[2] == 'm')
483 cCamel++;
484 }
485 }
486
487 ScmStreamRewindForReading(pIn);
488
489 if (cLower >= cUpper && cLower >= cCamel)
490 return kScmCommentStyle_Rem_Lower;
491 if (cCamel >= cLower && cCamel >= cUpper)
492 return kScmCommentStyle_Rem_Camel;
493 return kScmCommentStyle_Rem_Upper;
494}
495
496
497/**
498 * Worker for isBlankLine.
499 *
500 * @returns true if blank, false if not.
501 * @param pchLine Pointer to the start of the line.
502 * @param cchLine The (encoded) length of the line, excluding EOL char.
503 */
504static bool isBlankLineSlow(const char *pchLine, size_t cchLine)
505{
506 /*
507 * From the end, more likely to hit a non-blank char there.
508 */
509 while (cchLine-- > 0)
510 if (!RT_C_IS_BLANK(pchLine[cchLine]))
511 return false;
512 return true;
513}
514
515/**
516 * Helper for checking whether a line is blank.
517 *
518 * @returns true if blank, false if not.
519 * @param pchLine Pointer to the start of the line.
520 * @param cchLine The (encoded) length of the line, excluding EOL char.
521 */
522DECLINLINE(bool) isBlankLine(const char *pchLine, size_t cchLine)
523{
524 if (cchLine == 0)
525 return true;
526 /*
527 * We're more likely to fine a non-space char at the end of the line than
528 * at the start, due to source code indentation.
529 */
530 if (pchLine[cchLine - 1])
531 return false;
532
533 /*
534 * Don't bother inlining loop code.
535 */
536 return isBlankLineSlow(pchLine, cchLine);
537}
538
539
540/**
541 * Checks if there are @a cch blanks at @a pch.
542 *
543 * @returns true if span of @a cch blanks, false if not.
544 * @param pch The start of the span to check.
545 * @param cch The length of the span.
546 */
547DECLINLINE(bool) isSpanOfBlanks(const char *pch, size_t cch)
548{
549 while (cch-- > 0)
550 {
551 char const ch = *pch++;
552 if (!RT_C_IS_BLANK(ch))
553 return false;
554 }
555 return true;
556}
557
558
559/**
560 * Strip trailing blanks (space & tab).
561 *
562 * @returns True if modified, false if not.
563 * @param pIn The input stream.
564 * @param pOut The output stream.
565 * @param pSettings The settings.
566 */
567bool rewrite_StripTrailingBlanks(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
568{
569 if (!pSettings->fStripTrailingBlanks)
570 return false;
571
572 bool fModified = false;
573 SCMEOL enmEol;
574 size_t cchLine;
575 const char *pchLine;
576 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
577 {
578 int rc;
579 if ( cchLine == 0
580 || !RT_C_IS_BLANK(pchLine[cchLine - 1]) )
581 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
582 else
583 {
584 cchLine--;
585 while (cchLine > 0 && RT_C_IS_BLANK(pchLine[cchLine - 1]))
586 cchLine--;
587 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
588 fModified = true;
589 }
590 if (RT_FAILURE(rc))
591 return false;
592 }
593 if (fModified)
594 ScmVerbose(pState, 2, " * Stripped trailing blanks\n");
595 return fModified;
596}
597
598/**
599 * Expand tabs.
600 *
601 * @returns True if modified, false if not.
602 * @param pIn The input stream.
603 * @param pOut The output stream.
604 * @param pSettings The settings.
605 */
606bool rewrite_ExpandTabs(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
607{
608 if (!pSettings->fConvertTabs)
609 return false;
610
611 size_t const cchTab = pSettings->cchTab;
612 bool fModified = false;
613 SCMEOL enmEol;
614 size_t cchLine;
615 const char *pchLine;
616 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
617 {
618 int rc;
619 const char *pchTab = (const char *)memchr(pchLine, '\t', cchLine);
620 if (!pchTab)
621 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
622 else
623 {
624 size_t offTab = 0;
625 const char *pchChunk = pchLine;
626 for (;;)
627 {
628 size_t cchChunk = pchTab - pchChunk;
629 offTab += cchChunk;
630 ScmStreamWrite(pOut, pchChunk, cchChunk);
631
632 size_t cchToTab = cchTab - offTab % cchTab;
633 ScmStreamWrite(pOut, g_szTabSpaces, cchToTab);
634 offTab += cchToTab;
635
636 pchChunk = pchTab + 1;
637 size_t cchLeft = cchLine - (pchChunk - pchLine);
638 pchTab = (const char *)memchr(pchChunk, '\t', cchLeft);
639 if (!pchTab)
640 {
641 rc = ScmStreamPutLine(pOut, pchChunk, cchLeft, enmEol);
642 break;
643 }
644 }
645
646 fModified = true;
647 }
648 if (RT_FAILURE(rc))
649 return false;
650 }
651 if (fModified)
652 ScmVerbose(pState, 2, " * Expanded tabs\n");
653 return fModified;
654}
655
656/**
657 * Worker for rewrite_ForceNativeEol, rewrite_ForceLF and rewrite_ForceCRLF.
658 *
659 * @returns true if modifications were made, false if not.
660 * @param pIn The input stream.
661 * @param pOut The output stream.
662 * @param pSettings The settings.
663 * @param enmDesiredEol The desired end of line indicator type.
664 * @param pszDesiredSvnEol The desired svn:eol-style.
665 */
666static bool rewrite_ForceEol(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings,
667 SCMEOL enmDesiredEol, const char *pszDesiredSvnEol)
668{
669 if (!pSettings->fConvertEol)
670 return false;
671
672 bool fModified = false;
673 SCMEOL enmEol;
674 size_t cchLine;
675 const char *pchLine;
676 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
677 {
678 if ( enmEol != enmDesiredEol
679 && enmEol != SCMEOL_NONE)
680 {
681 fModified = true;
682 enmEol = enmDesiredEol;
683 }
684 int rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
685 if (RT_FAILURE(rc))
686 return false;
687 }
688 if (fModified)
689 ScmVerbose(pState, 2, " * Converted EOL markers\n");
690
691 /* Check svn:eol-style if appropriate */
692 if ( pSettings->fSetSvnEol
693 && ScmSvnIsInWorkingCopy(pState))
694 {
695 char *pszEol;
696 int rc = ScmSvnQueryProperty(pState, "svn:eol-style", &pszEol);
697 if ( (RT_SUCCESS(rc) && strcmp(pszEol, pszDesiredSvnEol))
698 || rc == VERR_NOT_FOUND)
699 {
700 if (rc == VERR_NOT_FOUND)
701 ScmVerbose(pState, 2, " * Setting svn:eol-style to %s (missing)\n", pszDesiredSvnEol);
702 else
703 ScmVerbose(pState, 2, " * Setting svn:eol-style to %s (was: %s)\n", pszDesiredSvnEol, pszEol);
704 int rc2 = ScmSvnSetProperty(pState, "svn:eol-style", pszDesiredSvnEol);
705 if (RT_FAILURE(rc2))
706 ScmError(pState, rc2, "ScmSvnSetProperty: %Rrc\n", rc2);
707 }
708 if (RT_SUCCESS(rc))
709 RTStrFree(pszEol);
710 }
711
712 /** @todo also check the subversion svn:eol-style state! */
713 return fModified;
714}
715
716/**
717 * Force native end of line indicator.
718 *
719 * @returns true if modifications were made, false if not.
720 * @param pIn The input stream.
721 * @param pOut The output stream.
722 * @param pSettings The settings.
723 */
724bool rewrite_ForceNativeEol(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
725{
726#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
727 return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_CRLF, "native");
728#else
729 return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_LF, "native");
730#endif
731}
732
733/**
734 * Force the stream to use LF as the end of line indicator.
735 *
736 * @returns true if modifications were made, false if not.
737 * @param pIn The input stream.
738 * @param pOut The output stream.
739 * @param pSettings The settings.
740 */
741bool rewrite_ForceLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
742{
743 return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_LF, "LF");
744}
745
746/**
747 * Force the stream to use CRLF as the end of line indicator.
748 *
749 * @returns true if modifications were made, false if not.
750 * @param pIn The input stream.
751 * @param pOut The output stream.
752 * @param pSettings The settings.
753 */
754bool rewrite_ForceCRLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
755{
756 return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_CRLF, "CRLF");
757}
758
759/**
760 * Strip trailing blank lines and/or make sure there is exactly one blank line
761 * at the end of the file.
762 *
763 * @returns true if modifications were made, false if not.
764 * @param pIn The input stream.
765 * @param pOut The output stream.
766 * @param pSettings The settings.
767 *
768 * @remarks ASSUMES trailing white space has been removed already.
769 */
770bool rewrite_AdjustTrailingLines(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
771{
772 if ( !pSettings->fStripTrailingLines
773 && !pSettings->fForceTrailingLine
774 && !pSettings->fForceFinalEol)
775 return false;
776
777 size_t const cLines = ScmStreamCountLines(pIn);
778
779 /* Empty files remains empty. */
780 if (cLines <= 1)
781 return false;
782
783 /* Figure out if we need to adjust the number of lines or not. */
784 size_t cLinesNew = cLines;
785
786 if ( pSettings->fStripTrailingLines
787 && ScmStreamIsWhiteLine(pIn, cLinesNew - 1))
788 {
789 while ( cLinesNew > 1
790 && ScmStreamIsWhiteLine(pIn, cLinesNew - 2))
791 cLinesNew--;
792 }
793
794 if ( pSettings->fForceTrailingLine
795 && !ScmStreamIsWhiteLine(pIn, cLinesNew - 1))
796 cLinesNew++;
797
798 bool fFixMissingEol = pSettings->fForceFinalEol
799 && ScmStreamGetEolByLine(pIn, cLinesNew - 1) == SCMEOL_NONE;
800
801 if ( !fFixMissingEol
802 && cLines == cLinesNew)
803 return false;
804
805 /* Copy the number of lines we've arrived at. */
806 ScmStreamRewindForReading(pIn);
807
808 size_t cCopied = RT_MIN(cLinesNew, cLines);
809 ScmStreamCopyLines(pOut, pIn, cCopied);
810
811 if (cCopied != cLinesNew)
812 {
813 while (cCopied++ < cLinesNew)
814 ScmStreamPutLine(pOut, "", 0, ScmStreamGetEol(pIn));
815 }
816 /* Fix missing EOL if required. */
817 else if (fFixMissingEol)
818 {
819 if (ScmStreamGetEol(pIn) == SCMEOL_LF)
820 ScmStreamWrite(pOut, "\n", 1);
821 else
822 ScmStreamWrite(pOut, "\r\n", 2);
823 }
824
825 ScmVerbose(pState, 2, " * Adjusted trailing blank lines\n");
826 return true;
827}
828
829/**
830 * Make sure there is no svn:executable property on the current file.
831 *
832 * @returns false - the state carries these kinds of changes.
833 * @param pState The rewriter state.
834 * @param pIn The input stream.
835 * @param pOut The output stream.
836 * @param pSettings The settings.
837 */
838bool rewrite_SvnNoExecutable(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
839{
840 RT_NOREF2(pIn, pOut);
841 if ( !pSettings->fSetSvnExecutable
842 || !ScmSvnIsInWorkingCopy(pState))
843 return false;
844
845 int rc = ScmSvnQueryProperty(pState, "svn:executable", NULL);
846 if (RT_SUCCESS(rc))
847 {
848 ScmVerbose(pState, 2, " * removing svn:executable\n");
849 rc = ScmSvnDelProperty(pState, "svn:executable");
850 if (RT_FAILURE(rc))
851 ScmError(pState, rc, "ScmSvnSetProperty: %Rrc\n", rc);
852 }
853 return false;
854}
855
856/**
857 * Make sure there is no svn:keywords property on the current file.
858 *
859 * @returns false - the state carries these kinds of changes.
860 * @param pState The rewriter state.
861 * @param pIn The input stream.
862 * @param pOut The output stream.
863 * @param pSettings The settings.
864 */
865bool rewrite_SvnNoKeywords(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
866{
867 RT_NOREF2(pIn, pOut);
868 if ( !pSettings->fSetSvnExecutable
869 || !ScmSvnIsInWorkingCopy(pState))
870 return false;
871
872 int rc = ScmSvnQueryProperty(pState, "svn:keywords", NULL);
873 if (RT_SUCCESS(rc))
874 {
875 ScmVerbose(pState, 2, " * removing svn:keywords\n");
876 rc = ScmSvnDelProperty(pState, "svn:keywords");
877 if (RT_FAILURE(rc))
878 ScmError(pState, rc, "ScmSvnSetProperty: %Rrc\n", rc);
879 }
880 return false;
881}
882
883/**
884 * Make sure there is no svn:eol-style property on the current file.
885 *
886 * @returns false - the state carries these kinds of changes.
887 * @param pState The rewriter state.
888 * @param pIn The input stream.
889 * @param pOut The output stream.
890 * @param pSettings The settings.
891 */
892bool rewrite_SvnNoEolStyle(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
893{
894 RT_NOREF2(pIn, pOut);
895 if ( !pSettings->fSetSvnExecutable
896 || !ScmSvnIsInWorkingCopy(pState))
897 return false;
898
899 int rc = ScmSvnQueryProperty(pState, "svn:eol-style", NULL);
900 if (RT_SUCCESS(rc))
901 {
902 ScmVerbose(pState, 2, " * removing svn:eol-style\n");
903 rc = ScmSvnDelProperty(pState, "svn:eol-style");
904 if (RT_FAILURE(rc))
905 ScmError(pState, rc, "ScmSvnSetProperty: %Rrc\n", rc);
906 }
907 return false;
908}
909
910/**
911 * Makes sure the svn properties are appropriate for a binary.
912 *
913 * @returns false - the state carries these kinds of changes.
914 * @param pState The rewriter state.
915 * @param pIn The input stream.
916 * @param pOut The output stream.
917 * @param pSettings The settings.
918 */
919bool rewrite_SvnBinary(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
920{
921 RT_NOREF2(pIn, pOut);
922 if ( !pSettings->fSetSvnExecutable
923 || !ScmSvnIsInWorkingCopy(pState))
924 return false;
925
926 /* remove svn:eol-style and svn:keywords */
927 static const char * const s_apszRemove[] = { "svn:eol-style", "svn:keywords" };
928 for (uint32_t i = 0; i < RT_ELEMENTS(s_apszRemove); i++)
929 {
930 char *pszValue;
931 int rc = ScmSvnQueryProperty(pState, s_apszRemove[i], &pszValue);
932 if (RT_SUCCESS(rc))
933 {
934 ScmVerbose(pState, 2, " * removing %s=%s\n", s_apszRemove[i], pszValue);
935 RTStrFree(pszValue);
936 rc = ScmSvnDelProperty(pState, s_apszRemove[i]);
937 if (RT_FAILURE(rc))
938 ScmError(pState, rc, "ScmSvnSetProperty(,%s): %Rrc\n", s_apszRemove[i], rc);
939 }
940 else if (rc != VERR_NOT_FOUND)
941 ScmError(pState, rc, "ScmSvnQueryProperty: %Rrc\n", rc);
942 }
943
944 /* Make sure there is a svn:mime-type set. */
945 int rc = ScmSvnQueryProperty(pState, "svn:mime-type", NULL);
946 if (rc == VERR_NOT_FOUND)
947 {
948 ScmVerbose(pState, 2, " * settings svn:mime-type\n");
949 rc = ScmSvnSetProperty(pState, "svn:mime-type", "application/octet-stream");
950 if (RT_FAILURE(rc))
951 ScmError(pState, rc, "ScmSvnSetProperty: %Rrc\n", rc);
952 }
953 else if (RT_FAILURE(rc))
954 ScmError(pState, rc, "ScmSvnQueryProperty: %Rrc\n", rc);
955
956 return false;
957}
958
959/**
960 * Make sure the Id and Revision keywords are expanded.
961 *
962 * @returns false - the state carries these kinds of changes.
963 * @param pState The rewriter state.
964 * @param pIn The input stream.
965 * @param pOut The output stream.
966 * @param pSettings The settings.
967 */
968bool rewrite_SvnKeywords(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
969{
970 RT_NOREF2(pIn, pOut);
971 if ( !pSettings->fSetSvnKeywords
972 || !ScmSvnIsInWorkingCopy(pState))
973 return false;
974
975 char *pszKeywords;
976 int rc = ScmSvnQueryProperty(pState, "svn:keywords", &pszKeywords);
977 if ( RT_SUCCESS(rc)
978 && ( !strstr(pszKeywords, "Id") /** @todo need some function for finding a word in a string. */
979 || !strstr(pszKeywords, "Revision")) )
980 {
981 if (!strstr(pszKeywords, "Id") && !strstr(pszKeywords, "Revision"))
982 rc = RTStrAAppend(&pszKeywords, " Id Revision");
983 else if (!strstr(pszKeywords, "Id"))
984 rc = RTStrAAppend(&pszKeywords, " Id");
985 else
986 rc = RTStrAAppend(&pszKeywords, " Revision");
987 if (RT_SUCCESS(rc))
988 {
989 ScmVerbose(pState, 2, " * changing svn:keywords to '%s'\n", pszKeywords);
990 rc = ScmSvnSetProperty(pState, "svn:keywords", pszKeywords);
991 if (RT_FAILURE(rc))
992 ScmError(pState, rc, "ScmSvnSetProperty: %Rrc\n", rc);
993 }
994 else
995 ScmError(pState, rc, "RTStrAppend: %Rrc\n", rc);
996 RTStrFree(pszKeywords);
997 }
998 else if (rc == VERR_NOT_FOUND)
999 {
1000 ScmVerbose(pState, 2, " * setting svn:keywords to 'Id Revision'\n");
1001 rc = ScmSvnSetProperty(pState, "svn:keywords", "Id Revision");
1002 if (RT_FAILURE(rc))
1003 ScmError(pState, rc, "ScmSvnSetProperty: %Rrc\n", rc);
1004 }
1005 else if (RT_SUCCESS(rc))
1006 RTStrFree(pszKeywords);
1007
1008 return false;
1009}
1010
1011/**
1012 * Checks the svn:sync-process value and that parent is exported too.
1013 *
1014 * @returns false - the state carries these kinds of changes.
1015 * @param pState The rewriter state.
1016 * @param pIn The input stream.
1017 * @param pOut The output stream.
1018 * @param pSettings The settings.
1019 */
1020bool rewrite_SvnSyncProcess(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1021{
1022 RT_NOREF2(pIn, pOut);
1023 if ( pSettings->fSkipSvnSyncProcess
1024 || !ScmSvnIsInWorkingCopy(pState))
1025 return false;
1026
1027 char *pszSyncProcess;
1028 int rc = ScmSvnQueryProperty(pState, "svn:sync-process", &pszSyncProcess);
1029 if (RT_SUCCESS(rc))
1030 {
1031 if (strcmp(pszSyncProcess, "export") == 0)
1032 {
1033 char *pszParentSyncProcess;
1034 rc = ScmSvnQueryParentProperty(pState, "svn:sync-process", &pszParentSyncProcess);
1035 if (RT_SUCCESS(rc))
1036 {
1037 if (strcmp(pszSyncProcess, "export") != 0)
1038 ScmError(pState, VERR_INVALID_STATE,
1039 "svn:sync-process=export, but parent directory differs: %s\n"
1040 "WARNING! Make sure to unexport everything inside the directory first!\n"
1041 " Then you may export the directory and stuff inside it if you want.\n"
1042 " (Just exporting the directory will not make anything inside it externally visible.)\n"
1043 , pszParentSyncProcess);
1044 RTStrFree(pszParentSyncProcess);
1045 }
1046 else if (rc == VERR_NOT_FOUND)
1047 ScmError(pState, VERR_NOT_FOUND,
1048 "svn:sync-process=export, but parent directory is not exported!\n"
1049 "WARNING! Make sure to unexport everything inside the directory first!\n"
1050 " Then you may export the directory and stuff inside it if you want.\n"
1051 " (Just exporting the directory will not make anything inside it externally visible.)\n");
1052 else
1053 ScmError(pState, rc, "ScmSvnQueryParentProperty: %Rrc\n", rc);
1054 }
1055 else if (strcmp(pszSyncProcess, "ignore") != 0)
1056 ScmError(pState, VERR_INVALID_NAME, "Bad sync-process value: %s\n", pszSyncProcess);
1057 RTStrFree(pszSyncProcess);
1058 }
1059 else if (rc != VERR_NOT_FOUND)
1060 ScmError(pState, rc, "ScmSvnQueryProperty: %Rrc\n", rc);
1061
1062 return false;
1063}
1064
1065/**
1066 * Checks the that there is no bidirectional unicode fun in the file.
1067 *
1068 * @returns false - the state carries these kinds of changes.
1069 * @param pState The rewriter state.
1070 * @param pIn The input stream.
1071 * @param pOut The output stream.
1072 * @param pSettings The settings.
1073 */
1074bool rewrite_UnicodeChecks(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1075{
1076 RT_NOREF2(pIn, pOut);
1077 if (pSettings->fSkipUnicodeChecks)
1078 return false;
1079
1080 /*
1081 * Just scan the input for weird stuff and fail if we find anything we don't like.
1082 */
1083 uint32_t iLine = 0;
1084 SCMEOL enmEol;
1085 size_t cchLine;
1086 const char *pchLine;
1087 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
1088 {
1089 iLine++;
1090 const char *pchCur = pchLine;
1091 size_t cchLeft = cchLine;
1092 while (cchLeft > 0)
1093 {
1094 RTUNICP uc = 0;
1095 int rc = RTStrGetCpNEx(&pchCur, &cchLeft, &uc);
1096 if (RT_SUCCESS(rc))
1097 {
1098 const char *pszWhat;
1099 switch (uc)
1100 {
1101 default:
1102 continue;
1103
1104 /* Potentially evil bi-directional control codes (Table I, trojan-source.pdf): */
1105 case 0x202a: pszWhat = "LRE - left-to-right embedding"; break;
1106 case 0x202b: pszWhat = "RLE - right-to-left embedding"; break;
1107 case 0x202d: pszWhat = "LRO - left-to-right override"; break;
1108 case 0x202e: pszWhat = "RLO - right-to-left override"; break;
1109 case 0x2066: pszWhat = "LRI - left-to-right isolate"; break;
1110 case 0x2067: pszWhat = "RLI - right-to-left isolate"; break;
1111 case 0x2068: pszWhat = "FSI - first strong isolate"; break;
1112 case 0x202c: pszWhat = "PDF - pop directional formatting (LRE, RLE, LRO, RLO)"; break;
1113 case 0x2069: pszWhat = "PDI - pop directional isolate (LRI, RLI)"; break;
1114
1115 /** @todo add checks for homoglyphs too. */
1116 }
1117#if 1
1118 ScmError(pState, rc, "%u:%zu: Evil unicode codepoint: %s\n", iLine, pchCur - pchLine, pszWhat);
1119#else
1120 ScmVerbose(pState, 0, "%u:%zu: Evil unicode codepoint: %s\n", iLine, pchCur - pchLine, pszWhat);
1121#endif
1122 }
1123 else
1124#if 1
1125 ScmError(pState, rc, "%u:%zu: Invalid UTF-8 encoding: %Rrc\n", iLine, pchCur - pchLine, rc);
1126#else
1127 ScmVerbose(pState, 0, "%u:%zu: Invalid UTF-8 encoding: %Rrc\n", iLine, pchCur - pchLine, rc);
1128#endif
1129 }
1130 }
1131
1132 return false;
1133}
1134
1135/**
1136 * Compares two strings word-by-word, ignoring spaces, punctuation and case.
1137 *
1138 * Assumes ASCII strings.
1139 *
1140 * @returns true if they match, false if not.
1141 * @param psz1 The first string. This is typically the known one.
1142 * @param psz2 The second string. This is typically the unknown one,
1143 * which is why we return a next pointer for this one.
1144 * @param ppsz2Next Where to return the next part of the 2nd string. If
1145 * this is NULL, the whole string must match.
1146 */
1147static bool IsEqualWordByWordIgnoreCase(const char *psz1, const char *psz2, const char **ppsz2Next)
1148{
1149 for (;;)
1150 {
1151 /* Try compare raw strings first. */
1152 char ch1 = *psz1;
1153 char ch2 = *psz2;
1154 if ( ch1 == ch2
1155 || RT_C_TO_LOWER(ch1) == RT_C_TO_LOWER(ch2))
1156 {
1157 if (ch1)
1158 {
1159 psz1++;
1160 psz2++;
1161 }
1162 else
1163 {
1164 if (ppsz2Next)
1165 *ppsz2Next = psz2;
1166 return true;
1167 }
1168 }
1169 else
1170 {
1171 /* Try skip spaces an punctuation. */
1172 while ( RT_C_IS_SPACE(ch1)
1173 || RT_C_IS_PUNCT(ch1))
1174 ch1 = *++psz1;
1175
1176 if (ch1 == '\0' && ppsz2Next)
1177 {
1178 *ppsz2Next = psz2;
1179 return true;
1180 }
1181
1182 while ( RT_C_IS_SPACE(ch2)
1183 || RT_C_IS_PUNCT(ch2))
1184 ch2 = *++psz2;
1185
1186 if ( ch1 != ch2
1187 && RT_C_TO_LOWER(ch1) != RT_C_TO_LOWER(ch2))
1188 {
1189 if (ppsz2Next)
1190 *ppsz2Next = psz2;
1191 return false;
1192 }
1193 }
1194 }
1195}
1196
1197/**
1198 * Looks for @a pszFragment anywhere in @a pszText, ignoring spaces, punctuation
1199 * and case.
1200 *
1201 * @returns true if found, false if not.
1202 * @param pszText The haystack to search in.
1203 * @param cchText The length @a pszText.
1204 * @param pszFragment The needle to search for.
1205 * @param ppszStart Where to return the address in @a pszText where
1206 * the fragment was found. Optional.
1207 * @param ppszNext Where to return the pointer to the first char in
1208 * @a pszText after the fragment. Optional.
1209 *
1210 * @remarks First character of @a pszFragment must be an 7-bit ASCII character!
1211 * This character must not be space or punctuation.
1212 */
1213static bool scmContainsWordByWordIgnoreCase(const char *pszText, size_t cchText, const char *pszFragment,
1214 const char **ppszStart, const char **ppszNext)
1215{
1216 Assert(!((unsigned)*pszFragment & 0x80));
1217 Assert(pszText[cchText] == '\0');
1218 Assert(!RT_C_IS_BLANK(*pszFragment));
1219 Assert(!RT_C_IS_PUNCT(*pszFragment));
1220
1221 char chLower = RT_C_TO_LOWER(*pszFragment);
1222 char chUpper = RT_C_TO_UPPER(*pszFragment);
1223 for (;;)
1224 {
1225 const char *pszHit = (const char *)memchr(pszText, chLower, cchText);
1226 const char *pszHit2 = (const char *)memchr(pszText, chUpper, cchText);
1227 if (!pszHit && !pszHit2)
1228 {
1229 if (ppszStart)
1230 *ppszStart = NULL;
1231 if (ppszNext)
1232 *ppszNext = NULL;
1233 return false;
1234 }
1235
1236 if ( pszHit == NULL
1237 || ( pszHit2 != NULL
1238 && ((uintptr_t)pszHit2 < (uintptr_t)pszHit)) )
1239 pszHit = pszHit2;
1240
1241 const char *pszNext;
1242 if (IsEqualWordByWordIgnoreCase(pszFragment, pszHit, &pszNext))
1243 {
1244 if (ppszStart)
1245 *ppszStart = pszHit;
1246 if (ppszNext)
1247 *ppszNext = pszNext;
1248 return true;
1249 }
1250
1251 cchText -= pszHit - pszText + 1;
1252 pszText = pszHit + 1;
1253 }
1254}
1255
1256
1257/**
1258 * Counts the number of lines in the given substring.
1259 *
1260 * @returns The number of lines.
1261 * @param psz The start of the substring.
1262 * @param cch The length of the substring.
1263 */
1264static uint32_t CountLinesInSubstring(const char *psz, size_t cch)
1265{
1266 uint32_t cLines = 0;
1267 for (;;)
1268 {
1269 const char *pszEol = (const char *)memchr(psz, '\n', cch);
1270 if (pszEol)
1271 cLines++;
1272 else
1273 return cLines + (*psz != '\0');
1274 cch -= pszEol + 1 - psz;
1275 if (!cch)
1276 return cLines;
1277 psz = pszEol + 1;
1278 }
1279}
1280
1281
1282/**
1283 * Comment parser callback for locating copyright and license.
1284 */
1285static DECLCALLBACK(int)
1286rewrite_Copyright_CommentCallback(PCSCMCOMMENTINFO pInfo, const char *pszBody, size_t cchBody, void *pvUser)
1287{
1288 PSCMCOPYRIGHTINFO pState = (PSCMCOPYRIGHTINFO)pvUser;
1289 Assert(strlen(pszBody) == cchBody);
1290 //RTPrintf("--- comment at %u, type %u ---\n%s\n--- end ---\n", pInfo->iLineStart, pInfo->enmType, pszBody);
1291 ScmVerbose(pState->pState, 5,
1292 "--- comment at %u col %u, %u lines, type %u, %u lines before body, %u lines after body\n",
1293 pInfo->iLineStart, pInfo->offStart, pInfo->iLineEnd - pInfo->iLineStart + 1, pInfo->enmType,
1294 pInfo->cBlankLinesBefore, pInfo->cBlankLinesAfter);
1295
1296 pState->cComments++;
1297
1298 uint32_t iLine = pInfo->iLineStart + pInfo->cBlankLinesBefore;
1299
1300 /*
1301 * Look for a 'contributed by' or 'includes contributions from' line, these
1302 * comes first when present.
1303 */
1304 const char *pchContributedBy = NULL;
1305 size_t cchContributedBy = 0;
1306 size_t cBlankLinesAfterContributedBy = 0;
1307 if ( pState->pszContributedBy == NULL
1308 && ( pState->iLineCopyright == UINT32_MAX
1309 || pState->iLineLicense == UINT32_MAX)
1310 && ( ( cchBody > sizeof("Contributed by")
1311 && RTStrNICmp(pszBody, RT_STR_TUPLE("contributed by")) == 0)
1312 || ( cchBody > sizeof("Includes contributions from")
1313 && RTStrNICmp(pszBody, RT_STR_TUPLE("Includes contributions from")) == 0) ) )
1314 {
1315 const char *pszNextLine = (const char *)memchr(pszBody, '\n', cchBody);
1316 while (pszNextLine && pszNextLine[1] != '\n')
1317 pszNextLine = (const char *)memchr(pszNextLine + 1, '\n', cchBody);
1318 if (pszNextLine)
1319 {
1320 pchContributedBy = pszBody;
1321 cchContributedBy = pszNextLine - pszBody;
1322
1323 /* Skip the copyright line and any blank lines following it. */
1324 cchBody -= cchContributedBy + 1;
1325 pszBody = pszNextLine + 1;
1326 iLine += 1;
1327 while (*pszBody == '\n')
1328 {
1329 pszBody++;
1330 cchBody--;
1331 iLine++;
1332 cBlankLinesAfterContributedBy++;
1333 }
1334 }
1335 }
1336
1337 /*
1338 * Look for the copyright line.
1339 */
1340 bool fFoundCopyright = false;
1341 uint32_t cBlankLinesAfterCopyright = 0;
1342 if ( pState->iLineCopyright == UINT32_MAX
1343 && cchBody > sizeof("Copyright") + sizeof(g_szCopyrightHolder)
1344 && RTStrNICmp(pszBody, RT_STR_TUPLE("copyright")) == 0)
1345 {
1346 const char *pszNextLine = (const char *)memchr(pszBody, '\n', cchBody);
1347
1348 /* Oracle copyright? */
1349 const char *pszEnd = pszNextLine ? pszNextLine : &pszBody[cchBody];
1350 while (RT_C_IS_SPACE(pszEnd[-1]))
1351 pszEnd--;
1352 if ( (uintptr_t)(pszEnd - pszBody) > sizeof(g_szCopyrightHolder)
1353 && (*(unsigned char *)(pszEnd - sizeof(g_szCopyrightHolder) + 1) & 0x80) == 0 /* to avoid annoying assertion */
1354 && RTStrNICmp(pszEnd - sizeof(g_szCopyrightHolder) + 1, RT_STR_TUPLE(g_szCopyrightHolder)) == 0)
1355 {
1356 /* Parse out the year(s). */
1357 const char *psz = pszBody + sizeof("copyright");
1358 while ((uintptr_t)psz < (uintptr_t)pszEnd && !RT_C_IS_DIGIT(*psz))
1359 psz++;
1360 if (RT_C_IS_DIGIT(*psz))
1361 {
1362 char *pszNext;
1363 int rc = RTStrToUInt32Ex(psz, &pszNext, 10, &pState->uFirstYear);
1364 if ( RT_SUCCESS(rc)
1365 && rc != VWRN_NUMBER_TOO_BIG
1366 && rc != VWRN_NEGATIVE_UNSIGNED)
1367 {
1368 if ( pState->uFirstYear < 1975
1369 || pState->uFirstYear > 3000)
1370 {
1371 ScmError(pState->pState, VERR_OUT_OF_RANGE, "Copyright year is out of range: %u ('%.*s')\n",
1372 pState->uFirstYear, pszEnd - pszBody, pszBody);
1373 pState->uFirstYear = UINT32_MAX;
1374 }
1375
1376 while (RT_C_IS_SPACE(*pszNext))
1377 pszNext++;
1378 if (*pszNext == '-')
1379 {
1380 do
1381 pszNext++;
1382 while (RT_C_IS_SPACE(*pszNext));
1383 rc = RTStrToUInt32Ex(pszNext, &pszNext, 10, &pState->uLastYear);
1384 if ( RT_SUCCESS(rc)
1385 && rc != VWRN_NUMBER_TOO_BIG
1386 && rc != VWRN_NEGATIVE_UNSIGNED)
1387 {
1388 if ( pState->uLastYear < 1975
1389 || pState->uLastYear > 3000)
1390 {
1391 ScmError(pState->pState, VERR_OUT_OF_RANGE, "Second copyright year is out of range: %u ('%.*s')\n",
1392 pState->uLastYear, pszEnd - pszBody, pszBody);
1393 pState->uLastYear = UINT32_MAX;
1394 }
1395 else if (pState->uFirstYear > pState->uLastYear)
1396 {
1397 RTMsgWarning("Copyright years switched(?): '%.*s'\n", pszEnd - pszBody, pszBody);
1398 uint32_t iTmp = pState->uLastYear;
1399 pState->uLastYear = pState->uFirstYear;
1400 pState->uFirstYear = iTmp;
1401 }
1402 }
1403 else
1404 {
1405 pState->uLastYear = UINT32_MAX;
1406 ScmError(pState->pState, RT_SUCCESS(rc) ? -rc : rc,
1407 "Failed to parse second copyright year: '%.*s'\n", pszEnd - pszBody, pszBody);
1408 }
1409 }
1410 else if (*pszNext != g_szCopyrightHolder[0])
1411 ScmError(pState->pState, VERR_PARSE_ERROR,
1412 "Failed to parse copyright: '%.*s'\n", pszEnd - pszBody, pszBody);
1413 else
1414 pState->uLastYear = pState->uFirstYear;
1415 }
1416 else
1417 {
1418 pState->uFirstYear = UINT32_MAX;
1419 ScmError(pState->pState, RT_SUCCESS(rc) ? -rc : rc,
1420 "Failed to parse copyright year: '%.*s'\n", pszEnd - pszBody, pszBody);
1421 }
1422 }
1423
1424 /* The copyright comment must come before the license. */
1425 if (pState->iLineLicense != UINT32_MAX)
1426 ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright (line %u) must come before the license (line %u)!\n",
1427 iLine, pState->iLineLicense);
1428
1429 /* In C/C++ code, this must be a multiline comment. While in python it
1430 must be a */
1431 if (pState->enmCommentStyle == kScmCommentStyle_C && pInfo->enmType != kScmCommentType_MultiLine)
1432 ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright must appear in a multiline comment (no doxygen stuff)\n");
1433 else if (pState->enmCommentStyle == kScmCommentStyle_Python && pInfo->enmType != kScmCommentType_DocString)
1434 ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright must appear in a doc-string\n");
1435
1436 /* The copyright must be followed by the license. */
1437 if (!pszNextLine)
1438 ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright should be followed by the license text!\n");
1439
1440 /* Quit if we've flagged a failure. */
1441 if (RT_FAILURE(pState->pState->rc))
1442 return VERR_CALLBACK_RETURN;
1443
1444 /* Check if it's well formed and up to date. */
1445 char szWellFormed[256];
1446 size_t cchWellFormed;
1447 if (pState->uFirstYear == pState->uLastYear)
1448 cchWellFormed = RTStrPrintf(szWellFormed, sizeof(szWellFormed), "Copyright (C) %u %s",
1449 pState->uFirstYear, g_szCopyrightHolder);
1450 else
1451 cchWellFormed = RTStrPrintf(szWellFormed, sizeof(szWellFormed), "Copyright (C) %u-%u %s",
1452 pState->uFirstYear, pState->uLastYear, g_szCopyrightHolder);
1453 pState->fUpToDateCopyright = pState->uLastYear == g_uYear;
1454 pState->iLineCopyright = iLine;
1455 pState->fWellFormedCopyright = cchWellFormed == (uintptr_t)(pszEnd - pszBody)
1456 && memcmp(pszBody, szWellFormed, cchWellFormed) == 0;
1457 if (!pState->fWellFormedCopyright)
1458 ScmVerbose(pState->pState, 1, "* copyright isn't well formed\n");
1459
1460 /* If there wasn't exactly one blank line before the comment, trigger a rewrite. */
1461 if (pInfo->cBlankLinesBefore != 1)
1462 {
1463 ScmVerbose(pState->pState, 1, "* copyright comment is preceeded by %u blank lines instead of 1\n",
1464 pInfo->cBlankLinesBefore);
1465 pState->fWellFormedCopyright = false;
1466 }
1467
1468 /* If the comment doesn't start in column 1, trigger rewrite. */
1469 if (pInfo->offStart != 0)
1470 {
1471 ScmVerbose(pState->pState, 1, "* copyright comment starts in column %u instead of 1\n", pInfo->offStart + 1);
1472 pState->fWellFormedCopyright = false;
1473 /** @todo check that there isn't any code preceeding the comment. */
1474 }
1475
1476 if (pchContributedBy)
1477 {
1478 pState->pszContributedBy = RTStrDupN(pchContributedBy, cchContributedBy);
1479 if (cBlankLinesAfterContributedBy != 1)
1480 {
1481 ScmVerbose(pState->pState, 1, "* %u blank lines between contributed by and copyright, should be 1\n",
1482 cBlankLinesAfterContributedBy);
1483 pState->fWellFormedCopyright = false;
1484 }
1485 }
1486
1487 fFoundCopyright = true;
1488 ScmVerbose(pState->pState, 3, "oracle copyright %u-%u: up-to-date=%RTbool well-formed=%RTbool\n",
1489 pState->uFirstYear, pState->uLastYear, pState->fUpToDateCopyright, pState->fWellFormedCopyright);
1490 }
1491 else
1492 ScmVerbose(pState->pState, 3, "not oracle copyright: '%.*s'\n", pszEnd - pszBody, pszBody);
1493
1494 if (!pszNextLine)
1495 return VINF_SUCCESS;
1496
1497 /* Skip the copyright line and any blank lines following it. */
1498 cchBody -= pszNextLine - pszBody + 1;
1499 pszBody = pszNextLine + 1;
1500 iLine += 1;
1501 while (*pszBody == '\n')
1502 {
1503 pszBody++;
1504 cchBody--;
1505 iLine++;
1506 cBlankLinesAfterCopyright++;
1507 }
1508
1509 /*
1510 * If we have a based-on-mit scenario, check for the lead in now and
1511 * complain if not found.
1512 */
1513 if ( fFoundCopyright
1514 && pState->enmLicenceOpt == kScmLicense_BasedOnMit
1515 && pState->iLineLicense == UINT32_MAX)
1516 {
1517 if (RTStrNICmp(pszBody, RT_STR_TUPLE("This file is based on ")) == 0)
1518 {
1519 /* Take down a comment area which goes up to 'this file is based on'.
1520 The license line and length isn't used but gets set to cover the current line. */
1521 pState->iLineComment = pInfo->iLineStart;
1522 pState->cLinesComment = iLine - pInfo->iLineStart;
1523 pState->iLineLicense = iLine;
1524 pState->cLinesLicense = 1;
1525 pState->fExternalLicense = true;
1526 pState->fIsCorrectLicense = true;
1527 pState->fWellFormedLicense = true;
1528
1529 /* Check if we've got a MIT a license here or not. */
1530 pState->pCurrentLicense = NULL;
1531 do
1532 {
1533 const char *pszEol = (const char *)memchr(pszBody, '\n', cchBody);
1534 if (!pszEol || pszEol[1] == '\0')
1535 {
1536 pszBody += cchBody;
1537 cchBody = 0;
1538 break;
1539 }
1540 cchBody -= pszEol - pszBody + 1;
1541 pszBody = pszEol + 1;
1542 iLine++;
1543
1544 for (PCSCMLICENSETEXT pCur = pState->paLicenses; pCur->cch > 0; pCur++)
1545 {
1546 const char *pszNext;
1547 if ( pCur->cch <= cchBody + 32 /* (+ 32 since we don't compare spaces and punctuation) */
1548 && IsEqualWordByWordIgnoreCase(pCur->psz, pszBody, &pszNext))
1549 {
1550 pState->pCurrentLicense = pCur;
1551 break;
1552 }
1553 }
1554 } while (!pState->pCurrentLicense);
1555 if (!pState->pCurrentLicense)
1556 ScmError(pState->pState, VERR_NOT_FOUND, "Could not find the based-on license!\n");
1557 else if (pState->pCurrentLicense->enmType != kScmLicenseType_Mit)
1558 ScmError(pState->pState, VERR_NOT_FOUND, "The based-on license is not MIT (%.32s...)\n",
1559 pState->pCurrentLicense->psz);
1560 }
1561 else
1562 ScmError(pState->pState, VERR_WRONG_ORDER, "Expected 'This file is based on ...' after our copyright!\n");
1563 return VINF_SUCCESS;
1564 }
1565 }
1566
1567 /*
1568 * Look for LGPL like text in the comment.
1569 */
1570 if (pState->fCheckforLgpl && cchBody > 128)
1571 {
1572 /* We look for typical LGPL notices. */
1573 if (pState->iLineLgplNotice == UINT32_MAX)
1574 {
1575 static const char * const s_apszFragments[] =
1576 {
1577 "under the terms of the GNU Lesser General Public License",
1578 };
1579 for (unsigned i = 0; i < RT_ELEMENTS(s_apszFragments); i++)
1580 if (scmContainsWordByWordIgnoreCase(pszBody, cchBody, s_apszFragments[i], NULL, NULL))
1581 {
1582 pState->iLineLgplNotice = iLine;
1583 pState->iLineAfterLgplComment = pInfo->iLineEnd + 1;
1584 ScmVerbose(pState->pState, 3, "Found LGPL notice at %u\n", iLine);
1585 break;
1586 }
1587 }
1588
1589 if ( pState->iLineLgplDisclaimer == UINT32_MAX
1590 && scmContainsWordByWordIgnoreCase(pszBody, cchBody, g_szLgplDisclaimer, NULL, NULL))
1591 {
1592 pState->iLineLgplDisclaimer = iLine;
1593 ScmVerbose(pState->pState, 3, "Found LGPL disclaimer at %u\n", iLine);
1594 }
1595 }
1596
1597 /*
1598 * Look for the license text.
1599 */
1600 if (pState->iLineLicense == UINT32_MAX)
1601 {
1602 for (PCSCMLICENSETEXT pCur = pState->paLicenses; pCur->cch > 0; pCur++)
1603 {
1604 const char *pszNext;
1605 if ( pCur->cch <= cchBody + 32 /* (+ 32 since we don't compare spaces and punctuation) */
1606 && IsEqualWordByWordIgnoreCase(pCur->psz, pszBody, &pszNext))
1607 {
1608 while ( RT_C_IS_SPACE(*pszNext)
1609 || (RT_C_IS_PUNCT(*pszNext) && *pszNext != '-'))
1610 pszNext++;
1611
1612 uint32_t cDashes = 0;
1613 while (*pszNext == '-')
1614 cDashes++, pszNext++;
1615 bool fExternal = cDashes > 10;
1616
1617 if ( *pszNext == '\0'
1618 || fExternal)
1619 {
1620 /* In C/C++ code, this must be a multiline comment. While in python it
1621 must be a */
1622 if (pState->enmCommentStyle == kScmCommentStyle_C && pInfo->enmType != kScmCommentType_MultiLine)
1623 ScmError(pState->pState, VERR_WRONG_ORDER, "License must appear in a multiline comment (no doxygen stuff)\n");
1624 else if (pState->enmCommentStyle == kScmCommentStyle_Python && pInfo->enmType != kScmCommentType_DocString)
1625 ScmError(pState->pState, VERR_WRONG_ORDER, "License must appear in a doc-string\n");
1626
1627 /* Quit if we've flagged a failure. */
1628 if (RT_FAILURE(pState->pState->rc))
1629 return VERR_CALLBACK_RETURN;
1630
1631 /* Record it. */
1632 pState->iLineLicense = iLine;
1633 pState->cLinesLicense = CountLinesInSubstring(pszBody, pszNext - pszBody) - fExternal;
1634 pState->pCurrentLicense = pCur;
1635 pState->fExternalLicense = fExternal;
1636 pState->fIsCorrectLicense = pState->fOpenSource
1637 ? pCur == pState->pExpectedLicense
1638 : pCur->enmType == kScmLicenseType_Confidential;
1639 pState->fWellFormedLicense = memcmp(pszBody, pCur->psz, pCur->cch - 1) == 0;
1640 if (!pState->fWellFormedLicense)
1641 ScmVerbose(pState->pState, 1, "* license text isn't well-formed\n");
1642
1643 /* If there was more than one blank line between the copyright and the
1644 license text, extend the license text area and force a rewrite of it. */
1645 if (cBlankLinesAfterCopyright > 1)
1646 {
1647 ScmVerbose(pState->pState, 1, "* %u blank lines between copyright and license text, instead of 1\n",
1648 cBlankLinesAfterCopyright);
1649 pState->iLineLicense -= cBlankLinesAfterCopyright - 1;
1650 pState->cLinesLicense += cBlankLinesAfterCopyright - 1;
1651 pState->fWellFormedLicense = false;
1652 }
1653
1654 /* If there was more than one blank line after the license, trigger a rewrite. */
1655 if (!fExternal && pInfo->cBlankLinesAfter != 1)
1656 {
1657 ScmVerbose(pState->pState, 1, "* copyright comment is followed by %u blank lines instead of 1\n",
1658 pInfo->cBlankLinesAfter);
1659 pState->fWellFormedLicense = false;
1660 }
1661
1662 /** @todo Check that the last comment line doesn't have any code on it. */
1663 /** @todo Check that column 2 contains '*' for C/C++ files. */
1664
1665 ScmVerbose(pState->pState, 3,
1666 "Found license %d/%d at %u..%u: is-correct=%RTbool well-formed=%RTbool external-part=%RTbool open-source=%RTbool\n",
1667 pCur->enmType, pCur->enmOpt, pState->iLineLicense, pState->iLineLicense + pState->cLinesLicense,
1668 pState->fIsCorrectLicense, pState->fWellFormedLicense,
1669 pState->fExternalLicense, pState->fOpenSource);
1670
1671 if (fFoundCopyright)
1672 {
1673 pState->iLineComment = pInfo->iLineStart;
1674 pState->cLinesComment = (fExternal ? pState->iLineLicense + pState->cLinesLicense : pInfo->iLineEnd + 1)
1675 - pInfo->iLineStart;
1676 }
1677 else
1678 ScmError(pState->pState, VERR_WRONG_ORDER, "License should be preceeded by the copyright!\n");
1679 break;
1680 }
1681 }
1682 }
1683 }
1684
1685 if (fFoundCopyright && pState->iLineLicense == UINT32_MAX)
1686 ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright should be followed by the license text!\n");
1687
1688 /*
1689 * Stop looking for stuff after 100 comments.
1690 */
1691 if (pState->cComments > 100)
1692 return VERR_CALLBACK_RETURN;
1693 return VINF_SUCCESS;
1694}
1695
1696/**
1697 * Writes comment body text.
1698 *
1699 * @returns Stream status.
1700 * @param pOut The output stream.
1701 * @param pszText The text to write.
1702 * @param cchText The length of the text.
1703 * @param enmCommentStyle The comment style.
1704 * @param enmEol The EOL style.
1705 */
1706static int scmWriteCommentBody(PSCMSTREAM pOut, const char *pszText, size_t cchText,
1707 SCMCOMMENTSTYLE enmCommentStyle, SCMEOL enmEol)
1708{
1709 Assert(pszText[cchText - 1] == '\n');
1710 Assert(pszText[cchText - 2] != '\n');
1711 NOREF(cchText);
1712 do
1713 {
1714 const char *pszEol = strchr(pszText, '\n');
1715 if (pszEol != pszText)
1716 {
1717 ScmStreamWrite(pOut, g_aCopyrightCommentPrefix[enmCommentStyle].psz,
1718 g_aCopyrightCommentPrefix[enmCommentStyle].cch);
1719 ScmStreamWrite(pOut, pszText, pszEol - pszText);
1720 ScmStreamPutEol(pOut, enmEol);
1721 }
1722 else
1723 ScmStreamPutLine(pOut, g_aCopyrightCommentEmpty[enmCommentStyle].psz,
1724 g_aCopyrightCommentEmpty[enmCommentStyle].cch, enmEol);
1725 pszText = pszEol + 1;
1726 } while (*pszText != '\0');
1727 return ScmStreamGetStatus(pOut);
1728}
1729
1730
1731/**
1732 * Updates the copyright year and/or license text.
1733 *
1734 * @returns true if modifications were made, false if not.
1735 * @param pState The rewriter state.
1736 * @param pIn The input stream.
1737 * @param pOut The output stream.
1738 * @param pSettings The settings.
1739 * @param enmCommentStyle The comment style used by the file.
1740 */
1741static bool rewrite_Copyright_Common(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings,
1742 SCMCOMMENTSTYLE enmCommentStyle)
1743{
1744 if ( !pSettings->fUpdateCopyrightYear
1745 && pSettings->enmUpdateLicense == kScmLicense_LeaveAlone)
1746 return false;
1747
1748 /*
1749 * Try locate the relevant comments.
1750 */
1751 SCMCOPYRIGHTINFO Info =
1752 {
1753 /*.pState = */ pState,
1754 /*.enmCommentStyle = */ enmCommentStyle,
1755
1756 /*.cComments = */ 0,
1757
1758 /*.pszContributedBy = */ NULL,
1759
1760 /*.iLineComment = */ UINT32_MAX,
1761 /*.cLinesComment = */ 0,
1762
1763 /*.iLineCopyright = */ UINT32_MAX,
1764 /*.uFirstYear = */ UINT32_MAX,
1765 /*.uLastYear = */ UINT32_MAX,
1766 /*.fWellFormedCopyright = */ false,
1767 /*.fUpToDateCopyright = */ false,
1768
1769 /*.fOpenSource = */ true,
1770 /*.pExpectedLicense = */ NULL,
1771 /*.paLicenses = */ pSettings->enmUpdateLicense != kScmLicense_Mit
1772 && pSettings->enmUpdateLicense != kScmLicense_BasedOnMit
1773 ? &g_aLicenses[0] : &g_aLicensesWithMit[0],
1774 /*.enmLicenceOpt = */ pSettings->enmUpdateLicense,
1775 /*.iLineLicense = */ UINT32_MAX,
1776 /*.cLinesLicense = */ 0,
1777 /*.pCurrentLicense = */ NULL,
1778 /*.fIsCorrectLicense = */ false,
1779 /*.fWellFormedLicense = */ false,
1780 /*.fExternalLicense = */ false,
1781
1782 /*.fCheckForLgpl = */ true,
1783 /*.iLineLgplNotice = */ UINT32_MAX,
1784 /*.iLineAfterLgplComment = */ UINT32_MAX,
1785 /*.iLineLgplDisclaimer = */ UINT32_MAX,
1786 };
1787
1788 /* Figure Info.fOpenSource and the desired license: */
1789 char *pszSyncProcess;
1790 int rc = ScmSvnQueryProperty(pState, "svn:sync-process", &pszSyncProcess);
1791 if (RT_SUCCESS(rc))
1792 {
1793 Info.fOpenSource = strcmp(RTStrStrip(pszSyncProcess), "export") == 0;
1794 RTStrFree(pszSyncProcess);
1795 }
1796 else if (rc == VERR_NOT_FOUND)
1797 Info.fOpenSource = false;
1798 else
1799 return ScmError(pState, rc, "ScmSvnQueryProperty(svn:sync-process): %Rrc\n", rc);
1800
1801 Info.pExpectedLicense = Info.paLicenses;
1802 if (Info.fOpenSource)
1803 {
1804 if ( pSettings->enmUpdateLicense != kScmLicense_Mit
1805 && pSettings->enmUpdateLicense != kScmLicense_BasedOnMit)
1806 while (Info.pExpectedLicense->enmOpt != pSettings->enmUpdateLicense)
1807 Info.pExpectedLicense++;
1808 else
1809 Assert(Info.pExpectedLicense->enmOpt == kScmLicense_Mit);
1810 }
1811 else
1812 while (Info.pExpectedLicense->enmType != kScmLicenseType_Confidential)
1813 Info.pExpectedLicense++;
1814
1815 /* Scan the comments. */
1816 rc = ScmEnumerateComments(pIn, enmCommentStyle, rewrite_Copyright_CommentCallback, &Info);
1817 if ( (rc == VERR_CALLBACK_RETURN || RT_SUCCESS(rc))
1818 && RT_SUCCESS(pState->rc))
1819 {
1820 /*
1821 * Do conformity checks.
1822 */
1823 bool fAddLgplDisclaimer = false;
1824 if (Info.fCheckforLgpl)
1825 {
1826 if ( Info.iLineLgplNotice != UINT32_MAX
1827 && Info.iLineLgplDisclaimer == UINT32_MAX)
1828 {
1829 if (!pSettings->fLgplDisclaimer) /** @todo reconcile options with common sense. */
1830 ScmError(pState, VERR_NOT_FOUND, "LGPL licence notice on line %u, but no LGPL disclaimer was found!\n",
1831 Info.iLineLgplNotice + 1);
1832 else
1833 {
1834 ScmVerbose(pState, 1, "* Need to add LGPL disclaimer\n");
1835 fAddLgplDisclaimer = true;
1836 }
1837 }
1838 else if ( Info.iLineLgplNotice == UINT32_MAX
1839 && Info.iLineLgplDisclaimer != UINT32_MAX)
1840 ScmError(pState, VERR_NOT_FOUND, "LGPL disclaimer on line %u, but no LGPL copyright notice!\n",
1841 Info.iLineLgplDisclaimer + 1);
1842 }
1843
1844 if (!pSettings->fExternalCopyright)
1845 {
1846 if (Info.iLineCopyright == UINT32_MAX)
1847 ScmError(pState, VERR_NOT_FOUND, "Missing copyright!\n");
1848 if (Info.iLineLicense == UINT32_MAX)
1849 ScmError(pState, VERR_NOT_FOUND, "Missing license!\n");
1850 }
1851 else if (Info.iLineCopyright != UINT32_MAX)
1852 ScmError(pState, VERR_NOT_FOUND,
1853 "Marked as external copyright only, but found non-external copyright statement at line %u!\n",
1854 Info.iLineCopyright + 1);
1855
1856
1857 if (RT_SUCCESS(pState->rc))
1858 {
1859 /*
1860 * Do we need to make any changes?
1861 */
1862 bool fUpdateCopyright = !pSettings->fExternalCopyright
1863 && ( !Info.fWellFormedCopyright
1864 || (!Info.fUpToDateCopyright && pSettings->fUpdateCopyrightYear));
1865 bool fUpdateLicense = !pSettings->fExternalCopyright
1866 && Info.enmLicenceOpt != kScmLicense_LeaveAlone
1867 && ( !Info.fWellFormedLicense
1868 || !Info.fIsCorrectLicense);
1869 if ( fUpdateCopyright
1870 || fUpdateLicense
1871 || fAddLgplDisclaimer)
1872 {
1873 Assert(Info.iLineComment != UINT32_MAX);
1874 Assert(Info.cLinesComment > 0);
1875
1876 /*
1877 * Okay, do the work.
1878 */
1879 ScmStreamRewindForReading(pIn);
1880
1881 if (pSettings->fUpdateCopyrightYear)
1882 Info.uLastYear = g_uYear;
1883
1884 uint32_t iLine = 0;
1885 SCMEOL enmEol;
1886 size_t cchLine;
1887 const char *pchLine;
1888 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
1889 {
1890 if ( iLine == Info.iLineComment
1891 && (fUpdateCopyright || fUpdateLicense) )
1892 {
1893 /* Leading blank line. */
1894 ScmStreamPutLine(pOut, g_aCopyrightCommentStart[enmCommentStyle].psz,
1895 g_aCopyrightCommentStart[enmCommentStyle].cch, enmEol);
1896
1897 /* Contributed by someone? */
1898 if (Info.pszContributedBy)
1899 {
1900 const char *psz = Info.pszContributedBy;
1901 for (;;)
1902 {
1903 const char *pszEol = strchr(psz, '\n');
1904 size_t cchContribLine = pszEol ? pszEol - psz : strlen(psz);
1905 ScmStreamWrite(pOut, g_aCopyrightCommentPrefix[enmCommentStyle].psz,
1906 g_aCopyrightCommentPrefix[enmCommentStyle].cch);
1907 ScmStreamWrite(pOut, psz, cchContribLine);
1908 ScmStreamPutEol(pOut, enmEol);
1909 if (!pszEol)
1910 break;
1911 psz = pszEol + 1;
1912 }
1913
1914 ScmStreamPutLine(pOut, g_aCopyrightCommentEmpty[enmCommentStyle].psz,
1915 g_aCopyrightCommentEmpty[enmCommentStyle].cch, enmEol);
1916 }
1917
1918 /* Write the copyright comment line. */
1919 ScmStreamWrite(pOut, g_aCopyrightCommentPrefix[enmCommentStyle].psz,
1920 g_aCopyrightCommentPrefix[enmCommentStyle].cch);
1921
1922 char szCopyright[256];
1923 size_t cchCopyright;
1924 if (Info.uFirstYear == Info.uLastYear)
1925 cchCopyright = RTStrPrintf(szCopyright, sizeof(szCopyright), "Copyright (C) %u %s",
1926 Info.uFirstYear, g_szCopyrightHolder);
1927 else
1928 cchCopyright = RTStrPrintf(szCopyright, sizeof(szCopyright), "Copyright (C) %u-%u %s",
1929 Info.uFirstYear, Info.uLastYear, g_szCopyrightHolder);
1930
1931 ScmStreamWrite(pOut, szCopyright, cchCopyright);
1932 ScmStreamPutEol(pOut, enmEol);
1933
1934 if (pSettings->enmUpdateLicense != kScmLicense_BasedOnMit)
1935 {
1936 /* Blank line separating the two. */
1937 ScmStreamPutLine(pOut, g_aCopyrightCommentEmpty[enmCommentStyle].psz,
1938 g_aCopyrightCommentEmpty[enmCommentStyle].cch, enmEol);
1939
1940 /* Write the license text. */
1941 scmWriteCommentBody(pOut, Info.pExpectedLicense->psz, Info.pExpectedLicense->cch,
1942 enmCommentStyle, enmEol);
1943
1944 /* Final comment line. */
1945 if (!Info.fExternalLicense)
1946 ScmStreamPutLine(pOut, g_aCopyrightCommentEnd[enmCommentStyle].psz,
1947 g_aCopyrightCommentEnd[enmCommentStyle].cch, enmEol);
1948 }
1949 else
1950 Assert(Info.fExternalLicense);
1951
1952 /* Skip the copyright and license text in the input file. */
1953 rc = ScmStreamGetStatus(pOut);
1954 if (RT_SUCCESS(rc))
1955 {
1956 iLine = Info.iLineComment + Info.cLinesComment;
1957 rc = ScmStreamSeekByLine(pIn, iLine);
1958 }
1959 }
1960 /*
1961 * Add LGPL disclaimer?
1962 */
1963 else if ( iLine == Info.iLineAfterLgplComment
1964 && fAddLgplDisclaimer)
1965 {
1966 ScmStreamPutEol(pOut, enmEol);
1967 ScmStreamPutLine(pOut, g_aCopyrightCommentStart[enmCommentStyle].psz,
1968 g_aCopyrightCommentStart[enmCommentStyle].cch, enmEol);
1969 scmWriteCommentBody(pOut, g_szLgplDisclaimer, sizeof(g_szLgplDisclaimer) - 1,
1970 enmCommentStyle, enmEol);
1971 ScmStreamPutLine(pOut, g_aCopyrightCommentEnd[enmCommentStyle].psz,
1972 g_aCopyrightCommentEnd[enmCommentStyle].cch, enmEol);
1973
1974 /* put the actual line */
1975 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
1976 iLine++;
1977 }
1978 else
1979 {
1980 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
1981 iLine++;
1982 }
1983 if (RT_FAILURE(rc))
1984 {
1985 RTStrFree(Info.pszContributedBy);
1986 return false;
1987 }
1988 } /* for each source line */
1989
1990 RTStrFree(Info.pszContributedBy);
1991 return true;
1992 }
1993 }
1994 }
1995 else
1996 ScmError(pState, rc, "ScmEnumerateComments: %Rrc\n", rc);
1997 NOREF(pState); NOREF(pOut);
1998 RTStrFree(Info.pszContributedBy);
1999 return false;
2000}
2001
2002
2003/** Copyright updater for C-style comments. */
2004bool rewrite_Copyright_CstyleComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2005{
2006 return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_C);
2007}
2008
2009/** Copyright updater for hash-prefixed comments. */
2010bool rewrite_Copyright_HashComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2011{
2012 return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Hash);
2013}
2014
2015/** Copyright updater for REM-prefixed comments. */
2016bool rewrite_Copyright_RemComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2017{
2018 return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, determinBatchFileCommentStyle(pIn));
2019}
2020
2021/** Copyright updater for python comments. */
2022bool rewrite_Copyright_PythonComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2023{
2024 return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Python);
2025}
2026
2027/** Copyright updater for semicolon-prefixed comments. */
2028bool rewrite_Copyright_SemicolonComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2029{
2030 return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Semicolon);
2031}
2032
2033/** Copyright updater for sql comments. */
2034bool rewrite_Copyright_SqlComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2035{
2036 return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Sql);
2037}
2038
2039/** Copyright updater for tick-prefixed comments. */
2040bool rewrite_Copyright_TickComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2041{
2042 return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Tick);
2043}
2044
2045
2046/**
2047 * Makefile.kup are empty files, enforce this.
2048 *
2049 * @returns true if modifications were made, false if not.
2050 * @param pIn The input stream.
2051 * @param pOut The output stream.
2052 * @param pSettings The settings.
2053 */
2054bool rewrite_Makefile_kup(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2055{
2056 RT_NOREF2(pOut, pSettings);
2057
2058 /* These files should be zero bytes. */
2059 if (pIn->cb == 0)
2060 return false;
2061 ScmVerbose(pState, 2, " * Truncated file to zero bytes\n");
2062 return true;
2063}
2064
2065/**
2066 * Rewrite a kBuild makefile.
2067 *
2068 * @returns true if modifications were made, false if not.
2069 * @param pIn The input stream.
2070 * @param pOut The output stream.
2071 * @param pSettings The settings.
2072 *
2073 * @todo
2074 *
2075 * Ideas for Makefile.kmk and Config.kmk:
2076 * - sort if1of/ifn1of sets.
2077 * - line continuation slashes should only be preceded by one space.
2078 */
2079bool rewrite_Makefile_kmk(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2080{
2081 RT_NOREF4(pState, pIn, pOut, pSettings);
2082 return false;
2083}
2084
2085
2086static bool isFlowerBoxSectionMarker(PSCMSTREAM pIn, const char *pchLine, size_t cchLine, uint32_t cchWidth,
2087 const char **ppchText, size_t *pcchText, bool *pfNeedFixing)
2088{
2089 *ppchText = NULL;
2090 *pcchText = 0;
2091 *pfNeedFixing = false;
2092
2093 /*
2094 * The first line.
2095 */
2096 if (pchLine[0] != '/')
2097 return false;
2098 size_t offLine = 1;
2099 while (offLine < cchLine && pchLine[offLine] == '*')
2100 offLine++;
2101 if (offLine < 20) /* (Code below depend on a reasonable minimum here.) */
2102 return false;
2103 while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine]))
2104 offLine++;
2105 if (offLine != cchLine)
2106 return false;
2107
2108 size_t const cchBox = cchLine;
2109 *pfNeedFixing = cchBox != cchWidth;
2110
2111 /*
2112 * The next line, extracting the text.
2113 */
2114 SCMEOL enmEol;
2115 pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
2116 if (cchLine < cchBox - 3)
2117 return false;
2118
2119 offLine = 0;
2120 if (RT_C_IS_BLANK(pchLine[0]))
2121 {
2122 *pfNeedFixing = true;
2123 offLine = RT_C_IS_BLANK(pchLine[1]) ? 2 : 1;
2124 }
2125
2126 if (pchLine[offLine] != '*')
2127 return false;
2128 offLine++;
2129
2130 if (!RT_C_IS_BLANK(pchLine[offLine + 1]))
2131 return false;
2132 offLine++;
2133
2134 while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine]))
2135 offLine++;
2136 if (offLine >= cchLine)
2137 return false;
2138 if (!RT_C_IS_UPPER(pchLine[offLine]))
2139 return false;
2140
2141 if (offLine != 4 || cchLine != cchBox)
2142 *pfNeedFixing = true;
2143
2144 *ppchText = &pchLine[offLine];
2145 size_t const offText = offLine;
2146
2147 /* From the end now. */
2148 offLine = cchLine - 1;
2149 while (RT_C_IS_BLANK(pchLine[offLine]))
2150 offLine--;
2151
2152 if (pchLine[offLine] != '*')
2153 return false;
2154 offLine--;
2155 if (!RT_C_IS_BLANK(pchLine[offLine]))
2156 return false;
2157 offLine--;
2158 while (RT_C_IS_BLANK(pchLine[offLine]))
2159 offLine--;
2160 *pcchText = offLine - offText + 1;
2161
2162 /*
2163 * Third line closes the box.
2164 */
2165 pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
2166 if (cchLine < cchBox - 3)
2167 return false;
2168
2169 offLine = 0;
2170 if (RT_C_IS_BLANK(pchLine[0]))
2171 {
2172 *pfNeedFixing = true;
2173 offLine = RT_C_IS_BLANK(pchLine[1]) ? 2 : 1;
2174 }
2175 while (offLine < cchLine && pchLine[offLine] == '*')
2176 offLine++;
2177 if (offLine < cchBox - 4)
2178 return false;
2179
2180 if (pchLine[offLine] != '/')
2181 return false;
2182 offLine++;
2183
2184 if (offLine != cchBox)
2185 *pfNeedFixing = true;
2186
2187 while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine]))
2188 offLine++;
2189 if (offLine != cchLine)
2190 return false;
2191
2192 return true;
2193}
2194
2195
2196/**
2197 * Flower box marker comments in C and C++ code.
2198 *
2199 * @returns true if modifications were made, false if not.
2200 * @param pIn The input stream.
2201 * @param pOut The output stream.
2202 * @param pSettings The settings.
2203 */
2204bool rewrite_FixFlowerBoxMarkers(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2205{
2206 if (!pSettings->fFixFlowerBoxMarkers)
2207 return false;
2208
2209 /*
2210 * Work thru the file line by line looking for flower box markers.
2211 */
2212 size_t cChanges = 0;
2213 size_t cBlankLines = 0;
2214 SCMEOL enmEol;
2215 size_t cchLine;
2216 const char *pchLine;
2217 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
2218 {
2219 /*
2220 * Get a likely match for a first line.
2221 */
2222 if ( pchLine[0] == '/'
2223 && cchLine > 20
2224 && pchLine[1] == '*'
2225 && pchLine[2] == '*'
2226 && pchLine[3] == '*')
2227 {
2228 size_t const offSaved = ScmStreamTell(pIn);
2229 char const *pchText;
2230 size_t cchText;
2231 bool fNeedFixing;
2232 bool fIsFlowerBoxSection = isFlowerBoxSectionMarker(pIn, pchLine, cchLine, pSettings->cchWidth,
2233 &pchText, &cchText, &fNeedFixing);
2234 if ( fIsFlowerBoxSection
2235 && ( fNeedFixing
2236 || cBlankLines < pSettings->cMinBlankLinesBeforeFlowerBoxMakers) )
2237 {
2238 while (cBlankLines < pSettings->cMinBlankLinesBeforeFlowerBoxMakers)
2239 {
2240 ScmStreamPutEol(pOut, enmEol);
2241 cBlankLines++;
2242 }
2243
2244 ScmStreamPutCh(pOut, '/');
2245 ScmStreamWrite(pOut, g_szAsterisks, pSettings->cchWidth - 1);
2246 ScmStreamPutEol(pOut, enmEol);
2247
2248 static const char s_szLead[] = "* ";
2249 ScmStreamWrite(pOut, s_szLead, sizeof(s_szLead) - 1);
2250 ScmStreamWrite(pOut, pchText, cchText);
2251 size_t offCurPlus1 = sizeof(s_szLead) - 1 + cchText + 1;
2252 ScmStreamWrite(pOut, g_szSpaces, offCurPlus1 < pSettings->cchWidth ? pSettings->cchWidth - offCurPlus1 : 1);
2253 ScmStreamPutCh(pOut, '*');
2254 ScmStreamPutEol(pOut, enmEol);
2255
2256 ScmStreamWrite(pOut, g_szAsterisks, pSettings->cchWidth - 1);
2257 ScmStreamPutCh(pOut, '/');
2258 ScmStreamPutEol(pOut, enmEol);
2259
2260 cChanges++;
2261 cBlankLines = 0;
2262 continue;
2263 }
2264
2265 int rc = ScmStreamSeekAbsolute(pIn, offSaved);
2266 if (RT_FAILURE(rc))
2267 return false;
2268 }
2269
2270 int rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
2271 if (RT_FAILURE(rc))
2272 return false;
2273
2274 /* Do blank line accounting so we can ensure at least two blank lines
2275 before each section marker. */
2276 if (!isBlankLine(pchLine, cchLine))
2277 cBlankLines = 0;
2278 else
2279 cBlankLines++;
2280 }
2281 if (cChanges > 0)
2282 ScmVerbose(pState, 2, " * Converted %zu flower boxer markers\n", cChanges);
2283 return cChanges != 0;
2284}
2285
2286
2287/**
2288 * Looks for the start of a todo comment.
2289 *
2290 * @returns Offset into the line of the comment start sequence.
2291 * @param pchLine The line to search.
2292 * @param cchLineBeforeTodo The length of the line before the todo.
2293 * @param pfSameLine Indicates whether it's refering to a statemtn on
2294 * the same line comment (true), or the next
2295 * statement (false).
2296 */
2297static size_t findTodoCommentStart(char const *pchLine, size_t cchLineBeforeTodo, bool *pfSameLine)
2298{
2299 *pfSameLine = false;
2300
2301 /* Skip one '@' or '\\'. */
2302 char ch;
2303 if ( cchLineBeforeTodo > 2
2304 && ( ((ch = pchLine[cchLineBeforeTodo - 1]) == '@')
2305 || ch == '\\' ) )
2306 cchLineBeforeTodo--;
2307
2308 /* Skip blanks. */
2309 while ( cchLineBeforeTodo > 2
2310 && RT_C_IS_BLANK(pchLine[cchLineBeforeTodo - 1]))
2311 cchLineBeforeTodo--;
2312
2313 /* Look for same line indicator. */
2314 if ( cchLineBeforeTodo > 0
2315 && pchLine[cchLineBeforeTodo - 1] == '<')
2316 {
2317 *pfSameLine = true;
2318 cchLineBeforeTodo--;
2319 }
2320
2321 /* Skip *s */
2322 while ( cchLineBeforeTodo > 1
2323 && pchLine[cchLineBeforeTodo - 1] == '*')
2324 cchLineBeforeTodo--;
2325
2326 /* Do we have a comment opening sequence. */
2327 if ( cchLineBeforeTodo > 0
2328 && pchLine[cchLineBeforeTodo - 1] == '/'
2329 && ( ( cchLineBeforeTodo >= 2
2330 && pchLine[cchLineBeforeTodo - 2] == '/')
2331 || pchLine[cchLineBeforeTodo] == '*'))
2332 {
2333 /* Skip slashes at the start. */
2334 while ( cchLineBeforeTodo > 0
2335 && pchLine[cchLineBeforeTodo - 1] == '/')
2336 cchLineBeforeTodo--;
2337
2338 return cchLineBeforeTodo;
2339 }
2340
2341 return ~(size_t)0;
2342}
2343
2344
2345/**
2346 * Looks for a TODO or todo in the given line.
2347 *
2348 * @returns Offset into the line of found, ~(size_t)0 if not.
2349 * @param pchLine The line to search.
2350 * @param cchLine The length of the line.
2351 */
2352static size_t findTodo(char const *pchLine, size_t cchLine)
2353{
2354 if (cchLine >= 4 + 2)
2355 {
2356 /* We don't search the first to chars because we need the start of a comment.
2357 Also, skip the last three chars since we need at least four for a match. */
2358 size_t const cchLineT = cchLine - 3;
2359 if ( memchr(pchLine + 2, 't', cchLineT - 2) != NULL
2360 || memchr(pchLine + 2, 'T', cchLineT - 2) != NULL)
2361 {
2362 for (size_t off = 2; off < cchLineT; off++)
2363 {
2364 char ch = pchLine[off];
2365 if ( ( ch != 't'
2366 && ch != 'T')
2367 || ( (ch = pchLine[off + 1]) != 'o'
2368 && ch != 'O')
2369 || ( (ch = pchLine[off + 2]) != 'd'
2370 && ch != 'D')
2371 || ( (ch = pchLine[off + 3]) != 'o'
2372 && ch != 'O')
2373 || ( off + 4 != cchLine
2374 && (ch = pchLine[off + 4]) != ' '
2375 && ch != '\t'
2376 && ch != ':' /** @todo */
2377 && (ch != '*' || off + 5 > cchLine || pchLine[off + 5] != '/') /** @todo */
2378 ) )
2379 { /* not a hit - likely */ }
2380 else
2381 return off;
2382 }
2383 }
2384 }
2385 return ~(size_t)0;
2386}
2387
2388
2389/**
2390 * Doxygen todos in C and C++ code.
2391 *
2392 * @returns true if modifications were made, false if not.
2393 * @param pState The rewriter state.
2394 * @param pIn The input stream.
2395 * @param pOut The output stream.
2396 * @param pSettings The settings.
2397 */
2398bool rewrite_Fix_C_and_CPP_Todos(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2399{
2400 if (!pSettings->fFixTodos)
2401 return false;
2402
2403 /*
2404 * Work thru the file line by line looking for the start of todo comments.
2405 */
2406 size_t cChanges = 0;
2407 SCMEOL enmEol;
2408 size_t cchLine;
2409 const char *pchLine;
2410 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
2411 {
2412 /*
2413 * Look for the word 'todo' in the line. We're currently only trying
2414 * to catch comments starting with the word todo and adjust the start of
2415 * the doxygen statement.
2416 */
2417 size_t offTodo = findTodo(pchLine, cchLine);
2418 if ( offTodo != ~(size_t)0
2419 && offTodo >= 2)
2420 {
2421 /* Work backwards to find the start of the comment. */
2422 bool fSameLine = false;
2423 size_t offCommentStart = findTodoCommentStart(pchLine, offTodo, &fSameLine);
2424 if (offCommentStart != ~(size_t)0)
2425 {
2426 char szNew[64];
2427 size_t cchNew = 0;
2428 szNew[cchNew++] = '/';
2429 szNew[cchNew++] = pchLine[offCommentStart + 1];
2430 szNew[cchNew++] = pchLine[offCommentStart + 1];
2431 if (fSameLine)
2432 szNew[cchNew++] = '<';
2433 szNew[cchNew++] = ' ';
2434 szNew[cchNew++] = '@';
2435 szNew[cchNew++] = 't';
2436 szNew[cchNew++] = 'o';
2437 szNew[cchNew++] = 'd';
2438 szNew[cchNew++] = 'o';
2439
2440 /* Figure out wheter to continue after the @todo statement opening, we'll strip ':'
2441 but need to take into account that we might be at the end of the line before
2442 adding the space. */
2443 size_t offTodoAfter = offTodo + 4;
2444 if ( offTodoAfter < cchLine
2445 && pchLine[offTodoAfter] == ':')
2446 offTodoAfter++;
2447 if ( offTodoAfter < cchLine
2448 && RT_C_IS_BLANK(pchLine[offTodoAfter]))
2449 offTodoAfter++;
2450 if (offTodoAfter < cchLine)
2451 szNew[cchNew++] = ' ';
2452
2453 /* Write it out. */
2454 ScmStreamWrite(pOut, pchLine, offCommentStart);
2455 ScmStreamWrite(pOut, szNew, cchNew);
2456 if (offTodoAfter < cchLine)
2457 ScmStreamWrite(pOut, &pchLine[offTodoAfter], cchLine - offTodoAfter);
2458 ScmStreamPutEol(pOut, enmEol);
2459
2460 /* Check whether we actually made any changes. */
2461 if ( cchNew != offTodoAfter - offCommentStart
2462 || memcmp(szNew, &pchLine[offCommentStart], cchNew))
2463 cChanges++;
2464 continue;
2465 }
2466 }
2467
2468 int rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
2469 if (RT_FAILURE(rc))
2470 return false;
2471 }
2472 if (cChanges > 0)
2473 ScmVerbose(pState, 2, " * Converted %zu todo statements.\n", cChanges);
2474 return cChanges != 0;
2475}
2476
2477
2478/**
2479 * Tries to parse a C/C++ preprocessor include directive.
2480 *
2481 * This is resonably forgiving and expects sane input.
2482 *
2483 * @retval kScmIncludeDir_Invalid if not a valid include directive.
2484 * @retval kScmIncludeDir_Quoted
2485 * @retval kScmIncludeDir_Bracketed
2486 * @retval kScmIncludeDir_Macro
2487 *
2488 * @param pState The rewriter state (for repording malformed
2489 * directives).
2490 * @param pchLine The line to try parse as an include statement.
2491 * @param cchLine The line length.
2492 * @param ppchFilename Where to return the pointer to the filename part.
2493 * @param pcchFilename Where to return the length of the filename.
2494 */
2495SCMINCLUDEDIR ScmMaybeParseCIncludeLine(PSCMRWSTATE pState, const char *pchLine, size_t cchLine,
2496 const char **ppchFilename, size_t *pcchFilename)
2497{
2498 /* Skip leading spaces: */
2499 while (cchLine > 0 && RT_C_IS_BLANK(*pchLine))
2500 cchLine--, pchLine++;
2501
2502 /* Check for '#': */
2503 if (cchLine > 0 && *pchLine == '#')
2504 {
2505 cchLine--;
2506 pchLine++;
2507
2508 /* Skip spaces after '#' (optional): */
2509 while (cchLine > 0 && RT_C_IS_BLANK(*pchLine))
2510 cchLine--, pchLine++;
2511
2512 /* Check for 'include': */
2513 static char const s_szInclude[] = "include";
2514 if ( cchLine >= sizeof(s_szInclude)
2515 && memcmp(pchLine, RT_STR_TUPLE(s_szInclude)) == 0)
2516 {
2517 cchLine -= sizeof(s_szInclude) - 1;
2518 pchLine += sizeof(s_szInclude) - 1;
2519
2520 /* Skip spaces after 'include' word (optional): */
2521 while (cchLine > 0 && RT_C_IS_BLANK(*pchLine))
2522 cchLine--, pchLine++;
2523 if (cchLine > 0)
2524 {
2525 /* Quoted or bracketed? */
2526 char const chFirst = *pchLine;
2527 if (chFirst == '"' || chFirst == '<')
2528 {
2529 cchLine--;
2530 pchLine++;
2531 const char *pchEnd = (const char *)memchr(pchLine, chFirst == '"' ? '"' : '>', cchLine);
2532 if (pchEnd)
2533 {
2534 if (ppchFilename)
2535 *ppchFilename = pchLine;
2536 if (pcchFilename)
2537 *pcchFilename = pchEnd - pchLine;
2538 return chFirst == '"' ? kScmIncludeDir_Quoted : kScmIncludeDir_Bracketed;
2539 }
2540 ScmError(pState, VERR_PARSE_ERROR, "Unbalanced #include filename %s: %.*s\n",
2541 chFirst == '"' ? "quotes" : "brackets" , cchLine, pchLine);
2542 }
2543 /* C prepreprocessor macro? */
2544 else if (ScmIsCIdentifierLeadChar(chFirst))
2545 {
2546 size_t cchFilename = 1;
2547 while ( cchFilename < cchLine
2548 && ScmIsCIdentifierChar(pchLine[cchFilename]))
2549 cchFilename++;
2550 if (ppchFilename)
2551 *ppchFilename = pchLine;
2552 if (pcchFilename)
2553 *pcchFilename = cchFilename;
2554 return kScmIncludeDir_Macro;
2555 }
2556 else
2557 ScmError(pState, VERR_PARSE_ERROR, "Malformed #include filename part: %.*s\n", cchLine, pchLine);
2558 }
2559 else
2560 ScmError(pState, VERR_PARSE_ERROR, "Missing #include filename!\n");
2561 }
2562 }
2563
2564 if (ppchFilename)
2565 *ppchFilename = NULL;
2566 if (pcchFilename)
2567 *pcchFilename = 0;
2568 return kScmIncludeDir_Invalid;
2569}
2570
2571
2572/**
2573 * Fix err.h/errcore.h usage.
2574 *
2575 * @returns true if modifications were made, false if not.
2576 * @param pIn The input stream.
2577 * @param pOut The output stream.
2578 * @param pSettings The settings.
2579 */
2580bool rewrite_Fix_Err_H(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2581{
2582 if (!pSettings->fFixErrH)
2583 return false;
2584
2585 static struct
2586 {
2587 const char *pszHeader;
2588 unsigned cchHeader;
2589 int iLevel;
2590 } const s_aHeaders[] =
2591 {
2592 { RT_STR_TUPLE("iprt/errcore.h"), 1 },
2593 { RT_STR_TUPLE("iprt/err.h"), 2 },
2594 { RT_STR_TUPLE("VBox/err.h"), 3 },
2595 };
2596 static RTSTRTUPLE const g_aLevel1Statuses[] = /* Note! Keep in sync with errcore.h content! */
2597 {
2598 { RT_STR_TUPLE("VINF_SUCCESS") },
2599 { RT_STR_TUPLE("VERR_GENERAL_FAILURE") },
2600 { RT_STR_TUPLE("VERR_INVALID_PARAMETER") },
2601 { RT_STR_TUPLE("VWRN_INVALID_PARAMETER") },
2602 { RT_STR_TUPLE("VERR_INVALID_MAGIC") },
2603 { RT_STR_TUPLE("VWRN_INVALID_MAGIC") },
2604 { RT_STR_TUPLE("VERR_INVALID_HANDLE") },
2605 { RT_STR_TUPLE("VWRN_INVALID_HANDLE") },
2606 { RT_STR_TUPLE("VERR_INVALID_POINTER") },
2607 { RT_STR_TUPLE("VERR_NO_MEMORY") },
2608 { RT_STR_TUPLE("VERR_PERMISSION_DENIED") },
2609 { RT_STR_TUPLE("VINF_PERMISSION_DENIED") },
2610 { RT_STR_TUPLE("VERR_VERSION_MISMATCH") },
2611 { RT_STR_TUPLE("VERR_NOT_IMPLEMENTED") },
2612 { RT_STR_TUPLE("VERR_INVALID_FLAGS") },
2613 { RT_STR_TUPLE("VERR_WRONG_ORDER") },
2614 { RT_STR_TUPLE("VERR_INVALID_FUNCTION") },
2615 { RT_STR_TUPLE("VERR_NOT_SUPPORTED") },
2616 { RT_STR_TUPLE("VINF_NOT_SUPPORTED") },
2617 { RT_STR_TUPLE("VERR_ACCESS_DENIED") },
2618 { RT_STR_TUPLE("VERR_INTERRUPTED") },
2619 { RT_STR_TUPLE("VINF_INTERRUPTED") },
2620 { RT_STR_TUPLE("VERR_TIMEOUT") },
2621 { RT_STR_TUPLE("VINF_TIMEOUT") },
2622 { RT_STR_TUPLE("VERR_BUFFER_OVERFLOW") },
2623 { RT_STR_TUPLE("VINF_BUFFER_OVERFLOW") },
2624 { RT_STR_TUPLE("VERR_TOO_MUCH_DATA") },
2625 { RT_STR_TUPLE("VERR_TRY_AGAIN") },
2626 { RT_STR_TUPLE("VINF_TRY_AGAIN") },
2627 { RT_STR_TUPLE("VERR_PARSE_ERROR") },
2628 { RT_STR_TUPLE("VERR_OUT_OF_RANGE") },
2629 { RT_STR_TUPLE("VERR_NUMBER_TOO_BIG") },
2630 { RT_STR_TUPLE("VWRN_NUMBER_TOO_BIG") },
2631 { RT_STR_TUPLE("VERR_CANCELLED") },
2632 { RT_STR_TUPLE("VERR_TRAILING_CHARS") },
2633 { RT_STR_TUPLE("VWRN_TRAILING_CHARS") },
2634 { RT_STR_TUPLE("VERR_TRAILING_SPACES") },
2635 { RT_STR_TUPLE("VWRN_TRAILING_SPACES") },
2636 { RT_STR_TUPLE("VERR_NOT_FOUND") },
2637 { RT_STR_TUPLE("VWRN_NOT_FOUND") },
2638 { RT_STR_TUPLE("VERR_INVALID_STATE") },
2639 { RT_STR_TUPLE("VWRN_INVALID_STATE") },
2640 { RT_STR_TUPLE("VERR_OUT_OF_RESOURCES") },
2641 { RT_STR_TUPLE("VWRN_OUT_OF_RESOURCES") },
2642 { RT_STR_TUPLE("VERR_END_OF_STRING") },
2643 { RT_STR_TUPLE("VERR_CALLBACK_RETURN") },
2644 { RT_STR_TUPLE("VINF_CALLBACK_RETURN") },
2645 { RT_STR_TUPLE("VERR_DUPLICATE") },
2646 { RT_STR_TUPLE("VERR_MISSING") },
2647 { RT_STR_TUPLE("VERR_BUFFER_UNDERFLOW") },
2648 { RT_STR_TUPLE("VINF_BUFFER_UNDERFLOW") },
2649 { RT_STR_TUPLE("VERR_NOT_AVAILABLE") },
2650 { RT_STR_TUPLE("VERR_MISMATCH") },
2651 { RT_STR_TUPLE("VERR_WRONG_TYPE") },
2652 { RT_STR_TUPLE("VWRN_WRONG_TYPE") },
2653 { RT_STR_TUPLE("VERR_WRONG_PARAMETER_COUNT") },
2654 { RT_STR_TUPLE("VERR_WRONG_PARAMETER_TYPE") },
2655 { RT_STR_TUPLE("VERR_INVALID_CLIENT_ID") },
2656 { RT_STR_TUPLE("VERR_INVALID_SESSION_ID") },
2657 { RT_STR_TUPLE("VERR_INCOMPATIBLE_CONFIG") },
2658 { RT_STR_TUPLE("VERR_INTERNAL_ERROR") },
2659 { RT_STR_TUPLE("VINF_GETOPT_NOT_OPTION") },
2660 { RT_STR_TUPLE("VERR_GETOPT_UNKNOWN_OPTION") },
2661 };
2662
2663 /*
2664 * First pass: Scout #include err.h/errcore.h locations and usage.
2665 *
2666 * Note! This isn't entirely optimal since it's also parsing comments and
2667 * strings, not just code. However it does a decent job for now.
2668 */
2669 int iIncludeLevel = 0;
2670 int iUsageLevel = 0;
2671 uint32_t iLine = 0;
2672 SCMEOL enmEol;
2673 size_t cchLine;
2674 const char *pchLine;
2675 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
2676 {
2677 iLine++;
2678 if (cchLine < 6)
2679 continue;
2680
2681 /*
2682 * Look for #includes.
2683 */
2684 const char *pchHash = (const char *)memchr(pchLine, '#', cchLine);
2685 if ( pchHash
2686 && isSpanOfBlanks(pchLine, pchHash - pchLine))
2687 {
2688 const char *pchFilename;
2689 size_t cchFilename;
2690 SCMINCLUDEDIR enmIncDir = ScmMaybeParseCIncludeLine(pState, pchLine, cchLine, &pchFilename, &cchFilename);
2691 if ( enmIncDir == kScmIncludeDir_Bracketed
2692 || enmIncDir == kScmIncludeDir_Quoted)
2693 {
2694 unsigned i = RT_ELEMENTS(s_aHeaders);
2695 while (i-- > 0)
2696 if ( s_aHeaders[i].cchHeader == cchFilename
2697 && RTStrNICmpAscii(pchFilename, s_aHeaders[i].pszHeader, cchFilename) == 0)
2698 {
2699 if (iIncludeLevel < s_aHeaders[i].iLevel)
2700 iIncludeLevel = s_aHeaders[i].iLevel;
2701 break;
2702 }
2703
2704 /* Special hack for error info. */
2705 if (cchFilename == sizeof("errmsgdata.h") - 1 && memcmp(pchFilename, RT_STR_TUPLE("errmsgdata.h")) == 0)
2706 iUsageLevel = 4;
2707
2708 /* Special hack for code templates. */
2709 if ( cchFilename >= sizeof(".cpp.h")
2710 && memcmp(&pchFilename[cchFilename - sizeof(".cpp.h") + 1], RT_STR_TUPLE(".cpp.h")) == 0)
2711 iUsageLevel = 4;
2712 continue;
2713 }
2714 }
2715 /*
2716 * Look for VERR_, VWRN_, VINF_ prefixed identifiers in the current line.
2717 */
2718 const char *pchHit = (const char *)memchr(pchLine, 'V', cchLine);
2719 if (pchHit)
2720 {
2721 const char *pchLeft = pchLine;
2722 size_t cchLeft = cchLine;
2723 do
2724 {
2725 size_t cchLeftHit = &pchLeft[cchLeft] - pchHit;
2726 if (cchLeftHit < 6)
2727 break;
2728 if ( pchHit[4] == '_'
2729 && ( pchHit == pchLine
2730 || !ScmIsCIdentifierChar(pchHit[-1]))
2731 && ( (pchHit[1] == 'E' && pchHit[2] == 'R' && pchHit[3] == 'R')
2732 || (pchHit[1] == 'W' && pchHit[2] == 'R' && pchHit[3] == 'N')
2733 || (pchHit[1] == 'I' && pchHit[2] == 'N' && pchHit[3] == 'F') ) )
2734 {
2735 size_t cchIdentifier = 5;
2736 while (cchIdentifier < cchLeftHit && ScmIsCIdentifierChar(pchHit[cchIdentifier]))
2737 cchIdentifier++;
2738 ScmVerbose(pState, 4, "--- status code at %u col %zu: %.*s\n",
2739 iLine, pchHit - pchLine, cchIdentifier, pchHit);
2740
2741 if (iUsageLevel <= 1)
2742 {
2743 iUsageLevel = 3; /* Cannot distingish between iprt/err.h and VBox/err.h, so pick the latter for now. */
2744 for (unsigned i = 0; i < RT_ELEMENTS(g_aLevel1Statuses); i++)
2745 if ( cchIdentifier == g_aLevel1Statuses[i].cch
2746 && memcmp(pchHit, g_aLevel1Statuses[i].psz, cchIdentifier) == 0)
2747 {
2748 iUsageLevel = 1;
2749 break;
2750 }
2751 }
2752
2753 pchLeft = pchHit + cchIdentifier;
2754 cchLeft = cchLeftHit - cchIdentifier;
2755 }
2756 else
2757 {
2758 pchLeft = pchHit + 1;
2759 cchLeft = cchLeftHit - 1;
2760 }
2761 pchHit = (const char *)memchr(pchLeft, 'V', cchLeft);
2762 } while (pchHit != NULL);
2763 }
2764 }
2765 ScmVerbose(pState, 3, "--- iIncludeLevel=%d iUsageLevel=%d\n", iIncludeLevel, iUsageLevel);
2766
2767 /*
2768 * Second pass: Change err.h to errcore.h if we detected a need for change.
2769 */
2770 if ( iIncludeLevel <= iUsageLevel
2771 || iIncludeLevel <= 1 /* we cannot safely eliminate errcore.h includes atm. */)
2772 return false;
2773
2774 unsigned cChanges = 0;
2775 ScmStreamRewindForReading(pIn);
2776 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
2777 {
2778 /*
2779 * Look for #includes to modify.
2780 */
2781 if (cchLine >= 6)
2782 {
2783 const char *pchHash = (const char *)memchr(pchLine, '#', cchLine);
2784 if ( pchHash
2785 && isSpanOfBlanks(pchLine, pchHash - pchLine))
2786 {
2787 const char *pchFilename;
2788 size_t cchFilename;
2789 SCMINCLUDEDIR enmIncDir = ScmMaybeParseCIncludeLine(pState, pchLine, cchLine, &pchFilename, &cchFilename);
2790 if ( enmIncDir == kScmIncludeDir_Bracketed
2791 || enmIncDir == kScmIncludeDir_Quoted)
2792 {
2793 unsigned i = RT_ELEMENTS(s_aHeaders);
2794 while (i-- > 0)
2795 if ( s_aHeaders[i].cchHeader == cchFilename
2796 && RTStrNICmpAscii(pchFilename, s_aHeaders[i].pszHeader, cchFilename) == 0)
2797 {
2798 ScmStreamWrite(pOut, pchLine, pchFilename - pchLine - 1);
2799 ScmStreamWrite(pOut, RT_STR_TUPLE("<iprt/errcore.h>"));
2800 size_t cchTrailing = &pchLine[cchLine] - &pchFilename[cchFilename + 1];
2801 if (cchTrailing > 0)
2802 ScmStreamWrite(pOut, &pchFilename[cchFilename + 1], cchTrailing);
2803 ScmStreamPutEol(pOut, enmEol);
2804 cChanges++;
2805 pchLine = NULL;
2806 break;
2807 }
2808 if (!pchLine)
2809 continue;
2810 }
2811 }
2812 }
2813
2814 int rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
2815 if (RT_FAILURE(rc))
2816 return false;
2817 }
2818 ScmVerbose(pState, 2, " * Converted %zu err.h/errcore.h include statements.\n", cChanges);
2819 return true;
2820}
2821
2822typedef struct
2823{
2824 const char *pch;
2825 uint8_t cch;
2826 uint8_t cchSpaces; /**< Number of expected spaces before the word. */
2827 bool fSpacesBefore : 1; /**< Whether there may be spaces or tabs before the word. */
2828 bool fIdentifier : 1; /**< Whether we're to expect a C/C++ identifier rather than pch/cch. */
2829} SCMMATCHWORD;
2830
2831
2832int ScmMatchWords(const char *pchLine, size_t cchLine, SCMMATCHWORD const *paWords, size_t cWords,
2833 size_t *poffNext, PRTSTRTUPLE paIdentifiers, PRTERRINFO pErrInfo)
2834{
2835 int rc = VINF_SUCCESS;
2836
2837 size_t offLine = 0;
2838 for (size_t i = 0; i < cWords; i++)
2839 {
2840 SCMMATCHWORD const *pWord = &paWords[i];
2841
2842 /*
2843 * Deal with spaces preceeding the word first:
2844 */
2845 if (pWord->fSpacesBefore)
2846 {
2847 size_t cchSpaces = 0;
2848 size_t cchTabs = 0;
2849 while (offLine < cchLine)
2850 {
2851 const char ch = pchLine[offLine];
2852 if (ch == ' ')
2853 cchSpaces++;
2854 else if (ch == '\t')
2855 cchTabs++;
2856 else
2857 break;
2858 offLine++;
2859 }
2860
2861 if (cchSpaces == pWord->cchSpaces && cchTabs == 0)
2862 { /* likely */ }
2863 else if (cchSpaces == 0 && cchTabs == 0)
2864 return RTErrInfoSetF(pErrInfo, VERR_PARSE_ERROR, "expected space at offset %u", offLine);
2865 else
2866 rc = VWRN_TRAILING_SPACES;
2867 }
2868 else
2869 Assert(pWord->cchSpaces == 0);
2870
2871 /*
2872 * C/C++ identifier?
2873 */
2874 if (pWord->fIdentifier)
2875 {
2876 if (offLine >= cchLine)
2877 return RTErrInfoSetF(pErrInfo, VERR_END_OF_STRING,
2878 "expected '%.*s' (C/C++ identifier) at offset %u, not end of string",
2879 pWord->cch, pWord->pch, offLine);
2880 if (!ScmIsCIdentifierLeadChar(pchLine[offLine]))
2881 return RTErrInfoSetF(pErrInfo, VERR_MISMATCH, "expected '%.*s' (C/C++ identifier) at offset %u",
2882 pWord->cch, pWord->pch, offLine);
2883 size_t const offStart = offLine++;
2884 while (offLine < cchLine && ScmIsCIdentifierChar(pchLine[offLine]))
2885 offLine++;
2886 if (paIdentifiers)
2887 {
2888 paIdentifiers->cch = offLine - offStart;
2889 paIdentifiers->psz = &pchLine[offStart];
2890 paIdentifiers++;
2891 }
2892 }
2893 /*
2894 * Match the exact word.
2895 */
2896 else if ( pWord->cch == 0
2897 || ( pWord->cch <= cchLine - offLine
2898 && !memcmp(pWord->pch, &pchLine[offLine], pWord->cch)))
2899 offLine += pWord->cch;
2900 else
2901 return RTErrInfoSetF(pErrInfo, VERR_MISMATCH, "expected '%.*s' at offset %u", pWord->cch, pWord->pch, offLine);
2902 }
2903
2904 /*
2905 * Check for trailing characters/whatnot.
2906 */
2907 if (poffNext)
2908 *poffNext = offLine;
2909 else if (offLine != cchLine)
2910 rc = RTErrInfoSetF(pErrInfo, VERR_TRAILING_CHARS, "unexpected trailing characters at offset %u", offLine);
2911 return rc;
2912}
2913
2914
2915/**
2916 * Fix header file include guards and \#pragma once.
2917 *
2918 * @returns true if modifications were made, false if not.
2919 * @param pIn The input stream.
2920 * @param pOut The output stream.
2921 * @param pSettings The settings.
2922 */
2923bool rewrite_FixHeaderGuards(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2924{
2925 if (!pSettings->fFixHeaderGuards)
2926 return false;
2927
2928 /* always skip .cpp.h files */
2929 size_t cchFilename = strlen(pState->pszFilename);
2930 if ( cchFilename > sizeof(".cpp.h")
2931 && RTStrICmpAscii(&pState->pszFilename[cchFilename - sizeof(".cpp.h") + 1], ".cpp.h") == 0)
2932 return false;
2933
2934 RTERRINFOSTATIC ErrInfo;
2935 char szNormalized[168];
2936 size_t cchNormalized = 0;
2937 int rc;
2938 bool fRet = false;
2939
2940 /*
2941 * Calculate the expected guard for this file, if so tasked.
2942 * ASSUMES pState->pszFilename is absolute as is pSettings->pszGuardRelativeToDir.
2943 */
2944 szNormalized[0] = '\0';
2945 if (pSettings->pszGuardRelativeToDir)
2946 {
2947 rc = RTStrCopy(szNormalized, sizeof(szNormalized), pSettings->pszGuardPrefix);
2948 if (RT_FAILURE(rc))
2949 return ScmError(pState, rc, "Guard prefix too long (or something): %s\n", pSettings->pszGuardPrefix);
2950 cchNormalized = strlen(szNormalized);
2951 if (strcmp(pSettings->pszGuardRelativeToDir, "{dir}") == 0)
2952 rc = RTStrCopy(&szNormalized[cchNormalized], sizeof(szNormalized) - cchNormalized,
2953 RTPathFilename(pState->pszFilename));
2954 else if (strcmp(pSettings->pszGuardRelativeToDir, "{parent}") == 0)
2955 {
2956 const char *pszSrc = RTPathFilename(pState->pszFilename);
2957 if (!pszSrc || (uintptr_t)&pszSrc[-2] < (uintptr_t)pState->pszFilename || !RTPATH_IS_SLASH(pszSrc[-1]))
2958 return ScmError(pState, VERR_INTERNAL_ERROR, "Error calculating {parent} header guard!\n");
2959 pszSrc -= 2;
2960 while ( (uintptr_t)pszSrc > (uintptr_t)pState->pszFilename
2961 && !RTPATH_IS_SLASH(pszSrc[-1])
2962 && !RTPATH_IS_VOLSEP(pszSrc[-1]))
2963 pszSrc--;
2964 rc = RTStrCopy(&szNormalized[cchNormalized], sizeof(szNormalized) - cchNormalized, pszSrc);
2965 }
2966 else
2967 rc = RTPathCalcRelative(&szNormalized[cchNormalized], sizeof(szNormalized) - cchNormalized,
2968 pSettings->pszGuardRelativeToDir, false /*fFromFile*/, pState->pszFilename);
2969 if (RT_FAILURE(rc))
2970 return ScmError(pState, rc, "Error calculating guard prefix (RTPathCalcRelative): %Rrc\n", rc);
2971 char ch;
2972 while ((ch = szNormalized[cchNormalized]) != '\0')
2973 {
2974 if (!ScmIsCIdentifierChar(ch))
2975 szNormalized[cchNormalized] = '_';
2976 cchNormalized++;
2977 }
2978 }
2979
2980 /*
2981 * First part looks for the #ifndef xxxx paired with #define xxxx.
2982 *
2983 * We blindly assume the first preprocessor directive in the file is the guard
2984 * and will be upset if this isn't the case.
2985 */
2986 RTSTRTUPLE Guard = { NULL, 0 };
2987 uint32_t cBlankLines = 0;
2988 SCMEOL enmEol;
2989 size_t cchLine;
2990 const char *pchLine;
2991 for (;;)
2992 {
2993 pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
2994 if (pchLine == NULL)
2995 return ScmError(pState, VERR_PARSE_ERROR, "Did not find any include guards!\n");
2996 if (cchLine >= 2)
2997 {
2998 const char *pchHash = (const char *)memchr(pchLine, '#', cchLine);
2999 if ( pchHash
3000 && isSpanOfBlanks(pchLine, pchHash - pchLine))
3001 {
3002 /* #ifndef xxxx */
3003 static const SCMMATCHWORD s_aIfndefGuard[] =
3004 {
3005 { RT_STR_TUPLE("#"), 0, true, false },
3006 { RT_STR_TUPLE("ifndef"), 0, true, false },
3007 { RT_STR_TUPLE("IDENTIFIER"), 1, true, true },
3008 { RT_STR_TUPLE(""), 0, true, false },
3009 };
3010 rc = ScmMatchWords(pchLine, cchLine, s_aIfndefGuard, RT_ELEMENTS(s_aIfndefGuard),
3011 NULL /*poffNext*/, &Guard, RTErrInfoInitStatic(&ErrInfo));
3012 if (RT_FAILURE(rc))
3013 return ScmError(pState, rc, "%u: Expected first preprocessor directive to be '#ifndef xxxx'. %s (%.*s)\n",
3014 ScmStreamTellLine(pIn) - 1, ErrInfo.Core.pszMsg, cchLine, pchLine);
3015 fRet |= rc != VINF_SUCCESS;
3016 ScmVerbose(pState, 3, "line %u in %s: #ifndef %.*s\n",
3017 ScmStreamTellLine(pIn) - 1, pState->pszFilename, Guard.cch, Guard.psz);
3018
3019 /* #define xxxx */
3020 pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
3021 if (!pchLine)
3022 return ScmError(pState, VERR_PARSE_ERROR, "%u: Unexpected end of file after '#ifndef %.*s'\n",
3023 ScmStreamTellLine(pIn) - 1, Guard.cch, Guard.psz);
3024 const SCMMATCHWORD aDefineGuard[] =
3025 {
3026 { RT_STR_TUPLE("#"), 0, true, false },
3027 { RT_STR_TUPLE("define"), 0, true, false },
3028 { Guard.psz, (uint8_t)Guard.cch, 1, true, false },
3029 { RT_STR_TUPLE(""), 0, true, false },
3030 };
3031 rc = ScmMatchWords(pchLine, cchLine, aDefineGuard, RT_ELEMENTS(aDefineGuard),
3032 NULL /*poffNext*/, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
3033 if (RT_FAILURE(rc))
3034 return ScmError(pState, rc, "%u: Expected '#define %.*s' to follow '#ifndef %.*s'. %s (%.*s)\n",
3035 ScmStreamTellLine(pIn) - 1, Guard.cch, Guard.psz, Guard.cch, Guard.psz,
3036 ErrInfo.Core.pszMsg, cchLine, pchLine);
3037 fRet |= rc != VINF_SUCCESS;
3038
3039 if (Guard.cch >= sizeof(szNormalized))
3040 return ScmError(pState, VERR_BUFFER_OVERFLOW, "%u: Guard macro too long! %.*s\n",
3041 ScmStreamTellLine(pIn) - 2, Guard.cch, Guard.psz);
3042
3043 if (szNormalized[0] != '\0')
3044 {
3045 if ( Guard.cch != cchNormalized
3046 || memcmp(Guard.psz, szNormalized, cchNormalized) != 0)
3047 {
3048 ScmVerbose(pState, 2, "guard changed from %.*s to %s\n", Guard.cch, Guard.psz, szNormalized);
3049 ScmVerbose(pState, 2, "grep -rw %.*s ${WCROOT} | grep -Fv %s\n",
3050 Guard.cch, Guard.psz, pState->pszFilename);
3051 fRet = true;
3052 }
3053 Guard.psz = szNormalized;
3054 Guard.cch = cchNormalized;
3055 }
3056
3057 /*
3058 * Write guard, making sure we've got a single blank line preceeding it.
3059 */
3060 ScmStreamPutEol(pOut, enmEol);
3061 ScmStreamWrite(pOut, RT_STR_TUPLE("#ifndef "));
3062 ScmStreamWrite(pOut, Guard.psz, Guard.cch);
3063 ScmStreamPutEol(pOut, enmEol);
3064 ScmStreamWrite(pOut, RT_STR_TUPLE("#define "));
3065 ScmStreamWrite(pOut, Guard.psz, Guard.cch);
3066 rc = ScmStreamPutEol(pOut, enmEol);
3067 if (RT_FAILURE(rc))
3068 return false;
3069 break;
3070 }
3071 }
3072
3073 if (!isBlankLine(pchLine, cchLine))
3074 {
3075 while (cBlankLines-- > 0)
3076 ScmStreamPutEol(pOut, enmEol);
3077 cBlankLines = 0;
3078 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
3079 if (RT_FAILURE(rc))
3080 return false;
3081 }
3082 else
3083 cBlankLines++;
3084 }
3085
3086 /*
3087 * Look for pragma once wrapped in #ifndef RT_WITHOUT_PRAGMA_ONCE.
3088 */
3089 size_t const iPragmaOnce = ScmStreamTellLine(pIn);
3090 static const SCMMATCHWORD s_aIfndefRtWithoutPragmaOnce[] =
3091 {
3092 { RT_STR_TUPLE("#"), 0, true, false },
3093 { RT_STR_TUPLE("ifndef"), 0, true, false },
3094 { RT_STR_TUPLE("RT_WITHOUT_PRAGMA_ONCE"), 1, true, false },
3095 { RT_STR_TUPLE(""), 0, true, false },
3096 };
3097 static const SCMMATCHWORD s_aPragmaOnce[] =
3098 {
3099 { RT_STR_TUPLE("#"), 0, true, false },
3100 { RT_STR_TUPLE("pragma"), 1, true, false },
3101 { RT_STR_TUPLE("once"), 1, true, false},
3102 { RT_STR_TUPLE(""), 0, true, false },
3103 };
3104 static const SCMMATCHWORD s_aEndif[] =
3105 {
3106 { RT_STR_TUPLE("#"), 0, true, false },
3107 { RT_STR_TUPLE("endif"), 0, true, false },
3108 { RT_STR_TUPLE(""), 0, true, false },
3109 };
3110
3111 /* #ifndef RT_WITHOUT_PRAGMA_ONCE */
3112 pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
3113 if (!pchLine)
3114 return ScmError(pState, VERR_PARSE_ERROR, "%u: Unexpected end of file after header guard!\n", iPragmaOnce + 1);
3115 size_t offNext;
3116 rc = ScmMatchWords(pchLine, cchLine, s_aIfndefRtWithoutPragmaOnce, RT_ELEMENTS(s_aIfndefRtWithoutPragmaOnce),
3117 &offNext, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
3118 if (RT_SUCCESS(rc))
3119 {
3120 fRet |= rc != VINF_SUCCESS;
3121 if (offNext != cchLine)
3122 return ScmError(pState, VERR_PARSE_ERROR, "%u: Characters trailing '#ifndef RT_WITHOUT_PRAGMA_ONCE' (%.*s)\n",
3123 iPragmaOnce + 1, cchLine, pchLine);
3124
3125 /* # pragma once */
3126 pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
3127 if (!pchLine)
3128 return ScmError(pState, VERR_PARSE_ERROR, "%u: Unexpected end of file after '#ifndef RT_WITHOUT_PRAGMA_ONCE'\n",
3129 iPragmaOnce + 2);
3130 rc = ScmMatchWords(pchLine, cchLine, s_aPragmaOnce, RT_ELEMENTS(s_aPragmaOnce),
3131 NULL /*poffNext*/, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
3132 if (RT_SUCCESS(rc))
3133 fRet |= rc != VINF_SUCCESS;
3134 else
3135 return ScmError(pState, rc, "%u: Expected '# pragma once' to follow '#ifndef RT_WITHOUT_PRAGMA_ONCE'! %s (%.*s)\n",
3136 iPragmaOnce + 2, ErrInfo.Core.pszMsg, cchLine, pchLine);
3137
3138 /* #endif */
3139 pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
3140 if (!pchLine)
3141 return ScmError(pState, VERR_PARSE_ERROR, "%u: Unexpected end of file after '#ifndef RT_WITHOUT_PRAGMA_ONCE' and '#pragma once'\n",
3142 iPragmaOnce + 3);
3143 rc = ScmMatchWords(pchLine, cchLine, s_aEndif, RT_ELEMENTS(s_aEndif),
3144 NULL /*poffNext*/, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
3145 if (RT_SUCCESS(rc))
3146 fRet |= rc != VINF_SUCCESS;
3147 else
3148 return ScmError(pState, rc,
3149 "%u: Expected '#endif' to follow '#ifndef RT_WITHOUT_PRAGMA_ONCE' and '# pragma once'! %s (%.*s)\n",
3150 iPragmaOnce + 3, ErrInfo.Core.pszMsg, cchLine, pchLine);
3151 ScmVerbose(pState, 3, "Found pragma once\n");
3152 fRet |= !pSettings->fPragmaOnce;
3153 }
3154 else
3155 {
3156 rc = ScmStreamSeekByLine(pIn, iPragmaOnce);
3157 if (RT_FAILURE(rc))
3158 return ScmError(pState, rc, "seek error\n");
3159 fRet |= pSettings->fPragmaOnce;
3160 ScmVerbose(pState, 2, "Missing #pragma once\n");
3161 }
3162
3163 /*
3164 * Write the pragma once stuff.
3165 */
3166 if (pSettings->fPragmaOnce)
3167 {
3168 ScmStreamPutLine(pOut, RT_STR_TUPLE("#ifndef RT_WITHOUT_PRAGMA_ONCE"), enmEol);
3169 ScmStreamPutLine(pOut, RT_STR_TUPLE("# pragma once"), enmEol);
3170 rc = ScmStreamPutLine(pOut, RT_STR_TUPLE("#endif"), enmEol);
3171 if (RT_FAILURE(rc))
3172 return false;
3173 }
3174
3175 /*
3176 * Copy the rest of the file and remove pragma once statements, while
3177 * looking for the last #endif in the file.
3178 */
3179 size_t iEndIfIn = 0;
3180 size_t iEndIfOut = 0;
3181 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
3182 {
3183 if (cchLine > 2)
3184 {
3185 const char *pchHash = (const char *)memchr(pchLine, '#', cchLine);
3186 if ( pchHash
3187 && isSpanOfBlanks(pchLine, pchHash - pchLine))
3188 {
3189 size_t off = pchHash - pchLine + 1;
3190 while (off < cchLine && RT_C_IS_BLANK(pchLine[off]))
3191 off++;
3192 /* #pragma once */
3193 if ( off + sizeof("pragma") - 1 <= cchLine
3194 && !memcmp(&pchLine[off], RT_STR_TUPLE("pragma")))
3195 {
3196 rc = ScmMatchWords(pchLine, cchLine, s_aPragmaOnce, RT_ELEMENTS(s_aPragmaOnce),
3197 &offNext, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
3198 if (RT_SUCCESS(rc))
3199 {
3200 fRet = true;
3201 continue;
3202 }
3203 }
3204 /* #endif */
3205 else if ( off + sizeof("endif") - 1 <= cchLine
3206 && !memcmp(&pchLine[off], RT_STR_TUPLE("endif")))
3207 {
3208 iEndIfIn = ScmStreamTellLine(pIn) - 1;
3209 iEndIfOut = ScmStreamTellLine(pOut);
3210 }
3211 }
3212 }
3213
3214 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
3215 if (RT_FAILURE(rc))
3216 return false;
3217 }
3218
3219 /*
3220 * Check out the last endif, making sure it's well formed and make sure it has the
3221 * right kind of comment following it.
3222 */
3223 if (pSettings->fFixHeaderGuardEndif)
3224 {
3225 if (iEndIfOut == 0)
3226 return ScmError(pState, VERR_PARSE_ERROR, "Expected '#endif' at the end of the file...\n");
3227 rc = ScmStreamSeekByLine(pIn, iEndIfIn);
3228 if (RT_FAILURE(rc))
3229 return false;
3230 rc = ScmStreamSeekByLine(pOut, iEndIfOut);
3231 if (RT_FAILURE(rc))
3232 return false;
3233
3234 pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
3235 if (!pchLine)
3236 return ScmError(pState, VERR_INTERNAL_ERROR, "ScmStreamGetLine failed re-reading #endif!\n");
3237
3238 char szTmp[64 + sizeof(szNormalized)];
3239 size_t cchTmp;
3240 if (pSettings->fEndifGuardComment)
3241 cchTmp = RTStrPrintf(szTmp, sizeof(szTmp), "#endif /* !%.*s */", Guard.cch, Guard.psz);
3242 else
3243 cchTmp = RTStrPrintf(szTmp, sizeof(szTmp), "#endif"); /* lazy bird */
3244 fRet |= cchTmp != cchLine || memcmp(szTmp, pchLine, cchTmp) != 0;
3245 rc = ScmStreamPutLine(pOut, szTmp, cchTmp, enmEol);
3246 if (RT_FAILURE(rc))
3247 return false;
3248
3249 /* Copy out the remaining lines (assumes no #pragma once here). */
3250 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
3251 {
3252 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
3253 if (RT_FAILURE(rc))
3254 return false;
3255 }
3256 }
3257
3258 return fRet;
3259}
3260
3261
3262/**
3263 * Rewrite a C/C++ source or header file.
3264 *
3265 * @returns true if modifications were made, false if not.
3266 * @param pIn The input stream.
3267 * @param pOut The output stream.
3268 * @param pSettings The settings.
3269 *
3270 * @todo
3271 *
3272 * Ideas for C/C++:
3273 * - space after if, while, for, switch
3274 * - spaces in for (i=0;i<x;i++)
3275 * - complex conditional, bird style.
3276 * - remove unnecessary parentheses.
3277 * - sort defined RT_OS_*|| and RT_ARCH
3278 * - sizeof without parenthesis.
3279 * - defined without parenthesis.
3280 * - trailing spaces.
3281 * - parameter indentation.
3282 * - space after comma.
3283 * - while (x--); -> multi line + comment.
3284 * - else statement;
3285 * - space between function and left parenthesis.
3286 * - TODO, XXX, @todo cleanup.
3287 * - Space before/after '*'.
3288 * - ensure new line at end of file.
3289 * - Indentation of precompiler statements (#ifdef, #defines).
3290 * - space between functions.
3291 * - string.h -> iprt/string.h, stdarg.h -> iprt/stdarg.h, etc.
3292 */
3293bool rewrite_C_and_CPP(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
3294{
3295
3296 RT_NOREF4(pState, pIn, pOut, pSettings);
3297 return false;
3298}
3299
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette