<commit_before>#!/usr/bin/env python
# -*- coding: utf8 -*-
"""
Copyright (c) 2011 <NAME> <<EMAIL>>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
from solum import ConstantType

from .topping import Topping


class BlocksTopping(Topping):
    """Gets most available block types."""

    PROVIDES = [
        "blocks"
    ]

    DEPENDS = [
        "identify.block.superclass",
        "language",
        "version.protocol"
    ]

    @staticmethod
    def act(aggregate, jar, verbose=False):
        superclass = aggregate["classes"]["block.superclass"]
        cf = jar.open_class(superclass)
        individual_textures = True #aggregate["version"]["protocol"] >= 52 # assume >1.5 http://wiki.vg/Protocol_History#1.5.x since don't read packets TODO

        if "tile" in aggregate["language"]:
            language = aggregate["language"]["tile"]
        else:
            language = None

        # Find the static block registration method
        method = cf.methods.find_one(args=(), returns="void", flags=9) # public static void ...() {}
        blocks = aggregate.setdefault("blocks", {})
        block = blocks.setdefault("block", {})
        tmp = []

        def skip_it():
            """
            Skip the misc. crap at the beginning of the constructor,
            and resume when we hit the first item.
            """
            watch_for_new = False
            found_new = False
            for ins in method.instructions:
                if found_new:
                    yield ins
                    continue

                if ins.name == "newarray":
                    # The next 'new' that comes along is where
                    # we want to break
                    watch_for_new = True
                elif watch_for_new and ins.name == "new":
                    found_new = True
                    yield ins
                    continue

        ditch = False
        for ins in skip_it():
            if ins.name == "new":
                # The beginning of a new block definition
                stack = []
                const_i = ins.operands[0][1]
                const = cf.constants[const_i]
                class_name = const["name"]["value"]
                current_block = {
                    "class": class_name,
                    "calls": {}
                }
            elif ins.name.startswith("iconst"):
                stack.append(int(ins.name[-1]))
            elif ins.name.startswith("fconst"):
                stack.append(float(ins.name[-1]))
            elif ins.name.endswith("ipush"):
                stack.append(ins.operands[0][1])
            elif ins.name in ("ldc", "ldc_w"):
                const_i = ins.operands[0][1]
                const = cf.constants[const_i]

                if const["tag"] == ConstantType.CLASS:
                    stack.append("%s.class" % const["name"]["value"])
                elif const["tag"] == ConstantType.STRING:
                    stack.append(const["string"]["value"])
                else:
                    stack.append(const["value"])
            elif ins.name.startswith("invoke"):
                # A method invocation
                const_i = ins.operands[0][1]
                const = cf.constants[const_i]
                method_name = const["name_and_type"]["name"]["value"]
                method_desc = const["name_and_type"]["descriptor"]["value"]
                current_block["calls"][method_name] = stack
                current_block["calls"][method_name + method_desc] = stack
                stack = []
            elif ins.name == "putstatic":
                # Store the newly constructed object into a static
                # field. This means we have everything we're going to
                # get on this block.
                if ditch:
                    ditch = False
                    continue
                const_i = ins.operands[0][1]
                const = cf.constants[const_i]
                field_name = const["name_and_type"]["name"]["value"]
                current_block["assigned_to_field"] = field_name
                tmp.append(current_block)
            elif ins.name == "aconst_null":
                # These are random incomplete blocks, we have no
                # choice but to ignore them for now.
                ditch = True

        # Now that we have all of the blocks, we need a few more things
        # to make sense of what it all means. So,
        #   1. Find the function that returns 'this' and accepts a string.
        #      This is the name or texture setting function.
        #   2. Find the function that returns 'this' and accepts a float.
        #      This is the function that sets the hardness.
        #   3. Find the field which is set by the second
        #      argument of the constructor.
        #      This is the field containing the texture.
        #   4. The first parameter of a blocks <init> function will/should
        #      be the block's data ID.

        string_setters = [
            f.name + cf.constants[f.descriptor_index]["value"]
            for f in cf.methods.find(
                returns=superclass,
                args=("java.lang.String",)
            )
        ]

        assert 1 <= len(string_setters) <= 2
        if len(string_setters) == 2:
            name_setter, texture_setter = string_setters
        else:
            name_setter = string_setters[0]
            texture_setter = None

        hardness_setters = cf.methods.find(
            returns=superclass,
            args=("float",),
            f=lambda x: x.is_protected
        )

        for method in hardness_setters:
            for ins in method.instructions:
                if ins.name == "ifge":
                    const = cf.constants[method.descriptor_index]
                    hardness_setter = method.name + const["value"]
                    break

        if not individual_textures:
            constructor = cf.methods.find_one(
                name="<init>",
                f=lambda x: (len(x.args) == 3 and x.args[:2] == ("int", "int"))
            )

            found_iload = False
            texture_field = None
            for instruction in constructor.instructions:
                if instruction.opcode == 28:                        # iload_2
                    found_iload = True
                elif found_iload and instruction.opcode == 181:     # putfield
                    texture_field = (cf.constants[instruction.operands[0][1]]
                                     ["name_and_type"]["name"]["value"])
                    break

        for blk in tmp:
            final = {}

            if name_setter in blk["calls"]:
                final["name"] = blk["calls"][name_setter][0]

                # Texture and name setter might be swapped,
                # so we swap them back if necessary
                if final["name"] == "cobblestone" and texture_setter:
                    final["name"] = "stonebrick"
                    texture_setter, name_setter = name_setter, texture_setter
                # For some reason wool is the only block without an ID
                elif final["name"] == "cloth":
                    final["id"] = 35
                lang_key = "%s.name" % final["name"]
                if language and lang_key in language:
                    final["display_name"] = language[lang_key]

            if texture_setter and texture_setter in blk["calls"]:
                final["texture"] = blk["calls"][texture_setter][0]

            init = blk["calls"]["<init>"]

            if hardness_setter not in blk["calls"]:
                final["hardness"] = 0.00
            else:
                final["hardness"] = blk["calls"][hardness_setter][0]

            if init:
                final["id"] = init[0]
            final["field"] = blk["assigned_to_field"]
            final["class"] = blk["class"]

            if "id" in final:
                block[final["id"]] = final

            if individual_textures:
                continue

            # The texture can be set in different places
            #   1. Directly as the second argument of the constructor
            #   2. In the constructor by setting the field
            #   3. In the constructor calling the constructor of the superclass

            texture = None
            if len(init) >= 2 and isinstance(init[1], int) and init[1] > 1:
                texture = init[1]
            else:
                block_cf = jar.open_class(final["class"])
                constructor = block_cf.methods.find_one(name="<init>")
                if (len(init) >= 2 and len(constructor.args) >= 2 and
                        constructor.args[1] == "int" and
                        isinstance(init[1], int)):
                    texture = init[1]
                else:
                    stack = []
                    for instruction in constructor.instructions:
                        if instruction.name.startswith("iconst_"):
                            stack.append(instruction.opcode - 3)
                        elif instruction.opcode in (16, 17):  # bipush, sipush
                            stack.append(instruction.operands[0][1])
                        elif instruction.name.startswith("iload_"):  # iload_n
                            stack.append(-1)
                        elif instruction.opcode == 183:       # invokespecial
                            method = block_cf.constants[
                                instruction.operands[0][1]
                            ]
                            args = method["name_and_type"]
                            args = args["descriptor"]["value"]
                            if "II" in args:
                                stack.append(0)
                                for c in args:
                                    if c == "I" and len(stack) > 0:
                                        texture = stack.pop()
                                if "id" not in final:
                                    final["id"] = stack.pop()
                                break
                        elif instruction.opcode == 181:       # putfield
                            field = block_cf.constants[
                                instruction.operands[0][1]
                            ]
                            field = field["name_and_type"]["name"]["value"]
                            if field == texture_field and len(stack) >= 1:
                                texture = stack.pop()
                                break

            if texture and texture >= 0:
                final["texture"] = {"x": texture % 16,
                                    "y": texture / 16}

        blocks["info"] = {
            "count": len(block),
            "real_count": len(tmp)
        }
<commit_msg>Some progress identifying blocks, at least air, stone, and weighted pressure plates<commit_after>#!/usr/bin/env python
# -*- coding: utf8 -*-
"""
Copyright (c) 2011 <NAME> <<EMAIL>>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
from solum import ConstantType

