diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:17:27 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:17:27 +0000 |
commit | f215e02bf85f68d3a6106c2a1f4f7f063f819064 (patch) | |
tree | 6bb5b92c046312c4e95ac2620b10ddf482d3fa8b /src/bldprogs | |
parent | Initial commit. (diff) | |
download | virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.tar.xz virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.zip |
Adding upstream version 7.0.14-dfsg.upstream/7.0.14-dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
34 files changed, 29621 insertions, 0 deletions
diff --git a/src/bldprogs/Makefile.kmk b/src/bldprogs/Makefile.kmk new file mode 100644 index 00000000..37f5ad31 --- /dev/null +++ b/src/bldprogs/Makefile.kmk @@ -0,0 +1,119 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for various generic build tools (there is currently only one of them). +# + +# +# Copyright (C) 2006-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# SPDX-License-Identifier: GPL-3.0-only +# + +SUB_DEPTH = ../.. +include $(KBUILD_PATH)/subheader.kmk + +BLDPROGS += bin2c +ifn1of ($(KBUILD_TARGET).$(KBUILD_TARGET_ARCH), solaris.sparc64) + BLDPROGS += biossums filesplitter genalias VBoxCmp +endif + +bin2c_TEMPLATE = VBoxBldProg +bin2c_SOURCES = \ + bin2c.c + +biossums_TEMPLATE = VBoxBldProg +biossums_SOURCES = biossums.c + +filesplitter_TEMPLATE = VBoxBldProg +filesplitter_SOURCES = filesplitter.cpp + +genalias_TEMPLATE = VBoxBldProg +genalias_SOURCES = genalias.cpp + +VBoxCmp_TEMPLATE = VBoxBldProg +VBoxCmp_SOURCES = VBoxCmp.cpp + +ifndef VBOX_ONLY_BUILD + PROGRAMS += scm + scm_TEMPLATE = VBoxR3Tool + scm_SOURCES = \ + scm.cpp \ + scmdiff.cpp \ + scmrw.cpp \ + scmrw-kmk.cpp \ + scmparser.cpp \ + scmstream.cpp \ + scmsubversion.cpp + ifdef VBOX_PATH_SUBVERSION_INCS + scm_INCS += $(VBOX_PATH_SUBVERSION_INCS) $(VBOX_PATH_APACHE_RUNTIME_INCS) + scm_DEFS += SCM_WITH_SVN_HEADERS + endif + + BLDPROGS += VBoxCPP + VBoxCPP_TEMPLATE = VBoxAdvBldProg + VBoxCPP_SOURCES = \ + VBoxCPP.cpp \ + scmstream.cpp +endif + +if !defined(VBOX_ONLY_BUILD) || defined(VBOX_ONLY_EXTPACKS) + BLDPROGS += VBoxTpG + VBoxTpG_TEMPLATE = VBoxAdvBldProg + VBoxTpG_SOURCES = \ + VBoxTpG.cpp \ + scmstream.cpp +endif + +ifeq ($(KBUILD_TARGET),win) + BLDPROGS += VBoxPeSetVersion +endif +VBoxPeSetVersion_TEMPLATE = VBoxBldProg +VBoxPeSetVersion_SOURCES = VBoxPeSetVersion.cpp + +BLDPROGS.win += VBoxCheckImports +VBoxCheckImports_TEMPLATE = VBoxBldProg +VBoxCheckImports_SOURCES = VBoxCheckImports.cpp + +ifneq ($(KBUILD_TARGET).$(KBUILD_TARGET_ARCH),solaris.sparc64) + BLDPROGS += VBoxDef2LazyLoad +endif +VBoxDef2LazyLoad_TEMPLATE = VBoxBldProg +VBoxDef2LazyLoad_SOURCES = VBoxDef2LazyLoad.cpp + +ifeq ($(KBUILD_TARGET),win) + BLDPROGS += VBoxEditCoffLib +endif +VBoxEditCoffLib_TEMPLATE = VBoxBldProg +VBoxEditCoffLib_SOURCES = VBoxEditCoffLib.cpp + +# temp hack. +VBoxCompilerPlugInsGcc.o VBoxCompilerPlugInsCommon.o VBoxCompilerPlugIns.o gccplugin: gccplugin$(SUFF_DLL) +gccplugin$(SUFF_DLL): VBoxCompilerPlugInsGcc.cpp VBoxCompilerPlugInsCommon.cpp VBoxCompilerPlugIns.h + $(TOOL_GXX3_CXX) -shared -fPIC -fno-rtti -g \ + -DIN_RING3 \ + $(if-expr "$(KBUILD_TYPE)" != "release",-DDEBUG,) \ + -I$(shell $(TOOL_GXX3_CXX) -print-file-name=plugin)/include \ + -I$(PATH_ROOT)/include \ + $(if-expr "$(KBUILD_HOST)" == "solaris", -I/usr/include/gmp -I$(PATH_ROOT)/src/bldprogs/solgcc/,) \ + -o $@ \ + VBoxCompilerPlugInsGcc.cpp \ + VBoxCompilerPlugInsCommon.cpp + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/bldprogs/VBoxCPP.cpp b/src/bldprogs/VBoxCPP.cpp new file mode 100644 index 00000000..eb5a7d44 --- /dev/null +++ b/src/bldprogs/VBoxCPP.cpp @@ -0,0 +1,5542 @@ +/* $Id: VBoxCPP.cpp $ */ +/** @file + * VBox Build Tool - A mini C Preprocessor. + * + * Purposes to which this preprocessor will be put: + * - Preprocessig vm.h into dtrace/lib/vm.d so we can access the VM + * structure (as well as substructures) from DTrace without having + * to handcraft it all. + * - Removing \#ifdefs relating to a new feature that has become + * stable and no longer needs \#ifdef'ing. + * - Pretty printing preprocessor directives. This will be used by + * SCM. + */ + +/* + * Copyright (C) 2012-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <VBox/VBoxTpG.h> + +#include <iprt/alloca.h> +#include <iprt/assert.h> +#include <iprt/asm.h> +#include <iprt/ctype.h> +#include <iprt/err.h> +#include <iprt/file.h> +#include <iprt/getopt.h> +#include <iprt/initterm.h> +#include <iprt/list.h> +#include <iprt/mem.h> +#include <iprt/message.h> +#include <iprt/path.h> +#include <iprt/stream.h> +#include <iprt/string.h> + +#include "scmstream.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The bitmap type. */ +#define VBCPP_BITMAP_TYPE uint64_t +/** The bitmap size as a multiple of VBCPP_BITMAP_TYPE. */ +#define VBCPP_BITMAP_SIZE (128 / 64) +/** Checks if a bit is set. */ +#define VBCPP_BITMAP_IS_SET(a_bm, a_ch) ASMBitTest(a_bm, (a_ch) & 0x7f) +/** Sets a bit. */ +#define VBCPP_BITMAP_SET(a_bm, a_ch) ASMBitSet(a_bm, (a_ch) & 0x7f) +/** Empties the bitmap. */ +#define VBCPP_BITMAP_EMPTY(a_bm) do { (a_bm)[0] = 0; (a_bm)[1] = 0; } while (0) +/** Joins to bitmaps by OR'ing their values.. */ +#define VBCPP_BITMAP_OR(a_bm1, a_bm2) do { (a_bm1)[0] |= (a_bm2)[0]; (a_bm1)[1] |= (a_bm2)[1]; } while (0) + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** Pointer to the C preprocessor instance data. */ +typedef struct VBCPP *PVBCPP; + + +/** + * Variable string buffer (very simple version of SCMSTREAM). + */ +typedef struct VBCPPSTRBUF +{ + /** The preprocessor instance (for error reporting). */ + struct VBCPP *pThis; + /** The length of the string in the buffer. */ + size_t cchBuf; + /** The string storage. */ + char *pszBuf; + /** Allocated buffer space. */ + size_t cbBufAllocated; +} VBCPPSTRBUF; +/** Pointer to a variable string buffer. */ +typedef VBCPPSTRBUF *PVBCPPSTRBUF; + + +/** + * The preprocessor mode. + */ +typedef enum VBCPPMODE +{ + kVBCppMode_Invalid = 0, + kVBCppMode_Standard, + kVBCppMode_Selective, + kVBCppMode_SelectiveD, + kVBCppMode_End +} VBCPPMODE; + + +/** + * A macro (aka define). + */ +typedef struct VBCPPMACRO +{ + /** The string space core. */ + RTSTRSPACECORE Core; +#if 0 + /** For linking macros that have the fExpanding flag set. */ + struct VBCPPMACRO *pUpExpanding; +#endif + /** Whether it's a function. */ + bool fFunction; + /** Variable argument count. */ + bool fVarArg; + /** Set if originating on the command line. */ + bool fCmdLine; + /** Set if this macro is currently being expanded and should not be + * recursively applied. */ + bool fExpanding; + /** The number of known arguments. */ + uint32_t cArgs; + /** Pointer to a list of argument names. */ + const char **papszArgs; + /** Lead character bitmap for the argument names. */ + VBCPP_BITMAP_TYPE bmArgs[VBCPP_BITMAP_SIZE]; + /** The value length. */ + size_t cchValue; + /** The define value. (This is followed by the name and arguments.) */ + char szValue[1]; +} VBCPPMACRO; +/** Pointer to a macro. */ +typedef VBCPPMACRO *PVBCPPMACRO; + + +/** + * Macro expansion data. + */ +typedef struct VBCPPMACROEXP +{ + /** The expansion buffer. */ + VBCPPSTRBUF StrBuf; +#if 0 + /** List of expanding macros (Stack). */ + PVBCPPMACRO pMacroStack; +#endif + /** The input stream (in case we want to look for parameter lists). */ + PSCMSTREAM pStrmInput; + /** Array of argument values. Used when expanding function style macros. */ + char **papszArgs; + /** The number of argument values current in papszArgs. */ + uint32_t cArgs; + /** The number of argument values papszArgs can currently hold */ + uint32_t cArgsAlloced; +} VBCPPMACROEXP; +/** Pointer to macro expansion data. */ +typedef VBCPPMACROEXP *PVBCPPMACROEXP; + + +/** + * The vbcppMacroExpandReScan mode of operation. + */ +typedef enum VBCPPMACRORESCANMODE +{ + /** Invalid mode. */ + kMacroReScanMode_Invalid = 0, + /** Normal expansion mode. */ + kMacroReScanMode_Normal, + /** Replaces known macros and heeds the 'defined' operator. */ + kMacroReScanMode_Expression, + /** End of valid modes. */ + kMacroReScanMode_End +} VBCPPMACRORESCANMODE; + + +/** + * Expression node type. + */ +typedef enum VBCPPEXPRKIND +{ + kVBCppExprKind_Invalid = 0, + kVBCppExprKind_Unary, + kVBCppExprKind_Binary, + kVBCppExprKind_Ternary, + kVBCppExprKind_SignedValue, + kVBCppExprKind_UnsignedValue, + kVBCppExprKind_End +} VBCPPEXPRKIND; + + +/** Macro used for the precedence field. */ +#define VBCPPOP_PRECEDENCE(a_iPrecedence) ((a_iPrecedence) << 8) +/** Mask for getting the precedence field value. */ +#define VBCPPOP_PRECEDENCE_MASK 0xff00 +/** Operator associativity - Left to right. */ +#define VBCPPOP_L2R (1 << 16) +/** Operator associativity - Right to left. */ +#define VBCPPOP_R2L (2 << 16) + +/** + * Unary operators. + */ +typedef enum VBCPPUNARYOP +{ + kVBCppUnaryOp_Invalid = 0, + kVBCppUnaryOp_Pluss = VBCPPOP_R2L | VBCPPOP_PRECEDENCE( 3) | 5, + kVBCppUnaryOp_Minus = VBCPPOP_R2L | VBCPPOP_PRECEDENCE( 3) | 6, + kVBCppUnaryOp_LogicalNot = VBCPPOP_R2L | VBCPPOP_PRECEDENCE( 3) | 7, + kVBCppUnaryOp_BitwiseNot = VBCPPOP_R2L | VBCPPOP_PRECEDENCE( 3) | 8, + kVBCppUnaryOp_Parenthesis = VBCPPOP_R2L | VBCPPOP_PRECEDENCE(15) | 9, + kVBCppUnaryOp_End +} VBCPPUNARYOP; + +/** + * Binary operators. + */ +typedef enum VBCPPBINARYOP +{ + kVBCppBinary_Invalid = 0, + kVBCppBinary_Multiplication = VBCPPOP_L2R | VBCPPOP_PRECEDENCE( 5) | 2, + kVBCppBinary_Division = VBCPPOP_L2R | VBCPPOP_PRECEDENCE( 5) | 4, + kVBCppBinary_Modulo = VBCPPOP_L2R | VBCPPOP_PRECEDENCE( 5) | 5, + kVBCppBinary_Addition = VBCPPOP_L2R | VBCPPOP_PRECEDENCE( 6) | 6, + kVBCppBinary_Subtraction = VBCPPOP_L2R | VBCPPOP_PRECEDENCE( 6) | 7, + kVBCppBinary_LeftShift = VBCPPOP_L2R | VBCPPOP_PRECEDENCE( 7) | 8, + kVBCppBinary_RightShift = VBCPPOP_L2R | VBCPPOP_PRECEDENCE( 7) | 9, + kVBCppBinary_LessThan = VBCPPOP_L2R | VBCPPOP_PRECEDENCE( 8) | 10, + kVBCppBinary_LessThanOrEqual = VBCPPOP_L2R | VBCPPOP_PRECEDENCE( 8) | 11, + kVBCppBinary_GreaterThan = VBCPPOP_L2R | VBCPPOP_PRECEDENCE( 8) | 12, + kVBCppBinary_GreaterThanOrEqual = VBCPPOP_L2R | VBCPPOP_PRECEDENCE( 8) | 13, + kVBCppBinary_EqualTo = VBCPPOP_L2R | VBCPPOP_PRECEDENCE( 9) | 14, + kVBCppBinary_NotEqualTo = VBCPPOP_L2R | VBCPPOP_PRECEDENCE( 9) | 15, + kVBCppBinary_BitwiseAnd = VBCPPOP_L2R | VBCPPOP_PRECEDENCE(10) | 16, + kVBCppBinary_BitwiseXor = VBCPPOP_L2R | VBCPPOP_PRECEDENCE(11) | 17, + kVBCppBinary_BitwiseOr = VBCPPOP_L2R | VBCPPOP_PRECEDENCE(12) | 18, + kVBCppBinary_LogicalAnd = VBCPPOP_L2R | VBCPPOP_PRECEDENCE(13) | 19, + kVBCppBinary_LogicalOr = VBCPPOP_L2R | VBCPPOP_PRECEDENCE(14) | 20, + kVBCppBinary_End +} VBCPPBINARYOP; + +/** The precedence of the ternary operator (expr ? true : false). */ +#define VBCPPTERNAROP_PRECEDENCE VBCPPOP_PRECEDENCE(16) + + +/** Pointer to an expression parsing node. */ +typedef struct VBCPPEXPR *PVBCPPEXPR; +/** + * Expression parsing node. + */ +typedef struct VBCPPEXPR +{ + /** Parent expression. */ + PVBCPPEXPR pParent; + /** Whether the expression is complete or not. */ + bool fComplete; + /** The kind of expression. */ + VBCPPEXPRKIND enmKind; + /** Kind specific content. */ + union + { + /** kVBCppExprKind_Unary */ + struct + { + VBCPPUNARYOP enmOperator; + PVBCPPEXPR pArg; + } Unary; + + /** kVBCppExprKind_Binary */ + struct + { + VBCPPBINARYOP enmOperator; + PVBCPPEXPR pLeft; + PVBCPPEXPR pRight; + } Binary; + + /** kVBCppExprKind_Ternary */ + struct + { + PVBCPPEXPR pExpr; + PVBCPPEXPR pTrue; + PVBCPPEXPR pFalse; + } Ternary; + + /** kVBCppExprKind_SignedValue */ + struct + { + int64_t s64; + } SignedValue; + + /** kVBCppExprKind_UnsignedValue */ + struct + { + uint64_t u64; + } UnsignedValue; + } u; +} VBCPPEXPR; + + +/** + * Operator return statuses. + */ +typedef enum VBCPPEXPRRET +{ + kExprRet_Error = -1, + kExprRet_Ok = 0, + kExprRet_UnaryOperator, + kExprRet_Value, + kExprRet_EndOfExpr, + kExprRet_End +} VBCPPEXPRRET; + +/** + * Expression parser context. + */ +typedef struct VBCPPEXPRPARSER +{ + /** The current expression posistion. */ + const char *pszCur; + /** The root node. */ + PVBCPPEXPR pRoot; + /** The current expression node. */ + PVBCPPEXPR pCur; + /** Where to insert the next expression. */ + PVBCPPEXPR *ppCur; + /** The expression. */ + const char *pszExpr; + /** The number of undefined macros we've encountered while parsing. */ + size_t cUndefined; + /** Pointer to the C preprocessor instance. */ + PVBCPP pThis; +} VBCPPEXPRPARSER; +/** Pointer to an expression parser context. */ +typedef VBCPPEXPRPARSER *PVBCPPEXPRPARSER; + + +/** + * Evaluation result. + */ +typedef enum VBCPPEVAL +{ + kVBCppEval_Invalid = 0, + kVBCppEval_True, + kVBCppEval_False, + kVBCppEval_Undecided, + kVBCppEval_End +} VBCPPEVAL; + + +/** + * The condition kind. + */ +typedef enum VBCPPCONDKIND +{ + kVBCppCondKind_Invalid = 0, + /** \#if expr */ + kVBCppCondKind_If, + /** \#ifdef define */ + kVBCppCondKind_IfDef, + /** \#ifndef define */ + kVBCppCondKind_IfNDef, + /** \#elif expr */ + kVBCppCondKind_ElIf, + /** The end of valid values. */ + kVBCppCondKind_End +} VBCPPCONDKIND; + + +/** + * Conditional stack entry. + */ +typedef struct VBCPPCOND +{ + /** The next conditional on the stack. */ + struct VBCPPCOND *pUp; + /** The kind of conditional. This changes on encountering \#elif. */ + VBCPPCONDKIND enmKind; + /** Evaluation result. */ + VBCPPEVAL enmResult; + /** The evaluation result of the whole stack. */ + VBCPPEVAL enmStackResult; + + /** Whether we've seen the last else. */ + bool fSeenElse; + /** Set if we have an else if which has already been decided. */ + bool fElIfDecided; + /** The nesting level of this condition. */ + uint16_t iLevel; + /** The nesting level of this condition wrt the ones we keep. */ + uint16_t iKeepLevel; + + /** The condition string. (Points within the stream buffer.) */ + const char *pchCond; + /** The condition length. */ + size_t cchCond; +} VBCPPCOND; +/** Pointer to a conditional stack entry. */ +typedef VBCPPCOND *PVBCPPCOND; + + +/** + * Input buffer stack entry. + */ +typedef struct VBCPPINPUT +{ + /** Pointer to the next input on the stack. */ + struct VBCPPINPUT *pUp; + /** The input stream. */ + SCMSTREAM StrmInput; + /** Pointer into szName to the part which was specified. */ + const char *pszSpecified; + /** The input file name with include path. */ + char szName[1]; +} VBCPPINPUT; +/** Pointer to a input buffer stack entry */ +typedef VBCPPINPUT *PVBCPPINPUT; + + +/** + * The action to take with \#include. + */ +typedef enum VBCPPINCLUDEACTION +{ + kVBCppIncludeAction_Invalid = 0, + kVBCppIncludeAction_Include, + kVBCppIncludeAction_PassThru, + kVBCppIncludeAction_Drop, + kVBCppIncludeAction_End +} VBCPPINCLUDEACTION; + + +/** + * C Preprocessor instance data. + */ +typedef struct VBCPP +{ + /** @name Options + * @{ */ + /** The preprocessing mode. */ + VBCPPMODE enmMode; + /** Whether to keep comments. */ + bool fKeepComments; + /** Whether to respect source defines. */ + bool fRespectSourceDefines; + /** Whether to let source defines overrides the ones on the command + * line. */ + bool fAllowRedefiningCmdLineDefines; + /** Whether to pass thru defines. */ + bool fPassThruDefines; + /** Whether to allow undecided conditionals. */ + bool fUndecidedConditionals; + /** Whether to pass thru D pragmas. */ + bool fPassThruPragmaD; + /** Whether to pass thru STD pragmas. */ + bool fPassThruPragmaSTD; + /** Whether to pass thru other pragmas. */ + bool fPassThruPragmaOther; + /** Whether to remove dropped lines from the output. */ + bool fRemoveDroppedLines; + /** Whether to preforme line splicing. + * @todo implement line splicing */ + bool fLineSplicing; + /** What to do about include files. */ + VBCPPINCLUDEACTION enmIncludeAction; + + /** The number of include directories. */ + uint32_t cIncludes; + /** Array of directories to search for include files. */ + char **papszIncludes; + + /** The name of the input file. */ + const char *pszInput; + /** The name of the output file. NULL if stdout. */ + const char *pszOutput; + /** @} */ + + /** The define string space. */ + RTSTRSPACE StrSpace; + /** The string space holding explicitly undefined macros for selective + * preprocessing runs. */ + RTSTRSPACE UndefStrSpace; + /** Indicates whether a C-word might need expansion. + * The bitmap is indexed by C-word lead character. Bits that are set + * indicates that the lead character is used in a \#define that we know and + * should expand. */ + VBCPP_BITMAP_TYPE bmDefined[VBCPP_BITMAP_SIZE]; + + /** The current depth of the conditional stack. */ + uint32_t cCondStackDepth; + /** Conditional stack. */ + PVBCPPCOND pCondStack; + /** The current condition evaluates to kVBCppEval_False, don't output. */ + bool fIf0Mode; + /** Just dropped a line and should maybe drop the current line. */ + bool fJustDroppedLine; + + /** Whether the current line could be a preprocessor line. + * This is set when EOL is encountered and cleared again when a + * non-comment-or-space character is encountered. See vbcppPreprocess. */ + bool fMaybePreprocessorLine; + + /** The input stack depth */ + uint32_t cInputStackDepth; + /** The input buffer stack. */ + PVBCPPINPUT pInputStack; + + /** The output stream. */ + SCMSTREAM StrmOutput; + + /** The status of the whole job, as far as we know. */ + RTEXITCODE rcExit; + /** Whether StrmOutput is valid (for vbcppTerm). */ + bool fStrmOutputValid; +} VBCPP; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static PVBCPPMACRO vbcppMacroLookup(PVBCPP pThis, const char *pszDefine, size_t cchDefine); +static RTEXITCODE vbcppMacroExpandIt(PVBCPP pThis, PVBCPPMACROEXP pExp, size_t offMacro, PVBCPPMACRO pMacro, size_t offParameters); +static RTEXITCODE vbcppMacroExpandReScan(PVBCPP pThis, PVBCPPMACROEXP pExp, VBCPPMACRORESCANMODE enmMode, size_t *pcReplacements); +static void vbcppMacroExpandCleanup(PVBCPPMACROEXP pExp); + + + +/* + * + * + * Message Handling. + * Message Handling. + * Message Handling. + * Message Handling. + * Message Handling. + * + * + */ + + +/** + * Displays an error message. + * + * @returns RTEXITCODE_FAILURE + * @param pThis The C preprocessor instance. + * @param pszMsg The message. + * @param va Message arguments. + */ +static RTEXITCODE vbcppErrorV(PVBCPP pThis, const char *pszMsg, va_list va) +{ + NOREF(pThis); + if (pThis->pInputStack) + { + PSCMSTREAM pStrm = &pThis->pInputStack->StrmInput; + + size_t const off = ScmStreamTell(pStrm); + size_t const iLine = ScmStreamTellLine(pStrm); + ScmStreamSeekByLine(pStrm, iLine); + size_t const offLine = ScmStreamTell(pStrm); + + RTPrintf("%s:%d:%zd: error: %N.\n", pThis->pInputStack->szName, iLine + 1, off - offLine + 1, pszMsg, va); + + size_t cchLine; + SCMEOL enmEof; + const char *pszLine = ScmStreamGetLineByNo(pStrm, iLine, &cchLine, &enmEof); + if (pszLine) + RTPrintf(" %.*s\n" + " %*s^\n", + cchLine, pszLine, off - offLine, ""); + + ScmStreamSeekAbsolute(pStrm, off); + } + else + RTMsgErrorV(pszMsg, va); + return pThis->rcExit = RTEXITCODE_FAILURE; +} + + +/** + * Displays an error message. + * + * @returns RTEXITCODE_FAILURE + * @param pThis The C preprocessor instance. + * @param pszMsg The message. + * @param ... Message arguments. + */ +static RTEXITCODE vbcppError(PVBCPP pThis, const char *pszMsg, ...) +{ + va_list va; + va_start(va, pszMsg); + RTEXITCODE rcExit = vbcppErrorV(pThis, pszMsg, va); + va_end(va); + return rcExit; +} + + +/** + * Displays an error message. + * + * @returns RTEXITCODE_FAILURE + * @param pThis The C preprocessor instance. + * @param pszPos Pointer to the offending character. + * @param pszMsg The message. + * @param ... Message arguments. + */ +static RTEXITCODE vbcppErrorPos(PVBCPP pThis, const char *pszPos, const char *pszMsg, ...) +{ + NOREF(pszPos); NOREF(pThis); + va_list va; + va_start(va, pszMsg); + RTMsgErrorV(pszMsg, va); + va_end(va); + return pThis->rcExit = RTEXITCODE_FAILURE; +} + + + + + + + +/* + * + * + * Variable String Buffers. + * Variable String Buffers. + * Variable String Buffers. + * Variable String Buffers. + * Variable String Buffers. + * + * + */ + + +/** + * Initializes a string buffer. + * + * @param pStrBuf The buffer structure to initialize. + * @param pThis The C preprocessor instance. + */ +static void vbcppStrBufInit(PVBCPPSTRBUF pStrBuf, PVBCPP pThis) +{ + pStrBuf->pThis = pThis; + pStrBuf->cchBuf = 0; + pStrBuf->cbBufAllocated = 0; + pStrBuf->pszBuf = NULL; +} + + +/** + * Deletes a string buffer. + * + * @param pStrBuf Pointer to the string buffer. + */ +static void vbcppStrBufDelete(PVBCPPSTRBUF pStrBuf) +{ + RTMemFree(pStrBuf->pszBuf); + pStrBuf->pszBuf = NULL; +} + + +/** + * Ensures that sufficient bufferspace is available, growing the buffer if + * necessary. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pStrBuf Pointer to the string buffer. + * @param cbMin The minimum buffer size. + */ +static RTEXITCODE vbcppStrBufGrow(PVBCPPSTRBUF pStrBuf, size_t cbMin) +{ + if (pStrBuf->cbBufAllocated >= cbMin) + return RTEXITCODE_SUCCESS; + + size_t cbNew = pStrBuf->cbBufAllocated * 2; + if (cbNew < cbMin) + cbNew = RT_ALIGN_Z(cbMin, _1K); + void *pv = RTMemRealloc(pStrBuf->pszBuf, cbNew); + if (!pv) + return vbcppError(pStrBuf->pThis, "out of memory (%zu bytes)", cbNew); + + pStrBuf->pszBuf = (char *)pv; + pStrBuf->cbBufAllocated = cbNew; + return RTEXITCODE_SUCCESS; +} + + +/** + * Appends a substring. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pStrBuf Pointer to the string buffer. + * @param pchSrc Pointer to the first character in the substring. + * @param cchSrc The length of the substring. + */ +static RTEXITCODE vbcppStrBufAppendN(PVBCPPSTRBUF pStrBuf, const char *pchSrc, size_t cchSrc) +{ + size_t cchBuf = pStrBuf->cchBuf; + if (cchBuf + cchSrc + 1 > pStrBuf->cbBufAllocated) + { + RTEXITCODE rcExit = vbcppStrBufGrow(pStrBuf, cchBuf + cchSrc + 1); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + } + + memcpy(&pStrBuf->pszBuf[cchBuf], pchSrc, cchSrc); + cchBuf += cchSrc; + pStrBuf->pszBuf[cchBuf] = '\0'; + pStrBuf->cchBuf = cchBuf; + + return RTEXITCODE_SUCCESS; +} + + +/** + * Appends a character. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pStrBuf Pointer to the string buffer. + * @param ch The charater to append. + */ +static RTEXITCODE vbcppStrBufAppendCh(PVBCPPSTRBUF pStrBuf, char ch) +{ + size_t cchBuf = pStrBuf->cchBuf; + if (cchBuf + 2 > pStrBuf->cbBufAllocated) + { + RTEXITCODE rcExit = vbcppStrBufGrow(pStrBuf, cchBuf + 2); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + } + + pStrBuf->pszBuf[cchBuf++] = ch; + pStrBuf->pszBuf[cchBuf] = '\0'; + pStrBuf->cchBuf = cchBuf; + + return RTEXITCODE_SUCCESS; +} + + +/** + * Appends a string to the buffer. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pStrBuf Pointer to the string buffer. + * @param psz The string to append. + */ +static RTEXITCODE vbcppStrBufAppend(PVBCPPSTRBUF pStrBuf, const char *psz) +{ + return vbcppStrBufAppendN(pStrBuf, psz, strlen(psz)); +} + + +/** + * Gets the last char in the buffer. + * + * @returns Last character, 0 if empty. + * @param pStrBuf Pointer to the string buffer. + */ +static char vbcppStrBufLastCh(PVBCPPSTRBUF pStrBuf) +{ + if (!pStrBuf->cchBuf) + return '\0'; + return pStrBuf->pszBuf[pStrBuf->cchBuf - 1]; +} + + + + + + + +/* + * + * + * C Identifier/Word Parsing. + * C Identifier/Word Parsing. + * C Identifier/Word Parsing. + * C Identifier/Word Parsing. + * C Identifier/Word Parsing. + * + * + */ + + +/** + * Checks if the given character is a valid C identifier lead character. + * + * @returns true / false. + * @param ch The character to inspect. + */ +DECLINLINE(bool) vbcppIsCIdentifierLeadChar(char ch) +{ + return RT_C_IS_ALPHA(ch) + || ch == '_'; +} + + +/** + * Checks if the given character is a valid C identifier character. + * + * @returns true / false. + * @param ch The character to inspect. + */ +DECLINLINE(bool) vbcppIsCIdentifierChar(char ch) +{ + return RT_C_IS_ALNUM(ch) + || ch == '_'; +} + + + +/** + * + * @returns @c true if valid, @c false if not. Error message already displayed + * on failure. + * @param pThis The C preprocessor instance. + * @param pchIdentifier The start of the identifier to validate. + * @param cchIdentifier The length of the identifier. RTSTR_MAX if not + * known. + */ +static bool vbcppValidateCIdentifier(PVBCPP pThis, const char *pchIdentifier, size_t cchIdentifier) +{ + if (cchIdentifier == RTSTR_MAX) + cchIdentifier = strlen(pchIdentifier); + + if (cchIdentifier == 0) + { + vbcppErrorPos(pThis, pchIdentifier, "Zero length identifier"); + return false; + } + + if (!vbcppIsCIdentifierLeadChar(*pchIdentifier)) + { + vbcppErrorPos(pThis, pchIdentifier, "Bad lead chararacter in identifier: '%.*s'", cchIdentifier, pchIdentifier); + return false; + } + + for (size_t off = 1; off < cchIdentifier; off++) + { + if (!vbcppIsCIdentifierChar(pchIdentifier[off])) + { + vbcppErrorPos(pThis, pchIdentifier + off, "Illegal chararacter in identifier: '%.*s' (#%zu)", cchIdentifier, pchIdentifier, off + 1); + return false; + } + } + + return true; +} + +#if 0 + +/** + * Checks if the given character is valid C punctuation. + * + * @returns true / false. + * @param ch The character to inspect. + */ +DECLINLINE(bool) vbcppIsCPunctuationLeadChar(char ch) +{ + switch (ch) + { + case '!': + case '#': + case '%': + case '&': + case '(': + case ')': + case '*': + case '+': + case ',': + case '-': + case '.': + case '/': + case ':': + case ';': + case '<': + case '=': + case '>': + case '?': + case '[': + case ']': + case '^': + case '{': + case '|': + case '}': + case '~': + return true; + default: + return false; + } +} + + +/** + * Checks if the given string start with valid C punctuation. + * + * @returns 0 if not, otherwise the length of the punctuation. + * @param pch The which start we should evaluate. + * @param cchMax The maximum string length. + */ +static size_t vbcppIsCPunctuationLeadChar(const char *psz, size_t cchMax) +{ + if (!cchMax) + return 0; + + switch (psz[0]) + { + case '!': + case '*': + case '/': + case '=': + case '^': + if (cchMax >= 2 && psz[1] == '=') + return 2; + return 1; + + case '#': + if (cchMax >= 2 && psz[1] == '#') + return 2; + return 1; + + case '%': + if (cchMax >= 2 && (psz[1] == '=' || psz[1] == '>')) + return 2; + if (cchMax >= 2 && psz[1] == ':') + { + if (cchMax >= 4 && psz[2] == '%' && psz[3] == ':') + return 4; + return 2; + } + return 1; + + case '&': + if (cchMax >= 2 && (psz[1] == '=' || psz[1] == '&')) + return 2; + return 1; + + case '(': + case ')': + case ',': + case '?': + case '[': + case ']': + case '{': + case '}': + return 1; + + case '+': + if (cchMax >= 2 && (psz[1] == '=' || psz[1] == '+')) + return 2; + return 1; + + case '-': + if (cchMax >= 2 && (psz[1] == '=' || psz[1] == '-' || psz[1] == '>')) + return 2; + return 1; + + case ':': + if (cchMax >= 2 && psz[1] == '>') + return 2; + return 1; + + case ';': + return 1; + + case '<': + if (cchMax >= 2 && psz[1] == '<') + { + if (cchMax >= 3 && psz[2] == '=') + return 3; + return 2; + } + if (cchMax >= 2 && (psz[1] == '=' || psz[1] == ':' || psz[1] == '%')) + return 2; + return 1; + + case '.': + if (cchMax >= 3 && psz[1] == '.' && psz[2] == '.') + return 3; + return 1; + + case '>': + if (cchMax >= 2 && psz[1] == '>') + { + if (cchMax >= 3 && psz[2] == '=') + return 3; + return 2; + } + if (cchMax >= 2 && psz[1] == '=') + return 2; + return 1; + + case '|': + if (cchMax >= 2 && (psz[1] == '=' || psz[1] == '|')) + return 2; + return 1; + + case '~': + return 1; + + default: + return 0; + } +} + +#endif + + + + + +/* + * + * + * Output + * Output + * Output + * Output + * Output + * + * + */ + + +/** + * Outputs a character. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param ch The character to output. + */ +static RTEXITCODE vbcppOutputCh(PVBCPP pThis, char ch) +{ + int rc = ScmStreamPutCh(&pThis->StrmOutput, ch); + if (RT_SUCCESS(rc)) + return RTEXITCODE_SUCCESS; + return vbcppError(pThis, "Output error: %Rrc", rc); +} + + +/** + * Outputs a string. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pch The string. + * @param cch The number of characters to write. + */ +static RTEXITCODE vbcppOutputWrite(PVBCPP pThis, const char *pch, size_t cch) +{ + int rc = ScmStreamWrite(&pThis->StrmOutput, pch, cch); + if (RT_SUCCESS(rc)) + return RTEXITCODE_SUCCESS; + return vbcppError(pThis, "Output error: %Rrc", rc); +} + + +static RTEXITCODE vbcppOutputComment(PVBCPP pThis, PSCMSTREAM pStrmInput, size_t offStart, size_t cchOutputted, + size_t cchMinIndent) +{ + RT_NOREF_PV(cchMinIndent); /** @todo cchMinIndent */ + + size_t offCur = ScmStreamTell(pStrmInput); + if (offStart < offCur) + { + int rc = ScmStreamSeekAbsolute(pStrmInput, offStart); + AssertRCReturn(rc, vbcppError(pThis, "Input seek error: %Rrc", rc)); + + /* + * Use the same indent, if possible. + */ + size_t cchIndent = offStart - ScmStreamTellOffsetOfLine(pStrmInput, ScmStreamTellLine(pStrmInput)); + if (cchOutputted < cchIndent) + rc = ScmStreamPrintf(&pThis->StrmOutput, "%*s", cchIndent - cchOutputted, ""); + else + rc = ScmStreamPutCh(&pThis->StrmOutput, ' '); + if (RT_FAILURE(rc)) + return vbcppError(pThis, "Output error: %Rrc", rc); + + /* + * Copy the bytes. + */ + while (ScmStreamTell(pStrmInput) < offCur) + { + unsigned ch = ScmStreamGetCh(pStrmInput); + if (ch == ~(unsigned)0) + return vbcppError(pThis, "Input error: %Rrc", rc); + rc = ScmStreamPutCh(&pThis->StrmOutput, ch); + if (RT_FAILURE(rc)) + return vbcppError(pThis, "Output error: %Rrc", rc); + } + } + + return RTEXITCODE_SUCCESS; +} + + + + + +/* + * + * + * Input + * Input + * Input + * Input + * Input + * + * + */ + + +#if 0 /* unused */ +/** + * Skips white spaces, including escaped new-lines. + * + * @param pStrmInput The input stream. + */ +static void vbcppProcessSkipWhiteAndEscapedEol(PSCMSTREAM pStrmInput) +{ + unsigned chPrev = ~(unsigned)0; + unsigned ch; + while ((ch = ScmStreamPeekCh(pStrmInput)) != ~(unsigned)0) + { + if (ch == '\r' || ch == '\n') + { + if (chPrev != '\\') + break; + chPrev = ch; + ScmStreamSeekByLine(pStrmInput, ScmStreamTellLine(pStrmInput) + 1); + } + else if (RT_C_IS_SPACE(ch)) + { + chPrev = ch; + ch = ScmStreamGetCh(pStrmInput); + Assert(ch == chPrev); + } + else + break; + } +} +#endif + + +/** + * Skips white spaces, escaped new-lines and multi line comments. + * + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + */ +static RTEXITCODE vbcppProcessSkipWhiteEscapedEolAndComments(PVBCPP pThis, PSCMSTREAM pStrmInput) +{ + unsigned chPrev = ~(unsigned)0; + unsigned ch; + while ((ch = ScmStreamPeekCh(pStrmInput)) != ~(unsigned)0) + { + if (!RT_C_IS_SPACE(ch)) + { + /* Multi-line Comment? */ + if (ch != '/') + break; /* most definitely, not. */ + + size_t offSaved = ScmStreamTell(pStrmInput); + ScmStreamGetCh(pStrmInput); + if (ScmStreamPeekCh(pStrmInput) != '*') + { + ScmStreamSeekAbsolute(pStrmInput, offSaved); + break; /* no */ + } + + /* Skip to the end of the comment. */ + while ((ch = ScmStreamGetCh(pStrmInput)) != ~(unsigned)0) + { + if (ch == '*') + { + ch = ScmStreamGetCh(pStrmInput); + if (ch == '/') + break; + if (ch == ~(unsigned)0) + break; + } + } + if (ch == ~(unsigned)0) + return vbcppError(pThis, "unterminated multi-line comment"); + chPrev = '/'; + } + /* New line (also matched by RT_C_IS_SPACE). */ + else if (ch == '\r' || ch == '\n') + { + /* Stop if not escaped. */ + if (chPrev != '\\') + break; + chPrev = ch; + ScmStreamSeekByLine(pStrmInput, ScmStreamTellLine(pStrmInput) + 1); + } + /* Real space char. */ + else + { + chPrev = ch; + ch = ScmStreamGetCh(pStrmInput); + Assert(ch == chPrev); + } + } + return RTEXITCODE_SUCCESS; +} + + +/** + * Skips white spaces, escaped new-lines, and multi line comments, then checking + * that we're at the end of a line. + * + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + */ +static RTEXITCODE vbcppProcessSkipWhiteEscapedEolAndCommentsCheckEol(PVBCPP pThis, PSCMSTREAM pStrmInput) +{ + RTEXITCODE rcExit = vbcppProcessSkipWhiteEscapedEolAndComments(pThis, pStrmInput); + if (rcExit == RTEXITCODE_SUCCESS) + { + unsigned ch = ScmStreamPeekCh(pStrmInput); + if ( ch != ~(unsigned)0 + && ch != '\r' + && ch != '\n') + rcExit = vbcppError(pThis, "Did not expected anything more on this line"); + } + return rcExit; +} + + +/** + * Skips white spaces. + * + * @returns The current location upon return. + * @param pStrmInput The input stream. + */ +static size_t vbcppProcessSkipWhite(PSCMSTREAM pStrmInput) +{ + unsigned ch; + while ((ch = ScmStreamPeekCh(pStrmInput)) != ~(unsigned)0) + { + if (!RT_C_IS_SPACE(ch) || ch == '\r' || ch == '\n') + break; + unsigned chCheck = ScmStreamGetCh(pStrmInput); + AssertBreak(chCheck == ch); + } + return ScmStreamTell(pStrmInput); +} + + +/** + * Looks for a left parenthesis in the input stream. + * + * Used during macro expansion. Will ignore comments, newlines and other + * whitespace. + * + * @retval true if found. The stream position at opening parenthesis. + * @retval false if not found. The stream position is unchanged. + * + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + */ +static bool vbcppInputLookForLeftParenthesis(PVBCPP pThis, PSCMSTREAM pStrmInput) +{ + size_t offSaved = ScmStreamTell(pStrmInput); + /*RTEXITCODE rcExit =*/ vbcppProcessSkipWhiteEscapedEolAndComments(pThis, pStrmInput); + unsigned ch = ScmStreamPeekCh(pStrmInput); + if (ch == '(') + return true; + + int rc = ScmStreamSeekAbsolute(pStrmInput, offSaved); + AssertFatalRC(rc); + return false; +} + + +/** + * Skips input until the real end of the current directive line has been + * reached. + * + * This includes multiline comments starting on the same line + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + * @param poffComment Where to note down the position of the final + * comment. Optional. + */ +static RTEXITCODE vbcppInputSkipToEndOfDirectiveLine(PVBCPP pThis, PSCMSTREAM pStrmInput, size_t *poffComment) +{ + if (poffComment) + *poffComment = ~(size_t)0; + + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + bool fInComment = false; + unsigned chPrev = 0; + unsigned ch; + while ((ch = ScmStreamPeekCh(pStrmInput)) != ~(unsigned)0) + { + if (ch == '\r' || ch == '\n') + { + if (chPrev == '\\') + { + ScmStreamSeekByLine(pStrmInput, ScmStreamTellLine(pStrmInput) + 1); + continue; + } + if (!fInComment) + break; + /* The expression continues after multi-line comments. Cool. :-) */ + } + else if (!fInComment) + { + if (chPrev == '/' && ch == '*' ) + { + fInComment = true; + if (poffComment) + *poffComment = ScmStreamTell(pStrmInput) - 1; + } + else if (chPrev == '/' && ch == '/') + { + if (poffComment) + *poffComment = ScmStreamTell(pStrmInput) - 1; + rcExit = vbcppProcessSkipWhiteEscapedEolAndComments(pThis, pStrmInput); + break; /* done */ + } + } + else if (ch == '/' && chPrev == '*') + fInComment = false; + + /* advance */ + chPrev = ch; + ch = ScmStreamGetCh(pStrmInput); Assert(ch == chPrev); + } + return rcExit; +} + + +/** + * Processes a multi-line comment. + * + * Must either string the comment or keep it. If the latter, we must refrain + * from replacing C-words in it. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + */ +static RTEXITCODE vbcppProcessMultiLineComment(PVBCPP pThis, PSCMSTREAM pStrmInput) +{ + /* The open comment sequence. */ + ScmStreamGetCh(pStrmInput); /* '*' */ + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + if ( pThis->fKeepComments + && !pThis->fIf0Mode) + rcExit = vbcppOutputWrite(pThis, "/*", 2); + + /* The comment.*/ + unsigned ch; + while ( rcExit == RTEXITCODE_SUCCESS + && (ch = ScmStreamGetCh(pStrmInput)) != ~(unsigned)0 ) + { + if (ch == '*') + { + /* Closing sequence? */ + unsigned ch2 = ScmStreamPeekCh(pStrmInput); + if (ch2 == '/') + { + ScmStreamGetCh(pStrmInput); + if ( pThis->fKeepComments + && !pThis->fIf0Mode) + rcExit = vbcppOutputWrite(pThis, "*/", 2); + break; + } + } + + if (ch == '\r' || ch == '\n') + { + if ( ( pThis->fKeepComments + && !pThis->fIf0Mode) + || !pThis->fRemoveDroppedLines + || !ScmStreamIsAtStartOfLine(&pThis->StrmOutput)) + rcExit = vbcppOutputCh(pThis, ch); + pThis->fJustDroppedLine = false; + pThis->fMaybePreprocessorLine = true; + } + else if ( pThis->fKeepComments + && !pThis->fIf0Mode) + rcExit = vbcppOutputCh(pThis, ch); + + if (rcExit != RTEXITCODE_SUCCESS) + break; + } + return rcExit; +} + + +/** + * Processes a single line comment. + * + * Must either string the comment or keep it. If the latter, we must refrain + * from replacing C-words in it. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + */ +static RTEXITCODE vbcppProcessOneLineComment(PVBCPP pThis, PSCMSTREAM pStrmInput) +{ + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + SCMEOL enmEol; + size_t cchLine; + const char *pszLine = ScmStreamGetLine(pStrmInput, &cchLine, &enmEol); Assert(pszLine); + pszLine--; cchLine++; /* unfetching the first slash. */ + for (;;) + { + if ( pThis->fKeepComments + && !pThis->fIf0Mode) + rcExit = vbcppOutputWrite(pThis, pszLine, cchLine + enmEol); + else if ( !pThis->fIf0Mode + || !pThis->fRemoveDroppedLines + || !ScmStreamIsAtStartOfLine(&pThis->StrmOutput) ) + rcExit = vbcppOutputWrite(pThis, pszLine + cchLine, enmEol); + if (rcExit != RTEXITCODE_SUCCESS) + break; + if ( cchLine == 0 + || pszLine[cchLine - 1] != '\\') + break; + + pszLine = ScmStreamGetLine(pStrmInput, &cchLine, &enmEol); + if (!pszLine) + break; + } + pThis->fJustDroppedLine = false; + pThis->fMaybePreprocessorLine = true; + return rcExit; +} + + +/** + * Processes a double quoted string. + * + * Must not replace any C-words in strings. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + */ +static RTEXITCODE vbcppProcessStringLitteral(PVBCPP pThis, PSCMSTREAM pStrmInput) +{ + RTEXITCODE rcExit = vbcppOutputCh(pThis, '"'); + if (rcExit == RTEXITCODE_SUCCESS) + { + bool fEscaped = false; + for (;;) + { + unsigned ch = ScmStreamGetCh(pStrmInput); + if (ch == ~(unsigned)0) + { + rcExit = vbcppError(pThis, "Unterminated double quoted string"); + break; + } + + rcExit = vbcppOutputCh(pThis, ch); + if (rcExit != RTEXITCODE_SUCCESS) + break; + + if (ch == '"' && !fEscaped) + break; + fEscaped = !fEscaped && ch == '\\'; + } + } + return rcExit; +} + + +/** + * Processes a single quoted constant. + * + * Must not replace any C-words in character constants. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + */ +static RTEXITCODE vbcppProcessCharacterConstant(PVBCPP pThis, PSCMSTREAM pStrmInput) +{ + RTEXITCODE rcExit = vbcppOutputCh(pThis, '\''); + if (rcExit == RTEXITCODE_SUCCESS) + { + bool fEscaped = false; + for (;;) + { + unsigned ch = ScmStreamGetCh(pStrmInput); + if (ch == ~(unsigned)0) + { + rcExit = vbcppError(pThis, "Unterminated singled quoted string"); + break; + } + + rcExit = vbcppOutputCh(pThis, ch); + if (rcExit != RTEXITCODE_SUCCESS) + break; + + if (ch == '\'' && !fEscaped) + break; + fEscaped = !fEscaped && ch == '\\'; + } + } + return rcExit; +} + + +/** + * Processes a integer or floating point number constant. + * + * Must not replace the type suffix. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + * @param chFirst The first character. + */ +static RTEXITCODE vbcppProcessNumber(PVBCPP pThis, PSCMSTREAM pStrmInput, char chFirst) +{ + RTEXITCODE rcExit = vbcppOutputCh(pThis, chFirst); + + unsigned ch; + while ( rcExit == RTEXITCODE_SUCCESS + && (ch = ScmStreamPeekCh(pStrmInput)) != ~(unsigned)0) + { + if ( !vbcppIsCIdentifierChar(ch) + && ch != '.') + break; + + unsigned ch2 = ScmStreamGetCh(pStrmInput); + AssertBreakStmt(ch2 == ch, rcExit = vbcppError(pThis, "internal error")); + rcExit = vbcppOutputCh(pThis, ch); + } + + return rcExit; +} + + +/** + * Processes a identifier, possibly replacing it with a definition. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + */ +static RTEXITCODE vbcppProcessIdentifier(PVBCPP pThis, PSCMSTREAM pStrmInput) +{ + RTEXITCODE rcExit; + size_t cchDefine; + const char *pchDefine = ScmStreamCGetWordM1(pStrmInput, &cchDefine); + AssertReturn(pchDefine, vbcppError(pThis, "Internal error in ScmStreamCGetWordM1")); + + /* + * Does this look like a define we know? + */ + PVBCPPMACRO pMacro = vbcppMacroLookup(pThis, pchDefine, cchDefine); + if ( pMacro + && ( !pMacro->fFunction + || vbcppInputLookForLeftParenthesis(pThis, pStrmInput)) ) + { + /* + * Expand it. + */ + VBCPPMACROEXP ExpCtx; +#if 0 + ExpCtx.pMacroStack = NULL; +#endif + ExpCtx.pStrmInput = pStrmInput; + ExpCtx.papszArgs = NULL; + ExpCtx.cArgs = 0; + ExpCtx.cArgsAlloced = 0; + vbcppStrBufInit(&ExpCtx.StrBuf, pThis); + rcExit = vbcppStrBufAppendN(&ExpCtx.StrBuf, pchDefine, cchDefine); + if (rcExit == RTEXITCODE_SUCCESS) + rcExit = vbcppMacroExpandIt(pThis, &ExpCtx, 0 /* offset */, pMacro, cchDefine); + if (rcExit == RTEXITCODE_SUCCESS) + rcExit = vbcppMacroExpandReScan(pThis, &ExpCtx, kMacroReScanMode_Normal, NULL); + if (rcExit == RTEXITCODE_SUCCESS) + { + /* + * Insert it into the output stream. Make sure there is a + * whitespace following it. + */ + int rc = ScmStreamWrite(&pThis->StrmOutput, ExpCtx.StrBuf.pszBuf, ExpCtx.StrBuf.cchBuf); + if (RT_SUCCESS(rc)) + { + unsigned chAfter = ScmStreamPeekCh(pStrmInput); + if (chAfter != ~(unsigned)0 && !RT_C_IS_SPACE(chAfter)) + rcExit = vbcppOutputCh(pThis, ' '); + } + else + rcExit = vbcppError(pThis, "Output error: %Rrc", rc); + } + vbcppMacroExpandCleanup(&ExpCtx); + } + else + { + /* + * Not a macro or a function-macro name match but no invocation, just + * output the text unchanged. + */ + int rc = ScmStreamWrite(&pThis->StrmOutput, pchDefine, cchDefine); + if (RT_SUCCESS(rc)) + rcExit = RTEXITCODE_SUCCESS; + else + rcExit = vbcppError(pThis, "Output error: %Rrc", rc); + } + return rcExit; +} + + + + + + + +/* + * + * + * D E F I N E S / M A C R O S + * D E F I N E S / M A C R O S + * D E F I N E S / M A C R O S + * D E F I N E S / M A C R O S + * D E F I N E S / M A C R O S + * + * + */ + + +/** + * Checks if a define exists. + * + * @returns true or false. + * @param pThis The C preprocessor instance. + * @param pszDefine The define name and optionally the argument + * list. + * @param cchDefine The length of the name. RTSTR_MAX is ok. + */ +static bool vbcppMacroExists(PVBCPP pThis, const char *pszDefine, size_t cchDefine) +{ + return cchDefine > 0 + && VBCPP_BITMAP_IS_SET(pThis->bmDefined, *pszDefine) + && RTStrSpaceGetN(&pThis->StrSpace, pszDefine, cchDefine) != NULL; +} + + +/** + * Looks up a define. + * + * @returns Pointer to the define if found, NULL if not. + * @param pThis The C preprocessor instance. + * @param pszDefine The define name and optionally the argument + * list. + * @param cchDefine The length of the name. RTSTR_MAX is ok. + */ +static PVBCPPMACRO vbcppMacroLookup(PVBCPP pThis, const char *pszDefine, size_t cchDefine) +{ + if (!cchDefine) + return NULL; + if (!VBCPP_BITMAP_IS_SET(pThis->bmDefined, *pszDefine)) + return NULL; + return (PVBCPPMACRO)RTStrSpaceGetN(&pThis->StrSpace, pszDefine, cchDefine); +} + + +static uint32_t vbcppMacroLookupArg(PVBCPPMACRO pMacro, const char *pchName, size_t cchName) +{ + Assert(cchName > 0); + + char const ch = *pchName; + for (uint32_t i = 0; i < pMacro->cArgs; i++) + if ( pMacro->papszArgs[i][0] == ch + && !strncmp(pMacro->papszArgs[i], pchName, cchName) + && pMacro->papszArgs[i][cchName] == '\0') + return i; + + if ( pMacro->fVarArg + && cchName == sizeof("__VA_ARGS__") - 1 + && !strncmp(pchName, "__VA_ARGS__", sizeof("__VA_ARGS__") - 1) ) + return pMacro->cArgs; + + return UINT32_MAX; +} + + +static RTEXITCODE vbcppMacroExpandReplace(PVBCPP pThis, PVBCPPMACROEXP pExp, size_t off, size_t cchToReplace, + const char *pchReplacement, size_t cchReplacement) +{ + RT_NOREF_PV(pThis); + + /* + * Figure how much space we actually need. + * (Hope this whitespace stuff is correct...) + */ + bool const fLeadingSpace = off > 0 + && !RT_C_IS_SPACE(pExp->StrBuf.pszBuf[off - 1]); + bool const fTrailingSpace = off + cchToReplace < pExp->StrBuf.cchBuf + && !RT_C_IS_SPACE(pExp->StrBuf.pszBuf[off + cchToReplace]); + size_t const cchActualReplacement = fLeadingSpace + cchReplacement + fTrailingSpace; + + /* + * Adjust the buffer size and contents. + */ + if (cchActualReplacement > cchToReplace) + { + size_t const offMore = cchActualReplacement - cchToReplace; + + /* Ensure enough buffer space. */ + size_t cbMinBuf = offMore + pExp->StrBuf.cchBuf + 1; + RTEXITCODE rcExit = vbcppStrBufGrow(&pExp->StrBuf, cbMinBuf); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + + /* Push the chars following the replacement area down to make room. */ + memmove(&pExp->StrBuf.pszBuf[off + cchToReplace + offMore], + &pExp->StrBuf.pszBuf[off + cchToReplace], + pExp->StrBuf.cchBuf - off - cchToReplace + 1); + pExp->StrBuf.cchBuf += offMore; + + } + else if (cchActualReplacement < cchToReplace) + { + size_t const offLess = cchToReplace - cchActualReplacement; + + /* Pull the chars following the replacement area up. */ + memmove(&pExp->StrBuf.pszBuf[off + cchToReplace - offLess], + &pExp->StrBuf.pszBuf[off + cchToReplace], + pExp->StrBuf.cchBuf - off - cchToReplace + 1); + pExp->StrBuf.cchBuf -= offLess; + } + + /* + * Insert the replacement string. + */ + char *pszCur = &pExp->StrBuf.pszBuf[off]; + if (fLeadingSpace) + *pszCur++ = ' '; + memcpy(pszCur, pchReplacement, cchReplacement); + if (fTrailingSpace) + *pszCur++ = ' '; + + Assert(strlen(pExp->StrBuf.pszBuf) == pExp->StrBuf.cchBuf); + + return RTEXITCODE_SUCCESS; +} + + +static unsigned vbcppMacroExpandPeekCh(PVBCPPMACROEXP pExp, size_t *poff) +{ + size_t off = *poff; + if (off >= pExp->StrBuf.cchBuf) + return pExp->pStrmInput ? ScmStreamPeekCh(pExp->pStrmInput) : ~(unsigned)0; + return pExp->StrBuf.pszBuf[off]; +} + + +static unsigned vbcppMacroExpandGetCh(PVBCPPMACROEXP pExp, size_t *poff) +{ + size_t off = *poff; + if (off >= pExp->StrBuf.cchBuf) + return pExp->pStrmInput ? ScmStreamGetCh(pExp->pStrmInput) : ~(unsigned)0; + *poff = off + 1; + return pExp->StrBuf.pszBuf[off]; +} + + +static RTEXITCODE vbcppMacroExpandSkipEolEx(PVBCPP pThis, PVBCPPMACROEXP pExp, size_t *poff, unsigned chFirst) +{ + if (chFirst == '\r') + { + unsigned ch2 = vbcppMacroExpandPeekCh(pExp, poff); + if (ch2 == '\n') + { + ch2 = ScmStreamGetCh(pExp->pStrmInput); + AssertReturn(ch2 == '\n', vbcppError(pThis, "internal error")); + } + } + return RTEXITCODE_SUCCESS; +} + + +static RTEXITCODE vbcppMacroExpandSkipEol(PVBCPP pThis, PVBCPPMACROEXP pExp, size_t *poff) +{ + unsigned ch = vbcppMacroExpandGetCh(pExp, poff); + AssertReturn(ch == '\r' || ch == '\n', vbcppError(pThis, "internal error")); + return vbcppMacroExpandSkipEolEx(pThis, pExp, poff, ch); +} + + +static RTEXITCODE vbcppMacroExpandSkipCommentLine(PVBCPP pThis, PVBCPPMACROEXP pExp, size_t *poff) +{ + unsigned ch = vbcppMacroExpandGetCh(pExp, poff); + AssertReturn(ch == '/', vbcppError(pThis, "Internal error - expected '/' got '%c'", ch)); + + unsigned chPrev = 0; + while ((ch = vbcppMacroExpandGetCh(pExp, poff)) != ~(unsigned)0) + { + if (ch == '\r' || ch == '\n') + { + RTEXITCODE rcExit = vbcppMacroExpandSkipEolEx(pThis, pExp, poff, ch); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + if (chPrev != '\\') + break; + } + + chPrev = ch; + } + return RTEXITCODE_SUCCESS; +} + + +static RTEXITCODE vbcppMacroExpandSkipComment(PVBCPP pThis, PVBCPPMACROEXP pExp, size_t *poff) +{ + unsigned ch = vbcppMacroExpandGetCh(pExp, poff); + AssertReturn(ch == '*', vbcppError(pThis, "Internal error - expected '*' got '%c'", ch)); + + unsigned chPrev2 = 0; + unsigned chPrev = 0; + while ((ch = vbcppMacroExpandGetCh(pExp, poff)) != ~(unsigned)0) + { + if (ch == '/' && chPrev == '*') + break; + + if (ch == '\r' || ch == '\n') + { + RTEXITCODE rcExit = vbcppMacroExpandSkipEolEx(pThis, pExp, poff, ch); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + if (chPrev == '\\') + { + chPrev = chPrev2; /* for line splicing */ + continue; + } + } + + chPrev2 = chPrev; + chPrev = ch; + } + return RTEXITCODE_SUCCESS; +} + + +static RTEXITCODE vbcppMacroExpandGrowArgArray(PVBCPP pThis, PVBCPPMACROEXP pExp, uint32_t cMinArgs) +{ + if (cMinArgs > pExp->cArgsAlloced) + { + void *pv = RTMemRealloc(pExp->papszArgs, cMinArgs * sizeof(char *)); + if (!pv) + return vbcppError(pThis, "out of memory"); + pExp->papszArgs = (char **)pv; + pExp->cArgsAlloced = cMinArgs; + } + return RTEXITCODE_SUCCESS; +} + + +static RTEXITCODE vbcppMacroExpandAddEmptyParameter(PVBCPP pThis, PVBCPPMACROEXP pExp) +{ + RTEXITCODE rcExit = vbcppMacroExpandGrowArgArray(pThis, pExp, pExp->cArgs + 1); + if (rcExit == RTEXITCODE_SUCCESS) + { + char *pszArg = (char *)RTMemAllocZ(1); + if (pszArg) + pExp->papszArgs[pExp->cArgs++] = pszArg; + else + rcExit = vbcppError(pThis, "out of memory"); + } + return rcExit; +} + + +static RTEXITCODE vbcppMacroExpandGatherParameters(PVBCPP pThis, PVBCPPMACROEXP pExp, size_t *poff, uint32_t cArgsHint) +{ + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + + /* + * Free previous argument values. + */ + while (pExp->cArgs > 0) + { + RTMemFree(pExp->papszArgs[--pExp->cArgs]); + pExp->papszArgs[pExp->cArgs] = NULL; + } + + /* + * The current character should be an opening parenthsis. + */ + unsigned ch = vbcppMacroExpandGetCh(pExp, poff); + if (ch != '(') + return vbcppError(pThis, "Internal error - expected '(', found '%c' (#x)", ch, ch); + + /* + * Parse the argument list. + */ + char chQuote = 0; + size_t cbArgAlloc = 0; + size_t cchArg = 0; + char *pszArg = NULL; + size_t cParentheses = 1; + unsigned chPrev = 0; + while ((ch = vbcppMacroExpandGetCh(pExp, poff)) != ~(unsigned)0) + { +/** @todo check for '#directives'! */ + if (ch == ')' && !chQuote) + { + Assert(cParentheses >= 1); + cParentheses--; + + /* The end? */ + if (!cParentheses) + { + if (cchArg) + while (cchArg > 0 && RT_C_IS_SPACE(pszArg[cchArg - 1])) + pszArg[--cchArg] = '\0'; + else if (pExp->cArgs || cArgsHint > 0) + rcExit = vbcppMacroExpandAddEmptyParameter(pThis, pExp); + break; + } + } + else if (ch == '(' && !chQuote) + cParentheses++; + else if (ch == ',' && cParentheses == 1 && !chQuote) + { + /* End of one argument, start of the next. */ + if (cchArg) + while (cchArg > 0 && RT_C_IS_SPACE(pszArg[cchArg - 1])) + pszArg[--cchArg] = '\0'; + else + { + rcExit = vbcppMacroExpandAddEmptyParameter(pThis, pExp); + if (rcExit != RTEXITCODE_SUCCESS) + break; + } + + cbArgAlloc = 0; + cchArg = 0; + pszArg = NULL; + continue; + } + else if (ch == '/' && !chQuote) + { + /* Comment? */ + unsigned ch2 = vbcppMacroExpandPeekCh(pExp, poff); + /** @todo This ain't right wrt line splicing. */ + if (ch2 == '/' || ch == '*') + { + if (ch2 == '/') + rcExit = vbcppMacroExpandSkipCommentLine(pThis, pExp, poff); + else + rcExit = vbcppMacroExpandSkipComment(pThis, pExp, poff); + if (rcExit != RTEXITCODE_SUCCESS) + break; + continue; + } + } + else if (ch == '"') + { + if (!chQuote) + chQuote = '"'; + else if (chPrev != '\\') + chQuote = 0; + } + else if (ch == '\'') + { + if (!chQuote) + chQuote = '\''; + else if (chPrev != '\\') + chQuote = 0; + } + else if (ch == '\\') + { + /* Splice lines? */ + unsigned ch2 = vbcppMacroExpandPeekCh(pExp, poff); + if (ch2 == '\r' || ch2 == '\n') + { + rcExit = vbcppMacroExpandSkipEol(pThis, pExp, poff); + if (rcExit != RTEXITCODE_SUCCESS) + break; + continue; + } + } + else if (cchArg == 0 && RT_C_IS_SPACE(ch)) + continue; /* ignore spaces leading up to an argument value */ + + /* Append the character to the argument value, adding the argument + to the output array if it's first character in it. */ + if (cchArg + 1 >= cbArgAlloc) + { + /* Add argument to the vector. */ + if (!cchArg) + { + rcExit = vbcppMacroExpandGrowArgArray(pThis, pExp, RT_MAX(pExp->cArgs + 1, cArgsHint)); + if (rcExit != RTEXITCODE_SUCCESS) + break; + pExp->papszArgs[pExp->cArgs++] = pszArg; + } + + /* Resize the argument value buffer. */ + cbArgAlloc = cbArgAlloc ? cbArgAlloc * 2 : 16; + pszArg = (char *)RTMemRealloc(pszArg, cbArgAlloc); + if (!pszArg) + { + rcExit = vbcppError(pThis, "out of memory"); + break; + } + pExp->papszArgs[pExp->cArgs - 1] = pszArg; + } + + pszArg[cchArg++] = ch; + pszArg[cchArg] = '\0'; + } + + /* + * Check that we're leaving on good terms. + */ + if (rcExit == RTEXITCODE_SUCCESS) + { + if (cParentheses) + rcExit = vbcppError(pThis, "Missing ')'"); + } + + return rcExit; +} + + +/** + * Expands the arguments referenced in the macro value. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + msg. + * @param pThis The C preprocessor instance. + * @param pExp The expansion context. + * @param pMacro The macro. Must be a function macro. + * @param pStrBuf String buffer containing the result. The caller + * should initialize and destroy this! + */ +static RTEXITCODE vbcppMacroExpandValueWithArguments(PVBCPP pThis, PVBCPPMACROEXP pExp, PVBCPPMACRO pMacro, + PVBCPPSTRBUF pStrBuf) +{ + Assert(pMacro->fFunction); + + /* + * Empty? + */ + if ( !pMacro->cchValue + || (pMacro->cchValue == 1 && pMacro->szValue[0] == '#')) + return RTEXITCODE_SUCCESS; + + /* + * Parse the value. + */ + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + const char *pszSrc = pMacro->szValue; + const char *pszSrcSeq; + char ch; + while ((ch = *pszSrc++) != '\0') + { + Assert(ch != '\r'); Assert(ch != '\n'); /* probably not true atm. */ + if (ch == '#') + { + if (*pszSrc == '#') + { + /* Concatenate operator. */ + rcExit = vbcppError(pThis, "The '##' operatore is not yet implemented"); + } + else + { + /* Stringify macro argument. */ + rcExit = vbcppError(pThis, "The '#' operatore is not yet implemented"); + } + return rcExit; + } + else if (ch == '"') + { + /* String litteral. */ + pszSrcSeq = pszSrc - 1; + while ((ch = *pszSrc++) != '"') + { + if (ch == '\\') + ch = *pszSrc++; + if (ch == '\0') + { + rcExit = vbcppError(pThis, "String litteral is missing closing quote (\")."); + break; + } + } + rcExit = vbcppStrBufAppendN(pStrBuf, pszSrcSeq, pszSrc - pszSrcSeq); + } + else if (ch == '\'') + { + /* Character constant. */ + pszSrcSeq = pszSrc - 1; + while ((ch = *pszSrc++) != '\'') + { + if (ch == '\\') + ch = *pszSrc++; + if (ch == '\0') + { + rcExit = vbcppError(pThis, "Character constant is missing closing quote (')."); + break; + } + } + rcExit = vbcppStrBufAppendN(pStrBuf, pszSrcSeq, pszSrc - pszSrcSeq); + } + else if (RT_C_IS_DIGIT(ch)) + { + /* Process numerical constants correctly (i.e. don't mess with the suffix). */ + pszSrcSeq = pszSrc - 1; + while ( (ch = *pszSrc) != '\0' + && ( vbcppIsCIdentifierChar(ch) + || ch == '.') ) + pszSrc++; + rcExit = vbcppStrBufAppendN(pStrBuf, pszSrcSeq, pszSrc - pszSrcSeq); + } + else if (RT_C_IS_SPACE(ch)) + { + /* join spaces */ + if (RT_C_IS_SPACE(vbcppStrBufLastCh(pStrBuf))) + continue; + rcExit = vbcppStrBufAppendCh(pStrBuf, ch); + } + else if (vbcppIsCIdentifierLeadChar(ch)) + { + /* Something we should replace? */ + pszSrcSeq = pszSrc - 1; + while ( (ch = *pszSrc) != '\0' + && vbcppIsCIdentifierChar(ch)) + pszSrc++; + size_t cchDefine = pszSrc - pszSrcSeq; + uint32_t iArg; + if ( VBCPP_BITMAP_IS_SET(pMacro->bmArgs, *pszSrcSeq) + && (iArg = vbcppMacroLookupArg(pMacro, pszSrcSeq, cchDefine)) != UINT32_MAX) + { + /** @todo check out spaces here! */ + if (iArg < pMacro->cArgs) + { + Assert(iArg < pExp->cArgs); + rcExit = vbcppStrBufAppend(pStrBuf, pExp->papszArgs[iArg]); + if (*pExp->papszArgs[iArg] != '\0' && rcExit == RTEXITCODE_SUCCESS) + rcExit = vbcppStrBufAppendCh(pStrBuf, ' '); + } + else + { + /* __VA_ARGS__ */ + if (iArg < pExp->cArgs) + { + for (;;) + { + rcExit = vbcppStrBufAppend(pStrBuf, pExp->papszArgs[iArg]); + if (rcExit != RTEXITCODE_SUCCESS) + break; + iArg++; + if (iArg >= pExp->cArgs) + break; + rcExit = vbcppStrBufAppendCh(pStrBuf, ','); + if (rcExit != RTEXITCODE_SUCCESS) + break; + } + } + if (rcExit == RTEXITCODE_SUCCESS) + rcExit = vbcppStrBufAppendCh(pStrBuf, ' '); + } + } + /* Not an argument needing replacing. */ + else + rcExit = vbcppStrBufAppendN(pStrBuf, pszSrcSeq, cchDefine); + } + else + { + rcExit = vbcppStrBufAppendCh(pStrBuf, ch); + } + } + + return rcExit; +} + + + +/** + * Expands the given macro. + * + * Caller already checked if a function macro should be expanded, i.e. whether + * there is a parameter list. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + msg. + * @param pThis The C preprocessor instance. + * @param pExp The expansion context. + * @param offMacro Offset into the expansion buffer of the macro + * invocation. + * @param pMacro The macro. + * @param offParameters The start of the parameter list if applicable. + * Ignored if not function macro. If the + * parameter list starts at the current stream + * position shall be at the end of the expansion + * buffer. + */ +static RTEXITCODE vbcppMacroExpandIt(PVBCPP pThis, PVBCPPMACROEXP pExp, size_t offMacro, PVBCPPMACRO pMacro, + size_t offParameters) +{ + RTEXITCODE rcExit; + Assert(offMacro + pMacro->Core.cchString <= pExp->StrBuf.cchBuf); + Assert(!pMacro->fExpanding); + + /* + * Function macros are kind of difficult... + */ + if (pMacro->fFunction) + { + rcExit = vbcppMacroExpandGatherParameters(pThis, pExp, &offParameters, pMacro->cArgs + pMacro->fVarArg); + if (rcExit == RTEXITCODE_SUCCESS) + { + if (pExp->cArgs > pMacro->cArgs && !pMacro->fVarArg) + rcExit = vbcppError(pThis, "Too many arguments to macro '%s' - found %u, expected %u", + pMacro->Core.pszString, pExp->cArgs, pMacro->cArgs); + else if (pExp->cArgs < pMacro->cArgs) + rcExit = vbcppError(pThis, "Too few arguments to macro '%s' - found %u, expected %u", + pMacro->Core.pszString, pExp->cArgs, pMacro->cArgs); + } + if (rcExit == RTEXITCODE_SUCCESS) + { + VBCPPSTRBUF ValueBuf; + vbcppStrBufInit(&ValueBuf, pThis); + rcExit = vbcppMacroExpandValueWithArguments(pThis, pExp, pMacro, &ValueBuf); + if (rcExit == RTEXITCODE_SUCCESS) + rcExit = vbcppMacroExpandReplace(pThis, pExp, offMacro, offParameters - offMacro, + ValueBuf.pszBuf, ValueBuf.cchBuf); + vbcppStrBufDelete(&ValueBuf); + } + } + /* + * Object-like macros are easy. :-) + */ + else + rcExit = vbcppMacroExpandReplace(pThis, pExp, offMacro, pMacro->Core.cchString, pMacro->szValue, pMacro->cchValue); + if (rcExit == RTEXITCODE_SUCCESS) + { +#if 0 /* wrong */ + /* + * Push the macro onto the stack. + */ + pMacro->fExpanding = true; + pMacro->pUpExpanding = pExp->pMacroStack; + pExp->pMacroStack = pMacro; +#endif + } + + return rcExit; +} + + +/** + * Looks for a left parenthesis in the macro expansion buffer and the input + * stream. + * + * @retval true if found. The stream position at opening parenthesis. + * @retval false if not found. The stream position is unchanged. + * + * @param pThis The C preprocessor instance. + * @param pExp The expansion context. + * @param poff The current offset in the expansion context. + * Will be updated on success. + * + * @sa vbcppInputLookForLeftParenthesis + */ +static bool vbcppMacroExpandLookForLeftParenthesis(PVBCPP pThis, PVBCPPMACROEXP pExp, size_t *poff) +{ + /* + * Search the buffer first. (No comments there.) + */ + size_t off = *poff; + while (off < pExp->StrBuf.cchBuf) + { + char ch = pExp->StrBuf.pszBuf[off]; + if (!RT_C_IS_SPACE(ch)) + { + if (ch == '(') + { + *poff = off; + return true; + } + return false; + } + off++; + } + + /* + * Reached the end of the buffer, continue searching in the stream. + */ + PSCMSTREAM pStrmInput = pExp->pStrmInput; + size_t offSaved = ScmStreamTell(pStrmInput); + /*RTEXITCODE rcExit = */ vbcppProcessSkipWhiteEscapedEolAndComments(pThis, pStrmInput); + unsigned ch = ScmStreamPeekCh(pStrmInput); + if (ch == '(') + { + *poff = pExp->StrBuf.cchBuf; + return true; + } + + int rc = ScmStreamSeekAbsolute(pStrmInput, offSaved); + AssertFatalRC(rc); + return false; +} + + +/** + * Implements the 'defined' unary operator for \#if and \#elif expressions. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + msg. + * @param pThis The C preprocessor instance. + * @param pExp The expansion context. + * @param offStart The expansion buffer offset where the 'defined' + * occurs. + * @param poff Where to store the offset at which the re-scan + * shall resume upon return. + */ +static RTEXITCODE vbcppMacroExpandDefinedOperator(PVBCPP pThis, PVBCPPMACROEXP pExp, size_t offStart, size_t *poff) +{ + Assert(!pExp->pStrmInput); /* offset usage below. */ + + /* + * Skip white space. + */ + unsigned ch; + while ((ch = vbcppMacroExpandGetCh(pExp, poff)) != ~(unsigned)0) + if (!RT_C_IS_SPACE(ch)) + break; + bool const fWithParenthesis = ch == '('; + if (fWithParenthesis) + while ((ch = vbcppMacroExpandGetCh(pExp, poff)) != ~(unsigned)0) + if (!RT_C_IS_SPACE(ch)) + break; + + /* + * Macro identifier. + */ + if (!vbcppIsCIdentifierLeadChar(ch)) + return vbcppError(pThis, "Expected macro name after 'defined' operator"); + + size_t const offDefine = *poff - 1; + while ((ch = vbcppMacroExpandGetCh(pExp, poff)) != ~(unsigned)0) + if (!vbcppIsCIdentifierChar(ch)) + break; + size_t const cchDefine = *poff - offDefine - 1; + + /* + * Check for closing parenthesis. + */ + if (fWithParenthesis) + { + while (RT_C_IS_SPACE(ch)) + ch = vbcppMacroExpandGetCh(pExp, poff); + if (ch != ')') + return vbcppError(pThis, "Expected closing parenthesis after macro name"); + } + + /* + * Do the job. + */ + const char *pszResult = vbcppMacroExists(pThis, &pExp->StrBuf.pszBuf[offDefine], cchDefine) + ? "1" : "0"; + RTEXITCODE rcExit = vbcppMacroExpandReplace(pThis, pExp, offStart, *poff - offStart, pszResult, 1); + *poff = offStart + 1; + return rcExit; +} + + +/** + * Re-scan the expanded macro. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + msg. + * @param pThis The C preprocessor instance. + * @param pExp The expansion context. + * @param enmMode The re-scan mode. + * @param pcReplacements Where to return the number of replacements + * performed. Optional. + */ +static RTEXITCODE vbcppMacroExpandReScan(PVBCPP pThis, PVBCPPMACROEXP pExp, VBCPPMACRORESCANMODE enmMode, size_t *pcReplacements) +{ + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + size_t cReplacements = 0; + size_t off = 0; + unsigned ch; + while ( off < pExp->StrBuf.cchBuf + && (ch = vbcppMacroExpandGetCh(pExp, &off)) != ~(unsigned)0) + { + /* + * String litteral or character constant. + */ + if (ch == '\'' || ch == '"') + { + unsigned const chEndQuote = ch; + while ( off < pExp->StrBuf.cchBuf + && (ch = vbcppMacroExpandGetCh(pExp, &off)) != ~(unsigned)0) + { + if (ch == '\\') + { + ch = vbcppMacroExpandGetCh(pExp, &off); + if (ch == ~(unsigned)0) + break; + } + else if (ch == chEndQuote) + break; + } + if (ch == ~(unsigned)0) + return vbcppError(pThis, "Missing end quote (%c)", chEndQuote); + } + /* + * Number constant. + */ + else if ( RT_C_IS_DIGIT(ch) + || ( ch == '.' + && off + 1 < pExp->StrBuf.cchBuf + && RT_C_IS_DIGIT(vbcppMacroExpandPeekCh(pExp, &off)) + ) + ) + { + while ( off < pExp->StrBuf.cchBuf + && (ch = vbcppMacroExpandPeekCh(pExp, &off)) != ~(unsigned)0 + && vbcppIsCIdentifierChar(ch) ) + vbcppMacroExpandGetCh(pExp, &off); + } + /* + * Something that can be replaced? + */ + else if (vbcppIsCIdentifierLeadChar(ch)) + { + size_t offDefine = off - 1; + while ( off < pExp->StrBuf.cchBuf + && (ch = vbcppMacroExpandPeekCh(pExp, &off)) != ~(unsigned)0 + && vbcppIsCIdentifierChar(ch) ) + vbcppMacroExpandGetCh(pExp, &off); + size_t cchDefine = off - offDefine; + + PVBCPPMACRO pMacro = vbcppMacroLookup(pThis, &pExp->StrBuf.pszBuf[offDefine], cchDefine); + if ( pMacro + && ( !pMacro->fFunction + || vbcppMacroExpandLookForLeftParenthesis(pThis, pExp, &off)) ) + { + cReplacements++; + rcExit = vbcppMacroExpandIt(pThis, pExp, offDefine, pMacro, off); + off = offDefine; + } + else + { + if ( !pMacro + && enmMode == kMacroReScanMode_Expression + && cchDefine == sizeof("defined") - 1 + && !strncmp(&pExp->StrBuf.pszBuf[offDefine], "defined", cchDefine)) + { + cReplacements++; + rcExit = vbcppMacroExpandDefinedOperator(pThis, pExp, offDefine, &off); + } + else + off = offDefine + cchDefine; + } + } + else + { + Assert(RT_C_IS_SPACE(ch) || RT_C_IS_PUNCT(ch)); + Assert(ch != '\r' && ch != '\n'); + } + } + + if (pcReplacements) + *pcReplacements = cReplacements; + return rcExit; +} + + +/** + * Cleans up the expansion context. + * + * This involves clearing VBCPPMACRO::fExpanding and VBCPPMACRO::pUpExpanding, + * and freeing the memory resources associated with the expansion context. + * + * @param pExp The expansion context. + */ +static void vbcppMacroExpandCleanup(PVBCPPMACROEXP pExp) +{ +#if 0 + while (pExp->pMacroStack) + { + PVBCPPMACRO pMacro = pExp->pMacroStack; + pExp->pMacroStack = pMacro->pUpExpanding; + + pMacro->fExpanding = false; + pMacro->pUpExpanding = NULL; + } +#endif + + while (pExp->cArgs > 0) + { + RTMemFree(pExp->papszArgs[--pExp->cArgs]); + pExp->papszArgs[pExp->cArgs] = NULL; + } + + RTMemFree(pExp->papszArgs); + pExp->papszArgs = NULL; + + vbcppStrBufDelete(&pExp->StrBuf); +} + + + +/** + * Frees a define. + * + * @returns VINF_SUCCESS (used when called by RTStrSpaceDestroy) + * @param pStr Pointer to the VBCPPMACRO::Core member. + * @param pvUser Unused. + */ +static DECLCALLBACK(int) vbcppMacroFree(PRTSTRSPACECORE pStr, void *pvUser) +{ + RTMemFree(pStr); + NOREF(pvUser); + return VINF_SUCCESS; +} + + +/** + * Removes a define. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + msg. + * @param pThis The C preprocessor instance. + * @param pszDefine The define name, no argument list or anything. + * @param cchDefine The length of the name. RTSTR_MAX is ok. + * @param fExplicitUndef Explicit undefinition, that is, in a selective + * preprocessing run it will evaluate to undefined. + */ +static RTEXITCODE vbcppMacroUndef(PVBCPP pThis, const char *pszDefine, size_t cchDefine, bool fExplicitUndef) +{ + PRTSTRSPACECORE pHit = RTStrSpaceGetN(&pThis->StrSpace, pszDefine, cchDefine); + if (pHit) + { + RTStrSpaceRemove(&pThis->StrSpace, pHit->pszString); + vbcppMacroFree(pHit, NULL); + } + + if (fExplicitUndef) + { + if (cchDefine == RTSTR_MAX) + cchDefine = strlen(pszDefine); + + PRTSTRSPACECORE pStr = (PRTSTRSPACECORE)RTMemAlloc(sizeof(*pStr) + cchDefine + 1); + if (!pStr) + return vbcppError(pThis, "out of memory"); + char *pszDst = (char *)(pStr + 1); + pStr->pszString = pszDst; + memcpy(pszDst, pszDefine, cchDefine); + pszDst[cchDefine] = '\0'; + if (!RTStrSpaceInsert(&pThis->UndefStrSpace, pStr)) + RTMemFree(pStr); + } + + return RTEXITCODE_SUCCESS; +} + + +/** + * Inserts a define (rejecting and freeing it in some case). + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + msg. + * @param pThis The C preprocessor instance. + * @param pMacro The define to insert. + */ +static RTEXITCODE vbcppMacroInsert(PVBCPP pThis, PVBCPPMACRO pMacro) +{ + /* + * Reject illegal macro names. + */ + if (!strcmp(pMacro->Core.pszString, "defined")) + { + RTEXITCODE rcExit = vbcppError(pThis, "Cannot use '%s' as a macro name", pMacro->Core.pszString); + vbcppMacroFree(&pMacro->Core, NULL); + return rcExit; + } + + /* + * Ignore in source-file defines when doing selective preprocessing. + */ + if ( !pThis->fRespectSourceDefines + && !pMacro->fCmdLine) + { + /* Ignore*/ + vbcppMacroFree(&pMacro->Core, NULL); + return RTEXITCODE_SUCCESS; + } + + /* + * Insert it and update the lead character hint bitmap. + */ + if (RTStrSpaceInsert(&pThis->StrSpace, &pMacro->Core)) + VBCPP_BITMAP_SET(pThis->bmDefined, *pMacro->Core.pszString); + else + { + /* + * Duplicate. When doing selective D preprocessing, let the command + * line take precendece. + */ + PVBCPPMACRO pOld = (PVBCPPMACRO)RTStrSpaceGet(&pThis->StrSpace, pMacro->Core.pszString); Assert(pOld); + if ( pThis->fAllowRedefiningCmdLineDefines + || pMacro->fCmdLine == pOld->fCmdLine) + { + if (pMacro->fCmdLine) + RTMsgWarning("Redefining '%s'", pMacro->Core.pszString); + + RTStrSpaceRemove(&pThis->StrSpace, pOld->Core.pszString); + vbcppMacroFree(&pOld->Core, NULL); + + bool fRc = RTStrSpaceInsert(&pThis->StrSpace, &pMacro->Core); + Assert(fRc); NOREF(fRc); + } + else + { + RTMsgWarning("Ignoring redefinition of '%s'", pMacro->Core.pszString); + vbcppMacroFree(&pMacro->Core, NULL); + } + } + + return RTEXITCODE_SUCCESS; +} + + +/** + * Adds a define. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + msg. + * @param pThis The C preprocessor instance. + * @param pszDefine The define name, no parameter list. + * @param cchDefine The length of the name. + * @param pszParams The parameter list. + * @param cchParams The length of the parameter list. + * @param pszValue The value. + * @param cchDefine The length of the value. + * @param fCmdLine Set if originating on the command line. + */ +static RTEXITCODE vbcppMacroAddFn(PVBCPP pThis, const char *pszDefine, size_t cchDefine, + const char *pszParams, size_t cchParams, + const char *pszValue, size_t cchValue, + bool fCmdLine) + +{ + Assert(RTStrNLen(pszDefine, cchDefine) == cchDefine); + Assert(RTStrNLen(pszParams, cchParams) == cchParams); + Assert(RTStrNLen(pszValue, cchValue) == cchValue); + + /* + * Determin the number of arguments and how much space their names + * requires. Performing syntax validation while parsing. + */ + uint32_t cchArgNames = 0; + uint32_t cArgs = 0; + for (size_t off = 0; off < cchParams; off++) + { + /* Skip blanks and maybe one comma. */ + bool fIgnoreComma = cArgs != 0; + while (off < cchParams) + { + if (!RT_C_IS_SPACE(pszParams[off])) + { + if (pszParams[off] != ',' || !fIgnoreComma) + { + if (vbcppIsCIdentifierLeadChar(pszParams[off])) + break; + /** @todo variadic macros. */ + return vbcppErrorPos(pThis, &pszParams[off], "Unexpected character"); + } + fIgnoreComma = false; + } + off++; + } + if (off >= cchParams) + break; + + /* Found and argument. First character is already validated. */ + cArgs++; + cchArgNames += 2; + off++; + while ( off < cchParams + && vbcppIsCIdentifierChar(pszParams[off])) + off++, cchArgNames++; + } + + /* + * Allocate a structure. + */ + size_t cbDef = RT_UOFFSETOF_DYN(VBCPPMACRO, szValue[cchValue + 1 + cchDefine + 1 + cchArgNames]) + + sizeof(const char *) * cArgs; + cbDef = RT_ALIGN_Z(cbDef, sizeof(const char *)); + PVBCPPMACRO pMacro = (PVBCPPMACRO)RTMemAlloc(cbDef); + if (!pMacro) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "out of memory"); + + char *pszDst = &pMacro->szValue[cchValue + 1]; + pMacro->Core.pszString = pszDst; + memcpy(pszDst, pszDefine, cchDefine); + pszDst += cchDefine; + *pszDst++ = '\0'; + pMacro->fFunction = true; + pMacro->fVarArg = false; + pMacro->fCmdLine = fCmdLine; + pMacro->fExpanding = false; + pMacro->cArgs = cArgs; + pMacro->papszArgs = (const char **)((uintptr_t)pMacro + cbDef - sizeof(const char *) * cArgs); + VBCPP_BITMAP_EMPTY(pMacro->bmArgs); + pMacro->cchValue = cchValue; + memcpy(pMacro->szValue, pszValue, cchValue); + pMacro->szValue[cchValue] = '\0'; + + /* + * Set up the arguments. + */ + uint32_t iArg = 0; + for (size_t off = 0; off < cchParams; off++) + { + /* Skip blanks and maybe one comma. */ + bool fIgnoreComma = cArgs != 0; + while (off < cchParams) + { + if (!RT_C_IS_SPACE(pszParams[off])) + { + if (pszParams[off] != ',' || !fIgnoreComma) + break; + fIgnoreComma = false; + } + off++; + } + if (off >= cchParams) + break; + + /* Found and argument. First character is already validated. */ + VBCPP_BITMAP_SET(pMacro->bmArgs, pszParams[off]); + pMacro->papszArgs[iArg] = pszDst; + do + { + *pszDst++ = pszParams[off++]; + } while ( off < cchParams + && vbcppIsCIdentifierChar(pszParams[off])); + *pszDst++ = '\0'; + iArg++; + } + Assert((uintptr_t)pszDst <= (uintptr_t)pMacro->papszArgs); + + return vbcppMacroInsert(pThis, pMacro); +} + + +/** + * Adds a define. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + msg. + * @param pThis The C preprocessor instance. + * @param pszDefine The define name and optionally the argument + * list. + * @param cchDefine The length of the name. RTSTR_MAX is ok. + * @param pszValue The value. + * @param cchDefine The length of the value. RTSTR_MAX is ok. + * @param fCmdLine Set if originating on the command line. + */ +static RTEXITCODE vbcppMacroAdd(PVBCPP pThis, const char *pszDefine, size_t cchDefine, + const char *pszValue, size_t cchValue, bool fCmdLine) +{ + /* + * We need the lengths. Trim the input. + */ + if (cchDefine == RTSTR_MAX) + cchDefine = strlen(pszDefine); + while (cchDefine > 0 && RT_C_IS_SPACE(*pszDefine)) + pszDefine++, cchDefine--; + while (cchDefine > 0 && RT_C_IS_SPACE(pszDefine[cchDefine - 1])) + cchDefine--; + if (!cchDefine) + return vbcppErrorPos(pThis, pszDefine, "The define has no name"); + + if (cchValue == RTSTR_MAX) + cchValue = strlen(pszValue); + while (cchValue > 0 && RT_C_IS_SPACE(*pszValue)) + pszValue++, cchValue--; + while (cchValue > 0 && RT_C_IS_SPACE(pszValue[cchValue - 1])) + cchValue--; + + /* + * Arguments make the job a bit more annoying. Handle that elsewhere + */ + const char *pszParams = (const char *)memchr(pszDefine, '(', cchDefine); + if (pszParams) + { + size_t cchParams = pszDefine + cchDefine - pszParams; + cchDefine -= cchParams; + if (!vbcppValidateCIdentifier(pThis, pszDefine, cchDefine)) + return RTEXITCODE_FAILURE; + if (pszParams[cchParams - 1] != ')') + return vbcppErrorPos(pThis, pszParams + cchParams - 1, "Missing closing parenthesis"); + pszParams++; + cchParams -= 2; + return vbcppMacroAddFn(pThis, pszDefine, cchDefine, pszParams, cchParams, pszValue, cchValue, fCmdLine); + } + + /* + * Simple define, no arguments. + */ + if (!vbcppValidateCIdentifier(pThis, pszDefine, cchDefine)) + return RTEXITCODE_FAILURE; + + PVBCPPMACRO pMacro = (PVBCPPMACRO)RTMemAlloc(RT_UOFFSETOF_DYN(VBCPPMACRO, szValue[cchValue + 1 + cchDefine + 1])); + if (!pMacro) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "out of memory"); + + pMacro->Core.pszString = &pMacro->szValue[cchValue + 1]; + memcpy((char *)pMacro->Core.pszString, pszDefine, cchDefine); + ((char *)pMacro->Core.pszString)[cchDefine] = '\0'; + pMacro->fFunction = false; + pMacro->fVarArg = false; + pMacro->fCmdLine = fCmdLine; + pMacro->fExpanding = false; + pMacro->cArgs = 0; + pMacro->papszArgs = NULL; + VBCPP_BITMAP_EMPTY(pMacro->bmArgs); + pMacro->cchValue = cchValue; + memcpy(pMacro->szValue, pszValue, cchValue); + pMacro->szValue[cchValue] = '\0'; + + return vbcppMacroInsert(pThis, pMacro); +} + + +/** + * Tries to convert a define into an inline D constant. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pMacro The macro. + */ +static RTEXITCODE vbcppMacroTryConvertToInlineD(PVBCPP pThis, PVBCPPMACRO pMacro) +{ + AssertReturn(pMacro, vbcppError(pThis, "Internal error")); + if (pMacro->fFunction) + return RTEXITCODE_SUCCESS; + + /* + * Do some simple macro resolving. (Mostly to make x86.h work.) + */ + const char *pszDefine = pMacro->Core.pszString; + const char *pszValue = pMacro->szValue; + size_t cchValue = pMacro->cchValue; + + unsigned i = 0; + PVBCPPMACRO pMacro2; + while ( i < 10 + && cchValue > 0 + && vbcppIsCIdentifierLeadChar(*pszValue) + && (pMacro2 = vbcppMacroLookup(pThis, pszValue, cchValue)) != NULL + && !pMacro2->fFunction ) + { + pszValue = pMacro2->szValue; + cchValue = pMacro2->cchValue; + i++; + } + + if (!pMacro->cchValue) + return RTEXITCODE_SUCCESS; + + + /* + * A lone value? + */ + ssize_t cch = 0; + uint64_t u64; + char *pszNext; + int rc = RTStrToUInt64Ex(pszValue, &pszNext, 0, &u64); + if (RT_SUCCESS(rc)) + { + if ( rc == VWRN_TRAILING_SPACES + || rc == VWRN_NEGATIVE_UNSIGNED + || rc == VWRN_NUMBER_TOO_BIG) + return RTEXITCODE_SUCCESS; + const char *pszType; + if (rc == VWRN_TRAILING_CHARS) + { + if (!strcmp(pszNext, "u") || !strcmp(pszNext, "U")) + pszType = "uint32_t"; + else if (!strcmp(pszNext, "ul") || !strcmp(pszNext, "UL")) + pszType = "uintptr_t"; + else if (!strcmp(pszNext, "ull") || !strcmp(pszNext, "ULL")) + pszType = "uint64_t"; + else + pszType = NULL; + } + else if (u64 <= UINT8_MAX) + pszType = "uint8_t"; + else if (u64 <= UINT16_MAX) + pszType = "uint16_t"; + else if (u64 <= UINT32_MAX) + pszType = "uint32_t"; + else + pszType = "uint64_t"; + if (!pszType) + return RTEXITCODE_SUCCESS; + cch = ScmStreamPrintf(&pThis->StrmOutput, "inline %s %s = %.*s;\n", + pszType, pszDefine, pszNext - pszValue, pszValue); + } + /* + * A value wrapped in a constant macro? + */ + else if ( (pszNext = (char *)strchr(pszValue, '(')) != NULL + && pszValue[cchValue - 1] == ')' ) + { + size_t cchPrefix = pszNext - pszValue; + size_t cchInnerValue = cchValue - cchPrefix - 2; + const char *pchInnerValue = &pszValue[cchPrefix + 1]; + while (cchInnerValue > 0 && RT_C_IS_SPACE(*pchInnerValue)) + cchInnerValue--, pchInnerValue++; + while (cchInnerValue > 0 && RT_C_IS_SPACE(pchInnerValue[cchInnerValue - 1])) + cchInnerValue--; + if (!cchInnerValue || !RT_C_IS_XDIGIT(*pchInnerValue)) + return RTEXITCODE_SUCCESS; + + rc = RTStrToUInt64Ex(pchInnerValue, &pszNext, 0, &u64); + if ( RT_FAILURE(rc) + || rc == VWRN_TRAILING_SPACES + || rc == VWRN_NEGATIVE_UNSIGNED + || rc == VWRN_NUMBER_TOO_BIG) + return RTEXITCODE_SUCCESS; + + const char *pszType; +#define MY_MATCH_STR(a_sz) (sizeof(a_sz) - 1 == cchPrefix && !strncmp(pszValue, a_sz, sizeof(a_sz) - 1)) + if (MY_MATCH_STR("UINT8_C")) + pszType = "uint8_t"; + else if (MY_MATCH_STR("UINT16_C")) + pszType = "uint16_t"; + else if (MY_MATCH_STR("UINT32_C")) + pszType = "uint32_t"; + else if (MY_MATCH_STR("UINT64_C")) + pszType = "uint64_t"; + else + pszType = NULL; + if (pszType) + cch = ScmStreamPrintf(&pThis->StrmOutput, "inline %s %s = %.*s;\n", + pszType, pszDefine, cchInnerValue, pchInnerValue); + else if (MY_MATCH_STR("RT_BIT") || MY_MATCH_STR("RT_BIT_32")) + cch = ScmStreamPrintf(&pThis->StrmOutput, "inline uint32_t %s = 1U << %llu;\n", + pszDefine, u64); + else if (MY_MATCH_STR("RT_BIT_64")) + cch = ScmStreamPrintf(&pThis->StrmOutput, "inline uint64_t %s = 1ULL << %llu;\n", + pszDefine, u64); + else + return RTEXITCODE_SUCCESS; +#undef MY_MATCH_STR + } + /* Dunno what this is... */ + else + return RTEXITCODE_SUCCESS; + + /* + * Check for output error and clear the output suppression indicator. + */ + if (cch < 0) + return vbcppError(pThis, "Output error"); + + pThis->fJustDroppedLine = false; + return RTEXITCODE_SUCCESS; +} + + + +/** + * Processes a abbreviated line number directive. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + * @param offStart The stream position where the directive + * started (for pass thru). + */ +static RTEXITCODE vbcppDirectiveDefine(PVBCPP pThis, PSCMSTREAM pStrmInput, size_t offStart) +{ + RT_NOREF_PV(offStart); + + /* + * Parse it. + */ + RTEXITCODE rcExit = vbcppProcessSkipWhiteEscapedEolAndComments(pThis, pStrmInput); + if (rcExit == RTEXITCODE_SUCCESS) + { + size_t cchDefine; + const char *pchDefine = ScmStreamCGetWord(pStrmInput, &cchDefine); + if (pchDefine) + { + /* If it's a function style define, parse out the parameter list. */ + size_t cchParams = 0; + const char *pchParams = NULL; + unsigned ch = ScmStreamPeekCh(pStrmInput); + if (ch == '(') + { + ScmStreamGetCh(pStrmInput); + pchParams = ScmStreamGetCur(pStrmInput); + + unsigned chPrev = ch; + while ((ch = ScmStreamPeekCh(pStrmInput)) != ~(unsigned)0) + { + if (ch == '\r' || ch == '\n') + { + if (chPrev != '\\') + { + rcExit = vbcppError(pThis, "Missing ')'"); + break; + } + ScmStreamSeekByLine(pStrmInput, ScmStreamTellLine(pStrmInput) + 1); + } + if (ch == ')') + { + cchParams = ScmStreamGetCur(pStrmInput) - pchParams; + ScmStreamGetCh(pStrmInput); + break; + } + ScmStreamGetCh(pStrmInput); + } + } + /* The simple kind. */ + else if (!RT_C_IS_SPACE(ch) && ch != ~(unsigned)0) + rcExit = vbcppError(pThis, "Expected whitespace after macro name"); + + /* Parse out the value. */ + if (rcExit == RTEXITCODE_SUCCESS) + rcExit = vbcppProcessSkipWhiteEscapedEolAndComments(pThis, pStrmInput); + if (rcExit == RTEXITCODE_SUCCESS) + { + size_t offValue = ScmStreamTell(pStrmInput); + const char *pchValue = ScmStreamGetCur(pStrmInput); + unsigned chPrev = ch; + while ((ch = ScmStreamPeekCh(pStrmInput)) != ~(unsigned)0) + { + if (ch == '\r' || ch == '\n') + { + if (chPrev != '\\') + break; + ScmStreamSeekByLine(pStrmInput, ScmStreamTellLine(pStrmInput) + 1); + } + chPrev = ScmStreamGetCh(pStrmInput); + } + size_t cchValue = ScmStreamGetCur(pStrmInput) - pchValue; + + /* + * Execute. + */ + if (pchParams) + rcExit = vbcppMacroAddFn(pThis, pchDefine, cchDefine, pchParams, cchParams, pchValue, cchValue, false); + else + rcExit = vbcppMacroAdd(pThis, pchDefine, cchDefine, pchValue, cchValue, false); + + /* + * Pass thru? + */ + if ( rcExit == RTEXITCODE_SUCCESS + && pThis->fPassThruDefines) + { + unsigned cchIndent = pThis->pCondStack ? pThis->pCondStack->iKeepLevel : 0; + ssize_t cch; + if (pchParams) + cch = ScmStreamPrintf(&pThis->StrmOutput, "#%*sdefine %.*s(%.*s)", + cchIndent, "", cchDefine, pchDefine, cchParams, pchParams); + else + cch = ScmStreamPrintf(&pThis->StrmOutput, "#%*sdefine %.*s", + cchIndent, "", cchDefine, pchDefine); + if (cch > 0) + vbcppOutputComment(pThis, pStrmInput, offValue, cch, 1); + else + rcExit = vbcppError(pThis, "output error"); + } + else if ( rcExit == RTEXITCODE_SUCCESS + && pThis->enmMode == kVBCppMode_SelectiveD) + rcExit = vbcppMacroTryConvertToInlineD(pThis, vbcppMacroLookup(pThis, pchDefine, cchDefine)); + else + pThis->fJustDroppedLine = true; + } + } + } + return rcExit; +} + + +/** + * Processes a abbreviated line number directive. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + * @param offStart The stream position where the directive + * started (for pass thru). + */ +static RTEXITCODE vbcppDirectiveUndef(PVBCPP pThis, PSCMSTREAM pStrmInput, size_t offStart) +{ + RT_NOREF_PV(offStart); + + /* + * Parse it. + */ + RTEXITCODE rcExit = vbcppProcessSkipWhiteEscapedEolAndComments(pThis, pStrmInput); + if (rcExit == RTEXITCODE_SUCCESS) + { + size_t cchDefine; + const char *pchDefine = ScmStreamCGetWord(pStrmInput, &cchDefine); + if (pchDefine) + { + size_t offMaybeComment = vbcppProcessSkipWhite(pStrmInput); + rcExit = vbcppProcessSkipWhiteEscapedEolAndCommentsCheckEol(pThis, pStrmInput); + if (rcExit == RTEXITCODE_SUCCESS) + { + /* + * Take action. + */ + PVBCPPMACRO pMacro = vbcppMacroLookup(pThis, pchDefine, cchDefine); + if ( pMacro + && pThis->fRespectSourceDefines + && ( !pMacro->fCmdLine + || pThis->fAllowRedefiningCmdLineDefines ) ) + { + RTStrSpaceRemove(&pThis->StrSpace, pMacro->Core.pszString); + vbcppMacroFree(&pMacro->Core, NULL); + } + + /* + * Pass thru. + */ + if ( rcExit == RTEXITCODE_SUCCESS + && pThis->fPassThruDefines) + { + unsigned cchIndent = pThis->pCondStack ? pThis->pCondStack->iKeepLevel : 0; + ssize_t cch = ScmStreamPrintf(&pThis->StrmOutput, "#%*sundef %.*s", + cchIndent, "", cchDefine, pchDefine); + if (cch > 0) + vbcppOutputComment(pThis, pStrmInput, offMaybeComment, cch, 1); + else + rcExit = vbcppError(pThis, "output error"); + } + + } + } + else + rcExit = vbcppError(pThis, "Malformed #ifndef"); + } + return rcExit; + +} + + + + + +/* + * + * + * C O N D I T I O N A L S + * C O N D I T I O N A L S + * C O N D I T I O N A L S + * C O N D I T I O N A L S + * C O N D I T I O N A L S + * + * + */ + + +/** + * Combines current stack result with the one being pushed. + * + * @returns Combined result. + * @param enmEvalPush The result of the condition being pushed. + * @param enmEvalStack The current stack result. + */ +static VBCPPEVAL vbcppCondCombine(VBCPPEVAL enmEvalPush, VBCPPEVAL enmEvalStack) +{ + if (enmEvalStack == kVBCppEval_False) + return kVBCppEval_False; + return enmEvalPush; +} + + +/** + * Pushes an conditional onto the stack. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The current input stream. + * @param offStart Not currently used, using @a pchCondition and + * @a cchCondition instead. + * @param enmKind The kind of conditional. + * @param enmResult The result of the evaluation. + * @param pchCondition The raw condition. + * @param cchCondition The length of @a pchCondition. + */ +static RTEXITCODE vbcppCondPush(PVBCPP pThis, PSCMSTREAM pStrmInput, size_t offStart, + VBCPPCONDKIND enmKind, VBCPPEVAL enmResult, + const char *pchCondition, size_t cchCondition) +{ + RT_NOREF_PV(offStart); RT_NOREF_PV(pStrmInput); + + + if (pThis->cCondStackDepth >= _64K) + return vbcppError(pThis, "Too many nested #if/#ifdef/#ifndef statements"); + + /* + * Allocate a new entry and push it. + */ + PVBCPPCOND pCond = (PVBCPPCOND)RTMemAlloc(sizeof(*pCond)); + if (!pCond) + return vbcppError(pThis, "out of memory"); + + PVBCPPCOND pUp = pThis->pCondStack; + pCond->enmKind = enmKind; + pCond->enmResult = enmResult; + pCond->enmStackResult = pUp ? vbcppCondCombine(enmResult, pUp->enmStackResult) : enmResult; + pCond->fSeenElse = false; + pCond->fElIfDecided = enmResult == kVBCppEval_True; + pCond->iLevel = pThis->cCondStackDepth; + pCond->iKeepLevel = (pUp ? pUp->iKeepLevel : 0) + enmResult == kVBCppEval_Undecided; + pCond->pchCond = pchCondition; + pCond->cchCond = cchCondition; + + pCond->pUp = pThis->pCondStack; + pThis->pCondStack = pCond; + pThis->fIf0Mode = pCond->enmStackResult == kVBCppEval_False; + + /* + * Do pass thru. + */ + if ( !pThis->fIf0Mode + && enmResult == kVBCppEval_Undecided) + { + /** @todo this is stripping comments of \#ifdef and \#ifndef atm. */ + const char *pszDirective; + switch (enmKind) + { + case kVBCppCondKind_If: pszDirective = "if"; break; + case kVBCppCondKind_IfDef: pszDirective = "ifdef"; break; + case kVBCppCondKind_IfNDef: pszDirective = "ifndef"; break; + case kVBCppCondKind_ElIf: pszDirective = "elif"; break; + default: AssertFailedReturn(RTEXITCODE_FAILURE); + } + ssize_t cch = ScmStreamPrintf(&pThis->StrmOutput, "#%*s%s %.*s", + pCond->iKeepLevel - 1, "", pszDirective, cchCondition, pchCondition); + if (cch < 0) + return vbcppError(pThis, "Output error %Rrc", (int)cch); + } + else + pThis->fJustDroppedLine = true; + + return RTEXITCODE_SUCCESS; +} + + +/** + * Recursively destroys the expression tree. + * + * @param pExpr The root of the expression tree to destroy. + */ +static void vbcppExprDestoryTree(PVBCPPEXPR pExpr) +{ + if (!pExpr) + return; + + switch (pExpr->enmKind) + { + case kVBCppExprKind_Unary: + vbcppExprDestoryTree(pExpr->u.Unary.pArg); + break; + case kVBCppExprKind_Binary: + vbcppExprDestoryTree(pExpr->u.Binary.pLeft); + vbcppExprDestoryTree(pExpr->u.Binary.pRight); + break; + case kVBCppExprKind_Ternary: + vbcppExprDestoryTree(pExpr->u.Ternary.pExpr); + vbcppExprDestoryTree(pExpr->u.Ternary.pExpr); + vbcppExprDestoryTree(pExpr->u.Ternary.pFalse); + break; + case kVBCppExprKind_SignedValue: + case kVBCppExprKind_UnsignedValue: + break; + default: + AssertFailed(); + return; + } + RTMemFree(pExpr); +} + + +/** + * Report error during expression parsing. + * + * @returns kExprRet_Error + * @param pParser The parser instance. + * @param pszMsg The error message. + * @param ... Format arguments. + */ +static VBCPPEXPRRET vbcppExprParseError(PVBCPPEXPRPARSER pParser, const char *pszMsg, ...) +{ + va_list va; + va_start(va, pszMsg); + vbcppErrorV(pParser->pThis, pszMsg, va); + va_end(va); + return kExprRet_Error; +} + + +/** + * Skip white space. + * + * @param pParser The parser instance. + */ +static void vbcppExprParseSkipWhiteSpace(PVBCPPEXPRPARSER pParser) +{ + while (RT_C_IS_SPACE(*pParser->pszCur)) + pParser->pszCur++; +} + + +/** + * Allocate a new + * + * @returns Pointer to the node. NULL+msg on failure. + * @param pParser The parser instance. + */ +static PVBCPPEXPR vbcppExprParseAllocNode(PVBCPPEXPRPARSER pParser) +{ + PVBCPPEXPR pExpr = (PVBCPPEXPR)RTMemAllocZ(sizeof(*pExpr)); + if (!pExpr) + vbcppExprParseError(pParser, "out of memory (expression node)"); + return pExpr; +} + + +/** + * Looks for right parentheses and/or end of expression. + * + * @returns Expression status. + * @retval kExprRet_Ok + * @retval kExprRet_Error with msg. + * @retval kExprRet_EndOfExpr + * @param pParser The parser instance. + */ +static VBCPPEXPRRET vbcppExprParseMaybeRParenOrEoe(PVBCPPEXPRPARSER pParser) +{ + Assert(!pParser->ppCur); + for (;;) + { + vbcppExprParseSkipWhiteSpace(pParser); + char ch = *pParser->pszCur; + if (ch == '\0') + return kExprRet_EndOfExpr; + if (ch != ')') + break; + pParser->pszCur++; + + PVBCPPEXPR pCur = pParser->pCur; + while ( pCur + && ( pCur->enmKind != kVBCppExprKind_Unary + || pCur->u.Unary.enmOperator != kVBCppUnaryOp_Parenthesis)) + { + switch (pCur->enmKind) + { + case kVBCppExprKind_SignedValue: + case kVBCppExprKind_UnsignedValue: + Assert(pCur->fComplete); + break; + case kVBCppExprKind_Unary: + AssertReturn(pCur->u.Unary.pArg, vbcppExprParseError(pParser, "internal error")); + pCur->fComplete = true; + break; + case kVBCppExprKind_Binary: + AssertReturn(pCur->u.Binary.pLeft, vbcppExprParseError(pParser, "internal error")); + AssertReturn(pCur->u.Binary.pRight, vbcppExprParseError(pParser, "internal error")); + pCur->fComplete = true; + break; + case kVBCppExprKind_Ternary: +#if 1 /** @todo Check out the ternary operator implementation. */ + return vbcppExprParseError(pParser, "The ternary operator is not implemented"); +#else + Assert(pCur->u.Ternary.pExpr); + if (!pCur->u.Ternary.pTrue) + return vbcppExprParseError(pParser, "?!?!?"); + if (!pCur->u.Ternary.pFalse) + return vbcppExprParseError(pParser, "?!?!?!?"); + pCur->fComplete = true; +#endif + break; + default: + return vbcppExprParseError(pParser, "Internal error (enmKind=%d)", pCur->enmKind); + } + pCur = pCur->pParent; + } + if (!pCur) + return vbcppExprParseError(pParser, "Right parenthesis without a left one"); + pCur->fComplete = true; + + while ( pCur->enmKind == kVBCppExprKind_Unary + && pCur->u.Unary.enmOperator != kVBCppUnaryOp_Parenthesis + && pCur->pParent) + { + AssertReturn(pCur->u.Unary.pArg, vbcppExprParseError(pParser, "internal error")); + pCur->fComplete = true; + pCur = pCur->pParent; + } + } + + return kExprRet_Ok; +} + + +/** + * Parses an binary operator. + * + * @returns Expression status. + * @retval kExprRet_Ok + * @retval kExprRet_Error with msg. + * @param pParser The parser instance. + */ +static VBCPPEXPRRET vbcppExprParseBinaryOperator(PVBCPPEXPRPARSER pParser) +{ + /* + * Binary or ternary operator should follow now. + */ + VBCPPBINARYOP enmOp; + char ch = *pParser->pszCur; + switch (ch) + { + case '*': + if (pParser->pszCur[1] == '=') + return vbcppExprParseError(pParser, "The assignment by product operator is not valid in a preprocessor expression"); + enmOp = kVBCppBinary_Multiplication; + break; + case '/': + if (pParser->pszCur[1] == '=') + return vbcppExprParseError(pParser, "The assignment by quotient operator is not valid in a preprocessor expression"); + enmOp = kVBCppBinary_Division; + break; + case '%': + if (pParser->pszCur[1] == '=') + return vbcppExprParseError(pParser, "The assignment by remainder operator is not valid in a preprocessor expression"); + enmOp = kVBCppBinary_Modulo; + break; + case '+': + if (pParser->pszCur[1] == '=') + return vbcppExprParseError(pParser, "The assignment by sum operator is not valid in a preprocessor expression"); + enmOp = kVBCppBinary_Addition; + break; + case '-': + if (pParser->pszCur[1] == '=') + return vbcppExprParseError(pParser, "The assignment by difference operator is not valid in a preprocessor expression"); + enmOp = kVBCppBinary_Subtraction; + break; + case '<': + enmOp = kVBCppBinary_LessThan; + if (pParser->pszCur[1] == '=') + { + pParser->pszCur++; + enmOp = kVBCppBinary_LessThanOrEqual; + } + else if (pParser->pszCur[1] == '<') + { + pParser->pszCur++; + if (pParser->pszCur[1] == '=') + return vbcppExprParseError(pParser, "The assignment by bitwise left shift operator is not valid in a preprocessor expression"); + enmOp = kVBCppBinary_LeftShift; + } + break; + case '>': + enmOp = kVBCppBinary_GreaterThan; + if (pParser->pszCur[1] == '=') + { + pParser->pszCur++; + enmOp = kVBCppBinary_GreaterThanOrEqual; + } + else if (pParser->pszCur[1] == '<') + { + pParser->pszCur++; + if (pParser->pszCur[1] == '=') + return vbcppExprParseError(pParser, "The assignment by bitwise right shift operator is not valid in a preprocessor expression"); + enmOp = kVBCppBinary_LeftShift; + } + break; + case '=': + if (pParser->pszCur[1] != '=') + return vbcppExprParseError(pParser, "The assignment operator is not valid in a preprocessor expression"); + pParser->pszCur++; + enmOp = kVBCppBinary_EqualTo; + break; + + case '!': + if (pParser->pszCur[1] != '=') + return vbcppExprParseError(pParser, "Expected binary operator, found the unary operator logical NOT"); + pParser->pszCur++; + enmOp = kVBCppBinary_NotEqualTo; + break; + + case '&': + if (pParser->pszCur[1] == '=') + return vbcppExprParseError(pParser, "The assignment by bitwise AND operator is not valid in a preprocessor expression"); + if (pParser->pszCur[1] == '&') + { + pParser->pszCur++; + enmOp = kVBCppBinary_LogicalAnd; + } + else + enmOp = kVBCppBinary_BitwiseAnd; + break; + case '^': + if (pParser->pszCur[1] == '=') + return vbcppExprParseError(pParser, "The assignment by bitwise XOR operator is not valid in a preprocessor expression"); + enmOp = kVBCppBinary_BitwiseXor; + break; + case '|': + if (pParser->pszCur[1] == '=') + return vbcppExprParseError(pParser, "The assignment by bitwise AND operator is not valid in a preprocessor expression"); + if (pParser->pszCur[1] == '|') + { + pParser->pszCur++; + enmOp = kVBCppBinary_LogicalOr; + } + else + enmOp = kVBCppBinary_BitwiseOr; + break; + case '~': + return vbcppExprParseError(pParser, "Expected binary operator, found the unary operator bitwise NOT"); + + case ':': + case '?': + return vbcppExprParseError(pParser, "The ternary operator is not yet implemented"); + + default: + return vbcppExprParseError(pParser, "Expected binary operator, found '%.20s'", pParser->pszCur); + } + pParser->pszCur++; + + /* + * Create a binary operator node. + */ + PVBCPPEXPR pExpr = vbcppExprParseAllocNode(pParser); + if (!pExpr) + return kExprRet_Error; + pExpr->fComplete = true; + pExpr->enmKind = kVBCppExprKind_Binary; + pExpr->u.Binary.enmOperator = enmOp; + pExpr->u.Binary.pLeft = NULL; + pExpr->u.Binary.pRight = NULL; + + /* + * Back up the tree until we find our spot. + */ + PVBCPPEXPR *ppPlace = NULL; + PVBCPPEXPR pChild = NULL; + PVBCPPEXPR pParent = pParser->pCur; + while (pParent) + { + if (pParent->enmKind == kVBCppExprKind_Unary) + { + if (pParent->u.Unary.enmOperator == kVBCppUnaryOp_Parenthesis) + { + ppPlace = &pParent->u.Unary.pArg; + break; + } + AssertReturn(pParent->u.Unary.pArg, vbcppExprParseError(pParser, "internal error")); + pParent->fComplete = true; + } + else if (pParent->enmKind == kVBCppExprKind_Binary) + { + AssertReturn(pParent->u.Binary.pLeft, vbcppExprParseError(pParser, "internal error")); + AssertReturn(pParent->u.Binary.pRight, vbcppExprParseError(pParser, "internal error")); + if ((pParent->u.Binary.enmOperator & VBCPPOP_PRECEDENCE_MASK) >= (enmOp & VBCPPOP_PRECEDENCE_MASK)) + { + AssertReturn(pChild, vbcppExprParseError(pParser, "internal error")); + + if (pParent->u.Binary.pRight == pChild) + ppPlace = &pParent->u.Binary.pRight; + else + ppPlace = &pParent->u.Binary.pLeft; + AssertReturn(*ppPlace == pChild, vbcppExprParseError(pParser, "internal error")); + break; + } + pParent->fComplete = true; + } + else if (pParent->enmKind == kVBCppExprKind_Ternary) + { + return vbcppExprParseError(pParser, "The ternary operator is not implemented"); + } + else + AssertReturn( pParent->enmKind == kVBCppExprKind_SignedValue + || pParent->enmKind == kVBCppExprKind_UnsignedValue, + vbcppExprParseError(pParser, "internal error")); + + /* Up on level */ + pChild = pParent; + pParent = pParent->pParent; + } + + /* + * Do the rotation. + */ + Assert(pChild); + Assert(pChild->pParent == pParent); + pChild->pParent = pExpr; + + pExpr->u.Binary.pLeft = pChild; + pExpr->pParent = pParent; + + if (!pParent) + pParser->pRoot = pExpr; + else + *ppPlace = pExpr; + + pParser->ppCur = &pExpr->u.Binary.pRight; + pParser->pCur = pExpr; + + return kExprRet_Ok; +} + + +/** + * Deals with right paretheses or/and end of expression, looks for binary + * operators. + * + * @returns Expression status. + * @retval kExprRet_Ok if binary operator was found processed. + * @retval kExprRet_Error with msg. + * @retval kExprRet_EndOfExpr + * @param pParser The parser instance. + */ +static VBCPPEXPRRET vbcppExprParseBinaryOrEoeOrRparen(PVBCPPEXPRPARSER pParser) +{ + VBCPPEXPRRET enmRet = vbcppExprParseMaybeRParenOrEoe(pParser); + if (enmRet != kExprRet_Ok) + return enmRet; + return vbcppExprParseBinaryOperator(pParser); +} + + +/** + * Parses an identifier in the expression, replacing it by 0. + * + * All known identifiers has already been replaced by their macro values, so + * what's left are unknown macros. These are replaced by 0. + * + * @returns Expression status. + * @retval kExprRet_Value + * @retval kExprRet_Error with msg. + * @param pParser The parser instance. + */ +static VBCPPEXPRRET vbcppExprParseIdentifier(PVBCPPEXPRPARSER pParser) +{ +/** @todo don't increment if it's an actively undefined macro. Need to revise + * the expression related code wrt selective preprocessing. */ + pParser->cUndefined++; + + /* Find the end. */ + const char *pszMacro = pParser->pszCur; + const char *pszNext = pszMacro + 1; + while (vbcppIsCIdentifierChar(*pszNext)) + pszNext++; + size_t cchMacro = pszNext - pszMacro; + + /* Create a signed value node. */ + PVBCPPEXPR pExpr = vbcppExprParseAllocNode(pParser); + if (!pExpr) + return kExprRet_Error; + pExpr->fComplete = true; + pExpr->enmKind = kVBCppExprKind_UnsignedValue; + pExpr->u.UnsignedValue.u64 = 0; + + /* Link it. */ + pExpr->pParent = pParser->pCur; + pParser->pCur = pExpr; + *pParser->ppCur = pExpr; + pParser->ppCur = NULL; + + /* Skip spaces and check for parenthesis. */ + pParser->pszCur = pszNext; + vbcppExprParseSkipWhiteSpace(pParser); + if (*pParser->pszCur == '(') + return vbcppExprParseError(pParser, "Unknown unary operator '%.*s'", cchMacro, pszMacro); + + return kExprRet_Value; +} + + +/** + * Parses an numeric constant in the expression. + * + * @returns Expression status. + * @retval kExprRet_Value + * @retval kExprRet_Error with msg. + * @param pParser The parser instance. + */ +static VBCPPEXPRRET vbcppExprParseNumber(PVBCPPEXPRPARSER pParser) +{ + bool fSigned; + char *pszNext; + uint64_t u64; + char ch = *pParser->pszCur++; + char ch2 = *pParser->pszCur; + if ( ch == '0' + && (ch == 'x' || ch == 'X')) + { + ch2 = *++pParser->pszCur; + if (!RT_C_IS_XDIGIT(ch2)) + return vbcppExprParseError(pParser, "Expected hex digit following '0x'"); + int rc = RTStrToUInt64Ex(pParser->pszCur, &pszNext, 16, &u64); + if ( RT_FAILURE(rc) + || rc == VWRN_NUMBER_TOO_BIG) + return vbcppExprParseError(pParser, "Invalid hex value '%.20s...' (%Rrc)", pParser->pszCur, rc); + fSigned = false; + } + else if (ch == '0') + { + int rc = RTStrToUInt64Ex(pParser->pszCur - 1, &pszNext, 8, &u64); + if ( RT_FAILURE(rc) + || rc == VWRN_NUMBER_TOO_BIG) + return vbcppExprParseError(pParser, "Invalid octal value '%.20s...' (%Rrc)", pParser->pszCur, rc); + fSigned = u64 > (uint64_t)INT64_MAX ? false : true; + } + else + { + int rc = RTStrToUInt64Ex(pParser->pszCur - 1, &pszNext, 10, &u64); + if ( RT_FAILURE(rc) + || rc == VWRN_NUMBER_TOO_BIG) + return vbcppExprParseError(pParser, "Invalid decimal value '%.20s...' (%Rrc)", pParser->pszCur, rc); + fSigned = u64 > (uint64_t)INT64_MAX ? false : true; + } + + /* suffix. */ + if (vbcppIsCIdentifierLeadChar(*pszNext)) + { + size_t cchSuffix = 1; + while (vbcppIsCIdentifierLeadChar(pszNext[cchSuffix])) + cchSuffix++; + + if (cchSuffix == '1' && (*pszNext == 'u' || *pszNext == 'U')) + fSigned = false; + else if ( cchSuffix == '1' + && (*pszNext == 'l' || *pszNext == 'L')) + fSigned = true; + else if ( cchSuffix == '2' + && (!strncmp(pszNext, "ul", 2) || !strncmp(pszNext, "UL", 2))) + fSigned = false; + else if ( cchSuffix == '2' + && (!strncmp(pszNext, "ll", 2) || !strncmp(pszNext, "LL", 2))) + fSigned = true; + else if ( cchSuffix == '3' + && (!strncmp(pszNext, "ull", 3) || !strncmp(pszNext, "ULL", 3))) + fSigned = false; + else + return vbcppExprParseError(pParser, "Invalid number suffix '%.*s'", cchSuffix, pszNext); + + pszNext += cchSuffix; + } + pParser->pszCur = pszNext; + + /* Create a signed value node. */ + PVBCPPEXPR pExpr = vbcppExprParseAllocNode(pParser); + if (!pExpr) + return kExprRet_Error; + pExpr->fComplete = true; + if (fSigned) + { + pExpr->enmKind = kVBCppExprKind_SignedValue; + pExpr->u.SignedValue.s64 = (int64_t)u64; + } + else + { + pExpr->enmKind = kVBCppExprKind_UnsignedValue; + pExpr->u.UnsignedValue.u64 = u64; + } + + /* Link it. */ + pExpr->pParent = pParser->pCur; + pParser->pCur = pExpr; + *pParser->ppCur = pExpr; + pParser->ppCur = NULL; + + return kExprRet_Value; +} + + +/** + * Parses an character constant in the expression. + * + * @returns Expression status. + * @retval kExprRet_Value + * @retval kExprRet_Error with msg. + * @param pParser The parser instance. + */ +static VBCPPEXPRRET vbcppExprParseCharacterConstant(PVBCPPEXPRPARSER pParser) +{ + Assert(*pParser->pszCur == '\''); + pParser->pszCur++; + char ch2 = *pParser->pszCur++; + if (ch2 == '\'') + return vbcppExprParseError(pParser, "Empty character constant"); + int64_t s64; + if (ch2 == '\\') + { + ch2 = *pParser->pszCur++; + switch (ch2) + { + case '0': s64 = 0x00; break; + case 'n': s64 = 0x0d; break; + case 'r': s64 = 0x0a; break; + case 't': s64 = 0x09; break; + default: + return vbcppExprParseError(pParser, "Escape character '%c' is not implemented", ch2); + } + } + else + s64 = ch2; + if (*pParser->pszCur != '\'') + return vbcppExprParseError(pParser, "Character constant contains more than one character"); + + /* Create a signed value node. */ + PVBCPPEXPR pExpr = vbcppExprParseAllocNode(pParser); + if (!pExpr) + return kExprRet_Error; + pExpr->fComplete = true; + pExpr->enmKind = kVBCppExprKind_SignedValue; + pExpr->u.SignedValue.s64 = s64; + + /* Link it. */ + pExpr->pParent = pParser->pCur; + pParser->pCur = pExpr; + *pParser->ppCur = pExpr; + pParser->ppCur = NULL; + + return kExprRet_Value; +} + + +/** + * Parses a unary operator or a value. + * + * @returns Expression status. + * @retval kExprRet_Value if value was found and processed. + * @retval kExprRet_UnaryOperator if an unary operator was found and processed. + * @retval kExprRet_Error with msg. + * @param pParser The parser instance. + */ +static VBCPPEXPRRET vbcppExprParseUnaryOrValue(PVBCPPEXPRPARSER pParser) +{ + vbcppExprParseSkipWhiteSpace(pParser); + char ch = *pParser->pszCur; + if (ch == '\0') + return vbcppExprParseError(pParser, "Premature end of expression"); + + /* + * Value? + */ + if (ch == '\'') + return vbcppExprParseCharacterConstant(pParser); + if (RT_C_IS_DIGIT(ch)) + return vbcppExprParseNumber(pParser); + if (ch == '"') + return vbcppExprParseError(pParser, "String litteral"); + if (vbcppIsCIdentifierLeadChar(ch)) + return vbcppExprParseIdentifier(pParser); + + /* + * Operator? + */ + VBCPPUNARYOP enmOperator; + if (ch == '+') + { + enmOperator = kVBCppUnaryOp_Pluss; + if (pParser->pszCur[1] == '+') + return vbcppExprParseError(pParser, "The prefix increment operator is not valid in a preprocessor expression"); + } + else if (ch == '-') + { + enmOperator = kVBCppUnaryOp_Minus; + if (pParser->pszCur[1] == '-') + return vbcppExprParseError(pParser, "The prefix decrement operator is not valid in a preprocessor expression"); + } + else if (ch == '!') + enmOperator = kVBCppUnaryOp_LogicalNot; + else if (ch == '~') + enmOperator = kVBCppUnaryOp_BitwiseNot; + else if (ch == '(') + enmOperator = kVBCppUnaryOp_Parenthesis; + else + return vbcppExprParseError(pParser, "Unknown token '%.*s'", 32, pParser->pszCur - 1); + pParser->pszCur++; + + /* Create an operator node. */ + PVBCPPEXPR pExpr = vbcppExprParseAllocNode(pParser); + if (!pExpr) + return kExprRet_Error; + pExpr->fComplete = false; + pExpr->enmKind = kVBCppExprKind_Unary; + pExpr->u.Unary.enmOperator = enmOperator; + pExpr->u.Unary.pArg = NULL; + + /* Link it into the tree. */ + pExpr->pParent = pParser->pCur; + pParser->pCur = pExpr; + *pParser->ppCur = pExpr; + pParser->ppCur = &pExpr->u.Unary.pArg; + + return kExprRet_UnaryOperator; +} + + +/** + * Parses an expanded preprocessor expression. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pszExpr The expression to parse. + * @param cchExpr The length of the expression in case we need it. + * @param ppExprTree Where to return the parse tree. + * @param pcUndefined Where to return the number of unknown undefined + * macros. Optional. + */ +static RTEXITCODE vbcppExprParse(PVBCPP pThis, char *pszExpr, size_t cchExpr, PVBCPPEXPR *ppExprTree, size_t *pcUndefined) +{ + RTEXITCODE rcExit = RTEXITCODE_FAILURE; + NOREF(cchExpr); + + /* + * Initialize the parser context structure. + */ + VBCPPEXPRPARSER Parser; + Parser.pszCur = pszExpr; + Parser.pRoot = NULL; + Parser.pCur = NULL; + Parser.ppCur = &Parser.pRoot; + Parser.pszExpr = pszExpr; + Parser.cUndefined = 0; + Parser.pThis = pThis; + + /* + * Do the parsing. + */ + VBCPPEXPRRET enmRet; + for (;;) + { + /* + * Eat unary operators until we hit a value. + */ + do + enmRet = vbcppExprParseUnaryOrValue(&Parser); + while (enmRet == kExprRet_UnaryOperator); + if (enmRet == kExprRet_Error) + break; + AssertBreakStmt(enmRet == kExprRet_Value, enmRet = vbcppExprParseError(&Parser, "Expected value (enmRet=%d)", enmRet)); + + /* + * Non-unary operator, right parenthesis or end of expression is up next. + */ + enmRet = vbcppExprParseBinaryOrEoeOrRparen(&Parser); + if (enmRet == kExprRet_Error) + break; + if (enmRet == kExprRet_EndOfExpr) + { + /** @todo check if there are any open parentheses. */ + rcExit = RTEXITCODE_SUCCESS; + break; + } + AssertBreakStmt(enmRet == kExprRet_Ok, enmRet = vbcppExprParseError(&Parser, "Expected value (enmRet=%d)", enmRet)); + } + + if (rcExit != RTEXITCODE_SUCCESS) + { + vbcppExprDestoryTree(Parser.pRoot); + return rcExit; + } + + if (pcUndefined) + *pcUndefined = Parser.cUndefined; + *ppExprTree = Parser.pRoot; + return rcExit; +} + + +/** + * Checks if an expression value value is evaluates to @c true or @c false. + * + * @returns @c true or @c false. + * @param pExpr The value expression. + */ +static bool vbcppExprIsExprTrue(PVBCPPEXPR pExpr) +{ + Assert(pExpr->enmKind == kVBCppExprKind_SignedValue || pExpr->enmKind == kVBCppExprKind_UnsignedValue); + + return pExpr->enmKind == kVBCppExprKind_SignedValue + ? pExpr->u.SignedValue.s64 != 0 + : pExpr->u.UnsignedValue.u64 != 0; +} + + +/** + * Evalutes a parse (sub-)tree. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pRoot The root of the parse (sub-)tree. + * @param pResult Where to store the result value. + */ +static RTEXITCODE vbcppExprEvaluteTree(PVBCPP pThis, PVBCPPEXPR pRoot, PVBCPPEXPR pResult) +{ + RTEXITCODE rcExit; + switch (pRoot->enmKind) + { + case kVBCppExprKind_SignedValue: + pResult->enmKind = kVBCppExprKind_SignedValue; + pResult->u.SignedValue.s64 = pRoot->u.SignedValue.s64; + return RTEXITCODE_SUCCESS; + + case kVBCppExprKind_UnsignedValue: + pResult->enmKind = kVBCppExprKind_UnsignedValue; + pResult->u.UnsignedValue.u64 = pRoot->u.UnsignedValue.u64; + return RTEXITCODE_SUCCESS; + + case kVBCppExprKind_Unary: + rcExit = vbcppExprEvaluteTree(pThis, pRoot->u.Unary.pArg, pResult); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + + /* Apply the unary operator to the value */ + switch (pRoot->u.Unary.enmOperator) + { + case kVBCppUnaryOp_Minus: + if (pResult->enmKind == kVBCppExprKind_SignedValue) + pResult->u.SignedValue.s64 = -pResult->u.SignedValue.s64; + else + pResult->u.UnsignedValue.u64 = (uint64_t)-(int64_t)pResult->u.UnsignedValue.u64; + break; + + case kVBCppUnaryOp_LogicalNot: + if (pResult->enmKind == kVBCppExprKind_SignedValue) + pResult->u.SignedValue.s64 = !pResult->u.SignedValue.s64; + else + pResult->u.UnsignedValue.u64 = !pResult->u.UnsignedValue.u64; + break; + + case kVBCppUnaryOp_BitwiseNot: + if (pResult->enmKind == kVBCppExprKind_SignedValue) + pResult->u.SignedValue.s64 = ~pResult->u.SignedValue.s64; + else + pResult->u.UnsignedValue.u64 = ~pResult->u.UnsignedValue.u64; + break; + + case kVBCppUnaryOp_Pluss: + case kVBCppUnaryOp_Parenthesis: + /* do nothing. */ + break; + + default: + return vbcppError(pThis, "Internal error: u.Unary.enmOperator=%d", pRoot->u.Unary.enmOperator); + } + return RTEXITCODE_SUCCESS; + + case kVBCppExprKind_Binary: + { + /* Always evalute the left side. */ + rcExit = vbcppExprEvaluteTree(pThis, pRoot->u.Binary.pLeft, pResult); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + + /* If logical AND or OR we can sometimes skip evaluting the right side. */ + if ( pRoot->u.Binary.enmOperator == kVBCppBinary_LogicalAnd + && !vbcppExprIsExprTrue(pResult)) + return RTEXITCODE_SUCCESS; + + if ( pRoot->u.Binary.enmOperator == kVBCppBinary_LogicalOr + && vbcppExprIsExprTrue(pResult)) + return RTEXITCODE_SUCCESS; + + /* Evalute the right side. */ + VBCPPEXPR Result2; + rcExit = vbcppExprEvaluteTree(pThis, pRoot->u.Binary.pRight, &Result2); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + + /* If one of them is unsigned, promote the other to unsigned as well. */ + if ( pResult->enmKind == kVBCppExprKind_UnsignedValue + && Result2.enmKind == kVBCppExprKind_SignedValue) + { + Result2.enmKind = kVBCppExprKind_UnsignedValue; + Result2.u.UnsignedValue.u64 = Result2.u.SignedValue.s64; + } + else if ( pResult->enmKind == kVBCppExprKind_SignedValue + && Result2.enmKind == kVBCppExprKind_UnsignedValue) + { + pResult->enmKind = kVBCppExprKind_UnsignedValue; + pResult->u.UnsignedValue.u64 = pResult->u.SignedValue.s64; + } + + /* Perform the operation. */ + if (pResult->enmKind == kVBCppExprKind_UnsignedValue) + { + switch (pRoot->u.Binary.enmOperator) + { + case kVBCppBinary_Multiplication: + pResult->u.UnsignedValue.u64 *= Result2.u.UnsignedValue.u64; + break; + case kVBCppBinary_Division: + if (!Result2.u.UnsignedValue.u64) + return vbcppError(pThis, "Divide by zero"); + pResult->u.UnsignedValue.u64 /= Result2.u.UnsignedValue.u64; + break; + case kVBCppBinary_Modulo: + if (!Result2.u.UnsignedValue.u64) + return vbcppError(pThis, "Divide by zero"); + pResult->u.UnsignedValue.u64 %= Result2.u.UnsignedValue.u64; + break; + case kVBCppBinary_Addition: + pResult->u.UnsignedValue.u64 += Result2.u.UnsignedValue.u64; + break; + case kVBCppBinary_Subtraction: + pResult->u.UnsignedValue.u64 -= Result2.u.UnsignedValue.u64; + break; + case kVBCppBinary_LeftShift: + pResult->u.UnsignedValue.u64 <<= Result2.u.UnsignedValue.u64; + break; + case kVBCppBinary_RightShift: + pResult->u.UnsignedValue.u64 >>= Result2.u.UnsignedValue.u64; + break; + case kVBCppBinary_LessThan: + pResult->u.UnsignedValue.u64 = pResult->u.UnsignedValue.u64 < Result2.u.UnsignedValue.u64; + break; + case kVBCppBinary_LessThanOrEqual: + pResult->u.UnsignedValue.u64 = pResult->u.UnsignedValue.u64 <= Result2.u.UnsignedValue.u64; + break; + case kVBCppBinary_GreaterThan: + pResult->u.UnsignedValue.u64 = pResult->u.UnsignedValue.u64 > Result2.u.UnsignedValue.u64; + break; + case kVBCppBinary_GreaterThanOrEqual: + pResult->u.UnsignedValue.u64 = pResult->u.UnsignedValue.u64 >= Result2.u.UnsignedValue.u64; + break; + case kVBCppBinary_EqualTo: + pResult->u.UnsignedValue.u64 = pResult->u.UnsignedValue.u64 == Result2.u.UnsignedValue.u64; + break; + case kVBCppBinary_NotEqualTo: + pResult->u.UnsignedValue.u64 = pResult->u.UnsignedValue.u64 != Result2.u.UnsignedValue.u64; + break; + case kVBCppBinary_BitwiseAnd: + pResult->u.UnsignedValue.u64 &= Result2.u.UnsignedValue.u64; + break; + case kVBCppBinary_BitwiseXor: + pResult->u.UnsignedValue.u64 ^= Result2.u.UnsignedValue.u64; + break; + case kVBCppBinary_BitwiseOr: + pResult->u.UnsignedValue.u64 |= Result2.u.UnsignedValue.u64; + break; + case kVBCppBinary_LogicalAnd: + pResult->u.UnsignedValue.u64 = pResult->u.UnsignedValue.u64 && Result2.u.UnsignedValue.u64; + break; + case kVBCppBinary_LogicalOr: + pResult->u.UnsignedValue.u64 = pResult->u.UnsignedValue.u64 || Result2.u.UnsignedValue.u64; + break; + default: + return vbcppError(pThis, "Internal error: u.Binary.enmOperator=%d", pRoot->u.Binary.enmOperator); + } + } + else + { + switch (pRoot->u.Binary.enmOperator) + { + case kVBCppBinary_Multiplication: + pResult->u.SignedValue.s64 *= Result2.u.SignedValue.s64; + break; + case kVBCppBinary_Division: + if (!Result2.u.SignedValue.s64) + return vbcppError(pThis, "Divide by zero"); + pResult->u.SignedValue.s64 /= Result2.u.SignedValue.s64; + break; + case kVBCppBinary_Modulo: + if (!Result2.u.SignedValue.s64) + return vbcppError(pThis, "Divide by zero"); + pResult->u.SignedValue.s64 %= Result2.u.SignedValue.s64; + break; + case kVBCppBinary_Addition: + pResult->u.SignedValue.s64 += Result2.u.SignedValue.s64; + break; + case kVBCppBinary_Subtraction: + pResult->u.SignedValue.s64 -= Result2.u.SignedValue.s64; + break; + case kVBCppBinary_LeftShift: + pResult->u.SignedValue.s64 <<= Result2.u.SignedValue.s64; + break; + case kVBCppBinary_RightShift: + pResult->u.SignedValue.s64 >>= Result2.u.SignedValue.s64; + break; + case kVBCppBinary_LessThan: + pResult->u.SignedValue.s64 = pResult->u.SignedValue.s64 < Result2.u.SignedValue.s64; + break; + case kVBCppBinary_LessThanOrEqual: + pResult->u.SignedValue.s64 = pResult->u.SignedValue.s64 <= Result2.u.SignedValue.s64; + break; + case kVBCppBinary_GreaterThan: + pResult->u.SignedValue.s64 = pResult->u.SignedValue.s64 > Result2.u.SignedValue.s64; + break; + case kVBCppBinary_GreaterThanOrEqual: + pResult->u.SignedValue.s64 = pResult->u.SignedValue.s64 >= Result2.u.SignedValue.s64; + break; + case kVBCppBinary_EqualTo: + pResult->u.SignedValue.s64 = pResult->u.SignedValue.s64 == Result2.u.SignedValue.s64; + break; + case kVBCppBinary_NotEqualTo: + pResult->u.SignedValue.s64 = pResult->u.SignedValue.s64 != Result2.u.SignedValue.s64; + break; + case kVBCppBinary_BitwiseAnd: + pResult->u.SignedValue.s64 &= Result2.u.SignedValue.s64; + break; + case kVBCppBinary_BitwiseXor: + pResult->u.SignedValue.s64 ^= Result2.u.SignedValue.s64; + break; + case kVBCppBinary_BitwiseOr: + pResult->u.SignedValue.s64 |= Result2.u.SignedValue.s64; + break; + case kVBCppBinary_LogicalAnd: + pResult->u.SignedValue.s64 = pResult->u.SignedValue.s64 && Result2.u.SignedValue.s64; + break; + case kVBCppBinary_LogicalOr: + pResult->u.SignedValue.s64 = pResult->u.SignedValue.s64 || Result2.u.SignedValue.s64; + break; + default: + return vbcppError(pThis, "Internal error: u.Binary.enmOperator=%d", pRoot->u.Binary.enmOperator); + } + } + return rcExit; + } + + case kVBCppExprKind_Ternary: + rcExit = vbcppExprEvaluteTree(pThis, pRoot->u.Ternary.pExpr, pResult); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + if (vbcppExprIsExprTrue(pResult)) + return vbcppExprEvaluteTree(pThis, pRoot->u.Ternary.pTrue, pResult); + return vbcppExprEvaluteTree(pThis, pRoot->u.Ternary.pFalse, pResult); + + default: + return vbcppError(pThis, "Internal error: enmKind=%d", pRoot->enmKind); + } +} + + +/** + * Evalutes the expression. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pszExpr The expression. + * @param cchExpr The length of the expression. + * @param penmResult Where to store the result. + */ +static RTEXITCODE vbcppExprEval(PVBCPP pThis, char *pszExpr, size_t cchExpr, size_t cReplacements, VBCPPEVAL *penmResult) +{ + Assert(strlen(pszExpr) == cchExpr); + RT_NOREF_PV(cReplacements); + + size_t cUndefined; + PVBCPPEXPR pExprTree; + RTEXITCODE rcExit = vbcppExprParse(pThis, pszExpr, cchExpr, &pExprTree, &cUndefined); + if (rcExit == RTEXITCODE_SUCCESS) + { + if ( !cUndefined + || pThis->enmMode == kVBCppMode_SelectiveD + || pThis->enmMode == kVBCppMode_Standard) + { + VBCPPEXPR Result; + rcExit = vbcppExprEvaluteTree(pThis, pExprTree, &Result); + if (rcExit == RTEXITCODE_SUCCESS) + { + if (vbcppExprIsExprTrue(&Result)) + *penmResult = kVBCppEval_True; + else + *penmResult = kVBCppEval_False; + } + } + else + *penmResult = kVBCppEval_Undecided; + } + return rcExit; +} + + +static RTEXITCODE vbcppExtractSkipCommentLine(PVBCPP pThis, PSCMSTREAM pStrmInput) +{ + RT_NOREF_PV(pThis); + + unsigned chPrev = ScmStreamGetCh(pStrmInput); Assert(chPrev == '/'); + unsigned ch; + while ((ch = ScmStreamPeekCh(pStrmInput)) != ~(unsigned)0) + { + if (ch == '\r' || ch == '\n') + { + if (chPrev != '\\') + break; + ScmStreamSeekByLine(pStrmInput, ScmStreamTellLine(pStrmInput) + 1); + chPrev = ch; + } + else + { + chPrev = ScmStreamGetCh(pStrmInput); + Assert(chPrev == ch); + } + } + return RTEXITCODE_SUCCESS; +} + + +static RTEXITCODE vbcppExtractSkipComment(PVBCPP pThis, PSCMSTREAM pStrmInput) +{ + unsigned ch = ScmStreamGetCh(pStrmInput); Assert(ch == '*'); + while ((ch = ScmStreamGetCh(pStrmInput)) != ~(unsigned)0) + { + if (ch == '*') + { + ch = ScmStreamGetCh(pStrmInput); + if (ch == '/') + return RTEXITCODE_SUCCESS; + } + } + return vbcppError(pThis, "Expected '*/'"); +} + + +static RTEXITCODE vbcppExtractQuotedString(PVBCPP pThis, PSCMSTREAM pStrmInput, PVBCPPSTRBUF pStrBuf, + char chOpen, char chClose) +{ + unsigned ch = ScmStreamGetCh(pStrmInput); + Assert(ch == (unsigned)chOpen); + + RTEXITCODE rcExit = vbcppStrBufAppendCh(pStrBuf, chOpen); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + + for (;;) + { + ch = ScmStreamGetCh(pStrmInput); + if (ch == '\\') + { + ch = ScmStreamGetCh(pStrmInput); + if (ch == ~(unsigned)0) + break; + rcExit = vbcppStrBufAppendCh(pStrBuf, '\\'); + if (rcExit == RTEXITCODE_SUCCESS) + rcExit = vbcppStrBufAppendCh(pStrBuf, ch); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + } + else if (ch != ~(unsigned)0) + { + rcExit = vbcppStrBufAppendCh(pStrBuf, ch); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + if (ch == (unsigned)chClose) + return RTEXITCODE_SUCCESS; + } + else + break; + } + + return vbcppError(pThis, "File ended with an open character constant"); +} + + +/** + * Extracts a line from the stream, stripping it for comments and maybe + * optimzing some of the whitespace. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + * @param pStrBuf Where to store the extracted line. Caller must + * initialize this prior to the call an delete it + * after use (even on failure). + * @param poffComment Where to note down the position of the final + * comment. Optional. + */ +static RTEXITCODE vbcppExtractDirectiveLine(PVBCPP pThis, PSCMSTREAM pStrmInput, PVBCPPSTRBUF pStrBuf, size_t *poffComment) +{ + size_t offComment = ~(size_t)0; + unsigned ch; + while ((ch = ScmStreamPeekCh(pStrmInput)) != ~(unsigned)0) + { + RTEXITCODE rcExit; + if (ch == '/') + { + /* Comment? */ + unsigned ch2 = ScmStreamGetCh(pStrmInput); Assert(ch == ch2); NOREF(ch2); + ch = ScmStreamPeekCh(pStrmInput); + if (ch == '*') + { + offComment = ScmStreamTell(pStrmInput) - 1; + rcExit = vbcppExtractSkipComment(pThis, pStrmInput); + } + else if (ch == '/') + { + offComment = ScmStreamTell(pStrmInput) - 1; + rcExit = vbcppExtractSkipCommentLine(pThis, pStrmInput); + } + else + rcExit = vbcppStrBufAppendCh(pStrBuf, '/'); + } + else if (ch == '\'') + { + offComment = ~(size_t)0; + rcExit = vbcppExtractQuotedString(pThis, pStrmInput, pStrBuf, '\'', '\''); + } + else if (ch == '"') + { + offComment = ~(size_t)0; + rcExit = vbcppExtractQuotedString(pThis, pStrmInput, pStrBuf, '"', '"'); + } + else if (ch == '\r' || ch == '\n') + break; /* done */ + else if ( RT_C_IS_SPACE(ch) + && ( RT_C_IS_SPACE(vbcppStrBufLastCh(pStrBuf)) + || vbcppStrBufLastCh(pStrBuf) == '\0') ) + { + unsigned ch2 = ScmStreamGetCh(pStrmInput); + Assert(ch == ch2); NOREF(ch2); + rcExit = RTEXITCODE_SUCCESS; + } + else + { + unsigned ch2 = ScmStreamGetCh(pStrmInput); Assert(ch == ch2); + + /* Escaped newline? */ + if ( ch == '\\' + && ( (ch2 = ScmStreamPeekCh(pStrmInput)) == '\r' + || ch2 == '\n')) + { + ScmStreamSeekByLine(pStrmInput, ScmStreamTellLine(pStrmInput) + 1); + rcExit = RTEXITCODE_SUCCESS; + } + else + { + offComment = ~(size_t)0; + rcExit = vbcppStrBufAppendCh(pStrBuf, ch); + } + } + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + } + + if (poffComment) + *poffComment = offComment; + return RTEXITCODE_SUCCESS; +} + + +/** + * Processes a abbreviated line number directive. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + * @param offStart The stream position where the directive + * started (for pass thru). + * @param enmKind The kind of directive we're processing. + */ +static RTEXITCODE vbcppDirectiveIfOrElif(PVBCPP pThis, PSCMSTREAM pStrmInput, size_t offStart, + VBCPPCONDKIND enmKind) +{ + /* + * Check for missing #if if #elif. + */ + if ( enmKind == kVBCppCondKind_ElIf + && !pThis->pCondStack ) + return vbcppError(pThis, "#elif without #if"); + + /* + * Extract the expression string. + */ + const char *pchCondition = ScmStreamGetCur(pStrmInput); + size_t offComment; + VBCPPMACROEXP ExpCtx; +#if 0 + ExpCtx.pMacroStack = NULL; +#endif + ExpCtx.pStrmInput = NULL; + ExpCtx.papszArgs = NULL; + ExpCtx.cArgs = 0; + ExpCtx.cArgsAlloced = 0; + vbcppStrBufInit(&ExpCtx.StrBuf, pThis); + RTEXITCODE rcExit = vbcppExtractDirectiveLine(pThis, pStrmInput, &ExpCtx.StrBuf, &offComment); + if (rcExit == RTEXITCODE_SUCCESS) + { + size_t const cchCondition = ScmStreamGetCur(pStrmInput) - pchCondition; + + /* + * Expand known macros in it. + */ + size_t cReplacements; + rcExit = vbcppMacroExpandReScan(pThis, &ExpCtx, kMacroReScanMode_Expression, &cReplacements); + if (rcExit == RTEXITCODE_SUCCESS) + { + /* + * Strip it and check that it's not empty. + */ + char *pszExpr = ExpCtx.StrBuf.pszBuf; + size_t cchExpr = ExpCtx.StrBuf.cchBuf; + while (cchExpr > 0 && RT_C_IS_SPACE(*pszExpr)) + pszExpr++, cchExpr--; + + while (cchExpr > 0 && RT_C_IS_SPACE(pszExpr[cchExpr - 1])) + { + pszExpr[--cchExpr] = '\0'; + ExpCtx.StrBuf.cchBuf--; + } + if (cchExpr) + { + /* + * Now, evalute the expression. + */ + VBCPPEVAL enmResult; + rcExit = vbcppExprEval(pThis, pszExpr, cchExpr, cReplacements, &enmResult); + if (rcExit == RTEXITCODE_SUCCESS) + { + /* + * Take action. + */ + if (enmKind != kVBCppCondKind_ElIf) + rcExit = vbcppCondPush(pThis, pStrmInput, offComment, enmKind, enmResult, + pchCondition, cchCondition); + else + { + PVBCPPCOND pCond = pThis->pCondStack; + if ( pCond->enmResult != kVBCppEval_Undecided + && ( !pCond->pUp + || pCond->pUp->enmStackResult == kVBCppEval_True)) + { + Assert(enmResult == kVBCppEval_True || enmResult == kVBCppEval_False); + if ( pCond->enmResult == kVBCppEval_False + && enmResult == kVBCppEval_True + && !pCond->fElIfDecided) + { + pCond->enmStackResult = kVBCppEval_True; + pCond->fElIfDecided = true; + } + else + pCond->enmStackResult = kVBCppEval_False; + pThis->fIf0Mode = pCond->enmStackResult == kVBCppEval_False; + } + pCond->enmKind = kVBCppCondKind_ElIf; + pCond->enmResult = enmResult; + pCond->pchCond = pchCondition; + pCond->cchCond = cchCondition; + + /* + * Do #elif pass thru. + */ + if ( !pThis->fIf0Mode + && pCond->enmResult == kVBCppEval_Undecided) + { + ssize_t cch = ScmStreamPrintf(&pThis->StrmOutput, "#%*selif", pCond->iKeepLevel - 1, ""); + if (cch > 0) + rcExit = vbcppOutputComment(pThis, pStrmInput, offStart, cch, 2); + else + rcExit = vbcppError(pThis, "Output error %Rrc", (int)cch); + } + else + pThis->fJustDroppedLine = true; + } + } + } + else + rcExit = vbcppError(pThis, "Empty #if expression"); + } + } + vbcppMacroExpandCleanup(&ExpCtx); + return rcExit; +} + + +/** + * Processes a abbreviated line number directive. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + * @param offStart The stream position where the directive + * started (for pass thru). + */ +static RTEXITCODE vbcppDirectiveIfDef(PVBCPP pThis, PSCMSTREAM pStrmInput, size_t offStart) +{ + /* + * Parse it. + */ + RTEXITCODE rcExit = vbcppProcessSkipWhiteEscapedEolAndComments(pThis, pStrmInput); + if (rcExit == RTEXITCODE_SUCCESS) + { + size_t cchDefine; + const char *pchDefine = ScmStreamCGetWord(pStrmInput, &cchDefine); + if (pchDefine) + { + rcExit = vbcppProcessSkipWhiteEscapedEolAndCommentsCheckEol(pThis, pStrmInput); + if (rcExit == RTEXITCODE_SUCCESS) + { + /* + * Evaluate it. + */ + VBCPPEVAL enmEval; + if (vbcppMacroExists(pThis, pchDefine, cchDefine)) + enmEval = kVBCppEval_True; + else if ( !pThis->fUndecidedConditionals + || RTStrSpaceGetN(&pThis->UndefStrSpace, pchDefine, cchDefine) != NULL) + enmEval = kVBCppEval_False; + else + enmEval = kVBCppEval_Undecided; + rcExit = vbcppCondPush(pThis, pStrmInput, offStart, kVBCppCondKind_IfDef, enmEval, + pchDefine, cchDefine); + } + } + else + rcExit = vbcppError(pThis, "Malformed #ifdef"); + } + return rcExit; +} + + +/** + * Processes a abbreviated line number directive. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + * @param offStart The stream position where the directive + * started (for pass thru). + */ +static RTEXITCODE vbcppDirectiveIfNDef(PVBCPP pThis, PSCMSTREAM pStrmInput, size_t offStart) +{ + /* + * Parse it. + */ + RTEXITCODE rcExit = vbcppProcessSkipWhiteEscapedEolAndComments(pThis, pStrmInput); + if (rcExit == RTEXITCODE_SUCCESS) + { + size_t cchDefine; + const char *pchDefine = ScmStreamCGetWord(pStrmInput, &cchDefine); + if (pchDefine) + { + rcExit = vbcppProcessSkipWhiteEscapedEolAndCommentsCheckEol(pThis, pStrmInput); + if (rcExit == RTEXITCODE_SUCCESS) + { + /* + * Evaluate it. + */ + VBCPPEVAL enmEval; + if (vbcppMacroExists(pThis, pchDefine, cchDefine)) + enmEval = kVBCppEval_False; + else if ( !pThis->fUndecidedConditionals + || RTStrSpaceGetN(&pThis->UndefStrSpace, pchDefine, cchDefine) != NULL) + enmEval = kVBCppEval_True; + else + enmEval = kVBCppEval_Undecided; + rcExit = vbcppCondPush(pThis, pStrmInput, offStart, kVBCppCondKind_IfNDef, enmEval, + pchDefine, cchDefine); + } + } + else + rcExit = vbcppError(pThis, "Malformed #ifndef"); + } + return rcExit; +} + + +/** + * Processes a abbreviated line number directive. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + * @param offStart The stream position where the directive + * started (for pass thru). + */ +static RTEXITCODE vbcppDirectiveElse(PVBCPP pThis, PSCMSTREAM pStrmInput, size_t offStart) +{ + /* + * Nothing to parse, just comment positions to find and note down. + */ + offStart = vbcppProcessSkipWhite(pStrmInput); + RTEXITCODE rcExit = vbcppProcessSkipWhiteEscapedEolAndCommentsCheckEol(pThis, pStrmInput); + if (rcExit == RTEXITCODE_SUCCESS) + { + /* + * Execute. + */ + PVBCPPCOND pCond = pThis->pCondStack; + if (pCond) + { + if (!pCond->fSeenElse) + { + pCond->fSeenElse = true; + if ( pCond->enmResult != kVBCppEval_Undecided + && ( !pCond->pUp + || pCond->pUp->enmStackResult == kVBCppEval_True)) + { + if ( pCond->enmResult == kVBCppEval_True + || pCond->fElIfDecided) + + pCond->enmStackResult = kVBCppEval_False; + else + pCond->enmStackResult = kVBCppEval_True; + pThis->fIf0Mode = pCond->enmStackResult == kVBCppEval_False; + } + + /* + * Do pass thru. + */ + if ( !pThis->fIf0Mode + && pCond->enmResult == kVBCppEval_Undecided) + { + ssize_t cch = ScmStreamPrintf(&pThis->StrmOutput, "#%*selse", pCond->iKeepLevel - 1, ""); + if (cch > 0) + rcExit = vbcppOutputComment(pThis, pStrmInput, offStart, cch, 2); + else + rcExit = vbcppError(pThis, "Output error %Rrc", (int)cch); + } + else + pThis->fJustDroppedLine = true; + } + else + rcExit = vbcppError(pThis, "Double #else or/and missing #endif"); + } + else + rcExit = vbcppError(pThis, "#else without #if"); + } + return rcExit; +} + + +/** + * Processes a abbreviated line number directive. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + * @param offStart The stream position where the directive + * started (for pass thru). + */ +static RTEXITCODE vbcppDirectiveEndif(PVBCPP pThis, PSCMSTREAM pStrmInput, size_t offStart) +{ + /* + * Nothing to parse, just comment positions to find and note down. + */ + offStart = vbcppProcessSkipWhite(pStrmInput); + RTEXITCODE rcExit = vbcppProcessSkipWhiteEscapedEolAndCommentsCheckEol(pThis, pStrmInput); + if (rcExit == RTEXITCODE_SUCCESS) + { + /* + * Execute. + */ + PVBCPPCOND pCond = pThis->pCondStack; + if (pCond) + { + pThis->pCondStack = pCond->pUp; + pThis->fIf0Mode = pCond->pUp && pCond->pUp->enmStackResult == kVBCppEval_False; + + /* + * Do pass thru. + */ + if ( !pThis->fIf0Mode + && pCond->enmResult == kVBCppEval_Undecided) + { + ssize_t cch = ScmStreamPrintf(&pThis->StrmOutput, "#%*sendif", pCond->iKeepLevel - 1, ""); + if (cch > 0) + rcExit = vbcppOutputComment(pThis, pStrmInput, offStart, cch, 1); + else + rcExit = vbcppError(pThis, "Output error %Rrc", (int)cch); + } + else + pThis->fJustDroppedLine = true; + } + else + rcExit = vbcppError(pThis, "#endif without #if"); + } + return rcExit; +} + + + + + +/* + * + * + * Misc Directives + * Misc Directives + * Misc Directives + * Misc Directives + * + * + */ + + +/** + * Adds an include directory. + * + * @returns Program exit code, with error message on failure. + * @param pThis The C preprocessor instance. + * @param pszDir The directory to add. + */ +static RTEXITCODE vbcppAddInclude(PVBCPP pThis, const char *pszDir) +{ + uint32_t cIncludes = pThis->cIncludes; + if (cIncludes >= _64K) + return vbcppError(pThis, "Too many include directories"); + + void *pv = RTMemRealloc(pThis->papszIncludes, (cIncludes + 1) * sizeof(char **)); + if (!pv) + return vbcppError(pThis, "No memory for include directories"); + pThis->papszIncludes = (char **)pv; + + int rc = RTStrDupEx(&pThis->papszIncludes[cIncludes], pszDir); + if (RT_FAILURE(rc)) + return vbcppError(pThis, "No string memory for include directories"); + + pThis->cIncludes = cIncludes + 1; + return RTEXITCODE_SUCCESS; +} + + +/** + * Processes a abbreviated line number directive. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + * @param offStart The stream position where the directive + * started (for pass thru). + */ +static RTEXITCODE vbcppDirectiveInclude(PVBCPP pThis, PSCMSTREAM pStrmInput, size_t offStart) +{ + RT_NOREF_PV(offStart); + + /* + * Parse it. + */ + RTEXITCODE rcExit = vbcppProcessSkipWhiteEscapedEolAndComments(pThis, pStrmInput); + if (rcExit == RTEXITCODE_SUCCESS) + { + size_t cchFileSpec = 0; + const char *pchFileSpec = NULL; + size_t cchFilename = 0; + const char *pchFilename = NULL; + + unsigned ch = ScmStreamPeekCh(pStrmInput); + unsigned chType = ch; + if (ch == '"' || ch == '<') + { + ScmStreamGetCh(pStrmInput); + pchFileSpec = pchFilename = ScmStreamGetCur(pStrmInput); + unsigned chEnd = chType == '<' ? '>' : '"'; + while ( (ch = ScmStreamGetCh(pStrmInput)) != ~(unsigned)0 + && ch != chEnd) + { + if (ch == '\r' || ch == '\n') + { + rcExit = vbcppError(pThis, "Multi-line include file specfications are not supported"); + break; + } + } + + if (rcExit == RTEXITCODE_SUCCESS) + { + if (ch != ~(unsigned)0) + cchFileSpec = cchFilename = ScmStreamGetCur(pStrmInput) - pchFilename - 1; + else + rcExit = vbcppError(pThis, "Expected '%c'", chType); + } + } + else if (vbcppIsCIdentifierLeadChar(ch)) + { + //pchFileSpec = ScmStreamCGetWord(pStrmInput, &cchFileSpec); + rcExit = vbcppError(pThis, "Including via a define is not implemented yet"); + } + else + rcExit = vbcppError(pThis, "Malformed include directive"); + + /* + * Take down the location of the next non-white space, in case we need + * to pass thru the directive further down. Then skip to the end of the + * line. + */ + size_t const offIncEnd = vbcppProcessSkipWhite(pStrmInput); + if (rcExit == RTEXITCODE_SUCCESS) + rcExit = vbcppProcessSkipWhiteEscapedEolAndCommentsCheckEol(pThis, pStrmInput); + + if (rcExit == RTEXITCODE_SUCCESS) + { + /* + * Execute it. + */ + if (pThis->enmIncludeAction == kVBCppIncludeAction_Include) + { + /** @todo Search for the include file and push it onto the input stack. + * Not difficult, just unnecessary rigth now. */ + rcExit = vbcppError(pThis, "Includes are fully implemented"); + } + else if (pThis->enmIncludeAction == kVBCppIncludeAction_PassThru) + { + /* Pretty print the passthru. */ + unsigned cchIndent = pThis->pCondStack ? pThis->pCondStack->iKeepLevel : 0; + ssize_t cch; + if (chType == '<') + cch = ScmStreamPrintf(&pThis->StrmOutput, "#%*sinclude <%.*s>", + cchIndent, "", cchFileSpec, pchFileSpec); + else if (chType == '"') + cch = ScmStreamPrintf(&pThis->StrmOutput, "#%*sinclude \"%.*s\"", + cchIndent, "", cchFileSpec, pchFileSpec); + else + cch = ScmStreamPrintf(&pThis->StrmOutput, "#%*sinclude %.*s", + cchIndent, "", cchFileSpec, pchFileSpec); + if (cch > 0) + rcExit = vbcppOutputComment(pThis, pStrmInput, offIncEnd, cch, 1); + else + rcExit = vbcppError(pThis, "Output error %Rrc", (int)cch); + + } + else + { + Assert(pThis->enmIncludeAction == kVBCppIncludeAction_Drop); + pThis->fJustDroppedLine = true; + } + } + } + return rcExit; +} + + +/** + * Processes a abbreviated line number directive. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + * @param offStart The stream position where the directive + * started (for pass thru). + */ +static RTEXITCODE vbcppDirectivePragma(PVBCPP pThis, PSCMSTREAM pStrmInput, size_t offStart) +{ + RT_NOREF_PV(offStart); + + /* + * Parse out the first word. + */ + RTEXITCODE rcExit = vbcppProcessSkipWhiteEscapedEolAndComments(pThis, pStrmInput); + if (rcExit == RTEXITCODE_SUCCESS) + { + size_t cchPragma; + const char *pchPragma = ScmStreamCGetWord(pStrmInput, &cchPragma); + if (pchPragma) + { + size_t const off2nd = vbcppProcessSkipWhite(pStrmInput); + size_t offComment; + rcExit = vbcppInputSkipToEndOfDirectiveLine(pThis, pStrmInput, &offComment); + if (rcExit == RTEXITCODE_SUCCESS) + { + /* + * What to do about this + */ + bool fPassThru = false; + if ( cchPragma == 1 + && *pchPragma == 'D') + fPassThru = pThis->fPassThruPragmaD; + else if ( cchPragma == 3 + && !strncmp(pchPragma, "STD", 3)) + fPassThru = pThis->fPassThruPragmaSTD; + else + fPassThru = pThis->fPassThruPragmaOther; + if (fPassThru) + { + unsigned cchIndent = pThis->pCondStack ? pThis->pCondStack->iKeepLevel : 0; + ssize_t cch = ScmStreamPrintf(&pThis->StrmOutput, "#%*spragma %.*s", + cchIndent, "", cchPragma, pchPragma); + if (cch > 0) + rcExit = vbcppOutputComment(pThis, pStrmInput, off2nd, cch, 1); + else + rcExit = vbcppError(pThis, "output error"); + } + else + pThis->fJustDroppedLine = true; + } + } + else + rcExit = vbcppError(pThis, "Malformed #pragma"); + } + + return rcExit; +} + + +/** + * Processes an error directive. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + * @param offStart The stream position where the directive + * started (for pass thru). + */ +static RTEXITCODE vbcppDirectiveError(PVBCPP pThis, PSCMSTREAM pStrmInput, size_t offStart) +{ + RT_NOREF_PV(offStart); + RT_NOREF_PV(pStrmInput); + return vbcppError(pThis, "Hit an #error"); +} + + +/** + * Processes a abbreviated line number directive. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + * @param offStart The stream position where the directive + * started (for pass thru). + */ +static RTEXITCODE vbcppDirectiveLineNo(PVBCPP pThis, PSCMSTREAM pStrmInput, size_t offStart) +{ + RT_NOREF_PV(offStart); + RT_NOREF_PV(pStrmInput); + return vbcppError(pThis, "Not implemented: %s", __FUNCTION__); +} + + +/** + * Processes a abbreviated line number directive. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + */ +static RTEXITCODE vbcppDirectiveLineNoShort(PVBCPP pThis, PSCMSTREAM pStrmInput) +{ + RT_NOREF_PV(pStrmInput); + return vbcppError(pThis, "Not implemented: %s", __FUNCTION__); +} + + +/** + * Handles a preprocessor directive. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + */ +static RTEXITCODE vbcppProcessDirective(PVBCPP pThis, PSCMSTREAM pStrmInput) +{ + /* + * Get the directive and do a string switch on it. + */ + RTEXITCODE rcExit = vbcppProcessSkipWhiteEscapedEolAndComments(pThis, pStrmInput); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + size_t cchDirective; + const char *pchDirective = ScmStreamCGetWord(pStrmInput, &cchDirective); + if (pchDirective) + { + size_t const offStart = ScmStreamTell(pStrmInput); +#define IS_DIRECTIVE(a_sz) ( sizeof(a_sz) - 1 == cchDirective && strncmp(pchDirective, a_sz, sizeof(a_sz) - 1) == 0) + if (IS_DIRECTIVE("if")) + rcExit = vbcppDirectiveIfOrElif(pThis, pStrmInput, offStart, kVBCppCondKind_If); + else if (IS_DIRECTIVE("elif")) + rcExit = vbcppDirectiveIfOrElif(pThis, pStrmInput, offStart, kVBCppCondKind_ElIf); + else if (IS_DIRECTIVE("ifdef")) + rcExit = vbcppDirectiveIfDef(pThis, pStrmInput, offStart); + else if (IS_DIRECTIVE("ifndef")) + rcExit = vbcppDirectiveIfNDef(pThis, pStrmInput, offStart); + else if (IS_DIRECTIVE("else")) + rcExit = vbcppDirectiveElse(pThis, pStrmInput, offStart); + else if (IS_DIRECTIVE("endif")) + rcExit = vbcppDirectiveEndif(pThis, pStrmInput, offStart); + else if (!pThis->fIf0Mode) + { + if (IS_DIRECTIVE("include")) + rcExit = vbcppDirectiveInclude(pThis, pStrmInput, offStart); + else if (IS_DIRECTIVE("define")) + rcExit = vbcppDirectiveDefine(pThis, pStrmInput, offStart); + else if (IS_DIRECTIVE("undef")) + rcExit = vbcppDirectiveUndef(pThis, pStrmInput, offStart); + else if (IS_DIRECTIVE("pragma")) + rcExit = vbcppDirectivePragma(pThis, pStrmInput, offStart); + else if (IS_DIRECTIVE("error")) + rcExit = vbcppDirectiveError(pThis, pStrmInput, offStart); + else if (IS_DIRECTIVE("line")) + rcExit = vbcppDirectiveLineNo(pThis, pStrmInput, offStart); + else + rcExit = vbcppError(pThis, "Unknown preprocessor directive '#%.*s'", cchDirective, pchDirective); + } +#undef IS_DIRECTIVE + } + else if (!pThis->fIf0Mode) + { + /* Could it be a # <num> "file" directive? */ + unsigned ch = ScmStreamPeekCh(pStrmInput); + if (RT_C_IS_DIGIT(ch)) + rcExit = vbcppDirectiveLineNoShort(pThis, pStrmInput); + else + rcExit = vbcppError(pThis, "Malformed preprocessor directive"); + } + return rcExit; +} + + +/* + * + * + * M a i n b o d y. + * M a i n b o d y. + * M a i n b o d y. + * M a i n b o d y. + * M a i n b o d y. + * + * + */ + + +/** + * Does the actually preprocessing of the input file. + * + * @returns Exit code. + * @param pThis The C preprocessor instance. + */ +static RTEXITCODE vbcppPreprocess(PVBCPP pThis) +{ + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + + /* + * Parse. + */ + while (pThis->pInputStack) + { + pThis->fMaybePreprocessorLine = true; + + PSCMSTREAM pStrmInput = &pThis->pInputStack->StrmInput; + unsigned ch; + while ((ch = ScmStreamGetCh(pStrmInput)) != ~(unsigned)0) + { + if (ch == '/') + { + ch = ScmStreamPeekCh(pStrmInput); + if (ch == '*') + rcExit = vbcppProcessMultiLineComment(pThis, pStrmInput); + else if (ch == '/') + rcExit = vbcppProcessOneLineComment(pThis, pStrmInput); + else + { + pThis->fMaybePreprocessorLine = false; + if (!pThis->fIf0Mode) + rcExit = vbcppOutputCh(pThis, '/'); + } + } + else if (ch == '#' && pThis->fMaybePreprocessorLine) + { + rcExit = vbcppProcessDirective(pThis, pStrmInput); + pStrmInput = &pThis->pInputStack->StrmInput; + } + else if (ch == '\r' || ch == '\n') + { + if ( ( !pThis->fIf0Mode + && !pThis->fJustDroppedLine) + || !pThis->fRemoveDroppedLines + || !ScmStreamIsAtStartOfLine(&pThis->StrmOutput)) + rcExit = vbcppOutputCh(pThis, ch); + pThis->fJustDroppedLine = false; + pThis->fMaybePreprocessorLine = true; + } + else if (RT_C_IS_SPACE(ch)) + { + if (!pThis->fIf0Mode) + rcExit = vbcppOutputCh(pThis, ch); + } + else + { + pThis->fMaybePreprocessorLine = false; + if (!pThis->fIf0Mode) + { + if (ch == '"') + rcExit = vbcppProcessStringLitteral(pThis, pStrmInput); + else if (ch == '\'') + rcExit = vbcppProcessCharacterConstant(pThis, pStrmInput); + else if (vbcppIsCIdentifierLeadChar(ch)) + rcExit = vbcppProcessIdentifier(pThis, pStrmInput); + else if (RT_C_IS_DIGIT(ch)) + rcExit = vbcppProcessNumber(pThis, pStrmInput, ch); + else + rcExit = vbcppOutputCh(pThis, ch); + } + } + if (rcExit != RTEXITCODE_SUCCESS) + break; + } + + /* + * Check for errors. + */ + if (rcExit != RTEXITCODE_SUCCESS) + break; + + /* + * Pop the input stack. + */ + PVBCPPINPUT pPopped = pThis->pInputStack; + pThis->pInputStack = pPopped->pUp; + RTMemFree(pPopped); + } + + return rcExit; +} + + +/** + * Opens the input and output streams. + * + * @returns Exit code. + * @param pThis The C preprocessor instance. + */ +static RTEXITCODE vbcppOpenStreams(PVBCPP pThis) +{ + if (!pThis->pszInput) + return vbcppError(pThis, "Preprocessing the standard input stream is currently not supported"); + + size_t cchName = strlen(pThis->pszInput); + PVBCPPINPUT pInput = (PVBCPPINPUT)RTMemAlloc(RT_UOFFSETOF_DYN(VBCPPINPUT, szName[cchName + 1])); + if (!pInput) + return vbcppError(pThis, "out of memory"); + pInput->pUp = pThis->pInputStack; + pInput->pszSpecified = pInput->szName; + memcpy(pInput->szName, pThis->pszInput, cchName + 1); + pThis->pInputStack = pInput; + int rc = ScmStreamInitForReading(&pInput->StrmInput, pThis->pszInput); + if (RT_FAILURE(rc)) + return vbcppError(pThis, "ScmStreamInitForReading returned %Rrc when opening input file (%s)", + rc, pThis->pszInput); + + rc = ScmStreamInitForWriting(&pThis->StrmOutput, &pInput->StrmInput); + if (RT_FAILURE(rc)) + return vbcppError(pThis, "ScmStreamInitForWriting returned %Rrc", rc); + + pThis->fStrmOutputValid = true; + return RTEXITCODE_SUCCESS; +} + + +/** + * Changes the preprocessing mode. + * + * @param pThis The C preprocessor instance. + * @param enmMode The new mode. + */ +static void vbcppSetMode(PVBCPP pThis, VBCPPMODE enmMode) +{ + switch (enmMode) + { + case kVBCppMode_Standard: + pThis->fKeepComments = false; + pThis->fRespectSourceDefines = true; + pThis->fAllowRedefiningCmdLineDefines = true; + pThis->fPassThruDefines = false; + pThis->fUndecidedConditionals = false; + pThis->fPassThruPragmaD = false; + pThis->fPassThruPragmaSTD = true; + pThis->fPassThruPragmaOther = true; + pThis->fRemoveDroppedLines = false; + pThis->fLineSplicing = true; + pThis->enmIncludeAction = kVBCppIncludeAction_Include; + break; + + case kVBCppMode_Selective: + pThis->fKeepComments = true; + pThis->fRespectSourceDefines = false; + pThis->fAllowRedefiningCmdLineDefines = false; + pThis->fPassThruDefines = true; + pThis->fUndecidedConditionals = true; + pThis->fPassThruPragmaD = true; + pThis->fPassThruPragmaSTD = true; + pThis->fPassThruPragmaOther = true; + pThis->fRemoveDroppedLines = true; + pThis->fLineSplicing = false; + pThis->enmIncludeAction = kVBCppIncludeAction_PassThru; + break; + + case kVBCppMode_SelectiveD: + pThis->fKeepComments = true; + pThis->fRespectSourceDefines = true; + pThis->fAllowRedefiningCmdLineDefines = false; + pThis->fPassThruDefines = false; + pThis->fUndecidedConditionals = false; + pThis->fPassThruPragmaD = true; + pThis->fPassThruPragmaSTD = false; + pThis->fPassThruPragmaOther = false; + pThis->fRemoveDroppedLines = true; + pThis->fLineSplicing = false; + pThis->enmIncludeAction = kVBCppIncludeAction_Drop; + break; + + default: + AssertFailedReturnVoid(); + } + pThis->enmMode = enmMode; +} + + +/** + * Parses the command line options. + * + * @returns Program exit code. Exit on non-success or if *pfExit is set. + * @param pThis The C preprocessor instance. + * @param argc The argument count. + * @param argv The argument vector. + * @param pfExit Pointer to the exit indicator. + */ +static RTEXITCODE vbcppParseOptions(PVBCPP pThis, int argc, char **argv, bool *pfExit) +{ + RTEXITCODE rcExit; + + *pfExit = false; + + /* + * Option config. + */ + static RTGETOPTDEF const s_aOpts[] = + { + { "--define", 'D', RTGETOPT_REQ_STRING }, + { "--include-dir", 'I', RTGETOPT_REQ_STRING }, + { "--undefine", 'U', RTGETOPT_REQ_STRING }, + { "--keep-comments", 'C', RTGETOPT_REQ_NOTHING }, + { "--strip-comments", 'c', RTGETOPT_REQ_NOTHING }, + { "--D-strip", 'd', RTGETOPT_REQ_NOTHING }, + }; + + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetOptState; + int rc = RTGetOptInit(&GetOptState, argc, argv, &s_aOpts[0], RT_ELEMENTS(s_aOpts), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST); + AssertReleaseRCReturn(rc, RTEXITCODE_FAILURE); + + /* + * Process the options. + */ + while ((rc = RTGetOpt(&GetOptState, &ValueUnion)) != 0) + { + switch (rc) + { + case 'c': + pThis->fKeepComments = false; + break; + + case 'C': + pThis->fKeepComments = false; + break; + + case 'd': + vbcppSetMode(pThis, kVBCppMode_SelectiveD); + break; + + case 'D': + { + const char *pszEqual = strchr(ValueUnion.psz, '='); + if (pszEqual) + rcExit = vbcppMacroAdd(pThis, ValueUnion.psz, pszEqual - ValueUnion.psz, pszEqual + 1, RTSTR_MAX, true); + else + rcExit = vbcppMacroAdd(pThis, ValueUnion.psz, RTSTR_MAX, "1", 1, true); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + break; + } + + case 'I': + rcExit = vbcppAddInclude(pThis, ValueUnion.psz); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + break; + + case 'U': + rcExit = vbcppMacroUndef(pThis, ValueUnion.psz, RTSTR_MAX, true); + break; + + case 'h': + RTPrintf("No help yet, sorry\n"); + *pfExit = true; + return RTEXITCODE_SUCCESS; + + case 'V': + { + /* The following is assuming that svn does it's job here. */ + static const char s_szRev[] = "$Revision: 155244 $"; + const char *psz = RTStrStripL(strchr(s_szRev, ' ')); + RTPrintf("r%.*s\n", strchr(psz, ' ') - psz, psz); + *pfExit = true; + return RTEXITCODE_SUCCESS; + } + + case VINF_GETOPT_NOT_OPTION: + if (!pThis->pszInput) + pThis->pszInput = ValueUnion.psz; + else if (!pThis->pszOutput) + pThis->pszOutput = ValueUnion.psz; + else + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "too many file arguments"); + break; + + + /* + * Errors and bugs. + */ + default: + return RTGetOptPrintError(rc, &ValueUnion); + } + } + + return RTEXITCODE_SUCCESS; +} + + +/** + * Terminates the preprocessor. + * + * This may return failure if an error was delayed. + * + * @returns Exit code. + * @param pThis The C preprocessor instance. + */ +static RTEXITCODE vbcppTerm(PVBCPP pThis) +{ + /* + * Flush the output first. + */ + if (pThis->fStrmOutputValid) + { + if (pThis->pszOutput) + { + int rc = ScmStreamWriteToFile(&pThis->StrmOutput, "%s", pThis->pszOutput); + if (RT_FAILURE(rc)) + vbcppError(pThis, "ScmStreamWriteToFile failed with %Rrc when writing '%s'", rc, pThis->pszOutput); + } + else + { + int rc = ScmStreamWriteToStdOut(&pThis->StrmOutput); + if (RT_FAILURE(rc)) + vbcppError(pThis, "ScmStreamWriteToStdOut failed with %Rrc", rc); + } + } + + /* + * Cleanup. + */ + while (pThis->pInputStack) + { + ScmStreamDelete(&pThis->pInputStack->StrmInput); + void *pvFree = pThis->pInputStack; + pThis->pInputStack = pThis->pInputStack->pUp; + RTMemFree(pvFree); + } + + ScmStreamDelete(&pThis->StrmOutput); + + RTStrSpaceDestroy(&pThis->StrSpace, vbcppMacroFree, NULL); + pThis->StrSpace = NULL; + + uint32_t i = pThis->cIncludes; + while (i-- > 0) + RTStrFree(pThis->papszIncludes[i]); + RTMemFree(pThis->papszIncludes); + pThis->papszIncludes = NULL; + + return pThis->rcExit; +} + + +/** + * Initializes the C preprocessor instance data. + * + * @param pThis The C preprocessor instance data. + */ +static void vbcppInit(PVBCPP pThis) +{ + vbcppSetMode(pThis, kVBCppMode_Selective); + pThis->cIncludes = 0; + pThis->papszIncludes = NULL; + pThis->pszInput = NULL; + pThis->pszOutput = NULL; + pThis->StrSpace = NULL; + pThis->UndefStrSpace = NULL; + pThis->cCondStackDepth = 0; + pThis->pCondStack = NULL; + pThis->fIf0Mode = false; + pThis->fJustDroppedLine = false; + pThis->fMaybePreprocessorLine = true; + VBCPP_BITMAP_EMPTY(pThis->bmDefined); + pThis->cCondStackDepth = 0; + pThis->pInputStack = NULL; + RT_ZERO(pThis->StrmOutput); + pThis->rcExit = RTEXITCODE_SUCCESS; + pThis->fStrmOutputValid = false; +} + + + +int main(int argc, char **argv) +{ + int rc = RTR3InitExe(argc, &argv, 0); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + + /* + * Do the job. The code says it all. + */ + VBCPP This; + vbcppInit(&This); + bool fExit; + RTEXITCODE rcExit = vbcppParseOptions(&This, argc, argv, &fExit); + if (!fExit && rcExit == RTEXITCODE_SUCCESS) + { + rcExit = vbcppOpenStreams(&This); + if (rcExit == RTEXITCODE_SUCCESS) + rcExit = vbcppPreprocess(&This); + } + + if (rcExit == RTEXITCODE_SUCCESS) + rcExit = vbcppTerm(&This); + else + vbcppTerm(&This); + return rcExit; +} + diff --git a/src/bldprogs/VBoxCheckImports.cpp b/src/bldprogs/VBoxCheckImports.cpp new file mode 100644 index 00000000..42dd40c0 --- /dev/null +++ b/src/bldprogs/VBoxCheckImports.cpp @@ -0,0 +1,381 @@ +/* $Id: VBoxCheckImports.cpp $ */ +/** @file + * IPRT - Checks that a windows image only imports from a given set of DLLs. + */ + +/* + * Copyright (C) 2012-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/formats/mz.h> +#include <iprt/formats/pecoff.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef struct +{ + const char *pszImage; + FILE *pFile; + bool f32Bit; + union + { + IMAGE_NT_HEADERS32 Nt32; + IMAGE_NT_HEADERS64 Nt64; + } Hdrs; + + uint32_t cSections; + PIMAGE_SECTION_HEADER paSections; +} MYIMAGE; + + +static bool Failed(MYIMAGE *pThis, const char *pszFmt, ...) +{ + va_list va; + fprintf(stderr, "error '%s': ", pThis->pszImage); + va_start(va, pszFmt); + vfprintf(stderr, pszFmt, va); + va_end(va); + fprintf(stderr, "\n"); + return false; +} + + +static bool ReadPeHeaders(MYIMAGE *pThis) +{ + /* + * MZ header. + */ + IMAGE_DOS_HEADER MzHdr; + if (fread(&MzHdr, sizeof(MzHdr), 1, pThis->pFile) != 1) + return Failed(pThis, "Reading DOS header"); + + if (MzHdr.e_magic != IMAGE_DOS_SIGNATURE) + return Failed(pThis, "No MZ magic (found %#x)", MzHdr.e_magic); + + if (fseek(pThis->pFile, MzHdr.e_lfanew, SEEK_SET) != 0) + return Failed(pThis, "Seeking to %#lx", (unsigned long)MzHdr.e_lfanew); + + /* + * NT signature + file header. + */ + if (fread(&pThis->Hdrs.Nt32, offsetof(IMAGE_NT_HEADERS32, OptionalHeader), 1, pThis->pFile) != 1) + return Failed(pThis, "Reading NT file header"); + + if (pThis->Hdrs.Nt32.Signature != IMAGE_NT_SIGNATURE) + return Failed(pThis, "No PE magic (found %#x)", pThis->Hdrs.Nt32.Signature); + + if (pThis->Hdrs.Nt32.FileHeader.SizeOfOptionalHeader == sizeof(pThis->Hdrs.Nt32.OptionalHeader)) + pThis->f32Bit = true; + else if (pThis->Hdrs.Nt32.FileHeader.SizeOfOptionalHeader == sizeof(pThis->Hdrs.Nt64.OptionalHeader)) + pThis->f32Bit = false; + else + return Failed(pThis, "Unsupported SizeOfOptionalHeaders value: %#x", + pThis->Hdrs.Nt32.FileHeader.SizeOfOptionalHeader); + + /* + * NT optional header. + */ + if (fread(&pThis->Hdrs.Nt32.OptionalHeader, pThis->Hdrs.Nt32.FileHeader.SizeOfOptionalHeader, 1, pThis->pFile) != 1) + return Failed(pThis, "Reading NT optional header"); + + if ( pThis->Hdrs.Nt32.OptionalHeader.Magic + != (pThis->f32Bit ? IMAGE_NT_OPTIONAL_HDR32_MAGIC : IMAGE_NT_OPTIONAL_HDR64_MAGIC) ) + return Failed(pThis, "Bad optional header magic: %#x", pThis->Hdrs.Nt32.OptionalHeader.Magic); + + uint32_t NumberOfRvaAndSizes = pThis->f32Bit + ? pThis->Hdrs.Nt32.OptionalHeader.NumberOfRvaAndSizes + : pThis->Hdrs.Nt64.OptionalHeader.NumberOfRvaAndSizes; + if (NumberOfRvaAndSizes != IMAGE_NUMBEROF_DIRECTORY_ENTRIES) + return Failed(pThis, "Unsupported NumberOfRvaAndSizes value: %#x", NumberOfRvaAndSizes); + + /* + * Read the section table. + */ + pThis->cSections = pThis->Hdrs.Nt32.FileHeader.NumberOfSections; + if (!pThis->cSections) + return Failed(pThis, "No sections in image!"); + pThis->paSections = (PIMAGE_SECTION_HEADER)calloc(sizeof(pThis->paSections[0]), pThis->cSections); + if (!pThis->paSections) + return Failed(pThis, "Out of memory!"); + if (fread(pThis->paSections, sizeof(pThis->paSections[0]), pThis->cSections, pThis->pFile) != pThis->cSections) + return Failed(pThis, "Reading NT section headers"); + + + return true; +} + + +static bool ReadAtRva(MYIMAGE *pThis, uint32_t uRva, void *pvBuf, size_t cbToRead) +{ + unsigned const uRvaOrg = uRva; + size_t const cbToReadOrg = cbToRead; + + /* + * Header section. + */ + int iSh = -1; + uint32_t uSectRva = 0; + uint32_t offSectRaw = 0; + uint32_t cbSectRaw = pThis->f32Bit + ? pThis->Hdrs.Nt32.OptionalHeader.SizeOfHeaders + : pThis->Hdrs.Nt64.OptionalHeader.SizeOfHeaders; + uint32_t cbSectMax = pThis->paSections[0].VirtualAddress; + + for (;;) + { + /* Read if we've got a match. */ + uint32_t off = uRva - uSectRva; + if (off < cbSectMax) + { + uint32_t cbThis = cbSectMax - off; + if (cbThis > cbToRead) + cbThis = (uint32_t)cbToRead; + + memset(pvBuf, 0, cbThis); + + if (off < cbSectRaw) + { + if (fseek(pThis->pFile, offSectRaw + off, SEEK_SET) != 0) + return Failed(pThis, "Seeking to %#x", offSectRaw + off); + if (fread(pvBuf, RT_MIN(cbThis, cbSectRaw - off), 1, pThis->pFile) != 1) + return Failed(pThis, "Reading %u bytes at %#x", RT_MIN(cbThis, cbSectRaw - off), offSectRaw + off); + } + + cbToRead -= cbThis; + if (!cbToRead) + return true; + uRva += cbThis; + pvBuf = (uint8_t *)pvBuf + cbThis; + } + + /* next section */ + iSh++; + if ((unsigned)iSh >= pThis->cSections) + return Failed(pThis, "RVA %#x LB %u is outside the image", uRvaOrg, cbToReadOrg); + uSectRva = pThis->paSections[iSh].VirtualAddress; + offSectRaw = pThis->paSections[iSh].PointerToRawData; + cbSectRaw = pThis->paSections[iSh].SizeOfRawData; + if ((unsigned)iSh + 1 < pThis->cSections) + cbSectMax = pThis->paSections[iSh + 1].VirtualAddress - uSectRva; + else + cbSectMax = pThis->paSections[iSh].Misc.VirtualSize; + } +} + + +static bool ReadStringAtRva(MYIMAGE *pThis, uint32_t uRva, char *pszBuf, size_t cbMax) +{ + uint32_t const uRvaOrg = uRva; + + /* + * Try read the whole string at once. + */ + uint32_t cbImage = pThis->f32Bit + ? pThis->Hdrs.Nt32.OptionalHeader.SizeOfImage + : pThis->Hdrs.Nt64.OptionalHeader.SizeOfImage; + uint32_t cbThis = uRva < cbImage ? cbImage - uRva : 1; + if (cbThis > cbMax) + cbThis = (uint32_t)cbMax; + if (!ReadAtRva(pThis, uRva, pszBuf, cbThis)) + return false; + if (memchr(pszBuf, 0, cbThis) != NULL) + return true; + + /* + * Read more, byte-by-byte. + */ + for (;;) + { + cbMax -= cbThis; + if (!cbMax) + return Failed(pThis, "String to long at %#x", uRvaOrg); + pszBuf += cbThis; + uRva += cbThis; + + cbThis = 1; + if (!ReadAtRva(pThis, uRva, pszBuf, cbThis)) + return false; + if (!*pszBuf) + return true; + } +} + + +static void *ReadAtRvaAlloc(MYIMAGE *pThis, uint32_t uRva, size_t cbToRead) +{ + void *pvBuf = malloc(cbToRead); + if (pvBuf) + { + if (ReadAtRva(pThis, uRva, pvBuf, cbToRead)) + return pvBuf; + free(pvBuf); + } + else + Failed(pThis, "Out of memory!"); + return NULL; +} + + +static bool ParseAndCheckImports(MYIMAGE *pThis, const char **papszAllowed, unsigned cAllowed) +{ + /* + * Do we have an import directory? If so, read it. + */ + IMAGE_DATA_DIRECTORY ImpDir = pThis->f32Bit + ? pThis->Hdrs.Nt32.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT] + : pThis->Hdrs.Nt64.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]; + if (ImpDir.Size == 0) + return true; + + uint32_t cImps = ImpDir.Size / sizeof(IMAGE_IMPORT_DESCRIPTOR); + if (cImps * sizeof(IMAGE_IMPORT_DESCRIPTOR) != ImpDir.Size) + return Failed(pThis, "Import directory size is not a multiple of IMAGE_IMPORT_DESCRIPTOR: %#x", ImpDir.Size); + + PIMAGE_IMPORT_DESCRIPTOR paImps = (PIMAGE_IMPORT_DESCRIPTOR)ReadAtRvaAlloc(pThis, ImpDir.VirtualAddress, ImpDir.Size); + if (!paImps) + return false; + + /* There is usually an empty entry at the end. */ + if ( paImps[cImps - 1].Name == 0 + || paImps[cImps - 1].FirstThunk == 0) + cImps--; + + /* + * Do the processing. + */ + bool fRc = true; + for (uint32_t i = 0; i < cImps; i++) + { + /* Read the import name string. */ + char szName[128]; + if (!ReadStringAtRva(pThis, paImps[i].Name, szName, sizeof(szName))) + { + fRc = false; + break; + } + + /* Check it against the list of allowed DLLs. */ + bool fFound = false; + unsigned j = cAllowed; + while (j-- > 0) + if (stricmp(papszAllowed[j], szName) == 0) + { + fFound = true; + break; + } + if (!fFound) + fRc = Failed(pThis, "Illegal import: '%s'", szName); + } + + free(paImps); + return fRc; +} + + +static int usage(const char *argv0) +{ + printf("usage: %s --image <image> [allowed-dll [..]]\n", argv0); + return RTEXITCODE_SUCCESS; +} + + +int main(int argc, char **argv) +{ + /* + * Parse arguments. + */ + const char *pszImage = NULL; + const char **papszAllowed = (const char **)calloc(argc, sizeof(const char *)); + unsigned cAllowed = 0; + + for (int i = 1; i < argc; i++) + { + const char *psz = argv[i]; + if (*psz == '-') + { + if (!strcmp(psz, "--image") || !strcmp(psz, "-i")) + { + if (++i >= argc) + { + fprintf(stderr, "syntax error: File name expected after '%s'.\n", psz); + return RTEXITCODE_SYNTAX; + } + pszImage = argv[i]; + } + else if ( !strcmp(psz, "--help") + || !strcmp(psz, "-help") + || !strcmp(psz, "-h") + || !strcmp(psz, "-?") ) + return usage(argv[0]); + else if ( !strcmp(psz, "--version") + || !strcmp(psz, "-V")) + { + printf("$Revision: 155244 $\n"); + return RTEXITCODE_SUCCESS; + } + else + { + fprintf(stderr, "syntax error: Unknown option '%s'.\n", psz); + return RTEXITCODE_SYNTAX; + } + } + else + papszAllowed[cAllowed++] = argv[i]; + } + + if (!pszImage) + { + fprintf(stderr, "syntax error: No input file specified.\n"); + return RTEXITCODE_SYNTAX; + } + + /* + * Open the image and process headers. + */ + RTEXITCODE rcExit = RTEXITCODE_FAILURE; + MYIMAGE MyImage; + memset(&MyImage, 0, sizeof(MyImage)); + MyImage.pszImage = pszImage; + MyImage.pFile = fopen(pszImage, "rb"); + if (MyImage.pFile) + { + if ( ReadPeHeaders(&MyImage) + && ParseAndCheckImports(&MyImage, papszAllowed, cAllowed)) + rcExit = RTEXITCODE_SUCCESS; + + fclose(MyImage.pFile); + free(MyImage.paSections); + } + else + Failed(&MyImage, "Failed to open image for binary reading."); + + return rcExit; +} + diff --git a/src/bldprogs/VBoxCmp.cpp b/src/bldprogs/VBoxCmp.cpp new file mode 100644 index 00000000..c4f08950 --- /dev/null +++ b/src/bldprogs/VBoxCmp.cpp @@ -0,0 +1,141 @@ +/* $Id: VBoxCmp.cpp $ */ +/** @file + * File Compare - Compares two files byte by byte. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <sys/types.h> +#include <sys/stat.h> +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> + +#include <iprt/string.h> +#include <iprt/stdarg.h> + + +/** + * Writes an error message. + * + * @returns RTEXITCODE_FAILURE. + * @param pcszFormat Error message. + * @param ... Format argument referenced in the message. + */ +static RTEXITCODE printErr(const char *pcszFormat, ...) +{ + va_list va; + + fprintf(stderr, "VBoxCmp: "); + va_start(va, pcszFormat); + vfprintf(stderr, pcszFormat, va); + va_end(va); + + return RTEXITCODE_FAILURE; +} + + +static FILE *openFile(const char *pszFile) +{ +#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) + FILE *pFile = fopen(pszFile, "rb"); +#else + FILE *pFile = fopen(pszFile, "r"); +#endif + if (!pFile) + printErr("Failed to open '%s': %s\n", pszFile, strerror(errno)); + return pFile; +} + + +static RTEXITCODE compareFiles(FILE *pFile1, FILE *pFile2) +{ + if (!pFile1 || !pFile2) + return RTEXITCODE_FAILURE; + + uint32_t cMismatches = 1; + RTEXITCODE rcRet = RTEXITCODE_SUCCESS; + uint64_t off = 0; + for (;;) + { + uint8_t b1; + size_t cb1 = fread(&b1, sizeof(b1), 1, pFile1); + uint8_t b2; + size_t cb2 = fread(&b2, sizeof(b2), 1, pFile2); + if (cb1 != 1 || cb2 != 1) + break; + if (b1 != b2) + { + printErr("0x%x%08x: %#04x (%3d) != %#04x (%3d)\n", (uint32_t)(off >> 32), (uint32_t)off, b1, b1, b2, b2); + rcRet = RTEXITCODE_FAILURE; + cMismatches++; + if (cMismatches > 128) + { + printErr("Too many mismatches, giving up\n"); + return rcRet; + } + } + off++; + } + + if (!feof(pFile1) || !feof(pFile2)) + { + if (!feof(pFile1) && ferror(pFile1)) + rcRet = printErr("Read error on file #1.\n"); + else if (!feof(pFile2) && ferror(pFile2)) + rcRet = printErr("Read error on file #2.\n"); + else if (!feof(pFile2)) + rcRet = printErr("0x%x%08x: file #1 ends before file #2\n", (uint32_t)(off >> 32), (uint32_t)off); + else + rcRet = printErr("0x%x%08x: file #2 ends before file #1\n", (uint32_t)(off >> 32), (uint32_t)off); + } + + return rcRet; +} + + +int main(int argc, char *argv[]) +{ + RTEXITCODE rcExit; + + if (argc == 3) + { + const char *pszFile1 = argv[1]; + const char *pszFile2 = argv[2]; + FILE *pFile1 = openFile(pszFile1); + FILE *pFile2 = openFile(pszFile2); + rcExit = compareFiles(pFile1, pFile2); + if (pFile1) + fclose(pFile1); + if (pFile2) + fclose(pFile2); + } + else + rcExit = printErr("Syntax error: usage: VBoxCmp <file1> <file2>\n"); + return rcExit; +} + diff --git a/src/bldprogs/VBoxCompilerPlugIns.h b/src/bldprogs/VBoxCompilerPlugIns.h new file mode 100644 index 00000000..9f521449 --- /dev/null +++ b/src/bldprogs/VBoxCompilerPlugIns.h @@ -0,0 +1,137 @@ +/* $Id: VBoxCompilerPlugIns.h $ */ +/** @file + * VBoxCompilerPlugIns - Types, Prototypes and Macros common to the VBox compiler plug-ins. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef VBOX_INCLUDED_SRC_bldprogs_VBoxCompilerPlugIns_h +#define VBOX_INCLUDED_SRC_bldprogs_VBoxCompilerPlugIns_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/types.h> +#include <stdio.h> + + +/** @def dprintf + * Macro for debug printing using fprintf. Only active when DEBUG is defined. + */ +#ifdef DEBUG +# define dprintf(...) do { fprintf(stderr, __VA_ARGS__); } while (0) +#else +# define dprintf(...) do { } while (0) +#endif + + +/** + * Checker state. + */ +typedef struct VFMTCHKSTATE +{ + long iFmt; + long iArgs; + const char *pszFmt; + bool fMaybeNull; +#if defined(__GNUC__) && !defined(VBOX_COMPILER_PLUG_IN_AGNOSTIC) +# if RT_GNUC_PREREQ(6, 0) + gimple const *hStmt; +# else + gimple hStmt; +# endif + location_t hFmtLoc; +#endif +} VFMTCHKSTATE; +/** Pointer to my checker state. */ +typedef VFMTCHKSTATE *PVFMTCHKSTATE; + +#define MYSTATE_FMT_FILE(a_pState) VFmtChkGetFmtLocFile(a_pState) +#define MYSTATE_FMT_LINE(a_pState) VFmtChkGetFmtLocLine(a_pState) +#define MYSTATE_FMT_COLUMN(a_pState) VFmtChkGetFmtLocColumn(a_pState) + +const char *VFmtChkGetFmtLocFile(PVFMTCHKSTATE pState); + +unsigned int VFmtChkGetFmtLocLine(PVFMTCHKSTATE pState); + +unsigned int VFmtChkGetFmtLocColumn(PVFMTCHKSTATE pState); + +/** + * Implements checking format string replacement (%M). + * + * Caller will have checked all that can be checked. This means that there is a + * string argument present, or it won't make the call. + * + * @param pState The format string checking state. + * @param pszPctM The position of the '%M'. + * @param iArg The next argument number. + */ +void VFmtChkHandleReplacementFormatString(PVFMTCHKSTATE pState, const char *pszPctM, unsigned iArg); + +/** + * Warning. + * + * @returns + * @param pState . + * @param pszLoc . + * @param pszFormat . + * @param ... . + */ +void VFmtChkWarnFmt(PVFMTCHKSTATE pState, const char *pszLoc, const char *pszFormat, ...); + +/** + * Error. + * + * @returns + * @param pState . + * @param pszLoc . + * @param pszFormat . + * @param ... . + */ +void VFmtChkErrFmt(PVFMTCHKSTATE pState, const char *pszLoc, const char *pszFormat, ...); + +/** + * Checks that @a iFmtArg isn't present or a valid final dummy argument. + * + * Will issue warning/error if there are more arguments at @a iFmtArg. + * + * @param pState The format string checking state. + * @param iArg The index of the end of arguments, this is + * relative to VFMTCHKSTATE::iArgs. + */ +void VFmtChkVerifyEndOfArgs(PVFMTCHKSTATE pState, unsigned iArg); + +bool VFmtChkRequirePresentArg(PVFMTCHKSTATE pState, const char *pszLoc, unsigned iArg, const char *pszMessage); + +bool VFmtChkRequireIntArg(PVFMTCHKSTATE pState, const char *pszLoc, unsigned iArg, const char *pszMessage); + +bool VFmtChkRequireStringArg(PVFMTCHKSTATE pState, const char *pszLoc, unsigned iArg, const char *pszMessage); + +bool VFmtChkRequireVaListPtrArg(PVFMTCHKSTATE pState, const char *pszLoc, unsigned iArg, const char *pszMessage); + +/* VBoxCompilerPlugInsCommon.cpp */ +void MyCheckFormatCString(PVFMTCHKSTATE pState, const char *pszFmt); + + +#endif /* !VBOX_INCLUDED_SRC_bldprogs_VBoxCompilerPlugIns_h */ + diff --git a/src/bldprogs/VBoxCompilerPlugInsCommon.cpp b/src/bldprogs/VBoxCompilerPlugInsCommon.cpp new file mode 100644 index 00000000..071e9951 --- /dev/null +++ b/src/bldprogs/VBoxCompilerPlugInsCommon.cpp @@ -0,0 +1,448 @@ +/* $Id: VBoxCompilerPlugInsCommon.cpp $ */ +/** @file + * VBoxCompilerPlugInsCommon - Code common to the compiler plug-ins. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define VBOX_COMPILER_PLUG_IN_AGNOSTIC +#include "VBoxCompilerPlugIns.h" + +#include <iprt/string.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define MY_ISDIGIT(c) ((c) >= '0' && (c) <= '9') + +/** @name RTSTR_Z_XXX - Size modifiers + * @{ */ +#define RTSTR_Z_DEFAULT UINT16_C(0x0001) +#define RTSTR_Z_LONG UINT16_C(0x0002) /**< l */ +#define RTSTR_Z_LONGLONG UINT16_C(0x0004) /**< ll, L, q. */ +#define RTSTR_Z_HALF UINT16_C(0x0008) /**< h */ +#define RTSTR_Z_HALFHALF UINT16_C(0x0010) /**< hh (internally H) */ +#define RTSTR_Z_SIZE UINT16_C(0x0020) /**< z */ +#define RTSTR_Z_PTRDIFF UINT16_C(0x0040) /**< t */ +#define RTSTR_Z_INTMAX UINT16_C(0x0080) /**< j */ +#define RTSTR_Z_MS_I32 UINT16_C(0x1000) /**< I32 */ +#define RTSTR_Z_MS_I64 UINT16_C(0x2000) /**< I64 */ +#define RTSTR_Z_ALL_INT UINT16_C(0x30fe) /**< short hand for integers. */ +/** @} */ + + +/** @name VFMTCHKTYPE_F_XXX - Type flags. + * @{ */ +/** Pointers type. */ +#define VFMTCHKTYPE_F_PTR UINT8_C(0x01) +/** Both const and non-const pointer types. */ +#define VFMTCHKTYPE_F_CPTR (UINT8_C(0x02) | VFMTCHKTYPE_F_PTR) +/** @} */ + +/** @name VFMTCHKTYPE_Z_XXX - Special type sizes + * @{ */ +#define VFMTCHKTYPE_Z_CHAR UINT8_C(0xe0) +#define VFMTCHKTYPE_Z_SHORT UINT8_C(0xe1) +#define VFMTCHKTYPE_Z_INT UINT8_C(0xe2) +#define VFMTCHKTYPE_Z_LONG UINT8_C(0xe3) +#define VFMTCHKTYPE_Z_LONGLONG UINT8_C(0xe4) +#define VFMTCHKTYPE_Z_PTR UINT8_C(0xe5) /**< ASSUMED to be the same for 'void *', 'size_t' and 'ptrdiff_t'. */ +/** @} */ + +/** @name VFMTCHKTYPE_NM_XXX - Standard C type names. + * @{ */ +#define VFMTCHKTYPE_NM_INT "int" +#define VFMTCHKTYPE_NM_UINT "unsigned int" +#define VFMTCHKTYPE_NM_LONG "long" +#define VFMTCHKTYPE_NM_ULONG "unsigned long" +#define VFMTCHKTYPE_NM_LONGLONG "long long" +#define VFMTCHKTYPE_NM_ULONGLONG "unsigned long long" +#define VFMTCHKTYPE_NM_SHORT "short" +#define VFMTCHKTYPE_NM_USHORT "unsigned short" +#define VFMTCHKTYPE_NM_CHAR "char" +#define VFMTCHKTYPE_NM_SCHAR "signed char" +#define VFMTCHKTYPE_NM_UCHAR "unsigned char" +/** @} */ + + +/** @name VFMTCHKDESC_F_XXX - Format descriptor flags. + * @{ */ +#define VFMTCHKDESC_F_NONE UINT32_C(0) +#define VFMTCHKDESC_F_SIGNED RT_BIT_32(0) +#define VFMTCHKDESC_F_UNSIGNED RT_BIT_32(1) +/** @} */ + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Format check type entry. + */ +typedef struct VFMTCHKTYPE +{ + /** The format size flag(s). */ + uint16_t fSize; + /** The argument size. */ + uint8_t cbArg; + /** Argument flags (VFMTCHKTYPE_F_XXX). */ + uint8_t fFlags; + /** List of strings with acceptable types, if NULL only check the sizes. */ + const char *pszzTypeNames; +} VFMTCHKTYPE; +/** Pointer to a read only format check type entry. */ +typedef VFMTCHKTYPE const *PCVFMTCHKTYPE; + +/** For use as an initializer in VFMTCHKDESK where it indicates that + * everything is covered by VFMTCHKDESC::paMoreTypes. Useful for repeating + * stuff. */ +#define VFMTCHKTYPE_USE_MORE_TYPES { 0, 0, 0, NULL } + +/** + * Format type descriptor. + */ +typedef struct VFMTCHKDESC +{ + /** The format type. */ + const char *pszType; + /** Recognized format flags (RTSTR_F_XXX). */ + uint16_t fFmtFlags; + /** Recognized format sizes (RTSTR_Z_XXX). */ + uint16_t fFmtSize; + /** Flags (VFMTCHKDESC_F_XXX). */ + uint32_t fFlags; + /** Primary type. */ + VFMTCHKTYPE Type; + /** More recognized types (optional). */ + PCVFMTCHKTYPE paMoreTypes; +} VFMTCHKDESC; +typedef VFMTCHKDESC const *PCVFMTCHKDESC; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Integer type specs for 'x', 'd', 'u', 'i', ++ + * + * @todo RTUINT32U and friends... The whole type matching thing. + */ +static VFMTCHKTYPE const g_aIntTypes[] = +{ + { RTSTR_Z_DEFAULT, VFMTCHKTYPE_Z_INT, 0, VFMTCHKTYPE_NM_INT "\0" VFMTCHKTYPE_NM_UINT "\0" }, + { RTSTR_Z_LONG, VFMTCHKTYPE_Z_LONG, 0, VFMTCHKTYPE_NM_LONG "\0" VFMTCHKTYPE_NM_ULONG "\0" }, + { RTSTR_Z_LONGLONG, VFMTCHKTYPE_Z_LONGLONG, 0, VFMTCHKTYPE_NM_LONGLONG "\0" VFMTCHKTYPE_NM_ULONGLONG "\0" }, + { RTSTR_Z_HALF, VFMTCHKTYPE_Z_SHORT, 0, VFMTCHKTYPE_NM_SHORT "\0" VFMTCHKTYPE_NM_USHORT "\0" }, + { RTSTR_Z_HALFHALF, VFMTCHKTYPE_Z_CHAR, 0, VFMTCHKTYPE_NM_SCHAR "\0" VFMTCHKTYPE_NM_UCHAR "\0" VFMTCHKTYPE_NM_CHAR "\0" }, + { RTSTR_Z_SIZE, VFMTCHKTYPE_Z_PTR, 0, "size_t\0" "RTUINTPTR\0" "RTINTPTR\0" }, + { RTSTR_Z_PTRDIFF, VFMTCHKTYPE_Z_PTR, 0, "ptrdiff_t\0" "RTUINTPTR\0" "RTINTPTR\0" }, + { RTSTR_Z_INTMAX, VFMTCHKTYPE_Z_PTR, 0, "uint64_t\0" "int64_t\0" "RTUINT64U\0" VFMTCHKTYPE_NM_LONGLONG "\0" VFMTCHKTYPE_NM_ULONGLONG "\0" }, + { RTSTR_Z_MS_I32, sizeof(uint32_t), 0, "uint32_t\0" "int32_t\0" "RTUINT32U\0" }, + { RTSTR_Z_MS_I64, sizeof(uint64_t), 0, "uint64_t\0" "int64_t\0" "RTUINT64U\0" }, +}; + +/** String type specs for 's', 'ls' and 'Ls'. + */ +static VFMTCHKTYPE const g_aStringTypes[] = +{ + { RTSTR_Z_DEFAULT, VFMTCHKTYPE_Z_PTR, VFMTCHKTYPE_F_CPTR, VFMTCHKTYPE_NM_CHAR "\0" }, + { RTSTR_Z_LONG, VFMTCHKTYPE_Z_PTR, VFMTCHKTYPE_F_CPTR, "RTUTF16\0" }, + { RTSTR_Z_LONGLONG, VFMTCHKTYPE_Z_PTR, VFMTCHKTYPE_F_CPTR, "RTUNICP\0" }, +}; + +static VFMTCHKDESC const g_aFmtDescs[] = +{ + { "s", + RTSTR_F_LEFT | RTSTR_F_WIDTH | RTSTR_F_PRECISION, + RTSTR_Z_DEFAULT | RTSTR_Z_LONG | RTSTR_Z_LONGLONG, + VFMTCHKDESC_F_UNSIGNED, + VFMTCHKTYPE_USE_MORE_TYPES, + g_aStringTypes + }, + { "x", + RTSTR_F_LEFT | RTSTR_F_ZEROPAD | RTSTR_F_SPECIAL | RTSTR_F_WIDTH | RTSTR_F_PRECISION, + RTSTR_Z_ALL_INT, + VFMTCHKDESC_F_UNSIGNED, + VFMTCHKTYPE_USE_MORE_TYPES, + g_aIntTypes + }, + { "RX32", + RTSTR_F_LEFT | RTSTR_F_ZEROPAD | RTSTR_F_SPECIAL | RTSTR_F_WIDTH | RTSTR_F_PRECISION, + RTSTR_Z_ALL_INT, + VFMTCHKDESC_F_UNSIGNED, + { RTSTR_Z_DEFAULT, sizeof(uint32_t), 0, "uint32_t\0" "int32_t\0" }, + NULL + }, + + + +}; + + +/** + * Does the actual format string checking. + * + * @todo Move this to different file common to both GCC and CLANG later. + * + * @param pState The format string checking state. + * @param pszFmt The format string. + */ +void MyCheckFormatCString(PVFMTCHKSTATE pState, const char *pszFmt) +{ + dprintf("checker2: \"%s\" at %s:%d col %d\n", pszFmt, + MYSTATE_FMT_FILE(pState), MYSTATE_FMT_LINE(pState), MYSTATE_FMT_COLUMN(pState)); + pState->pszFmt = pszFmt; + + unsigned iArg = 0; + for (;;) + { + /* + * Skip to the next argument. + * Quits the loop with the first char following the '%' in 'ch'. + */ + char ch; + for (;;) + { + ch = *pszFmt++; + if (ch == '%') + { + ch = *pszFmt++; + if (ch != '%') + break; + } + else if (ch == '\0') + { + VFmtChkVerifyEndOfArgs(pState, iArg); + return; + } + } + const char * const pszPct = pszFmt - 2; + + /* + * Flags + */ + uint32_t fFmtFlags = 0; + for (;;) + { + uint32_t fFlag; + switch (ch) + { + case '#': fFlag = RTSTR_F_SPECIAL; break; + case '-': fFlag = RTSTR_F_LEFT; break; + case '+': fFlag = RTSTR_F_PLUS; break; + case ' ': fFlag = RTSTR_F_BLANK; break; + case '0': fFlag = RTSTR_F_ZEROPAD; break; + case '\'': fFlag = RTSTR_F_THOUSAND_SEP; break; + default: fFlag = 0; break; + } + if (!fFlag) + break; + if (fFmtFlags & fFlag) + VFmtChkWarnFmt(pState, pszPct, "duplicate flag '%c'", ch); + fFmtFlags |= fFlag; + ch = *pszFmt++; + } + + /* + * Width. + */ + int cchWidth = -1; + if (MY_ISDIGIT(ch)) + { + cchWidth = ch - '0'; + while ( (ch = *pszFmt++) != '\0' + && MY_ISDIGIT(ch)) + { + cchWidth *= 10; + cchWidth += ch - '0'; + } + fFmtFlags |= RTSTR_F_WIDTH; + } + else if (ch == '*') + { + VFmtChkRequireIntArg(pState, pszPct, iArg, "width should be an 'int' sized argument"); + iArg++; + cchWidth = 0; + fFmtFlags |= RTSTR_F_WIDTH; + ch = *pszFmt++; + } + + /* + * Precision + */ + int cchPrecision = -1; + if (ch == '.') + { + ch = *pszFmt++; + if (MY_ISDIGIT(ch)) + { + cchPrecision = ch - '0'; + while ( (ch = *pszFmt++) != '\0' + && MY_ISDIGIT(ch)) + { + cchPrecision *= 10; + cchPrecision += ch - '0'; + } + } + else if (ch == '*') + { + VFmtChkRequireIntArg(pState, pszPct, iArg, "precision should be an 'int' sized argument"); + iArg++; + cchPrecision = 0; + ch = *pszFmt++; + } + else + VFmtChkErrFmt(pState, pszPct, "Missing precision value, only got the '.'"); + if (cchPrecision < 0) + { + VFmtChkErrFmt(pState, pszPct, "Negative precision value: %d", cchPrecision); + cchPrecision = 0; + } + fFmtFlags |= RTSTR_F_PRECISION; + } + + /* + * Argument size. + */ + uint16_t fFmtSize = RTSTR_Z_DEFAULT; + switch (ch) + { + default: + fFmtSize = RTSTR_Z_DEFAULT; + break; + + case 'z': + fFmtSize = RTSTR_Z_SIZE; + ch = *pszFmt++; + break; + case 'j': + fFmtSize = RTSTR_Z_INTMAX; + ch = *pszFmt++; + break; + case 't': + fFmtSize = RTSTR_Z_PTRDIFF; + ch = *pszFmt++; + break; + + case 'l': + fFmtSize = RTSTR_Z_LONG; + ch = *pszFmt++; + if (ch == 'l') + { + fFmtSize = RTSTR_Z_LONGLONG; + ch = *pszFmt++; + } + break; + + case 'q': /* Used on BSD platforms. */ + case 'L': + fFmtSize = RTSTR_Z_LONGLONG; + ch = *pszFmt++; + break; + + case 'h': + fFmtSize = RTSTR_Z_HALF; + ch = *pszFmt++; + if (ch == 'h') + { + fFmtSize = RTSTR_Z_HALFHALF; + ch = *pszFmt++; + } + break; + + case 'I': /* Used by Win32/64 compilers. */ + if ( pszFmt[0] == '6' + && pszFmt[1] == '4') + { + pszFmt += 2; + fFmtSize = RTSTR_Z_MS_I64; + } + else if ( pszFmt[0] == '3' + && pszFmt[1] == '2') + { + pszFmt += 2; + fFmtSize = RTSTR_Z_MS_I32; + } + else + { + VFmtChkErrFmt(pState, pszFmt, "Unknow format type/size/flag 'I%c'", pszFmt[0]); + fFmtSize = RTSTR_Z_INTMAX; + } + ch = *pszFmt++; + break; + } + + /* + * The type. + */ + switch (ch) + { + /* + * Nested extensions. + */ + case 'M': /* replace the format string (not stacked yet). */ + { + if (*pszFmt) + VFmtChkErrFmt(pState, pszFmt, "Characters following '%%M' will be ignored"); + if (fFmtSize != RTSTR_Z_DEFAULT) + VFmtChkWarnFmt(pState, pszFmt, "'%%M' does not support any size flags (%#x)", fFmtSize); + if (fFmtFlags != 0) + VFmtChkWarnFmt(pState, pszFmt, "'%%M' does not support any format flags (%#x)", fFmtFlags); + if (VFmtChkRequireStringArg(pState, pszPct, iArg, "'%M' expects a format string")) + VFmtChkHandleReplacementFormatString(pState, pszPct, iArg); + return; + } + + case 'N': /* real nesting. */ + { + if (fFmtSize != RTSTR_Z_DEFAULT) + VFmtChkWarnFmt(pState, pszFmt, "'%%N' does not support any size flags (%#x)", fFmtSize); + if (fFmtFlags != 0) + VFmtChkWarnFmt(pState, pszFmt, "'%%N' does not support any format flags (%#x)", fFmtFlags); + VFmtChkRequireStringArg(pState, pszPct, iArg, "'%N' expects a string followed by a va_list pointer"); + VFmtChkRequireVaListPtrArg(pState, pszPct, iArg + 1, "'%N' expects a string followed by a va_list pointer"); + iArg += 2; + break; + } + + case 'R': + if ( pszFmt[0] == 'h' + && pszFmt[1] == 'X') + { + VFmtChkRequirePresentArg(pState, pszPct, iArg, "Expected argument"); + iArg++; + } + RT_FALL_THROUGH(); + + default: + VFmtChkRequirePresentArg(pState, pszPct, iArg, "Expected argument"); + iArg++; + break; + } + } +} + diff --git a/src/bldprogs/VBoxCompilerPlugInsGcc.cpp b/src/bldprogs/VBoxCompilerPlugInsGcc.cpp new file mode 100644 index 00000000..04a0a133 --- /dev/null +++ b/src/bldprogs/VBoxCompilerPlugInsGcc.cpp @@ -0,0 +1,1012 @@ +/* $Id: VBoxCompilerPlugInsGcc.cpp $ */ +/** @file + * gccplugin - GCC plugin for checking IPRT format strings. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <stdio.h> +#include <iprt/cdefs.h> +#include <iprt/stdarg.h> + +#if RT_GNUC_PREREQ(5, 1) +# include "gcc-plugin.h" +# include "plugin-version.h" +#endif +#if __GNUC__ == 4 && __GNUC_MINOR__ == 5 +# include "gmp.h" +extern "C" { +#endif +#if __GNUC__ == 4 && __GNUC_MINOR__ == 5 +# include "coretypes.h" +#endif +#include "plugin.h" +#include "basic-block.h" +#include "tree.h" +#include "tree-pass.h" +#if __GNUC__ == 5 && __GNUC_MINOR__ == 4 +# include "tree-ssa-alias.h" +# include "gimple-expr.h" +#endif +#include "gimple.h" +#if RT_GNUC_PREREQ(4, 9) +# include "gimple-iterator.h" +# include "context.h" /* for g */ +#endif +#include "cp/cp-tree.h" +#if RT_GNUC_PREREQ(10, 0) +# include "stringpool.h" +# include "attribs.h" +#endif +#if __GNUC__ == 4 && __GNUC_MINOR__ == 5 +} +#endif + +#include "VBoxCompilerPlugIns.h" + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** License indicator. */ +int plugin_is_GPL_compatible; + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Convencience macro not present in earlier gcc versions. */ +#ifndef VAR_P +# define VAR_P(a_hNode) (TREE_CODE(a_hNode) == VAR_DECL) +#endif +/** Replacement for the 4.9.0 get_tree_code_name function. */ +#if !RT_GNUC_PREREQ(4, 9) +# define get_tree_code_name(a_enmTreeCode) (tree_code_name[a_enmTreeCode]) +#endif + + +/** For use with messages. + * @todo needs some more work... Actually, seems we're a bit handicapped by + * working on gimplified stuff. */ +#define MY_LOC(a_hPreferred, a_pState) EXPR_LOC_OR_LOC(a_hPreferred, (a_pState)->hFmtLoc) + +/** @name Compatibility glue + * @{ */ +#if __GNUC__ == 4 && __GNUC_MINOR__ == 5 +# define linemap_location_from_macro_expansion_p(a, b) false +#endif +#if __GNUC__ == 4 && __GNUC_MINOR__ == 5 +static tree gimple_call_fntype(gimple hStmt) +{ + tree hDecl = gimple_call_fndecl(hStmt); + if (hDecl) + return TREE_TYPE(hDecl); + hDecl = gimple_call_fn(hStmt); + if (TREE_CODE(hDecl) == OBJ_TYPE_REF) + hDecl = OBJ_TYPE_REF_EXPR(hDecl); + if (DECL_P(hDecl)) + { + tree hType = TREE_TYPE(hDecl); + if (POINTER_TYPE_P(hType)) + hType = TREE_TYPE(hType); + return hType; + } + return NULL_TREE; /* caller bitches about this*/ +} +#endif + +///* Integer to HOST_WIDE_INT conversion fun. */ +//#if RT_GNUC_PREREQ(4, 6) +//# define MY_INT_FITS_SHWI(hNode) (hNode).fits_shwi() +//# define MY_INT_TO_SHWI(hNode) (hNode).to_shwi() +//#else +//# define MY_INT_FITS_SHWI(hNode) double_int_fits_in_shwi_p(hNode) +//# define MY_INT_TO_SHWI(hNode) double_int_to_shwi(hNode) +//#endif + +/* Integer to HOST_WIDE_INT conversion fun. */ +#if RT_GNUC_PREREQ(5, 1) +# define MY_DOUBLE_INT_FITS_SHWI(hNode) tree_fits_shwi_p(hNode) +# define MY_DOUBLE_INT_TO_SHWI(hNode) tree_to_shwi(hNode) +#elif RT_GNUC_PREREQ(4, 6) +# define MY_DOUBLE_INT_FITS_SHWI(hNode) (TREE_INT_CST(hNode).fits_shwi()) +# define MY_DOUBLE_INT_TO_SHWI(hNode) (TREE_INT_CST(hNode).to_shwi()) +#else +# define MY_DOUBLE_INT_FITS_SHWI(hNode) double_int_fits_in_shwi_p(TREE_INT_CST(hNode)) +# define MY_DOUBLE_INT_TO_SHWI(hNode) double_int_to_shwi(TREE_INT_CST(hNode)) +#endif + +#ifndef EXPR_LOC_OR_LOC +# define EXPR_LOC_OR_LOC(a,b) (b) +#endif +/** @} */ + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static bool MyPassGateCallback(void); +static unsigned int MyPassExecuteCallback(void); +static unsigned int MyPassExecuteCallbackWithFunction(struct function *pFun); +static tree AttributeHandler(tree *, tree, tree, int, bool *); + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Plug-in info. */ +static const struct plugin_info g_PlugInInfo = +{ + version: "0.0.0-ALPHA", + help : "Implements the __iprt_format__ attribute for checking format strings and arguments." +}; + +#if RT_GNUC_PREREQ(4, 9) + +/** My pass. */ +static const pass_data g_MyPassData = +{ + type : GIMPLE_PASS, + name : "*iprt-format-checks", /* asterisk = no dump */ +# if RT_GNUC_PREREQ(10, 0) + optinfo_flags : OPTGROUP_NONE, +# else + optinfo_flags : 0, +# endif + tv_id : TV_NONE, + properties_required : 0, + properties_provided : 0, + properties_destroyed : 0, + todo_flags_start : 0, + todo_flags_finish : 0, +}; + +class MyPass : public gimple_opt_pass +{ +public: + MyPass(gcc::context *pCtx) : gimple_opt_pass(g_MyPassData, pCtx) + { } + + virtual bool gate(function *pFun) + { + NOREF(pFun); + return MyPassGateCallback(); + } + + virtual unsigned int execute(function *pFun) + { + NOREF(pFun); + return MyPassExecuteCallbackWithFunction(pFun); + } +}; + +#else /* < 4.9.0 */ + +/** My pass. */ +static struct gimple_opt_pass g_MyPass = +{ + pass: + { + type : GIMPLE_PASS, + name : "*iprt-format-checks", /* asterisk = no dump */ +# if RT_GNUC_PREREQ(4, 6) + optinfo_flags : 0, +# endif + gate : MyPassGateCallback, + execute : MyPassExecuteCallback, + sub : NULL, + next : NULL, + static_pass_number : 0, + tv_id : TV_NONE, + properties_required : 0, + properties_provided : 0, + properties_destroyed : 0, + todo_flags_start : 0, + todo_flags_finish : 0, + } +}; + +/** The registration info for my pass. */ +static const struct register_pass_info g_MyPassInfo = +{ + pass : &g_MyPass.pass, + reference_pass_name : "ssa", + ref_pass_instance_number : 1, + pos_op : PASS_POS_INSERT_BEFORE, +}; + +#endif /* < 4.9.0 */ + + +/** Attribute specifications. */ +static const struct attribute_spec g_AttribSpecs[] = +{ + { + name : "iprt_format", + min_length : 2, + max_length : 2, + decl_required : false, + type_required : true, + function_type_required : true, +// gcc 7.3 at least moves this field to after "handler", and with 8.3 it is back +#if RT_GNUC_PREREQ(4, 6) && !(RT_GNUC_PREREQ(7, 0) && !RT_GNUC_PREREQ(8, 0)) + affects_type_identity : false, +#endif + handler : AttributeHandler, +#if RT_GNUC_PREREQ(7, 0) && !RT_GNUC_PREREQ(8, 0) + affects_type_identity : false, +#endif +#if RT_GNUC_PREREQ(8, 0) + exclude : NULL, +#endif + }, + { + name : "iprt_format_maybe_null", + min_length : 2, + max_length : 2, + decl_required : false, + type_required : true, + function_type_required : true, +#if RT_GNUC_PREREQ(4, 6) && !(RT_GNUC_PREREQ(7, 0) && !RT_GNUC_PREREQ(8, 0)) + affects_type_identity : false, +#endif + handler : AttributeHandler, +#if RT_GNUC_PREREQ(7, 0) && !RT_GNUC_PREREQ(8, 0) + affects_type_identity : false, +#endif +#if RT_GNUC_PREREQ(8, 0) + exclude : NULL, +#endif + } +}; + + +#ifdef DEBUG + +/** + * Debug function for printing the scope of a decl. + * @param hDecl Declaration to print scope for. + */ +static void dprintScope(tree hDecl) +{ +# if 0 /* later? */ + tree hScope = CP_DECL_CONTEXT(hDecl); + if (hScope == global_namespace) + return; + if (TREE_CODE(hScope) == RECORD_TYPE) + hScope = TYPE_NAME(hScope); + + /* recurse */ + dprintScope(hScope); + + /* name the scope. */ + dprintf("::%s", DECL_NAME(hScope) ? IDENTIFIER_POINTER(DECL_NAME(hScope)) : "<noname>"); +# endif +} + + +/** + * Debug function for printing a declaration. + * @param hDecl The declaration to print. + */ +static void dprintDecl(tree hDecl) +{ + enum tree_code const enmDeclCode = TREE_CODE(hDecl); + tree const hType = TREE_TYPE(hDecl); + enum tree_code const enmTypeCode = hType ? TREE_CODE(hType) : (enum tree_code)-1; +#if 0 + if ( enmTypeCode == RECORD_TYPE + && enmDeclCode == TYPE_DECL + && DECL_ARTIFICIAL(hDecl)) + dprint_class(hType); +#endif + + dprintf("%s ", get_tree_code_name(enmDeclCode)); + dprintScope(hDecl); + dprintf("::%s", DECL_NAME(hDecl) ? IDENTIFIER_POINTER(DECL_NAME(hDecl)) : "<noname>"); + if (hType) + dprintf(" type %s", get_tree_code_name(enmTypeCode)); + dprintf(" @%s:%d", DECL_SOURCE_FILE(hDecl), DECL_SOURCE_LINE(hDecl)); +} + +#endif /* DEBUG */ + + +static location_t MyGetLocationPlusColumnOffset(location_t hLoc, unsigned int offColumn) +{ + /* + * Skip NOOPs, reserved locations and macro expansion. + */ + if ( offColumn != 0 + && hLoc >= RESERVED_LOCATION_COUNT + && !linemap_location_from_macro_expansion_p(line_table, hLoc)) + { +#if __GNUC__ >= 5 /** @todo figure this... */ + /* + * There is an API for doing this, nice. + */ + location_t hNewLoc = linemap_position_for_loc_and_offset(line_table, hLoc, offColumn); + if (hNewLoc && hNewLoc != hLoc) + { + dprintf("MyGetLocationPlusColumnOffset: hNewLoc=%#x hLoc=%#x offColumn=%u\n", hNewLoc, hLoc, offColumn); + return hNewLoc; + } + +#elif __GNUC_MINOR__ > 5 + /* + * Have to do the job ourselves, it seems. This is a bit hairy... + */ + line_map const *pMap = NULL; + location_t hLoc2 = linemap_resolve_location(line_table, hLoc, LRK_SPELLING_LOCATION, &pMap); + if (hLoc2) + hLoc = hLoc2; + + /* Guard against wrap arounds and overlaps. */ + if ( hLoc + offColumn > MAP_START_LOCATION(pMap) /** @todo Use MAX_SOURCE_LOCATION? */ + && ( pMap == LINEMAPS_LAST_ORDINARY_MAP(line_table) + || hLoc + offColumn < MAP_START_LOCATION((pMap + 1)))) + { + /* Calc new column and check that it's within the valid range. */ + unsigned int uColumn = SOURCE_COLUMN(pMap, hLoc) + offColumn; + if (uColumn < RT_BIT_32(ORDINARY_MAP_NUMBER_OF_COLUMN_BITS(pMap))) + { + /* Try add the position. If we get a valid result, replace the location. */ + source_location hNewLoc = linemap_position_for_line_and_column((line_map *)pMap, SOURCE_LINE(pMap, hLoc), uColumn); + if ( hNewLoc <= line_table->highest_location + && linemap_lookup(line_table, hNewLoc) != NULL) + { + dprintf("MyGetLocationPlusColumnOffset: hNewLoc=%#x hLoc=%#x offColumn=%u uColumn=%u\n", + hNewLoc, hLoc, offColumn, uColumn); + return hNewLoc; + } + } + } +#endif + } + dprintf("MyGetLocationPlusColumnOffset: taking fallback\n"); + return hLoc; +} + + +#if 0 +DECLINLINE(int) MyGetLineLength(const char *pszLine) +{ + if (pszLine) + { + const char *pszEol = strpbrk(pszLine, "\n\r"); + if (!pszEol) + pszEol = strchr(pszLine, '\0'); + return (int)(pszEol - pszLine); + } + return 0; +} +#endif + +static location_t MyGetFormatStringLocation(PVFMTCHKSTATE pState, const char *pszLoc) +{ + location_t hLoc = pState->hFmtLoc; +#if RT_GNUC_PREREQ(4,6) + intptr_t offString = pszLoc - pState->pszFmt; + if ( offString >= 0 + && !linemap_location_from_macro_expansion_p(line_table, hLoc)) + { + unsigned uCol = 1 + offString; +# if 0 /* apparently not needed */ + expanded_location XLoc = expand_location_to_spelling_point(hLoc); +# if RT_GNUC_PREREQ(10,0) + char_span Span = location_get_source_line(XLoc.file, XLoc.line); + const char *pszLine = Span.m_ptr; /** @todo if enabled */ + int cchLine = (int)Span.m_n_elts; +# elif RT_GNUC_PREREQ(6,0) + int cchLine = 0; + const char *pszLine = location_get_source_line(XLoc.file, XLoc.line, &cchLine); +# elif RT_GNUC_PREREQ(5,0) + int cchLine = 0; + const char *pszLine = location_get_source_line(XLoc, &cchLine); +# else + const char *pszLine = location_get_source_line(XLoc); + int cchLine = MyGetLineLength(pszLine); +# endif + if (pszLine) + { + /** @todo Adjust the position by parsing the source. */ + pszLine += XLoc.column - 1; + cchLine -= XLoc.column - 1; + } +# endif + + hLoc = MyGetLocationPlusColumnOffset(hLoc, uCol); + } +#endif + return hLoc; +} + + +/** + * Non-recursive worker for MyCheckFormatRecursive. + * + * This will attempt to result @a hFmtArg into a string literal which it then + * passes on to MyCheckFormatString for the actual analyzis. + * + * @param pState The format string checking state. + * @param hFmtArg The format string node. + */ +DECL_NO_INLINE(static, void) MyCheckFormatNonRecursive(PVFMTCHKSTATE pState, tree hFmtArg) +{ + dprintf("checker: hFmtArg=%p %s\n", hFmtArg, get_tree_code_name(TREE_CODE(hFmtArg))); + + /* + * Try resolve variables into constant strings. + */ + if (VAR_P(hFmtArg)) + { + hFmtArg = decl_constant_value(hFmtArg); + STRIP_NOPS(hFmtArg); /* Used as argument and assigned call result. */ + dprintf("checker1: variable => hFmtArg=%p %s\n", hFmtArg, get_tree_code_name(TREE_CODE(hFmtArg))); + } + + /* + * Fend off NULLs. + */ + if (integer_zerop(hFmtArg)) + { + if (pState->fMaybeNull) + VFmtChkVerifyEndOfArgs(pState, 0); + else + error_at(MY_LOC(hFmtArg, pState), "Format string should not be NULL"); + } + /* + * Need address expression to get any further. + */ + else if (TREE_CODE(hFmtArg) != ADDR_EXPR) + dprintf("checker1: Not address expression (%s)\n", get_tree_code_name(TREE_CODE(hFmtArg))); + else + { + pState->hFmtLoc = EXPR_LOC_OR_LOC(hFmtArg, pState->hFmtLoc); + hFmtArg = TREE_OPERAND(hFmtArg, 0); + + /* + * Deal with fixed string indexing, if possible. + */ + HOST_WIDE_INT off = 0; + if ( TREE_CODE(hFmtArg) == ARRAY_REF + && MY_DOUBLE_INT_FITS_SHWI(TREE_OPERAND(hFmtArg, 1)) + && MY_DOUBLE_INT_FITS_SHWI(TREE_OPERAND(hFmtArg, 1)) ) + { + off = MY_DOUBLE_INT_TO_SHWI(TREE_OPERAND(hFmtArg, 1)); + if (off < 0) + { + dprintf("checker1: ARRAY_REF, off=%ld\n", off); + return; + } + hFmtArg = TREE_OPERAND(hFmtArg, 0); + dprintf("checker1: ARRAY_REF => hFmtArg=%p %s, off=%ld\n", hFmtArg, get_tree_code_name(TREE_CODE(hFmtArg)), off); + } + + /* + * Deal with static const char g_szFmt[] = "qwerty"; Take care as + * the actual string constant may not necessarily include the terminator. + */ + tree hArraySize = NULL_TREE; + if ( VAR_P(hFmtArg) + && TREE_CODE(TREE_TYPE(hFmtArg)) == ARRAY_TYPE) + { + tree hArrayInitializer = decl_constant_value(hFmtArg); + if ( hArrayInitializer != hFmtArg + && TREE_CODE(hArrayInitializer) == STRING_CST) + { + hArraySize = DECL_SIZE_UNIT(hFmtArg); + hFmtArg = hArrayInitializer; + } + } + + /* + * Are we dealing with a string literal now? + */ + if (TREE_CODE(hFmtArg) != STRING_CST) + dprintf("checker1: Not string literal (%s)\n", get_tree_code_name(TREE_CODE(hFmtArg))); + else if (TYPE_MAIN_VARIANT(TREE_TYPE(TREE_TYPE(hFmtArg))) != char_type_node) + warning_at(pState->hFmtLoc, 0, "expected 'char' type string literal"); + else + { + /* + * Yes we are, so get the pointer to the string and its length. + */ + const char *pszFmt = TREE_STRING_POINTER(hFmtArg); + int cchFmt = TREE_STRING_LENGTH(hFmtArg); + + /* Adjust cchFmt to the initialized array size if appropriate. */ + if (hArraySize != NULL_TREE) + { + if (TREE_CODE(hArraySize) != INTEGER_CST) + warning_at(pState->hFmtLoc, 0, "Expected integer array size (not %s)", get_tree_code_name(TREE_CODE(hArraySize))); + else if (!MY_DOUBLE_INT_FITS_SHWI(hArraySize)) + warning_at(pState->hFmtLoc, 0, "Unexpected integer overflow in array size constant"); + else + { + HOST_WIDE_INT cbArray = MY_DOUBLE_INT_TO_SHWI(hArraySize); + if ( cbArray <= 0 + || cbArray != (int)cbArray) + warning_at(pState->hFmtLoc, 0, "Unexpected integer array size constant value: %ld", cbArray); + else if (cchFmt > cbArray) + { + dprintf("checker1: cchFmt=%d => cchFmt=%ld (=cbArray)\n", cchFmt, cbArray); + cchFmt = (int)cbArray; + } + } + } + + /* Apply the offset, if given. */ + if (off) + { + if (off >= cchFmt) + { + dprintf("checker1: off=%ld >= cchFmt=%d -> skipping\n", off, cchFmt); + return; + } + pszFmt += off; + cchFmt -= (int)off; + } + + /* + * Check for unterminated strings. + */ + if ( cchFmt < 1 + || pszFmt[cchFmt - 1] != '\0') + warning_at(pState->hFmtLoc, 0, "Unterminated format string (cchFmt=%d)", cchFmt); + /* + * Call worker to check the actual string. + */ + else + MyCheckFormatCString(pState, pszFmt); + } + } +} + + +/** + * Deal recursively with special format string constructs. + * + * This will call MyCheckFormatNonRecursive to validate each format string. + * + * @param pState The format string checking state. + * @param hFmtArg The format string node. + */ +static void MyCheckFormatRecursive(PVFMTCHKSTATE pState, tree hFmtArg) +{ + /* + * Catch wrong attribute use. + */ + if (hFmtArg == NULL_TREE) + error_at(pState->hFmtLoc, "IPRT format attribute is probably used incorrectly (hFmtArg is NULL)"); + /* + * NULL format strings may cause crashes. + */ + else if (integer_zerop(hFmtArg)) + { + if (pState->fMaybeNull) + VFmtChkVerifyEndOfArgs(pState, 0); + else + error_at(MY_LOC(hFmtArg, pState), "Format string should not be NULL"); + } + /* + * Check both branches of a ternary operator. + */ + else if (TREE_CODE(hFmtArg) == COND_EXPR) + { + MyCheckFormatRecursive(pState, TREE_OPERAND(hFmtArg, 1)); + MyCheckFormatRecursive(pState, TREE_OPERAND(hFmtArg, 2)); + } + /* + * Strip coercion. + */ + else if ( CONVERT_EXPR_P(hFmtArg) + && TYPE_PRECISION(TREE_TYPE(hFmtArg)) == TYPE_PRECISION(TREE_TYPE(TREE_OPERAND(hFmtArg, 0))) ) + MyCheckFormatRecursive(pState, TREE_OPERAND(hFmtArg, 0)); + /* + * We're good, hand it to the non-recursive worker. + */ + else + MyCheckFormatNonRecursive(pState, hFmtArg); +} + + +#if !RT_GNUC_PREREQ(4, 9) +/** + * Execute my pass. + * @returns Flags indicates stuff todo, we return 0. + */ +static unsigned int MyPassExecuteCallback(void) +{ + return MyPassExecuteCallbackWithFunction(cfun); +} +#endif + +/** + * Execute my pass. + * @returns Flags indicates stuff todo, we return 0. + */ +static unsigned int MyPassExecuteCallbackWithFunction(struct function *pFun) +{ + dprintf("MyPassExecuteCallback:\n"); + + /* + * Enumerate the basic blocks. + */ + basic_block hBasicBlock; + FOR_EACH_BB_FN(hBasicBlock, pFun) + { + dprintf(" hBasicBlock=%p\n", hBasicBlock); + + /* + * Enumerate the statements in the current basic block. + * We're interested in calls to functions with the __iprt_format__ attribute. + */ + for (gimple_stmt_iterator hStmtItr = gsi_start_bb(hBasicBlock); !gsi_end_p(hStmtItr); gsi_next(&hStmtItr)) + { +#if RT_GNUC_PREREQ(6, 0) + const gimple * const hStmt = gsi_stmt(hStmtItr); +#else + gimple const hStmt = gsi_stmt(hStmtItr); +#endif + + enum gimple_code const enmCode = gimple_code(hStmt); +#ifdef DEBUG + unsigned const cOps = gimple_num_ops(hStmt); + dprintf(" hStmt=%p %s (%d) ops=%d\n", hStmt, gimple_code_name[enmCode], enmCode, cOps); + for (unsigned iOp = 0; iOp < cOps; iOp++) + { + tree const hOp = gimple_op(hStmt, iOp); + if (hOp) + dprintf(" %02d: %p, code %s(%d)\n", iOp, hOp, get_tree_code_name(TREE_CODE(hOp)), TREE_CODE(hOp)); + else + dprintf(" %02d: NULL_TREE\n", iOp); + } +#endif + if (enmCode == GIMPLE_CALL) + { + /* + * Check if the function type has the __iprt_format__ attribute. + */ + tree const hFn = gimple_call_fn(hStmt); + dprintf(" hFn =%p %s(%d); args=%d\n", + hFn, hFn ? get_tree_code_name(TREE_CODE(hFn)) : NULL, hFn ? TREE_CODE(hFn) : - 1, + gimple_call_num_args(hStmt)); +#ifdef DEBUG + if (hFn && DECL_P(hFn)) + dprintf(" hFn is decl: %s %s:%d\n", + DECL_NAME(hFn) ? IDENTIFIER_POINTER(DECL_NAME(hFn)) : "<unamed>", + DECL_SOURCE_FILE(hFn), DECL_SOURCE_LINE(hFn)); +#endif + tree const hFnDecl = gimple_call_fndecl(hStmt); + if (hFnDecl) + dprintf(" hFnDecl=%p %s(%d) %s type=%p %s:%d\n", + hFnDecl, get_tree_code_name(TREE_CODE(hFnDecl)), TREE_CODE(hFnDecl), + DECL_NAME(hFnDecl) ? IDENTIFIER_POINTER(DECL_NAME(hFnDecl)) : "<unamed>", + TREE_TYPE(hFnDecl), DECL_SOURCE_FILE(hFnDecl), DECL_SOURCE_LINE(hFnDecl)); + tree const hFnType = gimple_call_fntype(hStmt); + if (hFnType == NULL_TREE) + { + if ( hFnDecl == NULL_TREE + && gimple_call_internal_p(hStmt) /* va_arg() kludge */) + continue; + error_at(gimple_location(hStmt), "Failed to resolve function type [fn=%s fndecl=%s]\n", + hFn ? get_tree_code_name(TREE_CODE(hFn)) : "<null>", + hFnDecl ? get_tree_code_name(TREE_CODE(hFnDecl)) : "<null>"); + } + else if (POINTER_TYPE_P(hFnType)) + error_at(gimple_location(hStmt), "Got a POINTER_TYPE when expecting a function type [fn=%s]\n", + get_tree_code_name(TREE_CODE(hFn))); + if (hFnType) + dprintf(" hFnType=%p %s(%d) %s\n", hFnType, get_tree_code_name(TREE_CODE(hFnType)), TREE_CODE(hFnType), + TYPE_NAME(hFnType) && DECL_NAME(TYPE_NAME(hFnType)) + ? IDENTIFIER_POINTER(DECL_NAME(TYPE_NAME(hFnType))) : "<unamed>"); + + tree const hAttr = hFnType ? lookup_attribute("iprt_format", TYPE_ATTRIBUTES(hFnType)) : NULL_TREE; + tree const hAttrMaybe0 = hFnType ? lookup_attribute("iprt_format_maybe_null", TYPE_ATTRIBUTES(hFnType)) : NULL_TREE; + if (hAttr || hAttrMaybe0) + { + /* + * Yeah, it has the attribute! + */ + tree const hAttrArgs = hAttr ? TREE_VALUE(hAttr) : TREE_VALUE(hAttrMaybe0); + VFMTCHKSTATE State; + State.iFmt = MY_DOUBLE_INT_TO_SHWI(TREE_VALUE(hAttrArgs)); + State.iArgs = MY_DOUBLE_INT_TO_SHWI(TREE_VALUE(TREE_CHAIN(hAttrArgs))); + State.pszFmt = NULL; + State.fMaybeNull = hAttr == NULL_TREE; + State.hStmt = hStmt; + State.hFmtLoc = gimple_location(hStmt); + dprintf(" %s() __iprt_format%s__(iFmt=%ld, iArgs=%ld)\n", + hFnDecl && DECL_NAME(hFnDecl) ? IDENTIFIER_POINTER(DECL_NAME(hFnDecl)) : "<unamed>", + State.fMaybeNull ? "_maybe_null" : "", State.iFmt, State.iArgs); + + unsigned cCallArgs = gimple_call_num_args(hStmt); + if (cCallArgs >= State.iFmt) + MyCheckFormatRecursive(&State, gimple_call_arg(hStmt, State.iFmt - 1)); + else + error_at(gimple_location(hStmt), + "Call has only %d arguments; %s() format string is argument #%lu (1-based), thus missing\n", + cCallArgs, DECL_NAME(hFnDecl) ? IDENTIFIER_POINTER(DECL_NAME(hFnDecl)) : "<unamed>", State.iFmt); + } + } + } + } + return 0; +} + + +/** + * Gate callback for my pass that indicates whether it should execute or not. + * @returns true to execute. + */ +static bool MyPassGateCallback(void) +{ + dprintf("MyPassGateCallback:\n"); + return true; +} + + +/** + * Validate the use of an attribute. + * + * @returns ?? + * @param phOnNode The node the attribute is being used on. + * @param hAttrName The attribute name. + * @param hAttrArgs The attribute arguments. + * @param fFlags Some kind of flags... + * @param pfDontAddAttrib Whether to add the attribute to this node or not. + */ +static tree AttributeHandler(tree *phOnNode, tree hAttrName, tree hAttrArgs, int fFlags, bool *pfDontAddAttrib) +{ + dprintf("AttributeHandler: name=%s fFlags=%#x", IDENTIFIER_POINTER(hAttrName), fFlags); + long iFmt = MY_DOUBLE_INT_TO_SHWI(TREE_VALUE(hAttrArgs)); + long iArgs = MY_DOUBLE_INT_TO_SHWI(TREE_VALUE(TREE_CHAIN(hAttrArgs))); + dprintf(" iFmt=%ld iArgs=%ld", iFmt, iArgs); + + tree hType = *phOnNode; + dprintf(" hType=%p %s(%d)\n", hType, get_tree_code_name(TREE_CODE(hType)), TREE_CODE(hType)); + + if (pfDontAddAttrib) + *pfDontAddAttrib = false; + return NULL_TREE; +} + + +/** + * Called when we can register attributes. + * + * @param pvEventData Ignored. + * @param pvUser Ignored. + */ +static void RegisterAttributesEvent(void *pvEventData, void *pvUser) +{ + NOREF(pvEventData); NOREF(pvUser); + dprintf("RegisterAttributesEvent: pvEventData=%p\n", pvEventData); + + register_attribute(&g_AttribSpecs[0]); + register_attribute(&g_AttribSpecs[1]); +} + + +/** + * The plug-in entry point. + * + * @returns 0 to indicate success? + * @param pPlugInInfo Plugin info structure. + * @param pGccVer GCC Version. + */ +int plugin_init(plugin_name_args *pPlugInInfo, plugin_gcc_version *pGccVer) +{ + dprintf("plugin_init: %s\n", pPlugInInfo->full_name); + dprintf("gcc version: basever=%s datestamp=%s devphase=%s revision=%s\n", + pGccVer->basever, pGccVer->datestamp, pGccVer->devphase, pGccVer->revision); + + /* Ask for callback in which we may register the attribute. */ + register_callback(pPlugInInfo->base_name, PLUGIN_ATTRIBUTES, RegisterAttributesEvent, NULL /*pvUser*/); + + /* Register our pass. */ +#if RT_GNUC_PREREQ(4, 9) + /** The registration info for my pass. */ + struct register_pass_info MyPassInfo; + MyPassInfo.pass = new MyPass(g); + MyPassInfo.reference_pass_name = "ssa"; + MyPassInfo.ref_pass_instance_number = 1; + MyPassInfo.pos_op = PASS_POS_INSERT_BEFORE; + register_callback(pPlugInInfo->base_name, PLUGIN_PASS_MANAGER_SETUP, NULL, &MyPassInfo); +#else + register_callback(pPlugInInfo->base_name, PLUGIN_PASS_MANAGER_SETUP, NULL, (void *)&g_MyPassInfo); +#endif + + /* Register plug-in info. */ + register_callback(pPlugInInfo->base_name, PLUGIN_INFO, NULL, (void *)&g_PlugInInfo); + + return 0; +} + + + + +/* + * + * Functions used by the common code. + * Functions used by the common code. + * Functions used by the common code. + * + */ + +void VFmtChkWarnFmt(PVFMTCHKSTATE pState, const char *pszLoc, const char *pszFormat, ...) +{ + char szTmp[1024]; + va_list va; + va_start(va, pszFormat); + vsnprintf(szTmp, sizeof(szTmp), pszFormat, va); + va_end(va); + + /* display the warning. */ + warning_at(MyGetFormatStringLocation(pState, pszLoc), 0, "%s", szTmp); +} + + +void VFmtChkErrFmt(PVFMTCHKSTATE pState, const char *pszLoc, const char *pszFormat, ...) +{ + char szTmp[1024]; + va_list va; + va_start(va, pszFormat); + vsnprintf(szTmp, sizeof(szTmp), pszFormat, va); + va_end(va); + + /* display the warning. */ + error_at(MyGetFormatStringLocation(pState, pszLoc), "%s", szTmp); +} + + + +void VFmtChkVerifyEndOfArgs(PVFMTCHKSTATE pState, unsigned iArg) +{ + dprintf("VFmtChkVerifyEndOfArgs: iArg=%u iArgs=%ld cArgs=%u\n", iArg, pState->iArgs, gimple_call_num_args(pState->hStmt)); + if (pState->iArgs > 0) + { + iArg += pState->iArgs - 1; + unsigned cArgs = gimple_call_num_args(pState->hStmt); + if (iArg == cArgs) + { /* fine */ } + else if (iArg < cArgs) + { + tree hArg = gimple_call_arg(pState->hStmt, iArg); + if (cArgs - iArg > 1) + error_at(MY_LOC(hArg, pState), "%u extra arguments not consumed by format string", cArgs - iArg); + else if ( TREE_CODE(hArg) != INTEGER_CST + || !MY_DOUBLE_INT_FITS_SHWI(hArg) + || MY_DOUBLE_INT_TO_SHWI(hArg) != -99) /* ignore final dummy argument: ..., -99); */ + error_at(MY_LOC(hArg, pState), "one extra argument not consumed by format string"); + } + /* This should be handled elsewhere, but just in case. */ + else if (iArg - 1 == cArgs) + error_at(pState->hFmtLoc, "one argument too few"); + else + error_at(pState->hFmtLoc, "%u arguments too few", iArg - cArgs); + } +} + + +bool VFmtChkRequirePresentArg(PVFMTCHKSTATE pState, const char *pszLoc, unsigned iArg, const char *pszMessage) +{ + if (pState->iArgs > 0) + { + iArg += pState->iArgs - 1; + unsigned cArgs = gimple_call_num_args(pState->hStmt); + if (iArg >= cArgs) + { + VFmtChkErrFmt(pState, pszLoc, "Missing argument! %s", pszMessage); + return false; + } + + tree hArg = gimple_call_arg(pState->hStmt, iArg); + tree hType = TREE_TYPE(hArg); + dprintf("arg%u: hArg=%p [%s] hType=%p [%s] cls=%s\n", iArg, hArg, get_tree_code_name(TREE_CODE(hArg)), + hType, get_tree_code_name(TREE_CODE(hType)), tree_code_class_strings[TREE_CODE_CLASS(TREE_CODE(hType))]); + dprintf(" nm=%p\n", TYPE_NAME(hType)); + dprintf(" cb=%p %s value=%ld\n", TYPE_SIZE(hType), get_tree_code_name(TREE_CODE(TYPE_SIZE(hType))), + MY_DOUBLE_INT_TO_SHWI(TYPE_SIZE(hType)) ); + dprintf(" unit=%p %s value=%ld\n", TYPE_SIZE_UNIT(hType), get_tree_code_name(TREE_CODE(TYPE_SIZE_UNIT(hType))), + MY_DOUBLE_INT_TO_SHWI(TYPE_SIZE_UNIT(hType)) ); + tree hTypeNm = TYPE_NAME(hType); + if (hTypeNm) + dprintf(" typenm=%p %s '%s'\n", hTypeNm, get_tree_code_name(TREE_CODE(hTypeNm)), + IDENTIFIER_POINTER(DECL_NAME(hTypeNm))); + } + return true; +} + + +bool VFmtChkRequireIntArg(PVFMTCHKSTATE pState, const char *pszLoc, unsigned iArg, const char *pszMessage) +{ + if (VFmtChkRequirePresentArg(pState, pszLoc, iArg, pszMessage)) + { + /** @todo type check. */ + return true; + } + return false; +} + + +bool VFmtChkRequireStringArg(PVFMTCHKSTATE pState, const char *pszLoc, unsigned iArg, const char *pszMessage) +{ + if (VFmtChkRequirePresentArg(pState, pszLoc, iArg, pszMessage)) + { + /** @todo type check. */ + return true; + } + return false; +} + + +bool VFmtChkRequireVaListPtrArg(PVFMTCHKSTATE pState, const char *pszLoc, unsigned iArg, const char *pszMessage) +{ + if (VFmtChkRequirePresentArg(pState, pszLoc, iArg, pszMessage)) + { + /** @todo type check. */ + return true; + } + return false; +} + + +void VFmtChkHandleReplacementFormatString(PVFMTCHKSTATE pState, const char *pszPctM, unsigned iArg) +{ + if (pState->iArgs > 0) + { + pState->iFmt = pState->iArgs + iArg; + pState->iArgs = pState->iFmt + 1; + pState->fMaybeNull = false; + MyCheckFormatRecursive(pState, gimple_call_arg(pState->hStmt, pState->iFmt - 1)); + } +} + + +const char *VFmtChkGetFmtLocFile(PVFMTCHKSTATE pState) +{ + return LOCATION_FILE(pState->hFmtLoc); +} + + +unsigned int VFmtChkGetFmtLocLine(PVFMTCHKSTATE pState) +{ + return LOCATION_LINE(pState->hFmtLoc); +} + + +unsigned int VFmtChkGetFmtLocColumn(PVFMTCHKSTATE pState) +{ +#ifdef LOCATION_COLUMN + return LOCATION_COLUMN(pState->hFmtLoc); +#else + return 1; +#endif +} + diff --git a/src/bldprogs/VBoxDef2LazyLoad.cpp b/src/bldprogs/VBoxDef2LazyLoad.cpp new file mode 100644 index 00000000..30321377 --- /dev/null +++ b/src/bldprogs/VBoxDef2LazyLoad.cpp @@ -0,0 +1,1624 @@ +/* $Id: VBoxDef2LazyLoad.cpp $ */ +/** @file + * VBoxDef2LazyLoad - Lazy Library Loader Generator. + * + * @note Only tested on win.amd64 & darwin.amd64. + */ + +/* + * Copyright (C) 2013-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <ctype.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <iprt/types.h> +#include <iprt/ldr.h> /* For RTLDRARCH. */ + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef struct MYEXPORT +{ + struct MYEXPORT *pNext; + /** Pointer to unmangled name for stdcall (after szName), NULL if not. */ + char *pszUnstdcallName; + /** Pointer to the exported name. */ + char const *pszExportedNm; + unsigned uOrdinal; + bool fNoName; + char szName[1]; +} MYEXPORT; +typedef MYEXPORT *PMYEXPORT; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** @name Options + * @{ */ +static const char *g_pszOutput = NULL; +static const char *g_pszLibrary = NULL; +static const char *g_apszInputs[8] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; +static unsigned g_cInputs = 0; +static bool g_fIgnoreData = true; +static bool g_fWithExplictLoadFunction = false; +static bool g_fSystemLibrary = false; +#if defined(RT_ARCH_AMD64) +static RTLDRARCH g_enmTarget = RTLDRARCH_AMD64; +#elif defined(RT_ARCH_X86) +static RTLDRARCH g_enmTarget = RTLDRARCH_X86_32; +#elif defined(RT_ARCH_ARM64) +static RTLDRARCH g_enmTarget = RTLDRARCH_ARM64; +#else +# error "Port me!" +#endif +/** @} */ + +/** Pointer to the export name list head. */ +static PMYEXPORT g_pExpHead = NULL; +/** Pointer to the next pointer for insertion. */ +static PMYEXPORT *g_ppExpNext = &g_pExpHead; + + + +#if 0 /* unused */ +static const char *leftStrip(const char *psz) +{ + while (isspace(*psz)) + psz++; + return psz; +} +#endif + + +static char *leftStrip(char *psz) +{ + while (isspace(*psz)) + psz++; + return psz; +} + + +static unsigned wordLength(const char *pszWord) +{ + unsigned off = 0; + char ch; + while ( (ch = pszWord[off]) != '\0' + && ch != '=' + && ch != ',' + && ch != ':' + && !isspace(ch) ) + off++; + return off; +} + + +/** + * Parses the module definition file, collecting export information. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE, in the latter case full + * details has been displayed. + * @param pInput The input stream. + */ +static RTEXITCODE parseInputInner(FILE *pInput, const char *pszInput) +{ + /* + * Process the file line-by-line. + */ + bool fInExports = false; + unsigned iLine = 0; + char szLine[16384]; + while (fgets(szLine, sizeof(szLine), pInput)) + { + iLine++; + + /* + * Strip leading and trailing spaces from the line as well as + * trailing comments. + */ + char *psz = leftStrip(szLine); + if (*psz == ';') + continue; /* comment line. */ + + char *pszComment = strchr(psz, ';'); + if (pszComment) + *pszComment = '\0'; + + unsigned cch = (unsigned)strlen(psz); + while (cch > 0 && (isspace(psz[cch - 1]) || psz[cch - 1] == '\r' || psz[cch - 1] == '\n')) + psz[--cch] = '\0'; + + if (!cch) + continue; + + /* + * Check for known directives. + */ + size_t cchWord0 = wordLength(psz); +#define WORD_CMP(pszWord1, cchWord1, szWord2) \ + ( (cchWord1) == sizeof(szWord2) - 1 && memcmp(pszWord1, szWord2, sizeof(szWord2) - 1) == 0 ) + if (WORD_CMP(psz, cchWord0, "EXPORTS")) + { + fInExports = true; + + /* In case there is an export on the same line. (Really allowed?) */ + psz = leftStrip(psz + sizeof("EXPORTS") - 1); + if (!*psz) + continue; + } + /* Directives that we don't care about, but need to catch in order to + terminate the EXPORTS section in a timely manner. */ + else if ( WORD_CMP(psz, cchWord0, "NAME") + || WORD_CMP(psz, cchWord0, "LIBRARY") + || WORD_CMP(psz, cchWord0, "DESCRIPTION") + || WORD_CMP(psz, cchWord0, "STACKSIZE") + || WORD_CMP(psz, cchWord0, "SECTIONS") + || WORD_CMP(psz, cchWord0, "SEGMENTS") + || WORD_CMP(psz, cchWord0, "VERSION") + ) + { + fInExports = false; + } + + /* + * Process exports: + * entryname[=internalname] [@ordinal[ ][NONAME]] [DATA] [PRIVATE] + */ + if (fInExports) + { + const char *pchName = psz; + unsigned cchName = wordLength(psz); + + psz = leftStrip(psz + cchName); + if (*psz == '=') + { + psz = leftStrip(psz + 1); + psz = leftStrip(psz + wordLength(psz)); + } + + bool fNoName = false; + unsigned uOrdinal = ~0U; + if (*psz == '@') + { + psz++; + if (!isdigit(*psz)) + { + fprintf(stderr, "%s:%u: error: Invalid ordinal spec.\n", pszInput, iLine); + return RTEXITCODE_FAILURE; + } + uOrdinal = *psz++ - '0'; + while (isdigit(*psz)) + { + uOrdinal *= 10; + uOrdinal += *psz++ - '0'; + } + psz = leftStrip(psz); + cch = wordLength(psz); + if (WORD_CMP(psz, cch, "NONAME")) + { + fNoName = true; + psz = leftStrip(psz + cch); + } + } + + while (*psz) + { + cch = wordLength(psz); + if (WORD_CMP(psz, cch, "DATA")) + { + if (!g_fIgnoreData) + { + fprintf(stderr, "%s:%u: error: Cannot wrap up DATA export '%.*s'.\n", + pszInput, iLine, cchName, pchName); + return RTEXITCODE_SUCCESS; + } + } + else if (!WORD_CMP(psz, cch, "PRIVATE")) + { + fprintf(stderr, "%s:%u: error: Cannot wrap up DATA export '%.*s'.\n", + pszInput, iLine, cchName, pchName); + return RTEXITCODE_SUCCESS; + } + psz = leftStrip(psz + cch); + } + + /* + * Check for stdcall mangling. + */ + size_t cbExp = sizeof(MYEXPORT) + cchName; + unsigned cchStdcall = 0; + if (cchName > 3 && *pchName == '_' && isdigit(pchName[cchName - 1])) + { + if (cchName > 3 && pchName[cchName - 2] == '@') + cchStdcall = 2; + else if (cchName > 4 && pchName[cchName - 3] == '@' && isdigit(pchName[cchName - 2])) + cchStdcall = 3; + if (cchStdcall) + cbExp += cchName - 1 - cchStdcall; + } + + /* + * Add the export. + */ + + PMYEXPORT pExp = (PMYEXPORT)malloc(cbExp); + if (!pExp) + { + fprintf(stderr, "%s:%u: error: Out of memory.\n", pszInput, iLine); + return RTEXITCODE_SUCCESS; + } + memcpy(pExp->szName, pchName, cchName); + pExp->szName[cchName] = '\0'; + if (!cchStdcall) + { + pExp->pszUnstdcallName = NULL; + pExp->pszExportedNm = pExp->szName; + } + else + { + pExp->pszUnstdcallName = &pExp->szName[cchName + 1]; + memcpy(pExp->pszUnstdcallName, pchName + 1, cchName - 1 - cchStdcall); + pExp->pszUnstdcallName[cchName - 1 - cchStdcall] = '\0'; + pExp->pszExportedNm = pExp->pszUnstdcallName; + } + pExp->uOrdinal = uOrdinal; + pExp->fNoName = fNoName; + pExp->pNext = NULL; + *g_ppExpNext = pExp; + g_ppExpNext = &pExp->pNext; + } + } + + /* + * Why did we quit the loop, EOF or error? + */ + if (feof(pInput)) + return RTEXITCODE_SUCCESS; + fprintf(stderr, "error: Read while reading '%s' (iLine=%u).\n", pszInput, iLine); + return RTEXITCODE_FAILURE; +} + + +/** + * Parses a_apszInputs, populating the list pointed to by g_pExpHead. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE, in the latter case full + * details has been displayed. + */ +static RTEXITCODE parseInputs(void) +{ + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + for (unsigned i = 0; i < g_cInputs; i++) + { + FILE *pInput = fopen(g_apszInputs[i], "r"); + if (pInput) + { + RTEXITCODE rcExit2 = parseInputInner(pInput, g_apszInputs[i]); + fclose(pInput); + if (rcExit2 == RTEXITCODE_SUCCESS && !g_pExpHead) + { + fprintf(stderr, "error: Found no exports in '%s'.\n", g_apszInputs[i]); + rcExit2 = RTEXITCODE_FAILURE; + } + if (rcExit2 != RTEXITCODE_SUCCESS) + rcExit = rcExit2; + } + else + { + fprintf(stderr, "error: Failed to open '%s' for reading.\n", g_apszInputs[i]); + rcExit = RTEXITCODE_FAILURE; + } + } + return rcExit; +} + + +/** + * Generates the assembly source code for AMD64 and x86, writing it + * to @a pOutput. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE, in the latter case full + * details has been displayed. + * @param pOutput The output stream (caller checks it for errors + * when closing). + */ +static RTEXITCODE generateOutputInnerX86AndAMD64(FILE *pOutput) +{ + fprintf(pOutput, ";;\n"); + for (unsigned i = 0; i < g_cInputs; i++) + fprintf(pOutput, ";; Autogenerated from '%s'.\n", g_apszInputs[i]); + + fprintf(pOutput, + ";; DO NOT EDIT!\n" + ";;\n" + "\n" + "\n" + "%%include \"iprt/asmdefs.mac\"\n" + "\n" + "\n"); + + /* + * Put the thunks first for alignment and other reasons. It's the hot part of the code. + */ + fprintf(pOutput, + ";\n" + "; Thunks.\n" + ";\n" + "BEGINCODE\n"); + for (PMYEXPORT pExp = g_pExpHead; pExp; pExp = pExp->pNext) + if (!pExp->pszUnstdcallName) + fprintf(pOutput, + "BEGINPROC %s\n" + " jmp RTCCPTR_PRE [g_pfn%s xWrtRIP]\n" + "ENDPROC %s\n", + pExp->szName, pExp->szName, pExp->szName); + else + fprintf(pOutput, + "%%ifdef RT_ARCH_X86\n" + "global %s\n" + "%s:\n" + " jmp RTCCPTR_PRE [g_pfn%s xWrtRIP]\n" + "%%else\n" + "BEGINPROC %s\n" + " jmp RTCCPTR_PRE [g_pfn%s xWrtRIP]\n" + "ENDPROC %s\n" + "%%endif\n", + pExp->szName, pExp->szName, pExp->pszUnstdcallName, + pExp->pszUnstdcallName, pExp->pszUnstdcallName, pExp->pszUnstdcallName); + + fprintf(pOutput, + "\n" + "\n"); + + /* + * Import pointers + */ + fprintf(pOutput, + ";\n" + "; Import pointers. Initialized to point a lazy loading stubs.\n" + ";\n" + "BEGINDATA\n" + "g_apfnImports:\n"); + for (PMYEXPORT pExp = g_pExpHead; pExp; pExp = pExp->pNext) + if (pExp->pszUnstdcallName) + fprintf(pOutput, + "%%ifdef ASM_FORMAT_PE\n" + " %%ifdef RT_ARCH_X86\n" + "global __imp_%s\n" + "__imp_%s:\n" + " %%else\n" + "global __imp_%s\n" + "__imp_%s:\n" + " %%endif\n" + "%%endif\n" + "g_pfn%s RTCCPTR_DEF ___LazyLoad___%s\n" + "\n", + pExp->szName, + pExp->szName, + pExp->pszUnstdcallName, + pExp->pszUnstdcallName, + pExp->pszExportedNm, + pExp->pszExportedNm); + else + fprintf(pOutput, + "%%ifdef ASM_FORMAT_PE\n" + "global __imp_%s\n" + "__imp_%s:\n" + "%%endif\n" + "g_pfn%s RTCCPTR_DEF ___LazyLoad___%s\n" + "\n", + pExp->szName, + pExp->szName, + pExp->pszExportedNm, + pExp->pszExportedNm); + fprintf(pOutput, + "RTCCPTR_DEF 0 ; Terminator entry for traversal.\n" + "\n" + "\n"); + + /* + * Now for the less important stuff, starting with the names. + * + * We keep the names separate so we can traverse them in parallel to + * g_apfnImports in the load-everything routine further down. + */ + fprintf(pOutput, + ";\n" + "; Imported names.\n" + ";\n" + "BEGINCODE\n" + "g_szLibrary: db '%s',0\n" + "\n" + "g_szzNames:\n", + g_pszLibrary); + for (PMYEXPORT pExp = g_pExpHead; pExp; pExp = pExp->pNext) + if (!pExp->fNoName) + fprintf(pOutput, " g_sz%s:\n db '%s',0\n", pExp->pszExportedNm, pExp->pszExportedNm); + else + fprintf(pOutput, " g_sz%s:\n db '#%u',0\n", pExp->pszExportedNm, pExp->uOrdinal); + fprintf(pOutput, + "g_EndOfNames: db 0\n" + "\n" + "g_szFailLoadFmt: db 'Lazy loader failed to load \"%%s\": %%Rrc', 10, 0\n" + "g_szFailResolveFmt: db 'Lazy loader failed to resolve symbol \"%%s\" in \"%%s\": %%Rrc', 10, 0\n" + "\n" + "\n"); + + /* + * The per import lazy load code. + */ + fprintf(pOutput, + ";\n" + "; Lazy load+resolve stubs.\n" + ";\n" + "BEGINCODE\n"); + for (PMYEXPORT pExp = g_pExpHead; pExp; pExp = pExp->pNext) + { + if (!pExp->fNoName) + fprintf(pOutput, + "___LazyLoad___%s:\n" + /* "int3\n" */ + "%%ifdef RT_ARCH_AMD64\n" + " lea rax, [g_sz%s wrt rip]\n" + " lea r10, [g_pfn%s wrt rip]\n" + " call LazyLoadResolver\n" + "%%elifdef RT_ARCH_X86\n" + " push g_sz%s\n" + " push g_pfn%s\n" + " call LazyLoadResolver\n" + " add esp, 8h\n" + "%%else\n" + " %%error \"Unsupported architecture\"\n" + "%%endif\n" + , + pExp->pszExportedNm, + pExp->pszExportedNm, + pExp->pszExportedNm, + pExp->pszExportedNm, + pExp->pszExportedNm); + else + fprintf(pOutput, + "___LazyLoad___%s:\n" + /* "int3\n" */ + "%%ifdef RT_ARCH_AMD64\n" + " mov eax, %u\n" + " lea r10, [g_pfn%s wrt rip]\n" + " call LazyLoadResolver\n" + "%%elifdef RT_ARCH_X86\n" + " push %u\n" + " push g_pfn%s\n" + " call LazyLoadResolver\n" + " add esp, 8h\n" + "%%else\n" + " %%error \"Unsupported architecture\"\n" + "%%endif\n" + , + pExp->pszExportedNm, + pExp->uOrdinal, + pExp->pszExportedNm, + pExp->uOrdinal, + pExp->pszExportedNm); + if (!pExp->pszUnstdcallName) + fprintf(pOutput, " jmp NAME(%s)\n", pExp->szName); + else + fprintf(pOutput, + "%%ifdef RT_ARCH_X86\n" + " jmp %s\n" + "%%else\n" + " jmp NAME(%s)\n" + "%%endif\n" + , + pExp->szName, pExp->pszUnstdcallName); + fprintf(pOutput, "\n"); + } + fprintf(pOutput, + "\n" + "\n" + "\n"); + + /* + * The code that does the loading and resolving. + */ + fprintf(pOutput, + ";\n" + "; The module handle.\n" + ";\n" + "BEGINDATA\n" + "g_hMod RTCCPTR_DEF 0\n" + "\n" + "\n" + "\n"); + + /* + * How we load the module needs to be selectable later on. + * + * The LazyLoading routine returns the module handle in RCX/ECX, caller + * saved all necessary registers. + */ + if (!g_fSystemLibrary) + fprintf(pOutput, + ";\n" + ";SUPR3DECL(int) SUPR3HardenedLdrLoadAppPriv(const char *pszFilename, PRTLDRMOD phLdrMod,\n" + "; uint32_t fFlags, PRTERRINFO pErrInfo);\n" + ";\n" + "EXTERN_IMP2 SUPR3HardenedLdrLoadAppPriv\n" + "%%ifdef IN_RT_R3\n" + "extern NAME(RTAssertMsg2Weak)\n" + "%%else\n" + "EXTERN_IMP2 RTAssertMsg2Weak\n" + "%%endif\n" + "BEGINCODE\n" + "\n" + "LazyLoading:\n" + " mov xCX, [g_hMod xWrtRIP]\n" + " or xCX, xCX\n" + " jnz .return\n" + "\n" + "%%ifdef ASM_CALL64_GCC\n" + " xor rcx, rcx ; pErrInfo\n" + " xor rdx, rdx ; fFlags (local load)\n" + " lea rsi, [g_hMod wrt rip] ; phLdrMod\n" + " lea rdi, [g_szLibrary wrt rip] ; pszFilename\n" + " sub rsp, 08h\n" + " call IMP2(SUPR3HardenedLdrLoadAppPriv)\n" + " add rsp, 08h\n" + "\n" + "%%elifdef ASM_CALL64_MSC\n" + " xor r9, r9 ; pErrInfo\n" + " xor r8, r8 ; fFlags (local load)\n" + " lea rdx, [g_hMod wrt rip] ; phLdrMod\n" + " lea rcx, [g_szLibrary wrt rip] ; pszFilename\n" + " sub rsp, 28h\n" + " call IMP2(SUPR3HardenedLdrLoadAppPriv)\n" + " add rsp, 28h\n" + "\n" + "%%elifdef RT_ARCH_X86\n" + " sub xSP, 0ch\n" + " push 0 ; pErrInfo\n" + " push 0 ; fFlags (local load)\n" + " push g_hMod ; phLdrMod\n" + " push g_szLibrary ; pszFilename\n" + " call IMP2(SUPR3HardenedLdrLoadAppPriv)\n" + " add esp, 1ch\n" + "%%else\n" + " %%error \"Unsupported architecture\"\n" + "%%endif\n"); + else + fprintf(pOutput, + ";\n" + "; RTDECL(int) RTLdrLoadSystem(const char *pszFilename, bool fNoUnload, PRTLDRMOD phLdrMod);\n" + ";\n" + "%%ifdef IN_RT_R3\n" + "extern NAME(RTLdrLoadSystem)\n" + "extern NAME(RTAssertMsg2Weak)\n" + "%%else\n" + "EXTERN_IMP2 RTLdrLoadSystem\n" + "EXTERN_IMP2 RTAssertMsg2Weak\n" + "%%endif\n" + "BEGINCODE\n" + "\n" + "LazyLoading:\n" + " mov xCX, [g_hMod xWrtRIP]\n" + " or xCX, xCX\n" + " jnz .return\n" + "\n" + "%%ifdef ASM_CALL64_GCC\n" + " lea rdx, [g_hMod wrt rip] ; phLdrMod\n" + " mov esi, 1 ; fNoUnload=true\n" + " lea rdi, [g_szLibrary wrt rip] ; pszFilename\n" + " sub rsp, 08h\n" + " %%ifdef IN_RT_R3\n" + " call NAME(RTLdrLoadSystem)\n" + " %%else\n" + " call IMP2(RTLdrLoadSystem)\n" + " %%endif\n" + " add rsp, 08h\n" + "\n" + "%%elifdef ASM_CALL64_MSC\n" + " lea r8, [g_hMod wrt rip] ; phLdrMod\n" + " mov edx, 1 ; fNoUnload=true\n" + " lea rcx, [g_szLibrary wrt rip] ; pszFilename\n" + " sub rsp, 28h\n" + " %%ifdef IN_RT_R3\n" + " call NAME(RTLdrLoadSystem)\n" + " %%else\n" + " call IMP2(RTLdrLoadSystem)\n" + " %%endif\n" + " add rsp, 28h\n" + "\n" + "%%elifdef RT_ARCH_X86\n" + " push g_hMod ; phLdrMod\n" + " push 1 ; fNoUnload=true\n" + " push g_szLibrary ; pszFilename\n" + " %%ifdef IN_RT_R3\n" + " call NAME(RTLdrLoadSystem)\n" + " %%else\n" + " call IMP2(RTLdrLoadSystem)\n" + " %%endif\n" + " add esp, 0ch\n" + "%%else\n" + " %%error \"Unsupported architecture\"\n" + "%%endif\n"); + fprintf(pOutput, + " or eax, eax\n" + " jnz .badload\n" + " mov xCX, [g_hMod xWrtRIP]\n" + ".return:\n" + " ret\n" + "\n" + ".badload:\n" + "%%ifdef ASM_CALL64_GCC\n" + " mov edx, eax\n" + " lea rsi, [g_szLibrary wrt rip]\n" + " lea rdi, [g_szFailLoadFmt wrt rip]\n" + " sub rsp, 08h\n" + "%%elifdef ASM_CALL64_MSC\n" + " mov r8d, eax\n" + " lea rdx, [g_szLibrary wrt rip]\n" + " lea rcx, [g_szFailLoadFmt wrt rip]\n" + " sub rsp, 28h\n" + "%%elifdef RT_ARCH_X86\n" + " push eax\n" + " push g_szLibrary\n" + " push g_szFailLoadFmt\n" + "%%endif\n" + "%%ifdef IN_RT_R3\n" + " call NAME(RTAssertMsg2Weak)\n" + "%%else\n" + " call IMP2(RTAssertMsg2Weak)\n" + "%%endif\n" + ".badloadloop:\n" + " int3\n" + " jmp .badloadloop\n" + "LazyLoading_End:\n" + "\n" + "\n"); + + + fprintf(pOutput, + ";\n" + ";RTDECL(int) RTLdrGetSymbol(RTLDRMOD hLdrMod, const char *pszSymbol, void **ppvValue);\n" + ";\n" + "%%ifdef IN_RT_R3\n" + "extern NAME(RTLdrGetSymbol)\n" + "%%else\n" + "EXTERN_IMP2 RTLdrGetSymbol\n" + "%%endif\n" + "BEGINCODE\n" + "LazyLoadResolver:\n" + "%%ifdef RT_ARCH_AMD64\n" + " push rbp\n" + " mov rbp, rsp\n" + " push r15\n" + " push r14\n" + " mov r15, rax ; name\n" + " mov r14, r10 ; ppfn\n" + " push r9\n" + " push r8\n" + " push rcx\n" + " push rdx\n" + " push r12\n" + " %%ifdef ASM_CALL64_GCC\n" + " push rsi\n" + " push rdi\n" + " mov r12, rsp\n" + " %%else\n" + " mov r12, rsp\n" + " sub rsp, 20h\n" + " %%endif\n" + " and rsp, 0fffffff0h ; Try make sure the stack is aligned\n" + "\n" + " call LazyLoading ; returns handle in rcx\n" + " %%ifdef ASM_CALL64_GCC\n" + " mov rdi, rcx ; hLdrMod\n" + " mov rsi, r15 ; pszSymbol\n" + " mov rdx, r14 ; ppvValue\n" + " %%else\n" + " mov rdx, r15 ; pszSymbol\n" + " mov r8, r14 ; ppvValue\n" + " %%endif\n" + " %%ifdef IN_RT_R3\n" + " call NAME(RTLdrGetSymbol)\n" + " %%else\n" + " call IMP2(RTLdrGetSymbol)\n" + " %%endif\n" + " or eax, eax\n" + " jnz .badsym\n" + "\n" + " mov rsp, r12\n" + " %%ifdef ASM_CALL64_GCC\n" + " pop rdi\n" + " pop rsi\n" + " %%endif\n" + " pop r12\n" + " pop rdx\n" + " pop rcx\n" + " pop r8\n" + " pop r9\n" + " pop r14\n" + " pop r15\n" + " leave\n" + "\n" + "%%elifdef RT_ARCH_X86\n" + " push ebp\n" + " mov ebp, esp\n" + " push eax\n" + " push ecx\n" + " push edx\n" + " and esp, 0fffffff0h\n" + "\n" + ".loaded:\n" + " call LazyLoading ; returns handle in ecx\n" + " push dword [ebp + 8] ; value addr\n" + " push dword [ebp + 12] ; symbol name\n" + " push ecx\n" + " %%ifdef IN_RT_R3\n" + " call NAME(RTLdrGetSymbol)\n" + " %%else\n" + " call IMP2(RTLdrGetSymbol)\n" + " %%endif\n" + " or eax, eax\n" + " jnz .badsym\n" + " lea esp, [ebp - 0ch]\n" + " pop edx\n" + " pop ecx\n" + " pop eax\n" + " leave\n" + "%%else\n" + " %%error \"Unsupported architecture\"\n" + "%%endif\n" + " ret\n" + "\n" + ".badsym:\n" + "%%ifdef ASM_CALL64_GCC\n" + " mov ecx, eax\n" + " lea rdx, [g_szLibrary wrt rip]\n" + " mov rsi, r15\n" + " lea rdi, [g_szFailResolveFmt wrt rip]\n" + " sub rsp, 08h\n" + "%%elifdef ASM_CALL64_MSC\n" + " mov r9d, eax\n" + " mov r8, r15\n" + " lea rdx, [g_szLibrary wrt rip]\n" + " lea rcx, [g_szFailResolveFmt wrt rip]\n" + " sub rsp, 28h\n" + "%%elifdef RT_ARCH_X86\n" + " push eax\n" + " push dword [ebp + 12]\n" + " push g_szLibrary\n" + " push g_szFailResolveFmt\n" + "%%endif\n" + "%%ifdef IN_RT_R3\n" + " call NAME(RTAssertMsg2Weak)\n" + "%%else\n" + " call IMP2(RTAssertMsg2Weak)\n" + "%%endif\n" + ".badsymloop:\n" + " int3\n" + " jmp .badsymloop\n" + "\n" + "LazyLoadResolver_End:\n" + "\n" + "\n" + ); + + + + /* + * C callable method for explicitly loading the library and optionally + * resolving all the imports. + */ + if (g_fWithExplictLoadFunction) + { + if (g_fSystemLibrary) /* Lazy bird. */ + { + fprintf(stderr, "error: cannot use --system with --explicit-load-function, sorry\n"); + return RTEXITCODE_FAILURE; + } + + int cchLibBaseName = (int)(strchr(g_pszLibrary, '.') ? strchr(g_pszLibrary, '.') - g_pszLibrary : strlen(g_pszLibrary)); + fprintf(pOutput, + ";;\n" + "; ExplicitlyLoad%.*s(bool fResolveAllImports, pErrInfo);\n" + ";\n" + "EXTERN_IMP2 RTErrInfoSet\n" + "BEGINCODE\n" + "BEGINPROC ExplicitlyLoad%.*s\n" + " push xBP\n" + " mov xBP, xSP\n" + " push xBX\n" + "%%ifdef ASM_CALL64_GCC\n" + " %%define pszCurStr r14\n" + " push r14\n" + "%%else\n" + " %%define pszCurStr xDI\n" + " push xDI\n" + "%%endif\n" + " sub xSP, 40h\n" + "\n" + " ;\n" + " ; Save parameters on stack (64-bit only).\n" + " ;\n" + "%%ifdef ASM_CALL64_GCC\n" + " mov [xBP - xCB * 3], rdi ; fResolveAllImports\n" + " mov [xBP - xCB * 4], rsi ; pErrInfo\n" + "%%elifdef ASM_CALL64_MSC\n" + " mov [xBP - xCB * 3], rcx ; fResolveAllImports\n" + " mov [xBP - xCB * 4], rdx ; pErrInfo\n" + "%%endif\n" + "\n" + " ;\n" + " ; Is the module already loaded?\n" + " ;\n" + " cmp RTCCPTR_PRE [g_hMod xWrtRIP], 0\n" + " jnz .loaded\n" + "\n" + " ;\n" + " ; Load the module.\n" + " ;\n" + "%%ifdef ASM_CALL64_GCC\n" + " mov rcx, [xBP - xCB * 4] ; pErrInfo\n" + " xor rdx, rdx ; fFlags (local load)\n" + " lea rsi, [g_hMod wrt rip] ; phLdrMod\n" + " lea rdi, [g_szLibrary wrt rip] ; pszFilename\n" + " call IMP2(SUPR3HardenedLdrLoadAppPriv)\n" + "\n" + "%%elifdef ASM_CALL64_MSC\n" + " mov r9, [xBP - xCB * 4] ; pErrInfo\n" + " xor r8, r8 ; fFlags (local load)\n" + " lea rdx, [g_hMod wrt rip] ; phLdrMod\n" + " lea rcx, [g_szLibrary wrt rip] ; pszFilename\n" + " call IMP2(SUPR3HardenedLdrLoadAppPriv)\n" + "\n" + "%%elifdef RT_ARCH_X86\n" + " sub xSP, 0ch\n" + " push dword [xBP + 12] ; pErrInfo\n" + " push 0 ; fFlags (local load)\n" + " push g_hMod ; phLdrMod\n" + " push g_szLibrary ; pszFilename\n" + " call IMP2(SUPR3HardenedLdrLoadAppPriv)\n" + " add esp, 1ch\n" + "%%else\n" + " %%error \"Unsupported architecture\"\n" + "%%endif\n" + " or eax, eax\n" + " jnz .return\n" + "\n" + " ;\n" + " ; Resolve the imports too if requested to do so.\n" + " ;\n" + ".loaded:\n" + "%%ifdef ASM_ARCH_X86\n" + " cmp byte [xBP + 8], 0\n" + "%%else\n" + " cmp byte [xBP - xCB * 3], 0\n" + "%%endif\n" + " je .return\n" + "\n" + " lea pszCurStr, [g_szzNames xWrtRIP]\n" + " lea xBX, [g_apfnImports xWrtRIP]\n" + ".next_import:\n" + " cmp RTCCPTR_PRE [xBX], 0\n" + " je .return\n" + "%%ifdef ASM_CALL64_GCC\n" + " mov rdx, xBX ; ppvValue\n" + " mov rsi, pszCurStr ; pszSymbol\n" + " mov rdi, [g_hMod wrt rip] ; hLdrMod\n" + " call IMP2(RTLdrGetSymbol)\n" + "%%elifdef ASM_CALL64_MSC\n" + " mov r8, xBX ; ppvValue\n" + " mov rdx, pszCurStr ; pszSymbol\n" + " mov rcx, [g_hMod wrt rip] ; pszSymbol\n" + " call IMP2(RTLdrGetSymbol)\n" + "%%else\n" + " push xBX ; ppvValue\n" + " push pszCurStr ; pszSymbol\n" + " push RTCCPTR_PRE [g_hMod] ; hLdrMod\n" + " call IMP2(RTLdrGetSymbol)\n" + " add xSP, 0ch\n" + "%%endif\n" + " or eax, eax\n" + " jnz .symbol_error\n" + "\n" + " ; Advance.\n" + " add xBX, RTCCPTR_CB\n" + " xor eax, eax\n" + " mov xCX, 0ffffffffh\n" + "%%ifdef ASM_CALL64_GCC\n" + " mov xDI, pszCurStr\n" + " repne scasb\n" + " mov pszCurStr, xDI\n" + "%%else\n" + " repne scasb\n" + "%%endif\n" + " jmp .next_import\n" + "\n" + " ;\n" + " ; Error loading a symbol. Call RTErrInfoSet on pErrInfo (preserves eax).\n" + " ;\n" + ".symbol_error:\n" + "%%ifdef ASM_CALL64_GCC\n" + " mov rdx, pszCurStr ; pszMsg\n" + " mov esi, eax ; rc\n" + " mov rdi, [xBP - xCB * 4] ; pErrInfo\n" + " call IMP2(RTErrInfoSet)\n" + "%%elifdef ASM_CALL64_MSC\n" + " mov r8, pszCurStr ; pszMsg\n" + " mov edx, eax ; rc\n" + " mov rcx, [xBP - xCB * 4] ; pErrInfo\n" + " call IMP2(RTErrInfoSet)\n" + "%%else\n" + " push pszCurStr ; pszMsg\n" + " push eax ; pszSymbol\n" + " push dword [xBP + 0ch] ; pErrInfo\n" + " call IMP2(RTErrInfoSet)\n" + " add xSP, 0ch\n" + "%%endif\n" + " " + "\n" + ".return:\n" + " mov pszCurStr, [xBP - xCB * 2]\n" + " mov xBX, [xBP - xCB * 1]\n" + " leave\n" + " ret\n" + "ENDPROC ExplicitlyLoad%.*s\n" + "\n" + "\n" + , + cchLibBaseName, g_pszLibrary, + cchLibBaseName, g_pszLibrary, + cchLibBaseName, g_pszLibrary + ); + } + + + return RTEXITCODE_SUCCESS; +} + + +/** + * Generates the assembly source code for ARM64, writing it + * to @a pOutput. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE, in the latter case full + * details has been displayed. + * @param pOutput The output stream (caller checks it for errors + * when closing). + */ +static RTEXITCODE generateOutputInnerArm64(FILE *pOutput) +{ +// bool fMachO = true; +// bool fDarwin = true; + const char *pszNmPfx = "_"; + + fprintf(pOutput, ";;\n"); + for (unsigned i = 0; i < g_cInputs; i++) + fprintf(pOutput, ";; Autogenerated from '%s'.\n", g_apszInputs[i]); + + fprintf(pOutput, + ";; DO NOT EDIT!\n" + ";;\n" + "\n" + "\n" + /*"%%include \"iprt/asmdefs.mac\"\n"*/ + "\n" + "\n"); + + /* + * Put the thunks first for alignment and other reasons. It's the hot part of the code. + */ + fprintf(pOutput, + ";\n" + "; Thunks.\n" + ";\n" + ".section __TEXT,__text,regular,pure_instructions\n"); + for (PMYEXPORT pExp = g_pExpHead; pExp; pExp = pExp->pNext) + fprintf(pOutput, + ".p2align 3\n" + ".globl %s%s\n" + "%s%s:\n" + " adrp x9, %sg_pfn%s@PAGE\n" + " ldr x9, [x9, %sg_pfn%s@PAGEOFF]\n" + " br x9\n", + pszNmPfx, pExp->szName, pszNmPfx, pExp->szName, pszNmPfx, pExp->szName, pszNmPfx, pExp->szName); + fprintf(pOutput, + "\n" + "\n"); + + /* + * Import pointers + */ + fprintf(pOutput, + ";\n" + "; Import pointers. Initialized to point a lazy loading stubs.\n" + ";\n" + ".section __DATA,__data\n" + ".p2align 3\n" + "g_apfnImports:\n"); + for (PMYEXPORT pExp = g_pExpHead; pExp; pExp = pExp->pNext) + fprintf(pOutput, + ".globl __imp_%s\n" + "__imp_%s:\n" + ".globl %sg_pfn%s\n" + "%sg_pfn%s:\n" + " .quad ___LazyLoad___%s\n" + "\n", + pExp->szName, pExp->szName, + pszNmPfx, pExp->szName, pszNmPfx, pExp->szName, + pExp->pszExportedNm); + fprintf(pOutput, + " .quad 0 ; Terminator entry for traversal.\n" + "\n" + "\n"); + + /* + * Now for the less important stuff, starting with the names. + * + * We keep the names separate so we can traverse them in parallel to + * g_apfnImports in the load-everything routine further down. + */ + fprintf(pOutput, + ";\n" + "; Imported names.\n" + ";\n" + ".section __TEXT,__cstring,cstring_literals\n" + "g_szLibrary:\n" + " .asciz \"%s\"\n" + "\n" + "g_szzNames:\n", + g_pszLibrary); + for (PMYEXPORT pExp = g_pExpHead; pExp; pExp = pExp->pNext) + if (!pExp->fNoName) + fprintf(pOutput, " g_sz%s:\n .asciz \"%s\"\n", pExp->pszExportedNm, pExp->pszExportedNm); + else + fprintf(pOutput, " g_sz%s:\n .asciz \"#%u\"\n", pExp->pszExportedNm, pExp->uOrdinal); + fprintf(pOutput, + "g_EndOfNames: .byte 0\n" + "\n" + "g_szFailLoadFmt: .asciz \"Lazy loader failed to load \\\"%%s\\\": %%Rrc\\n\"\n" + "g_szFailResolveFmt: .asciz \"Lazy loader failed to resolve symbol \\\"%%s\\\" in \\\"%%s\\\": %%Rrc\\n\"\n" + "\n" + "\n"); + + /* + * The per import lazy load code. + */ + fprintf(pOutput, + ";\n" + "; Lazy load+resolve stubs.\n" + ";\n" + ".section __TEXT,__text,regular,pure_instructions\n" + ".p2align 3\n"); + for (PMYEXPORT pExp = g_pExpHead; pExp; pExp = pExp->pNext) + { + if (!pExp->fNoName) + fprintf(pOutput, + "___LazyLoad___%s:\n" + " adrp x9, g_sz%s@PAGE\n" + " add x9, x9, g_sz%s@PAGEOFF\n" + " adrp x10, %sg_pfn%s@PAGE\n" + " add x10, x10, %sg_pfn%s@PAGEOFF\n" + " bl LazyLoadResolver\n" + , pExp->pszExportedNm, + pExp->pszExportedNm, pExp->pszExportedNm, + pszNmPfx, pExp->pszExportedNm, pszNmPfx, pExp->pszExportedNm); + else + fprintf(pOutput, + "___LazyLoad___%s:\n" + " movk w9, #%u\n" + " adrp x10, %sg_pfn%s@PAGE\n" + " add x10, x10, %sg_pfn%s@PAGEOFF\n" + , pExp->pszExportedNm, + pExp->uOrdinal, + pszNmPfx, pExp->pszExportedNm, pszNmPfx, pExp->pszExportedNm); + fprintf(pOutput, " b %s%s\n", pszNmPfx, pExp->szName); + fprintf(pOutput, "\n"); + } + fprintf(pOutput, + "\n" + "\n" + "\n"); + + /* + * The code that does the loading and resolving. + */ + fprintf(pOutput, + ";\n" + "; The module handle.\n" + ";\n" + ".section __DATA,__data\n" + "g_hMod:\n" + " .quad 0\n" + "\n" + "\n" + "\n"); + + /* + * Common lazy loader and resolved. + */ + fprintf(pOutput, + ";\n" + "; The resolver code.\n" + ";\n" + ".section __TEXT,__text,regular,pure_instructions\n" + ".p2align 3\n" + "LazyLoadResolver:\n" + " .cfi_startproc\n" + " ; Create frame.\n" + " sub sp, sp, #(16 + 192)\n" + " stp x29, x30, [sp, #192]\n" + " add x29, sp, #192\n" + " .cfi_def_cfa x29, 16\n" + " .cfi_offset x30, -8\n" + " .cfi_offset x29, -16\n" + " ; Save all argument registers and a handful of preserved ones.\n" + " stp x0, x1, [sp, #(192 - 16)]\n" + " .cfi_offset x0, -32\n" + " .cfi_offset x1, -24\n" + " stp x2, x3, [sp, #(192 - 32)]\n" + " .cfi_offset x3, -40\n" + " .cfi_offset x2, -48\n" + " stp x4, x5, [sp, #(192 - 48)]\n" + " .cfi_offset x6, -56\n" + " .cfi_offset x5, -64\n" + " stp x6, x7, [sp, #(192 - 64)]\n" + " .cfi_offset x7, -72\n" + " .cfi_offset x6, -80\n" + " stp x16, x17, [sp, #(192 - 80)]\n" + " .cfi_offset x17, -88\n" + " .cfi_offset x16, -96\n" + " stp x18, x19, [sp, #(192 - 96)]\n" + " .cfi_offset x19, -104\n" + " .cfi_offset x18, -112\n" + " stp x20, x21, [sp, #(192 - 112)]\n" + " .cfi_offset x21, -120\n" + " .cfi_offset x20, -128\n" + " stp x22, x23, [sp, #(192 - 128)]\n" + " .cfi_offset x23, -136\n" + " .cfi_offset x22, -144\n" + " str x8, [sp, #(192 - 144)]\n" + "\n" + " ; Shift the symbol name to x19 and g_pfnXXXX pointer to x20 as these are preserved registers\n" + " ; (in case we need to call LazyLoadModule/RTLdrLoad)\n" + " mov x19, x9\n" + " mov x20, x10\n" + "\n" + " ; Get the module handle and call RTLdrGetSymbol(RTLDRMOD hLdrMod, const char *pszSymbol, void **ppvValue)\n" + " adrp x0, g_hMod@PAGE\n" + " ldr x0, [x0, g_hMod@PAGEOFF]\n" + " cmp x0, #0\n" + " b.eq LazyLoading\n" + " mov x1, x19\n" + " mov x2, x20\n" + " bl %sRTLdrGetSymbol\n" + "\n" + " cmp w0, #0\n" + " b.eq Lreturn\n" + "\n" + "Lbadsym: ; Call sRTAssertMsg2Weak. Variadic (...) arguments are passed on the stack it seems.\n" + " mov x3, x0\n" + " adrp x2, g_szLibrary@PAGE\n" + " add x2, x2, g_szLibrary@PAGEOFF\n" + " mov x1, x19\n" + " adrp x0, g_szFailLoadFmt@PAGE\n" + " add x0, x0, g_szFailLoadFmt@PAGEOFF\n" + " stp x1, x2, [sp]\n" + " str x3, [sp, #16]\n" + " bl %sRTAssertMsg2Weak\n" + "Lbadsymloop:\n" + " brk #0x1\n" + " b Lbadsymloop\n" + + "Lreturn:\n" + " ; Restore saved register\n" + " ldr x8, [sp, #(192 - 144)]\n" + " .cfi_restore x8\n" + " ldp x22, x23, [sp, #(192 - 128)]\n" + " .cfi_restore x23\n" + " .cfi_restore x22\n" + " ldp x20, x21, [sp, #(192 - 112)]\n" + " .cfi_restore x21\n" + " .cfi_restore x20\n" + " ldp x18, x19, [sp, #(192 - 96)]\n" + " .cfi_restore x19\n" + " .cfi_restore x18\n" + " ldp x16, x17, [sp, #(192 - 80)]\n" + " .cfi_restore x17\n" + " .cfi_restore x18\n" + " ldp x6, x7, [sp, #(192 - 64)]\n" + " .cfi_restore x7\n" + " .cfi_restore x6\n" + " ldp x4, x5, [sp, #(192 - 48)]\n" + " .cfi_restore x5\n" + " .cfi_restore x4\n" + " ldp x2, x3, [sp, #(192 - 32)]\n" + " .cfi_restore x3\n" + " .cfi_restore x2\n" + " ldp x0, x1, [sp, #(192 - 16)]\n" + " .cfi_restore x1\n" + " .cfi_restore x0\n" + "\n" + " ldp x29, x30, [sp, #192]\n" + " .cfi_restore x29\n" + " .cfi_restore x30\n" + " add sp, sp, #(16 + 192)\n" + " ret\n" + " .cfi_endproc\n" + "\n" + "\n" + , pszNmPfx, pszNmPfx); + + fprintf(pOutput, + ";\n" + "; Loads the module.\n" + "; ASSUMES called from LazyLoadResolver where all relevant registers are already saved.\n" + ";\n" + "LazyLoading:\n" + " .cfi_startproc\n" + " ; Create frame.\n" + " sub sp, sp, #(16 + 48)\n" + " stp x29, x30, [sp, #48]\n" + " add x29, sp, #48\n" + " .cfi_def_cfa x29, 16\n" + " .cfi_offset x30, -8\n" + " .cfi_offset x29, -16\n" + "\n"); + + if (!g_fSystemLibrary) + fprintf(pOutput, + " ; Call SUPR3HardenedLdrLoadAppPriv(const char *pszFilename, PRTLDRMOD phLdrMod, uint32_t fFlags, PRTERRINFO pErrInfo);\n" + " mov x3, #0\n" + " mov x2, #0\n" + " adrp x1, g_hMod@PAGE\n" + " add x1, x1, g_hMod@PAGEOFF\n" + " adrp x0, g_szLibrary@PAGE\n" + " add x0, x0, g_szLibrary@PAGEOFF\n" + " bl %sSUPR3HardenedLdrLoadAppPriv\n" + , pszNmPfx); + else + fprintf(pOutput, + " ; Call RTLdrLoadSystem(const char *pszFilename, bool fNoUnload, PRTLDRMOD phLdrMod);\n" + " adrp x2, g_hMod@PAGE\n" + " add x2, x2, g_hMod@PAGEOFF\n" + " mov x1, #1\n" + " adrp x0, g_szLibrary@PAGE\n" + " add x0, x0, g_szLibrary@PAGEOFF\n" + " bl %sRTLdrLoadSystem\n" + , pszNmPfx); + + fprintf(pOutput, + " cmp w0, #0\n" + " b.eq Lload_return\n" + "\n" + "Lbadload: ; Call sRTAssertMsg2Weak. Variadic (...) arguments are passed on the stack it seems.\n" + " mov x2, x0\n" + " adrp x1, g_szLibrary@PAGE\n" + " add x1, x1, g_szLibrary@PAGEOFF\n" + " adrp x0, g_szFailResolveFmt@PAGE\n" + " add x0, x0, g_szFailResolveFmt@PAGEOFF\n" + " stp x1, x2, [sp]\n" + " bl %sRTAssertMsg2Weak\n" + "Lbadloadloop:\n" + " brk #0x1\n" + " b Lbadloadloop\n" + "Lload_return:\n" + " adrp x0, g_hMod@PAGE\n" + " ldr x0, [x0, g_hMod@PAGEOFF]\n" + " ldp x29, x30, [sp, #48]\n" + " .cfi_restore x29\n" + " .cfi_restore x30\n" + " add sp, sp, #(16 + 48)\n" + " ret\n" + " .cfi_endproc\n" + "\n" + "\n" + , pszNmPfx); + + /* + * C callable method for explicitly loading the library and optionally + * resolving all the imports. + */ + if (g_fWithExplictLoadFunction) + { + if (g_fSystemLibrary) /* Lazy bird. */ + { + fprintf(stderr, "error: cannot use --system with --explicit-load-function, sorry\n"); + return RTEXITCODE_FAILURE; + } + + int cchLibBaseName = (int)(strchr(g_pszLibrary, '.') ? strchr(g_pszLibrary, '.') - g_pszLibrary : strlen(g_pszLibrary)); + fprintf(pOutput, + ";;\n" + "; ExplicitlyLoad%.*s(bool fResolveAllImports, pErrInfo);\n" + ";\n" + ".section __TEXT,__text,regular,pure_instructions\n" + ".p2align 3\n" + ".globl %sExplicitlyLoad%.*s\n" + "%sExplicitlyLoad%.*s:\n" + " .cfi_startproc\n" + " ; Create frame.\n" + " sub sp, sp, #(16 + 96)\n" + " stp x29, x30, [sp, #96]\n" + " add x29, sp, #96\n" + " .cfi_def_cfa x29, 16\n" + " .cfi_offset x30, -8\n" + " .cfi_offset x29, -16\n" + "\n" + " stp x20, x21, [sp, #(96 - 16)]\n" + " .cfi_offset x21, -24\n" + " .cfi_offset x20, -32\n" + " stp x22, x23, [sp, #(96 - 32)]\n" + " .cfi_offset x23, -40\n" + " .cfi_offset x22, -48\n" + + " ; Save the input parameters.\n" + " mov x20, x0\n" + " mov x21, x1\n" + "\n" + " ;\n" + " ; Is the module already loaded?\n" + " ;\n" + " adrp x0, g_hMod@PAGE\n" + " ldr x0, [x0, g_hMod@PAGEOFF]\n" + " cmp x0, #0\n" + " b.ne Lexplicit_loaded_module\n" + "\n" + , + cchLibBaseName, g_pszLibrary, + pszNmPfx, cchLibBaseName, g_pszLibrary, + pszNmPfx, cchLibBaseName, g_pszLibrary); + fprintf(pOutput, + "Lexplicit_load_module:\n" + " ; Call SUPR3HardenedLdrLoadAppPriv(const char *pszFilename, PRTLDRMOD phLdrMod, uint32_t fFlags, PRTERRINFO pErrInfo);\n" + " mov x3, #0\n" + " mov x2, #0\n" + " adrp x1, g_hMod@PAGE\n" + " add x1, x1, g_hMod@PAGEOFF\n" + " adrp x0, g_szLibrary@PAGE\n" + " add x0, x0, g_szLibrary@PAGEOFF\n" + " bl %sSUPR3HardenedLdrLoadAppPriv\n" + " cmp x0, #0\n" + " b.ne Lexplicit_load_return\n" + "\n" + , pszNmPfx); + + fprintf(pOutput, + " ;\n" + " ; Resolve the imports too if requested to do so.\n" + " ;\n" + "Lexplicit_loaded_module:\n" + " cmp w20, #0\n" + " b.eq Lexplicit_load_return\n" + "\n" + " adrp x22, g_szzNames@PAGE\n" + " add x22, x22, g_szzNames@PAGEOFF\n" + " adrp x23, g_apfnImports@PAGE\n" + " add x23, x23, g_apfnImports@PAGEOFF\n" + "Lexplicit_load_next_import:\n" + " ldr x0, [x23]\n" + " cmp x0, #0\n" + " b.eq Lexplicit_load_return\n" + "\n" + " ; Get the module handle and call RTLdrGetSymbol(RTLDRMOD hLdrMod, const char *pszSymbol, void **ppvValue)\n" + " adrp x0, g_hMod@PAGE\n" + " ldr x0, [x0, g_hMod@PAGEOFF]\n" + " mov x1, x22\n" + " mov x2, x23\n" + " bl %sRTLdrGetSymbol\n" + " cmp x0, #0\n" + " b.ne Lexplicit_load_symbol_error\n" + "\n" + " ; Advance.\n" + " add x23, x23, #8\n" + "Lexplict_load_advance_string:\n" + " ldrb w0, [x22]\n" + " add x22, x22, #1\n" + " cmp w0, #0\n" + " b.ne Lexplict_load_advance_string\n" + " b Lexplicit_load_next_import\n" + "\n" + " ;\n" + " ; Error loading a symbol. Call RTErrInfoSet(PRTERRINFO pErrInfo, int rc, const char *pszMsg) on pErrInfo (preserves x0).\n" + " ;\n" + "Lexplicit_load_symbol_error:\n" + " mov x2, x22\n" + " mov x1, x0\n" + " mov x0, x21\n" + " bl %sRTErrInfoSet\n" + " b Lexplicit_load_return" + " " + "\n" + "Lexplicit_load_return:\n" + " ldp x22, x23, [sp, #(96 - 32)]\n" + " .cfi_restore x23\n" + " .cfi_restore x22\n" + " ldp x20, x21, [sp, #(96 - 16)]\n" + " .cfi_restore x21\n" + " .cfi_restore x20\n" + "\n" + " ldp x29, x30, [sp, #96]\n" + " .cfi_restore x29\n" + " .cfi_restore x30\n" + " add sp, sp, #(16 + 96)\n" + " ret\n" + " .cfi_endproc\n" + "\n" + "\n" + , pszNmPfx, pszNmPfx); + } + + return RTEXITCODE_SUCCESS; +} + + +/** + * Generates the assembly source code, writing it to g_pszOutput. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE, in the latter case full + * details has been displayed. + */ +static RTEXITCODE generateOutput(void) +{ + RTEXITCODE rcExit = RTEXITCODE_FAILURE; + FILE *pOutput = fopen(g_pszOutput, "w"); + if (pOutput) + { + switch (g_enmTarget) + { + case RTLDRARCH_AMD64: + case RTLDRARCH_X86_32: + rcExit = generateOutputInnerX86AndAMD64(pOutput); + break; + case RTLDRARCH_ARM64: + rcExit = generateOutputInnerArm64(pOutput); + break; + default: + rcExit = RTEXITCODE_FAILURE; + break; + } + if (fclose(pOutput)) + { + fprintf(stderr, "error: Error closing '%s'.\n", g_pszOutput); + rcExit = RTEXITCODE_FAILURE; + } + } + else + fprintf(stderr, "error: Failed to open '%s' for writing.\n", g_pszOutput); + return rcExit; +} + + +/** + * Displays usage information. + * + * @returns RTEXITCODE_SUCCESS. + * @param pszArgv0 The argv[0] string. + */ +static int usage(const char *pszArgv0) +{ + printf("usage: %s [options] --libary <loadname> --output <lazyload.asm> <input.def>\n" + "\n" + "Options:\n" + " --explicit-load-function, --no-explicit-load-function\n" + " Whether to include the explicit load function, default is not to.\n" + "\n" + "Copyright (C) 2013-2016 Oracle Corporation\n" + , pszArgv0); + + return RTEXITCODE_SUCCESS; +} + + +int main(int argc, char **argv) +{ + /* + * Parse options. + */ + for (int i = 1; i < argc; i++) + { + const char *psz = argv[i]; + if (*psz == '-') + { + if (!strcmp(psz, "--output") || !strcmp(psz, "-o")) + { + if (++i >= argc) + { + fprintf(stderr, "syntax error: File name expected after '%s'.\n", psz); + return RTEXITCODE_SYNTAX; + } + g_pszOutput = argv[i]; + } + else if (!strcmp(psz, "--library") || !strcmp(psz, "-l")) + { + if (++i >= argc) + { + fprintf(stderr, "syntax error: Library name expected after '%s'.\n", psz); + return RTEXITCODE_SYNTAX; + } + g_pszLibrary = argv[i]; + } + else if (!strcmp(psz, "--explicit-load-function")) + g_fWithExplictLoadFunction = true; + else if (!strcmp(psz, "--no-explicit-load-function")) + g_fWithExplictLoadFunction = false; + else if (!strcmp(psz, "--system")) + g_fSystemLibrary = true; + /** @todo Support different load methods so this can be used on system libs and + * such if we like. */ + else if ( !strcmp(psz, "--help") + || !strcmp(psz, "-help") + || !strcmp(psz, "-h") + || !strcmp(psz, "-?") ) + return usage(argv[0]); + else if ( !strcmp(psz, "--version") + || !strcmp(psz, "-V")) + { + printf("$Revision: 155244 $\n"); + return RTEXITCODE_SUCCESS; + } + else + { + fprintf(stderr, "syntax error: Unknown option '%s'.\n", psz); + return RTEXITCODE_SYNTAX; + } + } + else + { + if (g_cInputs >= RT_ELEMENTS(g_apszInputs)) + { + fprintf(stderr, "syntax error: Too many input files, max is %d.\n", (int)RT_ELEMENTS(g_apszInputs)); + return RTEXITCODE_SYNTAX; + } + g_apszInputs[g_cInputs++] = argv[i]; + } + } + if (g_cInputs == 0) + { + fprintf(stderr, "syntax error: No input file specified.\n"); + return RTEXITCODE_SYNTAX; + } + if (!g_pszOutput) + { + fprintf(stderr, "syntax error: No output file specified.\n"); + return RTEXITCODE_SYNTAX; + } + if (!g_pszLibrary) + { + fprintf(stderr, "syntax error: No library name specified.\n"); + return RTEXITCODE_SYNTAX; + } + + /* + * Do the job. + */ + RTEXITCODE rcExit = parseInputs(); + if (rcExit == RTEXITCODE_SUCCESS) + rcExit = generateOutput(); + return rcExit; +} + diff --git a/src/bldprogs/VBoxEditCoffLib.cpp b/src/bldprogs/VBoxEditCoffLib.cpp new file mode 100644 index 00000000..42bd5494 --- /dev/null +++ b/src/bldprogs/VBoxEditCoffLib.cpp @@ -0,0 +1,476 @@ +/* $Id: VBoxEditCoffLib.cpp $ */ +/** @file + * VBoxEditCoffLib - Simple COFF editor for library files. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include <iprt/assertcompile.h> +#include <iprt/types.h> +#include <iprt/ctype.h> +#include <iprt/formats/pecoff.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef struct ARHDR +{ + char achName[16]; + char achDate[12]; + char achUid[6]; + char achGid[6]; + char achMode[8]; + char achSize[10]; + char achMagic[2]; +} ARHDR; +AssertCompileSize(ARHDR, 16+12+6+6+8+10+2); + + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Verbosity level. */ +static int g_cVerbosity = 0; + +/** The binary size. */ +static unsigned g_cbBinary = 0; +/** The binary data we're editing. */ +static uint8_t *g_pbBinary = NULL; + +/** Size of the currently selected member. */ +static unsigned g_cbMember = 0; +/** Pointer to the data for the currently selected member. */ +static uint8_t *g_pbMember = NULL; + + +/** + * File size. + * + * @returns file size in bytes. + * @returns 0 on failure. + * @param pFile File to size. + */ +static unsigned fsize(FILE *pFile) +{ + long cbFile; + off_t Pos = ftell(pFile); + if ( Pos >= 0 + && !fseek(pFile, 0, SEEK_END)) + { + cbFile = ftell(pFile); + if ( cbFile >= 0 + && !fseek(pFile, 0, SEEK_SET)) + return cbFile; + } + return 0; +} + + +/** + * Reports a problem. + * + * @returns RTEXITCODE_FAILURE + */ +static int error(const char *pszFormat, ...) +{ + fprintf(stderr, "error: "); + va_list va; + va_start(va, pszFormat); + vfprintf(stderr, pszFormat, va); + va_end(va); + return RTEXITCODE_FAILURE; +} + + +/** + * Reports a syntax problem. + * + * @returns RTEXITCODE_SYNTAX + */ +static int syntax(const char *pszFormat, ...) +{ + fprintf(stderr, "syntax error: "); + va_list va; + va_start(va, pszFormat); + vfprintf(stderr, pszFormat, va); + va_end(va); + return RTEXITCODE_FAILURE; +} + + +/** + * Display usage + * + * @returns success if stdout, syntax error if stderr. + */ +static int usage(FILE *pOut, const char *argv0) +{ + fprintf(pOut, + "usage: %s --input <in.lib> --output <out.lib> [options and operations]\n" + "\n" + "Operations and Options (processed in place):\n" + " --verbose Noisier.\n" + " --quiet Quiet execution.\n" + " --select <member>\n" + " Selects archive member which name ends in the given string.\n" + " --redefine-sym <old>=<new>\n" + " Redefine the symbol <old> to <new>.\n" + " Note! the length must be the same!\n" + , argv0); + return pOut == stdout ? RTEXITCODE_SUCCESS : RTEXITCODE_SYNTAX; +} + + +/** + * Helper for SelectMember. + */ +static bool AreAllDigits(const char *pch, size_t cch, size_t *puValue) +{ + *puValue = 0; + do + { + if (!RT_C_IS_DIGIT(*pch)) + return false; + *puValue = *puValue * 10 + *pch - '0'; + pch++; + cch--; + } while (cch > 0); + return true; +} + + +/** + * Selects archive member ending with the given name. + * + * Updates g_cbMember and g_pbMember. + */ +static int SelectMember(const char *pszEndsWith) +{ + size_t const cchEndsWith = strlen(pszEndsWith); + + /* + * Check the header. + */ + if (memcmp(g_pbBinary, RT_STR_TUPLE("!<arch>\n"))) + return error("Not an AR library!\n"); + + /* + * Work the members. + */ + const char *pszStringTab = NULL; + size_t cbStringTab = 0; + for (size_t off = sizeof("!<arch>\n") - 1; off < g_cbBinary;) + { + ARHDR *pHdr = (ARHDR *)&g_pbBinary[off]; + char szTmp[16 + 8]; + size_t uValue; + char *pszIgn; +#define COPY_AND_TRIM(a_pchSrc, a_cbSrc) do { \ + memcpy(szTmp, (a_pchSrc), (a_cbSrc)); \ + size_t offCopy = (a_cbSrc); \ + while (offCopy > 0 && (szTmp[offCopy - 1] == ' ' || szTmp[offCopy - 1] == '\0')) \ + offCopy--; \ + szTmp[offCopy] = '\0'; \ + } while (0) + + /* + * Parse the header. + */ + + /* The size: */ + COPY_AND_TRIM(pHdr->achSize, sizeof(pHdr->achSize)); + size_t cbFile = strtol(szTmp, &pszIgn, 10); + + /* The name: */ + size_t cbExtra = 0; + size_t cchName = sizeof(pHdr->achName); + const char *pchName = pHdr->achName; + if ( pchName[0] == '#' + && pchName[1] == '1' + && pchName[2] == '/') + { + COPY_AND_TRIM(&pchName[3], cchName - 3); + cchName = cbExtra = strtol(szTmp, &pszIgn, 10); + pchName = (char *)(pHdr + 1); + } + else + { + while (cchName > 0 && (pchName[cchName - 1] == ' ' || pchName[cchName - 1] == '\0')) + cchName--; + + /* Long filename string table? */ + if ( (cchName == 2 && pchName[0] == '/' && pchName[1] == '/') + || (cchName == sizeof("ARFILENAMES/") - 1 && memcmp(pchName, RT_STR_TUPLE("ARFILENAMES/")) == 0)) + { + pszStringTab = (char *)(pHdr + 1); + cbStringTab = cbFile; + } + /* Long filename string table reference? */ + else if ( cchName >= 2 + && ( pchName[0] == '/' /* System V */ + || pchName[0] == ' ' /* Other */) + && AreAllDigits(&pchName[1], cchName - 1, &uValue) && uValue < cbStringTab) + { + pchName = &pszStringTab[uValue]; + cchName = strlen(pchName); /** @todo unsafe! */ + } + /* Drop trailing slash in case of System V filename: */ + else if (cchName > 1 && pchName[cchName - 1] == '/') + cchName -= 1; + } + + if (g_cVerbosity > 2) + fprintf(stderr, "debug: %#08x: %#010x %*.*s\n", + (unsigned)off, (unsigned)(cbFile - cbExtra), (int)cchName, (int)cchName, pchName); + + /* + * Do matching. + */ + if ( cchName >= cchEndsWith + && strncmp(&pchName[cchName - cchEndsWith], pszEndsWith, cchEndsWith) == 0) + { + g_pbMember = (uint8_t *)(pHdr + 1) + cbExtra; + g_cbMember = (unsigned)(cbFile - cbExtra); + if (g_cVerbosity > 1) + fprintf(stderr, "debug: selected '%*.*s': %#x LB %#x\n", + (int)cchName, (int)cchName, pchName, (unsigned)(off + sizeof(*pHdr) + cbExtra), g_cbMember); + return 0; + } + + /* + * Advance. + */ + off += sizeof(ARHDR) + cbFile + (cbFile & 1); + } + + return error("No member ending with '%s' was found!\n", pszEndsWith); +} + + +/** + * @note Borrowed from VBoxBs3objConverter.cpp + */ +static const char *coffGetSymbolName(PCIMAGE_SYMBOL pSym, const char *pchStrTab, uint32_t cbStrTab, char pszShortName[16]) +{ + if (pSym->N.Name.Short != 0) + { + memcpy(pszShortName, pSym->N.ShortName, 8); + pszShortName[8] = '\0'; + return pszShortName; + } + if (pSym->N.Name.Long < cbStrTab) + { + uint32_t const cbLeft = cbStrTab - pSym->N.Name.Long; + const char *pszRet = pchStrTab + pSym->N.Name.Long; + if (memchr(pszRet, '\0', cbLeft) != NULL) + return pszRet; + } + error("Invalid string table index %#x!\n", pSym->N.Name.Long); + return "Invalid Symbol Table Entry"; +} + + +/** + * Redefine a symbol with a different name. + */ +static int RedefineSymbol(const char *pszOldEqualNew) +{ + /* + * Check state and split up the input. + */ + if (!g_pbMember) + return error("No selected archive member!\n"); + + const char *pszNew = strchr(pszOldEqualNew, '='); + if (!pszNew || pszNew[1] == '\0') + return error("Malformed 'old=new' argument: %s\n", pszOldEqualNew); + const char *pszOld = pszOldEqualNew; + size_t const cchOld = pszNew - pszOldEqualNew; + pszNew += 1; + size_t const cchNew = strlen(pszNew); + if (cchNew > cchOld) + return error("The new symbol must not be longer than the old symbol: %#x vs %#x (%s)\n", cchNew, cchOld, pszOldEqualNew); + + if (g_cVerbosity > 2) + fprintf(stderr, "debug: redefining symbol '%*.*s' to '%*.*s'...\n", + (int)cchOld, (int)cchOld, pszOld, (int)cchNew, (int)cchNew, pszNew); + + /* + * Parse COFF header. + */ + const IMAGE_FILE_HEADER *pHdr = (const IMAGE_FILE_HEADER *)g_pbMember; + if (sizeof(*pHdr) >= g_cbMember) + return error("member too small for COFF\n"); + if ( pHdr->Machine != IMAGE_FILE_MACHINE_AMD64 + && pHdr->Machine != IMAGE_FILE_MACHINE_I386) + return error("Unsupported COFF machine: %#x\n", pHdr->Machine); + if ( pHdr->PointerToSymbolTable >= g_cbMember + || pHdr->PointerToSymbolTable < sizeof(*pHdr)) + return error("PointerToSymbolTable is out of bounds: %#x, max %#x\n", pHdr->PointerToSymbolTable, g_cbMember); + unsigned const cSymbols = pHdr->NumberOfSymbols; + if ( cSymbols >= g_cbMember - pHdr->PointerToSymbolTable + || cSymbols * sizeof(IMAGE_SYMBOL) > g_cbMember - pHdr->PointerToSymbolTable) + return error("PointerToSymbolTable + NumberOfSymbols is out of bounds: %#x + %#x * %#x (%#x), max %#x\n", + pHdr->PointerToSymbolTable, cSymbols, sizeof(IMAGE_SYMBOL), + pHdr->PointerToSymbolTable + cSymbols * sizeof(IMAGE_SYMBOL), g_cbMember); + + /* + * Work the symbol table. + */ + unsigned cRenames = 0; + PIMAGE_SYMBOL const paSymTab = (PIMAGE_SYMBOL)&g_pbMember[pHdr->PointerToSymbolTable]; + const char * const pchStrTab = (const char *)&paSymTab[pHdr->NumberOfSymbols]; + uint32_t const cbStrTab = (uint32_t)((uintptr_t)&g_pbMember[g_cbMember] - (uintptr_t)pchStrTab); + for (unsigned iSym = 0; iSym < cSymbols; iSym++) + { + char szShort[16]; + const char *pszSymName = coffGetSymbolName(&paSymTab[iSym], pchStrTab, cbStrTab, szShort); + size_t cchSymName = strlen(pszSymName); + if (g_cVerbosity > 3 && cchSymName > 0) + fprintf(stderr, "debug: symbol %u: %s\n", iSym, pszSymName); + if ( cchSymName == cchOld + && memcmp(pszSymName, pszOld, cchSymName) == 0) + { + size_t const offStrTab = (size_t)(pszSymName - pchStrTab); + if (offStrTab < cbStrTab) + { + if (g_cVerbosity > 1) + fprintf(stderr, "debug: Found symbol '%s' in at string table offset %#x, renaming to '%s'.\n", + pszSymName, (uint32_t)offStrTab, pszNew); + if (offStrTab > 0 && pchStrTab[offStrTab - 1] != '\0') + return error("Cannot rename sub-string!\n"); + memset((char *)pszSymName, 0, cchOld); + memcpy((char *)pszSymName, pszNew, cchNew); + } + else + { + if (g_cVerbosity > 1) + fprintf(stderr, "debug: Found symbol '%s' in symbol table, renaming to '%s'.\n", pszSymName, pszNew); + memset(paSymTab[iSym].N.ShortName, 0, sizeof(paSymTab[iSym].N.ShortName)); + memcpy(paSymTab[iSym].N.ShortName, pszNew, cchNew); + } + cRenames++; + } + + /* Skip AUX symbols. */ + uint8_t cAuxSyms = paSymTab[iSym].NumberOfAuxSymbols; + while (cAuxSyms-- > 0) + iSym++; + } + + if (cRenames > 0) + return RTEXITCODE_SUCCESS; + return error("Symbol '%*.*s' was not found!\n", cchOld, cchOld, pszOld); +} + + +int main(int argc, char **argv) +{ + /* + * Parse arguments. + */ + const char *pszIn = NULL; + const char *pszOut = NULL; + for (int i = 1; i < argc; i++) + { + const char *pszArg = argv[i]; + + /* Options without values first: */ + if ( strcmp(pszArg, "--verbose") == 0 + || strcmp(pszArg, "-v") == 0) + g_cVerbosity += 1; + else if ( strcmp(pszArg, "--quiet") == 0 + || strcmp(pszArg, "--q") == 0) + g_cVerbosity = 0; + else if ( strcmp(pszArg, "--help") == 0 + || strcmp(pszArg, "-h") == 0 + || strcmp(pszArg, "-?") == 0) + return usage(stdout, argv[0]); + else if (i + 1 >= argc) + return syntax("Missing argument value or unknown option '%s'!\n", pszArg); + else + { + i++; + const char *pszValue = argv[i]; + int rc = 0; + if (strcmp(pszArg, "--input") == 0) + { + if (pszIn) + return syntax("--input can only be specified once!\n"); + pszIn = pszValue; + + /* Load it into memory: */ + FILE *pIn = fopen(pszIn, "rb"); + if (!pIn) + return error("Failed to open '%s' for reading!\n", pszIn); + g_cbBinary = fsize(pIn); + if (!g_cbBinary) + return error("Failed to determin the size of '%s'!\n", pszIn); + if (g_cbBinary > _128M) + return error("'%s' is too large: %x, max %x\n", g_cbBinary, (size_t)_128M); + g_pbBinary = (uint8_t *)calloc(1, g_cbBinary + 4096); + if (!g_pbBinary) + return error("Out of memory!\n"); + if (fread(g_pbBinary, g_cbBinary, 1, pIn) != 1) + return error("Failed to read '%s' into memory!\n", pszIn); + fclose(pIn); + } + else if (strcmp(pszArg, "--output") == 0) + pszOut = pszValue; + else if (strcmp(pszArg, "--select") == 0) + rc = SelectMember(pszValue); + else if (strcmp(pszArg, "--redefine-sym") == 0) + rc = RedefineSymbol(pszValue); + else + return syntax("Unknown option: %s\n", pszArg); + if (rc != RTEXITCODE_SUCCESS) + return rc; + } + } + + if (!pszIn || !pszOut) + return syntax("No %s specified!\n", pszIn ? "output file" : "intput library file"); + + /* + * Write out the result. + */ + FILE *pOut = fopen(pszOut, "wb"); + if (!pOut) + return error("Failed to open '%s' for writing!\n", pszOut); + if (fwrite(g_pbBinary, g_cbBinary, 1, pOut) != 1) + return error("Error writing %#x bytes to '%s'!\n", g_cbBinary, pszOut); + if (fclose(pOut) != 0) + return error("Error closing '%s'!\n", pszOut); + return RTEXITCODE_SUCCESS; +} + diff --git a/src/bldprogs/VBoxPeSetVersion.cpp b/src/bldprogs/VBoxPeSetVersion.cpp new file mode 100644 index 00000000..0c17c971 --- /dev/null +++ b/src/bldprogs/VBoxPeSetVersion.cpp @@ -0,0 +1,404 @@ +/* $Id: VBoxPeSetVersion.cpp $ */ +/** @file + * IPRT - Change the OS and SubSystem version to value suitable for NT v3.1. + * + * Also make sure the IAT is writable, since NT v3.1 expects this. These are + * tricks necessary to make binaries created by newer Visual C++ linkers work + * on ancient NT version like W2K, NT4 and NT 3.x. + */ + +/* + * Copyright (C) 2012-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/formats/mz.h> +#include <iprt/formats/pecoff.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define MK_VER(a_uHi, a_uLo) ( ((a_uHi) << 8) | (a_uLo)) + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static const char *g_pszFilename; +static unsigned g_cVerbosity = 0; + + +static int Error(const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + char szTmp[1024]; + _vsnprintf(szTmp, sizeof(szTmp), pszFormat, va); + va_end(va); + fprintf(stderr, "VBoxPeSetVersion: %s: error: %s\n", g_pszFilename, szTmp); + return RTEXITCODE_FAILURE; +} + + +static void Info(unsigned iLevel, const char *pszFormat, ...) +{ + if (iLevel <= g_cVerbosity) + { + va_list va; + va_start(va, pszFormat); + char szTmp[1024]; + _vsnprintf(szTmp, sizeof(szTmp), pszFormat, va); + va_end(va); + fprintf(stderr, "VBoxPeSetVersion: %s: info: %s\n", g_pszFilename, szTmp); + } +} + + +static int UpdateFile(FILE *pFile, unsigned uNtVersion, PIMAGE_SECTION_HEADER *ppaShdr) +{ + /* + * Locate and read the PE header. + * + * Note! We'll be reading the 64-bit size even for 32-bit since the difference + * is 16 bytes, which is less than a section header, so it won't be a problem. + */ + unsigned long offNtHdrs; + { + IMAGE_DOS_HEADER MzHdr; + if (fread(&MzHdr, sizeof(MzHdr), 1, pFile) != 1) + return Error("Failed to read MZ header: %s", strerror(errno)); + if (MzHdr.e_magic != IMAGE_DOS_SIGNATURE) + return Error("Invalid MZ magic: %#x", MzHdr.e_magic); + offNtHdrs = MzHdr.e_lfanew; + } + + if (fseek(pFile, offNtHdrs, SEEK_SET) != 0) + return Error("Failed to seek to PE header at %#lx: %s", offNtHdrs, strerror(errno)); + union + { + IMAGE_NT_HEADERS32 x32; + IMAGE_NT_HEADERS64 x64; + } NtHdrs, + NtHdrsNew; + if (fread(&NtHdrs, sizeof(NtHdrs), 1, pFile) != 1) + return Error("Failed to read PE header at %#lx: %s", offNtHdrs, strerror(errno)); + + /* + * Validate it a little bit. + */ + if (NtHdrs.x32.Signature != IMAGE_NT_SIGNATURE) + return Error("Invalid PE signature: %#x", NtHdrs.x32.Signature); + uint32_t cbNewHdrs; + if (NtHdrs.x32.FileHeader.Machine == IMAGE_FILE_MACHINE_AMD64) + { + if (NtHdrs.x64.FileHeader.SizeOfOptionalHeader != sizeof(NtHdrs.x64.OptionalHeader)) + return Error("Invalid optional header size: %#x", NtHdrs.x64.FileHeader.SizeOfOptionalHeader); + if (NtHdrs.x64.OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC) + return Error("Invalid optional header magic: %#x", NtHdrs.x64.OptionalHeader.Magic); + if (!uNtVersion) + uNtVersion = MK_VER(5, 2); + else if (uNtVersion < MK_VER(5, 2)) + return Error("Selected version is too old for AMD64: %u.%u", uNtVersion >> 8, uNtVersion & 0xff); + cbNewHdrs = sizeof(NtHdrsNew.x64); + } + else if (NtHdrs.x32.FileHeader.Machine != IMAGE_FILE_MACHINE_I386) + return Error("Not I386 or AMD64 machine: %#x", NtHdrs.x32.FileHeader.Machine); + else + { + if (NtHdrs.x32.FileHeader.SizeOfOptionalHeader != sizeof(NtHdrs.x32.OptionalHeader)) + return Error("Invalid optional header size: %#x", NtHdrs.x32.FileHeader.SizeOfOptionalHeader); + if (NtHdrs.x32.OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR32_MAGIC) + return Error("Invalid optional header magic: %#x", NtHdrs.x32.OptionalHeader.Magic); + if (!uNtVersion) + uNtVersion = MK_VER(3, 10); + cbNewHdrs = sizeof(NtHdrsNew.x32); + } + + /* + * Do the header modifications. + */ + memcpy(&NtHdrsNew, &NtHdrs, sizeof(NtHdrsNew)); + NtHdrsNew.x32.OptionalHeader.MajorOperatingSystemVersion = uNtVersion >> 8; + NtHdrsNew.x32.OptionalHeader.MinorOperatingSystemVersion = uNtVersion & 0xff; + NtHdrsNew.x32.OptionalHeader.MajorSubsystemVersion = uNtVersion >> 8; + NtHdrsNew.x32.OptionalHeader.MinorSubsystemVersion = uNtVersion & 0xff; + AssertCompileMembersAtSameOffset(IMAGE_NT_HEADERS32, OptionalHeader.MajorOperatingSystemVersion, IMAGE_NT_HEADERS64, OptionalHeader.MajorOperatingSystemVersion); + AssertCompileMembersAtSameOffset(IMAGE_NT_HEADERS32, OptionalHeader.MinorOperatingSystemVersion, IMAGE_NT_HEADERS64, OptionalHeader.MinorOperatingSystemVersion); + AssertCompileMembersAtSameOffset(IMAGE_NT_HEADERS32, OptionalHeader.MajorSubsystemVersion, IMAGE_NT_HEADERS64, OptionalHeader.MajorSubsystemVersion); + AssertCompileMembersAtSameOffset(IMAGE_NT_HEADERS32, OptionalHeader.MinorSubsystemVersion, IMAGE_NT_HEADERS64, OptionalHeader.MinorSubsystemVersion); + + if (uNtVersion <= MK_VER(3, 50)) + { + NtHdrsNew.x32.OptionalHeader.MajorOperatingSystemVersion = 1; + NtHdrsNew.x32.OptionalHeader.MinorOperatingSystemVersion = 0; + AssertCompileMembersAtSameOffset(IMAGE_NT_HEADERS32, OptionalHeader.MajorOperatingSystemVersion, IMAGE_NT_HEADERS64, OptionalHeader.MajorOperatingSystemVersion); + AssertCompileMembersAtSameOffset(IMAGE_NT_HEADERS32, OptionalHeader.MinorOperatingSystemVersion, IMAGE_NT_HEADERS64, OptionalHeader.MinorOperatingSystemVersion); + } + + if (memcmp(&NtHdrsNew, &NtHdrs, sizeof(NtHdrs))) + { + /** @todo calc checksum. */ + NtHdrsNew.x32.OptionalHeader.CheckSum = 0; + AssertCompileMembersAtSameOffset(IMAGE_NT_HEADERS32, OptionalHeader.MinorOperatingSystemVersion, IMAGE_NT_HEADERS64, OptionalHeader.MinorOperatingSystemVersion); + + if ( NtHdrsNew.x32.OptionalHeader.MajorOperatingSystemVersion != NtHdrs.x32.OptionalHeader.MajorOperatingSystemVersion + || NtHdrsNew.x32.OptionalHeader.MinorOperatingSystemVersion != NtHdrs.x32.OptionalHeader.MinorOperatingSystemVersion) + Info(1,"OperatingSystemVersion %u.%u -> %u.%u", + NtHdrs.x32.OptionalHeader.MajorOperatingSystemVersion, NtHdrs.x32.OptionalHeader.MinorOperatingSystemVersion, + NtHdrsNew.x32.OptionalHeader.MajorOperatingSystemVersion, NtHdrsNew.x32.OptionalHeader.MinorOperatingSystemVersion); + if ( NtHdrsNew.x32.OptionalHeader.MajorSubsystemVersion != NtHdrs.x32.OptionalHeader.MajorSubsystemVersion + || NtHdrsNew.x32.OptionalHeader.MinorSubsystemVersion != NtHdrs.x32.OptionalHeader.MinorSubsystemVersion) + Info(1,"SubsystemVersion %u.%u -> %u.%u", + NtHdrs.x32.OptionalHeader.MajorSubsystemVersion, NtHdrs.x32.OptionalHeader.MinorSubsystemVersion, + NtHdrsNew.x32.OptionalHeader.MajorSubsystemVersion, NtHdrsNew.x32.OptionalHeader.MinorSubsystemVersion); + + if (fseek(pFile, offNtHdrs, SEEK_SET) != 0) + return Error("Failed to seek to PE header at %#lx: %s", offNtHdrs, strerror(errno)); + if (fwrite(&NtHdrsNew, cbNewHdrs, 1, pFile) != 1) + return Error("Failed to write PE header at %#lx: %s", offNtHdrs, strerror(errno)); + } + + /* + * Make the IAT writable for NT 3.1 and drop the non-cachable flag from .bss. + * + * The latter is a trick we use to prevent the linker from merging .data and .bss, + * because NT 3.1 does not honor Misc.VirtualSize and won't zero padd the .bss part + * if it's not zero padded in the file. This seemed simpler than adding zero padding. + */ + if ( uNtVersion <= MK_VER(3, 10) + && NtHdrsNew.x32.FileHeader.NumberOfSections > 0) + { + uint32_t cbShdrs = sizeof(IMAGE_SECTION_HEADER) * NtHdrsNew.x32.FileHeader.NumberOfSections; + PIMAGE_SECTION_HEADER paShdrs = (PIMAGE_SECTION_HEADER)calloc(1, cbShdrs); + if (!paShdrs) + return Error("Out of memory"); + *ppaShdr = paShdrs; + + unsigned long offShdrs = offNtHdrs + + RT_UOFFSETOF_DYN(IMAGE_NT_HEADERS32, + OptionalHeader.DataDirectory[NtHdrsNew.x32.OptionalHeader.NumberOfRvaAndSizes]); + if (fseek(pFile, offShdrs, SEEK_SET) != 0) + return Error("Failed to seek to section headers at %#lx: %s", offShdrs, strerror(errno)); + if (fread(paShdrs, cbShdrs, 1, pFile) != 1) + return Error("Failed to read section headers at %#lx: %s", offShdrs, strerror(errno)); + + bool fFoundBss = false; + uint32_t uRvaEnd = NtHdrsNew.x32.OptionalHeader.SizeOfImage; + uint32_t uRvaIat = NtHdrsNew.x32.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].Size > 0 + ? NtHdrsNew.x32.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].VirtualAddress : UINT32_MAX; + uint32_t i = NtHdrsNew.x32.FileHeader.NumberOfSections; + while (i-- > 0) + if (!(paShdrs[i].Characteristics & IMAGE_SCN_TYPE_NOLOAD)) + { + bool fModified = false; + if (uRvaIat >= paShdrs[i].VirtualAddress && uRvaIat < uRvaEnd) + { + if (!(paShdrs[i].Characteristics & IMAGE_SCN_MEM_WRITE)) + { + paShdrs[i].Characteristics |= IMAGE_SCN_MEM_WRITE; + fModified = true; + } + uRvaIat = UINT32_MAX; + } + + if ( !fFoundBss + && strcmp((const char *)paShdrs[i].Name, ".bss") == 0) + { + if (paShdrs[i].Characteristics & IMAGE_SCN_MEM_NOT_CACHED) + { + paShdrs[i].Characteristics &= ~IMAGE_SCN_MEM_NOT_CACHED; + fModified = true; + } + fFoundBss = true; + } + + if (fModified) + { + unsigned long offShdr = offShdrs + i * sizeof(IMAGE_SECTION_HEADER); + if (fseek(pFile, offShdr, SEEK_SET) != 0) + return Error("Failed to seek to section header #%u at %#lx: %s", i, offShdr, strerror(errno)); + if (fwrite(&paShdrs[i], sizeof(IMAGE_SECTION_HEADER), 1, pFile) != 1) + return Error("Failed to write %8.8s section header header at %#lx: %s", + paShdrs[i].Name, offShdr, strerror(errno)); + if (uRvaIat == UINT32_MAX && fFoundBss) + break; + } + + /* Advance */ + uRvaEnd = paShdrs[i].VirtualAddress; + } + + } + + return RTEXITCODE_SUCCESS; +} + + +static int Usage(FILE *pOutput) +{ + fprintf(pOutput, + "Usage: VBoxPeSetVersion [options] <PE-image>\n" + "Options:\n" + " -v, --verbose\n" + " Increases verbosity.\n" + " -q, --quiet\n" + " Quiet operation (default).\n" + " --nt31, --nt350, --nt351, --nt4, --w2k, --xp, --w2k3, --vista,\n" + " --w7, --w8, --w81, --w10\n" + " Which version to set. Default: --nt31\n" + ); + return RTEXITCODE_SYNTAX; +} + + +/** @todo Rewrite this so it can take options and print out error messages. */ +int main(int argc, char **argv) +{ + /* + * Parse arguments. + * This stucks + */ + unsigned uNtVersion = 0; + const char *pszFilename = NULL; + bool fAcceptOptions = true; + for (int i = 1; i < argc; i++) + { + const char *psz = argv[i]; + if (fAcceptOptions && *psz == '-') + { + char ch = psz[1]; + psz += 2; + if (ch == '-') + { + if (!*psz) + { + fAcceptOptions = false; + continue; + } + + if (strcmp(psz, "verbose") == 0) + ch = 'v'; + else if (strcmp(psz, "quiet") == 0) + ch = 'q'; + else if (strcmp(psz, "help") == 0) + ch = 'h'; + else if (strcmp(psz, "version") == 0) + ch = 'V'; + else + { + if (strcmp(psz, "nt31") == 0) + uNtVersion = MK_VER(3,10); + else if (strcmp(psz, "nt350") == 0) + uNtVersion = MK_VER(3,50); + else if (strcmp(psz, "nt351") == 0) + uNtVersion = MK_VER(3,51); + else if (strcmp(psz, "nt4") == 0) + uNtVersion = MK_VER(4,0); + else if (strcmp(psz, "w2k") == 0) + uNtVersion = MK_VER(5,0); + else if (strcmp(psz, "xp") == 0) + uNtVersion = MK_VER(5,1); + else if (strcmp(psz, "w2k3") == 0) + uNtVersion = MK_VER(5,2); + else if (strcmp(psz, "vista") == 0) + uNtVersion = MK_VER(6,0); + else if (strcmp(psz, "w7") == 0) + uNtVersion = MK_VER(6,1); + else if (strcmp(psz, "w8") == 0) + uNtVersion = MK_VER(6,2); + else if (strcmp(psz, "w81") == 0) + uNtVersion = MK_VER(6,3); + else if (strcmp(psz, "w10") == 0) + uNtVersion = MK_VER(10,0); + else + { + fprintf(stderr, "VBoxPeSetVersion: syntax error: Unknown option: --%s\n", psz); + return RTEXITCODE_SYNTAX; + } + continue; + } + psz = " "; + } + do + { + switch (ch) + { + case 'q': + g_cVerbosity = 0; + break; + case 'v': + g_cVerbosity++; + break; + case 'V': + printf("2.0\n"); + return RTEXITCODE_SUCCESS; + case 'h': + Usage(stdout); + return RTEXITCODE_SUCCESS; + default: + fprintf(stderr, "VBoxPeSetVersion: syntax error: Unknown option: -%c\n", ch ? ch : ' '); + return RTEXITCODE_SYNTAX; + } + } while ((ch = *psz++) != '\0'); + + } + else if (!pszFilename) + pszFilename = psz; + else + { + fprintf(stderr, "VBoxPeSetVersion: syntax error: More than one PE-image specified!\n"); + return RTEXITCODE_SYNTAX; + } + } + + if (!pszFilename) + { + fprintf(stderr, "VBoxPeSetVersion: syntax error: No PE-image specified!\n"); + return RTEXITCODE_SYNTAX; + } + g_pszFilename = pszFilename; + + /* + * Process the file. + */ + int rcExit; + FILE *pFile = fopen(pszFilename, "r+b"); + if (pFile) + { + PIMAGE_SECTION_HEADER paShdrs = NULL; + rcExit = UpdateFile(pFile, uNtVersion, &paShdrs); + if (paShdrs) + free(paShdrs); + if (fclose(pFile) != 0) + rcExit = Error("fclose failed on '%s': %s", pszFilename, strerror(errno)); + } + else + rcExit = Error("Failed to open '%s' for updating: %s", pszFilename, strerror(errno)); + return rcExit; +} + diff --git a/src/bldprogs/VBoxTpG.cpp b/src/bldprogs/VBoxTpG.cpp new file mode 100644 index 00000000..516b749e --- /dev/null +++ b/src/bldprogs/VBoxTpG.cpp @@ -0,0 +1,2665 @@ +/* $Id: VBoxTpG.cpp $ */ +/** @file + * VBox Build Tool - VBox Tracepoint Generator. + */ + +/* + * Copyright (C) 2012-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <VBox/VBoxTpG.h> + +#include <iprt/alloca.h> +#include <iprt/assert.h> +#include <iprt/ctype.h> +#include <iprt/env.h> +#include <iprt/errcore.h> +#include <iprt/file.h> +#include <iprt/getopt.h> +#include <iprt/initterm.h> +#include <iprt/list.h> +#include <iprt/mem.h> +#include <iprt/message.h> +#include <iprt/path.h> +#include <iprt/process.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/uuid.h> + +#include "scmstream.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef struct VTGPROBE *PVTGPROBE; + +typedef struct VTGATTRS +{ + kVTGStability enmCode; + kVTGStability enmData; + kVTGClass enmDataDep; +} VTGATTRS; +typedef VTGATTRS *PVTGATTRS; + + +typedef struct VTGARG +{ + RTLISTNODE ListEntry; + /** The argument name. (heap) */ + char *pszName; + /** The type presented to the tracer (in string table). */ + const char *pszTracerType; + /** The argument type used in the probe method in that context. (heap) */ + char *pszCtxType; + /** Argument passing format string. First and only argument is the name. + * (const string) */ + const char *pszArgPassingFmt; + /** The type flags. */ + uint32_t fType; + /** The argument number (0-based) for complaining/whatever. */ + uint16_t iArgNo; + /** The probe the argument belongs to (for complaining/whatever). */ + PVTGPROBE pProbe; + /** The absolute source position. */ + size_t offSrc; +} VTGARG; +typedef VTGARG *PVTGARG; + +typedef struct VTGPROBE +{ + RTLISTNODE ListEntry; + char *pszMangledName; + const char *pszUnmangledName; + RTLISTANCHOR ArgHead; + uint32_t cArgs; + bool fHaveLargeArgs; + uint32_t offArgList; + uint32_t iProbe; + size_t iLine; +} VTGPROBE; + +typedef struct VTGPROVIDER +{ + RTLISTNODE ListEntry; + const char *pszName; + + uint16_t iFirstProbe; + uint16_t cProbes; + + VTGATTRS AttrSelf; + VTGATTRS AttrModules; + VTGATTRS AttrFunctions; + VTGATTRS AttrName; + VTGATTRS AttrArguments; + + RTLISTANCHOR ProbeHead; +} VTGPROVIDER; +typedef VTGPROVIDER *PVTGPROVIDER; + +/** + * A string table string. + */ +typedef struct VTGSTRING +{ + /** The string space core. */ + RTSTRSPACECORE Core; + /** The string table offset. */ + uint32_t offStrTab; + /** The actual string. */ + char szString[1]; +} VTGSTRING; +typedef VTGSTRING *PVTGSTRING; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** The string space organizing the string table strings. Each node is a VTGSTRING. */ +static RTSTRSPACE g_StrSpace = NULL; +/** Used by the string table enumerator to set VTGSTRING::offStrTab. */ +static uint32_t g_offStrTab; +/** List of providers created by the parser. */ +static RTLISTANCHOR g_ProviderHead; +/** The number of type errors. */ +static uint32_t g_cTypeErrors = 0; + + +/** @name Options + * @{ */ +static enum +{ + kVBoxTpGAction_Nothing, + kVBoxTpGAction_GenerateHeader, + kVBoxTpGAction_GenerateWrapperHeader, + kVBoxTpGAction_GenerateObject +} g_enmAction = kVBoxTpGAction_Nothing; +static uint32_t g_cBits = HC_ARCH_BITS; +static uint32_t g_cHostBits = HC_ARCH_BITS; +static uint32_t g_fTypeContext = VTG_TYPE_CTX_R0; +static const char *g_pszContextDefine = "IN_RING0"; +static const char *g_pszContextDefine2 = NULL; +static bool g_fApplyCpp = false; +static uint32_t g_cVerbosity = 0; +static const char *g_pszOutput = NULL; +static const char *g_pszScript = NULL; +static const char *g_pszTempAsm = NULL; +#ifdef RT_OS_DARWIN +static const char *g_pszAssembler = "yasm"; +static const char *g_pszAssemblerFmtOpt = "-f"; +static const char g_szAssemblerFmtVal32[] = "macho32"; +static const char g_szAssemblerFmtVal64[] = "macho64"; +static const char g_szAssemblerOsDef[] = "RT_OS_DARWIN"; +#elif defined(RT_OS_OS2) +static const char *g_pszAssembler = "nasm.exe"; +static const char *g_pszAssemblerFmtOpt = "-f"; +static const char g_szAssemblerFmtVal32[] = "obj"; +static const char g_szAssemblerFmtVal64[] = "elf64"; +static const char g_szAssemblerOsDef[] = "RT_OS_OS2"; +#elif defined(RT_OS_WINDOWS) +static const char *g_pszAssembler = "yasm.exe"; +static const char *g_pszAssemblerFmtOpt = "-f"; +static const char g_szAssemblerFmtVal32[] = "win32"; +static const char g_szAssemblerFmtVal64[] = "win64"; +static const char g_szAssemblerOsDef[] = "RT_OS_WINDOWS"; +#else +static const char *g_pszAssembler = "yasm"; +static const char *g_pszAssemblerFmtOpt = "-f"; +static const char g_szAssemblerFmtVal32[] = "elf32"; +static const char g_szAssemblerFmtVal64[] = "elf64"; +# ifdef RT_OS_FREEBSD +static const char g_szAssemblerOsDef[] = "RT_OS_FREEBSD"; +# elif defined(RT_OS_NETBSD) +static const char g_szAssemblerOsDef[] = "RT_OS_NETBSD"; +# elif defined(RT_OS_OPENBSD) +static const char g_szAssemblerOsDef[] = "RT_OS_OPENBSD"; +# elif defined(RT_OS_LINUX) +static const char g_szAssemblerOsDef[] = "RT_OS_LINUX"; +# elif defined(RT_OS_SOLARIS) +static const char g_szAssemblerOsDef[] = "RT_OS_SOLARIS"; +# else +# error "Port me!" +# endif +#endif +static const char *g_pszAssemblerFmtVal = RT_CONCAT(g_szAssemblerFmtVal, HC_ARCH_BITS); +static const char *g_pszAssemblerDefOpt = "-D"; +static const char *g_pszAssemblerIncOpt = "-I"; +static char g_szAssemblerIncVal[RTPATH_MAX]; +static const char *g_pszAssemblerIncVal = __FILE__ "/../../../include/"; +static const char *g_pszAssemblerOutputOpt = "-o"; +static unsigned g_cAssemblerOptions = 0; +static const char *g_apszAssemblerOptions[32]; +static const char *g_pszProbeFnName = "SUPR0TracerFireProbe"; +static bool g_fProbeFnImported = true; +static bool g_fPic = false; +/** @} */ + + + + +/** + * Inserts a string into the string table, reusing any matching existing string + * if possible. + * + * @returns Read only string. + * @param pch The string to insert (need not be terminated). + * @param cch The length of the string. + */ +static const char *strtabInsertN(const char *pch, size_t cch) +{ + PVTGSTRING pStr = (PVTGSTRING)RTStrSpaceGetN(&g_StrSpace, pch, cch); + if (pStr) + return pStr->szString; + + /* + * Create a new entry. + */ + pStr = (PVTGSTRING)RTMemAlloc(RT_UOFFSETOF_DYN(VTGSTRING, szString[cch + 1])); + if (!pStr) + return NULL; + + pStr->Core.pszString = pStr->szString; + memcpy(pStr->szString, pch, cch); + pStr->szString[cch] = '\0'; + pStr->offStrTab = UINT32_MAX; + + bool fRc = RTStrSpaceInsert(&g_StrSpace, &pStr->Core); + Assert(fRc); NOREF(fRc); + return pStr->szString; +} + + +/** + * Retrieves the string table offset of the given string table string. + * + * @returns String table offset. + * @param pszStrTabString The string table string. + */ +static uint32_t strtabGetOff(const char *pszStrTabString) +{ + PVTGSTRING pStr = RT_FROM_MEMBER(pszStrTabString, VTGSTRING, szString[0]); + Assert(pStr->Core.pszString == pszStrTabString); + return pStr->offStrTab; +} + + +/** + * Invokes the assembler. + * + * @returns Exit code. + * @param pszOutput The output file. + * @param pszTempAsm The source file. + */ +static RTEXITCODE generateInvokeAssembler(const char *pszOutput, const char *pszTempAsm) +{ + const char *apszArgs[64]; + unsigned iArg = 0; + + apszArgs[iArg++] = g_pszAssembler; + apszArgs[iArg++] = g_pszAssemblerFmtOpt; + apszArgs[iArg++] = g_pszAssemblerFmtVal; + apszArgs[iArg++] = g_pszAssemblerDefOpt; + if (!strcmp(g_pszAssemblerFmtVal, "macho32") || !strcmp(g_pszAssemblerFmtVal, "macho64")) + apszArgs[iArg++] = "ASM_FORMAT_MACHO"; + else if (!strcmp(g_pszAssemblerFmtVal, "obj") || !strcmp(g_pszAssemblerFmtVal, "omf")) + apszArgs[iArg++] = "ASM_FORMAT_OMF"; + else if ( !strcmp(g_pszAssemblerFmtVal, "win32") + || !strcmp(g_pszAssemblerFmtVal, "win64") + || !strcmp(g_pszAssemblerFmtVal, "pe32") + || !strcmp(g_pszAssemblerFmtVal, "pe64") + || !strcmp(g_pszAssemblerFmtVal, "pe") ) + apszArgs[iArg++] = "ASM_FORMAT_PE"; + else if ( !strcmp(g_pszAssemblerFmtVal, "elf32") + || !strcmp(g_pszAssemblerFmtVal, "elf64") + || !strcmp(g_pszAssemblerFmtVal, "elf")) + apszArgs[iArg++] = "ASM_FORMAT_ELF"; + else + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Unknown assembler format '%s'", g_pszAssemblerFmtVal); + apszArgs[iArg++] = g_pszAssemblerDefOpt; + if (g_cBits == 32) + apszArgs[iArg++] = "ARCH_BITS=32"; + else + apszArgs[iArg++] = "ARCH_BITS=64"; + apszArgs[iArg++] = g_pszAssemblerDefOpt; + if (g_cHostBits == 32) + apszArgs[iArg++] = "HC_ARCH_BITS=32"; + else + apszArgs[iArg++] = "HC_ARCH_BITS=64"; + apszArgs[iArg++] = g_pszAssemblerDefOpt; + if (g_cBits == 32) + apszArgs[iArg++] = "RT_ARCH_X86"; + else + apszArgs[iArg++] = "RT_ARCH_AMD64"; + apszArgs[iArg++] = g_pszAssemblerDefOpt; + apszArgs[iArg++] = g_pszContextDefine; + if (g_pszContextDefine2) + { + apszArgs[iArg++] = g_pszAssemblerDefOpt; + apszArgs[iArg++] = g_pszContextDefine2; + } + if (g_szAssemblerOsDef[0]) + { + apszArgs[iArg++] = g_pszAssemblerDefOpt; + apszArgs[iArg++] = g_szAssemblerOsDef; + } + apszArgs[iArg++] = g_pszAssemblerIncOpt; + apszArgs[iArg++] = g_pszAssemblerIncVal; + apszArgs[iArg++] = g_pszAssemblerOutputOpt; + apszArgs[iArg++] = pszOutput; + for (unsigned i = 0; i < g_cAssemblerOptions; i++) + apszArgs[iArg++] = g_apszAssemblerOptions[i]; + apszArgs[iArg++] = pszTempAsm; + apszArgs[iArg] = NULL; + Assert(iArg <= RT_ELEMENTS(apszArgs)); + + if (g_cVerbosity > 1) + { + RTMsgInfo("Starting assmbler '%s' with arguments:\n", g_pszAssembler); + for (unsigned i = 0; i < iArg; i++) + RTMsgInfo(" #%02u: '%s'\n", i, apszArgs[i]); + } + + RTPROCESS hProc; + int rc = RTProcCreate(apszArgs[0], apszArgs, RTENV_DEFAULT, RTPROC_FLAGS_SEARCH_PATH, &hProc); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to start '%s' (assembler): %Rrc", apszArgs[0], rc); + + RTPROCSTATUS Status; + rc = RTProcWait(hProc, RTPROCWAIT_FLAGS_BLOCK, &Status); + if (RT_FAILURE(rc)) + { + RTProcTerminate(hProc); + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTProcWait failed: %Rrc", rc); + } + if (Status.enmReason == RTPROCEXITREASON_SIGNAL) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "The assembler failed: signal %d", Status.iStatus); + if (Status.enmReason != RTPROCEXITREASON_NORMAL) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "The assembler failed: abend"); + if (Status.iStatus != 0) + return RTMsgErrorExit((RTEXITCODE)Status.iStatus, "The assembler failed: exit code %d", Status.iStatus); + + return RTEXITCODE_SUCCESS; +} + + +/** + * Worker that does the boring bits when generating a file. + * + * @returns Exit code. + * @param pszOutput The name of the output file. + * @param pszWhat What kind of file it is. + * @param pfnGenerator The callback function that provides the contents + * of the file. + */ +static RTEXITCODE generateFile(const char *pszOutput, const char *pszWhat, + RTEXITCODE (*pfnGenerator)(PSCMSTREAM)) +{ + SCMSTREAM Strm; + int rc = ScmStreamInitForWriting(&Strm, NULL); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "ScmStreamInitForWriting returned %Rrc when generating the %s file", + rc, pszWhat); + + RTEXITCODE rcExit = pfnGenerator(&Strm); + if (RT_FAILURE(ScmStreamGetStatus(&Strm))) + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Stream error %Rrc generating the %s file", + ScmStreamGetStatus(&Strm), pszWhat); + if (rcExit == RTEXITCODE_SUCCESS) + { + rc = ScmStreamWriteToFile(&Strm, "%s", pszOutput); + if (RT_FAILURE(rc)) + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "ScmStreamWriteToFile returned %Rrc when writing '%s' (%s)", + rc, pszOutput, pszWhat); + if (rcExit == RTEXITCODE_SUCCESS) + { + if (g_cVerbosity > 0) + RTMsgInfo("Successfully generated '%s'.", pszOutput); + if (g_cVerbosity > 1) + { + RTMsgInfo("================ %s - start ================", pszWhat); + ScmStreamRewindForReading(&Strm); + const char *pszLine; + size_t cchLine; + SCMEOL enmEol; + while ((pszLine = ScmStreamGetLine(&Strm, &cchLine, &enmEol)) != NULL) + RTPrintf("%.*s\n", cchLine, pszLine); + RTMsgInfo("================ %s - end ================", pszWhat); + } + } + } + ScmStreamDelete(&Strm); + return rcExit; +} + + +/** + * @callback_method_impl{FNRTSTRSPACECALLBACK, Writes the string table strings.} + */ +static DECLCALLBACK(int) generateAssemblyStrTabCallback(PRTSTRSPACECORE pStr, void *pvUser) +{ + PVTGSTRING pVtgStr = (PVTGSTRING)pStr; + PSCMSTREAM pStrm = (PSCMSTREAM)pvUser; + + pVtgStr->offStrTab = g_offStrTab; + g_offStrTab += (uint32_t)pVtgStr->Core.cchString + 1; + + ScmStreamPrintf(pStrm, + " db '%s', 0 ; off=%u len=%zu\n", + pVtgStr->szString, pVtgStr->offStrTab, pVtgStr->Core.cchString); + return VINF_SUCCESS; +} + + +/** + * Generate assembly source that can be turned into an object file. + * + * (This is a generateFile callback.) + * + * @returns Exit code. + * @param pStrm The output stream. + */ +static RTEXITCODE generateAssembly(PSCMSTREAM pStrm) +{ + PVTGPROVIDER pProvider; + PVTGPROBE pProbe; + PVTGARG pArg; + + + if (g_cVerbosity > 0) + RTMsgInfo("Generating assembly code..."); + + /* + * Write the file header. + */ + ScmStreamPrintf(pStrm, + "; $Id: VBoxTpG.cpp $ \n" + ";; @file\n" + "; Automatically generated from %s. Do NOT edit!\n" + ";\n" + "\n" + "%%include \"iprt/asmdefs.mac\"\n" + "\n" + "\n" + ";" + "; We put all the data in a dedicated section / segment.\n" + ";\n" + "; In order to find the probe location specifiers, we do the necessary\n" + "; trickery here, ASSUMING that this object comes in first in the link\n" + "; editing process.\n" + ";\n" + "%%ifdef ASM_FORMAT_OMF\n" + " %%macro VTG_GLOBAL 2\n" + " global NAME(%%1)\n" + " NAME(%%1):\n" + " %%endmacro\n" + " segment VTG.Obj public CLASS=VTG align=4096 use32\n" + "\n" + "%%elifdef ASM_FORMAT_MACHO\n" + " %%macro VTG_GLOBAL 2\n" + " global NAME(%%1)\n" + " NAME(%%1):\n" + " %%endmacro\n" + " %%ifdef IN_RING3\n" + " %%define VTG_NEW_MACHO_LINKER\n" + " %%elif ARCH_BITS == 64\n" + " %%define VTG_NEW_MACHO_LINKER\n" + " %%elifdef IN_RING0_AGNOSTIC\n" + " %%define VTG_NEW_MACHO_LINKER\n" + " %%endif\n" + " %%ifdef VTG_NEW_MACHO_LINKER\n" + " ; Section order hack!\n" + " ; With the ld64-97.17 linker there was a problem with it determining the section\n" + " ; order based on symbol references. The references to the start and end of the\n" + " ; __VTGPrLc section forced it in front of __VTGObj, we want __VTGObj first.\n" + " extern section$start$__VTG$__VTGObj\n" + " extern section$end$__VTG$__VTGObj\n" + " %%else\n" + " ; Creating 32-bit kext of the type MH_OBJECT. No fancy section end/start symbols handy.\n" + " [section __VTG __VTGObj align=16]\n" + "VTG_GLOBAL g_aVTGObj_LinkerPleaseNoticeMe, data\n" + " [section __VTG __VTGPrLc.Begin align=16]\n" + " dq 0, 0 ; Paranoia, related to the fudge below.\n" + "VTG_GLOBAL g_aVTGPrLc, data\n" + " [section __VTG __VTGPrLc align=16]\n" + "VTG_GLOBAL g_aVTGPrLc_LinkerPleaseNoticeMe, data\n" + " [section __VTG __VTGPrLc.End align=16]\n" + "VTG_GLOBAL g_aVTGPrLc_End, data\n" + " dq 0, 0 ; Fudge to work around unidentified linker where it would otherwise generate\n" + " ; a fix up of the first dword in __VTGPrLc.Begin despite the fact that it were\n" + " ; an empty section with nothing whatsoever to fix up.\n" + " %%endif\n" + " [section __VTG __VTGObj]\n" + "\n" + "%%elifdef ASM_FORMAT_PE\n" + " %%macro VTG_GLOBAL 2\n" + " global NAME(%%1)\n" + " NAME(%%1):\n" + " %%endmacro\n" + " [section VTGPrLc.Begin data align=64]\n" + /*" times 16 db 0xcc\n"*/ + "VTG_GLOBAL g_aVTGPrLc, data\n" + " [section VTGPrLc.Data data align=4]\n" + " [section VTGPrLc.End data align=4]\n" + "VTG_GLOBAL g_aVTGPrLc_End, data\n" + /*" times 16 db 0xcc\n"*/ + " [section VTGObj data align=32]\n" + "\n" + "%%elifdef ASM_FORMAT_ELF\n" + " %%macro VTG_GLOBAL 2\n" + " global NAME(%%1):%%2 hidden\n" + " NAME(%%1):\n" + " %%endmacro\n" + " [section .VTGData progbits alloc noexec write align=4096]\n" + " [section .VTGPrLc.Begin progbits alloc noexec write align=32]\n" + " dd 0,0,0,0, 0,0,0,0\n" + "VTG_GLOBAL g_aVTGPrLc, data\n" + " [section .VTGPrLc progbits alloc noexec write align=1]\n" + " [section .VTGPrLc.End progbits alloc noexec write align=1]\n" + "VTG_GLOBAL g_aVTGPrLc_End, data\n" + " dd 0,0,0,0, 0,0,0,0\n" + " [section .VTGData]\n" + "\n" + "%%else\n" + " %%error \"ASM_FORMAT_XXX is not defined\"\n" + "%%endif\n" + "\n" + "\n" + "VTG_GLOBAL g_VTGObjHeader, data\n" + " ;0 1 2 3\n" + " ;012345678901234567890123456789012\n" + " db 'VTG Object Header v1.7', 0, 0\n" + " dd %u\n" + " dd NAME(g_acVTGProbeEnabled_End) - NAME(g_VTGObjHeader)\n" + " dd NAME(g_achVTGStringTable) - NAME(g_VTGObjHeader)\n" + " dd NAME(g_achVTGStringTable_End) - NAME(g_achVTGStringTable)\n" + " dd NAME(g_aVTGArgLists) - NAME(g_VTGObjHeader)\n" + " dd NAME(g_aVTGArgLists_End) - NAME(g_aVTGArgLists)\n" + " dd NAME(g_aVTGProbes) - NAME(g_VTGObjHeader)\n" + " dd NAME(g_aVTGProbes_End) - NAME(g_aVTGProbes)\n" + " dd NAME(g_aVTGProviders) - NAME(g_VTGObjHeader)\n" + " dd NAME(g_aVTGProviders_End) - NAME(g_aVTGProviders)\n" + " dd NAME(g_acVTGProbeEnabled) - NAME(g_VTGObjHeader)\n" + " dd NAME(g_acVTGProbeEnabled_End) - NAME(g_acVTGProbeEnabled)\n" + " dd 0\n" + " dd 0\n" + "%%ifdef VTG_NEW_MACHO_LINKER\n" + " extern section$start$__VTG$__VTGPrLc\n" + " RTCCPTR_DEF section$start$__VTG$__VTGPrLc\n" + " %%if ARCH_BITS == 32\n" + " dd 0\n" + " %%endif\n" + " extern section$end$__VTG$__VTGPrLc\n" + " RTCCPTR_DEF section$end$__VTG$__VTGPrLc\n" + " %%if ARCH_BITS == 32\n" + " dd 0\n" + " %%endif\n" + "%%else\n" + " RTCCPTR_DEF NAME(g_aVTGPrLc)\n" + " %%if ARCH_BITS == 32\n" + " dd 0\n" + " %%endif\n" + " RTCCPTR_DEF NAME(g_aVTGPrLc_End)\n" + " %%if ARCH_BITS == 32\n" + " dd 0\n" + " %%endif\n" + "%%endif\n" + , + g_pszScript, g_cBits); + RTUUID Uuid; + int rc = RTUuidCreate(&Uuid); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTUuidCreate failed: %Rrc", rc); + ScmStreamPrintf(pStrm, + " dd 0%08xh, 0%08xh, 0%08xh, 0%08xh\n" + "%%ifdef VTG_NEW_MACHO_LINKER\n" + " RTCCPTR_DEF section$start$__VTG$__VTGObj\n" + " %%if ARCH_BITS == 32\n" + " dd 0\n" + " %%endif\n" + "%%else\n" + " dd 0, 0\n" + "%%endif\n" + " dd 0, 0\n" + , Uuid.au32[0], Uuid.au32[1], Uuid.au32[2], Uuid.au32[3]); + + /* + * Dump the string table before we start using the strings. + */ + ScmStreamPrintf(pStrm, + "\n" + ";\n" + "; The string table.\n" + ";\n" + "VTG_GLOBAL g_achVTGStringTable, data\n"); + g_offStrTab = 0; + RTStrSpaceEnumerate(&g_StrSpace, generateAssemblyStrTabCallback, pStrm); + ScmStreamPrintf(pStrm, + "VTG_GLOBAL g_achVTGStringTable_End, data\n"); + + /* + * Write out the argument lists before we use them. + */ + ScmStreamPrintf(pStrm, + "\n" + ";\n" + "; The argument lists.\n" + ";\n" + "ALIGNDATA(16)\n" + "VTG_GLOBAL g_aVTGArgLists, data\n"); + uint32_t off = 0; + RTListForEach(&g_ProviderHead, pProvider, VTGPROVIDER, ListEntry) + { + RTListForEach(&pProvider->ProbeHead, pProbe, VTGPROBE, ListEntry) + { + if (pProbe->offArgList != UINT32_MAX) + continue; + + /* Write it. */ + pProbe->offArgList = off; + ScmStreamPrintf(pStrm, + " ; off=%u\n" + " db %2u ; Argument count\n" + " db %u ; fHaveLargeArgs\n" + " db 0, 0 ; Reserved\n" + , off, pProbe->cArgs, (int)pProbe->fHaveLargeArgs); + off += 4; + RTListForEach(&pProbe->ArgHead, pArg, VTGARG, ListEntry) + { + ScmStreamPrintf(pStrm, + " dd %8u ; type '%s' (name '%s')\n" + " dd 0%08xh ; type flags\n", + strtabGetOff(pArg->pszTracerType), pArg->pszTracerType, pArg->pszName, + pArg->fType); + off += 8; + } + + /* Look for matching argument lists (lazy bird walks the whole list). */ + PVTGPROVIDER pProv2; + RTListForEach(&g_ProviderHead, pProv2, VTGPROVIDER, ListEntry) + { + PVTGPROBE pProbe2; + RTListForEach(&pProvider->ProbeHead, pProbe2, VTGPROBE, ListEntry) + { + if (pProbe2->offArgList != UINT32_MAX) + continue; + if (pProbe2->cArgs != pProbe->cArgs) + continue; + + PVTGARG pArg2; + pArg = RTListNodeGetNext(&pProbe->ArgHead, VTGARG, ListEntry); + pArg2 = RTListNodeGetNext(&pProbe2->ArgHead, VTGARG, ListEntry); + int32_t cArgs = pProbe->cArgs; + while ( cArgs-- > 0 + && pArg2->pszTracerType == pArg->pszTracerType + && pArg2->fType == pArg->fType) + { + pArg = RTListNodeGetNext(&pArg->ListEntry, VTGARG, ListEntry); + pArg2 = RTListNodeGetNext(&pArg2->ListEntry, VTGARG, ListEntry); + } + if (cArgs >= 0) + continue; + pProbe2->offArgList = pProbe->offArgList; + } + } + } + } + ScmStreamPrintf(pStrm, + "VTG_GLOBAL g_aVTGArgLists_End, data\n"); + + + /* + * Probe definitions. + */ + ScmStreamPrintf(pStrm, + "\n" + ";\n" + "; Prob definitions.\n" + ";\n" + "ALIGNDATA(16)\n" + "VTG_GLOBAL g_aVTGProbes, data\n" + "\n"); + uint32_t iProvider = 0; + uint32_t iProbe = 0; + RTListForEach(&g_ProviderHead, pProvider, VTGPROVIDER, ListEntry) + { + pProvider->iFirstProbe = iProbe; + RTListForEach(&pProvider->ProbeHead, pProbe, VTGPROBE, ListEntry) + { + ScmStreamPrintf(pStrm, + "VTG_GLOBAL g_VTGProbeData_%s_%s, data ; idx=#%4u\n" + " dd %6u ; offName\n" + " dd %6u ; offArgList\n" + " dw (NAME(g_cVTGProbeEnabled_%s_%s) - NAME(g_acVTGProbeEnabled)) / 4 ; idxEnabled\n" + " dw %6u ; idxProvider\n" + " dd NAME(g_VTGObjHeader) - NAME(g_VTGProbeData_%s_%s) ; offObjHdr\n" + , + pProvider->pszName, pProbe->pszMangledName, iProbe, + strtabGetOff(pProbe->pszUnmangledName), + pProbe->offArgList, + pProvider->pszName, pProbe->pszMangledName, + iProvider, + pProvider->pszName, pProbe->pszMangledName + ); + pProbe->iProbe = iProbe; + iProbe++; + } + pProvider->cProbes = iProbe - pProvider->iFirstProbe; + iProvider++; + } + ScmStreamPrintf(pStrm, "VTG_GLOBAL g_aVTGProbes_End, data\n"); + + /* + * The provider data. + */ + ScmStreamPrintf(pStrm, + "\n" + ";\n" + "; Provider data.\n" + ";\n" + "ALIGNDATA(16)\n" + "VTG_GLOBAL g_aVTGProviders, data\n"); + iProvider = 0; + RTListForEach(&g_ProviderHead, pProvider, VTGPROVIDER, ListEntry) + { + ScmStreamPrintf(pStrm, + " ; idx=#%4u - %s\n" + " dd %6u ; name\n" + " dw %6u ; index of first probe\n" + " dw %6u ; count of probes\n" + " db %d, %d, %d ; AttrSelf\n" + " db %d, %d, %d ; AttrModules\n" + " db %d, %d, %d ; AttrFunctions\n" + " db %d, %d, %d ; AttrName\n" + " db %d, %d, %d ; AttrArguments\n" + " db 0 ; reserved\n" + "VTG_GLOBAL g_cVTGProviderProbesEnabled_%s, data\n" + " dd 0\n" + "VTG_GLOBAL g_cVTGProviderSettingsSeqNo_%s, data\n" + " dd 0\n" + , + iProvider, pProvider->pszName, + strtabGetOff(pProvider->pszName), + pProvider->iFirstProbe, + pProvider->cProbes, + pProvider->AttrSelf.enmCode, pProvider->AttrSelf.enmData, pProvider->AttrSelf.enmDataDep, + pProvider->AttrModules.enmCode, pProvider->AttrModules.enmData, pProvider->AttrModules.enmDataDep, + pProvider->AttrFunctions.enmCode, pProvider->AttrFunctions.enmData, pProvider->AttrFunctions.enmDataDep, + pProvider->AttrName.enmCode, pProvider->AttrName.enmData, pProvider->AttrName.enmDataDep, + pProvider->AttrArguments.enmCode, pProvider->AttrArguments.enmData, pProvider->AttrArguments.enmDataDep, + pProvider->pszName, + pProvider->pszName); + iProvider++; + } + ScmStreamPrintf(pStrm, "VTG_GLOBAL g_aVTGProviders_End, data\n"); + + /* + * Declare the probe enable flags. + * + * These must be placed at the end so they'll end up adjacent to the probe + * locations. This is important for reducing the amount of memory we need + * to lock down for user mode modules. + */ + ScmStreamPrintf(pStrm, + ";\n" + "; Probe enabled flags.\n" + ";\n" + "ALIGNDATA(16)\n" + "VTG_GLOBAL g_acVTGProbeEnabled, data\n" + ); + uint32_t cProbes = 0; + RTListForEach(&g_ProviderHead, pProvider, VTGPROVIDER, ListEntry) + { + RTListForEach(&pProvider->ProbeHead, pProbe, VTGPROBE, ListEntry) + { + ScmStreamPrintf(pStrm, + "VTG_GLOBAL g_cVTGProbeEnabled_%s_%s, data\n" + " dd 0\n", + pProvider->pszName, pProbe->pszMangledName); + cProbes++; + } + } + ScmStreamPrintf(pStrm, "VTG_GLOBAL g_acVTGProbeEnabled_End, data\n"); + if (cProbes >= _32K) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Too many probes: %u (max %u)", cProbes, _32K - 1); + + + /* + * Emit code for the stub functions. + */ + bool const fWin64 = g_cBits == 64 && (!strcmp(g_pszAssemblerFmtVal, "win64") || !strcmp(g_pszAssemblerFmtVal, "pe64")); + bool const fElf = !strcmp(g_pszAssemblerFmtVal, "elf32") || !strcmp(g_pszAssemblerFmtVal, "elf64"); + ScmStreamPrintf(pStrm, + "\n" + ";\n" + "; Prob stubs.\n" + ";\n" + "BEGINCODE\n" + ); + if (g_fProbeFnImported) + ScmStreamPrintf(pStrm, + "EXTERN_IMP2 %s\n" + "BEGINCODE ; EXTERN_IMP2 changes section\n", + g_pszProbeFnName); + else + ScmStreamPrintf(pStrm, "extern NAME(%s)\n", g_pszProbeFnName); + + RTListForEach(&g_ProviderHead, pProvider, VTGPROVIDER, ListEntry) + { + RTListForEach(&pProvider->ProbeHead, pProbe, VTGPROBE, ListEntry) + { + ScmStreamPrintf(pStrm, + "\n" + "VTG_GLOBAL VTGProbeStub_%s_%s, function; (VBOXTPGPROBELOC pVTGProbeLoc", + pProvider->pszName, pProbe->pszMangledName); + RTListForEach(&pProbe->ArgHead, pArg, VTGARG, ListEntry) + { + ScmStreamPrintf(pStrm, ", %s %s", pArg->pszTracerType, pArg->pszName); + } + ScmStreamPrintf(pStrm, + ");\n"); + + /* + * Check if the probe in question is enabled. + */ + if (g_cBits == 32) + ScmStreamPrintf(pStrm, + " mov eax, [esp + 4]\n" + " test byte [eax+3], 0x80 ; fEnabled == true?\n" + " jz .return ; jump on false\n"); + else if (fWin64) + ScmStreamPrintf(pStrm, + " test byte [rcx+3], 0x80 ; fEnabled == true?\n" + " jz .return ; jump on false\n"); + else + ScmStreamPrintf(pStrm, + " test byte [rdi+3], 0x80 ; fEnabled == true?\n" + " jz .return ; jump on false\n"); + + /* + * Jump to the fire-probe function. + */ + if (g_cBits == 32) + ScmStreamPrintf(pStrm, g_fPic && fElf ? + " jmp %s wrt ..plt\n" + : g_fProbeFnImported ? + " mov ecx, IMP2(%s)\n" + " jmp ecx\n" + : + " jmp NAME(%s)\n" + , g_pszProbeFnName); + else + ScmStreamPrintf(pStrm, g_fPic && fElf ? + " jmp [rel %s wrt ..got]\n" + : g_fProbeFnImported ? + " jmp IMP2(%s)\n" + : + " jmp NAME(%s)\n" + , g_pszProbeFnName); + + ScmStreamPrintf(pStrm, + ".return:\n" + " ret ; The probe was disabled, return\n" + "\n"); + } + } + + return RTEXITCODE_SUCCESS; +} + + +static RTEXITCODE generateObject(const char *pszOutput, const char *pszTempAsm) +{ + if (!pszTempAsm) + { + size_t cch = strlen(pszOutput); + char *psz = (char *)alloca(cch + sizeof(".asm")); + memcpy(psz, pszOutput, cch); + memcpy(psz + cch, ".asm", sizeof(".asm")); + pszTempAsm = psz; + } + + RTEXITCODE rcExit = generateFile(pszTempAsm, "assembly", generateAssembly); + if (rcExit == RTEXITCODE_SUCCESS) + rcExit = generateInvokeAssembler(pszOutput, pszTempAsm); + RTFileDelete(pszTempAsm); + return rcExit; +} + + +static RTEXITCODE generateProbeDefineName(char *pszBuf, size_t cbBuf, const char *pszProvider, const char *pszProbe) +{ + size_t cbMax = strlen(pszProvider) + 1 + strlen(pszProbe) + 1; + if (cbMax > cbBuf || cbMax > 80) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Probe '%s' in provider '%s' ends up with a too long defined\n", pszProbe, pszProvider); + + while (*pszProvider) + *pszBuf++ = RT_C_TO_UPPER(*pszProvider++); + + *pszBuf++ = '_'; + + while (*pszProbe) + { + if (pszProbe[0] == '_' && pszProbe[1] == '_') + pszProbe++; + *pszBuf++ = RT_C_TO_UPPER(*pszProbe++); + } + + *pszBuf = '\0'; + return RTEXITCODE_SUCCESS; +} + + +static RTEXITCODE generateProviderDefineName(char *pszBuf, size_t cbBuf, const char *pszProvider) +{ + size_t cbMax = strlen(pszProvider) + 1; + if (cbMax > cbBuf || cbMax > 80) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Provider '%s' ends up with a too long defined\n", pszProvider); + + while (*pszProvider) + *pszBuf++ = RT_C_TO_UPPER(*pszProvider++); + + *pszBuf = '\0'; + return RTEXITCODE_SUCCESS; +} + + +/** + * Called via generateFile to generate the header file. + * + * @returns Exit code status. + * @param pStrm The output stream. + */ +static RTEXITCODE generateHeader(PSCMSTREAM pStrm) +{ + /* + * Calc the double inclusion blocker define and then write the file header. + */ + char szTmp[4096]; + const char *pszName = RTPathFilename(g_pszScript); + size_t cchName = strlen(pszName); + if (cchName >= sizeof(szTmp) - 64) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "File name is too long '%s'", pszName); + szTmp[0] = '_'; + szTmp[1] = '_'; + szTmp[2] = '_'; + memcpy(&szTmp[3], pszName, cchName); + szTmp[3 + cchName + 0] = '_'; + szTmp[3 + cchName + 1] = '_'; + szTmp[3 + cchName + 2] = '_'; + szTmp[3 + cchName + 3] = '\0'; + char *psz = &szTmp[3]; + while (*psz) + { + if (!RT_C_IS_ALNUM(*psz) && *psz != '_') + *psz = '_'; + psz++; + } + + ScmStreamPrintf(pStrm, + "/* $Id: VBoxTpG.cpp $ */\n" + "/** @file\n" + " * Automatically generated from %s. Do NOT edit!\n" + " */\n" + "\n" + "#ifndef %s\n" + "#define %s\n" + "#ifndef RT_WITHOUT_PRAGMA_ONCE\n" + "# pragma once\n" + "#endif\n" + "\n" + "#include <VBox/VBoxTpG.h>\n" + "\n" + "#ifndef %s\n" + "# error \"Expected '%s' to be defined\"\n" + "#endif\n" + "\n" + "RT_C_DECLS_BEGIN\n" + "\n" + "#ifdef VBOX_WITH_DTRACE\n" + "\n" + "# ifdef _MSC_VER\n" + "# pragma data_seg(VTG_LOC_SECT)\n" + "# pragma data_seg()\n" + "# endif\n" + "\n" + , + g_pszScript, + szTmp, + szTmp, + g_pszContextDefine, + g_pszContextDefine); + + /* + * Declare data, code and macros for each probe. + */ + PVTGPROVIDER pProv; + PVTGPROBE pProbe; + PVTGARG pArg; + RTListForEach(&g_ProviderHead, pProv, VTGPROVIDER, ListEntry) + { + /* This macro is not available in ring-3 because we don't have + anything similar available for native dtrace. */ + ScmStreamPrintf(pStrm, "\n\n"); + if (g_fTypeContext != VTG_TYPE_CTX_R3) + { + generateProviderDefineName(szTmp, sizeof(szTmp), pProv->pszName); + ScmStreamPrintf(pStrm, + "extern uint32_t const volatile g_cVTGProviderProbesEnabled_%s;\n" + "# define %s_ANY_PROBES_ENABLED() \\\n" + " (RT_UNLIKELY(g_cVTGProviderProbesEnabled_%s != 0))\n" + "extern uint32_t const volatile g_cVTGProviderSettingsSeqNo_%s;\n" + "# define %s_GET_SETTINGS_SEQ_NO() (g_cVTGProviderSettingsSeqNo_%s)\n" + "\n", + pProv->pszName, + szTmp, pProv->pszName, + pProv->pszName, + szTmp, pProv->pszName); + } + + RTListForEach(&pProv->ProbeHead, pProbe, VTGPROBE, ListEntry) + { + ScmStreamPrintf(pStrm, + "extern uint32_t const volatile g_cVTGProbeEnabled_%s_%s;\n" + "extern VTGDESCPROBE g_VTGProbeData_%s_%s;\n" + "DECLASM(void) VTGProbeStub_%s_%s(PVTGPROBELOC", + pProv->pszName, pProbe->pszMangledName, + pProv->pszName, pProbe->pszMangledName, + pProv->pszName, pProbe->pszMangledName); + RTListForEach(&pProbe->ArgHead, pArg, VTGARG, ListEntry) + { + ScmStreamPrintf(pStrm, ", %s", pArg->pszCtxType); + } + generateProbeDefineName(szTmp, sizeof(szTmp), pProv->pszName, pProbe->pszMangledName); + ScmStreamPrintf(pStrm, + ");\n" + "# define %s_ENABLED() (RT_UNLIKELY(g_cVTGProbeEnabled_%s_%s != 0))\n" + "# define %s_ENABLED_RAW() (g_cVTGProbeEnabled_%s_%s)\n" + "# define %s(" + , + szTmp, pProv->pszName, pProbe->pszMangledName, + szTmp, pProv->pszName, pProbe->pszMangledName, + szTmp); + RTListForEach(&pProbe->ArgHead, pArg, VTGARG, ListEntry) + { + if (RTListNodeIsFirst(&pProbe->ArgHead, &pArg->ListEntry)) + ScmStreamPrintf(pStrm, "%s", pArg->pszName); + else + ScmStreamPrintf(pStrm, ", %s", pArg->pszName); + } + ScmStreamPrintf(pStrm, + ") \\\n" + " do { \\\n" + " if (RT_UNLIKELY(g_cVTGProbeEnabled_%s_%s)) \\\n" + " { \\\n" + " VTG_DECL_VTGPROBELOC(s_VTGProbeLoc) = \\\n" + " { __LINE__, 0, 0, __FUNCTION__, &g_VTGProbeData_%s_%s }; \\\n" + " VTGProbeStub_%s_%s(&s_VTGProbeLoc", + pProv->pszName, pProbe->pszMangledName, + pProv->pszName, pProbe->pszMangledName, + pProv->pszName, pProbe->pszMangledName); + RTListForEach(&pProbe->ArgHead, pArg, VTGARG, ListEntry) + { + ScmStreamPrintf(pStrm, pArg->pszArgPassingFmt, pArg->pszName); + } + ScmStreamPrintf(pStrm, + "); \\\n" + " } \\\n" + " { \\\n" ); + uint32_t iArg = 0; + RTListForEach(&pProbe->ArgHead, pArg, VTGARG, ListEntry) + { + if ((pArg->fType & (VTG_TYPE_FIXED_SIZED | VTG_TYPE_AUTO_CONV_PTR)) == VTG_TYPE_FIXED_SIZED) + ScmStreamPrintf(pStrm, + " AssertCompile(sizeof(%s) == %u); \\\n" + " AssertCompile(sizeof(%s) <= %u); \\\n", + pArg->pszTracerType, pArg->fType & VTG_TYPE_SIZE_MASK, + pArg->pszName, pArg->fType & VTG_TYPE_SIZE_MASK); + else if (pArg->fType & (VTG_TYPE_POINTER | VTG_TYPE_HC_ARCH_SIZED)) + ScmStreamPrintf(pStrm, + " AssertCompile(sizeof(%s) <= sizeof(uintptr_t)); \\\n" + " AssertCompile(sizeof(%s) <= sizeof(uintptr_t)); \\\n", + pArg->pszName, + pArg->pszTracerType); + iArg++; + } + ScmStreamPrintf(pStrm, + " } \\\n" + " } while (0)\n" + "\n"); + } + } + + ScmStreamPrintf(pStrm, + "\n" + "#else\n" + "\n"); + RTListForEach(&g_ProviderHead, pProv, VTGPROVIDER, ListEntry) + { + if (g_fTypeContext != VTG_TYPE_CTX_R3) + { + generateProviderDefineName(szTmp, sizeof(szTmp), pProv->pszName); + ScmStreamPrintf(pStrm, + "# define %s_ANY_PROBES_ENABLED() (false)\n" + "# define %s_GET_SETTINGS_SEQ_NO() UINT32_C(0)\n" + "\n", + szTmp, szTmp); + } + + RTListForEach(&pProv->ProbeHead, pProbe, VTGPROBE, ListEntry) + { + generateProbeDefineName(szTmp, sizeof(szTmp), pProv->pszName, pProbe->pszMangledName); + ScmStreamPrintf(pStrm, + "# define %s_ENABLED() (false)\n" + "# define %s_ENABLED_RAW() UINT32_C(0)\n" + "# define %s(" + , szTmp, szTmp, szTmp); + RTListForEach(&pProbe->ArgHead, pArg, VTGARG, ListEntry) + { + if (RTListNodeIsFirst(&pProbe->ArgHead, &pArg->ListEntry)) + ScmStreamPrintf(pStrm, "%s", pArg->pszName); + else + ScmStreamPrintf(pStrm, ", %s", pArg->pszName); + } + ScmStreamPrintf(pStrm, + ") do { } while (0)\n"); + } + } + + ScmStreamWrite(pStrm, RT_STR_TUPLE("\n" + "#endif\n" + "\n" + "RT_C_DECLS_END\n" + "#endif\n")); + return RTEXITCODE_SUCCESS; +} + + +/** + * Called via generateFile to generate the wrapper header file. + * + * @returns Exit code status. + * @param pStrm The output stream. + */ +static RTEXITCODE generateWrapperHeader(PSCMSTREAM pStrm) +{ + /* + * Calc the double inclusion blocker define and then write the file header. + */ + char szTmp[4096]; + const char *pszName = RTPathFilename(g_pszScript); + size_t cchName = strlen(pszName); + if (cchName >= sizeof(szTmp) - 64) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "File name is too long '%s'", pszName); + szTmp[0] = '_'; + szTmp[1] = '_'; + szTmp[2] = '_'; + memcpy(&szTmp[3], pszName, cchName); + strcpy(&szTmp[3 + cchName ], "___WRAPPER___"); + char *psz = &szTmp[3]; + while (*psz) + { + if (!RT_C_IS_ALNUM(*psz) && *psz != '_') + *psz = '_'; + psz++; + } + + ScmStreamPrintf(pStrm, + "/* $Id: VBoxTpG.cpp $ */\n" + "/** @file\n" + " * Automatically generated from %s. Do NOT edit!\n" + " */\n" + "\n" + "#ifndef %s\n" + "#define %s\n" + "\n" + "#include <VBox/VBoxTpG.h>\n" + "\n" + "#ifndef %s\n" + "# error \"Expected '%s' to be defined\"\n" + "#endif\n" + "\n" + "#ifdef VBOX_WITH_DTRACE\n" + "\n" + , + g_pszScript, + szTmp, + szTmp, + g_pszContextDefine, + g_pszContextDefine); + + /* + * Declare macros for each probe. + */ + PVTGPROVIDER pProv; + PVTGPROBE pProbe; + PVTGARG pArg; + RTListForEach(&g_ProviderHead, pProv, VTGPROVIDER, ListEntry) + { + RTListForEach(&pProv->ProbeHead, pProbe, VTGPROBE, ListEntry) + { + generateProbeDefineName(szTmp, sizeof(szTmp), pProv->pszName, pProbe->pszMangledName); + ScmStreamPrintf(pStrm, + "# define %s(" + , szTmp); + RTListForEach(&pProbe->ArgHead, pArg, VTGARG, ListEntry) + { + if (RTListNodeIsFirst(&pProbe->ArgHead, &pArg->ListEntry)) + ScmStreamPrintf(pStrm, "%s", pArg->pszName); + else + ScmStreamPrintf(pStrm, ", %s", pArg->pszName); + } + ScmStreamPrintf(pStrm, + ") \\\n" + " do { \\\n" + " if (RT_UNLIKELY(%s_ENABLED())) \\\n" + " { \\\n" + " %s_ORIGINAL(" + , szTmp, szTmp); + RTListForEach(&pProbe->ArgHead, pArg, VTGARG, ListEntry) + { + const char *pszFmt = pArg->pszArgPassingFmt; + if (pArg->fType & VTG_TYPE_AUTO_CONV_PTR) + { + /* Casting is required. ASSUMES sizeof(RTR0PTR) == sizeof(RTR3PTR) - safe! */ + pszFmt += sizeof(", ") - 1; + if (RTListNodeIsFirst(&pProbe->ArgHead, &pArg->ListEntry)) + ScmStreamPrintf(pStrm, "(%s)%M", pArg->pszTracerType, pszFmt, pArg->pszName); + else + ScmStreamPrintf(pStrm, ", (%s)%M", pArg->pszTracerType, pszFmt, pArg->pszName); + } + else if (pArg->fType & VTG_TYPE_CONST_CHAR_PTR) + { + /* Casting from 'const char *' (probe) to 'char *' (dtrace) is required to shut up warnings. */ + pszFmt += sizeof(", ") - 1; + if (RTListNodeIsFirst(&pProbe->ArgHead, &pArg->ListEntry)) + ScmStreamPrintf(pStrm, "(char *)%M", pszFmt, pArg->pszName); + else + ScmStreamPrintf(pStrm, ", (char *)%M", pszFmt, pArg->pszName); + } + else + { + if (RTListNodeIsFirst(&pProbe->ArgHead, &pArg->ListEntry)) + ScmStreamPrintf(pStrm, pArg->pszArgPassingFmt + sizeof(", ") - 1, pArg->pszName); + else + ScmStreamPrintf(pStrm, pArg->pszArgPassingFmt, pArg->pszName); + } + } + ScmStreamPrintf(pStrm, + "); \\\n" + " } \\\n" + " } while (0)\n" + "\n"); + } + } + + ScmStreamPrintf(pStrm, + "\n" + "#else\n" + "\n"); + RTListForEach(&g_ProviderHead, pProv, VTGPROVIDER, ListEntry) + { + RTListForEach(&pProv->ProbeHead, pProbe, VTGPROBE, ListEntry) + { + generateProbeDefineName(szTmp, sizeof(szTmp), pProv->pszName, pProbe->pszMangledName); + ScmStreamPrintf(pStrm, + "# define %s(" + , szTmp); + RTListForEach(&pProbe->ArgHead, pArg, VTGARG, ListEntry) + { + if (RTListNodeIsFirst(&pProbe->ArgHead, &pArg->ListEntry)) + ScmStreamPrintf(pStrm, "%s", pArg->pszName); + else + ScmStreamPrintf(pStrm, ", %s", pArg->pszName); + } + ScmStreamPrintf(pStrm, + ") do { } while (0)\n"); + } + } + + ScmStreamWrite(pStrm, RT_STR_TUPLE("\n" + "#endif\n" + "\n" + "#endif\n")); + return RTEXITCODE_SUCCESS; +} + + +/** + * Parser error with line and position. + * + * @returns RTEXITCODE_FAILURE. + * @param pStrm The stream. + * @param fAbs Absolute or relative offset. + * @param offSeek When @a fAbs is @c false, the offset from the current + * position to the point of failure. When @a fAbs is @c + * true, it's the absolute position. + * @param pszFormat The message format string. + * @param va Format arguments. + */ +static RTEXITCODE parseErrorExV(PSCMSTREAM pStrm, bool fAbs, size_t offSeek, const char *pszFormat, va_list va) +{ + if (fAbs) + ScmStreamSeekAbsolute(pStrm, offSeek); + else if (offSeek != 0) + ScmStreamSeekRelative(pStrm, -(ssize_t)offSeek); + size_t const off = ScmStreamTell(pStrm); + size_t const iLine = ScmStreamTellLine(pStrm); + ScmStreamSeekByLine(pStrm, iLine); + size_t const offLine = ScmStreamTell(pStrm); + + va_list va2; + va_copy(va2, va); + RTPrintf("%s:%d:%zd: error: %N.\n", g_pszScript, iLine + 1, off - offLine + 1, pszFormat, &va2); + va_end(va2); + + size_t cchLine; + SCMEOL enmEof; + const char *pszLine = ScmStreamGetLineByNo(pStrm, iLine, &cchLine, &enmEof); + if (pszLine) + RTPrintf(" %.*s\n" + " %*s^\n", + cchLine, pszLine, off - offLine, ""); + return RTEXITCODE_FAILURE; +} + + +/** + * Parser error with line and position. + * + * @returns RTEXITCODE_FAILURE. + * @param pStrm The stream. + * @param off The offset from the current position to the point of + * failure. + * @param pszFormat The message format string. + * @param ... Format arguments. + */ +static RTEXITCODE parseError(PSCMSTREAM pStrm, size_t off, const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + RTEXITCODE rcExit = parseErrorExV(pStrm, false, off, pszFormat, va); + va_end(va); + return rcExit; +} + + +/** + * Parser error with line and position, absolute version. + * + * @returns RTEXITCODE_FAILURE. + * @param pStrm The stream. + * @param off The offset from the current position to the point of + * failure. + * @param pszFormat The message format string. + * @param ... Format arguments. + */ +static RTEXITCODE parseErrorAbs(PSCMSTREAM pStrm, size_t off, const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + RTEXITCODE rcExit = parseErrorExV(pStrm, true /*fAbs*/, off, pszFormat, va); + va_end(va); + return rcExit; +} + + +/** + * Parser warning with line and position. + * + * @param pStrm The stream. + * @param fAbs Absolute or relative offset. + * @param offSeek When @a fAbs is @c false, the offset from the current + * position to the point of failure. When @a fAbs is @c + * true, it's the absolute position. + * @param pszFormat The message format string. + * @param va Format arguments. + */ +static void parseWarnExV(PSCMSTREAM pStrm, bool fAbs, size_t offSeek, const char *pszFormat, va_list va) +{ + /* Save the stream position. */ + size_t const offOrg = ScmStreamTell(pStrm); + + if (fAbs) + ScmStreamSeekAbsolute(pStrm, offSeek); + else if (offSeek != 0) + ScmStreamSeekRelative(pStrm, -(ssize_t)offSeek); + size_t const off = ScmStreamTell(pStrm); + size_t const iLine = ScmStreamTellLine(pStrm); + ScmStreamSeekByLine(pStrm, iLine); + size_t const offLine = ScmStreamTell(pStrm); + + va_list va2; + va_copy(va2, va); + RTPrintf("%s:%d:%zd: warning: %N.\n", g_pszScript, iLine + 1, off - offLine + 1, pszFormat, &va2); + va_end(va2); + + size_t cchLine; + SCMEOL enmEof; + const char *pszLine = ScmStreamGetLineByNo(pStrm, iLine, &cchLine, &enmEof); + if (pszLine) + RTPrintf(" %.*s\n" + " %*s^\n", + cchLine, pszLine, off - offLine, ""); + + /* restore the position. */ + int rc = ScmStreamSeekAbsolute(pStrm, offOrg); + AssertRC(rc); +} + +#if 0 /* unused */ +/** + * Parser warning with line and position. + * + * @param pStrm The stream. + * @param off The offset from the current position to the point of + * failure. + * @param pszFormat The message format string. + * @param ... Format arguments. + */ +static void parseWarn(PSCMSTREAM pStrm, size_t off, const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + parseWarnExV(pStrm, false, off, pszFormat, va); + va_end(va); +} +#endif /* unused */ + +/** + * Parser warning with line and position, absolute version. + * + * @param pStrm The stream. + * @param off The offset from the current position to the point of + * failure. + * @param pszFormat The message format string. + * @param ... Format arguments. + */ +static void parseWarnAbs(PSCMSTREAM pStrm, size_t off, const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + parseWarnExV(pStrm, true /*fAbs*/, off, pszFormat, va); + va_end(va); +} + + +/** + * Handles a C++ one line comment. + * + * @returns Exit code. + * @param pStrm The stream. + */ +static RTEXITCODE parseOneLineComment(PSCMSTREAM pStrm) +{ + ScmStreamSeekByLine(pStrm, ScmStreamTellLine(pStrm) + 1); + return RTEXITCODE_SUCCESS; +} + + +/** + * Handles a multi-line C/C++ comment. + * + * @returns Exit code. + * @param pStrm The stream. + */ +static RTEXITCODE parseMultiLineComment(PSCMSTREAM pStrm) +{ + unsigned ch; + while ((ch = ScmStreamGetCh(pStrm)) != ~(unsigned)0) + { + if (ch == '*') + { + do + ch = ScmStreamGetCh(pStrm); + while (ch == '*'); + if (ch == '/') + return RTEXITCODE_SUCCESS; + } + } + + parseError(pStrm, 1, "Expected end of comment, got end of file"); + return RTEXITCODE_FAILURE; +} + + +/** + * Skips spaces and comments. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE. + * @param pStrm The stream.. + */ +static RTEXITCODE parseSkipSpacesAndComments(PSCMSTREAM pStrm) +{ + unsigned ch; + while ((ch = ScmStreamPeekCh(pStrm)) != ~(unsigned)0) + { + if (!RT_C_IS_SPACE(ch) && ch != '/') + return RTEXITCODE_SUCCESS; + unsigned ch2 = ScmStreamGetCh(pStrm); AssertBreak(ch == ch2); NOREF(ch2); + if (ch == '/') + { + ch = ScmStreamGetCh(pStrm); + RTEXITCODE rcExit; + if (ch == '*') + rcExit = parseMultiLineComment(pStrm); + else if (ch == '/') + rcExit = parseOneLineComment(pStrm); + else + rcExit = parseError(pStrm, 2, "Unexpected character"); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + } + } + + return parseError(pStrm, 0, "Unexpected end of file"); +} + + +/** + * Skips spaces and comments, returning the next character. + * + * @returns Next non-space-non-comment character. ~(unsigned)0 on EOF or + * failure. + * @param pStrm The stream. + */ +static unsigned parseGetNextNonSpaceNonCommentCh(PSCMSTREAM pStrm) +{ + unsigned ch; + while ((ch = ScmStreamGetCh(pStrm)) != ~(unsigned)0) + { + if (!RT_C_IS_SPACE(ch) && ch != '/') + return ch; + if (ch == '/') + { + ch = ScmStreamGetCh(pStrm); + RTEXITCODE rcExit; + if (ch == '*') + rcExit = parseMultiLineComment(pStrm); + else if (ch == '/') + rcExit = parseOneLineComment(pStrm); + else + rcExit = parseError(pStrm, 2, "Unexpected character"); + if (rcExit != RTEXITCODE_SUCCESS) + return ~(unsigned)0; + } + } + + parseError(pStrm, 0, "Unexpected end of file"); + return ~(unsigned)0; +} + + +/** + * Get the next non-space-non-comment character on a preprocessor line. + * + * @returns The next character. On error message and ~(unsigned)0. + * @param pStrm The stream. + */ +static unsigned parseGetNextNonSpaceNonCommentChOnPpLine(PSCMSTREAM pStrm) +{ + size_t off = ScmStreamTell(pStrm) - 1; + unsigned ch; + while ((ch = ScmStreamGetCh(pStrm)) != ~(unsigned)0) + { + if (RT_C_IS_SPACE(ch)) + { + if (ch == '\n' || ch == '\r') + { + parseErrorAbs(pStrm, off, "Invalid preprocessor statement"); + break; + } + } + else if (ch == '\\') + { + size_t off2 = ScmStreamTell(pStrm) - 1; + ch = ScmStreamGetCh(pStrm); + if (ch == '\r') + ch = ScmStreamGetCh(pStrm); + if (ch != '\n') + { + parseErrorAbs(pStrm, off2, "Expected new line"); + break; + } + } + else + return ch; + } + return ~(unsigned)0; +} + + + +/** + * Skips spaces and comments. + * + * @returns Same as ScmStreamCGetWord + * @param pStrm The stream.. + * @param pcchWord Where to return the length. + */ +static const char *parseGetNextCWord(PSCMSTREAM pStrm, size_t *pcchWord) +{ + if (parseSkipSpacesAndComments(pStrm) != RTEXITCODE_SUCCESS) + return NULL; + return ScmStreamCGetWord(pStrm, pcchWord); +} + + + +/** + * Parses interface stability. + * + * @returns Interface stability if parsed correctly, otherwise error message and + * kVTGStability_Invalid. + * @param pStrm The stream. + * @param ch The first character in the stability spec. + */ +static kVTGStability parseStability(PSCMSTREAM pStrm, unsigned ch) +{ + switch (ch) + { + case 'E': + if (ScmStreamCMatchingWordM1(pStrm, RT_STR_TUPLE("External"))) + return kVTGStability_External; + if (ScmStreamCMatchingWordM1(pStrm, RT_STR_TUPLE("Evolving"))) + return kVTGStability_Evolving; + break; + case 'I': + if (ScmStreamCMatchingWordM1(pStrm, RT_STR_TUPLE("Internal"))) + return kVTGStability_Internal; + break; + case 'O': + if (ScmStreamCMatchingWordM1(pStrm, RT_STR_TUPLE("Obsolete"))) + return kVTGStability_Obsolete; + break; + case 'P': + if (ScmStreamCMatchingWordM1(pStrm, RT_STR_TUPLE("Private"))) + return kVTGStability_Private; + break; + case 'S': + if (ScmStreamCMatchingWordM1(pStrm, RT_STR_TUPLE("Stable"))) + return kVTGStability_Stable; + if (ScmStreamCMatchingWordM1(pStrm, RT_STR_TUPLE("Standard"))) + return kVTGStability_Standard; + break; + case 'U': + if (ScmStreamCMatchingWordM1(pStrm, RT_STR_TUPLE("Unstable"))) + return kVTGStability_Unstable; + break; + } + parseError(pStrm, 1, "Unknown stability specifier"); + return kVTGStability_Invalid; +} + + +/** + * Parses data depndency class. + * + * @returns Data dependency class if parsed correctly, otherwise error message + * and kVTGClass_Invalid. + * @param pStrm The stream. + * @param ch The first character in the stability spec. + */ +static kVTGClass parseDataDepClass(PSCMSTREAM pStrm, unsigned ch) +{ + switch (ch) + { + case 'C': + if (ScmStreamCMatchingWordM1(pStrm, RT_STR_TUPLE("Common"))) + return kVTGClass_Common; + if (ScmStreamCMatchingWordM1(pStrm, RT_STR_TUPLE("Cpu"))) + return kVTGClass_Cpu; + break; + case 'G': + if (ScmStreamCMatchingWordM1(pStrm, RT_STR_TUPLE("Group"))) + return kVTGClass_Group; + break; + case 'I': + if (ScmStreamCMatchingWordM1(pStrm, RT_STR_TUPLE("Isa"))) + return kVTGClass_Isa; + break; + case 'P': + if (ScmStreamCMatchingWordM1(pStrm, RT_STR_TUPLE("Platform"))) + return kVTGClass_Platform; + break; + case 'U': + if (ScmStreamCMatchingWordM1(pStrm, RT_STR_TUPLE("Unknown"))) + return kVTGClass_Unknown; + break; + } + parseError(pStrm, 1, "Unknown data dependency class specifier"); + return kVTGClass_Invalid; +} + +/** + * Parses a pragma D attributes statement. + * + * @returns Suitable exit code, errors message already written on failure. + * @param pStrm The stream. + */ +static RTEXITCODE parsePragmaDAttributes(PSCMSTREAM pStrm) +{ + /* + * "CodeStability/DataStability/DataDepClass" - no spaces allowed. + */ + unsigned ch = parseGetNextNonSpaceNonCommentChOnPpLine(pStrm); + if (ch == ~(unsigned)0) + return RTEXITCODE_FAILURE; + + kVTGStability enmCode = parseStability(pStrm, ch); + if (enmCode == kVTGStability_Invalid) + return RTEXITCODE_FAILURE; + ch = ScmStreamGetCh(pStrm); + if (ch != '/') + return parseError(pStrm, 1, "Expected '/' following the code stability specifier"); + + kVTGStability enmData = parseStability(pStrm, ScmStreamGetCh(pStrm)); + if (enmData == kVTGStability_Invalid) + return RTEXITCODE_FAILURE; + ch = ScmStreamGetCh(pStrm); + if (ch != '/') + return parseError(pStrm, 1, "Expected '/' following the data stability specifier"); + + kVTGClass enmDataDep = parseDataDepClass(pStrm, ScmStreamGetCh(pStrm)); + if (enmDataDep == kVTGClass_Invalid) + return RTEXITCODE_FAILURE; + + /* + * Expecting 'provider' followed by the name of an provider defined earlier. + */ + ch = parseGetNextNonSpaceNonCommentChOnPpLine(pStrm); + if (ch == ~(unsigned)0) + return RTEXITCODE_FAILURE; + if (ch != 'p' || !ScmStreamCMatchingWordM1(pStrm, RT_STR_TUPLE("provider"))) + return parseError(pStrm, 1, "Expected 'provider'"); + + size_t cchName; + const char *pszName = parseGetNextCWord(pStrm, &cchName); + if (!pszName) + return parseError(pStrm, 1, "Expected provider name"); + + PVTGPROVIDER pProv; + RTListForEach(&g_ProviderHead, pProv, VTGPROVIDER, ListEntry) + { + if ( !strncmp(pProv->pszName, pszName, cchName) + && pProv->pszName[cchName] == '\0') + break; + } + if (RTListNodeIsDummy(&g_ProviderHead, pProv, VTGPROVIDER, ListEntry)) + return parseError(pStrm, cchName, "Provider not found"); + + /* + * Which aspect of the provider? + */ + size_t cchAspect; + const char *pszAspect = parseGetNextCWord(pStrm, &cchAspect); + if (!pszAspect) + return parseError(pStrm, 1, "Expected provider aspect"); + + PVTGATTRS pAttrs; + if (cchAspect == 8 && !memcmp(pszAspect, "provider", 8)) + pAttrs = &pProv->AttrSelf; + else if (cchAspect == 8 && !memcmp(pszAspect, "function", 8)) + pAttrs = &pProv->AttrFunctions; + else if (cchAspect == 6 && !memcmp(pszAspect, "module", 6)) + pAttrs = &pProv->AttrModules; + else if (cchAspect == 4 && !memcmp(pszAspect, "name", 4)) + pAttrs = &pProv->AttrName; + else if (cchAspect == 4 && !memcmp(pszAspect, "args", 4)) + pAttrs = &pProv->AttrArguments; + else + return parseError(pStrm, cchAspect, "Unknown aspect"); + + if (pAttrs->enmCode != kVTGStability_Invalid) + return parseError(pStrm, cchAspect, "You have already specified these attributes"); + + pAttrs->enmCode = enmCode; + pAttrs->enmData = enmData; + pAttrs->enmDataDep = enmDataDep; + return RTEXITCODE_SUCCESS; +} + +/** + * Parses a D pragma statement. + * + * @returns Suitable exit code, errors message already written on failure. + * @param pStrm The stream. + */ +static RTEXITCODE parsePragma(PSCMSTREAM pStrm) +{ + RTEXITCODE rcExit; + unsigned ch = parseGetNextNonSpaceNonCommentChOnPpLine(pStrm); + if (ch == ~(unsigned)0) + rcExit = RTEXITCODE_FAILURE; + else if (ch == 'D' && ScmStreamCMatchingWordM1(pStrm, RT_STR_TUPLE("D"))) + { + ch = parseGetNextNonSpaceNonCommentChOnPpLine(pStrm); + if (ch == ~(unsigned)0) + rcExit = RTEXITCODE_FAILURE; + else if (ch == 'a' && ScmStreamCMatchingWordM1(pStrm, RT_STR_TUPLE("attributes"))) + rcExit = parsePragmaDAttributes(pStrm); + else + rcExit = parseError(pStrm, 1, "Unknown pragma D"); + } + else + rcExit = parseError(pStrm, 1, "Unknown pragma"); + return rcExit; +} + + +/** + * Classifies the given type expression. + * + * @return Type flags. + * @param pszType The type expression. + * @param pStrm The input stream (for errors + warnings). + * @param offSrc The absolute source position of this expression (for + * warnings). + */ +static uint32_t parseTypeExpression(const char *pszType, PSCMSTREAM pStrm, size_t offSrc) +{ + size_t cchType = strlen(pszType); +#define MY_STRMATCH(a_sz) (cchType == sizeof(a_sz) - 1 && !memcmp(a_sz, pszType, sizeof(a_sz) - 1)) + + /* + * Try detect pointers. + */ + if (pszType[cchType - 1] == '*') + { + if (MY_STRMATCH("const char *")) return VTG_TYPE_POINTER | VTG_TYPE_CONST_CHAR_PTR; + return VTG_TYPE_POINTER; + } + if (pszType[cchType - 1] == '&') + { + parseWarnAbs(pStrm, offSrc, "Please avoid using references like '%s' for probe arguments!", pszType); + return VTG_TYPE_POINTER; + } + + /* + * Standard integer types and IPRT variants. + * It's important that we catch all types larger than 32-bit here or we'll + * screw up the probe argument handling. + */ + if (MY_STRMATCH("int")) return VTG_TYPE_FIXED_SIZED | sizeof(int) | VTG_TYPE_SIGNED; + if (MY_STRMATCH("uintptr_t")) return VTG_TYPE_HC_ARCH_SIZED | VTG_TYPE_UNSIGNED; + if (MY_STRMATCH("intptr_t")) return VTG_TYPE_HC_ARCH_SIZED | VTG_TYPE_SIGNED; + + //if (MY_STRMATCH("uint128_t")) return VTG_TYPE_FIXED_SIZED | sizeof(uint128_t) | VTG_TYPE_UNSIGNED; + if (MY_STRMATCH("uint64_t")) return VTG_TYPE_FIXED_SIZED | sizeof(uint64_t) | VTG_TYPE_UNSIGNED; + if (MY_STRMATCH("uint32_t")) return VTG_TYPE_FIXED_SIZED | sizeof(uint32_t) | VTG_TYPE_UNSIGNED; + if (MY_STRMATCH("uint16_t")) return VTG_TYPE_FIXED_SIZED | sizeof(uint16_t) | VTG_TYPE_UNSIGNED; + if (MY_STRMATCH("uint8_t")) return VTG_TYPE_FIXED_SIZED | sizeof(uint8_t) | VTG_TYPE_UNSIGNED; + + //if (MY_STRMATCH("int128_t")) return VTG_TYPE_FIXED_SIZED | sizeof(int128_t) | VTG_TYPE_SIGNED; + if (MY_STRMATCH("int64_t")) return VTG_TYPE_FIXED_SIZED | sizeof(int64_t) | VTG_TYPE_SIGNED; + if (MY_STRMATCH("int32_t")) return VTG_TYPE_FIXED_SIZED | sizeof(int32_t) | VTG_TYPE_SIGNED; + if (MY_STRMATCH("int16_t")) return VTG_TYPE_FIXED_SIZED | sizeof(int16_t) | VTG_TYPE_SIGNED; + if (MY_STRMATCH("int8_t")) return VTG_TYPE_FIXED_SIZED | sizeof(int8_t) | VTG_TYPE_SIGNED; + + if (MY_STRMATCH("RTUINT64U")) return VTG_TYPE_FIXED_SIZED | sizeof(uint64_t) | VTG_TYPE_UNSIGNED; + if (MY_STRMATCH("RTUINT32U")) return VTG_TYPE_FIXED_SIZED | sizeof(uint32_t) | VTG_TYPE_UNSIGNED; + if (MY_STRMATCH("RTUINT16U")) return VTG_TYPE_FIXED_SIZED | sizeof(uint16_t) | VTG_TYPE_UNSIGNED; + + if (MY_STRMATCH("RTMSINTERVAL")) return VTG_TYPE_FIXED_SIZED | sizeof(RTMSINTERVAL) | VTG_TYPE_UNSIGNED; + if (MY_STRMATCH("RTTIMESPEC")) return VTG_TYPE_FIXED_SIZED | sizeof(RTTIMESPEC) | VTG_TYPE_SIGNED; + if (MY_STRMATCH("RTPROCESS")) return VTG_TYPE_FIXED_SIZED | sizeof(RTPROCESS) | VTG_TYPE_UNSIGNED; + if (MY_STRMATCH("RTHCPHYS")) return VTG_TYPE_FIXED_SIZED | sizeof(RTHCPHYS) | VTG_TYPE_UNSIGNED | VTG_TYPE_PHYS; + + if (MY_STRMATCH("RTR3PTR")) return VTG_TYPE_CTX_POINTER | VTG_TYPE_CTX_R3; + if (MY_STRMATCH("RTR0PTR")) return VTG_TYPE_CTX_POINTER | VTG_TYPE_CTX_R0; + if (MY_STRMATCH("RTRCPTR")) return VTG_TYPE_CTX_POINTER | VTG_TYPE_CTX_RC; + if (MY_STRMATCH("RTHCPTR")) return VTG_TYPE_CTX_POINTER | VTG_TYPE_CTX_R3 | VTG_TYPE_CTX_R0; + + if (MY_STRMATCH("RTR3UINTPTR")) return VTG_TYPE_CTX_POINTER | VTG_TYPE_CTX_R3 | VTG_TYPE_UNSIGNED; + if (MY_STRMATCH("RTR0UINTPTR")) return VTG_TYPE_CTX_POINTER | VTG_TYPE_CTX_R0 | VTG_TYPE_UNSIGNED; + if (MY_STRMATCH("RTRCUINTPTR")) return VTG_TYPE_CTX_POINTER | VTG_TYPE_CTX_RC | VTG_TYPE_UNSIGNED; + if (MY_STRMATCH("RTHCUINTPTR")) return VTG_TYPE_CTX_POINTER | VTG_TYPE_CTX_R3 | VTG_TYPE_CTX_R0 | VTG_TYPE_UNSIGNED; + + if (MY_STRMATCH("RTR3INTPTR")) return VTG_TYPE_CTX_POINTER | VTG_TYPE_CTX_R3 | VTG_TYPE_SIGNED; + if (MY_STRMATCH("RTR0INTPTR")) return VTG_TYPE_CTX_POINTER | VTG_TYPE_CTX_R0 | VTG_TYPE_SIGNED; + if (MY_STRMATCH("RTRCINTPTR")) return VTG_TYPE_CTX_POINTER | VTG_TYPE_CTX_RC | VTG_TYPE_SIGNED; + if (MY_STRMATCH("RTHCINTPTR")) return VTG_TYPE_CTX_POINTER | VTG_TYPE_CTX_R3 | VTG_TYPE_CTX_R0 | VTG_TYPE_SIGNED; + + if (MY_STRMATCH("RTUINTPTR")) return VTG_TYPE_CTX_POINTER | VTG_TYPE_CTX_R3 | VTG_TYPE_CTX_R0 | VTG_TYPE_CTX_RC | VTG_TYPE_UNSIGNED; + if (MY_STRMATCH("RTINTPTR")) return VTG_TYPE_CTX_POINTER | VTG_TYPE_CTX_R3 | VTG_TYPE_CTX_R0 | VTG_TYPE_CTX_RC | VTG_TYPE_SIGNED; + + if (MY_STRMATCH("RTHCUINTREG")) return VTG_TYPE_HC_ARCH_SIZED | VTG_TYPE_CTX_R3 | VTG_TYPE_CTX_R0 | VTG_TYPE_UNSIGNED; + if (MY_STRMATCH("RTR3UINTREG")) return VTG_TYPE_HC_ARCH_SIZED | VTG_TYPE_CTX_R3 | VTG_TYPE_UNSIGNED; + if (MY_STRMATCH("RTR0UINTREG")) return VTG_TYPE_HC_ARCH_SIZED | VTG_TYPE_CTX_R3 | VTG_TYPE_UNSIGNED; + + if (MY_STRMATCH("RTGCUINTREG")) return VTG_TYPE_FIXED_SIZED | sizeof(RTGCUINTREG) | VTG_TYPE_UNSIGNED | VTG_TYPE_CTX_GST; + if (MY_STRMATCH("RTGCPTR")) return VTG_TYPE_FIXED_SIZED | sizeof(RTGCPTR) | VTG_TYPE_UNSIGNED | VTG_TYPE_CTX_GST; + if (MY_STRMATCH("RTGCINTPTR")) return VTG_TYPE_FIXED_SIZED | sizeof(RTGCUINTPTR) | VTG_TYPE_SIGNED | VTG_TYPE_CTX_GST; + if (MY_STRMATCH("RTGCPTR32")) return VTG_TYPE_FIXED_SIZED | sizeof(RTGCPTR32) | VTG_TYPE_SIGNED | VTG_TYPE_CTX_GST; + if (MY_STRMATCH("RTGCPTR64")) return VTG_TYPE_FIXED_SIZED | sizeof(RTGCPTR64) | VTG_TYPE_SIGNED | VTG_TYPE_CTX_GST; + if (MY_STRMATCH("RTGCPHYS")) return VTG_TYPE_FIXED_SIZED | sizeof(RTGCPHYS) | VTG_TYPE_UNSIGNED | VTG_TYPE_PHYS | VTG_TYPE_CTX_GST; + if (MY_STRMATCH("RTGCPHYS32")) return VTG_TYPE_FIXED_SIZED | sizeof(RTGCPHYS32) | VTG_TYPE_UNSIGNED | VTG_TYPE_PHYS | VTG_TYPE_CTX_GST; + if (MY_STRMATCH("RTGCPHYS64")) return VTG_TYPE_FIXED_SIZED | sizeof(RTGCPHYS64) | VTG_TYPE_UNSIGNED | VTG_TYPE_PHYS | VTG_TYPE_CTX_GST; + + /* + * The special VBox types. + */ + if (MY_STRMATCH("PVM")) return VTG_TYPE_POINTER; + if (MY_STRMATCH("PVMCPU")) return VTG_TYPE_POINTER; + if (MY_STRMATCH("PCPUMCTX")) return VTG_TYPE_POINTER; + + /* + * Preaching time. + */ + if ( MY_STRMATCH("unsigned long") + || MY_STRMATCH("unsigned long long") + || MY_STRMATCH("signed long") + || MY_STRMATCH("signed long long") + || MY_STRMATCH("long") + || MY_STRMATCH("long long") + || MY_STRMATCH("char") + || MY_STRMATCH("signed char") + || MY_STRMATCH("unsigned char") + || MY_STRMATCH("double") + || MY_STRMATCH("long double") + || MY_STRMATCH("float") + ) + { + RTMsgError("Please do NOT use the type '%s' for probe arguments!", pszType); + g_cTypeErrors++; + return 0; + } + + if ( MY_STRMATCH("unsigned") + || MY_STRMATCH("signed") + || MY_STRMATCH("signed int") + || MY_STRMATCH("unsigned int") + || MY_STRMATCH("short") + || MY_STRMATCH("signed short") + || MY_STRMATCH("unsigned short") + ) + parseWarnAbs(pStrm, offSrc, "Please avoid using the type '%s' for probe arguments!", pszType); + if (MY_STRMATCH("unsigned")) return VTG_TYPE_FIXED_SIZED | sizeof(int) | VTG_TYPE_UNSIGNED; + if (MY_STRMATCH("unsigned int")) return VTG_TYPE_FIXED_SIZED | sizeof(int) | VTG_TYPE_UNSIGNED; + if (MY_STRMATCH("signed")) return VTG_TYPE_FIXED_SIZED | sizeof(int) | VTG_TYPE_SIGNED; + if (MY_STRMATCH("signed int")) return VTG_TYPE_FIXED_SIZED | sizeof(int) | VTG_TYPE_SIGNED; + if (MY_STRMATCH("short")) return VTG_TYPE_FIXED_SIZED | sizeof(short) | VTG_TYPE_SIGNED; + if (MY_STRMATCH("signed short")) return VTG_TYPE_FIXED_SIZED | sizeof(short) | VTG_TYPE_SIGNED; + if (MY_STRMATCH("unsigned short")) return VTG_TYPE_FIXED_SIZED | sizeof(short) | VTG_TYPE_UNSIGNED; + + /* + * What we haven't caught by now is either unknown to us or wrong. + */ + if (pszType[0] == 'P') + { + RTMsgError("Type '%s' looks like a pointer typedef, please do NOT use those " + "but rather the non-pointer typedef or struct with '*'", + pszType); + g_cTypeErrors++; + return VTG_TYPE_POINTER; + } + + RTMsgError("Don't know '%s' - please change or fix VBoxTpG", pszType); + g_cTypeErrors++; + +#undef MY_STRCMP + return 0; +} + + +/** + * Initializes the members of an argument. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pProbe The probe. + * @param pArg The argument. + * @param pStrm The input stream (for errors + warnings). + * @param pchType The type. + * @param cchType The type length. + * @param pchName The name. + * @param cchName The name length. + */ +static RTEXITCODE parseInitArgument(PVTGPROBE pProbe, PVTGARG pArg, PSCMSTREAM pStrm, + char *pchType, size_t cchType, char *pchName, size_t cchName) +{ + Assert(!pArg->pszName); Assert(!pArg->pszTracerType); Assert(!pArg->pszCtxType); Assert(!pArg->fType); + + pArg->pszArgPassingFmt = ", %s"; + pArg->pszName = RTStrDupN(pchName, cchName); + pArg->pszTracerType = strtabInsertN(pchType, cchType); + if (!pArg->pszTracerType || !pArg->pszName) + return parseError(pStrm, 1, "Out of memory"); + pArg->fType = parseTypeExpression(pArg->pszTracerType, pStrm, pArg->offSrc); + + if ( (pArg->fType & VTG_TYPE_POINTER) + && !(g_fTypeContext & VTG_TYPE_CTX_R0) ) + { + pArg->fType &= ~VTG_TYPE_POINTER; + if ( !strcmp(pArg->pszTracerType, "struct VM *") || !strcmp(pArg->pszTracerType, "PVM") + || !strcmp(pArg->pszTracerType, "struct VMCPU *") || !strcmp(pArg->pszTracerType, "PVMCPU") + || !strcmp(pArg->pszTracerType, "struct CPUMCTX *") || !strcmp(pArg->pszTracerType, "PCPUMCTX") + ) + { + pArg->fType |= VTG_TYPE_CTX_POINTER | VTG_TYPE_CTX_R0 + | VTG_TYPE_FIXED_SIZED | (g_cHostBits / 8) + | VTG_TYPE_AUTO_CONV_PTR; + pArg->pszCtxType = RTStrDup("RTR0PTR"); + + if (!strcmp(pArg->pszTracerType, "struct VM *") || !strcmp(pArg->pszTracerType, "PVM")) + pArg->pszArgPassingFmt = ", VTG_VM_TO_R0(%s)"; + else if (!strcmp(pArg->pszTracerType, "struct VMCPU *") || !strcmp(pArg->pszTracerType, "PVMCPU")) + pArg->pszArgPassingFmt = ", VTG_VMCPU_TO_R0(%s)"; + else + { + PVTGARG pFirstArg = RTListGetFirst(&pProbe->ArgHead, VTGARG, ListEntry); + if ( !pFirstArg + || pFirstArg == pArg + || strcmp(pFirstArg->pszName, "a_pVCpu") + || ( strcmp(pFirstArg->pszTracerType, "struct VMCPU *") + && strcmp(pFirstArg->pszTracerType, "PVMCPU *")) ) + return parseError(pStrm, 1, "The automatic ring-0 pointer conversion requires 'a_pVCpu' with type 'struct VMCPU *' as the first argument"); + + if (!strcmp(pArg->pszTracerType, "struct CPUMCTX *")|| !strcmp(pArg->pszTracerType, "PCPUMCTX")) + pArg->pszArgPassingFmt = ", VTG_CPUMCTX_TO_R0(a_pVCpu, %s)"; + else + pArg->pszArgPassingFmt = ", VBoxTpG-Is-Buggy!!"; + } + } + else + { + pArg->fType |= VTG_TYPE_CTX_POINTER | g_fTypeContext | VTG_TYPE_FIXED_SIZED | (g_cBits / 8); + pArg->pszCtxType = RTStrDupN(pchType, cchType); + } + } + else + pArg->pszCtxType = RTStrDupN(pchType, cchType); + if (!pArg->pszCtxType) + return parseError(pStrm, 1, "Out of memory"); + + return RTEXITCODE_SUCCESS; +} + + +/** + * Unmangles the probe name. + * + * This involves translating double underscore to dash. + * + * @returns Pointer to the unmangled name in the string table. + * @param pszMangled The mangled name. + */ +static const char *parseUnmangleProbeName(const char *pszMangled) +{ + size_t cchMangled = strlen(pszMangled); + char *pszTmp = (char *)alloca(cchMangled + 2); + const char *pszSrc = pszMangled; + char *pszDst = pszTmp; + + while (*pszSrc) + { + if (pszSrc[0] == '_' && pszSrc[1] == '_' && pszSrc[2] != '_') + { + *pszDst++ = '-'; + pszSrc += 2; + } + else + *pszDst++ = *pszSrc++; + } + *pszDst = '\0'; + + return strtabInsertN(pszTmp, pszDst - pszTmp); +} + + +/** + * Parses a D probe statement. + * + * @returns Suitable exit code, errors message already written on failure. + * @param pStrm The stream. + * @param pProv The provider being parsed. + */ +static RTEXITCODE parseProbe(PSCMSTREAM pStrm, PVTGPROVIDER pProv) +{ + size_t const iProbeLine = ScmStreamTellLine(pStrm); + + /* + * Next up is a name followed by an opening parenthesis. + */ + size_t cchProbe; + const char *pszProbe = parseGetNextCWord(pStrm, &cchProbe); + if (!pszProbe) + return parseError(pStrm, 1, "Expected a probe name starting with an alphabetical character"); + unsigned ch = parseGetNextNonSpaceNonCommentCh(pStrm); + if (ch != '(') + return parseError(pStrm, 1, "Expected '(' after the probe name"); + + /* + * Create a probe instance. + */ + PVTGPROBE pProbe = (PVTGPROBE)RTMemAllocZ(sizeof(*pProbe)); + if (!pProbe) + return parseError(pStrm, 0, "Out of memory"); + RTListInit(&pProbe->ArgHead); + RTListAppend(&pProv->ProbeHead, &pProbe->ListEntry); + pProbe->offArgList = UINT32_MAX; + pProbe->iLine = iProbeLine; + pProbe->pszMangledName = RTStrDupN(pszProbe, cchProbe); + if (!pProbe->pszMangledName) + return parseError(pStrm, 0, "Out of memory"); + pProbe->pszUnmangledName = parseUnmangleProbeName(pProbe->pszMangledName); + if (!pProbe->pszUnmangledName) + return parseError(pStrm, 0, "Out of memory"); + + /* + * Parse loop for the argument. + */ + PVTGARG pArg = NULL; + size_t cchName = 0; + size_t cchArg = 0; + char szArg[4096]; + for (;;) + { + ch = parseGetNextNonSpaceNonCommentCh(pStrm); + switch (ch) + { + case ')': + case ',': + { + /* commit the argument */ + if (pArg) + { + if (!cchName) + return parseError(pStrm, 1, "Argument has no name"); + if (cchArg - cchName - 1 >= 128) + return parseError(pStrm, 1, "Argument type too long"); + RTEXITCODE rcExit = parseInitArgument(pProbe, pArg, pStrm, + szArg, cchArg - cchName - 1, + &szArg[cchArg - cchName], cchName); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + if (VTG_TYPE_IS_LARGE(pArg->fType)) + pProbe->fHaveLargeArgs = true; + pArg = NULL; + cchName = cchArg = 0; + } + if (ch == ')') + { + size_t off = ScmStreamTell(pStrm); + ch = parseGetNextNonSpaceNonCommentCh(pStrm); + if (ch != ';') + return parseErrorAbs(pStrm, off, "Expected ';'"); + return RTEXITCODE_SUCCESS; + } + break; + } + + default: + { + size_t cchWord; + const char *pszWord = ScmStreamCGetWordM1(pStrm, &cchWord); + if (!pszWord) + return parseError(pStrm, 0, "Expected argument"); + if (!pArg) + { + pArg = (PVTGARG)RTMemAllocZ(sizeof(*pArg)); + if (!pArg) + return parseError(pStrm, 1, "Out of memory"); + RTListAppend(&pProbe->ArgHead, &pArg->ListEntry); + pArg->iArgNo = pProbe->cArgs++; + pArg->pProbe = pProbe; + pArg->offSrc = ScmStreamTell(pStrm) - cchWord; + + if (cchWord + 1 > sizeof(szArg)) + return parseError(pStrm, 1, "Too long parameter declaration"); + memcpy(szArg, pszWord, cchWord); + szArg[cchWord] = '\0'; + cchArg = cchWord; + cchName = 0; + } + else + { + if (cchArg + 1 + cchWord + 1 > sizeof(szArg)) + return parseError(pStrm, 1, "Too long parameter declaration"); + + szArg[cchArg++] = ' '; + memcpy(&szArg[cchArg], pszWord, cchWord); + cchArg += cchWord; + szArg[cchArg] = '\0'; + cchName = cchWord; + } + break; + } + + case '*': + { + if (!pArg) + return parseError(pStrm, 1, "A parameter type does not start with an asterix"); + if (cchArg + sizeof(" *") >= sizeof(szArg)) + return parseError(pStrm, 1, "Too long parameter declaration"); + szArg[cchArg++] = ' '; + szArg[cchArg++] = '*'; + szArg[cchArg ] = '\0'; + cchName = 0; + break; + } + + case ~(unsigned)0: + return parseError(pStrm, 0, "Missing closing ')' on probe"); + } + } +} + +/** + * Parses a D provider statement. + * + * @returns Suitable exit code, errors message already written on failure. + * @param pStrm The stream. + */ +static RTEXITCODE parseProvider(PSCMSTREAM pStrm) +{ + /* + * Next up is a name followed by a curly bracket. Ignore comments. + */ + RTEXITCODE rcExit = parseSkipSpacesAndComments(pStrm); + if (rcExit != RTEXITCODE_SUCCESS) + return parseError(pStrm, 1, "Expected a provider name starting with an alphabetical character"); + size_t cchName; + const char *pszName = ScmStreamCGetWord(pStrm, &cchName); + if (!pszName) + return parseError(pStrm, 0, "Bad provider name"); + if (RT_C_IS_DIGIT(pszName[cchName - 1])) + return parseError(pStrm, 1, "A provider name cannot end with digit"); + + unsigned ch = parseGetNextNonSpaceNonCommentCh(pStrm); + if (ch != '{') + return parseError(pStrm, 1, "Expected '{' after the provider name"); + + /* + * Create a provider instance. + */ + PVTGPROVIDER pProv = (PVTGPROVIDER)RTMemAllocZ(sizeof(*pProv)); + if (!pProv) + return parseError(pStrm, 0, "Out of memory"); + RTListInit(&pProv->ProbeHead); + RTListAppend(&g_ProviderHead, &pProv->ListEntry); + pProv->pszName = strtabInsertN(pszName, cchName); + if (!pProv->pszName) + return parseError(pStrm, 0, "Out of memory"); + + /* + * Parse loop. + */ + for (;;) + { + ch = parseGetNextNonSpaceNonCommentCh(pStrm); + switch (ch) + { + case 'p': + if (ScmStreamCMatchingWordM1(pStrm, RT_STR_TUPLE("probe"))) + rcExit = parseProbe(pStrm, pProv); + else + rcExit = parseError(pStrm, 1, "Unexpected character"); + break; + + case '}': + { + size_t off = ScmStreamTell(pStrm); + ch = parseGetNextNonSpaceNonCommentCh(pStrm); + if (ch == ';') + return RTEXITCODE_SUCCESS; + rcExit = parseErrorAbs(pStrm, off, "Expected ';'"); + break; + } + + case ~(unsigned)0: + rcExit = parseError(pStrm, 0, "Missing closing '}' on provider"); + break; + + default: + rcExit = parseError(pStrm, 1, "Unexpected character"); + break; + } + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + } +} + + +static RTEXITCODE parseScript(const char *pszScript) +{ + SCMSTREAM Strm; + int rc = ScmStreamInitForReading(&Strm, pszScript); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to open & read '%s' into memory: %Rrc", pszScript, rc); + if (g_cVerbosity > 0) + RTMsgInfo("Parsing '%s'...", pszScript); + + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + unsigned ch; + while ((ch = ScmStreamGetCh(&Strm)) != ~(unsigned)0) + { + if (RT_C_IS_SPACE(ch)) + continue; + switch (ch) + { + case '/': + ch = ScmStreamGetCh(&Strm); + if (ch == '*') + rcExit = parseMultiLineComment(&Strm); + else if (ch == '/') + rcExit = parseOneLineComment(&Strm); + else + rcExit = parseError(&Strm, 2, "Unexpected character"); + break; + + case 'p': + if (ScmStreamCMatchingWordM1(&Strm, RT_STR_TUPLE("provider"))) + rcExit = parseProvider(&Strm); + else + rcExit = parseError(&Strm, 1, "Unexpected character"); + break; + + case '#': + { + ch = parseGetNextNonSpaceNonCommentChOnPpLine(&Strm); + if (ch == ~(unsigned)0) + rcExit = RTEXITCODE_FAILURE; + else if (ch == 'p' && ScmStreamCMatchingWordM1(&Strm, RT_STR_TUPLE("pragma"))) + rcExit = parsePragma(&Strm); + else + rcExit = parseError(&Strm, 1, "Unsupported preprocessor directive"); + break; + } + + default: + rcExit = parseError(&Strm, 1, "Unexpected character"); + break; + } + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + } + + ScmStreamDelete(&Strm); + if (g_cVerbosity > 0 && rcExit == RTEXITCODE_SUCCESS) + RTMsgInfo("Successfully parsed '%s'.", pszScript); + return rcExit; +} + + +/** + * Parses the arguments. + */ +static RTEXITCODE parseArguments(int argc, char **argv) +{ + /* + * Set / Adjust defaults. + */ + int rc = RTPathAbs(g_pszAssemblerIncVal, g_szAssemblerIncVal, sizeof(g_szAssemblerIncVal) - 1); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTPathAbs failed: %Rrc", rc); + strcat(g_szAssemblerIncVal, "/"); + g_pszAssemblerIncVal = g_szAssemblerIncVal; + + /* + * Option config. + */ + enum + { + kVBoxTpGOpt_32Bit = 1000, + kVBoxTpGOpt_64Bit, + kVBoxTpGOpt_GenerateWrapperHeader, + kVBoxTpGOpt_Assembler, + kVBoxTpGOpt_AssemblerFmtOpt, + kVBoxTpGOpt_AssemblerFmtVal, + kVBoxTpGOpt_AssemblerOutputOpt, + kVBoxTpGOpt_AssemblerOption, + kVBoxTpGOpt_Pic, + kVBoxTpGOpt_ProbeFnName, + kVBoxTpGOpt_ProbeFnImported, + kVBoxTpGOpt_ProbeFnNotImported, + kVBoxTpGOpt_Host32Bit, + kVBoxTpGOpt_Host64Bit, + kVBoxTpGOpt_RawModeContext, + kVBoxTpGOpt_Ring0Context, + kVBoxTpGOpt_Ring0ContextAgnostic, + kVBoxTpGOpt_Ring3Context, + kVBoxTpGOpt_End + }; + + static RTGETOPTDEF const s_aOpts[] = + { + /* dtrace w/ long options */ + { "-32", kVBoxTpGOpt_32Bit, RTGETOPT_REQ_NOTHING }, + { "-64", kVBoxTpGOpt_64Bit, RTGETOPT_REQ_NOTHING }, + { "--apply-cpp", 'C', RTGETOPT_REQ_NOTHING }, + { "--generate-obj", 'G', RTGETOPT_REQ_NOTHING }, + { "--generate-header", 'h', RTGETOPT_REQ_NOTHING }, + { "--output", 'o', RTGETOPT_REQ_STRING }, + { "--script", 's', RTGETOPT_REQ_STRING }, + { "--verbose", 'v', RTGETOPT_REQ_NOTHING }, + /* our stuff */ + { "--generate-wrapper-header", kVBoxTpGOpt_GenerateWrapperHeader, RTGETOPT_REQ_NOTHING }, + { "--assembler", kVBoxTpGOpt_Assembler, RTGETOPT_REQ_STRING }, + { "--assembler-fmt-opt", kVBoxTpGOpt_AssemblerFmtOpt, RTGETOPT_REQ_STRING }, + { "--assembler-fmt-val", kVBoxTpGOpt_AssemblerFmtVal, RTGETOPT_REQ_STRING }, + { "--assembler-output-opt", kVBoxTpGOpt_AssemblerOutputOpt, RTGETOPT_REQ_STRING }, + { "--assembler-option", kVBoxTpGOpt_AssemblerOption, RTGETOPT_REQ_STRING }, + { "--pic", kVBoxTpGOpt_Pic, RTGETOPT_REQ_NOTHING }, + { "--probe-fn-name", kVBoxTpGOpt_ProbeFnName, RTGETOPT_REQ_STRING }, + { "--probe-fn-imported", kVBoxTpGOpt_ProbeFnImported, RTGETOPT_REQ_NOTHING }, + { "--probe-fn-not-imported", kVBoxTpGOpt_ProbeFnNotImported, RTGETOPT_REQ_NOTHING }, + { "--host-32-bit", kVBoxTpGOpt_Host32Bit, RTGETOPT_REQ_NOTHING }, + { "--host-64-bit", kVBoxTpGOpt_Host64Bit, RTGETOPT_REQ_NOTHING }, + { "--raw-mode-context", kVBoxTpGOpt_RawModeContext, RTGETOPT_REQ_NOTHING }, + { "--ring-0-context", kVBoxTpGOpt_Ring0Context, RTGETOPT_REQ_NOTHING }, + { "--ring-0-context-agnostic", kVBoxTpGOpt_Ring0ContextAgnostic, RTGETOPT_REQ_NOTHING }, + { "--ring-3-context", kVBoxTpGOpt_Ring3Context, RTGETOPT_REQ_NOTHING }, + /** @todo We're missing a bunch of assembler options! */ + }; + + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetOptState; + rc = RTGetOptInit(&GetOptState, argc, argv, &s_aOpts[0], RT_ELEMENTS(s_aOpts), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST); + AssertReleaseRCReturn(rc, RTEXITCODE_FAILURE); + + /* + * Process the options. + */ + while ((rc = RTGetOpt(&GetOptState, &ValueUnion)) != 0) + { + switch (rc) + { + /* + * DTrace compatible options. + */ + case kVBoxTpGOpt_32Bit: + g_cHostBits = g_cBits = 32; + g_pszAssemblerFmtVal = g_szAssemblerFmtVal32; + break; + + case kVBoxTpGOpt_64Bit: + g_cHostBits = g_cBits = 64; + g_pszAssemblerFmtVal = g_szAssemblerFmtVal64; + break; + + case 'C': + g_fApplyCpp = true; + RTMsgWarning("Ignoring the -C option - no preprocessing of the D script will be performed"); + break; + + case 'G': + if ( g_enmAction != kVBoxTpGAction_Nothing + && g_enmAction != kVBoxTpGAction_GenerateObject) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "-G does not mix with -h or --generate-wrapper-header"); + g_enmAction = kVBoxTpGAction_GenerateObject; + break; + + case 'h': + if (!strcmp(GetOptState.pDef->pszLong, "--generate-header")) + { + if ( g_enmAction != kVBoxTpGAction_Nothing + && g_enmAction != kVBoxTpGAction_GenerateHeader) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "-h does not mix with -G or --generate-wrapper-header"); + g_enmAction = kVBoxTpGAction_GenerateHeader; + } + else + { + /* --help or similar */ + RTPrintf("VirtualBox Tracepoint Generator\n" + "\n" + "Usage: %s [options]\n" + "\n" + "Options:\n", RTProcShortName()); + for (size_t i = 0; i < RT_ELEMENTS(s_aOpts); i++) + if ((unsigned)s_aOpts[i].iShort < 128) + RTPrintf(" -%c,%s\n", s_aOpts[i].iShort, s_aOpts[i].pszLong); + else + RTPrintf(" %s\n", s_aOpts[i].pszLong); + return RTEXITCODE_SUCCESS; + } + break; + + case 'o': + if (g_pszOutput) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Output file is already set to '%s'", g_pszOutput); + g_pszOutput = ValueUnion.psz; + break; + + case 's': + if (g_pszScript) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Script file is already set to '%s'", g_pszScript); + g_pszScript = ValueUnion.psz; + break; + + case 'v': + g_cVerbosity++; + break; + + case 'V': + { + /* The following is assuming that svn does it's job here. */ + static const char s_szRev[] = "$Revision: 155244 $"; + const char *psz = RTStrStripL(strchr(s_szRev, ' ')); + RTPrintf("r%.*s\n", strchr(psz, ' ') - psz, psz); + return RTEXITCODE_SUCCESS; + } + + case VINF_GETOPT_NOT_OPTION: + if (g_enmAction == kVBoxTpGAction_GenerateObject) + break; /* object files, ignore them. */ + return RTGetOptPrintError(rc, &ValueUnion); + + + /* + * Our options. + */ + case kVBoxTpGOpt_GenerateWrapperHeader: + if ( g_enmAction != kVBoxTpGAction_Nothing + && g_enmAction != kVBoxTpGAction_GenerateWrapperHeader) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "--generate-wrapper-header does not mix with -h or -G"); + g_enmAction = kVBoxTpGAction_GenerateWrapperHeader; + break; + + case kVBoxTpGOpt_Assembler: + g_pszAssembler = ValueUnion.psz; + break; + + case kVBoxTpGOpt_AssemblerFmtOpt: + g_pszAssemblerFmtOpt = ValueUnion.psz; + break; + + case kVBoxTpGOpt_AssemblerFmtVal: + g_pszAssemblerFmtVal = ValueUnion.psz; + break; + + case kVBoxTpGOpt_AssemblerOutputOpt: + g_pszAssemblerOutputOpt = ValueUnion.psz; + break; + + case kVBoxTpGOpt_AssemblerOption: + if (g_cAssemblerOptions >= RT_ELEMENTS(g_apszAssemblerOptions)) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many assembly options (max %u)", RT_ELEMENTS(g_apszAssemblerOptions)); + g_apszAssemblerOptions[g_cAssemblerOptions] = ValueUnion.psz; + g_cAssemblerOptions++; + break; + + case kVBoxTpGOpt_Pic: + g_fPic = true; + break; + + case kVBoxTpGOpt_ProbeFnName: + g_pszProbeFnName = ValueUnion.psz; + break; + + case kVBoxTpGOpt_ProbeFnImported: + g_fProbeFnImported = true; + break; + + case kVBoxTpGOpt_ProbeFnNotImported: + g_fProbeFnImported = false; + break; + + case kVBoxTpGOpt_Host32Bit: + g_cHostBits = 32; + break; + + case kVBoxTpGOpt_Host64Bit: + g_cHostBits = 64; + break; + + case kVBoxTpGOpt_RawModeContext: + g_fTypeContext = VTG_TYPE_CTX_RC; + g_pszContextDefine = "IN_RC"; + g_pszContextDefine2 = NULL; + break; + + case kVBoxTpGOpt_Ring0Context: + g_fTypeContext = VTG_TYPE_CTX_R0; + g_pszContextDefine = "IN_RING0"; + g_pszContextDefine2 = NULL; + break; + + case kVBoxTpGOpt_Ring0ContextAgnostic: + g_fTypeContext = VTG_TYPE_CTX_R0; + g_pszContextDefine = "IN_RING0_AGNOSTIC"; + g_pszContextDefine2 = "IN_RING0"; + break; + + case kVBoxTpGOpt_Ring3Context: + g_fTypeContext = VTG_TYPE_CTX_R3; + g_pszContextDefine = "IN_RING3"; + g_pszContextDefine2 = NULL; + break; + + + /* + * Errors and bugs. + */ + default: + return RTGetOptPrintError(rc, &ValueUnion); + } + } + + /* + * Check that we've got all we need. + */ + if (g_enmAction == kVBoxTpGAction_Nothing) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No action specified (-h, -G or --generate-wrapper-header)"); + if (!g_pszScript) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No script file specified (-s)"); + if (!g_pszOutput) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No output file specified (-o)"); + + return RTEXITCODE_SUCCESS; +} + + +int main(int argc, char **argv) +{ + int rc = RTR3InitExe(argc, &argv, 0); + if (RT_FAILURE(rc)) + return 1; + + RTEXITCODE rcExit = parseArguments(argc, argv); + if (rcExit == RTEXITCODE_SUCCESS) + { + /* + * Parse the script. + */ + RTListInit(&g_ProviderHead); + rcExit = parseScript(g_pszScript); + if (rcExit == RTEXITCODE_SUCCESS) + { + /* + * Take action. + */ + if (g_enmAction == kVBoxTpGAction_GenerateHeader) + rcExit = generateFile(g_pszOutput, "header", generateHeader); + else if (g_enmAction == kVBoxTpGAction_GenerateWrapperHeader) + rcExit = generateFile(g_pszOutput, "wrapper header", generateWrapperHeader); + else + rcExit = generateObject(g_pszOutput, g_pszTempAsm); + } + } + + if (rcExit == RTEXITCODE_SUCCESS && g_cTypeErrors > 0) + rcExit = RTEXITCODE_FAILURE; + return rcExit; +} + diff --git a/src/bldprogs/bin2c.c b/src/bldprogs/bin2c.c new file mode 100644 index 00000000..5e7c2f19 --- /dev/null +++ b/src/bldprogs/bin2c.c @@ -0,0 +1,274 @@ +/* $Id: bin2c.c $ */ +/** @file + * bin2c - Binary 2 C Structure Converter. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <ctype.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <sys/types.h> + + +/** + * File size. + * + * @returns file size in bytes. + * @returns 0 on failure. + * @param pFile File to size. + */ +static size_t fsize(FILE *pFile) +{ + long cbFile; + off_t Pos = ftell(pFile); + if ( Pos >= 0 + && !fseek(pFile, 0, SEEK_END)) + { + cbFile = ftell(pFile); + if ( cbFile >= 0 + && !fseek(pFile, 0, SEEK_SET)) + return cbFile; + } + return 0; +} + +static int usage(const char *argv0) +{ + fprintf(stderr, + "Syntax: %s [options] <arrayname> <binaryfile> <outname>\n" + " --min <n> check if <binaryfile> is not smaller than <n>KB\n" + " --max <n> check if <binaryfile> is not bigger than <n>KB\n" + " --mask <n> check if size of binaryfile is <n>-aligned\n" + " --width <n> number of bytes per line (default: 16)\n" + " --break <n> break every <n> lines (default: -1)\n" + , argv0); + fprintf(stderr, + " --ascii show ASCII representation of binary as comment\n" + " --export emit DECLEXPORT\n" + " --append append to the output file (default: truncate)\n" + " --no-size Skip the size.\n" + " --static Static data scope.\n"); + + return 1; +} + +int main(int argc, char *argv[]) +{ + FILE *pFileIn; + FILE *pFileOut; + int iArg; + size_t cbMin = 0; + size_t cbMax = ~0U; + size_t uMask = 0; + int fAscii = 0; + int fAppend = 0; + int fExport = 0; + int fNoSize = 0; + int fStatic = 0; + long iBreakEvery = -1; + unsigned char abLine[32]; + size_t cbLine = 16; + size_t off; + size_t cbRead; + size_t cbBin; + int rc = 1; /* assume the worst... */ + + if (argc < 2) + return usage(argv[0]); + + for (iArg = 1; iArg < argc; iArg++) + { + if (!strcmp(argv[iArg], "--min") || !strcmp(argv[iArg], "-min")) + { + if (++iArg >= argc) + return usage(argv[0]); + cbMin = 1024 * strtoul(argv[iArg], NULL, 0); + } + else if (!strcmp(argv[iArg], "--max") || !strcmp(argv[iArg], "-max")) + { + if (++iArg >= argc) + return usage(argv[0]); + cbMax = 1024 * strtoul(argv[iArg], NULL, 0); + } + else if (!strcmp(argv[iArg], "--mask") || !strcmp(argv[iArg], "-mask")) + { + if (++iArg >= argc) + return usage(argv[0]); + uMask = strtoul(argv[iArg], NULL, 0); + } + else if (!strcmp(argv[iArg], "--ascii") || !strcmp(argv[iArg], "-ascii")) + fAscii = 1; + else if (!strcmp(argv[iArg], "--append")) + fAppend = 1; + else if (!strcmp(argv[iArg], "--export") || !strcmp(argv[iArg], "-export")) + fExport = 1; + else if (!strcmp(argv[iArg], "--no-size")) + fNoSize = 1; + else if (!strcmp(argv[iArg], "--static")) + fStatic = 1; + else if (!strcmp(argv[iArg], "--width") || !strcmp(argv[iArg], "-width")) + { + if (++iArg >= argc) + return usage(argv[0]); + cbLine = strtoul(argv[iArg], NULL, 0); + if (cbLine == 0 || cbLine > sizeof(abLine)) + { + fprintf(stderr, "%s: '%s' is too wide, max %u\n", + argv[0], argv[iArg], (unsigned)sizeof(abLine)); + return 1; + } + } + else if (!strcmp(argv[iArg], "--break") || !strcmp(argv[iArg], "-break")) + { + if (++iArg >= argc) + return usage(argv[0]); + iBreakEvery = strtol(argv[iArg], NULL, 0); + if (iBreakEvery <= 0 && iBreakEvery != -1) + { + fprintf(stderr, "%s: -break value '%s' is not >= 1 or -1.\n", + argv[0], argv[iArg]); + return 1; + } + } + else if (iArg == argc - 3) + break; + else + { + fprintf(stderr, "%s: syntax error: Unknown argument '%s'\n", + argv[0], argv[iArg]); + return usage(argv[0]); + } + } + + pFileIn = fopen(argv[iArg+1], "rb"); + if (!pFileIn) + { + fprintf(stderr, "Error: failed to open input file '%s'!\n", argv[iArg+1]); + return 1; + } + + pFileOut = fopen(argv[iArg+2], fAppend ? "a" : "w"); /* no b! */ + if (!pFileOut) + { + fprintf(stderr, "Error: failed to open output file '%s'!\n", argv[iArg+2]); + fclose(pFileIn); + return 1; + } + + cbBin = fsize(pFileIn); + + fprintf(pFileOut, + "/*\n" + " * This file was automatically generated\n" + " * from %s\n" + " * by %s.\n" + " */\n" + "\n" + "#include <iprt/cdefs.h>\n" + "\n" + "%sconst unsigned char%s g_ab%s[] =\n" + "{\n", + argv[iArg+1], argv[0], fStatic ? "static " : fExport ? "DECLEXPORT(" : "", !fStatic && fExport ? ")" : "", argv[iArg]); + + /* check size restrictions */ + if (uMask && (cbBin & uMask)) + fprintf(stderr, "%s: size=%ld - Not aligned!\n", argv[0], (long)cbBin); + else if (cbBin < cbMin || cbBin > cbMax) + fprintf(stderr, "%s: size=%ld - Not %ld-%ldb in size!\n", + argv[0], (long)cbBin, (long)cbMin, (long)cbMax); + else + { + /* the binary data */ + off = 0; + while ((cbRead = fread(&abLine[0], 1, cbLine, pFileIn)) > 0) + { + size_t j; + + if ( iBreakEvery > 0 + && off + && (off / cbLine) % iBreakEvery == 0) + fprintf(pFileOut, "\n"); + + fprintf(pFileOut, " "); + for (j = 0; j < cbRead; j++) + fprintf(pFileOut, " 0x%02x,", abLine[j]); + for (; j < cbLine; j++) + fprintf(pFileOut, " "); + if (fAscii) + { + fprintf(pFileOut, " /* 0x%08lx: ", (long)off); + for (j = 0; j < cbRead; j++) + /* be careful with '/' prefixed/followed by a '*'! */ + fprintf(pFileOut, "%c", + isprint(abLine[j]) && abLine[j] != '/' ? abLine[j] : '.'); + for (; j < cbLine; j++) + fprintf(pFileOut, " "); + fprintf(pFileOut, " */"); + } + fprintf(pFileOut, "\n"); + + off += cbRead; + } + + /* check for errors */ + if (ferror(pFileIn) && !feof(pFileIn)) + fprintf(stderr, "%s: read error\n", argv[0]); + else if (off != cbBin) + fprintf(stderr, "%s: read error off=%ld cbBin=%ld\n", argv[0], (long)off, (long)cbBin); + else + { + /* no errors, finish the structure. */ + fprintf(pFileOut, + "};\n"); + + if (!fNoSize) + fprintf(pFileOut, + "\n" + "%sconst unsigned%s g_cb%s = sizeof(g_ab%s);\n", + fExport ? "DECLEXPORT(" : "", fExport ? ")" : "", argv[iArg], argv[iArg]); + + fprintf(pFileOut, "/* end of file */\n"); + + /* flush output and check for error. */ + fflush(pFileOut); + if (ferror(pFileOut)) + fprintf(stderr, "%s: write error\n", argv[0]); + else + rc = 0; /* success! */ + } + } + + /* cleanup, delete the output file on failure. */ + fclose(pFileOut); + fclose(pFileIn); + if (rc) + remove(argv[iArg+2]); + + return rc; +} diff --git a/src/bldprogs/biossums.c b/src/bldprogs/biossums.c new file mode 100644 index 00000000..37dbc8fb --- /dev/null +++ b/src/bldprogs/biossums.c @@ -0,0 +1,255 @@ +/* $Id: biossums.c $ */ +/** @file + * Tool for modifying a BIOS image to write the BIOS checksum. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <stdarg.h> +#include <errno.h> +#ifndef RT_OS_WINDOWS +# include <unistd.h> /* unlink */ +#endif + +typedef unsigned char uint8_t; + +static uint8_t abBios[64*1024]; +static FILE *g_pIn = NULL; +static FILE *g_pOut = NULL; +static const char *g_pszOutFile = NULL; +static const char *g_argv0; + +/** + * Find where the filename starts in the given path. + */ +static const char *name(const char *pszPath) +{ + const char *psz = strrchr(pszPath, '/'); +#if defined(_MSC_VER) || defined(__OS2__) + const char *psz2 = strrchr(pszPath, '\\'); + if (!psz2) + psz2 = strrchr(pszPath, ':'); + if (psz2 && (!psz || psz2 > psz)) + psz = psz2; +#endif + return psz ? psz + 1 : pszPath; +} + +/** + * Report an error. + */ +static int fatal(const char *pszFormat, ...) +{ + va_list va; + + fprintf(stderr, "%s: ", name(g_argv0)); + + va_start(va, pszFormat); + vfprintf(stderr, pszFormat, va); + va_end(va); + + /* clean up */ + if (g_pIn) + fclose(g_pIn); + if (g_pOut) + fclose(g_pOut); + if (g_pszOutFile) + unlink(g_pszOutFile); + + return 1; +} + +/** + * Calculate the checksum. + */ +static uint8_t calculateChecksum(uint8_t *pb, size_t cb, size_t iChecksum) +{ + uint8_t u8Sum = 0; + size_t i; + + for (i = 0; i < cb; i++) + if (i != iChecksum) + u8Sum += pb[i]; + + return -u8Sum; +} + +/** + * Find a header in the binary. + * + * @param pb Where to search for the signature + * @param cb Size of the search area + * @param pbHeader Pointer to the start of the signature + * @returns 0 if signature was not found, 1 if found or + * 2 if more than one signature was found */ +static int searchHeader(uint8_t *pb, size_t cb, const char *pszHeader, uint8_t **pbHeader) +{ + int fFound = 0; + unsigned int i; + size_t cbSignature = strlen(pszHeader); + + for (i = 0; i < cb; i += 16) + if (!memcmp(pb + i, pszHeader, cbSignature)) + { + if (fFound++) + return 2; + *pbHeader = pb + i; + } + + return fFound; +} + +int main(int argc, char **argv) +{ + FILE *pIn, *pOut; + size_t cbIn, cbOut; + int fAdapterBios = 0; + + g_argv0 = argv[0]; + + if (argc != 3) + return fatal("Input file name and output file name required.\n"); + + pIn = g_pIn = fopen(argv[1], "rb"); + if (!pIn) + return fatal("Error opening '%s' for reading (%s).\n", argv[1], strerror(errno)); + + pOut = g_pOut = fopen(argv[2], "wb"); + if (!pOut) + return fatal("Error opening '%s' for writing (%s).\n", argv[2], strerror(errno)); + g_pszOutFile = argv[2]; + + /* safety precaution (aka. complete paranoia :-) */ + memset(abBios, 0, sizeof(abBios)); + + cbIn = fread(abBios, 1, sizeof(abBios), pIn); + if (ferror(pIn)) + return fatal("Error reading from '%s' (%s).\n", argv[1], strerror(errno)); + g_pIn = NULL; + fclose(pIn); + + fAdapterBios = abBios[0] == 0x55 && abBios[1] == 0xaa; + + /* align size to page size */ + if ((cbIn % 4096) != 0) + cbIn = (cbIn + 4095) & ~4095; + + if (!fAdapterBios && cbIn != 64*1024) + return fatal("Size of system BIOS is not 64KB!\n"); + + if (fAdapterBios) + { + /* adapter BIOS */ + + /* set the length indicator */ + abBios[2] = (uint8_t)(cbIn / 512); + } + else + { + /* system BIOS */ + size_t cbChecksum; + uint8_t u8Checksum; + uint8_t *pbHeader; + + /* Set the BIOS32 header checksum. */ + switch (searchHeader(abBios, cbIn, "_32_", &pbHeader)) + { + case 0: + return fatal("No BIOS32 header not found!\n"); + case 2: + return fatal("More than one BIOS32 header found!\n"); + case 1: + cbChecksum = (size_t)pbHeader[9] * 16; + u8Checksum = calculateChecksum(pbHeader, cbChecksum, 10); + pbHeader[10] = u8Checksum; + break; + } + + /* Set the PIR header checksum according to PCI IRQ Routing table + * specification version 1.0, Microsoft Corporation, 1996 */ + switch (searchHeader(abBios, cbIn, "$PIR", &pbHeader)) + { + case 0: + return fatal("No PCI IRQ routing table found!\n"); + case 2: + return fatal("More than one PCI IRQ routing table found!\n"); + case 1: + cbChecksum = (size_t)pbHeader[6] + (size_t)pbHeader[7] * 256; + u8Checksum = calculateChecksum(pbHeader, cbChecksum, 31); + pbHeader[31] = u8Checksum; + break; + } + + /* Set the SMBIOS header checksum according to System Management BIOS + * Reference Specification Version 2.5, DSP0134. */ + switch (searchHeader(abBios, cbIn, "_SM_", &pbHeader)) + { + case 0: + return fatal("No SMBIOS header found!\n"); + case 2: + return fatal("More than one SMBIOS header found!\n"); + case 1: + /* at first fix the DMI header starting at SMBIOS header offset 16 */ + u8Checksum = calculateChecksum(pbHeader+16, 15, 5); + pbHeader[21] = u8Checksum; + + /* now fix the checksum of the whole SMBIOS header */ + cbChecksum = (size_t)pbHeader[5]; + u8Checksum = calculateChecksum(pbHeader, cbChecksum, 4); + pbHeader[4] = u8Checksum; + break; + } + + /* If there is a VPD table, adjust its checksum. */ + switch (searchHeader(abBios, cbIn, "\xAA\x55VPD", &pbHeader)) + { + case 0: + break; /* VPD is optional */ + case 2: + return fatal("More than one VPD header found!\n"); + case 1: + cbChecksum = (size_t)pbHeader[5]; + if (cbChecksum < 0x30) + return fatal("VPD size too small!\n"); + u8Checksum = calculateChecksum(pbHeader, cbChecksum, cbChecksum - 1); + pbHeader[cbChecksum - 1] = u8Checksum; + break; + } + } + + /* set the BIOS checksum */ + abBios[cbIn-1] = calculateChecksum(abBios, cbIn, cbIn - 1); + + cbOut = fwrite(abBios, 1, cbIn, pOut); + if (ferror(pOut)) + return fatal("Error writing to '%s' (%s).\n", g_pszOutFile, strerror(errno)); + g_pOut = NULL; + if (fclose(pOut)) + return fatal("Error closing '%s' (%s).\n", g_pszOutFile, strerror(errno)); + + return 0; +} + diff --git a/src/bldprogs/checkUndefined.sh b/src/bldprogs/checkUndefined.sh new file mode 100755 index 00000000..cb5165f5 --- /dev/null +++ b/src/bldprogs/checkUndefined.sh @@ -0,0 +1,102 @@ +#!/bin/sh + +# +# Copyright (C) 2006-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# SPDX-License-Identifier: GPL-3.0-only +# + +# +# Compare undefined symbols in a shared or static object against a new-line +# separated list of grep patterns in a set of text files and complain if +# symbols are found which aren't in the files. +# +# Usage: /bin/sh <script name> <object> [--static] <undefined symbol file...> +# +# Currently only works for native objects on Linux (and Solaris?) platforms. +# + +echoerr() +{ + echo $* 1>&2 +} + +hostos="${1}" +target="${2}" +shift 2 +if test "${1}" = "--static"; then + static="${1}" + shift +fi + +if test $# -lt 1; then + echoerr "${0}: Wrong number of arguments" + args_ok="no" +fi +if test ! -r "${target}"; then + echoerr "${0}: '${target}' not readable" + args_ok="no" +fi +for i in "${@}"; do + if test ! -r "${i}"; then + echoerr "${0}: '${i}' not readable" + args_ok="no" + fi +done + +if test "$args_ok" = "no"; then + echoerr "Usage: $0 <object> [--static] <undefined symbol file...>" + exit 1 +fi + +if test "$hostos" = "solaris"; then + objdumpbin=/usr/sfw/bin/gobjdump + grepbin=/usr/sfw/bin/ggrep +elif test "$hostos" = "linux"; then + objdumpbin=`which objdump` + grepbin=`which grep` +else + echoerr "$0: '$hostos' not a valid hostos string. supported 'linux' 'solaris'" + exit 1 +fi + +command="-T" +if test "$static" = "--static"; then + command="-t" +fi + +if test ! -x "${objdumpbin}"; then + echoerr "${0}: '${objdumpbin}' not found or not executable." + exit 1 +fi + +undefined=`"${objdumpbin}" ${command} "${target}" | kmk_sed -n 's/.*\*UND\*.*\s\([:graph:]*\)/\1/p'` +for i in "${@}"; do + undefined=`echo "${undefined}" | "${grepbin}" -w -v -f "${i}"` +done +num_undef=`echo $undefined | wc -w` + +if test $num_undef -ne 0; then + echoerr "${0}: following symbols not defined in the files ${@}:" + echoerr "${undefined}" + exit 1 +fi +# Return code +exit 0 + diff --git a/src/bldprogs/deftoimp.sed b/src/bldprogs/deftoimp.sed new file mode 100644 index 00000000..c4d0793b --- /dev/null +++ b/src/bldprogs/deftoimp.sed @@ -0,0 +1,66 @@ +# $Id: deftoimp.sed $ +## @file +# SED script for generating a dummy .c from a windows .def file. +# + +# +# Copyright (C) 2006-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# SPDX-License-Identifier: GPL-3.0-only +# + +# +# Remove comments and space. Skip empty lines. +# +s/;.*$//g +s/^[[:space:]][[:space:]]*//g +s/[[:space:]][[:space:]]*$//g +/^$/d + +# Handle text after EXPORTS +/EXPORTS/,//{ +s/^EXPORTS$// +/^$/b end + + +/[[:space:]]DATA$/b data + +# +# Function export +# +:code +s/^\(.*\)$/EXPORT\nvoid \1(void);\nvoid \1(void){}/ +b end + + +# +# Data export +# +:data +s/^\(.*\)[[:space:]]*DATA$/EXPORT_DATA void *\1 = (void *)0;/ +b end + +} +d +b end + + +# next expression +:end + diff --git a/src/bldprogs/filesplitter.cpp b/src/bldprogs/filesplitter.cpp new file mode 100644 index 00000000..90c7dbe7 --- /dev/null +++ b/src/bldprogs/filesplitter.cpp @@ -0,0 +1,388 @@ +/* $Id: filesplitter.cpp $ */ +/** @file + * File splitter - Splits a text file according to ###### markers in it. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <sys/types.h> +#include <sys/stat.h> +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> + +#include <iprt/string.h> +#include <iprt/stdarg.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#ifndef S_ISDIR +# define S_ISDIR(a_fMode) ( (S_IFMT & (a_fMode)) == S_IFDIR ) +#endif + + +/** + * Calculates the line number for a file position. + * + * @returns Line number. + * @param pcszContent The file content. + * @param pcszPos The current position. + */ +static unsigned long lineNumber(const char *pcszContent, const char *pcszPos) +{ + unsigned long cLine = 0; + while ( *pcszContent + && (uintptr_t)pcszContent < (uintptr_t)pcszPos) + { + pcszContent = strchr(pcszContent, '\n'); + if (!pcszContent) + break; + ++cLine; + ++pcszContent; + } + + return cLine; +} + + +/** + * Writes an error message. + * + * @returns RTEXITCODE_FAILURE. + * @param pcszFormat Error message. + * @param ... Format argument referenced in the message. + */ +static int printErr(const char *pcszFormat, ...) +{ + va_list va; + + fprintf(stderr, "filesplitter: "); + va_start(va, pcszFormat); + vfprintf(stderr, pcszFormat, va); + va_end(va); + + return RTEXITCODE_FAILURE; +} + + +/** + * Opens the makefile list for writing. + * + * @returns Exit code. + * @param pcszPath The path to the file. + * @param pcszVariableName The make variable name. + * @param ppFile Where to return the file stream. + */ +static int openMakefileList(const char *pcszPath, const char *pcszVariableName, FILE **ppFile) +{ + *ppFile = NULL; + + FILE *pFile= fopen(pcszPath, "w"); + if (!pFile) +#ifdef _MSC_VER + return printErr("Failed to open \"%s\" for writing the file list: %s (win32: %d)\n", + pcszPath, strerror(errno), _doserrno); +#else + return printErr("Failed to open \"%s\" for writing the file list: %s\n", pcszPath, strerror(errno)); +#endif + + if (fprintf(pFile, "%s := \\\n", pcszVariableName) <= 0) + { + fclose(pFile); + return printErr("Error writing to the makefile list.\n"); + } + + *ppFile = pFile; + return 0; +} + + +/** + * Adds the given file to the makefile list. + * + * @returns Exit code. + * @param pFile The file stream of the makefile list. + * @param pszFilename The file name to add. + */ +static int addFileToMakefileList(FILE *pFile, char *pszFilename) +{ + if (pFile) + { + char *pszSlash = pszFilename; + while ((pszSlash = strchr(pszSlash, '\\')) != NULL) + *pszSlash++ = '/'; + + if (fprintf(pFile, "\t%s \\\n", pszFilename) <= 0) + return printErr("Error adding file to makefile list.\n"); + } + return 0; +} + + +/** + * Closes the makefile list. + * + * @returns Exit code derived from @a rc. + * @param pFile The file stream of the makefile list. + * @param rc The current exit code. + */ +static int closeMakefileList(FILE *pFile, int rc) +{ + fprintf(pFile, "\n\n"); + if (fclose(pFile)) + return printErr("Error closing the file list file: %s\n", strerror(errno)); + return rc; +} + + +/** + * Reads in a file. + * + * @returns Exit code. + * @param pcszFile The path to the file. + * @param ppszFile Where to return the buffer. + * @param pcchFile Where to return the file size. + */ +static int readFile(const char *pcszFile, char **ppszFile, size_t *pcchFile) +{ + FILE *pFile; + struct stat FileStat; + int rc; + + if (stat(pcszFile, &FileStat)) + return printErr("Failed to stat \"%s\": %s\n", pcszFile, strerror(errno)); + + pFile = fopen(pcszFile, "r"); + if (!pFile) + return printErr("Failed to open \"%s\": %s\n", pcszFile, strerror(errno)); + + *ppszFile = (char *)malloc(FileStat.st_size + 1); + if (*ppszFile) + { + errno = 0; + size_t cbRead = fread(*ppszFile, 1, FileStat.st_size, pFile); + if ( cbRead <= (size_t)FileStat.st_size + && (cbRead > 0 || !ferror(pFile)) ) + { + if (ftell(pFile) == FileStat.st_size) /* (\r\n vs \n in the DOS world) */ + { + (*ppszFile)[cbRead] = '\0'; + if (pcchFile) + *pcchFile = (size_t)cbRead; + + fclose(pFile); + return 0; + } + } + + rc = printErr("Error reading \"%s\": %s\n", pcszFile, strerror(errno)); + free(*ppszFile); + *ppszFile = NULL; + } + else + rc = printErr("Failed to allocate %lu bytes\n", (unsigned long)(FileStat.st_size + 1)); + fclose(pFile); + return rc; +} + + +/** + * Checks whether the sub-file already exists and has the exact + * same content. + * + * @returns @c true if the existing file matches exactly, otherwise @c false. + * @param pcszFilename The path to the file. + * @param pcszSubContent The content to write. + * @param cchSubContent The length of the content. + */ +static bool compareSubFile(const char *pcszFilename, const char *pcszSubContent, size_t cchSubContent) +{ + struct stat FileStat; + if (stat(pcszFilename, &FileStat)) + return false; + if ((size_t)FileStat.st_size < cchSubContent) + return false; + + size_t cchExisting; + char *pszExisting; + int rc = readFile(pcszFilename, &pszExisting, &cchExisting); + if (rc) + return false; + + bool fRc = cchExisting == cchSubContent + && !memcmp(pcszSubContent, pszExisting, cchSubContent); + free(pszExisting); + + return fRc; +} + + +/** + * Writes out a sub-file. + * + * @returns exit code. + * @param pcszFilename The path to the sub-file. + * @param pcszSubContent The content of the file. + * @param cchSubContent The size of the content. + */ +static int writeSubFile(const char *pcszFilename, const char *pcszSubContent, size_t cchSubContent) +{ + FILE *pFile = fopen(pcszFilename, "w"); + if (!pFile) +#ifdef _MSC_VER + return printErr("Failed to open \"%s\" for writing: %s (win32: %d)\n", pcszFilename, strerror(errno), _doserrno); +#else + return printErr("Failed to open \"%s\" for writing: %s\n", pcszFilename, strerror(errno)); +#endif + + errno = 0; + int rc = 0; + if (fwrite(pcszSubContent, cchSubContent, 1, pFile) != 1) + rc = printErr("Error writing \"%s\": %s\n", pcszFilename, strerror(errno)); + + errno = 0; + int rc2 = fclose(pFile); + if (rc2 == EOF) + rc = printErr("Error closing \"%s\": %s\n", pcszFilename, strerror(errno)); + return rc; +} + + +/** + * Does the actual file splitting. + * + * @returns exit code. + * @param pcszOutDir Path to the output directory. + * @param pcszContent The content to split up. + * @param pFileList The file stream of the makefile list. Can be NULL. + */ +static int splitFile(const char *pcszOutDir, const char *pcszContent, FILE *pFileList) +{ + static char const s_szBeginMarker[] = "\n// ##### BEGINFILE \""; + static char const s_szEndMarker[] = "\n// ##### ENDFILE"; + const size_t cchBeginMarker = sizeof(s_szBeginMarker) - 1; + const char *pcszSearch = pcszContent; + size_t const cchOutDir = strlen(pcszOutDir); + unsigned long cFilesWritten = 0; + unsigned long cFilesUnchanged = 0; + int rc = 0; + + do + { + /* find begin marker */ + const char *pcszBegin = strstr(pcszSearch, s_szBeginMarker); + if (!pcszBegin) + break; + + /* find line after begin marker */ + const char *pcszLineAfterBegin = strchr(pcszBegin + cchBeginMarker, '\n'); + if (!pcszLineAfterBegin) + return printErr("No newline after begin-file marker found.\n"); + ++pcszLineAfterBegin; + + /* find filename end quote in begin marker line */ + const char *pcszStartFilename = pcszBegin + cchBeginMarker; + const char *pcszEndQuote = (const char *)memchr(pcszStartFilename, '\"', pcszLineAfterBegin - pcszStartFilename); + if (!pcszEndQuote) + return printErr("Can't parse filename after begin-file marker (line %lu).\n", + lineNumber(pcszContent, s_szBeginMarker)); + + /* find end marker */ + const char *pcszEnd = strstr(pcszLineAfterBegin, s_szEndMarker); + if (!pcszEnd) + return printErr("No matching end-line marker for begin-file marker found (line %lu).\n", + lineNumber(pcszContent, s_szBeginMarker)); + + /* construct output filename */ + size_t cchFilename = pcszEndQuote - pcszStartFilename; + char *pszFilename = (char *)malloc(cchOutDir + 1 + cchFilename + 1); + if (!pszFilename) + return printErr("Can't allocate memory for filename.\n"); + + memcpy(pszFilename, pcszOutDir, cchOutDir); + pszFilename[cchOutDir] = '/'; + memcpy(pszFilename + cchOutDir + 1, pcszStartFilename, cchFilename); + pszFilename[cchFilename + 1 + cchOutDir] = '\0'; + + /* Write the file only if necessary. */ + if (compareSubFile(pszFilename, pcszLineAfterBegin, pcszEnd - pcszLineAfterBegin)) + cFilesUnchanged++; + else + { + rc = writeSubFile(pszFilename, pcszLineAfterBegin, pcszEnd - pcszLineAfterBegin); + cFilesWritten++; + } + + if (!rc) + rc = addFileToMakefileList(pFileList, pszFilename); + + free(pszFilename); + + pcszSearch = pcszEnd; + } while (rc == 0 && pcszSearch); + + printf("filesplitter: Out of %lu files: %lu rewritten, %lu unchanged. (%s)\n", + cFilesWritten + cFilesUnchanged, cFilesWritten, cFilesUnchanged, pcszOutDir); + return rc; +} + + +int main(int argc, char *argv[]) +{ + int rc = 0; + + if (argc == 3 || argc == 5) + { + struct stat DirStat; + if ( stat(argv[2], &DirStat) == 0 + && S_ISDIR(DirStat.st_mode)) + { + char *pszContent; + rc = readFile(argv[1], &pszContent, NULL); + if (!rc) + { + FILE *pFileList = NULL; + if (argc == 5) + rc = openMakefileList(argv[3], argv[4], &pFileList); + + if (argc < 4 || pFileList) + rc = splitFile(argv[2], pszContent, pFileList); + + if (pFileList) + rc = closeMakefileList(pFileList, rc); + free(pszContent); + } + } + else + rc = printErr("Given argument \"%s\" is not a valid directory.\n", argv[2]); + } + else + rc = printErr("Syntax error: usage: filesplitter <infile> <outdir> [<list.kmk> <kmkvar>]\n"); + return rc; +} diff --git a/src/bldprogs/genalias.cpp b/src/bldprogs/genalias.cpp new file mode 100644 index 00000000..073f169f --- /dev/null +++ b/src/bldprogs/genalias.cpp @@ -0,0 +1,500 @@ +/* $Id: genalias.cpp $ */ +/** @file + * genalias - generate a number of alias objects. + * + * @note The code has its origin with kLIBC and was added to VBox by the author. + */ + +/* + * Copyright (C) 2022-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <stdarg.h> +#include <stdio.h> +#include <iprt/stdint.h> +#include <string.h> +#include <time.h> +#include <assert.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#if defined(RT_OS_DARWIN) || (defined(RT_ARCH_X86) && (defined(RT_OS_WINDOWS) || defined(RT_OS_OS2))) +# define GENALIAS_UNDERSCORED 1 +#else +# define GENALIAS_UNDERSCORED 0 +#endif + + + +static int Error(const char *pszFormat, ...) +{ + va_list va; + fprintf(stderr, "genalias: error: "); + va_start(va, pszFormat); + vfprintf(stderr, pszFormat, va); + va_end(va); + return 1; +} + +static int SyntaxError(const char *pszFormat, ...) +{ + va_list va; + fprintf(stderr, "genalias: syntax error: "); + va_start(va, pszFormat); + vfprintf(stderr, pszFormat, va); + va_end(va); + return 1; +} + +static int WriteAliasObjectAOUT(FILE *pOutput, const char *pszAlias, const char *pszReal) +{ +#pragma pack(1) + /* ASSUMES 32-bit target. */ + struct AoutHdr + { + uint32_t a_info; + uint32_t a_text; + uint32_t a_data; + uint32_t a_bss; + uint32_t a_syms; + uint32_t a_entry; + uint32_t a_trsize; + uint32_t a_drsize; + } Hdr; +#define OMAGIC 0407 + struct AoutSym + { + uint32_t n_strx; + uint8_t n_type; + int8_t n_other; + uint16_t n_desc; + uint32_t n_value; + } Sym; +#define N_EXT 1 +#define N_INDR 10 +#pragma pack() + const uint32_t cchAlias = (uint32_t)strlen(pszAlias); + const uint32_t cchReal = (uint32_t)strlen(pszReal); + uint32_t u32; + + /* write the header. */ + memset(&Hdr, 0, sizeof(Hdr)); + Hdr.a_info = OMAGIC; + Hdr.a_syms = 2 * sizeof(Sym); + if (fwrite(&Hdr, sizeof(Hdr), 1, pOutput) != 1) + return -2; + + /* The alias symbol. */ + Sym.n_strx = 4 + cchReal + 1 + GENALIAS_UNDERSCORED; + Sym.n_type = N_INDR | N_EXT; + Sym.n_other = 0; + Sym.n_desc = 0; + Sym.n_value = 0; + if (fwrite(&Sym, sizeof(Sym), 1, pOutput) != 1) + return -2; + + /* The real symbol. */ + Sym.n_strx = 4; + Sym.n_type = N_EXT; + Sym.n_other = 0; + Sym.n_desc = 0; + Sym.n_value = 0; + if (fwrite(&Sym, sizeof(Sym), 1, pOutput) != 1) + return -2; + + /* the string table. */ + u32 = 4 + cchReal + 1 + cchAlias + 1 + GENALIAS_UNDERSCORED * 2; + if (fwrite(&u32, 4, 1, pOutput) != 1) + return -2; +#if GENALIAS_UNDERSCORED + if (fputc('_', pOutput) == EOF) + return -2; +#endif + if (fwrite(pszReal, cchReal + 1, 1, pOutput) != 1) + return -2; +#if GENALIAS_UNDERSCORED + if (fputc('_', pOutput) == EOF) + return -2; +#endif + if (fwrite(pszAlias, cchAlias + 1, 1, pOutput) != 1) + return -2; + return 0; +} + +static int WriteAliasObjectCOFF(FILE *pOutput, const char *pszAlias, const char *pszReal, bool fUnderscored) +{ +#pragma pack(1) + struct CoffHdr + { + uint16_t Machine; + uint16_t NumberOfSections; + uint32_t TimeDateStamp; + uint32_t PointerToSymbolTable; + uint32_t NumberOfSymbols; + uint16_t SizeOfOptionalHeader; + uint16_t Characteristics; + } Hdr; + struct CoffShdr + { + char Name[8]; + uint32_t VirtualSize; + uint32_t VirtualAddress; + uint32_t SizeOfRawData; + uint32_t PointerToRawData; + uint32_t PointerToRelocations; + uint32_t PointerToLinenumbers; + uint16_t NumberOfRelocations; + uint16_t NumberOfLinenumbers; + uint32_t Characteristics; + } Shdr; +#define IMAGE_SCN_LNK_INFO 0x200 +#define IMAGE_SCN_LNK_REMOVE 0x800 + struct CoffSym + { + union + { + char ShortName[8]; + struct + { + uint32_t Zeros; + uint32_t Offset; + } s; + } u; + uint32_t Value; + uint16_t SectionNumber; + uint16_t Type; + uint8_t StorageClass; + uint8_t NumberOfAuxSymbols; + } Sym; +#define IMAGE_SYM_UNDEFINED 0 +#define IMAGE_SYM_TYPE_NULL 0 +#define IMAGE_SYM_CLASS_EXTERNAL 2 +#define IMAGE_SYM_CLASS_WEAK_EXTERNAL 105 + struct CoffAuxWeakExt + { + uint32_t TagIndex; + uint32_t Characteristics; + uint8_t padding[10]; + } Aux; +#define IMAGE_WEAK_EXTERN_SEARCH_ALIAS 3 + assert(sizeof(Hdr) == 20); assert(sizeof(Sym) == 18); assert(sizeof(Aux) == sizeof(Sym)); +#pragma pack() + const uint32_t cchAlias = (uint32_t)strlen(pszAlias); + const uint32_t cchReal = (uint32_t)strlen(pszReal); + uint32_t u32; + + /* write the header. */ + Hdr.Machine = 0 /*unknown*/; //0x14c /* i386 */; + Hdr.NumberOfSections = 1; + Hdr.TimeDateStamp = time(NULL); + Hdr.PointerToSymbolTable = sizeof(Hdr) + sizeof(Shdr); + Hdr.NumberOfSymbols = 3; + Hdr.SizeOfOptionalHeader = 0; + Hdr.Characteristics = 0; + if (fwrite(&Hdr, sizeof(Hdr), 1, pOutput) != 1) + return -2; + + /* The directive section. */ + if (Hdr.NumberOfSections == 1) + { + memset(&Shdr, 0, sizeof(Shdr)); + memcpy(Shdr.Name, ".drectve", 8); + Shdr.Characteristics = IMAGE_SCN_LNK_REMOVE | IMAGE_SCN_LNK_INFO; + if (fwrite(&Shdr, sizeof(Shdr), 1, pOutput) != 1) + return -2; + } + + /* The real symbol. */ + memset(&Sym, 0, sizeof(Sym)); + Sym.u.s.Offset = 4; + Sym.SectionNumber = IMAGE_SYM_UNDEFINED; + Sym.Type = IMAGE_SYM_TYPE_NULL; + Sym.StorageClass = IMAGE_SYM_CLASS_EXTERNAL; + if (fwrite(&Sym, sizeof(Sym), 1, pOutput) != 1) + return -2; + + /* The alias symbol. */ + memset(&Sym, 0, sizeof(Sym)); + Sym.u.s.Offset = fUnderscored + cchReal + 1 + 4; + Sym.SectionNumber = IMAGE_SYM_UNDEFINED; + Sym.Type = IMAGE_SYM_TYPE_NULL; + Sym.StorageClass = IMAGE_SYM_CLASS_WEAK_EXTERNAL; + Sym.NumberOfAuxSymbols = 1; + if (fwrite(&Sym, sizeof(Sym), 1, pOutput) != 1) + return -2; + + /* aux entry for that. */ + memset(&Aux, 0, sizeof(Aux)); + Aux.TagIndex = 0; + Aux.Characteristics = IMAGE_WEAK_EXTERN_SEARCH_ALIAS; + if (fwrite(&Aux, sizeof(Aux), 1, pOutput) != 1) + return -2; + + /* the string table. */ + u32 = 4 + cchReal + 1 + cchAlias + 1 + fUnderscored * 2; + if (fwrite(&u32, 4, 1, pOutput) != 1) + return -2; + if (fUnderscored) + if (fputc('_', pOutput) == EOF) + return -2; + if (fwrite(pszReal, cchReal + 1, 1, pOutput) != 1) + return -2; + if (fUnderscored) + if (fputc('_', pOutput) == EOF) + return -2; + if (fwrite(pszAlias, cchAlias + 1, 1, pOutput) != 1) + return -2; + return 0; +} + +static int WriteAliasObjectTargetCOFF(FILE *pOutput, const char *pszAlias, const char *pszReal) +{ + return WriteAliasObjectCOFF(pOutput, pszAlias, pszReal, GENALIAS_UNDERSCORED); +} + +static int WriteAliasObjectX86COFF(FILE *pOutput, const char *pszAlias, const char *pszReal) +{ + return WriteAliasObjectCOFF(pOutput, pszAlias, pszReal, true /*fUnderscored*/); +} + +static int WriteAliasObjectAmd64COFF(FILE *pOutput, const char *pszAlias, const char *pszReal) +{ + return WriteAliasObjectCOFF(pOutput, pszAlias, pszReal, false /*fUnderscored*/); +} + + +static int WriteAliasObjectELF(FILE *pOutput, const char *pszAlias, const char *pszReal) +{ + RT_NOREF(pOutput, pszAlias, pszReal); + fprintf(stderr, "ELF does not support proper aliasing, only option seems to be adding weak symbols with the strong one.\n"); + return -1; +} + +static int WriteAliasObjectOMF(FILE *pOutput, const char *pszAlias, const char *pszReal) +{ + const uint32_t cchAlias = (uint32_t)strlen(pszAlias); + const uint32_t cchReal = (uint32_t)strlen(pszReal); + //const uint32_t cchName = cchAlias > 250 ? 250 : cchAlias; + uint32_t cch; + + if (cchReal >= 250) + return Error("Symbol '%s' is too long!\n", pszReal); + if (cchAlias >= 250) + return Error("Symbol '%s' is too long!\n", pszAlias); + + /* THEADR */ + fputc(0x80, pOutput); + cch = cchAlias + 2; + fputc(cch & 0xff, pOutput); + fputc(cch >> 8, pOutput); + fputc(cchAlias, pOutput); + fwrite(pszAlias, cchAlias, 1, pOutput); + fputc(0, pOutput); /* CRC */ + + /* ALIAS */ + fputc(0xc6, pOutput); + cch = cchAlias + 1 + cchReal + 1 + GENALIAS_UNDERSCORED * 2 + 1; + fputc(cch & 0xff, pOutput); + fputc(cch >> 8, pOutput); + fputc(cchAlias + GENALIAS_UNDERSCORED, pOutput); + if (GENALIAS_UNDERSCORED) + fputc('_', pOutput); + fwrite(pszAlias, cchAlias, 1, pOutput); + fputc(cchReal + GENALIAS_UNDERSCORED, pOutput); + if (GENALIAS_UNDERSCORED) + fputc('_', pOutput); + fwrite(pszReal, cchReal, 1, pOutput); + fputc(0, pOutput); /* CRC */ + + /* MODEND32 */ + fputc(0x8b, pOutput); + fputc(2, pOutput); + fputc(0, pOutput); + fputc(0, pOutput); + if (fputc(0, pOutput) == EOF) /* CRC */ + return -2; + return 0; +} + +static int WriteAliasObjectMACHO(FILE *pOutput, const char *pszAlias, const char *pszReal) +{ + RT_NOREF(pOutput, pszAlias, pszReal); + fprintf(stderr, "Mach-O support not implemented yet\n"); + return -1; +} + +static int CreateAlias(char *pszBuf, size_t cchInput, char *pszFileBuf, char *pszFilename, + int (*pfnWriter)(FILE *, const char *, const char *)) +{ + char *pszAlias = pszBuf; + char *pszReal; + char *pszFile; + FILE *pOutput; + int rc; + RT_NOREF(cchInput); + + /* + * Parse input. + */ + pszReal = strchr(pszBuf, '='); + if (!pszReal) + return Error("Malformed request: '%s'\n", pszBuf); + *pszReal++ = '\0'; + + pszFile = strchr(pszReal, '='); + if (pszFile) + { + *pszFile++ = '\0'; + strcpy(pszFilename, pszFile); + } + else + strcat(strcpy(pszFilename, pszAlias), ".o"); + + /* + * Open the output file. + */ + pOutput = fopen(pszFileBuf, "wb"); + if (!pOutput) + return Error("Failed to open '%s' for writing!\n", pszFileBuf); + rc = pfnWriter(pOutput, pszAlias, pszReal); + if (rc == -2) + rc = Error("Write error writing '%s'!\n", pszFileBuf); + fclose(pOutput); + return rc; +} + + +static int Syntax(void) +{ + printf("syntax: genalias -f <format> -D <output-dir> alias=real[=file] [alias2=real2[=file2] [..]]\n" + " OR\n" + " genalias -f <format> -D <output-dir> -r <response-file>\n" + "\n" + "Format can be: aout, coff or omf\n" + "The responsefile is a single argument per line.\n"); + return 1; +} + + +int main(int argc, char **argv) +{ + static char s_szBuf[4096]; + static char s_szFile[1024 + sizeof(s_szBuf)]; + int (*pfnWriter)(FILE *pOutput, const char *pszAlias, const char *pszReal); + char *pszFilename; + int i; + int rc; + + /* + * Parse arguments. + */ + if (argc <= 5) + return Syntax(); + if (strcmp(argv[1], "-f")) + return SyntaxError("Expected -f as the 1st argument.\n"); + if (!strcmp(argv[2], "aout")) + pfnWriter = WriteAliasObjectAOUT; + else if (!strcmp(argv[2], "coff")) + pfnWriter = WriteAliasObjectTargetCOFF; + else if (!strcmp(argv[2], "coff.x86")) + pfnWriter = WriteAliasObjectX86COFF; + else if (!strcmp(argv[2], "coff.amd64")) + pfnWriter = WriteAliasObjectAmd64COFF; + else if (!strcmp(argv[2], "elf")) + pfnWriter = WriteAliasObjectELF; + else if (!strcmp(argv[2], "omf")) + pfnWriter = WriteAliasObjectOMF; + else if (!strcmp(argv[2], "macho")) + pfnWriter = WriteAliasObjectMACHO; + else + return SyntaxError("Unknown format '%s'.\n", argv[2]); + if (strcmp(argv[3], "-D")) + return SyntaxError("Expected -D as the 3rd argument\n"); + if (!*argv[4]) + return SyntaxError("The output directory name is empty.\n"); + size_t cchFile = strlen(argv[4]); + if (cchFile > sizeof(s_szFile) - sizeof(s_szBuf)) + return SyntaxError("The output directory name is too long.\n"); + memcpy(s_szFile, argv[4], cchFile); + s_szFile[cchFile++] = '/'; + pszFilename = &s_szFile[cchFile]; + + /* anything to do? */ + if (argc == 5) + return 0; + + rc = 0; + if (!strcmp(argv[5], "-r")) + { + /* + * Responsefile. + */ + FILE *pResp; + if (argc <= 6) + return SyntaxError("Missing response file name\n"); + pResp = fopen(argv[6], "rt"); + if (!pResp) + return Error("Failed to open '%s' for reading.\n", argv[6]); + + i = 0; + while (fgets(s_szBuf, sizeof(s_szBuf), pResp)) + { + size_t cch = strlen(s_szBuf); + i++; + if (cch == sizeof(s_szBuf) && s_szBuf[cch - 1] != '\n') + { + rc = Error("Line %d is too long!\n", i); + break; + } + if (cch && s_szBuf[cch - 1] == '\n') + s_szBuf[--cch] = '\0'; + rc = CreateAlias(s_szBuf, cch, s_szFile, pszFilename, pfnWriter); + if (rc) + break; + } + + fclose(pResp); + } + else + { + /* + * Alias descriptors. + */ + for (i = 5; i < argc; i++) + { + size_t cch = strlen(argv[i]); + if (cch >= sizeof(s_szBuf)) + return SyntaxError("Argument %d is too long\n", i); + memcpy(s_szBuf, argv[i], cch + 1); + rc = CreateAlias(s_szBuf, cch, s_szFile, pszFilename, pfnWriter); + if (rc) + break; + } + } + return rc; +} + diff --git a/src/bldprogs/preload.cpp b/src/bldprogs/preload.cpp new file mode 100644 index 00000000..5285047c --- /dev/null +++ b/src/bldprogs/preload.cpp @@ -0,0 +1,222 @@ +/* $Id: preload.cpp $ */ +/** @file + * bin2c - Binary 2 C Structure Converter. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#ifdef RT_OS_WINDOWS +# include <Windows.h> +#else +# include <sys/mman.h> +# include <sys/stat.h> +# include <fcntl.h> +# include <unistd.h> +# include <errno.h> +#endif +#include <stdio.h> +#include <string.h> + + +static int load(const char *pszImage) +{ +#ifdef RT_OS_WINDOWS + HANDLE hFile = CreateFile(pszImage, + GENERIC_READ, + FILE_SHARE_READ, + NULL /*pSecurityAttributes*/, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL /*hTemplateFile*/); + if (hFile == INVALID_HANDLE_VALUE) + { + printf("error: CreateFile('%s',): %d\n", pszImage, GetLastError()); + return 1; + } + + DWORD cbHigh = 0; + DWORD cbLow = GetFileSize(hFile, &cbHigh); + size_t cbFile = cbLow != INVALID_FILE_SIZE + ? cbHigh == 0 + ? cbLow + : ~(DWORD)0 / 4 + : 64; + + HANDLE hMap = CreateFileMapping(hFile, + NULL /*pAttributes*/, + PAGE_READONLY | SEC_COMMIT, + 0 /*dwMaximumSizeHigh -> file size*/, + 0 /*dwMaximumSizeLow -> file size*/, + NULL /*pName*/); + if (hMap == INVALID_HANDLE_VALUE) + printf("error: CreateFile('%s',): %d\n", pszImage, GetLastError()); + CloseHandle(hFile); + if (hMap == INVALID_HANDLE_VALUE) + return 1; + + void *pvWhere = MapViewOfFile(hMap, + FILE_MAP_READ, + 0 /*dwFileOffsetHigh*/, + 0 /*dwFileOffsetLow*/, + 0 /*dwNumberOfBytesToMap - file size */); + if (!pvWhere) + { + printf("error: MapViewOfView('%s',): %d\n", pszImage, GetLastError()); + CloseHandle(hMap); + return 1; + } + +#else + int fd = open(pszImage, O_RDONLY, 0); + if (fd < 0) + { + printf("error: open('%s',): %d\n", pszImage, errno); + return 1; + } + + struct stat st; + memset(&st, 0, sizeof(st)); + if (fstat(fd, &st)) + st.st_size = 64; + size_t cbFile = st.st_size < ~(size_t)0 + ? (size_t)st.st_size + : ~(size_t)0 / 4; + + void *pvWhere = mmap(NULL /*addr*/, cbFile, PROT_READ, MAP_FILE | MAP_PRIVATE, fd, 0 /*offset*/); + if (pvWhere == MAP_FAILED) + printf("error: mmap(,%lu,)/'%s': %d\n", (unsigned long)cbFile, pszImage, errno); + close(fd); + if (pvWhere == MAP_FAILED) + return 1; + +#endif + + /* Touch the whole image... do a dummy crc to keep the optimizer from begin + smart with us. */ + unsigned char *puchFile = (unsigned char *)pvWhere; + size_t off = 0; + unsigned int uCrc = 0; + while (off < cbFile) + uCrc += puchFile[off++]; + printf("info: %p/%#lx/%#x - %s\n", pvWhere, (unsigned long)cbFile, (unsigned char)uCrc, pszImage); + + return 0; +} + +static int usage(const char *argv0) +{ + printf("Generic executable image preloader.\n" + "Usage: %s [dll|exe|file []]\n", argv0); + return 1; +} + +int main(int argc, char **argv) +{ + /* + * Check for options. + */ + for (int i = 1; i < argc; i++) + { + if (argv[i][0] == '-') + { + if ( argv[i][1] == '-' + && argv[i][2] == '\0') + break; + if ( !strcmp(argv[i], "--help") + || !strcmp(argv[i], "-help") + || !strcmp(argv[i], "-h") + || !strcmp(argv[i], "-?")) + { + usage(argv[0]); + return 1; + } + if ( !strcmp(argv[i], "--version") + || !strcmp(argv[i], "-V")) + { + printf("$Revision: 155244 $\n"); + return 0; + } + fprintf(stderr, "syntax error: unknown option '%s'\n", argv[i]); + return 1; + } + } + if (argc <= 1) + return usage(argv[0]); + + /* + * Do the loading. + */ + for (int i = 1; i < argc; i++) + { + if (!strcmp(argv[i], "--")) + continue; + if (argv[i][0] == '@') + { + FILE *pFile = fopen(&argv[i][1], "r"); + if (pFile) + { + char szLine[4096]; + while (fgets(szLine, sizeof(szLine), pFile)) + { + char *psz = szLine; + while (*psz == ' ' || *psz == '\t') + psz++; + size_t off = strlen(psz); + while ( off > 0 + && ( psz[off - 1] == ' ' + || psz[off - 1] == '\t' + || psz[off - 1] == '\n' + || psz[off - 1] == '\r') + ) + psz[--off] = '\0'; + + if (*psz && *psz != '#') + load(psz); + } + fclose(pFile); + } + else + fprintf(stderr, "error: fopen('%s','r'): %d\n", &argv[i][1], errno); + } + else + load(argv[i]); + } + + /* + * Sleep for ever. + */ + for (;;) + { +#ifdef RT_OS_WINDOWS + Sleep(3600*1000); +#else + sleep(3600); +#endif + } + + return 0; +} diff --git a/src/bldprogs/scm.cpp b/src/bldprogs/scm.cpp new file mode 100644 index 00000000..0b093255 --- /dev/null +++ b/src/bldprogs/scm.cpp @@ -0,0 +1,3269 @@ +/* $Id: scm.cpp $ */ +/** @file + * IPRT Testcase / Tool - Source Code Massager. + */ + +/* + * Copyright (C) 2010-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/assert.h> +#include <iprt/ctype.h> +#include <iprt/dir.h> +#include <iprt/env.h> +#include <iprt/file.h> +#include <iprt/err.h> +#include <iprt/getopt.h> +#include <iprt/initterm.h> +#include <iprt/mem.h> +#include <iprt/message.h> +#include <iprt/param.h> +#include <iprt/path.h> +#include <iprt/process.h> +#include <iprt/stream.h> +#include <iprt/string.h> + +#include "scm.h" +#include "scmdiff.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The name of the settings files. */ +#define SCM_SETTINGS_FILENAME ".scm-settings" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ + +/** + * Option identifiers. + * + * @note The first chunk, down to SCMOPT_TAB_SIZE, are alternately set & + * clear. So, the option setting a flag (boolean) will have an even + * number and the one clearing it will have an odd number. + * @note Down to SCMOPT_LAST_SETTINGS corresponds exactly to SCMSETTINGSBASE. + */ +typedef enum SCMOPT +{ + SCMOPT_CONVERT_EOL = 10000, + SCMOPT_NO_CONVERT_EOL, + SCMOPT_CONVERT_TABS, + SCMOPT_NO_CONVERT_TABS, + SCMOPT_FORCE_FINAL_EOL, + SCMOPT_NO_FORCE_FINAL_EOL, + SCMOPT_FORCE_TRAILING_LINE, + SCMOPT_NO_FORCE_TRAILING_LINE, + SCMOPT_STRIP_TRAILING_BLANKS, + SCMOPT_NO_STRIP_TRAILING_BLANKS, + SCMOPT_STRIP_TRAILING_LINES, + SCMOPT_NO_STRIP_TRAILING_LINES, + SCMOPT_FIX_FLOWER_BOX_MARKERS, + SCMOPT_NO_FIX_FLOWER_BOX_MARKERS, + SCMOPT_FIX_HEADER_GUARDS, + SCMOPT_NO_FIX_HEADER_GUARDS, + SCMOPT_PRAGMA_ONCE, + SCMOPT_NO_PRAGMA_ONCE, + SCMOPT_FIX_HEADER_GUARD_ENDIF, + SCMOPT_NO_FIX_HEADER_GUARD_ENDIF, + SCMOPT_ENDIF_GUARD_COMMENT, + SCMOPT_NO_ENDIF_GUARD_COMMENT, + SCMOPT_GUARD_PREFIX, + SCMOPT_GUARD_RELATIVE_TO_DIR, + SCMOPT_FIX_TODOS, + SCMOPT_NO_FIX_TODOS, + SCMOPT_FIX_ERR_H, + SCMOPT_NO_FIX_ERR_H, + SCMOPT_ONLY_GUEST_HOST_PAGE, + SCMOPT_NO_ASM_MEM_PAGE_USE, + SCMOPT_UNRESTRICTED_ASM_MEM_PAGE_USE, + SCMOPT_NO_PAGE_RESTRICTIONS, + SCMOPT_NO_RC_USE, + SCMOPT_UNRESTRICTED_RC_USE, + SCMOPT_STANDARIZE_KMK, + SCMOPT_NO_STANDARIZE_KMK, + SCMOPT_UPDATE_COPYRIGHT_YEAR, + SCMOPT_NO_UPDATE_COPYRIGHT_YEAR, + SCMOPT_EXTERNAL_COPYRIGHT, + SCMOPT_NO_EXTERNAL_COPYRIGHT, + SCMOPT_NO_UPDATE_LICENSE, + SCMOPT_LICENSE_OSE_GPL, + SCMOPT_LICENSE_OSE_DUAL_GPL_CDDL, + SCMOPT_LICENSE_OSE_CDDL, + SCMOPT_LICENSE_LGPL, + SCMOPT_LICENSE_MIT, + SCMOPT_LICENSE_BASED_ON_MIT, + SCMOPT_LGPL_DISCLAIMER, + SCMOPT_NO_LGPL_DISCLAIMER, + SCMOPT_MIN_BLANK_LINES_BEFORE_FLOWER_BOX_MARKERS, + SCMOPT_ONLY_SVN_DIRS, + SCMOPT_NOT_ONLY_SVN_DIRS, + SCMOPT_ONLY_SVN_FILES, + SCMOPT_NOT_ONLY_SVN_FILES, + SCMOPT_SET_SVN_EOL, + SCMOPT_DONT_SET_SVN_EOL, + SCMOPT_SET_SVN_EXECUTABLE, + SCMOPT_DONT_SET_SVN_EXECUTABLE, + SCMOPT_SET_SVN_KEYWORDS, + SCMOPT_DONT_SET_SVN_KEYWORDS, + SCMOPT_SKIP_SVN_SYNC_PROCESS, + SCMOPT_DONT_SKIP_SVN_SYNC_PROCESS, + SCMOPT_SKIP_UNICODE_CHECKS, + SCMOPT_DONT_SKIP_UNICODE_CHECKS, + SCMOPT_TAB_SIZE, + SCMOPT_WIDTH, + SCMOPT_FILTER_OUT_DIRS, + SCMOPT_FILTER_FILES, + SCMOPT_FILTER_OUT_FILES, + SCMOPT_TREAT_AS, + SCMOPT_ADD_ACTION, + SCMOPT_DEL_ACTION, + SCMOPT_LAST_SETTINGS = SCMOPT_DEL_ACTION, + // + SCMOPT_CHECK_RUN, + SCMOPT_DIFF_IGNORE_EOL, + SCMOPT_DIFF_NO_IGNORE_EOL, + SCMOPT_DIFF_IGNORE_SPACE, + SCMOPT_DIFF_NO_IGNORE_SPACE, + SCMOPT_DIFF_IGNORE_LEADING_SPACE, + SCMOPT_DIFF_NO_IGNORE_LEADING_SPACE, + SCMOPT_DIFF_IGNORE_TRAILING_SPACE, + SCMOPT_DIFF_NO_IGNORE_TRAILING_SPACE, + SCMOPT_DIFF_SPECIAL_CHARS, + SCMOPT_DIFF_NO_SPECIAL_CHARS, + SCMOPT_HELP_CONFIG, + SCMOPT_HELP_ACTIONS, + SCMOPT_END +} SCMOPT; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +const char g_szTabSpaces[16+1] = " "; +const char g_szAsterisks[255+1] = +"****************************************************************************************************" +"****************************************************************************************************" +"*******************************************************"; +const char g_szSpaces[255+1] = +" " +" " +" "; +static const char g_szProgName[] = "scm"; +static const char *g_pszChangedSuff = ""; +static bool g_fDryRun = true; +static bool g_fDiffSpecialChars = true; +static bool g_fDiffIgnoreEol = false; +static bool g_fDiffIgnoreLeadingWS = false; +static bool g_fDiffIgnoreTrailingWS = false; +static int g_iVerbosity = 2;//99; //0; +uint32_t g_uYear = 0; /**< The current year. */ +/** @name Statistics + * @{ */ +static uint32_t g_cDirsProcessed = 0; +static uint32_t g_cFilesProcessed = 0; +static uint32_t g_cFilesModified = 0; +static uint32_t g_cFilesSkipped = 0; +static uint32_t g_cFilesNotInSvn = 0; +static uint32_t g_cFilesNoRewriters = 0; +static uint32_t g_cFilesBinaries = 0; +static uint32_t g_cFilesRequiringManualFixing = 0; +/** @} */ + +/** The global settings. */ +static SCMSETTINGSBASE const g_Defaults = +{ + /* .fConvertEol = */ true, + /* .fConvertTabs = */ true, + /* .fForceFinalEol = */ true, + /* .fForceTrailingLine = */ false, + /* .fStripTrailingBlanks = */ true, + /* .fStripTrailingLines = */ true, + /* .fFixFlowerBoxMarkers = */ true, + /* .cMinBlankLinesBeforeFlowerBoxMakers = */ 2, + /* .fFixHeaderGuards = */ true, + /* .fPragmaOnce = */ true, + /* .fFixHeaderGuardEndif = */ true, + /* .fEndifGuardComment = */ true, + /* .pszGuardPrefix = */ (char *)"VBOX_INCLUDED_SRC_", + /* .pszGuardRelativeToDir = */ (char *)"{parent}", + /* .fFixTodos = */ true, + /* .fFixErrH = */ true, + /* .fOnlyGuestHostPage = */ false, + /* .fNoASMMemPageUse = */ false, + /* .fOnlyHrcVrcInsteadOfRc */ false, + /* .fStandarizeKmk */ true, + /* .fUpdateCopyrightYear = */ false, + /* .fExternalCopyright = */ false, + /* .fLgplDisclaimer = */ false, + /* .enmUpdateLicense = */ kScmLicense_OseGpl, + /* .fOnlySvnFiles = */ false, + /* .fOnlySvnDirs = */ false, + /* .fSetSvnEol = */ false, + /* .fSetSvnExecutable = */ false, + /* .fSetSvnKeywords = */ false, + /* .fSkipSvnSyncProcess = */ false, + /* .fSkipUnicodeChecks = */ false, + /* .cchTab = */ 8, + /* .cchWidth = */ 130, + /* .fFreeTreatAs = */ false, + /* .pTreatAs = */ NULL, + /* .pszFilterFiles = */ (char *)"", + /* .pszFilterOutFiles = */ (char *)"*.exe|*.com|20*-*-*.log", + /* .pszFilterOutDirs = */ (char *)".svn|.hg|.git|CVS", +}; + +/** Option definitions for the base settings. */ +static RTGETOPTDEF g_aScmOpts[] = +{ + /* rewriters */ + { "--convert-eol", SCMOPT_CONVERT_EOL, RTGETOPT_REQ_NOTHING }, + { "--no-convert-eol", SCMOPT_NO_CONVERT_EOL, RTGETOPT_REQ_NOTHING }, + { "--convert-tabs", SCMOPT_CONVERT_TABS, RTGETOPT_REQ_NOTHING }, + { "--no-convert-tabs", SCMOPT_NO_CONVERT_TABS, RTGETOPT_REQ_NOTHING }, + { "--force-final-eol", SCMOPT_FORCE_FINAL_EOL, RTGETOPT_REQ_NOTHING }, + { "--no-force-final-eol", SCMOPT_NO_FORCE_FINAL_EOL, RTGETOPT_REQ_NOTHING }, + { "--force-trailing-line", SCMOPT_FORCE_TRAILING_LINE, RTGETOPT_REQ_NOTHING }, + { "--no-force-trailing-line", SCMOPT_NO_FORCE_TRAILING_LINE, RTGETOPT_REQ_NOTHING }, + { "--strip-trailing-blanks", SCMOPT_STRIP_TRAILING_BLANKS, RTGETOPT_REQ_NOTHING }, + { "--no-strip-trailing-blanks", SCMOPT_NO_STRIP_TRAILING_BLANKS, RTGETOPT_REQ_NOTHING }, + { "--strip-trailing-lines", SCMOPT_STRIP_TRAILING_LINES, RTGETOPT_REQ_NOTHING }, + { "--strip-no-trailing-lines", SCMOPT_NO_STRIP_TRAILING_LINES, RTGETOPT_REQ_NOTHING }, + { "--min-blank-lines-before-flower-box-makers", SCMOPT_MIN_BLANK_LINES_BEFORE_FLOWER_BOX_MARKERS, RTGETOPT_REQ_UINT8 }, + { "--fix-flower-box-markers", SCMOPT_FIX_FLOWER_BOX_MARKERS, RTGETOPT_REQ_NOTHING }, + { "--no-fix-flower-box-markers", SCMOPT_NO_FIX_FLOWER_BOX_MARKERS, RTGETOPT_REQ_NOTHING }, + { "--fix-header-guards", SCMOPT_FIX_HEADER_GUARDS, RTGETOPT_REQ_NOTHING }, + { "--no-fix-header-guards", SCMOPT_NO_FIX_HEADER_GUARDS, RTGETOPT_REQ_NOTHING }, + { "--pragma-once", SCMOPT_PRAGMA_ONCE, RTGETOPT_REQ_NOTHING }, + { "--no-pragma-once", SCMOPT_NO_PRAGMA_ONCE, RTGETOPT_REQ_NOTHING }, + { "--fix-header-guard-endif", SCMOPT_FIX_HEADER_GUARD_ENDIF, RTGETOPT_REQ_NOTHING }, + { "--no-fix-header-guard-endif", SCMOPT_NO_FIX_HEADER_GUARD_ENDIF, RTGETOPT_REQ_NOTHING }, + { "--endif-guard-comment", SCMOPT_ENDIF_GUARD_COMMENT, RTGETOPT_REQ_NOTHING }, + { "--no-endif-guard-comment", SCMOPT_NO_ENDIF_GUARD_COMMENT, RTGETOPT_REQ_NOTHING }, + { "--guard-prefix", SCMOPT_GUARD_PREFIX, RTGETOPT_REQ_STRING }, + { "--guard-relative-to-dir", SCMOPT_GUARD_RELATIVE_TO_DIR, RTGETOPT_REQ_STRING }, + { "--fix-todos", SCMOPT_FIX_TODOS, RTGETOPT_REQ_NOTHING }, + { "--no-fix-todos", SCMOPT_NO_FIX_TODOS, RTGETOPT_REQ_NOTHING }, + { "--fix-err-h", SCMOPT_FIX_ERR_H, RTGETOPT_REQ_NOTHING }, + { "--no-fix-err-h", SCMOPT_NO_FIX_ERR_H, RTGETOPT_REQ_NOTHING }, + { "--only-guest-host-page", SCMOPT_ONLY_GUEST_HOST_PAGE, RTGETOPT_REQ_NOTHING }, + { "--no-page-restrictions", SCMOPT_NO_PAGE_RESTRICTIONS, RTGETOPT_REQ_NOTHING }, + { "--no-ASMMemPage-use", SCMOPT_NO_ASM_MEM_PAGE_USE, RTGETOPT_REQ_NOTHING }, + { "--unrestricted-ASMMemPage-use", SCMOPT_UNRESTRICTED_ASM_MEM_PAGE_USE, RTGETOPT_REQ_NOTHING }, + { "--no-rc-use", SCMOPT_NO_RC_USE, RTGETOPT_REQ_NOTHING }, + { "--unrestricted-rc-use", SCMOPT_UNRESTRICTED_RC_USE, RTGETOPT_REQ_NOTHING }, + { "--standarize-kmk", SCMOPT_STANDARIZE_KMK, RTGETOPT_REQ_NOTHING }, + { "--no-standarize-kmk", SCMOPT_NO_STANDARIZE_KMK, RTGETOPT_REQ_NOTHING }, + { "--update-copyright-year", SCMOPT_UPDATE_COPYRIGHT_YEAR, RTGETOPT_REQ_NOTHING }, + { "--no-update-copyright-year", SCMOPT_NO_UPDATE_COPYRIGHT_YEAR, RTGETOPT_REQ_NOTHING }, + { "--external-copyright", SCMOPT_EXTERNAL_COPYRIGHT, RTGETOPT_REQ_NOTHING }, + { "--no-external-copyright", SCMOPT_NO_EXTERNAL_COPYRIGHT, RTGETOPT_REQ_NOTHING }, + { "--no-update-license", SCMOPT_NO_UPDATE_LICENSE, RTGETOPT_REQ_NOTHING }, + { "--license-ose-gpl", SCMOPT_LICENSE_OSE_GPL, RTGETOPT_REQ_NOTHING }, + { "--license-ose-dual", SCMOPT_LICENSE_OSE_DUAL_GPL_CDDL, RTGETOPT_REQ_NOTHING }, + { "--license-ose-cddl", SCMOPT_LICENSE_OSE_CDDL, RTGETOPT_REQ_NOTHING }, + { "--license-lgpl", SCMOPT_LICENSE_LGPL, RTGETOPT_REQ_NOTHING }, + { "--license-mit", SCMOPT_LICENSE_MIT, RTGETOPT_REQ_NOTHING }, + { "--license-based-on-mit", SCMOPT_LICENSE_BASED_ON_MIT, RTGETOPT_REQ_NOTHING }, + { "--lgpl-disclaimer", SCMOPT_LGPL_DISCLAIMER, RTGETOPT_REQ_NOTHING }, + { "--no-lgpl-disclaimer", SCMOPT_NO_LGPL_DISCLAIMER, RTGETOPT_REQ_NOTHING }, + { "--set-svn-eol", SCMOPT_SET_SVN_EOL, RTGETOPT_REQ_NOTHING }, + { "--dont-set-svn-eol", SCMOPT_DONT_SET_SVN_EOL, RTGETOPT_REQ_NOTHING }, + { "--set-svn-executable", SCMOPT_SET_SVN_EXECUTABLE, RTGETOPT_REQ_NOTHING }, + { "--dont-set-svn-executable", SCMOPT_DONT_SET_SVN_EXECUTABLE, RTGETOPT_REQ_NOTHING }, + { "--set-svn-keywords", SCMOPT_SET_SVN_KEYWORDS, RTGETOPT_REQ_NOTHING }, + { "--dont-set-svn-keywords", SCMOPT_DONT_SET_SVN_KEYWORDS, RTGETOPT_REQ_NOTHING }, + { "--skip-svn-sync-process", SCMOPT_SKIP_SVN_SYNC_PROCESS, RTGETOPT_REQ_NOTHING }, + { "--dont-skip-svn-sync-process", SCMOPT_DONT_SKIP_SVN_SYNC_PROCESS, RTGETOPT_REQ_NOTHING }, + { "--skip-unicode-checks", SCMOPT_SKIP_UNICODE_CHECKS, RTGETOPT_REQ_NOTHING }, + { "--dont-skip-unicode-checks", SCMOPT_DONT_SKIP_UNICODE_CHECKS, RTGETOPT_REQ_NOTHING }, + { "--tab-size", SCMOPT_TAB_SIZE, RTGETOPT_REQ_UINT8 }, + { "--width", SCMOPT_WIDTH, RTGETOPT_REQ_UINT8 }, + + /* input selection */ + { "--only-svn-dirs", SCMOPT_ONLY_SVN_DIRS, RTGETOPT_REQ_NOTHING }, + { "--not-only-svn-dirs", SCMOPT_NOT_ONLY_SVN_DIRS, RTGETOPT_REQ_NOTHING }, + { "--only-svn-files", SCMOPT_ONLY_SVN_FILES, RTGETOPT_REQ_NOTHING }, + { "--not-only-svn-files", SCMOPT_NOT_ONLY_SVN_FILES, RTGETOPT_REQ_NOTHING }, + { "--filter-out-dirs", SCMOPT_FILTER_OUT_DIRS, RTGETOPT_REQ_STRING }, + { "--filter-files", SCMOPT_FILTER_FILES, RTGETOPT_REQ_STRING }, + { "--filter-out-files", SCMOPT_FILTER_OUT_FILES, RTGETOPT_REQ_STRING }, + + /* rewriter selection */ + { "--treat-as", SCMOPT_TREAT_AS, RTGETOPT_REQ_STRING }, + { "--add-action", SCMOPT_ADD_ACTION, RTGETOPT_REQ_STRING }, + { "--del-action", SCMOPT_DEL_ACTION, RTGETOPT_REQ_STRING }, + + /* Additional help */ + { "--help-config", SCMOPT_HELP_CONFIG, RTGETOPT_REQ_NOTHING }, + { "--help-actions", SCMOPT_HELP_ACTIONS, RTGETOPT_REQ_NOTHING }, +}; + +/** Consider files matching the following patterns (base names only). */ +static const char *g_pszFileFilter = NULL; + +/* The rewriter configuration. */ +#define SCM_REWRITER_CFG(a_Global, a_szName, fnRewriter) static const SCMREWRITERCFG a_Global = { &fnRewriter, a_szName } +SCM_REWRITER_CFG(g_StripTrailingBlanks, "strip-trailing-blanks", rewrite_StripTrailingBlanks); +SCM_REWRITER_CFG(g_ExpandTabs, "expand-tabs", rewrite_ExpandTabs); +SCM_REWRITER_CFG(g_ForceNativeEol, "force-native-eol", rewrite_ForceNativeEol); +SCM_REWRITER_CFG(g_ForceLF, "force-lf", rewrite_ForceLF); +SCM_REWRITER_CFG(g_ForceCRLF, "force-crlf", rewrite_ForceCRLF); +SCM_REWRITER_CFG(g_AdjustTrailingLines, "adjust-trailing-lines", rewrite_AdjustTrailingLines); +SCM_REWRITER_CFG(g_SvnNoExecutable, "svn-no-executable", rewrite_SvnNoExecutable); +SCM_REWRITER_CFG(g_SvnNoKeywords, "svn-no-keywords", rewrite_SvnNoKeywords); +SCM_REWRITER_CFG(g_SvnNoEolStyle, "svn-no-eol-style", rewrite_SvnNoEolStyle); +SCM_REWRITER_CFG(g_SvnBinary, "svn-binary", rewrite_SvnBinary); +SCM_REWRITER_CFG(g_SvnKeywords, "svn-keywords", rewrite_SvnKeywords); +SCM_REWRITER_CFG(g_SvnSyncProcess, "svn-sync-process", rewrite_SvnSyncProcess); +SCM_REWRITER_CFG(g_UnicodeChecks, "unicode-checks", rewrite_UnicodeChecks); +SCM_REWRITER_CFG(g_PageChecks, "page-checks", rewrite_PageChecks); +SCM_REWRITER_CFG(g_ForceHrcVrcInsteadOfRc, "force-hrc-vrc-no-rc", rewrite_ForceHrcVrcInsteadOfRc); +SCM_REWRITER_CFG(g_Copyright_CstyleComment, "copyright-c-style", rewrite_Copyright_CstyleComment); +SCM_REWRITER_CFG(g_Copyright_HashComment, "copyright-hash-style", rewrite_Copyright_HashComment); +SCM_REWRITER_CFG(g_Copyright_PythonComment, "copyright-python-style", rewrite_Copyright_PythonComment); +SCM_REWRITER_CFG(g_Copyright_RemComment, "copyright-rem-style", rewrite_Copyright_RemComment); +SCM_REWRITER_CFG(g_Copyright_SemicolonComment, "copyright-semicolon-style", rewrite_Copyright_SemicolonComment); +SCM_REWRITER_CFG(g_Copyright_SqlComment, "copyright-sql-style", rewrite_Copyright_SqlComment); +SCM_REWRITER_CFG(g_Copyright_TickComment, "copyright-tick-style", rewrite_Copyright_TickComment); +SCM_REWRITER_CFG(g_Copyright_XmlComment, "copyright-xml-style", rewrite_Copyright_XmlComment); +SCM_REWRITER_CFG(g_Makefile_kup, "makefile-kup", rewrite_Makefile_kup); +SCM_REWRITER_CFG(g_Makefile_kmk, "makefile-kmk", rewrite_Makefile_kmk); +SCM_REWRITER_CFG(g_FixFlowerBoxMarkers, "fix-flower-boxes", rewrite_FixFlowerBoxMarkers); +SCM_REWRITER_CFG(g_FixHeaderGuards, "fix-header-guard", rewrite_FixHeaderGuards); +SCM_REWRITER_CFG(g_Fix_C_and_CPP_Todos, "fix-c-todos", rewrite_Fix_C_and_CPP_Todos); +SCM_REWRITER_CFG(g_Fix_Err_H, "fix-err-h", rewrite_Fix_Err_H); +SCM_REWRITER_CFG(g_C_and_CPP, "c-and-cpp", rewrite_C_and_CPP); + +/** The rewriter actions. */ +static PCSCMREWRITERCFG const g_papRewriterActions[] = +{ + &g_StripTrailingBlanks, + &g_ExpandTabs, + &g_ForceNativeEol, + &g_ForceLF, + &g_ForceCRLF, + &g_AdjustTrailingLines, + &g_SvnNoExecutable, + &g_SvnNoKeywords, + &g_SvnNoEolStyle, + &g_SvnBinary, + &g_SvnKeywords, + &g_SvnSyncProcess, + &g_Copyright_CstyleComment, + &g_Copyright_HashComment, + &g_Copyright_PythonComment, + &g_Copyright_RemComment, + &g_Copyright_SemicolonComment, + &g_Copyright_SqlComment, + &g_Copyright_TickComment, + &g_Makefile_kup, + &g_Makefile_kmk, + &g_FixFlowerBoxMarkers, + &g_FixHeaderGuards, + &g_Fix_C_and_CPP_Todos, + &g_Fix_Err_H, + &g_UnicodeChecks, + &g_PageChecks, + &g_ForceHrcVrcInsteadOfRc, + &g_C_and_CPP, +}; + + +static PCSCMREWRITERCFG const g_apRewritersFor_Makefile_kup[] = +{ + &g_SvnNoExecutable, + &g_SvnSyncProcess, + &g_UnicodeChecks, + &g_Makefile_kup +}; + +static PCSCMREWRITERCFG const g_apRewritersFor_Makefile_kmk[] = +{ + &g_ForceNativeEol, + &g_StripTrailingBlanks, + &g_AdjustTrailingLines, + &g_SvnNoExecutable, + &g_SvnKeywords, + &g_SvnSyncProcess, + &g_UnicodeChecks, + &g_Copyright_HashComment, + &g_Makefile_kmk +}; + +static PCSCMREWRITERCFG const g_apRewritersFor_OtherMakefiles[] = +{ + &g_ForceNativeEol, + &g_StripTrailingBlanks, + &g_AdjustTrailingLines, + &g_SvnNoExecutable, + &g_SvnKeywords, + &g_SvnSyncProcess, + &g_UnicodeChecks, + &g_Copyright_HashComment, +}; + +static PCSCMREWRITERCFG const g_apRewritersFor_C_and_CPP[] = +{ + &g_ForceNativeEol, + &g_ExpandTabs, + &g_StripTrailingBlanks, + &g_AdjustTrailingLines, + &g_SvnNoExecutable, + &g_SvnKeywords, + &g_SvnSyncProcess, + &g_UnicodeChecks, + &g_PageChecks, + &g_ForceHrcVrcInsteadOfRc, + &g_Copyright_CstyleComment, + &g_FixFlowerBoxMarkers, + &g_Fix_C_and_CPP_Todos, + &g_Fix_Err_H, + &g_C_and_CPP, +}; + +static PCSCMREWRITERCFG const g_apRewritersFor_H_and_HPP[] = +{ + &g_ForceNativeEol, + &g_ExpandTabs, + &g_StripTrailingBlanks, + &g_AdjustTrailingLines, + &g_SvnNoExecutable, + &g_SvnKeywords, + &g_SvnSyncProcess, + &g_UnicodeChecks, + &g_PageChecks, + &g_ForceHrcVrcInsteadOfRc, + &g_Copyright_CstyleComment, + /// @todo &g_FixFlowerBoxMarkers, + &g_FixHeaderGuards, + &g_C_and_CPP +}; + +static PCSCMREWRITERCFG const g_apRewritersFor_RC[] = +{ + &g_ForceNativeEol, + &g_ExpandTabs, + &g_StripTrailingBlanks, + &g_AdjustTrailingLines, + &g_SvnNoExecutable, + &g_SvnKeywords, + &g_SvnSyncProcess, + &g_UnicodeChecks, + &g_Copyright_CstyleComment, +}; + +static PCSCMREWRITERCFG const g_apRewritersFor_DTrace[] = +{ + &g_ForceNativeEol, + &g_ExpandTabs, + &g_StripTrailingBlanks, + &g_AdjustTrailingLines, + &g_SvnKeywords, + &g_SvnSyncProcess, + &g_UnicodeChecks, + &g_Copyright_CstyleComment, +}; + +static PCSCMREWRITERCFG const g_apRewritersFor_DSL[] = +{ + &g_ForceNativeEol, + &g_ExpandTabs, + &g_StripTrailingBlanks, + &g_AdjustTrailingLines, + &g_SvnNoExecutable, + &g_SvnKeywords, + &g_SvnSyncProcess, + &g_UnicodeChecks, + &g_Copyright_CstyleComment, +}; + +static PCSCMREWRITERCFG const g_apRewritersFor_ASM[] = +{ + &g_ForceNativeEol, + &g_ExpandTabs, + &g_StripTrailingBlanks, + &g_AdjustTrailingLines, + &g_SvnNoExecutable, + &g_SvnKeywords, + &g_SvnSyncProcess, + &g_UnicodeChecks, + &g_Copyright_SemicolonComment, +}; + +static PCSCMREWRITERCFG const g_apRewritersFor_DEF[] = +{ + &g_ForceNativeEol, + &g_ExpandTabs, + &g_StripTrailingBlanks, + &g_AdjustTrailingLines, + &g_SvnNoExecutable, + &g_SvnKeywords, + &g_SvnSyncProcess, + &g_UnicodeChecks, + &g_Copyright_SemicolonComment, +}; + +static PCSCMREWRITERCFG const g_apRewritersFor_ShellScripts[] = +{ + &g_ForceLF, + &g_ExpandTabs, + &g_StripTrailingBlanks, + &g_SvnSyncProcess, + &g_UnicodeChecks, + &g_Copyright_HashComment, +}; + +static PCSCMREWRITERCFG const g_apRewritersFor_BatchFiles[] = +{ + &g_ForceCRLF, + &g_ExpandTabs, + &g_StripTrailingBlanks, + &g_SvnSyncProcess, + &g_UnicodeChecks, + &g_Copyright_RemComment, +}; + +static PCSCMREWRITERCFG const g_apRewritersFor_BasicScripts[] = +{ + &g_ForceCRLF, + &g_ExpandTabs, + &g_StripTrailingBlanks, + &g_SvnSyncProcess, + &g_UnicodeChecks, + &g_Copyright_TickComment, +}; + +static PCSCMREWRITERCFG const g_apRewritersFor_SedScripts[] = +{ + &g_ForceLF, + &g_ExpandTabs, + &g_StripTrailingBlanks, + &g_SvnSyncProcess, + &g_UnicodeChecks, + &g_Copyright_HashComment, +}; + +static PCSCMREWRITERCFG const g_apRewritersFor_Python[] = +{ + /** @todo &g_ForceLFIfExecutable */ + &g_ExpandTabs, + &g_StripTrailingBlanks, + &g_AdjustTrailingLines, + &g_SvnKeywords, + &g_SvnSyncProcess, + &g_UnicodeChecks, + &g_Copyright_PythonComment, +}; + +static PCSCMREWRITERCFG const g_apRewritersFor_Perl[] = +{ + /** @todo &g_ForceLFIfExecutable */ + &g_ExpandTabs, + &g_StripTrailingBlanks, + &g_AdjustTrailingLines, + &g_SvnKeywords, + &g_SvnSyncProcess, + &g_UnicodeChecks, + &g_Copyright_HashComment, +}; + +static PCSCMREWRITERCFG const g_apRewritersFor_DriverInfFiles[] = +{ + &g_ForceNativeEol, + &g_ExpandTabs, + &g_StripTrailingBlanks, + &g_AdjustTrailingLines, + &g_SvnKeywords, + &g_SvnNoExecutable, + &g_SvnSyncProcess, + &g_UnicodeChecks, + &g_Copyright_SemicolonComment, +}; + +static PCSCMREWRITERCFG const g_apRewritersFor_NsisFiles[] = +{ + &g_ForceNativeEol, + &g_ExpandTabs, + &g_StripTrailingBlanks, + &g_AdjustTrailingLines, + &g_SvnKeywords, + &g_SvnNoExecutable, + &g_SvnSyncProcess, + &g_UnicodeChecks, + &g_Copyright_SemicolonComment, +}; + +static PCSCMREWRITERCFG const g_apRewritersFor_Java[] = +{ + &g_ForceNativeEol, + &g_ExpandTabs, + &g_StripTrailingBlanks, + &g_AdjustTrailingLines, + &g_SvnNoExecutable, + &g_SvnKeywords, + &g_SvnSyncProcess, + &g_UnicodeChecks, + &g_Copyright_CstyleComment, + &g_FixFlowerBoxMarkers, + &g_Fix_C_and_CPP_Todos, +}; + +static PCSCMREWRITERCFG const g_apRewritersFor_ScmSettings[] = +{ + &g_ForceNativeEol, + &g_ExpandTabs, + &g_StripTrailingBlanks, + &g_AdjustTrailingLines, + &g_SvnNoExecutable, + &g_SvnKeywords, + &g_SvnSyncProcess, + &g_UnicodeChecks, + &g_Copyright_HashComment, +}; + +static PCSCMREWRITERCFG const g_apRewritersFor_Images[] = +{ + &g_SvnNoExecutable, + &g_SvnBinary, + &g_SvnSyncProcess, +}; + +static PCSCMREWRITERCFG const g_apRewritersFor_Xslt[] = +{ + &g_ForceNativeEol, + &g_ExpandTabs, + &g_StripTrailingBlanks, + &g_AdjustTrailingLines, + &g_SvnNoExecutable, + &g_SvnKeywords, + &g_SvnSyncProcess, + &g_UnicodeChecks, + &g_Copyright_XmlComment, +}; + +static PCSCMREWRITERCFG const g_apRewritersFor_Xml[] = +{ + &g_ForceNativeEol, + &g_ExpandTabs, + &g_StripTrailingBlanks, + &g_AdjustTrailingLines, + &g_SvnNoExecutable, + &g_SvnKeywords, + &g_SvnSyncProcess, + &g_UnicodeChecks, + &g_Copyright_XmlComment, +}; + +static PCSCMREWRITERCFG const g_apRewritersFor_Wix[] = +{ + &g_ForceNativeEol, + &g_ExpandTabs, + &g_StripTrailingBlanks, + &g_AdjustTrailingLines, + &g_SvnNoExecutable, + &g_SvnKeywords, + &g_SvnSyncProcess, + &g_UnicodeChecks, + &g_Copyright_XmlComment, +}; + +static PCSCMREWRITERCFG const g_apRewritersFor_QtProject[] = +{ + &g_ForceNativeEol, + &g_StripTrailingBlanks, + &g_AdjustTrailingLines, + &g_SvnNoExecutable, + &g_SvnKeywords, + &g_SvnSyncProcess, + &g_UnicodeChecks, + &g_Copyright_HashComment, +}; + +static PCSCMREWRITERCFG const g_apRewritersFor_QtResourceFiles[] = +{ + &g_ForceNativeEol, + &g_SvnNoExecutable, + &g_SvnKeywords, + &g_SvnSyncProcess, + &g_UnicodeChecks, + /** @todo figure out copyright for Qt resource XML files. */ +}; + +static PCSCMREWRITERCFG const g_apRewritersFor_QtTranslations[] = +{ + &g_ForceNativeEol, + &g_SvnNoExecutable, +}; + +static PCSCMREWRITERCFG const g_apRewritersFor_QtUiFiles[] = +{ + &g_ForceNativeEol, + &g_SvnNoExecutable, + &g_SvnKeywords, + &g_SvnSyncProcess, + &g_UnicodeChecks, + /** @todo copyright is in an XML 'comment' element. */ +}; + +static PCSCMREWRITERCFG const g_apRewritersFor_SifFiles[] = +{ + &g_ForceCRLF, + &g_ExpandTabs, + &g_StripTrailingBlanks, + &g_AdjustTrailingLines, + &g_SvnKeywords, + &g_SvnNoExecutable, + &g_SvnSyncProcess, + &g_UnicodeChecks, + &g_Copyright_SemicolonComment, +}; + +static PCSCMREWRITERCFG const g_apRewritersFor_SqlFiles[] = +{ + &g_ForceNativeEol, + &g_ExpandTabs, + &g_StripTrailingBlanks, + &g_AdjustTrailingLines, + &g_SvnKeywords, + &g_SvnNoExecutable, + &g_SvnSyncProcess, + &g_UnicodeChecks, + &g_Copyright_SqlComment, +}; + +static PCSCMREWRITERCFG const g_apRewritersFor_GnuAsm[] = +{ + &g_ForceNativeEol, + &g_ExpandTabs, + &g_StripTrailingBlanks, + &g_AdjustTrailingLines, + &g_SvnKeywords, + &g_SvnNoExecutable, + &g_SvnSyncProcess, + &g_UnicodeChecks, + &g_Copyright_CstyleComment, +}; + +static PCSCMREWRITERCFG const g_apRewritersFor_TextFiles[] = +{ + &g_ForceNativeEol, + &g_StripTrailingBlanks, + &g_SvnKeywords, + &g_SvnNoExecutable, + &g_SvnSyncProcess, + &g_UnicodeChecks, + /** @todo check for plain copyright + license in text files. */ +}; + +static PCSCMREWRITERCFG const g_apRewritersFor_PlainTextFiles[] = +{ + &g_ForceNativeEol, + &g_StripTrailingBlanks, + &g_SvnKeywords, + &g_SvnNoExecutable, + &g_SvnSyncProcess, + &g_UnicodeChecks, +}; + +static PCSCMREWRITERCFG const g_apRewritersFor_BinaryFiles[] = +{ + &g_SvnBinary, + &g_SvnSyncProcess, +}; + +static PCSCMREWRITERCFG const g_apRewritersFor_FileLists[] = /* both makefile and shell script */ +{ + &g_ForceLF, + &g_ExpandTabs, + &g_StripTrailingBlanks, + &g_AdjustTrailingLines, + &g_SvnSyncProcess, + &g_UnicodeChecks, + &g_Copyright_HashComment, +}; + + +/** + * Array of standard rewriter configurations. + */ +static SCMCFGENTRY const g_aConfigs[] = +{ +#define SCM_CFG_ENTRY(a_szName, a_aRewriters, a_fBinary, a_szFilePatterns) \ + { RT_ELEMENTS(a_aRewriters), &a_aRewriters[0], a_fBinary, a_szFilePatterns, a_szName } + SCM_CFG_ENTRY("kup", g_apRewritersFor_Makefile_kup, false, "Makefile.kup" ), + SCM_CFG_ENTRY("kmk", g_apRewritersFor_Makefile_kmk, false, "*.kmk" ), + SCM_CFG_ENTRY("c", g_apRewritersFor_C_and_CPP, false, "*.c|*.cpp|*.C|*.CPP|*.cxx|*.cc|*.m|*.mm|*.lds" ), + SCM_CFG_ENTRY("h", g_apRewritersFor_H_and_HPP, false, "*.h|*.hpp" ), + SCM_CFG_ENTRY("rc", g_apRewritersFor_RC, false, "*.rc" ), + SCM_CFG_ENTRY("asm", g_apRewritersFor_ASM, false, "*.asm|*.mac|*.inc" ), + SCM_CFG_ENTRY("dtrace", g_apRewritersFor_DTrace, false, "*.d" ), + SCM_CFG_ENTRY("def", g_apRewritersFor_DEF, false, "*.def" ), + SCM_CFG_ENTRY("iasl", g_apRewritersFor_DSL, false, "*.dsl" ), + SCM_CFG_ENTRY("shell", g_apRewritersFor_ShellScripts, false, "*.sh|configure" ), + SCM_CFG_ENTRY("batch", g_apRewritersFor_BatchFiles, false, "*.bat|*.cmd|*.btm" ), + SCM_CFG_ENTRY("vbs", g_apRewritersFor_BasicScripts, false, "*.vbs|*.vb" ), + SCM_CFG_ENTRY("sed", g_apRewritersFor_SedScripts, false, "*.sed" ), + SCM_CFG_ENTRY("python", g_apRewritersFor_Python, false, "*.py" ), + SCM_CFG_ENTRY("perl", g_apRewritersFor_Perl, false, "*.pl|*.pm" ), + SCM_CFG_ENTRY("drvinf", g_apRewritersFor_DriverInfFiles, false, "*.inf" ), + SCM_CFG_ENTRY("nsis", g_apRewritersFor_NsisFiles, false, "*.nsh|*.nsi|*.nsis" ), + SCM_CFG_ENTRY("java", g_apRewritersFor_Java, false, "*.java" ), + SCM_CFG_ENTRY("scm", g_apRewritersFor_ScmSettings, false, "*.scm-settings" ), + SCM_CFG_ENTRY("image", g_apRewritersFor_Images, true, "*.png|*.bmp|*.jpg|*.pnm|*.ico|*.icns|*.tiff|*.tif|" + "*.xcf|*.gif|*.jar|*.dll|*.exe|*.ttf|*.woff|*.woff2" ), + SCM_CFG_ENTRY("xslt", g_apRewritersFor_Xslt, false, "*.xsl" ), + SCM_CFG_ENTRY("xml", g_apRewritersFor_Xml, false, "*.xml|*.dist|*.qhcp" ), + SCM_CFG_ENTRY("wix", g_apRewritersFor_Wix, false, "*.wxi|*.wxs|*.wxl" ), + SCM_CFG_ENTRY("qt-pro", g_apRewritersFor_QtProject, false, "*.pro" ), + SCM_CFG_ENTRY("qt-rc", g_apRewritersFor_QtResourceFiles, false, "*.qrc" ), + SCM_CFG_ENTRY("qt-ts", g_apRewritersFor_QtTranslations, false, "*.ts" ), + SCM_CFG_ENTRY("qt-ui", g_apRewritersFor_QtUiFiles, false, "*.ui" ), + SCM_CFG_ENTRY("sif", g_apRewritersFor_SifFiles, false, "*.sif" ), + SCM_CFG_ENTRY("sql", g_apRewritersFor_SqlFiles, false, "*.pgsql|*.sql" ), + SCM_CFG_ENTRY("gas", g_apRewritersFor_GnuAsm, false, "*.S" ), + SCM_CFG_ENTRY("binary", g_apRewritersFor_BinaryFiles, true, "*.bin|*.pdf|*.zip|*.bz2|*.gz" ), + /* These should be be last: */ + SCM_CFG_ENTRY("make", g_apRewritersFor_OtherMakefiles, false, "Makefile|makefile|GNUmakefile|SMakefile|Makefile.am|Makefile.in|*.cmake|*.gmk" ), + SCM_CFG_ENTRY("text", g_apRewritersFor_TextFiles, false, "*.txt|README*|readme*|ReadMe*|NOTE*|TODO*" ), + SCM_CFG_ENTRY("plaintext", g_apRewritersFor_PlainTextFiles, false, "LICENSE|ChangeLog|FAQ|AUTHORS|INSTALL|NEWS" ), + SCM_CFG_ENTRY("file-list", g_apRewritersFor_FileLists, false, "files_*" ), +}; + + + +/* -=-=-=-=-=- settings -=-=-=-=-=- */ + +/** + * Delete the given config entry. + * + * @param pEntry The configuration entry to delete. + */ +static void scmCfgEntryDelete(PSCMCFGENTRY pEntry) +{ + RTMemFree((void *)pEntry->paRewriters); + pEntry->paRewriters = NULL; + RTMemFree(pEntry); +} + +/** + * Create a new configuration entry. + * + * @returns The new entry. NULL if out of memory. + * @param pEntry The configuration entry to duplicate. + */ +static PSCMCFGENTRY scmCfgEntryNew(void) +{ + PSCMCFGENTRY pNew = (PSCMCFGENTRY)RTMemAlloc(sizeof(*pNew)); + if (pNew) + { + pNew->pszName = "custom"; + pNew->pszFilePattern = "custom"; + pNew->cRewriters = 0; + pNew->paRewriters = NULL; + pNew->fBinary = false; + } + return pNew; +} + +/** + * Duplicate the given config entry. + * + * @returns The duplicate. NULL if out of memory. + * @param pEntry The configuration entry to duplicate. + */ +static PSCMCFGENTRY scmCfgEntryDup(PCSCMCFGENTRY pEntry) +{ + if (pEntry) + { + PSCMCFGENTRY pDup = (PSCMCFGENTRY)RTMemDup(pEntry, sizeof(*pEntry)); + if (pDup) + { + size_t cbSrcRewriters = sizeof(pEntry->paRewriters[0]) * pEntry->cRewriters; + size_t cbDstRewriters = sizeof(pEntry->paRewriters[0]) * RT_ALIGN_Z(pEntry->cRewriters, 8); + pDup->paRewriters = (PCSCMREWRITERCFG const *)RTMemDupEx(pEntry->paRewriters, cbSrcRewriters, + cbDstRewriters - cbSrcRewriters); + if (pDup->paRewriters) + return pDup; + + RTMemFree(pDup); + } + return NULL; + } + return scmCfgEntryNew(); +} + +/** + * Adds a rewriter action to the given config entry (--add-action). + * + * @returns VINF_SUCCESS. + * @param pEntry The configuration entry. + * @param pAction The rewriter action to add. + */ +static int scmCfgEntryAddAction(PSCMCFGENTRY pEntry, PCSCMREWRITERCFG pAction) +{ + PCSCMREWRITERCFG *paRewriters = (PCSCMREWRITERCFG *)pEntry->paRewriters; + if (pEntry->cRewriters % 8 == 0) + { + size_t cbRewriters = sizeof(pEntry->paRewriters[0]) * RT_ALIGN_Z((pEntry->cRewriters + 1), 8); + void *pvNew = RTMemRealloc(paRewriters, cbRewriters); + if (pvNew) + pEntry->paRewriters = paRewriters = (PCSCMREWRITERCFG *)pvNew; + else + return VERR_NO_MEMORY; + } + + paRewriters[pEntry->cRewriters++] = pAction; + return VINF_SUCCESS; +} + +/** + * Delets an rewriter action from the given config entry (--del-action). + * + * @param pEntry The configuration entry. + * @param pAction The rewriter action to remove. + */ +static void scmCfgEntryDelAction(PSCMCFGENTRY pEntry, PCSCMREWRITERCFG pAction) +{ + PCSCMREWRITERCFG *paRewriters = (PCSCMREWRITERCFG *)pEntry->paRewriters; + size_t const cEntries = pEntry->cRewriters; + size_t iDst = 0; + for (size_t iSrc = 0; iSrc < cEntries; iSrc++) + { + PCSCMREWRITERCFG pCurAction = paRewriters[iSrc]; + if (pCurAction != pAction) + paRewriters[iDst++] = pCurAction; + } + pEntry->cRewriters = iDst; +} + +/** + * Init a settings structure with settings from @a pSrc. + * + * @returns IPRT status code + * @param pSettings The settings. + * @param pSrc The source settings. + */ +static int scmSettingsBaseInitAndCopy(PSCMSETTINGSBASE pSettings, PCSCMSETTINGSBASE pSrc) +{ + *pSettings = *pSrc; + + int rc = RTStrDupEx(&pSettings->pszFilterFiles, pSrc->pszFilterFiles); + if (RT_SUCCESS(rc)) + { + rc = RTStrDupEx(&pSettings->pszFilterOutFiles, pSrc->pszFilterOutFiles); + if (RT_SUCCESS(rc)) + { + rc = RTStrDupEx(&pSettings->pszFilterOutDirs, pSrc->pszFilterOutDirs); + if (RT_SUCCESS(rc)) + { + rc = RTStrDupEx(&pSettings->pszGuardPrefix, pSrc->pszGuardPrefix); + if (RT_SUCCESS(rc)) + { + if (pSrc->pszGuardRelativeToDir) + rc = RTStrDupEx(&pSettings->pszGuardRelativeToDir, pSrc->pszGuardRelativeToDir); + if (RT_SUCCESS(rc)) + { + + if (!pSrc->fFreeTreatAs) + return VINF_SUCCESS; + + pSettings->pTreatAs = scmCfgEntryDup(pSrc->pTreatAs); + if (pSettings->pTreatAs) + return VINF_SUCCESS; + + RTStrFree(pSettings->pszGuardRelativeToDir); + } + RTStrFree(pSettings->pszGuardPrefix); + } + } + RTStrFree(pSettings->pszFilterOutFiles); + } + RTStrFree(pSettings->pszFilterFiles); + } + + pSettings->pszGuardRelativeToDir = NULL; + pSettings->pszGuardPrefix = NULL; + pSettings->pszFilterFiles = NULL; + pSettings->pszFilterOutFiles = NULL; + pSettings->pszFilterOutDirs = NULL; + pSettings->pTreatAs = NULL; + return rc; +} + +/** + * Init a settings structure. + * + * @returns IPRT status code + * @param pSettings The settings. + */ +static int scmSettingsBaseInit(PSCMSETTINGSBASE pSettings) +{ + return scmSettingsBaseInitAndCopy(pSettings, &g_Defaults); +} + +/** + * Deletes the settings, i.e. free any dynamically allocated content. + * + * @param pSettings The settings. + */ +static void scmSettingsBaseDelete(PSCMSETTINGSBASE pSettings) +{ + if (pSettings) + { + Assert(pSettings->cchTab != UINT8_MAX); + pSettings->cchTab = UINT8_MAX; + + RTStrFree(pSettings->pszGuardPrefix); + RTStrFree(pSettings->pszGuardRelativeToDir); + RTStrFree(pSettings->pszFilterFiles); + RTStrFree(pSettings->pszFilterOutFiles); + RTStrFree(pSettings->pszFilterOutDirs); + if (pSettings->fFreeTreatAs) + scmCfgEntryDelete((PSCMCFGENTRY)pSettings->pTreatAs); + + pSettings->pszGuardPrefix = NULL; + pSettings->pszGuardRelativeToDir = NULL; + pSettings->pszFilterOutDirs = NULL; + pSettings->pszFilterOutFiles = NULL; + pSettings->pszFilterFiles = NULL; + pSettings->pTreatAs = NULL; + pSettings->fFreeTreatAs = false; + } +} + +/** + * Processes a RTGetOpt result. + * + * @retval VINF_SUCCESS if handled. + * @retval VERR_OUT_OF_RANGE if the option value was out of range. + * @retval VERR_GETOPT_UNKNOWN_OPTION if the option was not recognized. + * + * @param pSettings The settings to change. + * @param rc The RTGetOpt return value. + * @param pValueUnion The RTGetOpt value union. + * @param pchDir The absolute path to the directory relative + * components in pchLine should be relative to. + * @param cchDir The length of the @a pchDir string. + */ +static int scmSettingsBaseHandleOpt(PSCMSETTINGSBASE pSettings, int rc, PRTGETOPTUNION pValueUnion, + const char *pchDir, size_t cchDir) +{ + Assert(pchDir[cchDir - 1] == '/'); + + switch (rc) + { + case SCMOPT_CONVERT_EOL: + pSettings->fConvertEol = true; + return VINF_SUCCESS; + case SCMOPT_NO_CONVERT_EOL: + pSettings->fConvertEol = false; + return VINF_SUCCESS; + + case SCMOPT_CONVERT_TABS: + pSettings->fConvertTabs = true; + return VINF_SUCCESS; + case SCMOPT_NO_CONVERT_TABS: + pSettings->fConvertTabs = false; + return VINF_SUCCESS; + + case SCMOPT_FORCE_FINAL_EOL: + pSettings->fForceFinalEol = true; + return VINF_SUCCESS; + case SCMOPT_NO_FORCE_FINAL_EOL: + pSettings->fForceFinalEol = false; + return VINF_SUCCESS; + + case SCMOPT_FORCE_TRAILING_LINE: + pSettings->fForceTrailingLine = true; + return VINF_SUCCESS; + case SCMOPT_NO_FORCE_TRAILING_LINE: + pSettings->fForceTrailingLine = false; + return VINF_SUCCESS; + + + case SCMOPT_STRIP_TRAILING_BLANKS: + pSettings->fStripTrailingBlanks = true; + return VINF_SUCCESS; + case SCMOPT_NO_STRIP_TRAILING_BLANKS: + pSettings->fStripTrailingBlanks = false; + return VINF_SUCCESS; + + case SCMOPT_MIN_BLANK_LINES_BEFORE_FLOWER_BOX_MARKERS: + pSettings->cMinBlankLinesBeforeFlowerBoxMakers = pValueUnion->u8; + return VINF_SUCCESS; + + + case SCMOPT_STRIP_TRAILING_LINES: + pSettings->fStripTrailingLines = true; + return VINF_SUCCESS; + case SCMOPT_NO_STRIP_TRAILING_LINES: + pSettings->fStripTrailingLines = false; + return VINF_SUCCESS; + + case SCMOPT_FIX_FLOWER_BOX_MARKERS: + pSettings->fFixFlowerBoxMarkers = true; + return VINF_SUCCESS; + case SCMOPT_NO_FIX_FLOWER_BOX_MARKERS: + pSettings->fFixFlowerBoxMarkers = false; + return VINF_SUCCESS; + + case SCMOPT_FIX_HEADER_GUARDS: + pSettings->fFixHeaderGuards = true; + return VINF_SUCCESS; + case SCMOPT_NO_FIX_HEADER_GUARDS: + pSettings->fFixHeaderGuards = false; + return VINF_SUCCESS; + + case SCMOPT_PRAGMA_ONCE: + pSettings->fPragmaOnce = true; + return VINF_SUCCESS; + case SCMOPT_NO_PRAGMA_ONCE: + pSettings->fPragmaOnce = false; + return VINF_SUCCESS; + + case SCMOPT_FIX_HEADER_GUARD_ENDIF: + pSettings->fFixHeaderGuardEndif = true; + return VINF_SUCCESS; + case SCMOPT_NO_FIX_HEADER_GUARD_ENDIF: + pSettings->fFixHeaderGuardEndif = false; + return VINF_SUCCESS; + + case SCMOPT_ENDIF_GUARD_COMMENT: + pSettings->fEndifGuardComment = true; + return VINF_SUCCESS; + case SCMOPT_NO_ENDIF_GUARD_COMMENT: + pSettings->fEndifGuardComment = false; + return VINF_SUCCESS; + + case SCMOPT_GUARD_PREFIX: + RTStrFree(pSettings->pszGuardPrefix); + pSettings->pszGuardPrefix = NULL; + return RTStrDupEx(&pSettings->pszGuardPrefix, pValueUnion->psz); + + case SCMOPT_GUARD_RELATIVE_TO_DIR: + RTStrFree(pSettings->pszGuardRelativeToDir); + pSettings->pszGuardRelativeToDir = NULL; + if (*pValueUnion->psz != '\0') + { + if ( strcmp(pValueUnion->psz, "{dir}") == 0 + || strcmp(pValueUnion->psz, "{parent}") == 0) + return RTStrDupEx(&pSettings->pszGuardRelativeToDir, pValueUnion->psz); + if (cchDir == 1 && *pchDir == '/') + { + pSettings->pszGuardRelativeToDir = RTPathAbsDup(pValueUnion->psz); + if (pSettings->pszGuardRelativeToDir) + return VINF_SUCCESS; + } + else + { + char *pszDir = RTStrDupN(pchDir, cchDir); + if (pszDir) + { + pSettings->pszGuardRelativeToDir = RTPathAbsExDup(pszDir, pValueUnion->psz, RTPATH_STR_F_STYLE_HOST); + RTStrFree(pszDir); + if (pSettings->pszGuardRelativeToDir) + return VINF_SUCCESS; + } + } + RTMsgError("Failed to abspath --guard-relative-to-dir value '%s' - probably out of memory\n", pValueUnion->psz); + return VERR_NO_STR_MEMORY; + } + return VINF_SUCCESS; + + case SCMOPT_FIX_TODOS: + pSettings->fFixTodos = true; + return VINF_SUCCESS; + case SCMOPT_NO_FIX_TODOS: + pSettings->fFixTodos = false; + return VINF_SUCCESS; + + case SCMOPT_FIX_ERR_H: + pSettings->fFixErrH = true; + return VINF_SUCCESS; + case SCMOPT_NO_FIX_ERR_H: + pSettings->fFixErrH = false; + return VINF_SUCCESS; + + case SCMOPT_ONLY_GUEST_HOST_PAGE: + pSettings->fOnlyGuestHostPage = true; + return VINF_SUCCESS; + case SCMOPT_NO_PAGE_RESTRICTIONS: + pSettings->fOnlyGuestHostPage = false; + return VINF_SUCCESS; + + case SCMOPT_NO_ASM_MEM_PAGE_USE: + pSettings->fNoASMMemPageUse = true; + return VINF_SUCCESS; + case SCMOPT_UNRESTRICTED_ASM_MEM_PAGE_USE: + pSettings->fNoASMMemPageUse = false; + return VINF_SUCCESS; + + case SCMOPT_NO_RC_USE: + pSettings->fOnlyHrcVrcInsteadOfRc = true; + return VINF_SUCCESS; + case SCMOPT_UNRESTRICTED_RC_USE: + pSettings->fOnlyHrcVrcInsteadOfRc = false; + return VINF_SUCCESS; + + case SCMOPT_STANDARIZE_KMK: + pSettings->fStandarizeKmk = true; + return VINF_SUCCESS; + case SCMOPT_NO_STANDARIZE_KMK: + pSettings->fStandarizeKmk = false; + return VINF_SUCCESS; + + case SCMOPT_UPDATE_COPYRIGHT_YEAR: + pSettings->fUpdateCopyrightYear = true; + return VINF_SUCCESS; + case SCMOPT_NO_UPDATE_COPYRIGHT_YEAR: + pSettings->fUpdateCopyrightYear = false; + return VINF_SUCCESS; + + case SCMOPT_EXTERNAL_COPYRIGHT: + pSettings->fExternalCopyright = true; + return VINF_SUCCESS; + case SCMOPT_NO_EXTERNAL_COPYRIGHT: + pSettings->fExternalCopyright = false; + return VINF_SUCCESS; + + case SCMOPT_NO_UPDATE_LICENSE: + pSettings->enmUpdateLicense = kScmLicense_LeaveAlone; + return VINF_SUCCESS; + case SCMOPT_LICENSE_OSE_GPL: + pSettings->enmUpdateLicense = kScmLicense_OseGpl; + return VINF_SUCCESS; + case SCMOPT_LICENSE_OSE_DUAL_GPL_CDDL: + pSettings->enmUpdateLicense = kScmLicense_OseDualGplCddl; + return VINF_SUCCESS; + case SCMOPT_LICENSE_OSE_CDDL: + pSettings->enmUpdateLicense = kScmLicense_OseCddl; + return VINF_SUCCESS; + case SCMOPT_LICENSE_LGPL: + pSettings->enmUpdateLicense = kScmLicense_Lgpl; + return VINF_SUCCESS; + case SCMOPT_LICENSE_MIT: + pSettings->enmUpdateLicense = kScmLicense_Mit; + return VINF_SUCCESS; + case SCMOPT_LICENSE_BASED_ON_MIT: + pSettings->enmUpdateLicense = kScmLicense_BasedOnMit; + return VINF_SUCCESS; + + case SCMOPT_LGPL_DISCLAIMER: + pSettings->fLgplDisclaimer = true; + return VINF_SUCCESS; + case SCMOPT_NO_LGPL_DISCLAIMER: + pSettings->fLgplDisclaimer = false; + return VINF_SUCCESS; + + case SCMOPT_ONLY_SVN_DIRS: + pSettings->fOnlySvnDirs = true; + return VINF_SUCCESS; + case SCMOPT_NOT_ONLY_SVN_DIRS: + pSettings->fOnlySvnDirs = false; + return VINF_SUCCESS; + + case SCMOPT_ONLY_SVN_FILES: + pSettings->fOnlySvnFiles = true; + return VINF_SUCCESS; + case SCMOPT_NOT_ONLY_SVN_FILES: + pSettings->fOnlySvnFiles = false; + return VINF_SUCCESS; + + case SCMOPT_SET_SVN_EOL: + pSettings->fSetSvnEol = true; + return VINF_SUCCESS; + case SCMOPT_DONT_SET_SVN_EOL: + pSettings->fSetSvnEol = false; + return VINF_SUCCESS; + + case SCMOPT_SET_SVN_EXECUTABLE: + pSettings->fSetSvnExecutable = true; + return VINF_SUCCESS; + case SCMOPT_DONT_SET_SVN_EXECUTABLE: + pSettings->fSetSvnExecutable = false; + return VINF_SUCCESS; + + case SCMOPT_SET_SVN_KEYWORDS: + pSettings->fSetSvnKeywords = true; + return VINF_SUCCESS; + case SCMOPT_DONT_SET_SVN_KEYWORDS: + pSettings->fSetSvnKeywords = false; + return VINF_SUCCESS; + + case SCMOPT_SKIP_SVN_SYNC_PROCESS: + pSettings->fSkipSvnSyncProcess = true; + return VINF_SUCCESS; + case SCMOPT_DONT_SKIP_SVN_SYNC_PROCESS: + pSettings->fSkipSvnSyncProcess = false; + return VINF_SUCCESS; + + case SCMOPT_SKIP_UNICODE_CHECKS: + pSettings->fSkipUnicodeChecks = true; + return VINF_SUCCESS; + case SCMOPT_DONT_SKIP_UNICODE_CHECKS: + pSettings->fSkipUnicodeChecks = false; + return VINF_SUCCESS; + + case SCMOPT_TAB_SIZE: + if ( pValueUnion->u8 < 1 + || pValueUnion->u8 >= RT_ELEMENTS(g_szTabSpaces)) + { + RTMsgError("Invalid tab size: %u - must be in {1..%u}\n", + pValueUnion->u8, RT_ELEMENTS(g_szTabSpaces) - 1); + return VERR_OUT_OF_RANGE; + } + pSettings->cchTab = pValueUnion->u8; + return VINF_SUCCESS; + + case SCMOPT_WIDTH: + if (pValueUnion->u8 < 20 || pValueUnion->u8 > 200) + { + RTMsgError("Invalid width size: %u - must be in {20..200} range\n", pValueUnion->u8); + return VERR_OUT_OF_RANGE; + } + pSettings->cchWidth = pValueUnion->u8; + return VINF_SUCCESS; + + case SCMOPT_FILTER_OUT_DIRS: + case SCMOPT_FILTER_FILES: + case SCMOPT_FILTER_OUT_FILES: + { + char **ppsz = NULL; + switch (rc) + { + case SCMOPT_FILTER_OUT_DIRS: ppsz = &pSettings->pszFilterOutDirs; break; + case SCMOPT_FILTER_FILES: ppsz = &pSettings->pszFilterFiles; break; + case SCMOPT_FILTER_OUT_FILES: ppsz = &pSettings->pszFilterOutFiles; break; + } + + /* + * An empty string zaps the current list. + */ + if (!*pValueUnion->psz) + return RTStrATruncate(ppsz, 0); + + /* + * Non-empty strings are appended to the pattern list. + * + * Strip leading and trailing pattern separators before attempting + * to append it. If it's just separators, don't do anything. + */ + const char *pszSrc = pValueUnion->psz; + while (*pszSrc == '|') + pszSrc++; + size_t cchSrc = strlen(pszSrc); + while (cchSrc > 0 && pszSrc[cchSrc - 1] == '|') + cchSrc--; + if (!cchSrc) + return VINF_SUCCESS; + + /* Append it pattern by pattern, turning settings-relative paths into absolute ones. */ + for (;;) + { + const char *pszEnd = (const char *)memchr(pszSrc, '|', cchSrc); + size_t cchPattern = pszEnd ? pszEnd - pszSrc : cchSrc; + int rc2; + if (*pszSrc == '/') + rc2 = RTStrAAppendExN(ppsz, 3, + "|", *ppsz && **ppsz != '\0' ? (size_t)1 : (size_t)0, + pchDir, cchDir - 1, + pszSrc, cchPattern); + else + rc2 = RTStrAAppendExN(ppsz, 2, + "|", *ppsz && **ppsz != '\0' ? (size_t)1 : (size_t)0, + pszSrc, cchPattern); + if (RT_FAILURE(rc2)) + return rc2; + + /* next */ + cchSrc -= cchPattern; + if (!cchSrc) + return VINF_SUCCESS; + cchSrc -= 1; + pszSrc += cchPattern + 1; + } + /* not reached */ + } + + case SCMOPT_TREAT_AS: + if (pSettings->fFreeTreatAs) + { + scmCfgEntryDelete((PSCMCFGENTRY)pSettings->pTreatAs); + pSettings->pTreatAs = NULL; + pSettings->fFreeTreatAs = false; + } + + if (*pValueUnion->psz) + { + /* first check the names, then patterns (legacy). */ + for (size_t iCfg = 0; iCfg < RT_ELEMENTS(g_aConfigs); iCfg++) + if (strcmp(g_aConfigs[iCfg].pszName, pValueUnion->psz) == 0) + { + pSettings->pTreatAs = &g_aConfigs[iCfg]; + return VINF_SUCCESS; + } + for (size_t iCfg = 0; iCfg < RT_ELEMENTS(g_aConfigs); iCfg++) + if (RTStrSimplePatternMultiMatch(g_aConfigs[iCfg].pszFilePattern, RTSTR_MAX, + pValueUnion->psz, RTSTR_MAX, NULL)) + { + pSettings->pTreatAs = &g_aConfigs[iCfg]; + return VINF_SUCCESS; + } + /* Special help for listing the possibilities? */ + if (strcmp(pValueUnion->psz, "help") == 0) + { + RTPrintf("Possible --treat-as values:\n"); + for (size_t iCfg = 0; iCfg < RT_ELEMENTS(g_aConfigs); iCfg++) + RTPrintf(" %s (%s)\n", g_aConfigs[iCfg].pszName, g_aConfigs[iCfg].pszFilePattern); + } + return VERR_NOT_FOUND; + } + + pSettings->pTreatAs = NULL; + return VINF_SUCCESS; + + case SCMOPT_ADD_ACTION: + for (uint32_t iAction = 0; iAction < RT_ELEMENTS(g_papRewriterActions); iAction++) + if (strcmp(g_papRewriterActions[iAction]->pszName, pValueUnion->psz) == 0) + { + PSCMCFGENTRY pEntry = (PSCMCFGENTRY)pSettings->pTreatAs; + if (!pSettings->fFreeTreatAs) + { + pEntry = scmCfgEntryDup(pEntry); + if (!pEntry) + return VERR_NO_MEMORY; + pSettings->pTreatAs = pEntry; + pSettings->fFreeTreatAs = true; + } + return scmCfgEntryAddAction(pEntry, g_papRewriterActions[iAction]); + } + RTMsgError("Unknown --add-action value '%s'. Try --help-actions for a list.", pValueUnion->psz); + return VERR_NOT_FOUND; + + case SCMOPT_DEL_ACTION: + { + uint32_t cActions = 0; + for (uint32_t iAction = 0; iAction < RT_ELEMENTS(g_papRewriterActions); iAction++) + if (RTStrSimplePatternMatch(pValueUnion->psz, g_papRewriterActions[iAction]->pszName)) + { + cActions++; + PSCMCFGENTRY pEntry = (PSCMCFGENTRY)pSettings->pTreatAs; + if (!pSettings->fFreeTreatAs) + { + pEntry = scmCfgEntryDup(pEntry); + if (!pEntry) + return VERR_NO_MEMORY; + pSettings->pTreatAs = pEntry; + pSettings->fFreeTreatAs = true; + } + scmCfgEntryDelAction(pEntry, g_papRewriterActions[iAction]); + if (!strchr(pValueUnion->psz, '*')) + return VINF_SUCCESS; + } + if (cActions > 0) + return VINF_SUCCESS; + RTMsgError("Unknown --del-action value '%s'. Try --help-actions for a list.", pValueUnion->psz); + return VERR_NOT_FOUND; + } + + default: + return VERR_GETOPT_UNKNOWN_OPTION; + } +} + +/** + * Parses an option string. + * + * @returns IPRT status code. + * @param pBase The base settings structure to apply the options + * to. + * @param pszOptions The options to parse. + * @param pchDir The absolute path to the directory relative + * components in pchLine should be relative to. + * @param cchDir The length of the @a pchDir string. + */ +static int scmSettingsBaseParseString(PSCMSETTINGSBASE pBase, const char *pszLine, const char *pchDir, size_t cchDir) +{ + int cArgs; + char **papszArgs; + int rc = RTGetOptArgvFromString(&papszArgs, &cArgs, pszLine, RTGETOPTARGV_CNV_QUOTE_BOURNE_SH, NULL); + if (RT_SUCCESS(rc)) + { + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetOptState; + rc = RTGetOptInit(&GetOptState, cArgs, papszArgs, &g_aScmOpts[0], RT_ELEMENTS(g_aScmOpts), 0, 0 /*fFlags*/); + if (RT_SUCCESS(rc)) + { + while ((rc = RTGetOpt(&GetOptState, &ValueUnion)) != 0) + { + rc = scmSettingsBaseHandleOpt(pBase, rc, &ValueUnion, pchDir, cchDir); + if (RT_FAILURE(rc)) + break; + } + } + RTGetOptArgvFree(papszArgs); + } + + return rc; +} + +/** + * Parses an unterminated option string. + * + * @returns IPRT status code. + * @param pBase The base settings structure to apply the options + * to. + * @param pchLine The line. + * @param cchLine The line length. + * @param pchDir The absolute path to the directory relative + * components in pchLine should be relative to. + * @param cchDir The length of the @a pchDir string. + */ +static int scmSettingsBaseParseStringN(PSCMSETTINGSBASE pBase, const char *pchLine, size_t cchLine, + const char *pchDir, size_t cchDir) +{ + char *pszLine = RTStrDupN(pchLine, cchLine); + if (!pszLine) + return VERR_NO_MEMORY; + int rc = scmSettingsBaseParseString(pBase, pszLine, pchDir, cchDir); + RTStrFree(pszLine); + return rc; +} + +/** + * Verifies the options string. + * + * @returns IPRT status code. + * @param pszOptions The options to verify . + */ +static int scmSettingsBaseVerifyString(const char *pszOptions) +{ + SCMSETTINGSBASE Base; + int rc = scmSettingsBaseInit(&Base); + if (RT_SUCCESS(rc)) + { + rc = scmSettingsBaseParseString(&Base, pszOptions, "/", 1); + scmSettingsBaseDelete(&Base); + } + return rc; +} + +/** + * Loads settings found in editor and SCM settings directives within the + * document (@a pStream). + * + * @returns IPRT status code. + * @param pBase The settings base to load settings into. + * @param pStream The stream to scan for settings directives. + */ +static int scmSettingsBaseLoadFromDocument(PSCMSETTINGSBASE pBase, PSCMSTREAM pStream) +{ + /** @todo Editor and SCM settings directives in documents. */ + RT_NOREF2(pBase, pStream); + return VINF_SUCCESS; +} + +/** + * Creates a new settings file struct, cloning @a pSettings. + * + * @returns IPRT status code. + * @param ppSettings Where to return the new struct. + * @param pSettingsBase The settings to inherit from. + */ +static int scmSettingsCreate(PSCMSETTINGS *ppSettings, PCSCMSETTINGSBASE pSettingsBase) +{ + PSCMSETTINGS pSettings = (PSCMSETTINGS)RTMemAlloc(sizeof(*pSettings)); + if (!pSettings) + return VERR_NO_MEMORY; + int rc = scmSettingsBaseInitAndCopy(&pSettings->Base, pSettingsBase); + if (RT_SUCCESS(rc)) + { + pSettings->pDown = NULL; + pSettings->pUp = NULL; + pSettings->paPairs = NULL; + pSettings->cPairs = 0; + *ppSettings = pSettings; + return VINF_SUCCESS; + } + RTMemFree(pSettings); + return rc; +} + +/** + * Destroys a settings structure. + * + * @param pSettings The settings structure to destroy. NULL is OK. + */ +static void scmSettingsDestroy(PSCMSETTINGS pSettings) +{ + if (pSettings) + { + scmSettingsBaseDelete(&pSettings->Base); + for (size_t i = 0; i < pSettings->cPairs; i++) + { + RTStrFree(pSettings->paPairs[i].pszPattern); + RTStrFree(pSettings->paPairs[i].pszOptions); + RTStrFree(pSettings->paPairs[i].pszRelativeTo); + pSettings->paPairs[i].pszPattern = NULL; + pSettings->paPairs[i].pszOptions = NULL; + pSettings->paPairs[i].pszRelativeTo = NULL; + } + RTMemFree(pSettings->paPairs); + pSettings->paPairs = NULL; + RTMemFree(pSettings); + } +} + +/** + * Adds a pattern/options pair to the settings structure. + * + * @returns IPRT status code. + * @param pSettings The settings. + * @param pchLine The line containing the unparsed pair. + * @param cchLine The length of the line. + * @param offColon The offset of the colon into the line. + * @param pchDir The absolute path to the directory relative + * components in pchLine should be relative to. + * @param cchDir The length of the @a pchDir string. + */ +static int scmSettingsAddPair(PSCMSETTINGS pSettings, const char *pchLine, size_t cchLine, size_t offColon, + const char *pchDir, size_t cchDir) +{ + Assert(pchLine[offColon] == ':' && offColon < cchLine); + Assert(pchDir[cchDir - 1] == '/'); + + /* + * Split the string. + */ + size_t cchPattern = offColon; + size_t cchOptions = cchLine - cchPattern - 1; + + /* strip spaces everywhere */ + while (cchPattern > 0 && RT_C_IS_SPACE(pchLine[cchPattern - 1])) + cchPattern--; + while (cchPattern > 0 && RT_C_IS_SPACE(*pchLine)) + cchPattern--, pchLine++; + + const char *pchOptions = &pchLine[offColon + 1]; + while (cchOptions > 0 && RT_C_IS_SPACE(pchOptions[cchOptions - 1])) + cchOptions--; + while (cchOptions > 0 && RT_C_IS_SPACE(*pchOptions)) + cchOptions--, pchOptions++; + + /* Quietly ignore empty patterns and empty options. */ + if (!cchOptions || !cchPattern) + return VINF_SUCCESS; + + /* + * Prepair the pair and verify the option string. + */ + uint32_t iPair = pSettings->cPairs; + if ((iPair % 32) == 0) + { + void *pvNew = RTMemRealloc(pSettings->paPairs, (iPair + 32) * sizeof(pSettings->paPairs[0])); + if (!pvNew) + return VERR_NO_MEMORY; + pSettings->paPairs = (PSCMPATRNOPTPAIR)pvNew; + } + + pSettings->paPairs[iPair].pszPattern = RTStrDupN(pchLine, cchPattern); + pSettings->paPairs[iPair].pszOptions = RTStrDupN(pchOptions, cchOptions); + pSettings->paPairs[iPair].pszRelativeTo = RTStrDupN(pchDir, cchDir); + int rc; + if ( pSettings->paPairs[iPair].pszPattern + && pSettings->paPairs[iPair].pszOptions + && pSettings->paPairs[iPair].pszRelativeTo) + rc = scmSettingsBaseVerifyString(pSettings->paPairs[iPair].pszOptions); + else + rc = VERR_NO_MEMORY; + + /* + * If it checked out fine, expand any relative paths in the pattern. + */ + if (RT_SUCCESS(rc)) + { + size_t cPattern = 1; + size_t cRelativePaths = 0; + const char *pszSrc = pSettings->paPairs[iPair].pszPattern; + for (;;) + { + if (*pszSrc == '/') + cRelativePaths++; + pszSrc = strchr(pszSrc, '|'); + if (!pszSrc) + break; + pszSrc++; + cPattern++; + } + pSettings->paPairs[iPair].fMultiPattern = cPattern > 1; + if (cRelativePaths > 0) + { + char *pszNewPattern = RTStrAlloc(cchPattern + cRelativePaths * (cchDir - 1) + 1); + if (pszNewPattern) + { + char *pszDst = pszNewPattern; + pszSrc = pSettings->paPairs[iPair].pszPattern; + for (;;) + { + if (*pszSrc == '/') + { + memcpy(pszDst, pchDir, cchDir); + pszDst += cchDir; + pszSrc += 1; + } + + /* Look for the next relative path. */ + const char *pszSrcNext = strchr(pszSrc, '|'); + while (pszSrcNext && pszSrcNext[1] != '/') + pszSrcNext = strchr(pszSrcNext, '|'); + if (!pszSrcNext) + break; + + /* Copy stuff between current and the next path. */ + pszSrcNext++; + memcpy(pszDst, pszSrc, pszSrcNext - pszSrc); + pszDst += pszSrcNext - pszSrc; + pszSrc = pszSrcNext; + } + + /* Copy the final portion and replace the pattern. */ + strcpy(pszDst, pszSrc); + + RTStrFree(pSettings->paPairs[iPair].pszPattern); + pSettings->paPairs[iPair].pszPattern = pszNewPattern; + } + else + rc = VERR_NO_MEMORY; + } + } + if (RT_SUCCESS(rc)) + /* + * Commit the pair. + */ + pSettings->cPairs = iPair + 1; + else + { + RTStrFree(pSettings->paPairs[iPair].pszPattern); + RTStrFree(pSettings->paPairs[iPair].pszOptions); + RTStrFree(pSettings->paPairs[iPair].pszRelativeTo); + } + return rc; +} + +/** + * Loads in the settings from @a pszFilename. + * + * @returns IPRT status code. + * @param pSettings Where to load the settings file. + * @param pszFilename The file to load. + */ +static int scmSettingsLoadFile(PSCMSETTINGS pSettings, const char *pszFilename) +{ + ScmVerbose(NULL, 3, "Loading settings file '%s'...\n", pszFilename); + + /* Turn filename into an absolute path and drop the filename. */ + char szAbsPath[RTPATH_MAX]; + int rc = RTPathAbs(pszFilename, szAbsPath, sizeof(szAbsPath)); + if (RT_FAILURE(rc)) + { + RTMsgError("%s: RTPathAbs -> %Rrc\n", pszFilename, rc); + return rc; + } + RTPathChangeToUnixSlashes(szAbsPath, true); + size_t cchDir = RTPathFilename(szAbsPath) - &szAbsPath[0]; + + /* Try open it.*/ + SCMSTREAM Stream; + rc = ScmStreamInitForReading(&Stream, pszFilename); + if (RT_SUCCESS(rc)) + { + SCMEOL enmEol; + const char *pchLine; + size_t cchLine; + while ((pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol)) != NULL) + { + /* Ignore leading spaces. */ + while (cchLine > 0 && RT_C_IS_SPACE(*pchLine)) + pchLine++, cchLine--; + + /* Ignore empty lines and comment lines. */ + if (cchLine < 1 || *pchLine == '#') + continue; + + /* Deal with escaped newlines. */ + size_t iFirstLine = ~(size_t)0; + char *pszFreeLine = NULL; + if ( pchLine[cchLine - 1] == '\\' + && ( cchLine < 2 + || pchLine[cchLine - 2] != '\\') ) + { + iFirstLine = ScmStreamTellLine(&Stream); + + cchLine--; + while (cchLine > 0 && RT_C_IS_SPACE(pchLine[cchLine - 1])) + cchLine--; + + size_t cchTotal = cchLine; + pszFreeLine = RTStrDupN(pchLine, cchLine); + if (pszFreeLine) + { + /* Append following lines. */ + while ((pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol)) != NULL) + { + while (cchLine > 0 && RT_C_IS_SPACE(*pchLine)) + pchLine++, cchLine--; + + bool const fDone = cchLine == 0 + || pchLine[cchLine - 1] != '\\' + || (cchLine >= 2 && pchLine[cchLine - 2] == '\\'); + if (!fDone) + { + cchLine--; + while (cchLine > 0 && RT_C_IS_SPACE(pchLine[cchLine - 1])) + cchLine--; + } + + rc = RTStrRealloc(&pszFreeLine, cchTotal + 1 + cchLine + 1); + if (RT_FAILURE(rc)) + break; + pszFreeLine[cchTotal++] = ' '; + memcpy(&pszFreeLine[cchTotal], pchLine, cchLine); + cchTotal += cchLine; + pszFreeLine[cchTotal] = '\0'; + + if (fDone) + break; + } + } + else + rc = VERR_NO_STR_MEMORY; + + if (RT_FAILURE(rc)) + { + RTStrFree(pszFreeLine); + rc = RTMsgErrorRc(VERR_NO_MEMORY, "%s: Ran out of memory deal with escaped newlines", pszFilename); + break; + } + + pchLine = pszFreeLine; + cchLine = cchTotal; + } + + /* What kind of line is it? */ + const char *pchColon = (const char *)memchr(pchLine, ':', cchLine); + if (pchColon) + rc = scmSettingsAddPair(pSettings, pchLine, cchLine, pchColon - pchLine, szAbsPath, cchDir); + else + rc = scmSettingsBaseParseStringN(&pSettings->Base, pchLine, cchLine, szAbsPath, cchDir); + if (pszFreeLine) + RTStrFree(pszFreeLine); + if (RT_FAILURE(rc)) + { + RTMsgError("%s:%d: %Rrc\n", + pszFilename, iFirstLine == ~(size_t)0 ? ScmStreamTellLine(&Stream) : iFirstLine, rc); + break; + } + } + + if (RT_SUCCESS(rc)) + { + rc = ScmStreamGetStatus(&Stream); + if (RT_FAILURE(rc)) + RTMsgError("%s: ScmStreamGetStatus- > %Rrc\n", pszFilename, rc); + } + ScmStreamDelete(&Stream); + } + else + RTMsgError("%s: ScmStreamInitForReading -> %Rrc\n", pszFilename, rc); + return rc; +} + +#if 0 /* unused */ +/** + * Parse the specified settings file creating a new settings struct from it. + * + * @returns IPRT status code + * @param ppSettings Where to return the new settings. + * @param pszFilename The file to parse. + * @param pSettingsBase The base settings we inherit from. + */ +static int scmSettingsCreateFromFile(PSCMSETTINGS *ppSettings, const char *pszFilename, PCSCMSETTINGSBASE pSettingsBase) +{ + PSCMSETTINGS pSettings; + int rc = scmSettingsCreate(&pSettings, pSettingsBase); + if (RT_SUCCESS(rc)) + { + rc = scmSettingsLoadFile(pSettings, pszFilename, RTPathFilename(pszFilename) - pszFilename); + if (RT_SUCCESS(rc)) + { + *ppSettings = pSettings; + return VINF_SUCCESS; + } + + scmSettingsDestroy(pSettings); + } + *ppSettings = NULL; + return rc; +} +#endif + + +/** + * Create an initial settings structure when starting processing a new file or + * directory. + * + * This will look for .scm-settings files from the root and down to the + * specified directory, combining them into the returned settings structure. + * + * @returns IPRT status code. + * @param ppSettings Where to return the pointer to the top stack + * object. + * @param pBaseSettings The base settings we inherit from (globals + * typically). + * @param pszPath The absolute path to the new directory or file. + */ +static int scmSettingsCreateForPath(PSCMSETTINGS *ppSettings, PCSCMSETTINGSBASE pBaseSettings, const char *pszPath) +{ + *ppSettings = NULL; /* try shut up gcc. */ + + /* + * We'll be working with a stack copy of the path. + */ + char szFile[RTPATH_MAX]; + size_t cchDir = strlen(pszPath); + if (cchDir >= sizeof(szFile) - sizeof(SCM_SETTINGS_FILENAME)) + return VERR_FILENAME_TOO_LONG; + + /* + * Create the bottom-most settings. + */ + PSCMSETTINGS pSettings; + int rc = scmSettingsCreate(&pSettings, pBaseSettings); + if (RT_FAILURE(rc)) + return rc; + + /* + * Enumerate the path components from the root and down. Load any setting + * files we find. + */ + size_t cComponents = RTPathCountComponents(pszPath); + for (size_t i = 1; i <= cComponents; i++) + { + rc = RTPathCopyComponents(szFile, sizeof(szFile), pszPath, i); + if (RT_SUCCESS(rc)) + rc = RTPathAppend(szFile, sizeof(szFile), SCM_SETTINGS_FILENAME); + if (RT_FAILURE(rc)) + break; + RTPathChangeToUnixSlashes(szFile, true); + + if (RTFileExists(szFile)) + { + rc = scmSettingsLoadFile(pSettings, szFile); + if (RT_FAILURE(rc)) + break; + } + } + + if (RT_SUCCESS(rc)) + *ppSettings = pSettings; + else + scmSettingsDestroy(pSettings); + return rc; +} + +/** + * Pushes a new settings set onto the stack. + * + * @param ppSettingsStack The pointer to the pointer to the top stack + * element. This will be used as input and output. + * @param pSettings The settings to push onto the stack. + */ +static void scmSettingsStackPush(PSCMSETTINGS *ppSettingsStack, PSCMSETTINGS pSettings) +{ + PSCMSETTINGS pOld = *ppSettingsStack; + pSettings->pDown = pOld; + pSettings->pUp = NULL; + if (pOld) + pOld->pUp = pSettings; + *ppSettingsStack = pSettings; +} + +/** + * Pushes the settings of the specified directory onto the stack. + * + * We will load any .scm-settings in the directory. A stack entry is added even + * if no settings file was found. + * + * @returns IPRT status code. + * @param ppSettingsStack The pointer to the pointer to the top stack + * element. This will be used as input and output. + * @param pszDir The directory to do this for. + */ +static int scmSettingsStackPushDir(PSCMSETTINGS *ppSettingsStack, const char *pszDir) +{ + char szFile[RTPATH_MAX]; + int rc = RTPathJoin(szFile, sizeof(szFile), pszDir, SCM_SETTINGS_FILENAME); + if (RT_SUCCESS(rc)) + { + RTPathChangeToUnixSlashes(szFile, true); + + PSCMSETTINGS pSettings; + rc = scmSettingsCreate(&pSettings, &(*ppSettingsStack)->Base); + if (RT_SUCCESS(rc)) + { + if (RTFileExists(szFile)) + rc = scmSettingsLoadFile(pSettings, szFile); + if (RT_SUCCESS(rc)) + { + scmSettingsStackPush(ppSettingsStack, pSettings); + return VINF_SUCCESS; + } + + scmSettingsDestroy(pSettings); + } + } + return rc; +} + + +/** + * Pops a settings set off the stack. + * + * @returns The popped settings. + * @param ppSettingsStack The pointer to the pointer to the top stack + * element. This will be used as input and output. + */ +static PSCMSETTINGS scmSettingsStackPop(PSCMSETTINGS *ppSettingsStack) +{ + PSCMSETTINGS pRet = *ppSettingsStack; + PSCMSETTINGS pNew = pRet ? pRet->pDown : NULL; + *ppSettingsStack = pNew; + if (pNew) + pNew->pUp = NULL; + if (pRet) + { + pRet->pUp = NULL; + pRet->pDown = NULL; + } + return pRet; +} + +/** + * Pops and destroys the top entry of the stack. + * + * @param ppSettingsStack The pointer to the pointer to the top stack + * element. This will be used as input and output. + */ +static void scmSettingsStackPopAndDestroy(PSCMSETTINGS *ppSettingsStack) +{ + scmSettingsDestroy(scmSettingsStackPop(ppSettingsStack)); +} + +/** + * Constructs the base settings for the specified file name. + * + * @returns IPRT status code. + * @param pSettingsStack The top element on the settings stack. + * @param pszFilename The file name. + * @param pszBasename The base name (pointer within @a pszFilename). + * @param cchBasename The length of the base name. (For passing to + * RTStrSimplePatternMultiMatch.) + * @param pBase Base settings to initialize. + */ +static int scmSettingsStackMakeFileBase(PCSCMSETTINGS pSettingsStack, const char *pszFilename, + const char *pszBasename, size_t cchBasename, PSCMSETTINGSBASE pBase) +{ + ScmVerbose(NULL, 5, "scmSettingsStackMakeFileBase(%s, %.*s)\n", pszFilename, cchBasename, pszBasename); + + int rc = scmSettingsBaseInitAndCopy(pBase, &pSettingsStack->Base); + if (RT_SUCCESS(rc)) + { + /* find the bottom entry in the stack. */ + PCSCMSETTINGS pCur = pSettingsStack; + while (pCur->pDown) + pCur = pCur->pDown; + + /* Work our way up thru the stack and look for matching pairs. */ + while (pCur) + { + size_t const cPairs = pCur->cPairs; + if (cPairs) + { + for (size_t i = 0; i < cPairs; i++) + if ( !pCur->paPairs[i].fMultiPattern + ? RTStrSimplePatternNMatch(pCur->paPairs[i].pszPattern, RTSTR_MAX, + pszBasename, cchBasename) + || RTStrSimplePatternMatch(pCur->paPairs[i].pszPattern, pszFilename) + : RTStrSimplePatternMultiMatch(pCur->paPairs[i].pszPattern, RTSTR_MAX, + pszBasename, cchBasename, NULL) + || RTStrSimplePatternMultiMatch(pCur->paPairs[i].pszPattern, RTSTR_MAX, + pszFilename, RTSTR_MAX, NULL)) + { + ScmVerbose(NULL, 5, "scmSettingsStackMakeFileBase: Matched '%s' : '%s'\n", + pCur->paPairs[i].pszPattern, pCur->paPairs[i].pszOptions); + rc = scmSettingsBaseParseString(pBase, pCur->paPairs[i].pszOptions, + pCur->paPairs[i].pszRelativeTo, strlen(pCur->paPairs[i].pszRelativeTo)); + if (RT_FAILURE(rc)) + break; + } + if (RT_FAILURE(rc)) + break; + } + + /* advance */ + pCur = pCur->pUp; + } + } + if (RT_FAILURE(rc)) + scmSettingsBaseDelete(pBase); + return rc; +} + + +/* -=-=-=-=-=- misc -=-=-=-=-=- */ + + +/** + * Prints the per file banner needed and the message level is high enough. + * + * @param pState The rewrite state. + * @param iLevel The required verbosity level. + */ +void ScmVerboseBanner(PSCMRWSTATE pState, int iLevel) +{ + if (iLevel <= g_iVerbosity && !pState->fFirst) + { + RTPrintf("%s: info: --= Rewriting '%s' =--\n", g_szProgName, pState->pszFilename); + pState->fFirst = true; + } +} + + +/** + * Prints a verbose message if the level is high enough. + * + * @param pState The rewrite state. Optional. + * @param iLevel The required verbosity level. + * @param pszFormat The message format string. Can be NULL if we + * only want to trigger the per file message. + * @param ... Format arguments. + */ +void ScmVerbose(PSCMRWSTATE pState, int iLevel, const char *pszFormat, ...) +{ + if (iLevel <= g_iVerbosity) + { + if (pState && !pState->fFirst) + { + RTPrintf("%s: info: --= Rewriting '%s' =--\n", g_szProgName, pState->pszFilename); + pState->fFirst = true; + } + RTPrintf(pState + ? "%s: info: " + : "%s: info: ", + g_szProgName); + va_list va; + va_start(va, pszFormat); + RTPrintfV(pszFormat, va); + va_end(va); + } +} + + +/** + * Prints an error message. + * + * @returns kScmUnmodified + * @param pState The rewrite state. Optional. + * @param rc The error code. + * @param pszFormat The message format string. + * @param ... Format arguments. + */ +SCMREWRITERRES ScmError(PSCMRWSTATE pState, int rc, const char *pszFormat, ...) +{ + if (RT_SUCCESS(pState->rc)) + pState->rc = rc; + + if (!pState->fFirst) + { + RTPrintf("%s: info: --= Rewriting '%s' =--\n", g_szProgName, pState->pszFilename); + pState->fFirst = true; + } + va_list va; + va_start(va, pszFormat); + RTPrintf("%s: error: %s: %N", g_szProgName, pState->pszFilename, pszFormat, &va); + va_end(va); + + return kScmUnmodified; +} + + +/** + * Prints message indicating that something requires manual fixing. + * + * @returns false + * @param pState The rewrite state. Optional. + * @param rc The error code. + * @param pszFormat The message format string. + * @param ... Format arguments. + */ +bool ScmFixManually(PSCMRWSTATE pState, const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + ScmFixManuallyV(pState, pszFormat, va); + va_end(va); + return false; +} + + +/** + * Prints message indicating that something requires manual fixing. + * + * @returns false + * @param pState The rewrite state. Optional. + * @param rc The error code. + * @param pszFormat The message format string. + * @param va Format arguments. + */ +bool ScmFixManuallyV(PSCMRWSTATE pState, const char *pszFormat, va_list va) +{ + pState->fNeedsManualRepair = true; + + if (!pState->fFirst) + { + RTPrintf("%s: info: --= Rewriting '%s' =--\n", g_szProgName, pState->pszFilename); + pState->fFirst = true; + } + va_list vaCopy; + va_copy(vaCopy, va); + RTPrintf("%s: error/fixme: %s: %N", g_szProgName, pState->pszFilename, pszFormat, &vaCopy); + va_end(vaCopy); + + return false; +} + + +/* -=-=-=-=-=- file and directory processing -=-=-=-=-=- */ + + +/** + * Processes a file. + * + * @returns IPRT status code. + * @param pState The rewriter state. + * @param pszFilename The file name. + * @param pszBasename The base name (pointer within @a pszFilename). + * @param cchBasename The length of the base name. (For passing to + * RTStrSimplePatternMultiMatch.) + * @param pBaseSettings The base settings to use. It's OK to modify + * these. + */ +static int scmProcessFileInner(PSCMRWSTATE pState, const char *pszFilename, const char *pszBasename, size_t cchBasename, + PSCMSETTINGSBASE pBaseSettings) +{ + /* + * Do the file level filtering. + */ + if ( pBaseSettings->pszFilterFiles + && *pBaseSettings->pszFilterFiles + && !RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterFiles, RTSTR_MAX, pszBasename, cchBasename, NULL)) + { + ScmVerbose(NULL, 5, "skipping '%s': file filter mismatch\n", pszFilename); + g_cFilesSkipped++; + return VINF_SUCCESS; + } + if ( pBaseSettings->pszFilterOutFiles + && *pBaseSettings->pszFilterOutFiles + && ( RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterOutFiles, RTSTR_MAX, pszBasename, cchBasename, NULL) + || RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterOutFiles, RTSTR_MAX, pszFilename, RTSTR_MAX, NULL)) ) + { + ScmVerbose(NULL, 5, "skipping '%s': filterd out\n", pszFilename); + g_cFilesSkipped++; + return VINF_SUCCESS; + } + if ( pBaseSettings->fOnlySvnFiles + && !ScmSvnIsInWorkingCopy(pState)) + { + ScmVerbose(NULL, 5, "skipping '%s': not in SVN WC\n", pszFilename); + g_cFilesNotInSvn++; + return VINF_SUCCESS; + } + + /* + * Create an input stream from the file and check that it's text. + */ + SCMSTREAM Stream1; + int rc = ScmStreamInitForReading(&Stream1, pszFilename); + if (RT_FAILURE(rc)) + { + RTMsgError("Failed to read '%s': %Rrc\n", pszFilename, rc); + return rc; + } + bool const fIsText = ScmStreamIsText(&Stream1); + + /* + * Try find a matching rewrite config for this filename. + */ + PCSCMCFGENTRY pCfg = pBaseSettings->pTreatAs; + if (!pCfg) + { + for (size_t iCfg = 0; iCfg < RT_ELEMENTS(g_aConfigs); iCfg++) + if (RTStrSimplePatternMultiMatch(g_aConfigs[iCfg].pszFilePattern, RTSTR_MAX, pszBasename, cchBasename, NULL)) + { + pCfg = &g_aConfigs[iCfg]; + break; + } + if (!pCfg) + { + /* On failure try check for hash-bang stuff before giving up. */ + if (fIsText) + { + SCMEOL enmIgn; + size_t cchFirst; + const char *pchFirst = ScmStreamGetLine(&Stream1, &cchFirst, &enmIgn); + if (cchFirst >= 9 && pchFirst && *pchFirst == '#') + { + do + { + pchFirst++; + cchFirst--; + } while (cchFirst > 0 && RT_C_IS_BLANK(*pchFirst)); + if (*pchFirst == '!') + { + do + { + pchFirst++; + cchFirst--; + } while (cchFirst > 0 && RT_C_IS_BLANK(*pchFirst)); + const char *pszTreatAs = NULL; + if ( (cchFirst >= 7 && strncmp(pchFirst, "/bin/sh", 7) == 0) + || (cchFirst >= 9 && strncmp(pchFirst, "/bin/bash", 9) == 0) + || (cchFirst >= 4+9 && strncmp(pchFirst, "/usr/bin/bash", 4+9) == 0) ) + pszTreatAs = "shell"; + else if ( (cchFirst >= 15 && strncmp(pchFirst, "/usr/bin/python", 15) == 0) + || (cchFirst >= 19 && strncmp(pchFirst, "/usr/bin/env python", 19) == 0) ) + pszTreatAs = "python"; + else if ( (cchFirst >= 13 && strncmp(pchFirst, "/usr/bin/perl", 13) == 0) + || (cchFirst >= 17 && strncmp(pchFirst, "/usr/bin/env perl", 17) == 0) ) + pszTreatAs = "perl"; + if (pszTreatAs) + { + for (size_t iCfg = 0; iCfg < RT_ELEMENTS(g_aConfigs); iCfg++) + if (strcmp(pszTreatAs, g_aConfigs[iCfg].pszName) == 0) + { + pCfg = &g_aConfigs[iCfg]; + break; + } + Assert(pCfg); + } + } + } + ScmStreamRewindForReading(&Stream1); + } + if (!pCfg) + { + ScmVerbose(NULL, 2, "skipping '%s': no rewriters configured\n", pszFilename); + g_cFilesNoRewriters++; + ScmStreamDelete(&Stream1); + return VINF_SUCCESS; + } + } + ScmVerbose(pState, 4, "matched \"%s\" (%s)\n", pCfg->pszFilePattern, pCfg->pszName); + } + else + ScmVerbose(pState, 4, "treat-as \"%s\"\n", pCfg->pszName); + + if (fIsText || pCfg->fBinary) + { + ScmVerboseBanner(pState, 3); + + /* + * Gather SCM and editor settings from the stream. + */ + rc = scmSettingsBaseLoadFromDocument(pBaseSettings, &Stream1); + if (RT_SUCCESS(rc)) + { + ScmStreamRewindForReading(&Stream1); + + /* + * Create two more streams for output and push the text thru all the + * rewriters, switching the two streams around when something is + * actually rewritten. Stream1 remains unchanged. + */ + SCMSTREAM Stream2; + rc = ScmStreamInitForWriting(&Stream2, &Stream1); + if (RT_SUCCESS(rc)) + { + SCMSTREAM Stream3; + rc = ScmStreamInitForWriting(&Stream3, &Stream1); + if (RT_SUCCESS(rc)) + { + bool fModified = false; + PSCMSTREAM pIn = &Stream1; + PSCMSTREAM pOut = &Stream2; + for (size_t iRw = 0; iRw < pCfg->cRewriters; iRw++) + { + pState->rc = VINF_SUCCESS; + SCMREWRITERRES enmRes = pCfg->paRewriters[iRw]->pfnRewriter(pState, pIn, pOut, pBaseSettings); + if (RT_FAILURE(pState->rc)) + break; + if (enmRes == kScmMaybeModified) + enmRes = ScmStreamAreIdentical(pIn, pOut) ? kScmUnmodified : kScmModified; + if (enmRes == kScmModified) + { + PSCMSTREAM pTmp = pOut; + pOut = pIn == &Stream1 ? &Stream3 : pIn; + pIn = pTmp; + fModified = true; + } + + ScmStreamRewindForReading(pIn); + ScmStreamRewindForWriting(pOut); + } + + rc = pState->rc; + if (RT_SUCCESS(rc)) + { + rc = ScmStreamGetStatus(&Stream1); + if (RT_SUCCESS(rc)) + rc = ScmStreamGetStatus(&Stream2); + if (RT_SUCCESS(rc)) + rc = ScmStreamGetStatus(&Stream3); + if (RT_SUCCESS(rc)) + { + /* + * If rewritten, write it back to disk. + */ + if (fModified && !pCfg->fBinary) + { + if (!g_fDryRun) + { + ScmVerbose(pState, 1, "writing modified file to \"%s%s\"\n", pszFilename, g_pszChangedSuff); + rc = ScmStreamWriteToFile(pIn, "%s%s", pszFilename, g_pszChangedSuff); + if (RT_FAILURE(rc)) + RTMsgError("Error writing '%s%s': %Rrc\n", pszFilename, g_pszChangedSuff, rc); + } + else + { + ScmVerboseBanner(pState, 1); + ScmDiffStreams(pszFilename, &Stream1, pIn, g_fDiffIgnoreEol, + g_fDiffIgnoreLeadingWS, g_fDiffIgnoreTrailingWS, g_fDiffSpecialChars, + pBaseSettings->cchTab, g_pStdOut); + ScmVerbose(pState, 2, "would have modified the file \"%s%s\"\n", + pszFilename, g_pszChangedSuff); + } + g_cFilesModified++; + } + else if (fModified) + rc = RTMsgErrorRc(VERR_INTERNAL_ERROR, "Rewriters modified binary file! Impossible!"); + + /* + * If pending SVN property changes, apply them. + */ + if (pState->cSvnPropChanges && RT_SUCCESS(rc)) + { + if (!g_fDryRun) + { + rc = ScmSvnApplyChanges(pState); + if (RT_FAILURE(rc)) + RTMsgError("%s: failed to apply SVN property changes (%Rrc)\n", pszFilename, rc); + } + else + ScmSvnDisplayChanges(pState); + if (!fModified) + g_cFilesModified++; + } + + if (!fModified && !pState->cSvnPropChanges) + ScmVerbose(pState, 3, "%s: no change\n", pszFilename); + } + else + RTMsgError("%s: stream error %Rrc\n", pszFilename, rc); + } + ScmStreamDelete(&Stream3); + } + else + RTMsgError("Failed to init stream for writing: %Rrc\n", rc); + ScmStreamDelete(&Stream2); + } + else + RTMsgError("Failed to init stream for writing: %Rrc\n", rc); + } + else + RTMsgError("scmSettingsBaseLoadFromDocument: %Rrc\n", rc); + } + else + { + ScmVerbose(pState, 2, "not text file: \"%s\"\n", pszFilename); + g_cFilesBinaries++; + } + ScmStreamDelete(&Stream1); + + return rc; +} + +/** + * Processes a file. + * + * This is just a wrapper for scmProcessFileInner for avoid wasting stack in the + * directory recursion method. + * + * @returns IPRT status code. + * @param pszFilename The file name. + * @param pszBasename The base name (pointer within @a pszFilename). + * @param cchBasename The length of the base name. (For passing to + * RTStrSimplePatternMultiMatch.) + * @param pSettingsStack The settings stack (pointer to the top element). + */ +static int scmProcessFile(const char *pszFilename, const char *pszBasename, size_t cchBasename, + PSCMSETTINGS pSettingsStack) +{ + SCMSETTINGSBASE Base; + int rc = scmSettingsStackMakeFileBase(pSettingsStack, pszFilename, pszBasename, cchBasename, &Base); + if (RT_SUCCESS(rc)) + { + SCMRWSTATE State; + State.pszFilename = pszFilename; + State.fFirst = false; + State.fNeedsManualRepair = false; + State.fIsInSvnWorkingCopy = 0; + State.cSvnPropChanges = 0; + State.paSvnPropChanges = NULL; + State.rc = VINF_SUCCESS; + + rc = scmProcessFileInner(&State, pszFilename, pszBasename, cchBasename, &Base); + + size_t i = State.cSvnPropChanges; + while (i-- > 0) + { + RTStrFree(State.paSvnPropChanges[i].pszName); + RTStrFree(State.paSvnPropChanges[i].pszValue); + } + RTMemFree(State.paSvnPropChanges); + + scmSettingsBaseDelete(&Base); + + if (State.fNeedsManualRepair) + g_cFilesRequiringManualFixing++; + g_cFilesProcessed++; + } + return rc; +} + +/** + * Tries to correct RTDIRENTRY_UNKNOWN. + * + * @returns Corrected type. + * @param pszPath The path to the object in question. + */ +static RTDIRENTRYTYPE scmFigureUnknownType(const char *pszPath) +{ + RTFSOBJINFO Info; + int rc = RTPathQueryInfo(pszPath, &Info, RTFSOBJATTRADD_NOTHING); + if (RT_FAILURE(rc)) + return RTDIRENTRYTYPE_UNKNOWN; + if (RTFS_IS_DIRECTORY(Info.Attr.fMode)) + return RTDIRENTRYTYPE_DIRECTORY; + if (RTFS_IS_FILE(Info.Attr.fMode)) + return RTDIRENTRYTYPE_FILE; + return RTDIRENTRYTYPE_UNKNOWN; +} + +/** + * Recurse into a sub-directory and process all the files and directories. + * + * @returns IPRT status code. + * @param pszBuf Path buffer containing the directory path on + * entry. This ends with a dot. This is passed + * along when recursing in order to save stack space + * and avoid needless copying. + * @param cchDir Length of our path in pszbuf. + * @param pEntry Directory entry buffer. This is also passed + * along when recursing to save stack space. + * @param pSettingsStack The settings stack (pointer to the top element). + * @param iRecursion The recursion depth. This is used to restrict + * the recursions. + */ +static int scmProcessDirTreeRecursion(char *pszBuf, size_t cchDir, PRTDIRENTRY pEntry, + PSCMSETTINGS pSettingsStack, unsigned iRecursion) +{ + int rc; + Assert(cchDir > 1 && pszBuf[cchDir - 1] == '.'); + + /* + * Make sure we stop somewhere. + */ + if (iRecursion > 128) + { + RTMsgError("recursion too deep: %d\n", iRecursion); + return VINF_SUCCESS; /* ignore */ + } + + /* + * Check if it's excluded by --only-svn-dir. + */ + if (pSettingsStack->Base.fOnlySvnDirs) + { + if (!ScmSvnIsDirInWorkingCopy(pszBuf)) + return VINF_SUCCESS; + } + g_cDirsProcessed++; + + /* + * Try open and read the directory. + */ + RTDIR hDir; + rc = RTDirOpenFiltered(&hDir, pszBuf, RTDIRFILTER_NONE, 0 /*fFlags*/); + if (RT_FAILURE(rc)) + { + RTMsgError("Failed to enumerate directory '%s': %Rrc", pszBuf, rc); + return rc; + } + for (;;) + { + /* Read the next entry. */ + rc = RTDirRead(hDir, pEntry, NULL); + if (RT_FAILURE(rc)) + { + if (rc == VERR_NO_MORE_FILES) + rc = VINF_SUCCESS; + else + RTMsgError("RTDirRead -> %Rrc\n", rc); + break; + } + + /* Skip '.' and '..'. */ + if ( pEntry->szName[0] == '.' + && ( pEntry->cbName == 1 + || ( pEntry->cbName == 2 + && pEntry->szName[1] == '.'))) + continue; + + /* Enter it into the buffer so we've got a full name to work + with when needed. */ + if (pEntry->cbName + cchDir >= RTPATH_MAX) + { + RTMsgError("Skipping too long entry: %s", pEntry->szName); + continue; + } + memcpy(&pszBuf[cchDir - 1], pEntry->szName, pEntry->cbName + 1); + + /* Figure the type. */ + RTDIRENTRYTYPE enmType = pEntry->enmType; + if (enmType == RTDIRENTRYTYPE_UNKNOWN) + enmType = scmFigureUnknownType(pszBuf); + + /* Process the file or directory, skip the rest. */ + if (enmType == RTDIRENTRYTYPE_FILE) + rc = scmProcessFile(pszBuf, pEntry->szName, pEntry->cbName, pSettingsStack); + else if (enmType == RTDIRENTRYTYPE_DIRECTORY) + { + /* Append the dot for the benefit of the pattern matching. */ + if (pEntry->cbName + cchDir + 5 >= RTPATH_MAX) + { + RTMsgError("Skipping too deep dir entry: %s", pEntry->szName); + continue; + } + memcpy(&pszBuf[cchDir - 1 + pEntry->cbName], "/.", sizeof("/.")); + size_t cchSubDir = cchDir - 1 + pEntry->cbName + sizeof("/.") - 1; + + if ( !pSettingsStack->Base.pszFilterOutDirs + || !*pSettingsStack->Base.pszFilterOutDirs + || ( !RTStrSimplePatternMultiMatch(pSettingsStack->Base.pszFilterOutDirs, RTSTR_MAX, + pEntry->szName, pEntry->cbName, NULL) + && !RTStrSimplePatternMultiMatch(pSettingsStack->Base.pszFilterOutDirs, RTSTR_MAX, + pszBuf, cchSubDir, NULL) + ) + ) + { + rc = scmSettingsStackPushDir(&pSettingsStack, pszBuf); + if (RT_SUCCESS(rc)) + { + rc = scmProcessDirTreeRecursion(pszBuf, cchSubDir, pEntry, pSettingsStack, iRecursion + 1); + scmSettingsStackPopAndDestroy(&pSettingsStack); + } + } + } + if (RT_FAILURE(rc)) + break; + } + RTDirClose(hDir); + return rc; + +} + +/** + * Process a directory tree. + * + * @returns IPRT status code. + * @param pszDir The directory to start with. This is pointer to + * a RTPATH_MAX sized buffer. + */ +static int scmProcessDirTree(char *pszDir, PSCMSETTINGS pSettingsStack) +{ + /* + * Setup the recursion. + */ + int rc = RTPathAppend(pszDir, RTPATH_MAX, "."); + if (RT_SUCCESS(rc)) + { + RTPathChangeToUnixSlashes(pszDir, true); + + RTDIRENTRY Entry; + rc = scmProcessDirTreeRecursion(pszDir, strlen(pszDir), &Entry, pSettingsStack, 0); + } + else + RTMsgError("RTPathAppend: %Rrc\n", rc); + return rc; +} + + +/** + * Processes a file or directory specified as an command line argument. + * + * @returns IPRT status code + * @param pszSomething What we found in the command line arguments. + * @param pSettingsStack The settings stack (pointer to the top element). + */ +static int scmProcessSomething(const char *pszSomething, PSCMSETTINGS pSettingsStack) +{ + char szBuf[RTPATH_MAX]; + int rc = RTPathAbs(pszSomething, szBuf, sizeof(szBuf)); + if (RT_SUCCESS(rc)) + { + RTPathChangeToUnixSlashes(szBuf, false /*fForce*/); + + PSCMSETTINGS pSettings; + rc = scmSettingsCreateForPath(&pSettings, &pSettingsStack->Base, szBuf); + if (RT_SUCCESS(rc)) + { + scmSettingsStackPush(&pSettingsStack, pSettings); + + if (RTFileExists(szBuf)) + { + const char *pszBasename = RTPathFilename(szBuf); + if (pszBasename) + { + size_t cchBasename = strlen(pszBasename); + rc = scmProcessFile(szBuf, pszBasename, cchBasename, pSettingsStack); + } + else + { + RTMsgError("RTPathFilename: NULL\n"); + rc = VERR_IS_A_DIRECTORY; + } + } + else + rc = scmProcessDirTree(szBuf, pSettingsStack); + + PSCMSETTINGS pPopped = scmSettingsStackPop(&pSettingsStack); + Assert(pPopped == pSettings); RT_NOREF_PV(pPopped); + scmSettingsDestroy(pSettings); + } + else + RTMsgError("scmSettingsInitStack: %Rrc\n", rc); + } + else + RTMsgError("RTPathAbs: %Rrc\n", rc); + return rc; +} + +/** + * Print some stats. + */ +static void scmPrintStats(void) +{ + ScmVerbose(NULL, 0, + g_fDryRun + ? "%u out of %u file%s in %u dir%s would be modified (%u without rewriter%s, %u binar%s, %u not in svn, %u skipped)\n" + : "%u out of %u file%s in %u dir%s was modified (%u without rewriter%s, %u binar%s, %u not in svn, %u skipped)\n", + g_cFilesModified, + g_cFilesProcessed, g_cFilesProcessed == 1 ? "" : "s", + g_cDirsProcessed, g_cDirsProcessed == 1 ? "" : "s", + g_cFilesNoRewriters, g_cFilesNoRewriters == 1 ? "" : "s", + g_cFilesBinaries, g_cFilesBinaries == 1 ? "y" : "ies", + g_cFilesNotInSvn, g_cFilesSkipped); +} + +/** + * Display the rewriter actions. + * + * @returns RTEXITCODE_SUCCESS. + */ +static int scmHelpActions(void) +{ + RTPrintf("Available rewriter actions:\n"); + for (uint32_t i = 0; i < RT_ELEMENTS(g_papRewriterActions); i++) + RTPrintf(" %s\n", g_papRewriterActions[i]->pszName); + return RTEXITCODE_SUCCESS; +} + +/** + * Display the default configuration. + * + * @returns RTEXITCODE_SUCCESS. + */ +static int scmHelpConfig(void) +{ + RTPrintf("Rewriter configuration:\n"); + for (size_t iCfg = 0; iCfg < RT_ELEMENTS(g_aConfigs); iCfg++) + { + RTPrintf("\n %s%s - %s:\n", + g_aConfigs[iCfg].pszName, g_aConfigs[iCfg].fBinary ? " (binary)" : "", g_aConfigs[iCfg].pszFilePattern); + for (size_t i = 0; i < g_aConfigs[iCfg].cRewriters; i++) + RTPrintf(" %s\n", g_aConfigs[iCfg].paRewriters[i]->pszName); + } + return RTEXITCODE_SUCCESS; +} + +/** + * Display the primary help text. + * + * @returns RTEXITCODE_SUCCESS. + * @param paOpts Options. + * @param cOpts Number of options. + */ +static int scmHelp(PCRTGETOPTDEF paOpts, size_t cOpts) +{ + RTPrintf("VirtualBox Source Code Massager\n" + "\n" + "Usage: %s [options] <files & dirs>\n" + "\n" + "General options:\n", g_szProgName); + for (size_t i = 0; i < cOpts; i++) + { + /* Grouping. */ + switch (paOpts[i].iShort) + { + case SCMOPT_DIFF_IGNORE_EOL: + RTPrintf("\nDiff options (dry runs):\n"); + break; + case SCMOPT_CONVERT_EOL: + RTPrintf("\nRewriter action options:\n"); + break; + case SCMOPT_ONLY_SVN_DIRS: + RTPrintf("\nInput selection options:\n"); + break; + case SCMOPT_TREAT_AS: + RTPrintf("\nMisc options:\n"); + break; + } + + size_t cExtraAdvance = 0; + if ((paOpts[i].fFlags & RTGETOPT_REQ_MASK) == RTGETOPT_REQ_NOTHING) + { + cExtraAdvance = i + 1 < cOpts + && ( strstr(paOpts[i+1].pszLong, "-no-") != NULL + || strstr(paOpts[i+1].pszLong, "-not-") != NULL + || strstr(paOpts[i+1].pszLong, "-dont-") != NULL + || strstr(paOpts[i+1].pszLong, "-unrestricted-") != NULL + || (paOpts[i].iShort == 'q' && paOpts[i+1].iShort == 'v') + || (paOpts[i].iShort == 'd' && paOpts[i+1].iShort == 'D') + ); + if (cExtraAdvance) + RTPrintf(" %s, %s\n", paOpts[i].pszLong, paOpts[i + 1].pszLong); + else if (paOpts[i].iShort != SCMOPT_NO_UPDATE_LICENSE) + RTPrintf(" %s\n", paOpts[i].pszLong); + else + { + RTPrintf(" %s,\n" + " %s,\n" + " %s,\n" + " %s,\n" + " %s,\n" + " %s,\n" + " %s\n", + paOpts[i].pszLong, + paOpts[i + 1].pszLong, + paOpts[i + 2].pszLong, + paOpts[i + 3].pszLong, + paOpts[i + 4].pszLong, + paOpts[i + 5].pszLong, + paOpts[i + 6].pszLong); + cExtraAdvance = 6; + } + } + else if ((paOpts[i].fFlags & RTGETOPT_REQ_MASK) == RTGETOPT_REQ_STRING) + switch (paOpts[i].iShort) + { + case SCMOPT_DEL_ACTION: + RTPrintf(" %s pattern\n", paOpts[i].pszLong); + break; + case SCMOPT_FILTER_OUT_DIRS: + case SCMOPT_FILTER_FILES: + case SCMOPT_FILTER_OUT_FILES: + RTPrintf(" %s multi-pattern\n", paOpts[i].pszLong); + break; + default: + RTPrintf(" %s string\n", paOpts[i].pszLong); + } + else + RTPrintf(" %s value\n", paOpts[i].pszLong); + switch (paOpts[i].iShort) + { + case 'd': + case 'D': RTPrintf(" Default: --dry-run\n"); break; + case SCMOPT_CHECK_RUN: RTPrintf(" Default: --dry-run\n"); break; + case 'f': RTPrintf(" Default: none\n"); break; + case 'q': + case 'v': RTPrintf(" Default: -vv\n"); break; + case SCMOPT_HELP_CONFIG: RTPrintf(" Shows the standard file rewriter configurations.\n"); break; + case SCMOPT_HELP_ACTIONS: RTPrintf(" Shows the available rewriter actions.\n"); break; + + case SCMOPT_DIFF_IGNORE_EOL: RTPrintf(" Default: false\n"); break; + case SCMOPT_DIFF_IGNORE_SPACE: RTPrintf(" Default: false\n"); break; + case SCMOPT_DIFF_IGNORE_LEADING_SPACE: RTPrintf(" Default: false\n"); break; + case SCMOPT_DIFF_IGNORE_TRAILING_SPACE: RTPrintf(" Default: false\n"); break; + case SCMOPT_DIFF_SPECIAL_CHARS: RTPrintf(" Default: true\n"); break; + + case SCMOPT_CONVERT_EOL: RTPrintf(" Default: %RTbool\n", g_Defaults.fConvertEol); break; + case SCMOPT_CONVERT_TABS: RTPrintf(" Default: %RTbool\n", g_Defaults.fConvertTabs); break; + case SCMOPT_FORCE_FINAL_EOL: RTPrintf(" Default: %RTbool\n", g_Defaults.fForceFinalEol); break; + case SCMOPT_FORCE_TRAILING_LINE: RTPrintf(" Default: %RTbool\n", g_Defaults.fForceTrailingLine); break; + case SCMOPT_STRIP_TRAILING_BLANKS: RTPrintf(" Default: %RTbool\n", g_Defaults.fStripTrailingBlanks); break; + case SCMOPT_STRIP_TRAILING_LINES: RTPrintf(" Default: %RTbool\n", g_Defaults.fStripTrailingLines); break; + case SCMOPT_FIX_FLOWER_BOX_MARKERS: RTPrintf(" Default: %RTbool\n", g_Defaults.fFixFlowerBoxMarkers); break; + case SCMOPT_MIN_BLANK_LINES_BEFORE_FLOWER_BOX_MARKERS: RTPrintf(" Default: %u\n", g_Defaults.cMinBlankLinesBeforeFlowerBoxMakers); break; + + case SCMOPT_FIX_HEADER_GUARDS: + RTPrintf(" Fix header guards and #pragma once. Default: %RTbool\n", g_Defaults.fFixHeaderGuards); + break; + case SCMOPT_PRAGMA_ONCE: + RTPrintf(" Whether to include #pragma once with the header guard. Default: %RTbool\n", g_Defaults.fPragmaOnce); + break; + case SCMOPT_FIX_HEADER_GUARD_ENDIF: + RTPrintf(" Whether to fix the #endif of a header guard. Default: %RTbool\n", g_Defaults.fFixHeaderGuardEndif); + break; + case SCMOPT_ENDIF_GUARD_COMMENT: + RTPrintf(" Put a comment on the header guard #endif or not. Default: %RTbool\n", g_Defaults.fEndifGuardComment); + break; + case SCMOPT_GUARD_RELATIVE_TO_DIR: + RTPrintf(" Header guard should be normalized relative to given dir.\n" + " When relative to settings files, no preceeding slash.\n" + " Header relative directory specification: {dir} and {parent}\n" + " If empty no normalization takes place. Default: '%s'\n", g_Defaults.pszGuardRelativeToDir); + break; + case SCMOPT_GUARD_PREFIX: + RTPrintf(" Prefix to use with --guard-relative-to-dir. Default: %s\n", g_Defaults.pszGuardPrefix); + break; + case SCMOPT_FIX_TODOS: + RTPrintf(" Fix @todo statements so doxygen sees them. Default: %RTbool\n", g_Defaults.fFixTodos); + break; + case SCMOPT_FIX_ERR_H: + RTPrintf(" Fix err.h/errcore.h usage. Default: %RTbool\n", g_Defaults.fFixErrH); + break; + case SCMOPT_ONLY_GUEST_HOST_PAGE: + RTPrintf(" No PAGE_SIZE, PAGE_SHIFT or PAGE_OFFSET_MASK allowed, must have\n" + " GUEST_ or HOST_ prefix. Also forbids use of PAGE_BASE_MASK,\n" + " PAGE_BASE_HC_MASK, PAGE_BASE_GC_MASK, PAGE_ADDRESS,\n" + " PHYS_PAGE_ADDRESS. Default: %RTbool\n", g_Defaults.fOnlyGuestHostPage); + break; + case SCMOPT_NO_ASM_MEM_PAGE_USE: + RTPrintf(" No ASMMemIsZeroPage or ASMMemZeroPage allowed, must instead use\n" + " ASMMemIsZero and RT_BZERO with appropriate page size. Default: %RTbool\n", + g_Defaults.fNoASMMemPageUse); + break; + case SCMOPT_NO_RC_USE: + RTPrintf(" No rc declaration allowed, must instead use\n" + " vrc for IPRT status codes and hrc for COM status codes. Default: %RTbool\n", + g_Defaults.fOnlyHrcVrcInsteadOfRc); + break; + case SCMOPT_STANDARIZE_KMK: + RTPrintf(" Clean up kmk files (the makefile-kmk action). Default: %RTbool\n", g_Defaults.fStandarizeKmk); + break; + case SCMOPT_UPDATE_COPYRIGHT_YEAR: + RTPrintf(" Update the copyright year. Default: %RTbool\n", g_Defaults.fUpdateCopyrightYear); + break; + case SCMOPT_EXTERNAL_COPYRIGHT: + RTPrintf(" Only external copyright holders. Default: %RTbool\n", g_Defaults.fExternalCopyright); + break; + case SCMOPT_NO_UPDATE_LICENSE: + RTPrintf(" License selection. Default: --license-ose-gpl\n"); + break; + + case SCMOPT_LGPL_DISCLAIMER: + RTPrintf(" Include LGPL version disclaimer. Default: --no-lgpl-disclaimer\n"); + break; + + case SCMOPT_SET_SVN_EOL: RTPrintf(" Default: %RTbool\n", g_Defaults.fSetSvnEol); break; + case SCMOPT_SET_SVN_EXECUTABLE: RTPrintf(" Default: %RTbool\n", g_Defaults.fSetSvnExecutable); break; + case SCMOPT_SET_SVN_KEYWORDS: RTPrintf(" Default: %RTbool\n", g_Defaults.fSetSvnKeywords); break; + case SCMOPT_SKIP_SVN_SYNC_PROCESS: RTPrintf(" Default: %RTbool\n", g_Defaults.fSkipSvnSyncProcess); break; + case SCMOPT_SKIP_UNICODE_CHECKS: RTPrintf(" Default: %RTbool\n", g_Defaults.fSkipUnicodeChecks); break; + case SCMOPT_TAB_SIZE: RTPrintf(" Default: %u\n", g_Defaults.cchTab); break; + case SCMOPT_WIDTH: RTPrintf(" Default: %u\n", g_Defaults.cchWidth); break; + + case SCMOPT_ONLY_SVN_DIRS: RTPrintf(" Default: %RTbool\n", g_Defaults.fOnlySvnDirs); break; + case SCMOPT_ONLY_SVN_FILES: RTPrintf(" Default: %RTbool\n", g_Defaults.fOnlySvnFiles); break; + case SCMOPT_FILTER_OUT_DIRS: RTPrintf(" Default: %s\n", g_Defaults.pszFilterOutDirs); break; + case SCMOPT_FILTER_FILES: RTPrintf(" Default: %s\n", g_Defaults.pszFilterFiles); break; + case SCMOPT_FILTER_OUT_FILES: RTPrintf(" Default: %s\n", g_Defaults.pszFilterOutFiles); break; + + case SCMOPT_TREAT_AS: + RTPrintf(" For treat the input file(s) differently, restting any --add-action.\n" + " If the value is empty defaults will be used again. Possible values:\n"); + for (size_t iCfg = 0; iCfg < RT_ELEMENTS(g_aConfigs); iCfg++) + RTPrintf(" %s (%s)\n", g_aConfigs[iCfg].pszName, g_aConfigs[iCfg].pszFilePattern); + break; + + case SCMOPT_ADD_ACTION: + RTPrintf(" Adds a rewriter action. The first use after a --treat-as will copy and\n" + " the action list selected by the --treat-as. The action list will be\n" + " flushed by --treat-as.\n"); + break; + + case SCMOPT_DEL_ACTION: + RTPrintf(" Deletes one or more rewriter action (pattern). Best used after\n" + " a --treat-as.\n"); + break; + + default: AssertMsgFailed(("i=%d %d %s\n", i, paOpts[i].iShort, paOpts[i].pszLong)); + } + i += cExtraAdvance; + } + + return RTEXITCODE_SUCCESS; +} + +int main(int argc, char **argv) +{ + int rc = RTR3InitExe(argc, &argv, 0); + if (RT_FAILURE(rc)) + return 1; + + /* + * Init the current year. + */ + RTTIMESPEC Now; + RTTIME Time; + RTTimeExplode(&Time, RTTimeNow(&Now)); + g_uYear = Time.i32Year; + + /* + * Init the settings. + */ + PSCMSETTINGS pSettings; + rc = scmSettingsCreate(&pSettings, &g_Defaults); + if (RT_FAILURE(rc)) + { + RTMsgError("scmSettingsCreate: %Rrc\n", rc); + return 1; + } + + /* + * Parse arguments and process input in order (because this is the only + * thing that works at the moment). + */ + static RTGETOPTDEF s_aOpts[14 + RT_ELEMENTS(g_aScmOpts)] = + { + { "--dry-run", 'd', RTGETOPT_REQ_NOTHING }, + { "--real-run", 'D', RTGETOPT_REQ_NOTHING }, + { "--check-run", SCMOPT_CHECK_RUN, RTGETOPT_REQ_NOTHING }, + { "--file-filter", 'f', RTGETOPT_REQ_STRING }, + { "--quiet", 'q', RTGETOPT_REQ_NOTHING }, + { "--verbose", 'v', RTGETOPT_REQ_NOTHING }, + { "--diff-ignore-eol", SCMOPT_DIFF_IGNORE_EOL, RTGETOPT_REQ_NOTHING }, + { "--diff-no-ignore-eol", SCMOPT_DIFF_NO_IGNORE_EOL, RTGETOPT_REQ_NOTHING }, + { "--diff-ignore-space", SCMOPT_DIFF_IGNORE_SPACE, RTGETOPT_REQ_NOTHING }, + { "--diff-no-ignore-space", SCMOPT_DIFF_NO_IGNORE_SPACE, RTGETOPT_REQ_NOTHING }, + { "--diff-ignore-leading-space", SCMOPT_DIFF_IGNORE_LEADING_SPACE, RTGETOPT_REQ_NOTHING }, + { "--diff-no-ignore-leading-space", SCMOPT_DIFF_NO_IGNORE_LEADING_SPACE, RTGETOPT_REQ_NOTHING }, + { "--diff-ignore-trailing-space", SCMOPT_DIFF_IGNORE_TRAILING_SPACE, RTGETOPT_REQ_NOTHING }, + { "--diff-no-ignore-trailing-space", SCMOPT_DIFF_NO_IGNORE_TRAILING_SPACE, RTGETOPT_REQ_NOTHING }, + { "--diff-special-chars", SCMOPT_DIFF_SPECIAL_CHARS, RTGETOPT_REQ_NOTHING }, + { "--diff-no-special-chars", SCMOPT_DIFF_NO_SPECIAL_CHARS, RTGETOPT_REQ_NOTHING }, + }; + memcpy(&s_aOpts[RT_ELEMENTS(s_aOpts) - RT_ELEMENTS(g_aScmOpts)], &g_aScmOpts[0], sizeof(g_aScmOpts)); + + bool fCheckRun = false; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetOptState; + rc = RTGetOptInit(&GetOptState, argc, argv, &s_aOpts[0], RT_ELEMENTS(s_aOpts), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST); + AssertReleaseRCReturn(rc, 1); + + while ( (rc = RTGetOpt(&GetOptState, &ValueUnion)) != 0 + && rc != VINF_GETOPT_NOT_OPTION) + { + switch (rc) + { + case 'd': + g_fDryRun = true; + fCheckRun = false; + break; + case 'D': + g_fDryRun = fCheckRun = false; + break; + case SCMOPT_CHECK_RUN: + g_fDryRun = fCheckRun = true; + break; + + case 'f': + g_pszFileFilter = ValueUnion.psz; + break; + + case 'h': + return scmHelp(s_aOpts, RT_ELEMENTS(s_aOpts)); + + case SCMOPT_HELP_CONFIG: + return scmHelpConfig(); + + case SCMOPT_HELP_ACTIONS: + return scmHelpActions(); + + case 'q': + g_iVerbosity = 0; + break; + + case 'v': + g_iVerbosity++; + break; + + case 'V': + { + /* The following is assuming that svn does it's job here. */ + static const char s_szRev[] = "$Revision: 155710 $"; + const char *psz = RTStrStripL(strchr(s_szRev, ' ')); + RTPrintf("r%.*s\n", strchr(psz, ' ') - psz, psz); + return 0; + } + + case SCMOPT_DIFF_IGNORE_EOL: + g_fDiffIgnoreEol = true; + break; + case SCMOPT_DIFF_NO_IGNORE_EOL: + g_fDiffIgnoreEol = false; + break; + + case SCMOPT_DIFF_IGNORE_SPACE: + g_fDiffIgnoreTrailingWS = g_fDiffIgnoreLeadingWS = true; + break; + case SCMOPT_DIFF_NO_IGNORE_SPACE: + g_fDiffIgnoreTrailingWS = g_fDiffIgnoreLeadingWS = false; + break; + + case SCMOPT_DIFF_IGNORE_LEADING_SPACE: + g_fDiffIgnoreLeadingWS = true; + break; + case SCMOPT_DIFF_NO_IGNORE_LEADING_SPACE: + g_fDiffIgnoreLeadingWS = false; + break; + + case SCMOPT_DIFF_IGNORE_TRAILING_SPACE: + g_fDiffIgnoreTrailingWS = true; + break; + case SCMOPT_DIFF_NO_IGNORE_TRAILING_SPACE: + g_fDiffIgnoreTrailingWS = false; + break; + + case SCMOPT_DIFF_SPECIAL_CHARS: + g_fDiffSpecialChars = true; + break; + case SCMOPT_DIFF_NO_SPECIAL_CHARS: + g_fDiffSpecialChars = false; + break; + + default: + { + int rc2 = scmSettingsBaseHandleOpt(&pSettings->Base, rc, &ValueUnion, "/", 1); + if (RT_SUCCESS(rc2)) + break; + if (rc2 != VERR_GETOPT_UNKNOWN_OPTION) + return 2; + return RTGetOptPrintError(rc, &ValueUnion); + } + } + } + + /* + * Process non-options. + */ + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + if (rc == VINF_GETOPT_NOT_OPTION) + { + ScmSvnInit(); + + bool fWarned = g_fDryRun; + while (rc == VINF_GETOPT_NOT_OPTION) + { + if (!fWarned) + { + RTPrintf("%s: Warning! This program will make changes to your source files and\n" + "%s: there is a slight risk that bugs or a full disk may cause\n" + "%s: LOSS OF DATA. So, please make sure you have checked in\n" + "%s: all your changes already. If you didn't, then don't blame\n" + "%s: anyone for not warning you!\n" + "%s:\n" + "%s: Press any key to continue...\n", + g_szProgName, g_szProgName, g_szProgName, g_szProgName, g_szProgName, + g_szProgName, g_szProgName); + RTStrmGetCh(g_pStdIn); + fWarned = true; + } + + rc = scmProcessSomething(ValueUnion.psz, pSettings); + if (RT_FAILURE(rc)) + { + rcExit = RTEXITCODE_FAILURE; + break; + } + + /* next */ + rc = RTGetOpt(&GetOptState, &ValueUnion); + if (RT_FAILURE(rc)) + rcExit = RTGetOptPrintError(rc, &ValueUnion); + } + + scmPrintStats(); + ScmSvnTerm(); + } + else + RTMsgWarning("No files or directories specified. Doing nothing"); + + scmSettingsDestroy(pSettings); + + /* If we're in checking mode, fail if any files needed modification. */ + if ( rcExit == RTEXITCODE_SUCCESS + && fCheckRun + && g_cFilesModified > 0) + { + RTMsgError("Checking mode failed! %u file%s needs modifications", g_cFilesBinaries, g_cFilesBinaries > 1 ? "s" : ""); + rcExit = RTEXITCODE_FAILURE; + } + + /* Fail if any files require manual repair. */ + if (g_cFilesRequiringManualFixing > 0) + { + RTMsgError("%u file%s needs manual modifications", g_cFilesRequiringManualFixing, + g_cFilesRequiringManualFixing > 1 ? "s" : ""); + if (rcExit == RTEXITCODE_SUCCESS) + rcExit = RTEXITCODE_FAILURE; + } + + return rcExit; +} + diff --git a/src/bldprogs/scm.h b/src/bldprogs/scm.h new file mode 100644 index 00000000..76819108 --- /dev/null +++ b/src/bldprogs/scm.h @@ -0,0 +1,498 @@ +/* $Id: scm.h $ */ +/** @file + * IPRT Testcase / Tool - Source Code Massager. + */ + +/* + * Copyright (C) 2010-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef VBOX_INCLUDED_SRC_bldprogs_scm_h +#define VBOX_INCLUDED_SRC_bldprogs_scm_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include "scmstream.h" + +RT_C_DECLS_BEGIN + +/** Pointer to the rewriter state. */ +typedef struct SCMRWSTATE *PSCMRWSTATE; +/** Pointer to const massager settings. */ +typedef struct SCMSETTINGSBASE const *PCSCMSETTINGSBASE; + + +/** @name Subversion Access + * @{ */ + +/** + * SVN property. + */ +typedef struct SCMSVNPROP +{ + /** The property. */ + char *pszName; + /** The value. + * When used to record updates, this can be set to NULL to trigger the + * deletion of the property. */ + char *pszValue; +} SCMSVNPROP; +/** Pointer to a SVN property. */ +typedef SCMSVNPROP *PSCMSVNPROP; +/** Pointer to a const SVN property. */ +typedef SCMSVNPROP const *PCSCMSVNPROP; + + +void ScmSvnInit(void); +void ScmSvnTerm(void); +bool ScmSvnIsDirInWorkingCopy(const char *pszDir); +bool ScmSvnIsInWorkingCopy(PSCMRWSTATE pState); +int ScmSvnQueryProperty(PSCMRWSTATE pState, const char *pszName, char **ppszValue); +int ScmSvnQueryParentProperty(PSCMRWSTATE pState, const char *pszName, char **ppszValue); +int ScmSvnSetProperty(PSCMRWSTATE pState, const char *pszName, const char *pszValue); +int ScmSvnDelProperty(PSCMRWSTATE pState, const char *pszName); +int ScmSvnDisplayChanges(PSCMRWSTATE pState); +int ScmSvnApplyChanges(PSCMRWSTATE pState); + +/** @} */ + + +/** @name Code Parsing + * @{ */ + +/** + * Comment style. + */ +typedef enum SCMCOMMENTSTYLE +{ + kScmCommentStyle_Invalid = 0, + kScmCommentStyle_C, + kScmCommentStyle_Hash, + kScmCommentStyle_Python, /**< Same as hash, except for copyright/license. */ + kScmCommentStyle_Semicolon, + kScmCommentStyle_Rem_Upper, + kScmCommentStyle_Rem_Lower, + kScmCommentStyle_Rem_Camel, + kScmCommentStyle_Sql, + kScmCommentStyle_Tick, + kScmCommentStyle_Xml, + kScmCommentStyle_End +} SCMCOMMENTSTYLE; + +/** + * Comment types. + */ +typedef enum SCMCOMMENTTYPE +{ + kScmCommentType_Invalid = 0, /**< Customary invalid zero value. */ + kScmCommentType_Line, /**< Line comment. */ + kScmCommentType_Line_JavaDoc, /**< Line comment, JavaDoc style. */ + kScmCommentType_Line_JavaDoc_After, /**< Line comment, JavaDoc after-member style. */ + kScmCommentType_Line_Qt, /**< Line comment, JavaDoc style. */ + kScmCommentType_Line_Qt_After, /**< Line comment, JavaDoc after-member style. */ + kScmCommentType_MultiLine, /**< Multi-line comment (e.g. ansi C). */ + kScmCommentType_MultiLine_JavaDoc, /**< Multi-line comment, JavaDoc style. */ + kScmCommentType_MultiLine_JavaDoc_After, /**< Multi-line comment, JavaDoc after-member style. */ + kScmCommentType_MultiLine_Qt, /**< Multi-line comment, Qt style. */ + kScmCommentType_MultiLine_Qt_After, /**< Multi-line comment, Qt after-member style. */ + kScmCommentType_DocString, /**< Triple quoted python doc string. */ + kScmCommentType_Xml, /**< XML comment style. */ + kScmCommentType_End /**< Customary exclusive end value. */ +} SCMCOMMENTTYPE; + + +/** + * Comment information. + */ +typedef struct SCMCOMMENTINFO +{ + /** Comment type. */ + SCMCOMMENTTYPE enmType; + /** Start line number (0-based). */ + uint32_t iLineStart; + /** Start line offset (0-based). */ + uint32_t offStart; + /** End line number (0-based). */ + uint32_t iLineEnd; + /** End line offset (0-based). */ + uint32_t offEnd; + /** Number of blank lines before the body (@a pszBody). */ + uint32_t cBlankLinesBefore; + /** Number of blank lines after the body (@a pszBody + @a cchBody). */ + uint32_t cBlankLinesAfter; + /** @todo add min/max indent. Raw length. Etc. */ +} SCMCOMMENTINFO; +/** Pointer to comment info. */ +typedef SCMCOMMENTINFO *PSCMCOMMENTINFO; +/** Pointer to const comment info. */ +typedef SCMCOMMENTINFO const *PCSCMCOMMENTINFO; + + +/** + * Comment enumeration callback function. + * + * @returns IPRT style status code. Failures causes immediate return. While an + * informational status code is saved (first one) and returned later. + * @param pInfo Additional comment info. + * @param pszBody The comment body. This is somewhat stripped. + * @param cchBody The comment body length. + * @param pvUser User callback argument. + */ +typedef DECLCALLBACKTYPE(int, FNSCMCOMMENTENUMERATOR,(PCSCMCOMMENTINFO pInfo, const char *pszBody, size_t cchBody, void *pvUser)); +/** Poiter to a omment enumeration callback function. */ +typedef FNSCMCOMMENTENUMERATOR *PFNSCMCOMMENTENUMERATOR; + +int ScmEnumerateComments(PSCMSTREAM pIn, SCMCOMMENTSTYLE enmCommentStyle, PFNSCMCOMMENTENUMERATOR pfnCallback, void *pvUser); + + +/** + * Include directive type. + */ +typedef enum SCMINCLUDEDIR +{ + kScmIncludeDir_Invalid = 0, /**< Constomary invalid enum value. */ + kScmIncludeDir_Quoted, /**< \#include \"filename.h\" */ + kScmIncludeDir_Bracketed, /**< \#include \<filename.h\> */ + kScmIncludeDir_Macro, /**< \#include MACRO_H */ + kScmIncludeDir_End /**< End of valid enum values. */ +} SCMINCLUDEDIR; + +SCMINCLUDEDIR ScmMaybeParseCIncludeLine(PSCMRWSTATE pState, const char *pchLine, size_t cchLine, + const char **ppchFilename, size_t *pcchFilename); + +/** + * Checks if the given character is a valid C identifier lead character. + * + * @returns true / false. + * @param ch The character to inspect. + * @sa vbcppIsCIdentifierLeadChar + */ +DECLINLINE(bool) ScmIsCIdentifierLeadChar(char ch) +{ + return RT_C_IS_ALPHA(ch) + || ch == '_'; +} + + +/** + * Checks if the given character is a valid C identifier character. + * + * @returns true / false. + * @param ch The character to inspect. + * @sa vbcppIsCIdentifierChar + */ +DECLINLINE(bool) ScmIsCIdentifierChar(char ch) +{ + return RT_C_IS_ALNUM(ch) + || ch == '_'; +} + +size_t ScmCalcSpacesForSrcSpan(const char *pchLine, size_t offStart, size_t offEnd, PCSCMSETTINGSBASE pSettings); + +/** @} */ + + +/** @name Rewriters + * @{ */ + +/** + * Rewriter state. + */ +typedef struct SCMRWSTATE +{ + /** The filename. */ + const char *pszFilename; + /** Set after the printing the first verbose message about a file under + * rewrite. */ + bool fFirst; + /** Set if the file requires manual repair. */ + bool fNeedsManualRepair; + /** Cached ScmSvnIsInWorkingCopy response. 0 indicates not known, 1 means it + * is in WC, -1 means it doesn't. */ + int8_t fIsInSvnWorkingCopy; + /** The number of SVN property changes. */ + size_t cSvnPropChanges; + /** Pointer to an array of SVN property changes. */ + struct SCMSVNPROP *paSvnPropChanges; + /** For error propagation. */ + int32_t rc; +} SCMRWSTATE; + +/** Rewriter result. */ +typedef enum { kScmUnmodified = 0, kScmModified, kScmMaybeModified } SCMREWRITERRES; + +/** + * A rewriter. + * + * This works like a stream editor, reading @a pIn, modifying it and writing it + * to @a pOut. + * + * @returns kScmUnmodified, kScmModified or kScmMaybeModified. + * @param pIn The input stream. + * @param pOut The output stream. + * @param pSettings The settings. + */ +typedef SCMREWRITERRES FNSCMREWRITER(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings); +/** Pointer to a rewriter method. */ +typedef FNSCMREWRITER *PFNSCMREWRITER; + +FNSCMREWRITER rewrite_StripTrailingBlanks; +FNSCMREWRITER rewrite_ExpandTabs; +FNSCMREWRITER rewrite_ForceNativeEol; +FNSCMREWRITER rewrite_ForceLF; +FNSCMREWRITER rewrite_ForceCRLF; +FNSCMREWRITER rewrite_AdjustTrailingLines; +FNSCMREWRITER rewrite_SvnNoExecutable; +FNSCMREWRITER rewrite_SvnNoKeywords; +FNSCMREWRITER rewrite_SvnNoEolStyle; +FNSCMREWRITER rewrite_SvnBinary; +FNSCMREWRITER rewrite_SvnKeywords; +FNSCMREWRITER rewrite_SvnSyncProcess; +FNSCMREWRITER rewrite_UnicodeChecks; +FNSCMREWRITER rewrite_PageChecks; +FNSCMREWRITER rewrite_ForceHrcVrcInsteadOfRc; +FNSCMREWRITER rewrite_Copyright_CstyleComment; +FNSCMREWRITER rewrite_Copyright_HashComment; +FNSCMREWRITER rewrite_Copyright_PythonComment; +FNSCMREWRITER rewrite_Copyright_RemComment; +FNSCMREWRITER rewrite_Copyright_SemicolonComment; +FNSCMREWRITER rewrite_Copyright_SqlComment; +FNSCMREWRITER rewrite_Copyright_TickComment; +FNSCMREWRITER rewrite_Copyright_XmlComment; +FNSCMREWRITER rewrite_Makefile_kup; +FNSCMREWRITER rewrite_Makefile_kmk; +FNSCMREWRITER rewrite_FixFlowerBoxMarkers; +FNSCMREWRITER rewrite_Fix_C_and_CPP_Todos; +FNSCMREWRITER rewrite_Fix_Err_H; +FNSCMREWRITER rewrite_FixHeaderGuards; +FNSCMREWRITER rewrite_C_and_CPP; + +/** + * Rewriter configuration. + */ +typedef struct SCMREWRITERCFG +{ + /** The rewriter function. */ + PFNSCMREWRITER pfnRewriter; + /** The name of the rewriter. */ + const char *pszName; +} SCMREWRITERCFG; +/** Pointer to a const rewriter config. */ +typedef SCMREWRITERCFG const *PCSCMREWRITERCFG; + +/** @} */ + + +/** @name Settings + * @{ */ + +/** + * Configuration entry. + */ +typedef struct SCMCFGENTRY +{ + /** Number of rewriters. */ + size_t cRewriters; + /** Pointer to an array of rewriters. */ + PCSCMREWRITERCFG const *paRewriters; + /** Set if the entry handles binaries. */ + bool fBinary; + /** File pattern (simple). */ + const char *pszFilePattern; + /** Name (for treat as). */ + const char *pszName; +} SCMCFGENTRY; +typedef SCMCFGENTRY *PSCMCFGENTRY; +typedef SCMCFGENTRY const *PCSCMCFGENTRY; + + +/** License update options. */ +typedef enum SCMLICENSE +{ + kScmLicense_LeaveAlone = 0, /**< Leave it alone. */ + kScmLicense_OseGpl, /**< VBox OSE GPL if public. */ + kScmLicense_OseDualGplCddl, /**< VBox OSE dual GPL & CDDL if public. */ + kScmLicense_OseCddl, /**< VBox OSE CDDL if public. */ + kScmLicense_Lgpl, /**< LGPL if public. */ + kScmLicense_Mit, /**< MIT if public. */ + kScmLicense_BasedOnMit, /**< Copyright us but based on someone else's MIT. */ + kScmLicense_End +} SCMLICENSE; + +/** + * Source Code Massager Settings. + */ +typedef struct SCMSETTINGSBASE +{ + bool fConvertEol; + bool fConvertTabs; + bool fForceFinalEol; + bool fForceTrailingLine; + bool fStripTrailingBlanks; + bool fStripTrailingLines; + + /** Whether to fix C/C++ flower box section markers. */ + bool fFixFlowerBoxMarkers; + /** The minimum number of blank lines we want before flowerbox markers. */ + uint8_t cMinBlankLinesBeforeFlowerBoxMakers; + + /** Whether to fix C/C++ header guards and \#pragma once directives. */ + bool fFixHeaderGuards; + /** Whether to include a pragma once statement with the header guard. */ + bool fPragmaOnce; + /** Whether to fix the \#endif part of a header guard. */ + bool fFixHeaderGuardEndif; + /** Whether to add a comment on the \#endif part of the header guard. */ + bool fEndifGuardComment; + /** The guard name prefix. */ + char *pszGuardPrefix; + /** Header guards should be normalized using prefix and this directory. + * When NULL the guard identifiers found in the header is preserved. */ + char *pszGuardRelativeToDir; + + /** Whether to fix C/C++ todos. */ + bool fFixTodos; + /** Whether to fix C/C++ err.h/errcore.h usage. */ + bool fFixErrH; + /** No PAGE_SIZE, PAGE_SHIFT, PAGE_OFFSET_MASK allowed in C/C++, only the GUEST_ + * or HOST_ prefixed versions. */ + bool fOnlyGuestHostPage; + /** No ASMMemIsZeroPage or ASMMemZeroPage calls allowed (C/C++). */ + bool fNoASMMemPageUse; + /** No rc declarations allowed, only hrc or vrc depending on the result type. */ + bool fOnlyHrcVrcInsteadOfRc; + + /** Whether to standarize kmk makefiles. */ + bool fStandarizeKmk; + + /** Update the copyright year. */ + bool fUpdateCopyrightYear; + /** Only external copyright holders. */ + bool fExternalCopyright; + /** Whether there should be a LGPL disclaimer. */ + bool fLgplDisclaimer; + /** How to update the license. */ + SCMLICENSE enmUpdateLicense; + + /** Only process files that are part of a SVN working copy. */ + bool fOnlySvnFiles; + /** Only recurse into directories containing an .svn dir. */ + bool fOnlySvnDirs; + /** Set svn:eol-style if missing or incorrect. */ + bool fSetSvnEol; + /** Set svn:executable according to type (unusually this means deleting it). */ + bool fSetSvnExecutable; + /** Set svn:keyword if completely or partially missing. */ + bool fSetSvnKeywords; + /** Skip checking svn:sync-process. */ + bool fSkipSvnSyncProcess; + /** Skip the unicode checks. */ + bool fSkipUnicodeChecks; + /** Tab size. */ + uint8_t cchTab; + /** Optimal source code width. */ + uint8_t cchWidth; + /** Free the treat as structure. */ + bool fFreeTreatAs; + /** Prematched config entry. */ + PCSCMCFGENTRY pTreatAs; + /** Only consider files matching these patterns. This is only applied to the + * base names. */ + char *pszFilterFiles; + /** Filter out files matching the following patterns. This is applied to base + * names as well as the absolute paths. */ + char *pszFilterOutFiles; + /** Filter out directories matching the following patterns. This is applied + * to base names as well as the absolute paths. All absolute paths ends with a + * slash and dot ("/."). */ + char *pszFilterOutDirs; +} SCMSETTINGSBASE; +/** Pointer to massager settings. */ +typedef SCMSETTINGSBASE *PSCMSETTINGSBASE; + +/** + * File/dir pattern + options. + */ +typedef struct SCMPATRNOPTPAIR +{ + char *pszPattern; + char *pszOptions; + char *pszRelativeTo; + bool fMultiPattern; +} SCMPATRNOPTPAIR; +/** Pointer to a pattern + option pair. */ +typedef SCMPATRNOPTPAIR *PSCMPATRNOPTPAIR; + + +/** Pointer to a settings set. */ +typedef struct SCMSETTINGS *PSCMSETTINGS; +/** + * Settings set. + * + * This structure is constructed from the command line arguments or any + * .scm-settings file found in a directory we recurse into. When recursing in + * and out of a directory, we push and pop a settings set for it. + * + * The .scm-settings file has two kinds of setttings, first there are the + * unqualified base settings and then there are the settings which applies to a + * set of files or directories. The former are lines with command line options. + * For the latter, the options are preceded by a string pattern and a colon. + * The pattern specifies which files (and/or directories) the options applies + * to. + * + * We parse the base options into the Base member and put the others into the + * paPairs array. + */ +typedef struct SCMSETTINGS +{ + /** Pointer to the setting file below us in the stack. */ + PSCMSETTINGS pDown; + /** Pointer to the setting file above us in the stack. */ + PSCMSETTINGS pUp; + /** File/dir patterns and their options. */ + PSCMPATRNOPTPAIR paPairs; + /** The number of entires in paPairs. */ + uint32_t cPairs; + /** The base settings that was read out of the file. */ + SCMSETTINGSBASE Base; +} SCMSETTINGS; +/** Pointer to a const settings set. */ +typedef SCMSETTINGS const *PCSCMSETTINGS; + +/** @} */ + + +void ScmVerboseBanner(PSCMRWSTATE pState, int iLevel); +void ScmVerbose(PSCMRWSTATE pState, int iLevel, const char *pszFormat, ...) RT_IPRT_FORMAT_ATTR(3, 4); +SCMREWRITERRES ScmError(PSCMRWSTATE pState, int rc, const char *pszFormat, ...) RT_IPRT_FORMAT_ATTR(3, 4); +bool ScmFixManually(PSCMRWSTATE pState, const char *pszFormat, ...) RT_IPRT_FORMAT_ATTR(2, 3); +bool ScmFixManuallyV(PSCMRWSTATE pState, const char *pszFormat, va_list va) RT_IPRT_FORMAT_ATTR(2, 0); + +extern const char g_szTabSpaces[16+1]; +extern const char g_szAsterisks[255+1]; +extern const char g_szSpaces[255+1]; +extern uint32_t g_uYear; + +RT_C_DECLS_END + +#endif /* !VBOX_INCLUDED_SRC_bldprogs_scm_h */ + diff --git a/src/bldprogs/scmdiff.cpp b/src/bldprogs/scmdiff.cpp new file mode 100644 index 00000000..df642c4b --- /dev/null +++ b/src/bldprogs/scmdiff.cpp @@ -0,0 +1,450 @@ +/* $Id: scmdiff.cpp $ */ +/** @file + * IPRT Testcase / Tool - Source Code Massager. + */ + +/* + * Copyright (C) 2010-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/assert.h> +#include <iprt/ctype.h> +#include <iprt/message.h> +#include <iprt/stream.h> +#include <iprt/string.h> + +#include "scmdiff.h" + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static const char g_szTabSpaces[16+1] = " "; + + + +/** + * Prints a range of lines with a prefix. + * + * @param pState The diff state. + * @param chPrefix The prefix. + * @param pStream The stream to get the lines from. + * @param iLine The first line. + * @param cLines The number of lines. + */ +static void scmDiffPrintLines(PSCMDIFFSTATE pState, char chPrefix, PSCMSTREAM pStream, size_t iLine, size_t cLines) +{ + while (cLines-- > 0) + { + SCMEOL enmEol; + size_t cchLine; + const char *pchLine = ScmStreamGetLineByNo(pStream, iLine, &cchLine, &enmEol); + + RTStrmPutCh(pState->pDiff, chPrefix); + if (pchLine && cchLine) + { + if (!pState->fSpecialChars) + RTStrmWrite(pState->pDiff, pchLine, cchLine); + else + { + size_t offVir = 0; + const char *pchStart = pchLine; + const char *pchTab = (const char *)memchr(pchLine, '\t', cchLine); + while (pchTab) + { + RTStrmWrite(pState->pDiff, pchStart, pchTab - pchStart); + offVir += pchTab - pchStart; + + size_t cchTab = pState->cchTab - offVir % pState->cchTab; + switch (cchTab) + { + case 1: RTStrmPutStr(pState->pDiff, "."); break; + case 2: RTStrmPutStr(pState->pDiff, ".."); break; + case 3: RTStrmPutStr(pState->pDiff, "[T]"); break; + case 4: RTStrmPutStr(pState->pDiff, "[TA]"); break; + case 5: RTStrmPutStr(pState->pDiff, "[TAB]"); break; + default: RTStrmPrintf(pState->pDiff, "[TAB%.*s]", cchTab - 5, g_szTabSpaces); break; + } + offVir += cchTab; + + /* next */ + pchStart = pchTab + 1; + pchTab = (const char *)memchr(pchStart, '\t', cchLine - (pchStart - pchLine)); + } + size_t cchLeft = cchLine - (pchStart - pchLine); + if (cchLeft) + RTStrmWrite(pState->pDiff, pchStart, cchLeft); + } + } + + if (!pState->fSpecialChars) + RTStrmPutCh(pState->pDiff, '\n'); + else if (enmEol == SCMEOL_LF) + RTStrmPutStr(pState->pDiff, "[LF]\n"); + else if (enmEol == SCMEOL_CRLF) + RTStrmPutStr(pState->pDiff, "[CRLF]\n"); + else + RTStrmPutStr(pState->pDiff, "[NONE]\n"); + + iLine++; + } +} + + +/** + * Reports a difference and propels the streams to the lines following the + * resync. + * + * + * @returns New pState->cDiff value (just to return something). + * @param pState The diff state. The cDiffs member will be + * incremented. + * @param cMatches The resync length. + * @param iLeft Where the difference starts on the left side. + * @param cLeft How long it is on this side. ~(size_t)0 is used + * to indicate that it goes all the way to the end. + * @param iRight Where the difference starts on the right side. + * @param cRight How long it is. + */ +static size_t scmDiffReport(PSCMDIFFSTATE pState, size_t cMatches, + size_t iLeft, size_t cLeft, + size_t iRight, size_t cRight) +{ + /* + * Adjust the input. + */ + if (cLeft == ~(size_t)0) + { + size_t c = ScmStreamCountLines(pState->pLeft); + if (c >= iLeft) + cLeft = c - iLeft; + else + { + iLeft = c; + cLeft = 0; + } + } + + if (cRight == ~(size_t)0) + { + size_t c = ScmStreamCountLines(pState->pRight); + if (c >= iRight) + cRight = c - iRight; + else + { + iRight = c; + cRight = 0; + } + } + + /* + * Print header if it's the first difference + */ + if (!pState->cDiffs) + RTStrmPrintf(pState->pDiff, "diff %s %s\n", pState->pszFilename, pState->pszFilename); + + /* + * Emit the change description. + */ + char ch = cLeft == 0 + ? 'a' + : cRight == 0 + ? 'd' + : 'c'; + if (cLeft > 1 && cRight > 1) + RTStrmPrintf(pState->pDiff, "%zu,%zu%c%zu,%zu\n", iLeft + 1, iLeft + cLeft, ch, iRight + 1, iRight + cRight); + else if (cLeft > 1) + RTStrmPrintf(pState->pDiff, "%zu,%zu%c%zu\n", iLeft + 1, iLeft + cLeft, ch, iRight + 1); + else if (cRight > 1) + RTStrmPrintf(pState->pDiff, "%zu%c%zu,%zu\n", iLeft + 1, ch, iRight + 1, iRight + cRight); + else + RTStrmPrintf(pState->pDiff, "%zu%c%zu\n", iLeft + 1, ch, iRight + 1); + + /* + * And the lines. + */ + if (cLeft) + scmDiffPrintLines(pState, '<', pState->pLeft, iLeft, cLeft); + if (cLeft && cRight) + RTStrmPrintf(pState->pDiff, "---\n"); + if (cRight) + scmDiffPrintLines(pState, '>', pState->pRight, iRight, cRight); + + /* + * Reposition the streams (safely ignores return value). + */ + ScmStreamSeekByLine(pState->pLeft, iLeft + cLeft + cMatches); + ScmStreamSeekByLine(pState->pRight, iRight + cRight + cMatches); + + pState->cDiffs++; + return pState->cDiffs; +} + +/** + * Helper for scmDiffCompare that takes care of trailing spaces and stuff + * like that. + */ +static bool scmDiffCompareSlow(PSCMDIFFSTATE pState, + const char *pchLeft, size_t cchLeft, SCMEOL enmEolLeft, + const char *pchRight, size_t cchRight, SCMEOL enmEolRight) +{ + if (pState->fIgnoreTrailingWhite) + { + while (cchLeft > 0 && RT_C_IS_SPACE(pchLeft[cchLeft - 1])) + cchLeft--; + while (cchRight > 0 && RT_C_IS_SPACE(pchRight[cchRight - 1])) + cchRight--; + } + + if (pState->fIgnoreLeadingWhite) + { + while (cchLeft > 0 && RT_C_IS_SPACE(*pchLeft)) + pchLeft++, cchLeft--; + while (cchRight > 0 && RT_C_IS_SPACE(*pchRight)) + pchRight++, cchRight--; + } + + if ( cchLeft != cchRight + || (enmEolLeft != enmEolRight && !pState->fIgnoreEol) + || memcmp(pchLeft, pchRight, cchLeft)) + return false; + return true; +} + +/** + * Compare two lines. + * + * @returns true if the are equal, false if not. + */ +DECLINLINE(bool) scmDiffCompare(PSCMDIFFSTATE pState, + const char *pchLeft, size_t cchLeft, SCMEOL enmEolLeft, + const char *pchRight, size_t cchRight, SCMEOL enmEolRight) +{ + if ( cchLeft != cchRight + || (enmEolLeft != enmEolRight && !pState->fIgnoreEol) + || memcmp(pchLeft, pchRight, cchLeft)) + { + if ( pState->fIgnoreTrailingWhite + || pState->fIgnoreLeadingWhite) + return scmDiffCompareSlow(pState, + pchLeft, cchLeft, enmEolLeft, + pchRight, cchRight, enmEolRight); + return false; + } + return true; +} + +/** + * Compares two sets of lines from the two files. + * + * @returns true if they matches, false if they don't. + * @param pState The diff state. + * @param iLeft Where to start in the left stream. + * @param iRight Where to start in the right stream. + * @param cLines How many lines to compare. + */ +static bool scmDiffCompareLines(PSCMDIFFSTATE pState, size_t iLeft, size_t iRight, size_t cLines) +{ + for (size_t iLine = 0; iLine < cLines; iLine++) + { + SCMEOL enmEolLeft; + size_t cchLeft; + const char *pchLeft = ScmStreamGetLineByNo(pState->pLeft, iLeft + iLine, &cchLeft, &enmEolLeft); + + SCMEOL enmEolRight; + size_t cchRight; + const char *pchRight = ScmStreamGetLineByNo(pState->pRight, iRight + iLine, &cchRight, &enmEolRight); + + if (!scmDiffCompare(pState, pchLeft, cchLeft, enmEolLeft, pchRight, cchRight, enmEolRight)) + return false; + } + return true; +} + + +/** + * Resynchronize the two streams and reports the difference. + * + * Upon return, the streams will be positioned after the block of @a cMatches + * lines where it resynchronized them. + * + * @returns pState->cDiffs (just so we can use it in a return statement). + * @param pState The state. + * @param cMatches The number of lines that needs to match for the + * stream to be considered synchronized again. + */ +static size_t scmDiffSynchronize(PSCMDIFFSTATE pState, size_t cMatches) +{ + size_t const iStartLeft = ScmStreamTellLine(pState->pLeft) - 1; + size_t const iStartRight = ScmStreamTellLine(pState->pRight) - 1; + Assert(cMatches > 0); + + /* + * Compare each new line from each of the streams will all the preceding + * ones, including iStartLeft/Right. + */ + for (size_t iRange = 1; ; iRange++) + { + /* + * Get the next line in the left stream and compare it against all the + * preceding lines on the right side. + */ + SCMEOL enmEol; + size_t cchLine; + const char *pchLine = ScmStreamGetLineByNo(pState->pLeft, iStartLeft + iRange, &cchLine, &enmEol); + if (!pchLine) + return scmDiffReport(pState, 0, iStartLeft, ~(size_t)0, iStartRight, ~(size_t)0); + + for (size_t iRight = cMatches - 1; iRight < iRange; iRight++) + { + SCMEOL enmEolRight; + size_t cchRight; + const char *pchRight = ScmStreamGetLineByNo(pState->pRight, iStartRight + iRight, + &cchRight, &enmEolRight); + if ( scmDiffCompare(pState, pchLine, cchLine, enmEol, pchRight, cchRight, enmEolRight) + && scmDiffCompareLines(pState, + iStartLeft + iRange + 1 - cMatches, + iStartRight + iRight + 1 - cMatches, + cMatches - 1) + ) + return scmDiffReport(pState, cMatches, + iStartLeft, iRange + 1 - cMatches, + iStartRight, iRight + 1 - cMatches); + } + + /* + * Get the next line in the right stream and compare it against all the + * lines on the right side. + */ + pchLine = ScmStreamGetLineByNo(pState->pRight, iStartRight + iRange, &cchLine, &enmEol); + if (!pchLine) + return scmDiffReport(pState, 0, iStartLeft, ~(size_t)0, iStartRight, ~(size_t)0); + + for (size_t iLeft = cMatches - 1; iLeft <= iRange; iLeft++) + { + SCMEOL enmEolLeft; + size_t cchLeft; + const char *pchLeft = ScmStreamGetLineByNo(pState->pLeft, iStartLeft + iLeft, + &cchLeft, &enmEolLeft); + if ( scmDiffCompare(pState, pchLeft, cchLeft, enmEolLeft, pchLine, cchLine, enmEol) + && scmDiffCompareLines(pState, + iStartLeft + iLeft + 1 - cMatches, + iStartRight + iRange + 1 - cMatches, + cMatches - 1) + ) + return scmDiffReport(pState, cMatches, + iStartLeft, iLeft + 1 - cMatches, + iStartRight, iRange + 1 - cMatches); + } + } +} + +/** + * Creates a diff of the changes between the streams @a pLeft and @a pRight. + * + * This currently only implements the simplest diff format, so no contexts. + * + * Also, note that we won't detect differences in the final newline of the + * streams. + * + * @returns The number of differences. + * @param pszFilename The filename. + * @param pLeft The left side stream. + * @param pRight The right side stream. + * @param fIgnoreEol Whether to ignore end of line markers. + * @param fIgnoreLeadingWhite Set if leading white space should be ignored. + * @param fIgnoreTrailingWhite Set if trailing white space should be ignored. + * @param fSpecialChars Whether to print special chars in a human + * readable form or not. + * @param cchTab The tab size. + * @param pDiff Where to write the diff. + */ +size_t ScmDiffStreams(const char *pszFilename, PSCMSTREAM pLeft, PSCMSTREAM pRight, bool fIgnoreEol, + bool fIgnoreLeadingWhite, bool fIgnoreTrailingWhite, bool fSpecialChars, + size_t cchTab, PRTSTREAM pDiff) +{ +#ifdef RT_STRICT + ScmStreamCheckItegrity(pLeft); + ScmStreamCheckItegrity(pRight); +#endif + + /* + * Set up the diff state. + */ + SCMDIFFSTATE State; + State.cDiffs = 0; + State.pszFilename = pszFilename; + State.pLeft = pLeft; + State.pRight = pRight; + State.fIgnoreEol = fIgnoreEol; + State.fIgnoreLeadingWhite = fIgnoreLeadingWhite; + State.fIgnoreTrailingWhite = fIgnoreTrailingWhite; + State.fSpecialChars = fSpecialChars; + State.cchTab = cchTab; + State.pDiff = pDiff; + + /* + * Compare them line by line. + */ + ScmStreamRewindForReading(pLeft); + ScmStreamRewindForReading(pRight); + const char *pchLeft; + const char *pchRight; + + for (;;) + { + SCMEOL enmEolLeft; + size_t cchLeft; + pchLeft = ScmStreamGetLine(pLeft, &cchLeft, &enmEolLeft); + + SCMEOL enmEolRight; + size_t cchRight; + pchRight = ScmStreamGetLine(pRight, &cchRight, &enmEolRight); + if (!pchLeft || !pchRight) + break; + + if (!scmDiffCompare(&State, pchLeft, cchLeft, enmEolLeft, pchRight, cchRight, enmEolRight)) + scmDiffSynchronize(&State, 3); + } + + /* + * Deal with any remaining differences. + */ + if (pchLeft) + scmDiffReport(&State, 0, ScmStreamTellLine(pLeft) - 1, ~(size_t)0, ScmStreamTellLine(pRight), 0); + else if (pchRight) + scmDiffReport(&State, 0, ScmStreamTellLine(pLeft), 0, ScmStreamTellLine(pRight) - 1, ~(size_t)0); + + /* + * Report any errors. + */ + if (RT_FAILURE(ScmStreamGetStatus(pLeft))) + RTMsgError("Left diff stream error: %Rrc\n", ScmStreamGetStatus(pLeft)); + if (RT_FAILURE(ScmStreamGetStatus(pRight))) + RTMsgError("Right diff stream error: %Rrc\n", ScmStreamGetStatus(pRight)); + + return State.cDiffs; +} + diff --git a/src/bldprogs/scmdiff.h b/src/bldprogs/scmdiff.h new file mode 100644 index 00000000..040904f7 --- /dev/null +++ b/src/bldprogs/scmdiff.h @@ -0,0 +1,74 @@ +/* $Id: scmdiff.h $ */ +/** @file + * IPRT Testcase / Tool - Source Code Massager Diff Code. + */ + +/* + * Copyright (C) 2010-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef VBOX_INCLUDED_SRC_bldprogs_scmdiff_h +#define VBOX_INCLUDED_SRC_bldprogs_scmdiff_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/stream.h> +#include "scmstream.h" + +RT_C_DECLS_BEGIN + +/** + * Diff state. + */ +typedef struct SCMDIFFSTATE +{ + size_t cDiffs; + const char *pszFilename; + + PSCMSTREAM pLeft; + PSCMSTREAM pRight; + + /** Whether to ignore end of line markers when diffing. */ + bool fIgnoreEol; + /** Whether to ignore trailing whitespace. */ + bool fIgnoreTrailingWhite; + /** Whether to ignore leading whitespace. */ + bool fIgnoreLeadingWhite; + /** Whether to print special characters in human readable form or not. */ + bool fSpecialChars; + /** The tab size. */ + size_t cchTab; + /** Where to push the diff. */ + PRTSTREAM pDiff; +} SCMDIFFSTATE; +/** Pointer to a diff state. */ +typedef SCMDIFFSTATE *PSCMDIFFSTATE; + + +size_t ScmDiffStreams(const char *pszFilename, PSCMSTREAM pLeft, PSCMSTREAM pRight, bool fIgnoreEol, + bool fIgnoreLeadingWhite, bool fIgnoreTrailingWhite, bool fSpecialChars, + size_t cchTab, PRTSTREAM pDiff); + +RT_C_DECLS_END + +#endif /* !VBOX_INCLUDED_SRC_bldprogs_scmdiff_h */ + diff --git a/src/bldprogs/scmparser.cpp b/src/bldprogs/scmparser.cpp new file mode 100644 index 00000000..557190c5 --- /dev/null +++ b/src/bldprogs/scmparser.cpp @@ -0,0 +1,1199 @@ +/* $Id: scmparser.cpp $ */ +/** @file + * IPRT Testcase / Tool - Source Code Massager, Code Parsers. + */ + +/* + * Copyright (C) 2010-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/assert.h> +#include <iprt/ctype.h> +#include <iprt/dir.h> +#include <iprt/env.h> +#include <iprt/file.h> +#include <iprt/errcore.h> +#include <iprt/getopt.h> +#include <iprt/initterm.h> +#include <iprt/mem.h> +#include <iprt/message.h> +#include <iprt/param.h> +#include <iprt/path.h> +#include <iprt/process.h> +#include <iprt/stream.h> +#include <iprt/string.h> + +#include "scm.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef size_t (*PFNISCOMMENT)(const char *pchLine, size_t cchLine, bool fSecond); + + +/** + * Callback for checking if C++ line comment. + */ +static size_t isCppLineComment(const char *pchLine, size_t cchLine, bool fSecond) +{ + if ( cchLine >= 2 + && pchLine[0] == '/' + && pchLine[1] == '/') + { + if (!fSecond) + return 2; + if (cchLine >= 3 && pchLine[2] == '/') + return 3; + } + return 0; +} + + +/** + * Callback for checking if hash comment. + */ +static size_t isHashComment(const char *pchLine, size_t cchLine, bool fSecond) +{ + if (cchLine >= 1 && *pchLine == '#') + { + if (!fSecond) + return 1; + if (cchLine >= 2 && pchLine[1] == '#') + return 2; + } + return 0; +} + + +/** + * Callback for checking if semicolon comment. + */ +static size_t isSemicolonComment(const char *pchLine, size_t cchLine, bool fSecond) +{ + if (cchLine >= 1 && *pchLine == ';') + { + if (!fSecond) + return 1; + if (cchLine >= 2 && pchLine[1] == ';') + return 2; + } + return 0; +} + + +/** Macro for checking for a XML comment start. */ +#define IS_XML_COMMENT_START(a_pch, a_off, a_cch) \ + ( (a_off) + 4 <= (a_cch) \ + && (a_pch)[(a_off) ] == '<' \ + && (a_pch)[(a_off) + 1] == '!' \ + && (a_pch)[(a_off) + 2] == '-' \ + && (a_pch)[(a_off) + 3] == '-' \ + && ((a_off) + 4 == (a_cch) || RT_C_IS_SPACE((a_pch)[(a_off) + 4])) ) + +/** Macro for checking for a XML comment end. */ +#define IS_XML_COMMENT_END(a_pch, a_off, a_cch) \ + ( (a_off) + 3 <= (a_cch) \ + && (a_pch)[(a_off) ] == '-' \ + && (a_pch)[(a_off) + 1] == '-' \ + && (a_pch)[(a_off) + 2] == '>') + + +/** Macro for checking for a batch file comment prefix. */ +#define IS_REM(a_pch, a_off, a_cch) \ + ( (a_off) + 3 <= (a_cch) \ + && ((a_pch)[(a_off) ] == 'R' || (a_pch)[(a_off) ] == 'r') \ + && ((a_pch)[(a_off) + 1] == 'E' || (a_pch)[(a_off) + 1] == 'e') \ + && ((a_pch)[(a_off) + 2] == 'M' || (a_pch)[(a_off) + 2] == 'm') \ + && ((a_off) + 3 == (a_cch) || RT_C_IS_SPACE((a_pch)[(a_off) + 3])) ) + + +/** + * Callback for checking if batch comment. + */ +static size_t isBatchComment(const char *pchLine, size_t cchLine, bool fSecond) +{ + if (!fSecond) + { + if (IS_REM(pchLine, 0, cchLine)) + return 3; + } + else + { + /* Check for the 2nd in "rem rem" lines. */ + if ( cchLine >= 4 + && RT_C_IS_SPACE(*pchLine) + && IS_REM(pchLine, 1, cchLine)) + return 4; + } + return 0; +} + +/** + * Callback for checking if SQL comment. + */ +static size_t isSqlComment(const char *pchLine, size_t cchLine, bool fSecond) +{ + if ( cchLine >= 2 + && pchLine[0] == '-' + && pchLine[1] == '-') + { + if (!fSecond) + return 2; + if ( cchLine >= 3 + && pchLine[2] == '-') + return 3; + } + return 0; +} + +/** + * Callback for checking if tick comment. + */ +static size_t isTickComment(const char *pchLine, size_t cchLine, bool fSecond) +{ + if (cchLine >= 1 && *pchLine == '\'') + { + if (!fSecond) + return 1; + if (cchLine >= 2 && pchLine[1] == '\'') + return 2; + } + return 0; +} + + +/** + * Common worker for enumeratePythonComments and enumerateSimpleLineComments. + * + * @returns IPRT status code. + * @param pIn The input stream. + * @param pfnIsComment Comment tester function. + * @param pfnCallback The callback. + * @param pvUser The user argument for the callback. + * @param ppchLine Pointer to the line variable. + * @param pcchLine Pointer to the line length variable. + * @param penmEol Pointer to the line ending type variable. + * @param piLine Pointer to the line number variable. + * @param poff Pointer to the line offset variable. On input this + * is positioned at the start of the comment. + */ +static int handleLineComment(PSCMSTREAM pIn, PFNISCOMMENT pfnIsComment, + PFNSCMCOMMENTENUMERATOR pfnCallback, void *pvUser, + const char **ppchLine, size_t *pcchLine, PSCMEOL penmEol, + uint32_t *piLine, size_t *poff) +{ + /* Unpack input/output variables. */ + uint32_t iLine = *piLine; + const char *pchLine = *ppchLine; + size_t cchLine = *pcchLine; + size_t off = *poff; + SCMEOL enmEol = *penmEol; + + /* + * Take down the basic info about the comment. + */ + SCMCOMMENTINFO Info; + Info.iLineStart = iLine; + Info.iLineEnd = iLine; + Info.offStart = (uint32_t)off; + Info.offEnd = (uint32_t)cchLine; + + size_t cchSkip = pfnIsComment(&pchLine[off], cchLine - off, false); + Assert(cchSkip > 0); + off += cchSkip; + + /* Determine comment type. */ + Info.enmType = kScmCommentType_Line; + char ch; + cchSkip = 1; + if ( off < cchLine + && ( (ch = pchLine[off]) == '!' + || (cchSkip = pfnIsComment(&pchLine[off], cchLine - off, true)) > 0) ) + { + unsigned ch2; + if ( off + cchSkip == cchLine + || RT_C_IS_SPACE(ch2 = pchLine[off + cchSkip]) ) + { + Info.enmType = ch != '!' ? kScmCommentType_Line_JavaDoc : kScmCommentType_Line_Qt; + off += cchSkip; + } + else if ( ch2 == '<' + && ( off + cchSkip + 1 == cchLine + || RT_C_IS_SPACE(pchLine[off + cchSkip + 1]) )) + { + Info.enmType = ch == '!' ? kScmCommentType_Line_JavaDoc_After : kScmCommentType_Line_Qt_After; + off += cchSkip + 1; + } + } + + /* + * Copy body of the first line. Like for C, we ignore a single space in the first comment line. + */ + if (off < cchLine && RT_C_IS_SPACE(pchLine[off])) + off++; + size_t cchBody = cchLine; + while (cchBody > off && RT_C_IS_SPACE(pchLine[cchBody - 1])) + cchBody--; + cchBody -= off; + size_t cbBodyAlloc = RT_MAX(_1K, RT_ALIGN_Z(cchBody + 64, 128)); + char *pszBody = (char *)RTMemAlloc(cbBodyAlloc); + if (!pszBody) + return VERR_NO_MEMORY; + memcpy(pszBody, &pchLine[off], cchBody); + pszBody[cchBody] = '\0'; + + Info.cBlankLinesBefore = cchBody == 0; + + /* + * Look for more comment lines and append them to the body. + */ + while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL) + { + iLine++; + + /* Skip leading spaces. */ + off = 0; + while (off < cchLine && RT_C_IS_SPACE(pchLine[off])) + off++; + + /* Check if it's a comment. */ + if ( off >= cchLine + || (cchSkip = pfnIsComment(&pchLine[off], cchLine - off, false)) == 0) + break; + off += cchSkip; + + /* Split on doxygen comment start (if not already in one). */ + if ( Info.enmType == kScmCommentType_Line + && off + 1 < cchLine + && ( pfnIsComment(&pchLine[off], cchLine - off, true) > 0 + || ( pchLine[off + 1] == '!' + && ( off + 2 == cchLine + || pchLine[off + 2] != '!') ) ) ) + { + off -= cchSkip; + break; + } + + /* Append the body w/o trailing spaces and some leading ones. */ + if (off < cchLine && RT_C_IS_SPACE(pchLine[off])) + off++; + while (off < cchLine && off < Info.offStart + 3 && RT_C_IS_SPACE(pchLine[off])) + off++; + size_t cchAppend = cchLine; + while (cchAppend > off && RT_C_IS_SPACE(pchLine[cchAppend - 1])) + cchAppend--; + cchAppend -= off; + + size_t cchNewBody = cchBody + 1 + cchAppend; + if (cchNewBody >= cbBodyAlloc) + { + cbBodyAlloc = RT_MAX(cbBodyAlloc ? cbBodyAlloc * 2 : _1K, RT_ALIGN_Z(cchNewBody + 64, 128)); + void *pvNew = RTMemRealloc(pszBody, cbBodyAlloc); + if (pvNew) + pszBody = (char *)pvNew; + else + { + RTMemFree(pszBody); + return VERR_NO_MEMORY; + } + } + + if ( cchBody > 0 + || cchAppend > 0) + { + if (cchBody > 0) + pszBody[cchBody++] = '\n'; + memcpy(&pszBody[cchBody], &pchLine[off], cchAppend); + cchBody += cchAppend; + pszBody[cchBody] = '\0'; + } + else + Info.cBlankLinesBefore++; + + /* Advance. */ + Info.offEnd = (uint32_t)cchLine; + Info.iLineEnd = iLine; + } + + /* + * Strip trailing empty lines in the body. + */ + Info.cBlankLinesAfter = 0; + while (cchBody >= 1 && pszBody[cchBody - 1] == '\n') + { + Info.cBlankLinesAfter++; + pszBody[--cchBody] = '\0'; + } + + /* + * Do the callback and return. + */ + int rc = pfnCallback(&Info, pszBody, cchBody, pvUser); + + RTMemFree(pszBody); + + *piLine = iLine; + *ppchLine = pchLine; + *pcchLine = cchLine; + *poff = off; + *penmEol = enmEol; + return rc; +} + + + +/** + * Common string literal handler. + * + * @returns new pchLine value. + * @param pIn The input string. + * @param chType The quotation type. + * @param pchLine The current line. + * @param ppchLine Pointer to the line variable. + * @param pcchLine Pointer to the line length variable. + * @param penmEol Pointer to the line ending type variable. + * @param piLine Pointer to the line number variable. + * @param poff Pointer to the line offset variable. + */ +static const char *handleStringLiteral(PSCMSTREAM pIn, char chType, const char *pchLine, size_t *pcchLine, PSCMEOL penmEol, + uint32_t *piLine, size_t *poff) +{ + size_t off = *poff; + for (;;) + { + bool fEnd = false; + bool fEscaped = false; + size_t const cchLine = *pcchLine; + while (off < cchLine) + { + char ch = pchLine[off++]; + if (!fEscaped) + { + if (ch != chType) + { + if (ch != '\\') + { /* likely */ } + else + fEscaped = true; + } + else + { + fEnd = true; + break; + } + } + else + fEscaped = false; + } + if (fEnd) + break; + + /* next line */ + pchLine = ScmStreamGetLine(pIn, pcchLine, penmEol); + if (!pchLine) + break; + *piLine += 1; + off = 0; + } + + *poff = off; + return pchLine; +} + + +/** + * Deals with comments in C and C++ code. + * + * @returns VBox status code / callback return code. + * @param pIn The stream to parse. + * @param pfnCallback The callback. + * @param pvUser The user parameter for the callback. + */ +static int enumerateCStyleComments(PSCMSTREAM pIn, PFNSCMCOMMENTENUMERATOR pfnCallback, void *pvUser) +{ + AssertCompile('\'' < '/'); + AssertCompile('"' < '/'); + + int rcRet = VINF_SUCCESS; + uint32_t iLine = 0; + SCMEOL enmEol; + size_t cchLine; + const char *pchLine; + while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL) + { + size_t off = 0; + while (off < cchLine) + { + unsigned ch = pchLine[off++]; + if (ch > (unsigned)'/') + { /* not interesting */ } + else if (ch == '/') + { + if (off < cchLine) + { + ch = pchLine[off++]; + if (ch == '*') + { + /* + * Multiline comment. Find the end. + * + * Note! This is very similar to the python doc string handling further down. + */ + SCMCOMMENTINFO Info; + Info.iLineStart = iLine; + Info.offStart = (uint32_t)off - 2; + Info.iLineEnd = UINT32_MAX; + Info.offEnd = UINT32_MAX; + Info.cBlankLinesBefore = 0; + + /* Determine comment type (same as for line-comments). */ + Info.enmType = kScmCommentType_MultiLine; + if ( off < cchLine + && ( (ch = pchLine[off]) == '*' + || ch == '!') ) + { + unsigned ch2; + if ( off + 1 == cchLine + || RT_C_IS_SPACE(ch2 = pchLine[off + 1]) ) + { + Info.enmType = ch == '*' ? kScmCommentType_MultiLine_JavaDoc : kScmCommentType_MultiLine_Qt; + off += 1; + } + else if ( ch2 == '<' + && ( off + 2 == cchLine + || RT_C_IS_SPACE(pchLine[off + 2]) )) + { + Info.enmType = ch == '*' ? kScmCommentType_MultiLine_JavaDoc_After + : kScmCommentType_MultiLine_Qt_After; + off += 2; + } + } + + /* + * Copy the body and find the end of the multiline comment. + */ + size_t cbBodyAlloc = 0; + size_t cchBody = 0; + char *pszBody = NULL; + for (;;) + { + /* Parse the line up to the end-of-comment or end-of-line. */ + size_t offLineStart = off; + size_t offLastNonBlank = off; + size_t offFirstNonBlank = ~(size_t)0; + while (off < cchLine) + { + ch = pchLine[off++]; + if (ch != '*' || off >= cchLine || pchLine[off] != '/') + { + if (RT_C_IS_BLANK(ch)) + {/* kind of likely */} + else + { + offLastNonBlank = off - 1; + if (offFirstNonBlank != ~(size_t)0) + {/* likely */} + else if ( ch != '*' /* ignore continuation-asterisks */ + || off > Info.offStart + 1 + 1 + || off > cchLine + || ( off < cchLine + && !RT_C_IS_SPACE(pchLine[off])) + || pszBody == NULL) + offFirstNonBlank = off - 1; + } + } + else + { + Info.offEnd = (uint32_t)++off; + Info.iLineEnd = iLine; + break; + } + } + + /* Append line content to the comment body string. */ + size_t cchAppend; + if (offFirstNonBlank == ~(size_t)0) + cchAppend = 0; /* empty line */ + else + { + if (pszBody) + offLineStart = RT_MIN(Info.offStart + 3, offFirstNonBlank); + else if (offFirstNonBlank > Info.offStart + 2) /* Skip one leading blank at the start of the comment. */ + offLineStart++; + cchAppend = offLastNonBlank + 1 - offLineStart; + Assert(cchAppend <= cchLine); + } + + size_t cchNewBody = cchBody + (cchBody > 0) + cchAppend; + if (cchNewBody >= cbBodyAlloc) + { + cbBodyAlloc = RT_MAX(cbBodyAlloc ? cbBodyAlloc * 2 : _1K, RT_ALIGN_Z(cchNewBody + 64, 128)); + void *pvNew = RTMemRealloc(pszBody, cbBodyAlloc); + if (pvNew) + pszBody = (char *)pvNew; + else + { + RTMemFree(pszBody); + return VERR_NO_MEMORY; + } + } + + if (cchBody > 0) /* no leading blank lines */ + pszBody[cchBody++] = '\n'; + else if (cchAppend == 0) + Info.cBlankLinesBefore++; + memcpy(&pszBody[cchBody], &pchLine[offLineStart], cchAppend); + cchBody += cchAppend; + pszBody[cchBody] = '\0'; + + /* Advance to the next line, if we haven't yet seen the end of this comment. */ + if (Info.iLineEnd != UINT32_MAX) + break; + pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol); + if (!pchLine) + { + Info.offEnd = (uint32_t)cchLine; + Info.iLineEnd = iLine; + break; + } + iLine++; + off = 0; + } + + /* Strip trailing empty lines in the body. */ + Info.cBlankLinesAfter = 0; + while (cchBody >= 1 && pszBody[cchBody - 1] == '\n') + { + Info.cBlankLinesAfter++; + pszBody[--cchBody] = '\0'; + } + + /* Do the callback. */ + int rc = pfnCallback(&Info, pszBody, cchBody, pvUser); + RTMemFree(pszBody); + if (RT_FAILURE(rc)) + return rc; + if (rc > VINF_SUCCESS && rcRet == VINF_SUCCESS) + rcRet = rc; + } + else if (ch == '/') + { + /* + * Line comment. Join the other line comment guys. + */ + off -= 2; + int rc = handleLineComment(pIn, isCppLineComment, pfnCallback, pvUser, + &pchLine, &cchLine, &enmEol, &iLine, &off); + if (RT_FAILURE(rc)) + return rc; + if (rcRet == VINF_SUCCESS) + rcRet = rc; + } + + if (!pchLine) + break; + } + } + else if (ch == '"') + { + /* + * String literal may include sequences that looks like comments. So, + * they needs special handling to avoid confusion. + */ + pchLine = handleStringLiteral(pIn, '"', pchLine, &cchLine, &enmEol, &iLine, &off); + } + /* else: We don't have to deal with character literal as these shouldn't + include comment-like sequences. */ + } /* for each character in the line */ + + iLine++; + } /* for each line in the stream */ + + int rcStream = ScmStreamGetStatus(pIn); + if (RT_SUCCESS(rcStream)) + return rcRet; + return rcStream; +} + + +/** + * Deals with comments in Python code. + * + * @returns VBox status code / callback return code. + * @param pIn The stream to parse. + * @param pfnCallback The callback. + * @param pvUser The user parameter for the callback. + */ +static int enumeratePythonComments(PSCMSTREAM pIn, PFNSCMCOMMENTENUMERATOR pfnCallback, void *pvUser) +{ + AssertCompile('#' < '\''); + AssertCompile('"' < '\''); + + int rcRet = VINF_SUCCESS; + uint32_t iLine = 0; + SCMEOL enmEol; + size_t cchLine; + const char *pchLine; + SCMCOMMENTINFO Info; + while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL) + { + size_t off = 0; + while (off < cchLine) + { + char ch = pchLine[off++]; + if ((unsigned char)ch > (unsigned char)'\'') + { /* not interesting */ } + else if (ch == '#') + { + /* + * Line comment. Join paths with the others. + */ + off -= 1; + int rc = handleLineComment(pIn, isHashComment, pfnCallback, pvUser, + &pchLine, &cchLine, &enmEol, &iLine, &off); + if (RT_FAILURE(rc)) + return rc; + if (rcRet == VINF_SUCCESS) + rcRet = rc; + + if (!pchLine) + break; + } + else if (ch == '"' || ch == '\'') + { + /* + * String literal may be doc strings and they may legally include hashes. + */ + const char chType = ch; + if ( off + 1 >= cchLine + || pchLine[off] != chType + || pchLine[off + 1] != chType) + pchLine = handleStringLiteral(pIn, chType, pchLine, &cchLine, &enmEol, &iLine, &off); + else + { + /* + * Doc string (/ long string). + * + * Note! This is very similar to the multiline C comment handling above. + */ + Info.iLineStart = iLine; + Info.offStart = (uint32_t)off - 1; + Info.iLineEnd = UINT32_MAX; + Info.offEnd = UINT32_MAX; + Info.cBlankLinesBefore = 0; + Info.enmType = kScmCommentType_DocString; + + off += 2; + + /* Copy the body and find the end of the doc string comment. */ + size_t cbBodyAlloc = 0; + size_t cchBody = 0; + char *pszBody = NULL; + for (;;) + { + /* Parse the line up to the end-of-comment or end-of-line. */ + size_t offLineStart = off; + size_t offLastNonBlank = off; + size_t offFirstNonBlank = ~(size_t)0; + bool fEscaped = false; + while (off < cchLine) + { + ch = pchLine[off++]; + if (!fEscaped) + { + if ( off + 1 >= cchLine + || ch != chType + || pchLine[off] != chType + || pchLine[off + 1] != chType) + { + if (RT_C_IS_BLANK(ch)) + {/* kind of likely */} + else + { + offLastNonBlank = off - 1; + if (offFirstNonBlank != ~(size_t)0) + {/* likely */} + else if ( ch != '*' /* ignore continuation-asterisks */ + || off > Info.offStart + 1 + 1 + || off > cchLine + || ( off < cchLine + && !RT_C_IS_SPACE(pchLine[off])) + || pszBody == NULL) + offFirstNonBlank = off - 1; + + if (ch != '\\') + {/* likely */ } + else + fEscaped = true; + } + } + else + { + off += 2; + Info.offEnd = (uint32_t)off; + Info.iLineEnd = iLine; + break; + } + } + else + fEscaped = false; + } + + /* Append line content to the comment body string. */ + size_t cchAppend; + if (offFirstNonBlank == ~(size_t)0) + cchAppend = 0; /* empty line */ + else + { + if (pszBody) + offLineStart = RT_MIN(Info.offStart + 3, offFirstNonBlank); + else if (offFirstNonBlank > Info.offStart + 2) /* Skip one leading blank at the start of the comment. */ + offLineStart++; + cchAppend = offLastNonBlank + 1 - offLineStart; + Assert(cchAppend <= cchLine); + } + + size_t cchNewBody = cchBody + (cchBody > 0) + cchAppend; + if (cchNewBody >= cbBodyAlloc) + { + cbBodyAlloc = RT_MAX(cbBodyAlloc ? cbBodyAlloc * 2 : _1K, RT_ALIGN_Z(cchNewBody + 64, 128)); + void *pvNew = RTMemRealloc(pszBody, cbBodyAlloc); + if (pvNew) + pszBody = (char *)pvNew; + else + { + RTMemFree(pszBody); + return VERR_NO_MEMORY; + } + } + + if (cchBody > 0) /* no leading blank lines */ + pszBody[cchBody++] = '\n'; + else if (cchAppend == 0) + Info.cBlankLinesBefore++; + memcpy(&pszBody[cchBody], &pchLine[offLineStart], cchAppend); + cchBody += cchAppend; + pszBody[cchBody] = '\0'; + + /* Advance to the next line, if we haven't yet seen the end of this comment. */ + if (Info.iLineEnd != UINT32_MAX) + break; + pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol); + if (!pchLine) + { + Info.offEnd = (uint32_t)cchLine; + Info.iLineEnd = iLine; + break; + } + iLine++; + off = 0; + } + + /* Strip trailing empty lines in the body. */ + Info.cBlankLinesAfter = 0; + while (cchBody >= 1 && pszBody[cchBody - 1] == '\n') + { + Info.cBlankLinesAfter++; + pszBody[--cchBody] = '\0'; + } + + /* Do the callback. */ + int rc = pfnCallback(&Info, pszBody, cchBody, pvUser); + RTMemFree(pszBody); + if (RT_FAILURE(rc)) + return rc; + if (rc > VINF_SUCCESS && rcRet == VINF_SUCCESS) + rcRet = rc; + } + + if (!pchLine) + break; + } + /* else: We don't have to deal with character literal as these shouldn't + include comment-like sequences. */ + } /* for each character in the line */ + + iLine++; + } /* for each line in the stream */ + + int rcStream = ScmStreamGetStatus(pIn); + if (RT_SUCCESS(rcStream)) + return rcRet; + return rcStream; +} + + +/** + * Deals with XML comments. + * + * @returns VBox status code / callback return code. + * @param pIn The stream to parse. + * @param pfnCallback The callback. + * @param pvUser The user parameter for the callback. + */ +static int enumerateXmlComments(PSCMSTREAM pIn, PFNSCMCOMMENTENUMERATOR pfnCallback, void *pvUser) +{ + int rcRet = VINF_SUCCESS; + uint32_t iLine = 0; + SCMEOL enmEol; + size_t cchLine; + const char *pchLine; + while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL) + { + size_t off = 0; + while (off < cchLine) + { + /* + * Skip leading blanks and check for start of XML comment. + */ + while (off + 3 < cchLine && RT_C_IS_SPACE(pchLine[off])) + off++; + if (IS_XML_COMMENT_START(pchLine, off, cchLine)) + { + /* + * XML comment. Find the end. + * + * Note! This is very similar to the python doc string handling above. + */ + SCMCOMMENTINFO Info; + Info.iLineStart = iLine; + Info.offStart = (uint32_t)off; + Info.iLineEnd = UINT32_MAX; + Info.offEnd = UINT32_MAX; + Info.cBlankLinesBefore = 0; + Info.enmType = kScmCommentType_Xml; + + off += 4; + + /* + * Copy the body and find the end of the XML comment. + */ + size_t cbBodyAlloc = 0; + size_t cchBody = 0; + char *pszBody = NULL; + for (;;) + { + /* Parse the line up to the end-of-comment or end-of-line. */ + size_t offLineStart = off; + size_t offLastNonBlank = off; + size_t offFirstNonBlank = ~(size_t)0; + while (off < cchLine) + { + if (!IS_XML_COMMENT_END(pchLine, off, cchLine)) + { + char ch = pchLine[off++]; + if (RT_C_IS_BLANK(ch)) + {/* kind of likely */} + else + { + offLastNonBlank = off - 1; + if (offFirstNonBlank != ~(size_t)0) + {/* likely */} + else if ( (ch != '*' && ch != '#') /* ignore continuation-asterisks */ + || off > Info.offStart + 1 + 1 + || off > cchLine + || ( off < cchLine + && !RT_C_IS_SPACE(pchLine[off])) + || pszBody == NULL) + offFirstNonBlank = off - 1; + } + } + else + { + off += 3; + Info.offEnd = (uint32_t)off; + Info.iLineEnd = iLine; + break; + } + } + + /* Append line content to the comment body string. */ + size_t cchAppend; + if (offFirstNonBlank == ~(size_t)0) + cchAppend = 0; /* empty line */ + else + { + offLineStart = offFirstNonBlank; + cchAppend = offLastNonBlank + 1 - offLineStart; + Assert(cchAppend <= cchLine); + } + + size_t cchNewBody = cchBody + (cchBody > 0) + cchAppend; + if (cchNewBody >= cbBodyAlloc) + { + cbBodyAlloc = RT_MAX(cbBodyAlloc ? cbBodyAlloc * 2 : _1K, RT_ALIGN_Z(cchNewBody + 64, 128)); + void *pvNew = RTMemRealloc(pszBody, cbBodyAlloc); + if (pvNew) + pszBody = (char *)pvNew; + else + { + RTMemFree(pszBody); + return VERR_NO_MEMORY; + } + } + + if (cchBody > 0) /* no leading blank lines */ + pszBody[cchBody++] = '\n'; + else if (cchAppend == 0) + Info.cBlankLinesBefore++; + memcpy(&pszBody[cchBody], &pchLine[offLineStart], cchAppend); + cchBody += cchAppend; + pszBody[cchBody] = '\0'; + + /* Advance to the next line, if we haven't yet seen the end of this comment. */ + if (Info.iLineEnd != UINT32_MAX) + break; + pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol); + if (!pchLine) + { + Info.offEnd = (uint32_t)cchLine; + Info.iLineEnd = iLine; + break; + } + iLine++; + off = 0; + } + + /* Strip trailing empty lines in the body. */ + Info.cBlankLinesAfter = 0; + while (cchBody >= 1 && pszBody[cchBody - 1] == '\n') + { + Info.cBlankLinesAfter++; + pszBody[--cchBody] = '\0'; + } + + /* Do the callback. */ + int rc = pfnCallback(&Info, pszBody, cchBody, pvUser); + RTMemFree(pszBody); + if (RT_FAILURE(rc)) + return rc; + if (rc > VINF_SUCCESS && rcRet == VINF_SUCCESS) + rcRet = rc; + } + else + off++; + } /* for each character in the line */ + + iLine++; + } /* for each line in the stream */ + + int rcStream = ScmStreamGetStatus(pIn); + if (RT_SUCCESS(rcStream)) + return rcRet; + return rcStream; +} + + +/** + * Deals with comments in DOS batch files. + * + * @returns VBox status code / callback return code. + * @param pIn The stream to parse. + * @param pfnCallback The callback. + * @param pvUser The user parameter for the callback. + */ +static int enumerateBatchComments(PSCMSTREAM pIn, PFNSCMCOMMENTENUMERATOR pfnCallback, void *pvUser) +{ + int rcRet = VINF_SUCCESS; + uint32_t iLine = 0; + SCMEOL enmEol; + size_t cchLine; + const char *pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol); + while (pchLine != NULL) + { + /* + * Skip leading blanks and check for 'rem'. + * At the moment we do not parse '::label-comments'. + */ + size_t off = 0; + while (off + 3 < cchLine && RT_C_IS_SPACE(pchLine[off])) + off++; + if (!IS_REM(pchLine, off, cchLine)) + { + iLine++; + pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol); + } + else + { + int rc = handleLineComment(pIn, isBatchComment, pfnCallback, pvUser, + &pchLine, &cchLine, &enmEol, &iLine, &off); + if (RT_FAILURE(rc)) + return rc; + if (rcRet == VINF_SUCCESS) + rcRet = rc; + } + } + + int rcStream = ScmStreamGetStatus(pIn); + if (RT_SUCCESS(rcStream)) + return rcRet; + return rcStream; +} + + +/** + * Deals with comments in SQL files. + * + * @returns VBox status code / callback return code. + * @param pIn The stream to parse. + * @param pfnCallback The callback. + * @param pvUser The user parameter for the callback. + */ +static int enumerateSqlComments(PSCMSTREAM pIn, PFNSCMCOMMENTENUMERATOR pfnCallback, void *pvUser) +{ + int rcRet = VINF_SUCCESS; + uint32_t iLine = 0; + SCMEOL enmEol; + size_t cchLine; + const char *pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol); + while (pchLine != NULL) + { + /* + * Skip leading blanks and check for '--'. + */ + size_t off = 0; + while (off + 3 < cchLine && RT_C_IS_SPACE(pchLine[off])) + off++; + if ( cchLine < 2 + || pchLine[0] != '-' + || pchLine[1] != '-') + { + iLine++; + pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol); + } + else + { + int rc = handleLineComment(pIn, isSqlComment, pfnCallback, pvUser, + &pchLine, &cchLine, &enmEol, &iLine, &off); + if (RT_FAILURE(rc)) + return rc; + if (rcRet == VINF_SUCCESS) + rcRet = rc; + } + } + + int rcStream = ScmStreamGetStatus(pIn); + if (RT_SUCCESS(rcStream)) + return rcRet; + return rcStream; +} + + +/** + * Deals with simple line comments. + * + * @returns VBox status code / callback return code. + * @param pIn The stream to parse. + * @param chStart The start of comment character. + * @param pfnIsComment Comment tester function. + * @param pfnCallback The callback. + * @param pvUser The user parameter for the callback. + */ +static int enumerateSimpleLineComments(PSCMSTREAM pIn, char chStart, PFNISCOMMENT pfnIsComment, + PFNSCMCOMMENTENUMERATOR pfnCallback, void *pvUser) +{ + int rcRet = VINF_SUCCESS; + uint32_t iLine = 0; + SCMEOL enmEol; + size_t cchLine; + const char *pchLine; + while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL) + { + size_t off = 0; + while (off < cchLine) + { + char ch = pchLine[off++]; + if (ch != chStart) + { /* not interesting */ } + else + { + off -= 1; + int rc = handleLineComment(pIn, pfnIsComment, pfnCallback, pvUser, + &pchLine, &cchLine, &enmEol, &iLine, &off); + if (RT_FAILURE(rc)) + return rc; + if (rcRet == VINF_SUCCESS) + rcRet = rc; + + if (!pchLine) + break; + } + } /* for each character in the line */ + + iLine++; + } /* for each line in the stream */ + + int rcStream = ScmStreamGetStatus(pIn); + if (RT_SUCCESS(rcStream)) + return rcRet; + return rcStream; +} + + +/** + * Enumerates the comments in the given stream, calling @a pfnCallback for each. + * + * @returns IPRT status code. + * @param pIn The stream to parse. + * @param enmCommentStyle The comment style of the source stream. + * @param pfnCallback The function to call. + * @param pvUser User argument to the callback. + */ +int ScmEnumerateComments(PSCMSTREAM pIn, SCMCOMMENTSTYLE enmCommentStyle, PFNSCMCOMMENTENUMERATOR pfnCallback, void *pvUser) +{ + switch (enmCommentStyle) + { + case kScmCommentStyle_C: + return enumerateCStyleComments(pIn, pfnCallback, pvUser); + + case kScmCommentStyle_Python: + return enumeratePythonComments(pIn, pfnCallback, pvUser); + + case kScmCommentStyle_Semicolon: + return enumerateSimpleLineComments(pIn, ';', isSemicolonComment, pfnCallback, pvUser); + + case kScmCommentStyle_Hash: + return enumerateSimpleLineComments(pIn, '#', isHashComment, pfnCallback, pvUser); + + case kScmCommentStyle_Rem_Upper: + case kScmCommentStyle_Rem_Lower: + case kScmCommentStyle_Rem_Camel: + return enumerateBatchComments(pIn, pfnCallback, pvUser); + + case kScmCommentStyle_Sql: + return enumerateSqlComments(pIn, pfnCallback, pvUser); + + case kScmCommentStyle_Tick: + return enumerateSimpleLineComments(pIn, '\'', isTickComment, pfnCallback, pvUser); + + case kScmCommentStyle_Xml: + return enumerateXmlComments(pIn, pfnCallback, pvUser); + + default: + AssertFailedReturn(VERR_INVALID_PARAMETER); + } +} + diff --git a/src/bldprogs/scmrw-kmk.cpp b/src/bldprogs/scmrw-kmk.cpp new file mode 100644 index 00000000..7907150b --- /dev/null +++ b/src/bldprogs/scmrw-kmk.cpp @@ -0,0 +1,2273 @@ +/* $Id: scmrw-kmk.cpp $ */ +/** @file + * IPRT Testcase / Tool - Source Code Massager, Makefile.kmk/kup. + */ + +/* + * Copyright (C) 2010-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/assert.h> +#include <iprt/ctype.h> +#include <iprt/dir.h> +#include <iprt/env.h> +#include <iprt/file.h> +#include <iprt/err.h> +#include <iprt/getopt.h> +#include <iprt/initterm.h> +#include <iprt/mem.h> +#include <iprt/message.h> +#include <iprt/param.h> +#include <iprt/path.h> +#include <iprt/process.h> +#include <iprt/stream.h> +#include <iprt/string.h> + +#include "scm.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef enum KMKASSIGNTYPE +{ + kKmkAssignType_Recursive, + kKmkAssignType_Conditional, + kKmkAssignType_Appending, + kKmkAssignType_Prepending, + kKmkAssignType_Simple, + kKmkAssignType_Immediate +} KMKASSIGNTYPE; + +/** Context for scmKmkWordLength. */ +typedef enum +{ + /** Target file or assignment. + * Separators: space, '=', ':' */ + kKmkWordCtx_TargetFileOrAssignment, + /** Target file. + * Separators: space, ':' */ + kKmkWordCtx_TargetFile, + /** Dependency file or (target variable) assignment. + * Separators: space, '=', ':', '|' */ + kKmkWordCtx_DepFileOrAssignment, + /** Dependency file. + * Separators: space, '|' */ + kKmkWordCtx_DepFile, + /** Last context which may do double expansion. */ + kKmkWordCtx_LastDoubleExpansion = kKmkWordCtx_DepFile +} KMKWORDCTX; + +typedef struct KMKWORDSTATE +{ + uint16_t uDepth; + char chOpen; +} KMKWORDSTATE; + +typedef enum KMKTOKEN +{ + kKmkToken_Word = 0, + kKmkToken_Comment, + + /* Conditionals: */ + kKmkToken_ifeq, + kKmkToken_ifneq, + kKmkToken_if1of, + kKmkToken_ifn1of, + kKmkToken_ifdef, + kKmkToken_ifndef, + kKmkToken_if, + kKmkToken_else, + kKmkToken_endif, + + /* Includes: */ + kKmkToken_include, + kKmkToken_sinclude, + kKmkToken_dash_include, + kKmkToken_includedep, + kKmkToken_includedep_queue, + kKmkToken_includedep_flush, + + /* Others: */ + kKmkToken_define, + kKmkToken_endef, + kKmkToken_export, + kKmkToken_unexport, + kKmkToken_local, + kKmkToken_override, + kKmkToken_undefine +} KMKTOKEN; + +typedef struct KMKPARSER +{ + struct + { + KMKTOKEN enmToken; + bool fIgnoreNesting; + size_t iLine; + } aDepth[64]; + unsigned iDepth; + unsigned iActualDepth; + bool fInRecipe; + + /** The EOL type of the current line. */ + SCMEOL enmEol; + /** The length of the current line. */ + size_t cchLine; + /** Pointer to the start of the current line. */ + char const *pchLine; + + /** @name Only used for rule/assignment parsing. + * @{ */ + /** Number of continuation lines at current rule/assignment. */ + uint32_t cLines; + /** Characters in continuation lines at current rule/assignment. */ + size_t cchTotalLine; + /** @} */ + + /** The SCM rewriter state. */ + PSCMRWSTATE pState; + /** The input stream. */ + PSCMSTREAM pIn; + /** The output stream. */ + PSCMSTREAM pOut; + /** The settings. */ + PCSCMSETTINGSBASE pSettings; + /** Scratch buffer. */ + char szBuf[4096]; +} KMKPARSER; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static char const g_szTabs[] = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"; + + +static KMKTOKEN scmKmkIdentifyToken(const char *pchWord, size_t cchWord) +{ + static struct { const char *psz; uint32_t cch; KMKTOKEN enmToken; } s_aTokens[] = + { + { RT_STR_TUPLE("if"), kKmkToken_if }, + { RT_STR_TUPLE("ifeq"), kKmkToken_ifeq }, + { RT_STR_TUPLE("ifneq"), kKmkToken_ifneq }, + { RT_STR_TUPLE("if1of"), kKmkToken_if1of }, + { RT_STR_TUPLE("ifn1of"), kKmkToken_ifn1of }, + { RT_STR_TUPLE("ifdef"), kKmkToken_ifdef }, + { RT_STR_TUPLE("ifndef"), kKmkToken_ifndef }, + { RT_STR_TUPLE("else"), kKmkToken_else }, + { RT_STR_TUPLE("endif"), kKmkToken_endif }, + { RT_STR_TUPLE("include"), kKmkToken_include }, + { RT_STR_TUPLE("sinclude"), kKmkToken_sinclude }, + { RT_STR_TUPLE("-include"), kKmkToken_dash_include }, + { RT_STR_TUPLE("includedep"), kKmkToken_includedep }, + { RT_STR_TUPLE("includedep-queue"), kKmkToken_includedep_queue }, + { RT_STR_TUPLE("includedep-flush"), kKmkToken_includedep_flush }, + { RT_STR_TUPLE("define"), kKmkToken_define }, + { RT_STR_TUPLE("endef"), kKmkToken_endef }, + { RT_STR_TUPLE("export"), kKmkToken_export }, + { RT_STR_TUPLE("unexport"), kKmkToken_unexport }, + { RT_STR_TUPLE("local"), kKmkToken_local }, + { RT_STR_TUPLE("override"), kKmkToken_override }, + { RT_STR_TUPLE("undefine"), kKmkToken_undefine }, + }; + char chFirst = *pchWord; + if ( chFirst == 'i' + || chFirst == 'e' + || chFirst == 'd' + || chFirst == 's' + || chFirst == '-' + || chFirst == 'u' + || chFirst == 'l' + || chFirst == 'o') + { + for (size_t i = 0; i < RT_ELEMENTS(s_aTokens); i++) + if ( s_aTokens[i].cch == cchWord + && *s_aTokens[i].psz == chFirst + && memcmp(s_aTokens[i].psz, pchWord, cchWord) == 0) + return s_aTokens[i].enmToken; + } +#ifdef VBOX_STRICT + else + for (size_t i = 0; i < RT_ELEMENTS(s_aTokens); i++) + Assert(chFirst != *s_aTokens[i].psz); +#endif + + if (chFirst == '#') + return kKmkToken_Comment; + return kKmkToken_Word; +} + + +/** + * Modifies the fInRecipe state variable, logging changes in verbose mode. + */ +static void scmKmkSetInRecipe(KMKPARSER *pParser, bool fInRecipe) +{ + if (pParser->fInRecipe != fInRecipe) + ScmVerbose(pParser->pState, 4, "%u: debug: %s\n", + ScmStreamTellLine(pParser->pIn), fInRecipe ? "in-recipe" : "not-in-recipe"); + pParser->fInRecipe = fInRecipe; +} + + +/** + * Gives up on the current line, copying it as it and requesting manual repair. + */ +static bool scmKmkGiveUp(KMKPARSER *pParser, const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + ScmFixManually(pParser->pState, "%u: %N\n", ScmStreamTellLine(pParser->pIn), pszFormat, &va); + va_end(va); + + ScmStreamPutLine(pParser->pOut, pParser->pchLine, pParser->cchLine, pParser->enmEol); + return false; +} + + +static bool scmKmkIsLineWithContinuationSlow(const char *pchLine, size_t cchLine) +{ + size_t cchSlashes = 1; + cchLine--; + while (cchSlashes < cchLine && pchLine[cchLine - cchSlashes - 1] == '\\') + cchSlashes++; + return RT_BOOL(cchSlashes & 1); +} + + +DECLINLINE(bool) scmKmkIsLineWithContinuation(const char *pchLine, size_t cchLine) +{ + if (cchLine == 0 || pchLine[cchLine - 1] != '\\') + return false; + return scmKmkIsLineWithContinuationSlow(pchLine, cchLine); +} + + +/** + * Finds the length of a line where line continuation is in play. + * + * @returns Length from start of current line to the final unescaped EOL. + * @param pParser The KMK parser state. + * @param pcLine Where to return the number of lines. Optional. + * @param pcchMaxLeadWord Where to return the max lead word length on + * subsequent lines. Used to help balance multi-line + * 'if' statements (imperfect). Optional. + */ +static size_t scmKmkLineContinuationPeek(KMKPARSER *pParser, uint32_t *pcLines, size_t *pcchMaxLeadWord) +{ + size_t const offSaved = ScmStreamTell(pParser->pIn); + uint32_t cLines = 1; + size_t cchMaxLeadWord = 0; + const char *pchLine = pParser->pchLine; + size_t cchLine = pParser->cchLine; + SCMEOL enmEol; + for (;;) + { + /* Return if no line continuation (or end of stream): */ + if ( cchLine == 0 + || !scmKmkIsLineWithContinuation(pchLine, cchLine) + || ScmStreamIsEndOfStream(pParser->pIn)) + { + ScmStreamSeekAbsolute(pParser->pIn, offSaved); + if (pcLines) + *pcLines = cLines; + if (pcchMaxLeadWord) + *pcchMaxLeadWord = cchMaxLeadWord; + return (size_t)(pchLine - pParser->pchLine) + cchLine; + } + + /* Get the next line: */ + pchLine = ScmStreamGetLine(pParser->pIn, &cchLine, &enmEol); + cLines++; + + /* Check the length of the first word if requested: */ + if (pcchMaxLeadWord) + { + size_t offLine = 0; + while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine])) + offLine++; + + size_t const offStartWord = offLine; + while (offLine < cchLine && !RT_C_IS_BLANK(pchLine[offLine])) + offLine++; + + if (offLine - offStartWord > cchMaxLeadWord) + cchMaxLeadWord = offLine - offStartWord; + } + } +} + + +/** + * Checks if the given line contains a comment with the @a pszMarker word in it. + * + * This can be used to disable warnings. + * + * @returns true if this is the case, false if not + * @param pchLine The line. + * @param cchLine The line length. + * @param offLine The current line position, 0 if uncertain. + * @param pszMarker The marker to check for. + * @param cchMarker The length of the marker string. + */ +static bool scmKmkHasCommentMarker(const char *pchLine, size_t cchLine, size_t offLine, const char *pszMarker, size_t cchMarker) +{ + const char *pchCur = (const char *)memchr(&pchLine[offLine], '#', cchLine - RT_MIN(offLine, cchLine)); + if (pchCur) + { + pchCur++; + size_t cchLeft = (size_t)(&pchLine[cchLine] - pchCur); + while (cchLeft >= cchMarker) + { + const char *pchHit = (char *)memchr(pchCur, *pszMarker, cchLeft - cchMarker + 1); + if (!pchHit) + break; + if (memcmp(pchHit, pszMarker, cchMarker) == 0) + return true; + pchCur = pchHit + 1; + cchLeft = (size_t)(&pchLine[cchLine] - pchCur); + } + } + return false; +} + + +/** + * Pushes a if or define on the nesting stack. + */ +static bool scmKmkPushNesting(KMKPARSER *pParser, KMKTOKEN enmToken) +{ + uint32_t iDepth = pParser->iDepth; + if (iDepth + 1 >= RT_ELEMENTS(pParser->aDepth)) + { + ScmError(pParser->pState, VERR_ASN1_TOO_DEEPLY_NESTED /*?*/, + "%u: Too deep if/define nesting!\n", ScmStreamTellLine(pParser->pIn)); + return false; + } + + pParser->aDepth[iDepth].enmToken = enmToken; + pParser->aDepth[iDepth].iLine = ScmStreamTellLine(pParser->pIn); + pParser->aDepth[iDepth].fIgnoreNesting = false; + pParser->iDepth = iDepth + 1; + pParser->iActualDepth += 1; + ScmVerbose(pParser->pState, 5, "%u: debug: nesting %u (token %u)\n", pParser->aDepth[iDepth].iLine, iDepth + 1, enmToken); + return true; +} + + +/** + * Checks if we're inside a define or not. + */ +static bool scmKmkIsInsideDefine(KMKPARSER const *pParser) +{ + unsigned iDepth = pParser->iDepth; + while (iDepth-- > 0) + if (pParser->aDepth[iDepth].enmToken == kKmkToken_define) + return true; + return false; +} + + +/** + * Skips a string stopping at @a chStop1 or @a chStop2, taking $() and ${} into + * account. + */ +static size_t scmKmkSkipExpString(const char *pchLine, size_t cchLine, size_t off, char chStop1, char chStop2 = '\0') +{ + unsigned iExpDepth = 0; + char ch; + while ( off < cchLine + && (ch = pchLine[off]) + && ( (ch != chStop1 && ch != chStop2) + || iExpDepth > 0)) + { + off++; + if (ch == '$') + { + ch = pchLine[off]; + if (ch == '(' || ch == '{') + { + iExpDepth++; + off++; + } + } + else if ((ch == ')' || ch == '}') && iExpDepth > 0) + iExpDepth--; + } + return off; +} + + +/** + * Finds the length of the word (file) @a offStart. + * + * This only takes one line into account, so variable expansions (function + * calls) spanning multiple lines will be handled as one word per line with help + * from @a pState. This allows the caller to properly continutation intend the + * additional lines. + * + * @returns Length of word starting at @a offStart. Zero if there is whitespace + * at given offset or it's beyond the end of the line (both cases will + * assert). + * @param pchLine The line. + * @param cchLine The line length. + * @param offStart Offset to the start of the word. + * @param pState Where multiline variable expansion is tracked. + */ +static size_t scmKmkWordLength(const char *pchLine, size_t cchLine, size_t offStart, KMKWORDCTX enmCtx, KMKWORDSTATE *pState) +{ + AssertReturn(offStart < cchLine && !RT_C_IS_BLANK(pchLine[offStart]), 0); + + /* + * Drop any line continuation slash from the line length, so we don't count + * it into the word length. Also, any spaces preceeding it (for multiline + * variable function expansion). ASSUMES no trailing slash escaping. + */ + if (cchLine > 0 && pchLine[cchLine - 1] == '\\') + do + cchLine--; + while (cchLine > offStart && RT_C_IS_SPACE(pchLine[cchLine - 1])); + + /* + * If we were inside a variable function expansion, continue till we reach the end. + * This kind of duplicates the code below. + */ + size_t off = offStart; + if (pState->uDepth > 0) + { + Assert(pState->chOpen == '(' || pState->chOpen == '{'); + char const chOpen = pState->chOpen; + char const chClose = chOpen == '(' ? ')' : '}'; + unsigned uDepth = pState->uDepth; + for (;;) + { + char ch; + if (off < cchLine) + ch = pchLine[off++]; + else /* Reached the end while still inside the expansion. */ + { + pState->chOpen = chOpen; + pState->uDepth = (uint16_t)uDepth; + return cchLine - offStart; + } + if (ch == chOpen) + uDepth++; + else if (ch == chClose && --uDepth == 0) + break; + } + pState->uDepth = 0; + pState->chOpen = 0; + } + + /* + * Process till we find blank or end of the line. + */ + while (off < cchLine) + { + char ch = pchLine[off]; + if (RT_C_IS_BLANK(ch)) + break; + + if (ch == '$') + { + /* + * Skip variable expansion. ASSUMING double expansion being enabled + * for rules, we respond to both $() and $$() here, $$$$() + */ + size_t cDollars = 0; + do + { + off++; + if (off >= cchLine) + return cchLine - offStart; + cDollars++; + ch = pchLine[off]; + } while (ch == '$'); + if ((cDollars & 1) || (cDollars == 2 && enmCtx <= kKmkWordCtx_LastDoubleExpansion)) + { + char const chOpen = ch; + if (ch == '(' || ch == '{') + { + char const chClose = chOpen == '(' ? ')' : '}'; + unsigned uDepth = 1; + off++; + for (;;) + { + if (off < cchLine) + ch = pchLine[off++]; + else /* Reached the end while inside the expansion. */ + { + pState->chOpen = chOpen; + pState->uDepth = (uint16_t)uDepth; + return cchLine - offStart; + } + if (ch == chOpen) + uDepth++; + else if (ch == chClose && --uDepth == 0) + break; + } + } + else if (cDollars & 1) + off++; /* $X */ + } + continue; + } + else if (ch == ':') + { + /* + * Check for plain driver letter, omitting the archive member variant. + */ + if (off - offStart != 1 || !RT_C_IS_ALPHA(pchLine[off - 1])) + { + if (off == offStart) + { + /* We need to check for single and double colon rules as well as + simple and immediate assignments here. */ + off++; + if (pchLine[off] == ':') + { + off++; + if (pchLine[off] == '=') + { + if (enmCtx == kKmkWordCtx_TargetFileOrAssignment || enmCtx == kKmkWordCtx_DepFileOrAssignment) + return 3; /* ::= - immediate assignment. */ + off++; + } + else if (enmCtx != kKmkWordCtx_DepFile) + return 2; /* :: - double colon rule */ + } + else if (pchLine[off] == '=') + { + if (enmCtx == kKmkWordCtx_TargetFileOrAssignment || enmCtx == kKmkWordCtx_DepFileOrAssignment) + return 2; /* := - simple assignment. */ + off++; + } + else if (enmCtx != kKmkWordCtx_DepFile) + return 1; /* : - regular rule. */ + continue; + } + /* ':' is a separator except in DepFile context. */ + else if (enmCtx != kKmkWordCtx_DepFile) + return off - offStart; + } + } + else if (ch == '=') + { + /* + * Assignment. We check for the previous character too so we'll catch + * append, prepend and conditional assignments. Simple and immediate + * assignments are handled above. + */ + if ( enmCtx == kKmkWordCtx_TargetFileOrAssignment + || enmCtx == kKmkWordCtx_DepFileOrAssignment) + { + if (off > offStart) + { + ch = pchLine[off - 1]; + if (ch == '?' || ch == '+' || ch == '>') + off = off - 1 == offStart + ? off + 2 /* return '+=', '?=', '<=' */ + : off - 1; /* up to '+=', '?=', '<=' */ + else + Assert(ch != ':'); /* handled above */ + } + else + off++; /* '=' */ + return off - offStart; + } + } + else if (ch == '|') + { + /* + * This is rather straight forward. + */ + if (enmCtx == kKmkWordCtx_DepFileOrAssignment || enmCtx == kKmkWordCtx_DepFile) + { + if (off == offStart) + return 1; + return off - offStart; + } + } + off++; + } + return off - offStart; +} + + +static bool scmKmkTailComment(KMKPARSER *pParser, const char *pchLine, size_t cchLine, size_t offSrc, char **ppszDst) +{ + /* Wind back offSrc to the first blank space (not all callers can do this). */ + Assert(offSrc <= cchLine); + while (offSrc > 0 && RT_C_IS_SPACE(pchLine[offSrc - 1])) + offSrc--; + size_t const offSrcStart = offSrc; + + /* Skip blanks. */ + while (offSrc < cchLine && RT_C_IS_SPACE(pchLine[offSrc])) + offSrc++; + if (offSrc >= cchLine) + return true; + + /* Is it a comment? */ + char *pszDst = *ppszDst; + if (pchLine[offSrc] == '#') + { + /* Try preserve the start column number. */ +/** @todo tabs */ + size_t const offDst = pszDst - pParser->szBuf; + if (offDst < offSrc) + { + memset(pszDst, ' ', offSrc - offDst); + pszDst += offSrc - offDst; + } + else if (offSrc != offSrcStart) + *pszDst++ = ' '; + + *ppszDst = pszDst = (char *)mempcpy(pszDst, &pchLine[offSrc], cchLine - offSrc); + return false; /*dummy*/ + } + + /* Complain and copy out the text unmodified. */ + ScmError(pParser->pState, VERR_PARSE_ERROR, "%u:%u: Expected comment, found: %.*s", + ScmStreamTellLine(pParser->pIn), offSrc, cchLine - offSrc, &pchLine[offSrc]); + *ppszDst = (char *)mempcpy(pszDst, &pchLine[offSrcStart], cchLine - offSrcStart); + return false; /*dummy*/ +} + + +/** + * Deals with: ifeq, ifneq, if1of and ifn1of + * + * @returns dummy (false) to facility return + call. + */ +static bool scmKmkHandleIfParentheses(KMKPARSER *pParser, size_t offToken, KMKTOKEN enmToken, size_t cchToken, bool fElse) +{ + const char * const pchLine = pParser->pchLine; + size_t const cchLine = pParser->cchLine; + uint32_t const cchIndent = pParser->iActualDepth + - (fElse && pParser->iActualDepth > 0 && !pParser->aDepth[pParser->iDepth - 1].fIgnoreNesting); + + /* + * Push it onto the stack. All these nestings are relevant. + */ + if (!fElse) + { + if (!scmKmkPushNesting(pParser, enmToken)) + return false; + } + else + { + pParser->aDepth[pParser->iDepth - 1].enmToken = enmToken; + pParser->aDepth[pParser->iDepth - 1].iLine = ScmStreamTellLine(pParser->pIn); + } + + /* + * We do not allow line continuation for these. + */ + if (scmKmkIsLineWithContinuation(pchLine, cchLine)) + return scmKmkGiveUp(pParser, "Line continuation not allowed with '%.*s' directive.", cchToken, &pchLine[offToken]); + + /* + * We stage the modified line in the buffer, so check that the line isn't + * too long (it seriously should be). + */ + if (cchLine + cchIndent + 32 > sizeof(pParser->szBuf)) + return scmKmkGiveUp(pParser, "Line too long for a '%.*s' directive: %u chars", cchToken, &pchLine[offToken], cchLine); + char *pszDst = pParser->szBuf; + + /* + * Emit indent and initial token. + */ + memset(pszDst, ' ', cchIndent); + pszDst += cchIndent; + + if (fElse) + pszDst = (char *)mempcpy(pszDst, RT_STR_TUPLE("else ")); + + memcpy(pszDst, &pchLine[offToken], cchToken); + pszDst += cchToken; + + size_t offSrc = offToken + cchToken; + + /* + * There shall be exactly one space between the token and the opening parenthesis. + */ + if (pchLine[offSrc] == ' ' && pchLine[offSrc + 1] == '(') + offSrc += 2; + else + { + while (offSrc < cchLine && RT_C_IS_BLANK(pchLine[offSrc])) + offSrc++; + if (pchLine[offSrc] != '(') + return scmKmkGiveUp(pParser, "Expected '(' to follow '%.*s'", cchToken, &pchLine[offToken]); + offSrc++; + } + *pszDst++ = ' '; + *pszDst++ = '('; + + /* + * Skip spaces after the opening parenthesis. + */ + while (offSrc < cchLine && RT_C_IS_BLANK(pchLine[offSrc])) + offSrc++; + + /* + * Work up to the ',' separator. It shall likewise not be preceeded by any spaces. + * Need to take $(func 1,2,3) calls into account here, so we trac () and {} while + * skipping ahead. + */ + if (pchLine[offSrc] != ',') + { + size_t const offSrcStart = offSrc; + offSrc = scmKmkSkipExpString(pchLine, cchLine, offSrc, ','); + if (pchLine[offSrc] != ',') + return scmKmkGiveUp(pParser, "Expected ',' somewhere after '%.*s('", cchToken, &pchLine[offToken]); + + size_t cchCopy = offSrc - offSrcStart; + while (cchCopy > 0 && RT_C_IS_BLANK(pchLine[offSrcStart + cchCopy - 1])) + cchCopy--; + + pszDst = (char *)mempcpy(pszDst, &pchLine[offSrcStart], cchCopy); + } + /* 'if1of(, stuff)' does not make sense in committed code: */ + else if (enmToken == kKmkToken_if1of || enmToken == kKmkToken_ifn1of) + return scmKmkGiveUp(pParser, "Left set cannot be empty for '%.*s'", cchToken, &pchLine[offToken]); + offSrc++; + *pszDst++ = ','; + + /* + * For if1of and ifn1of we require a space after the comma, whereas ifeq and + * ifneq shall not have any blanks. This is to help tell them apart. + */ + if (enmToken == kKmkToken_if1of || enmToken == kKmkToken_ifn1of) + { + *pszDst++ = ' '; + if (pchLine[offSrc] == ' ') + offSrc++; + } + while (offSrc < cchLine && RT_C_IS_BLANK(pchLine[offSrc])) + offSrc++; + + if (pchLine[offSrc] != ')') + { + size_t const offSrcStart = offSrc; + offSrc = scmKmkSkipExpString(pchLine, cchLine, offSrc, ')'); + if (pchLine[offSrc] != ')') + return scmKmkGiveUp(pParser, "No closing parenthesis for '%.*s'?", cchToken, &pchLine[offToken]); + + size_t cchCopy = offSrc - offSrcStart; + while (cchCopy > 0 && RT_C_IS_BLANK(pchLine[offSrcStart + cchCopy - 1])) + cchCopy--; + + pszDst = (char *)mempcpy(pszDst, &pchLine[offSrcStart], cchCopy); + } + /* 'if1of(stuff, )' does not make sense in committed code: */ + else if ( (enmToken == kKmkToken_if1of || enmToken == kKmkToken_ifn1of) + && !scmKmkHasCommentMarker(pchLine, cchLine, offSrc, RT_STR_TUPLE("scm:ignore-empty-if1of-set"))) + return scmKmkGiveUp(pParser, "Right set cannot be empty for '%.*s'", cchToken, &pchLine[offToken]); + offSrc++; + *pszDst++ = ')'; + + /* + * Handle comment. + */ + if (offSrc < cchLine) + scmKmkTailComment(pParser, pchLine, cchLine, offSrc, &pszDst); + + /* + * Done. + */ + *pszDst = '\0'; + ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol); + return false; /* dummy */ +} + + +/** + * Deals with: if, ifdef and ifndef + * + * @returns dummy (false) to facility return + call. + */ +static bool scmKmkHandleIfSpace(KMKPARSER *pParser, size_t offToken, KMKTOKEN enmToken, size_t cchToken, bool fElse) +{ + const char *pchLine = pParser->pchLine; + size_t cchLine = pParser->cchLine; + uint32_t const cchIndent = pParser->iActualDepth + - (fElse && pParser->iActualDepth > 0 && !pParser->aDepth[pParser->iDepth - 1].fIgnoreNesting); + + /* + * Push it onto the stack. + * + * For ifndef we ignore the outmost ifndef in non-Makefile.kmk files, if + * the define matches the typical pattern for a file blocker. + */ + bool fIgnoredNesting = false; + if (!fElse) + { + if (!scmKmkPushNesting(pParser, enmToken)) + return false; + if (enmToken == kKmkToken_ifndef) + { + /** @todo */ + } + } + else + { + pParser->aDepth[pParser->iDepth - 1].enmToken = enmToken; + pParser->aDepth[pParser->iDepth - 1].iLine = ScmStreamTellLine(pParser->pIn); + } + + /* + * We do not allow line continuation for these. + */ + uint32_t cLines = 1; + size_t cchMaxLeadWord = 0; + size_t cchTotalLine = cchLine; + if (scmKmkIsLineWithContinuation(pchLine, cchLine)) + { + if (enmToken != kKmkToken_if) + return scmKmkGiveUp(pParser, "Line continuation not allowed with '%.*s' directive.", cchToken, &pchLine[offToken]); + cchTotalLine = scmKmkLineContinuationPeek(pParser, &cLines, &cchMaxLeadWord); + } + + /* + * We stage the modified line in the buffer, so check that the line isn't + * too long (plain if can be long, but not ifndef/ifdef). + */ + if (cchTotalLine + pParser->iActualDepth + 32 > sizeof(pParser->szBuf)) + return scmKmkGiveUp(pParser, "Line too long for a '%.*s' directive: %u chars", + cchToken, &pchLine[offToken], cchTotalLine); + char *pszDst = pParser->szBuf; + + /* + * Emit indent and initial token. + */ + memset(pszDst, ' ', cchIndent); + pszDst += cchIndent; + + if (fElse) + pszDst = (char *)mempcpy(pszDst, RT_STR_TUPLE("else ")); + + memcpy(pszDst, &pchLine[offToken], cchToken); + pszDst += cchToken; + + size_t offSrc = offToken + cchToken; + + /* + * ifndef/ifdef shall have exactly one space. For 'if' we allow up to 4, but + * we'll deal with that further down. + */ + size_t cchSpaces = 0; + while (offSrc < cchLine && RT_C_IS_BLANK(pchLine[offSrc])) + { + cchSpaces++; + offSrc++; + } + if (cchSpaces == 0) + return scmKmkGiveUp(pParser, "Nothing following '%.*s' or bogus line continuation?", cchToken, &pchLine[offToken]); + *pszDst++ = ' '; + + /* + * For ifdef and ifndef there now comes a single word. + */ + if (enmToken != kKmkToken_if) + { + size_t const offSrcStart = offSrc; + offSrc = scmKmkSkipExpString(pchLine, cchLine, offSrc, ' ', '\t'); /** @todo probably not entirely correct */ + if (offSrc == offSrcStart) + return scmKmkGiveUp(pParser, "No word following '%.*s'?", cchToken, &pchLine[offToken]); + + pszDst = (char *)mempcpy(pszDst, &pchLine[offSrcStart], offSrc - offSrcStart); + } + /* + * While for 'if' things are more complicated, especially if it spans more + * than one line. + */ + else if (cLines <= 1) + { + /* Single line expression: Just assume the expression goes up to the + EOL or comment hash. Strip and copy as-is for now. */ + const char *pchSrcHash = (const char *)memchr(&pchLine[offSrc], '#', cchLine - offSrc); + size_t cchExpr = pchSrcHash ? pchSrcHash - &pchLine[offSrc] : cchLine - offSrc; + while (cchExpr > 0 && RT_C_IS_BLANK(pchLine[offSrc + cchExpr - 1])) + cchExpr--; + + pszDst = (char *)mempcpy(pszDst, &pchLine[offSrc], cchExpr); + offSrc += cchExpr; + } + else + { + /* Multi line expression: We normalize leading whitespace using + cchMaxLeadWord for now. Expression on line 2+ are indented by two + extra characters, because we'd otherwise be puttin the operator on + the same level as the 'if', which would be confusing. Thus: + + if expr1 + + expr2 + endif + + if expr1 + || expr2 + endif + + if expr3 + vtg expr4 + endif + + We do '#' / EOL handling for the final line the same way as above. + + Later we should add the ability to rework the expression properly, + making sure new lines starts with operators and such. */ + /** @todo Implement simples expression parser and indenter, possibly also + * removing unnecessary parentheses. Can be shared with C/C++. */ + if (cchMaxLeadWord > 3) + return scmKmkGiveUp(pParser, + "Bogus multi-line 'if' expression! Extra lines must start with operator (cchMaxLeadWord=%u).", + cchMaxLeadWord); + memset(pszDst, ' ', cchMaxLeadWord); + pszDst += cchMaxLeadWord; + + size_t cchSrcContIndent = offToken + 2; + for (uint32_t iSubLine = 0; iSubLine < cLines - 1; iSubLine++) + { + /* Trim the line. */ + size_t offSrcEnd = cchLine; + Assert(pchLine[offSrcEnd - 1] == '\\'); + offSrcEnd--; + + if (pchLine[offSrcEnd - 1] == '\\') + return scmKmkGiveUp(pParser, "Escaped '\\' before line continuation in 'if' expression is not allowed!"); + + while (offSrcEnd > offSrc && RT_C_IS_BLANK(pchLine[offSrcEnd - 1])) + offSrcEnd--; + + /* Comments with line continuation is not allowed in commited makefiles. */ + if (offSrc < offSrcEnd && memchr(&pchLine[offSrc], '#', cchLine - offSrc) != NULL) + return scmKmkGiveUp(pParser, "Comment in multi-line 'if' expression is not allowed to start before the final line!"); + + /* Output it. */ + if (offSrc < offSrcEnd) + { + if (iSubLine > 0 && offSrc > cchSrcContIndent) + { + memset(pszDst, ' ', offSrc - cchSrcContIndent); + pszDst += offSrc - cchSrcContIndent; + } + pszDst = (char *)mempcpy(pszDst, &pchLine[offSrc], offSrcEnd - offSrc); + *pszDst++ = ' '; + } + else if (iSubLine == 0) + return scmKmkGiveUp(pParser, "Expected expression after 'if', not line continuation!"); + *pszDst++ = '\\'; + *pszDst = '\0'; + size_t cchDst = (size_t)(pszDst - pParser->szBuf); + ScmStreamPutLine(pParser->pOut, pParser->szBuf, cchDst, pParser->enmEol); + + /* + * Fetch the next line and start processing it. + */ + pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol); + if (!pchLine) + { + ScmError(pParser->pState, VERR_INTERNAL_ERROR_3, "ScmStreamGetLine unexpectedly returned NULL!"); + return false; + } + cchLine = pParser->cchLine; + + /* Skip leading whitespace and adjust the source continuation indent: */ + offSrc = 0; + while (offSrc < cchLine && RT_C_IS_SPACE(pchLine[offSrc])) + offSrc++; + /** @todo tabs */ + + if (iSubLine == 0) + cchSrcContIndent = offSrc; + + /* Initial indent: */ + pszDst = pParser->szBuf; + memset(pszDst, ' ', cchIndent + 2); + pszDst += cchIndent + 2; + } + + /* Output the expression on the final line. */ + const char *pchSrcHash = (const char *)memchr(&pchLine[offSrc], '#', cchLine - offSrc); + size_t cchExpr = pchSrcHash ? pchSrcHash - &pchLine[offSrc] : cchLine - offSrc; + while (cchExpr > 0 && RT_C_IS_BLANK(pchLine[offSrc + cchExpr - 1])) + cchExpr--; + + pszDst = (char *)mempcpy(pszDst, &pchLine[offSrc], cchExpr); + offSrc += cchExpr; + } + + + /* + * Handle comment. + * + * Here we check for the "scm:ignore-nesting" directive that makes us not + * add indentation for this directive. We do this on the destination buffer + * as that can be zero terminated and is therefore usable with strstr. + */ + if (offSrc >= cchLine) + *pszDst = '\0'; + else + { + char * const pszDstSrc = pszDst; + scmKmkTailComment(pParser, pchLine, cchLine, offSrc, &pszDst); + *pszDst = '\0'; + + /* Check for special comment making us ignore the nesting. We do this + on the destination buffer since it's zero terminated allowing normal + strstr use. */ + if (!fIgnoredNesting && strstr(pszDstSrc, "scm:ignore-nesting") != NULL) + { + pParser->aDepth[pParser->iDepth - 1].fIgnoreNesting = true; + pParser->iActualDepth--; + ScmVerbose(pParser->pState, 5, "%u: debug: ignoring nesting - actual depth: %u\n", + pParser->aDepth[pParser->iDepth - 1].iLine, pParser->iActualDepth); + } + } + + /* + * Done. + */ + ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol); + return false; /* dummy */ +} + + +/** + * Deals with: else + * + * @returns dummy (false) to facility return + call. + */ +static bool scmKmkHandleElse(KMKPARSER *pParser, size_t offToken) +{ + const char * const pchLine = pParser->pchLine; + size_t const cchLine = pParser->cchLine; + + if (pParser->iDepth < 1) + return scmKmkGiveUp(pParser, "Lone 'else'"); + uint32_t const cchIndent = pParser->iActualDepth + - (pParser->iActualDepth > 0 && !pParser->aDepth[pParser->iDepth - 1].fIgnoreNesting); + + /* + * Look past the else and check if there any ifxxx token following it. + */ + size_t offSrc = offToken + 4; + while (offSrc < cchLine && RT_C_IS_BLANK(pchLine[offSrc])) + offSrc++; + if (offSrc < cchLine) + { + size_t cchWord = 0; + while (offSrc + cchWord < cchLine && RT_C_IS_ALNUM(pchLine[offSrc + cchWord])) + cchWord++; + if (cchWord) + { + KMKTOKEN enmToken = scmKmkIdentifyToken(&pchLine[offSrc], cchWord); + switch (enmToken) + { + case kKmkToken_ifeq: + case kKmkToken_ifneq: + case kKmkToken_if1of: + case kKmkToken_ifn1of: + return scmKmkHandleIfParentheses(pParser, offSrc, enmToken, cchWord, true /*fElse*/); + + case kKmkToken_ifdef: + case kKmkToken_ifndef: + case kKmkToken_if: + return scmKmkHandleIfSpace(pParser, offSrc, enmToken, cchWord, true /*fElse*/); + + default: + break; + } + } + } + + /* + * We do not allow line continuation for these. + */ + if (scmKmkIsLineWithContinuation(pchLine, cchLine)) + return scmKmkGiveUp(pParser, "Line continuation not allowed with 'else' directive."); + + /* + * We stage the modified line in the buffer, so check that the line isn't + * too long (it seriously should be). + */ + if (cchLine + cchIndent + 32 > sizeof(pParser->szBuf)) + return scmKmkGiveUp(pParser, "Line too long for a 'else' directive: %u chars", cchLine); + char *pszDst = pParser->szBuf; + + /* + * Emit indent and initial token. + */ + memset(pszDst, ' ', cchIndent); + pszDst = (char *)mempcpy(&pszDst[cchIndent], RT_STR_TUPLE("else")); + + offSrc = offToken + 4; + + /* + * Handle comment. + */ + if (offSrc < cchLine) + scmKmkTailComment(pParser, pchLine, cchLine, offSrc, &pszDst); + + /* + * Done. + */ + *pszDst = '\0'; + ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol); + return false; /* dummy */ +} + + +/** + * Deals with: endif + * + * @returns dummy (false) to facility return + call. + */ +static bool scmKmkHandleEndif(KMKPARSER *pParser, size_t offToken) +{ + const char * const pchLine = pParser->pchLine; + size_t const cchLine = pParser->cchLine; + + /* + * Pop a nesting. + */ + if (pParser->iDepth < 1) + return scmKmkGiveUp(pParser, "Lone 'endif'"); + uint32_t iDepth = pParser->iDepth - 1; + pParser->iDepth = iDepth; + if (!pParser->aDepth[iDepth].fIgnoreNesting) + { + AssertStmt(pParser->iActualDepth > 0, pParser->iActualDepth++); + pParser->iActualDepth -= 1; + } + ScmVerbose(pParser->pState, 5, "%u: debug: unnesting %u/%u (endif)\n", + ScmStreamTellLine(pParser->pIn), iDepth, pParser->iActualDepth); + uint32_t const cchIndent = pParser->iActualDepth; + + /* + * We do not allow line continuation for these. + */ + if (scmKmkIsLineWithContinuation(pchLine, cchLine)) + return scmKmkGiveUp(pParser, "Line continuation not allowed with 'endif' directive."); + + /* + * We stage the modified line in the buffer, so check that the line isn't + * too long (it seriously should be). + */ + if (cchLine + cchIndent + 32 > sizeof(pParser->szBuf)) + return scmKmkGiveUp(pParser, "Line too long for a 'else' directive: %u chars", cchLine); + char *pszDst = pParser->szBuf; + + /* + * Emit indent and initial token. + */ + memset(pszDst, ' ', cchIndent); + pszDst = (char *)mempcpy(&pszDst[cchIndent], RT_STR_TUPLE("endif")); + + size_t offSrc = offToken + 5; + + /* + * Handle comment. + */ + if (offSrc < cchLine) + scmKmkTailComment(pParser, pchLine, cchLine, offSrc, &pszDst); + + /* + * Done. + */ + *pszDst = '\0'; + ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol); + return false; /* dummy */ +} + + +/** + * Passing thru any line continuation lines following the current one. + */ +static bool scmKmkPassThruLineContinuationLines(KMKPARSER *pParser) +{ + while (scmKmkIsLineWithContinuation(pParser->pchLine, pParser->cchLine)) + { + pParser->pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol); + if (!pParser->pchLine) + break; + ScmStreamPutLine(pParser->pOut, pParser->pchLine, pParser->cchLine, pParser->enmEol); + } + return false; /* dummy */ +} + + +/** + * For dealing with a directive w/o special formatting rules (yet). + * + * @returns dummy (false) to facility return + call. + */ +static bool scmKmkHandleSimple(KMKPARSER *pParser, size_t offToken, bool fIndentIt = true) +{ + const char *pchLine = pParser->pchLine; + size_t cchLine = pParser->cchLine; + uint32_t const cchIndent = fIndentIt ? pParser->iActualDepth : 0; + + /* + * Just reindent the statement. + */ + ScmStreamWrite(pParser->pOut, g_szSpaces, cchIndent); + ScmStreamWrite(pParser->pOut, &pchLine[offToken], cchLine - offToken); + ScmStreamPutEol(pParser->pOut, pParser->enmEol); + + /* + * Check for line continuation and output concatenated lines. + */ + scmKmkPassThruLineContinuationLines(pParser); + return false; /* dummy */ +} + + +static bool scmKmkHandleDefine(KMKPARSER *pParser, size_t offToken) +{ + scmKmkHandleSimple(pParser, offToken); + + /* Hack Alert! Start out parsing the define in recipe mode. + + Technically, we shouldn't evaluate the content of a define till it's + used. However, we ASSUME they are either makefile code snippets or + recipe templates. */ + scmKmkPushNesting(pParser, kKmkToken_define); + scmKmkSetInRecipe(pParser, true); + return false; +} + + +static bool scmKmkHandleEndef(KMKPARSER *pParser, size_t offToken) +{ + /* Leaving a define resets the recipt mode. */ + scmKmkSetInRecipe(pParser, false); + + /* + * Pop a nesting. + */ + if (pParser->iDepth < 1) + return scmKmkGiveUp(pParser, "Lone 'endef'"); + uint32_t iDepth = pParser->iDepth - 1; + if (pParser->aDepth[iDepth].enmToken != kKmkToken_define) + return scmKmkGiveUp(pParser, "Unpexected 'endef', expected 'endif' for line %u", pParser->aDepth[iDepth].iLine); + pParser->iDepth = iDepth; + if (!pParser->aDepth[iDepth].fIgnoreNesting) + { + AssertStmt(pParser->iActualDepth > 0, pParser->iActualDepth++); + pParser->iActualDepth -= 1; + } + ScmVerbose(pParser->pState, 5, "%u: debug: unnesting %u/%u (endef)\n", + ScmStreamTellLine(pParser->pIn), iDepth, pParser->iActualDepth); + + return scmKmkHandleSimple(pParser, offToken); +} + + +/** + * Checks for escaped trailing slashes on a line, giving up and asking the + * developer to fix those manually. + * + * @returns true if we gave up. false if no escaped slashed and we didn't. + */ +static bool scmKmkGiveUpIfTrailingEscapedSlashed(KMKPARSER *pParser, const char *pchLine, size_t cchLine) +{ + if (cchLine > 2 && pchLine[cchLine - 2] == '\\' && pchLine[cchLine - 1] == '\\') + { + scmKmkGiveUp(pParser, "Escaped slashes at end of line not allowed. Insert space before line continuation slash!"); + return true; + } + return false; +} + +/** + * @returns dummy (false) to facility return + call. + */ +static bool scmKmkHandleAssignment2(KMKPARSER *pParser, size_t offVarStart, size_t offVarEnd, KMKASSIGNTYPE enmType, + size_t offAssignOp, unsigned fFlags) +{ + unsigned const cchIndent = pParser->iActualDepth; + const char *pchLine = pParser->pchLine; + size_t cchLine = pParser->cchLine; + uint32_t const cLines = pParser->cLines; + uint32_t iSubLine = 0; + + RT_NOREF(fFlags); + Assert(offVarStart < cchLine); + Assert(offVarEnd <= cchLine); + Assert(offVarStart < offVarEnd); + Assert(!RT_C_IS_SPACE(pchLine[offVarStart])); + Assert(!RT_C_IS_SPACE(pchLine[offVarEnd - 1])); + + /* Assignments takes us out of recipe mode. */ + ScmVerbose(pParser->pState, 6, "%u: debug: assignment\n", ScmStreamTellLine(pParser->pIn)); + scmKmkSetInRecipe(pParser, false); + + /* This is too much hazzle to deal with. */ + if (cLines > 1 && scmKmkGiveUpIfTrailingEscapedSlashed(pParser, pchLine, cchLine)) + return false; + if (cchLine + 64 > sizeof(pParser->szBuf)) + return scmKmkGiveUp(pParser, "Line too long!"); + + /* + * Indent and output the variable name. + */ + char *pszDst = pParser->szBuf; + memset(pszDst, ' ', cchIndent); + pszDst += cchIndent; + pszDst = (char *)mempcpy(pszDst, &pchLine[offVarStart], offVarEnd - offVarStart); + + /* + * Try preserve the assignment operator position, but make sure we've got a + * space in front of it. + */ + if (offAssignOp < cchLine) + { + size_t offDst = (size_t)(pszDst - pParser->szBuf); + size_t offEffAssignOp = ScmCalcSpacesForSrcSpan(pchLine, 0, offAssignOp, pParser->pSettings); + if (offDst < offEffAssignOp) + { + size_t cchSpacesToWrite = offEffAssignOp - offDst; + memset(pszDst, ' ', cchSpacesToWrite); + pszDst += cchSpacesToWrite; + } + else + *pszDst++ = ' '; + } + else + { + /* Pull up the assignment operator to the variable line. */ + *pszDst++ = ' '; + + /* Eat up lines till we hit the operator. */ + while (offAssignOp < cchLine) + { + const char * const pchPrevLine = pchLine; + Assert(iSubLine + 1 < cLines); + pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol); + AssertReturn(pchLine, false /*dummy*/); + cchLine = pParser->cchLine; + iSubLine++; + if (iSubLine + 1 < cLines && scmKmkGiveUpIfTrailingEscapedSlashed(pParser, pchLine, cchLine)) + return false; + + /* Adjust offAssignOp: */ + offAssignOp -= (uintptr_t)pchLine - (uintptr_t)pchPrevLine; + Assert(offAssignOp < ~(size_t)0 / 2); + } + + if ((size_t)(pszDst - pParser->szBuf) > sizeof(pParser->szBuf)) + return scmKmkGiveUp(pParser, "Line too long!"); + } + + /* + * Emit the operator. + */ + size_t offLine = offAssignOp; + switch (enmType) + { + default: + AssertReleaseFailed(); + RT_FALL_THRU(); + case kKmkAssignType_Recursive: + *pszDst++ = '='; + Assert(pchLine[offLine] == '='); + offLine++; + break; + case kKmkAssignType_Conditional: + *pszDst++ = '?'; + *pszDst++ = '='; + Assert(pchLine[offLine] == '?'); Assert(pchLine[offLine + 1] == '='); + offLine += 2; + break; + case kKmkAssignType_Appending: + *pszDst++ = '+'; + *pszDst++ = '='; + Assert(pchLine[offLine] == '+'); Assert(pchLine[offLine + 1] == '='); + offLine += 2; + break; + case kKmkAssignType_Prepending: + *pszDst++ = '<'; + *pszDst++ = '='; + Assert(pchLine[offLine] == '<'); Assert(pchLine[offLine + 1] == '='); + offLine += 2; + break; + case kKmkAssignType_Immediate: + *pszDst++ = ':'; + Assert(pchLine[offLine] == ':'); + offLine++; + RT_FALL_THRU(); + case kKmkAssignType_Simple: + *pszDst++ = ':'; + *pszDst++ = '='; + Assert(pchLine[offLine] == ':'); Assert(pchLine[offLine + 1] == '='); + offLine += 2; + break; + } + + /* + * Skip space till we hit the value or comment. + */ + while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine])) + offLine++; + +/** @todo this block can probably be merged into the final loop below. */ + unsigned cPendingEols = 0; + while (iSubLine + 1 < cLines && offLine + 1 == cchLine && pchLine[offLine] == '\\') + { + pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol); + AssertReturn(pchLine, false /*dummy*/); + cchLine = pParser->cchLine; + iSubLine++; + if (iSubLine + 1 < cLines && pchLine[cchLine - 2] == '\\') + { + *pszDst++ = ' '; + *pszDst++ = '\\'; + *pszDst = '\0'; + ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol); + return scmKmkGiveUp(pParser, "Escaped slashes at end of line not allowed. Insert space before line continuation slash!"); + } + cPendingEols = 1; + + /* Skip indent/whitespace. */ + offLine = 0; + while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine])) + offLine++; + } + + /* + * Okay, we've gotten to the value / comment part. + */ + for (;;) + { + /* + * The end? Flush what we've got. + */ + if (offLine == cchLine) + { + Assert(iSubLine + 1 == cLines); + *pszDst = '\0'; + ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol); + if (cPendingEols > 0) + ScmStreamPutEol(pParser->pOut, pParser->enmEol); + return false; /* dummy */ + } + + /* + * Output any non-comment stuff, stripping off newlines. + */ + const char *pchHash = (const char *)memchr(&pchLine[offLine], '#', cchLine - offLine); + if (pchHash != &pchLine[offLine]) + { + /* Add space or flush pending EOLs. */ + if (!cPendingEols) + *pszDst++ = ' '; + else + { + unsigned iEol = 0; + cPendingEols = RT_MIN(2, cPendingEols); /* reduce to two, i.e. only one empty separator line */ + do + { + if (iEol++ == 0) /* skip this for the 2nd empty line. */ + *pszDst++ = ' '; + *pszDst++ = '\\'; + *pszDst = '\0'; + ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol); + + pszDst = pParser->szBuf; + memset(pszDst, ' ', cchIndent); + pszDst += cchIndent; + *pszDst++ = '\t'; + cPendingEols--; + } while (cPendingEols > 0); + } + + /* Strip backwards. */ + size_t const offValueEnd2 = pchHash ? (size_t)(pchHash - pchLine) : cchLine - (iSubLine + 1 < cLines); + size_t offValueEnd = offValueEnd2; + while (offValueEnd > offLine && RT_C_IS_BLANK(pchLine[offValueEnd - 1])) + offValueEnd--; + Assert(offValueEnd > offLine); + + /* Append the value part we found. */ + pszDst = (char *)mempcpy(pszDst, &pchLine[offLine], offValueEnd - offLine); + offLine = offValueEnd2; + } + + /* + * If we found a comment hash, emit it and whatever follows just as-is w/o + * any particular reformatting. Comments within a variable definition are + * usually to disable portitions of a property like _DEFS or _SOURCES. + */ + if (pchHash != NULL) + { + if (cPendingEols == 0) + scmKmkTailComment(pParser, pchLine, cchLine, offLine, &pszDst); + size_t const cchDst = (size_t)(pszDst - pParser->szBuf); + *pszDst = '\0'; + ScmStreamPutLine(pParser->pOut, pParser->szBuf, cchDst, pParser->enmEol); + + if (cPendingEols > 1) + ScmStreamPutEol(pParser->pOut, pParser->enmEol); + + if (cPendingEols > 0) + ScmStreamPutLine(pParser->pOut, pchLine, cchLine, pParser->enmEol); + scmKmkPassThruLineContinuationLines(pParser); + return false; /* dummy */ + } + + /* + * Fetch another line, if we've got one. + */ + if (iSubLine + 1 >= cLines) + Assert(offLine == cchLine); + else + { + Assert(offLine + 1 == cchLine); + while (iSubLine + 1 < cLines && offLine + 1 == cchLine && pchLine[offLine] == '\\') + { + pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol); + AssertReturn(pchLine, false /*dummy*/); + cchLine = pParser->cchLine; + iSubLine++; + if (iSubLine + 1 < cLines && pchLine[cchLine - 2] == '\\') + { + *pszDst++ = ' '; + *pszDst++ = '\\'; + *pszDst = '\0'; + ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol); + if (cPendingEols > 1) + ScmError(pParser->pState, VERR_NOT_SUPPORTED, "oops #1: Manually fix the next issue after reverting edits!"); + return scmKmkGiveUp(pParser, "Escaped slashes at end of line not allowed. Insert space before line continuation slash!"); + } + cPendingEols++; + + /* Deal with indent/whitespace. */ + offLine = 0; + while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine])) + offLine++; + } + } + } +} + + +/** + * A rule. + * + * This is a bit involved. Sigh. + * + * @returns dummy (false) to facility return + call. + */ +static bool scmKmkHandleRule(KMKPARSER *pParser, size_t offFirstWord, bool fDoubleColon, size_t offColon) +{ + SCMSTREAM *pOut = pParser->pOut; + unsigned const cchIndent = pParser->iActualDepth; + const char *pchLine = pParser->pchLine; + size_t cchLine = pParser->cchLine; + Assert(offFirstWord < cchLine); + uint32_t const cLines = pParser->cLines; + uint32_t iSubLine = 0; + + /* Following this, we'll be in recipe-mode. */ + ScmVerbose(pParser->pState, 4, "%u: debug: start rule\n", ScmStreamTellLine(pParser->pIn)); + scmKmkSetInRecipe(pParser, true); + + /* This is too much hazzle to deal with. */ + if (cLines > 0 && scmKmkGiveUpIfTrailingEscapedSlashed(pParser, pchLine, cchLine)) + return false; + + /* Too special case. */ + if (offColon <= offFirstWord) + return scmKmkGiveUp(pParser, "Missing target file before colon!"); + + /* + * Indent it. + */ + ScmStreamWrite(pOut, g_szSpaces, cchIndent); + size_t offLine = offFirstWord; + + /* + * Process word by word past the colon, taking new lines into account. + */ + KMKWORDSTATE WordState = { 0, 0 }; + KMKWORDCTX enmCtx = kKmkWordCtx_TargetFileOrAssignment; + unsigned cPendingEols = 0; + for (;;) + { + /* + * Output the next word. + */ + size_t cchWord = scmKmkWordLength(pchLine, cchLine, offLine, enmCtx, &WordState); + Assert(offLine + cchWord <= offColon); + ScmStreamWrite(pOut, &pchLine[offLine], cchWord); + offLine += cchWord; + + /* Skip whitespace (if any). */ + while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine])) + offLine++; + + /* Have we reached the colon already? */ + if (offLine >= offColon) + { + Assert(pchLine[offLine] == ':'); + Assert(!fDoubleColon || pchLine[offLine + 1] == ':'); + offLine += fDoubleColon ? 2 : 1; + + ScmStreamPutCh(pOut, ':'); + if (fDoubleColon) + ScmStreamPutCh(pOut, ':'); + break; + } + + /* Deal with new line and emit indentation. */ + if (offLine + 1 == cchLine && pchLine[offLine] == '\\') + { + /* Get the next input line. */ + for (;;) + { + const char * const pchPrevLine = pchLine; + Assert(iSubLine + 1 < cLines); + pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol); + AssertReturn(pchLine, false /*dummy*/); + cchLine = pParser->cchLine; + iSubLine++; + if (iSubLine + 1 < cLines && scmKmkGiveUpIfTrailingEscapedSlashed(pParser, pchLine, cchLine)) + return false; + + /* Adjust offColon: */ + offColon -= (uintptr_t)pchLine - (uintptr_t)pchPrevLine; + Assert(offColon < ~(size_t)0 / 2); + + /* Skip leading spaces. */ + offLine = 0; + while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine])) + offLine++; + + /* Just drop empty lines. */ + if (offLine + 1 == cchLine && pchLine[offLine] == '\\') + continue; + + /* Complete the current line and emit indent, unless we reached the colon: */ + if (offLine >= offColon) + { + Assert(pchLine[offLine] == ':'); + Assert(!fDoubleColon || pchLine[offLine + 1] == ':'); + offLine += fDoubleColon ? 2 : 1; + + ScmStreamPutCh(pOut, ':'); + if (fDoubleColon) + ScmStreamPutCh(pOut, ':'); + + cPendingEols = 1; + } + else + { + ScmStreamWrite(pOut, RT_STR_TUPLE(" \\")); + ScmStreamPutEol(pOut, pParser->enmEol); + ScmStreamWrite(pOut, g_szSpaces, cchIndent); + if (WordState.uDepth > 0) + ScmStreamWrite(pOut, g_szTabs, RT_MIN(WordState.uDepth, sizeof(g_szTabs) - 1)); + } + break; + } + if (offLine >= offColon) + break; + } + else + ScmStreamPutCh(pOut, ' '); + enmCtx = kKmkWordCtx_TargetFile; + } + + /* + * We're immediately past the colon now, so eat whitespace and newlines and + * whatever till we get to a solid word or the end of the line. + */ + /* Skip spaces - there should be exactly one. */ + while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine])) + offLine++; + + /* Deal with new lines: */ + while (offLine + 1 == cchLine && pchLine[offLine] == '\\') + { + cPendingEols = 1; + + Assert(iSubLine + 1 < cLines); + pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol); + AssertReturn(pchLine, false /*dummy*/); + cchLine = pParser->cchLine; + iSubLine++; + if (iSubLine + 1 < cLines && scmKmkGiveUpIfTrailingEscapedSlashed(pParser, pchLine, cchLine)) + return false; + + /* Skip leading spaces. */ + offLine = 0; + while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine])) + offLine++; + + /* Just drop empty lines. */ + if (offLine + 1 == cchLine && pchLine[offLine] == '\\') + continue; + } + + /* + * Special case: No dependencies. + */ + if (offLine == cchLine && iSubLine + 1 >= cLines) + { + ScmStreamPutEol(pOut, pParser->enmEol); + return false /*dummy*/; + } + + /* + * Work the dependencies word for word. Indent in spaces + two tabs. + * (Pattern rules will also end up here, but we'll just ignore that for now.) + */ + enmCtx = kKmkWordCtx_DepFileOrAssignment; + for (;;) + { + /* Indent the next word. */ + if (cPendingEols == 0) + ScmStreamPutCh(pOut, ' '); + else + { + ScmStreamWrite(pOut, RT_STR_TUPLE(" \\")); + ScmStreamPutEol(pOut, pParser->enmEol); + ScmStreamWrite(pOut, g_szSpaces, cchIndent); + ScmStreamWrite(pOut, RT_STR_TUPLE("\t\t")); + if (cPendingEols > 1) + { + ScmStreamWrite(pOut, RT_STR_TUPLE("\\")); + ScmStreamPutEol(pOut, pParser->enmEol); + ScmStreamWrite(pOut, g_szSpaces, cchIndent); + ScmStreamWrite(pOut, RT_STR_TUPLE("\t\t")); + } + cPendingEols = 0; + } + if (WordState.uDepth > 0) + ScmStreamWrite(pOut, g_szTabs, RT_MIN(WordState.uDepth, sizeof(g_szTabs) - 1)); + + /* Get the next word and output it. */ + size_t cchWord = scmKmkWordLength(pchLine, cchLine, offLine, enmCtx, &WordState); + Assert(offLine + cchWord <= cchLine); + + ScmStreamWrite(pOut, &pchLine[offLine], cchWord); + offLine += cchWord; + + /* Skip whitespace (if any). */ + while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine])) + offLine++; + + /* Deal with new line and emit indentation. */ + if (iSubLine + 1 < cLines && offLine + 1 == cchLine && pchLine[offLine] == '\\') + { + /* Get the next input line. */ + for (;;) + { + Assert(iSubLine + 1 < cLines); + pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol); + AssertReturn(pchLine, false /*dummy*/); + cchLine = pParser->cchLine; + iSubLine++; + if (iSubLine + 1 < cLines && scmKmkGiveUpIfTrailingEscapedSlashed(pParser, pchLine, cchLine)) + return false; + + /* Skip leading spaces. */ + offLine = 0; + while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine])) + offLine++; + + /* Just drop empty lines, we'll re-add one of them afterward if we find more dependencies. */ + cPendingEols++; + if (offLine + 1 == cchLine && pchLine[offLine] == '\\') + continue; + break; + } + } + + if (offLine >= cchLine) + { + /* End of input. */ +/** @todo deal with comments */ + Assert(iSubLine + 1 == cLines); + ScmStreamPutEol(pOut, pParser->enmEol); + return false; /* dummmy */ + } + enmCtx = kKmkWordCtx_DepFile; + } +} + + +/** + * Checks if the (extended) line is a variable assignment. + * + * We scan past line continuation stuff here as the assignment operator could be + * on the next line, even if that's very unlikely it is recommened by the coding + * guide lines if the line needs to be split. Fortunately, though, the caller + * already removes empty empty leading lines, so we only have to consider the + * line continuation issue if no '=' was found on the first line. + * + * @returns Modified or not. + * @param pParser The parser. + * @param cLines Number of lines to consider. + * @param cchTotalLine Total length of all the lines to consider. + * @param offWord Where the first word of the line starts. + * @param pfIsAssignment Where to return whether this is an assignment or + * not. + */ +static bool scmKmkHandleAssignmentOrRule(KMKPARSER *pParser, size_t offWord) +{ + const char *pchLine = pParser->pchLine; + size_t const cchTotalLine = pParser->cchTotalLine; + + /* + * Scan words till we find ':' or '='. + */ + uint32_t iWord = 0; + size_t offCurWord = offWord; + size_t offEndPrev = 0; + size_t offLine = offWord; + while (offLine < cchTotalLine) + { + char ch = pchLine[offLine++]; + if (ch == '$') + { + /* + * Skip variable expansion. + */ + char const chOpen = pchLine[offLine++]; + if (chOpen == '(' || chOpen == '{') + { + char const chClose = chOpen == '(' ? ')' : '}'; + unsigned cDepth = 1; + while (offLine < cchTotalLine) + { + ch = pchLine[offLine++]; + if (ch == chOpen) + cDepth++; + else if (ch == chClose) + if (!--cDepth) + break; + } + } + /* else: $x or $$, so just skip the next character. */ + } + else if (RT_C_IS_SPACE(ch)) + { + /* + * End of word. Skip whitespace till the next word starts. + */ + offEndPrev = offLine - 1; + Assert(offLine != offWord); + while (offLine < cchTotalLine) + { + ch = pchLine[offLine]; + if (RT_C_IS_SPACE(ch)) + offLine++; + else if (ch == '\\' && (pchLine[offLine] == '\r' || pchLine[offLine] == '\n')) + offLine += 2; + else + break; + } + offCurWord = offLine; + iWord++; + + /* + * To simplify the assignment operator checks, we just check the + * start of the 2nd word when we're here. + */ + if (iWord == 1 && offLine < cchTotalLine) + { + ch = pchLine[offLine]; + if (ch == '=') + return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Recursive, offLine, 0); + if (offLine + 1 < cchTotalLine && pchLine[offLine + 1] == '=') + { + if (ch == ':') + return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Simple, offLine, 0); + if (ch == '+') + return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Appending, offLine, 0); + if (ch == '<') + return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Prepending, offLine, 0); + if (ch == '?') + return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Conditional, offLine, 0); + } + else if ( ch == ':' + && pchLine[offLine + 1] == ':' + && pchLine[offLine + 2] == '=') + return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Immediate, offLine, 0); + + /* Check for rule while we're here. */ + if (ch == ':') + return scmKmkHandleRule(pParser, offWord, pchLine[offLine + 1] == ':', offLine); + } + } + /* + * If '=' is found in the first word it's an assignment. + */ + else if (ch == '=') + { + if (iWord == 0) + { + KMKASSIGNTYPE enmType = kKmkAssignType_Recursive; + ch = pchLine[offLine - 2]; + if (ch == '+') + enmType = kKmkAssignType_Appending; + else if (ch == '?') + enmType = kKmkAssignType_Conditional; + else if (ch == '<') + enmType = kKmkAssignType_Prepending; + else + { + Assert(ch != ':'); + return scmKmkHandleAssignment2(pParser, offWord, offLine - 1, enmType, offLine - 1, 0); + } + return scmKmkHandleAssignment2(pParser, offWord, offLine - 2, enmType, offLine - 2, 0); + } + } + /* + * When ':' is found it can mean a drive letter, a rule or in the + * first word a simple or immediate assignment. + */ + else if (ch == ':') + { + /* Check for drive letters (we ignore the archive form): */ + if (offLine - offWord == 2 && RT_C_IS_ALPHA(pchLine[offLine - 2])) + { /* ignore */ } + else + { + /* Simple or immediate assignment? */ + ch = pchLine[offLine]; + if (iWord == 0) + { + if (ch == '=') + return scmKmkHandleAssignment2(pParser, offWord, offLine - 1, kKmkAssignType_Simple, offLine - 1, 0); + if (ch == ':' && pchLine[offLine + 1] == '=') + return scmKmkHandleAssignment2(pParser, offWord, offLine - 1, kKmkAssignType_Immediate, offLine - 1, 0); + } + + /* Okay, it's a rule then. */ + return scmKmkHandleRule(pParser, offWord, ch == ':', offLine - 1); + } + } + } + + /* + * Check if this is a $(error ) or similar function call line. + * + * If we're inside a 'define' we treat $$ as $ as it's probably a case of + * double expansion (e.g. def_vmm_lib_dtrace_preprocess in VMM/Makefile.kmk). + */ + if (pchLine[offWord] == '$') + { + size_t const cDollars = pchLine[offWord + 1] != '$' || !scmKmkIsInsideDefine(pParser) ? 1 : 2; + if ( pchLine[offWord + cDollars] == '(' + || pchLine[offWord + cDollars] == '{') + { + size_t const cchLine = pParser->cchLine; + size_t offEnd = offWord + cDollars + 1; + char ch = '\0'; + while (offEnd < cchLine && (RT_C_IS_LOWER(ch = pchLine[offEnd]) || RT_C_IS_DIGIT(ch) || ch == '-')) + offEnd++; + if (offEnd >= cchLine || RT_C_IS_SPACE(ch) || (offEnd == cchLine - 1 && ch == '\\')) + { + static const RTSTRTUPLE s_aAllowedFunctions[] = + { + { RT_STR_TUPLE("info") }, + { RT_STR_TUPLE("error") }, + { RT_STR_TUPLE("warning") }, + { RT_STR_TUPLE("set-umask") }, + { RT_STR_TUPLE("foreach") }, + { RT_STR_TUPLE("call") }, + { RT_STR_TUPLE("eval") }, + { RT_STR_TUPLE("evalctx") }, + { RT_STR_TUPLE("evalval") }, + { RT_STR_TUPLE("evalvalctx") }, + { RT_STR_TUPLE("evalcall") }, + { RT_STR_TUPLE("evalcall2") }, + { RT_STR_TUPLE("eval-opt-var") }, + { RT_STR_TUPLE("kb-src-one") }, + }; + size_t cchFunc = offEnd - offWord - cDollars - 1; + for (size_t i = 0; i < RT_ELEMENTS(s_aAllowedFunctions); i++) + if ( cchFunc == s_aAllowedFunctions[i].cch + && memcmp(&pchLine[offWord + cDollars + 1], s_aAllowedFunctions[i].psz, cchFunc) == 0) + return scmKmkHandleSimple(pParser, offWord); + } + } + } + + /* + * If we didn't find anything, output it as-as. + * We use scmKmkHandleSimple in a special way to do this. + */ + if (!RTStrStartsWith(pchLine, "$(TOOL_")) /* ValKit/Config.kmk */ + ScmVerbose(pParser->pState, 1, "%u: debug: Unable to make sense of this line!\n", ScmStreamTellLine(pParser->pIn)); + return scmKmkHandleSimple(pParser, 0 /*offToken*/, false /*fIndentIt*/); +} + + +static bool scmKmkHandleAssignKeyword(KMKPARSER *pParser, size_t offToken, KMKTOKEN enmToken, size_t cchWord, + bool fMustBeAssignment) +{ + /* Assignments takes us out of recipe mode. */ + scmKmkSetInRecipe(pParser, false); + + RT_NOREF(pParser, offToken, enmToken, cchWord, fMustBeAssignment); + return scmKmkHandleSimple(pParser, offToken); +} + + +/** + * Rewrite a kBuild makefile. + * + * @returns kScmMaybeModified or kScmUnmodified. + * @param pIn The input stream. + * @param pOut The output stream. + * @param pSettings The settings. + * + * @todo + * + * Ideas for Makefile.kmk and Config.kmk: + * - sort if1of/ifn1of sets. + * - line continuation slashes should only be preceded by one space. + */ +SCMREWRITERRES rewrite_Makefile_kmk(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings) +{ + if (!pSettings->fStandarizeKmk) + return kScmUnmodified; + + /* + * Parser state. + */ + KMKPARSER Parser; + Parser.iDepth = 0; + Parser.iActualDepth = 0; + Parser.fInRecipe = false; + Parser.pState = pState; + Parser.pIn = pIn; + Parser.pOut = pOut; + Parser.pSettings = pSettings; + + /* + * Iterate the file. + */ + const char *pchLine; + while ((Parser.pchLine = pchLine = ScmStreamGetLine(pIn, &Parser.cchLine, &Parser.enmEol)) != NULL) + { + size_t cchLine = Parser.cchLine; + + /* + * If we're in the command part of a recipe, anything starting with a + * tab is considered another command for the recipe. + */ + if (Parser.fInRecipe && *pchLine == '\t') + { + /* Do we do anything here? */ + } + else + { + /* + * Skip leading whitespace and check for directives (simplified). + * + * This is simplified in the sense that GNU make first checks for variable + * assignments, so that directive can be used as variable names. We don't + * want that, so we do the variable assignment check later. + */ + size_t offLine = 0; + while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine])) + offLine++; + + /* Find end of word (if any) - only looking for keywords here: */ + size_t cchWord = 0; + while ( offLine + cchWord < cchLine + && ( RT_C_IS_ALNUM(pchLine[offLine + cchWord]) + || pchLine[offLine + cchWord] == '-')) + cchWord++; + if (cchWord > 0) + { + /* If the line is just a line continuation slash, simply remove it + (this also makes the parsing a lot easier). */ + if (cchWord == 1 && offLine == cchLine - 1 && pchLine[cchLine] == '\\') + continue; + + /* Unlike the GNU make parser, we won't recognize 'if' or any other + directives as variable names, so we can */ + KMKTOKEN enmToken = scmKmkIdentifyToken(&pchLine[offLine], cchWord); + switch (enmToken) + { + case kKmkToken_ifeq: + case kKmkToken_ifneq: + case kKmkToken_if1of: + case kKmkToken_ifn1of: + scmKmkHandleIfParentheses(&Parser, offLine, enmToken, cchWord, false /*fElse*/); + continue; + + case kKmkToken_ifdef: + case kKmkToken_ifndef: + case kKmkToken_if: + scmKmkHandleIfSpace(&Parser, offLine, enmToken, cchWord, false /*fElse*/); + continue; + + case kKmkToken_else: + scmKmkHandleElse(&Parser, offLine); + continue; + + case kKmkToken_endif: + scmKmkHandleEndif(&Parser, offLine); + continue; + + /* Includes: */ + case kKmkToken_include: + case kKmkToken_sinclude: + case kKmkToken_dash_include: + case kKmkToken_includedep: + case kKmkToken_includedep_queue: + case kKmkToken_includedep_flush: + scmKmkHandleSimple(&Parser, offLine); + continue; + + /* Others: */ + case kKmkToken_define: + scmKmkHandleDefine(&Parser, offLine); + continue; + case kKmkToken_endef: + scmKmkHandleEndef(&Parser, offLine); + continue; + + case kKmkToken_override: + case kKmkToken_local: + scmKmkHandleAssignKeyword(&Parser, offLine, enmToken, cchWord, true /*fMustBeAssignment*/); + continue; + + case kKmkToken_export: + scmKmkHandleAssignKeyword(&Parser, offLine, enmToken, cchWord, false /*fMustBeAssignment*/); + continue; + + case kKmkToken_unexport: + case kKmkToken_undefine: + scmKmkHandleSimple(&Parser, offLine); + continue; + + case kKmkToken_Comment: + AssertFailed(); /* not possible */ + break; + + /* + * Check if it's perhaps an variable assignment or start of a rule. + * We'll do this in a very simple fashion. + */ + case kKmkToken_Word: + { + Parser.cLines = 1; + Parser.cchTotalLine = cchLine; + if (scmKmkIsLineWithContinuation(pchLine, cchLine)) + Parser.cchTotalLine = scmKmkLineContinuationPeek(&Parser, &Parser.cLines, NULL); + scmKmkHandleAssignmentOrRule(&Parser, offLine); + continue; + } + } + } + /* + * Not keyword, check for assignment, rule or comment: + */ + else if (offLine < cchLine) + { + if (pchLine[offLine] != '#') + { + Parser.cLines = 1; + Parser.cchTotalLine = cchLine; + if (scmKmkIsLineWithContinuation(pchLine, cchLine)) + Parser.cchTotalLine = scmKmkLineContinuationPeek(&Parser, &Parser.cLines, NULL); + scmKmkHandleAssignmentOrRule(&Parser, offLine); + continue; + } + + /* + * Indent comment lines, unless the comment is too far too the right. + */ + size_t const offEffLine = ScmCalcSpacesForSrcSpan(pchLine, 0, offLine, pSettings); + if (offEffLine <= Parser.iActualDepth + 7) + { + ScmStreamWrite(pOut, g_szSpaces, Parser.iActualDepth); + ScmStreamWrite(pOut, &pchLine[offLine], cchLine - offLine); + ScmStreamPutEol(pOut, Parser.enmEol); + + /* If line continuation is used, it's typically to disable + a property variable, so we just pass it thru as-is */ + while (scmKmkIsLineWithContinuation(pchLine, cchLine)) + { + Parser.pchLine = pchLine = ScmStreamGetLine(pIn, &Parser.cchLine, &Parser.enmEol); + if (!pchLine) + break; + cchLine = Parser.cchLine; + ScmStreamPutLine(pOut, pchLine, cchLine, Parser.enmEol); + } + continue; + } + } + } + + /* + * Pass it thru as-is with line continuation. + */ + while (scmKmkIsLineWithContinuation(pchLine, cchLine)) + { + ScmStreamPutLine(pOut, pchLine, cchLine, Parser.enmEol); + Parser.pchLine = pchLine = ScmStreamGetLine(pIn, &Parser.cchLine, &Parser.enmEol); + if (!pchLine) + break; + cchLine = Parser.cchLine; + } + if (pchLine) + ScmStreamPutLine(pOut, pchLine, cchLine, Parser.enmEol); + } + + return kScmMaybeModified; /* Make the caller check */ +} + + +/** + * Makefile.kup are empty files, enforce this. + * + * @returns true if modifications were made, false if not. + * @param pIn The input stream. + * @param pOut The output stream. + * @param pSettings The settings. + */ +SCMREWRITERRES rewrite_Makefile_kup(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings) +{ + RT_NOREF2(pOut, pSettings); + + /* These files should be zero bytes. */ + if (pIn->cb == 0) + return kScmUnmodified; + ScmVerbose(pState, 2, " * Truncated file to zero bytes\n"); + return kScmModified; +} + diff --git a/src/bldprogs/scmrw.cpp b/src/bldprogs/scmrw.cpp new file mode 100644 index 00000000..526a38da --- /dev/null +++ b/src/bldprogs/scmrw.cpp @@ -0,0 +1,3613 @@ +/* $Id: scmrw.cpp $ */ +/** @file + * IPRT Testcase / Tool - Source Code Massager. + */ + +/* + * Copyright (C) 2010-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/assert.h> +#include <iprt/ctype.h> +#include <iprt/dir.h> +#include <iprt/env.h> +#include <iprt/file.h> +#include <iprt/err.h> +#include <iprt/getopt.h> +#include <iprt/initterm.h> +#include <iprt/mem.h> +#include <iprt/message.h> +#include <iprt/param.h> +#include <iprt/path.h> +#include <iprt/process.h> +#include <iprt/stream.h> +#include <iprt/string.h> + +#include "scm.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** License types. */ +typedef enum SCMLICENSETYPE +{ + kScmLicenseType_Invalid = 0, + kScmLicenseType_OseGpl, + kScmLicenseType_OseDualGplCddl, + kScmLicenseType_OseCddl, + kScmLicenseType_VBoxLgpl, + kScmLicenseType_Mit, + kScmLicenseType_Confidential +} SCMLICENSETYPE; + +/** A license. */ +typedef struct SCMLICENSETEXT +{ + /** The license type. */ + SCMLICENSETYPE enmType; + /** The license option. */ + SCMLICENSE enmOpt; + /** The license text. */ + const char *psz; + /** The license text length. */ + size_t cch; +} SCMLICENSETEXT; +/** Pointer to a license. */ +typedef SCMLICENSETEXT const *PCSCMLICENSETEXT; + +/** + * Copyright + license rewriter state. + */ +typedef struct SCMCOPYRIGHTINFO +{ + /** State. */ + PSCMRWSTATE pState; /**< input */ + /** The comment style (neede for C/C++). */ + SCMCOMMENTSTYLE enmCommentStyle; /**< input */ + + /** Number of comments we've parsed. */ + uint32_t cComments; + + /** Copy of the contributed-by line if present. */ + char *pszContributedBy; + + /** @name Common info + * @{ */ + uint32_t iLineComment; + uint32_t cLinesComment; /**< This excludes any external license lines. */ + /** @} */ + + /** @name Copyright info + * @{ */ + uint32_t iLineCopyright; + uint32_t uFirstYear; + uint32_t uLastYear; + bool fWellFormedCopyright; + bool fUpToDateCopyright; + /** @} */ + + /** @name License info + * @{ */ + bool fOpenSource; /**< input */ + PCSCMLICENSETEXT pExpectedLicense; /**< input */ + PCSCMLICENSETEXT paLicenses; /**< input */ + SCMLICENSE enmLicenceOpt; /**< input */ + uint32_t iLineLicense; + uint32_t cLinesLicense; + PCSCMLICENSETEXT pCurrentLicense; + bool fIsCorrectLicense; + bool fWellFormedLicense; + bool fExternalLicense; + /** @} */ + + /** @name LGPL licence notice and disclaimer info + * @{ */ + /** Wheter to check for LGPL license notices and disclaimers. */ + bool fCheckforLgpl; + /** The approximate line we found the (first) LGPL licence notice on. */ + uint32_t iLineLgplNotice; + /** The line number after the LGPL notice comment. */ + uint32_t iLineAfterLgplComment; + /** The LGPL disclaimer line. */ + uint32_t iLineLgplDisclaimer; + /** @} */ + +} SCMCOPYRIGHTINFO; +typedef SCMCOPYRIGHTINFO *PSCMCOPYRIGHTINFO; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** --license-ose-gpl */ +static const char g_szVBoxOseGpl[] = + "This file is part of VirtualBox base platform packages, as\n" + "available from https://www.virtualbox.org.\n" + "\n" + "This program is free software; you can redistribute it and/or\n" + "modify it under the terms of the GNU General Public License\n" + "as published by the Free Software Foundation, in version 3 of the\n" + "License.\n" + "\n" + "This program is distributed in the hope that it will be useful, but\n" + "WITHOUT ANY WARRANTY; without even the implied warranty of\n" + "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n" + "General Public License for more details.\n" + "\n" + "You should have received a copy of the GNU General Public License\n" + "along with this program; if not, see <https://www.gnu.org/licenses>.\n" + "\n" + "SPDX-License-Identifier: GPL-3.0-only\n"; + +static const char g_szVBoxOseOldGpl2[] = + "This file is part of VirtualBox Open Source Edition (OSE), as\n" + "available from http://www.virtualbox.org. This file is free software;\n" + "you can redistribute it and/or modify it under the terms of the GNU\n" + "General Public License (GPL) as published by the Free Software\n" + "Foundation, in version 2 as it comes in the \"COPYING\" file of the\n" + "VirtualBox OSE distribution. VirtualBox OSE is distributed in the\n" + "hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.\n"; + +/** --license-ose-dual */ +static const char g_szVBoxOseDualGplCddl[] = + "This file is part of VirtualBox base platform packages, as\n" + "available from https://www.virtualbox.org.\n" + "\n" + "This program is free software; you can redistribute it and/or\n" + "modify it under the terms of the GNU General Public License\n" + "as published by the Free Software Foundation, in version 3 of the\n" + "License.\n" + "\n" + "This program is distributed in the hope that it will be useful, but\n" + "WITHOUT ANY WARRANTY; without even the implied warranty of\n" + "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n" + "General Public License for more details.\n" + "\n" + "You should have received a copy of the GNU General Public License\n" + "along with this program; if not, see <https://www.gnu.org/licenses>.\n" + "\n" + "The contents of this file may alternatively be used under the terms\n" + "of the Common Development and Distribution License Version 1.0\n" + "(CDDL), a copy of it is provided in the \"COPYING.CDDL\" file included\n" + "in the VirtualBox distribution, in which case the provisions of the\n" + "CDDL are applicable instead of those of the GPL.\n" + "\n" + "You may elect to license modified versions of this file under the\n" + "terms and conditions of either the GPL or the CDDL or both.\n" + "\n" + "SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0\n"; + +static const char g_szVBoxOseOldDualGpl2Cddl[] = + "This file is part of VirtualBox Open Source Edition (OSE), as\n" + "available from http://www.virtualbox.org. This file is free software;\n" + "you can redistribute it and/or modify it under the terms of the GNU\n" + "General Public License (GPL) as published by the Free Software\n" + "Foundation, in version 2 as it comes in the \"COPYING\" file of the\n" + "VirtualBox OSE distribution. VirtualBox OSE is distributed in the\n" + "hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.\n" + "\n" + "The contents of this file may alternatively be used under the terms\n" + "of the Common Development and Distribution License Version 1.0\n" + "(CDDL) only, as it comes in the \"COPYING.CDDL\" file of the\n" + "VirtualBox OSE distribution, in which case the provisions of the\n" + "CDDL are applicable instead of those of the GPL.\n" + "\n" + "You may elect to license modified versions of this file under the\n" + "terms and conditions of either the GPL or the CDDL or both.\n"; + +/** --license-ose-cddl */ +static const char g_szVBoxOseCddl[] = + "This file is part of VirtualBox base platform packages, as\n" + "available from http://www.virtualbox.org.\n" + "\n" + "The contents of this file are subject to the terms of the Common\n" + "Development and Distribution License Version 1.0 (CDDL) only, as it\n" + "comes in the \"COPYING.CDDL\" file of the VirtualBox distribution.\n" + "\n" + "SPDX-License-Identifier: CDDL-1.0\n"; + +static const char g_szVBoxOseOldCddl[] = + "This file is part of VirtualBox Open Source Edition (OSE), as\n" + "available from http://www.virtualbox.org. This file is free software;\n" + "you can redistribute it and/or modify it under the terms of the Common\n" + "Development and Distribution License Version 1.0 (CDDL) only, as it\n" + "comes in the \"COPYING.CDDL\" file of the VirtualBox OSE distribution.\n" + "VirtualBox OSE is distributed in the hope that it will be useful, but\n" + "WITHOUT ANY WARRANTY of any kind.\n"; + +/** --license-lgpl */ +static const char g_szVBoxLgpl[] = + "This file is part of a free software library; you can redistribute\n" + "it and/or modify it under the terms of the GNU Lesser General\n" + "Public License version 2.1 as published by the Free Software\n" + "Foundation and shipped in the \"COPYING.LIB\" file with this library.\n" + "The library is distributed in the hope that it will be useful,\n" + "but WITHOUT ANY WARRANTY of any kind.\n" + "\n" + "Oracle LGPL Disclaimer: For the avoidance of doubt, except that if\n" + "any license choice other than GPL or LGPL is available it will\n" + "apply instead, Oracle elects to use only the Lesser General Public\n" + "License version 2.1 (LGPLv2) at this time for any software where\n" + "a choice of LGPL license versions is made available with the\n" + "language indicating that LGPLv2 or any later version may be used,\n" + "or where a choice of which version of the LGPL is applied is\n" + "otherwise unspecified.\n" + "\n" + "SPDX-License-Identifier: LGPL-2.1-only\n"; + +/** --license-mit + * @note This isn't detectable as VirtualBox or Oracle specific. + */ +static const char g_szMit[] = + "Permission is hereby granted, free of charge, to any person\n" + "obtaining a copy of this software and associated documentation\n" + "files (the \"Software\"), to deal in the Software without\n" + "restriction, including without limitation the rights to use,\n" + "copy, modify, merge, publish, distribute, sublicense, and/or sell\n" + "copies of the Software, and to permit persons to whom the\n" + "Software is furnished to do so, subject to the following\n" + "conditions:\n" + "\n" + "The above copyright notice and this permission notice shall be\n" + "included in all copies or substantial portions of the Software.\n" + "\n" + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n" + "EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n" + "OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n" + "NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n" + "HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n" + "WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n" + "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n" + "OTHER DEALINGS IN THE SOFTWARE.\n"; + +/** --license-mit, alternative wording \#1. + * @note This differes from g_szMit in "AUTHORS OR COPYRIGHT HOLDERS" is written + * "COPYRIGHT HOLDER(S) OR AUTHOR(S)". Its layout is wider, so it is a + * couple of lines shorter. */ +static const char g_szMitAlt1[] = + "Permission is hereby granted, free of charge, to any person obtaining a\n" + "copy of this software and associated documentation files (the \"Software\"),\n" + "to deal in the Software without restriction, including without limitation\n" + "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n" + "and/or sell copies of the Software, and to permit persons to whom the\n" + "Software is furnished to do so, subject to the following conditions:\n" + "\n" + "The above copyright notice and this permission notice shall be included in\n" + "all copies or substantial portions of the Software.\n" + "\n" + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n" + "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n" + "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n" + "THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR\n" + "OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\n" + "ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n" + "OTHER DEALINGS IN THE SOFTWARE.\n"; + +/** --license-mit, alternative wording \#2. + * @note This differes from g_szMit in that "AUTHORS OR COPYRIGHT HOLDERS" is + * replaced with "THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS". + * Its layout is wider, so it is a couple of lines shorter. */ +static const char g_szMitAlt2[] = + "Permission is hereby granted, free of charge, to any person obtaining a\n" + "copy of this software and associated documentation files (the \"Software\"),\n" + "to deal in the Software without restriction, including without limitation\n" + "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n" + "and/or sell copies of the Software, and to permit persons to whom the\n" + "Software is furnished to do so, subject to the following conditions:\n" + "\n" + "The above copyright notice and this permission notice shall be included in\n" + "all copies or substantial portions of the Software.\n" + "\n" + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n" + "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n" + "FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n" + "THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,\n" + "DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n" + "OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n" + "USE OR OTHER DEALINGS IN THE SOFTWARE.\n"; + +/** --license-mit, alternative wording \#3. + * @note This differes from g_szMitAlt2 in that the second and third sections + * have been switch. */ +static const char g_szMitAlt3[] = + "Permission is hereby granted, free of charge, to any person obtaining a\n" + "copy of this software and associated documentation files (the \"Software\"),\n" + "to deal in the Software without restriction, including without limitation\n" + "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n" + "and/or sell copies of the Software, and to permit persons to whom the\n" + "Software is furnished to do so, subject to the following conditions:\n" + "\n" + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n" + "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n" + "FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n" + "THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,\n" + "DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n" + "OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n" + "USE OR OTHER DEALINGS IN THE SOFTWARE.\n" + "\n" + "The above copyright notice and this permission notice shall be included in\n" + "all copies or substantial portions of the Software.\n"; + +/** --license-(based-on)mit, alternative wording \#4. + * @note This differs from g_szMitAlt2 in injecting "(including the next + * paragraph)". */ +static const char g_szMitAlt4[] = + "Permission is hereby granted, free of charge, to any person obtaining a\n" + "copy of this software and associated documentation files (the \"Software\"),\n" + "to deal in the Software without restriction, including without limitation\n" + "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n" + "and/or sell copies of the Software, and to permit persons to whom the\n" + "Software is furnished to do so, subject to the following conditions:\n" + "\n" + "The above copyright notice and this permission notice (including the next\n" + "paragraph) shall be included in all copies or substantial portions of the\n" + "Software.\n" + "\n" + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n" + "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n" + "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n" + "THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n" + "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n" + "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n" + "DEALINGS IN THE SOFTWARE.\n"; + +/** --license-(based-on)mit, alternative wording \#5. + * @note This differs from g_szMitAlt3 in using "sub license" instead of + * "sublicense" and adding an illogical "(including the next + * paragraph)" remark to the final paragraph. (vbox_ttm.c) */ +static const char g_szMitAlt5[] = + "Permission is hereby granted, free of charge, to any person obtaining a\n" + "copy of this software and associated documentation files (the\n" + "\"Software\"), to deal in the Software without restriction, including\n" + "without limitation the rights to use, copy, modify, merge, publish,\n" + "distribute, sub license, and/or sell copies of the Software, and to\n" + "permit persons to whom the Software is furnished to do so, subject to\n" + "the following conditions:\n" + "\n" + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n" + "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n" + "FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n" + "THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,\n" + "DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n" + "OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n" + "USE OR OTHER DEALINGS IN THE SOFTWARE.\n" + "\n" + "The above copyright notice and this permission notice (including the\n" + "next paragraph) shall be included in all copies or substantial portions\n" + "of the Software.\n"; + +/** Oracle confidential. */ +static const char g_szOracleConfidential[] = + "Oracle Corporation confidential\n"; + +/** Oracle confidential, old style. */ +static const char g_szOracleConfidentialOld[] = + "Oracle Corporation confidential\n" + "All rights reserved\n"; + +/** Licenses to detect when --license-mit isn't used. */ +static const SCMLICENSETEXT g_aLicenses[] = +{ + { kScmLicenseType_OseGpl, kScmLicense_OseGpl, RT_STR_TUPLE(g_szVBoxOseGpl)}, + { kScmLicenseType_OseGpl, kScmLicense_OseGpl, RT_STR_TUPLE(g_szVBoxOseOldGpl2)}, + { kScmLicenseType_OseDualGplCddl, kScmLicense_OseDualGplCddl, RT_STR_TUPLE(g_szVBoxOseDualGplCddl) }, + { kScmLicenseType_OseDualGplCddl, kScmLicense_OseDualGplCddl, RT_STR_TUPLE(g_szVBoxOseOldDualGpl2Cddl) }, + { kScmLicenseType_OseCddl, kScmLicense_OseCddl, RT_STR_TUPLE(g_szVBoxOseCddl) }, + { kScmLicenseType_OseCddl, kScmLicense_OseCddl, RT_STR_TUPLE(g_szVBoxOseOldCddl) }, + { kScmLicenseType_VBoxLgpl, kScmLicense_Lgpl, RT_STR_TUPLE(g_szVBoxLgpl)}, + { kScmLicenseType_Confidential, kScmLicense_End, RT_STR_TUPLE(g_szOracleConfidential) }, + { kScmLicenseType_Confidential, kScmLicense_End, RT_STR_TUPLE(g_szOracleConfidentialOld) }, + { kScmLicenseType_Invalid, kScmLicense_End, NULL, 0 }, +}; + +/** Licenses to detect when --license-mit or --license-based-on-mit are used. */ +static const SCMLICENSETEXT g_aLicensesWithMit[] = +{ + { kScmLicenseType_Mit, kScmLicense_Mit, RT_STR_TUPLE(g_szMit) }, + { kScmLicenseType_Mit, kScmLicense_Mit, RT_STR_TUPLE(g_szMitAlt1) }, + { kScmLicenseType_Mit, kScmLicense_Mit, RT_STR_TUPLE(g_szMitAlt2) }, + { kScmLicenseType_Mit, kScmLicense_Mit, RT_STR_TUPLE(g_szMitAlt3) }, + { kScmLicenseType_Mit, kScmLicense_Mit, RT_STR_TUPLE(g_szMitAlt4) }, + { kScmLicenseType_Mit, kScmLicense_Mit, RT_STR_TUPLE(g_szMitAlt5) }, + { kScmLicenseType_OseGpl, kScmLicense_OseGpl, RT_STR_TUPLE(g_szVBoxOseGpl)}, + { kScmLicenseType_OseGpl, kScmLicense_OseGpl, RT_STR_TUPLE(g_szVBoxOseOldGpl2)}, + { kScmLicenseType_OseDualGplCddl, kScmLicense_OseDualGplCddl, RT_STR_TUPLE(g_szVBoxOseDualGplCddl) }, + { kScmLicenseType_OseDualGplCddl, kScmLicense_OseDualGplCddl, RT_STR_TUPLE(g_szVBoxOseOldDualGpl2Cddl) }, + { kScmLicenseType_VBoxLgpl, kScmLicense_Lgpl, RT_STR_TUPLE(g_szVBoxLgpl)}, + { kScmLicenseType_Confidential, kScmLicense_End, RT_STR_TUPLE(g_szOracleConfidential) }, + { kScmLicenseType_Confidential, kScmLicense_End, RT_STR_TUPLE(g_szOracleConfidentialOld) }, + { kScmLicenseType_Invalid, kScmLicense_End, NULL, 0 }, +}; + +/** Copyright holder. */ +static const char g_szCopyrightHolder[] = "Oracle and/or its affiliates."; + +/** Old copyright holder. */ +static const char g_szOldCopyrightHolder[] = "Oracle Corporation"; + +/** LGPL disclaimer. */ +static const char g_szLgplDisclaimer[] = + "Oracle LGPL Disclaimer: For the avoidance of doubt, except that if any license choice\n" + "other than GPL or LGPL is available it will apply instead, Oracle elects to use only\n" + "the Lesser General Public License version 2.1 (LGPLv2) at this time for any software where\n" + "a choice of LGPL license versions is made available with the language indicating\n" + "that LGPLv2 or any later version may be used, or where a choice of which version\n" + "of the LGPL is applied is otherwise unspecified.\n"; + +/** Copyright+license comment start for each SCMCOMMENTSTYLE. */ +static RTSTRTUPLE const g_aCopyrightCommentStart[] = +{ + { RT_STR_TUPLE("<invalid> ") }, + { RT_STR_TUPLE("/*") }, + { RT_STR_TUPLE("#") }, + { RT_STR_TUPLE("\"\"\"") }, + { RT_STR_TUPLE(";") }, + { RT_STR_TUPLE("REM") }, + { RT_STR_TUPLE("rem") }, + { RT_STR_TUPLE("Rem") }, + { RT_STR_TUPLE("--") }, + { RT_STR_TUPLE("'") }, + { RT_STR_TUPLE("<!--") }, + { RT_STR_TUPLE("<end>") }, +}; + +/** Copyright+license line prefix for each SCMCOMMENTSTYLE. */ +static RTSTRTUPLE const g_aCopyrightCommentPrefix[] = +{ + { RT_STR_TUPLE("<invalid> ") }, + { RT_STR_TUPLE(" * ") }, + { RT_STR_TUPLE("# ") }, + { RT_STR_TUPLE("") }, + { RT_STR_TUPLE("; ") }, + { RT_STR_TUPLE("REM ") }, + { RT_STR_TUPLE("rem ") }, + { RT_STR_TUPLE("Rem ") }, + { RT_STR_TUPLE("-- ") }, + { RT_STR_TUPLE("' ") }, + { RT_STR_TUPLE(" ") }, + { RT_STR_TUPLE("<end>") }, +}; + +/** Copyright+license empty line for each SCMCOMMENTSTYLE. */ +static RTSTRTUPLE const g_aCopyrightCommentEmpty[] = +{ + { RT_STR_TUPLE("<invalid>") }, + { RT_STR_TUPLE(" *") }, + { RT_STR_TUPLE("#") }, + { RT_STR_TUPLE("") }, + { RT_STR_TUPLE(";") }, + { RT_STR_TUPLE("REM") }, + { RT_STR_TUPLE("rem") }, + { RT_STR_TUPLE("Rem") }, + { RT_STR_TUPLE("--") }, + { RT_STR_TUPLE("'") }, + { RT_STR_TUPLE("") }, + { RT_STR_TUPLE("<end>") }, +}; + +/** Copyright+license end of comment for each SCMCOMMENTSTYLE. */ +static RTSTRTUPLE const g_aCopyrightCommentEnd[] = +{ + { RT_STR_TUPLE("<invalid> ") }, + { RT_STR_TUPLE(" */") }, + { RT_STR_TUPLE("#") }, + { RT_STR_TUPLE("\"\"\"") }, + { RT_STR_TUPLE(";") }, + { RT_STR_TUPLE("REM") }, + { RT_STR_TUPLE("rem") }, + { RT_STR_TUPLE("Rem") }, + { RT_STR_TUPLE("--") }, + { RT_STR_TUPLE("'") }, + { RT_STR_TUPLE("-->") }, + { RT_STR_TUPLE("<end>") }, +}; + + +/** + * Figures out the predominant casing of the "REM" keyword in a batch file. + * + * @returns Predominant comment style. + * @param pIn The file to scan. Will be rewound. + */ +static SCMCOMMENTSTYLE determineBatchFileCommentStyle(PSCMSTREAM pIn) +{ + /* + * Figure out whether it's using upper or lower case REM comments before + * doing the work. + */ + uint32_t cUpper = 0; + uint32_t cLower = 0; + uint32_t cCamel = 0; + SCMEOL enmEol; + size_t cchLine; + const char *pchLine; + while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL) + { + while ( cchLine > 2 + && RT_C_IS_SPACE(*pchLine)) + { + pchLine++; + cchLine--; + } + if ( ( cchLine > 3 + && RT_C_IS_SPACE(pchLine[2])) + || cchLine == 3) + { + if ( pchLine[0] == 'R' + && pchLine[1] == 'E' + && pchLine[2] == 'M') + cUpper++; + else if ( pchLine[0] == 'r' + && pchLine[1] == 'e' + && pchLine[2] == 'm') + cLower++; + else if ( pchLine[0] == 'R' + && pchLine[1] == 'e' + && pchLine[2] == 'm') + cCamel++; + } + } + + ScmStreamRewindForReading(pIn); + + if (cLower >= cUpper && cLower >= cCamel) + return kScmCommentStyle_Rem_Lower; + if (cCamel >= cLower && cCamel >= cUpper) + return kScmCommentStyle_Rem_Camel; + return kScmCommentStyle_Rem_Upper; +} + + +/** + * Calculates the number of spaces from @a offStart to @a offEnd in @a pchLine, + * taking tabs into account. + */ +size_t ScmCalcSpacesForSrcSpan(const char *pchLine, size_t offStart, size_t offEnd, PCSCMSETTINGSBASE pSettings) +{ + size_t cchRet = 0; + if (offStart < offEnd) + { + offEnd -= offStart; /* becomes cchLeft now */ + pchLine += offStart; + while (offEnd > 0) + { + const char *pszTab = (const char *)memchr(pchLine, '\t', offEnd); + if (!pszTab) + { + cchRet += offEnd; + break; + } + size_t offTab = (size_t)(pszTab - pchLine); + size_t cchToTab = pSettings->cchTab - offTab % pSettings->cchTab; + cchRet += offTab + cchToTab; + offEnd -= offTab + 1; + pchLine = pszTab + 1; + } + } + return cchRet; +} + + +/** + * Worker for isBlankLine. + * + * @returns true if blank, false if not. + * @param pchLine Pointer to the start of the line. + * @param cchLine The (encoded) length of the line, excluding EOL char. + */ +static bool isBlankLineSlow(const char *pchLine, size_t cchLine) +{ + /* + * From the end, more likely to hit a non-blank char there. + */ + while (cchLine-- > 0) + if (!RT_C_IS_BLANK(pchLine[cchLine])) + return false; + return true; +} + +/** + * Helper for checking whether a line is blank. + * + * @returns true if blank, false if not. + * @param pchLine Pointer to the start of the line. + * @param cchLine The (encoded) length of the line, excluding EOL char. + */ +DECLINLINE(bool) isBlankLine(const char *pchLine, size_t cchLine) +{ + if (cchLine == 0) + return true; + /* + * We're more likely to fine a non-space char at the end of the line than + * at the start, due to source code indentation. + */ + if (pchLine[cchLine - 1]) + return false; + + /* + * Don't bother inlining loop code. + */ + return isBlankLineSlow(pchLine, cchLine); +} + + +/** + * Checks if there are @a cch blanks at @a pch. + * + * @returns true if span of @a cch blanks, false if not. + * @param pch The start of the span to check. + * @param cch The length of the span. + */ +DECLINLINE(bool) isSpanOfBlanks(const char *pch, size_t cch) +{ + while (cch-- > 0) + { + char const ch = *pch++; + if (!RT_C_IS_BLANK(ch)) + return false; + } + return true; +} + + +/** + * Strip trailing blanks (space & tab). + * + * @returns Modification state. + * @param pIn The input stream. + * @param pOut The output stream. + * @param pSettings The settings. + */ +SCMREWRITERRES rewrite_StripTrailingBlanks(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings) +{ + if (!pSettings->fStripTrailingBlanks) + return kScmUnmodified; + + bool fModified = false; + SCMEOL enmEol; + size_t cchLine; + const char *pchLine; + while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL) + { + int rc; + if ( cchLine == 0 + || !RT_C_IS_BLANK(pchLine[cchLine - 1]) ) + rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol); + else + { + cchLine--; + while (cchLine > 0 && RT_C_IS_BLANK(pchLine[cchLine - 1])) + cchLine--; + rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol); + fModified = true; + } + if (RT_FAILURE(rc)) + return kScmUnmodified; + } + if (fModified) + ScmVerbose(pState, 2, " * Stripped trailing blanks\n"); + return fModified ? kScmModified : kScmUnmodified; +} + +/** + * Expand tabs. + * + * @returns Modification state. + * @param pIn The input stream. + * @param pOut The output stream. + * @param pSettings The settings. + */ +SCMREWRITERRES rewrite_ExpandTabs(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings) +{ + if (!pSettings->fConvertTabs) + return kScmUnmodified; + + size_t const cchTab = pSettings->cchTab; + bool fModified = false; + SCMEOL enmEol; + size_t cchLine; + const char *pchLine; + while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL) + { + int rc; + const char *pchTab = (const char *)memchr(pchLine, '\t', cchLine); + if (!pchTab) + rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol); + else + { + size_t offTab = 0; + const char *pchChunk = pchLine; + for (;;) + { + size_t cchChunk = pchTab - pchChunk; + offTab += cchChunk; + ScmStreamWrite(pOut, pchChunk, cchChunk); + + size_t cchToTab = cchTab - offTab % cchTab; + ScmStreamWrite(pOut, g_szTabSpaces, cchToTab); + offTab += cchToTab; + + pchChunk = pchTab + 1; + size_t cchLeft = cchLine - (pchChunk - pchLine); + pchTab = (const char *)memchr(pchChunk, '\t', cchLeft); + if (!pchTab) + { + rc = ScmStreamPutLine(pOut, pchChunk, cchLeft, enmEol); + break; + } + } + + fModified = true; + } + if (RT_FAILURE(rc)) + return kScmUnmodified; + } + if (fModified) + ScmVerbose(pState, 2, " * Expanded tabs\n"); + return fModified ? kScmModified : kScmUnmodified; +} + +/** + * Worker for rewrite_ForceNativeEol, rewrite_ForceLF and rewrite_ForceCRLF. + * + * @returns Modification state. + * @param pIn The input stream. + * @param pOut The output stream. + * @param pSettings The settings. + * @param enmDesiredEol The desired end of line indicator type. + * @param pszDesiredSvnEol The desired svn:eol-style. + */ +static SCMREWRITERRES rewrite_ForceEol(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings, + SCMEOL enmDesiredEol, const char *pszDesiredSvnEol) +{ + if (!pSettings->fConvertEol) + return kScmUnmodified; + + bool fModified = false; + SCMEOL enmEol; + size_t cchLine; + const char *pchLine; + while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL) + { + if ( enmEol != enmDesiredEol + && enmEol != SCMEOL_NONE) + { + fModified = true; + enmEol = enmDesiredEol; + } + int rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol); + if (RT_FAILURE(rc)) + return kScmUnmodified; + } + if (fModified) + ScmVerbose(pState, 2, " * Converted EOL markers\n"); + + /* Check svn:eol-style if appropriate */ + if ( pSettings->fSetSvnEol + && ScmSvnIsInWorkingCopy(pState)) + { + char *pszEol; + int rc = ScmSvnQueryProperty(pState, "svn:eol-style", &pszEol); + if ( (RT_SUCCESS(rc) && strcmp(pszEol, pszDesiredSvnEol)) + || rc == VERR_NOT_FOUND) + { + if (rc == VERR_NOT_FOUND) + ScmVerbose(pState, 2, " * Setting svn:eol-style to %s (missing)\n", pszDesiredSvnEol); + else + ScmVerbose(pState, 2, " * Setting svn:eol-style to %s (was: %s)\n", pszDesiredSvnEol, pszEol); + int rc2 = ScmSvnSetProperty(pState, "svn:eol-style", pszDesiredSvnEol); + if (RT_FAILURE(rc2)) + ScmError(pState, rc2, "ScmSvnSetProperty: %Rrc\n", rc2); + } + if (RT_SUCCESS(rc)) + RTStrFree(pszEol); + } + + /** @todo also check the subversion svn:eol-style state! */ + return fModified ? kScmModified : kScmUnmodified; +} + +/** + * Force native end of line indicator. + * + * @returns Modification state. + * @param pIn The input stream. + * @param pOut The output stream. + * @param pSettings The settings. + */ +SCMREWRITERRES rewrite_ForceNativeEol(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings) +{ +#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) + return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_CRLF, "native"); +#else + return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_LF, "native"); +#endif +} + +/** + * Force the stream to use LF as the end of line indicator. + * + * @returns Modification state. + * @param pIn The input stream. + * @param pOut The output stream. + * @param pSettings The settings. + */ +SCMREWRITERRES rewrite_ForceLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings) +{ + return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_LF, "LF"); +} + +/** + * Force the stream to use CRLF as the end of line indicator. + * + * @returns Modification state. + * @param pIn The input stream. + * @param pOut The output stream. + * @param pSettings The settings. + */ +SCMREWRITERRES rewrite_ForceCRLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings) +{ + return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_CRLF, "CRLF"); +} + +/** + * Strip trailing blank lines and/or make sure there is exactly one blank line + * at the end of the file. + * + * @returns Modification state. + * @param pIn The input stream. + * @param pOut The output stream. + * @param pSettings The settings. + * + * @remarks ASSUMES trailing white space has been removed already. + */ +SCMREWRITERRES rewrite_AdjustTrailingLines(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings) +{ + if ( !pSettings->fStripTrailingLines + && !pSettings->fForceTrailingLine + && !pSettings->fForceFinalEol) + return kScmUnmodified; + + size_t const cLines = ScmStreamCountLines(pIn); + + /* Empty files remains empty. */ + if (cLines <= 1) + return kScmUnmodified; + + /* Figure out if we need to adjust the number of lines or not. */ + size_t cLinesNew = cLines; + + if ( pSettings->fStripTrailingLines + && ScmStreamIsWhiteLine(pIn, cLinesNew - 1)) + { + while ( cLinesNew > 1 + && ScmStreamIsWhiteLine(pIn, cLinesNew - 2)) + cLinesNew--; + } + + if ( pSettings->fForceTrailingLine + && !ScmStreamIsWhiteLine(pIn, cLinesNew - 1)) + cLinesNew++; + + bool fFixMissingEol = pSettings->fForceFinalEol + && ScmStreamGetEolByLine(pIn, cLinesNew - 1) == SCMEOL_NONE; + + if ( !fFixMissingEol + && cLines == cLinesNew) + return kScmUnmodified; + + /* Copy the number of lines we've arrived at. */ + ScmStreamRewindForReading(pIn); + + size_t cCopied = RT_MIN(cLinesNew, cLines); + ScmStreamCopyLines(pOut, pIn, cCopied); + + if (cCopied != cLinesNew) + { + while (cCopied++ < cLinesNew) + ScmStreamPutLine(pOut, "", 0, ScmStreamGetEol(pIn)); + } + /* Fix missing EOL if required. */ + else if (fFixMissingEol) + { + if (ScmStreamGetEol(pIn) == SCMEOL_LF) + ScmStreamWrite(pOut, "\n", 1); + else + ScmStreamWrite(pOut, "\r\n", 2); + } + + ScmVerbose(pState, 2, " * Adjusted trailing blank lines\n"); + return kScmModified; +} + +/** + * Make sure there is no svn:executable property on the current file. + * + * @returns kScmUnmodified - the state carries these kinds of changes. + * @param pState The rewriter state. + * @param pIn The input stream. + * @param pOut The output stream. + * @param pSettings The settings. + */ +SCMREWRITERRES rewrite_SvnNoExecutable(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings) +{ + RT_NOREF2(pIn, pOut); + if ( !pSettings->fSetSvnExecutable + || !ScmSvnIsInWorkingCopy(pState)) + return kScmUnmodified; + + int rc = ScmSvnQueryProperty(pState, "svn:executable", NULL); + if (RT_SUCCESS(rc)) + { + ScmVerbose(pState, 2, " * removing svn:executable\n"); + rc = ScmSvnDelProperty(pState, "svn:executable"); + if (RT_FAILURE(rc)) + ScmError(pState, rc, "ScmSvnSetProperty: %Rrc\n", rc); + } + return kScmUnmodified; +} + +/** + * Make sure there is no svn:keywords property on the current file. + * + * @returns kScmUnmodified - the state carries these kinds of changes. + * @param pState The rewriter state. + * @param pIn The input stream. + * @param pOut The output stream. + * @param pSettings The settings. + */ +SCMREWRITERRES rewrite_SvnNoKeywords(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings) +{ + RT_NOREF2(pIn, pOut); + if ( !pSettings->fSetSvnExecutable + || !ScmSvnIsInWorkingCopy(pState)) + return kScmUnmodified; + + int rc = ScmSvnQueryProperty(pState, "svn:keywords", NULL); + if (RT_SUCCESS(rc)) + { + ScmVerbose(pState, 2, " * removing svn:keywords\n"); + rc = ScmSvnDelProperty(pState, "svn:keywords"); + if (RT_FAILURE(rc)) + ScmError(pState, rc, "ScmSvnSetProperty: %Rrc\n", rc); + } + return kScmUnmodified; +} + +/** + * Make sure there is no svn:eol-style property on the current file. + * + * @returns kScmUnmodified - the state carries these kinds of changes. + * @param pState The rewriter state. + * @param pIn The input stream. + * @param pOut The output stream. + * @param pSettings The settings. + */ +SCMREWRITERRES rewrite_SvnNoEolStyle(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings) +{ + RT_NOREF2(pIn, pOut); + if ( !pSettings->fSetSvnExecutable + || !ScmSvnIsInWorkingCopy(pState)) + return kScmUnmodified; + + int rc = ScmSvnQueryProperty(pState, "svn:eol-style", NULL); + if (RT_SUCCESS(rc)) + { + ScmVerbose(pState, 2, " * removing svn:eol-style\n"); + rc = ScmSvnDelProperty(pState, "svn:eol-style"); + if (RT_FAILURE(rc)) + ScmError(pState, rc, "ScmSvnSetProperty: %Rrc\n", rc); + } + return kScmUnmodified; +} + +/** + * Makes sure the svn properties are appropriate for a binary. + * + * @returns kScmUnmodified - the state carries these kinds of changes. + * @param pState The rewriter state. + * @param pIn The input stream. + * @param pOut The output stream. + * @param pSettings The settings. + */ +SCMREWRITERRES rewrite_SvnBinary(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings) +{ + RT_NOREF2(pIn, pOut); + if ( !pSettings->fSetSvnExecutable + || !ScmSvnIsInWorkingCopy(pState)) + return kScmUnmodified; + + /* remove svn:eol-style and svn:keywords */ + static const char * const s_apszRemove[] = { "svn:eol-style", "svn:keywords" }; + for (uint32_t i = 0; i < RT_ELEMENTS(s_apszRemove); i++) + { + char *pszValue; + int rc = ScmSvnQueryProperty(pState, s_apszRemove[i], &pszValue); + if (RT_SUCCESS(rc)) + { + ScmVerbose(pState, 2, " * removing %s=%s\n", s_apszRemove[i], pszValue); + RTStrFree(pszValue); + rc = ScmSvnDelProperty(pState, s_apszRemove[i]); + if (RT_FAILURE(rc)) + ScmError(pState, rc, "ScmSvnSetProperty(,%s): %Rrc\n", s_apszRemove[i], rc); + } + else if (rc != VERR_NOT_FOUND) + ScmError(pState, rc, "ScmSvnQueryProperty: %Rrc\n", rc); + } + + /* Make sure there is a svn:mime-type set. */ + int rc = ScmSvnQueryProperty(pState, "svn:mime-type", NULL); + if (rc == VERR_NOT_FOUND) + { + ScmVerbose(pState, 2, " * settings svn:mime-type\n"); + rc = ScmSvnSetProperty(pState, "svn:mime-type", "application/octet-stream"); + if (RT_FAILURE(rc)) + ScmError(pState, rc, "ScmSvnSetProperty: %Rrc\n", rc); + } + else if (RT_FAILURE(rc)) + ScmError(pState, rc, "ScmSvnQueryProperty: %Rrc\n", rc); + + return kScmUnmodified; +} + +/** + * Make sure the Id and Revision keywords are expanded. + * + * @returns kScmUnmodified - the state carries these kinds of changes. + * @param pState The rewriter state. + * @param pIn The input stream. + * @param pOut The output stream. + * @param pSettings The settings. + */ +SCMREWRITERRES rewrite_SvnKeywords(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings) +{ + RT_NOREF2(pIn, pOut); + if ( !pSettings->fSetSvnKeywords + || !ScmSvnIsInWorkingCopy(pState)) + return kScmUnmodified; + + char *pszKeywords; + int rc = ScmSvnQueryProperty(pState, "svn:keywords", &pszKeywords); + if ( RT_SUCCESS(rc) + && ( !strstr(pszKeywords, "Id") /** @todo need some function for finding a word in a string. */ + || !strstr(pszKeywords, "Revision")) ) + { + if (!strstr(pszKeywords, "Id") && !strstr(pszKeywords, "Revision")) + rc = RTStrAAppend(&pszKeywords, " Id Revision"); + else if (!strstr(pszKeywords, "Id")) + rc = RTStrAAppend(&pszKeywords, " Id"); + else + rc = RTStrAAppend(&pszKeywords, " Revision"); + if (RT_SUCCESS(rc)) + { + ScmVerbose(pState, 2, " * changing svn:keywords to '%s'\n", pszKeywords); + rc = ScmSvnSetProperty(pState, "svn:keywords", pszKeywords); + if (RT_FAILURE(rc)) + ScmError(pState, rc, "ScmSvnSetProperty: %Rrc\n", rc); + } + else + ScmError(pState, rc, "RTStrAppend: %Rrc\n", rc); + RTStrFree(pszKeywords); + } + else if (rc == VERR_NOT_FOUND) + { + ScmVerbose(pState, 2, " * setting svn:keywords to 'Id Revision'\n"); + rc = ScmSvnSetProperty(pState, "svn:keywords", "Id Revision"); + if (RT_FAILURE(rc)) + ScmError(pState, rc, "ScmSvnSetProperty: %Rrc\n", rc); + } + else if (RT_SUCCESS(rc)) + RTStrFree(pszKeywords); + + return kScmUnmodified; +} + +/** + * Checks the svn:sync-process value and that parent is exported too. + * + * @returns kScmUnmodified - the state carries these kinds of changes. + * @param pState The rewriter state. + * @param pIn The input stream. + * @param pOut The output stream. + * @param pSettings The settings. + */ +SCMREWRITERRES rewrite_SvnSyncProcess(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings) +{ + RT_NOREF2(pIn, pOut); + if ( pSettings->fSkipSvnSyncProcess + || !ScmSvnIsInWorkingCopy(pState)) + return kScmUnmodified; + + char *pszSyncProcess; + int rc = ScmSvnQueryProperty(pState, "svn:sync-process", &pszSyncProcess); + if (RT_SUCCESS(rc)) + { + if (strcmp(pszSyncProcess, "export") == 0) + { + char *pszParentSyncProcess; + rc = ScmSvnQueryParentProperty(pState, "svn:sync-process", &pszParentSyncProcess); + if (RT_SUCCESS(rc)) + { + if (strcmp(pszSyncProcess, "export") != 0) + ScmError(pState, VERR_INVALID_STATE, + "svn:sync-process=export, but parent directory differs: %s\n" + "WARNING! Make sure to unexport everything inside the directory first!\n" + " Then you may export the directory and stuff inside it if you want.\n" + " (Just exporting the directory will not make anything inside it externally visible.)\n" + , pszParentSyncProcess); + RTStrFree(pszParentSyncProcess); + } + else if (rc == VERR_NOT_FOUND) + ScmError(pState, VERR_NOT_FOUND, + "svn:sync-process=export, but parent directory is not exported!\n" + "WARNING! Make sure to unexport everything inside the directory first!\n" + " Then you may export the directory and stuff inside it if you want.\n" + " (Just exporting the directory will not make anything inside it externally visible.)\n"); + else + ScmError(pState, rc, "ScmSvnQueryParentProperty: %Rrc\n", rc); + } + else if (strcmp(pszSyncProcess, "ignore") != 0) + ScmError(pState, VERR_INVALID_NAME, "Bad sync-process value: %s\n", pszSyncProcess); + RTStrFree(pszSyncProcess); + } + else if (rc != VERR_NOT_FOUND) + ScmError(pState, rc, "ScmSvnQueryProperty: %Rrc\n", rc); + + return kScmUnmodified; +} + +/** + * Checks the that there is no bidirectional unicode fun in the file. + * + * @returns kScmUnmodified - the state carries these kinds of changes. + * @param pState The rewriter state. + * @param pIn The input stream. + * @param pOut The output stream. + * @param pSettings The settings. + */ +SCMREWRITERRES rewrite_UnicodeChecks(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings) +{ + RT_NOREF2(pIn, pOut); + if (pSettings->fSkipUnicodeChecks) + return kScmUnmodified; + + /* + * Just scan the input for weird stuff and fail if we find anything we don't like. + */ + uint32_t iLine = 0; + SCMEOL enmEol; + size_t cchLine; + const char *pchLine; + while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL) + { + iLine++; + const char *pchCur = pchLine; + size_t cchLeft = cchLine; + while (cchLeft > 0) + { + RTUNICP uc = 0; + int rc = RTStrGetCpNEx(&pchCur, &cchLeft, &uc); + if (RT_SUCCESS(rc)) + { + const char *pszWhat; + switch (uc) + { + default: + continue; + + /* Potentially evil bi-directional control codes (Table I, trojan-source.pdf): */ + case 0x202a: pszWhat = "LRE - left-to-right embedding"; break; + case 0x202b: pszWhat = "RLE - right-to-left embedding"; break; + case 0x202d: pszWhat = "LRO - left-to-right override"; break; + case 0x202e: pszWhat = "RLO - right-to-left override"; break; + case 0x2066: pszWhat = "LRI - left-to-right isolate"; break; + case 0x2067: pszWhat = "RLI - right-to-left isolate"; break; + case 0x2068: pszWhat = "FSI - first strong isolate"; break; + case 0x202c: pszWhat = "PDF - pop directional formatting (LRE, RLE, LRO, RLO)"; break; + case 0x2069: pszWhat = "PDI - pop directional isolate (LRI, RLI)"; break; + + /** @todo add checks for homoglyphs too. */ + } + ScmFixManually(pState, "%u:%zu: Evil unicode codepoint: %s\n", iLine, pchCur - pchLine, pszWhat); + } + else + ScmFixManually(pState, "%u:%zu: Invalid UTF-8 encoding: %Rrc\n", iLine, pchCur - pchLine, rc); + } + } + + return kScmUnmodified; +} + + + +/********************************************************************************************************************************* +* Copyright & License * +*********************************************************************************************************************************/ + +/** + * Compares two strings word-by-word, ignoring spaces, punctuation and case. + * + * Assumes ASCII strings. + * + * @returns true if they match, false if not. + * @param psz1 The first string. This is typically the known one. + * @param psz2 The second string. This is typically the unknown one, + * which is why we return a next pointer for this one. + * @param ppsz2Next Where to return the next part of the 2nd string. If + * this is NULL, the whole string must match. + */ +static bool IsEqualWordByWordIgnoreCase(const char *psz1, const char *psz2, const char **ppsz2Next) +{ + for (;;) + { + /* Try compare raw strings first. */ + char ch1 = *psz1; + char ch2 = *psz2; + if ( ch1 == ch2 + || RT_C_TO_LOWER(ch1) == RT_C_TO_LOWER(ch2)) + { + if (ch1) + { + psz1++; + psz2++; + } + else + { + if (ppsz2Next) + *ppsz2Next = psz2; + return true; + } + } + else + { + /* Try skip spaces an punctuation. */ + while ( RT_C_IS_SPACE(ch1) + || RT_C_IS_PUNCT(ch1)) + ch1 = *++psz1; + + if (ch1 == '\0' && ppsz2Next) + { + *ppsz2Next = psz2; + return true; + } + + while ( RT_C_IS_SPACE(ch2) + || RT_C_IS_PUNCT(ch2)) + ch2 = *++psz2; + + if ( ch1 != ch2 + && RT_C_TO_LOWER(ch1) != RT_C_TO_LOWER(ch2)) + { + if (ppsz2Next) + *ppsz2Next = psz2; + return false; + } + } + } +} + +/** + * Looks for @a pszFragment anywhere in @a pszText, ignoring spaces, punctuation + * and case. + * + * @returns true if found, false if not. + * @param pszText The haystack to search in. + * @param cchText The length @a pszText. + * @param pszFragment The needle to search for. + * @param ppszStart Where to return the address in @a pszText where + * the fragment was found. Optional. + * @param ppszNext Where to return the pointer to the first char in + * @a pszText after the fragment. Optional. + * + * @remarks First character of @a pszFragment must be an 7-bit ASCII character! + * This character must not be space or punctuation. + */ +static bool scmContainsWordByWordIgnoreCase(const char *pszText, size_t cchText, const char *pszFragment, + const char **ppszStart, const char **ppszNext) +{ + Assert(!((unsigned)*pszFragment & 0x80)); + Assert(pszText[cchText] == '\0'); + Assert(!RT_C_IS_BLANK(*pszFragment)); + Assert(!RT_C_IS_PUNCT(*pszFragment)); + + char chLower = RT_C_TO_LOWER(*pszFragment); + char chUpper = RT_C_TO_UPPER(*pszFragment); + for (;;) + { + const char *pszHit = (const char *)memchr(pszText, chLower, cchText); + const char *pszHit2 = (const char *)memchr(pszText, chUpper, cchText); + if (!pszHit && !pszHit2) + { + if (ppszStart) + *ppszStart = NULL; + if (ppszNext) + *ppszNext = NULL; + return false; + } + + if ( pszHit == NULL + || ( pszHit2 != NULL + && ((uintptr_t)pszHit2 < (uintptr_t)pszHit)) ) + pszHit = pszHit2; + + const char *pszNext; + if (IsEqualWordByWordIgnoreCase(pszFragment, pszHit, &pszNext)) + { + if (ppszStart) + *ppszStart = pszHit; + if (ppszNext) + *ppszNext = pszNext; + return true; + } + + cchText -= pszHit - pszText + 1; + pszText = pszHit + 1; + } +} + + +/** + * Counts the number of lines in the given substring. + * + * @returns The number of lines. + * @param psz The start of the substring. + * @param cch The length of the substring. + */ +static uint32_t CountLinesInSubstring(const char *psz, size_t cch) +{ + uint32_t cLines = 0; + for (;;) + { + const char *pszEol = (const char *)memchr(psz, '\n', cch); + if (pszEol) + cLines++; + else + return cLines + (*psz != '\0'); + cch -= pszEol + 1 - psz; + if (!cch) + return cLines; + psz = pszEol + 1; + } +} + + +/** + * Comment parser callback for locating copyright and license. + */ +static DECLCALLBACK(int) +rewrite_Copyright_CommentCallback(PCSCMCOMMENTINFO pInfo, const char *pszBody, size_t cchBody, void *pvUser) +{ + PSCMCOPYRIGHTINFO pState = (PSCMCOPYRIGHTINFO)pvUser; + Assert(strlen(pszBody) == cchBody); + //RTPrintf("--- comment at %u, type %u ---\n%s\n--- end ---\n", pInfo->iLineStart, pInfo->enmType, pszBody); + ScmVerbose(pState->pState, 5, + "--- comment at %u col %u, %u lines, type %u, %u lines before body, %u lines after body\n", + pInfo->iLineStart, pInfo->offStart, pInfo->iLineEnd - pInfo->iLineStart + 1, pInfo->enmType, + pInfo->cBlankLinesBefore, pInfo->cBlankLinesAfter); + + pState->cComments++; + + uint32_t iLine = pInfo->iLineStart + pInfo->cBlankLinesBefore; + + /* + * Look for a 'contributed by' or 'includes contributions from' line, these + * comes first when present. + */ + const char *pchContributedBy = NULL; + size_t cchContributedBy = 0; + size_t cBlankLinesAfterContributedBy = 0; + if ( pState->pszContributedBy == NULL + && ( pState->iLineCopyright == UINT32_MAX + || pState->iLineLicense == UINT32_MAX) + && ( ( cchBody > sizeof("Contributed by") + && RTStrNICmp(pszBody, RT_STR_TUPLE("contributed by")) == 0) + || ( cchBody > sizeof("Includes contributions from") + && RTStrNICmp(pszBody, RT_STR_TUPLE("Includes contributions from")) == 0) ) ) + { + const char *pszNextLine = (const char *)memchr(pszBody, '\n', cchBody); + while (pszNextLine && pszNextLine[1] != '\n') + pszNextLine = (const char *)memchr(pszNextLine + 1, '\n', cchBody); + if (pszNextLine) + { + pchContributedBy = pszBody; + cchContributedBy = pszNextLine - pszBody; + + /* Skip the copyright line and any blank lines following it. */ + cchBody -= cchContributedBy + 1; + pszBody = pszNextLine + 1; + iLine += 1; + while (*pszBody == '\n') + { + pszBody++; + cchBody--; + iLine++; + cBlankLinesAfterContributedBy++; + } + } + } + + /* + * Look for the copyright line. + */ + bool fFoundCopyright = false; + uint32_t cBlankLinesAfterCopyright = 0; + if ( pState->iLineCopyright == UINT32_MAX + && cchBody > sizeof("Copyright") + RT_MIN(sizeof(g_szCopyrightHolder), sizeof(g_szOldCopyrightHolder)) + && RTStrNICmp(pszBody, RT_STR_TUPLE("copyright")) == 0) + { + const char *pszNextLine = (const char *)memchr(pszBody, '\n', cchBody); + + /* Oracle copyright? */ + const char *pszEnd = pszNextLine ? pszNextLine : &pszBody[cchBody]; + while (RT_C_IS_SPACE(pszEnd[-1])) + pszEnd--; + if ( ( (uintptr_t)(pszEnd - pszBody) > sizeof(g_szCopyrightHolder) + && (*(unsigned char *)(pszEnd - sizeof(g_szCopyrightHolder) + 1) & 0x80) == 0 /* to avoid annoying assertion */ + && RTStrNICmp(pszEnd - sizeof(g_szCopyrightHolder) + 1, RT_STR_TUPLE(g_szCopyrightHolder)) == 0) + || ( (uintptr_t)(pszEnd - pszBody) > sizeof(g_szOldCopyrightHolder) + && (*(unsigned char *)(pszEnd - sizeof(g_szOldCopyrightHolder) + 1) & 0x80) == 0 /* to avoid annoying assertion */ + && RTStrNICmp(pszEnd - sizeof(g_szOldCopyrightHolder) + 1, RT_STR_TUPLE(g_szOldCopyrightHolder)) == 0) ) + { + /* Parse out the year(s). */ + const char *psz = pszBody + sizeof("copyright"); + while ((uintptr_t)psz < (uintptr_t)pszEnd && !RT_C_IS_DIGIT(*psz)) + psz++; + if (RT_C_IS_DIGIT(*psz)) + { + char *pszNext; + int rc = RTStrToUInt32Ex(psz, &pszNext, 10, &pState->uFirstYear); + if ( RT_SUCCESS(rc) + && rc != VWRN_NUMBER_TOO_BIG + && rc != VWRN_NEGATIVE_UNSIGNED) + { + if ( pState->uFirstYear < 1975 + || pState->uFirstYear > 3000) + { + char *pszCopy = RTStrDupN(pszBody, pszEnd - pszBody); + RTStrPurgeEncoding(pszCopy); + ScmError(pState->pState, VERR_OUT_OF_RANGE, "Copyright year is out of range: %u ('%s')\n", + pState->uFirstYear, pszCopy); + RTStrFree(pszCopy); + pState->uFirstYear = UINT32_MAX; + } + + while (RT_C_IS_SPACE(*pszNext)) + pszNext++; + if (*pszNext == '-') + { + do + pszNext++; + while (RT_C_IS_SPACE(*pszNext)); + rc = RTStrToUInt32Ex(pszNext, &pszNext, 10, &pState->uLastYear); + if ( RT_SUCCESS(rc) + && rc != VWRN_NUMBER_TOO_BIG + && rc != VWRN_NEGATIVE_UNSIGNED) + { + if ( pState->uLastYear < 1975 + || pState->uLastYear > 3000) + { + char *pszCopy = RTStrDupN(pszBody, pszEnd - pszBody); + RTStrPurgeEncoding(pszCopy); + ScmError(pState->pState, VERR_OUT_OF_RANGE, "Second copyright year is out of range: %u ('%s')\n", + pState->uLastYear, pszCopy); + RTStrFree(pszCopy); + pState->uLastYear = UINT32_MAX; + } + else if (pState->uFirstYear > pState->uLastYear) + { + char *pszCopy = RTStrDupN(pszBody, pszEnd - pszBody); + RTStrPurgeEncoding(pszCopy); + RTMsgWarning("Copyright years switched(?): '%s'\n", pszCopy); + RTStrFree(pszCopy); + uint32_t iTmp = pState->uLastYear; + pState->uLastYear = pState->uFirstYear; + pState->uFirstYear = iTmp; + } + } + else + { + pState->uLastYear = UINT32_MAX; + char *pszCopy = RTStrDupN(pszBody, pszEnd - pszBody); + RTStrPurgeEncoding(pszCopy); + ScmError(pState->pState, RT_SUCCESS(rc) ? -rc : rc, + "Failed to parse second copyright year: '%s'\n", pszCopy); + RTMemFree(pszCopy); + } + } + else if (*pszNext != g_szCopyrightHolder[0]) + { + char *pszCopy = RTStrDupN(pszBody, pszEnd - pszBody); + RTStrPurgeEncoding(pszCopy); + ScmError(pState->pState, VERR_PARSE_ERROR, + "Failed to parse copyright: '%s'\n", pszCopy); + RTMemFree(pszCopy); + } else + pState->uLastYear = pState->uFirstYear; + } + else + { + pState->uFirstYear = UINT32_MAX; + char *pszCopy = RTStrDupN(pszBody, pszEnd - pszBody); + RTStrPurgeEncoding(pszCopy); + ScmError(pState->pState, RT_SUCCESS(rc) ? -rc : rc, + "Failed to parse copyright year: '%s'\n", pszCopy); + RTMemFree(pszCopy); + } + } + + /* The copyright comment must come before the license. */ + if (pState->iLineLicense != UINT32_MAX) + ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright (line %u) must come before the license (line %u)!\n", + iLine, pState->iLineLicense); + + /* In C/C++ code, this must be a multiline comment. While in python it + must be a */ + if (pState->enmCommentStyle == kScmCommentStyle_C && pInfo->enmType != kScmCommentType_MultiLine) + ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright must appear in a multiline comment (no doxygen stuff)\n"); + else if (pState->enmCommentStyle == kScmCommentStyle_Python && pInfo->enmType != kScmCommentType_DocString) + ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright must appear in a doc-string\n"); + + /* The copyright must be followed by the license. */ + if (!pszNextLine) + ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright should be followed by the license text!\n"); + + /* Quit if we've flagged a failure. */ + if (RT_FAILURE(pState->pState->rc)) + return VERR_CALLBACK_RETURN; + + /* Check if it's well formed and up to date. */ + char szWellFormed[256]; + size_t cchWellFormed; + if (pState->uFirstYear == pState->uLastYear) + cchWellFormed = RTStrPrintf(szWellFormed, sizeof(szWellFormed), "Copyright (C) %u %s", + pState->uFirstYear, g_szCopyrightHolder); + else + cchWellFormed = RTStrPrintf(szWellFormed, sizeof(szWellFormed), "Copyright (C) %u-%u %s", + pState->uFirstYear, pState->uLastYear, g_szCopyrightHolder); + pState->fUpToDateCopyright = pState->uLastYear == g_uYear; + pState->iLineCopyright = iLine; + pState->fWellFormedCopyright = cchWellFormed == (uintptr_t)(pszEnd - pszBody) + && memcmp(pszBody, szWellFormed, cchWellFormed) == 0; + if (!pState->fWellFormedCopyright) + ScmVerbose(pState->pState, 1, "* copyright isn't well formed\n"); + + /* If there wasn't exactly one blank line before the comment, trigger a rewrite. */ + if (pInfo->cBlankLinesBefore != 1) + { + ScmVerbose(pState->pState, 1, "* copyright comment is preceeded by %u blank lines instead of 1\n", + pInfo->cBlankLinesBefore); + pState->fWellFormedCopyright = false; + } + + /* If the comment doesn't start in column 1, trigger rewrite. */ + if (pInfo->offStart != 0) + { + ScmVerbose(pState->pState, 1, "* copyright comment starts in column %u instead of 1\n", pInfo->offStart + 1); + pState->fWellFormedCopyright = false; + /** @todo check that there isn't any code preceeding the comment. */ + } + + if (pchContributedBy) + { + pState->pszContributedBy = RTStrDupN(pchContributedBy, cchContributedBy); + if (cBlankLinesAfterContributedBy != 1) + { + ScmVerbose(pState->pState, 1, "* %u blank lines between contributed by and copyright, should be 1\n", + cBlankLinesAfterContributedBy); + pState->fWellFormedCopyright = false; + } + } + + fFoundCopyright = true; + ScmVerbose(pState->pState, 3, "oracle copyright %u-%u: up-to-date=%RTbool well-formed=%RTbool\n", + pState->uFirstYear, pState->uLastYear, pState->fUpToDateCopyright, pState->fWellFormedCopyright); + } + else + { + char *pszCopy = RTStrDupN(pszBody, pszEnd - pszBody); + RTStrPurgeEncoding(pszCopy); + ScmVerbose(pState->pState, 3, "not oracle copyright: '%s'\n", pszCopy); + RTStrFree(pszCopy); + } + + if (!pszNextLine) + return VINF_SUCCESS; + + /* Skip the copyright line and any blank lines following it. */ + cchBody -= pszNextLine - pszBody + 1; + pszBody = pszNextLine + 1; + iLine += 1; + while (*pszBody == '\n') + { + pszBody++; + cchBody--; + iLine++; + cBlankLinesAfterCopyright++; + } + + /* + * If we have a based-on-mit scenario, check for the lead in now and + * complain if not found. + */ + if ( fFoundCopyright + && pState->enmLicenceOpt == kScmLicense_BasedOnMit + && pState->iLineLicense == UINT32_MAX) + { + if (RTStrNICmp(pszBody, RT_STR_TUPLE("This file is based on ")) == 0) + { + /* Take down a comment area which goes up to 'this file is based on'. + The license line and length isn't used but gets set to cover the current line. */ + pState->iLineComment = pInfo->iLineStart; + pState->cLinesComment = iLine - pInfo->iLineStart; + pState->iLineLicense = iLine; + pState->cLinesLicense = 1; + pState->fExternalLicense = true; + pState->fIsCorrectLicense = true; + pState->fWellFormedLicense = true; + + /* Check if we've got a MIT a license here or not. */ + pState->pCurrentLicense = NULL; + do + { + const char *pszEol = (const char *)memchr(pszBody, '\n', cchBody); + if (!pszEol || pszEol[1] == '\0') + { + pszBody += cchBody; + cchBody = 0; + break; + } + cchBody -= pszEol - pszBody + 1; + pszBody = pszEol + 1; + iLine++; + + for (PCSCMLICENSETEXT pCur = pState->paLicenses; pCur->cch > 0; pCur++) + { + const char *pszNext; + if ( pCur->cch <= cchBody + 32 /* (+ 32 since we don't compare spaces and punctuation) */ + && IsEqualWordByWordIgnoreCase(pCur->psz, pszBody, &pszNext)) + { + pState->pCurrentLicense = pCur; + break; + } + } + } while (!pState->pCurrentLicense); + if (!pState->pCurrentLicense) + ScmError(pState->pState, VERR_NOT_FOUND, "Could not find the based-on license!\n"); + else if (pState->pCurrentLicense->enmType != kScmLicenseType_Mit) + ScmError(pState->pState, VERR_NOT_FOUND, "The based-on license is not MIT (%.32s...)\n", + pState->pCurrentLicense->psz); + } + else + ScmError(pState->pState, VERR_WRONG_ORDER, "Expected 'This file is based on ...' after our copyright!\n"); + return VINF_SUCCESS; + } + } + + /* + * Look for LGPL like text in the comment. + */ + if (pState->fCheckforLgpl && cchBody > 128) + { + /* We look for typical LGPL notices. */ + if (pState->iLineLgplNotice == UINT32_MAX) + { + static const char * const s_apszFragments[] = + { + "under the terms of the GNU Lesser General Public License", + }; + for (unsigned i = 0; i < RT_ELEMENTS(s_apszFragments); i++) + if (scmContainsWordByWordIgnoreCase(pszBody, cchBody, s_apszFragments[i], NULL, NULL)) + { + pState->iLineLgplNotice = iLine; + pState->iLineAfterLgplComment = pInfo->iLineEnd + 1; + ScmVerbose(pState->pState, 3, "Found LGPL notice at %u\n", iLine); + break; + } + } + + if ( pState->iLineLgplDisclaimer == UINT32_MAX + && scmContainsWordByWordIgnoreCase(pszBody, cchBody, g_szLgplDisclaimer, NULL, NULL)) + { + pState->iLineLgplDisclaimer = iLine; + ScmVerbose(pState->pState, 3, "Found LGPL disclaimer at %u\n", iLine); + } + } + + /* + * Look for the license text. + */ + if (pState->iLineLicense == UINT32_MAX) + { + for (PCSCMLICENSETEXT pCur = pState->paLicenses; pCur->cch > 0; pCur++) + { + const char *pszNext; + if ( pCur->cch <= cchBody + 32 /* (+ 32 since we don't compare spaces and punctuation) */ + && IsEqualWordByWordIgnoreCase(pCur->psz, pszBody, &pszNext)) + { + while ( RT_C_IS_SPACE(*pszNext) + || (RT_C_IS_PUNCT(*pszNext) && *pszNext != '-')) + pszNext++; + + uint32_t cDashes = 0; + while (*pszNext == '-') + cDashes++, pszNext++; + bool fExternal = cDashes > 10; + + if ( *pszNext == '\0' + || fExternal) + { + /* In C/C++ code, this must be a multiline comment. While in python it + must be a doc-string. */ + if (pState->enmCommentStyle == kScmCommentStyle_C && pInfo->enmType != kScmCommentType_MultiLine) + ScmError(pState->pState, VERR_WRONG_ORDER, "License must appear in a multiline comment (no doxygen stuff)\n"); + else if (pState->enmCommentStyle == kScmCommentStyle_Python && pInfo->enmType != kScmCommentType_DocString) + ScmError(pState->pState, VERR_WRONG_ORDER, "License must appear in a doc-string\n"); + + /* Quit if we've flagged a failure. */ + if (RT_FAILURE(pState->pState->rc)) + return VERR_CALLBACK_RETURN; + + /* Record it. */ + pState->iLineLicense = iLine; + pState->cLinesLicense = CountLinesInSubstring(pszBody, pszNext - pszBody) - fExternal; + pState->pCurrentLicense = pCur; + pState->fExternalLicense = fExternal; + pState->fIsCorrectLicense = pCur == pState->pExpectedLicense; + pState->fWellFormedLicense = memcmp(pszBody, pCur->psz, pCur->cch - 1) == 0; + if (!pState->fWellFormedLicense) + ScmVerbose(pState->pState, 1, "* license text isn't well-formed\n"); + + /* If there was more than one blank line between the copyright and the + license text, extend the license text area and force a rewrite of it. */ + if (cBlankLinesAfterCopyright > 1) + { + ScmVerbose(pState->pState, 1, "* %u blank lines between copyright and license text, instead of 1\n", + cBlankLinesAfterCopyright); + pState->iLineLicense -= cBlankLinesAfterCopyright - 1; + pState->cLinesLicense += cBlankLinesAfterCopyright - 1; + pState->fWellFormedLicense = false; + } + + /* If there was more than one blank line after the license, trigger a rewrite. */ + if (!fExternal && pInfo->cBlankLinesAfter != 1) + { + ScmVerbose(pState->pState, 1, "* copyright comment is followed by %u blank lines instead of 1\n", + pInfo->cBlankLinesAfter); + pState->fWellFormedLicense = false; + } + + /** @todo Check that the last comment line doesn't have any code on it. */ + /** @todo Check that column 2 contains '*' for C/C++ files. */ + + ScmVerbose(pState->pState, 3, + "Found license %d/%d at %u..%u: is-correct=%RTbool well-formed=%RTbool external-part=%RTbool open-source=%RTbool\n", + pCur->enmType, pCur->enmOpt, pState->iLineLicense, pState->iLineLicense + pState->cLinesLicense, + pState->fIsCorrectLicense, pState->fWellFormedLicense, + pState->fExternalLicense, pState->fOpenSource); + + if (fFoundCopyright) + { + pState->iLineComment = pInfo->iLineStart; + pState->cLinesComment = (fExternal ? pState->iLineLicense + pState->cLinesLicense : pInfo->iLineEnd + 1) + - pInfo->iLineStart; + } + else + ScmError(pState->pState, VERR_WRONG_ORDER, "License should be preceeded by the copyright!\n"); + break; + } + } + } + } + + if (fFoundCopyright && pState->iLineLicense == UINT32_MAX) + ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright should be followed by the license text!\n"); + + /* + * Stop looking for stuff after 100 comments. + */ + if (pState->cComments > 100) + return VERR_CALLBACK_RETURN; + return VINF_SUCCESS; +} + +/** + * Writes comment body text. + * + * @returns Stream status. + * @param pOut The output stream. + * @param pszText The text to write. + * @param cchText The length of the text. + * @param enmCommentStyle The comment style. + * @param enmEol The EOL style. + */ +static int scmWriteCommentBody(PSCMSTREAM pOut, const char *pszText, size_t cchText, + SCMCOMMENTSTYLE enmCommentStyle, SCMEOL enmEol) +{ + Assert(pszText[cchText - 1] == '\n'); + Assert(pszText[cchText - 2] != '\n'); + NOREF(cchText); + do + { + const char *pszEol = strchr(pszText, '\n'); + if (pszEol != pszText) + { + ScmStreamWrite(pOut, g_aCopyrightCommentPrefix[enmCommentStyle].psz, + g_aCopyrightCommentPrefix[enmCommentStyle].cch); + ScmStreamWrite(pOut, pszText, pszEol - pszText); + ScmStreamPutEol(pOut, enmEol); + } + else + ScmStreamPutLine(pOut, g_aCopyrightCommentEmpty[enmCommentStyle].psz, + g_aCopyrightCommentEmpty[enmCommentStyle].cch, enmEol); + pszText = pszEol + 1; + } while (*pszText != '\0'); + return ScmStreamGetStatus(pOut); +} + + +/** + * Updates the copyright year and/or license text. + * + * @returns Modification state. + * @param pState The rewriter state. + * @param pIn The input stream. + * @param pOut The output stream. + * @param pSettings The settings. + * @param enmCommentStyle The comment style used by the file. + */ +static SCMREWRITERRES rewrite_Copyright_Common(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, + PCSCMSETTINGSBASE pSettings, SCMCOMMENTSTYLE enmCommentStyle) +{ + if ( !pSettings->fUpdateCopyrightYear + && pSettings->enmUpdateLicense == kScmLicense_LeaveAlone) + return kScmUnmodified; + + /* + * Try locate the relevant comments. + */ + SCMCOPYRIGHTINFO Info = + { + /*.pState = */ pState, + /*.enmCommentStyle = */ enmCommentStyle, + + /*.cComments = */ 0, + + /*.pszContributedBy = */ NULL, + + /*.iLineComment = */ UINT32_MAX, + /*.cLinesComment = */ 0, + + /*.iLineCopyright = */ UINT32_MAX, + /*.uFirstYear = */ UINT32_MAX, + /*.uLastYear = */ UINT32_MAX, + /*.fWellFormedCopyright = */ false, + /*.fUpToDateCopyright = */ false, + + /*.fOpenSource = */ true, + /*.pExpectedLicense = */ NULL, + /*.paLicenses = */ pSettings->enmUpdateLicense != kScmLicense_Mit + && pSettings->enmUpdateLicense != kScmLicense_BasedOnMit + ? &g_aLicenses[0] : &g_aLicensesWithMit[0], + /*.enmLicenceOpt = */ pSettings->enmUpdateLicense, + /*.iLineLicense = */ UINT32_MAX, + /*.cLinesLicense = */ 0, + /*.pCurrentLicense = */ NULL, + /*.fIsCorrectLicense = */ false, + /*.fWellFormedLicense = */ false, + /*.fExternalLicense = */ false, + + /*.fCheckForLgpl = */ true, + /*.iLineLgplNotice = */ UINT32_MAX, + /*.iLineAfterLgplComment = */ UINT32_MAX, + /*.iLineLgplDisclaimer = */ UINT32_MAX, + }; + + /* Figure Info.fOpenSource and the desired license: */ + char *pszSyncProcess; + int rc = ScmSvnQueryProperty(pState, "svn:sync-process", &pszSyncProcess); + if (RT_SUCCESS(rc)) + { + Info.fOpenSource = strcmp(RTStrStrip(pszSyncProcess), "export") == 0; + RTStrFree(pszSyncProcess); + } + else if (rc == VERR_NOT_FOUND) + Info.fOpenSource = false; + else + return ScmError(pState, rc, "ScmSvnQueryProperty(svn:sync-process): %Rrc\n", rc); + + Info.pExpectedLicense = Info.paLicenses; + if (Info.fOpenSource) + { + if ( pSettings->enmUpdateLicense != kScmLicense_Mit + && pSettings->enmUpdateLicense != kScmLicense_BasedOnMit) + while (Info.pExpectedLicense->enmOpt != pSettings->enmUpdateLicense) + Info.pExpectedLicense++; + else + Assert(Info.pExpectedLicense->enmOpt == kScmLicense_Mit); + } + else + while (Info.pExpectedLicense->enmType != kScmLicenseType_Confidential) + Info.pExpectedLicense++; + + /* Scan the comments. */ + rc = ScmEnumerateComments(pIn, enmCommentStyle, rewrite_Copyright_CommentCallback, &Info); + if ( (rc == VERR_CALLBACK_RETURN || RT_SUCCESS(rc)) + && RT_SUCCESS(pState->rc)) + { + /* + * Do conformity checks. + */ + bool fAddLgplDisclaimer = false; + if (Info.fCheckforLgpl) + { + if ( Info.iLineLgplNotice != UINT32_MAX + && Info.iLineLgplDisclaimer == UINT32_MAX) + { + if (!pSettings->fLgplDisclaimer) /** @todo reconcile options with common sense. */ + ScmError(pState, VERR_NOT_FOUND, "LGPL licence notice on line %u, but no LGPL disclaimer was found!\n", + Info.iLineLgplNotice + 1); + else + { + ScmVerbose(pState, 1, "* Need to add LGPL disclaimer\n"); + fAddLgplDisclaimer = true; + } + } + else if ( Info.iLineLgplNotice == UINT32_MAX + && Info.iLineLgplDisclaimer != UINT32_MAX) + ScmError(pState, VERR_NOT_FOUND, "LGPL disclaimer on line %u, but no LGPL copyright notice!\n", + Info.iLineLgplDisclaimer + 1); + } + + if (!pSettings->fExternalCopyright) + { + if (Info.iLineCopyright == UINT32_MAX) + ScmError(pState, VERR_NOT_FOUND, "Missing copyright!\n"); + if (Info.iLineLicense == UINT32_MAX) + ScmError(pState, VERR_NOT_FOUND, "Missing license!\n"); + } + else if (Info.iLineCopyright != UINT32_MAX) + ScmError(pState, VERR_NOT_FOUND, + "Marked as external copyright only, but found non-external copyright statement at line %u!\n", + Info.iLineCopyright + 1); + + + if (RT_SUCCESS(pState->rc)) + { + /* + * Do we need to make any changes? + */ + bool fUpdateCopyright = !pSettings->fExternalCopyright + && ( !Info.fWellFormedCopyright + || (!Info.fUpToDateCopyright && pSettings->fUpdateCopyrightYear)); + bool fUpdateLicense = !pSettings->fExternalCopyright + && Info.enmLicenceOpt != kScmLicense_LeaveAlone + && ( !Info.fWellFormedLicense + || !Info.fIsCorrectLicense); + if ( fUpdateCopyright + || fUpdateLicense + || fAddLgplDisclaimer) + { + Assert(Info.iLineComment != UINT32_MAX); + Assert(Info.cLinesComment > 0); + + /* + * Okay, do the work. + */ + ScmStreamRewindForReading(pIn); + + if (pSettings->fUpdateCopyrightYear) + Info.uLastYear = g_uYear; + + uint32_t iLine = 0; + SCMEOL enmEol; + size_t cchLine; + const char *pchLine; + while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL) + { + if ( iLine == Info.iLineComment + && (fUpdateCopyright || fUpdateLicense) ) + { + /* Leading blank line. */ + ScmStreamPutLine(pOut, g_aCopyrightCommentStart[enmCommentStyle].psz, + g_aCopyrightCommentStart[enmCommentStyle].cch, enmEol); + + /* Contributed by someone? */ + if (Info.pszContributedBy) + { + const char *psz = Info.pszContributedBy; + for (;;) + { + const char *pszEol = strchr(psz, '\n'); + size_t cchContribLine = pszEol ? pszEol - psz : strlen(psz); + ScmStreamWrite(pOut, g_aCopyrightCommentPrefix[enmCommentStyle].psz, + g_aCopyrightCommentPrefix[enmCommentStyle].cch); + ScmStreamWrite(pOut, psz, cchContribLine); + ScmStreamPutEol(pOut, enmEol); + if (!pszEol) + break; + psz = pszEol + 1; + } + + ScmStreamPutLine(pOut, g_aCopyrightCommentEmpty[enmCommentStyle].psz, + g_aCopyrightCommentEmpty[enmCommentStyle].cch, enmEol); + } + + /* Write the copyright comment line. */ + ScmStreamWrite(pOut, g_aCopyrightCommentPrefix[enmCommentStyle].psz, + g_aCopyrightCommentPrefix[enmCommentStyle].cch); + + char szCopyright[256]; + size_t cchCopyright; + if (Info.uFirstYear == Info.uLastYear) + cchCopyright = RTStrPrintf(szCopyright, sizeof(szCopyright), "Copyright (C) %u %s", + Info.uFirstYear, g_szCopyrightHolder); + else + cchCopyright = RTStrPrintf(szCopyright, sizeof(szCopyright), "Copyright (C) %u-%u %s", + Info.uFirstYear, Info.uLastYear, g_szCopyrightHolder); + + ScmStreamWrite(pOut, szCopyright, cchCopyright); + ScmStreamPutEol(pOut, enmEol); + + if (pSettings->enmUpdateLicense != kScmLicense_BasedOnMit) + { + /* Blank line separating the two. */ + ScmStreamPutLine(pOut, g_aCopyrightCommentEmpty[enmCommentStyle].psz, + g_aCopyrightCommentEmpty[enmCommentStyle].cch, enmEol); + + /* Write the license text. */ + scmWriteCommentBody(pOut, Info.pExpectedLicense->psz, Info.pExpectedLicense->cch, + enmCommentStyle, enmEol); + + /* Final comment line. */ + if (!Info.fExternalLicense) + ScmStreamPutLine(pOut, g_aCopyrightCommentEnd[enmCommentStyle].psz, + g_aCopyrightCommentEnd[enmCommentStyle].cch, enmEol); + } + else + Assert(Info.fExternalLicense); + + /* Skip the copyright and license text in the input file. */ + rc = ScmStreamGetStatus(pOut); + if (RT_SUCCESS(rc)) + { + iLine = Info.iLineComment + Info.cLinesComment; + rc = ScmStreamSeekByLine(pIn, iLine); + } + } + /* + * Add LGPL disclaimer? + */ + else if ( iLine == Info.iLineAfterLgplComment + && fAddLgplDisclaimer) + { + ScmStreamPutEol(pOut, enmEol); + ScmStreamPutLine(pOut, g_aCopyrightCommentStart[enmCommentStyle].psz, + g_aCopyrightCommentStart[enmCommentStyle].cch, enmEol); + scmWriteCommentBody(pOut, g_szLgplDisclaimer, sizeof(g_szLgplDisclaimer) - 1, + enmCommentStyle, enmEol); + ScmStreamPutLine(pOut, g_aCopyrightCommentEnd[enmCommentStyle].psz, + g_aCopyrightCommentEnd[enmCommentStyle].cch, enmEol); + + /* put the actual line */ + rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol); + iLine++; + } + else + { + rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol); + iLine++; + } + if (RT_FAILURE(rc)) + { + RTStrFree(Info.pszContributedBy); + return kScmUnmodified; + } + } /* for each source line */ + + RTStrFree(Info.pszContributedBy); + return kScmModified; + } + } + } + else + ScmError(pState, rc, "ScmEnumerateComments: %Rrc\n", rc); + NOREF(pState); NOREF(pOut); + RTStrFree(Info.pszContributedBy); + return kScmUnmodified; +} + + +/** Copyright updater for C-style comments. */ +SCMREWRITERRES rewrite_Copyright_CstyleComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings) +{ + return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_C); +} + +/** Copyright updater for hash-prefixed comments. */ +SCMREWRITERRES rewrite_Copyright_HashComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings) +{ + return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Hash); +} + +/** Copyright updater for REM-prefixed comments. */ +SCMREWRITERRES rewrite_Copyright_RemComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings) +{ + return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, determineBatchFileCommentStyle(pIn)); +} + +/** Copyright updater for python comments. */ +SCMREWRITERRES rewrite_Copyright_PythonComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings) +{ + return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Python); +} + +/** Copyright updater for semicolon-prefixed comments. */ +SCMREWRITERRES rewrite_Copyright_SemicolonComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, + PCSCMSETTINGSBASE pSettings) +{ + return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Semicolon); +} + +/** Copyright updater for sql comments. */ +SCMREWRITERRES rewrite_Copyright_SqlComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings) +{ + return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Sql); +} + +/** Copyright updater for tick-prefixed comments. */ +SCMREWRITERRES rewrite_Copyright_TickComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings) +{ + return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Tick); +} + +/** Copyright updater for XML comments. */ +SCMREWRITERRES rewrite_Copyright_XmlComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings) +{ + return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Xml); +} + + + +/********************************************************************************************************************************* +* Flower Box Section Markers * +*********************************************************************************************************************************/ + +static bool isFlowerBoxSectionMarker(PSCMSTREAM pIn, const char *pchLine, size_t cchLine, uint32_t cchWidth, + const char **ppchText, size_t *pcchText, bool *pfNeedFixing) +{ + *ppchText = NULL; + *pcchText = 0; + *pfNeedFixing = false; + + /* + * The first line. + */ + if (pchLine[0] != '/') + return false; + size_t offLine = 1; + while (offLine < cchLine && pchLine[offLine] == '*') + offLine++; + if (offLine < 20) /* (Code below depend on a reasonable minimum here.) */ + return false; + while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine])) + offLine++; + if (offLine != cchLine) + return false; + + size_t const cchBox = cchLine; + *pfNeedFixing = cchBox != cchWidth; + + /* + * The next line, extracting the text. + */ + SCMEOL enmEol; + pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol); + if (cchLine < cchBox - 3) + return false; + + offLine = 0; + if (RT_C_IS_BLANK(pchLine[0])) + { + *pfNeedFixing = true; + offLine = RT_C_IS_BLANK(pchLine[1]) ? 2 : 1; + } + + if (pchLine[offLine] != '*') + return false; + offLine++; + + if (!RT_C_IS_BLANK(pchLine[offLine + 1])) + return false; + offLine++; + + while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine])) + offLine++; + if (offLine >= cchLine) + return false; + if (!RT_C_IS_UPPER(pchLine[offLine])) + return false; + + if (offLine != 4 || cchLine != cchBox) + *pfNeedFixing = true; + + *ppchText = &pchLine[offLine]; + size_t const offText = offLine; + + /* From the end now. */ + offLine = cchLine - 1; + while (RT_C_IS_BLANK(pchLine[offLine])) + offLine--; + + if (pchLine[offLine] != '*') + return false; + offLine--; + if (!RT_C_IS_BLANK(pchLine[offLine])) + return false; + offLine--; + while (RT_C_IS_BLANK(pchLine[offLine])) + offLine--; + *pcchText = offLine - offText + 1; + + /* + * Third line closes the box. + */ + pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol); + if (cchLine < cchBox - 3) + return false; + + offLine = 0; + if (RT_C_IS_BLANK(pchLine[0])) + { + *pfNeedFixing = true; + offLine = RT_C_IS_BLANK(pchLine[1]) ? 2 : 1; + } + while (offLine < cchLine && pchLine[offLine] == '*') + offLine++; + if (offLine < cchBox - 4) + return false; + + if (pchLine[offLine] != '/') + return false; + offLine++; + + if (offLine != cchBox) + *pfNeedFixing = true; + + while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine])) + offLine++; + if (offLine != cchLine) + return false; + + return true; +} + + +/** + * Flower box marker comments in C and C++ code. + * + * @returns Modification state. + * @param pIn The input stream. + * @param pOut The output stream. + * @param pSettings The settings. + */ +SCMREWRITERRES rewrite_FixFlowerBoxMarkers(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings) +{ + if (!pSettings->fFixFlowerBoxMarkers) + return kScmUnmodified; + + /* + * Work thru the file line by line looking for flower box markers. + */ + size_t cChanges = 0; + size_t cBlankLines = 0; + SCMEOL enmEol; + size_t cchLine; + const char *pchLine; + while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL) + { + /* + * Get a likely match for a first line. + */ + if ( pchLine[0] == '/' + && cchLine > 20 + && pchLine[1] == '*' + && pchLine[2] == '*' + && pchLine[3] == '*') + { + size_t const offSaved = ScmStreamTell(pIn); + char const *pchText; + size_t cchText; + bool fNeedFixing; + bool fIsFlowerBoxSection = isFlowerBoxSectionMarker(pIn, pchLine, cchLine, pSettings->cchWidth, + &pchText, &cchText, &fNeedFixing); + if ( fIsFlowerBoxSection + && ( fNeedFixing + || cBlankLines < pSettings->cMinBlankLinesBeforeFlowerBoxMakers) ) + { + while (cBlankLines < pSettings->cMinBlankLinesBeforeFlowerBoxMakers) + { + ScmStreamPutEol(pOut, enmEol); + cBlankLines++; + } + + ScmStreamPutCh(pOut, '/'); + ScmStreamWrite(pOut, g_szAsterisks, pSettings->cchWidth - 1); + ScmStreamPutEol(pOut, enmEol); + + static const char s_szLead[] = "* "; + ScmStreamWrite(pOut, s_szLead, sizeof(s_szLead) - 1); + ScmStreamWrite(pOut, pchText, cchText); + size_t offCurPlus1 = sizeof(s_szLead) - 1 + cchText + 1; + ScmStreamWrite(pOut, g_szSpaces, offCurPlus1 < pSettings->cchWidth ? pSettings->cchWidth - offCurPlus1 : 1); + ScmStreamPutCh(pOut, '*'); + ScmStreamPutEol(pOut, enmEol); + + ScmStreamWrite(pOut, g_szAsterisks, pSettings->cchWidth - 1); + ScmStreamPutCh(pOut, '/'); + ScmStreamPutEol(pOut, enmEol); + + cChanges++; + cBlankLines = 0; + continue; + } + + int rc = ScmStreamSeekAbsolute(pIn, offSaved); + if (RT_FAILURE(rc)) + return kScmUnmodified; + } + + int rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol); + if (RT_FAILURE(rc)) + return kScmUnmodified; + + /* Do blank line accounting so we can ensure at least two blank lines + before each section marker. */ + if (!isBlankLine(pchLine, cchLine)) + cBlankLines = 0; + else + cBlankLines++; + } + if (cChanges > 0) + ScmVerbose(pState, 2, " * Converted %zu flower boxer markers\n", cChanges); + return cChanges != 0 ? kScmModified : kScmUnmodified; +} + + +/** + * Looks for the start of a todo comment. + * + * @returns Offset into the line of the comment start sequence. + * @param pchLine The line to search. + * @param cchLineBeforeTodo The length of the line before the todo. + * @param pfSameLine Indicates whether it's refering to a statemtn on + * the same line comment (true), or the next + * statement (false). + */ +static size_t findTodoCommentStart(char const *pchLine, size_t cchLineBeforeTodo, bool *pfSameLine) +{ + *pfSameLine = false; + + /* Skip one '@' or '\\'. */ + char ch; + if ( cchLineBeforeTodo > 2 + && ( ((ch = pchLine[cchLineBeforeTodo - 1]) == '@') + || ch == '\\' ) ) + cchLineBeforeTodo--; + + /* Skip blanks. */ + while ( cchLineBeforeTodo > 2 + && RT_C_IS_BLANK(pchLine[cchLineBeforeTodo - 1])) + cchLineBeforeTodo--; + + /* Look for same line indicator. */ + if ( cchLineBeforeTodo > 0 + && pchLine[cchLineBeforeTodo - 1] == '<') + { + *pfSameLine = true; + cchLineBeforeTodo--; + } + + /* Skip *s */ + while ( cchLineBeforeTodo > 1 + && pchLine[cchLineBeforeTodo - 1] == '*') + cchLineBeforeTodo--; + + /* Do we have a comment opening sequence. */ + if ( cchLineBeforeTodo > 0 + && pchLine[cchLineBeforeTodo - 1] == '/' + && ( ( cchLineBeforeTodo >= 2 + && pchLine[cchLineBeforeTodo - 2] == '/') + || pchLine[cchLineBeforeTodo] == '*')) + { + /* Skip slashes at the start. */ + while ( cchLineBeforeTodo > 0 + && pchLine[cchLineBeforeTodo - 1] == '/') + cchLineBeforeTodo--; + + return cchLineBeforeTodo; + } + + return ~(size_t)0; +} + + +/** + * Looks for a TODO or todo in the given line. + * + * @returns Offset into the line of found, ~(size_t)0 if not. + * @param pchLine The line to search. + * @param cchLine The length of the line. + */ +static size_t findTodo(char const *pchLine, size_t cchLine) +{ + if (cchLine >= 4 + 2) + { + /* We don't search the first to chars because we need the start of a comment. + Also, skip the last three chars since we need at least four for a match. */ + size_t const cchLineT = cchLine - 3; + if ( memchr(pchLine + 2, 't', cchLineT - 2) != NULL + || memchr(pchLine + 2, 'T', cchLineT - 2) != NULL) + { + for (size_t off = 2; off < cchLineT; off++) + { + char ch = pchLine[off]; + if ( ( ch != 't' + && ch != 'T') + || ( (ch = pchLine[off + 1]) != 'o' + && ch != 'O') + || ( (ch = pchLine[off + 2]) != 'd' + && ch != 'D') + || ( (ch = pchLine[off + 3]) != 'o' + && ch != 'O') + || ( off + 4 != cchLine + && (ch = pchLine[off + 4]) != ' ' + && ch != '\t' + && ch != ':' /** @todo */ + && (ch != '*' || off + 5 > cchLine || pchLine[off + 5] != '/') /** @todo */ + ) ) + { /* not a hit - likely */ } + else + return off; + } + } + } + return ~(size_t)0; +} + + +/** + * Doxygen todos in C and C++ code. + * + * @returns Modification state. + * @param pState The rewriter state. + * @param pIn The input stream. + * @param pOut The output stream. + * @param pSettings The settings. + */ +SCMREWRITERRES rewrite_Fix_C_and_CPP_Todos(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings) +{ + if (!pSettings->fFixTodos) + return kScmUnmodified; + + /* + * Work thru the file line by line looking for the start of todo comments. + */ + size_t cChanges = 0; + SCMEOL enmEol; + size_t cchLine; + const char *pchLine; + while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL) + { + /* + * Look for the word 'todo' in the line. We're currently only trying + * to catch comments starting with the word todo and adjust the start of + * the doxygen statement. + */ + size_t offTodo = findTodo(pchLine, cchLine); + if ( offTodo != ~(size_t)0 + && offTodo >= 2) + { + /* Work backwards to find the start of the comment. */ + bool fSameLine = false; + size_t offCommentStart = findTodoCommentStart(pchLine, offTodo, &fSameLine); + if (offCommentStart != ~(size_t)0) + { + char szNew[64]; + size_t cchNew = 0; + szNew[cchNew++] = '/'; + szNew[cchNew++] = pchLine[offCommentStart + 1]; + szNew[cchNew++] = pchLine[offCommentStart + 1]; + if (fSameLine) + szNew[cchNew++] = '<'; + szNew[cchNew++] = ' '; + szNew[cchNew++] = '@'; + szNew[cchNew++] = 't'; + szNew[cchNew++] = 'o'; + szNew[cchNew++] = 'd'; + szNew[cchNew++] = 'o'; + + /* Figure out wheter to continue after the @todo statement opening, we'll strip ':' + but need to take into account that we might be at the end of the line before + adding the space. */ + size_t offTodoAfter = offTodo + 4; + if ( offTodoAfter < cchLine + && pchLine[offTodoAfter] == ':') + offTodoAfter++; + if ( offTodoAfter < cchLine + && RT_C_IS_BLANK(pchLine[offTodoAfter])) + offTodoAfter++; + if (offTodoAfter < cchLine) + szNew[cchNew++] = ' '; + + /* Write it out. */ + ScmStreamWrite(pOut, pchLine, offCommentStart); + ScmStreamWrite(pOut, szNew, cchNew); + if (offTodoAfter < cchLine) + ScmStreamWrite(pOut, &pchLine[offTodoAfter], cchLine - offTodoAfter); + ScmStreamPutEol(pOut, enmEol); + + /* Check whether we actually made any changes. */ + if ( cchNew != offTodoAfter - offCommentStart + || memcmp(szNew, &pchLine[offCommentStart], cchNew)) + cChanges++; + continue; + } + } + + int rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol); + if (RT_FAILURE(rc)) + return kScmUnmodified; + } + if (cChanges > 0) + ScmVerbose(pState, 2, " * Converted %zu todo statements.\n", cChanges); + return cChanges != 0 ? kScmModified : kScmUnmodified; +} + + +/** + * Tries to parse a C/C++ preprocessor include directive. + * + * This is resonably forgiving and expects sane input. + * + * @retval kScmIncludeDir_Invalid if not a valid include directive. + * @retval kScmIncludeDir_Quoted + * @retval kScmIncludeDir_Bracketed + * @retval kScmIncludeDir_Macro + * + * @param pState The rewriter state (for repording malformed + * directives). + * @param pchLine The line to try parse as an include statement. + * @param cchLine The line length. + * @param ppchFilename Where to return the pointer to the filename part. + * @param pcchFilename Where to return the length of the filename. + */ +SCMINCLUDEDIR ScmMaybeParseCIncludeLine(PSCMRWSTATE pState, const char *pchLine, size_t cchLine, + const char **ppchFilename, size_t *pcchFilename) +{ + /* Skip leading spaces: */ + while (cchLine > 0 && RT_C_IS_BLANK(*pchLine)) + cchLine--, pchLine++; + + /* Check for '#': */ + if (cchLine > 0 && *pchLine == '#') + { + cchLine--; + pchLine++; + + /* Skip spaces after '#' (optional): */ + while (cchLine > 0 && RT_C_IS_BLANK(*pchLine)) + cchLine--, pchLine++; + + /* Check for 'include': */ + static char const s_szInclude[] = "include"; + if ( cchLine >= sizeof(s_szInclude) + && memcmp(pchLine, RT_STR_TUPLE(s_szInclude)) == 0) + { + cchLine -= sizeof(s_szInclude) - 1; + pchLine += sizeof(s_szInclude) - 1; + + /* Skip spaces after 'include' word (optional): */ + while (cchLine > 0 && RT_C_IS_BLANK(*pchLine)) + cchLine--, pchLine++; + if (cchLine > 0) + { + /* Quoted or bracketed? */ + char const chFirst = *pchLine; + if (chFirst == '"' || chFirst == '<') + { + cchLine--; + pchLine++; + const char *pchEnd = (const char *)memchr(pchLine, chFirst == '"' ? '"' : '>', cchLine); + if (pchEnd) + { + if (ppchFilename) + *ppchFilename = pchLine; + if (pcchFilename) + *pcchFilename = pchEnd - pchLine; + return chFirst == '"' ? kScmIncludeDir_Quoted : kScmIncludeDir_Bracketed; + } + ScmError(pState, VERR_PARSE_ERROR, "Unbalanced #include filename %s: %.*s\n", + chFirst == '"' ? "quotes" : "brackets" , cchLine, pchLine); + } + /* C prepreprocessor macro? */ + else if (ScmIsCIdentifierLeadChar(chFirst)) + { + size_t cchFilename = 1; + while ( cchFilename < cchLine + && ScmIsCIdentifierChar(pchLine[cchFilename])) + cchFilename++; + if (ppchFilename) + *ppchFilename = pchLine; + if (pcchFilename) + *pcchFilename = cchFilename; + return kScmIncludeDir_Macro; + } + else + ScmError(pState, VERR_PARSE_ERROR, "Malformed #include filename part: %.*s\n", cchLine, pchLine); + } + else + ScmError(pState, VERR_PARSE_ERROR, "Missing #include filename!\n"); + } + } + + if (ppchFilename) + *ppchFilename = NULL; + if (pcchFilename) + *pcchFilename = 0; + return kScmIncludeDir_Invalid; +} + + +/** + * Fix err.h/errcore.h usage. + * + * @returns Modification state. + * @param pIn The input stream. + * @param pOut The output stream. + * @param pSettings The settings. + */ +SCMREWRITERRES rewrite_Fix_Err_H(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings) +{ + if (!pSettings->fFixErrH) + return kScmUnmodified; + + static struct + { + const char *pszHeader; + unsigned cchHeader; + int iLevel; + } const s_aHeaders[] = + { + { RT_STR_TUPLE("iprt/errcore.h"), 1 }, + { RT_STR_TUPLE("iprt/err.h"), 2 }, + { RT_STR_TUPLE("VBox/err.h"), 3 }, + }; + static RTSTRTUPLE const g_aLevel1Statuses[] = /* Note! Keep in sync with errcore.h content! */ + { + { RT_STR_TUPLE("VINF_SUCCESS") }, + { RT_STR_TUPLE("VERR_GENERAL_FAILURE") }, + { RT_STR_TUPLE("VERR_INVALID_PARAMETER") }, + { RT_STR_TUPLE("VWRN_INVALID_PARAMETER") }, + { RT_STR_TUPLE("VERR_INVALID_MAGIC") }, + { RT_STR_TUPLE("VWRN_INVALID_MAGIC") }, + { RT_STR_TUPLE("VERR_INVALID_HANDLE") }, + { RT_STR_TUPLE("VWRN_INVALID_HANDLE") }, + { RT_STR_TUPLE("VERR_INVALID_POINTER") }, + { RT_STR_TUPLE("VERR_NO_MEMORY") }, + { RT_STR_TUPLE("VERR_PERMISSION_DENIED") }, + { RT_STR_TUPLE("VINF_PERMISSION_DENIED") }, + { RT_STR_TUPLE("VERR_VERSION_MISMATCH") }, + { RT_STR_TUPLE("VERR_NOT_IMPLEMENTED") }, + { RT_STR_TUPLE("VERR_INVALID_FLAGS") }, + { RT_STR_TUPLE("VERR_WRONG_ORDER") }, + { RT_STR_TUPLE("VERR_INVALID_FUNCTION") }, + { RT_STR_TUPLE("VERR_NOT_SUPPORTED") }, + { RT_STR_TUPLE("VINF_NOT_SUPPORTED") }, + { RT_STR_TUPLE("VERR_ACCESS_DENIED") }, + { RT_STR_TUPLE("VERR_INTERRUPTED") }, + { RT_STR_TUPLE("VINF_INTERRUPTED") }, + { RT_STR_TUPLE("VERR_TIMEOUT") }, + { RT_STR_TUPLE("VINF_TIMEOUT") }, + { RT_STR_TUPLE("VERR_BUFFER_OVERFLOW") }, + { RT_STR_TUPLE("VINF_BUFFER_OVERFLOW") }, + { RT_STR_TUPLE("VERR_TOO_MUCH_DATA") }, + { RT_STR_TUPLE("VERR_TRY_AGAIN") }, + { RT_STR_TUPLE("VINF_TRY_AGAIN") }, + { RT_STR_TUPLE("VERR_PARSE_ERROR") }, + { RT_STR_TUPLE("VERR_OUT_OF_RANGE") }, + { RT_STR_TUPLE("VERR_NUMBER_TOO_BIG") }, + { RT_STR_TUPLE("VWRN_NUMBER_TOO_BIG") }, + { RT_STR_TUPLE("VERR_CANCELLED") }, + { RT_STR_TUPLE("VERR_TRAILING_CHARS") }, + { RT_STR_TUPLE("VWRN_TRAILING_CHARS") }, + { RT_STR_TUPLE("VERR_TRAILING_SPACES") }, + { RT_STR_TUPLE("VWRN_TRAILING_SPACES") }, + { RT_STR_TUPLE("VERR_NOT_FOUND") }, + { RT_STR_TUPLE("VWRN_NOT_FOUND") }, + { RT_STR_TUPLE("VERR_INVALID_STATE") }, + { RT_STR_TUPLE("VWRN_INVALID_STATE") }, + { RT_STR_TUPLE("VERR_OUT_OF_RESOURCES") }, + { RT_STR_TUPLE("VWRN_OUT_OF_RESOURCES") }, + { RT_STR_TUPLE("VERR_END_OF_STRING") }, + { RT_STR_TUPLE("VERR_CALLBACK_RETURN") }, + { RT_STR_TUPLE("VINF_CALLBACK_RETURN") }, + { RT_STR_TUPLE("VERR_DUPLICATE") }, + { RT_STR_TUPLE("VERR_MISSING") }, + { RT_STR_TUPLE("VERR_BUFFER_UNDERFLOW") }, + { RT_STR_TUPLE("VINF_BUFFER_UNDERFLOW") }, + { RT_STR_TUPLE("VERR_NOT_AVAILABLE") }, + { RT_STR_TUPLE("VERR_MISMATCH") }, + { RT_STR_TUPLE("VERR_WRONG_TYPE") }, + { RT_STR_TUPLE("VWRN_WRONG_TYPE") }, + { RT_STR_TUPLE("VERR_WRONG_PARAMETER_COUNT") }, + { RT_STR_TUPLE("VERR_WRONG_PARAMETER_TYPE") }, + { RT_STR_TUPLE("VERR_INVALID_CLIENT_ID") }, + { RT_STR_TUPLE("VERR_INVALID_SESSION_ID") }, + { RT_STR_TUPLE("VERR_INCOMPATIBLE_CONFIG") }, + { RT_STR_TUPLE("VERR_INTERNAL_ERROR") }, + { RT_STR_TUPLE("VINF_GETOPT_NOT_OPTION") }, + { RT_STR_TUPLE("VERR_GETOPT_UNKNOWN_OPTION") }, + }; + + /* + * First pass: Scout #include err.h/errcore.h locations and usage. + * + * Note! This isn't entirely optimal since it's also parsing comments and + * strings, not just code. However it does a decent job for now. + */ + int iIncludeLevel = 0; + int iUsageLevel = 0; + uint32_t iLine = 0; + SCMEOL enmEol; + size_t cchLine; + const char *pchLine; + while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL) + { + iLine++; + if (cchLine < 6) + continue; + + /* + * Look for #includes. + */ + const char *pchHash = (const char *)memchr(pchLine, '#', cchLine); + if ( pchHash + && isSpanOfBlanks(pchLine, pchHash - pchLine)) + { + const char *pchFilename; + size_t cchFilename; + SCMINCLUDEDIR enmIncDir = ScmMaybeParseCIncludeLine(pState, pchLine, cchLine, &pchFilename, &cchFilename); + if ( enmIncDir == kScmIncludeDir_Bracketed + || enmIncDir == kScmIncludeDir_Quoted) + { + unsigned i = RT_ELEMENTS(s_aHeaders); + while (i-- > 0) + if ( s_aHeaders[i].cchHeader == cchFilename + && RTStrNICmpAscii(pchFilename, s_aHeaders[i].pszHeader, cchFilename) == 0) + { + if (iIncludeLevel < s_aHeaders[i].iLevel) + iIncludeLevel = s_aHeaders[i].iLevel; + break; + } + + /* Special hack for error info. */ + if (cchFilename == sizeof("errmsgdata.h") - 1 && memcmp(pchFilename, RT_STR_TUPLE("errmsgdata.h")) == 0) + iUsageLevel = 4; + + /* Special hack for code templates. */ + if ( cchFilename >= sizeof(".cpp.h") + && memcmp(&pchFilename[cchFilename - sizeof(".cpp.h") + 1], RT_STR_TUPLE(".cpp.h")) == 0) + iUsageLevel = 4; + continue; + } + } + /* + * Look for VERR_, VWRN_, VINF_ prefixed identifiers in the current line. + */ + const char *pchHit = (const char *)memchr(pchLine, 'V', cchLine); + if (pchHit) + { + const char *pchLeft = pchLine; + size_t cchLeft = cchLine; + do + { + size_t cchLeftHit = &pchLeft[cchLeft] - pchHit; + if (cchLeftHit < 6) + break; + if ( pchHit[4] == '_' + && ( pchHit == pchLine + || !ScmIsCIdentifierChar(pchHit[-1])) + && ( (pchHit[1] == 'E' && pchHit[2] == 'R' && pchHit[3] == 'R') + || (pchHit[1] == 'W' && pchHit[2] == 'R' && pchHit[3] == 'N') + || (pchHit[1] == 'I' && pchHit[2] == 'N' && pchHit[3] == 'F') ) ) + { + size_t cchIdentifier = 5; + while (cchIdentifier < cchLeftHit && ScmIsCIdentifierChar(pchHit[cchIdentifier])) + cchIdentifier++; + ScmVerbose(pState, 4, "--- status code at %u col %zu: %.*s\n", + iLine, pchHit - pchLine, cchIdentifier, pchHit); + + if (iUsageLevel <= 1) + { + iUsageLevel = 3; /* Cannot distingish between iprt/err.h and VBox/err.h, so pick the latter for now. */ + for (unsigned i = 0; i < RT_ELEMENTS(g_aLevel1Statuses); i++) + if ( cchIdentifier == g_aLevel1Statuses[i].cch + && memcmp(pchHit, g_aLevel1Statuses[i].psz, cchIdentifier) == 0) + { + iUsageLevel = 1; + break; + } + } + + pchLeft = pchHit + cchIdentifier; + cchLeft = cchLeftHit - cchIdentifier; + } + else + { + pchLeft = pchHit + 1; + cchLeft = cchLeftHit - 1; + } + pchHit = (const char *)memchr(pchLeft, 'V', cchLeft); + } while (pchHit != NULL); + } + } + ScmVerbose(pState, 3, "--- iIncludeLevel=%d iUsageLevel=%d\n", iIncludeLevel, iUsageLevel); + + /* + * Second pass: Change err.h to errcore.h if we detected a need for change. + */ + if ( iIncludeLevel <= iUsageLevel + || iIncludeLevel <= 1 /* we cannot safely eliminate errcore.h includes atm. */) + return kScmUnmodified; + + unsigned cChanges = 0; + ScmStreamRewindForReading(pIn); + while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL) + { + /* + * Look for #includes to modify. + */ + if (cchLine >= 6) + { + const char *pchHash = (const char *)memchr(pchLine, '#', cchLine); + if ( pchHash + && isSpanOfBlanks(pchLine, pchHash - pchLine)) + { + const char *pchFilename; + size_t cchFilename; + SCMINCLUDEDIR enmIncDir = ScmMaybeParseCIncludeLine(pState, pchLine, cchLine, &pchFilename, &cchFilename); + if ( enmIncDir == kScmIncludeDir_Bracketed + || enmIncDir == kScmIncludeDir_Quoted) + { + unsigned i = RT_ELEMENTS(s_aHeaders); + while (i-- > 0) + if ( s_aHeaders[i].cchHeader == cchFilename + && RTStrNICmpAscii(pchFilename, s_aHeaders[i].pszHeader, cchFilename) == 0) + { + ScmStreamWrite(pOut, pchLine, pchFilename - pchLine - 1); + ScmStreamWrite(pOut, RT_STR_TUPLE("<iprt/errcore.h>")); + size_t cchTrailing = &pchLine[cchLine] - &pchFilename[cchFilename + 1]; + if (cchTrailing > 0) + ScmStreamWrite(pOut, &pchFilename[cchFilename + 1], cchTrailing); + ScmStreamPutEol(pOut, enmEol); + cChanges++; + pchLine = NULL; + break; + } + if (!pchLine) + continue; + } + } + } + + int rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol); + if (RT_FAILURE(rc)) + return kScmUnmodified; + } + ScmVerbose(pState, 2, " * Converted %zu err.h/errcore.h include statements.\n", cChanges); + return kScmModified; +} + +typedef struct +{ + const char *pch; + uint8_t cch; + uint8_t cchSpaces; /**< Number of expected spaces before the word. */ + bool fSpacesBefore : 1; /**< Whether there may be spaces or tabs before the word. */ + bool fIdentifier : 1; /**< Whether we're to expect a C/C++ identifier rather than pch/cch. */ +} SCMMATCHWORD; + + +int ScmMatchWords(const char *pchLine, size_t cchLine, SCMMATCHWORD const *paWords, size_t cWords, + size_t *poffNext, PRTSTRTUPLE paIdentifiers, PRTERRINFO pErrInfo) +{ + int rc = VINF_SUCCESS; + + size_t offLine = 0; + for (size_t i = 0; i < cWords; i++) + { + SCMMATCHWORD const *pWord = &paWords[i]; + + /* + * Deal with spaces preceeding the word first: + */ + if (pWord->fSpacesBefore) + { + size_t cchSpaces = 0; + size_t cchTabs = 0; + while (offLine < cchLine) + { + const char ch = pchLine[offLine]; + if (ch == ' ') + cchSpaces++; + else if (ch == '\t') + cchTabs++; + else + break; + offLine++; + } + + if (cchSpaces == pWord->cchSpaces && cchTabs == 0) + { /* likely */ } + else if (cchSpaces == 0 && cchTabs == 0) + return RTErrInfoSetF(pErrInfo, VERR_PARSE_ERROR, "expected space at offset %u", offLine); + else + rc = VWRN_TRAILING_SPACES; + } + else + Assert(pWord->cchSpaces == 0); + + /* + * C/C++ identifier? + */ + if (pWord->fIdentifier) + { + if (offLine >= cchLine) + return RTErrInfoSetF(pErrInfo, VERR_END_OF_STRING, + "expected '%.*s' (C/C++ identifier) at offset %u, not end of string", + pWord->cch, pWord->pch, offLine); + if (!ScmIsCIdentifierLeadChar(pchLine[offLine])) + return RTErrInfoSetF(pErrInfo, VERR_MISMATCH, "expected '%.*s' (C/C++ identifier) at offset %u", + pWord->cch, pWord->pch, offLine); + size_t const offStart = offLine++; + while (offLine < cchLine && ScmIsCIdentifierChar(pchLine[offLine])) + offLine++; + if (paIdentifiers) + { + paIdentifiers->cch = offLine - offStart; + paIdentifiers->psz = &pchLine[offStart]; + paIdentifiers++; + } + } + /* + * Match the exact word. + */ + else if ( pWord->cch == 0 + || ( pWord->cch <= cchLine - offLine + && !memcmp(pWord->pch, &pchLine[offLine], pWord->cch))) + offLine += pWord->cch; + else + return RTErrInfoSetF(pErrInfo, VERR_MISMATCH, "expected '%.*s' at offset %u", pWord->cch, pWord->pch, offLine); + } + + /* + * Check for trailing characters/whatnot. + */ + if (poffNext) + *poffNext = offLine; + else if (offLine != cchLine) + rc = RTErrInfoSetF(pErrInfo, VERR_TRAILING_CHARS, "unexpected trailing characters at offset %u", offLine); + return rc; +} + + +/** + * Fix header file include guards and \#pragma once. + * + * @returns Modification state. + * @param pIn The input stream. + * @param pOut The output stream. + * @param pSettings The settings. + */ +SCMREWRITERRES rewrite_FixHeaderGuards(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings) +{ + if (!pSettings->fFixHeaderGuards) + return kScmUnmodified; + + /* always skip .cpp.h files */ + size_t cchFilename = strlen(pState->pszFilename); + if ( cchFilename > sizeof(".cpp.h") + && RTStrICmpAscii(&pState->pszFilename[cchFilename - sizeof(".cpp.h") + 1], ".cpp.h") == 0) + return kScmUnmodified; + + RTERRINFOSTATIC ErrInfo; + char szNormalized[168]; + size_t cchNormalized = 0; + int rc; + bool fRet = false; + + /* + * Calculate the expected guard for this file, if so tasked. + * ASSUMES pState->pszFilename is absolute as is pSettings->pszGuardRelativeToDir. + */ + szNormalized[0] = '\0'; + if (pSettings->pszGuardRelativeToDir) + { + rc = RTStrCopy(szNormalized, sizeof(szNormalized), pSettings->pszGuardPrefix); + if (RT_FAILURE(rc)) + return ScmError(pState, rc, "Guard prefix too long (or something): %s\n", pSettings->pszGuardPrefix); + cchNormalized = strlen(szNormalized); + if (strcmp(pSettings->pszGuardRelativeToDir, "{dir}") == 0) + rc = RTStrCopy(&szNormalized[cchNormalized], sizeof(szNormalized) - cchNormalized, + RTPathFilename(pState->pszFilename)); + else if (strcmp(pSettings->pszGuardRelativeToDir, "{parent}") == 0) + { + const char *pszSrc = RTPathFilename(pState->pszFilename); + if (!pszSrc || (uintptr_t)&pszSrc[-2] < (uintptr_t)pState->pszFilename || !RTPATH_IS_SLASH(pszSrc[-1])) + return ScmError(pState, VERR_INTERNAL_ERROR, "Error calculating {parent} header guard!\n"); + pszSrc -= 2; + while ( (uintptr_t)pszSrc > (uintptr_t)pState->pszFilename + && !RTPATH_IS_SLASH(pszSrc[-1]) + && !RTPATH_IS_VOLSEP(pszSrc[-1])) + pszSrc--; + rc = RTStrCopy(&szNormalized[cchNormalized], sizeof(szNormalized) - cchNormalized, pszSrc); + } + else + rc = RTPathCalcRelative(&szNormalized[cchNormalized], sizeof(szNormalized) - cchNormalized, + pSettings->pszGuardRelativeToDir, false /*fFromFile*/, pState->pszFilename); + if (RT_FAILURE(rc)) + return ScmError(pState, rc, "Error calculating guard prefix (RTPathCalcRelative): %Rrc\n", rc); + char ch; + while ((ch = szNormalized[cchNormalized]) != '\0') + { + if (!ScmIsCIdentifierChar(ch)) + szNormalized[cchNormalized] = '_'; + cchNormalized++; + } + } + + /* + * First part looks for the #ifndef xxxx paired with #define xxxx. + * + * We blindly assume the first preprocessor directive in the file is the guard + * and will be upset if this isn't the case. + */ + RTSTRTUPLE Guard = { NULL, 0 }; + uint32_t cBlankLines = 0; + SCMEOL enmEol; + size_t cchLine; + const char *pchLine; + for (;;) + { + pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol); + if (pchLine == NULL) + return ScmError(pState, VERR_PARSE_ERROR, "Did not find any include guards!\n"); + if (cchLine >= 2) + { + const char *pchHash = (const char *)memchr(pchLine, '#', cchLine); + if ( pchHash + && isSpanOfBlanks(pchLine, pchHash - pchLine)) + { + /* #ifndef xxxx */ + static const SCMMATCHWORD s_aIfndefGuard[] = + { + { RT_STR_TUPLE("#"), 0, true, false }, + { RT_STR_TUPLE("ifndef"), 0, true, false }, + { RT_STR_TUPLE("IDENTIFIER"), 1, true, true }, + { RT_STR_TUPLE(""), 0, true, false }, + }; + rc = ScmMatchWords(pchLine, cchLine, s_aIfndefGuard, RT_ELEMENTS(s_aIfndefGuard), + NULL /*poffNext*/, &Guard, RTErrInfoInitStatic(&ErrInfo)); + if (RT_FAILURE(rc)) + return ScmError(pState, rc, "%u: Expected first preprocessor directive to be '#ifndef xxxx'. %s (%.*s)\n", + ScmStreamTellLine(pIn) - 1, ErrInfo.Core.pszMsg, cchLine, pchLine); + fRet |= rc != VINF_SUCCESS; + ScmVerbose(pState, 3, "line %u in %s: #ifndef %.*s\n", + ScmStreamTellLine(pIn) - 1, pState->pszFilename, Guard.cch, Guard.psz); + + /* #define xxxx */ + pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol); + if (!pchLine) + return ScmError(pState, VERR_PARSE_ERROR, "%u: Unexpected end of file after '#ifndef %.*s'\n", + ScmStreamTellLine(pIn) - 1, Guard.cch, Guard.psz); + const SCMMATCHWORD aDefineGuard[] = + { + { RT_STR_TUPLE("#"), 0, true, false }, + { RT_STR_TUPLE("define"), 0, true, false }, + { Guard.psz, (uint8_t)Guard.cch, 1, true, false }, + { RT_STR_TUPLE(""), 0, true, false }, + }; + rc = ScmMatchWords(pchLine, cchLine, aDefineGuard, RT_ELEMENTS(aDefineGuard), + NULL /*poffNext*/, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo)); + if (RT_FAILURE(rc)) + return ScmError(pState, rc, "%u: Expected '#define %.*s' to follow '#ifndef %.*s'. %s (%.*s)\n", + ScmStreamTellLine(pIn) - 1, Guard.cch, Guard.psz, Guard.cch, Guard.psz, + ErrInfo.Core.pszMsg, cchLine, pchLine); + fRet |= rc != VINF_SUCCESS; + + if (Guard.cch >= sizeof(szNormalized)) + return ScmError(pState, VERR_BUFFER_OVERFLOW, "%u: Guard macro too long! %.*s\n", + ScmStreamTellLine(pIn) - 2, Guard.cch, Guard.psz); + + if (szNormalized[0] != '\0') + { + if ( Guard.cch != cchNormalized + || memcmp(Guard.psz, szNormalized, cchNormalized) != 0) + { + ScmVerbose(pState, 2, "guard changed from %.*s to %s\n", Guard.cch, Guard.psz, szNormalized); + ScmVerbose(pState, 2, "grep -rw %.*s ${WCROOT} | grep -Fv %s\n", + Guard.cch, Guard.psz, pState->pszFilename); + fRet = true; + } + Guard.psz = szNormalized; + Guard.cch = cchNormalized; + } + + /* + * Write guard, making sure we've got a single blank line preceeding it. + */ + ScmStreamPutEol(pOut, enmEol); + ScmStreamWrite(pOut, RT_STR_TUPLE("#ifndef ")); + ScmStreamWrite(pOut, Guard.psz, Guard.cch); + ScmStreamPutEol(pOut, enmEol); + ScmStreamWrite(pOut, RT_STR_TUPLE("#define ")); + ScmStreamWrite(pOut, Guard.psz, Guard.cch); + rc = ScmStreamPutEol(pOut, enmEol); + if (RT_FAILURE(rc)) + return kScmUnmodified; + break; + } + } + + if (!isBlankLine(pchLine, cchLine)) + { + while (cBlankLines-- > 0) + ScmStreamPutEol(pOut, enmEol); + cBlankLines = 0; + rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol); + if (RT_FAILURE(rc)) + return kScmUnmodified; + } + else + cBlankLines++; + } + + /* + * Look for pragma once wrapped in #ifndef RT_WITHOUT_PRAGMA_ONCE. + */ + size_t const iPragmaOnce = ScmStreamTellLine(pIn); + static const SCMMATCHWORD s_aIfndefRtWithoutPragmaOnce[] = + { + { RT_STR_TUPLE("#"), 0, true, false }, + { RT_STR_TUPLE("ifndef"), 0, true, false }, + { RT_STR_TUPLE("RT_WITHOUT_PRAGMA_ONCE"), 1, true, false }, + { RT_STR_TUPLE(""), 0, true, false }, + }; + static const SCMMATCHWORD s_aPragmaOnce[] = + { + { RT_STR_TUPLE("#"), 0, true, false }, + { RT_STR_TUPLE("pragma"), 1, true, false }, + { RT_STR_TUPLE("once"), 1, true, false}, + { RT_STR_TUPLE(""), 0, true, false }, + }; + static const SCMMATCHWORD s_aEndif[] = + { + { RT_STR_TUPLE("#"), 0, true, false }, + { RT_STR_TUPLE("endif"), 0, true, false }, + { RT_STR_TUPLE(""), 0, true, false }, + }; + + /* #ifndef RT_WITHOUT_PRAGMA_ONCE */ + pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol); + if (!pchLine) + return ScmError(pState, VERR_PARSE_ERROR, "%u: Unexpected end of file after header guard!\n", iPragmaOnce + 1); + size_t offNext; + rc = ScmMatchWords(pchLine, cchLine, s_aIfndefRtWithoutPragmaOnce, RT_ELEMENTS(s_aIfndefRtWithoutPragmaOnce), + &offNext, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo)); + if (RT_SUCCESS(rc)) + { + fRet |= rc != VINF_SUCCESS; + if (offNext != cchLine) + return ScmError(pState, VERR_PARSE_ERROR, "%u: Characters trailing '#ifndef RT_WITHOUT_PRAGMA_ONCE' (%.*s)\n", + iPragmaOnce + 1, cchLine, pchLine); + + /* # pragma once */ + pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol); + if (!pchLine) + return ScmError(pState, VERR_PARSE_ERROR, "%u: Unexpected end of file after '#ifndef RT_WITHOUT_PRAGMA_ONCE'\n", + iPragmaOnce + 2); + rc = ScmMatchWords(pchLine, cchLine, s_aPragmaOnce, RT_ELEMENTS(s_aPragmaOnce), + NULL /*poffNext*/, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo)); + if (RT_SUCCESS(rc)) + fRet |= rc != VINF_SUCCESS; + else + return ScmError(pState, rc, "%u: Expected '# pragma once' to follow '#ifndef RT_WITHOUT_PRAGMA_ONCE'! %s (%.*s)\n", + iPragmaOnce + 2, ErrInfo.Core.pszMsg, cchLine, pchLine); + + /* #endif */ + pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol); + if (!pchLine) + return ScmError(pState, VERR_PARSE_ERROR, "%u: Unexpected end of file after '#ifndef RT_WITHOUT_PRAGMA_ONCE' and '#pragma once'\n", + iPragmaOnce + 3); + rc = ScmMatchWords(pchLine, cchLine, s_aEndif, RT_ELEMENTS(s_aEndif), + NULL /*poffNext*/, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo)); + if (RT_SUCCESS(rc)) + fRet |= rc != VINF_SUCCESS; + else + return ScmError(pState, rc, + "%u: Expected '#endif' to follow '#ifndef RT_WITHOUT_PRAGMA_ONCE' and '# pragma once'! %s (%.*s)\n", + iPragmaOnce + 3, ErrInfo.Core.pszMsg, cchLine, pchLine); + ScmVerbose(pState, 3, "Found pragma once\n"); + fRet |= !pSettings->fPragmaOnce; + } + else + { + rc = ScmStreamSeekByLine(pIn, iPragmaOnce); + if (RT_FAILURE(rc)) + return ScmError(pState, rc, "seek error\n"); + fRet |= pSettings->fPragmaOnce; + ScmVerbose(pState, pSettings->fPragmaOnce ? 2 : 3, "Missing #pragma once\n"); + } + + /* + * Write the pragma once stuff. + */ + if (pSettings->fPragmaOnce) + { + ScmStreamPutLine(pOut, RT_STR_TUPLE("#ifndef RT_WITHOUT_PRAGMA_ONCE"), enmEol); + ScmStreamPutLine(pOut, RT_STR_TUPLE("# pragma once"), enmEol); + rc = ScmStreamPutLine(pOut, RT_STR_TUPLE("#endif"), enmEol); + if (RT_FAILURE(rc)) + return kScmUnmodified; + } + + /* + * Copy the rest of the file and remove pragma once statements, while + * looking for the last #endif in the file. + */ + size_t iEndIfIn = 0; + size_t iEndIfOut = 0; + while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL) + { + if (cchLine > 2) + { + const char *pchHash = (const char *)memchr(pchLine, '#', cchLine); + if ( pchHash + && isSpanOfBlanks(pchLine, pchHash - pchLine)) + { + size_t off = pchHash - pchLine + 1; + while (off < cchLine && RT_C_IS_BLANK(pchLine[off])) + off++; + /* #pragma once */ + if ( off + sizeof("pragma") - 1 <= cchLine + && !memcmp(&pchLine[off], RT_STR_TUPLE("pragma"))) + { + rc = ScmMatchWords(pchLine, cchLine, s_aPragmaOnce, RT_ELEMENTS(s_aPragmaOnce), + &offNext, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo)); + if (RT_SUCCESS(rc)) + { + fRet = true; + continue; + } + } + /* #endif */ + else if ( off + sizeof("endif") - 1 <= cchLine + && !memcmp(&pchLine[off], RT_STR_TUPLE("endif"))) + { + iEndIfIn = ScmStreamTellLine(pIn) - 1; + iEndIfOut = ScmStreamTellLine(pOut); + } + } + } + + rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol); + if (RT_FAILURE(rc)) + return kScmUnmodified; + } + + /* + * Check out the last endif, making sure it's well formed and make sure it has the + * right kind of comment following it. + */ + if (pSettings->fFixHeaderGuardEndif) + { + if (iEndIfOut == 0) + return ScmError(pState, VERR_PARSE_ERROR, "Expected '#endif' at the end of the file...\n"); + rc = ScmStreamSeekByLine(pIn, iEndIfIn); + if (RT_FAILURE(rc)) + return kScmUnmodified; + rc = ScmStreamSeekByLine(pOut, iEndIfOut); + if (RT_FAILURE(rc)) + return kScmUnmodified; + + pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol); + if (!pchLine) + return ScmError(pState, VERR_INTERNAL_ERROR, "ScmStreamGetLine failed re-reading #endif!\n"); + + char szTmp[64 + sizeof(szNormalized)]; + size_t cchTmp; + if (pSettings->fEndifGuardComment) + cchTmp = RTStrPrintf(szTmp, sizeof(szTmp), "#endif /* !%.*s */", Guard.cch, Guard.psz); + else + cchTmp = RTStrPrintf(szTmp, sizeof(szTmp), "#endif"); /* lazy bird */ + fRet |= cchTmp != cchLine || memcmp(szTmp, pchLine, cchTmp) != 0; + rc = ScmStreamPutLine(pOut, szTmp, cchTmp, enmEol); + if (RT_FAILURE(rc)) + return kScmUnmodified; + + /* Copy out the remaining lines (assumes no #pragma once here). */ + while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL) + { + rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol); + if (RT_FAILURE(rc)) + return kScmUnmodified; + } + } + + return fRet ? kScmModified : kScmUnmodified; +} + + +/** + * Checks for PAGE_SIZE, PAGE_SHIFT and PAGE_OFFSET_MASK w/o a GUEST_ or HOST_ + * prefix as well as banning PAGE_BASE_HC_MASK, PAGE_BASE_GC_MASK and + * PAGE_BASE_MASK. + * + * @returns kScmUnmodified - requires manual fix. + * @param pIn The input stream. + * @param pOut The output stream. + * @param pSettings The settings. + */ +SCMREWRITERRES rewrite_PageChecks(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings) +{ + RT_NOREF(pOut); + if (!pSettings->fOnlyGuestHostPage && !pSettings->fNoASMMemPageUse) + return kScmUnmodified; + + static RTSTRTUPLE const g_aWords[] = + { + { RT_STR_TUPLE("PAGE_SIZE") }, + { RT_STR_TUPLE("PAGE_SHIFT") }, + { RT_STR_TUPLE("PAGE_OFFSET_MASK") }, + { RT_STR_TUPLE("PAGE_BASE_MASK") }, + { RT_STR_TUPLE("PAGE_BASE_GC_MASK") }, + { RT_STR_TUPLE("PAGE_BASE_HC_MASK") }, + { RT_STR_TUPLE("PAGE_ADDRESS") }, + { RT_STR_TUPLE("PHYS_PAGE_ADDRESS") }, + { RT_STR_TUPLE("ASMMemIsZeroPage") }, + { RT_STR_TUPLE("ASMMemZeroPage") }, + }; + size_t const iFirstWord = pSettings->fOnlyGuestHostPage ? 0 : 7; + size_t const iEndWords = pSettings->fNoASMMemPageUse ? 9 : 7; + + uint32_t iLine = 0; + SCMEOL enmEol; + size_t cchLine; + const char *pchLine; + while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL) + { + iLine++; + for (size_t i = iFirstWord; i < iEndWords; i++) + { + size_t const cchWord = g_aWords[i].cch; + if (cchLine >= cchWord) + { + const char * const pszWord = g_aWords[i].psz; + const char *pchHit = (const char *)memchr(pchLine, *pszWord, cchLine); + while (pchHit) + { + size_t cchLeft = (uintptr_t)&pchLine[cchLine] - (uintptr_t)pchHit; + if ( cchLeft >= cchWord + && memcmp(pchHit, pszWord, cchWord) == 0 + && ( pchHit == pchLine + || !ScmIsCIdentifierChar(pchHit[-1])) + && ( cchLeft == cchWord + || !ScmIsCIdentifierChar(pchHit[cchWord])) ) + { + if (i < 3) + ScmFixManually(pState, "%u:%zu: %s is not allow! Use GUEST_%s or HOST_%s instead.\n", + iLine, pchHit - pchLine + 1, pszWord, pszWord, pszWord); + else if (i < 7) + ScmFixManually(pState, "%u:%zu: %s is not allow! Rewrite using GUEST/HOST_PAGE_OFFSET_MASK.\n", + iLine, pchHit - pchLine + 1, pszWord); + else + ScmFixManually(pState, "%u:%zu: %s is not allow! Use %s with correct page size instead.\n", + iLine, pchHit - pchLine + 1, pszWord, i == 3 ? "ASMMemIsZero" : "RT_BZERO"); + } + + /* next */ + cchLeft -= 1; + if (cchLeft < cchWord) + break; + pchHit = (const char *)memchr(pchHit + 1, *pszWord, cchLeft); + } + } + } + } + + return kScmUnmodified; +} + + +/** + * Checks for usage of rc in code instead of vrc for IPRT status codes (int) and hrc for COM + * status codes (HRESULT). + * + * @returns kScmUnmodified - requires manual fix. + * @param pIn The input stream. + * @param pOut The output stream. + * @param pSettings The settings. + * + * @note Used in Main to avoid ambiguity when just using rc. + */ +SCMREWRITERRES rewrite_ForceHrcVrcInsteadOfRc(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings) +{ + RT_NOREF(pOut); + if (!pSettings->fOnlyHrcVrcInsteadOfRc) + return kScmUnmodified; + + static const SCMMATCHWORD s_aHresultVrc[] = + { + { RT_STR_TUPLE("HRESULT"), 0, true, false }, + { RT_STR_TUPLE("vrc"), 1, true, false } + }; + + static const SCMMATCHWORD s_aIntHrc[] = + { + { RT_STR_TUPLE("int"), 0, true, false }, + { RT_STR_TUPLE("hrc"), 1, true, false } + }; + + uint32_t iLine = 0; + SCMEOL enmEol; + size_t cchLine; + const char *pchLine; + RTERRINFOSTATIC ErrInfo; + while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL) + { + iLine++; + + /* Look for forbidden declarations first. */ + size_t offNext = 0; + int rc = ScmMatchWords(pchLine, cchLine, s_aHresultVrc, RT_ELEMENTS(s_aHresultVrc), + &offNext, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo)); + if (RT_SUCCESS(rc)) + { + ScmFixManually(pState, "%u:%zu: 'HRESULT vrc' is not allowed! Use 'HRESULT hrc' instead.\n", + iLine, offNext); + continue; + } + + rc = ScmMatchWords(pchLine, cchLine, s_aIntHrc, RT_ELEMENTS(s_aIntHrc), + &offNext, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo)); + if (RT_SUCCESS(rc)) + { + ScmFixManually(pState, "%u:%zu: 'int hrc' is not allowed! Use 'int vrc' instead.\n", + iLine, offNext); + continue; + } + +#if 0 /* This is too broad and triggers on things we don't want to trigger on (like autoCaller.rc()). */ + const RTSTRTUPLE RcTuple = { RT_STR_TUPLE("rc") }; + size_t const cchWord = RcTuple.cch; + if (cchLine >= cchWord) + { + const char *pchHit = (const char *)memchr(pchLine, *RcTuple.psz, cchLine); + while (pchHit) + { + size_t cchLeft = (uintptr_t)&pchLine[cchLine] - (uintptr_t)pchHit; + if ( cchLeft >= cchWord + && memcmp(pchHit, RcTuple.psz, cchWord) == 0 + && ( pchHit == pchLine + || !ScmIsCIdentifierChar(pchHit[-1])) + && ( cchLeft == cchWord + || !ScmIsCIdentifierChar(pchHit[cchWord])) ) + ScmFixManually(pState, "%u:%zu: %s is not allowed! Use hrc or vrc instead.\n", + iLine, pchHit - pchLine + 1, RcTuple.psz); + + /* next */ + cchLeft -= 1; + if (cchLeft < cchWord) + break; + pchHit = (const char *)memchr(pchHit + 1, *RcTuple.psz, cchLeft); + } + } +#else + /* Trigger on declarations of 'HRESULT rc' and 'int rc'. */ + static const SCMMATCHWORD s_aHresultRc[] = + { + { RT_STR_TUPLE("HRESULT"), 0, true, false }, + { RT_STR_TUPLE("rc"), 1, true, false } + }; + + static const SCMMATCHWORD s_aIntRc[] = + { + { RT_STR_TUPLE("int"), 0, true, false }, + { RT_STR_TUPLE("rc"), 1, true, false } + }; + + rc = ScmMatchWords(pchLine, cchLine, s_aHresultRc, RT_ELEMENTS(s_aHresultRc), + &offNext, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo)); + if (RT_SUCCESS(rc)) + { + ScmFixManually(pState, "%u:%zu: 'HRESULT rc' is not allowed! Use 'HRESULT hrc' instead.\n", + iLine, offNext); + continue; + } + + rc = ScmMatchWords(pchLine, cchLine, s_aIntRc, RT_ELEMENTS(s_aIntRc), + &offNext, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo)); + if (RT_SUCCESS(rc)) + { + ScmFixManually(pState, "%u:%zu: 'int rc' is not allowed! Use 'int vrc' instead.\n", + iLine, offNext); + continue; + } +#endif + } + + return kScmUnmodified; +} + + +/** + * Rewrite a C/C++ source or header file. + * + * @returns Modification state. + * @param pIn The input stream. + * @param pOut The output stream. + * @param pSettings The settings. + * + * @todo + * + * Ideas for C/C++: + * - space after if, while, for, switch + * - spaces in for (i=0;i<x;i++) + * - complex conditional, bird style. + * - remove unnecessary parentheses. + * - sort defined RT_OS_*|| and RT_ARCH + * - sizeof without parenthesis. + * - defined without parenthesis. + * - trailing spaces. + * - parameter indentation. + * - space after comma. + * - while (x--); -> multi line + comment. + * - else statement; + * - space between function and left parenthesis. + * - TODO, XXX, @todo cleanup. + * - Space before/after '*'. + * - ensure new line at end of file. + * - Indentation of precompiler statements (#ifdef, #defines). + * - space between functions. + * - string.h -> iprt/string.h, stdarg.h -> iprt/stdarg.h, etc. + */ +SCMREWRITERRES rewrite_C_and_CPP(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings) +{ + + RT_NOREF4(pState, pIn, pOut, pSettings); + return kScmUnmodified; +} + diff --git a/src/bldprogs/scmstream.cpp b/src/bldprogs/scmstream.cpp new file mode 100644 index 00000000..e14cdf30 --- /dev/null +++ b/src/bldprogs/scmstream.cpp @@ -0,0 +1,1478 @@ +/* $Id: scmstream.cpp $ */ +/** @file + * IPRT Testcase / Tool - Source Code Massager Stream Code. + */ + +/* + * Copyright (C) 2010-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/assert.h> +#include <iprt/ctype.h> +#include <iprt/err.h> +#include <iprt/file.h> +#include <iprt/handle.h> +#include <iprt/mem.h> +#include <iprt/pipe.h> +#include <iprt/string.h> + +#include "scmstream.h" + + +/** + * Initializes the stream structure. + * + * @param pStream The stream structure. + * @param fWriteOrRead The value of the fWriteOrRead stream member. + */ +static void scmStreamInitInternal(PSCMSTREAM pStream, bool fWriteOrRead) +{ + pStream->pch = NULL; + pStream->off = 0; + pStream->cb = 0; + pStream->cbAllocated = 0; + + pStream->paLines = NULL; + pStream->iLine = 0; + pStream->cLines = 0; + pStream->cLinesAllocated = 0; + + pStream->fWriteOrRead = fWriteOrRead; + pStream->fFileMemory = false; + pStream->fFullyLineated = false; + + pStream->rc = VINF_SUCCESS; +} + +/** + * Initialize an input stream. + * + * @returns IPRT status code. + * @param pStream The stream to initialize. + * @param pszFilename The file to take the stream content from. + */ +int ScmStreamInitForReading(PSCMSTREAM pStream, const char *pszFilename) +{ + scmStreamInitInternal(pStream, false /*fWriteOrRead*/); + + void *pvFile; + size_t cbFile; + int rc = pStream->rc = RTFileReadAll(pszFilename, &pvFile, &cbFile); + if (RT_SUCCESS(rc)) + { + pStream->pch = (char *)pvFile; + pStream->cb = cbFile; + pStream->cbAllocated = cbFile; + pStream->fFileMemory = true; + } + return rc; +} + +/** + * Initialize an output stream. + * + * @returns IPRT status code + * @param pStream The stream to initialize. + * @param pRelatedStream Pointer to a related stream. NULL is fine. + */ +int ScmStreamInitForWriting(PSCMSTREAM pStream, PCSCMSTREAM pRelatedStream) +{ + scmStreamInitInternal(pStream, true /*fWriteOrRead*/); + + /* allocate stuff */ + size_t cbEstimate = !pRelatedStream ? _64K + : pRelatedStream->cb > 0 ? pRelatedStream->cb + pRelatedStream->cb / 10 : 64; + cbEstimate = RT_ALIGN(cbEstimate, _4K); + pStream->pch = (char *)RTMemAlloc(cbEstimate); + if (pStream->pch) + { + size_t cLinesEstimate = pRelatedStream && pRelatedStream->fFullyLineated + ? pRelatedStream->cLines + pRelatedStream->cLines / 10 + : cbEstimate / 24; + cLinesEstimate = RT_ALIGN(cLinesEstimate, 512); + if (cLinesEstimate == 0) + cLinesEstimate = 16; + pStream->paLines = (PSCMSTREAMLINE)RTMemAlloc(cLinesEstimate * sizeof(SCMSTREAMLINE)); + if (pStream->paLines) + { + pStream->paLines[0].off = 0; + pStream->paLines[0].cch = 0; + pStream->paLines[0].enmEol = SCMEOL_NONE; + pStream->cbAllocated = cbEstimate; + pStream->cLinesAllocated = cLinesEstimate; + return VINF_SUCCESS; + } + + RTMemFree(pStream->pch); + pStream->pch = NULL; + } + return pStream->rc = VERR_NO_MEMORY; +} + +/** + * Frees the resources associated with the stream. + * + * Nothing is happens to whatever the stream was initialized from or dumped to. + * + * @param pStream The stream to delete. + */ +void ScmStreamDelete(PSCMSTREAM pStream) +{ + if (pStream->pch) + { + if (pStream->fFileMemory) + RTFileReadAllFree(pStream->pch, pStream->cbAllocated); + else + RTMemFree(pStream->pch); + pStream->pch = NULL; + } + pStream->cbAllocated = 0; + + if (pStream->paLines) + { + RTMemFree(pStream->paLines); + pStream->paLines = NULL; + } + pStream->cLinesAllocated = 0; +} + +/** + * Get the stream status code. + * + * @returns IPRT status code. + * @param pStream The stream. + */ +int ScmStreamGetStatus(PCSCMSTREAM pStream) +{ + return pStream->rc; +} + +/** + * Grows the buffer of a write stream. + * + * @returns IPRT status code. + * @param pStream The stream. Must be in write mode. + * @param cbAppending The minimum number of bytes to grow the buffer + * with. + */ +static int scmStreamGrowBuffer(PSCMSTREAM pStream, size_t cbAppending) +{ + size_t cbAllocated = pStream->cbAllocated; + cbAllocated += RT_MAX(0x1000 + cbAppending, cbAllocated); + cbAllocated = RT_ALIGN(cbAllocated, 0x1000); + void *pvNew; + if (!pStream->fFileMemory) + { + pvNew = RTMemRealloc(pStream->pch, cbAllocated); + if (!pvNew) + return pStream->rc = VERR_NO_MEMORY; + } + else + { + pvNew = RTMemDupEx(pStream->pch, pStream->off, cbAllocated - pStream->off); + if (!pvNew) + return pStream->rc = VERR_NO_MEMORY; + RTFileReadAllFree(pStream->pch, pStream->cbAllocated); + pStream->fFileMemory = false; + } + pStream->pch = (char *)pvNew; + pStream->cbAllocated = cbAllocated; + + return VINF_SUCCESS; +} + +/** + * Grows the line array of a stream. + * + * @returns IPRT status code. + * @param pStream The stream. + * @param iMinLine Minimum line number. + */ +static int scmStreamGrowLines(PSCMSTREAM pStream, size_t iMinLine) +{ + size_t cLinesAllocated = pStream->cLinesAllocated; + cLinesAllocated += RT_MAX(512 + iMinLine, cLinesAllocated); + cLinesAllocated = RT_ALIGN(cLinesAllocated, 512); + void *pvNew = RTMemRealloc(pStream->paLines, cLinesAllocated * sizeof(SCMSTREAMLINE)); + if (!pvNew) + return pStream->rc = VERR_NO_MEMORY; + + pStream->paLines = (PSCMSTREAMLINE)pvNew; + pStream->cLinesAllocated = cLinesAllocated; + return VINF_SUCCESS; +} + +/** + * Rewinds the stream and sets the mode to read. + * + * @param pStream The stream. + */ +void ScmStreamRewindForReading(PSCMSTREAM pStream) +{ + pStream->off = 0; + pStream->iLine = 0; + pStream->fWriteOrRead = false; + pStream->rc = VINF_SUCCESS; +} + +/** + * Rewinds the stream and sets the mode to write. + * + * @param pStream The stream. + */ +void ScmStreamRewindForWriting(PSCMSTREAM pStream) +{ + pStream->off = 0; + pStream->iLine = 0; + pStream->cLines = 0; + pStream->fWriteOrRead = true; + pStream->fFullyLineated = true; + pStream->rc = VINF_SUCCESS; + + /* Initialize the first line with a zero length so ScmStreamWrite won't misbehave. */ + if (pStream->cLinesAllocated == 0) + scmStreamGrowLines(pStream, 1); + if (pStream->cLinesAllocated > 0) + { + pStream->paLines[0].off = 0; + pStream->paLines[0].cch = 0; + pStream->paLines[0].enmEol = SCMEOL_NONE; + } +} + +/** + * Checks if it's a text stream. + * + * Not 100% proof. + * + * @returns true if it probably is a text file, false if not. + * @param pStream The stream. Write or read, doesn't matter. + */ +bool ScmStreamIsText(PSCMSTREAM pStream) +{ + if (RTStrEnd(pStream->pch, pStream->cb)) + return false; + if (!pStream->cb) + return true; + return true; +} + +/** + * Performs an integrity check of the stream. + * + * @returns IPRT status code. + * @param pStream The stream. + */ +int ScmStreamCheckItegrity(PSCMSTREAM pStream) +{ + /* + * Perform sanity checks. + */ + size_t const cbFile = pStream->cb; + for (size_t iLine = 0; iLine < pStream->cLines; iLine++) + { + size_t offEol = pStream->paLines[iLine].off + pStream->paLines[iLine].cch; + AssertReturn(offEol + pStream->paLines[iLine].enmEol <= cbFile, VERR_INTERNAL_ERROR_2); + switch (pStream->paLines[iLine].enmEol) + { + case SCMEOL_LF: + AssertReturn(pStream->pch[offEol] == '\n', VERR_INTERNAL_ERROR_3); + break; + case SCMEOL_CRLF: + AssertReturn(pStream->pch[offEol] == '\r', VERR_INTERNAL_ERROR_3); + AssertReturn(pStream->pch[offEol + 1] == '\n', VERR_INTERNAL_ERROR_3); + break; + case SCMEOL_NONE: + AssertReturn(iLine + 1 >= pStream->cLines, VERR_INTERNAL_ERROR_4); + break; + default: + AssertReturn(iLine + 1 >= pStream->cLines, VERR_INTERNAL_ERROR_5); + } + } + return VINF_SUCCESS; +} + +/** + * Writes the stream to a file. + * + * @returns IPRT status code + * @param pStream The stream. + * @param pszFilenameFmt The filename format string. + * @param ... Format arguments. + */ +int ScmStreamWriteToFile(PSCMSTREAM pStream, const char *pszFilenameFmt, ...) +{ + int rc; + +#ifdef RT_STRICT + /* + * Check that what we're going to write makes sense first. + */ + rc = ScmStreamCheckItegrity(pStream); + if (RT_FAILURE(rc)) + return rc; +#endif + + /* + * Do the actual writing. + */ + RTFILE hFile; + va_list va; + va_start(va, pszFilenameFmt); + rc = RTFileOpenV(&hFile, RTFILE_O_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_WRITE, pszFilenameFmt, va); + if (RT_SUCCESS(rc)) + { + rc = RTFileWrite(hFile, pStream->pch, pStream->cb, NULL); + RTFileClose(hFile); + } + va_end(va); + return rc; +} + +/** + * Writes the stream to standard output. + * + * @returns IPRT status code + * @param pStream The stream. + */ +int ScmStreamWriteToStdOut(PSCMSTREAM pStream) +{ + int rc; + +#ifdef RT_STRICT + /* + * Check that what we're going to write makes sense first. + */ + rc = ScmStreamCheckItegrity(pStream); + if (RT_FAILURE(rc)) + return rc; +#endif + + /* + * Do the actual writing. + */ + RTHANDLE h; + rc = RTHandleGetStandard(RTHANDLESTD_OUTPUT, true /*fLeaveOpen*/, &h); + if (RT_SUCCESS(rc)) + { + switch (h.enmType) + { + case RTHANDLETYPE_FILE: + rc = RTFileWrite(h.u.hFile, pStream->pch, pStream->cb, NULL); + /** @todo RTFileClose */ + break; + case RTHANDLETYPE_PIPE: + rc = RTPipeWriteBlocking(h.u.hPipe, pStream->pch, pStream->cb, NULL); + RTPipeClose(h.u.hPipe); + break; + default: + rc = VERR_INVALID_HANDLE; + break; + } + } + return rc; +} + +/** + * Worker for ScmStreamGetLine that builds the line number index while parsing + * the stream. + * + * @returns Same as SCMStreamGetLine. + * @param pStream The stream. Must be in read mode. + * @param pcchLine Where to return the line length. + * @param penmEol Where to return the kind of end of line marker. + */ +static const char *scmStreamGetLineInternal(PSCMSTREAM pStream, size_t *pcchLine, PSCMEOL penmEol) +{ + AssertReturn(!pStream->fWriteOrRead, NULL); + if (RT_FAILURE(pStream->rc)) + return NULL; + + size_t off = pStream->off; + size_t cb = pStream->cb; + if (RT_UNLIKELY(off >= cb)) + { + pStream->fFullyLineated = true; + return NULL; + } + + size_t iLine = pStream->iLine; + if (RT_UNLIKELY(iLine >= pStream->cLinesAllocated)) + { + int rc = scmStreamGrowLines(pStream, iLine); + if (RT_FAILURE(rc)) + return NULL; + } + pStream->paLines[iLine].off = off; + + cb -= off; + const char *pchRet = &pStream->pch[off]; + const char *pch = (const char *)memchr(pchRet, '\n', cb); + if (RT_LIKELY(pch)) + { + cb = pch - pchRet; + pStream->off = off + cb + 1; + if ( cb < 1 + || pch[-1] != '\r') + pStream->paLines[iLine].enmEol = *penmEol = SCMEOL_LF; + else + { + pStream->paLines[iLine].enmEol = *penmEol = SCMEOL_CRLF; + cb--; + } + } + else + { + pStream->off = off + cb; + pStream->paLines[iLine].enmEol = *penmEol = SCMEOL_NONE; + } + *pcchLine = cb; + pStream->paLines[iLine].cch = cb; + pStream->cLines = pStream->iLine = ++iLine; + + return pchRet; +} + +/** + * Internal worker that delineates a stream. + * + * @returns IPRT status code. + * @param pStream The stream. Caller must check that it is in + * read mode. + */ +static int scmStreamLineate(PSCMSTREAM pStream) +{ + /* Save the stream position. */ + size_t const offSaved = pStream->off; + size_t const iLineSaved = pStream->iLine; + + /* Get each line. */ + size_t cchLine; + SCMEOL enmEol; + while (scmStreamGetLineInternal(pStream, &cchLine, &enmEol)) + /* nothing */; + Assert(RT_FAILURE(pStream->rc) || pStream->fFullyLineated); + + /* Restore the position */ + pStream->off = offSaved; + pStream->iLine = iLineSaved; + + return pStream->rc; +} + +/** + * Get the current stream position as an byte offset. + * + * @returns The current byte offset + * @param pStream The stream. + */ +size_t ScmStreamTell(PSCMSTREAM pStream) +{ + return pStream->off; +} + +/** + * Get the current stream position as a line number. + * + * @returns The current line (0-based). + * @param pStream The stream. + */ +size_t ScmStreamTellLine(PSCMSTREAM pStream) +{ + return pStream->iLine; +} + + +/** + * Gets the stream offset of a given line. + * + * @returns The offset of the line, or the stream size if the line number is too + * high. + * @param pStream The stream. Must be in read mode. + * @param iLine The line we're asking about. + */ +size_t ScmStreamTellOffsetOfLine(PSCMSTREAM pStream, size_t iLine) +{ + AssertReturn(!pStream->fWriteOrRead, pStream->cb); + if (!pStream->fFullyLineated) + { + int rc = scmStreamLineate(pStream); + AssertRCReturn(rc, pStream->cb); + } + if (iLine >= pStream->cLines) + return pStream->cb; + return pStream->paLines[iLine].off; +} + + +/** + * Get the current stream size in bytes. + * + * @returns Count of bytes. + * @param pStream The stream. + */ +size_t ScmStreamSize(PSCMSTREAM pStream) +{ + return pStream->cb; +} + +/** + * Gets the number of lines in the stream. + * + * @returns The number of lines. + * @param pStream The stream. + */ +size_t ScmStreamCountLines(PSCMSTREAM pStream) +{ + if (!pStream->fFullyLineated) + scmStreamLineate(pStream); + return pStream->cLines; +} + +/** + * Seeks to a given byte offset in the stream. + * + * @returns IPRT status code. + * @retval VERR_SEEK if the new stream position is the middle of an EOL marker. + * This is a temporary restriction. + * + * @param pStream The stream. Must be in read mode. + * @param offAbsolute The offset to seek to. If this is beyond the + * end of the stream, the position is set to the + * end. + */ +int ScmStreamSeekAbsolute(PSCMSTREAM pStream, size_t offAbsolute) +{ + AssertReturn(!pStream->fWriteOrRead, VERR_ACCESS_DENIED); + if (RT_FAILURE(pStream->rc)) + return pStream->rc; + + /* Must be fully delineated. (lazy bird) */ + if (RT_UNLIKELY(!pStream->fFullyLineated)) + { + int rc = scmStreamLineate(pStream); + if (RT_FAILURE(rc)) + return rc; + } + + /* Ok, do the job. */ + if (offAbsolute < pStream->cb) + { + /** @todo Should do a binary search here, but I'm too darn lazy tonight. */ + pStream->off = ~(size_t)0; + for (size_t i = 0; i < pStream->cLines; i++) + { + if (offAbsolute < pStream->paLines[i].off + pStream->paLines[i].cch + pStream->paLines[i].enmEol) + { + pStream->off = offAbsolute; + pStream->iLine = i; + if (offAbsolute > pStream->paLines[i].off + pStream->paLines[i].cch) + return pStream->rc = VERR_SEEK; + break; + } + } + AssertReturn(pStream->off != ~(size_t)0, pStream->rc = VERR_INTERNAL_ERROR_3); + } + else + { + pStream->off = pStream->cb; + pStream->iLine = pStream->cLines; + } + return VINF_SUCCESS; +} + + +/** + * Seeks a number of bytes relative to the current stream position. + * + * @returns IPRT status code. + * @retval VERR_SEEK if the new stream position is the middle of an EOL marker. + * This is a temporary restriction. + * + * @param pStream The stream. Must be in read mode. + * @param offRelative The offset to seek to. A negative offset + * rewinds and positive one fast forwards the + * stream. Will quietly stop at the beginning and + * end of the stream. + */ +int ScmStreamSeekRelative(PSCMSTREAM pStream, ssize_t offRelative) +{ + size_t offAbsolute; + if (offRelative >= 0) + offAbsolute = pStream->off + offRelative; + else if ((size_t)-offRelative <= pStream->off) + offAbsolute = pStream->off + offRelative; + else + offAbsolute = 0; + return ScmStreamSeekAbsolute(pStream, offAbsolute); +} + +/** + * Seeks to a given line in the stream. + * + * @returns IPRT status code. + * + * @param pStream The stream. Must be in read mode. + * @param iLine The line to seek to. If this is beyond the end + * of the stream, the position is set to the end. + */ +int ScmStreamSeekByLine(PSCMSTREAM pStream, size_t iLine) +{ + if (RT_FAILURE(pStream->rc)) + return pStream->rc; + + /* Must be fully delineated. (lazy bird) */ + if (RT_UNLIKELY(!pStream->fFullyLineated)) + { + AssertReturn(!pStream->fWriteOrRead, VERR_ACCESS_DENIED); + int rc = scmStreamLineate(pStream); + if (RT_FAILURE(rc)) + return rc; + } + + /* Ok, do the job. */ + if (iLine < pStream->cLines) + { + pStream->iLine = iLine; + pStream->off = pStream->paLines[iLine].off; + if (pStream->fWriteOrRead) + { + pStream->cb = pStream->paLines[iLine].off; + pStream->cLines = iLine; + pStream->paLines[iLine].cch = 0; + pStream->paLines[iLine].enmEol = SCMEOL_NONE; + } + } + else + { + AssertReturn(!pStream->fWriteOrRead, VERR_ACCESS_DENIED); + pStream->off = pStream->cb; + pStream->iLine = pStream->cLines; + } + return VINF_SUCCESS; +} + +/** + * Checks if the stream position is at the start of a line. + * + * @returns @c true if at the start, @c false if not. + * @param pStream The stream. + */ +bool ScmStreamIsAtStartOfLine(PSCMSTREAM pStream) +{ + if ( !pStream->fFullyLineated + && !pStream->fWriteOrRead) + { + int rc = scmStreamLineate(pStream); + if (RT_FAILURE(rc)) + return false; + } + return pStream->off == pStream->paLines[pStream->iLine].off; +} + +/** + * Compares the two streams from the start to end, binary fashion. + * + * The stream position does not change nor does it matter whether they are + * writable or readable. + * + * @returns true if identical, false if not. + * @param pStream1 The first stream. + * @param pStream2 The second stream. + */ +bool ScmStreamAreIdentical(PCSCMSTREAM pStream1, PCSCMSTREAM pStream2) +{ + return pStream1->cb == pStream2->cb + && memcmp(pStream1->pch, pStream2->pch, pStream1->cb) == 0; +} + + +/** + * Worker for ScmStreamGetLineByNo and ScmStreamGetLine. + * + * Works on a fully lineated stream. + * + * @returns Pointer to the first character in the line, not NULL terminated. + * NULL if the end of the stream has been reached or some problem + * occurred. + * + * @param pStream The stream. Must be in read mode. + * @param iLine The line to get (0-based). + * @param pcchLine The length. + * @param penmEol Where to return the end of line type indicator. + */ +DECLINLINE(const char *) scmStreamGetLineByNoCommon(PSCMSTREAM pStream, size_t iLine, size_t *pcchLine, PSCMEOL penmEol) +{ + Assert(!pStream->fWriteOrRead); + Assert(pStream->fFullyLineated); + + /* Check stream status. */ + if (RT_SUCCESS(pStream->rc)) + { + /* Not at the end of the stream yet? */ + if (RT_LIKELY(iLine < pStream->cLines)) + { + /* Get the data. */ + const char *pchRet = &pStream->pch[pStream->paLines[iLine].off]; + *pcchLine = pStream->paLines[iLine].cch; + *penmEol = pStream->paLines[iLine].enmEol; + + /* update the stream position. */ + pStream->off = pStream->paLines[iLine].off + pStream->paLines[iLine].cch + pStream->paLines[iLine].enmEol; + pStream->iLine = iLine + 1; + return pchRet; + } + pStream->off = pStream->cb; + pStream->iLine = pStream->cLines; + } + *pcchLine = 0; + *penmEol = SCMEOL_NONE; + return NULL; +} + + +/** + * Get a numbered line from the stream (changes the position). + * + * A line is always delimited by a LF character or the end of the stream. The + * delimiter is not included in returned line length, but instead returned via + * the @a penmEol indicator. + * + * @returns Pointer to the first character in the line, not NULL terminated. + * NULL if the end of the stream has been reached or some problem + * occurred (*pcchLine set to zero and *penmEol to SCMEOL_NONE). + * + * @param pStream The stream. Must be in read mode. + * @param iLine The line to get (0-based). + * @param pcchLine The length. + * @param penmEol Where to return the end of line type indicator. + */ +const char *ScmStreamGetLineByNo(PSCMSTREAM pStream, size_t iLine, size_t *pcchLine, PSCMEOL penmEol) +{ + AssertReturn(!pStream->fWriteOrRead, NULL); + + /* Make sure it's fully delineated so we can use the index. */ + if (RT_LIKELY(pStream->fFullyLineated)) + return scmStreamGetLineByNoCommon(pStream, iLine, pcchLine, penmEol); + + int rc = pStream->rc; + if (RT_SUCCESS(rc)) + { + rc = scmStreamLineate(pStream); + if (RT_SUCCESS(rc)) + return scmStreamGetLineByNoCommon(pStream, iLine, pcchLine, penmEol); + } + + *pcchLine = 0; + *penmEol = SCMEOL_NONE; + return NULL; +} + +/** + * Get a line from the stream. + * + * A line is always delimited by a LF character or the end of the stream. The + * delimiter is not included in returned line length, but instead returned via + * the @a penmEol indicator. + * + * @returns Pointer to the first character in the line, not NULL terminated. + * NULL if the end of the stream has been reached or some problem + * occurred (*pcchLine set to zero and *penmEol to SCMEOL_NONE). + * + * @param pStream The stream. Must be in read mode. + * @param pcchLine The length. + * @param penmEol Where to return the end of line type indicator. + */ +const char *ScmStreamGetLine(PSCMSTREAM pStream, size_t *pcchLine, PSCMEOL penmEol) +{ + if (RT_LIKELY(pStream->fFullyLineated)) + { + size_t offCur = pStream->off; + size_t iCurLine = pStream->iLine; + const char *pszLine = scmStreamGetLineByNoCommon(pStream, iCurLine, pcchLine, penmEol); + if ( pszLine + && offCur > pStream->paLines[iCurLine].off) + { + offCur -= pStream->paLines[iCurLine].off; + Assert(offCur <= pStream->paLines[iCurLine].cch + pStream->paLines[iCurLine].enmEol); + if (offCur < pStream->paLines[iCurLine].cch) + *pcchLine -= offCur; + else + *pcchLine = 0; + pszLine += offCur; + } + return pszLine; + } + return scmStreamGetLineInternal(pStream, pcchLine, penmEol); +} + +/** + * Get the current buffer pointer. + * + * @returns Buffer pointer on success, NULL on failure (asserted). + * @param pStream The stream. Must be in read mode. + */ +const char *ScmStreamGetCur(PSCMSTREAM pStream) +{ + AssertReturn(!pStream->fWriteOrRead, NULL); + return pStream->pch + pStream->off; +} + +/** + * Gets a character from the stream. + * + * @returns The next unsigned character in the stream. + * ~(unsigned)0 on failure. + * @param pStream The stream. Must be in read mode. + */ +unsigned ScmStreamGetCh(PSCMSTREAM pStream) +{ + /* Check stream state. */ + AssertReturn(!pStream->fWriteOrRead, ~(unsigned)0); + if (RT_FAILURE(pStream->rc)) + return ~(unsigned)0; + if (RT_UNLIKELY(!pStream->fFullyLineated)) + { + int rc = scmStreamLineate(pStream); + if (RT_FAILURE(rc)) + return ~(unsigned)0; + } + + /* If there isn't enough stream left, fail already. */ + if (RT_UNLIKELY(pStream->off >= pStream->cb)) + return ~(unsigned)0; + + /* Read a character. */ + char ch = pStream->pch[pStream->off++]; + + /* Advance the line indicator. */ + size_t iLine = pStream->iLine; + if (pStream->off >= pStream->paLines[iLine].off + pStream->paLines[iLine].cch + pStream->paLines[iLine].enmEol) + pStream->iLine++; + + return (unsigned)ch; +} + + +/** + * Peeks at the next character from the stream. + * + * @returns The next unsigned character in the stream. + * ~(unsigned)0 on failure. + * @param pStream The stream. Must be in read mode. + */ +unsigned ScmStreamPeekCh(PSCMSTREAM pStream) +{ + /* Check stream state. */ + AssertReturn(!pStream->fWriteOrRead, ~(unsigned)0); + if (RT_FAILURE(pStream->rc)) + return ~(unsigned)0; + if (RT_UNLIKELY(!pStream->fFullyLineated)) + { + int rc = scmStreamLineate(pStream); + if (RT_FAILURE(rc)) + return ~(unsigned)0; + } + + /* If there isn't enough stream left, fail already. */ + if (RT_UNLIKELY(pStream->off >= pStream->cb)) + return ~(unsigned)0; + + /* Peek at the next character. */ + char ch = pStream->pch[pStream->off]; + return (unsigned)ch; +} + + +/** + * Reads @a cbToRead bytes into @a pvBuf. + * + * Will fail if end of stream is encountered before the entire read has been + * completed. + * + * @returns IPRT status code. + * @retval VERR_EOF if there isn't @a cbToRead bytes left to read. Stream + * position will be unchanged. + * + * @param pStream The stream. Must be in read mode. + * @param pvBuf The buffer to read into. + * @param cbToRead The number of bytes to read. + */ +int ScmStreamRead(PSCMSTREAM pStream, void *pvBuf, size_t cbToRead) +{ + AssertReturn(!pStream->fWriteOrRead, VERR_PERMISSION_DENIED); + if (RT_FAILURE(pStream->rc)) + return pStream->rc; + + /* If there isn't enough stream left, fail already. */ + if (RT_UNLIKELY(pStream->cb - pStream->off < cbToRead)) + return VERR_EOF; + + /* Copy the data and simply seek to the new stream position. */ + memcpy(pvBuf, &pStream->pch[pStream->off], cbToRead); + return ScmStreamSeekAbsolute(pStream, pStream->off + cbToRead); +} + + +/** + * Checks if we're at the end of the stream. + * + * @returns true if end of stream, false if not. + * @param pStream The stream. Must be in read mode. + */ +bool ScmStreamIsEndOfStream(PSCMSTREAM pStream) +{ + AssertReturn(!pStream->fWriteOrRead, false); + return pStream->off >= pStream->cb + || RT_FAILURE(pStream->rc); +} + + +/** + * Checks if the given line is empty or full of white space. + * + * @returns true if white space only, false if not (or if non-existant). + * @param pStream The stream. Must be in read mode. + * @param iLine The line in question. + */ +bool ScmStreamIsWhiteLine(PSCMSTREAM pStream, size_t iLine) +{ + SCMEOL enmEol; + size_t cchLine; + const char *pchLine = ScmStreamGetLineByNo(pStream, iLine, &cchLine, &enmEol); + if (!pchLine) + return false; + while (cchLine && RT_C_IS_SPACE(*pchLine)) + pchLine++, cchLine--; + return cchLine == 0; +} + + +/** + * Try figure out the end of line style of the give stream. + * + * @returns Most likely end of line style. + * @param pStream The stream. + */ +SCMEOL ScmStreamGetEol(PSCMSTREAM pStream) +{ + SCMEOL enmEol; + if (pStream->cLines > 0) + enmEol = pStream->paLines[0].enmEol; + else if (pStream->cb == 0) + enmEol = SCMEOL_NONE; + else + { + const char *pchLF = (const char *)memchr(pStream->pch, '\n', pStream->cb); + if (pchLF && pchLF != pStream->pch && pchLF[-1] == '\r') + enmEol = SCMEOL_CRLF; + else + enmEol = SCMEOL_LF; + } + + if (enmEol == SCMEOL_NONE) +#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) + enmEol = SCMEOL_CRLF; +#else + enmEol = SCMEOL_LF; +#endif + return enmEol; +} + + +/** + * Get the end of line indicator type for a line. + * + * @returns The EOL indicator. If the line isn't found, the default EOL + * indicator is return. + * @param pStream The stream. + * @param iLine The line (0-base). + */ +SCMEOL ScmStreamGetEolByLine(PSCMSTREAM pStream, size_t iLine) +{ + SCMEOL enmEol; + if (iLine < pStream->cLines) + enmEol = pStream->paLines[iLine].enmEol; + else +#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) + enmEol = SCMEOL_CRLF; +#else + enmEol = SCMEOL_LF; +#endif + return enmEol; +} + + +/** + * Appends a line to the stream. + * + * @returns IPRT status code. + * @param pStream The stream. Must be in write mode. + * @param pchLine Pointer to the line. + * @param cchLine Line length. + * @param enmEol Which end of line indicator to use. + */ +int ScmStreamPutLine(PSCMSTREAM pStream, const char *pchLine, size_t cchLine, SCMEOL enmEol) +{ + AssertReturn(pStream->fWriteOrRead, VERR_ACCESS_DENIED); + if (RT_FAILURE(pStream->rc)) + return pStream->rc; + + /* + * Make sure the previous line has a new-line indicator. + */ + size_t off = pStream->off; + size_t iLine = pStream->iLine; + if (RT_UNLIKELY( iLine != 0 + && pStream->paLines[iLine - 1].enmEol == SCMEOL_NONE)) + { + AssertReturn(pStream->paLines[iLine].cch == 0, VERR_INTERNAL_ERROR_3); + SCMEOL enmEol2 = enmEol != SCMEOL_NONE ? enmEol : ScmStreamGetEol(pStream); + if (RT_UNLIKELY(off + cchLine + enmEol + enmEol2 > pStream->cbAllocated)) + { + int rc = scmStreamGrowBuffer(pStream, cchLine + enmEol + enmEol2); + if (RT_FAILURE(rc)) + return rc; + } + if (enmEol2 == SCMEOL_LF) + pStream->pch[off++] = '\n'; + else + { + pStream->pch[off++] = '\r'; + pStream->pch[off++] = '\n'; + } + pStream->paLines[iLine - 1].enmEol = enmEol2; + pStream->paLines[iLine].off = off; + pStream->off = off; + pStream->cb = off; + } + + /* + * Ensure we've got sufficient buffer space. + */ + if (RT_UNLIKELY(off + cchLine + enmEol > pStream->cbAllocated)) + { + int rc = scmStreamGrowBuffer(pStream, cchLine + enmEol); + if (RT_FAILURE(rc)) + return rc; + } + + /* + * Add a line record. + */ + if (RT_UNLIKELY(iLine + 1 >= pStream->cLinesAllocated)) + { + int rc = scmStreamGrowLines(pStream, iLine); + if (RT_FAILURE(rc)) + return rc; + } + + pStream->paLines[iLine].cch = off - pStream->paLines[iLine].off + cchLine; + pStream->paLines[iLine].enmEol = enmEol; + + iLine++; + pStream->cLines = iLine; + pStream->iLine = iLine; + + /* + * Copy the line + */ + memcpy(&pStream->pch[off], pchLine, cchLine); + off += cchLine; + if (enmEol == SCMEOL_LF) + pStream->pch[off++] = '\n'; + else if (enmEol == SCMEOL_CRLF) + { + pStream->pch[off++] = '\r'; + pStream->pch[off++] = '\n'; + } + pStream->off = off; + pStream->cb = off; + + /* + * Start a new line. + */ + pStream->paLines[iLine].off = off; + pStream->paLines[iLine].cch = 0; + pStream->paLines[iLine].enmEol = SCMEOL_NONE; + + return VINF_SUCCESS; +} + +/** + * Writes to the stream. + * + * @returns IPRT status code + * @param pStream The stream. Must be in write mode. + * @param pchBuf What to write. + * @param cchBuf How much to write. + */ +int ScmStreamWrite(PSCMSTREAM pStream, const char *pchBuf, size_t cchBuf) +{ + AssertReturn(pStream->fWriteOrRead, VERR_ACCESS_DENIED); + if (RT_FAILURE(pStream->rc)) + return pStream->rc; + + /* + * Ensure we've got sufficient buffer space. + */ + size_t off = pStream->off; + if (RT_UNLIKELY(off + cchBuf > pStream->cbAllocated)) + { + int rc = scmStreamGrowBuffer(pStream, cchBuf); + if (RT_FAILURE(rc)) + return rc; + } + + /* + * Deal with the odd case where we've already pushed a line with SCMEOL_NONE. + */ + size_t iLine = pStream->iLine; + if (RT_UNLIKELY( iLine > 0 + && pStream->paLines[iLine - 1].enmEol == SCMEOL_NONE)) + { + iLine--; + pStream->cLines = iLine; + pStream->iLine = iLine; + } + + /* + * Deal with lines. + */ + const char *pchLF = (const char *)memchr(pchBuf, '\n', cchBuf); + if (!pchLF) + pStream->paLines[iLine].cch += cchBuf; + else + { + const char *pchLine = pchBuf; + for (;;) + { + if (RT_UNLIKELY(iLine + 1 >= pStream->cLinesAllocated)) + { + int rc = scmStreamGrowLines(pStream, iLine); + if (RT_FAILURE(rc)) + { + iLine = pStream->iLine; + pStream->paLines[iLine].cch = off - pStream->paLines[iLine].off; + pStream->paLines[iLine].enmEol = SCMEOL_NONE; + return rc; + } + } + + size_t cchLine = pchLF - pchLine; + if ( cchLine + ? pchLF[-1] != '\r' + : !pStream->paLines[iLine].cch + || pStream->pch[pStream->paLines[iLine].off + pStream->paLines[iLine].cch - 1] != '\r') + pStream->paLines[iLine].enmEol = SCMEOL_LF; + else + { + pStream->paLines[iLine].enmEol = SCMEOL_CRLF; + cchLine--; + } + pStream->paLines[iLine].cch += cchLine; + + iLine++; + size_t offBuf = pchLF + 1 - pchBuf; + pStream->paLines[iLine].off = off + offBuf; + pStream->paLines[iLine].cch = 0; + pStream->paLines[iLine].enmEol = SCMEOL_NONE; + + size_t cchLeft = cchBuf - offBuf; + pchLine = pchLF + 1; + pchLF = (const char *)memchr(pchLine, '\n', cchLeft); + if (!pchLF) + { + pStream->paLines[iLine].cch = cchLeft; + break; + } + } + + pStream->iLine = iLine; + pStream->cLines = iLine; + } + + /* + * Copy the data and update position and size. + */ + memcpy(&pStream->pch[off], pchBuf, cchBuf); + off += cchBuf; + pStream->off = off; + pStream->cb = off; + + return VINF_SUCCESS; +} + +/** + * Write a character to the stream. + * + * @returns IPRT status code + * @param pStream The stream. Must be in write mode. + * @param pchBuf What to write. + * @param cchBuf How much to write. + */ +int ScmStreamPutCh(PSCMSTREAM pStream, char ch) +{ + AssertReturn(pStream->fWriteOrRead, VERR_ACCESS_DENIED); + if (RT_FAILURE(pStream->rc)) + return pStream->rc; + + /* + * Only deal with the simple cases here, use ScmStreamWrite for the + * annoying stuff. + */ + size_t off = pStream->off; + if ( ch == '\n' + || RT_UNLIKELY(off + 1 > pStream->cbAllocated)) + return ScmStreamWrite(pStream, &ch, 1); + + /* + * Just append it. + */ + pStream->pch[off] = ch; + pStream->off = off + 1; + pStream->paLines[pStream->iLine].cch++; + + return VINF_SUCCESS; +} + +/** + * Puts an EOL marker to the stream. + * + * @returns IPRt status code. + * @param pStream The stream. Must be in write mode. + * @param enmEol The end-of-line marker to write. + */ +int ScmStreamPutEol(PSCMSTREAM pStream, SCMEOL enmEol) +{ + if (enmEol == SCMEOL_LF) + return ScmStreamWrite(pStream, "\n", 1); + if (enmEol == SCMEOL_CRLF) + return ScmStreamWrite(pStream, "\r\n", 2); + if (enmEol == SCMEOL_NONE) + return VINF_SUCCESS; + AssertFailedReturn(VERR_INVALID_PARAMETER); +} + +/** + * Formats a string and writes it to the SCM stream. + * + * @returns The number of bytes written (>= 0). Negative value are IPRT error + * status codes. + * @param pStream The stream to write to. + * @param pszFormat The format string. + * @param va The arguments to format. + */ +ssize_t ScmStreamPrintfV(PSCMSTREAM pStream, const char *pszFormat, va_list va) +{ + char *psz; + ssize_t cch = RTStrAPrintfV(&psz, pszFormat, va); + if (cch) + { + int rc = ScmStreamWrite(pStream, psz, cch); + RTStrFree(psz); + if (RT_FAILURE(rc)) + cch = rc; + } + return cch; +} + +/** + * Formats a string and writes it to the SCM stream. + * + * @returns The number of bytes written (>= 0). Negative value are IPRT error + * status codes. + * @param pStream The stream to write to. + * @param pszFormat The format string. + * @param ... The arguments to format. + */ +ssize_t ScmStreamPrintf(PSCMSTREAM pStream, const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + ssize_t cch = ScmStreamPrintfV(pStream, pszFormat, va); + va_end(va); + return cch; +} + +/** + * Copies @a cLines from the @a pSrc stream onto the @a pDst stream. + * + * The stream positions will be used and changed in both streams. + * + * @returns IPRT status code. + * @param pDst The destination stream. Must be in write mode. + * @param cLines The number of lines. (0 is accepted.) + * @param pSrc The source stream. Must be in read mode. + */ +int ScmStreamCopyLines(PSCMSTREAM pDst, PSCMSTREAM pSrc, size_t cLines) +{ + AssertReturn(pDst->fWriteOrRead, VERR_ACCESS_DENIED); + if (RT_FAILURE(pDst->rc)) + return pDst->rc; + + AssertReturn(!pSrc->fWriteOrRead, VERR_ACCESS_DENIED); + if (RT_FAILURE(pSrc->rc)) + return pSrc->rc; + + while (cLines-- > 0) + { + SCMEOL enmEol; + size_t cchLine; + const char *pchLine = ScmStreamGetLine(pSrc, &cchLine, &enmEol); + if (!pchLine) + return pDst->rc = RT_FAILURE(pSrc->rc) ? pSrc->rc : VERR_EOF; + + int rc = ScmStreamPutLine(pDst, pchLine, cchLine, enmEol); + if (RT_FAILURE(rc)) + return rc; + } + + return VINF_SUCCESS; +} + + +/** + * If the given C word is at off - 1, return @c true and skip beyond it, + * otherwise return @c false. + * + * @retval true if the given C-word is at the current position minus one char. + * The stream position changes. + * @retval false if not. The stream position is unchanged. + * + * @param pStream The stream. + * @param cchWord The length of the word. + * @param pszWord The word. + */ +bool ScmStreamCMatchingWordM1(PSCMSTREAM pStream, const char *pszWord, size_t cchWord) +{ + /* Check stream state. */ + AssertReturn(!pStream->fWriteOrRead, false); + AssertReturn(RT_SUCCESS(pStream->rc), false); + AssertReturn(pStream->fFullyLineated, false); + + /* Sufficient chars left on the line? */ + size_t const iLine = pStream->iLine; + AssertReturn(pStream->off > pStream->paLines[iLine].off, false); + size_t const cchLeft = pStream->paLines[iLine].cch + pStream->paLines[iLine].off - (pStream->off - 1); + if (cchWord > cchLeft) + return false; + + /* Do they match? */ + const char *psz = &pStream->pch[pStream->off - 1]; + if (memcmp(psz, pszWord, cchWord)) + return false; + + /* Is it the end of a C word? */ + if (cchWord < cchLeft) + { + psz += cchWord; + if (RT_C_IS_ALNUM(*psz) || *psz == '_') + return false; + } + + /* Skip ahead. */ + pStream->off += cchWord - 1; + return true; +} + + +/** + * Get's the C word starting at the current position. + * + * @returns Pointer to the word on success and the stream position advanced to + * the end of it. + * NULL on failure, stream position normally unchanged. + * @param pStream The stream to get the C word from. + * @param pcchWord Where to return the word length. + */ +const char *ScmStreamCGetWord(PSCMSTREAM pStream, size_t *pcchWord) +{ + /* Check stream state. */ + AssertReturn(!pStream->fWriteOrRead, NULL); + AssertReturn(RT_SUCCESS(pStream->rc), NULL); + AssertReturn(pStream->fFullyLineated, NULL); + + /* Get the number of chars left on the line and locate the current char. */ + size_t const iLine = pStream->iLine; + size_t const cchLeft = pStream->paLines[iLine].cch + pStream->paLines[iLine].off - pStream->off; + const char *psz = &pStream->pch[pStream->off]; + + /* Is it a leading C character. */ + if (!RT_C_IS_ALPHA(*psz) && *psz != '_') + return NULL; + + /* Find the end of the word. */ + char ch; + size_t off = 1; + while ( off < cchLeft + && ( (ch = psz[off]) == '_' + || RT_C_IS_ALNUM(ch))) + off++; + + pStream->off += off; + *pcchWord = off; + return psz; +} + + +/** + * Get's the C word starting at the current position minus one. + * + * @returns Pointer to the word on success and the stream position advanced to + * the end of it. + * NULL on failure, stream position normally unchanged. + * @param pStream The stream to get the C word from. + * @param pcchWord Where to return the word length. + */ +const char *ScmStreamCGetWordM1(PSCMSTREAM pStream, size_t *pcchWord) +{ + /* Check stream state. */ + AssertReturn(!pStream->fWriteOrRead, NULL); + AssertReturn(RT_SUCCESS(pStream->rc), NULL); + AssertReturn(pStream->fFullyLineated, NULL); + + /* Get the number of chars left on the line and locate the current char. */ + size_t const iLine = pStream->iLine; + size_t const cchLeft = pStream->paLines[iLine].cch + pStream->paLines[iLine].off - (pStream->off - 1); + const char *psz = &pStream->pch[pStream->off - 1]; + + /* Is it a leading C character. */ + if (!RT_C_IS_ALPHA(*psz) && *psz != '_') + return NULL; + + /* Find the end of the word. */ + char ch; + size_t off = 1; + while ( off < cchLeft + && ( (ch = psz[off]) == '_' + || RT_C_IS_ALNUM(ch))) + off++; + + pStream->off += off - 1; + *pcchWord = off; + return psz; +} + diff --git a/src/bldprogs/scmstream.h b/src/bldprogs/scmstream.h new file mode 100644 index 00000000..e66311ca --- /dev/null +++ b/src/bldprogs/scmstream.h @@ -0,0 +1,152 @@ +/* $Id: scmstream.h $ */ +/** @file + * IPRT Testcase / Tool - Source Code Massager Stream Code. + */ + +/* + * Copyright (C) 2012-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef VBOX_INCLUDED_SRC_bldprogs_scmstream_h +#define VBOX_INCLUDED_SRC_bldprogs_scmstream_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/types.h> + +RT_C_DECLS_BEGIN + +/** End of line marker type. */ +typedef enum SCMEOL +{ + SCMEOL_NONE = 0, + SCMEOL_LF = 1, + SCMEOL_CRLF = 2 +} SCMEOL; +/** Pointer to an end of line marker type. */ +typedef SCMEOL *PSCMEOL; + +/** + * Line record. + */ +typedef struct SCMSTREAMLINE +{ + /** The offset of the line. */ + size_t off; + /** The line length, excluding the LF character. + * @todo This could be derived from the offset of the next line if that wasn't + * so tedious. */ + size_t cch; + /** The end of line marker type. */ + SCMEOL enmEol; +} SCMSTREAMLINE; +/** Pointer to a line record. */ +typedef SCMSTREAMLINE *PSCMSTREAMLINE; + +/** + * Source code massager stream. + */ +typedef struct SCMSTREAM +{ + /** Pointer to the file memory. */ + char *pch; + /** The current stream position. */ + size_t off; + /** The current stream size. */ + size_t cb; + /** The size of the memory pb points to. */ + size_t cbAllocated; + + /** Line records. */ + PSCMSTREAMLINE paLines; + /** The current line. */ + size_t iLine; + /** The current stream size given in lines. */ + size_t cLines; + /** The sizeof the memory backing paLines. */ + size_t cLinesAllocated; + + /** Set if write-only, clear if read-only. */ + bool fWriteOrRead; + /** Set if the memory pb points to is from RTFileReadAll. */ + bool fFileMemory; + /** Set if fully broken into lines. */ + bool fFullyLineated; + + /** Stream status code (IPRT). */ + int rc; +} SCMSTREAM; +/** Pointer to a SCM stream. */ +typedef SCMSTREAM *PSCMSTREAM; +/** Pointer to a const SCM stream. */ +typedef SCMSTREAM const *PCSCMSTREAM; + + +int ScmStreamInitForReading(PSCMSTREAM pStream, const char *pszFilename); +int ScmStreamInitForWriting(PSCMSTREAM pStream, PCSCMSTREAM pRelatedStream); +void ScmStreamDelete(PSCMSTREAM pStream); +int ScmStreamGetStatus(PCSCMSTREAM pStream); +void ScmStreamRewindForReading(PSCMSTREAM pStream); +void ScmStreamRewindForWriting(PSCMSTREAM pStream); +bool ScmStreamIsText(PSCMSTREAM pStream); +int ScmStreamCheckItegrity(PSCMSTREAM pStream); +int ScmStreamWriteToFile(PSCMSTREAM pStream, const char *pszFilenameFmt, ...); +int ScmStreamWriteToStdOut(PSCMSTREAM pStream); + +size_t ScmStreamTell(PSCMSTREAM pStream); +size_t ScmStreamTellLine(PSCMSTREAM pStream); +size_t ScmStreamTellOffsetOfLine(PSCMSTREAM pStream, size_t iLine); +size_t ScmStreamSize(PSCMSTREAM pStream); +size_t ScmStreamCountLines(PSCMSTREAM pStream); +int ScmStreamSeekAbsolute(PSCMSTREAM pStream, size_t offAbsolute); +int ScmStreamSeekRelative(PSCMSTREAM pStream, ssize_t offRelative); +int ScmStreamSeekByLine(PSCMSTREAM pStream, size_t iLine); +bool ScmStreamIsAtStartOfLine(PSCMSTREAM pStream); +bool ScmStreamAreIdentical(PCSCMSTREAM pStream1, PCSCMSTREAM pStream2); + +const char *ScmStreamGetLineByNo(PSCMSTREAM pStream, size_t iLine, size_t *pcchLine, PSCMEOL penmEol); +const char *ScmStreamGetLine(PSCMSTREAM pStream, size_t *pcchLine, PSCMEOL penmEol); +unsigned ScmStreamGetCh(PSCMSTREAM pStream); +const char *ScmStreamGetCur(PSCMSTREAM pStream); +unsigned ScmStreamPeekCh(PSCMSTREAM pStream); +int ScmStreamRead(PSCMSTREAM pStream, void *pvBuf, size_t cbToRead); +bool ScmStreamIsEndOfStream(PSCMSTREAM pStream); +bool ScmStreamIsWhiteLine(PSCMSTREAM pStream, size_t iLine); +SCMEOL ScmStreamGetEol(PSCMSTREAM pStream); +SCMEOL ScmStreamGetEolByLine(PSCMSTREAM pStream, size_t iLine); + +int ScmStreamPutLine(PSCMSTREAM pStream, const char *pchLine, size_t cchLine, SCMEOL enmEol); +int ScmStreamWrite(PSCMSTREAM pStream, const char *pchBuf, size_t cchBuf); +int ScmStreamPutCh(PSCMSTREAM pStream, char ch); +int ScmStreamPutEol(PSCMSTREAM pStream, SCMEOL enmEol); +ssize_t ScmStreamPrintf(PSCMSTREAM pStream, const char *pszFormat, ...); +ssize_t ScmStreamPrintfV(PSCMSTREAM pStream, const char *pszFormat, va_list va); +int ScmStreamCopyLines(PSCMSTREAM pDst, PSCMSTREAM pSrc, size_t cLines); + +bool ScmStreamCMatchingWordM1(PSCMSTREAM pStream, const char *pszWord, size_t cchWord); +const char *ScmStreamCGetWord(PSCMSTREAM pStream, size_t *pcchWord); +const char *ScmStreamCGetWordM1(PSCMSTREAM pStream, size_t *pcchWord); + +RT_C_DECLS_END + +#endif /* !VBOX_INCLUDED_SRC_bldprogs_scmstream_h */ + diff --git a/src/bldprogs/scmsubversion.cpp b/src/bldprogs/scmsubversion.cpp new file mode 100644 index 00000000..f55ee85e --- /dev/null +++ b/src/bldprogs/scmsubversion.cpp @@ -0,0 +1,1690 @@ +/* $Id: scmsubversion.cpp $ */ +/** @file + * IPRT Testcase / Tool - Source Code Massager, Subversion Access. + */ + +/* + * Copyright (C) 2010-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#define SCM_WITH_DYNAMIC_LIB_SVN + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/assert.h> +#include <iprt/ctype.h> +#include <iprt/dir.h> +#include <iprt/env.h> +#include <iprt/err.h> +#include <iprt/file.h> +#include <iprt/getopt.h> +#include <iprt/handle.h> +#include <iprt/initterm.h> +#include <iprt/ldr.h> +#include <iprt/mem.h> +#include <iprt/message.h> +#include <iprt/param.h> +#include <iprt/path.h> +#include <iprt/pipe.h> +#include <iprt/poll.h> +#include <iprt/process.h> +#include <iprt/stream.h> +#include <iprt/string.h> + +#include "scm.h" + +#if defined(SCM_WITH_DYNAMIC_LIB_SVN) && defined(SCM_WITH_SVN_HEADERS) +# include <svn_client.h> +#endif + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#ifdef SCM_WITH_DYNAMIC_LIB_SVN +# if defined(RT_OS_WINDOWS) && defined(RT_ARCH_X86) +# define APR_CALL __stdcall +# define SVN_CALL /* __stdcall ?? */ +# else +# define APR_CALL +# define SVN_CALL +# endif +#endif +#if defined(SCM_WITH_DYNAMIC_LIB_SVN) && !defined(SCM_WITH_SVN_HEADERS) +# define SVN_ERR_MISC_CATEGORY_START 200000 +# define SVN_ERR_UNVERSIONED_RESOURCE (SVN_ERR_MISC_CATEGORY_START + 5) +#endif + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +#if defined(SCM_WITH_DYNAMIC_LIB_SVN) && !defined(SCM_WITH_SVN_HEADERS) +typedef int apr_status_t; +typedef int64_t apr_time_t; +typedef struct apr_pool_t apr_pool_t; +typedef struct apr_hash_t apr_hash_t; +typedef struct apr_hash_index_t apr_hash_index_t; +typedef struct apr_array_header_t apr_array_header_t; + + +typedef struct svn_error_t +{ + apr_status_t apr_err; + const char *_dbgr_message; + struct svn_error_t *_dbgr_child; + apr_pool_t *_dbgr_pool; + const char *_dbgr_file; + long _dbgr_line; +} svn_error_t; +typedef int svn_boolean_t; +typedef long int svn_revnum_t; +typedef struct svn_client_ctx_t svn_client_ctx_t; +typedef enum svn_opt_revision_kind +{ + svn_opt_revision_unspecified = 0, + svn_opt_revision_number, + svn_opt_revision_date, + svn_opt_revision_committed, + svn_opt_revision_previous, + svn_opt_revision_base, + svn_opt_revision_working, + svn_opt_revision_head +} svn_opt_revision_kind; +typedef union svn_opt_revision_value_t +{ + svn_revnum_t number; + apr_time_t date; +} svn_opt_revision_value_t; +typedef struct svn_opt_revision_t +{ + svn_opt_revision_kind kind; + svn_opt_revision_value_t value; +} svn_opt_revision_t; +typedef enum svn_depth_t +{ + svn_depth_unknown = -2, + svn_depth_exclude, + svn_depth_empty, + svn_depth_files, + svn_depth_immediates, + svn_depth_infinity +} svn_depth_t; + +#endif /* SCM_WITH_DYNAMIC_LIB_SVN && !SCM_WITH_SVN_HEADERS */ + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static char g_szSvnPath[RTPATH_MAX]; +static enum +{ + kScmSvnVersion_Ancient = 1, + kScmSvnVersion_1_6, + kScmSvnVersion_1_7, + kScmSvnVersion_1_8, + kScmSvnVersion_End +} g_enmSvnVersion = kScmSvnVersion_Ancient; + + +#ifdef SCM_WITH_DYNAMIC_LIB_SVN +/** Set if all the function pointers are valid. */ +static bool g_fSvnFunctionPointersValid; +/** @name SVN and APR imports. + * @{ */ +static apr_status_t (APR_CALL *g_pfnAprInitialize)(void); +static apr_hash_index_t * (APR_CALL *g_pfnAprHashFirst)(apr_pool_t *pPool, apr_hash_t *pHashTab); +static apr_hash_index_t * (APR_CALL *g_pfnAprHashNext)(apr_hash_index_t *pCurIdx); +static void * (APR_CALL *g_pfnAprHashThisVal)(apr_hash_index_t *pHashIdx); +static apr_pool_t * (SVN_CALL *g_pfnSvnPoolCreateEx)(apr_pool_t *pParent, void *pvAllocator); +static void (APR_CALL *g_pfnAprPoolClear)(apr_pool_t *pPool); +static void (APR_CALL *g_pfnAprPoolDestroy)(apr_pool_t *pPool); + +static svn_error_t * (SVN_CALL *g_pfnSvnClientCreateContext)(svn_client_ctx_t **ppCtx, apr_pool_t *pPool); +static svn_error_t * (SVN_CALL *g_pfnSvnClientPropGet4)(apr_hash_t **ppHashProps, const char *pszPropName, + const char *pszTarget, const svn_opt_revision_t *pPeggedRev, + const svn_opt_revision_t *pRevision, svn_revnum_t *pActualRev, + svn_depth_t enmDepth, const apr_array_header_t *pChangeList, + svn_client_ctx_t *pCtx, apr_pool_t *pResultPool, + apr_pool_t *pScratchPool); +/**@} */ + +/** Cached APR pool. */ +static apr_pool_t *g_pSvnPool = NULL; +/** Cached SVN client context. */ +static svn_client_ctx_t *g_pSvnClientCtx = NULL; +/** Number of times the current context has been used. */ +static uint32_t g_cSvnClientCtxUsed = 0; + +#endif + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +#ifdef SCM_WITH_DYNAMIC_LIB_SVN +static void scmSvnFlushClientContextAndPool(void); +#endif + + + +/** + * Callback that is call for each path to search. + */ +static DECLCALLBACK(int) scmSvnFindSvnBinaryCallback(char const *pchPath, size_t cchPath, void *pvUser1, void *pvUser2) +{ + char *pszDst = (char *)pvUser1; + size_t cchDst = (size_t)pvUser2; + if (cchDst > cchPath) + { + memcpy(pszDst, pchPath, cchPath); + pszDst[cchPath] = '\0'; +#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) + int rc = RTPathAppend(pszDst, cchDst, "svn.exe"); +#else + int rc = RTPathAppend(pszDst, cchDst, "svn"); +#endif + if ( RT_SUCCESS(rc) + && RTFileExists(pszDst)) + return VINF_SUCCESS; + } + return VERR_TRY_AGAIN; +} + + +/** + * Reads from a pipe. + * + * @returns @a rc or other status code. + * @param rc The current status of the operation. Error status + * are preserved and returned. + * @param phPipeR Pointer to the pipe handle. + * @param pcbAllocated Pointer to the buffer size variable. + * @param poffCur Pointer to the buffer offset variable. + * @param ppszBuffer Pointer to the buffer pointer variable. + */ +static int rtProcProcessOutput(int rc, PRTPIPE phPipeR, size_t *pcbAllocated, size_t *poffCur, char **ppszBuffer, + RTPOLLSET hPollSet, uint32_t idPollSet) +{ + size_t cbRead; + char szTmp[_4K - 1]; + for (;;) + { + int rc2 = RTPipeRead(*phPipeR, szTmp, sizeof(szTmp), &cbRead); + if (RT_SUCCESS(rc2) && cbRead) + { + /* Resize the buffer. */ + if (*poffCur + cbRead >= *pcbAllocated) + { + if (*pcbAllocated >= _1G) + { + RTPollSetRemove(hPollSet, idPollSet); + rc2 = RTPipeClose(*phPipeR); AssertRC(rc2); + *phPipeR = NIL_RTPIPE; + return RT_SUCCESS(rc) ? VERR_TOO_MUCH_DATA : rc; + } + + size_t cbNew = *pcbAllocated ? *pcbAllocated * 2 : sizeof(szTmp) + 1; + Assert(*poffCur + cbRead < cbNew); + rc2 = RTStrRealloc(ppszBuffer, cbNew); + if (RT_FAILURE(rc2)) + { + RTPollSetRemove(hPollSet, idPollSet); + rc2 = RTPipeClose(*phPipeR); AssertRC(rc2); + *phPipeR = NIL_RTPIPE; + return RT_SUCCESS(rc) ? rc2 : rc; + } + *pcbAllocated = cbNew; + } + + /* Append the new data, terminating it. */ + memcpy(*ppszBuffer + *poffCur, szTmp, cbRead); + *poffCur += cbRead; + (*ppszBuffer)[*poffCur] = '\0'; + + /* Check for null terminators in the string. */ + if (RT_SUCCESS(rc) && memchr(szTmp, '\0', cbRead)) + rc = VERR_NO_TRANSLATION; + + /* If we read a full buffer, try read some more. */ + if (RT_SUCCESS(rc) && cbRead == sizeof(szTmp)) + continue; + } + else if (rc2 != VINF_TRY_AGAIN) + { + if (RT_FAILURE(rc) && rc2 != VERR_BROKEN_PIPE) + rc = rc2; + RTPollSetRemove(hPollSet, idPollSet); + rc2 = RTPipeClose(*phPipeR); AssertRC(rc2); + *phPipeR = NIL_RTPIPE; + } + return rc; + } +} + +/** @name RTPROCEXEC_FLAGS_XXX - flags for RTProcExec and RTProcExecToString. + * @{ */ +/** Redirect /dev/null to standard input. */ +#define RTPROCEXEC_FLAGS_STDIN_NULL RT_BIT_32(0) +/** Redirect standard output to /dev/null. */ +#define RTPROCEXEC_FLAGS_STDOUT_NULL RT_BIT_32(1) +/** Redirect standard error to /dev/null. */ +#define RTPROCEXEC_FLAGS_STDERR_NULL RT_BIT_32(2) +/** Redirect all standard output to /dev/null as well as directing /dev/null + * to standard input. */ +#define RTPROCEXEC_FLAGS_STD_NULL ( RTPROCEXEC_FLAGS_STDIN_NULL \ + | RTPROCEXEC_FLAGS_STDOUT_NULL \ + | RTPROCEXEC_FLAGS_STDERR_NULL) +/** Mask containing the valid flags. */ +#define RTPROCEXEC_FLAGS_VALID_MASK UINT32_C(0x00000007) +/** @} */ + +/** + * Runs a process, collecting the standard output and/or standard error. + * + * + * @returns IPRT status code + * @retval VERR_NO_TRANSLATION if the output of the program isn't valid UTF-8 + * or contains a nul character. + * @retval VERR_TOO_MUCH_DATA if the process produced too much data. + * + * @param pszExec Executable image to use to create the child process. + * @param papszArgs Pointer to an array of arguments to the child. The + * array terminated by an entry containing NULL. + * @param hEnv Handle to the environment block for the child. + * @param fFlags A combination of RTPROCEXEC_FLAGS_XXX. The @a + * ppszStdOut and @a ppszStdErr parameters takes precedence + * over redirection flags. + * @param pStatus Where to return the status on success. + * @param ppszStdOut Where to return the text written to standard output. If + * NULL then standard output will not be collected and go + * to the standard output handle of the process. + * Free with RTStrFree, regardless of return status. + * @param ppszStdErr Where to return the text written to standard error. If + * NULL then standard output will not be collected and go + * to the standard error handle of the process. + * Free with RTStrFree, regardless of return status. + */ +int RTProcExecToString(const char *pszExec, const char * const *papszArgs, RTENV hEnv, uint32_t fFlags, + PRTPROCSTATUS pStatus, char **ppszStdOut, char **ppszStdErr) +{ + int rc2; + + /* + * Clear output arguments (no returning failure here, simply crash!). + */ + AssertPtr(pStatus); + pStatus->enmReason = RTPROCEXITREASON_ABEND; + pStatus->iStatus = RTEXITCODE_FAILURE; + AssertPtrNull(ppszStdOut); + if (ppszStdOut) + *ppszStdOut = NULL; + AssertPtrNull(ppszStdOut); + if (ppszStdErr) + *ppszStdErr = NULL; + + /* + * Check input arguments. + */ + AssertReturn(!(fFlags & ~RTPROCEXEC_FLAGS_VALID_MASK), VERR_INVALID_PARAMETER); + + /* + * Do we need a standard input bitbucket? + */ + int rc = VINF_SUCCESS; + PRTHANDLE phChildStdIn = NULL; + RTHANDLE hChildStdIn; + hChildStdIn.enmType = RTHANDLETYPE_FILE; + hChildStdIn.u.hFile = NIL_RTFILE; + if ((fFlags & RTPROCEXEC_FLAGS_STDIN_NULL) && RT_SUCCESS(rc)) + { + phChildStdIn = &hChildStdIn; + rc = RTFileOpenBitBucket(&hChildStdIn.u.hFile, RTFILE_O_READ); + } + + /* + * Create the output pipes / bitbuckets. + */ + RTPIPE hPipeStdOutR = NIL_RTPIPE; + PRTHANDLE phChildStdOut = NULL; + RTHANDLE hChildStdOut; + hChildStdOut.enmType = RTHANDLETYPE_PIPE; + hChildStdOut.u.hPipe = NIL_RTPIPE; + if (ppszStdOut && RT_SUCCESS(rc)) + { + phChildStdOut = &hChildStdOut; + rc = RTPipeCreate(&hPipeStdOutR, &hChildStdOut.u.hPipe, 0 /*fFlags*/); + } + else if ((fFlags & RTPROCEXEC_FLAGS_STDOUT_NULL) && RT_SUCCESS(rc)) + { + phChildStdOut = &hChildStdOut; + hChildStdOut.enmType = RTHANDLETYPE_FILE; + hChildStdOut.u.hFile = NIL_RTFILE; + rc = RTFileOpenBitBucket(&hChildStdOut.u.hFile, RTFILE_O_WRITE); + } + + RTPIPE hPipeStdErrR = NIL_RTPIPE; + PRTHANDLE phChildStdErr = NULL; + RTHANDLE hChildStdErr; + hChildStdErr.enmType = RTHANDLETYPE_PIPE; + hChildStdErr.u.hPipe = NIL_RTPIPE; + if (ppszStdErr && RT_SUCCESS(rc)) + { + phChildStdErr = &hChildStdErr; + rc = RTPipeCreate(&hPipeStdErrR, &hChildStdErr.u.hPipe, 0 /*fFlags*/); + } + else if ((fFlags & RTPROCEXEC_FLAGS_STDERR_NULL) && RT_SUCCESS(rc)) + { + phChildStdErr = &hChildStdErr; + hChildStdErr.enmType = RTHANDLETYPE_FILE; + hChildStdErr.u.hFile = NIL_RTFILE; + rc = RTFileOpenBitBucket(&hChildStdErr.u.hFile, RTFILE_O_WRITE); + } + + if (RT_SUCCESS(rc)) + { + RTPOLLSET hPollSet; + rc = RTPollSetCreate(&hPollSet); + if (RT_SUCCESS(rc)) + { + if (hPipeStdOutR != NIL_RTPIPE && RT_SUCCESS(rc)) + rc = RTPollSetAddPipe(hPollSet, hPipeStdOutR, RTPOLL_EVT_READ | RTPOLL_EVT_ERROR, 1); + if (hPipeStdErrR != NIL_RTPIPE) + rc = RTPollSetAddPipe(hPollSet, hPipeStdErrR, RTPOLL_EVT_READ | RTPOLL_EVT_ERROR, 2); + } + if (RT_SUCCESS(rc)) + { + /* + * Create the process. + */ + RTPROCESS hProc; + rc = RTProcCreateEx(pszExec, + papszArgs, + hEnv, + 0 /*fFlags*/, + NULL /*phStdIn*/, + phChildStdOut, + phChildStdErr, + NULL /*pszAsUser*/, + NULL /*pszPassword*/, + NULL /*pvExtraData*/, + &hProc); + rc2 = RTHandleClose(&hChildStdErr); AssertRC(rc2); + rc2 = RTHandleClose(&hChildStdOut); AssertRC(rc2); + + if (RT_SUCCESS(rc)) + { + /* + * Process output and wait for the process to finish. + */ + size_t cbStdOut = 0; + size_t offStdOut = 0; + size_t cbStdErr = 0; + size_t offStdErr = 0; + for (;;) + { + if (hPipeStdOutR != NIL_RTPIPE) + rc = rtProcProcessOutput(rc, &hPipeStdOutR, &cbStdOut, &offStdOut, ppszStdOut, hPollSet, 1); + if (hPipeStdErrR != NIL_RTPIPE) + rc = rtProcProcessOutput(rc, &hPipeStdErrR, &cbStdErr, &offStdErr, ppszStdErr, hPollSet, 2); + if (hPipeStdOutR == NIL_RTPIPE && hPipeStdErrR == NIL_RTPIPE) + break; + + if (hProc != NIL_RTPROCESS) + { + rc2 = RTProcWait(hProc, RTPROCWAIT_FLAGS_NOBLOCK, pStatus); + if (rc2 != VERR_PROCESS_RUNNING) + { + if (RT_FAILURE(rc2)) + rc = rc2; + hProc = NIL_RTPROCESS; + } + } + + rc2 = RTPoll(hPollSet, 10000, NULL, NULL); + Assert(RT_SUCCESS(rc2) || rc2 == VERR_TIMEOUT); + } + + if (RT_SUCCESS(rc)) + { + if ( (ppszStdOut && *ppszStdOut && !RTStrIsValidEncoding(*ppszStdOut)) + || (ppszStdErr && *ppszStdErr && !RTStrIsValidEncoding(*ppszStdErr)) ) + rc = VERR_NO_TRANSLATION; + } + + /* + * No more output, just wait for it to finish. + */ + if (hProc != NIL_RTPROCESS) + { + rc2 = RTProcWait(hProc, RTPROCWAIT_FLAGS_BLOCK, pStatus); + if (RT_FAILURE(rc2)) + rc = rc2; + } + } + RTPollSetDestroy(hPollSet); + } + } + + rc2 = RTHandleClose(&hChildStdErr); AssertRC(rc2); + rc2 = RTHandleClose(&hChildStdOut); AssertRC(rc2); + rc2 = RTHandleClose(&hChildStdIn); AssertRC(rc2); + rc2 = RTPipeClose(hPipeStdErrR); AssertRC(rc2); + rc2 = RTPipeClose(hPipeStdOutR); AssertRC(rc2); + return rc; +} + + +/** + * Runs a process, waiting for it to complete. + * + * @returns IPRT status code + * + * @param pszExec Executable image to use to create the child process. + * @param papszArgs Pointer to an array of arguments to the child. The + * array terminated by an entry containing NULL. + * @param hEnv Handle to the environment block for the child. + * @param fFlags A combination of RTPROCEXEC_FLAGS_XXX. + * @param pStatus Where to return the status on success. + */ +int RTProcExec(const char *pszExec, const char * const *papszArgs, RTENV hEnv, uint32_t fFlags, + PRTPROCSTATUS pStatus) +{ + int rc; + + /* + * Clear output argument (no returning failure here, simply crash!). + */ + AssertPtr(pStatus); + pStatus->enmReason = RTPROCEXITREASON_ABEND; + pStatus->iStatus = RTEXITCODE_FAILURE; + + /* + * Check input arguments. + */ + AssertReturn(!(fFlags & ~RTPROCEXEC_FLAGS_VALID_MASK), VERR_INVALID_PARAMETER); + + /* + * Set up /dev/null redirections. + */ + PRTHANDLE aph[3] = { NULL, NULL, NULL }; + RTHANDLE ah[3]; + for (uint32_t i = 0; i < 3; i++) + { + ah[i].enmType = RTHANDLETYPE_FILE; + ah[i].u.hFile = NIL_RTFILE; + } + rc = VINF_SUCCESS; + if ((fFlags & RTPROCEXEC_FLAGS_STDIN_NULL) && RT_SUCCESS(rc)) + { + aph[0] = &ah[0]; + rc = RTFileOpenBitBucket(&ah[0].u.hFile, RTFILE_O_READ); + } + if ((fFlags & RTPROCEXEC_FLAGS_STDOUT_NULL) && RT_SUCCESS(rc)) + { + aph[1] = &ah[1]; + rc = RTFileOpenBitBucket(&ah[1].u.hFile, RTFILE_O_WRITE); + } + if ((fFlags & RTPROCEXEC_FLAGS_STDERR_NULL) && RT_SUCCESS(rc)) + { + aph[2] = &ah[2]; + rc = RTFileOpenBitBucket(&ah[2].u.hFile, RTFILE_O_WRITE); + } + + /* + * Create the process. + */ + RTPROCESS hProc = NIL_RTPROCESS; + if (RT_SUCCESS(rc)) + rc = RTProcCreateEx(pszExec, + papszArgs, + hEnv, + 0 /*fFlags*/, + aph[0], + aph[1], + aph[2], + NULL /*pszAsUser*/, + NULL /*pszPassword*/, + NULL /*pvExtraData*/, + &hProc); + + for (uint32_t i = 0; i < 3; i++) + RTFileClose(ah[i].u.hFile); + + if (RT_SUCCESS(rc)) + rc = RTProcWait(hProc, RTPROCWAIT_FLAGS_BLOCK, pStatus); + return rc; +} + + + +/** + * Executes SVN and gets the output. + * + * Standard error is suppressed. + * + * @returns VINF_SUCCESS if the command executed successfully. + * @param pState The rewrite state to work on. Can be NULL. + * @param papszArgs The SVN argument. + * @param fNormalFailureOk Whether normal failure is ok. + * @param ppszStdOut Where to return the output on success. + */ +static int scmSvnRunAndGetOutput(PSCMRWSTATE pState, const char **papszArgs, bool fNormalFailureOk, char **ppszStdOut) +{ + *ppszStdOut = NULL; + +#ifdef SCM_WITH_DYNAMIC_LIB_SVN + scmSvnFlushClientContextAndPool(); +#endif + + char *pszCmdLine = NULL; + int rc = RTGetOptArgvToString(&pszCmdLine, papszArgs, RTGETOPTARGV_CNV_QUOTE_BOURNE_SH); + if (RT_FAILURE(rc)) + return rc; + ScmVerbose(pState, 2, "executing: %s\n", pszCmdLine); + + RTPROCSTATUS Status; + rc = RTProcExecToString(g_szSvnPath, papszArgs, RTENV_DEFAULT, + RTPROCEXEC_FLAGS_STD_NULL, &Status, ppszStdOut, NULL); + + if ( RT_SUCCESS(rc) + && ( Status.enmReason != RTPROCEXITREASON_NORMAL + || Status.iStatus != 0) ) + { + if (fNormalFailureOk || Status.enmReason != RTPROCEXITREASON_NORMAL) + RTMsgError("%s: %s -> %s %u\n", + pState ? pState->pszFilename : "<NONE>", pszCmdLine, + Status.enmReason == RTPROCEXITREASON_NORMAL ? "exit code" + : Status.enmReason == RTPROCEXITREASON_SIGNAL ? "signal" + : Status.enmReason == RTPROCEXITREASON_ABEND ? "abnormal end" + : "abducted by alien", + Status.iStatus); + rc = VERR_GENERAL_FAILURE; + } + else if (RT_FAILURE(rc)) + { + if (pState) + RTMsgError("%s: executing: %s => %Rrc\n", pState->pszFilename, pszCmdLine, rc); + else + RTMsgError("executing: %s => %Rrc\n", pszCmdLine, rc); + } + + if (RT_FAILURE(rc)) + { + RTStrFree(*ppszStdOut); + *ppszStdOut = NULL; + } + RTStrFree(pszCmdLine); + return rc; +} + + +/** + * Executes SVN. + * + * Standard error and standard output is suppressed. + * + * @returns VINF_SUCCESS if the command executed successfully. + * @param pState The rewrite state to work on. + * @param papszArgs The SVN argument. + * @param fNormalFailureOk Whether normal failure is ok. + */ +static int scmSvnRun(PSCMRWSTATE pState, const char **papszArgs, bool fNormalFailureOk) +{ +#ifdef SCM_WITH_DYNAMIC_LIB_SVN + scmSvnFlushClientContextAndPool(); +#endif + + char *pszCmdLine = NULL; + int rc = RTGetOptArgvToString(&pszCmdLine, papszArgs, RTGETOPTARGV_CNV_QUOTE_BOURNE_SH); + if (RT_FAILURE(rc)) + return rc; + ScmVerbose(pState, 2, "executing: %s\n", pszCmdLine); + + /* Lazy bird uses RTProcExec. */ + RTPROCSTATUS Status; + rc = RTProcExec(g_szSvnPath, papszArgs, RTENV_DEFAULT, RTPROCEXEC_FLAGS_STD_NULL, &Status); + + if ( RT_SUCCESS(rc) + && ( Status.enmReason != RTPROCEXITREASON_NORMAL + || Status.iStatus != 0) ) + { + if (fNormalFailureOk || Status.enmReason != RTPROCEXITREASON_NORMAL) + RTMsgError("%s: %s -> %s %u\n", + pState->pszFilename, + pszCmdLine, + Status.enmReason == RTPROCEXITREASON_NORMAL ? "exit code" + : Status.enmReason == RTPROCEXITREASON_SIGNAL ? "signal" + : Status.enmReason == RTPROCEXITREASON_ABEND ? "abnormal end" + : "abducted by alien", + Status.iStatus); + rc = VERR_GENERAL_FAILURE; + } + else if (RT_FAILURE(rc)) + RTMsgError("%s: %s -> %Rrc\n", pState->pszFilename, pszCmdLine, rc); + + RTStrFree(pszCmdLine); + return rc; +} + + +#ifdef SCM_WITH_DYNAMIC_LIB_SVN +/** + * Attempts to resolve the necessary subversion and apache portable runtime APIs + * we require dynamically. + * + * Will set all global function pointers and g_fSvnFunctionPointersValid to true + * on success. + */ +static void scmSvnTryResolveFunctions(void) +{ + char szPath[RTPATH_MAX]; + int rc = RTStrCopy(szPath, sizeof(szPath), g_szSvnPath); + if (RT_SUCCESS(rc)) + { + RTPathStripFilename(szPath); + char *pszEndPath = strchr(szPath, '\0'); +# ifdef RT_OS_WINDOWS + RTPathChangeToDosSlashes(szPath, false); +# endif + + /* + * Try various prefixes/suffxies/locations. + */ + static struct + { + const char *pszPrefix; + const char *pszSuffix; + } const s_aVariations[] = + { +# ifdef RT_OS_WINDOWS + { "SlikSvn-lib", "-1.dll" }, /* SlikSVN */ + { "lib", "-1.dll" }, /* Win32Svn,CollabNet,++ */ +# elif defined(RT_OS_DARWIN) + { "../lib/lib", "-1.dylib" }, +# else + { "../lib/lib", ".so" }, + { "../lib/lib", "-1.so" }, +# if ARCH_BITS == 32 + { "../lib32/lib", ".so" }, + { "../lib32/lib", "-1.so" }, +# else + { "../lib64/lib", ".so" }, + { "../lib64/lib", "-1.so" }, +# ifdef RT_OS_SOLARIS + { "../lib/svn/amd64/lib", ".so" }, + { "../lib/svn/amd64/lib", "-1.so" }, + { "../apr/1.6/lib/amd64/lib", ".so" }, + { "../apr/1.6/lib/amd64/lib", "-1.so" }, +# endif +# endif +# ifdef RT_ARCH_X86 + { "../lib/i386-linux-gnu/lib", ".so" }, + { "../lib/i386-linux-gnu/lib", "-1.so" }, +# elif defined(RT_ARCH_AMD64) + { "../lib/x86_64-linux-gnu/lib", ".so" }, + { "../lib/x86_64-linux-gnu/lib", "-1.so" }, +# endif +# endif + }; + for (unsigned iVar = 0; iVar < RT_ELEMENTS(s_aVariations); iVar++) + { + /* + * Try load the svn_client library ... + */ + static const char * const s_apszLibraries[] = { "svn_client", "svn_subr", "apr" }; + RTLDRMOD ahMods[RT_ELEMENTS(s_apszLibraries)] = { NIL_RTLDRMOD, NIL_RTLDRMOD, NIL_RTLDRMOD }; + + rc = VINF_SUCCESS; + unsigned iLib; + for (iLib = 0; iLib < RT_ELEMENTS(s_apszLibraries) && RT_SUCCESS(rc); iLib++) + { + static const char * const s_apszSuffixes[] = { "", ".0", ".1" }; + for (unsigned iSuff = 0; iSuff < RT_ELEMENTS(s_apszSuffixes); iSuff++) + { + *pszEndPath = '\0'; + rc = RTPathAppend(szPath, sizeof(szPath), s_aVariations[iVar].pszPrefix); + if (RT_SUCCESS(rc)) + rc = RTStrCat(szPath, sizeof(szPath), s_apszLibraries[iLib]); + if (RT_SUCCESS(rc)) + rc = RTStrCat(szPath, sizeof(szPath), s_aVariations[iVar].pszSuffix); + if (RT_SUCCESS(rc)) + rc = RTStrCat(szPath, sizeof(szPath), s_apszSuffixes[iSuff]); + if (RT_SUCCESS(rc)) + { +# ifdef RT_OS_WINDOWS + RTPathChangeToDosSlashes(pszEndPath, false); +# endif + rc = RTLdrLoadEx(szPath, &ahMods[iLib], RTLDRLOAD_FLAGS_NT_SEARCH_DLL_LOAD_DIR , NULL); + if (RT_SUCCESS(rc)) + { + RTMEM_WILL_LEAK(ahMods[iLib]); + break; + } + } + } +# ifdef RT_OS_SOLARIS + /* + * HACK: Solaris may keep libapr.so separately from svn, so do a separate search for it. + */ + /** @todo It would make a lot more sense to use the dlfcn.h machinery to figure + * out which libapr*.so* file was loaded into the process together with + * the two svn libraries and get a dlopen handle for it. We risk ending + * up with the completely wrong libapr here! */ + if (iLib == RT_ELEMENTS(s_apszLibraries) - 1 && RT_FAILURE(rc)) + { + ahMods[iLib] = NIL_RTLDRMOD; + for (unsigned iVar2 = 0; iVar2 < RT_ELEMENTS(s_aVariations) && ahMods[iLib] == NIL_RTLDRMOD; iVar2++) + for (unsigned iSuff2 = 0; iSuff2 < RT_ELEMENTS(s_apszSuffixes) && ahMods[iLib] == NIL_RTLDRMOD; iSuff2++) + { + *pszEndPath = '\0'; + rc = RTPathAppend(szPath, sizeof(szPath), s_aVariations[iVar2].pszPrefix); + if (RT_SUCCESS(rc)) + rc = RTStrCat(szPath, sizeof(szPath), s_apszLibraries[iLib]); + if (RT_SUCCESS(rc)) + rc = RTStrCat(szPath, sizeof(szPath), s_aVariations[iVar2].pszSuffix); + if (RT_SUCCESS(rc)) + rc = RTStrCat(szPath, sizeof(szPath), s_apszSuffixes[iSuff2]); + if (RT_SUCCESS(rc)) + rc = RTLdrLoadEx(szPath, &ahMods[iLib], RTLDRLOAD_FLAGS_NT_SEARCH_DLL_LOAD_DIR, NULL); + if (RT_SUCCESS(rc)) + RTMEM_WILL_LEAK(ahMods[iLib]); + else + ahMods[iLib] = NIL_RTLDRMOD; + } + } +# endif /* RT_OS_SOLARIS */ + } + if (iLib == RT_ELEMENTS(s_apszLibraries) && RT_SUCCESS(rc)) + { + static const struct + { + unsigned iLib; + const char *pszSymbol; + uintptr_t *ppfn; /**< The nothrow attrib of PFNRT goes down the wrong way with Clang 11, thus uintptr_t. */ + } s_aSymbols[] = + { + { 2, "apr_initialize", (uintptr_t *)&g_pfnAprInitialize }, + { 2, "apr_hash_first", (uintptr_t *)&g_pfnAprHashFirst }, + { 2, "apr_hash_next", (uintptr_t *)&g_pfnAprHashNext }, + { 2, "apr_hash_this_val", (uintptr_t *)&g_pfnAprHashThisVal }, + { 1, "svn_pool_create_ex", (uintptr_t *)&g_pfnSvnPoolCreateEx }, + { 2, "apr_pool_clear", (uintptr_t *)&g_pfnAprPoolClear }, + { 2, "apr_pool_destroy", (uintptr_t *)&g_pfnAprPoolDestroy }, + { 0, "svn_client_create_context", (uintptr_t *)&g_pfnSvnClientCreateContext }, + { 0, "svn_client_propget4", (uintptr_t *)&g_pfnSvnClientPropGet4 }, + }; + for (unsigned i = 0; i < RT_ELEMENTS(s_aSymbols); i++) + { + rc = RTLdrGetSymbol(ahMods[s_aSymbols[i].iLib], s_aSymbols[i].pszSymbol, + (void **)(uintptr_t)s_aSymbols[i].ppfn); + if (RT_FAILURE(rc)) + { + ScmVerbose(NULL, 0, "Failed to resolve '%s' in '%s'", + s_aSymbols[i].pszSymbol, s_apszLibraries[s_aSymbols[i].iLib]); + break; + } + } + if (RT_SUCCESS(rc)) + { + apr_status_t rcApr = g_pfnAprInitialize(); + if (rcApr == 0) + { + ScmVerbose(NULL, 1, "Found subversion APIs.\n"); + g_fSvnFunctionPointersValid = true; + } + else + { + ScmVerbose(NULL, 0, "apr_initialize failed: %#x (%d)\n", rcApr, rcApr); + AssertMsgFailed(("%#x (%d)\n", rc, rc)); + } + return; + } + } + + while (iLib-- > 0) + RTLdrClose(ahMods[iLib]); + } + } +} +#endif /* SCM_WITH_DYNAMIC_LIB_SVN */ + + +/** + * Finds the svn binary, updating g_szSvnPath and g_enmSvnVersion. + */ +static void scmSvnFindSvnBinary(PSCMRWSTATE pState) +{ + /* Already been called? */ + if (g_szSvnPath[0] != '\0') + return; + + /* + * Locate it. + */ + /** @todo code page fun... */ +#ifdef RT_OS_WINDOWS + const char *pszEnvVar = RTEnvGet("Path"); +#else + const char *pszEnvVar = RTEnvGet("PATH"); +#endif + if (pszEnvVar) + { +#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) + int rc = RTPathTraverseList(pszEnvVar, ';', scmSvnFindSvnBinaryCallback, g_szSvnPath, (void *)sizeof(g_szSvnPath)); +#else + int rc = RTPathTraverseList(pszEnvVar, ':', scmSvnFindSvnBinaryCallback, g_szSvnPath, (void *)sizeof(g_szSvnPath)); +#endif + if (RT_FAILURE(rc)) + strcpy(g_szSvnPath, "svn"); + } + else + strcpy(g_szSvnPath, "svn"); + + /* + * Check the version. + */ + const char *apszArgs[] = { g_szSvnPath, "--version", "--quiet", NULL }; + char *pszVersion; + int rc = scmSvnRunAndGetOutput(pState, apszArgs, false, &pszVersion); + if (RT_SUCCESS(rc)) + { + char *pszStripped = RTStrStrip(pszVersion); + if (RTStrVersionCompare(pszStripped, "1.8") >= 0) + g_enmSvnVersion = kScmSvnVersion_1_8; + else if (RTStrVersionCompare(pszStripped, "1.7") >= 0) + g_enmSvnVersion = kScmSvnVersion_1_7; + else if (RTStrVersionCompare(pszStripped, "1.6") >= 0) + g_enmSvnVersion = kScmSvnVersion_1_6; + else + g_enmSvnVersion = kScmSvnVersion_Ancient; + RTStrFree(pszVersion); + } + else + g_enmSvnVersion = kScmSvnVersion_Ancient; + +#ifdef SCM_WITH_DYNAMIC_LIB_SVN + /* + * If we got version 1.8 or later, try see if we can locate a few of the + * simpler SVN APIs. + */ + g_fSvnFunctionPointersValid = false; + if (g_enmSvnVersion >= kScmSvnVersion_1_8) + scmSvnTryResolveFunctions(); +#endif +} + + +/** + * Construct a dot svn filename for the file being rewritten. + * + * @returns IPRT status code. + * @param pState The rewrite state (for the name). + * @param pszDir The directory, including ".svn/". + * @param pszSuff The filename suffix. + * @param pszDst The output buffer. RTPATH_MAX in size. + */ +static int scmSvnConstructName(PSCMRWSTATE pState, const char *pszDir, const char *pszSuff, char *pszDst) +{ + strcpy(pszDst, pState->pszFilename); /* ASSUMES sizeof(szBuf) <= sizeof(szPath) */ + RTPathStripFilename(pszDst); + + int rc = RTPathAppend(pszDst, RTPATH_MAX, pszDir); + if (RT_SUCCESS(rc)) + { + rc = RTPathAppend(pszDst, RTPATH_MAX, RTPathFilename(pState->pszFilename)); + if (RT_SUCCESS(rc)) + { + size_t cchDst = strlen(pszDst); + size_t cchSuff = strlen(pszSuff); + if (cchDst + cchSuff < RTPATH_MAX) + { + memcpy(&pszDst[cchDst], pszSuff, cchSuff + 1); + return VINF_SUCCESS; + } + else + rc = VERR_BUFFER_OVERFLOW; + } + } + return rc; +} + +/** + * Interprets the specified string as decimal numbers. + * + * @returns true if parsed successfully, false if not. + * @param pch The string (not terminated). + * @param cch The string length. + * @param pu Where to return the value. + */ +static bool scmSvnReadNumber(const char *pch, size_t cch, size_t *pu) +{ + size_t u = 0; + while (cch-- > 0) + { + char ch = *pch++; + if (ch < '0' || ch > '9') + return false; + u *= 10; + u += ch - '0'; + } + *pu = u; + return true; +} + + +#ifdef SCM_WITH_DYNAMIC_LIB_SVN + +/** + * Wrapper around RTPathAbs. + * @returns Same as RTPathAbs. + * @param pszPath The relative path. + * @param pszAbsPath Where to return the absolute path. + * @param cbAbsPath Size of the @a pszAbsPath buffer. + */ +static int scmSvnAbsPath(const char *pszPath, char *pszAbsPath, size_t cbAbsPath) +{ + int rc = RTPathAbs(pszPath, pszAbsPath, cbAbsPath); +# if RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS + if (RT_SUCCESS(rc)) + { + RTPathChangeToUnixSlashes(pszAbsPath, true /*fForce*/); + /* To avoid: svn: E235000: In file '..\..\..\subversion\libsvn_client\prop_commands.c' line 796: assertion failed (svn_dirent_is_absolute(target)) */ + if (pszAbsPath[1] == ':') + pszAbsPath[0] = RT_C_TO_UPPER(pszAbsPath[0]); + } +# endif + return rc; +} + + +/** + * Gets a client context and pool. + * + * This implements caching. + * + * @returns IPRT status code. + * @param ppCtx Where to return the context + * @param ppPool Where to return the pool. + */ +static int scmSvnGetClientContextAndPool(svn_client_ctx_t **ppCtx, apr_pool_t **ppPool) +{ + /* + * Use cached if present. + */ + if (g_pSvnClientCtx && g_pSvnPool) + { + g_cSvnClientCtxUsed++; + *ppCtx = g_pSvnClientCtx; + *ppPool = g_pSvnPool; + return VINF_SUCCESS; + } + Assert(!g_pSvnClientCtx); + Assert(!g_pSvnPool); + + /* + * Create new pool and context. + */ + apr_pool_t *pPool = g_pfnSvnPoolCreateEx(NULL, NULL); + if (pPool) + { + svn_client_ctx_t *pCtx = NULL; + svn_error_t *pErr = g_pfnSvnClientCreateContext(&pCtx, pPool); + if (!pErr) + { + g_cSvnClientCtxUsed = 1; + g_pSvnClientCtx = *ppCtx = pCtx; + g_pSvnPool = *ppPool = pPool; + return VINF_SUCCESS; + } + g_pfnAprPoolDestroy(pPool); + } + + *ppCtx = NULL; + *ppPool = NULL; + return VERR_GENERAL_FAILURE; +} + + +/** + * Puts back a client context and pool after use. + * + * @param pCtx The context. + * @param pPool The pool. + * @param fFlush Whether to flush it. + */ +static void scmSvnPutClientContextAndPool(svn_client_ctx_t *pCtx, apr_pool_t *pPool, bool fFlush) +{ + if (fFlush || g_cSvnClientCtxUsed > 4096) /* Disable this to force new context every time. */ + { + g_pfnAprPoolDestroy(pPool); + g_pSvnPool = NULL; + g_pSvnClientCtx = NULL; + } + RT_NOREF(pCtx, fFlush); +} + + +/** + * Flushes the cached client context and pool + */ +static void scmSvnFlushClientContextAndPool(void) +{ + if (g_pSvnPool) + scmSvnPutClientContextAndPool(g_pSvnClientCtx, g_pSvnPool, true /*fFlush*/); + Assert(!g_pSvnPool); +} + + +/** + * Checks if @a pszPath exists in the current WC. + * + * @returns true, false or -1. In the latter case, please use the fallback. + * @param pszPath Path to the object that should be investigated. + */ +static int scmSvnIsObjectInWorkingCopy(const char *pszPath) +{ + /* svn_client_propget4 and later requires absolute target path. */ + char szAbsPath[RTPATH_MAX]; + int rc = scmSvnAbsPath(pszPath, szAbsPath, sizeof(szAbsPath)); + if (RT_SUCCESS(rc)) + { + apr_pool_t *pPool; + svn_client_ctx_t *pCtx = NULL; + rc = scmSvnGetClientContextAndPool(&pCtx, &pPool); + if (RT_SUCCESS(rc)) + { + /* Make the call. */ + apr_hash_t *pHash = NULL; + svn_opt_revision_t Rev; + RT_ZERO(Rev); + Rev.kind = svn_opt_revision_working; + Rev.value.number = -1L; + svn_error_t *pErr = g_pfnSvnClientPropGet4(&pHash, "svn:no-such-property", szAbsPath, &Rev, &Rev, + NULL /*pActualRev*/, svn_depth_empty, NULL /*pChangeList*/, + pCtx, pPool, pPool); + if (!pErr) + rc = true; + else if (pErr->apr_err == SVN_ERR_UNVERSIONED_RESOURCE) + rc = false; + + scmSvnPutClientContextAndPool(pCtx, pPool, false); + } + } + return rc; +} + +#endif /* SCM_WITH_DYNAMIC_LIB_SVN */ + + +/** + * Checks if the file we're operating on is part of a SVN working copy. + * + * @returns true if it is, false if it isn't or we cannot tell. + * @param pState The rewrite state to work on. Will use the + * fIsInSvnWorkingCopy member for caching the result. + */ +bool ScmSvnIsInWorkingCopy(PSCMRWSTATE pState) +{ + /* + * We don't ask SVN twice as that's expensive. + */ + if (pState->fIsInSvnWorkingCopy != 0) + return pState->fIsInSvnWorkingCopy > 0; + +#ifdef SCM_WITH_DYNAMIC_LIB_SVN + if (g_fSvnFunctionPointersValid) + { + int rc = scmSvnIsObjectInWorkingCopy(pState->pszFilename); + if (rc == (int)true || rc == (int)false) + { + pState->fIsInSvnWorkingCopy = rc == (int)true ? 1 : -1; + return rc == (int)true; + } + } + + /* Fallback: */ +#endif + if (g_enmSvnVersion < kScmSvnVersion_1_7) + { + /* + * Hack: check if the .svn/text-base/<file>.svn-base file exists. + */ + char szPath[RTPATH_MAX]; + int rc = scmSvnConstructName(pState, ".svn/text-base/", ".svn-base", szPath); + if (RT_SUCCESS(rc)) + { + if (RTFileExists(szPath)) + { + pState->fIsInSvnWorkingCopy = 1; + return true; + } + } + } + else + { + const char *apszArgs[] = { g_szSvnPath, "proplist", pState->pszFilename, NULL }; + char *pszValue; + int rc = scmSvnRunAndGetOutput(pState, apszArgs, true, &pszValue); + if (RT_SUCCESS(rc)) + { + RTStrFree(pszValue); + pState->fIsInSvnWorkingCopy = 1; + return true; + } + } + pState->fIsInSvnWorkingCopy = -1; + return false; +} + + +/** + * Checks if the specified directory is part of a SVN working copy. + * + * @returns true if it is, false if it isn't or we cannot tell. + * @param pszDir The directory in question. + */ +bool ScmSvnIsDirInWorkingCopy(const char *pszDir) +{ +#ifdef SCM_WITH_DYNAMIC_LIB_SVN + if (g_fSvnFunctionPointersValid) + { + int rc = scmSvnIsObjectInWorkingCopy(pszDir); + if (rc == (int)true || rc == (int)false) + return rc == (int)true; + } + + /* Fallback: */ +#endif + if (g_enmSvnVersion < kScmSvnVersion_1_7) + { + /* + * Hack: check if the .svn/ dir exists. + */ + char szPath[RTPATH_MAX]; + int rc = RTPathJoin(szPath, sizeof(szPath), pszDir, ".svn"); + if (RT_SUCCESS(rc)) + return RTDirExists(szPath); + } + else + { + const char *apszArgs[] = { g_szSvnPath, "propget", "svn:no-such-property", pszDir, NULL }; + char *pszValue; + int rc = scmSvnRunAndGetOutput(NULL, apszArgs, true, &pszValue); + if (RT_SUCCESS(rc)) + { + RTStrFree(pszValue); + return true; + } + } + return false; +} + + +#ifdef SCM_WITH_DYNAMIC_LIB_SVN +/** + * Checks if @a pszPath exists in the current WC. + * + * @returns IPRT status code - VERR_NOT_SUPPORT if fallback should be attempted. + * @param pszPath Path to the object that should be investigated. + * @param pszProperty The property name. + * @param ppszValue Where to return the property value. Optional. + */ +static int scmSvnQueryPropertyUsingApi(const char *pszPath, const char *pszProperty, char **ppszValue) +{ + /* svn_client_propget4 and later requires absolute target path. */ + char szAbsPath[RTPATH_MAX]; + int rc = scmSvnAbsPath(pszPath, szAbsPath, sizeof(szAbsPath)); + if (RT_SUCCESS(rc)) + { + apr_pool_t *pPool; + svn_client_ctx_t *pCtx = NULL; + rc = scmSvnGetClientContextAndPool(&pCtx, &pPool); + if (RT_SUCCESS(rc)) + { + /* Make the call. */ + apr_hash_t *pHash = NULL; + svn_opt_revision_t Rev; + RT_ZERO(Rev); + Rev.kind = svn_opt_revision_working; + Rev.value.number = -1L; + svn_error_t *pErr = g_pfnSvnClientPropGet4(&pHash, pszProperty, szAbsPath, &Rev, &Rev, + NULL /*pActualRev*/, svn_depth_empty, NULL /*pChangeList*/, + pCtx, pPool, pPool); + if (!pErr) + { + /* Get the first value, if any. */ + rc = VERR_NOT_FOUND; + apr_hash_index_t *pHashIdx = g_pfnAprHashFirst(pPool, pHash); + if (pHashIdx) + { + const char **ppszFirst = (const char **)g_pfnAprHashThisVal(pHashIdx); + if (ppszFirst && *ppszFirst) + { + if (ppszValue) + rc = RTStrDupEx(ppszValue, *ppszFirst); + else + rc = VINF_SUCCESS; + } + } + } + else if (pErr->apr_err == SVN_ERR_UNVERSIONED_RESOURCE) + rc = VERR_INVALID_STATE; + else + rc = VERR_GENERAL_FAILURE; + + scmSvnPutClientContextAndPool(pCtx, pPool, false); + } + } + return rc; +} +#endif /* SCM_WITH_DYNAMIC_LIB_SVN */ + + +/** + * Queries the value of an SVN property. + * + * This will automatically adjust for scheduled changes. + * + * @returns IPRT status code. + * @retval VERR_INVALID_STATE if not a SVN WC file. + * @retval VERR_NOT_FOUND if the property wasn't found. + * @param pState The rewrite state to work on. + * @param pszName The property name. + * @param ppszValue Where to return the property value. Free this + * using RTStrFree. Optional. + */ +int ScmSvnQueryProperty(PSCMRWSTATE pState, const char *pszName, char **ppszValue) +{ + int rc; + + /* + * Look it up in the scheduled changes. + */ + size_t i = pState->cSvnPropChanges; + while (i-- > 0) + if (!strcmp(pState->paSvnPropChanges[i].pszName, pszName)) + { + const char *pszValue = pState->paSvnPropChanges[i].pszValue; + if (!pszValue) + return VERR_NOT_FOUND; + if (ppszValue) + return RTStrDupEx(ppszValue, pszValue); + return VINF_SUCCESS; + } + +#ifdef SCM_WITH_DYNAMIC_LIB_SVN + if (g_fSvnFunctionPointersValid) + { + rc = scmSvnQueryPropertyUsingApi(pState->pszFilename, pszName, ppszValue); + if (rc != VERR_NOT_SUPPORTED) + return rc; + /* Fallback: */ + } +#endif + + if (g_enmSvnVersion < kScmSvnVersion_1_7) + { + /* + * Hack: Read the .svn/props/<file>.svn-work file exists. + */ + char szPath[RTPATH_MAX]; + rc = scmSvnConstructName(pState, ".svn/props/", ".svn-work", szPath); + if (RT_SUCCESS(rc) && !RTFileExists(szPath)) + rc = scmSvnConstructName(pState, ".svn/prop-base/", ".svn-base", szPath); + if (RT_SUCCESS(rc)) + { + SCMSTREAM Stream; + rc = ScmStreamInitForReading(&Stream, szPath); + if (RT_SUCCESS(rc)) + { + /* + * The current format is K len\n<name>\nV len\n<value>\n" ... END. + */ + rc = VERR_NOT_FOUND; + size_t const cchName = strlen(pszName); + SCMEOL enmEol; + size_t cchLine; + const char *pchLine; + while ((pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol)) != NULL) + { + /* + * Parse the 'K num' / 'END' line. + */ + if ( cchLine == 3 + && !memcmp(pchLine, "END", 3)) + break; + size_t cchKey; + if ( cchLine < 3 + || pchLine[0] != 'K' + || pchLine[1] != ' ' + || !scmSvnReadNumber(&pchLine[2], cchLine - 2, &cchKey) + || cchKey == 0 + || cchKey > 4096) + { + RTMsgError("%s:%u: Unexpected data '%.*s'\n", szPath, ScmStreamTellLine(&Stream), cchLine, pchLine); + rc = VERR_PARSE_ERROR; + break; + } + + /* + * Match the key and skip to the value line. Don't bother with + * names containing EOL markers. + */ + size_t const offKey = ScmStreamTell(&Stream); + bool fMatch = cchName == cchKey; + if (fMatch) + { + pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol); + if (!pchLine) + break; + fMatch = cchLine == cchName + && !memcmp(pchLine, pszName, cchName); + } + + if (RT_FAILURE(ScmStreamSeekAbsolute(&Stream, offKey + cchKey))) + break; + if (RT_FAILURE(ScmStreamSeekByLine(&Stream, ScmStreamTellLine(&Stream) + 1))) + break; + + /* + * Read and Parse the 'V num' line. + */ + pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol); + if (!pchLine) + break; + size_t cchValue; + if ( cchLine < 3 + || pchLine[0] != 'V' + || pchLine[1] != ' ' + || !scmSvnReadNumber(&pchLine[2], cchLine - 2, &cchValue) + || cchValue > _1M) + { + RTMsgError("%s:%u: Unexpected data '%.*s'\n", szPath, ScmStreamTellLine(&Stream), cchLine, pchLine); + rc = VERR_PARSE_ERROR; + break; + } + + /* + * If we have a match, allocate a return buffer and read the + * value into it. Otherwise skip this value and continue + * searching. + */ + if (fMatch) + { + if (!ppszValue) + rc = VINF_SUCCESS; + else + { + char *pszValue; + rc = RTStrAllocEx(&pszValue, cchValue + 1); + if (RT_SUCCESS(rc)) + { + rc = ScmStreamRead(&Stream, pszValue, cchValue); + if (RT_SUCCESS(rc)) + *ppszValue = pszValue; + else + RTStrFree(pszValue); + } + } + break; + } + + if (RT_FAILURE(ScmStreamSeekRelative(&Stream, cchValue))) + break; + if (RT_FAILURE(ScmStreamSeekByLine(&Stream, ScmStreamTellLine(&Stream) + 1))) + break; + } + + if (RT_FAILURE(ScmStreamGetStatus(&Stream))) + { + rc = ScmStreamGetStatus(&Stream); + RTMsgError("%s: stream error %Rrc\n", szPath, rc); + } + ScmStreamDelete(&Stream); + } + } + + if (rc == VERR_FILE_NOT_FOUND) + rc = VERR_NOT_FOUND; + } + else + { + const char *apszArgs[] = { g_szSvnPath, "propget", "--strict", pszName, pState->pszFilename, NULL }; + char *pszValue; + rc = scmSvnRunAndGetOutput(pState, apszArgs, false, &pszValue); + if (RT_SUCCESS(rc)) + { + if (pszValue && *pszValue) + { + if (ppszValue) + { + *ppszValue = pszValue; + pszValue = NULL; + } + } + else + rc = VERR_NOT_FOUND; + RTStrFree(pszValue); + } + } + return rc; +} + + +/** + * Queries the value of an SVN property on the parent dir/whatever. + * + * This will not adjust for scheduled changes to the parent! + * + * @returns IPRT status code. + * @retval VERR_INVALID_STATE if not a SVN WC file. + * @retval VERR_NOT_FOUND if the property wasn't found. + * @param pState The rewrite state to work on. + * @param pszName The property name. + * @param ppszValue Where to return the property value. Free this + * using RTStrFree. Optional. + */ +int ScmSvnQueryParentProperty(PSCMRWSTATE pState, const char *pszName, char **ppszValue) +{ + /* + * Strip the filename and use ScmSvnQueryProperty. + */ + char szPath[RTPATH_MAX]; + int rc = RTStrCopy(szPath, sizeof(szPath), pState->pszFilename); + if (RT_SUCCESS(rc)) + { + RTPathStripFilename(szPath); + SCMRWSTATE ParentState; + ParentState.pszFilename = szPath; + ParentState.fFirst = false; + ParentState.fNeedsManualRepair = false; + ParentState.fIsInSvnWorkingCopy = true; + ParentState.cSvnPropChanges = 0; + ParentState.paSvnPropChanges = NULL; + ParentState.rc = VINF_SUCCESS; + rc = ScmSvnQueryProperty(&ParentState, pszName, ppszValue); + if (RT_SUCCESS(rc)) + rc = ParentState.rc; + } + return rc; +} + + +/** + * Schedules the setting of a property. + * + * @returns IPRT status code. + * @retval VERR_INVALID_STATE if not a SVN WC file. + * @param pState The rewrite state to work on. + * @param pszName The name of the property to set. + * @param pszValue The value. NULL means deleting it. + */ +int ScmSvnSetProperty(PSCMRWSTATE pState, const char *pszName, const char *pszValue) +{ + /* + * Update any existing entry first. + */ + size_t i = pState->cSvnPropChanges; + while (i-- > 0) + if (!strcmp(pState->paSvnPropChanges[i].pszName, pszName)) + { + if (!pszValue) + { + RTStrFree(pState->paSvnPropChanges[i].pszValue); + pState->paSvnPropChanges[i].pszValue = NULL; + } + else + { + char *pszCopy; + int rc = RTStrDupEx(&pszCopy, pszValue); + if (RT_FAILURE(rc)) + return rc; + pState->paSvnPropChanges[i].pszValue = pszCopy; + } + return VINF_SUCCESS; + } + + /* + * Insert a new entry. + */ + i = pState->cSvnPropChanges; + if ((i % 32) == 0) + { + void *pvNew = RTMemRealloc(pState->paSvnPropChanges, (i + 32) * sizeof(SCMSVNPROP)); + if (!pvNew) + return VERR_NO_MEMORY; + pState->paSvnPropChanges = (PSCMSVNPROP)pvNew; + } + + pState->paSvnPropChanges[i].pszName = RTStrDup(pszName); + pState->paSvnPropChanges[i].pszValue = pszValue ? RTStrDup(pszValue) : NULL; + if ( pState->paSvnPropChanges[i].pszName + && (pState->paSvnPropChanges[i].pszValue || !pszValue) ) + pState->cSvnPropChanges = i + 1; + else + { + RTStrFree(pState->paSvnPropChanges[i].pszName); + pState->paSvnPropChanges[i].pszName = NULL; + RTStrFree(pState->paSvnPropChanges[i].pszValue); + pState->paSvnPropChanges[i].pszValue = NULL; + return VERR_NO_MEMORY; + } + return VINF_SUCCESS; +} + + +/** + * Schedules a property deletion. + * + * @returns IPRT status code. + * @param pState The rewrite state to work on. + * @param pszName The name of the property to delete. + */ +int ScmSvnDelProperty(PSCMRWSTATE pState, const char *pszName) +{ + return ScmSvnSetProperty(pState, pszName, NULL); +} + + +/** + * Applies any SVN property changes to the work copy of the file. + * + * @returns IPRT status code. + * @param pState The rewrite state which SVN property changes + * should be applied. + */ +int ScmSvnDisplayChanges(PSCMRWSTATE pState) +{ + size_t i = pState->cSvnPropChanges; + while (i-- > 0) + { + const char *pszName = pState->paSvnPropChanges[i].pszName; + const char *pszValue = pState->paSvnPropChanges[i].pszValue; + if (pszValue) + ScmVerbose(pState, 0, "svn propset '%s' '%s' %s\n", pszName, pszValue, pState->pszFilename); + else + ScmVerbose(pState, 0, "svn propdel '%s' %s\n", pszName, pState->pszFilename); + } + + return VINF_SUCCESS; +} + +/** + * Applies any SVN property changes to the work copy of the file. + * + * @returns IPRT status code. + * @param pState The rewrite state which SVN property changes + * should be applied. + */ +int ScmSvnApplyChanges(PSCMRWSTATE pState) +{ +#ifdef SCM_WITH_LATER + if (0) + { + return ...; + } + + /* Fallback: */ +#endif + + /* + * Iterate thru the changes and apply them by starting the svn client. + */ + for (size_t i = 0; i < pState->cSvnPropChanges; i++) + { + const char *apszArgv[6]; + apszArgv[0] = g_szSvnPath; + apszArgv[1] = pState->paSvnPropChanges[i].pszValue ? "propset" : "propdel"; + apszArgv[2] = pState->paSvnPropChanges[i].pszName; + int iArg = 3; + if (pState->paSvnPropChanges[i].pszValue) + apszArgv[iArg++] = pState->paSvnPropChanges[i].pszValue; + apszArgv[iArg++] = pState->pszFilename; + apszArgv[iArg++] = NULL; + + int rc = scmSvnRun(pState, apszArgv, false); + if (RT_FAILURE(rc)) + return rc; + } + + return VINF_SUCCESS; +} + + +/** + * Initializes the subversion interface. + */ +void ScmSvnInit(void) +{ + scmSvnFindSvnBinary(NULL); +} + + +void ScmSvnTerm(void) +{ +#ifdef SCM_WITH_DYNAMIC_LIB_SVN + scmSvnFlushClientContextAndPool(); +#endif +} diff --git a/src/bldprogs/solgcc/.scm-settings b/src/bldprogs/solgcc/.scm-settings new file mode 100644 index 00000000..04f23d1c --- /dev/null +++ b/src/bldprogs/solgcc/.scm-settings @@ -0,0 +1,29 @@ +# $Id: .scm-settings $ +## @file +# Source code massager settings for solgcc +# + +# +# Copyright (C) 2017-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# SPDX-License-Identifier: GPL-3.0-only +# + +/config/*: --external-copyright --no-fix-header-guards + diff --git a/src/bldprogs/solgcc/config/i386/sol2-gas.h b/src/bldprogs/solgcc/config/i386/sol2-gas.h new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/bldprogs/solgcc/config/i386/sol2-gas.h diff --git a/src/bldprogs/solgcc/config/usegas.h b/src/bldprogs/solgcc/config/usegas.h new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/bldprogs/solgcc/config/usegas.h diff --git a/src/bldprogs/test-gccplugin-2.c b/src/bldprogs/test-gccplugin-2.c new file mode 100644 index 00000000..15ad61d2 --- /dev/null +++ b/src/bldprogs/test-gccplugin-2.c @@ -0,0 +1,57 @@ +/* $Id: test-gccplugin-2.c $ */ +/** @file + * Compiler plugin testcase \#2. + */ + +/* + * Copyright (C) 2010-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +/* Only valid stuff in this one. */ +extern void MyIprtPrintf(const char *pszFormat, ...) __attribute__((__iprt_format__(1,2))); +extern void foo(const char *pszFormat, ...); + +extern unsigned long long g_ull; + +typedef unsigned long long RTGCPHYS; +extern RTGCPHYS g_GCPhys; + +typedef RTGCPHYS *PRTGCPHYS; +extern PRTGCPHYS g_pGCPhys; + +#include <stdarg.h> + +void foo(const char *pszFormat, ...) +{ + MyIprtPrintf("%s %RX64 %RGp %p %RGp", "foobar", g_ull, g_GCPhys, g_pGCPhys, *g_pGCPhys); + MyIprtPrintf("%RX32 %d %s\n", 10, 42, "string"); + MyIprtPrintf("%u %.8Rhxs %d\n", 10, &g_ull, 42); + MyIprtPrintf("%u %.8RhxD %d\n", 10, &g_ull, 42); + { + va_list va; + int iValue; + va_start(va, pszFormat); + iValue = va_arg(va, int); + va_end(va); + MyIprtPrintf("%u\n", iValue); + } +} + diff --git a/src/bldprogs/test-gccplugin-3.c b/src/bldprogs/test-gccplugin-3.c new file mode 100644 index 00000000..cc982560 --- /dev/null +++ b/src/bldprogs/test-gccplugin-3.c @@ -0,0 +1,47 @@ +/* $Id: test-gccplugin-3.c $ */ +/** @file + * Compiler plugin testcase \#3. + */ + +/* + * Copyright (C) 2010-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +/* Only valid stuff in this one. */ +#if defined(__i386__) || defined(_M_IX86) || defined(__X86__) +# define RTCALL __attribute__((__cdecl__,__regparm__(0))) +#else +# define RTCALL +#endif +typedef struct HELPERS +{ + void (RTCALL * pfnPrintf)(struct HELPERS *pHlp, const char *pszFormat, ...) + __attribute__((__iprt_format__(2, 3))); +} HELPERS; + +extern void foo(struct HELPERS *pHlp); + + +void foo(struct HELPERS *pHlp) +{ + pHlp->pfnPrintf(pHlp, "%36 %#x %#x", "string", 42, 42); /// @todo missing 's', need to detect this. +} + diff --git a/src/bldprogs/test-gccplugin.c b/src/bldprogs/test-gccplugin.c new file mode 100644 index 00000000..e121fa71 --- /dev/null +++ b/src/bldprogs/test-gccplugin.c @@ -0,0 +1,36 @@ +/* $Id: test-gccplugin.c $ */ +/** @file + * Compiler plugin testcase \#2. + */ + +/* + * Copyright (C) 2010-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +extern void MyIprtPrintf(const char *pszFormat, ...) __attribute__((__iprt_format__(1,2))); +extern void foo(void); + +void foo(void) +{ + MyIprtPrintf(0); + MyIprtPrintf("%RX32 %d %s\n", 10, 42, "string"); +} + |