prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  ak.fields (v2) passes a RecordArray's internal fields by reference
  Okay, so I hadn't noticed that Awkward v2's fields are passed by reference, which exposes them to the danger that someone might modify them downstream:
  
  v1:
  
  ```python
  >>> array = awkward.Array([{"x": 1, "y": 1.1}])
  >>> fields = awkward.fields(array)
  >>> array
  <Array [{x: 1, y: 1.1}] type='1 * {"x": int64, "y": float64}'>
  >>> fields
  ['x', 'y']
  >>> fields[0] = "XXX"
  >>> fields
  ['XXX', 'y']
  >>> array
  <Array [{x: 1, y: 1.1}] type='1 * {"x": int64, "y": float64}'>
  ```
  
  v2:
  
  ```python
  >>> array = awkward._v2.Array([{"x": 1, "y": 1.1}])
  >>> fields = awkward._v2.fields(array)
  >>> array
  <Array [{x: 1, y: 1.1}] type='1 * {x: int64, y: float64}'>
  >>> fields
  ['x', 'y']
  >>> fields[0] = "XXX"
  >>> fields
  ['XXX', 'y']
  >>> array
  <Array [{XXX: 1, y: 1.1}] type='1 * {XXX: int64, y: float64}'>
  ```
  
  It could be fixed [here, in Awkward](https://github.com/scikit-hep/awkward/blob/352b0dead74846ad2a56d385be4694ec87072a08/src/awkward/_v2/contents/recordarray.py#L162), or maybe [here](https://github.com/scikit-hep/awkward/blob/352b0dead74846ad2a56d385be4694ec87072a08/src/awkward/_v2/operations/ak_fields.py#L30) (to only suffer the list-copy when handing it off to a user, so that internal uses can still be by reference).
  
  I'll use this comment to open an issue in Awkward. Once `awkward.fields` is guarded, your `.copy()` can be removed, but it can also not be removed with no consequences but a little performance.
  
  _Originally posted by @jpivarski in https://github.com/scikit-hep/vector/pull/226#discussion_r958660705_
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `src/awkward/_v2/operations/ak_fields.py`
  Content:
  ```
  1 # BSD 3-Clause License; see https://github.com/scikit-hep/awkward-1.0/blob/main/LICENSE
  2 
  3 import awkward as ak
  4 
  5 np = ak.nplike.NumpyMetadata.instance()
  6 
  7 
  8 def fields(array):
  9     """
  10     Extracts record fields or tuple slot numbers from `array` (many types
  11     supported, including all Awkward Arrays and Records).
  12 
  13     If the array contains nested records, only the outermost record is
  14     queried. If it contains tuples instead of records, this function outputs
  15     string representations of integers, such as `"0"`, `"1"`, `"2"`, etc.
  16     The records or tuples may be within multiple layers of nested lists.
  17 
  18     If the array contains neither tuples nor records, this returns an empty
  19     list.
  20     """
  21     with ak._v2._util.OperationErrorContext(
  22         "ak._v2.fields",
  23         dict(array=array),
  24     ):
  25         return _impl(array)
  26 
  27 
  28 def _impl(array):
  29     layout = ak._v2.operations.to_layout(array, allow_record=True, allow_other=False)
  30     return layout.fields
  31
  ```
  
  Path: `src/awkward/_v2/operations/ak_parameters.py`
  Content:
  ```
  1 # BSD 3-Clause License; see https://github.com/scikit-hep/awkward-1.0/blob/main/LICENSE
  2 
  3 import awkward as ak
  4 
  5 np = ak.nplike.NumpyMetadata.instance()
  6 
  7 
  8 def parameters(array):
  9     """
  10     Extracts parameters from the outermost array node of `array` (many types
  11     supported, including all Awkward Arrays and Records).
  12 
  13     Parameters are a dict from str to JSON-like objects, usually strings.
  14     Every #ak.layout.Content node has a different set of parameters. Some
  15     key names are special, such as `"__record__"` and `"__array__"` that name
  16     particular records and arrays as capable of supporting special behaviors.
  17 
  18     See #ak.Array and #ak.behavior for a more complete description of
  19     behaviors.
  20     """
  21     with ak._v2._util.OperationErrorContext(
  22         "ak._v2.parameters",
  23         dict(array=array),
  24     ):
  25         return _impl(array)
  26 
  27 
  28 def _impl(array):
  29     if isinstance(array, (ak._v2.highlevel.Array, ak._v2.highlevel.Record)):
  30         return array.layout.parameters
  31 
  32     elif isinstance(
  33         array,
  34         (ak._v2.contents.Content, ak._v2.record.Record),
  35     ):
  36         return array.parameters
  37 
  38     elif isinstance(array, ak._v2.highlevel.ArrayBuilder):
  39         return array.snapshot().layout.parameters
  40 
  41     elif isinstance(array, ak.layout.ArrayBuilder):
  42         return array.snapshot().parameters
  43 
  44     else:
  45         return {}
  46
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/src/awkward/_v2/operations/ak_fields.py b/src/awkward/_v2/operations/ak_fields.py
  --- a/src/awkward/_v2/operations/ak_fields.py
  +++ b/src/awkward/_v2/operations/ak_fields.py
  @@ -27,4 +27,4 @@
   
   def _impl(array):
       layout = ak._v2.operations.to_layout(array, allow_record=True, allow_other=False)
  -    return layout.fields
  +    return layout.fields.copy()
  diff --git a/src/awkward/_v2/operations/ak_parameters.py b/src/awkward/_v2/operations/ak_parameters.py
  --- a/src/awkward/_v2/operations/ak_parameters.py
  +++ b/src/awkward/_v2/operations/ak_parameters.py
  @@ -1,5 +1,8 @@
   # BSD 3-Clause License; see https://github.com/scikit-hep/awkward-1.0/blob/main/LICENSE
   
  +import copy
  +import numbers
  +
   import awkward as ak
   
   np = ak.nplike.NumpyMetadata.instance()
  @@ -27,13 +30,13 @@
   
   def _impl(array):
       if isinstance(array, (ak._v2.highlevel.Array, ak._v2.highlevel.Record)):
  -        return array.layout.parameters
  +        return _copy(array.layout.parameters)
   
       elif isinstance(
           array,
           (ak._v2.contents.Content, ak._v2.record.Record),
       ):
  -        return array.parameters
  +        return _copy(array.parameters)
   
       elif isinstance(array, ak._v2.highlevel.ArrayBuilder):
           return array.snapshot().layout.parameters
  @@ -43,3 +46,10 @@
   
       else:
           return {}
  +
  +
  +def _copy(what):
  +    if all(isinstance(x, (str, numbers.Real)) for x in what.values()):
  +        return what.copy()
  +    else:
  +        return copy.deepcopy(what)
  
verification_info: {"golden_diff": "diff --git a/src/awkward/_v2/operations/ak_fields.py b/src/awkward/_v2/operations/ak_fields.py\n--- a/src/awkward/_v2/operations/ak_fields.py\n+++ b/src/awkward/_v2/operations/ak_fields.py\n@@ -27,4 +27,4 @@\n \n def _impl(array):\n     layout = ak._v2.operations.to_layout(array, allow_record=True, allow_other=False)\n-    return layout.fields\n+    return layout.fields.copy()\ndiff --git a/src/awkward/_v2/operations/ak_parameters.py b/src/awkward/_v2/operations/ak_parameters.py\n--- a/src/awkward/_v2/operations/ak_parameters.py\n+++ b/src/awkward/_v2/operations/ak_parameters.py\n@@ -1,5 +1,8 @@\n # BSD 3-Clause License; see https://github.com/scikit-hep/awkward-1.0/blob/main/LICENSE\n \n+import copy\n+import numbers\n+\n import awkward as ak\n \n np = ak.nplike.NumpyMetadata.instance()\n@@ -27,13 +30,13 @@\n \n def _impl(array):\n     if isinstance(array, (ak._v2.highlevel.Array, ak._v2.highlevel.Record)):\n-        return array.layout.parameters\n+        return _copy(array.layout.parameters)\n \n     elif isinstance(\n         array,\n         (ak._v2.contents.Content, ak._v2.record.Record),\n     ):\n-        return array.parameters\n+        return _copy(array.parameters)\n \n     elif isinstance(array, ak._v2.highlevel.ArrayBuilder):\n         return array.snapshot().layout.parameters\n@@ -43,3 +46,10 @@\n \n     else:\n         return {}\n+\n+\n+def _copy(what):\n+    if all(isinstance(x, (str, numbers.Real)) for x in what.values()):\n+        return what.copy()\n+    else:\n+        return copy.deepcopy(what)\n", "issue": "ak.fields (v2) passes a RecordArray's internal fields by reference\nOkay, so I hadn't noticed that Awkward v2's fields are passed by reference, which exposes them to the danger that someone might modify them downstream:\r\n\r\nv1:\r\n\r\n```python\r\n>>> array = awkward.Array([{\"x\": 1, \"y\": 1.1}])\r\n>>> fields = awkward.fields(array)\r\n>>> array\r\n<Array [{x: 1, y: 1.1}] type='1 * {\"x\": int64, \"y\": float64}'>\r\n>>> fields\r\n['x', 'y']\r\n>>> fields[0] = \"XXX\"\r\n>>> fields\r\n['XXX', 'y']\r\n>>> array\r\n<Array [{x: 1, y: 1.1}] type='1 * {\"x\": int64, \"y\": float64}'>\r\n```\r\n\r\nv2:\r\n\r\n```python\r\n>>> array = awkward._v2.Array([{\"x\": 1, \"y\": 1.1}])\r\n>>> fields = awkward._v2.fields(array)\r\n>>> array\r\n<Array [{x: 1, y: 1.1}] type='1 * {x: int64, y: float64}'>\r\n>>> fields\r\n['x', 'y']\r\n>>> fields[0] = \"XXX\"\r\n>>> fields\r\n['XXX', 'y']\r\n>>> array\r\n<Array [{XXX: 1, y: 1.1}] type='1 * {XXX: int64, y: float64}'>\r\n```\r\n\r\nIt could be fixed [here, in Awkward](https://github.com/scikit-hep/awkward/blob/352b0dead74846ad2a56d385be4694ec87072a08/src/awkward/_v2/contents/recordarray.py#L162), or maybe [here](https://github.com/scikit-hep/awkward/blob/352b0dead74846ad2a56d385be4694ec87072a08/src/awkward/_v2/operations/ak_fields.py#L30) (to only suffer the list-copy when handing it off to a user, so that internal uses can still be by reference).\r\n\r\nI'll use this comment to open an issue in Awkward. Once `awkward.fields` is guarded, your `.copy()` can be removed, but it can also not be removed with no consequences but a little performance.\r\n\r\n_Originally posted by @jpivarski in https://github.com/scikit-hep/vector/pull/226#discussion_r958660705_\n", "before_files": [{"content": "# BSD 3-Clause License; see https://github.com/scikit-hep/awkward-1.0/blob/main/LICENSE\n\nimport awkward as ak\n\nnp = ak.nplike.NumpyMetadata.instance()\n\n\ndef fields(array):\n    \"\"\"\n    Extracts record fields or tuple slot numbers from `array` (many types\n    supported, including all Awkward Arrays and Records).\n\n    If the array contains nested records, only the outermost record is\n    queried. If it contains tuples instead of records, this function outputs\n    string representations of integers, such as `\"0\"`, `\"1\"`, `\"2\"`, etc.\n    The records or tuples may be within multiple layers of nested lists.\n\n    If the array contains neither tuples nor records, this returns an empty\n    list.\n    \"\"\"\n    with ak._v2._util.OperationErrorContext(\n        \"ak._v2.fields\",\n        dict(array=array),\n    ):\n        return _impl(array)\n\n\ndef _impl(array):\n    layout = ak._v2.operations.to_layout(array, allow_record=True, allow_other=False)\n    return layout.fields\n", "path": "src/awkward/_v2/operations/ak_fields.py"}, {"content": "# BSD 3-Clause License; see https://github.com/scikit-hep/awkward-1.0/blob/main/LICENSE\n\nimport awkward as ak\n\nnp = ak.nplike.NumpyMetadata.instance()\n\n\ndef parameters(array):\n    \"\"\"\n    Extracts parameters from the outermost array node of `array` (many types\n    supported, including all Awkward Arrays and Records).\n\n    Parameters are a dict from str to JSON-like objects, usually strings.\n    Every #ak.layout.Content node has a different set of parameters. Some\n    key names are special, such as `\"__record__\"` and `\"__array__\"` that name\n    particular records and arrays as capable of supporting special behaviors.\n\n    See #ak.Array and #ak.behavior for a more complete description of\n    behaviors.\n    \"\"\"\n    with ak._v2._util.OperationErrorContext(\n        \"ak._v2.parameters\",\n        dict(array=array),\n    ):\n        return _impl(array)\n\n\ndef _impl(array):\n    if isinstance(array, (ak._v2.highlevel.Array, ak._v2.highlevel.Record)):\n        return array.layout.parameters\n\n    elif isinstance(\n        array,\n        (ak._v2.contents.Content, ak._v2.record.Record),\n    ):\n        return array.parameters\n\n    elif isinstance(array, ak._v2.highlevel.ArrayBuilder):\n        return array.snapshot().layout.parameters\n\n    elif isinstance(array, ak.layout.ArrayBuilder):\n        return array.snapshot().parameters\n\n    else:\n        return {}\n", "path": "src/awkward/_v2/operations/ak_parameters.py"}], "after_files": [{"content": "# BSD 3-Clause License; see https://github.com/scikit-hep/awkward-1.0/blob/main/LICENSE\n\nimport awkward as ak\n\nnp = ak.nplike.NumpyMetadata.instance()\n\n\ndef fields(array):\n    \"\"\"\n    Extracts record fields or tuple slot numbers from `array` (many types\n    supported, including all Awkward Arrays and Records).\n\n    If the array contains nested records, only the outermost record is\n    queried. If it contains tuples instead of records, this function outputs\n    string representations of integers, such as `\"0\"`, `\"1\"`, `\"2\"`, etc.\n    The records or tuples may be within multiple layers of nested lists.\n\n    If the array contains neither tuples nor records, this returns an empty\n    list.\n    \"\"\"\n    with ak._v2._util.OperationErrorContext(\n        \"ak._v2.fields\",\n        dict(array=array),\n    ):\n        return _impl(array)\n\n\ndef _impl(array):\n    layout = ak._v2.operations.to_layout(array, allow_record=True, allow_other=False)\n    return layout.fields.copy()\n", "path": "src/awkward/_v2/operations/ak_fields.py"}, {"content": "# BSD 3-Clause License; see https://github.com/scikit-hep/awkward-1.0/blob/main/LICENSE\n\nimport copy\nimport numbers\n\nimport awkward as ak\n\nnp = ak.nplike.NumpyMetadata.instance()\n\n\ndef parameters(array):\n    \"\"\"\n    Extracts parameters from the outermost array node of `array` (many types\n    supported, including all Awkward Arrays and Records).\n\n    Parameters are a dict from str to JSON-like objects, usually strings.\n    Every #ak.layout.Content node has a different set of parameters. Some\n    key names are special, such as `\"__record__\"` and `\"__array__\"` that name\n    particular records and arrays as capable of supporting special behaviors.\n\n    See #ak.Array and #ak.behavior for a more complete description of\n    behaviors.\n    \"\"\"\n    with ak._v2._util.OperationErrorContext(\n        \"ak._v2.parameters\",\n        dict(array=array),\n    ):\n        return _impl(array)\n\n\ndef _impl(array):\n    if isinstance(array, (ak._v2.highlevel.Array, ak._v2.highlevel.Record)):\n        return _copy(array.layout.parameters)\n\n    elif isinstance(\n        array,\n        (ak._v2.contents.Content, ak._v2.record.Record),\n    ):\n        return _copy(array.parameters)\n\n    elif isinstance(array, ak._v2.highlevel.ArrayBuilder):\n        return array.snapshot().layout.parameters\n\n    elif isinstance(array, ak.layout.ArrayBuilder):\n        return array.snapshot().parameters\n\n    else:\n        return {}\n\n\ndef _copy(what):\n    if all(isinstance(x, (str, numbers.Real)) for x in what.values()):\n        return what.copy()\n    else:\n        return copy.deepcopy(what)\n", "path": "src/awkward/_v2/operations/ak_parameters.py"}]}