VirtualBox

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

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

scm: New heuristic to trigger only on 'HRESULT rc' and 'int rc' declarations as the other one was too broad and triggered on things which we don't want it to trigger on, bugref:10223

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 137.6 KB
Line 
1/* $Id: scmrw.cpp 94913 2022-05-08 19:07:29Z vboxsync $ */
2/** @file
3 * IPRT Testcase / Tool - Source Code Massager.
4 */
5
6/*
7 * Copyright (C) 2010-2022 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 ScmFixManually(pState, "%u:%zu: Evil unicode codepoint: %s\n", iLine, pchCur - pchLine, pszWhat);
1118 }
1119 else
1120 ScmFixManually(pState, "%u:%zu: Invalid UTF-8 encoding: %Rrc\n", iLine, pchCur - pchLine, rc);
1121 }
1122 }
1123
1124 return false;
1125}
1126
1127/**
1128 * Compares two strings word-by-word, ignoring spaces, punctuation and case.
1129 *
1130 * Assumes ASCII strings.
1131 *
1132 * @returns true if they match, false if not.
1133 * @param psz1 The first string. This is typically the known one.
1134 * @param psz2 The second string. This is typically the unknown one,
1135 * which is why we return a next pointer for this one.
1136 * @param ppsz2Next Where to return the next part of the 2nd string. If
1137 * this is NULL, the whole string must match.
1138 */
1139static bool IsEqualWordByWordIgnoreCase(const char *psz1, const char *psz2, const char **ppsz2Next)
1140{
1141 for (;;)
1142 {
1143 /* Try compare raw strings first. */
1144 char ch1 = *psz1;
1145 char ch2 = *psz2;
1146 if ( ch1 == ch2
1147 || RT_C_TO_LOWER(ch1) == RT_C_TO_LOWER(ch2))
1148 {
1149 if (ch1)
1150 {
1151 psz1++;
1152 psz2++;
1153 }
1154 else
1155 {
1156 if (ppsz2Next)
1157 *ppsz2Next = psz2;
1158 return true;
1159 }
1160 }
1161 else
1162 {
1163 /* Try skip spaces an punctuation. */
1164 while ( RT_C_IS_SPACE(ch1)
1165 || RT_C_IS_PUNCT(ch1))
1166 ch1 = *++psz1;
1167
1168 if (ch1 == '\0' && ppsz2Next)
1169 {
1170 *ppsz2Next = psz2;
1171 return true;
1172 }
1173
1174 while ( RT_C_IS_SPACE(ch2)
1175 || RT_C_IS_PUNCT(ch2))
1176 ch2 = *++psz2;
1177
1178 if ( ch1 != ch2
1179 && RT_C_TO_LOWER(ch1) != RT_C_TO_LOWER(ch2))
1180 {
1181 if (ppsz2Next)
1182 *ppsz2Next = psz2;
1183 return false;
1184 }
1185 }
1186 }
1187}
1188
1189/**
1190 * Looks for @a pszFragment anywhere in @a pszText, ignoring spaces, punctuation
1191 * and case.
1192 *
1193 * @returns true if found, false if not.
1194 * @param pszText The haystack to search in.
1195 * @param cchText The length @a pszText.
1196 * @param pszFragment The needle to search for.
1197 * @param ppszStart Where to return the address in @a pszText where
1198 * the fragment was found. Optional.
1199 * @param ppszNext Where to return the pointer to the first char in
1200 * @a pszText after the fragment. Optional.
1201 *
1202 * @remarks First character of @a pszFragment must be an 7-bit ASCII character!
1203 * This character must not be space or punctuation.
1204 */
1205static bool scmContainsWordByWordIgnoreCase(const char *pszText, size_t cchText, const char *pszFragment,
1206 const char **ppszStart, const char **ppszNext)
1207{
1208 Assert(!((unsigned)*pszFragment & 0x80));
1209 Assert(pszText[cchText] == '\0');
1210 Assert(!RT_C_IS_BLANK(*pszFragment));
1211 Assert(!RT_C_IS_PUNCT(*pszFragment));
1212
1213 char chLower = RT_C_TO_LOWER(*pszFragment);
1214 char chUpper = RT_C_TO_UPPER(*pszFragment);
1215 for (;;)
1216 {
1217 const char *pszHit = (const char *)memchr(pszText, chLower, cchText);
1218 const char *pszHit2 = (const char *)memchr(pszText, chUpper, cchText);
1219 if (!pszHit && !pszHit2)
1220 {
1221 if (ppszStart)
1222 *ppszStart = NULL;
1223 if (ppszNext)
1224 *ppszNext = NULL;
1225 return false;
1226 }
1227
1228 if ( pszHit == NULL
1229 || ( pszHit2 != NULL
1230 && ((uintptr_t)pszHit2 < (uintptr_t)pszHit)) )
1231 pszHit = pszHit2;
1232
1233 const char *pszNext;
1234 if (IsEqualWordByWordIgnoreCase(pszFragment, pszHit, &pszNext))
1235 {
1236 if (ppszStart)
1237 *ppszStart = pszHit;
1238 if (ppszNext)
1239 *ppszNext = pszNext;
1240 return true;
1241 }
1242
1243 cchText -= pszHit - pszText + 1;
1244 pszText = pszHit + 1;
1245 }
1246}
1247
1248
1249/**
1250 * Counts the number of lines in the given substring.
1251 *
1252 * @returns The number of lines.
1253 * @param psz The start of the substring.
1254 * @param cch The length of the substring.
1255 */
1256static uint32_t CountLinesInSubstring(const char *psz, size_t cch)
1257{
1258 uint32_t cLines = 0;
1259 for (;;)
1260 {
1261 const char *pszEol = (const char *)memchr(psz, '\n', cch);
1262 if (pszEol)
1263 cLines++;
1264 else
1265 return cLines + (*psz != '\0');
1266 cch -= pszEol + 1 - psz;
1267 if (!cch)
1268 return cLines;
1269 psz = pszEol + 1;
1270 }
1271}
1272
1273
1274/**
1275 * Comment parser callback for locating copyright and license.
1276 */
1277static DECLCALLBACK(int)
1278rewrite_Copyright_CommentCallback(PCSCMCOMMENTINFO pInfo, const char *pszBody, size_t cchBody, void *pvUser)
1279{
1280 PSCMCOPYRIGHTINFO pState = (PSCMCOPYRIGHTINFO)pvUser;
1281 Assert(strlen(pszBody) == cchBody);
1282 //RTPrintf("--- comment at %u, type %u ---\n%s\n--- end ---\n", pInfo->iLineStart, pInfo->enmType, pszBody);
1283 ScmVerbose(pState->pState, 5,
1284 "--- comment at %u col %u, %u lines, type %u, %u lines before body, %u lines after body\n",
1285 pInfo->iLineStart, pInfo->offStart, pInfo->iLineEnd - pInfo->iLineStart + 1, pInfo->enmType,
1286 pInfo->cBlankLinesBefore, pInfo->cBlankLinesAfter);
1287
1288 pState->cComments++;
1289
1290 uint32_t iLine = pInfo->iLineStart + pInfo->cBlankLinesBefore;
1291
1292 /*
1293 * Look for a 'contributed by' or 'includes contributions from' line, these
1294 * comes first when present.
1295 */
1296 const char *pchContributedBy = NULL;
1297 size_t cchContributedBy = 0;
1298 size_t cBlankLinesAfterContributedBy = 0;
1299 if ( pState->pszContributedBy == NULL
1300 && ( pState->iLineCopyright == UINT32_MAX
1301 || pState->iLineLicense == UINT32_MAX)
1302 && ( ( cchBody > sizeof("Contributed by")
1303 && RTStrNICmp(pszBody, RT_STR_TUPLE("contributed by")) == 0)
1304 || ( cchBody > sizeof("Includes contributions from")
1305 && RTStrNICmp(pszBody, RT_STR_TUPLE("Includes contributions from")) == 0) ) )
1306 {
1307 const char *pszNextLine = (const char *)memchr(pszBody, '\n', cchBody);
1308 while (pszNextLine && pszNextLine[1] != '\n')
1309 pszNextLine = (const char *)memchr(pszNextLine + 1, '\n', cchBody);
1310 if (pszNextLine)
1311 {
1312 pchContributedBy = pszBody;
1313 cchContributedBy = pszNextLine - pszBody;
1314
1315 /* Skip the copyright line and any blank lines following it. */
1316 cchBody -= cchContributedBy + 1;
1317 pszBody = pszNextLine + 1;
1318 iLine += 1;
1319 while (*pszBody == '\n')
1320 {
1321 pszBody++;
1322 cchBody--;
1323 iLine++;
1324 cBlankLinesAfterContributedBy++;
1325 }
1326 }
1327 }
1328
1329 /*
1330 * Look for the copyright line.
1331 */
1332 bool fFoundCopyright = false;
1333 uint32_t cBlankLinesAfterCopyright = 0;
1334 if ( pState->iLineCopyright == UINT32_MAX
1335 && cchBody > sizeof("Copyright") + sizeof(g_szCopyrightHolder)
1336 && RTStrNICmp(pszBody, RT_STR_TUPLE("copyright")) == 0)
1337 {
1338 const char *pszNextLine = (const char *)memchr(pszBody, '\n', cchBody);
1339
1340 /* Oracle copyright? */
1341 const char *pszEnd = pszNextLine ? pszNextLine : &pszBody[cchBody];
1342 while (RT_C_IS_SPACE(pszEnd[-1]))
1343 pszEnd--;
1344 if ( (uintptr_t)(pszEnd - pszBody) > sizeof(g_szCopyrightHolder)
1345 && (*(unsigned char *)(pszEnd - sizeof(g_szCopyrightHolder) + 1) & 0x80) == 0 /* to avoid annoying assertion */
1346 && RTStrNICmp(pszEnd - sizeof(g_szCopyrightHolder) + 1, RT_STR_TUPLE(g_szCopyrightHolder)) == 0)
1347 {
1348 /* Parse out the year(s). */
1349 const char *psz = pszBody + sizeof("copyright");
1350 while ((uintptr_t)psz < (uintptr_t)pszEnd && !RT_C_IS_DIGIT(*psz))
1351 psz++;
1352 if (RT_C_IS_DIGIT(*psz))
1353 {
1354 char *pszNext;
1355 int rc = RTStrToUInt32Ex(psz, &pszNext, 10, &pState->uFirstYear);
1356 if ( RT_SUCCESS(rc)
1357 && rc != VWRN_NUMBER_TOO_BIG
1358 && rc != VWRN_NEGATIVE_UNSIGNED)
1359 {
1360 if ( pState->uFirstYear < 1975
1361 || pState->uFirstYear > 3000)
1362 {
1363 ScmError(pState->pState, VERR_OUT_OF_RANGE, "Copyright year is out of range: %u ('%.*s')\n",
1364 pState->uFirstYear, pszEnd - pszBody, pszBody);
1365 pState->uFirstYear = UINT32_MAX;
1366 }
1367
1368 while (RT_C_IS_SPACE(*pszNext))
1369 pszNext++;
1370 if (*pszNext == '-')
1371 {
1372 do
1373 pszNext++;
1374 while (RT_C_IS_SPACE(*pszNext));
1375 rc = RTStrToUInt32Ex(pszNext, &pszNext, 10, &pState->uLastYear);
1376 if ( RT_SUCCESS(rc)
1377 && rc != VWRN_NUMBER_TOO_BIG
1378 && rc != VWRN_NEGATIVE_UNSIGNED)
1379 {
1380 if ( pState->uLastYear < 1975
1381 || pState->uLastYear > 3000)
1382 {
1383 ScmError(pState->pState, VERR_OUT_OF_RANGE, "Second copyright year is out of range: %u ('%.*s')\n",
1384 pState->uLastYear, pszEnd - pszBody, pszBody);
1385 pState->uLastYear = UINT32_MAX;
1386 }
1387 else if (pState->uFirstYear > pState->uLastYear)
1388 {
1389 RTMsgWarning("Copyright years switched(?): '%.*s'\n", pszEnd - pszBody, pszBody);
1390 uint32_t iTmp = pState->uLastYear;
1391 pState->uLastYear = pState->uFirstYear;
1392 pState->uFirstYear = iTmp;
1393 }
1394 }
1395 else
1396 {
1397 pState->uLastYear = UINT32_MAX;
1398 ScmError(pState->pState, RT_SUCCESS(rc) ? -rc : rc,
1399 "Failed to parse second copyright year: '%.*s'\n", pszEnd - pszBody, pszBody);
1400 }
1401 }
1402 else if (*pszNext != g_szCopyrightHolder[0])
1403 ScmError(pState->pState, VERR_PARSE_ERROR,
1404 "Failed to parse copyright: '%.*s'\n", pszEnd - pszBody, pszBody);
1405 else
1406 pState->uLastYear = pState->uFirstYear;
1407 }
1408 else
1409 {
1410 pState->uFirstYear = UINT32_MAX;
1411 ScmError(pState->pState, RT_SUCCESS(rc) ? -rc : rc,
1412 "Failed to parse copyright year: '%.*s'\n", pszEnd - pszBody, pszBody);
1413 }
1414 }
1415
1416 /* The copyright comment must come before the license. */
1417 if (pState->iLineLicense != UINT32_MAX)
1418 ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright (line %u) must come before the license (line %u)!\n",
1419 iLine, pState->iLineLicense);
1420
1421 /* In C/C++ code, this must be a multiline comment. While in python it
1422 must be a */
1423 if (pState->enmCommentStyle == kScmCommentStyle_C && pInfo->enmType != kScmCommentType_MultiLine)
1424 ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright must appear in a multiline comment (no doxygen stuff)\n");
1425 else if (pState->enmCommentStyle == kScmCommentStyle_Python && pInfo->enmType != kScmCommentType_DocString)
1426 ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright must appear in a doc-string\n");
1427
1428 /* The copyright must be followed by the license. */
1429 if (!pszNextLine)
1430 ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright should be followed by the license text!\n");
1431
1432 /* Quit if we've flagged a failure. */
1433 if (RT_FAILURE(pState->pState->rc))
1434 return VERR_CALLBACK_RETURN;
1435
1436 /* Check if it's well formed and up to date. */
1437 char szWellFormed[256];
1438 size_t cchWellFormed;
1439 if (pState->uFirstYear == pState->uLastYear)
1440 cchWellFormed = RTStrPrintf(szWellFormed, sizeof(szWellFormed), "Copyright (C) %u %s",
1441 pState->uFirstYear, g_szCopyrightHolder);
1442 else
1443 cchWellFormed = RTStrPrintf(szWellFormed, sizeof(szWellFormed), "Copyright (C) %u-%u %s",
1444 pState->uFirstYear, pState->uLastYear, g_szCopyrightHolder);
1445 pState->fUpToDateCopyright = pState->uLastYear == g_uYear;
1446 pState->iLineCopyright = iLine;
1447 pState->fWellFormedCopyright = cchWellFormed == (uintptr_t)(pszEnd - pszBody)
1448 && memcmp(pszBody, szWellFormed, cchWellFormed) == 0;
1449 if (!pState->fWellFormedCopyright)
1450 ScmVerbose(pState->pState, 1, "* copyright isn't well formed\n");
1451
1452 /* If there wasn't exactly one blank line before the comment, trigger a rewrite. */
1453 if (pInfo->cBlankLinesBefore != 1)
1454 {
1455 ScmVerbose(pState->pState, 1, "* copyright comment is preceeded by %u blank lines instead of 1\n",
1456 pInfo->cBlankLinesBefore);
1457 pState->fWellFormedCopyright = false;
1458 }
1459
1460 /* If the comment doesn't start in column 1, trigger rewrite. */
1461 if (pInfo->offStart != 0)
1462 {
1463 ScmVerbose(pState->pState, 1, "* copyright comment starts in column %u instead of 1\n", pInfo->offStart + 1);
1464 pState->fWellFormedCopyright = false;
1465 /** @todo check that there isn't any code preceeding the comment. */
1466 }
1467
1468 if (pchContributedBy)
1469 {
1470 pState->pszContributedBy = RTStrDupN(pchContributedBy, cchContributedBy);
1471 if (cBlankLinesAfterContributedBy != 1)
1472 {
1473 ScmVerbose(pState->pState, 1, "* %u blank lines between contributed by and copyright, should be 1\n",
1474 cBlankLinesAfterContributedBy);
1475 pState->fWellFormedCopyright = false;
1476 }
1477 }
1478
1479 fFoundCopyright = true;
1480 ScmVerbose(pState->pState, 3, "oracle copyright %u-%u: up-to-date=%RTbool well-formed=%RTbool\n",
1481 pState->uFirstYear, pState->uLastYear, pState->fUpToDateCopyright, pState->fWellFormedCopyright);
1482 }
1483 else
1484 ScmVerbose(pState->pState, 3, "not oracle copyright: '%.*s'\n", pszEnd - pszBody, pszBody);
1485
1486 if (!pszNextLine)
1487 return VINF_SUCCESS;
1488
1489 /* Skip the copyright line and any blank lines following it. */
1490 cchBody -= pszNextLine - pszBody + 1;
1491 pszBody = pszNextLine + 1;
1492 iLine += 1;
1493 while (*pszBody == '\n')
1494 {
1495 pszBody++;
1496 cchBody--;
1497 iLine++;
1498 cBlankLinesAfterCopyright++;
1499 }
1500
1501 /*
1502 * If we have a based-on-mit scenario, check for the lead in now and
1503 * complain if not found.
1504 */
1505 if ( fFoundCopyright
1506 && pState->enmLicenceOpt == kScmLicense_BasedOnMit
1507 && pState->iLineLicense == UINT32_MAX)
1508 {
1509 if (RTStrNICmp(pszBody, RT_STR_TUPLE("This file is based on ")) == 0)
1510 {
1511 /* Take down a comment area which goes up to 'this file is based on'.
1512 The license line and length isn't used but gets set to cover the current line. */
1513 pState->iLineComment = pInfo->iLineStart;
1514 pState->cLinesComment = iLine - pInfo->iLineStart;
1515 pState->iLineLicense = iLine;
1516 pState->cLinesLicense = 1;
1517 pState->fExternalLicense = true;
1518 pState->fIsCorrectLicense = true;
1519 pState->fWellFormedLicense = true;
1520
1521 /* Check if we've got a MIT a license here or not. */
1522 pState->pCurrentLicense = NULL;
1523 do
1524 {
1525 const char *pszEol = (const char *)memchr(pszBody, '\n', cchBody);
1526 if (!pszEol || pszEol[1] == '\0')
1527 {
1528 pszBody += cchBody;
1529 cchBody = 0;
1530 break;
1531 }
1532 cchBody -= pszEol - pszBody + 1;
1533 pszBody = pszEol + 1;
1534 iLine++;
1535
1536 for (PCSCMLICENSETEXT pCur = pState->paLicenses; pCur->cch > 0; pCur++)
1537 {
1538 const char *pszNext;
1539 if ( pCur->cch <= cchBody + 32 /* (+ 32 since we don't compare spaces and punctuation) */
1540 && IsEqualWordByWordIgnoreCase(pCur->psz, pszBody, &pszNext))
1541 {
1542 pState->pCurrentLicense = pCur;
1543 break;
1544 }
1545 }
1546 } while (!pState->pCurrentLicense);
1547 if (!pState->pCurrentLicense)
1548 ScmError(pState->pState, VERR_NOT_FOUND, "Could not find the based-on license!\n");
1549 else if (pState->pCurrentLicense->enmType != kScmLicenseType_Mit)
1550 ScmError(pState->pState, VERR_NOT_FOUND, "The based-on license is not MIT (%.32s...)\n",
1551 pState->pCurrentLicense->psz);
1552 }
1553 else
1554 ScmError(pState->pState, VERR_WRONG_ORDER, "Expected 'This file is based on ...' after our copyright!\n");
1555 return VINF_SUCCESS;
1556 }
1557 }
1558
1559 /*
1560 * Look for LGPL like text in the comment.
1561 */
1562 if (pState->fCheckforLgpl && cchBody > 128)
1563 {
1564 /* We look for typical LGPL notices. */
1565 if (pState->iLineLgplNotice == UINT32_MAX)
1566 {
1567 static const char * const s_apszFragments[] =
1568 {
1569 "under the terms of the GNU Lesser General Public License",
1570 };
1571 for (unsigned i = 0; i < RT_ELEMENTS(s_apszFragments); i++)
1572 if (scmContainsWordByWordIgnoreCase(pszBody, cchBody, s_apszFragments[i], NULL, NULL))
1573 {
1574 pState->iLineLgplNotice = iLine;
1575 pState->iLineAfterLgplComment = pInfo->iLineEnd + 1;
1576 ScmVerbose(pState->pState, 3, "Found LGPL notice at %u\n", iLine);
1577 break;
1578 }
1579 }
1580
1581 if ( pState->iLineLgplDisclaimer == UINT32_MAX
1582 && scmContainsWordByWordIgnoreCase(pszBody, cchBody, g_szLgplDisclaimer, NULL, NULL))
1583 {
1584 pState->iLineLgplDisclaimer = iLine;
1585 ScmVerbose(pState->pState, 3, "Found LGPL disclaimer at %u\n", iLine);
1586 }
1587 }
1588
1589 /*
1590 * Look for the license text.
1591 */
1592 if (pState->iLineLicense == UINT32_MAX)
1593 {
1594 for (PCSCMLICENSETEXT pCur = pState->paLicenses; pCur->cch > 0; pCur++)
1595 {
1596 const char *pszNext;
1597 if ( pCur->cch <= cchBody + 32 /* (+ 32 since we don't compare spaces and punctuation) */
1598 && IsEqualWordByWordIgnoreCase(pCur->psz, pszBody, &pszNext))
1599 {
1600 while ( RT_C_IS_SPACE(*pszNext)
1601 || (RT_C_IS_PUNCT(*pszNext) && *pszNext != '-'))
1602 pszNext++;
1603
1604 uint32_t cDashes = 0;
1605 while (*pszNext == '-')
1606 cDashes++, pszNext++;
1607 bool fExternal = cDashes > 10;
1608
1609 if ( *pszNext == '\0'
1610 || fExternal)
1611 {
1612 /* In C/C++ code, this must be a multiline comment. While in python it
1613 must be a */
1614 if (pState->enmCommentStyle == kScmCommentStyle_C && pInfo->enmType != kScmCommentType_MultiLine)
1615 ScmError(pState->pState, VERR_WRONG_ORDER, "License must appear in a multiline comment (no doxygen stuff)\n");
1616 else if (pState->enmCommentStyle == kScmCommentStyle_Python && pInfo->enmType != kScmCommentType_DocString)
1617 ScmError(pState->pState, VERR_WRONG_ORDER, "License must appear in a doc-string\n");
1618
1619 /* Quit if we've flagged a failure. */
1620 if (RT_FAILURE(pState->pState->rc))
1621 return VERR_CALLBACK_RETURN;
1622
1623 /* Record it. */
1624 pState->iLineLicense = iLine;
1625 pState->cLinesLicense = CountLinesInSubstring(pszBody, pszNext - pszBody) - fExternal;
1626 pState->pCurrentLicense = pCur;
1627 pState->fExternalLicense = fExternal;
1628 pState->fIsCorrectLicense = pState->fOpenSource
1629 ? pCur == pState->pExpectedLicense
1630 : pCur->enmType == kScmLicenseType_Confidential;
1631 pState->fWellFormedLicense = memcmp(pszBody, pCur->psz, pCur->cch - 1) == 0;
1632 if (!pState->fWellFormedLicense)
1633 ScmVerbose(pState->pState, 1, "* license text isn't well-formed\n");
1634
1635 /* If there was more than one blank line between the copyright and the
1636 license text, extend the license text area and force a rewrite of it. */
1637 if (cBlankLinesAfterCopyright > 1)
1638 {
1639 ScmVerbose(pState->pState, 1, "* %u blank lines between copyright and license text, instead of 1\n",
1640 cBlankLinesAfterCopyright);
1641 pState->iLineLicense -= cBlankLinesAfterCopyright - 1;
1642 pState->cLinesLicense += cBlankLinesAfterCopyright - 1;
1643 pState->fWellFormedLicense = false;
1644 }
1645
1646 /* If there was more than one blank line after the license, trigger a rewrite. */
1647 if (!fExternal && pInfo->cBlankLinesAfter != 1)
1648 {
1649 ScmVerbose(pState->pState, 1, "* copyright comment is followed by %u blank lines instead of 1\n",
1650 pInfo->cBlankLinesAfter);
1651 pState->fWellFormedLicense = false;
1652 }
1653
1654 /** @todo Check that the last comment line doesn't have any code on it. */
1655 /** @todo Check that column 2 contains '*' for C/C++ files. */
1656
1657 ScmVerbose(pState->pState, 3,
1658 "Found license %d/%d at %u..%u: is-correct=%RTbool well-formed=%RTbool external-part=%RTbool open-source=%RTbool\n",
1659 pCur->enmType, pCur->enmOpt, pState->iLineLicense, pState->iLineLicense + pState->cLinesLicense,
1660 pState->fIsCorrectLicense, pState->fWellFormedLicense,
1661 pState->fExternalLicense, pState->fOpenSource);
1662
1663 if (fFoundCopyright)
1664 {
1665 pState->iLineComment = pInfo->iLineStart;
1666 pState->cLinesComment = (fExternal ? pState->iLineLicense + pState->cLinesLicense : pInfo->iLineEnd + 1)
1667 - pInfo->iLineStart;
1668 }
1669 else
1670 ScmError(pState->pState, VERR_WRONG_ORDER, "License should be preceeded by the copyright!\n");
1671 break;
1672 }
1673 }
1674 }
1675 }
1676
1677 if (fFoundCopyright && pState->iLineLicense == UINT32_MAX)
1678 ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright should be followed by the license text!\n");
1679
1680 /*
1681 * Stop looking for stuff after 100 comments.
1682 */
1683 if (pState->cComments > 100)
1684 return VERR_CALLBACK_RETURN;
1685 return VINF_SUCCESS;
1686}
1687
1688/**
1689 * Writes comment body text.
1690 *
1691 * @returns Stream status.
1692 * @param pOut The output stream.
1693 * @param pszText The text to write.
1694 * @param cchText The length of the text.
1695 * @param enmCommentStyle The comment style.
1696 * @param enmEol The EOL style.
1697 */
1698static int scmWriteCommentBody(PSCMSTREAM pOut, const char *pszText, size_t cchText,
1699 SCMCOMMENTSTYLE enmCommentStyle, SCMEOL enmEol)
1700{
1701 Assert(pszText[cchText - 1] == '\n');
1702 Assert(pszText[cchText - 2] != '\n');
1703 NOREF(cchText);
1704 do
1705 {
1706 const char *pszEol = strchr(pszText, '\n');
1707 if (pszEol != pszText)
1708 {
1709 ScmStreamWrite(pOut, g_aCopyrightCommentPrefix[enmCommentStyle].psz,
1710 g_aCopyrightCommentPrefix[enmCommentStyle].cch);
1711 ScmStreamWrite(pOut, pszText, pszEol - pszText);
1712 ScmStreamPutEol(pOut, enmEol);
1713 }
1714 else
1715 ScmStreamPutLine(pOut, g_aCopyrightCommentEmpty[enmCommentStyle].psz,
1716 g_aCopyrightCommentEmpty[enmCommentStyle].cch, enmEol);
1717 pszText = pszEol + 1;
1718 } while (*pszText != '\0');
1719 return ScmStreamGetStatus(pOut);
1720}
1721
1722
1723/**
1724 * Updates the copyright year and/or license text.
1725 *
1726 * @returns true if modifications were made, false if not.
1727 * @param pState The rewriter state.
1728 * @param pIn The input stream.
1729 * @param pOut The output stream.
1730 * @param pSettings The settings.
1731 * @param enmCommentStyle The comment style used by the file.
1732 */
1733static bool rewrite_Copyright_Common(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings,
1734 SCMCOMMENTSTYLE enmCommentStyle)
1735{
1736 if ( !pSettings->fUpdateCopyrightYear
1737 && pSettings->enmUpdateLicense == kScmLicense_LeaveAlone)
1738 return false;
1739
1740 /*
1741 * Try locate the relevant comments.
1742 */
1743 SCMCOPYRIGHTINFO Info =
1744 {
1745 /*.pState = */ pState,
1746 /*.enmCommentStyle = */ enmCommentStyle,
1747
1748 /*.cComments = */ 0,
1749
1750 /*.pszContributedBy = */ NULL,
1751
1752 /*.iLineComment = */ UINT32_MAX,
1753 /*.cLinesComment = */ 0,
1754
1755 /*.iLineCopyright = */ UINT32_MAX,
1756 /*.uFirstYear = */ UINT32_MAX,
1757 /*.uLastYear = */ UINT32_MAX,
1758 /*.fWellFormedCopyright = */ false,
1759 /*.fUpToDateCopyright = */ false,
1760
1761 /*.fOpenSource = */ true,
1762 /*.pExpectedLicense = */ NULL,
1763 /*.paLicenses = */ pSettings->enmUpdateLicense != kScmLicense_Mit
1764 && pSettings->enmUpdateLicense != kScmLicense_BasedOnMit
1765 ? &g_aLicenses[0] : &g_aLicensesWithMit[0],
1766 /*.enmLicenceOpt = */ pSettings->enmUpdateLicense,
1767 /*.iLineLicense = */ UINT32_MAX,
1768 /*.cLinesLicense = */ 0,
1769 /*.pCurrentLicense = */ NULL,
1770 /*.fIsCorrectLicense = */ false,
1771 /*.fWellFormedLicense = */ false,
1772 /*.fExternalLicense = */ false,
1773
1774 /*.fCheckForLgpl = */ true,
1775 /*.iLineLgplNotice = */ UINT32_MAX,
1776 /*.iLineAfterLgplComment = */ UINT32_MAX,
1777 /*.iLineLgplDisclaimer = */ UINT32_MAX,
1778 };
1779
1780 /* Figure Info.fOpenSource and the desired license: */
1781 char *pszSyncProcess;
1782 int rc = ScmSvnQueryProperty(pState, "svn:sync-process", &pszSyncProcess);
1783 if (RT_SUCCESS(rc))
1784 {
1785 Info.fOpenSource = strcmp(RTStrStrip(pszSyncProcess), "export") == 0;
1786 RTStrFree(pszSyncProcess);
1787 }
1788 else if (rc == VERR_NOT_FOUND)
1789 Info.fOpenSource = false;
1790 else
1791 return ScmError(pState, rc, "ScmSvnQueryProperty(svn:sync-process): %Rrc\n", rc);
1792
1793 Info.pExpectedLicense = Info.paLicenses;
1794 if (Info.fOpenSource)
1795 {
1796 if ( pSettings->enmUpdateLicense != kScmLicense_Mit
1797 && pSettings->enmUpdateLicense != kScmLicense_BasedOnMit)
1798 while (Info.pExpectedLicense->enmOpt != pSettings->enmUpdateLicense)
1799 Info.pExpectedLicense++;
1800 else
1801 Assert(Info.pExpectedLicense->enmOpt == kScmLicense_Mit);
1802 }
1803 else
1804 while (Info.pExpectedLicense->enmType != kScmLicenseType_Confidential)
1805 Info.pExpectedLicense++;
1806
1807 /* Scan the comments. */
1808 rc = ScmEnumerateComments(pIn, enmCommentStyle, rewrite_Copyright_CommentCallback, &Info);
1809 if ( (rc == VERR_CALLBACK_RETURN || RT_SUCCESS(rc))
1810 && RT_SUCCESS(pState->rc))
1811 {
1812 /*
1813 * Do conformity checks.
1814 */
1815 bool fAddLgplDisclaimer = false;
1816 if (Info.fCheckforLgpl)
1817 {
1818 if ( Info.iLineLgplNotice != UINT32_MAX
1819 && Info.iLineLgplDisclaimer == UINT32_MAX)
1820 {
1821 if (!pSettings->fLgplDisclaimer) /** @todo reconcile options with common sense. */
1822 ScmError(pState, VERR_NOT_FOUND, "LGPL licence notice on line %u, but no LGPL disclaimer was found!\n",
1823 Info.iLineLgplNotice + 1);
1824 else
1825 {
1826 ScmVerbose(pState, 1, "* Need to add LGPL disclaimer\n");
1827 fAddLgplDisclaimer = true;
1828 }
1829 }
1830 else if ( Info.iLineLgplNotice == UINT32_MAX
1831 && Info.iLineLgplDisclaimer != UINT32_MAX)
1832 ScmError(pState, VERR_NOT_FOUND, "LGPL disclaimer on line %u, but no LGPL copyright notice!\n",
1833 Info.iLineLgplDisclaimer + 1);
1834 }
1835
1836 if (!pSettings->fExternalCopyright)
1837 {
1838 if (Info.iLineCopyright == UINT32_MAX)
1839 ScmError(pState, VERR_NOT_FOUND, "Missing copyright!\n");
1840 if (Info.iLineLicense == UINT32_MAX)
1841 ScmError(pState, VERR_NOT_FOUND, "Missing license!\n");
1842 }
1843 else if (Info.iLineCopyright != UINT32_MAX)
1844 ScmError(pState, VERR_NOT_FOUND,
1845 "Marked as external copyright only, but found non-external copyright statement at line %u!\n",
1846 Info.iLineCopyright + 1);
1847
1848
1849 if (RT_SUCCESS(pState->rc))
1850 {
1851 /*
1852 * Do we need to make any changes?
1853 */
1854 bool fUpdateCopyright = !pSettings->fExternalCopyright
1855 && ( !Info.fWellFormedCopyright
1856 || (!Info.fUpToDateCopyright && pSettings->fUpdateCopyrightYear));
1857 bool fUpdateLicense = !pSettings->fExternalCopyright
1858 && Info.enmLicenceOpt != kScmLicense_LeaveAlone
1859 && ( !Info.fWellFormedLicense
1860 || !Info.fIsCorrectLicense);
1861 if ( fUpdateCopyright
1862 || fUpdateLicense
1863 || fAddLgplDisclaimer)
1864 {
1865 Assert(Info.iLineComment != UINT32_MAX);
1866 Assert(Info.cLinesComment > 0);
1867
1868 /*
1869 * Okay, do the work.
1870 */
1871 ScmStreamRewindForReading(pIn);
1872
1873 if (pSettings->fUpdateCopyrightYear)
1874 Info.uLastYear = g_uYear;
1875
1876 uint32_t iLine = 0;
1877 SCMEOL enmEol;
1878 size_t cchLine;
1879 const char *pchLine;
1880 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
1881 {
1882 if ( iLine == Info.iLineComment
1883 && (fUpdateCopyright || fUpdateLicense) )
1884 {
1885 /* Leading blank line. */
1886 ScmStreamPutLine(pOut, g_aCopyrightCommentStart[enmCommentStyle].psz,
1887 g_aCopyrightCommentStart[enmCommentStyle].cch, enmEol);
1888
1889 /* Contributed by someone? */
1890 if (Info.pszContributedBy)
1891 {
1892 const char *psz = Info.pszContributedBy;
1893 for (;;)
1894 {
1895 const char *pszEol = strchr(psz, '\n');
1896 size_t cchContribLine = pszEol ? pszEol - psz : strlen(psz);
1897 ScmStreamWrite(pOut, g_aCopyrightCommentPrefix[enmCommentStyle].psz,
1898 g_aCopyrightCommentPrefix[enmCommentStyle].cch);
1899 ScmStreamWrite(pOut, psz, cchContribLine);
1900 ScmStreamPutEol(pOut, enmEol);
1901 if (!pszEol)
1902 break;
1903 psz = pszEol + 1;
1904 }
1905
1906 ScmStreamPutLine(pOut, g_aCopyrightCommentEmpty[enmCommentStyle].psz,
1907 g_aCopyrightCommentEmpty[enmCommentStyle].cch, enmEol);
1908 }
1909
1910 /* Write the copyright comment line. */
1911 ScmStreamWrite(pOut, g_aCopyrightCommentPrefix[enmCommentStyle].psz,
1912 g_aCopyrightCommentPrefix[enmCommentStyle].cch);
1913
1914 char szCopyright[256];
1915 size_t cchCopyright;
1916 if (Info.uFirstYear == Info.uLastYear)
1917 cchCopyright = RTStrPrintf(szCopyright, sizeof(szCopyright), "Copyright (C) %u %s",
1918 Info.uFirstYear, g_szCopyrightHolder);
1919 else
1920 cchCopyright = RTStrPrintf(szCopyright, sizeof(szCopyright), "Copyright (C) %u-%u %s",
1921 Info.uFirstYear, Info.uLastYear, g_szCopyrightHolder);
1922
1923 ScmStreamWrite(pOut, szCopyright, cchCopyright);
1924 ScmStreamPutEol(pOut, enmEol);
1925
1926 if (pSettings->enmUpdateLicense != kScmLicense_BasedOnMit)
1927 {
1928 /* Blank line separating the two. */
1929 ScmStreamPutLine(pOut, g_aCopyrightCommentEmpty[enmCommentStyle].psz,
1930 g_aCopyrightCommentEmpty[enmCommentStyle].cch, enmEol);
1931
1932 /* Write the license text. */
1933 scmWriteCommentBody(pOut, Info.pExpectedLicense->psz, Info.pExpectedLicense->cch,
1934 enmCommentStyle, enmEol);
1935
1936 /* Final comment line. */
1937 if (!Info.fExternalLicense)
1938 ScmStreamPutLine(pOut, g_aCopyrightCommentEnd[enmCommentStyle].psz,
1939 g_aCopyrightCommentEnd[enmCommentStyle].cch, enmEol);
1940 }
1941 else
1942 Assert(Info.fExternalLicense);
1943
1944 /* Skip the copyright and license text in the input file. */
1945 rc = ScmStreamGetStatus(pOut);
1946 if (RT_SUCCESS(rc))
1947 {
1948 iLine = Info.iLineComment + Info.cLinesComment;
1949 rc = ScmStreamSeekByLine(pIn, iLine);
1950 }
1951 }
1952 /*
1953 * Add LGPL disclaimer?
1954 */
1955 else if ( iLine == Info.iLineAfterLgplComment
1956 && fAddLgplDisclaimer)
1957 {
1958 ScmStreamPutEol(pOut, enmEol);
1959 ScmStreamPutLine(pOut, g_aCopyrightCommentStart[enmCommentStyle].psz,
1960 g_aCopyrightCommentStart[enmCommentStyle].cch, enmEol);
1961 scmWriteCommentBody(pOut, g_szLgplDisclaimer, sizeof(g_szLgplDisclaimer) - 1,
1962 enmCommentStyle, enmEol);
1963 ScmStreamPutLine(pOut, g_aCopyrightCommentEnd[enmCommentStyle].psz,
1964 g_aCopyrightCommentEnd[enmCommentStyle].cch, enmEol);
1965
1966 /* put the actual line */
1967 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
1968 iLine++;
1969 }
1970 else
1971 {
1972 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
1973 iLine++;
1974 }
1975 if (RT_FAILURE(rc))
1976 {
1977 RTStrFree(Info.pszContributedBy);
1978 return false;
1979 }
1980 } /* for each source line */
1981
1982 RTStrFree(Info.pszContributedBy);
1983 return true;
1984 }
1985 }
1986 }
1987 else
1988 ScmError(pState, rc, "ScmEnumerateComments: %Rrc\n", rc);
1989 NOREF(pState); NOREF(pOut);
1990 RTStrFree(Info.pszContributedBy);
1991 return false;
1992}
1993
1994
1995/** Copyright updater for C-style comments. */
1996bool rewrite_Copyright_CstyleComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1997{
1998 return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_C);
1999}
2000
2001/** Copyright updater for hash-prefixed comments. */
2002bool rewrite_Copyright_HashComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2003{
2004 return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Hash);
2005}
2006
2007/** Copyright updater for REM-prefixed comments. */
2008bool rewrite_Copyright_RemComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2009{
2010 return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, determinBatchFileCommentStyle(pIn));
2011}
2012
2013/** Copyright updater for python comments. */
2014bool rewrite_Copyright_PythonComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2015{
2016 return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Python);
2017}
2018
2019/** Copyright updater for semicolon-prefixed comments. */
2020bool rewrite_Copyright_SemicolonComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2021{
2022 return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Semicolon);
2023}
2024
2025/** Copyright updater for sql comments. */
2026bool rewrite_Copyright_SqlComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2027{
2028 return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Sql);
2029}
2030
2031/** Copyright updater for tick-prefixed comments. */
2032bool rewrite_Copyright_TickComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2033{
2034 return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Tick);
2035}
2036
2037
2038/**
2039 * Makefile.kup are empty files, enforce this.
2040 *
2041 * @returns true if modifications were made, false if not.
2042 * @param pIn The input stream.
2043 * @param pOut The output stream.
2044 * @param pSettings The settings.
2045 */
2046bool rewrite_Makefile_kup(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2047{
2048 RT_NOREF2(pOut, pSettings);
2049
2050 /* These files should be zero bytes. */
2051 if (pIn->cb == 0)
2052 return false;
2053 ScmVerbose(pState, 2, " * Truncated file to zero bytes\n");
2054 return true;
2055}
2056
2057/**
2058 * Rewrite a kBuild makefile.
2059 *
2060 * @returns true if modifications were made, false if not.
2061 * @param pIn The input stream.
2062 * @param pOut The output stream.
2063 * @param pSettings The settings.
2064 *
2065 * @todo
2066 *
2067 * Ideas for Makefile.kmk and Config.kmk:
2068 * - sort if1of/ifn1of sets.
2069 * - line continuation slashes should only be preceded by one space.
2070 */
2071bool rewrite_Makefile_kmk(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2072{
2073 RT_NOREF4(pState, pIn, pOut, pSettings);
2074 return false;
2075}
2076
2077
2078static bool isFlowerBoxSectionMarker(PSCMSTREAM pIn, const char *pchLine, size_t cchLine, uint32_t cchWidth,
2079 const char **ppchText, size_t *pcchText, bool *pfNeedFixing)
2080{
2081 *ppchText = NULL;
2082 *pcchText = 0;
2083 *pfNeedFixing = false;
2084
2085 /*
2086 * The first line.
2087 */
2088 if (pchLine[0] != '/')
2089 return false;
2090 size_t offLine = 1;
2091 while (offLine < cchLine && pchLine[offLine] == '*')
2092 offLine++;
2093 if (offLine < 20) /* (Code below depend on a reasonable minimum here.) */
2094 return false;
2095 while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine]))
2096 offLine++;
2097 if (offLine != cchLine)
2098 return false;
2099
2100 size_t const cchBox = cchLine;
2101 *pfNeedFixing = cchBox != cchWidth;
2102
2103 /*
2104 * The next line, extracting the text.
2105 */
2106 SCMEOL enmEol;
2107 pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
2108 if (cchLine < cchBox - 3)
2109 return false;
2110
2111 offLine = 0;
2112 if (RT_C_IS_BLANK(pchLine[0]))
2113 {
2114 *pfNeedFixing = true;
2115 offLine = RT_C_IS_BLANK(pchLine[1]) ? 2 : 1;
2116 }
2117
2118 if (pchLine[offLine] != '*')
2119 return false;
2120 offLine++;
2121
2122 if (!RT_C_IS_BLANK(pchLine[offLine + 1]))
2123 return false;
2124 offLine++;
2125
2126 while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine]))
2127 offLine++;
2128 if (offLine >= cchLine)
2129 return false;
2130 if (!RT_C_IS_UPPER(pchLine[offLine]))
2131 return false;
2132
2133 if (offLine != 4 || cchLine != cchBox)
2134 *pfNeedFixing = true;
2135
2136 *ppchText = &pchLine[offLine];
2137 size_t const offText = offLine;
2138
2139 /* From the end now. */
2140 offLine = cchLine - 1;
2141 while (RT_C_IS_BLANK(pchLine[offLine]))
2142 offLine--;
2143
2144 if (pchLine[offLine] != '*')
2145 return false;
2146 offLine--;
2147 if (!RT_C_IS_BLANK(pchLine[offLine]))
2148 return false;
2149 offLine--;
2150 while (RT_C_IS_BLANK(pchLine[offLine]))
2151 offLine--;
2152 *pcchText = offLine - offText + 1;
2153
2154 /*
2155 * Third line closes the box.
2156 */
2157 pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
2158 if (cchLine < cchBox - 3)
2159 return false;
2160
2161 offLine = 0;
2162 if (RT_C_IS_BLANK(pchLine[0]))
2163 {
2164 *pfNeedFixing = true;
2165 offLine = RT_C_IS_BLANK(pchLine[1]) ? 2 : 1;
2166 }
2167 while (offLine < cchLine && pchLine[offLine] == '*')
2168 offLine++;
2169 if (offLine < cchBox - 4)
2170 return false;
2171
2172 if (pchLine[offLine] != '/')
2173 return false;
2174 offLine++;
2175
2176 if (offLine != cchBox)
2177 *pfNeedFixing = true;
2178
2179 while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine]))
2180 offLine++;
2181 if (offLine != cchLine)
2182 return false;
2183
2184 return true;
2185}
2186
2187
2188/**
2189 * Flower box marker comments in C and C++ code.
2190 *
2191 * @returns true if modifications were made, false if not.
2192 * @param pIn The input stream.
2193 * @param pOut The output stream.
2194 * @param pSettings The settings.
2195 */
2196bool rewrite_FixFlowerBoxMarkers(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2197{
2198 if (!pSettings->fFixFlowerBoxMarkers)
2199 return false;
2200
2201 /*
2202 * Work thru the file line by line looking for flower box markers.
2203 */
2204 size_t cChanges = 0;
2205 size_t cBlankLines = 0;
2206 SCMEOL enmEol;
2207 size_t cchLine;
2208 const char *pchLine;
2209 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
2210 {
2211 /*
2212 * Get a likely match for a first line.
2213 */
2214 if ( pchLine[0] == '/'
2215 && cchLine > 20
2216 && pchLine[1] == '*'
2217 && pchLine[2] == '*'
2218 && pchLine[3] == '*')
2219 {
2220 size_t const offSaved = ScmStreamTell(pIn);
2221 char const *pchText;
2222 size_t cchText;
2223 bool fNeedFixing;
2224 bool fIsFlowerBoxSection = isFlowerBoxSectionMarker(pIn, pchLine, cchLine, pSettings->cchWidth,
2225 &pchText, &cchText, &fNeedFixing);
2226 if ( fIsFlowerBoxSection
2227 && ( fNeedFixing
2228 || cBlankLines < pSettings->cMinBlankLinesBeforeFlowerBoxMakers) )
2229 {
2230 while (cBlankLines < pSettings->cMinBlankLinesBeforeFlowerBoxMakers)
2231 {
2232 ScmStreamPutEol(pOut, enmEol);
2233 cBlankLines++;
2234 }
2235
2236 ScmStreamPutCh(pOut, '/');
2237 ScmStreamWrite(pOut, g_szAsterisks, pSettings->cchWidth - 1);
2238 ScmStreamPutEol(pOut, enmEol);
2239
2240 static const char s_szLead[] = "* ";
2241 ScmStreamWrite(pOut, s_szLead, sizeof(s_szLead) - 1);
2242 ScmStreamWrite(pOut, pchText, cchText);
2243 size_t offCurPlus1 = sizeof(s_szLead) - 1 + cchText + 1;
2244 ScmStreamWrite(pOut, g_szSpaces, offCurPlus1 < pSettings->cchWidth ? pSettings->cchWidth - offCurPlus1 : 1);
2245 ScmStreamPutCh(pOut, '*');
2246 ScmStreamPutEol(pOut, enmEol);
2247
2248 ScmStreamWrite(pOut, g_szAsterisks, pSettings->cchWidth - 1);
2249 ScmStreamPutCh(pOut, '/');
2250 ScmStreamPutEol(pOut, enmEol);
2251
2252 cChanges++;
2253 cBlankLines = 0;
2254 continue;
2255 }
2256
2257 int rc = ScmStreamSeekAbsolute(pIn, offSaved);
2258 if (RT_FAILURE(rc))
2259 return false;
2260 }
2261
2262 int rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
2263 if (RT_FAILURE(rc))
2264 return false;
2265
2266 /* Do blank line accounting so we can ensure at least two blank lines
2267 before each section marker. */
2268 if (!isBlankLine(pchLine, cchLine))
2269 cBlankLines = 0;
2270 else
2271 cBlankLines++;
2272 }
2273 if (cChanges > 0)
2274 ScmVerbose(pState, 2, " * Converted %zu flower boxer markers\n", cChanges);
2275 return cChanges != 0;
2276}
2277
2278
2279/**
2280 * Looks for the start of a todo comment.
2281 *
2282 * @returns Offset into the line of the comment start sequence.
2283 * @param pchLine The line to search.
2284 * @param cchLineBeforeTodo The length of the line before the todo.
2285 * @param pfSameLine Indicates whether it's refering to a statemtn on
2286 * the same line comment (true), or the next
2287 * statement (false).
2288 */
2289static size_t findTodoCommentStart(char const *pchLine, size_t cchLineBeforeTodo, bool *pfSameLine)
2290{
2291 *pfSameLine = false;
2292
2293 /* Skip one '@' or '\\'. */
2294 char ch;
2295 if ( cchLineBeforeTodo > 2
2296 && ( ((ch = pchLine[cchLineBeforeTodo - 1]) == '@')
2297 || ch == '\\' ) )
2298 cchLineBeforeTodo--;
2299
2300 /* Skip blanks. */
2301 while ( cchLineBeforeTodo > 2
2302 && RT_C_IS_BLANK(pchLine[cchLineBeforeTodo - 1]))
2303 cchLineBeforeTodo--;
2304
2305 /* Look for same line indicator. */
2306 if ( cchLineBeforeTodo > 0
2307 && pchLine[cchLineBeforeTodo - 1] == '<')
2308 {
2309 *pfSameLine = true;
2310 cchLineBeforeTodo--;
2311 }
2312
2313 /* Skip *s */
2314 while ( cchLineBeforeTodo > 1
2315 && pchLine[cchLineBeforeTodo - 1] == '*')
2316 cchLineBeforeTodo--;
2317
2318 /* Do we have a comment opening sequence. */
2319 if ( cchLineBeforeTodo > 0
2320 && pchLine[cchLineBeforeTodo - 1] == '/'
2321 && ( ( cchLineBeforeTodo >= 2
2322 && pchLine[cchLineBeforeTodo - 2] == '/')
2323 || pchLine[cchLineBeforeTodo] == '*'))
2324 {
2325 /* Skip slashes at the start. */
2326 while ( cchLineBeforeTodo > 0
2327 && pchLine[cchLineBeforeTodo - 1] == '/')
2328 cchLineBeforeTodo--;
2329
2330 return cchLineBeforeTodo;
2331 }
2332
2333 return ~(size_t)0;
2334}
2335
2336
2337/**
2338 * Looks for a TODO or todo in the given line.
2339 *
2340 * @returns Offset into the line of found, ~(size_t)0 if not.
2341 * @param pchLine The line to search.
2342 * @param cchLine The length of the line.
2343 */
2344static size_t findTodo(char const *pchLine, size_t cchLine)
2345{
2346 if (cchLine >= 4 + 2)
2347 {
2348 /* We don't search the first to chars because we need the start of a comment.
2349 Also, skip the last three chars since we need at least four for a match. */
2350 size_t const cchLineT = cchLine - 3;
2351 if ( memchr(pchLine + 2, 't', cchLineT - 2) != NULL
2352 || memchr(pchLine + 2, 'T', cchLineT - 2) != NULL)
2353 {
2354 for (size_t off = 2; off < cchLineT; off++)
2355 {
2356 char ch = pchLine[off];
2357 if ( ( ch != 't'
2358 && ch != 'T')
2359 || ( (ch = pchLine[off + 1]) != 'o'
2360 && ch != 'O')
2361 || ( (ch = pchLine[off + 2]) != 'd'
2362 && ch != 'D')
2363 || ( (ch = pchLine[off + 3]) != 'o'
2364 && ch != 'O')
2365 || ( off + 4 != cchLine
2366 && (ch = pchLine[off + 4]) != ' '
2367 && ch != '\t'
2368 && ch != ':' /** @todo */
2369 && (ch != '*' || off + 5 > cchLine || pchLine[off + 5] != '/') /** @todo */
2370 ) )
2371 { /* not a hit - likely */ }
2372 else
2373 return off;
2374 }
2375 }
2376 }
2377 return ~(size_t)0;
2378}
2379
2380
2381/**
2382 * Doxygen todos in C and C++ code.
2383 *
2384 * @returns true if modifications were made, false if not.
2385 * @param pState The rewriter state.
2386 * @param pIn The input stream.
2387 * @param pOut The output stream.
2388 * @param pSettings The settings.
2389 */
2390bool rewrite_Fix_C_and_CPP_Todos(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2391{
2392 if (!pSettings->fFixTodos)
2393 return false;
2394
2395 /*
2396 * Work thru the file line by line looking for the start of todo comments.
2397 */
2398 size_t cChanges = 0;
2399 SCMEOL enmEol;
2400 size_t cchLine;
2401 const char *pchLine;
2402 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
2403 {
2404 /*
2405 * Look for the word 'todo' in the line. We're currently only trying
2406 * to catch comments starting with the word todo and adjust the start of
2407 * the doxygen statement.
2408 */
2409 size_t offTodo = findTodo(pchLine, cchLine);
2410 if ( offTodo != ~(size_t)0
2411 && offTodo >= 2)
2412 {
2413 /* Work backwards to find the start of the comment. */
2414 bool fSameLine = false;
2415 size_t offCommentStart = findTodoCommentStart(pchLine, offTodo, &fSameLine);
2416 if (offCommentStart != ~(size_t)0)
2417 {
2418 char szNew[64];
2419 size_t cchNew = 0;
2420 szNew[cchNew++] = '/';
2421 szNew[cchNew++] = pchLine[offCommentStart + 1];
2422 szNew[cchNew++] = pchLine[offCommentStart + 1];
2423 if (fSameLine)
2424 szNew[cchNew++] = '<';
2425 szNew[cchNew++] = ' ';
2426 szNew[cchNew++] = '@';
2427 szNew[cchNew++] = 't';
2428 szNew[cchNew++] = 'o';
2429 szNew[cchNew++] = 'd';
2430 szNew[cchNew++] = 'o';
2431
2432 /* Figure out wheter to continue after the @todo statement opening, we'll strip ':'
2433 but need to take into account that we might be at the end of the line before
2434 adding the space. */
2435 size_t offTodoAfter = offTodo + 4;
2436 if ( offTodoAfter < cchLine
2437 && pchLine[offTodoAfter] == ':')
2438 offTodoAfter++;
2439 if ( offTodoAfter < cchLine
2440 && RT_C_IS_BLANK(pchLine[offTodoAfter]))
2441 offTodoAfter++;
2442 if (offTodoAfter < cchLine)
2443 szNew[cchNew++] = ' ';
2444
2445 /* Write it out. */
2446 ScmStreamWrite(pOut, pchLine, offCommentStart);
2447 ScmStreamWrite(pOut, szNew, cchNew);
2448 if (offTodoAfter < cchLine)
2449 ScmStreamWrite(pOut, &pchLine[offTodoAfter], cchLine - offTodoAfter);
2450 ScmStreamPutEol(pOut, enmEol);
2451
2452 /* Check whether we actually made any changes. */
2453 if ( cchNew != offTodoAfter - offCommentStart
2454 || memcmp(szNew, &pchLine[offCommentStart], cchNew))
2455 cChanges++;
2456 continue;
2457 }
2458 }
2459
2460 int rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
2461 if (RT_FAILURE(rc))
2462 return false;
2463 }
2464 if (cChanges > 0)
2465 ScmVerbose(pState, 2, " * Converted %zu todo statements.\n", cChanges);
2466 return cChanges != 0;
2467}
2468
2469
2470/**
2471 * Tries to parse a C/C++ preprocessor include directive.
2472 *
2473 * This is resonably forgiving and expects sane input.
2474 *
2475 * @retval kScmIncludeDir_Invalid if not a valid include directive.
2476 * @retval kScmIncludeDir_Quoted
2477 * @retval kScmIncludeDir_Bracketed
2478 * @retval kScmIncludeDir_Macro
2479 *
2480 * @param pState The rewriter state (for repording malformed
2481 * directives).
2482 * @param pchLine The line to try parse as an include statement.
2483 * @param cchLine The line length.
2484 * @param ppchFilename Where to return the pointer to the filename part.
2485 * @param pcchFilename Where to return the length of the filename.
2486 */
2487SCMINCLUDEDIR ScmMaybeParseCIncludeLine(PSCMRWSTATE pState, const char *pchLine, size_t cchLine,
2488 const char **ppchFilename, size_t *pcchFilename)
2489{
2490 /* Skip leading spaces: */
2491 while (cchLine > 0 && RT_C_IS_BLANK(*pchLine))
2492 cchLine--, pchLine++;
2493
2494 /* Check for '#': */
2495 if (cchLine > 0 && *pchLine == '#')
2496 {
2497 cchLine--;
2498 pchLine++;
2499
2500 /* Skip spaces after '#' (optional): */
2501 while (cchLine > 0 && RT_C_IS_BLANK(*pchLine))
2502 cchLine--, pchLine++;
2503
2504 /* Check for 'include': */
2505 static char const s_szInclude[] = "include";
2506 if ( cchLine >= sizeof(s_szInclude)
2507 && memcmp(pchLine, RT_STR_TUPLE(s_szInclude)) == 0)
2508 {
2509 cchLine -= sizeof(s_szInclude) - 1;
2510 pchLine += sizeof(s_szInclude) - 1;
2511
2512 /* Skip spaces after 'include' word (optional): */
2513 while (cchLine > 0 && RT_C_IS_BLANK(*pchLine))
2514 cchLine--, pchLine++;
2515 if (cchLine > 0)
2516 {
2517 /* Quoted or bracketed? */
2518 char const chFirst = *pchLine;
2519 if (chFirst == '"' || chFirst == '<')
2520 {
2521 cchLine--;
2522 pchLine++;
2523 const char *pchEnd = (const char *)memchr(pchLine, chFirst == '"' ? '"' : '>', cchLine);
2524 if (pchEnd)
2525 {
2526 if (ppchFilename)
2527 *ppchFilename = pchLine;
2528 if (pcchFilename)
2529 *pcchFilename = pchEnd - pchLine;
2530 return chFirst == '"' ? kScmIncludeDir_Quoted : kScmIncludeDir_Bracketed;
2531 }
2532 ScmError(pState, VERR_PARSE_ERROR, "Unbalanced #include filename %s: %.*s\n",
2533 chFirst == '"' ? "quotes" : "brackets" , cchLine, pchLine);
2534 }
2535 /* C prepreprocessor macro? */
2536 else if (ScmIsCIdentifierLeadChar(chFirst))
2537 {
2538 size_t cchFilename = 1;
2539 while ( cchFilename < cchLine
2540 && ScmIsCIdentifierChar(pchLine[cchFilename]))
2541 cchFilename++;
2542 if (ppchFilename)
2543 *ppchFilename = pchLine;
2544 if (pcchFilename)
2545 *pcchFilename = cchFilename;
2546 return kScmIncludeDir_Macro;
2547 }
2548 else
2549 ScmError(pState, VERR_PARSE_ERROR, "Malformed #include filename part: %.*s\n", cchLine, pchLine);
2550 }
2551 else
2552 ScmError(pState, VERR_PARSE_ERROR, "Missing #include filename!\n");
2553 }
2554 }
2555
2556 if (ppchFilename)
2557 *ppchFilename = NULL;
2558 if (pcchFilename)
2559 *pcchFilename = 0;
2560 return kScmIncludeDir_Invalid;
2561}
2562
2563
2564/**
2565 * Fix err.h/errcore.h usage.
2566 *
2567 * @returns true if modifications were made, false if not.
2568 * @param pIn The input stream.
2569 * @param pOut The output stream.
2570 * @param pSettings The settings.
2571 */
2572bool rewrite_Fix_Err_H(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2573{
2574 if (!pSettings->fFixErrH)
2575 return false;
2576
2577 static struct
2578 {
2579 const char *pszHeader;
2580 unsigned cchHeader;
2581 int iLevel;
2582 } const s_aHeaders[] =
2583 {
2584 { RT_STR_TUPLE("iprt/errcore.h"), 1 },
2585 { RT_STR_TUPLE("iprt/err.h"), 2 },
2586 { RT_STR_TUPLE("VBox/err.h"), 3 },
2587 };
2588 static RTSTRTUPLE const g_aLevel1Statuses[] = /* Note! Keep in sync with errcore.h content! */
2589 {
2590 { RT_STR_TUPLE("VINF_SUCCESS") },
2591 { RT_STR_TUPLE("VERR_GENERAL_FAILURE") },
2592 { RT_STR_TUPLE("VERR_INVALID_PARAMETER") },
2593 { RT_STR_TUPLE("VWRN_INVALID_PARAMETER") },
2594 { RT_STR_TUPLE("VERR_INVALID_MAGIC") },
2595 { RT_STR_TUPLE("VWRN_INVALID_MAGIC") },
2596 { RT_STR_TUPLE("VERR_INVALID_HANDLE") },
2597 { RT_STR_TUPLE("VWRN_INVALID_HANDLE") },
2598 { RT_STR_TUPLE("VERR_INVALID_POINTER") },
2599 { RT_STR_TUPLE("VERR_NO_MEMORY") },
2600 { RT_STR_TUPLE("VERR_PERMISSION_DENIED") },
2601 { RT_STR_TUPLE("VINF_PERMISSION_DENIED") },
2602 { RT_STR_TUPLE("VERR_VERSION_MISMATCH") },
2603 { RT_STR_TUPLE("VERR_NOT_IMPLEMENTED") },
2604 { RT_STR_TUPLE("VERR_INVALID_FLAGS") },
2605 { RT_STR_TUPLE("VERR_WRONG_ORDER") },
2606 { RT_STR_TUPLE("VERR_INVALID_FUNCTION") },
2607 { RT_STR_TUPLE("VERR_NOT_SUPPORTED") },
2608 { RT_STR_TUPLE("VINF_NOT_SUPPORTED") },
2609 { RT_STR_TUPLE("VERR_ACCESS_DENIED") },
2610 { RT_STR_TUPLE("VERR_INTERRUPTED") },
2611 { RT_STR_TUPLE("VINF_INTERRUPTED") },
2612 { RT_STR_TUPLE("VERR_TIMEOUT") },
2613 { RT_STR_TUPLE("VINF_TIMEOUT") },
2614 { RT_STR_TUPLE("VERR_BUFFER_OVERFLOW") },
2615 { RT_STR_TUPLE("VINF_BUFFER_OVERFLOW") },
2616 { RT_STR_TUPLE("VERR_TOO_MUCH_DATA") },
2617 { RT_STR_TUPLE("VERR_TRY_AGAIN") },
2618 { RT_STR_TUPLE("VINF_TRY_AGAIN") },
2619 { RT_STR_TUPLE("VERR_PARSE_ERROR") },
2620 { RT_STR_TUPLE("VERR_OUT_OF_RANGE") },
2621 { RT_STR_TUPLE("VERR_NUMBER_TOO_BIG") },
2622 { RT_STR_TUPLE("VWRN_NUMBER_TOO_BIG") },
2623 { RT_STR_TUPLE("VERR_CANCELLED") },
2624 { RT_STR_TUPLE("VERR_TRAILING_CHARS") },
2625 { RT_STR_TUPLE("VWRN_TRAILING_CHARS") },
2626 { RT_STR_TUPLE("VERR_TRAILING_SPACES") },
2627 { RT_STR_TUPLE("VWRN_TRAILING_SPACES") },
2628 { RT_STR_TUPLE("VERR_NOT_FOUND") },
2629 { RT_STR_TUPLE("VWRN_NOT_FOUND") },
2630 { RT_STR_TUPLE("VERR_INVALID_STATE") },
2631 { RT_STR_TUPLE("VWRN_INVALID_STATE") },
2632 { RT_STR_TUPLE("VERR_OUT_OF_RESOURCES") },
2633 { RT_STR_TUPLE("VWRN_OUT_OF_RESOURCES") },
2634 { RT_STR_TUPLE("VERR_END_OF_STRING") },
2635 { RT_STR_TUPLE("VERR_CALLBACK_RETURN") },
2636 { RT_STR_TUPLE("VINF_CALLBACK_RETURN") },
2637 { RT_STR_TUPLE("VERR_DUPLICATE") },
2638 { RT_STR_TUPLE("VERR_MISSING") },
2639 { RT_STR_TUPLE("VERR_BUFFER_UNDERFLOW") },
2640 { RT_STR_TUPLE("VINF_BUFFER_UNDERFLOW") },
2641 { RT_STR_TUPLE("VERR_NOT_AVAILABLE") },
2642 { RT_STR_TUPLE("VERR_MISMATCH") },
2643 { RT_STR_TUPLE("VERR_WRONG_TYPE") },
2644 { RT_STR_TUPLE("VWRN_WRONG_TYPE") },
2645 { RT_STR_TUPLE("VERR_WRONG_PARAMETER_COUNT") },
2646 { RT_STR_TUPLE("VERR_WRONG_PARAMETER_TYPE") },
2647 { RT_STR_TUPLE("VERR_INVALID_CLIENT_ID") },
2648 { RT_STR_TUPLE("VERR_INVALID_SESSION_ID") },
2649 { RT_STR_TUPLE("VERR_INCOMPATIBLE_CONFIG") },
2650 { RT_STR_TUPLE("VERR_INTERNAL_ERROR") },
2651 { RT_STR_TUPLE("VINF_GETOPT_NOT_OPTION") },
2652 { RT_STR_TUPLE("VERR_GETOPT_UNKNOWN_OPTION") },
2653 };
2654
2655 /*
2656 * First pass: Scout #include err.h/errcore.h locations and usage.
2657 *
2658 * Note! This isn't entirely optimal since it's also parsing comments and
2659 * strings, not just code. However it does a decent job for now.
2660 */
2661 int iIncludeLevel = 0;
2662 int iUsageLevel = 0;
2663 uint32_t iLine = 0;
2664 SCMEOL enmEol;
2665 size_t cchLine;
2666 const char *pchLine;
2667 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
2668 {
2669 iLine++;
2670 if (cchLine < 6)
2671 continue;
2672
2673 /*
2674 * Look for #includes.
2675 */
2676 const char *pchHash = (const char *)memchr(pchLine, '#', cchLine);
2677 if ( pchHash
2678 && isSpanOfBlanks(pchLine, pchHash - pchLine))
2679 {
2680 const char *pchFilename;
2681 size_t cchFilename;
2682 SCMINCLUDEDIR enmIncDir = ScmMaybeParseCIncludeLine(pState, pchLine, cchLine, &pchFilename, &cchFilename);
2683 if ( enmIncDir == kScmIncludeDir_Bracketed
2684 || enmIncDir == kScmIncludeDir_Quoted)
2685 {
2686 unsigned i = RT_ELEMENTS(s_aHeaders);
2687 while (i-- > 0)
2688 if ( s_aHeaders[i].cchHeader == cchFilename
2689 && RTStrNICmpAscii(pchFilename, s_aHeaders[i].pszHeader, cchFilename) == 0)
2690 {
2691 if (iIncludeLevel < s_aHeaders[i].iLevel)
2692 iIncludeLevel = s_aHeaders[i].iLevel;
2693 break;
2694 }
2695
2696 /* Special hack for error info. */
2697 if (cchFilename == sizeof("errmsgdata.h") - 1 && memcmp(pchFilename, RT_STR_TUPLE("errmsgdata.h")) == 0)
2698 iUsageLevel = 4;
2699
2700 /* Special hack for code templates. */
2701 if ( cchFilename >= sizeof(".cpp.h")
2702 && memcmp(&pchFilename[cchFilename - sizeof(".cpp.h") + 1], RT_STR_TUPLE(".cpp.h")) == 0)
2703 iUsageLevel = 4;
2704 continue;
2705 }
2706 }
2707 /*
2708 * Look for VERR_, VWRN_, VINF_ prefixed identifiers in the current line.
2709 */
2710 const char *pchHit = (const char *)memchr(pchLine, 'V', cchLine);
2711 if (pchHit)
2712 {
2713 const char *pchLeft = pchLine;
2714 size_t cchLeft = cchLine;
2715 do
2716 {
2717 size_t cchLeftHit = &pchLeft[cchLeft] - pchHit;
2718 if (cchLeftHit < 6)
2719 break;
2720 if ( pchHit[4] == '_'
2721 && ( pchHit == pchLine
2722 || !ScmIsCIdentifierChar(pchHit[-1]))
2723 && ( (pchHit[1] == 'E' && pchHit[2] == 'R' && pchHit[3] == 'R')
2724 || (pchHit[1] == 'W' && pchHit[2] == 'R' && pchHit[3] == 'N')
2725 || (pchHit[1] == 'I' && pchHit[2] == 'N' && pchHit[3] == 'F') ) )
2726 {
2727 size_t cchIdentifier = 5;
2728 while (cchIdentifier < cchLeftHit && ScmIsCIdentifierChar(pchHit[cchIdentifier]))
2729 cchIdentifier++;
2730 ScmVerbose(pState, 4, "--- status code at %u col %zu: %.*s\n",
2731 iLine, pchHit - pchLine, cchIdentifier, pchHit);
2732
2733 if (iUsageLevel <= 1)
2734 {
2735 iUsageLevel = 3; /* Cannot distingish between iprt/err.h and VBox/err.h, so pick the latter for now. */
2736 for (unsigned i = 0; i < RT_ELEMENTS(g_aLevel1Statuses); i++)
2737 if ( cchIdentifier == g_aLevel1Statuses[i].cch
2738 && memcmp(pchHit, g_aLevel1Statuses[i].psz, cchIdentifier) == 0)
2739 {
2740 iUsageLevel = 1;
2741 break;
2742 }
2743 }
2744
2745 pchLeft = pchHit + cchIdentifier;
2746 cchLeft = cchLeftHit - cchIdentifier;
2747 }
2748 else
2749 {
2750 pchLeft = pchHit + 1;
2751 cchLeft = cchLeftHit - 1;
2752 }
2753 pchHit = (const char *)memchr(pchLeft, 'V', cchLeft);
2754 } while (pchHit != NULL);
2755 }
2756 }
2757 ScmVerbose(pState, 3, "--- iIncludeLevel=%d iUsageLevel=%d\n", iIncludeLevel, iUsageLevel);
2758
2759 /*
2760 * Second pass: Change err.h to errcore.h if we detected a need for change.
2761 */
2762 if ( iIncludeLevel <= iUsageLevel
2763 || iIncludeLevel <= 1 /* we cannot safely eliminate errcore.h includes atm. */)
2764 return false;
2765
2766 unsigned cChanges = 0;
2767 ScmStreamRewindForReading(pIn);
2768 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
2769 {
2770 /*
2771 * Look for #includes to modify.
2772 */
2773 if (cchLine >= 6)
2774 {
2775 const char *pchHash = (const char *)memchr(pchLine, '#', cchLine);
2776 if ( pchHash
2777 && isSpanOfBlanks(pchLine, pchHash - pchLine))
2778 {
2779 const char *pchFilename;
2780 size_t cchFilename;
2781 SCMINCLUDEDIR enmIncDir = ScmMaybeParseCIncludeLine(pState, pchLine, cchLine, &pchFilename, &cchFilename);
2782 if ( enmIncDir == kScmIncludeDir_Bracketed
2783 || enmIncDir == kScmIncludeDir_Quoted)
2784 {
2785 unsigned i = RT_ELEMENTS(s_aHeaders);
2786 while (i-- > 0)
2787 if ( s_aHeaders[i].cchHeader == cchFilename
2788 && RTStrNICmpAscii(pchFilename, s_aHeaders[i].pszHeader, cchFilename) == 0)
2789 {
2790 ScmStreamWrite(pOut, pchLine, pchFilename - pchLine - 1);
2791 ScmStreamWrite(pOut, RT_STR_TUPLE("<iprt/errcore.h>"));
2792 size_t cchTrailing = &pchLine[cchLine] - &pchFilename[cchFilename + 1];
2793 if (cchTrailing > 0)
2794 ScmStreamWrite(pOut, &pchFilename[cchFilename + 1], cchTrailing);
2795 ScmStreamPutEol(pOut, enmEol);
2796 cChanges++;
2797 pchLine = NULL;
2798 break;
2799 }
2800 if (!pchLine)
2801 continue;
2802 }
2803 }
2804 }
2805
2806 int rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
2807 if (RT_FAILURE(rc))
2808 return false;
2809 }
2810 ScmVerbose(pState, 2, " * Converted %zu err.h/errcore.h include statements.\n", cChanges);
2811 return true;
2812}
2813
2814typedef struct
2815{
2816 const char *pch;
2817 uint8_t cch;
2818 uint8_t cchSpaces; /**< Number of expected spaces before the word. */
2819 bool fSpacesBefore : 1; /**< Whether there may be spaces or tabs before the word. */
2820 bool fIdentifier : 1; /**< Whether we're to expect a C/C++ identifier rather than pch/cch. */
2821} SCMMATCHWORD;
2822
2823
2824int ScmMatchWords(const char *pchLine, size_t cchLine, SCMMATCHWORD const *paWords, size_t cWords,
2825 size_t *poffNext, PRTSTRTUPLE paIdentifiers, PRTERRINFO pErrInfo)
2826{
2827 int rc = VINF_SUCCESS;
2828
2829 size_t offLine = 0;
2830 for (size_t i = 0; i < cWords; i++)
2831 {
2832 SCMMATCHWORD const *pWord = &paWords[i];
2833
2834 /*
2835 * Deal with spaces preceeding the word first:
2836 */
2837 if (pWord->fSpacesBefore)
2838 {
2839 size_t cchSpaces = 0;
2840 size_t cchTabs = 0;
2841 while (offLine < cchLine)
2842 {
2843 const char ch = pchLine[offLine];
2844 if (ch == ' ')
2845 cchSpaces++;
2846 else if (ch == '\t')
2847 cchTabs++;
2848 else
2849 break;
2850 offLine++;
2851 }
2852
2853 if (cchSpaces == pWord->cchSpaces && cchTabs == 0)
2854 { /* likely */ }
2855 else if (cchSpaces == 0 && cchTabs == 0)
2856 return RTErrInfoSetF(pErrInfo, VERR_PARSE_ERROR, "expected space at offset %u", offLine);
2857 else
2858 rc = VWRN_TRAILING_SPACES;
2859 }
2860 else
2861 Assert(pWord->cchSpaces == 0);
2862
2863 /*
2864 * C/C++ identifier?
2865 */
2866 if (pWord->fIdentifier)
2867 {
2868 if (offLine >= cchLine)
2869 return RTErrInfoSetF(pErrInfo, VERR_END_OF_STRING,
2870 "expected '%.*s' (C/C++ identifier) at offset %u, not end of string",
2871 pWord->cch, pWord->pch, offLine);
2872 if (!ScmIsCIdentifierLeadChar(pchLine[offLine]))
2873 return RTErrInfoSetF(pErrInfo, VERR_MISMATCH, "expected '%.*s' (C/C++ identifier) at offset %u",
2874 pWord->cch, pWord->pch, offLine);
2875 size_t const offStart = offLine++;
2876 while (offLine < cchLine && ScmIsCIdentifierChar(pchLine[offLine]))
2877 offLine++;
2878 if (paIdentifiers)
2879 {
2880 paIdentifiers->cch = offLine - offStart;
2881 paIdentifiers->psz = &pchLine[offStart];
2882 paIdentifiers++;
2883 }
2884 }
2885 /*
2886 * Match the exact word.
2887 */
2888 else if ( pWord->cch == 0
2889 || ( pWord->cch <= cchLine - offLine
2890 && !memcmp(pWord->pch, &pchLine[offLine], pWord->cch)))
2891 offLine += pWord->cch;
2892 else
2893 return RTErrInfoSetF(pErrInfo, VERR_MISMATCH, "expected '%.*s' at offset %u", pWord->cch, pWord->pch, offLine);
2894 }
2895
2896 /*
2897 * Check for trailing characters/whatnot.
2898 */
2899 if (poffNext)
2900 *poffNext = offLine;
2901 else if (offLine != cchLine)
2902 rc = RTErrInfoSetF(pErrInfo, VERR_TRAILING_CHARS, "unexpected trailing characters at offset %u", offLine);
2903 return rc;
2904}
2905
2906
2907/**
2908 * Fix header file include guards and \#pragma once.
2909 *
2910 * @returns true if modifications were made, false if not.
2911 * @param pIn The input stream.
2912 * @param pOut The output stream.
2913 * @param pSettings The settings.
2914 */
2915bool rewrite_FixHeaderGuards(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2916{
2917 if (!pSettings->fFixHeaderGuards)
2918 return false;
2919
2920 /* always skip .cpp.h files */
2921 size_t cchFilename = strlen(pState->pszFilename);
2922 if ( cchFilename > sizeof(".cpp.h")
2923 && RTStrICmpAscii(&pState->pszFilename[cchFilename - sizeof(".cpp.h") + 1], ".cpp.h") == 0)
2924 return false;
2925
2926 RTERRINFOSTATIC ErrInfo;
2927 char szNormalized[168];
2928 size_t cchNormalized = 0;
2929 int rc;
2930 bool fRet = false;
2931
2932 /*
2933 * Calculate the expected guard for this file, if so tasked.
2934 * ASSUMES pState->pszFilename is absolute as is pSettings->pszGuardRelativeToDir.
2935 */
2936 szNormalized[0] = '\0';
2937 if (pSettings->pszGuardRelativeToDir)
2938 {
2939 rc = RTStrCopy(szNormalized, sizeof(szNormalized), pSettings->pszGuardPrefix);
2940 if (RT_FAILURE(rc))
2941 return ScmError(pState, rc, "Guard prefix too long (or something): %s\n", pSettings->pszGuardPrefix);
2942 cchNormalized = strlen(szNormalized);
2943 if (strcmp(pSettings->pszGuardRelativeToDir, "{dir}") == 0)
2944 rc = RTStrCopy(&szNormalized[cchNormalized], sizeof(szNormalized) - cchNormalized,
2945 RTPathFilename(pState->pszFilename));
2946 else if (strcmp(pSettings->pszGuardRelativeToDir, "{parent}") == 0)
2947 {
2948 const char *pszSrc = RTPathFilename(pState->pszFilename);
2949 if (!pszSrc || (uintptr_t)&pszSrc[-2] < (uintptr_t)pState->pszFilename || !RTPATH_IS_SLASH(pszSrc[-1]))
2950 return ScmError(pState, VERR_INTERNAL_ERROR, "Error calculating {parent} header guard!\n");
2951 pszSrc -= 2;
2952 while ( (uintptr_t)pszSrc > (uintptr_t)pState->pszFilename
2953 && !RTPATH_IS_SLASH(pszSrc[-1])
2954 && !RTPATH_IS_VOLSEP(pszSrc[-1]))
2955 pszSrc--;
2956 rc = RTStrCopy(&szNormalized[cchNormalized], sizeof(szNormalized) - cchNormalized, pszSrc);
2957 }
2958 else
2959 rc = RTPathCalcRelative(&szNormalized[cchNormalized], sizeof(szNormalized) - cchNormalized,
2960 pSettings->pszGuardRelativeToDir, false /*fFromFile*/, pState->pszFilename);
2961 if (RT_FAILURE(rc))
2962 return ScmError(pState, rc, "Error calculating guard prefix (RTPathCalcRelative): %Rrc\n", rc);
2963 char ch;
2964 while ((ch = szNormalized[cchNormalized]) != '\0')
2965 {
2966 if (!ScmIsCIdentifierChar(ch))
2967 szNormalized[cchNormalized] = '_';
2968 cchNormalized++;
2969 }
2970 }
2971
2972 /*
2973 * First part looks for the #ifndef xxxx paired with #define xxxx.
2974 *
2975 * We blindly assume the first preprocessor directive in the file is the guard
2976 * and will be upset if this isn't the case.
2977 */
2978 RTSTRTUPLE Guard = { NULL, 0 };
2979 uint32_t cBlankLines = 0;
2980 SCMEOL enmEol;
2981 size_t cchLine;
2982 const char *pchLine;
2983 for (;;)
2984 {
2985 pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
2986 if (pchLine == NULL)
2987 return ScmError(pState, VERR_PARSE_ERROR, "Did not find any include guards!\n");
2988 if (cchLine >= 2)
2989 {
2990 const char *pchHash = (const char *)memchr(pchLine, '#', cchLine);
2991 if ( pchHash
2992 && isSpanOfBlanks(pchLine, pchHash - pchLine))
2993 {
2994 /* #ifndef xxxx */
2995 static const SCMMATCHWORD s_aIfndefGuard[] =
2996 {
2997 { RT_STR_TUPLE("#"), 0, true, false },
2998 { RT_STR_TUPLE("ifndef"), 0, true, false },
2999 { RT_STR_TUPLE("IDENTIFIER"), 1, true, true },
3000 { RT_STR_TUPLE(""), 0, true, false },
3001 };
3002 rc = ScmMatchWords(pchLine, cchLine, s_aIfndefGuard, RT_ELEMENTS(s_aIfndefGuard),
3003 NULL /*poffNext*/, &Guard, RTErrInfoInitStatic(&ErrInfo));
3004 if (RT_FAILURE(rc))
3005 return ScmError(pState, rc, "%u: Expected first preprocessor directive to be '#ifndef xxxx'. %s (%.*s)\n",
3006 ScmStreamTellLine(pIn) - 1, ErrInfo.Core.pszMsg, cchLine, pchLine);
3007 fRet |= rc != VINF_SUCCESS;
3008 ScmVerbose(pState, 3, "line %u in %s: #ifndef %.*s\n",
3009 ScmStreamTellLine(pIn) - 1, pState->pszFilename, Guard.cch, Guard.psz);
3010
3011 /* #define xxxx */
3012 pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
3013 if (!pchLine)
3014 return ScmError(pState, VERR_PARSE_ERROR, "%u: Unexpected end of file after '#ifndef %.*s'\n",
3015 ScmStreamTellLine(pIn) - 1, Guard.cch, Guard.psz);
3016 const SCMMATCHWORD aDefineGuard[] =
3017 {
3018 { RT_STR_TUPLE("#"), 0, true, false },
3019 { RT_STR_TUPLE("define"), 0, true, false },
3020 { Guard.psz, (uint8_t)Guard.cch, 1, true, false },
3021 { RT_STR_TUPLE(""), 0, true, false },
3022 };
3023 rc = ScmMatchWords(pchLine, cchLine, aDefineGuard, RT_ELEMENTS(aDefineGuard),
3024 NULL /*poffNext*/, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
3025 if (RT_FAILURE(rc))
3026 return ScmError(pState, rc, "%u: Expected '#define %.*s' to follow '#ifndef %.*s'. %s (%.*s)\n",
3027 ScmStreamTellLine(pIn) - 1, Guard.cch, Guard.psz, Guard.cch, Guard.psz,
3028 ErrInfo.Core.pszMsg, cchLine, pchLine);
3029 fRet |= rc != VINF_SUCCESS;
3030
3031 if (Guard.cch >= sizeof(szNormalized))
3032 return ScmError(pState, VERR_BUFFER_OVERFLOW, "%u: Guard macro too long! %.*s\n",
3033 ScmStreamTellLine(pIn) - 2, Guard.cch, Guard.psz);
3034
3035 if (szNormalized[0] != '\0')
3036 {
3037 if ( Guard.cch != cchNormalized
3038 || memcmp(Guard.psz, szNormalized, cchNormalized) != 0)
3039 {
3040 ScmVerbose(pState, 2, "guard changed from %.*s to %s\n", Guard.cch, Guard.psz, szNormalized);
3041 ScmVerbose(pState, 2, "grep -rw %.*s ${WCROOT} | grep -Fv %s\n",
3042 Guard.cch, Guard.psz, pState->pszFilename);
3043 fRet = true;
3044 }
3045 Guard.psz = szNormalized;
3046 Guard.cch = cchNormalized;
3047 }
3048
3049 /*
3050 * Write guard, making sure we've got a single blank line preceeding it.
3051 */
3052 ScmStreamPutEol(pOut, enmEol);
3053 ScmStreamWrite(pOut, RT_STR_TUPLE("#ifndef "));
3054 ScmStreamWrite(pOut, Guard.psz, Guard.cch);
3055 ScmStreamPutEol(pOut, enmEol);
3056 ScmStreamWrite(pOut, RT_STR_TUPLE("#define "));
3057 ScmStreamWrite(pOut, Guard.psz, Guard.cch);
3058 rc = ScmStreamPutEol(pOut, enmEol);
3059 if (RT_FAILURE(rc))
3060 return false;
3061 break;
3062 }
3063 }
3064
3065 if (!isBlankLine(pchLine, cchLine))
3066 {
3067 while (cBlankLines-- > 0)
3068 ScmStreamPutEol(pOut, enmEol);
3069 cBlankLines = 0;
3070 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
3071 if (RT_FAILURE(rc))
3072 return false;
3073 }
3074 else
3075 cBlankLines++;
3076 }
3077
3078 /*
3079 * Look for pragma once wrapped in #ifndef RT_WITHOUT_PRAGMA_ONCE.
3080 */
3081 size_t const iPragmaOnce = ScmStreamTellLine(pIn);
3082 static const SCMMATCHWORD s_aIfndefRtWithoutPragmaOnce[] =
3083 {
3084 { RT_STR_TUPLE("#"), 0, true, false },
3085 { RT_STR_TUPLE("ifndef"), 0, true, false },
3086 { RT_STR_TUPLE("RT_WITHOUT_PRAGMA_ONCE"), 1, true, false },
3087 { RT_STR_TUPLE(""), 0, true, false },
3088 };
3089 static const SCMMATCHWORD s_aPragmaOnce[] =
3090 {
3091 { RT_STR_TUPLE("#"), 0, true, false },
3092 { RT_STR_TUPLE("pragma"), 1, true, false },
3093 { RT_STR_TUPLE("once"), 1, true, false},
3094 { RT_STR_TUPLE(""), 0, true, false },
3095 };
3096 static const SCMMATCHWORD s_aEndif[] =
3097 {
3098 { RT_STR_TUPLE("#"), 0, true, false },
3099 { RT_STR_TUPLE("endif"), 0, true, false },
3100 { RT_STR_TUPLE(""), 0, true, false },
3101 };
3102
3103 /* #ifndef RT_WITHOUT_PRAGMA_ONCE */
3104 pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
3105 if (!pchLine)
3106 return ScmError(pState, VERR_PARSE_ERROR, "%u: Unexpected end of file after header guard!\n", iPragmaOnce + 1);
3107 size_t offNext;
3108 rc = ScmMatchWords(pchLine, cchLine, s_aIfndefRtWithoutPragmaOnce, RT_ELEMENTS(s_aIfndefRtWithoutPragmaOnce),
3109 &offNext, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
3110 if (RT_SUCCESS(rc))
3111 {
3112 fRet |= rc != VINF_SUCCESS;
3113 if (offNext != cchLine)
3114 return ScmError(pState, VERR_PARSE_ERROR, "%u: Characters trailing '#ifndef RT_WITHOUT_PRAGMA_ONCE' (%.*s)\n",
3115 iPragmaOnce + 1, cchLine, pchLine);
3116
3117 /* # pragma once */
3118 pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
3119 if (!pchLine)
3120 return ScmError(pState, VERR_PARSE_ERROR, "%u: Unexpected end of file after '#ifndef RT_WITHOUT_PRAGMA_ONCE'\n",
3121 iPragmaOnce + 2);
3122 rc = ScmMatchWords(pchLine, cchLine, s_aPragmaOnce, RT_ELEMENTS(s_aPragmaOnce),
3123 NULL /*poffNext*/, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
3124 if (RT_SUCCESS(rc))
3125 fRet |= rc != VINF_SUCCESS;
3126 else
3127 return ScmError(pState, rc, "%u: Expected '# pragma once' to follow '#ifndef RT_WITHOUT_PRAGMA_ONCE'! %s (%.*s)\n",
3128 iPragmaOnce + 2, ErrInfo.Core.pszMsg, cchLine, pchLine);
3129
3130 /* #endif */
3131 pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
3132 if (!pchLine)
3133 return ScmError(pState, VERR_PARSE_ERROR, "%u: Unexpected end of file after '#ifndef RT_WITHOUT_PRAGMA_ONCE' and '#pragma once'\n",
3134 iPragmaOnce + 3);
3135 rc = ScmMatchWords(pchLine, cchLine, s_aEndif, RT_ELEMENTS(s_aEndif),
3136 NULL /*poffNext*/, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
3137 if (RT_SUCCESS(rc))
3138 fRet |= rc != VINF_SUCCESS;
3139 else
3140 return ScmError(pState, rc,
3141 "%u: Expected '#endif' to follow '#ifndef RT_WITHOUT_PRAGMA_ONCE' and '# pragma once'! %s (%.*s)\n",
3142 iPragmaOnce + 3, ErrInfo.Core.pszMsg, cchLine, pchLine);
3143 ScmVerbose(pState, 3, "Found pragma once\n");
3144 fRet |= !pSettings->fPragmaOnce;
3145 }
3146 else
3147 {
3148 rc = ScmStreamSeekByLine(pIn, iPragmaOnce);
3149 if (RT_FAILURE(rc))
3150 return ScmError(pState, rc, "seek error\n");
3151 fRet |= pSettings->fPragmaOnce;
3152 ScmVerbose(pState, pSettings->fPragmaOnce ? 2 : 3, "Missing #pragma once\n");
3153 }
3154
3155 /*
3156 * Write the pragma once stuff.
3157 */
3158 if (pSettings->fPragmaOnce)
3159 {
3160 ScmStreamPutLine(pOut, RT_STR_TUPLE("#ifndef RT_WITHOUT_PRAGMA_ONCE"), enmEol);
3161 ScmStreamPutLine(pOut, RT_STR_TUPLE("# pragma once"), enmEol);
3162 rc = ScmStreamPutLine(pOut, RT_STR_TUPLE("#endif"), enmEol);
3163 if (RT_FAILURE(rc))
3164 return false;
3165 }
3166
3167 /*
3168 * Copy the rest of the file and remove pragma once statements, while
3169 * looking for the last #endif in the file.
3170 */
3171 size_t iEndIfIn = 0;
3172 size_t iEndIfOut = 0;
3173 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
3174 {
3175 if (cchLine > 2)
3176 {
3177 const char *pchHash = (const char *)memchr(pchLine, '#', cchLine);
3178 if ( pchHash
3179 && isSpanOfBlanks(pchLine, pchHash - pchLine))
3180 {
3181 size_t off = pchHash - pchLine + 1;
3182 while (off < cchLine && RT_C_IS_BLANK(pchLine[off]))
3183 off++;
3184 /* #pragma once */
3185 if ( off + sizeof("pragma") - 1 <= cchLine
3186 && !memcmp(&pchLine[off], RT_STR_TUPLE("pragma")))
3187 {
3188 rc = ScmMatchWords(pchLine, cchLine, s_aPragmaOnce, RT_ELEMENTS(s_aPragmaOnce),
3189 &offNext, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
3190 if (RT_SUCCESS(rc))
3191 {
3192 fRet = true;
3193 continue;
3194 }
3195 }
3196 /* #endif */
3197 else if ( off + sizeof("endif") - 1 <= cchLine
3198 && !memcmp(&pchLine[off], RT_STR_TUPLE("endif")))
3199 {
3200 iEndIfIn = ScmStreamTellLine(pIn) - 1;
3201 iEndIfOut = ScmStreamTellLine(pOut);
3202 }
3203 }
3204 }
3205
3206 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
3207 if (RT_FAILURE(rc))
3208 return false;
3209 }
3210
3211 /*
3212 * Check out the last endif, making sure it's well formed and make sure it has the
3213 * right kind of comment following it.
3214 */
3215 if (pSettings->fFixHeaderGuardEndif)
3216 {
3217 if (iEndIfOut == 0)
3218 return ScmError(pState, VERR_PARSE_ERROR, "Expected '#endif' at the end of the file...\n");
3219 rc = ScmStreamSeekByLine(pIn, iEndIfIn);
3220 if (RT_FAILURE(rc))
3221 return false;
3222 rc = ScmStreamSeekByLine(pOut, iEndIfOut);
3223 if (RT_FAILURE(rc))
3224 return false;
3225
3226 pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
3227 if (!pchLine)
3228 return ScmError(pState, VERR_INTERNAL_ERROR, "ScmStreamGetLine failed re-reading #endif!\n");
3229
3230 char szTmp[64 + sizeof(szNormalized)];
3231 size_t cchTmp;
3232 if (pSettings->fEndifGuardComment)
3233 cchTmp = RTStrPrintf(szTmp, sizeof(szTmp), "#endif /* !%.*s */", Guard.cch, Guard.psz);
3234 else
3235 cchTmp = RTStrPrintf(szTmp, sizeof(szTmp), "#endif"); /* lazy bird */
3236 fRet |= cchTmp != cchLine || memcmp(szTmp, pchLine, cchTmp) != 0;
3237 rc = ScmStreamPutLine(pOut, szTmp, cchTmp, enmEol);
3238 if (RT_FAILURE(rc))
3239 return false;
3240
3241 /* Copy out the remaining lines (assumes no #pragma once here). */
3242 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
3243 {
3244 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
3245 if (RT_FAILURE(rc))
3246 return false;
3247 }
3248 }
3249
3250 return fRet;
3251}
3252
3253
3254/**
3255 * Checks for PAGE_SIZE, PAGE_SHIFT and PAGE_OFFSET_MASK w/o a GUEST_ or HOST_
3256 * prefix as well as banning PAGE_BASE_HC_MASK, PAGE_BASE_GC_MASK and
3257 * PAGE_BASE_MASK.
3258 *
3259 * @returns true if modifications were made, false if not.
3260 * @param pIn The input stream.
3261 * @param pOut The output stream.
3262 * @param pSettings The settings.
3263 */
3264bool rewrite_PageChecks(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
3265{
3266 RT_NOREF(pOut);
3267 if (!pSettings->fOnlyGuestHostPage && !pSettings->fNoASMMemPageUse)
3268 return false;
3269
3270 static RTSTRTUPLE const g_aWords[] =
3271 {
3272 { RT_STR_TUPLE("PAGE_SIZE") },
3273 { RT_STR_TUPLE("PAGE_SHIFT") },
3274 { RT_STR_TUPLE("PAGE_OFFSET_MASK") },
3275 { RT_STR_TUPLE("PAGE_BASE_MASK") },
3276 { RT_STR_TUPLE("PAGE_BASE_GC_MASK") },
3277 { RT_STR_TUPLE("PAGE_BASE_HC_MASK") },
3278 { RT_STR_TUPLE("PAGE_ADDRESS") },
3279 { RT_STR_TUPLE("PHYS_PAGE_ADDRESS") },
3280 { RT_STR_TUPLE("ASMMemIsZeroPage") },
3281 { RT_STR_TUPLE("ASMMemZeroPage") },
3282 };
3283 size_t const iFirstWord = pSettings->fOnlyGuestHostPage ? 0 : 7;
3284 size_t const iEndWords = pSettings->fNoASMMemPageUse ? 9 : 7;
3285
3286 uint32_t iLine = 0;
3287 SCMEOL enmEol;
3288 size_t cchLine;
3289 const char *pchLine;
3290 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
3291 {
3292 iLine++;
3293 for (size_t i = iFirstWord; i < iEndWords; i++)
3294 {
3295 size_t const cchWord = g_aWords[i].cch;
3296 if (cchLine >= cchWord)
3297 {
3298 const char * const pszWord = g_aWords[i].psz;
3299 const char *pchHit = (const char *)memchr(pchLine, *pszWord, cchLine);
3300 while (pchHit)
3301 {
3302 size_t cchLeft = (uintptr_t)&pchLine[cchLine] - (uintptr_t)pchHit;
3303 if ( cchLeft >= cchWord
3304 && memcmp(pchHit, pszWord, cchWord) == 0
3305 && ( pchHit == pchLine
3306 || !ScmIsCIdentifierChar(pchHit[-1]))
3307 && ( cchLeft == cchWord
3308 || !ScmIsCIdentifierChar(pchHit[cchWord])) )
3309 {
3310 if (i < 3)
3311 ScmFixManually(pState, "%u:%zu: %s is not allow! Use GUEST_%s or HOST_%s instead.\n",
3312 iLine, pchHit - pchLine + 1, pszWord, pszWord, pszWord);
3313 else if (i < 7)
3314 ScmFixManually(pState, "%u:%zu: %s is not allow! Rewrite using GUEST/HOST_PAGE_OFFSET_MASK.\n",
3315 iLine, pchHit - pchLine + 1, pszWord);
3316 else
3317 ScmFixManually(pState, "%u:%zu: %s is not allow! Use %s with correct page size instead.\n",
3318 iLine, pchHit - pchLine + 1, pszWord, i == 3 ? "ASMMemIsZero" : "RT_BZERO");
3319 }
3320
3321 /* next */
3322 cchLeft -= 1;
3323 if (cchLeft < cchWord)
3324 break;
3325 pchHit = (const char *)memchr(pchHit + 1, *pszWord, cchLeft);
3326 }
3327 }
3328 }
3329 }
3330
3331 return false;
3332}
3333
3334
3335/**
3336 * Checks for usage of rc in code instead of vrc for IPRT status codes (int) and hrc for COM
3337 * status codes (HRESULT).
3338 *
3339 * @returns true if modifications were made, false if not.
3340 * @param pIn The input stream.
3341 * @param pOut The output stream.
3342 * @param pSettings The settings.
3343 *
3344 * @note Used in Main to avoid ambiguity when just using rc.
3345 */
3346bool rewrite_ForceHrcVrcInsteadOfRc(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
3347{
3348 RT_NOREF(pOut);
3349 if (!pSettings->fOnlyHrcVrcInsteadOfRc)
3350 return false;
3351
3352 static const SCMMATCHWORD s_aHresultVrc[] =
3353 {
3354 { RT_STR_TUPLE("HRESULT"), 0, true, false },
3355 { RT_STR_TUPLE("vrc"), 1, true, false }
3356 };
3357
3358 static const SCMMATCHWORD s_aIntHrc[] =
3359 {
3360 { RT_STR_TUPLE("int"), 0, true, false },
3361 { RT_STR_TUPLE("hrc"), 1, true, false }
3362 };
3363
3364 uint32_t iLine = 0;
3365 SCMEOL enmEol;
3366 size_t cchLine;
3367 const char *pchLine;
3368 RTERRINFOSTATIC ErrInfo;
3369 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
3370 {
3371 iLine++;
3372
3373 /* Look for forbidden declarations first. */
3374 size_t offNext = 0;
3375 int rc = ScmMatchWords(pchLine, cchLine, s_aHresultVrc, RT_ELEMENTS(s_aHresultVrc),
3376 &offNext, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
3377 if (RT_SUCCESS(rc))
3378 {
3379 ScmFixManually(pState, "%u:%zu: 'HRESULT vrc' is not allowed! Use 'HRESULT hrc' instead.\n",
3380 iLine, offNext);
3381 continue;
3382 }
3383
3384 rc = ScmMatchWords(pchLine, cchLine, s_aIntHrc, RT_ELEMENTS(s_aIntHrc),
3385 &offNext, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
3386 if (RT_SUCCESS(rc))
3387 {
3388 ScmFixManually(pState, "%u:%zu: 'int hrc' is not allowed! Use 'int vrc' instead.\n",
3389 iLine, offNext);
3390 continue;
3391 }
3392
3393#if 0 /* This is too broad and triggers on things we don't want to trigger on (like autoCaller.rc()). */
3394 const RTSTRTUPLE RcTuple = { RT_STR_TUPLE("rc") };
3395 size_t const cchWord = RcTuple.cch;
3396 if (cchLine >= cchWord)
3397 {
3398 const char *pchHit = (const char *)memchr(pchLine, *RcTuple.psz, cchLine);
3399 while (pchHit)
3400 {
3401 size_t cchLeft = (uintptr_t)&pchLine[cchLine] - (uintptr_t)pchHit;
3402 if ( cchLeft >= cchWord
3403 && memcmp(pchHit, RcTuple.psz, cchWord) == 0
3404 && ( pchHit == pchLine
3405 || !ScmIsCIdentifierChar(pchHit[-1]))
3406 && ( cchLeft == cchWord
3407 || !ScmIsCIdentifierChar(pchHit[cchWord])) )
3408 ScmFixManually(pState, "%u:%zu: %s is not allowed! Use hrc or vrc instead.\n",
3409 iLine, pchHit - pchLine + 1, RcTuple.psz);
3410
3411 /* next */
3412 cchLeft -= 1;
3413 if (cchLeft < cchWord)
3414 break;
3415 pchHit = (const char *)memchr(pchHit + 1, *RcTuple.psz, cchLeft);
3416 }
3417 }
3418#else
3419 /* Trigger on declarations of 'HRESULT rc' and 'int rc'. */
3420 static const SCMMATCHWORD s_aHresultRc[] =
3421 {
3422 { RT_STR_TUPLE("HRESULT"), 0, true, false },
3423 { RT_STR_TUPLE("rc"), 1, true, false }
3424 };
3425
3426 static const SCMMATCHWORD s_aIntRc[] =
3427 {
3428 { RT_STR_TUPLE("int"), 0, true, false },
3429 { RT_STR_TUPLE("rc"), 1, true, false }
3430 };
3431
3432 rc = ScmMatchWords(pchLine, cchLine, s_aHresultRc, RT_ELEMENTS(s_aHresultRc),
3433 &offNext, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
3434 if (RT_SUCCESS(rc))
3435 {
3436 ScmFixManually(pState, "%u:%zu: 'HRESULT rc' is not allowed! Use 'HRESULT hrc' instead.\n",
3437 iLine, offNext);
3438 continue;
3439 }
3440
3441 rc = ScmMatchWords(pchLine, cchLine, s_aIntRc, RT_ELEMENTS(s_aIntRc),
3442 &offNext, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
3443 if (RT_SUCCESS(rc))
3444 {
3445 ScmFixManually(pState, "%u:%zu: 'int rc' is not allowed! Use 'int vrc' instead.\n",
3446 iLine, offNext);
3447 continue;
3448 }
3449#endif
3450 }
3451
3452 return false;
3453}
3454
3455
3456/**
3457 * Rewrite a C/C++ source or header file.
3458 *
3459 * @returns true if modifications were made, false if not.
3460 * @param pIn The input stream.
3461 * @param pOut The output stream.
3462 * @param pSettings The settings.
3463 *
3464 * @todo
3465 *
3466 * Ideas for C/C++:
3467 * - space after if, while, for, switch
3468 * - spaces in for (i=0;i<x;i++)
3469 * - complex conditional, bird style.
3470 * - remove unnecessary parentheses.
3471 * - sort defined RT_OS_*|| and RT_ARCH
3472 * - sizeof without parenthesis.
3473 * - defined without parenthesis.
3474 * - trailing spaces.
3475 * - parameter indentation.
3476 * - space after comma.
3477 * - while (x--); -> multi line + comment.
3478 * - else statement;
3479 * - space between function and left parenthesis.
3480 * - TODO, XXX, @todo cleanup.
3481 * - Space before/after '*'.
3482 * - ensure new line at end of file.
3483 * - Indentation of precompiler statements (#ifdef, #defines).
3484 * - space between functions.
3485 * - string.h -> iprt/string.h, stdarg.h -> iprt/stdarg.h, etc.
3486 */
3487bool rewrite_C_and_CPP(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
3488{
3489
3490 RT_NOREF4(pState, pIn, pOut, pSettings);
3491 return false;
3492}
3493
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