• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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