1#
2# Copyright (C) 2016 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#      http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15#
16"""Parses the contents of a GCDA file generated by the GCC compiler.
17
18The parse() function updates a summary object, which was created by
19the GCNO parser, and includes coverage information along arcs and at
20code blocks.
21
22
23    Typical usage example:
24
25    parse(file_name, file_summary)
26"""
27
28import struct
29import sys
30
31from vts.utils.python.coverage import parser
32from vts.utils.python.coverage import gcno_parser
33
34class GCDAParser(parser.GcovStreamParserUtil):
35    """Parser object class stores stateful information for parsing GCDA files.
36
37    Stores the file stream and a FileSummary object as it is updated.
38
39    Attributes:
40        checksum: The checksum (int) of the file
41        file_summary: The FileSummary object describing the source file
42        format: Character denoting the endianness of the file
43        stream: File stream object for a GCDA file
44    """
45
46    MAGIC = 0x67636461
47    TAG_FUNCTION = 0x01000000
48    TAG_COUNTER = 0x01a10000
49    TAG_OBJECT = 0xa1000000
50    TAG_PROGRAM = 0xa3000000
51
52    def __init__(self, stream):
53        """Inits the parser with the input stream and default values.
54
55        The byte order is set by default to little endian and the summary file
56        must be provided from the output of the GCNOparser.
57
58        Args:
59            stream: An input binary file stream to a .gcno file
60        """
61        self._file_summary = None
62        super(GCDAParser, self).__init__(stream, self.MAGIC)
63
64    @property
65    def file_summary(self):
66        """Gets the FileSummary object where coverage data is stored.
67
68        Returns:
69            A FileSummary object.
70        """
71        return self._file_summary
72
73    @file_summary.setter
74    def file_summary(self, file_summary):
75        """Sets the FileSummary object in which to store coverage data.
76
77        Args:
78            file_summary: A FileSummary object from a processed gcno file
79        """
80        self._file_summary = file_summary
81
82    def Parse(self, file_summary):
83        """Runs the parser on the file opened in the stream attribute.
84
85        Reads coverage information from the GCDA file stream and resolves
86        block and edge weights.
87
88        Returns:
89            FileSummary object representing the coverage for functions, blocks,
90            arcs, and lines in the opened GCNO file.
91
92        Raises:
93            parser.FileFormatError: invalid file format or invalid counts.
94        """
95        self.file_summary = file_summary
96        func = None
97
98        while True:
99            tag = str()
100
101            try:
102                while True:
103                    tag = self.ReadInt()
104                    if (tag == self.TAG_FUNCTION or tag == self.TAG_COUNTER or
105                            tag == self.TAG_OBJECT or tag == self.TAG_PROGRAM):
106                        break
107                length = self.ReadInt()
108            except parser.FileFormatError:
109                return self.file_summary  #  end of file reached
110
111            if tag == self.TAG_FUNCTION:
112                func = self.ReadFunction(length)
113            elif tag == self.TAG_COUNTER:
114                self.ReadCounts(func)
115                if not func.Resolve():
116                    raise parser.FileFormatError(
117                        "Corrupt file: Counts could not be resolved.")
118            elif tag == self.TAG_OBJECT:
119                pass
120            elif tag == self.TAG_PROGRAM:
121                self.ReadInt()  #  checksum
122                for i in range(length - 1):
123                    self.ReadInt()
124
125    def ReadFunction(self, length):
126        """Reads a function header from the stream.
127
128        Reads information about a function from the gcda file stream and
129        returns the function.
130
131        Args:
132            func: the function for which coverage information will be read.
133
134        Raises:
135            parser.FileFormatError: Corrupt file.
136        """
137        ident = self.ReadInt()
138        func = self.file_summary.functions[ident]
139        checksum = self.ReadInt()
140        words_read = 3
141        if int(self.version[1]) > 4:
142            self.ReadInt()
143            words_read = 4
144
145        if words_read < length:
146            gcda_name = self.ReadString()
147
148        return func
149
150    def ReadCounts(self, func):
151        """Reads arc counts from the stream.
152
153        Reads counts from the gcda file stream for arcs that are not
154        fake and are not in the tree. Updates their counts and marks them
155        as having resolved counts.
156
157        Args:
158            func: FunctionSummary for which arc counts will be read.
159        """
160        for block in func.blocks:
161            for arc in block.exit_arcs:
162                if not arc.fake and not arc.on_tree:
163                    count = self.ReadInt64()
164                    arc.count = count
165                    arc.resolved = True
166
167
168def ParseGcdaFile(file_name, file_summary):
169    """Parses the .gcno file specified by the input.
170
171    Reads the .gcno file specified and parses the information describing
172    basic blocks, functions, and arcs.
173
174    Args:
175        file_name: A string file path to a .gcno file
176        file_summary: The summary from a parsed gcno file
177
178    Returns:
179        A summary object containing information about the coverage for each
180        block in each function.
181    """
182
183    with open(file_name, 'rb') as stream:
184        return GCDAParser(stream).Parse(file_summary)
185
186
187if __name__ == '__main__':
188    if len(sys.argv) != 2:
189        print('usage: gcda_parser.py [gcda file name] [gcno file name]')
190    else:
191        file_summary = gcno_parser.ParseGcnoFile(sys.argv[2])
192        print(str(ParseGcdaFile(sys.argv[1], file_summary)))
193