From 3803fbfe2076ef511c497afcd88b8c9505baf64b Mon Sep 17 00:00:00 2001
From: Michael Flaherty <michaelwflaherty@me.com>
Date: Tue, 10 Jul 2018 14:38:40 -0700
Subject: [PATCH] Individualize NameHashSet Hashing & Revisit #709 (#740)

* Make mac/win lookups lowercase'd

* Revert #709 & 81042cc

* Adjust HashPolicy implementation across sourcemod

Basically, in order to implement our own (actual) hash policy in
`PluginSys.h`, we needed to remove the blanket implementation of `hash`
that was used before. Now, each policy must implement `hash` along with
`matches` in order to be used with `NameHashSet`. While this does force
us to change every implementation of policies across the entirety of
sourcemod, it allows core to use flexible implementations of `hash`.

* Remove logic duplication

* Improve lowercase checks
---
 core/ConVarManager.h            |  5 +++++
 core/EventManager.h             |  4 ++++
 core/HalfLife2.h                | 14 ++++++++++++-
 core/logic/AdminCache.h         |  4 ++++
 core/logic/GameConfigs.h        |  4 ++++
 core/logic/HandleSys.h          |  4 ++++
 core/logic/Native.h             |  5 +++++
 core/logic/PluginSys.cpp        | 31 +++-------------------------
 core/logic/PluginSys.h          | 36 +++++++++++++++++++++++++++------
 core/logic/RootConsoleMenu.h    |  4 ++++
 core/logic/smn_maplists.cpp     |  4 ++++
 core/smn_console.cpp            |  4 ++++
 extensions/clientprefs/cookie.h |  4 ++++
 extensions/topmenus/TopMenu.h   |  4 ++++
 public/sm_namehashset.h         | 12 ++++++-----
 15 files changed, 99 insertions(+), 40 deletions(-)

diff --git a/core/ConVarManager.h b/core/ConVarManager.h
index 07dfdd35..f39cd295 100644
--- a/core/ConVarManager.h
+++ b/core/ConVarManager.h
@@ -43,6 +43,7 @@
 #include <compat_wrappers.h>
 #include "concmd_cleaner.h"
 #include "PlayerManager.h"
+#include <sm_stringhashmap.h>
 
 using namespace SourceHook;
 
@@ -67,6 +68,10 @@ struct ConVarInfo
 	{
 		return strcmp(name, info->pVar->GetName()) == 0;
 	}
