17db96d56Sopenharmony_ci""" 27db96d56Sopenharmony_cipep384_macrocheck.py 37db96d56Sopenharmony_ci 47db96d56Sopenharmony_ciThis program tries to locate errors in the relevant Python header 57db96d56Sopenharmony_cifiles where macros access type fields when they are reachable from 67db96d56Sopenharmony_cithe limited API. 77db96d56Sopenharmony_ci 87db96d56Sopenharmony_ciThe idea is to search macros with the string "->tp_" in it. 97db96d56Sopenharmony_ciWhen the macro name does not begin with an underscore, 107db96d56Sopenharmony_cithen we have found a dormant error. 117db96d56Sopenharmony_ci 127db96d56Sopenharmony_ciChristian Tismer 137db96d56Sopenharmony_ci2018-06-02 147db96d56Sopenharmony_ci""" 157db96d56Sopenharmony_ci 167db96d56Sopenharmony_ciimport sys 177db96d56Sopenharmony_ciimport os 187db96d56Sopenharmony_ciimport re 197db96d56Sopenharmony_ci 207db96d56Sopenharmony_ci 217db96d56Sopenharmony_ciDEBUG = False 227db96d56Sopenharmony_ci 237db96d56Sopenharmony_cidef dprint(*args, **kw): 247db96d56Sopenharmony_ci if DEBUG: 257db96d56Sopenharmony_ci print(*args, **kw) 267db96d56Sopenharmony_ci 277db96d56Sopenharmony_cidef parse_headerfiles(startpath): 287db96d56Sopenharmony_ci """ 297db96d56Sopenharmony_ci Scan all header files which are reachable fronm Python.h 307db96d56Sopenharmony_ci """ 317db96d56Sopenharmony_ci search = "Python.h" 327db96d56Sopenharmony_ci name = os.path.join(startpath, search) 337db96d56Sopenharmony_ci if not os.path.exists(name): 347db96d56Sopenharmony_ci raise ValueError("file {} was not found in {}\n" 357db96d56Sopenharmony_ci "Please give the path to Python's include directory." 367db96d56Sopenharmony_ci .format(search, startpath)) 377db96d56Sopenharmony_ci errors = 0 387db96d56Sopenharmony_ci with open(name) as python_h: 397db96d56Sopenharmony_ci while True: 407db96d56Sopenharmony_ci line = python_h.readline() 417db96d56Sopenharmony_ci if not line: 427db96d56Sopenharmony_ci break 437db96d56Sopenharmony_ci found = re.match(r'^\s*#\s*include\s*"(\w+\.h)"', line) 447db96d56Sopenharmony_ci if not found: 457db96d56Sopenharmony_ci continue 467db96d56Sopenharmony_ci include = found.group(1) 477db96d56Sopenharmony_ci dprint("Scanning", include) 487db96d56Sopenharmony_ci name = os.path.join(startpath, include) 497db96d56Sopenharmony_ci if not os.path.exists(name): 507db96d56Sopenharmony_ci name = os.path.join(startpath, "../PC", include) 517db96d56Sopenharmony_ci errors += parse_file(name) 527db96d56Sopenharmony_ci return errors 537db96d56Sopenharmony_ci 547db96d56Sopenharmony_cidef ifdef_level_gen(): 557db96d56Sopenharmony_ci """ 567db96d56Sopenharmony_ci Scan lines for #ifdef and track the level. 577db96d56Sopenharmony_ci """ 587db96d56Sopenharmony_ci level = 0 597db96d56Sopenharmony_ci ifdef_pattern = r"^\s*#\s*if" # covers ifdef and ifndef as well 607db96d56Sopenharmony_ci endif_pattern = r"^\s*#\s*endif" 617db96d56Sopenharmony_ci while True: 627db96d56Sopenharmony_ci line = yield level 637db96d56Sopenharmony_ci if re.match(ifdef_pattern, line): 647db96d56Sopenharmony_ci level += 1 657db96d56Sopenharmony_ci elif re.match(endif_pattern, line): 667db96d56Sopenharmony_ci level -= 1 677db96d56Sopenharmony_ci 687db96d56Sopenharmony_cidef limited_gen(): 697db96d56Sopenharmony_ci """ 707db96d56Sopenharmony_ci Scan lines for Py_LIMITED_API yes(1) no(-1) or nothing (0) 717db96d56Sopenharmony_ci """ 727db96d56Sopenharmony_ci limited = [0] # nothing 737db96d56Sopenharmony_ci unlimited_pattern = r"^\s*#\s*ifndef\s+Py_LIMITED_API" 747db96d56Sopenharmony_ci limited_pattern = "|".join([ 757db96d56Sopenharmony_ci r"^\s*#\s*ifdef\s+Py_LIMITED_API", 767db96d56Sopenharmony_ci r"^\s*#\s*(el)?if\s+!\s*defined\s*\(\s*Py_LIMITED_API\s*\)\s*\|\|", 777db96d56Sopenharmony_ci r"^\s*#\s*(el)?if\s+defined\s*\(\s*Py_LIMITED_API" 787db96d56Sopenharmony_ci ]) 797db96d56Sopenharmony_ci else_pattern = r"^\s*#\s*else" 807db96d56Sopenharmony_ci ifdef_level = ifdef_level_gen() 817db96d56Sopenharmony_ci status = next(ifdef_level) 827db96d56Sopenharmony_ci wait_for = -1 837db96d56Sopenharmony_ci while True: 847db96d56Sopenharmony_ci line = yield limited[-1] 857db96d56Sopenharmony_ci new_status = ifdef_level.send(line) 867db96d56Sopenharmony_ci dir = new_status - status 877db96d56Sopenharmony_ci status = new_status 887db96d56Sopenharmony_ci if dir == 1: 897db96d56Sopenharmony_ci if re.match(unlimited_pattern, line): 907db96d56Sopenharmony_ci limited.append(-1) 917db96d56Sopenharmony_ci wait_for = status - 1 927db96d56Sopenharmony_ci elif re.match(limited_pattern, line): 937db96d56Sopenharmony_ci limited.append(1) 947db96d56Sopenharmony_ci wait_for = status - 1 957db96d56Sopenharmony_ci elif dir == -1: 967db96d56Sopenharmony_ci # this must have been an endif 977db96d56Sopenharmony_ci if status == wait_for: 987db96d56Sopenharmony_ci limited.pop() 997db96d56Sopenharmony_ci wait_for = -1 1007db96d56Sopenharmony_ci else: 1017db96d56Sopenharmony_ci # it could be that we have an elif 1027db96d56Sopenharmony_ci if re.match(limited_pattern, line): 1037db96d56Sopenharmony_ci limited.append(1) 1047db96d56Sopenharmony_ci wait_for = status - 1 1057db96d56Sopenharmony_ci elif re.match(else_pattern, line): 1067db96d56Sopenharmony_ci limited.append(-limited.pop()) # negate top 1077db96d56Sopenharmony_ci 1087db96d56Sopenharmony_cidef parse_file(fname): 1097db96d56Sopenharmony_ci errors = 0 1107db96d56Sopenharmony_ci with open(fname) as f: 1117db96d56Sopenharmony_ci lines = f.readlines() 1127db96d56Sopenharmony_ci type_pattern = r"^.*?->\s*tp_" 1137db96d56Sopenharmony_ci define_pattern = r"^\s*#\s*define\s+(\w+)" 1147db96d56Sopenharmony_ci limited = limited_gen() 1157db96d56Sopenharmony_ci status = next(limited) 1167db96d56Sopenharmony_ci for nr, line in enumerate(lines): 1177db96d56Sopenharmony_ci status = limited.send(line) 1187db96d56Sopenharmony_ci line = line.rstrip() 1197db96d56Sopenharmony_ci dprint(fname, nr, status, line) 1207db96d56Sopenharmony_ci if status != -1: 1217db96d56Sopenharmony_ci if re.match(define_pattern, line): 1227db96d56Sopenharmony_ci name = re.match(define_pattern, line).group(1) 1237db96d56Sopenharmony_ci if not name.startswith("_"): 1247db96d56Sopenharmony_ci # found a candidate, check it! 1257db96d56Sopenharmony_ci macro = line + "\n" 1267db96d56Sopenharmony_ci idx = nr 1277db96d56Sopenharmony_ci while line.endswith("\\"): 1287db96d56Sopenharmony_ci idx += 1 1297db96d56Sopenharmony_ci line = lines[idx].rstrip() 1307db96d56Sopenharmony_ci macro += line + "\n" 1317db96d56Sopenharmony_ci if re.match(type_pattern, macro, re.DOTALL): 1327db96d56Sopenharmony_ci # this type field can reach the limited API 1337db96d56Sopenharmony_ci report(fname, nr + 1, macro) 1347db96d56Sopenharmony_ci errors += 1 1357db96d56Sopenharmony_ci return errors 1367db96d56Sopenharmony_ci 1377db96d56Sopenharmony_cidef report(fname, nr, macro): 1387db96d56Sopenharmony_ci f = sys.stderr 1397db96d56Sopenharmony_ci print(fname + ":" + str(nr), file=f) 1407db96d56Sopenharmony_ci print(macro, file=f) 1417db96d56Sopenharmony_ci 1427db96d56Sopenharmony_ciif __name__ == "__main__": 1437db96d56Sopenharmony_ci p = sys.argv[1] if sys.argv[1:] else "../../Include" 1447db96d56Sopenharmony_ci errors = parse_headerfiles(p) 1457db96d56Sopenharmony_ci if errors: 1467db96d56Sopenharmony_ci # somehow it makes sense to raise a TypeError :-) 1477db96d56Sopenharmony_ci raise TypeError("These {} locations contradict the limited API." 1487db96d56Sopenharmony_ci .format(errors)) 149