#!/usr/bin/env python

# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

# run Microsoft's Binscope tool (https://www.microsoft.com/en-us/download/details.aspx?id=44995)
# against a fresh Windows build. output a 'binscope.log' file with full details
# of the run and appropriate strings to integrate with the buildbots

# the symbol dir should point to the symbol dir hierarchy created
# via running make buildsymbols in a windows build's objdir

import sys
import subprocess
import os

BINSCOPE_OUTPUT_LOGFILE = r".\binscope_xml_output.log"

# usage
if len(sys.argv) < 3:
    print("""usage : autobinscope.by path_to_binary path_to_symbols [log_file_path]"
    log_file_path is optional, log will be written to .\binscope_xml_output.log by default""")
    sys.exit(0)

binary_path = sys.argv[1]
symbol_path = sys.argv[2]

if len(sys.argv) == 4:
    log_file_path = sys.argv[3]
else:
    log_file_path = BINSCOPE_OUTPUT_LOGFILE

# execute binscope against the binary, using the BINSCOPE environment
# variable as the path to binscope.exe
try:
    binscope_path = os.environ['BINSCOPE']
except KeyError:
    print("TEST-UNEXPECTED-FAIL | autobinscope.py | BINSCOPE environment variable is not set, "
          "can't check DEP/ASLR etc. status.")
    sys.exit(0)

try:
    proc = subprocess.Popen([
        binscope_path,
        "/NoLogo",
        "/Target", binary_path,
        "/SymPath", symbol_path,
        "/Checks", "ATLVersionCheck",
        "/Checks", "ATLVulnCheck",
        # We do not ship in the Windows Store
        "/SkippedChecks", "AppContainerCheck",
        # The CompilerVersionCheck doesn't like clang-cl (we would need to set MinimumCompilerVersion)  # NOQA: E501
        # But we check the compiler in our build system anyway, so this doesn't seem useful
        "/SkippedChecks", "CompilerVersionCheck",
        "/Checks", "DBCheck",
        "/Checks", "DefaultGSCookieCheck",
        "/Checks", "ExecutableImportsCheck",
        # FunctonPointersCheck is disabled per bug 1014002
        "/SkippedChecks", "FunctionPointersCheck",
        # GSCheck doesn't know how to deal with Rust libs
        "/SkippedChecks", "GSCheck",
        "/Checks", "GSFriendlyInitCheck",
        # We are not safebuffers-clean, bug 1449951
        "/SkippedChecks", "GSFunctionSafeBuffersCheck",
        "/Checks", "HighEntropyVACheck",
        "/Checks", "NXCheck",
        "/Checks", "RSA32Check",
        "/Checks", "SafeSEHCheck",
        "/Checks", "SharedSectionCheck",
        "/Checks", "VB6Check",
        "/Checks", "WXCheck"
    ], stdout=subprocess.PIPE, stderr=subprocess.PIPE)

except WindowsError, (errno, strerror):  # noqa
    if errno != 2 and errno != 3:
        print("TEST-UNEXPECTED-FAIL | autobinscope.py | Unexpected error %d : %s" (
            errno, strerror))
        sys.exit(0)
    else:
        print("TEST-UNEXPECTED-FAIL | autobinscope.py | Could not locate binscope at location : "
              "%s\n" % binscope_path)
        sys.exit(0)

proc.wait()

output = proc.communicate()[1].decode('utf-8').splitlines()

errors = 0
for line in output:
    print(line)
    if 'error' in line:
        errors += 1

if proc.returncode != 0:
    print("TEST-UNEXPECTED-FAIL | autobinscope.py | Binscope returned error code %d for file %s" %
          (proc.returncode, binary_path))
elif errors != 0:
    print("TEST-UNEXPECTED-FAIL | autobinscope.py | Binscope reported %d error(s) for file %s" % (
        errors, binary_path))
else:
    print("TEST-PASS | autobinscope.py | %s succeeded" % binary_path)
