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())
|