+	static inline uint32_t hash(const detail::CharsAndLength &key)
+	{
+		return key.hash();
+	}
 };
 
 /**
diff --git a/core/EventManager.h b/core/EventManager.h
index bf7bd37b..1c8a1da5 100644
--- a/core/EventManager.h
+++ b/core/EventManager.h
@@ -77,6 +77,10 @@ struct EventHook
 	{
 		return strcmp(name, hook->name.chars()) == 0;
 	}
+	static inline uint32_t hash(const detail::CharsAndLength &key)
+	{
+		return key.hash();
+	}
 };
 
 enum EventHookMode
diff --git a/core/HalfLife2.h b/core/HalfLife2.h
index 9156069c..f7c077d4 100644
--- a/core/HalfLife2.h
+++ b/core/HalfLife2.h
@@ -92,13 +92,21 @@ struct DataTableInfo
 		{
 			return strcmp(name, info.prop->GetName()) == 0;
 		}
+		static inline uint32_t hash(const detail::CharsAndLength &key)
+		{
+			return key.hash();
+		}
 	};
 
 	static inline bool matches(const char *name, const DataTableInfo *info)
 	{
 		return strcmp(name, info->sc->GetName()) == 0;
 	}
-
+	static inline uint32_t hash(const detail::CharsAndLength &key)
+	{
+		return key.hash();
+	}
+	
 	DataTableInfo(ServerClass *sc)
 		: sc(sc)
 	{
@@ -114,6 +122,10 @@ struct DataMapCachePolicy
 	{
 		return strcmp(name, info.prop->fieldName) == 0;
 	}
+	static inline uint32_t hash(const detail::CharsAndLength &key)
+	{
+		return key.hash();
+	}
 };
 
 typedef NameHashSet<sm_datatable_info_t, DataMapCachePolicy> DataMapCache;
diff --git a/core/logic/AdminCache.h b/core/logic/AdminCache.h
index 0cdd5859..18aa2361 100644
--- a/core/logic/AdminCache.h
+++ b/core/logic/AdminCache.h
@@ -82,6 +82,10 @@ struct AuthMethod
 	{
 		return strcmp(name, method->name.c_str()) == 0;
 	}
+	static inline uint32_t hash(const detail::CharsAndLength &key)
+	{
+		return key.hash();
+	}
 };
 
 struct UserAuth
diff --git a/core/logic/GameConfigs.h b/core/logic/GameConfigs.h
index 802b9ebb..44a40110 100644
--- a/core/logic/GameConfigs.h
+++ b/core/logic/GameConfigs.h
@@ -72,6 +72,10 @@ public: //NameHashSet
 	{
 		return strcmp(key, value->m_File) == 0;
 	}
+	static inline uint32_t hash(const detail::CharsAndLength &key)
+	{
+		return key.hash();
+	}
 private:
 	char m_File[PLATFORM_MAX_PATH];
 	char m_CurFile[PLATFORM_MAX_PATH];
diff --git a/core/logic/HandleSys.h b/core/logic/HandleSys.h
index a549c251..bb4e5ea9 100644
--- a/core/logic/HandleSys.h
+++ b/core/logic/HandleSys.h
@@ -110,6 +110,10 @@ struct QHandleType
 	{
 		return type->name && type->name->compare(key) == 0;
 	}
+	static inline uint32_t hash(const detail::CharsAndLength &key)
+	{
+		return key.hash();
+	}
 };
 
 typedef ke::Lambda<void(const char *)> HandleReporter;
diff --git a/core/logic/Native.h b/core/logic/Native.h
index 3ac2aa9a..438d5d36 100644
--- a/core/logic/Native.h
+++ b/core/logic/Native.h
@@ -36,6 +36,7 @@
 #include <am-string.h>
 #include <am-utility.h>
 #include <am-refcounting.h>
+#include <sm_stringhashmap.h>
 #include "common_logic.h"
 
 class CNativeOwner;
@@ -93,6 +94,10 @@ struct Native : public ke::Refcounted<Native>
 	{
 		return strcmp(name, entry->name()) == 0;
 	}
+	static inline uint32_t hash(const detail::CharsAndLength &key)
+	{
+		return key.hash();
+	}
 };
 
 
diff --git a/core/logic/PluginSys.cpp b/core/logic/PluginSys.cpp
index e98f4a58..8ee33abd 100644
--- a/core/logic/PluginSys.cpp
+++ b/core/logic/PluginSys.cpp
@@ -31,7 +31,6 @@
 
 #include <stdio.h>
 #include <stdarg.h>
-#include <ctype.h>
 #include "PluginSys.h"
 #include "ShareSys.h"
 #include <ILibrarySys.h>
@@ -47,7 +46,6 @@
 #include "frame_tasks.h"
 #include <amtl/am-string.h>
 #include <amtl/am-linkedlist.h>
-#include <amtl/am-uniqueptr.h>
 #include <bridge/include/IVEngineServerBridge.h>
 #include <bridge/include/CoreProvider.h>
 
@@ -934,38 +932,16 @@ void CPluginManager::LoadPluginsFromDir(const char *basedir, const char *localpa
 	libsys->CloseDirectory(dir);
 }
 
-#if defined PLATFORM_WINDOWS || defined PLATFORM_APPLE
-char *strdup_tolower(const char *input)
-{
-	char *str = strdup(input);
-	
-	for (char *c = str; *c; c++)
-	{
-		*c = tolower((unsigned char)*c);
-	}
-	
-	return str;
-}
-#endif
-
 LoadRes CPluginManager::LoadPlugin(CPlugin **aResult, const char *path, bool debug, PluginType type)
 {
 	if (m_LoadingLocked)
 		return LoadRes_NeverLoad;
 
-/* For windows & mac, we convert the path to lower-case in order to avoid duplicate plugin loading */
-#if defined PLATFORM_WINDOWS || defined PLATFORM_APPLE
-	ke::UniquePtr<char> finalPath = ke::UniquePtr<char>(strdup_tolower(path));
-#else 
-	ke::UniquePtr<char> finalPath = ke::UniquePtr<char>(strdup(path));
-#endif
-
-
 	/**
 	 * Does this plugin already exist?
 	 */
 	CPlugin *pPlugin;
