Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

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

# Copyright (C) 2017 Free Software Foundation, Inc. 

 

# This program is free software; you can redistribute it and/or modify 

# it under the terms of the GNU General Public License as published by 

# the Free Software Foundation; either version 3 of the License, or 

# (at your option) any later version. 

# 

# This program is distributed in the hope that it will be useful, 

# but WITHOUT ANY WARRANTY; without even the implied warranty of 

# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

# GNU General Public License for more details. 

# 

# You should have received a copy of the GNU General Public License 

# along with GCC; see the file COPYING3. If not see 

# <http://www.gnu.org/licenses/>. 

 

# Helpers to implement the "scan-dwarf" check command (see gcc-dwarf.exp) 

 

import ast 

 

from dwarfutils.data import Matcher 

from testutils import check 

 

 

def expand_tabs(line): 

"""Expand tab characters in `line` into spaces.""" 

result = '' 

for c in line: 

if c == '\t': 

column_increment = 8 - len(result) % 8 

result += ' ' * column_increment 

else: 

result += c 

return ''.join(result) 

 

 

def extract_matchers_from_file(source_file): # no-coverage 

""" 

Wrapper around `extract_matchers` to read `source_file` 

 

:param str source_file: Path to the source file to scan. 

:rtype: list[(int, code-object)] 

""" 

with open(source_file, 'r') as f: 

return extract_matchers(source_file, f.read()) 

 

 

 

def extract_matchers(source_file, lines): 

""" 

Scan `source_file` to find the Python expressions located between ">>>" 

lines. 

 

For instance, if the source file is a C file that contains:: 

 

/* >>> 

Matcher('DW_TAG_subprogram') 

Matcher('DW_TAG_structure_type') 

>>> 

 

This will extract both calls to Matcher and turn them into compiled code 

that can be executed with `eval`. The result is a list of couples: source line 

number and code objects. 

 

The source file is expected to contain exactly two ">>>" lines and ">>>" 

parts must appear on the same column. Then, all the lines in between are 

required to be a valid sequence of Python expressions, starting on the same 

column and which evaluate to Matcher instances. 

 

:param str source_file: Path to the source file to scan. 

:param str lines: Source file content to scan. 

:rtype: list[(int, code-object)] 

""" 

result = [] 

 

lines = lines.splitlines() 

 

# Make sure there are only two ">>>" lines in this source file 

error = ValueError('{0} must contain exactly two lines that hold' 

' ">>>"'.format(source_file)) 

column_no = None 

opening = None 

closing = None 

for i, line in enumerate(lines): 

if '>>>' in line: 

line = expand_tabs(line) 

if opening is None: 

opening = i 

column_no = line.find('>>>') 

elif closing is None: 

closing = i 

if line.find('>>>') != column_no: 

raise ValueError('">>>" lines in {0} must appear on the' 

' same column'.format(source_file)) 

else: 

raise error 

if opening is None or closing is None: 

raise error 

 

# Now we have located the >>> pair, extract the code in between, removing 

# indentation. 

code_lines = '\n'.join(expand_tabs(line)[column_no:] 

for line in lines[opening + 1:closing]) 

 

# Parse the code chunk. This is supposed to return an ast.Module instance. 

location = '{0}[DWARF matching code]'.format(source_file) 

mod = ast.parse(code_lines, 'Matcher code in {0}'.format(source_file)) 

 

# And now compile each individual expression to build the result 

for expr in mod.body: 

lineno = opening + expr.lineno + 1 

if not isinstance(expr, ast.Expr): 

raise ValueError('{0}:{1}: expression expected'.format( 

source_file, lineno 

)) 

code = compile(ast.Expression(expr.value), location, 'eval') 

result.append((lineno, code)) 

 

return result 

 

 

def run_matchers(matchers, compilation_unit): 

""" 

Run the given matchers against the DIE tree in the given compilation unit. 

 

Check that all matchers succeed using `testutils.check`. 

 

:param list[(int, code-object)] matchers: Matchers to run. This is meant to 

be the result of `extract_matchers`. 

""" 

for lineno, matcher_code in matchers: 

def predicate(die): 

try: 

matcher = eval(matcher_code, {'Matcher': Matcher}) 

except Exception as exc: 

raise RuntimeError('line {0}: {1}: {2}'.format( 

lineno, type(exc).__name__, str(exc) 

)) 

if not isinstance(matcher, Matcher): 

raise RuntimeError('line {0}: got {1} but a Matcher instance' 

' was expected'.format(lineno, 

repr(matcher))) 

return die.tree_matches(matcher).mismatch_reason is None 

dies = compilation_unit.find(predicate, single=False) 

check(dies, 'find DIE to match pattern at line {0}'.format(lineno))