• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/python3
2#
3# Copyright (C) 2023 The Android Open Source Project
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#
17
18import os
19
20from collections import defaultdict
21from pyclibrary import CParser
22
23from utils import system_chre_abs_path
24
25
26class ApiParser:
27    """Given a file-specific set of annotations (extracted from JSON annotations file), parses a
28    single API header file into data structures suitable for use with code generation. This class
29    will contain the parsed representation of the headers when instantiated.
30    """
31
32    def __init__(self, json_obj):
33        """Initialize and parse the API file described in the provided JSON-derived object.
34
35        :param json_obj: Extracted file-specific annotations from JSON
36        """
37
38        self.json = json_obj
39        self.structs_and_unions = {}
40        self._parse_annotations()
41        self._parse_api()
42
43    def _parse_annotations(self):
44        # Converts annotations list to a more usable data structure: dict keyed by structure name,
45        # containing a dict keyed by field name, containing a list of annotations (as they
46        # appear in the JSON). In other words, we can easily get all of the annotations for the
47        # "version" field in "chreWwanCellInfoResult" via
48        # annotations['chreWwanCellInfoResult']['version']. This is also a defaultdict, so it's safe
49        # to access if there are no annotations for this structure + field; it'll just give you
50        # an empty list in that case.
51
52        self.annotations = defaultdict(lambda: defaultdict(list))
53        for struct_info in self.json['struct_info']:
54            for annotation in struct_info['annotations']:
55                self.annotations[struct_info['name']
56                                 ][annotation['field']].append(annotation)
57
58    def _files_to_parse(self):
59        """Returns a list of files to supply as input to CParser"""
60
61        # Input paths for CParser are stored in JSON relative to <android_root>/system/chre
62        # Reformulate these to absolute paths, and add in some default includes that we always
63        # supply
64        chre_project_base_dir = system_chre_abs_path()
65        default_includes = ['api_parser/parser_defines.h',
66                            'chre_api/include/chre_api/chre/version.h']
67        files = default_includes + \
68            self.json['includes'] + [self.json['filename']]
69        return [os.path.join(chre_project_base_dir, file) for file in files]
70
71    def _parse_structs_and_unions(self):
72        # Starts with the root structures (i.e. those that will appear at the top-level in one
73        # or more CHPP messages), build a data structure containing all of the information we'll
74        # need to emit the CHPP structure definition and conversion code.
75
76        structs_and_unions_to_parse = self.json['root_structs'].copy()
77        while len(structs_and_unions_to_parse) > 0:
78            type_name = structs_and_unions_to_parse.pop()
79            if type_name in self.structs_and_unions:
80                continue
81
82            entry = {
83                'appears_in': set(),  # Other types this type is nested within
84                'dependencies': set(),  # Types that are nested in this type
85                'has_vla_member': False,  # True if this type or any dependency has a VLA member
86                'members': [],  # Info about each member of this type
87            }
88            if type_name in self.parser.defs['structs']:
89                defs = self.parser.defs['structs'][type_name]
90                entry['is_union'] = False
91            elif type_name in self.parser.defs['unions']:
92                defs = self.parser.defs['unions'][type_name]
93                entry['is_union'] = True
94            else:
95                raise RuntimeError(
96                    "Couldn't find {} in parsed structs/unions".format(type_name))
97
98            for member_name, member_type, _ in defs['members']:
99                member_info = {
100                    'name': member_name,
101                    'type': member_type,
102                    'annotations': self.annotations[type_name][member_name],
103                    'is_nested_type': False,
104                }
105
106                if member_type.type_spec.startswith('struct ') or \
107                        member_type.type_spec.startswith('union '):
108                    member_info['is_nested_type'] = True
109                    member_type_name = member_type.type_spec.split(' ')[1]
110                    member_info['nested_type_name'] = member_type_name
111                    entry['dependencies'].add(member_type_name)
112                    structs_and_unions_to_parse.append(member_type_name)
113
114                entry['members'].append(member_info)
115
116                # Flip a flag if this structure has at least one variable-length array member, which
117                # means that the encoded size can only be computed at runtime
118                if not entry['has_vla_member']:
119                    for annotation in self.annotations[type_name][member_name]:
120                        if annotation['annotation'] == 'var_len_array':
121                            entry['has_vla_member'] = True
122
123            self.structs_and_unions[type_name] = entry
124
125        # Build reverse linkage of dependency chain (i.e. lookup between a type and the other types
126        # it appears in)
127        for type_name, type_info in self.structs_and_unions.items():
128            for dependency in type_info['dependencies']:
129                self.structs_and_unions[dependency]['appears_in'].add(
130                    type_name)
131
132        # Bubble up "has_vla_member" to types each type it appears in, i.e. if this flag is set to
133        # True on a leaf node, then all its ancestors should also have the flag set to True
134        for type_name, type_info in self.structs_and_unions.items():
135            if type_info['has_vla_member']:
136                types_to_mark = list(type_info['appears_in'])
137                while len(types_to_mark) > 0:
138                    type_to_mark = types_to_mark.pop()
139                    self.structs_and_unions[type_to_mark]['has_vla_member'] = True
140                    types_to_mark.extend(
141                        list(self.structs_and_unions[type_to_mark]['appears_in']))
142
143    def _parse_api(self):
144        """
145        Parses the API and stores the structs and unions.
146        """
147
148        file_to_parse = self._files_to_parse()
149        self.parser = CParser(file_to_parse, cache='parser_cache')
150        self._parse_structs_and_unions()
151