diff --git a/setup.py b/setup.py index 11e626b..181f8c4 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( name='universalmutator', - version='1.1.8', + version='1.1.9', description='Universal regexp-based mutation tool', long_description_content_type="text/markdown", long_description=open('README.md').read(), diff --git a/universalmutator/python_handler.py b/universalmutator/python_handler.py index 49450fe..dd3a270 100644 --- a/universalmutator/python_handler.py +++ b/universalmutator/python_handler.py @@ -2,6 +2,7 @@ import os import sys import py_compile +import uuid def buildCode(c): @@ -28,19 +29,33 @@ def buildCode(c): def getPythonCode(fname): - # Courtesy of Ned Batchelder, just get the code object from the .pyc file + # from https://stackoverflow.com/questions/32562163/how-can-i-understand-a-pyc-file-content + header_sizes = [ + # (size, first version this applies to) + # pyc files were introduced in 0.9.2 way, way back in June 1991. + (8, (0, 9, 2)), # 2 bytes magic number, \r\n, 4 bytes UNIX timestamp + (12, (3, 6)), # added 4 bytes file size + # bytes 4-8 are flags, meaning of 9-16 depends on what flags are set + # bit 0 not set: 9-12 timestamp, 13-16 file size + # bit 0 set: 9-16 file hash (SipHash-2-4, k0 = 4 bytes of the file, k1 = 0) + (16, (3, 7)), # inserted 4 bytes bit flag field at 4-8 + # future version may add more bytes still, at which point we can extend + # this table. It is correct for Python versions up to 3.9 + ] + header_size = next(s for s, v in reversed(header_sizes) if sys.version_info >= v) + with open(fname, "rb") as f: - f.read(4) - f.read(4) - if sys.version_info >= (3, 3): - f.read(4) + metadata = f.read(header_size) # first header_size bytes are metadata try: - code = marshal.load(f) - except (EOFError, TypeError, ValueError): - with open(fname, "rb") as fRetry: - fRetry.read(16) - code = marshal.load(fRetry) - return buildCode(code) + code = marshal.load(f) # rest is a marshalled code object + except: + print("WARNING: UNABLE TO MARSHAL CODE FROM PYC FILE!") + return(uuid.uuid4()) + if ("code" not in str(type(code))): + print("WARNING: INVALID CODE OBJECT READ FROM PYC FILE!") + return(uuid.uuid4()) + b = buildCode(code) + return b def handler(tmpMutantName, mutant, sourceFile, uniqueMutants): diff --git a/universalmutator/static/python.rules b/universalmutator/static/python.rules index bb2bf56..d4cce05 100644 --- a/universalmutator/static/python.rules +++ b/universalmutator/static/python.rules @@ -39,3 +39,5 @@ True ==> False '.+' ==> '' @.* ==> + +# ==> SKIP_MUTATING_REST \ No newline at end of file