File: extract_edits.py

package info (click to toggle)
chromium 138.0.7204.157-1
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 6,071,864 kB
  • sloc: cpp: 34,936,859; ansic: 7,176,967; javascript: 4,110,704; python: 1,419,953; asm: 946,768; xml: 739,967; pascal: 187,324; sh: 89,623; perl: 88,663; objc: 79,944; sql: 50,304; cs: 41,786; fortran: 24,137; makefile: 21,806; php: 13,980; tcl: 13,166; yacc: 8,925; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (187 lines) | stat: -rwxr-xr-x 5,807 bytes parent folder | download | duplicates (6)
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
#!/usr/bin/env vpython3
# Copyright 2023 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Script to extract edits from rewrite_templated_container_fields clang
tool output.

If the tool emits edits, then the edits should look like this:
    ...
    {lhs_node1};{lhs_node2}
    {node_n}
    {lhs_node3};{lhs_node4}
    ...
    ...
Where lhs_node, rhs_node, and node_n represent a node's text representation
generated using rewrite_templated_container_fields' Node::ToString() function.

The string representation has the following format:
`{is_field\,is_excluded\,has_auto_type\,r:::<file path>:::<offset>:::<length>
:::<replacement text>\,include-user-header:::<file path>:::-1:::-1
:::<include text>}`

where `is_field`,`is_excluded`, and `has_auto_type` are booleans represendted
as  0 or 1.

extract_edits.py takes input that is concatenated from multiple tool
invocations and extract just the edits with the following steps:
1- Construct the adjacency list of nodes
   (a pairs of nodes represents an edge in the graph)

2- Run `PropagateExclusions` to exclude fields reachable
   from a RAW_PTR_EXCLUSION annotated field.

3- Run `DFS` starting from non-excluded nodes and emit
   edtis for reachable nodes.

extract_edits.py would then emit the following output:
    <edit1>
    <edit2>
    <edit3>
    ...
Where the edit is either a replacemnt or an include directive.

For more details about how the tool works, see the doc here:
https://docs.google.com/document/d/1P8wLVS3xueI4p3EAPO4JJP6d1_zVp5SapQB0EW9iHQI/
"""

from __future__ import print_function
from collections import defaultdict
import sys


class Node:
  is_field = "0"
  is_excluded = "0"
  has_auto_type = "0"
  replacement = ""
  include_directive = ""
  neighbors = set()

  def __init__(self, is_field, is_excluded, has_auto_type, replacement,
               include_directive) -> None:
    self.is_field = is_field
    self.is_excluded = is_excluded
    self.replacement = replacement
    self.has_auto_type = has_auto_type
    self.include_directive = include_directive
    self.neighbors = set()

  def __eq__(self, other):
    if isinstance(other, Node):
      return self.replacement == other.replacement
    return False

  def __hash__(self) -> int:
    return hash((self.replacement, self.include_directive))


def GetNode(txt: str):
  txt = txt[1:len(txt) - 1]
  x = txt.split('\\,')
  return Node(x[0], x[1], x[2], x[3], x[4])


def DFS(visited: set, graph: defaultdict, key: str, key_to_node: defaultdict,
        changes: set):
  if key not in visited:
    node = key_to_node[key]
    if node.has_auto_type == "0":
      changes.add(node.replacement)
      changes.add(node.include_directive)
    visited.add(key)
    for neighbour in graph[key]:
      DFS(visited, graph, neighbour.replacement, key_to_node, changes)


# to propagate field exclusions to all neighbors
def PropagateExclusions(visited: set, graph: defaultdict, key: str,
                        key_to_node: defaultdict):
  if key not in visited:
    n = key_to_node[key]
    n.is_excluded = "1"
    key_to_node[key] = n
    visited.add(key)
    for neighbour in graph[key]:
      PropagateExclusions(visited, graph, neighbour.replacement, key_to_node)


def main():
  graph = defaultdict()
  key_to_node = defaultdict()  # since we cannot use nodes as keys
  # to map, use this to map node replacemnt to node.
  inside_marker_lines = False
  changes = set()
  excluded_fields = set()
  for line in sys.stdin:
    line = line.rstrip("\n\r")
    if line == '==== BEGIN EDITS ====':
      inside_marker_lines = True
      continue
    if line == '==== END EDITS ====':
      inside_marker_lines = False
      continue
    if inside_marker_lines:
      changes.add(line)
      continue

    ar = line.split(";")
    # These are fieldDecls
    if len(ar) == 1:
      lhs = GetNode(ar[0])
      # if the field is annotated with RAW_PTR_EXCLUSION,
      # add it to the set of excluded fields
      # this will be later propagated to all neighboring fields.
      if lhs.is_excluded == "1":
        excluded_fields.add(lhs.replacement)
        lhs.is_excluded = "0"
      key_to_node[lhs.replacement] = lhs
      if lhs.replacement not in graph:
        graph.setdefault(lhs.replacement, set())
      continue

    lhs = GetNode(ar[0])
    rhs = GetNode(ar[1])

    # In the case of a typedefNameDecl, all the var/param/fields
    # end up creating the same replacement. What is being done here is
    # that if any field has a typedefNameDecl type, make all matches
    # current and previous marked as is_field
    if lhs.replacement in key_to_node.keys():
      lhs.is_field = "1" if lhs.is_field == "1" or key_to_node[
          lhs.replacement].is_field == "1" else "0"

    if rhs.replacement in key_to_node.keys():
      rhs.is_field = "1" if rhs.is_field == "1" or key_to_node[
          rhs.replacement].is_field == "1" else "0"

    key_to_node[lhs.replacement] = lhs
    key_to_node[rhs.replacement] = rhs

    if lhs.replacement not in graph:
      graph.setdefault(lhs.replacement, set())
    graph[lhs.replacement].add(rhs)

    if rhs.replacement not in graph:
      graph.setdefault(rhs.replacement, set())
    graph[rhs.replacement].add(lhs)

  # Propagate changes to all excluded fields
  visited = set()
  for key in excluded_fields:
    key_to_node[key].is_excluded = "1"
    PropagateExclusions(visited, graph, key, key_to_node)

  visited = set()
  for key in graph.keys():
    node = key_to_node[key]
    if node.is_field == "1" and node.is_excluded == "0" and key not in visited:
      DFS(visited, graph, key, key_to_node, changes)
  changes = sorted(changes)
  for text in changes:
    print(text)
  return 0


if __name__ == '__main__':
  sys.exit(main())