#!/usr/bin/env python3
# Copyright (c) 2011, the Dart project authors.  Please see the AUTHORS file
# for details. All rights reserved. Use of this source code is governed by a
# BSD-style license that can be found in the LICENSE file.

import os
import cpplint
import re

USE_PYTHON3 = True

# memcpy does not handle overlapping memory regions. Even though this
# is well documented it seems to be used in error quite often. To avoid
# problems we disallow the direct use of memcpy.  The exceptions are in
# third-party code and in platform/globals.h which uses it to implement
# bit_cast and bit_copy.
# pthread_detach (and therefore std::thread::detach) have a bug that can
# result in crashes. https://sourceware.org/bugzilla/show_bug.cgi?id=19951
BANNED_FUNCTIONS = [
    ['\\bmemcpy\\(', 'memcpy'],
    ['\\bpthread_detach\\(', 'pthread_detach'],
]


def CheckBannedFunctions(filename):
    if filename.endswith(os.path.join('platform', 'globals.h')) or \
       filename.find('third_party') != -1:
        return 0
    fh = open(filename, 'r')
    content = fh.read()

    for banned_function in BANNED_FUNCTIONS:
        pattern = banned_function[0]
        name = banned_function[1]
        match = re.search(pattern, content)
        if match:
            offset = match.start()
            end_of_line = content.index('\n', offset)
            # We allow explicit use of memcpy with an opt-in via NOLINT
            if 'NOLINT' not in content[offset:end_of_line]:
                line_number = content[0:match.start()].count('\n') + 1
                print("%s:%d: use of %s is forbidden" %
                      (filename, line_number, name))
                return 1
    return 0


def RunLint(input_api, output_api):
    result = []
    cpplint._cpplint_state.ResetErrorCounts()
    banned_match_count = 0
    # Find all .cc and .h files in the change list.
    for git_file in input_api.AffectedTextFiles():
        filename = git_file.AbsoluteLocalPath()

        # Don't lint V8 sources.
        if 'runtime/vm/regexp/' in filename:
            continue

        if filename.endswith('.cc') or (
                # cpplint complains about the style of #ifndefs in our .pbzero.h
                # files, but they are generated by the protozero compiler, so we
                # can't fix this.
                not filename.endswith('.pbzero.h') and filename.endswith('.h')):
            # Run cpplint on the file.
            cpplint.ProcessFile(filename, 1)
            # Check for memcpy use.
            banned_match_count += CheckBannedFunctions(filename)

    # Report a presubmit error if any of the files had an error.
    if cpplint._cpplint_state.error_count > 0 or banned_match_count > 0:
        result = [output_api.PresubmitError('Failed cpplint check.')]
    return result


def CheckGn(input_api, output_api):
    return input_api.canned_checks.CheckGNFormatted(input_api, output_api)


def CheckFormatted(input_api, output_api):

    def convert_warning_to_error(presubmit_result):
        if not presubmit_result.fatal:
            # Convert this warning to an error.
            result_json = presubmit_result.json_format()
            return output_api.PresubmitError(
                message=result_json['message'],
                items=result_json['items'],
                long_text=result_json['long_text'])
        return presubmit_result

    results = input_api.canned_checks.CheckPatchFormatted(input_api, output_api)
    return [convert_warning_to_error(r) for r in results]


def CheckChangeOnUpload(input_api, output_api):
    return (RunLint(input_api, output_api) + CheckGn(input_api, output_api) +
            CheckFormatted(input_api, output_api))


def CheckChangeOnCommit(input_api, output_api):
    return (RunLint(input_api, output_api) + CheckGn(input_api, output_api) +
            CheckFormatted(input_api, output_api))
