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