-
Notifications
You must be signed in to change notification settings - Fork 930
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'ticket31176' into ticket31176_merged
- Loading branch information
Showing
12 changed files
with
462 additions
and
181 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
o Major features (developer tools): | ||
- Our best-practices tracker now integrates with our include-checker tool | ||
to keep track of the layering violations that we have not yet fixed. | ||
We hope to reduce this number over time to improve Tor's modularity. | ||
Closes ticket 31176. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,181 +1,14 @@ | ||
#!/usr/bin/python | ||
# Copyright 2018 The Tor Project, Inc. See LICENSE file for licensing info. | ||
|
||
"""This script looks through all the directories for files matching *.c or | ||
*.h, and checks their #include directives to make sure that only "permitted" | ||
headers are included. | ||
# This file is no longer here; see practracker/includes.py for this | ||
# functionality. This is a stub file that exists so that older git | ||
# hooks will know where to look. | ||
|
||
Any #include directives with angle brackets (like #include <stdio.h>) are | ||
ignored -- only directives with quotes (like #include "foo.h") are | ||
considered. | ||
import sys, os | ||
|
||
To decide what includes are permitted, this script looks at a .may_include | ||
file in each directory. This file contains empty lines, #-prefixed | ||
comments, filenames (like "lib/foo/bar.h") and file globs (like lib/*/*.h) | ||
for files that are permitted. | ||
""" | ||
dirname = os.path.split(sys.argv[0])[0] | ||
new_location = os.path.join(dirname, "practracker", "includes.py") | ||
python = sys.executable | ||
|
||
|
||
from __future__ import print_function | ||
|
||
import fnmatch | ||
import os | ||
import re | ||
import sys | ||
|
||
# Global: Have there been any errors? | ||
trouble = False | ||
|
||
if sys.version_info[0] <= 2: | ||
def open_file(fname): | ||
return open(fname, 'r') | ||
else: | ||
def open_file(fname): | ||
return open(fname, 'r', encoding='utf-8') | ||
|
||
def warn(msg): | ||
print(msg, file=sys.stderr) | ||
|
||
def err(msg): | ||
""" Declare that an error has happened, and remember that there has | ||
been an error. """ | ||
global trouble | ||
trouble = True | ||
print(msg, file=sys.stderr) | ||
|
||
def fname_is_c(fname): | ||
""" Return true iff 'fname' is the name of a file that we should | ||
search for possibly disallowed #include directives. """ | ||
return fname.endswith(".h") or fname.endswith(".c") | ||
|
||
INCLUDE_PATTERN = re.compile(r'\s*#\s*include\s+"([^"]*)"') | ||
RULES_FNAME = ".may_include" | ||
|
||
ALLOWED_PATTERNS = [ | ||
re.compile(r'^.*\*\.(h|inc)$'), | ||
re.compile(r'^.*/.*\.h$'), | ||
re.compile(r'^ext/.*\.c$'), | ||
re.compile(r'^orconfig.h$'), | ||
re.compile(r'^micro-revision.i$'), | ||
] | ||
|
||
def pattern_is_normal(s): | ||
for p in ALLOWED_PATTERNS: | ||
if p.match(s): | ||
return True | ||
return False | ||
|
||
class Rules(object): | ||
""" A 'Rules' object is the parsed version of a .may_include file. """ | ||
def __init__(self, dirpath): | ||
self.dirpath = dirpath | ||
if dirpath.startswith("src/"): | ||
self.incpath = dirpath[4:] | ||
else: | ||
self.incpath = dirpath | ||
self.patterns = [] | ||
self.usedPatterns = set() | ||
|
||
def addPattern(self, pattern): | ||
if not pattern_is_normal(pattern): | ||
warn("Unusual pattern {} in {}".format(pattern, self.dirpath)) | ||
self.patterns.append(pattern) | ||
|
||
def includeOk(self, path): | ||
for pattern in self.patterns: | ||
if fnmatch.fnmatchcase(path, pattern): | ||
self.usedPatterns.add(pattern) | ||
return True | ||
return False | ||
|
||
def applyToLines(self, lines, context=""): | ||
lineno = 0 | ||
for line in lines: | ||
lineno += 1 | ||
m = INCLUDE_PATTERN.match(line) | ||
if m: | ||
include = m.group(1) | ||
if not self.includeOk(include): | ||
err("Forbidden include of {} on line {}{}".format( | ||
include, lineno, context)) | ||
|
||
def applyToFile(self, fname): | ||
with open_file(fname) as f: | ||
#print(fname) | ||
self.applyToLines(iter(f), " of {}".format(fname)) | ||
|
||
def noteUnusedRules(self): | ||
for p in self.patterns: | ||
if p not in self.usedPatterns: | ||
print("Pattern {} in {} was never used.".format(p, self.dirpath)) | ||
|
||
def getAllowedDirectories(self): | ||
allowed = [] | ||
for p in self.patterns: | ||
m = re.match(r'^(.*)/\*\.(h|inc)$', p) | ||
if m: | ||
allowed.append(m.group(1)) | ||
continue | ||
m = re.match(r'^(.*)/[^/]*$', p) | ||
if m: | ||
allowed.append(m.group(1)) | ||
continue | ||
|
||
return allowed | ||
|
||
def load_include_rules(fname): | ||
""" Read a rules file from 'fname', and return it as a Rules object. """ | ||
result = Rules(os.path.split(fname)[0]) | ||
with open_file(fname) as f: | ||
for line in f: | ||
line = line.strip() | ||
if line.startswith("#") or not line: | ||
continue | ||
result.addPattern(line) | ||
return result | ||
|
||
list_unused = False | ||
log_sorted_levels = False | ||
|
||
uses_dirs = { } | ||
|
||
for dirpath, dirnames, fnames in os.walk("src"): | ||
if ".may_include" in fnames: | ||
rules = load_include_rules(os.path.join(dirpath, RULES_FNAME)) | ||
for fname in fnames: | ||
if fname_is_c(fname): | ||
rules.applyToFile(os.path.join(dirpath,fname)) | ||
if list_unused: | ||
rules.noteUnusedRules() | ||
|
||
uses_dirs[rules.incpath] = rules.getAllowedDirectories() | ||
|
||
if trouble: | ||
err( | ||
"""To change which includes are allowed in a C file, edit the {} | ||
files in its enclosing directory.""".format(RULES_FNAME)) | ||
sys.exit(1) | ||
|
||
all_levels = [] | ||
|
||
n = 0 | ||
while uses_dirs: | ||
n += 0 | ||
cur_level = [] | ||
for k in list(uses_dirs): | ||
uses_dirs[k] = [ d for d in uses_dirs[k] | ||
if (d in uses_dirs and d != k)] | ||
if uses_dirs[k] == []: | ||
cur_level.append(k) | ||
for k in cur_level: | ||
del uses_dirs[k] | ||
n += 1 | ||
if cur_level and log_sorted_levels: | ||
print(n, cur_level) | ||
if n > 100: | ||
break | ||
|
||
if uses_dirs: | ||
print("There are circular .may_include dependencies in here somewhere:", | ||
uses_dirs) | ||
sys.exit(1) | ||
os.execl(python, python, new_location, *sys.argv[1:]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.