1#!/usr/bin/env python
2
3"""Find Kconfig identifiers that are referenced but not defined."""
4
5# (c) 2014 Valentin Rothberg <valentinrothberg@gmail.com>
6# (c) 2014 Stefan Hengelein <stefan.hengelein@fau.de>
7#
8# Licensed under the terms of the GNU GPL License version 2
9
10
11import os
12import re
13from subprocess import Popen, PIPE, STDOUT
14
15
16# regex expressions
17OPERATORS = r"&|\(|\)|\||\!"
18FEATURE = r"(?:\w*[A-Z0-9]\w*){2,}"
19DEF = r"^\s*(?:menu){,1}config\s+(" + FEATURE + r")\s*"
20EXPR = r"(?:" + OPERATORS + r"|\s|" + FEATURE + r")+"
21STMT = r"^\s*(?:if|select|depends\s+on)\s+" + EXPR
22SOURCE_FEATURE = r"(?:\W|\b)+[D]{,1}CONFIG_(" + FEATURE + r")"
23
24# regex objects
25REGEX_FILE_KCONFIG = re.compile(r".*Kconfig[\.\w+\-]*$")
26REGEX_FEATURE = re.compile(r"(" + FEATURE + r")")
27REGEX_SOURCE_FEATURE = re.compile(SOURCE_FEATURE)
28REGEX_KCONFIG_DEF = re.compile(DEF)
29REGEX_KCONFIG_EXPR = re.compile(EXPR)
30REGEX_KCONFIG_STMT = re.compile(STMT)
31REGEX_KCONFIG_HELP = re.compile(r"^\s+(help|---help---)\s*$")
32REGEX_FILTER_FEATURES = re.compile(r"[A-Za-z0-9]$")
33
34
35def main():
36    """Main function of this module."""
37    source_files = []
38    kconfig_files = []
39    defined_features = set()
40    referenced_features = dict()  # {feature: [files]}
41
42    # use 'git ls-files' to get the worklist
43    pop = Popen("git ls-files", stdout=PIPE, stderr=STDOUT, shell=True)
44    (stdout, _) = pop.communicate()  # wait until finished
45    if len(stdout) > 0 and stdout[-1] == "\n":
46        stdout = stdout[:-1]
47
48    for gitfile in stdout.rsplit("\n"):
49        if ".git" in gitfile or "ChangeLog" in gitfile or \
50                ".log" in gitfile or os.path.isdir(gitfile):
51            continue
52        if REGEX_FILE_KCONFIG.match(gitfile):
53            kconfig_files.append(gitfile)
54        else:
55            # all non-Kconfig files are checked for consistency
56            source_files.append(gitfile)
57
58    for sfile in source_files:
59        parse_source_file(sfile, referenced_features)
60
61    for kfile in kconfig_files:
62        parse_kconfig_file(kfile, defined_features, referenced_features)
63
64    print "Undefined symbol used\tFile list"
65    for feature in sorted(referenced_features):
66        # filter some false positives
67        if feature == "FOO" or feature == "BAR" or \
68                feature == "FOO_BAR" or feature == "XXX":
69            continue
70        if feature not in defined_features:
71            if feature.endswith("_MODULE"):
72                # avoid false positives for kernel modules
73                if feature[:-len("_MODULE")] in defined_features:
74                    continue
75            files = referenced_features.get(feature)
76            print "%s\t%s" % (feature, ", ".join(files))
77
78
79def parse_source_file(sfile, referenced_features):
80    """Parse @sfile for referenced Kconfig features."""
81    lines = []
82    with open(sfile, "r") as stream:
83        lines = stream.readlines()
84
85    for line in lines:
86        if not "CONFIG_" in line:
87            continue
88        features = REGEX_SOURCE_FEATURE.findall(line)
89        for feature in features:
90            if not REGEX_FILTER_FEATURES.search(feature):
91                continue
92            sfiles = referenced_features.get(feature, set())
93            sfiles.add(sfile)
94            referenced_features[feature] = sfiles
95
96
97def get_features_in_line(line):
98    """Return mentioned Kconfig features in @line."""
99    return REGEX_FEATURE.findall(line)
100
101
102def parse_kconfig_file(kfile, defined_features, referenced_features):
103    """Parse @kfile and update feature definitions and references."""
104    lines = []
105    skip = False
106
107    with open(kfile, "r") as stream:
108        lines = stream.readlines()
109
110    for i in range(len(lines)):
111        line = lines[i]
112        line = line.strip('\n')
113        line = line.split("#")[0]  # ignore comments
114
115        if REGEX_KCONFIG_DEF.match(line):
116            feature_def = REGEX_KCONFIG_DEF.findall(line)
117            defined_features.add(feature_def[0])
118            skip = False
119        elif REGEX_KCONFIG_HELP.match(line):
120            skip = True
121        elif skip:
122            # ignore content of help messages
123            pass
124        elif REGEX_KCONFIG_STMT.match(line):
125            features = get_features_in_line(line)
126            # multi-line statements
127            while line.endswith("\\"):
128                i += 1
129                line = lines[i]
130                line = line.strip('\n')
131                features.extend(get_features_in_line(line))
132            for feature in set(features):
133                paths = referenced_features.get(feature, set())
134                paths.add(kfile)
135                referenced_features[feature] = paths
136
137
138if __name__ == "__main__":
139    main()
140