<commit_before>import json

import capa.engine


def convert_statement_to_result_document(statement):
    """
        "statement": {
            "type": "or"
        },

        "statement": {
            "max": 9223372036854775808,
            "min": 2,
            "type": "range"
        },
    """
    if isinstance(statement, capa.engine.And):
        return {
            'type': 'and',
        }
    elif isinstance(statement, capa.engine.Or):
        return {
            'type': 'or',
        }
    elif isinstance(statement, capa.engine.Not):
        return {
            'type': 'not',
        }
    elif isinstance(statement, capa.engine.Or):
        return {
            'type': 'or',
        }
    elif isinstance(statement, capa.engine.Some) and statement.count == 0:
        return {
            'type': 'optional'
        }
    elif isinstance(statement, capa.engine.Some) and statement.count > 0:
        return {
            'type': 'some',
            'count': statement.count,
        }
    elif isinstance(statement, capa.engine.Range):
        return {
            'type': 'range',
            'min': statement.min,
            'max': statement.max,
        }
    elif isinstance(statement, capa.engine.Regex):
        return {
            'type': 'regex',
            'pattern': statement.pattern,
        }
    elif isinstance(statement, capa.engine.Subscope):
        return {
            'type': 'subscope',
            'scope': statement.scope,
        }
    else:
        raise RuntimeError("unexpected match statement type: " + str(statement))


def convert_feature_to_result_document(feature):
    """
        "feature": {
            "number": 6,
            "type": "number"
        },

        "feature": {
            "api": "ws2_32.WSASocket",
            "type": "api"
        },

        "feature": {
            "match": "create TCP socket",
            "type": "match"
        },

        "feature": {
            "characteristic": [
                "loop",
                true
            ],
            "type": "characteristic"
        },
    """
    name, value = feature.freeze_serialize()

    # make the terms pretty
    name = name.lower()
    if name == 'matchedrule':
        name = 'match'

    # in the common case, there's a single argument
    # so use it directly.
    # like: name=number value=1
    if isinstance(value, list) and len(value) == 1:
        value = value[0]

    return {
        'type': name,
        name: value,
    }


def convert_node_to_result_document(node):
    """
        "node": {
            "type": "statement",
            "statement": { ... }
        },

        "node": {
            "type": "feature",
            "feature": { ... }
        },
    """

    if isinstance(node, capa.engine.Statement):
        return {
            'type': 'statement',
            'statement': convert_statement_to_result_document(node),
        }
    elif isinstance(node, capa.features.Feature):
        return {
            'type': 'feature',
            'feature': convert_feature_to_result_document(node),
        }
    else:
        raise RuntimeError("unexpected match node type")


def convert_match_to_result_document(rules, capabilities, result):
    """
    convert the given Result instance into a common, Python-native data structure.
    this will become part of the "result document" format that can be emitted to JSON.

    args:
      rules (RuleSet):
      result (Result):

    returns: Dict[str, Any]
    """
    doc = {
        'success': bool(result.success),
        'node': convert_node_to_result_document(result.statement),
        'children': [
            convert_match_to_result_document(rules, capabilities, child)
            for child in result.children
        ],
    }

    # logic expression, like `and`, don't have locations - their children do.
    # so only add `locations` to feature nodes.
    if isinstance(result.statement, capa.features.Feature):
        if bool(result.success):
            doc['locations'] = result.locations

    # if we have a `match` statement, then we're referencing another rule.
    # this could an external rule (written by a human), or
    #  rule generated to support a subscope (basic block, etc.)
    # we still want to include the matching logic in this tree.
    #
    # so, we need to lookup the other rule results
    # and then filter those down to the address used here.
    # finally, splice that logic into this tree.
    if (doc['node']['type'] == 'feature'
            and doc['node']['feature']['type'] == 'match'
            # only add subtree on success,
            # because there won't be results for the other rule on failure.
            and doc['success']):

        rule_name = doc['node']['feature']['match']
        rule = rules[rule_name]
        rule_matches = {address: result for (address, result) in capabilities[rule_name]}

        if rule.meta.get('capa/subscope-rule'):
            # for a subscope rule, rename the rule name to the scope,
            # which is consistent with the rule text.
            #
            # e.g. `contain loop/30c4c78e29bf4d54894fc74f664c62e8` -> `basic block`
            scope = rule.meta['scope']
            doc['node']['feature']['match'] = scope
<commit_msg>render: translate match nodes from features to statements<commit_after>import json

import capa.engine


