#!/usr/bin/env python3 # # cppcheck addon for Y2038 safeness detection # # Detects: # # 1. _TIME_BITS being defined to something else than 64 bits # 2. _USE_TIME_BITS64 being defined when _TIME_BITS is not # 3. Any Y2038-unsafe symbol when _USE_TIME_BITS64 is not defined. # # Example usage: # $ cppcheck --dump path-to-src/test.c # $ y2038.py path-to-src/test.c.dump # # y2038.py will walk the source tree for .dump files. from __future__ import print_function from . import cppcheckdata import sys import os import re # -------------------------------------------- # #define/#undef detection regular expressions # -------------------------------------------- # test for '#define _TIME_BITS 64' re_define_time_bits_64 = re.compile(r'^\s*#\s*define\s+_TIME_BITS\s+64\s*$') # test for '#define _TIME_BITS ...' (combine w/ above to test for 'not 64') re_define_time_bits = re.compile(r'^\s*#\s*define\s+_TIME_BITS\s+.*$') # test for '#undef _TIME_BITS' (if it ever happens) re_undef_time_bits = re.compile(r'^\s*#\s*undef\s+_TIME_BITS\s*$') # test for '#define _USE_TIME_BITS64' re_define_use_time_bits64 = re.compile(r'^\s*#\s*define\s+_USE_TIME_BITS64\s*$') # test for '#undef _USE_TIME_BITS64' (if it ever happens) re_undef_use_time_bits64 = re.compile(r'^\s*#\s*undef\s+_USE_TIME_BITS64\s*$') # -------------------------------- # List of Y2038-unsafe identifiers # -------------------------------- # This is WIP. Eventually it should contain all identifiers (types # and functions) which would be affected by the Y2038 bug. id_Y2038 = { # Y2038-unsafe types by definition 'time_t' # Types using Y2038-unsafe types 'lastlog', 'msqid_ds', 'semid_ds', 'timeb', 'timespec', 'timeval', 'utimbuf', 'itimerspec', 'stat', 'clnt_ops', 'elf_prstatus', 'itimerval', 'ntptimeval', 'rusage', 'timex', 'utmp', 'utmpx', # APIs using 2038-unsafe types 'ctime', 'ctime_r', 'difftime', 'gmtime', 'gmtime_r', 'localtime', 'localtime_r', 'mktime', 'stime', 'timegm', 'timelocal', 'time', 'msgctl', 'ftime', 'aio_suspend', 'clock_getres', 'clock_gettime', 'clock_nanosleep', 'clock_settime', 'futimens', 'mq_timedreceive', 'mq_timedsend', 'nanosleep', 'pselect', 'pthread_cond_timedwait', 'pthread_mutex_timedlock', 'pthread_rwlock_timedrdlock', 'pthread_rwlock_timedwrlock', 'sched_rr_get_interval', 'sem_timedwait', 'sigtimedwait', 'timespec_get', 'utimensat', 'adjtime', 'pmap_rmtcall', 'clntudp_bufcreate', 'clntudp_create', 'futimes', 'gettimeofday', 'lutimes', 'select', 'settimeofday', 'utimes', 'utime', 'timerfd_gettime', 'timerfd_settime', 'timer_gettime', 'timer_settime', 'fstatat', 'fstat', '__fxstatat', '__fxstat', 'lstat', '__lxstat', 'stat', '__xstat', 'struct itimerval', 'setitimer', 'getitimer', 'ntp_gettime', 'getrusage', 'wait3', 'wait4', 'adjtimex', 'ntp_adjtime', 'getutent_r', 'getutent', 'getutid_r', 'getutid', 'getutline_r', 'getutline', 'login', 'pututline', 'updwtmp', 'getutxent', 'getutxid', 'getutxline', 'pututxline' } def check_y2038_safe(dumpfile, quiet=False): # Assume that the code is Y2038 safe until proven otherwise y2038safe = True # load XML from .dump file data = cppcheckdata.CppcheckData(dumpfile) # Convert dump file path to source file in format generated by cppcheck. # For example after the following call: # cppcheck ./src/my-src.c --dump # We got 'src/my-src.c' value for 'file' field in cppcheckdata. srcfile = dumpfile.rstrip('.dump') srcfile = os.path.expanduser(srcfile) srcfile = os.path.normpath(srcfile) # go through each configuration for cfg in data.iterconfigurations(): if not quiet: print('Checking %s, config %s...' % (srcfile, cfg.name)) safe_ranges = [] safe = -1 time_bits_defined = False srclinenr = '0' for directive in cfg.directives: # track source line number if directive.file == srcfile: srclinenr = directive.linenr # check for correct _TIME_BITS if present if re_define_time_bits_64.match(directive.str): time_bits_defined = True elif re_define_time_bits.match(directive.str): cppcheckdata.reportError(directive, 'error', '_TIME_BITS must be defined equal to 64', 'y2038', 'type-bits-not-64') time_bits_defined = False y2038safe = False elif re_undef_time_bits.match(directive.str): time_bits_defined = False # check for _USE_TIME_BITS64 (un)definition if re_define_use_time_bits64.match(directive.str): safe = int(srclinenr) # warn about _TIME_BITS not being defined if not time_bits_defined: cppcheckdata.reportError(directive, 'warning', '_USE_TIME_BITS64 is defined but _TIME_BITS was not', 'y2038', 'type-bits-undef') elif re_undef_use_time_bits64.match(directive.str): unsafe = int(srclinenr) # do we have a safe..unsafe area? if unsafe > safe > 0: safe_ranges.append((safe, unsafe)) safe = -1 # check end of source beyond last directive if len(cfg.tokenlist) > 0: unsafe = int(cfg.tokenlist[-1].linenr) if unsafe > safe > 0: safe_ranges.append((safe, unsafe)) # go through all tokens for token in cfg.tokenlist: if token.str in id_Y2038: if not any(lower <= int(token.linenr) <= upper for (lower, upper) in safe_ranges): cppcheckdata.reportError(token, 'warning', token.str + ' is Y2038-unsafe', 'y2038', 'unsafe-call') y2038safe = False token = token.next return y2038safe def get_args(): parser = cppcheckdata.ArgumentParser() return parser.parse_args() if __name__ == '__main__': args = get_args() exit_code = 0 quiet = not any((args.quiet, args.cli)) if not args.dumpfile: if not args.quiet: print("no input files.") sys.exit(0) for dumpfile in args.dumpfile: if not os.path.isfile(dumpfile): print("Error: File not found: %s" % dumpfile) sys.exit(127) if not os.access(dumpfile, os.R_OK): print("Error: Permission denied: %s" % dumpfile) sys.exit(13) if not args.quiet: print('Checking ' + dumpfile + '...') y2038safe = check_y2038_safe(dumpfile, quiet) if not y2038safe and exit_code == 0: exit_code = 1 sys.exit(exit_code)