verify.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. # (C) Copyright 2018, 2020, 2023, 2025 by Rocky Bernstein
  2. #
  3. # This program is free software; you can redistribute it and/or
  4. # modify it under the terms of the GNU General Public License
  5. # as published by the Free Software Foundation; either version 2
  6. # of the License, or (at your option) any later version.
  7. #
  8. # This program is distributed in the hope that it will be useful,
  9. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. # GNU General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU General Public License
  14. # along with this program; if not, write to the Free Software
  15. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  16. import marshal
  17. import os
  18. import tempfile
  19. from types import CodeType
  20. from _io import BufferedWriter
  21. from xdis.load import load_module
  22. from xdis.magics import MAGIC, PYTHON_MAGIC_INT, int2magic
  23. from xdis.version_info import IS_PYPY, PYTHON3, PYTHON_VERSION_TRIPLE
  24. def wr_long(f: BufferedWriter, x) -> None:
  25. """Internal; write a 32-bit int to a file in little-endian order."""
  26. if PYTHON3:
  27. f.write(bytes([x & 0xFF]))
  28. f.write(bytes([(x >> 8) & 0xFF]))
  29. f.write(bytes([(x >> 16) & 0xFF]))
  30. f.write(bytes([(x >> 24) & 0xFF]))
  31. else:
  32. f.write(chr(x & 0xFF))
  33. f.write(chr((x >> 8) & 0xFF))
  34. f.write(chr((x >> 16) & 0xFF))
  35. f.write(chr((x >> 24) & 0xFF))
  36. def dump_compile(codeobject: CodeType, filename: str, timestamp, magic: bytes) -> None:
  37. """Write ``codeobject`` as a byte-compiled file.
  38. Arguments:
  39. codeobject: Code object
  40. filename: ytecode file to write
  41. timestamp: Tme stamp to put in file
  42. magic: Python bytecode magic
  43. """
  44. # Atomically write the pyc/pyo file. Issue #13146.
  45. # id() is used to generate a pseudo-random filename.
  46. path_tmp = "%s.%s" % (filename, id(filename))
  47. fc = None
  48. try:
  49. fc = open(path_tmp, "wb")
  50. if PYTHON3:
  51. fc.write(bytes([0, 0, 0, 0]))
  52. else:
  53. fc.write("\0\0\0\0")
  54. wr_long(fc, timestamp)
  55. marshal.dump(codeobject, fc)
  56. fc.flush()
  57. fc.seek(0, 0)
  58. fc.write(magic)
  59. fc.close()
  60. os.rename(path_tmp, filename)
  61. except OSError:
  62. try:
  63. os.unlink(path_tmp)
  64. except OSError:
  65. pass
  66. raise
  67. finally:
  68. if fc:
  69. fc.close()
  70. def compare_code(c1, c2) -> None:
  71. assert c1.co_code == c2.co_code, "code %s vs. %s" % (c1.co_code, c2.co_code)
  72. assert c1.co_argcount == c2.co_argcount
  73. assert c1.co_consts == c1.co_consts
  74. # assert len(c1.co_consts) == len(c2.co_consts), "consts:\n%s\nvs.\n%s" % (
  75. # c1.co_consts,
  76. # c2.co_consts,
  77. # )
  78. assert c1.co_filename == c2.co_filename
  79. assert c1.co_firstlineno == c2.co_firstlineno
  80. assert c1.co_flags == c2.co_flags
  81. assert c1.co_lnotab == c2.co_lnotab
  82. assert c1.co_name == c2.co_name
  83. assert c1.co_names == c2.co_names
  84. assert c1.co_nlocals == c2.co_nlocals
  85. assert c1.co_stacksize == c2.co_stacksize
  86. assert c1.co_varnames == c2.co_varnames
  87. def compare_bytecode_files(bc_file1: str, bc_file2: str) -> None:
  88. # Now compare bytes in bytecode files
  89. f = open(bc_file1, "rb")
  90. bytes1 = f.read()
  91. f.close()
  92. f = open(bc_file2, "rb")
  93. bytes2 = f.read()
  94. f.close()
  95. if PYTHON_VERSION_TRIPLE[:2] == (3, 2) and IS_PYPY:
  96. assert bytes1[4:] == bytes2[4:], "bytecode:\n%s\nvs\n%s" % (
  97. bytes1[4:],
  98. bytes2[4:],
  99. )
  100. else:
  101. assert bytes1 == bytes2, "bytecode:\n%s\nvs\n%s" % (bytes1, bytes2)
  102. def verify_file(real_source_filename, real_bytecode_filename) -> None:
  103. """Compile *real_source_filename* using
  104. the running Python interpreter. Then
  105. write bytecode out to a new place again using
  106. Python's routines.
  107. Next, load it in using two of our routines.
  108. Compare that the code objects there are equal.
  109. Then write out the bytecode using the same Python
  110. bytecode-writing routine as in step 1.
  111. Finally, compare the bytecode files.
  112. """
  113. tempdir = tempfile.gettempdir()
  114. source_filename = os.path.join(tempdir, "testing.py")
  115. if not os.path.exists(real_source_filename):
  116. return
  117. if PYTHON_VERSION_TRIPLE < (3, 0):
  118. f = open(real_source_filename, "U")
  119. elif PYTHON_VERSION_TRIPLE[:2] == (3, 0):
  120. # Too hard to get working on 3.0
  121. return
  122. elif (3, 1) <= PYTHON_VERSION_TRIPLE <= (3, 5):
  123. f = open(real_source_filename, "rb")
  124. else:
  125. f = open(real_source_filename, newline=None, errors="backslashreplace")
  126. codestring = f.read()
  127. f.close()
  128. codeobject1 = compile(codestring, source_filename, "exec")
  129. (
  130. version,
  131. timestamp,
  132. magic_int,
  133. codeobject2,
  134. is_pypy,
  135. source_size,
  136. _,
  137. ) = load_module(real_bytecode_filename)
  138. # A hack for PyPy 3.2
  139. if magic_int == 3180 + 7:
  140. magic_int = 48
  141. assert MAGIC == int2magic(magic_int), "magic_int %d vs %d in %s/%s" % (
  142. magic_int,
  143. PYTHON_MAGIC_INT,
  144. os.getcwd(),
  145. real_bytecode_filename,
  146. )
  147. bytecode_filename1 = os.path.join(tempdir, "testing1.pyc")
  148. dump_compile(codeobject1, bytecode_filename1, timestamp, MAGIC)
  149. (version, timestamp, magic_int, codeobject3, is_pypy, source_size, _) = load_module(
  150. real_bytecode_filename, fast_load=not is_pypy
  151. )
  152. # compare_code(codeobject1, codeobject2)
  153. # compare_code(codeobject2, codeobject3)
  154. bytecode_filename2 = os.path.join(tempdir, "testing2.pyc")
  155. dump_compile(codeobject1, bytecode_filename2, timestamp, int2magic(magic_int))
  156. compare_bytecode_files(bytecode_filename1, bytecode_filename2)
  157. return
  158. if __name__ == "__main__":
  159. import importlib
  160. pyc_path = importlib.util.cache_from_source(__file__)
  161. print("Verifying", pyc_path)
  162. verify_file(__file__, pyc_path)