1#!/usr/bin/env python 2# Copyright 2017 The PDFium Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6"""Verifies exported functions in public/*.h are in fpdfview_c_api_test.c. 7 8This script gathers a list of functions from public/*.h that contain 9FPDF_EXPORT. It then gathers a list of functions from 10fpdfsdk/fpdfview_c_api_test.c. It then verifies both lists do not contain 11duplicates, and they match each other. 12 13""" 14 15import os 16import re 17import sys 18 19def _IsValidFunctionName(function, filename): 20 if function.startswith('FPDF'): 21 return True 22 if function == 'FSDK_SetUnSpObjProcessHandler' and filename == 'fpdf_ext.h': 23 return True 24 if function.startswith('FORM_') and filename == 'fpdf_formfill.h': 25 return True 26 return False 27 28 29def _FindFunction(function_snippet, filename): 30 function_split = function_snippet.split('(') 31 assert len(function_split) == 2 32 function = function_split[0] 33 assert _IsValidFunctionName(function, filename) 34 return function 35 36 37def _GetExportsFromHeader(dirname, filename): 38 with open(os.path.join(dirname, filename)) as f: 39 contents = f.readlines() 40 look_for_function_name = False 41 functions = [] 42 for line in contents: 43 if look_for_function_name: 44 look_for_function_name = False 45 split_line = line.rstrip().split(' ') 46 functions.append(_FindFunction(split_line[0], filename)) 47 continue 48 49 if not line.startswith('FPDF_EXPORT '): 50 continue 51 52 # Format should be: FPDF_EXPORT return_type FPDF_CALLCONV 53 split_line = line.rstrip().split(' ') 54 callconv_index = split_line.index('FPDF_CALLCONV') 55 assert callconv_index >= 2 56 if callconv_index + 1 == len(split_line): 57 look_for_function_name = True 58 continue 59 60 functions.append(_FindFunction(split_line[callconv_index + 1], filename)) 61 return functions 62 63 64def _GetFunctionsFromPublicHeaders(src_path): 65 public_path = os.path.join(src_path, 'public') 66 functions = [] 67 for filename in os.listdir(public_path): 68 if filename.endswith('.h'): 69 functions.extend(_GetExportsFromHeader(public_path, filename)) 70 return functions 71 72 73def _GetFunctionsFromTest(api_test_path): 74 chk_regex = re.compile('^ CHK\((.*)\);\n$') 75 with open(api_test_path) as f: 76 contents = f.readlines() 77 functions = [] 78 for line in contents: 79 match = chk_regex.match(line) 80 if match: 81 functions.append(match.groups()[0]) 82 return functions 83 84 85def _FindDuplicates(functions): 86 return set([f for f in functions if functions.count(f) > 1]) 87 88 89def _CheckAndPrintFailures(failure_list, failure_message): 90 if not failure_list: 91 return True 92 93 print '%s:' % failure_message 94 for f in sorted(failure_list): 95 print f 96 return False 97 98 99def main(): 100 script_abspath = os.path.abspath(__file__) 101 src_path = os.path.dirname(os.path.dirname(os.path.dirname(script_abspath))) 102 public_functions = _GetFunctionsFromPublicHeaders(src_path) 103 104 api_test_relative_path = os.path.join('fpdfsdk', 'fpdfview_c_api_test.c') 105 api_test_path = os.path.join(src_path, api_test_relative_path) 106 test_functions = _GetFunctionsFromTest(api_test_path) 107 108 result = True 109 duplicate_public_functions = _FindDuplicates(public_functions) 110 check = _CheckAndPrintFailures(duplicate_public_functions, 111 'Found duplicate functions in public headers') 112 result = result and check 113 114 duplicate_test_functions = _FindDuplicates(test_functions) 115 check = _CheckAndPrintFailures(duplicate_test_functions, 116 'Found duplicate functions in API test') 117 result = result and check 118 119 public_functions_set = set(public_functions) 120 test_functions_set = set(test_functions) 121 not_tested = public_functions_set.difference(test_functions_set) 122 check = _CheckAndPrintFailures(not_tested, 'Functions not tested') 123 result = result and check 124 non_existent = test_functions_set.difference(public_functions_set) 125 check = _CheckAndPrintFailures(non_existent, 'Tested functions do not exist') 126 result = result and check 127 128 if not result: 129 print ('Some checks failed. Make sure %s is in sync with the public API ' 130 'headers.' 131 % api_test_relative_path); 132 return 1 133 134 return 0 135 136 137if __name__ == '__main__': 138 sys.exit(main()) 139