VirtualBox

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

Last change on this file was 99775, checked in by vboxsync, 12 months ago

*: Mark functions as static if not used outside of a given compilation unit. Enables the compiler to optimize inlining, reduces the symbol tables, exposes unused functions and in some rare cases exposes mismtaches between function declarations and definitions, but most importantly reduces the number of parfait reports for the extern-function-no-forward-declaration category. This should not result in any functional changes, bugref:3409

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

© 2023 Oracle
ContactPrivacy policyTerms of Use