1
1
forked from 0ad/0ad
0ad/build/dehydra/printf-type-check.js
2012-01-01 16:43:10 +00:00

303 lines
11 KiB
JavaScript

// This script attempts to check argument types of printf/wprintf/scanf/wscanf style functions.
// Mostly it's similar to built-in GCC warning functionality, but with the benefit that it can check
// wchar_t* format strings too.
//
// (This is somewhat duplicating the functionality of https://bug493996.bugzilla.mozilla.org/attachment.cgi?id=388700)
include('treehydra.js');
include('gcc_compat.js');
include('gcc_util.js');
include('gcc_print.js');
// Get string corresponding to string literal expressions
function get_string_constant(expr) {
if (TREE_CODE(expr) == NOP_EXPR)
return get_string_constant(expr.operands()[0]);
else if (TREE_CODE(expr) == ADDR_EXPR && TREE_CODE(expr.operands()[0]) == STRING_CST) {
return expr.operands()[0].string.str;
}
}
function is_vararg(decl) {
// Non-vararg functions end with a VOID_TYPE sentinel
for (var t in flatten_chain(TYPE_ARG_TYPES(TREE_TYPE(decl)))) {
if (TREE_CODE(TREE_VALUE(t)) == VOID_TYPE)
return false;
}
return true;
}
// Return ['a' (ascii) or 'w' (wide), 'printf' or 'scanf', string-index, first-to-check] or undefined
function find_printf_type(decl, loc) {
if (! is_vararg(decl))
return;
var decl_attrs = translate_attributes(DECL_ATTRIBUTES(decl)); // 'user' attrs are here
var type_attrs = translate_attributes(TYPE_ATTRIBUTES(TREE_TYPE(decl))); // 'format' attrs are here
for each (var a in decl_attrs.concat(type_attrs)) {
if (a.name == 'format') {
var start = a.value[1];
var first = a.value[2];
if (a.value[0] == 'printf')
return ['a', 'printf', start, first];
else if (a.value[0] == 'scanf')
return ['a', 'scanf', start, first];
else
error('Unrecognised format attribute type "'+a.value[0]+'"', loc());
} else if (a.name == 'user' && a.value[0].match(/^format/)) {
var [ctype, functype, start, first] = a.value[0].split(/,\s*/).slice(1);
if (first == '+1') first = (+start) + 1; // bit ugly, but lets our macros work easily
return [ctype, functype, start, first];
}
}
var name = decl_name(decl);
// Special cases for functions we use and aren't declared with attributes:
if (name == 'sscanf')
return ['a', 'scanf', 2, 3];
else if (name == 'swscanf')
return ['w', 'scanf', 2, 3];
else if (name == 'snprintf')
return ['a', 'printf', 3, 4];
else if (name == 'wprintf')
return ['w', 'printf', 1, 2];
else if (name == 'fwprintf')
return ['w', 'printf', 2, 3];
else if (name == 'swprintf')
return ['w', 'printf', 3, 4];
// SpiderMonkey:
if (name == 'JS_ReportError')
return ['a', 'printf', 2, 3];
else if (name == 'JS_ReportWarning')
return ['a', 'printf', 2, 3];
// Mongoose:
if (name == 'mg_printf')
return ['a', 'printf', 2, 3];
else if (name == 'mg_snprintf')
return ['a', 'printf', 4, 5];
else if (name == 'send_http_error')
return ['a', 'printf', 4, 5];
else if (name == 'cry')
return ['a', 'printf', 2, 3];
else if (name == 'mg_md5')
return;
// Ignore vararg functions that we know aren't using normal format strings
if (name.match(/^(__builtin_va_start|execlp|open|fcntl|ioctl|sem_open|h_alloc|sys_wopen|ogl_HaveExtensions|JS_ConvertArguments|curl_easy_setopt|curl_easy_getinfo|SMBIOS::FieldInitializer::Read|SMBIOS::FieldStringizer::Write)$/))
return;
warning('Ignoring unannotated vararg function "'+name+'"', loc());
}
function compare_format_type(ctype, functype, fmt, arg, loc) {
var m, len, spec;
if (functype == 'printf') {
m = fmt.match(/^%([-+ #0]*)(\*|\d+)?(\.\*|\.-?\d+)?(hh|h|ll|l|j|z|t|L)?([diouxXfFeEgGaAcspn%])$/);
if (m) {
len = m[4] || '';
spec = m[5];
}
} else if (functype == 'scanf') {
m = fmt.match(/^%(\*?)(\d*)(hh|h|ll|l|j|z|t|L)?([diouxaefgcs[pn%])$/);
if (m) {
len = m[3] || '';
spec = m[4];
}
} else {
error('Internal error: unknown functype '+functype, loc());
return true;
}
if (! spec) {
error('Invalid format specifier "'+fmt+'"', loc());
return true;
}
var t = len+spec;
if (ctype == 'w' && t == 's')
error('Non-portable %s used in wprintf-style function', loc());
if (ctype == 'a' && t == 'hs')
error('Illegal %hs used in printf-style function', loc());
if (functype == 'printf') {
if (t.match(/^[dic]$/))
return (arg == 'int');
if (t.match(/^[ouxX]$/))
return (arg == 'unsigned int');
if (t.match(/^lc$/))
return (arg == 'int' || arg == 'unsigned int'); // spec says wint_t
if (t.match(/^l[di]$/))
return (arg == 'long int');
if (t.match(/^l[ouxX]$/))
return (arg == 'long unsigned int');
if (t.match(/^ll[di]$/))
return (arg == 'long long int');
if (t.match(/^ll[ouxX]$/))
return (arg == 'long long unsigned int');
if (t.match(/^[fFeEgGaA]$/))
return (arg == 'double');
if (t.match(/^p$/))
return (arg.match(/\*$/));
// ...
} else if (functype == 'scanf') {
if (t.match(/^[di]$/))
return (arg == 'int*');
if (t.match(/^[ouxX]$/))
return (arg == 'unsigned int*');
if (t.match(/^l[di]$/))
return (arg == 'long int*');
if (t.match(/^l[ouxX]$/))
return (arg == 'long unsigned int*');
if (t.match(/^z[diouxX]$/))
return (arg == 'long unsigned int*'); // spec says size_t*
if (t.match(/^[c[]$/))
return (arg == 'char*' || arg == 'unsigned char*');
if (t.match(/^l[c[]$/))
return (arg == 'wchar_t*');
if (t.match(/^[aefg]$/))
return (arg == 'float*');
if (t.match(/^l[aefg]$/))
return (arg == 'double*');
if (t.match(/^n$/))
return (arg == 'int*');
// ...
}
if (t.match(/^h?s$/))
return (arg.match(/^(const )?(unsigned )?char\*$/));
if (t.match(/^ls$/))
return (arg.match(/^(const )?(unsigned )?wchar_t\*$/));
error('Unrecognized format specifier "'+fmt+'"', loc());
return true;
}
function check_arg_types(ctype, functype, fmt_string, arg_type_names, loc) {
// Match a superset of printf and scanf format strings
var fmt_types = fmt_string.match(/%([-+ #0*]*)(\*|\d+)?(\.\*|\.-?\d+)?(hh|h|ll|l|j|z|t|L)?(.)/g);
var num_fmt_types = 0;
for each (var fmt_type in fmt_types) {
if (fmt_type != '%%')
++num_fmt_types;
if (functype == 'printf') {
// In printf, each '*' eats an extra argument
var stars = fmt_type.match(/\*/g);
if (stars)
num_fmt_types += stars.length;
} else if (functype == 'scanf') {
// In scanf, a '*' prefix means the argument is omitted
if (fmt_type.match(/^%\*/))
--num_fmt_types;
}
}
if (num_fmt_types != arg_type_names.length) {
error('Number of format string specifiers ('+num_fmt_types+') != number of format arguments ('+arg_type_names.length+')', loc());
return;
}
for each (var fmt_type in fmt_types) {
if (fmt_type != '%%') {
if (functype == 'printf') {
// In printf, each '*' eats an extra argument of type int
var stars = fmt_type.match(/\*/g);
if (stars) {
for (var s in stars) {
var arg = arg_type_names.shift();
if (! compare_format_type(ctype, functype, '%d', arg, loc)) {
error('Invalid argument type "'+arg+'" for format specifier "'+fmt_type+'"', loc());
}
}
}
} else if (functype == 'scanf') {
// In scanf, a '*' prefix means the argument is omitted
if (fmt_type.match(/^%\*/))
continue;
}
var arg = arg_type_names.shift();
if (! compare_format_type(ctype, functype, fmt_type, arg, loc)) {
error('Invalid argument type "'+arg+'" for format specifier "'+fmt_type+'"', loc());
}
}
}
if (arg_type_names.length) {
error('Internal error: got some arg types left over');
}
}
function type_string_without_typedefs(type) {
// Walk up the typedef chain
while (TYPE_NAME(type) && TREE_CODE(TYPE_NAME(type)) == TYPE_DECL && DECL_ORIGINAL_TYPE(TYPE_NAME(type)))
type = DECL_ORIGINAL_TYPE(TYPE_NAME(type));
// Recursively strip typedefs from pointer types
if (TREE_CODE(type) == POINTER_TYPE) {
// This bit is copied from type_string():
let quals = [];
if (TYPE_VOLATILE(type)) quals.push('volatile');
if (TYPE_RESTRICT(type)) quals.push('restrict');
if (TYPE_READONLY(type)) quals.push('const');
var suffix = quals.length ? ' ' + quals.join(' ') : '';
return type_string_without_typedefs(TREE_TYPE(type)) + '*' + suffix;
} else {
return type_string(type);
}
}
function walk_printfs(fndecl) {
function tree_walker(t, stack) {
function getLocation() {
var loc = location_of(t);
if (loc) return loc;
for (var i = stack.length - 1; i >= 0; --i) {
var loc = location_of(stack[i]);
if (loc) return loc;
}
return location_of(DECL_SAVED_TREE(fndecl));
}
var code = TREE_CODE(t);
if (code == CALL_EXPR) {
var decl = call_function_decl(t);
if (! decl)
return true;
var printf_type = find_printf_type(decl, getLocation);
if (! printf_type)
return true;
// print('--------------');
// print(rectify_function_decl(decl));
// print(printf_type);
var fmt_arg = CALL_EXPR_ARG(t, printf_type[2]-1);
var fmt_string = get_string_constant(fmt_arg);
if (typeof fmt_string == 'undefined') {
warning('Non-constant format string argument - can\'t check types', getLocation());
return true;
}
var arg_type_names = [];
for (var operand in call_arg_iterator(t)) {
var type = type_string_without_typedefs(TREE_TYPE(operand));
arg_type_names.push(type);
}
check_arg_types(printf_type[0], printf_type[1], fmt_string, arg_type_names.slice(printf_type[3]-1), getLocation);
}
return true;
}
walk_tree (DECL_SAVED_TREE(fndecl), tree_walker);
}
function process_cp_pre_genericize(fndecl) {
walk_printfs(fndecl);
}