from .topping import Topping


class BlocksTopping(Topping):
    """Gets most available block types."""

    PROVIDES = [
        "blocks"
    ]

    DEPENDS = [
        "identify.block.superclass",
        "language",
        "version.protocol"
    ]

    @staticmethod
    def act(aggregate, jar, verbose=False):
        superclass = aggregate["classes"]["block.superclass"]
        cf = jar.open_class(superclass)
        individual_textures = True #aggregate["version"]["protocol"] >= 52 # assume >1.5 http://wiki.vg/Protocol_History#1.5.x since don't read packets TODO

        if "tile" in aggregate["language"]:
            language = aggregate["language"]["tile"]
        else:
            language = None

        # Find the static block registration method
        method = cf.methods.find_one(args=(), returns="void", flags=9) # public static void ...() {}
        blocks = aggregate.setdefault("blocks", {})
        block = blocks.setdefault("block", {})
        tmp = []

        def skip_it():
            """
            Skip the misc. crap at the beginning of the constructor,
            and resume when we hit the first item.
            """
            watch_for_new = False
            found_new = False
            for ins in method.instructions:
                if found_new:
                    yield ins
                    continue

                if ins.name == "newarray":
                    # The next 'new' that comes along is where
                    # we want to break
                    watch_for_new = True
                elif watch_for_new and ins.name == "new":
                    found_new = True
                    yield ins
                    continue

        ditch = False
        stack = []
        for ins in method.instructions:
            #print "INS",ins
            if ins.name == "new":
                # The beginning of a new block definition
                stack = []
                const_i = ins.operands[0][1]
                const = cf.constants[const_i]
                class_name = const["name"]["value"]
                current_block = {
                    "class": class_name,
                    "calls": {}
                }
            elif ins.name.startswith("iconst"):
                stack.append(int(ins.name[-1]))
            elif ins.name.startswith("fconst"):
                stack.append(float(ins.name[-1]))
            elif ins.name.endswith("ipush"):
                stack.append(ins.operands[0][1])
            elif ins.name in ("ldc", "ldc_w"):
                const_i = ins.operands[0][1]
                const = cf.constants[const_i]

                if const["tag"] == ConstantType.CLASS:
                    stack.append("%s.class" % const["name"]["value"])
                elif const["tag"] == ConstantType.STRING:
                    stack.append(const["string"]["value"])
                else:
                    stack.append(const["value"])
                #print "ldc",stack
            elif ins.name in ("invokevirtual", "invokespecial"):
                # A method invocation
                const_i = ins.operands[0][1]
                const = cf.constants[const_i]
                method_name = const["name_and_type"]["name"]["value"]
                method_desc = const["name_and_type"]["descriptor"]["value"]
                current_block["calls"][method_name] = stack
                current_block["calls"][method_name + method_desc] = stack
                stack = []
            elif ins.name == "invokestatic":
                # Call the static registration method.
                # This means we have everything we're going to
                # get on this block.
                if ditch:
                    ditch = False
                    continue
                const_i = ins.operands[0][1]
                const = cf.constants[const_i]
                field_name = const["name_and_type"]["name"]["value"]
                current_block["assigned_to_field"] = field_name
                #print "current_block",current_block
                tmp.append(current_block)
            elif ins.name == "aconst_null":
                # These are random incomplete blocks, we have no
                # choice but to ignore them for now.
                ditch = True

        # Now that we have all of the blocks, we need a few more things
        # to make sense of what it all means. So,
        #   1. Find the function that returns 'this' and accepts a string.
        #      This is the name or texture setting function.
        #   2. Find the function that returns 'this' and accepts a float.
        #      This is the function that sets the hardness.
        #   3. Find the field which is set by the second
        #      argument of the constructor.
        #      This is the field containing the texture.
        #   4. The first parameter of a blocks <init> function will/should
        #      be the block's data ID.

        string_setters = [
            f.name + cf.constants[f.descriptor_index]["value"]
            for f in cf.methods.find(
                returns=superclass,
                args=("java.lang.String",)
            )
        ]

        assert 1 <= len(string_setters) <= 2
        if len(string_setters) == 2:
            name_setter, texture_setter = string_setters
        else:
            name_setter = string_setters[0]
            texture_setter = None

        hardness_setters = cf.methods.find(
            returns=superclass,
            args=("float",),
            f=lambda x: x.is_protected
        )

        for method in hardness_setters:
            for ins in method.instructions:
                if ins.name == "ifge":
                    const = cf.constants[method.descriptor_index]
                    hardness_setter = method.name + const["value"]
                    break

        if not individual_textures:
            constructor = cf.methods.find_one(
                name="<init>",
                f=lambda x: (len(x.args) == 3 and x.args[:2] == ("int", "int"))
            )

            found_iload = False
            texture_field = None
            for instruction in constructor.instructions:
                if instruction.opcode == 28:                        # iload_2
                    found_iload = True
                elif found_iload and instruction.opcode == 181:     # putfield
                    texture_field = (cf.constants[instruction.operands[0][1]]
                                     ["name_and_type"]["name"]["value"])
                    break

        for blk in tmp:
            final = {}

            if name_setter in blk["calls"]:
                final["name"] = blk["calls"][name_setter][0]

                # Texture and name setter might be swapped,
                # so we swap them back if necessary
                if final["name"] == "cobblestone" and texture_setter:
                    final["name"] = "stonebrick"
                    texture_setter, name_setter = name_setter, texture_setter
                # For some reason wool is the only block without an ID
                elif final["name"] == "cloth":
                    final["id"] = 35
                lang_key = "%s.name" % final["name"]
                if language and lang_key in language:
                    final["display_name"] = language[lang_key]

            if texture_setter and texture_setter in blk["calls"]:
                final["texture"] = blk["calls"][texture_setter][0]

            init = blk["calls"]["<init>"]

            if hardness_setter not in blk["calls"]:
                final["hardness"] = 0.00
            else:
                final["hardness"] = blk["calls"][hardness_setter][0]

            if init:
                final["id"] = init[0]
            final["field"] = blk["assigned_to_field"]
            final["class"] = blk["class"]

            if "id" in final:
                block[final["id"]] = final

            if individual_textures:
                continue

            # The texture can be set in different places
            #   1. Directly as the second argument of the constructor
            #   2. In the constructor by setting the field
            #   3. In the constructor calling the constructor of the superclass

            texture = None
            if len(init) >= 2 and isinstance(init[1], int) and init[1] > 1:
                texture = init[1]
            else:
                block_cf = jar.open_class(final["class"])
                constructor = block_cf.methods.find_one(name="<init>")
                if (len(init) >= 2 and len(constructor.args) >= 2 and
                        constructor.args[1] == "int" and
                        isinstance(init[1], int)):
                    texture = init[1]
                else:
                    stack = []
                    for instruction in constructor.instructions:
                        if instruction.name.startswith("iconst_"):
                            stack.append(instruction.opcode - 3)
                        elif instruction.opcode in (16, 17):  # bipush, sipush
                            stack.append(instruction.operands[0][1])
                        elif instruction.name.startswith("iload_"):  # iload_n
                            stack.append(-1)
                        elif instruction.opcode == 183:       # invokespecial
                            method = block_cf.constants[
                                instruction.operands[0][1]
                            ]
                            args = method["name_and_type"]
                            args = args["descriptor"]["value"]
                            if "II" in args:
                                stack.append(0)
                                for c in args:
                                    if c == "I" and len(stack) > 0:
                                        texture = stack.pop()
                                if "id" not in final:
                                    final["id"] = stack.pop()
                                break
                        elif instruction.opcode == 181:       # putfield
                            field = block_cf.constants[
                                instruction.operands[0][1]
                            ]
                            field = field["name_and_type"]["name"]["value"]
                            if field == texture_field and len(stack) >= 1:
                                texture = stack.pop()
                                break

            if texture and texture >= 0:
                final["texture"] = {"x": texture % 16,
                                    "y": texture / 16}

        blocks["info"] = {
            "count": len(block),
            "real_count": len(tmp)
        }

{'fraction_non_alphanumeric': 0.09487578292488125, 'fraction_numerical': 0.0075665223422590275, 'mean_word_length': 1.4340085942295886, 'pattern_counts': {'":': 26, '<': 21, '<?xml version=': 0, '>': 35, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 20, 'profanity_word_list.json': 2, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2851078', 'n_tokens_mistral': 6131, 'n_tokens_neox': 5833, 'n_words': 1769}