{
    "file_diffs": [
        {
            "old_file_content": "from __future__ import annotations\nimport pytest\n\nfrom PIL import Image, ImageMath\n\n\ndef pixel(im):\n    if hasattr(im, \"im\"):\n        return f\"{im.mode} {repr(im.getpixel((0, 0)))}\"\n    if isinstance(im, int):\n        return int(im)  # hack to deal with booleans\n\n\nA = Image.new(\"L\", (1, 1), 1)\nB = Image.new(\"L\", (1, 1), 2)\nZ = Image.new(\"L\", (1, 1), 0)  # Z for zero\nF = Image.new(\"F\", (1, 1), 3)\nI = Image.new(\"I\", (1, 1), 4)  # noqa: E741\n\nA2 = A.resize((2, 2))\nB2 = B.resize((2, 2))\n\nimages = {\"A\": A, \"B\": B, \"F\": F, \"I\": I}\n\n\ndef test_sanity():\n    assert ImageMath.eval(\"1\") == 1\n    assert ImageMath.eval(\"1+A\", A=2) == 3\n    assert pixel(ImageMath.eval(\"A+B\", A=A, B=B)) == \"I 3\"\n    assert pixel(ImageMath.eval(\"A+B\", images)) == \"I 3\"\n    assert pixel(ImageMath.eval(\"float(A)+B\", images)) == \"F 3.0\"\n    assert pixel(ImageMath.eval(\"int(float(A)+B)\", images)) == \"I 3\"\n\n\ndef test_ops():\n    assert pixel(ImageMath.eval(\"-A\", images)) == \"I -1\"\n    assert pixel(ImageMath.eval(\"+B\", images)) == \"L 2\"\n\n    assert pixel(ImageMath.eval(\"A+B\", images)) == \"I 3\"\n    assert pixel(ImageMath.eval(\"A-B\", images)) == \"I -1\"\n    assert pixel(ImageMath.eval(\"A*B\", images)) == \"I 2\"\n    assert pixel(ImageMath.eval(\"A/B\", images)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"B**2\", images)) == \"I 4\"\n    assert pixel(ImageMath.eval(\"B**33\", images)) == \"I 2147483647\"\n\n    assert pixel(ImageMath.eval(\"float(A)+B\", images)) == \"F 3.0\"\n    assert pixel(ImageMath.eval(\"float(A)-B\", images)) == \"F -1.0\"\n    assert pixel(ImageMath.eval(\"float(A)*B\", images)) == \"F 2.0\"\n    assert pixel(ImageMath.eval(\"float(A)/B\", images)) == \"F 0.5\"\n    assert pixel(ImageMath.eval(\"float(B)**2\", images)) == \"F 4.0\"\n    assert pixel(ImageMath.eval(\"float(B)**33\", images)) == \"F 8589934592.0\"\n\n\n@pytest.mark.parametrize(\n    \"expression\",\n    (\n        \"exec('pass')\",\n        \"(lambda: exec('pass'))()\",\n        \"(lambda: (lambda: exec('pass'))())()\",\n    ),\n)\ndef test_prevent_exec(expression):\n    with pytest.raises(ValueError):\n        ImageMath.eval(expression)\n\n\ndef test_logical():\n    assert pixel(ImageMath.eval(\"not A\", images)) == 0\n    assert pixel(ImageMath.eval(\"A and B\", images)) == \"L 2\"\n    assert pixel(ImageMath.eval(\"A or B\", images)) == \"L 1\"\n\n\ndef test_convert():\n    assert pixel(ImageMath.eval(\"convert(A+B, 'L')\", images)) == \"L 3\"\n    assert pixel(ImageMath.eval(\"convert(A+B, '1')\", images)) == \"1 0\"\n    assert pixel(ImageMath.eval(\"convert(A+B, 'RGB')\", images)) == \"RGB (3, 3, 3)\"\n\n\ndef test_compare():\n    assert pixel(ImageMath.eval(\"min(A, B)\", images)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"max(A, B)\", images)) == \"I 2\"\n    assert pixel(ImageMath.eval(\"A == 1\", images)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"A == 2\", images)) == \"I 0\"\n\n\ndef test_one_image_larger():\n    assert pixel(ImageMath.eval(\"A+B\", A=A2, B=B)) == \"I 3\"\n    assert pixel(ImageMath.eval(\"A+B\", A=A, B=B2)) == \"I 3\"\n\n\ndef test_abs():\n    assert pixel(ImageMath.eval(\"abs(A)\", A=A)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"abs(B)\", B=B)) == \"I 2\"\n\n\ndef test_binary_mod():\n    assert pixel(ImageMath.eval(\"A%A\", A=A)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"B%B\", B=B)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"A%B\", A=A, B=B)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"B%A\", A=A, B=B)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"Z%A\", A=A, Z=Z)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"Z%B\", B=B, Z=Z)) == \"I 0\"\n\n\ndef test_bitwise_invert():\n    assert pixel(ImageMath.eval(\"~Z\", Z=Z)) == \"I -1\"\n    assert pixel(ImageMath.eval(\"~A\", A=A)) == \"I -2\"\n    assert pixel(ImageMath.eval(\"~B\", B=B)) == \"I -3\"\n\n\ndef test_bitwise_and():\n    assert pixel(ImageMath.eval(\"Z&Z\", A=A, Z=Z)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"Z&A\", A=A, Z=Z)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"A&Z\", A=A, Z=Z)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"A&A\", A=A, Z=Z)) == \"I 1\"\n\n\ndef test_bitwise_or():\n    assert pixel(ImageMath.eval(\"Z|Z\", A=A, Z=Z)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"Z|A\", A=A, Z=Z)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"A|Z\", A=A, Z=Z)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"A|A\", A=A, Z=Z)) == \"I 1\"\n\n\ndef test_bitwise_xor():\n    assert pixel(ImageMath.eval(\"Z^Z\", A=A, Z=Z)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"Z^A\", A=A, Z=Z)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"A^Z\", A=A, Z=Z)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"A^A\", A=A, Z=Z)) == \"I 0\"\n\n\ndef test_bitwise_leftshift():\n    assert pixel(ImageMath.eval(\"Z<<0\", Z=Z)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"Z<<1\", Z=Z)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"A<<0\", A=A)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"A<<1\", A=A)) == \"I 2\"\n\n\ndef test_bitwise_rightshift():\n    assert pixel(ImageMath.eval(\"Z>>0\", Z=Z)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"Z>>1\", Z=Z)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"A>>0\", A=A)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"A>>1\", A=A)) == \"I 0\"\n\n\ndef test_logical_eq():\n    assert pixel(ImageMath.eval(\"A==A\", A=A)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"B==B\", B=B)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"A==B\", A=A, B=B)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"B==A\", A=A, B=B)) == \"I 0\"\n\n\ndef test_logical_ne():\n    assert pixel(ImageMath.eval(\"A!=A\", A=A)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"B!=B\", B=B)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"A!=B\", A=A, B=B)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"B!=A\", A=A, B=B)) == \"I 1\"\n\n\ndef test_logical_lt():\n    assert pixel(ImageMath.eval(\"A<A\", A=A)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"B<B\", B=B)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"A<B\", A=A, B=B)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"B<A\", A=A, B=B)) == \"I 0\"\n\n\ndef test_logical_le():\n    assert pixel(ImageMath.eval(\"A<=A\", A=A)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"B<=B\", B=B)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"A<=B\", A=A, B=B)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"B<=A\", A=A, B=B)) == \"I 0\"\n\n\ndef test_logical_gt():\n    assert pixel(ImageMath.eval(\"A>A\", A=A)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"B>B\", B=B)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"A>B\", A=A, B=B)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"B>A\", A=A, B=B)) == \"I 1\"\n\n\ndef test_logical_ge():\n    assert pixel(ImageMath.eval(\"A>=A\", A=A)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"B>=B\", B=B)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"A>=B\", A=A, B=B)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"B>=A\", A=A, B=B)) == \"I 1\"\n\n\ndef test_logical_equal():\n    assert pixel(ImageMath.eval(\"equal(A, A)\", A=A)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"equal(B, B)\", B=B)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"equal(Z, Z)\", Z=Z)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"equal(A, B)\", A=A, B=B)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"equal(B, A)\", A=A, B=B)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"equal(A, Z)\", A=A, Z=Z)) == \"I 0\"\n\n\ndef test_logical_not_equal():\n    assert pixel(ImageMath.eval(\"notequal(A, A)\", A=A)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"notequal(B, B)\", B=B)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"notequal(Z, Z)\", Z=Z)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"notequal(A, B)\", A=A, B=B)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"notequal(B, A)\", A=A, B=B)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"notequal(A, Z)\", A=A, Z=Z)) == \"I 1\"\n",
            "new_file_content": "from __future__ import annotations\nimport pytest\n\nfrom PIL import Image, ImageMath\n\n\ndef pixel(im):\n    if hasattr(im, \"im\"):\n        return f\"{im.mode} {repr(im.getpixel((0, 0)))}\"\n    if isinstance(im, int):\n        return int(im)  # hack to deal with booleans\n\n\nA = Image.new(\"L\", (1, 1), 1)\nB = Image.new(\"L\", (1, 1), 2)\nZ = Image.new(\"L\", (1, 1), 0)  # Z for zero\nF = Image.new(\"F\", (1, 1), 3)\nI = Image.new(\"I\", (1, 1), 4)  # noqa: E741\n\nA2 = A.resize((2, 2))\nB2 = B.resize((2, 2))\n\nimages = {\"A\": A, \"B\": B, \"F\": F, \"I\": I}\n\n\ndef test_sanity():\n    assert ImageMath.eval(\"1\") == 1\n    assert ImageMath.eval(\"1+A\", A=2) == 3\n    assert pixel(ImageMath.eval(\"A+B\", A=A, B=B)) == \"I 3\"\n    assert pixel(ImageMath.eval(\"A+B\", images)) == \"I 3\"\n    assert pixel(ImageMath.eval(\"float(A)+B\", images)) == \"F 3.0\"\n    assert pixel(ImageMath.eval(\"int(float(A)+B)\", images)) == \"I 3\"\n\n\ndef test_ops():\n    assert pixel(ImageMath.eval(\"-A\", images)) == \"I -1\"\n    assert pixel(ImageMath.eval(\"+B\", images)) == \"L 2\"\n\n    assert pixel(ImageMath.eval(\"A+B\", images)) == \"I 3\"\n    assert pixel(ImageMath.eval(\"A-B\", images)) == \"I -1\"\n    assert pixel(ImageMath.eval(\"A*B\", images)) == \"I 2\"\n    assert pixel(ImageMath.eval(\"A/B\", images)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"B**2\", images)) == \"I 4\"\n    assert pixel(ImageMath.eval(\"B**33\", images)) == \"I 2147483647\"\n\n    assert pixel(ImageMath.eval(\"float(A)+B\", images)) == \"F 3.0\"\n    assert pixel(ImageMath.eval(\"float(A)-B\", images)) == \"F -1.0\"\n    assert pixel(ImageMath.eval(\"float(A)*B\", images)) == \"F 2.0\"\n    assert pixel(ImageMath.eval(\"float(A)/B\", images)) == \"F 0.5\"\n    assert pixel(ImageMath.eval(\"float(B)**2\", images)) == \"F 4.0\"\n    assert pixel(ImageMath.eval(\"float(B)**33\", images)) == \"F 8589934592.0\"\n\n\n@pytest.mark.parametrize(\n    \"expression\",\n    (\n        \"exec('pass')\",\n        \"(lambda: exec('pass'))()\",\n        \"(lambda: (lambda: exec('pass'))())()\",\n    ),\n)\ndef test_prevent_exec(expression):\n    with pytest.raises(ValueError):\n        ImageMath.eval(expression)\n\n\ndef test_prevent_double_underscores():\n    with pytest.raises(ValueError):\n        ImageMath.eval(\"1\", {\"__\": None})\n\n\ndef test_prevent_builtins():\n    with pytest.raises(ValueError):\n        ImageMath.eval(\"(lambda: exec('exit()'))()\", {\"exec\": None})\n\n\ndef test_logical():\n    assert pixel(ImageMath.eval(\"not A\", images)) == 0\n    assert pixel(ImageMath.eval(\"A and B\", images)) == \"L 2\"\n    assert pixel(ImageMath.eval(\"A or B\", images)) == \"L 1\"\n\n\ndef test_convert():\n    assert pixel(ImageMath.eval(\"convert(A+B, 'L')\", images)) == \"L 3\"\n    assert pixel(ImageMath.eval(\"convert(A+B, '1')\", images)) == \"1 0\"\n    assert pixel(ImageMath.eval(\"convert(A+B, 'RGB')\", images)) == \"RGB (3, 3, 3)\"\n\n\ndef test_compare():\n    assert pixel(ImageMath.eval(\"min(A, B)\", images)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"max(A, B)\", images)) == \"I 2\"\n    assert pixel(ImageMath.eval(\"A == 1\", images)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"A == 2\", images)) == \"I 0\"\n\n\ndef test_one_image_larger():\n    assert pixel(ImageMath.eval(\"A+B\", A=A2, B=B)) == \"I 3\"\n    assert pixel(ImageMath.eval(\"A+B\", A=A, B=B2)) == \"I 3\"\n\n\ndef test_abs():\n    assert pixel(ImageMath.eval(\"abs(A)\", A=A)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"abs(B)\", B=B)) == \"I 2\"\n\n\ndef test_binary_mod():\n    assert pixel(ImageMath.eval(\"A%A\", A=A)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"B%B\", B=B)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"A%B\", A=A, B=B)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"B%A\", A=A, B=B)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"Z%A\", A=A, Z=Z)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"Z%B\", B=B, Z=Z)) == \"I 0\"\n\n\ndef test_bitwise_invert():\n    assert pixel(ImageMath.eval(\"~Z\", Z=Z)) == \"I -1\"\n    assert pixel(ImageMath.eval(\"~A\", A=A)) == \"I -2\"\n    assert pixel(ImageMath.eval(\"~B\", B=B)) == \"I -3\"\n\n\ndef test_bitwise_and():\n    assert pixel(ImageMath.eval(\"Z&Z\", A=A, Z=Z)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"Z&A\", A=A, Z=Z)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"A&Z\", A=A, Z=Z)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"A&A\", A=A, Z=Z)) == \"I 1\"\n\n\ndef test_bitwise_or():\n    assert pixel(ImageMath.eval(\"Z|Z\", A=A, Z=Z)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"Z|A\", A=A, Z=Z)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"A|Z\", A=A, Z=Z)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"A|A\", A=A, Z=Z)) == \"I 1\"\n\n\ndef test_bitwise_xor():\n    assert pixel(ImageMath.eval(\"Z^Z\", A=A, Z=Z)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"Z^A\", A=A, Z=Z)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"A^Z\", A=A, Z=Z)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"A^A\", A=A, Z=Z)) == \"I 0\"\n\n\ndef test_bitwise_leftshift():\n    assert pixel(ImageMath.eval(\"Z<<0\", Z=Z)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"Z<<1\", Z=Z)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"A<<0\", A=A)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"A<<1\", A=A)) == \"I 2\"\n\n\ndef test_bitwise_rightshift():\n    assert pixel(ImageMath.eval(\"Z>>0\", Z=Z)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"Z>>1\", Z=Z)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"A>>0\", A=A)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"A>>1\", A=A)) == \"I 0\"\n\n\ndef test_logical_eq():\n    assert pixel(ImageMath.eval(\"A==A\", A=A)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"B==B\", B=B)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"A==B\", A=A, B=B)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"B==A\", A=A, B=B)) == \"I 0\"\n\n\ndef test_logical_ne():\n    assert pixel(ImageMath.eval(\"A!=A\", A=A)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"B!=B\", B=B)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"A!=B\", A=A, B=B)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"B!=A\", A=A, B=B)) == \"I 1\"\n\n\ndef test_logical_lt():\n    assert pixel(ImageMath.eval(\"A<A\", A=A)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"B<B\", B=B)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"A<B\", A=A, B=B)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"B<A\", A=A, B=B)) == \"I 0\"\n\n\ndef test_logical_le():\n    assert pixel(ImageMath.eval(\"A<=A\", A=A)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"B<=B\", B=B)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"A<=B\", A=A, B=B)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"B<=A\", A=A, B=B)) == \"I 0\"\n\n\ndef test_logical_gt():\n    assert pixel(ImageMath.eval(\"A>A\", A=A)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"B>B\", B=B)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"A>B\", A=A, B=B)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"B>A\", A=A, B=B)) == \"I 1\"\n\n\ndef test_logical_ge():\n    assert pixel(ImageMath.eval(\"A>=A\", A=A)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"B>=B\", B=B)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"A>=B\", A=A, B=B)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"B>=A\", A=A, B=B)) == \"I 1\"\n\n\ndef test_logical_equal():\n    assert pixel(ImageMath.eval(\"equal(A, A)\", A=A)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"equal(B, B)\", B=B)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"equal(Z, Z)\", Z=Z)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"equal(A, B)\", A=A, B=B)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"equal(B, A)\", A=A, B=B)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"equal(A, Z)\", A=A, Z=Z)) == \"I 0\"\n\n\ndef test_logical_not_equal():\n    assert pixel(ImageMath.eval(\"notequal(A, A)\", A=A)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"notequal(B, B)\", B=B)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"notequal(Z, Z)\", Z=Z)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"notequal(A, B)\", A=A, B=B)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"notequal(B, A)\", A=A, B=B)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"notequal(A, Z)\", A=A, Z=Z)) == \"I 1\"\n",
            "header": {
                "file": {
                    "path": "Tests/test_imagemath.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "22de86c7c",
                "new_commit_hash": "9281de6f6",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/Tests/test_imagemath.py"
            },
            "plus_file": {
                "path": "b/Tests/test_imagemath.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 64,
                            "length": 6
                        },
                        "new_range": {
                            "start": 64,
                            "length": 16
                        },
                        "section": "def test_prevent_exec(expression):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        ImageMath.eval(expression)",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "def test_prevent_double_underscores():",
                                "type": "added"
                            },
                            {
                                "content": "    with pytest.raises(ValueError):",
                                "type": "added"
                            },
                            {
                                "content": "        ImageMath.eval(\"1\", {\"__\": None})",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "def test_prevent_builtins():",
                                "type": "added"
                            },
                            {
                                "content": "    with pytest.raises(ValueError):",
                                "type": "added"
                            },
                            {
                                "content": "        ImageMath.eval(\"(lambda: exec('exit()'))()\", {\"exec\": None})",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "def test_logical():",
                                "type": "context"
                            },
                            {
                                "content": "    assert pixel(ImageMath.eval(\"not A\", images)) == 0",
                                "type": "context"
                            },
                            {
                                "content": "    assert pixel(ImageMath.eval(\"A and B\", images)) == \"L 2\"",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [],
                    "added_entities": [
                        {
                            "file_name": "Tests/test_imagemath.py",
                            "type": "function",
                            "name": "test_prevent_builtins",
                            "content": "def test_prevent_builtins():\n    with pytest.raises(ValueError):\n        ImageMath.eval(\"(lambda: exec('exit()'))()\", {\"exec\": None})",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 72,
                            "end_lineno": 74,
                            "parent": null
                        },
                        {
                            "file_name": "Tests/test_imagemath.py",
                            "type": "function",
                            "name": "test_prevent_double_underscores",
                            "content": "def test_prevent_double_underscores():\n    with pytest.raises(ValueError):\n        ImageMath.eval(\"1\", {\"__\": None})",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 67,
                            "end_lineno": 69,
                            "parent": null
                        }
                    ],
                    "deleted_entities": []
                }
            ]
        },
        {
            "old_file_content": "10.2.0\n------\n\nBackwards Incompatible Changes\n==============================\n\nTODO\n^^^^\n\nTODO\n\nDeprecations\n============\n\nImageFile.raise_oserror\n^^^^^^^^^^^^^^^^^^^^^^^\n\n``ImageFile.raise_oserror()`` has been deprecated and will be removed in Pillow\n12.0.0 (2025-10-15). The function is undocumented and is only useful for translating\nerror codes returned by a codec's ``decode()`` method, which ImageFile already does\nautomatically.\n\nTODO\n^^^^\n\nTODO\n\nAPI Changes\n===========\n\nZero or negative font size error\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nWhen creating a :py:class:`~PIL.ImageFont.FreeTypeFont` instance, either directly or\nthrough :py:func:`~PIL.ImageFont.truetype`, if the font size is zero or less, a\n:py:exc:`ValueError` will now be raised.\n\nAPI Additions\n=============\n\nAdded DdsImagePlugin enums\n^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n:py:class:`~PIL.DdsImagePlugin.DDSD`, :py:class:`~PIL.DdsImagePlugin.DDSCAPS`,\n:py:class:`~PIL.DdsImagePlugin.DDSCAPS2`, :py:class:`~PIL.DdsImagePlugin.DDPF`,\n:py:class:`~PIL.DdsImagePlugin.DXGI_FORMAT` and :py:class:`~PIL.DdsImagePlugin.D3DFMT`\nenums have been added to :py:class:`PIL.DdsImagePlugin`.\n\nJPEG restart marker interval\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nWhen saving JPEG files, ``restart_marker_blocks`` and ``restart_marker_rows`` can now\nbe used to emit restart markers whenever the specified number of MCU blocks or rows\nhave been produced.\n\nJPEG tables-only streamtype\n^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nWhen saving JPEG files, ``streamtype`` can now be set to 1, for tables-only. This will\noutput only the quantization and Huffman tables for the image.\n\nSecurity\n========\n\nTODO\n^^^^\n\nTODO\n\nOther Changes\n=============\n\nAdded DDS BC4U and DX10 BC1 and BC4 reading\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nSupport has been added to read the BC4U format of DDS images.\n\nSupport has also been added to read DX10 BC1 and BC4, whether UNORM or\nTYPELESS.\n\nOptimized ImageStat.Stat count and extrema\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nCalculating the :py:attr:`~PIL.ImageStat.Stat.count` and\n:py:attr:`~PIL.ImageStat.Stat.extrema` statistics is now faster. After the\nhistogram is created in ``st = ImageStat.Stat(im)``, ``st.count`` is 3x as fast\non average and ``st.extrema`` is 12x as fast on average.\n\nEncoder errors now report error detail as string\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n:py:exc:`OSError` exceptions from image encoders now include a textual description of\nthe error instead of a numeric error code.\n",
            "new_file_content": "10.2.0\n------\n\nBackwards Incompatible Changes\n==============================\n\nTODO\n^^^^\n\nTODO\n\nDeprecations\n============\n\nImageFile.raise_oserror\n^^^^^^^^^^^^^^^^^^^^^^^\n\n``ImageFile.raise_oserror()`` has been deprecated and will be removed in Pillow\n12.0.0 (2025-10-15). The function is undocumented and is only useful for translating\nerror codes returned by a codec's ``decode()`` method, which ImageFile already does\nautomatically.\n\nTODO\n^^^^\n\nTODO\n\nAPI Changes\n===========\n\nZero or negative font size error\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nWhen creating a :py:class:`~PIL.ImageFont.FreeTypeFont` instance, either directly or\nthrough :py:func:`~PIL.ImageFont.truetype`, if the font size is zero or less, a\n:py:exc:`ValueError` will now be raised.\n\nAPI Additions\n=============\n\nAdded DdsImagePlugin enums\n^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n:py:class:`~PIL.DdsImagePlugin.DDSD`, :py:class:`~PIL.DdsImagePlugin.DDSCAPS`,\n:py:class:`~PIL.DdsImagePlugin.DDSCAPS2`, :py:class:`~PIL.DdsImagePlugin.DDPF`,\n:py:class:`~PIL.DdsImagePlugin.DXGI_FORMAT` and :py:class:`~PIL.DdsImagePlugin.D3DFMT`\nenums have been added to :py:class:`PIL.DdsImagePlugin`.\n\nJPEG restart marker interval\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nWhen saving JPEG files, ``restart_marker_blocks`` and ``restart_marker_rows`` can now\nbe used to emit restart markers whenever the specified number of MCU blocks or rows\nhave been produced.\n\nJPEG tables-only streamtype\n^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nWhen saving JPEG files, ``streamtype`` can now be set to 1, for tables-only. This will\noutput only the quantization and Huffman tables for the image.\n\nSecurity\n========\n\nRestricted environment keys for ImageMath.eval\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n:cve:`2023-50447`: If an attacker has control over the keys passed to the\n``environment`` argument of :py:meth:`PIL.ImageMath.eval`, they may be able to execute\narbitrary code. To prevent this, keys matching the names of builtins and keys\ncontaining double underscores will now raise a :py:exc:`ValueError`.\n\nOther Changes\n=============\n\nAdded DDS BC4U and DX10 BC1 and BC4 reading\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nSupport has been added to read the BC4U format of DDS images.\n\nSupport has also been added to read DX10 BC1 and BC4, whether UNORM or\nTYPELESS.\n\nOptimized ImageStat.Stat count and extrema\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nCalculating the :py:attr:`~PIL.ImageStat.Stat.count` and\n:py:attr:`~PIL.ImageStat.Stat.extrema` statistics is now faster. After the\nhistogram is created in ``st = ImageStat.Stat(im)``, ``st.count`` is 3x as fast\non average and ``st.extrema`` is 12x as fast on average.\n\nEncoder errors now report error detail as string\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n:py:exc:`OSError` exceptions from image encoders now include a textual description of\nthe error instead of a numeric error code.\n",
            "header": {
                "file": {
                    "path": "docs/releasenotes/10.2.0.rst"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "9883f10ba",
                "new_commit_hash": "ade152fcd",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/docs/releasenotes/10.2.0.rst"
            },
            "plus_file": {
                "path": "b/docs/releasenotes/10.2.0.rst"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 62,
                            "length": 10
                        },
                        "new_range": {
                            "start": 62,
                            "length": 13
                        },
                        "section": "output only the quantization and Huffman tables for the image."
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "Security",
                                "type": "context"
                            },
                            {
                                "content": "========",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "TODO",
                                "type": "deleted"
                            },
                            {
                                "content": "^^^^",
                                "type": "deleted"
                            },
                            {
                                "content": "Restricted environment keys for ImageMath.eval",
                                "type": "added"
                            },
                            {
                                "content": "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "TODO",
                                "type": "deleted"
                            },
                            {
                                "content": ":cve:`2023-50447`: If an attacker has control over the keys passed to the",
                                "type": "added"
                            },
                            {
                                "content": "``environment`` argument of :py:meth:`PIL.ImageMath.eval`, they may be able to execute",
                                "type": "added"
                            },
                            {
                                "content": "arbitrary code. To prevent this, keys matching the names of builtins and keys",
                                "type": "added"
                            },
                            {
                                "content": "containing double underscores will now raise a :py:exc:`ValueError`.",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "Other Changes",
                                "type": "context"
                            },
                            {
                                "content": "=============",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [],
                    "added_entities": [],
                    "deleted_entities": []
                }
            ]
        },
        {
            "old_file_content": "#\n# The Python Imaging Library\n# $Id$\n#\n# a simple math add-on for the Python Imaging Library\n#\n# History:\n# 1999-02-15 fl   Original PIL Plus release\n# 2005-05-05 fl   Simplified and cleaned up for PIL 1.1.6\n# 2005-09-12 fl   Fixed int() and float() for Python 2.4.1\n#\n# Copyright (c) 1999-2005 by Secret Labs AB\n# Copyright (c) 2005 by Fredrik Lundh\n#\n# See the README file for information on usage and redistribution.\n#\nfrom __future__ import annotations\n\nimport builtins\n\nfrom . import Image, _imagingmath\n\n\nclass _Operand:\n    \"\"\"Wraps an image operand, providing standard operators\"\"\"\n\n    def __init__(self, im):\n        self.im = im\n\n    def __fixup(self, im1):\n        # convert image to suitable mode\n        if isinstance(im1, _Operand):\n            # argument was an image.\n            if im1.im.mode in (\"1\", \"L\"):\n                return im1.im.convert(\"I\")\n            elif im1.im.mode in (\"I\", \"F\"):\n                return im1.im\n            else:\n                msg = f\"unsupported mode: {im1.im.mode}\"\n                raise ValueError(msg)\n        else:\n            # argument was a constant\n            if isinstance(im1, (int, float)) and self.im.mode in (\"1\", \"L\", \"I\"):\n                return Image.new(\"I\", self.im.size, im1)\n            else:\n                return Image.new(\"F\", self.im.size, im1)\n\n    def apply(self, op, im1, im2=None, mode=None):\n        im1 = self.__fixup(im1)\n        if im2 is None:\n            # unary operation\n            out = Image.new(mode or im1.mode, im1.size, None)\n            im1.load()\n            try:\n                op = getattr(_imagingmath, op + \"_\" + im1.mode)\n            except AttributeError as e:\n                msg = f\"bad operand type for '{op}'\"\n                raise TypeError(msg) from e\n            _imagingmath.unop(op, out.im.id, im1.im.id)\n        else:\n            # binary operation\n            im2 = self.__fixup(im2)\n            if im1.mode != im2.mode:\n                # convert both arguments to floating point\n                if im1.mode != \"F\":\n                    im1 = im1.convert(\"F\")\n                if im2.mode != \"F\":\n                    im2 = im2.convert(\"F\")\n            if im1.size != im2.size:\n                # crop both arguments to a common size\n                size = (min(im1.size[0], im2.size[0]), min(im1.size[1], im2.size[1]))\n                if im1.size != size:\n                    im1 = im1.crop((0, 0) + size)\n                if im2.size != size:\n                    im2 = im2.crop((0, 0) + size)\n            out = Image.new(mode or im1.mode, im1.size, None)\n            im1.load()\n            im2.load()\n            try:\n                op = getattr(_imagingmath, op + \"_\" + im1.mode)\n            except AttributeError as e:\n                msg = f\"bad operand type for '{op}'\"\n                raise TypeError(msg) from e\n            _imagingmath.binop(op, out.im.id, im1.im.id, im2.im.id)\n        return _Operand(out)\n\n    # unary operators\n    def __bool__(self):\n        # an image is \"true\" if it contains at least one non-zero pixel\n        return self.im.getbbox() is not None\n\n    def __abs__(self):\n        return self.apply(\"abs\", self)\n\n    def __pos__(self):\n        return self\n\n    def __neg__(self):\n        return self.apply(\"neg\", self)\n\n    # binary operators\n    def __add__(self, other):\n        return self.apply(\"add\", self, other)\n\n    def __radd__(self, other):\n        return self.apply(\"add\", other, self)\n\n    def __sub__(self, other):\n        return self.apply(\"sub\", self, other)\n\n    def __rsub__(self, other):\n        return self.apply(\"sub\", other, self)\n\n    def __mul__(self, other):\n        return self.apply(\"mul\", self, other)\n\n    def __rmul__(self, other):\n        return self.apply(\"mul\", other, self)\n\n    def __truediv__(self, other):\n        return self.apply(\"div\", self, other)\n\n    def __rtruediv__(self, other):\n        return self.apply(\"div\", other, self)\n\n    def __mod__(self, other):\n        return self.apply(\"mod\", self, other)\n\n    def __rmod__(self, other):\n        return self.apply(\"mod\", other, self)\n\n    def __pow__(self, other):\n        return self.apply(\"pow\", self, other)\n\n    def __rpow__(self, other):\n        return self.apply(\"pow\", other, self)\n\n    # bitwise\n    def __invert__(self):\n        return self.apply(\"invert\", self)\n\n    def __and__(self, other):\n        return self.apply(\"and\", self, other)\n\n    def __rand__(self, other):\n        return self.apply(\"and\", other, self)\n\n    def __or__(self, other):\n        return self.apply(\"or\", self, other)\n\n    def __ror__(self, other):\n        return self.apply(\"or\", other, self)\n\n    def __xor__(self, other):\n        return self.apply(\"xor\", self, other)\n\n    def __rxor__(self, other):\n        return self.apply(\"xor\", other, self)\n\n    def __lshift__(self, other):\n        return self.apply(\"lshift\", self, other)\n\n    def __rshift__(self, other):\n        return self.apply(\"rshift\", self, other)\n\n    # logical\n    def __eq__(self, other):\n        return self.apply(\"eq\", self, other)\n\n    def __ne__(self, other):\n        return self.apply(\"ne\", self, other)\n\n    def __lt__(self, other):\n        return self.apply(\"lt\", self, other)\n\n    def __le__(self, other):\n        return self.apply(\"le\", self, other)\n\n    def __gt__(self, other):\n        return self.apply(\"gt\", self, other)\n\n    def __ge__(self, other):\n        return self.apply(\"ge\", self, other)\n\n\n# conversions\ndef imagemath_int(self):\n    return _Operand(self.im.convert(\"I\"))\n\n\ndef imagemath_float(self):\n    return _Operand(self.im.convert(\"F\"))\n\n\n# logical\ndef imagemath_equal(self, other):\n    return self.apply(\"eq\", self, other, mode=\"I\")\n\n\ndef imagemath_notequal(self, other):\n    return self.apply(\"ne\", self, other, mode=\"I\")\n\n\ndef imagemath_min(self, other):\n    return self.apply(\"min\", self, other)\n\n\ndef imagemath_max(self, other):\n    return self.apply(\"max\", self, other)\n\n\ndef imagemath_convert(self, mode):\n    return _Operand(self.im.convert(mode))\n\n\nops = {}\nfor k, v in list(globals().items()):\n    if k[:10] == \"imagemath_\":\n        ops[k[10:]] = v\n\n\ndef eval(expression, _dict={}, **kw):\n    \"\"\"\n    Evaluates an image expression.\n\n    :param expression: A string containing a Python-style expression.\n    :param options: Values to add to the evaluation context.  You\n                    can either use a dictionary, or one or more keyword\n                    arguments.\n    :return: The evaluated expression. This is usually an image object, but can\n             also be an integer, a floating point value, or a pixel tuple,\n             depending on the expression.\n    \"\"\"\n\n    # build execution namespace\n    args = ops.copy()\n    args.update(_dict)\n    args.update(kw)\n    for k, v in args.items():\n        if hasattr(v, \"im\"):\n            args[k] = _Operand(v)\n\n    compiled_code = compile(expression, \"<string>\", \"eval\")\n\n    def scan(code):\n        for const in code.co_consts:\n            if type(const) is type(compiled_code):\n                scan(const)\n\n        for name in code.co_names:\n            if name not in args and name != \"abs\":\n                msg = f\"'{name}' not allowed\"\n                raise ValueError(msg)\n\n    scan(compiled_code)\n    out = builtins.eval(expression, {\"__builtins\": {\"abs\": abs}}, args)\n    try:\n        return out.im\n    except AttributeError:\n        return out\n",
            "new_file_content": "#\n# The Python Imaging Library\n# $Id$\n#\n# a simple math add-on for the Python Imaging Library\n#\n# History:\n# 1999-02-15 fl   Original PIL Plus release\n# 2005-05-05 fl   Simplified and cleaned up for PIL 1.1.6\n# 2005-09-12 fl   Fixed int() and float() for Python 2.4.1\n#\n# Copyright (c) 1999-2005 by Secret Labs AB\n# Copyright (c) 2005 by Fredrik Lundh\n#\n# See the README file for information on usage and redistribution.\n#\nfrom __future__ import annotations\n\nimport builtins\n\nfrom . import Image, _imagingmath\n\n\nclass _Operand:\n    \"\"\"Wraps an image operand, providing standard operators\"\"\"\n\n    def __init__(self, im):\n        self.im = im\n\n    def __fixup(self, im1):\n        # convert image to suitable mode\n        if isinstance(im1, _Operand):\n            # argument was an image.\n            if im1.im.mode in (\"1\", \"L\"):\n                return im1.im.convert(\"I\")\n            elif im1.im.mode in (\"I\", \"F\"):\n                return im1.im\n            else:\n                msg = f\"unsupported mode: {im1.im.mode}\"\n                raise ValueError(msg)\n        else:\n            # argument was a constant\n            if isinstance(im1, (int, float)) and self.im.mode in (\"1\", \"L\", \"I\"):\n                return Image.new(\"I\", self.im.size, im1)\n            else:\n                return Image.new(\"F\", self.im.size, im1)\n\n    def apply(self, op, im1, im2=None, mode=None):\n        im1 = self.__fixup(im1)\n        if im2 is None:\n            # unary operation\n            out = Image.new(mode or im1.mode, im1.size, None)\n            im1.load()\n            try:\n                op = getattr(_imagingmath, op + \"_\" + im1.mode)\n            except AttributeError as e:\n                msg = f\"bad operand type for '{op}'\"\n                raise TypeError(msg) from e\n            _imagingmath.unop(op, out.im.id, im1.im.id)\n        else:\n            # binary operation\n            im2 = self.__fixup(im2)\n            if im1.mode != im2.mode:\n                # convert both arguments to floating point\n                if im1.mode != \"F\":\n                    im1 = im1.convert(\"F\")\n                if im2.mode != \"F\":\n                    im2 = im2.convert(\"F\")\n            if im1.size != im2.size:\n                # crop both arguments to a common size\n                size = (min(im1.size[0], im2.size[0]), min(im1.size[1], im2.size[1]))\n                if im1.size != size:\n                    im1 = im1.crop((0, 0) + size)\n                if im2.size != size:\n                    im2 = im2.crop((0, 0) + size)\n            out = Image.new(mode or im1.mode, im1.size, None)\n            im1.load()\n            im2.load()\n            try:\n                op = getattr(_imagingmath, op + \"_\" + im1.mode)\n            except AttributeError as e:\n                msg = f\"bad operand type for '{op}'\"\n                raise TypeError(msg) from e\n            _imagingmath.binop(op, out.im.id, im1.im.id, im2.im.id)\n        return _Operand(out)\n\n    # unary operators\n    def __bool__(self):\n        # an image is \"true\" if it contains at least one non-zero pixel\n        return self.im.getbbox() is not None\n\n    def __abs__(self):\n        return self.apply(\"abs\", self)\n\n    def __pos__(self):\n        return self\n\n    def __neg__(self):\n        return self.apply(\"neg\", self)\n\n    # binary operators\n    def __add__(self, other):\n        return self.apply(\"add\", self, other)\n\n    def __radd__(self, other):\n        return self.apply(\"add\", other, self)\n\n    def __sub__(self, other):\n        return self.apply(\"sub\", self, other)\n\n    def __rsub__(self, other):\n        return self.apply(\"sub\", other, self)\n\n    def __mul__(self, other):\n        return self.apply(\"mul\", self, other)\n\n    def __rmul__(self, other):\n        return self.apply(\"mul\", other, self)\n\n    def __truediv__(self, other):\n        return self.apply(\"div\", self, other)\n\n    def __rtruediv__(self, other):\n        return self.apply(\"div\", other, self)\n\n    def __mod__(self, other):\n        return self.apply(\"mod\", self, other)\n\n    def __rmod__(self, other):\n        return self.apply(\"mod\", other, self)\n\n    def __pow__(self, other):\n        return self.apply(\"pow\", self, other)\n\n    def __rpow__(self, other):\n        return self.apply(\"pow\", other, self)\n\n    # bitwise\n    def __invert__(self):\n        return self.apply(\"invert\", self)\n\n    def __and__(self, other):\n        return self.apply(\"and\", self, other)\n\n    def __rand__(self, other):\n        return self.apply(\"and\", other, self)\n\n    def __or__(self, other):\n        return self.apply(\"or\", self, other)\n\n    def __ror__(self, other):\n        return self.apply(\"or\", other, self)\n\n    def __xor__(self, other):\n        return self.apply(\"xor\", self, other)\n\n    def __rxor__(self, other):\n        return self.apply(\"xor\", other, self)\n\n    def __lshift__(self, other):\n        return self.apply(\"lshift\", self, other)\n\n    def __rshift__(self, other):\n        return self.apply(\"rshift\", self, other)\n\n    # logical\n    def __eq__(self, other):\n        return self.apply(\"eq\", self, other)\n\n    def __ne__(self, other):\n        return self.apply(\"ne\", self, other)\n\n    def __lt__(self, other):\n        return self.apply(\"lt\", self, other)\n\n    def __le__(self, other):\n        return self.apply(\"le\", self, other)\n\n    def __gt__(self, other):\n        return self.apply(\"gt\", self, other)\n\n    def __ge__(self, other):\n        return self.apply(\"ge\", self, other)\n\n\n# conversions\ndef imagemath_int(self):\n    return _Operand(self.im.convert(\"I\"))\n\n\ndef imagemath_float(self):\n    return _Operand(self.im.convert(\"F\"))\n\n\n# logical\ndef imagemath_equal(self, other):\n    return self.apply(\"eq\", self, other, mode=\"I\")\n\n\ndef imagemath_notequal(self, other):\n    return self.apply(\"ne\", self, other, mode=\"I\")\n\n\ndef imagemath_min(self, other):\n    return self.apply(\"min\", self, other)\n\n\ndef imagemath_max(self, other):\n    return self.apply(\"max\", self, other)\n\n\ndef imagemath_convert(self, mode):\n    return _Operand(self.im.convert(mode))\n\n\nops = {}\nfor k, v in list(globals().items()):\n    if k[:10] == \"imagemath_\":\n        ops[k[10:]] = v\n\n\ndef eval(expression, _dict={}, **kw):\n    \"\"\"\n    Evaluates an image expression.\n\n    :param expression: A string containing a Python-style expression.\n    :param options: Values to add to the evaluation context.  You\n                    can either use a dictionary, or one or more keyword\n                    arguments.\n    :return: The evaluated expression. This is usually an image object, but can\n             also be an integer, a floating point value, or a pixel tuple,\n             depending on the expression.\n    \"\"\"\n\n    # build execution namespace\n    args = ops.copy()\n    for k in list(_dict.keys()) + list(kw.keys()):\n        if \"__\" in k or hasattr(builtins, k):\n            msg = f\"'{k}' not allowed\"\n            raise ValueError(msg)\n\n    args.update(_dict)\n    args.update(kw)\n    for k, v in args.items():\n        if hasattr(v, \"im\"):\n            args[k] = _Operand(v)\n\n    compiled_code = compile(expression, \"<string>\", \"eval\")\n\n    def scan(code):\n        for const in code.co_consts:\n            if type(const) is type(compiled_code):\n                scan(const)\n\n        for name in code.co_names:\n            if name not in args and name != \"abs\":\n                msg = f\"'{name}' not allowed\"\n                raise ValueError(msg)\n\n    scan(compiled_code)\n    out = builtins.eval(expression, {\"__builtins\": {\"abs\": abs}}, args)\n    try:\n        return out.im\n    except AttributeError:\n        return out\n",
            "header": {
                "file": {
                    "path": "src/PIL/ImageMath.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "7ca512e75",
                "new_commit_hash": "b77f4bce5",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/src/PIL/ImageMath.py"
            },
            "plus_file": {
                "path": "b/src/PIL/ImageMath.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 234,
                            "length": 6
                        },
                        "new_range": {
                            "start": 234,
                            "length": 11
                        },
                        "section": "def eval(expression, _dict={}, **kw):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    # build execution namespace",
                                "type": "context"
                            },
                            {
                                "content": "    args = ops.copy()",
                                "type": "context"
                            },
                            {
                                "content": "    for k in list(_dict.keys()) + list(kw.keys()):",
                                "type": "added"
                            },
                            {
                                "content": "        if \"__\" in k or hasattr(builtins, k):",
                                "type": "added"
                            },
                            {
                                "content": "            msg = f\"'{k}' not allowed\"",
                                "type": "added"
                            },
                            {
                                "content": "            raise ValueError(msg)",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "    args.update(_dict)",
                                "type": "context"
                            },
                            {
                                "content": "    args.update(kw)",
                                "type": "context"
                            },
                            {
                                "content": "    for k, v in args.items():",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "src/PIL/ImageMath.py",
                            "type": "function",
                            "name": "eval",
                            "content": "def eval(expression, _dict={}, **kw):\n    \"\"\"\n    Evaluates an image expression.\n\n    :param expression: A string containing a Python-style expression.\n    :param options: Values to add to the evaluation context.  You\n                    can either use a dictionary, or one or more keyword\n                    arguments.\n    :return: The evaluated expression. This is usually an image object, but can\n             also be an integer, a floating point value, or a pixel tuple,\n             depending on the expression.\n    \"\"\"\n\n    # build execution namespace\n    args = ops.copy()\n    for k in list(_dict.keys()) + list(kw.keys()):\n        if \"__\" in k or hasattr(builtins, k):\n            msg = f\"'{k}' not allowed\"\n            raise ValueError(msg)\n\n    args.update(_dict)\n    args.update(kw)\n    for k, v in args.items():\n        if hasattr(v, \"im\"):\n            args[k] = _Operand(v)\n\n    compiled_code = compile(expression, \"<string>\", \"eval\")\n\n    def scan(code):\n        for const in code.co_consts:\n            if type(const) is type(compiled_code):\n                scan(const)\n\n        for name in code.co_names:\n            if name not in args and name != \"abs\":\n                msg = f\"'{name}' not allowed\"\n                raise ValueError(msg)\n\n    scan(compiled_code)\n    out = builtins.eval(expression, {\"__builtins\": {\"abs\": abs}}, args)\n    try:\n        return out.im\n    except AttributeError:\n        return out",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 222,
                            "end_lineno": 265,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                }
            ]
        }
    ],
    "old_commit_hash": "02c6183d41c68a8dd080f5739f566bd82485822d^",
    "new_commit_hash": "02c6183d41c68a8dd080f5739f566bd82485822d",
    "commit_message": "Merge pull request #7655 from radarhere/imagemath_eval_keys\n\nRestricted environment keys for ImageMath.eval()",
    "commit_date": "2023-12-30T10:23:50+11:00",
    "metadata": {}
}
{
    "repo_name": "pillow",
    "new_commit_hash": "02c6183d41c68a8dd080f5739f566bd82485822d",
    "test_file_codes": [
        "from __future__ import annotations\nimport pytest\n\nfrom PIL import Image, ImageMath\n\n\ndef pixel(im):\n    if hasattr(im, \"im\"):\n        return f\"{im.mode} {repr(im.getpixel((0, 0)))}\"\n    if isinstance(im, int):\n        return int(im)  # hack to deal with booleans\n\n\nA = Image.new(\"L\", (1, 1), 1)\nB = Image.new(\"L\", (1, 1), 2)\nZ = Image.new(\"L\", (1, 1), 0)  # Z for zero\nF = Image.new(\"F\", (1, 1), 3)\nI = Image.new(\"I\", (1, 1), 4)  # noqa: E741\n\nA2 = A.resize((2, 2))\nB2 = B.resize((2, 2))\n\nimages = {\"A\": A, \"B\": B, \"F\": F, \"I\": I}\n\n\ndef test_sanity():\n    assert ImageMath.eval(\"1\") == 1\n    assert ImageMath.eval(\"1+A\", A=2) == 3\n    assert pixel(ImageMath.eval(\"A+B\", A=A, B=B)) == \"I 3\"\n    assert pixel(ImageMath.eval(\"A+B\", images)) == \"I 3\"\n    assert pixel(ImageMath.eval(\"float(A)+B\", images)) == \"F 3.0\"\n    assert pixel(ImageMath.eval(\"int(float(A)+B)\", images)) == \"I 3\"\n\n\ndef test_ops():\n    assert pixel(ImageMath.eval(\"-A\", images)) == \"I -1\"\n    assert pixel(ImageMath.eval(\"+B\", images)) == \"L 2\"\n\n    assert pixel(ImageMath.eval(\"A+B\", images)) == \"I 3\"\n    assert pixel(ImageMath.eval(\"A-B\", images)) == \"I -1\"\n    assert pixel(ImageMath.eval(\"A*B\", images)) == \"I 2\"\n    assert pixel(ImageMath.eval(\"A/B\", images)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"B**2\", images)) == \"I 4\"\n    assert pixel(ImageMath.eval(\"B**33\", images)) == \"I 2147483647\"\n\n    assert pixel(ImageMath.eval(\"float(A)+B\", images)) == \"F 3.0\"\n    assert pixel(ImageMath.eval(\"float(A)-B\", images)) == \"F -1.0\"\n    assert pixel(ImageMath.eval(\"float(A)*B\", images)) == \"F 2.0\"\n    assert pixel(ImageMath.eval(\"float(A)/B\", images)) == \"F 0.5\"\n    assert pixel(ImageMath.eval(\"float(B)**2\", images)) == \"F 4.0\"\n    assert pixel(ImageMath.eval(\"float(B)**33\", images)) == \"F 8589934592.0\"\n\n\n@pytest.mark.parametrize(\n    \"expression\",\n    (\n        \"exec('pass')\",\n        \"(lambda: exec('pass'))()\",\n        \"(lambda: (lambda: exec('pass'))())()\",\n    ),\n)\ndef test_prevent_exec(expression):\n    with pytest.raises(ValueError):\n        ImageMath.eval(expression)\n\n\ndef test_prevent_double_underscores():\n    with pytest.raises(ValueError):\n        ImageMath.eval(\"1\", {\"__\": None})\n\n\ndef test_prevent_builtins():\n    with pytest.raises(ValueError):\n        ImageMath.eval(\"(lambda: exec('exit()'))()\", {\"exec\": None})\n\n\ndef test_logical():\n    assert pixel(ImageMath.eval(\"not A\", images)) == 0\n    assert pixel(ImageMath.eval(\"A and B\", images)) == \"L 2\"\n    assert pixel(ImageMath.eval(\"A or B\", images)) == \"L 1\"\n\n\ndef test_convert():\n    assert pixel(ImageMath.eval(\"convert(A+B, 'L')\", images)) == \"L 3\"\n    assert pixel(ImageMath.eval(\"convert(A+B, '1')\", images)) == \"1 0\"\n    assert pixel(ImageMath.eval(\"convert(A+B, 'RGB')\", images)) == \"RGB (3, 3, 3)\"\n\n\ndef test_compare():\n    assert pixel(ImageMath.eval(\"min(A, B)\", images)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"max(A, B)\", images)) == \"I 2\"\n    assert pixel(ImageMath.eval(\"A == 1\", images)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"A == 2\", images)) == \"I 0\"\n\n\ndef test_one_image_larger():\n    assert pixel(ImageMath.eval(\"A+B\", A=A2, B=B)) == \"I 3\"\n    assert pixel(ImageMath.eval(\"A+B\", A=A, B=B2)) == \"I 3\"\n\n\ndef test_abs():\n    assert pixel(ImageMath.eval(\"abs(A)\", A=A)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"abs(B)\", B=B)) == \"I 2\"\n\n\ndef test_binary_mod():\n    assert pixel(ImageMath.eval(\"A%A\", A=A)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"B%B\", B=B)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"A%B\", A=A, B=B)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"B%A\", A=A, B=B)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"Z%A\", A=A, Z=Z)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"Z%B\", B=B, Z=Z)) == \"I 0\"\n\n\ndef test_bitwise_invert():\n    assert pixel(ImageMath.eval(\"~Z\", Z=Z)) == \"I -1\"\n    assert pixel(ImageMath.eval(\"~A\", A=A)) == \"I -2\"\n    assert pixel(ImageMath.eval(\"~B\", B=B)) == \"I -3\"\n\n\ndef test_bitwise_and():\n    assert pixel(ImageMath.eval(\"Z&Z\", A=A, Z=Z)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"Z&A\", A=A, Z=Z)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"A&Z\", A=A, Z=Z)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"A&A\", A=A, Z=Z)) == \"I 1\"\n\n\ndef test_bitwise_or():\n    assert pixel(ImageMath.eval(\"Z|Z\", A=A, Z=Z)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"Z|A\", A=A, Z=Z)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"A|Z\", A=A, Z=Z)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"A|A\", A=A, Z=Z)) == \"I 1\"\n\n\ndef test_bitwise_xor():\n    assert pixel(ImageMath.eval(\"Z^Z\", A=A, Z=Z)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"Z^A\", A=A, Z=Z)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"A^Z\", A=A, Z=Z)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"A^A\", A=A, Z=Z)) == \"I 0\"\n\n\ndef test_bitwise_leftshift():\n    assert pixel(ImageMath.eval(\"Z<<0\", Z=Z)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"Z<<1\", Z=Z)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"A<<0\", A=A)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"A<<1\", A=A)) == \"I 2\"\n\n\ndef test_bitwise_rightshift():\n    assert pixel(ImageMath.eval(\"Z>>0\", Z=Z)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"Z>>1\", Z=Z)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"A>>0\", A=A)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"A>>1\", A=A)) == \"I 0\"\n\n\ndef test_logical_eq():\n    assert pixel(ImageMath.eval(\"A==A\", A=A)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"B==B\", B=B)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"A==B\", A=A, B=B)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"B==A\", A=A, B=B)) == \"I 0\"\n\n\ndef test_logical_ne():\n    assert pixel(ImageMath.eval(\"A!=A\", A=A)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"B!=B\", B=B)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"A!=B\", A=A, B=B)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"B!=A\", A=A, B=B)) == \"I 1\"\n\n\ndef test_logical_lt():\n    assert pixel(ImageMath.eval(\"A<A\", A=A)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"B<B\", B=B)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"A<B\", A=A, B=B)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"B<A\", A=A, B=B)) == \"I 0\"\n\n\ndef test_logical_le():\n    assert pixel(ImageMath.eval(\"A<=A\", A=A)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"B<=B\", B=B)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"A<=B\", A=A, B=B)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"B<=A\", A=A, B=B)) == \"I 0\"\n\n\ndef test_logical_gt():\n    assert pixel(ImageMath.eval(\"A>A\", A=A)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"B>B\", B=B)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"A>B\", A=A, B=B)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"B>A\", A=A, B=B)) == \"I 1\"\n\n\ndef test_logical_ge():\n    assert pixel(ImageMath.eval(\"A>=A\", A=A)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"B>=B\", B=B)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"A>=B\", A=A, B=B)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"B>=A\", A=A, B=B)) == \"I 1\"\n\n\ndef test_logical_equal():\n    assert pixel(ImageMath.eval(\"equal(A, A)\", A=A)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"equal(B, B)\", B=B)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"equal(Z, Z)\", Z=Z)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"equal(A, B)\", A=A, B=B)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"equal(B, A)\", A=A, B=B)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"equal(A, Z)\", A=A, Z=Z)) == \"I 0\"\n\n\ndef test_logical_not_equal():\n    assert pixel(ImageMath.eval(\"notequal(A, A)\", A=A)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"notequal(B, B)\", B=B)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"notequal(Z, Z)\", Z=Z)) == \"I 0\"\n    assert pixel(ImageMath.eval(\"notequal(A, B)\", A=A, B=B)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"notequal(B, A)\", A=A, B=B)) == \"I 1\"\n    assert pixel(ImageMath.eval(\"notequal(A, Z)\", A=A, Z=Z)) == \"I 1\"\n"
    ],
    "test_file_names": [
        "test_1.py"
    ],
    "setup_res_code": 0,
    "setup_res_stdout": "Starting Pillow installation attempts...\nVerifying Pillow installation...\n✅ Pillow installation successful!\nSuccessfully installed Pillow\n",
    "setup_res_stderr": "warning: `VIRTUAL_ENV=/home/gcpuser/r2e-edits-internal/.venv` does not match the project environment path `.venv` and will be ignored\nUsing CPython 3.9.21\nCreating virtual environment at: .venv\nActivate with: source .venv/bin/activate\nResolved 12 packages in 23ms\nInstalled 12 packages in 101ms\n + coverage==7.6.10\n + exceptiongroup==1.2.2\n + iniconfig==2.0.0\n + packaging==24.2\n + pluggy==1.5.0\n + pyqt5==5.15.11\n + pyqt5-qt5==5.15.16\n + pyqt5-sip==12.16.1\n + pytest==8.3.4\n + pytest-cov==6.0.0\n + setuptools==75.8.0\n + tomli==2.2.1\nResolved 1 package in 915ms\nPrepared 1 package in 32.13s\nInstalled 1 package in 8ms\n + pillow==10.2.0.dev0 (from file:///home/gcpuser/buckets/local_repoeval_bucket/repos/pillow_02c6183d41c68a8dd080f5739f566bd82485822d)\n",
    "new_commit_res_code": 0,
    "new_commit_res_stdout": "\u001b[1m============================= test session starts ==============================\u001b[0m\nplatform linux -- Python 3.9.21, pytest-8.3.4, pluggy-1.5.0\nrootdir: /home/gcpuser/buckets/local_repoeval_bucket/repos/pillow_02c6183d41c68a8dd080f5739f566bd82485822d\nconfigfile: pyproject.toml\nplugins: cov-6.0.0\ncollected 27 items\n\nr2e_tests/test_1.py \u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m                          [100%]\u001b[0m\n\n==================================== PASSES ====================================\n\u001b[36m\u001b[1m=========================== short test summary info ============================\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_sanity\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ops\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_prevent_exec[exec('pass')]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_prevent_exec[(lambda: exec('pass'))()]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_prevent_exec[(lambda: (lambda: exec('pass'))())()]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_prevent_double_underscores\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_prevent_builtins\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_logical\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_convert\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_compare\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_one_image_larger\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_abs\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_binary_mod\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_bitwise_invert\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_bitwise_and\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_bitwise_or\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_bitwise_xor\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_bitwise_leftshift\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_bitwise_rightshift\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_logical_eq\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_logical_ne\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_logical_lt\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_logical_le\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_logical_gt\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_logical_ge\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_logical_equal\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_logical_not_equal\u001b[0m\n\u001b[32m============================== \u001b[32m\u001b[1m27 passed\u001b[0m\u001b[32m in 0.16s\u001b[0m\u001b[32m ==============================\u001b[0m\n",
    "new_commit_res_stderr": "",
    "old_commit_res_code": 1,
    "old_commit_res_stdout": "\u001b[1m============================= test session starts ==============================\u001b[0m\nplatform linux -- Python 3.9.21, pytest-8.3.4, pluggy-1.5.0\nrootdir: /home/gcpuser/buckets/local_repoeval_bucket/repos/pillow_02c6183d41c68a8dd080f5739f566bd82485822d\nconfigfile: pyproject.toml\nplugins: cov-6.0.0\ncollected 27 items\n\nr2e_tests/test_1.py \u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[31mF\u001b[0m\u001b[31mF\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[31m                          [100%]\u001b[0m\n\n=================================== FAILURES ===================================\n\u001b[31m\u001b[1m_______________________ test_prevent_double_underscores ________________________\u001b[0m\n\n    def test_prevent_double_underscores():\n        with pytest.raises(ValueError):\n>           ImageMath.eval(\"1\", {\"__\": None})\n\u001b[1m\u001b[31mE           Failed: DID NOT RAISE <class 'ValueError'>\u001b[0m\n\n\u001b[1m\u001b[31mr2e_tests/test_1.py\u001b[0m:69: Failed\n\u001b[31m\u001b[1m____________________________ test_prevent_builtins _____________________________\u001b[0m\n\n    def test_prevent_builtins():\n        with pytest.raises(ValueError):\n>           ImageMath.eval(\"(lambda: exec('exit()'))()\", {\"exec\": None})\n\n\u001b[1m\u001b[31mr2e_tests/test_1.py\u001b[0m:74: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\u001b[1m\u001b[31msrc/PIL/ImageMath.py\u001b[0m:256: in eval\n    out = builtins.eval(expression, {\"__builtins\": {\"abs\": abs}}, args)\n\u001b[1m\u001b[31m<string>\u001b[0m:1: in <module>\n    ???\n\u001b[1m\u001b[31m<string>\u001b[0m:1: in <lambda>\n    ???\n\u001b[1m\u001b[31m<string>\u001b[0m:1: in <module>\n    ???\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nself = Use exit() or Ctrl-D (i.e. EOF) to exit, code = None\n\n    def __call__(self, code=None):\n        # Shells like IDLE catch the SystemExit, but listen when their\n        # stdin wrapper is closed.\n        try:\n            sys.stdin.close()\n        except:\n            pass\n>       raise SystemExit(code)\n\u001b[1m\u001b[31mE       SystemExit: None\u001b[0m\n\n\u001b[1m\u001b[31m../../../../.local/share/uv/python/cpython-3.9.21-linux-x86_64-gnu/lib/python3.9/_sitebuiltins.py\u001b[0m:26: SystemExit\n==================================== PASSES ====================================\n\u001b[36m\u001b[1m=========================== short test summary info ============================\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_sanity\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ops\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_prevent_exec[exec('pass')]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_prevent_exec[(lambda: exec('pass'))()]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_prevent_exec[(lambda: (lambda: exec('pass'))())()]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_logical\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_convert\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_compare\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_one_image_larger\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_abs\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_binary_mod\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_bitwise_invert\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_bitwise_and\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_bitwise_or\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_bitwise_xor\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_bitwise_leftshift\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_bitwise_rightshift\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_logical_eq\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_logical_ne\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_logical_lt\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_logical_le\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_logical_gt\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_logical_ge\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_logical_equal\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_logical_not_equal\u001b[0m\n\u001b[31mFAILED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_prevent_double_underscores\u001b[0m - Failed: DID NOT RAISE <class 'ValueError'>\n\u001b[31mFAILED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_prevent_builtins\u001b[0m - SystemExit: None\n\u001b[31m========================= \u001b[31m\u001b[1m2 failed\u001b[0m, \u001b[32m25 passed\u001b[0m\u001b[31m in 0.10s\u001b[0m\u001b[31m =========================\u001b[0m\n",
    "old_commit_res_stderr": ""
}
Tests/test_imagemath.py docs/releasenotes/10.2.0.rst src/PIL/ImageMath.py
We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[ISSUE]
**Title:** ImageMath.eval() Allows Restricted Keys with Double Underscores and Built-in Names

