Repository: Cog-Creators/Red-DiscordBot

Issue:
[V3 Config] config items defaulted with a list all share the same list until reloaded from file
It appears that name changes are logged ans saved to every user in the same guild. Error somewhere in ``on_member_update`` logic.



Discussion:
Root of issue tracked down.
https://github.com/Cog-Creators/Red-DiscordBot/blob/194981cd8ba7543b1f653ab1266c08028041c34c/redbot/cogs/mod/mod.py#L1351

So far unknown on why it takes and saves it to all members in a shared guild. I'm thinking this is a config bug regarding using "User"
I'm now seeing this with nicknames as well
Actually, the issue with nicks (and selfroles) is something that seems to be different from the issue occurring here. Nicks and selfroles lead to the wrong result showing when doing `[p]eval` to show the value, but those aren't being written to disk. In the case of names, the names are being written to disk in more than just their intended destination

EDIT: test cases should probably be included relating to this (@Chovin)
For more detail:

### Behaviors observed:
1. Config structures using lists\* added using `config.register_member`\*\* when accessed via async context manager like so: `async with settings.member(member).some_list() as the_list:` all share the same list.
2. Lists pulled from disk are not effected, so if only one default member is added followed by a reload of the cog, the following default member will not share the same list as the previous (NOTE: There are irregularities)

### Steps to reproduce: 
 with persons named `A, B, C` having ids `1, 2, 3` respectively
1. Delete mod cog's settings json if exists and `[p]reload mod`
2. `A` change name to `sloop`
3. `[p]names A` gives Past 20 names: **sloop**
4. `[p]names B` gives Past 20 names: **sloop**
5. `
#!/eval print(await bot.get_cog("Mod").settings.member(guild.get_member(1)).past_names() is await bot.get_cog("Mod").settings.member(guild.get_member(2)).past_names())
` 
gives `True`
This exhibits behavior 1
compare with `#!/eval print([] is [])` gives `False`

6. `[p]reload mod`
7. `A` change name to `pools`
8. `[p]names A` gives Past 20 names: **sloop, pools**
9. `[p]names B` gives Past 20 names: **sloop**
This exhibits behavior 2
10. repeat steps 2-9 but with nicknames instead. you should notice that steps 6-9 give different results, `B` having no past nicknames. 
These are the irregularities observed in behavior 2

Further examples can be be seen in the conversation following the most recent pin 
(id `411390561174683649` at time of writing) in the `#advanced-testing` channel of our Discord

\* Behaviors may not effect dicts
\*\* Behavior is assumed to be the same for member/user/guild/channel, etc.

Resolving patch:
diff --git a/redbot/core/config.py b/redbot/core/config.py
--- a/redbot/core/config.py
+++ b/redbot/core/config.py
@@ -31,7 +31,7 @@ def __await__(self):
         return self.coro.__await__()
 
     async def __aenter__(self):
-        self.raw_value =  await self
+        self.raw_value = await self
         if not isinstance(self.raw_value, (list, dict)):
             raise TypeError("Type of retrieved value must be mutable (i.e. "
                             "list or dict) in order to use a config value as "
@@ -171,7 +171,7 @@ def __init__(self, identifiers: Tuple[str],
 
     @property
     def defaults(self):
-        return self._defaults.copy()
+        return deepcopy(self._defaults)
 
     # noinspection PyTypeChecker
     def __getattr__(self, item: str) -> Union["Group", Value]:
@@ -428,7 +428,7 @@ def __init__(self, cog_name: str, unique_identifier: str,
 
     @property
     def defaults(self):
-        return self._defaults.copy()
+        return deepcopy(self._defaults)
 
     @classmethod
     def get_conf(cls, cog_instance, identifier: int,
@@ -669,7 +669,7 @@ def _get_base_group(self, key: str, *identifiers: str) -> Group:
         # noinspection PyTypeChecker
         return Group(
             identifiers=(self.unique_identifier, key) + identifiers,
-            defaults=self._defaults.get(key, {}),
+            defaults=self.defaults.get(key, {}),
             spawner=self.spawner,
             force_registration=self.force_registration
         )


Test patch:
diff --git a/tests/core/test_config.py b/tests/core/test_config.py
--- a/tests/core/test_config.py
+++ b/tests/core/test_config.py
@@ -376,3 +376,15 @@ async def test_value_ctxmgr_immutable(config):
 
     foo = await config.foo()
     assert foo is True
+
+
+@pytest.mark.asyncio
+async def test_ctxmgr_no_shared_default(config, member_factory):
+    config.register_member(foo=[])
+    m1 = member_factory.get()
+    m2 = member_factory.get()
+
+    async with config.member(m1).foo() as foo:
+        foo.append(1)
+
+    assert 1 not in await config.member(m2).foo()