forked from 0ad/0ad
303 lines
11 KiB
JavaScript
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);
|
|
}
|