**Description:**
When using `ImageMath.eval()`, providing environment keys that contain double underscores (`__`) or match Python's built-in names does not trigger the expected restrictions. This behavior can lead to unintended side effects or potential security vulnerabilities.

**Example Code:**
```python
from PIL import ImageMath

# Attempt to use a key with double underscores
ImageMath.eval("1", {"__": None})

# Attempt to use a built-in function name as a key
ImageMath.eval("(lambda: exec('exit()'))()", {"exec": None})
```

**Expected Behavior:**
The `ImageMath.eval()` function should restrict the use of environment keys that contain double underscores or clash with Python's built-in names. When such keys are provided, the function should raise a `ValueError` with a message indicating that the key is not allowed.

**Actual Behavior:**
- When a key with double underscores (`"__"`) is used, `ImageMath.eval()` does not raise any error, allowing the key to be processed.
- When a built-in name like `"exec"` is used as a key, instead of raising a `ValueError`, the function raises a `SystemExit` error.

This lack of proper restriction can lead to unexpected behavior and potential security issues, as malicious keys might exploit the evaluation environment.

[/ISSUE]
--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.

--- BEGIN FILE ---
```
### src/PIL/ImageMath.py
#
# The Python Imaging Library
# $Id$
#
# a simple math add-on for the Python Imaging Library
#
# History:
# 1999-02-15 fl   Original PIL Plus release
# 2005-05-05 fl   Simplified and cleaned up for PIL 1.1.6
# 2005-09-12 fl   Fixed int() and float() for Python 2.4.1
#
# Copyright (c) 1999-2005 by Secret Labs AB
# Copyright (c) 2005 by Fredrik Lundh
#
# See the README file for information on usage and redistribution.
#
from __future__ import annotations

import builtins

from . import Image, _imagingmath


class _Operand:
    """Wraps an image operand, providing standard operators"""

    def __init__(self, im):
        self.im = im

    def __fixup(self, im1):
        # convert image to suitable mode
        if isinstance(im1, _Operand):
            # argument was an image.
            if im1.im.mode in ("1", "L"):
                return im1.im.convert("I")
            elif im1.im.mode in ("I", "F"):
                return im1.im
            else:
                msg = f"unsupported mode: {im1.im.mode}"
                raise ValueError(msg)
        else:
            # argument was a constant
            if isinstance(im1, (int, float)) and self.im.mode in ("1", "L", "I"):
                return Image.new("I", self.im.size, im1)
            else:
                return Image.new("F", self.im.size, im1)

    def apply(self, op, im1, im2=None, mode=None):
        im1 = self.__fixup(im1)
        if im2 is None:
            # unary operation
            out = Image.new(mode or im1.mode, im1.size, None)
            im1.load()
            try:
                op = getattr(_imagingmath, op + "_" + im1.mode)
            except AttributeError as e:
                msg = f"bad operand type for '{op}'"
                raise TypeError(msg) from e
            _imagingmath.unop(op, out.im.id, im1.im.id)
        else:
            # binary operation
            im2 = self.__fixup(im2)
            if im1.mode != im2.mode:
                # convert both arguments to floating point
                if im1.mode != "F":
                    im1 = im1.convert("F")
                if im2.mode != "F":
                    im2 = im2.convert("F")
            if im1.size != im2.size:
                # crop both arguments to a common size
                size = (min(im1.size[0], im2.size[0]), min(im1.size[1], im2.size[1]))
                if im1.size != size:
                    im1 = im1.crop((0, 0) + size)
                if im2.size != size:
                    im2 = im2.crop((0, 0) + size)
            out = Image.new(mode or im1.mode, im1.size, None)
            im1.load()
            im2.load()
            try:
                op = getattr(_imagingmath, op + "_" + im1.mode)
            except AttributeError as e:
                msg = f"bad operand type for '{op}'"
                raise TypeError(msg) from e
            _imagingmath.binop(op, out.im.id, im1.im.id, im2.im.id)
        return _Operand(out)

    # unary operators
    def __bool__(self):
        # an image is "true" if it contains at least one non-zero pixel
        return self.im.getbbox() is not None

    def __abs__(self):
        return self.apply("abs", self)

    def __pos__(self):
        return self

    def __neg__(self):
        return self.apply("neg", self)

    # binary operators
    def __add__(self, other):
        return self.apply("add", self, other)

    def __radd__(self, other):
        return self.apply("add", other, self)

    def __sub__(self, other):
        return self.apply("sub", self, other)

    def __rsub__(self, other):
        return self.apply("sub", other, self)

    def __mul__(self, other):
        return self.apply("mul", self, other)

    def __rmul__(self, other):
        return self.apply("mul", other, self)

    def __truediv__(self, other):
        return self.apply("div", self, other)

    def __rtruediv__(self, other):
        return self.apply("div", other, self)

    def __mod__(self, other):
        return self.apply("mod", self, other)

    def __rmod__(self, other):
        return self.apply("mod", other, self)

    def __pow__(self, other):
        return self.apply("pow", self, other)

    def __rpow__(self, other):
        return self.apply("pow", other, self)

    # bitwise
    def __invert__(self):
        return self.apply("invert", self)

    def __and__(self, other):
        return self.apply("and", self, other)

    def __rand__(self, other):
        return self.apply("and", other, self)

    def __or__(self, other):
        return self.apply("or", self, other)

    def __ror__(self, other):
        return self.apply("or", other, self)

    def __xor__(self, other):
        return self.apply("xor", self, other)

    def __rxor__(self, other):
        return self.apply("xor", other, self)

    def __lshift__(self, other):
        return self.apply("lshift", self, other)

    def __rshift__(self, other):
        return self.apply("rshift", self, other)

    # logical
    def __eq__(self, other):
        return self.apply("eq", self, other)

    def __ne__(self, other):
        return self.apply("ne", self, other)

    def __lt__(self, other):
        return self.apply("lt", self, other)

    def __le__(self, other):
        return self.apply("le", self, other)

    def __gt__(self, other):
        return self.apply("gt", self, other)

    def __ge__(self, other):
        return self.apply("ge", self, other)


# conversions
def imagemath_int(self):
    return _Operand(self.im.convert("I"))


def imagemath_float(self):
    return _Operand(self.im.convert("F"))


# logical
def imagemath_equal(self, other):
    return self.apply("eq", self, other, mode="I")


def imagemath_notequal(self, other):
    return self.apply("ne", self, other, mode="I")


def imagemath_min(self, other):
    return self.apply("min", self, other)


def imagemath_max(self, other):
    return self.apply("max", self, other)


def imagemath_convert(self, mode):
    return _Operand(self.im.convert(mode))


ops = {}
for k, v in list(globals().items()):
    if k[:10] == "imagemath_":
        ops[k[10:]] = v


def eval(expression, _dict={}, **kw):
    """
    Evaluates an image expression.

    :param expression: A string containing a Python-style expression.
    :param options: Values to add to the evaluation context.  You
                    can either use a dictionary, or one or more keyword
                    arguments.
    :return: The evaluated expression. This is usually an image object, but can
             also be an integer, a floating point value, or a pixel tuple,
             depending on the expression.
    """

    # build execution namespace
    args = ops.copy()
    args.update(_dict)
    args.update(kw)
    for k, v in args.items():
        if hasattr(v, "im"):
            args[k] = _Operand(v)

    compiled_code = compile(expression, "<string>", "eval")

    def scan(code):
        for const in code.co_consts:
            if type(const) is type(compiled_code):
                scan(const)

        for name in code.co_names:
            if name not in args and name != "abs":
                msg = f"'{name}' not allowed"
                raise ValueError(msg)

    scan(compiled_code)
    out = builtins.eval(expression, {"__builtins": {"abs": abs}}, args)
    try:
        return out.im
    except AttributeError:
        return out

```
--- END FILE ---