def convert_statement_to_result_document(statement):
    """
        "statement": {
            "type": "or"
        },

        "statement": {
            "max": 9223372036854775808,
            "min": 2,
            "type": "range"
        },
    """
    if isinstance(statement, capa.engine.And):
        return {
            'type': 'and',
        }
    elif isinstance(statement, capa.engine.Or):
        return {
            'type': 'or',
        }
    elif isinstance(statement, capa.engine.Not):
        return {
            'type': 'not',
        }
    elif isinstance(statement, capa.engine.Or):
        return {
            'type': 'or',
        }
    elif isinstance(statement, capa.engine.Some) and statement.count == 0:
        return {
            'type': 'optional'
        }
    elif isinstance(statement, capa.engine.Some) and statement.count > 0:
        return {
            'type': 'some',
            'count': statement.count,
        }
    elif isinstance(statement, capa.engine.Range):
        return {
            'type': 'range',
            'min': statement.min,
            'max': statement.max,
        }
    elif isinstance(statement, capa.engine.Regex):
        return {
            'type': 'regex',
            'pattern': statement.pattern,
        }
    elif isinstance(statement, capa.engine.Subscope):
        return {
            'type': 'subscope',
            'subscope': statement.scope,
        }
    else:
        raise RuntimeError("unexpected match statement type: " + str(statement))


def convert_feature_to_result_document(feature):
    """
        "feature": {
            "number": 6,
            "type": "number"
        },

        "feature": {
            "api": "ws2_32.WSASocket",
            "type": "api"
        },

        "feature": {
            "match": "create TCP socket",
            "type": "match"
        },

        "feature": {
            "characteristic": [
                "loop",
                true
            ],
            "type": "characteristic"
        },
    """
    name, value = feature.freeze_serialize()

    # make the terms pretty
    name = name.lower()
    if name == 'matchedrule':
        name = 'match'

    # in the common case, there's a single argument
    # so use it directly.
    # like: name=number value=1
    if isinstance(value, list) and len(value) == 1:
        value = value[0]

    return {
        'type': name,
        name: value,
    }


def convert_node_to_result_document(node):
    """
        "node": {
            "type": "statement",
            "statement": { ... }
        },

        "node": {
            "type": "feature",
            "feature": { ... }
        },
    """

    if isinstance(node, capa.engine.Statement):
        return {
            'type': 'statement',
            'statement': convert_statement_to_result_document(node),
        }
    elif isinstance(node, capa.features.Feature):
        return {
            'type': 'feature',
            'feature': convert_feature_to_result_document(node),
        }
    else:
        raise RuntimeError("unexpected match node type")


def convert_match_to_result_document(rules, capabilities, result):
    """
    convert the given Result instance into a common, Python-native data structure.
    this will become part of the "result document" format that can be emitted to JSON.

    args:
      rules (RuleSet):
      result (Result):

    returns: Dict[str, Any]
    """
    doc = {
        'success': bool(result.success),
        'node': convert_node_to_result_document(result.statement),
        'children': [
            convert_match_to_result_document(rules, capabilities, child)
            for child in result.children
        ],
    }

    # logic expression, like `and`, don't have locations - their children do.
    # so only add `locations` to feature nodes.
    if isinstance(result.statement, capa.features.Feature):
        if bool(result.success):
            doc['locations'] = result.locations

    # if we have a `match` statement, then we're referencing another rule.
    # this could an external rule (written by a human), or
    #  rule generated to support a subscope (basic block, etc.)
    # we still want to include the matching logic in this tree.
    #
    # so, we need to lookup the other rule results
    # and then filter those down to the address used here.
    # finally, splice that logic into this tree.
    if (doc['node']['type'] == 'feature'
            and doc['node']['feature']['type'] == 'match'
            # only add subtree on success,
            # because there won't be results for the other rule on failure.
            and doc['success']):

        rule_name = doc['node']['feature']['match']
        rule = rules[rule_name]
        rule_matches = {address: result for (address, result) in capabilities[rule_name]}

        if rule.meta.get('capa/subscope-rule'):
            # for a subscope rule, fixup the node to be a scope node, rather than a match feature node.
            #
            # e.g. `contain loop/30c4c78e29bf4d54894fc74f664c62e8` -> `basic block`
            scope = rule.meta['scope']
            doc['node'] = {
                'type': 'statement',
                'statement': {
                    'type': 'subscope',
                    'subscope': scope,
                },
            }

{'fraction_non_alphanumeric': 0.1218397238548372, 'fraction_numerical': 0.009329228472805299, 'mean_word_length': 1.8397350993377484, 'pattern_counts': {'":': 48, '<': 3, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2846901', 'n_tokens_mistral': 3033, 'n_tokens_neox': 2916, 'n_words': 811}