1#!/usr/bin/env python 2 3# Copyright JS Foundation and other contributors, http://js.foundation 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17from __future__ import print_function 18 19try: 20 from configparser import ConfigParser 21except ImportError: 22 from ConfigParser import ConfigParser 23 24import argparse 25import fileinput 26import json 27import os 28import re 29 30from settings import PROJECT_DIR 31 32 33MAGIC_STRINGS_INI = os.path.join(PROJECT_DIR, 'jerry-core', 'lit', 'lit-magic-strings.ini') 34MAGIC_STRINGS_INC_H = os.path.join(PROJECT_DIR, 'jerry-core', 'lit', 'lit-magic-strings.inc.h') 35 36 37def debug_dump(obj): 38 def deepcopy(obj): 39 if isinstance(obj, (list, tuple)): 40 return [deepcopy(e) for e in obj] 41 if isinstance(obj, set): 42 return [repr(e) for e in obj] 43 if isinstance(obj, dict): 44 return {repr(k): deepcopy(e) for k, e in obj.items()} 45 return obj 46 return json.dumps(deepcopy(obj), indent=4) 47 48 49def read_magic_string_defs(debug=False): 50 # Read the `jerry-core/lit/lit-magic-strings.ini` file and returns the magic 51 # string definitions found therein in the form of 52 # [LIT_MAGIC_STRINGS] 53 # LIT_MAGIC_STRING_xxx = "vvv" 54 # ... 55 # as 56 # [('LIT_MAGIC_STRING_xxx', 'vvv'), ...] 57 # sorted by length and alpha. 58 ini_parser = ConfigParser() 59 ini_parser.optionxform = str # case sensitive options (magic string IDs) 60 ini_parser.read(MAGIC_STRINGS_INI) 61 62 defs = [(str_ref, json.loads(str_value) if str_value != '' else '') 63 for str_ref, str_value in ini_parser.items('LIT_MAGIC_STRINGS')] 64 defs = sorted(defs, key=lambda ref_value: (len(ref_value[1]), ref_value[1])) 65 66 if debug: 67 print('debug: magic string definitions: {dump}' 68 .format(dump=debug_dump(defs))) 69 70 return defs 71 72 73def extract_magic_string_refs(debug=False): 74 results = {} 75 76 def process_line(fname, lnum, line, guard_stack): 77 # Build `results` dictionary as 78 # results['LIT_MAGIC_STRING_xxx'][('!defined (CONFIG_DISABLE_yyy_BUILTIN)', ...)] 79 # = [('zzz.c', 123), ...] 80 # meaning that the given literal is referenced under the given guards at 81 # the listed (file, line number) locations. 82 for str_ref in re.findall('LIT_MAGIC_STRING_[a-zA-Z0-9_]+', line): 83 if str_ref in ['LIT_MAGIC_STRING_DEF', 84 'LIT_MAGIC_STRING_FIRST_STRING_WITH_SIZE', 85 'LIT_MAGIC_STRING_LENGTH_LIMIT', 86 'LIT_MAGIC_STRING__COUNT']: 87 continue 88 89 guard_set = set() 90 for guards in guard_stack: 91 guard_set.update(guards) 92 guard_tuple = tuple(sorted(guard_set)) 93 94 if str_ref not in results: 95 results[str_ref] = {} 96 str_guards = results[str_ref] 97 98 if guard_tuple not in str_guards: 99 str_guards[guard_tuple] = [] 100 file_list = str_guards[guard_tuple] 101 102 file_list.append((fname, lnum)) 103 104 def process_guard(guard): 105 # Transform `#ifndef MACRO` to `#if !defined (MACRO)` and 106 # `#ifdef MACRO` to `#if defined (MACRO)` to enable or-ing/and-ing the 107 # conditions later on. 108 if guard.startswith('ndef '): 109 guard = guard.replace('ndef ', '!defined (', 1) + ')' 110 elif guard.startswith('def '): 111 guard = guard.replace('def ', 'defined (', 1) + ')' 112 return guard 113 114 def process_file(fname): 115 # Builds `guard_stack` list for each line of a file as 116 # [['!defined (CONFIG_DISABLE_yyy_BUILTIN)', ...], ...] 117 # meaning that all the listed guards (conditionals) have to hold for the 118 # line to be kept by the preprocessor. 119 guard_stack = [] 120 121 for line in fileinput.input(fname): 122 if_match = re.match('^ *# *if(.*)', line) 123 elif_match = re.match('^ *# *elif(.*)', line) 124 else_match = re.match('^ *# *else', line) 125 endif_match = re.match('^ *# *endif', line) 126 if if_match is not None: 127 guard_stack.append([process_guard(if_match.group(1))]) 128 elif elif_match is not None: 129 guards = guard_stack[-1] 130 guards[-1] = '!(%s)' % guards[-1] 131 guards.append(process_guard(elif_match.group(1))) 132 elif else_match is not None: 133 guards = guard_stack[-1] 134 guards[-1] = '!(%s)' % guards[-1] 135 elif endif_match is not None: 136 guard_stack.pop() 137 138 lnum = fileinput.filelineno() 139 process_line(fname, lnum, line, guard_stack) 140 141 if guard_stack: 142 print('warning: {fname}: unbalanced preprocessor conditional ' 143 'directives (analysis finished with no closing `#endif` ' 144 'for {guard_stack})' 145 .format(fname=fname, guard_stack=guard_stack)) 146 147 for root, _, files in os.walk(os.path.join(PROJECT_DIR, 'jerry-core')): 148 for fname in files: 149 if (fname.endswith('.c') or fname.endswith('.h')) \ 150 and fname != 'lit-magic-strings.inc.h': 151 process_file(os.path.join(root, fname)) 152 153 if debug: 154 print('debug: magic string references: {dump}' 155 .format(dump=debug_dump(results))) 156 157 return results 158 159 160def calculate_magic_string_guards(defs, uses, debug=False): 161 extended_defs = [] 162 163 for str_ref, str_value in defs: 164 if str_ref not in uses: 165 print('warning: unused magic string {str_ref}' 166 .format(str_ref=str_ref)) 167 continue 168 169 # Calculate the most compact guard, i.e., if a magic string is 170 # referenced under various guards, keep the one that is more generic. 171 # E.g., 172 # guard1 = A and B and C and D and E and F 173 # guard2 = A and B and C 174 # then guard1 or guard2 == guard2. 175 guards = [set(guard_tuple) for guard_tuple in uses[str_ref].keys()] 176 for i, guard_i in enumerate(guards): 177 if guard_i is None: 178 continue 179 for j, guard_j in enumerate(guards): 180 if j == i or guard_j is None: 181 continue 182 if guard_i < guard_j: 183 guards[j] = None 184 guards = {tuple(sorted(guard)) for guard in guards if guard is not None} 185 186 extended_defs.append((str_ref, str_value, guards)) 187 188 if debug: 189 print('debug: magic string definitions (with guards): {dump}' 190 .format(dump=debug_dump(extended_defs))) 191 192 return extended_defs 193 194 195def guards_to_str(guards): 196 return ' \\\n|| '.join(' && '.join(g.strip() for g in sorted(guard)) 197 for guard in sorted(guards)) 198 199 200def generate_header(gen_file): 201 header = \ 202"""/* Copyright JS Foundation and other contributors, http://js.foundation 203 * 204 * Licensed under the Apache License, Version 2.0 (the "License"); 205 * you may not use this file except in compliance with the License. 206 * You may obtain a copy of the License at 207 * 208 * http://www.apache.org/licenses/LICENSE-2.0 209 * 210 * Unless required by applicable law or agreed to in writing, software 211 * distributed under the License is distributed on an "AS IS" BASIS 212 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 213 * See the License for the specific language governing permissions and 214 * limitations under the License. 215 */ 216 217/* This file is automatically generated by the %s script 218 * from %s. Do not edit! */ 219""" % (os.path.basename(__file__), os.path.basename(MAGIC_STRINGS_INI)) 220 print(header, file=gen_file) 221 222 223def generate_magic_string_defs(gen_file, defs): 224 last_guards = set([()]) 225 for str_ref, str_value, guards in defs: 226 if last_guards != guards: 227 if () not in last_guards: 228 print('#endif', file=gen_file) 229 if () not in guards: 230 print('#if {guards}'.format(guards=guards_to_str(guards)), file=gen_file) 231 232 print('LIT_MAGIC_STRING_DEF ({str_ref}, {str_value})' 233 .format(str_ref=str_ref, str_value=json.dumps(str_value)), file=gen_file) 234 235 last_guards = guards 236 237 if () not in last_guards: 238 print('#endif', file=gen_file) 239 240 241def generate_first_magic_strings(gen_file, defs): 242 print(file=gen_file) # empty line separator 243 244 max_size = len(defs[-1][1]) 245 for size in range(max_size + 1): 246 last_guards = set([()]) 247 for str_ref, str_value, guards in defs: 248 if len(str_value) >= size: 249 if () not in guards and () in last_guards: 250 print('#if {guards}'.format(guards=guards_to_str(guards)), file=gen_file) 251 elif () not in guards and () not in last_guards: 252 if guards == last_guards: 253 continue 254 print('#elif {guards}'.format(guards=guards_to_str(guards)), file=gen_file) 255 elif () in guards and () not in last_guards: 256 print('#else', file=gen_file) 257 258 print('LIT_MAGIC_STRING_FIRST_STRING_WITH_SIZE ({size}, {str_ref})' 259 .format(size=size, str_ref=str_ref), file=gen_file) 260 261 if () in guards: 262 break 263 264 last_guards = guards 265 266 if () not in last_guards: 267 print('#endif', file=gen_file) 268 269 270def main(): 271 parser = argparse.ArgumentParser(description='lit-magic-strings.inc.h generator') 272 parser.add_argument('--debug', action='store_true', help='enable debug output') 273 args = parser.parse_args() 274 275 defs = read_magic_string_defs(debug=args.debug) 276 uses = extract_magic_string_refs(debug=args.debug) 277 278 extended_defs = calculate_magic_string_guards(defs, uses, debug=args.debug) 279 280 with open(MAGIC_STRINGS_INC_H, 'w') as gen_file: 281 generate_header(gen_file) 282 generate_magic_string_defs(gen_file, extended_defs) 283 generate_first_magic_strings(gen_file, extended_defs) 284 285 286if __name__ == '__main__': 287 main() 288