Please first localize the bug based on the issue statement, and then generate *SEARCH/REPLACE* edits to fix the issue.

Every *SEARCH/REPLACE* edit must use this format:
1. The file path
2. The start of search block: <<<<<<< SEARCH
3. A contiguous chunk of lines to search for in the existing source code
4. The dividing line: =======
5. The lines to replace into the source code
6. The end of the replace block: >>>>>>> REPLACE

Here is an example:

```python
### mathweb/flask/app.py
<<<<<<< SEARCH
from flask import Flask
=======
import math
from flask import Flask
>>>>>>> REPLACE
```
</solution>

Please note that the *SEARCH/REPLACE* edit REQUIRES PROPER INDENTATION. If you would like to add the line '        print(x)', you must fully write that out, with all those spaces before the code!
Wrap each *SEARCH/REPLACE* edit in a code block as shown in the example above. If you have multiple *SEARCH/REPLACE* edits, use a separate code block for each one.
[ISSUE]
**Title:** ImageMath.eval() Allows Restricted Keys with Double Underscores and Built-in Names

**Description:**
When using `ImageMath.eval()`, providing environment keys that contain double underscores (`__`) or match Python's built-in names does not trigger the expected restrictions. This behavior can lead to unintended side effects or potential security vulnerabilities.

**Example Code:**
```python
from PIL import ImageMath

# Attempt to use a key with double underscores
ImageMath.eval("1", {"__": None})

# Attempt to use a built-in function name as a key
ImageMath.eval("(lambda: exec('exit()'))()", {"exec": None})
```