-	if (m_LoadLookup.retrieve(finalPath.get(), &pPlugin))
+	if (m_LoadLookup.retrieve(path, &pPlugin))
 	{
 		/* Check to see if we should try reloading it */
 		if (pPlugin->GetStatus() == Plugin_BadLoad
@@ -978,12 +954,11 @@ LoadRes CPluginManager::LoadPlugin(CPlugin **aResult, const char *path, bool deb
 		{
 			if (aResult)
 				*aResult = pPlugin;
-			
 			return LoadRes_AlreadyLoaded;
 		}
 	}
 
-	CPlugin *plugin = CompileAndPrep(finalPath.get());
+	CPlugin *plugin = CompileAndPrep(path);
 
 	// Assign our outparam so we can return early. It must be set.
 	*aResult = plugin;
@@ -2416,4 +2391,4 @@ static OldPluginAPI sOldPluginAPI;
 IPluginManager *CPluginManager::GetOldAPI()
 {
 	return &sOldPluginAPI;
-}
+}
\ No newline at end of file
diff --git a/core/logic/PluginSys.h b/core/logic/PluginSys.h
index 43f04c7b..4a6b617b 100644
--- a/core/logic/PluginSys.h
+++ b/core/logic/PluginSys.h
@@ -143,11 +143,6 @@ public:
 	 */
 	static CPlugin *Create(const char *file);
 
-	static inline bool matches(const char *file, const CPlugin *plugin)
-	{
-		return strcmp(plugin->m_filename, file) == 0;
-	}
-
 public:
 	// Evicts the plugin from memory and sets an error state.
 	void EvictWithError(PluginStatus status, const char *error_fmt, ...);
@@ -483,7 +478,36 @@ private:
 	typedef decltype(m_listeners)::iterator ListenerIter;
 	typedef decltype(m_plugins)::iterator PluginIter;
 
-	NameHashSet<CPlugin *> m_LoadLookup;
+	struct CPluginPolicy
+	{
+		static inline uint32_t hash(const detail::CharsAndLength &key)
+		{
+/* For windows & mac, we convert the path to lower-case in order to avoid duplicate plugin loading */
+#if defined PLATFORM_WINDOWS || defined PLATFORM_APPLE
+			ke::AString original(key.chars());
+			ke::AString lower = original.lowercase();
+			
+			return detail::CharsAndLength(lower.chars()).hash();
+#else
+			return key.hash();
+#endif
+		}
+		
+		static inline bool matches(const char *file, const CPlugin *plugin)
+		{
+			const char *pluginFileChars = const_cast<CPlugin*>(plugin)->GetFilename();
+#if defined PLATFORM_WINDOWS || defined PLATFORM_APPLE
+			ke::AString pluginFile = ke::AString(pluginFileChars).lowercase();
+			ke::AString input = ke::AString(file).lowercase();
+			
+			return pluginFile == input;
+#else
+			return strcmp(pluginFileChars, file) == 0;
+#endif
+		}
+	};
+	NameHashSet<CPlugin *, CPluginPolicy> m_LoadLookup;
+
 	bool m_AllPluginsLoaded;
 	IdentityToken_t *m_MyIdent;
 
diff --git a/core/logic/RootConsoleMenu.h b/core/logic/RootConsoleMenu.h
index 4b687bec..c240a488 100644
--- a/core/logic/RootConsoleMenu.h
+++ b/core/logic/RootConsoleMenu.h
@@ -46,6 +46,10 @@ struct ConsoleEntry
 	{
 		return strcmp(name, entry->command.c_str()) == 0;
 	}
+	static inline uint32_t hash(const detail::CharsAndLength &key)
+	{
+		return key.hash();
+	}
 };
 
 class RootConsoleMenu : 
