summaryrefslogtreecommitdiff
path: root/tools/process_iwyu.py
blob: 0bc6d5b9343faeefcb550a1d67fd97624c42977f (plain)
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
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# coding: utf8
# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
# SPDX-FileCopyrightText: 2020 Philip Chimento <philip.chimento@gmail.com>

# IWYU is missing the feature to designate a certain header as a "forward-decls
# header". In the case of SpiderMonkey, there are certain commonly used forward
# declarations that are all gathered in js/TypeDecls.h.
# We postprocess the IWYU output to fix this, and also fix the output format
# which is quite verbose, making it tedious to scroll through for 60 files.

import re
import sys


class Colors:
    NORMAL = '\33[0m'
    RED = '\33[31m'
    GREEN = '\33[32m'


ADD, REMOVE, FULL = range(3)
state = None
file = None
add = {}
remove = {}
all_includes = {}
there_were_errors = False

# When encountering one of these lines, move to a different state
MATCHERS = {
    r'\.\./(.*) should add these lines:': ADD,
    r'\.\./(.*) should remove these lines:': REMOVE,
    r'The full include-list for \.\./(.*):': FULL,
    r'\(\.\./(.*) has correct #includes/fwd-decls\)': None
}

FWD_HEADER = '#include <js/TypeDecls.h>'
FWD_DECLS_IN_HEADER = (
    'class JSAtom;',
    'struct JSContext;',
    'struct JSClass;',
    'class JSFunction;',
    'class JSFreeOp;',
    'class JSObject;',
    'struct JSRuntime;',
    'class JSScript;',
    'class JSString;',
    'namespace js { class TempAllocPolicy; }'
    'namespace JS { struct PropertyKey; }',
    'namespace JS { class Symbol; }',
    'namespace JS { class BigInt; }',
    'namespace JS { class Value; }',
    'namespace JS { class Compartment; }',
    'namespace JS { class Realm; }',
    'namespace JS { struct Runtime; }',
    'namespace JS { class Zone; }',
)
add_fwd_header = False

CSTDINT = '#include <cstdint>'
STDINTH = '#include <stdint.h>'

FALSE_POSITIVES = (
    # The bodies of these structs already come before their usage,
    # we don't need to have forward declarations of them as well
    ('gjs/atoms.h', 'class GjsAtoms;', ''),
    ('gjs/atoms.h', 'struct GjsSymbolAtom;', ''),

    # IWYU weird false positive when using std::vector::emplace_back() or
    # std::vector::push_back()
    ('gi/function.cpp', '#include <algorithm>', 'for max'),
    ('gi/private.cpp', '#include <algorithm>', 'for max'),
    ('gjs/importer.cpp', '#include <algorithm>', 'for max'),
    ('modules/cairo-context.cpp', '#include <algorithm>', 'for max'),

    # False positive when using Mozilla vectors' append() and
    # infallibleAppend()
    ('gi/function.cpp', '#include <utility>', 'for forward'),
    ('gi/ns.cpp', '#include <utility>', 'for forward'),
    ('gi/value.cpp', '#include <utility>', 'for forward'),
    ('gjs/importer.cpp', '#include <utility>', 'for forward'),
    ('gjs/module.cpp', '#include <utility>', 'for forward'),
    ('gjs/objectbox.cpp', '#include <utility>', 'for forward'),

    # False positive when using EnumType operators
    ('gi/arg-cache.h', '#include <type_traits>', 'for enable_if_t'),
    ('modules/cairo-context.cpp', '#include <type_traits>', 'for enable_if_t'),
    ('modules/cairo-region.cpp', '#include <type_traits>', 'for enable_if_t'),
    ('modules/cairo-surface.cpp', '#include <type_traits>', 'for enable_if_t'),

    # False positive when using GjsAutoPointer
    ('gi/object.cpp', '#include <type_traits>',
     'for remove_reference<>::type'),
    ('gi/private.cpp', '#include <type_traits>',
     'for remove_reference<>::type'),
    ('gi/value.cpp', '#include <type_traits>', 'for remove_reference<>::type'),
    ('gjs/context.cpp', '#include <type_traits>',
     'for remove_reference<>::type'),
    ('gjs/debugger.cpp', '#include <type_traits>',
     'for remove_reference<>::type'),
    ('gjs/importer.cpp', '#include <type_traits>',
     'for remove_reference<>::type'),
    ('gjs/profiler.cpp', '#include <type_traits>',
     'for remove_reference<>::type'),
    ('test/gjs-test-jsapi-utils.cpp', '#include <type_traits>',
     'for remove_reference<>::type'),

    # Weird false positive on some versions of IWYU
    ('gi/arg.cpp', 'struct _GVariant;', ''),
    ('gjs/profiler.cpp', '#include <gjs/profiler.h>', ''),
)


def output():
    global file, state, add_fwd_header, there_were_errors

    # Workaround for
    # https://github.com/include-what-you-use/include-what-you-use/issues/226
    if CSTDINT in add:
        why = add.pop(CSTDINT, None)
        if STDINTH in remove:
            remove.pop(STDINTH, None)
        elif STDINTH not in all_includes:
            add[STDINTH] = why

    if add_fwd_header:
        if FWD_HEADER not in all_includes:
            if FWD_HEADER in remove:
                remove.pop(FWD_HEADER, None)
            else:
                add[FWD_HEADER] = ''

    if add or remove:
        print(f'\n== {file} ==')
        for line, why in add.items():
            if why:
                why = '  // ' + why
            print(f'{Colors.GREEN}+{line}{Colors.NORMAL}{why}')
        for line, why in remove.items():
            if why:
                why = '  // ' + why
            print(f'{Colors.RED}-{line}{Colors.NORMAL}{why}')
        there_were_errors = True

    add.clear()
    remove.clear()
    all_includes.clear()
    add_fwd_header = False


for line in sys.stdin:
    line = line.strip()
    if not line:
        continue

    if 'fatal error:' in line:
        print(line)
        there_were_errors = True
        continue

    # filter out errors having to do with compiler arguments unknown to IWYU
    if line.startswith('error:'):
        continue

    if line == '---':
        output()
        continue

    state_changed = False
    file_changed = False
    for matcher, newstate in MATCHERS.items():
        match = re.match(matcher, line)
        if match:
            state = newstate
            if match.group(1) != file:
                if file is not None:
                    file_changed = True
                file = match.group(1)
            state_changed = True
            break
    if file_changed:
        output()
        continue
    if state_changed:
        continue

    line, _, why = line.partition(' // ')
    line = line.strip()
    if state == ADD:
        if line in FWD_DECLS_IN_HEADER:
            add_fwd_header = True
            continue
        if (file, line, why) in FALSE_POSITIVES:
            continue
        add[line] = why
    elif state == REMOVE:
        if line.startswith('- '):
            line = line[2:]
        remove[line] = why
    elif state == FULL:
        all_includes[line] = why

if there_were_errors:
    sys.exit(1)