**Expected Behavior:**
The `ImageMath.eval()` function should restrict the use of environment keys that contain double underscores or clash with Python's built-in names. When such keys are provided, the function should raise a `ValueError` with a message indicating that the key is not allowed.

**Actual Behavior:**
- When a key with double underscores (`"__"`) is used, `ImageMath.eval()` does not raise any error, allowing the key to be processed.
- When a built-in name like `"exec"` is used as a key, instead of raising a `ValueError`, the function raises a `SystemExit` error.

This lack of proper restriction can lead to unexpected behavior and potential security issues, as malicious keys might exploit the evaluation environment.

[/ISSUE]
{
    "\u001b[1mtest_sanity\u001b[0m": "PASSED",
    "\u001b[1mtest_ops\u001b[0m": "PASSED",
    "\u001b[1mtest_prevent_exec[exec('pass')]\u001b[0m": "PASSED",
    "\u001b[1mtest_prevent_exec[(lambda: exec('pass'))()]\u001b[0m": "PASSED",
    "\u001b[1mtest_prevent_exec[(lambda: (lambda: exec('pass'))())()]\u001b[0m": "PASSED",
    "\u001b[1mtest_prevent_double_underscores\u001b[0m": "PASSED",
    "\u001b[1mtest_prevent_builtins\u001b[0m": "PASSED",
    "\u001b[1mtest_logical\u001b[0m": "PASSED",
    "\u001b[1mtest_convert\u001b[0m": "PASSED",
    "\u001b[1mtest_compare\u001b[0m": "PASSED",
    "\u001b[1mtest_one_image_larger\u001b[0m": "PASSED",
    "\u001b[1mtest_abs\u001b[0m": "PASSED",
    "\u001b[1mtest_binary_mod\u001b[0m": "PASSED",
    "\u001b[1mtest_bitwise_invert\u001b[0m": "PASSED",
    "\u001b[1mtest_bitwise_and\u001b[0m": "PASSED",
    "\u001b[1mtest_bitwise_or\u001b[0m": "PASSED",
    "\u001b[1mtest_bitwise_xor\u001b[0m": "PASSED",
    "\u001b[1mtest_bitwise_leftshift\u001b[0m": "PASSED",
    "\u001b[1mtest_bitwise_rightshift\u001b[0m": "PASSED",
    "\u001b[1mtest_logical_eq\u001b[0m": "PASSED",
    "\u001b[1mtest_logical_ne\u001b[0m": "PASSED",
    "\u001b[1mtest_logical_lt\u001b[0m": "PASSED",
    "\u001b[1mtest_logical_le\u001b[0m": "PASSED",
    "\u001b[1mtest_logical_gt\u001b[0m": "PASSED",
    "\u001b[1mtest_logical_ge\u001b[0m": "PASSED",
    "\u001b[1mtest_logical_equal\u001b[0m": "PASSED",
    "\u001b[1mtest_logical_not_equal\u001b[0m": "PASSED"
}
diff --git a/src/PIL/ImageMath.py b/src/PIL/ImageMath.py
index 7ca512e75..b77f4bce5 100644
--- a/src/PIL/ImageMath.py
+++ b/src/PIL/ImageMath.py
@@ -234,6 +234,11 @@ def eval(expression, _dict={}, **kw):
 
     # build execution namespace
     args = ops.copy()