diff --git a/core/logic/smn_maplists.cpp b/core/logic/smn_maplists.cpp
index 093aaa15..2393a141 100644
--- a/core/logic/smn_maplists.cpp
+++ b/core/logic/smn_maplists.cpp
@@ -58,6 +58,10 @@ struct maplist_info_t
 	{
 		return strcmp(value->name, key) == 0;
 	}
+	static inline uint32_t hash(const detail::CharsAndLength &key)
+	{
+		return key.hash();
+	}
 };
 
 #define MAPLIST_FLAG_MAPSFOLDER		(1<<0)		/**< On failure, use all maps in the maps folder. */
diff --git a/core/smn_console.cpp b/core/smn_console.cpp
index 43f8ccc6..799e1359 100644
--- a/core/smn_console.cpp
+++ b/core/smn_console.cpp
@@ -184,6 +184,10 @@ private:
 		{
 			return strcmp(name, base->GetName()) == 0;
 		}
+		static inline uint32_t hash(const detail::CharsAndLength &key)
+		{
+			return key.hash();
+		}
 	};
 	NameHashSet<ConCommandBase *, ConCommandPolicy> m_CmdFlags;
 } s_CommandFlagsHelper;
diff --git a/extensions/clientprefs/cookie.h b/extensions/clientprefs/cookie.h
index 9684b264..31b59f35 100644
--- a/extensions/clientprefs/cookie.h
+++ b/extensions/clientprefs/cookie.h
@@ -96,6 +96,10 @@ struct Cookie
 	{
 		return strcmp(name, cookie->name) == 0;
 	}
+	static inline uint32_t hash(const detail::CharsAndLength &key)
+	{
+		return key.hash();
+	}
 };
 
 class CookieManager : public IClientListener, public IPluginsListener
diff --git a/extensions/topmenus/TopMenu.h b/extensions/topmenus/TopMenu.h
index a22a1ba0..298c7f58 100644
--- a/extensions/topmenus/TopMenu.h
+++ b/extensions/topmenus/TopMenu.h
@@ -77,6 +77,10 @@ struct topmenu_object_t
 	{
 		return strcmp(name, topmenu->name) == 0;
 	}
+	static inline uint32_t hash(const detail::CharsAndLength &key)
+	{
+		return key.hash();
+	}
 };
 
 struct topmenu_category_t
diff --git a/public/sm_namehashset.h b/public/sm_namehashset.h
index 5e6fbe95..26507353 100644
--- a/public/sm_namehashset.h
+++ b/public/sm_namehashset.h
@@ -48,10 +48,12 @@
 namespace SourceMod
 {
 
-// The HashPolicy type must have this method:
+// The HashPolicy type must have these methods:
 // 		static bool matches(const char *key, const T &value);
+// 		static uint32_t hash(const CharsAndLength &key);
 //
-// Depending on what lookup types are used.
+// Depending on what lookup types are used, and how hashing should be done.
+// Most of the time, key hashing will just call the key's hash() method.
 //
 // If these members are available on T, then the HashPolicy type can be left
 // default. It is okay to use |T *|, the functions will still be looked up
@@ -69,7 +71,7 @@ class NameHashSet : public ke::SystemAllocatorPolicy
 
 		static uint32_t hash(const CharsAndLength &key)
 		{
-			return key.hash();
+			return KeyPolicyType::hash(key);
 		}
 
 		static bool matches(const CharsAndLength &key, const KeyType &value)
@@ -85,9 +87,9 @@ class NameHashSet : public ke::SystemAllocatorPolicy
 	{
 		typedef KeyType *Payload;
 
-		static uint32_t hash(const detail::CharsAndLength &key)
+		static uint32_t hash(const CharsAndLength &key)
 		{
-			return key.hash();
+			return KeyType::hash(key);
 		}
 
 		static bool matches(const CharsAndLength &key, const KeyType *value)