Files
yallie 9fa3874800 Added the contents of the archive.
Moved the original file to the archive subfolder.
2017-11-20 16:11:42 +03:00

899 lines
28 KiB
C

// ==++==
//
//
// Copyright (c) 2002 Microsoft Corporation. All rights reserved.
//
// The use and distribution terms for this software are contained in the file
// named license.txt, which can be found in the root of this distribution.
// By using this software in any fashion, you are agreeing to be bound by the
// terms of this license.
//
// You must not remove this notice, or any other, from this software.
//
//
// ==--==
// ============================================================================
// File: cppmunge.c
//
// ============================================================================
// cppmunge reads in preprocessed C and C++ source files, munges their
// contents, and writes out a new version of the preprocessed file with
// assorted changes.
//
// cppmunge currently does these tasks:AddEscapeCharToString
// -Converts wide strings (L"...") from native wchar_t characters to
// two-byte characters, casting the result to a PAL-defined
// wchar_t *.
//
// As input, it takes a relative or absolute path to the preprocessed
// file. It is intended to be called from a makefile.
//
// To test, create a file containing the following line:
// WCHAR* p = L"te\x5544\033\x22\n";
// and run cppmunge on it. The file should be replaced by this line on
// little-endian machines:
// WCHAR* p = ((WCHAR *) ("t\000e\000\x55\x44\033\000\x22\000\n\000\0\000"));
// and on big-endian, with this:
// WCHAR* p = ((WCHAR *) ("\000t\000e\x44\x55\000\033\000\x22\000\n\000\0"));
#include <assert.h>
#include <ctype.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
typedef enum {
eStateNormal,
eStateEscape,
eStateOctal,
eStateHex,
eStateNotInString,
eStateNewStringPending,
eStateDone
} CharacterState;
typedef enum {
eModeLiteral, // L"foo"
eModeSizeOf, // sizeof(L"foo")
eModeInitializer // WCHAR bar[] = L"foo"
} ConvertMode;
static int ProcessFile(const char *filename);
static int ConvertFile(const char *filename, FILE *file);
static int CopyFile(const char *source, const char *destination);
static int ConvertCharacters(FILE *original, FILE *destination);
static int ConvertOneString(FILE *original, FILE *destination, ConvertMode mode);
static FILE *GetTempFile(char nameBuffer[], int bufferLength);
static int AddCharToString(char *string, int stringLength, char ch, ConvertMode mode);
static int AddEscapeCharToString(char *string, int stringLength,
char escapedChar[],
int charLength,
CharacterState state,
ConvertMode mode);
static int IsBaseDigit(char ch, CharacterState type);
static int MaxSizeForBase(CharacterState base);
static char *CheckStringBufferSize(char *buffer, int *allocatedSize,
int bytesUsed);
static int StripPCH(FILE *original, FILE *destination);
char SourceFile[PATH_MAX];
char PrecompiledHeader[PATH_MAX];
int main(int argc, char **argv) {
if (argc != 4) {
Usage:
fprintf(stderr, "Usage: cppmunge -s<SourceFile> -p[PrecompiledHeader] file\n");
exit(1);
}
if (*argv[1] != '-' || *(argv[1]+1) != 's' || *(argv[1]+2) == '\0') {
goto Usage;
}
if (realpath(argv[1]+2, SourceFile) == NULL) {
fprintf(stderr, "Error: realpath(%s, ...) failed with %d %s\n",
argv[1]+2, errno, strerror(errno));
exit(1);
}
if (*argv[2] != '-' || *(argv[2]+1) != 'p') {
goto Usage;
}
if (*(argv[2]+2) && realpath(argv[2]+2, PrecompiledHeader) == NULL) {
fprintf(stderr, "Error: realpath(%s, ...) failed with %d %s\n",
argv[2]+2, errno, strerror(errno));
exit(1);
}
return ProcessFile(argv[3]);
}
/* ProcessFile
* -----------
* Reads the specified file, converts it, and writes it out. Returns 0 if
* successful and an error code otherwise.
*/
static int ProcessFile(const char *filename) {
FILE *file;
int err;
file = fopen(filename, "r");
if (file == NULL) {
return errno;
}
err = ConvertFile(filename, file);
fclose(file);
return err;
}
/* ConvertFile
* -----------
* Takes a filename and a FILE * to that file that is already opened for
* reading. Creates a temporary file to write output to, and converts the
* given file, character by character, writing the result into the
* temporary file. The temporary file is renamed to the specified filename
* on successful completion. If renaming fails, the specified filename is
* overwritten with the contents of the temporary file. If renaming fails
* or the function itself fails, the temporary file is deleted.
*
* Returns 0 on success and an error code otherwise.
*/
static int ConvertFile(const char *filename, FILE *file) {
char tempFileName[PATH_MAX + 1];
FILE *tempFile;
int err;
int removeErr;
tempFile = GetTempFile(tempFileName, sizeof(tempFileName));
if (tempFile == NULL) {
return errno;
}
err = StripPCH(file, tempFile);
if (err != 0) {
fclose(tempFile);
remove(tempFileName);
return err;
}
err = ConvertCharacters(file, tempFile);
if (err != 0) {
fclose(tempFile);
remove(tempFileName);
return err;
}
// Close the files.
fclose(tempFile);
// Move the temporary file over the original.
err = rename(tempFileName, filename);
if (err != 0) {
err = CopyFile(tempFileName, filename);
removeErr = remove(tempFileName);
if (err == 0 && removeErr != 0) {
fprintf(stderr, "cppmunge: error: delete of %s failed\n", tempFileName);
err = errno;
}
}
return err;
}
/* CopyFile
* --------
* Copies the source file to the destination. Called if renaming doesn't
* work. Returns 0 on success and an error code on failure.
*/
static int CopyFile(const char *source, const char *destination) {
char buffer[1024]; // A good size
int err;
FILE *sourceFile;
FILE *destinationFile;
// Darn. Try to overwrite the original instead.
sourceFile = fopen(source, "r");
if (sourceFile == NULL) {
return errno;
}
destinationFile = fopen(destination, "w");
if (destinationFile == NULL) {
return errno;
}
while (fgets(buffer, sizeof(buffer), sourceFile) != NULL) {
err = fputs(buffer, destinationFile);
if (err == EOF) {
return errno;
}
}
if (!feof(sourceFile)) {
// fgets() returned NULL due to an error
return errno;
}
fclose(destinationFile);
fclose(sourceFile);
return 0;
}
/* ConvertCharacters
* -----------------
* Takes two FILE *s, one to the original file that is opened for reading
* and one to the destination that is open for writing. Copies the contents
* of the original to the destination, converting wide strings in the
* process. Returns 0 on success and an error code on failure.
*/
static int ConvertCharacters(FILE *original, FILE *destination) {
int ch;
int err;
int escape;
int previousCh;
int matched;
int terminator;
ConvertMode mode;
previousCh = EOF;
err = 0;
while ((ch = fgetc(original)) != EOF) {
Restart:
switch (ch) {
case '\"':
case '\'':
// regular string or char - pass it through
if (fputc(ch, destination) == EOF) {
return ferror(destination);
}
terminator = ch;
escape = 0;
while ((ch = fgetc(original)) != EOF) {
if (fputc(ch, destination) == EOF) {
return ferror(destination);
}
switch (ch) {
case '\"':
case '\'':
if (!escape) {
if (ch == terminator)
break;
}
escape = 0;
continue;
case '\\':
escape = !escape;
continue;
default:
escape = 0;
continue;
}
break;
}
previousCh = ch;
break;
case 'L':
case 'l':
mode = eModeLiteral;
UnicodeString:
previousCh = ch;
if ((ch = fgetc(original)) == EOF)
break;
if (ch != '\"') {
if (fputc(previousCh, destination) == EOF) {
return ferror(destination);
}
goto Restart;
}
// The start of a string
err = ConvertOneString(original, destination, mode);
previousCh = EOF;
break;
#define trymatch(expected, ignorewhitespace) \
matched = 0; \
for (;;) { \
if (ch == EOF) { \
if ((ch = fgetc(original)) == EOF) \
break; \
} \
if (ignorewhitespace && isspace(ch)) { \
if (fputc(ch, destination) == EOF) { \
return ferror(destination); \
} \
ch = EOF; \
continue; \
} \
if (ch == expected) { \
if (fputc(ch, destination) == EOF) { \
return ferror(destination); \
} \
matched = 1; \
ch = EOF; \
} \
break; \
}
#define match(c, ignorewhitespace) \
trymatch(c, ignorewhitespace); \
if (!matched) goto Restart;
case 's':
// look for "sizeof ( L"
if (isalnum(previousCh))
goto FallBack;
match('s', 0);
match('i', 0);
match('z', 0);
match('e', 0);
match('o', 0);
match('f', 0);
do {
trymatch('(', 1);
} while (matched);
if (ch != 'l' && ch != 'L')
goto Restart;
mode = eModeSizeOf;
goto UnicodeString;
case '[':
// look for "[ ] = ( L"
match('[', 1);
match(']', 1);
match('=', 1);
do {
trymatch('(', 1);
} while (matched);
if (ch != 'l' && ch != 'L')
goto Restart;
mode = eModeInitializer;
goto UnicodeString;
default:
FallBack:
if (fputc(ch, destination) == EOF) {
return ferror(destination);
}
previousCh = ch;
break;
}
}
return err;
}
/* ConvertOneString
* ----------------
* Takes in a FILE * to the original file and another FILE * to the
* destination. Assumes that the previous two characters read from
* the original file were L", so the mark is at the start of a wide
* string. This function reads the rest of the string, including any
* immediately following strings that should be concatenated with
* it, and writes out a CLR-compatible version of the string. Whitespace
* between concatenated strings will be skipped, but an equivalent number
* of newlines will be written out if any are part of that whitespace.
* Whitespace that follows a wide string will be decreased to a single
* space.
* Returns 0 on success and an error code on failure.
*/
static int ConvertOneString(FILE *original, FILE *destination, ConvertMode mode) {
char *newString;
int newStringLength;
int ch = '\0';
int bytesCopied;
int err;
char numberBuffer[8]; // A pending octal or hex number
int numberOffset;
CharacterState state;
int newlines;
int previousCh;
int hasTrailingWhitespace;
int i;
err = 0;
newStringLength = 1024;
bytesCopied = 0;
state = eStateNormal;
numberOffset = 0;
newlines = 0;
previousCh = EOF;
hasTrailingWhitespace = 0;
newString = malloc(newStringLength);
if (newString == NULL) {
return errno;
}
while (err == 0 && state != eStateDone && (ch = fgetc(original)) != EOF) {
switch (state) {
case eStateNormal:
if (ch == '\\') {
state = eStateEscape;
} else if (ch == '"') {
// Done with the string. Read on till we
// know we don't have a concatenated string.
state = eStateNotInString;
} else {
bytesCopied += AddCharToString(newString, bytesCopied, ch, mode);
}
break;
case eStateEscape:
if (IsBaseDigit(ch, eStateOctal)) {
// Octal number. Put it back so it gets handled
// in the octal case.
if (ungetc(ch, original) == EOF) {
err = EIO; // As good as anything else
}
state = eStateOctal;
memset(numberBuffer, 0, sizeof(numberBuffer));
} else if (ch == 'x') {
// Hex number.
state = eStateHex;
memset(numberBuffer, 0, sizeof(numberBuffer));
} else {
// Escape sequences that aren't numbers are
// two bytes. Write those out as a single
// character.
char curCh = ch;
bytesCopied += AddEscapeCharToString(newString,
bytesCopied, &curCh, 1,
eStateNormal,
mode);
state = eStateNormal;
}
break;
case eStateOctal:
case eStateHex:
if (IsBaseDigit(ch, state) &&
numberOffset < MaxSizeForBase(state)) {
// Add the number to our buffer.
numberBuffer[numberOffset++] = ch;
} else if (numberOffset == 0) {
// Weird.
err = EINVAL;
} else {
// The number's done.
// Write it out and put the character back.
bytesCopied += AddEscapeCharToString(newString,
bytesCopied,
numberBuffer, numberOffset,
state,
mode);
numberOffset = 0;
state = eStateNormal;
if (ungetc(ch, original) == EOF) {
err = EIO; // As good as anything else
}
}
break;
case eStateNotInString:
if (toupper(ch) == 'L') {
previousCh = ch;
state = eStateNewStringPending;
} else if (ch == '\n') {
newlines++;
} else if (!isspace(ch)) {
// Put the character back
if (ungetc(ch, original) == EOF) {
err = EIO; // As good as anything else
}
// We're done!
state = eStateDone;
} else {
// Bypass whitespace, but mark it for later.
hasTrailingWhitespace = 1;
}
break;
case eStateNewStringPending:
if (ch == '"') {
// New string. Add it to the previous one.
state = eStateNormal;
} else {
// Write out the previous character.
if (fputc(previousCh, destination) != EOF) {
// Put the current one back.
if (ungetc(ch, original) == EOF) {
err = EIO; // As good as anything else
}
} else {
err = ferror(destination);
}
// We're done!
state = eStateDone;
}
break;
default:
break;
}
// Reallocate our string if necessary
newString = CheckStringBufferSize(newString, &newStringLength,
bytesCopied);
if (newString == NULL) {
err = errno;
}
}
if (ch == EOF) {
// Hmm...that shouldn't have happened. We're only supposed
// to run on preprocessor output, and the preprocessor will
// catch an unterminated string constant.
err = EIO; // As good a value as any
}
if (err == 0) {
// Write out the string. First, add the trailing null character.
char zeroCh = '0';
bytesCopied += AddEscapeCharToString(newString, bytesCopied,
&zeroCh, 1, eStateNormal, mode);
// Reallocate our string if necessary
newString = CheckStringBufferSize(newString, &newStringLength,
bytesCopied);
if (newString == NULL) {
err = errno;
}
}
if (err == 0) {
if (mode == eModeInitializer) {
// eat the last comma
bytesCopied -= 1;
}
else {
// eat the last \000
bytesCopied -= 4;
}
// Terminate the buffer so we can use it as a real string.
newString[bytesCopied] = '\0';
// Now, write it out.
switch (mode) {
case eModeLiteral:
fprintf(destination, "((WCHAR *) (\"%s\"))", newString);
break;
case eModeSizeOf:
fprintf(destination, "(\"%s\")", newString);
break;
case eModeInitializer:
fprintf(destination, "{%s}", newString);
break;
default:
break;
}
// If there's trailing whitespace, print that.
if (hasTrailingWhitespace) {
fprintf(destination, " ");
}
// And print all of the newlines.
for(i = 0; i < newlines; i++) {
fprintf(destination, "\n");
}
}
return err;
}
/* GetTempFile
* -----------
* Returns a FILE * that points to a temporary file that is open for
* writing. Fills in the given buffer with the path to that file. It
* is recommended that the buffer be at least MAX_PATH + 1 bytes.
* Returns NULL on failure, with errno set.
*/
static FILE *GetTempFile(char nameBuffer[], int bufferLength) {
int tempFileDes;
const char *tempDir;
tempDir = getenv("TEMP");
if (tempDir == NULL) {
tempDir = getenv("TMP");
}
if (tempDir == NULL) {
tempDir = "/tmp";
}
snprintf(nameBuffer, bufferLength - 1, "%s/cppmunge.XXXXXX", tempDir);
tempFileDes = mkstemp(nameBuffer);
if (tempFileDes == -1) {
return NULL;
}
return fdopen(tempFileDes, "w");
}
/* AddCharToString
* ---------------
* Writes a two-byte version of the given character to the specified
* location in the string buffer. This should not fail, so there is
* no return value.
*
*/
static int AddCharToString(char *string, int stringLength, char ch, ConvertMode mode) {
int initialStringLength = stringLength;
if (mode != eModeInitializer) {
#if BIGENDIAN
// Octal zero
string[stringLength++] = '\\';
string[stringLength++] = '0';
string[stringLength++] = '0';
string[stringLength++] = '0';
// The character
string[stringLength++] = ch;
#else
string[stringLength++] = ch;
// Octal zero
string[stringLength++] = '\\';
string[stringLength++] = '0';
string[stringLength++] = '0';
string[stringLength++] = '0';
#endif
}
else {
string[stringLength++] = '\'';
if (ch == '\'') {
string[stringLength++] = '\\';
}
string[stringLength++] = ch;
string[stringLength++] = '\'';
string[stringLength++] = ',';
}
return stringLength - initialStringLength;
}
/* AddEscapeCharToString
* ---------------------
* Writes each character in the given buffer to the specified location
* in the string buffer, along with an appropriately-placed null
* character. Appends a backslash to the string prior to writing any
* characters in the buffer to produce an escape sequence in the
* resulting string. If the state is eStateHex, the character buffer
* is divided into two-character chunks, and each of those is written
* as a separate hex character.
*
*/
static int AddEscapeCharToString(char *string, int stringLength,
char escapedChar[],
int charLength,
CharacterState state,
ConvertMode mode) {
int i;
int initialStringLength = stringLength;
if (mode != eModeInitializer) {
#if BIGENDIAN
// Only write a zero if the last character to write isn't zero
// and, if we're writing hex characters, we aren't about to write
// an even block of four.
if (escapedChar[charLength - 1] != '\0' &&
(state != eStateHex || (charLength % 4 != 0))) {
// Octal zero
string[stringLength++] = '\\';
string[stringLength++] = '0';
string[stringLength++] = '0';
string[stringLength++] = '0';
}
#endif
if (state == eStateHex) {
// Put characters into the string four at a time.
// If a pair is left over, add a trailing zero for it.
// We only support hexadecimal characters that have
// an even number of bytes.
assert(charLength % 2 == 0);
assert(charLength <= 4);
#if BIGENDIAN
if (charLength == 4) {
// Byte-swap the character
char c;
c = escapedChar[0];
escapedChar[0] = escapedChar[2];
escapedChar[2] = c;
c = escapedChar[1];
escapedChar[1] = escapedChar[3];
escapedChar[3] = c;
}
#endif
for(i = 0; i < charLength; i++) {
string[stringLength++] = '\\';
string[stringLength++] = 'x';
string[stringLength++] = escapedChar[i];
i++;
string[stringLength++] = escapedChar[i];
}
} else {
string[stringLength++] = '\\';
for(i = 0; i < charLength; i++) {
string[stringLength++] = escapedChar[i];
}
}
#if !BIGENDIAN
// Only append a zero if our last character was not zero
// and, if we're writing hex characters, we didn't write
// an even block of four.
if (escapedChar[i - 1] != '\0' &&
(state != eStateHex || (i % 4 != 0))) {
// Octal zero
string[stringLength++] = '\\';
string[stringLength++] = '0';
string[stringLength++] = '0';
string[stringLength++] = '0';
}
#endif
}
else {
if (state == eStateHex) {
string[stringLength++] = '0';
string[stringLength++] = 'x';
for(i = 0; i < charLength; i++) {
string[stringLength++] = escapedChar[i];
}
}
else
if (state == eStateOctal) {
string[stringLength++] = '0';
for(i = 0; i < charLength; i++) {
string[stringLength++] = escapedChar[i];
}
}
else {
string[stringLength++] = '\'';
string[stringLength++] = '\\';
for(i = 0; i < charLength; i++) {
string[stringLength++] = escapedChar[i];
}
string[stringLength++] = '\'';
}
string[stringLength++] = ',';
}
return stringLength - initialStringLength;
}
/* IsBaseDigit
* -----------
* Returns whether the character is valid for a number of the specified
* type.
*/
static int IsBaseDigit(char ch, CharacterState type) {
if (type == eStateOctal) {
return (ch >= '0' && ch <= '7');
} else if (type == eStateHex) {
return isxdigit((unsigned char) ch);
} else {
return 0;
}
}
/* MaxSizeForBase
* --------------
* Returns the maximum number of bytes after the escape sequence that
* can be used to represent a character in the given base.
*/
static int MaxSizeForBase(CharacterState base) {
if (base == eStateOctal) {
return 3;
} else if (base == eStateHex) {
return 8; /* OK, so it's technically infinite, but there's
* a limit on just how big of an infinity is
* likely. */
} else {
return 0;
}
}
/* CheckStringBufferSize
* ---------------------
* Compares the size of the buffer to the size of the buffer's contents
* and realloc's to twice the original size if the contents are within
* six of the buffer size. Returns NULL and sets errno if reallocation
* fails.
*/
static char *CheckStringBufferSize(char *buffer, int *allocatedSize,
int bytesUsed) {
char *newBuffer;
// The actual max size of the margin should be 12 (an eight-digit hex
// character, such as * \x1234abcd, followed by a trailing null character
// to make a wide char)
// We will be generous about the margin. 32 should cover
// any border case that we have missed.
if (bytesUsed >= *allocatedSize - 32) {
newBuffer = realloc(buffer, 2 * (*allocatedSize));
if (newBuffer != NULL) {
*allocatedSize *= 2;
}
} else {
newBuffer = buffer;
}
return newBuffer;
}
static int StripPCH(FILE *original, FILE *destination)
{
char LineBuffer[10240];
char LineDirectiveFile[PATH_MAX];
char *p;
char *EndQuote;
int FoundPrecompiledHeaderStart = 0;
if (PrecompiledHeader[0] == '\0') {
// No precompiled header specified
return 0;
}
while (fgets(LineBuffer, sizeof(LineBuffer), original) != NULL) {
if (LineBuffer[0] != '#' || LineBuffer[1] != ' ' || !isdigit((unsigned char) LineBuffer[2])) {
// Not a line directive
continue;
}
p = &LineBuffer[3];
while (isdigit((unsigned char) *p)) {
p++;
}
if (*p != ' ' || *(p+1) != '\"') {
// A '# line' directive without a filename
continue;
}
p+=2;
EndQuote = strchr(p, '\"');
if (!EndQuote) {
fprintf(stderr, "Malformed directive '%s'\n", LineBuffer);
return 1;
}
*EndQuote = '\0';
if (realpath(p, LineDirectiveFile) == NULL) {
fprintf(stderr, "Error: realpath(%s, ...) failed with %d %s\n",
p+2, errno, strerror(errno));
return 1;
}
if (!strcmp(LineDirectiveFile, PrecompiledHeader)) {
// Found the beginning of the precompiled header file
FoundPrecompiledHeaderStart = 1;
} else if (FoundPrecompiledHeaderStart &&
!strcmp(LineDirectiveFile, SourceFile)) {
// Found the first line of the source file following
// the end of the precompiled header.
fprintf(destination, "# 1 \"%s\"\n", p);
fprintf(destination, "# 1 \"%s\" 1\n", PrecompiledHeader);
*EndQuote = '\"';
fputs(LineBuffer, destination);
fputc('\n', destination);
return 0;
}
}
// Hit end-of-file without finding the expected line directives
if (!FoundPrecompiledHeaderStart) {
fprintf(stderr, "Error: precompiled header filename not found\n");
} else {
fprintf(stderr, "Error: source filename not found after PCH name\n");
}
return 1;
}