+    for k in list(_dict.keys()) + list(kw.keys()):
+        if "__" in k or hasattr(builtins, k):
+            msg = f"'{k}' not allowed"
+            raise ValueError(msg)
+
     args.update(_dict)
     args.update(kw)
     for k, v in args.items():
 

{"src/PIL/ImageMath.py": "#\n# The Python Imaging Library\n# $Id$\n#\n# a simple math add-on for the Python Imaging Library\n#\n# History:\n# 1999-02-15 fl   Original PIL Plus release\n# 2005-05-05 fl   Simplified and cleaned up for PIL 1.1.6\n# 2005-09-12 fl   Fixed int() and float() for Python 2.4.1\n#\n# Copyright (c) 1999-2005 by Secret Labs AB\n# Copyright (c) 2005 by Fredrik Lundh\n#\n# See the README file for information on usage and redistribution.\n#\nfrom __future__ import annotations\n\nimport builtins\n\nfrom . import Image, _imagingmath\n\n\nclass _Operand:\n    \"\"\"Wraps an image operand, providing standard operators\"\"\"\n\n    def __init__(self, im):\n        self.im = im\n\n    def __fixup(self, im1):\n        # convert image to suitable mode\n        if isinstance(im1, _Operand):\n            # argument was an image.\n            if im1.im.mode in (\"1\", \"L\"):\n                return im1.im.convert(\"I\")\n            elif im1.im.mode in (\"I\", \"F\"):\n                return im1.im\n            else:\n                msg = f\"unsupported mode: {im1.im.mode}\"\n                raise ValueError(msg)\n        else:\n            # argument was a constant\n            if isinstance(im1, (int, float)) and self.im.mode in (\"1\", \"L\", \"I\"):\n                return Image.new(\"I\", self.im.size, im1)\n            else:\n                return Image.new(\"F\", self.im.size, im1)\n\n    def apply(self, op, im1, im2=None, mode=None):\n        im1 = self.__fixup(im1)\n        if im2 is None:\n            # unary operation\n            out = Image.new(mode or im1.mode, im1.size, None)\n            im1.load()\n            try:\n                op = getattr(_imagingmath, op + \"_\" + im1.mode)\n            except AttributeError as e:\n                msg = f\"bad operand type for '{op}'\"\n                raise TypeError(msg) from e\n            _imagingmath.unop(op, out.im.id, im1.im.id)\n        else:\n            # binary operation\n            im2 = self.__fixup(im2)\n            if im1.mode != im2.mode:\n                # convert both arguments to floating point\n                if im1.mode != \"F\":\n                    im1 = im1.convert(\"F\")\n                if im2.mode != \"F\":\n                    im2 = im2.convert(\"F\")\n            if im1.size != im2.size:\n                # crop both arguments to a common size\n                size = (min(im1.size[0], im2.size[0]), min(im1.size[1], im2.size[1]))\n                if im1.size != size:\n                    im1 = im1.crop((0, 0) + size)\n                if im2.size != size:\n                    im2 = im2.crop((0, 0) + size)\n            out = Image.new(mode or im1.mode, im1.size, None)\n            im1.load()\n            im2.load()\n            try:\n                op = getattr(_imagingmath, op + \"_\" + im1.mode)\n            except AttributeError as e:\n                msg = f\"bad operand type for '{op}'\"\n                raise TypeError(msg) from e\n            _imagingmath.binop(op, out.im.id, im1.im.id, im2.im.id)\n        return _Operand(out)\n\n    # unary operators\n    def __bool__(self):\n        # an image is \"true\" if it contains at least one non-zero pixel\n        return self.im.getbbox() is not None\n\n    def __abs__(self):\n        return self.apply(\"abs\", self)\n\n    def __pos__(self):\n        return self\n\n    def __neg__(self):\n        return self.apply(\"neg\", self)\n\n    # binary operators\n    def __add__(self, other):\n        return self.apply(\"add\", self, other)\n\n    def __radd__(self, other):\n        return self.apply(\"add\", other, self)\n\n    def __sub__(self, other):\n        return self.apply(\"sub\", self, other)\n\n    def __rsub__(self, other):\n        return self.apply(\"sub\", other, self)\n\n    def __mul__(self, other):\n        return self.apply(\"mul\", self, other)\n\n    def __rmul__(self, other):\n        return self.apply(\"mul\", other, self)\n\n    def __truediv__(self, other):\n        return self.apply(\"div\", self, other)\n\n    def __rtruediv__(self, other):\n        return self.apply(\"div\", other, self)\n\n    def __mod__(self, other):\n        return self.apply(\"mod\", self, other)\n\n    def __rmod__(self, other):\n        return self.apply(\"mod\", other, self)\n\n    def __pow__(self, other):\n        return self.apply(\"pow\", self, other)\n\n    def __rpow__(self, other):\n        return self.apply(\"pow\", other, self)\n\n    # bitwise\n    def __invert__(self):\n        return self.apply(\"invert\", self)\n\n    def __and__(self, other):\n        return self.apply(\"and\", self, other)\n\n    def __rand__(self, other):\n        return self.apply(\"and\", other, self)\n\n    def __or__(self, other):\n        return self.apply(\"or\", self, other)\n\n    def __ror__(self, other):\n        return self.apply(\"or\", other, self)\n\n    def __xor__(self, other):\n        return self.apply(\"xor\", self, other)\n\n    def __rxor__(self, other):\n        return self.apply(\"xor\", other, self)\n\n    def __lshift__(self, other):\n        return self.apply(\"lshift\", self, other)\n\n    def __rshift__(self, other):\n        return self.apply(\"rshift\", self, other)\n\n    # logical\n    def __eq__(self, other):\n        return self.apply(\"eq\", self, other)\n\n    def __ne__(self, other):\n        return self.apply(\"ne\", self, other)\n\n    def __lt__(self, other):\n        return self.apply(\"lt\", self, other)\n\n    def __le__(self, other):\n        return self.apply(\"le\", self, other)\n\n    def __gt__(self, other):\n        return self.apply(\"gt\", self, other)\n\n    def __ge__(self, other):\n        return self.apply(\"ge\", self, other)\n\n\n# conversions\ndef imagemath_int(self):\n    return _Operand(self.im.convert(\"I\"))\n\n\ndef imagemath_float(self):\n    return _Operand(self.im.convert(\"F\"))\n\n\n# logical\ndef imagemath_equal(self, other):\n    return self.apply(\"eq\", self, other, mode=\"I\")\n\n\ndef imagemath_notequal(self, other):\n    return self.apply(\"ne\", self, other, mode=\"I\")\n\n\ndef imagemath_min(self, other):\n    return self.apply(\"min\", self, other)\n\n\ndef imagemath_max(self, other):\n    return self.apply(\"max\", self, other)\n\n\ndef imagemath_convert(self, mode):\n    return _Operand(self.im.convert(mode))\n\n\nops = {}\nfor k, v in list(globals().items()):\n    if k[:10] == \"imagemath_\":\n        ops[k[10:]] = v\n\n\ndef eval(expression, _dict={}, **kw):\n    \"\"\"\n    Evaluates an image expression.\n\n    :param expression: A string containing a Python-style expression.\n    :param options: Values to add to the evaluation context.  You\n                    can either use a dictionary, or one or more keyword\n                    arguments.\n    :return: The evaluated expression. This is usually an image object, but can\n             also be an integer, a floating point value, or a pixel tuple,\n             depending on the expression.\n    \"\"\"\n\n    # build execution namespace\n    args = ops.copy()\n    args.update(_dict)\n    args.update(kw)\n    for k, v in args.items():\n        if hasattr(v, \"im\"):\n            args[k] = _Operand(v)\n\n    compiled_code = compile(expression, \"<string>\", \"eval\")\n\n    def scan(code):\n        for const in code.co_consts:\n            if type(const) is type(compiled_code):\n                scan(const)\n\n        for name in code.co_names:\n            if name not in args and name != \"abs\":\n                msg = f\"'{name}' not allowed\"\n                raise ValueError(msg)\n\n    scan(compiled_code)\n    out = builtins.eval(expression, {\"__builtins\": {\"abs\": abs}}, args)\n    try:\n        return out.im\n    except AttributeError:\n        return out\n"}
A user will ask you to solve a task. You should first draft your thinking process (inner monologue). Then, generate the solution.

