|  | # Copyright 2017 The Chromium Authors. All rights reserved. | 
|  | # Use of this source code is governed by a BSD-style license that can be | 
|  | # found in the LICENSE file. | 
|  |  | 
|  | import array | 
|  | import difflib | 
|  | import distutils.dir_util | 
|  | import filecmp | 
|  | import operator | 
|  | import os | 
|  | import re | 
|  | import shutil | 
|  | import struct | 
|  | import subprocess | 
|  | import sys | 
|  | import tempfile | 
|  | import uuid | 
|  |  | 
|  |  | 
|  | def ZapTimestamp(filename): | 
|  | contents = open(filename, 'rb').read() | 
|  | # midl.exe writes timestamp 2147483647 (2^31 - 1) as creation date into its | 
|  | # outputs, but using the local timezone.  To make the output timezone- | 
|  | # independent, replace that date with a fixed string of the same length. | 
|  | # Also blank out the minor version number. | 
|  | if filename.endswith('.tlb'): | 
|  | # See https://chromium-review.googlesource.com/c/chromium/src/+/693223 for | 
|  | # a fairly complete description of the .tlb binary format. | 
|  | # TLB files start with a 54 byte header. Offset 0x20 stores how many types | 
|  | # are defined in the file, and the header is followed by that many uint32s. | 
|  | # After that, 15 section headers appear.  Each section header is 16 bytes, | 
|  | # starting with offset and length uint32s. | 
|  | # Section 12 in the file contains custom() data. custom() data has a type | 
|  | # (int, string, etc).  Each custom data chunk starts with a uint16_t | 
|  | # describing its type.  Type 8 is string data, consisting of a uint32_t | 
|  | # len, followed by that many data bytes, followed by 'W' bytes to pad to a | 
|  | # 4 byte boundary.  Type 0x13 is uint32 data, followed by 4 data bytes, | 
|  | # followed by two 'W' to pad to a 4 byte boundary. | 
|  | # The custom block always starts with one string containing "Created by | 
|  | # MIDL version 8...", followed by one uint32 containing 0x7fffffff, | 
|  | # followed by another uint32 containing the MIDL compiler version (e.g. | 
|  | # 0x0801026e for v8.1.622 -- 0x26e == 622).  These 3 fields take 0x54 bytes. | 
|  | # There might be more custom data after that, but these 3 blocks are always | 
|  | # there for file-level metadata. | 
|  | # All data is little-endian in the file. | 
|  | assert contents[0:8] == 'MSFT\x02\x00\x01\x00' | 
|  | ntypes, = struct.unpack_from('<I', contents, 0x20) | 
|  | custom_off, custom_len = struct.unpack_from( | 
|  | '<II', contents, 0x54 + 4*ntypes + 11*16) | 
|  | assert custom_len >= 0x54 | 
|  | # First: Type string (0x8), followed by 0x3e characters. | 
|  | assert contents[custom_off:custom_off+6] == '\x08\x00\x3e\x00\x00\x00' | 
|  | assert re.match( | 
|  | 'Created by MIDL version 8\.\d\d\.\d{4} at ... Jan 1. ..:..:.. 2038\n', | 
|  | contents[custom_off+6:custom_off+6+0x3e]) | 
|  | # Second: Type uint32 (0x13) storing 0x7fffffff (followed by WW / 0x57 pad) | 
|  | assert contents[custom_off+6+0x3e:custom_off+6+0x3e+8] == \ | 
|  | '\x13\x00\xff\xff\xff\x7f\x57\x57' | 
|  | # Third: Type uint32 (0x13) storing MIDL compiler version. | 
|  | assert contents[custom_off+6+0x3e+8:custom_off+6+0x3e+8+2] == '\x13\x00' | 
|  | # Replace "Created by" string with fixed string, and fixed MIDL version with | 
|  | # 8.1.622 always. | 
|  | contents = (contents[0:custom_off+6] + | 
|  | 'Created by MIDL version 8.xx.xxxx at a redacted point in time\n' + | 
|  | # uint32 (0x13) val 0x7fffffff, WW, uint32 (0x13), val 0x0801026e, WW | 
|  | '\x13\x00\xff\xff\xff\x7f\x57\x57\x13\x00\x6e\x02\x01\x08\x57\x57' + | 
|  | contents[custom_off + 0x54:]) | 
|  | else: | 
|  | contents = re.sub( | 
|  | 'File created by MIDL compiler version 8\.\d\d\.\d{4} \*/\r\n' | 
|  | '/\* at ... Jan 1. ..:..:.. 2038', | 
|  | 'File created by MIDL compiler version 8.xx.xxxx */\r\n' | 
|  | '/* at a redacted point in time', | 
|  | contents) | 
|  | contents = re.sub( | 
|  | '    Oicf, W1, Zp8, env=(.....) \(32b run\), ' | 
|  | 'target_arch=(AMD64|X86) 8\.\d\d\.\d{4}', | 
|  | '    Oicf, W1, Zp8, env=\\1 (32b run), target_arch=\\2 8.xx.xxxx', | 
|  | contents) | 
|  | # TODO(thakis): If we need more hacks than these, try to verify checked-in | 
|  | # outputs when we're using the hermetic toolchain. | 
|  | # midl.exe older than 8.1.622 omit '//' after #endif, fix that: | 
|  | contents = contents.replace('#endif !_MIDL_USE_GUIDDEF_', | 
|  | '#endif // !_MIDL_USE_GUIDDEF_') | 
|  | # midl.exe puts the midl version into code in one place.  To have | 
|  | # predictable output, lie about the midl version if it's not 8.1.622. | 
|  | # This is unfortunate, but remember that there's beauty too in imperfection. | 
|  | contents = contents.replace('0x801026c, /* MIDL Version 8.1.620 */', | 
|  | '0x801026e, /* MIDL Version 8.1.622 */') | 
|  | open(filename, 'wb').write(contents) | 
|  |  | 
|  |  | 
|  | def overwrite_cls_guid_h(h_file, dynamic_guid): | 
|  | contents = open(h_file, 'rb').read() | 
|  | contents = re.sub('class DECLSPEC_UUID\("[^"]*"\)', | 
|  | 'class DECLSPEC_UUID("%s")' % str(dynamic_guid), contents) | 
|  | open(h_file, 'wb').write(contents) | 
|  |  | 
|  |  | 
|  | def overwrite_cls_guid_iid(iid_file, dynamic_guid): | 
|  | contents = open(iid_file, 'rb').read() | 
|  | hexuuid = '0x%08x,0x%04x,0x%04x,' % dynamic_guid.fields[0:3] | 
|  | hexuuid += ','.join('0x%02x' % ord(b) for b in dynamic_guid.bytes[8:]) | 
|  | contents = re.sub(r'MIDL_DEFINE_GUID\(CLSID, ([^,]*),[^)]*\)', | 
|  | r'MIDL_DEFINE_GUID(CLSID, \1,%s)' % hexuuid, contents) | 
|  | open(iid_file, 'wb').write(contents) | 
|  |  | 
|  |  | 
|  | def overwrite_cls_guid_tlb(tlb_file, dynamic_guid): | 
|  | # See ZapTimestamp() for a short overview of the .tlb format.  The 1st | 
|  | # section contains type descriptions, and the first type should be our | 
|  | # coclass.  It points to the type's GUID in section 6, the GUID section. | 
|  | contents = open(tlb_file, 'rb').read() | 
|  | assert contents[0:8] == 'MSFT\x02\x00\x01\x00' | 
|  | ntypes, = struct.unpack_from('<I', contents, 0x20) | 
|  | type_off, type_len = struct.unpack_from('<II', contents, 0x54 + 4*ntypes) | 
|  | assert ord(contents[type_off]) == 0x25, "expected coclass" | 
|  | guidind = struct.unpack_from('<I', contents, type_off + 0x2c)[0] | 
|  | guid_off, guid_len = struct.unpack_from( | 
|  | '<II', contents, 0x54 + 4*ntypes + 5*16) | 
|  | assert guidind + 14 <= guid_len | 
|  | contents = array.array('c', contents) | 
|  | struct.pack_into('<IHH8s', contents, guid_off + guidind, | 
|  | *(dynamic_guid.fields[0:3] + (dynamic_guid.bytes[8:],))) | 
|  | # The GUID is correct now, but there's also a GUID hashtable in section 5. | 
|  | # Need to recreate that too.  Since the hash table uses chaining, it's | 
|  | # easiest to recompute it from scratch rather than trying to patch it up. | 
|  | hashtab = [0xffffffff] * (0x80 / 4) | 
|  | for guidind in range(guid_off, guid_off + guid_len, 24): | 
|  | guidbytes, typeoff, nextguid = struct.unpack_from( | 
|  | '<16sII', contents, guidind) | 
|  | words = struct.unpack('<8H', guidbytes) | 
|  | # midl seems to use the following simple hash function for GUIDs: | 
|  | guidhash = reduce(operator.xor, [w for w in words]) % (0x80 / 4) | 
|  | nextguid = hashtab[guidhash] | 
|  | struct.pack_into('<I', contents, guidind + 0x14, nextguid) | 
|  | hashtab[guidhash] = guidind - guid_off | 
|  | hash_off, hash_len = struct.unpack_from( | 
|  | '<II', contents, 0x54 + 4*ntypes + 4*16) | 
|  | for i, hashval in enumerate(hashtab): | 
|  | struct.pack_into('<I', contents, hash_off + 4*i, hashval) | 
|  | open(tlb_file, 'wb').write(contents) | 
|  |  | 
|  |  | 
|  | def overwrite_cls_guid(h_file, iid_file, tlb_file, dynamic_guid): | 
|  | # Fix up GUID in .h, _i.c, and .tlb.  This currently assumes that there's | 
|  | # only one coclass in the idl file, and that that's the type with the | 
|  | # dynamic type. | 
|  | overwrite_cls_guid_h(h_file, dynamic_guid) | 
|  | overwrite_cls_guid_iid(iid_file, dynamic_guid) | 
|  | overwrite_cls_guid_tlb(tlb_file, dynamic_guid) | 
|  |  | 
|  |  | 
|  | def main(arch, outdir, dynamic_guid, tlb, h, dlldata, iid, proxy, idl, *flags): | 
|  | # Copy checked-in outputs to final location. | 
|  | THIS_DIR = os.path.abspath(os.path.dirname(__file__)) | 
|  | source = os.path.join(THIS_DIR, '..', '..', '..', | 
|  | 'third_party', 'win_build_output', outdir.replace('gen/', 'midl/')) | 
|  | if os.path.isdir(os.path.join(source, os.path.basename(idl))): | 
|  | source = os.path.join(source, os.path.basename(idl)) | 
|  | source = os.path.join(source, arch.split('.')[1])  # Append 'x86' or 'x64'. | 
|  | source = os.path.normpath(source) | 
|  | distutils.dir_util.copy_tree(source, outdir, preserve_times=False) | 
|  | if dynamic_guid != 'none': | 
|  | overwrite_cls_guid(os.path.join(outdir, h), | 
|  | os.path.join(outdir, iid), | 
|  | os.path.join(outdir, tlb), | 
|  | uuid.UUID(dynamic_guid)) | 
|  |  | 
|  | # On non-Windows, that's all we can do. | 
|  | if sys.platform != 'win32': | 
|  | return 0 | 
|  |  | 
|  | # On Windows, run midl.exe on the input and check that its outputs are | 
|  | # identical to the checked-in outputs (after possibly replacing their main | 
|  | # class guid). | 
|  | tmp_dir = tempfile.mkdtemp() | 
|  | delete_tmp_dir = True | 
|  |  | 
|  | # Read the environment block from the file. This is stored in the format used | 
|  | # by CreateProcess. Drop last 2 NULs, one for list terminator, one for | 
|  | # trailing vs. separator. | 
|  | env_pairs = open(arch).read()[:-2].split('\0') | 
|  | env_dict = dict([item.split('=', 1) for item in env_pairs]) | 
|  |  | 
|  | args = ['midl', '/nologo'] + list(flags) + [ | 
|  | '/out', tmp_dir, | 
|  | '/tlb', tlb, | 
|  | '/h', h, | 
|  | '/dlldata', dlldata, | 
|  | '/iid', iid, | 
|  | '/proxy', proxy, | 
|  | idl] | 
|  | try: | 
|  | popen = subprocess.Popen(args, shell=True, env=env_dict, | 
|  | stdout=subprocess.PIPE, stderr=subprocess.STDOUT) | 
|  | out, _ = popen.communicate() | 
|  | # Filter junk out of stdout, and write filtered versions. Output we want | 
|  | # to filter is pairs of lines that look like this: | 
|  | # Processing C:\Program Files (x86)\Microsoft SDKs\...\include\objidl.idl | 
|  | # objidl.idl | 
|  | lines = out.splitlines() | 
|  | prefixes = ('Processing ', '64 bit Processing ') | 
|  | processing = set(os.path.basename(x) | 
|  | for x in lines if x.startswith(prefixes)) | 
|  | for line in lines: | 
|  | if not line.startswith(prefixes) and line not in processing: | 
|  | print line | 
|  | if popen.returncode != 0: | 
|  | return popen.returncode | 
|  |  | 
|  | for f in os.listdir(tmp_dir): | 
|  | ZapTimestamp(os.path.join(tmp_dir, f)) | 
|  |  | 
|  | # Now compare the output in tmp_dir to the copied-over outputs. | 
|  | diff = filecmp.dircmp(tmp_dir, outdir) | 
|  | if diff.diff_files: | 
|  | print 'midl.exe output different from files in %s, see %s' \ | 
|  | % (outdir, tmp_dir) | 
|  | for f in diff.diff_files: | 
|  | if f.endswith('.tlb'): continue | 
|  | fromfile = os.path.join(outdir, f) | 
|  | tofile = os.path.join(tmp_dir, f) | 
|  | print ''.join(difflib.unified_diff(open(fromfile, 'U').readlines(), | 
|  | open(tofile, 'U').readlines(), | 
|  | fromfile, tofile)) | 
|  | delete_tmp_dir = False | 
|  | print 'To rebaseline:' | 
|  | print '  copy /y %s\* %s' % (tmp_dir, source) | 
|  | sys.exit(1) | 
|  | return 0 | 
|  | finally: | 
|  | if os.path.exists(tmp_dir) and delete_tmp_dir: | 
|  | shutil.rmtree(tmp_dir) | 
|  |  | 
|  |  | 
|  | if __name__ == '__main__': | 
|  | sys.exit(main(*sys.argv[1:])) |