ccache/unify.c
2010-08-29 13:26:55 +02:00

254 lines
5.9 KiB
C

/*
* Copyright (C) 2002 Andrew Tridgell
*
* 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; either version 3 of the License, or (at your option)
* any later version.
*
* 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, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
/*
* C/C++ unifier
*
* The idea is that changes that don't affect the resulting C code should not
* change the hash. This is achieved by folding white-space and other
* non-semantic fluff in the input into a single unified format.
*
* This unifier was design to match the output of the unifier in compilercache,
* which is flex based. The major difference is that this unifier is much
* faster (about 2x) and more forgiving of syntactic errors. Continuing on
* syntactic errors is important to cope with C/C++ extensions in the local
* compiler (for example, inline assembly systems).
*/
#include "ccache.h"
static const char *const s_tokens[] = {
"...", ">>=", "<<=", "+=", "-=", "*=", "/=", "%=", "&=", "^=",
"|=", ">>", "<<", "++", "--", "->", "&&", "||", "<=", ">=",
"==", "!=", ";", "{", "<%", "}", "%>", ",", ":", "=",
"(", ")", "[", "<:", "]", ":>", ".", "&", "!", "~",
"-", "+", "*", "/", "%", "<", ">", "^", "|", "?",
0
};
#define C_ALPHA 1
#define C_SPACE 2
#define C_TOKEN 4
#define C_QUOTE 8
#define C_DIGIT 16
#define C_HEX 32
#define C_FLOAT 64
#define C_SIGN 128
static struct {
unsigned char type;
unsigned char num_toks;
const char *toks[7];
} tokens[256];
/* build up the table used by the unifier */
static void
build_table(void)
{
unsigned char c;
int i;
static bool done;
if (done) return;
done = true;
memset(tokens, 0, sizeof(tokens));
for (c = 0; c < 128; c++) {
if (isalpha(c) || c == '_') tokens[c].type |= C_ALPHA;
if (isdigit(c)) tokens[c].type |= C_DIGIT;
if (isspace(c)) tokens[c].type |= C_SPACE;
if (isxdigit(c)) tokens[c].type |= C_HEX;
}
tokens['\''].type |= C_QUOTE;
tokens['"'].type |= C_QUOTE;
tokens['l'].type |= C_FLOAT;
tokens['L'].type |= C_FLOAT;
tokens['f'].type |= C_FLOAT;
tokens['F'].type |= C_FLOAT;
tokens['U'].type |= C_FLOAT;
tokens['u'].type |= C_FLOAT;
tokens['-'].type |= C_SIGN;
tokens['+'].type |= C_SIGN;
for (i = 0; s_tokens[i]; i++) {
c = s_tokens[i][0];
tokens[c].type |= C_TOKEN;
tokens[c].toks[tokens[c].num_toks] = s_tokens[i];
tokens[c].num_toks++;
}
}
/* buffer up characters before hashing them */
static void
pushchar(struct mdfour *hash, unsigned char c)
{
static unsigned char buf[64];
static size_t len;
if (c == 0) {
if (len > 0) {
hash_buffer(hash, (char *)buf, len);
len = 0;
}
hash_buffer(hash, NULL, 0);
return;
}
buf[len++] = c;
if (len == 64) {
hash_buffer(hash, (char *)buf, len);
len = 0;
}
}
/* hash some C/C++ code after unifying */
static void
unify(struct mdfour *hash, unsigned char *p, size_t size)
{
size_t ofs;
unsigned char q;
int i;
build_table();
for (ofs = 0; ofs < size;) {
if (p[ofs] == '#') {
if ((size-ofs) > 2 && p[ofs+1] == ' ' && isdigit(p[ofs+2])) {
do {
ofs++;
} while (ofs < size && p[ofs] != '\n');
ofs++;
} else {
do {
pushchar(hash, p[ofs]);
ofs++;
} while (ofs < size && p[ofs] != '\n');
pushchar(hash, '\n');
ofs++;
}
continue;
}
if (tokens[p[ofs]].type & C_ALPHA) {
do {
pushchar(hash, p[ofs]);
ofs++;
} while (ofs < size && (tokens[p[ofs]].type & (C_ALPHA|C_DIGIT)));
pushchar(hash, '\n');
continue;
}
if (tokens[p[ofs]].type & C_DIGIT) {
do {
pushchar(hash, p[ofs]);
ofs++;
} while (ofs < size &&
((tokens[p[ofs]].type & C_DIGIT) || p[ofs] == '.'));
if (ofs < size && (p[ofs] == 'x' || p[ofs] == 'X')) {
do {
pushchar(hash, p[ofs]);
ofs++;
} while (ofs < size && (tokens[p[ofs]].type & C_HEX));
}
if (ofs < size && (p[ofs] == 'E' || p[ofs] == 'e')) {
pushchar(hash, p[ofs]);
ofs++;
while (ofs < size && (tokens[p[ofs]].type & (C_DIGIT|C_SIGN))) {
pushchar(hash, p[ofs]);
ofs++;
}
}
while (ofs < size && (tokens[p[ofs]].type & C_FLOAT)) {
pushchar(hash, p[ofs]);
ofs++;
}
pushchar(hash, '\n');
continue;
}
if (tokens[p[ofs]].type & C_SPACE) {
do {
ofs++;
} while (ofs < size && (tokens[p[ofs]].type & C_SPACE));
continue;
}
if (tokens[p[ofs]].type & C_QUOTE) {
q = p[ofs];
pushchar(hash, p[ofs]);
do {
ofs++;
while (ofs < size-1 && p[ofs] == '\\') {
pushchar(hash, p[ofs]);
pushchar(hash, p[ofs+1]);
ofs += 2;
}
pushchar(hash, p[ofs]);
} while (ofs < size && p[ofs] != q);
pushchar(hash, '\n');
ofs++;
continue;
}
if (tokens[p[ofs]].type & C_TOKEN) {
q = p[ofs];
for (i = 0; i < tokens[q].num_toks; i++) {
unsigned char *s = (unsigned char *)tokens[q].toks[i];
int len = strlen((char *)s);
if (size >= ofs+len && memcmp(&p[ofs], s, len) == 0) {
int j;
for (j = 0; s[j]; j++) {
pushchar(hash, s[j]);
ofs++;
}
pushchar(hash, '\n');
break;
}
}
if (i < tokens[q].num_toks) {
continue;
}
}
pushchar(hash, p[ofs]);
pushchar(hash, '\n');
ofs++;
}
pushchar(hash, 0);
}
/* hash a file that consists of preprocessor output, but remove any line
number information from the hash
*/
int
unify_hash(struct mdfour *hash, const char *fname)
{
char *data;
size_t size;
if (!read_file(fname, 0, &data, &size)) {
stats_update(STATS_PREPROCESSOR);
return -1;
}
unify(hash, (unsigned char *)data, size);
free(data);
return 0;
}