Your response format must follow the template below:
<think>
Your thoughts or/and draft, like working through an exercise on scratch paper. Be as casual and as long as you want until you are confident to generate a correct solution.
</think>
<solution>
Final solution presented to the user.
</solution>
system
We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[ISSUE]
**Title:** ImageMath.eval() Allows Restricted Keys with Double Underscores and Built-in Names

**Description:**
When using `ImageMath.eval()`, providing environment keys that contain double underscores (`__`) or match Python's built-in names does not trigger the expected restrictions. This behavior can lead to unintended side effects or potential security vulnerabilities.

**Example Code:**
```python
from PIL import ImageMath

# Attempt to use a key with double underscores
ImageMath.eval("1", {"__": None})

# Attempt to use a built-in function name as a key
ImageMath.eval("(lambda: exec('exit()'))()", {"exec": None})
```

**Expected Behavior:**
The `ImageMath.eval()` function should restrict the use of environment keys that contain double underscores or clash with Python's built-in names. When such keys are provided, the function should raise a `ValueError` with a message indicating that the key is not allowed.

**Actual Behavior:**
- When a key with double underscores (`"__"`) is used, `ImageMath.eval()` does not raise any error, allowing the key to be processed.
- When a built-in name like `"exec"` is used as a key, instead of raising a `ValueError`, the function raises a `SystemExit` error.

