mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 02:15:10 +00:00 
			
		
		
		
	 4338aeeb9e
			
		
	
	
		4338aeeb9e
		
			
		
	
	
	
	
		
			
			Co-authored-by: Serhiy Storchaka <storchaka@gmail.com> Co-authored-by: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com>
		
			
				
	
	
		
			148 lines
		
	
	
	
		
			4.6 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			148 lines
		
	
	
	
		
			4.6 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| """
 | |
| pep384_macrocheck.py
 | |
| 
 | |
| This program tries to locate errors in the relevant Python header
 | |
| files where macros access type fields when they are reachable from
 | |
| the limited API.
 | |
| 
 | |
| The idea is to search macros with the string "->tp_" in it.
 | |
| When the macro name does not begin with an underscore,
 | |
| then we have found a dormant error.
 | |
| 
 | |
| Christian Tismer
 | |
| 2018-06-02
 | |
| """
 | |
| 
 | |
| import sys
 | |
| import os
 | |
| import re
 | |
| 
 | |
| 
 | |
| DEBUG = False
 | |
| 
 | |
| def dprint(*args, **kw):
 | |
|     if DEBUG:
 | |
|         print(*args, **kw)
 | |
| 
 | |
| def parse_headerfiles(startpath):
 | |
|     """
 | |
|     Scan all header files which are reachable fronm Python.h
 | |
|     """
 | |
|     search = "Python.h"
 | |
|     name = os.path.join(startpath, search)
 | |
|     if not os.path.exists(name):
 | |
|         raise ValueError("file {} was not found in {}\n"
 | |
|             "Please give the path to Python's include directory."
 | |
|             .format(search, startpath))
 | |
|     errors = 0
 | |
|     with open(name) as python_h:
 | |
|         while True:
 | |
|             line = python_h.readline()
 | |
|             if not line:
 | |
|                 break
 | |
|             found = re.match(r'^\s*#\s*include\s*"(\w+\.h)"', line)
 | |
|             if not found:
 | |
|                 continue
 | |
|             include = found.group(1)
 | |
|             dprint("Scanning", include)
 | |
|             name = os.path.join(startpath, include)
 | |
|             if not os.path.exists(name):
 | |
|                 name = os.path.join(startpath, "../PC", include)
 | |
|             errors += parse_file(name)
 | |
|     return errors
 | |
| 
 | |
| def ifdef_level_gen():
 | |
|     """
 | |
|     Scan lines for #ifdef and track the level.
 | |
|     """
 | |
|     level = 0
 | |
|     ifdef_pattern = r"^\s*#\s*if"  # covers ifdef and ifndef as well
 | |
|     endif_pattern = r"^\s*#\s*endif"
 | |
|     while True:
 | |
|         line = yield level
 | |
|         if re.match(ifdef_pattern, line):
 | |
|             level += 1
 | |
|         elif re.match(endif_pattern, line):
 | |
|             level -= 1
 | |
| 
 | |
| def limited_gen():
 | |
|     """
 | |
|     Scan lines for Py_LIMITED_API yes(1) no(-1) or nothing (0)
 | |
|     """
 | |
|     limited = [0]   # nothing
 | |
|     unlimited_pattern = r"^\s*#\s*ifndef\s+Py_LIMITED_API"
 | |
|     limited_pattern = "|".join([
 | |
|         r"^\s*#\s*ifdef\s+Py_LIMITED_API",
 | |
|         r"^\s*#\s*(el)?if\s+!\s*defined\s*\(\s*Py_LIMITED_API\s*\)\s*\|\|",
 | |
|         r"^\s*#\s*(el)?if\s+defined\s*\(\s*Py_LIMITED_API"
 | |
|         ])
 | |
|     else_pattern =      r"^\s*#\s*else"
 | |
|     ifdef_level = ifdef_level_gen()
 | |
|     status = next(ifdef_level)
 | |
|     wait_for = -1
 | |
|     while True:
 | |
|         line = yield limited[-1]
 | |
|         new_status = ifdef_level.send(line)
 | |
|         dir = new_status - status
 | |
|         status = new_status
 | |
|         if dir == 1:
 | |
|             if re.match(unlimited_pattern, line):
 | |
|                 limited.append(-1)
 | |
|                 wait_for = status - 1
 | |
|             elif re.match(limited_pattern, line):
 | |
|                 limited.append(1)
 | |
|                 wait_for = status - 1
 | |
|         elif dir == -1:
 | |
|             # this must have been an endif
 | |
|             if status == wait_for:
 | |
|                 limited.pop()
 | |
|                 wait_for = -1
 | |
|         else:
 | |
|             # it could be that we have an elif
 | |
|             if re.match(limited_pattern, line):
 | |
|                 limited.append(1)
 | |
|                 wait_for = status - 1
 | |
|             elif re.match(else_pattern, line):
 | |
|                 limited.append(-limited.pop())  # negate top
 | |
| 
 | |
| def parse_file(fname):
 | |
|     errors = 0
 | |
|     with open(fname) as f:
 | |
|         lines = f.readlines()
 | |
|     type_pattern = r"^.*?->\s*tp_"
 | |
|     define_pattern = r"^\s*#\s*define\s+(\w+)"
 | |
|     limited = limited_gen()
 | |
|     status = next(limited)
 | |
|     for nr, line in enumerate(lines):
 | |
|         status = limited.send(line)
 | |
|         line = line.rstrip()
 | |
|         dprint(fname, nr, status, line)
 | |
|         if status != -1:
 | |
|             if re.match(define_pattern, line):
 | |
|                 name = re.match(define_pattern, line).group(1)
 | |
|                 if not name.startswith("_"):
 | |
|                     # found a candidate, check it!
 | |
|                     macro = line + "\n"
 | |
|                     idx = nr
 | |
|                     while line.endswith("\\"):
 | |
|                         idx += 1
 | |
|                         line = lines[idx].rstrip()
 | |
|                         macro += line + "\n"
 | |
|                     if re.match(type_pattern, macro, re.DOTALL):
 | |
|                         # this type field can reach the limited API
 | |
|                         report(fname, nr + 1, macro)
 | |
|                         errors += 1
 | |
|     return errors
 | |
| 
 | |
| def report(fname, nr, macro):
 | |
|     f = sys.stderr
 | |
|     print(fname + ":" + str(nr), file=f)
 | |
|     print(macro, file=f)
 | |
| 
 | |
| if __name__ == "__main__":
 | |
|     p = sys.argv[1] if sys.argv[1:] else "../../Include"
 | |
|     errors = parse_headerfiles(p)
 | |
|     if errors:
 | |
|         # somehow it makes sense to raise a TypeError :-)
 | |
|         raise TypeError("These {} locations contradict the limited API."
 | |
|                         .format(errors))
 |