This lack of proper restriction can lead to unexpected behavior and potential security issues, as malicious keys might exploit the evaluation environment.

[/ISSUE]
--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.

--- BEGIN FILE ---
```
### src/PIL/ImageMath.py
#
# The Python Imaging Library
# $Id$
#
# a simple math add-on for the Python Imaging Library
#
# History:
# 1999-02-15 fl   Original PIL Plus release
# 2005-05-05 fl   Simplified and cleaned up for PIL 1.1.6
# 2005-09-12 fl   Fixed int() and float() for Python 2.4.1
#
# Copyright (c) 1999-2005 by Secret Labs AB
# Copyright (c) 2005 by Fredrik Lundh
#
# See the README file for information on usage and redistribution.
#
from __future__ import annotations

import builtins

from . import Image, _imagingmath


class _Operand:
    """Wraps an image operand, providing standard operators"""

    def __init__(self, im):
        self.im = im

    def __fixup(self, im1):
        # convert image to suitable mode
        if isinstance(im1, _Operand):
            # argument was an image.
            if im1.im.mode in ("1", "L"):
                return im1.im.convert("I")
            elif im1.im.mode in ("I", "F"):
                return im1.im
            else:
                msg = f"unsupported mode: {im1.im.mode}"
                raise ValueError(msg)
        else:
            # argument was a constant
            if isinstance(im1, (int, float)) and self.im.mode in ("1", "L", "I"):
                return Image.new("I", self.im.size, im1)
            else:
                return Image.new("F", self.im.size, im1)

    def apply(self, op, im1, im2=None, mode=None):
        im1 = self.__fixup(im1)
        if im2 is None:
            # unary operation
            out = Image.new(mode or im1.mode, im1.size, None)
            im1.load()
            try:
                op = getattr(_imagingmath, op + "_" + im1.mode)
            except AttributeError as e:
                msg = f"bad operand type for '{op}'"
                raise TypeError(msg) from e
            _imagingmath.unop(op, out.im.id, im1.im.id)
        else:
            # binary operation
            im2 = self.__fixup(im2)
            if im1.mode != im2.mode:
                # convert both arguments to floating point
                if im1.mode != "F":
                    im1 = im1.convert("F")
                if im2.mode != "F":
                    im2 = im2.convert("F")
            if im1.size != im2.size:
                # crop both arguments to a common size
                size = (min(im1.size[0], im2.size[0]), min(im1.size[1], im2.size[1]))
                if im1.size != size:
                    im1 = im1.crop((0, 0) + size)
                if im2.size != size:
                    im2 = im2.crop((0, 0) + size)
            out = Image.new(mode or im1.mode, im1.size, None)
            im1.load()
            im2.load()
            try:
                op = getattr(_imagingmath, op + "_" + im1.mode)
            except AttributeError as e:
                msg = f"bad operand type for '{op}'"
                raise TypeError(msg) from e
            _imagingmath.binop(op, out.im.id, im1.im.id, im2.im.id)
        return _Operand(out)

    # unary operators
    def __bool__(self):
        # an image is "true" if it contains at least one non-zero pixel
        return self.im.getbbox() is not None

    def __abs__(self):
        return self.apply("abs", self)

    def __pos__(self):
        return self

    def __neg__(self):
        return self.apply("neg", self)

    # binary operators
    def __add__(self, other):
        return self.apply("add", self, other)

    def __radd__(self, other):
        return self.apply("add", other, self)

    def __sub__(self, other):
        return self.apply("sub", self, other)

    def __rsub__(self, other):
        return self.apply("sub", other, self)

    def __mul__(self, other):
        return self.apply("mul", self, other)

    def __rmul__(self, other):
        return self.apply("mul", other, self)

    def __truediv__(self, other):
        return self.apply("div", self, other)

    def __rtruediv__(self, other):
        return self.apply("div", other, self)

    def __mod__(self, other):
        return self.apply("mod", self, other)

    def __rmod__(self, other):
        return self.apply("mod", other, self)

    def __pow__(self, other):
        return self.apply("pow", self, other)

    def __rpow__(self, other):
        return self.apply("pow", other, self)

    # bitwise
    def __invert__(self):
        return self.apply("invert", self)

    def __and__(self, other):
        return self.apply("and", self, other)

    def __rand__(self, other):
        return self.apply("and", other, self)

    def __or__(self, other):
        return self.apply("or", self, other)

    def __ror__(self, other):
        return self.apply("or", other, self)

    def __xor__(self, other):
        return self.apply("xor", self, other)

    def __rxor__(self, other):
        return self.apply("xor", other, self)

    def __lshift__(self, other):
        return self.apply("lshift", self, other)

    def __rshift__(self, other):
        return self.apply("rshift", self, other)

    # logical
    def __eq__(self, other):
        return self.apply("eq", self, other)

    def __ne__(self, other):
        return self.apply("ne", self, other)

    def __lt__(self, other):
        return self.apply("lt", self, other)

    def __le__(self, other):
        return self.apply("le", self, other)

    def __gt__(self, other):
        return self.apply("gt", self, other)

    def __ge__(self, other):
        return self.apply("ge", self, other)


# conversions
def imagemath_int(self):
    return _Operand(self.im.convert("I"))


def imagemath_float(self):
    return _Operand(self.im.convert("F"))


# logical
def imagemath_equal(self, other):
    return self.apply("eq", self, other, mode="I")


def imagemath_notequal(self, other):
    return self.apply("ne", self, other, mode="I")


def imagemath_min(self, other):
    return self.apply("min", self, other)


def imagemath_max(self, other):
    return self.apply("max", self, other)


def imagemath_convert(self, mode):
    return _Operand(self.im.convert(mode))


ops = {}
for k, v in list(globals().items()):
    if k[:10] == "imagemath_":
        ops[k[10:]] = v


def eval(expression, _dict={}, **kw):
    """
    Evaluates an image expression.

    :param expression: A string containing a Python-style expression.
    :param options: Values to add to the evaluation context.  You
                    can either use a dictionary, or one or more keyword
                    arguments.
    :return: The evaluated expression. This is usually an image object, but can
             also be an integer, a floating point value, or a pixel tuple,
             depending on the expression.
    """

    # build execution namespace
    args = ops.copy()
    args.update(_dict)
    args.update(kw)
    for k, v in args.items():
        if hasattr(v, "im"):
            args[k] = _Operand(v)

    compiled_code = compile(expression, "<string>", "eval")

    def scan(code):
        for const in code.co_consts:
            if type(const) is type(compiled_code):
                scan(const)

        for name in code.co_names:
            if name not in args and name != "abs":
                msg = f"'{name}' not allowed"
                raise ValueError(msg)

    scan(compiled_code)
    out = builtins.eval(expression, {"__builtins": {"abs": abs}}, args)
    try:
        return out.im
    except AttributeError:
        return out

```
--- END FILE ---

Please first localize the bug based on the issue statement, and then generate *SEARCH/REPLACE* edits to fix the issue.

Every *SEARCH/REPLACE* edit must use this format:
1. The file path
2. The start of search block: <<<<<<< SEARCH
3. A contiguous chunk of lines to search for in the existing source code
4. The dividing line: =======
5. The lines to replace into the source code
6. The end of the replace block: >>>>>>> REPLACE

Here is an example:

```python
### mathweb/flask/app.py
<<<<<<< SEARCH
from flask import Flask
=======
import math
from flask import Flask
>>>>>>> REPLACE
```
</solution>

Please note that the *SEARCH/REPLACE* edit REQUIRES PROPER INDENTATION. If you would like to add the line '        print(x)', you must fully write that out, with all those spaces before the code!
Wrap each *SEARCH/REPLACE* edit in a code block as shown in the example above. If you have multiple *SEARCH/REPLACE* edits, use a separate code block for each one.
user