Repository: nvaccess/nvda

Issue:
Script decoration system fails when in use by multiple global plugins
### Steps to reproduce:
1. Create two global plugins. Add a new script to both of them, named differently, and decorate it with the script decorator.
2. Start NVDA

### Actual behavior:
Several errors in the log indicating that script binding failed

### Expected behavior:
No errors

### Technical details
This is caused by the fact that GlobalPlugin classes are all called GlobalPlugin. When the ScriptableType's __new__ method modifies a class, it mutates _GlobalPlugin__gestures. However, _GlobalPlugin__gestures is part of the globalPluginHandler.GlobalPlugin class, so globalPluginHandler.GlobalPlugin._GlobalPlugin__gestures receives all the gestures defined on its subclasses.


Discussion:
I'm labeling this p1, as when we can't solve this properly, we will have to revert #8236 before 2018.3 comes out.
The following quote from the [PEP 8 style guide](https://www.python.org/dev/peps/pep-0008/#naming-conventions) describes our problem exactly.

> Note 1: Note that only the simple class name is used in the mangled name, so if a subclass chooses both the same class name and attribute name, you can still get name collisions.

Resolving patch:
diff --git a/source/baseObject.py b/source/baseObject.py
--- a/source/baseObject.py
+++ b/source/baseObject.py
@@ -133,7 +133,14 @@ class ScriptableType(AutoPropertyType):
 
 	def __new__(meta, name, bases, dict):
 		cls = super(ScriptableType, meta).__new__(meta, name, bases, dict)
-		gestures = getattr(cls, "_%s__gestures" % cls.__name__, {})
+		gesturesDictName = "_%s__gestures" % cls.__name__
+		# #8463: To avoid name mangling conflicts, create a copy of the __gestures dictionary.
+		try:
+			gestures = getattr(cls, gesturesDictName).copy()
+		except AttributeError:
+			# This class currently has no gestures dictionary,
+			# because no custom __gestures dictionary has been defined.
+			gestures = {}
 		# Python 3 incompatible.
 		for name, script in dict.iteritems():
 			if not name.startswith('script_'):
@@ -142,7 +149,8 @@ def __new__(meta, name, bases, dict):
 			if hasattr(script, 'gestures'):
 				for gesture in script.gestures:
 					gestures[gesture] = scriptName
-		setattr(cls, "_%s__gestures" % cls.__name__, gestures)
+		if gestures:
+			setattr(cls, gesturesDictName, gestures)
 		return cls
 
 class ScriptableObject(AutoPropertyObject):
@@ -188,11 +196,14 @@ def bindGesture(self, gestureIdentifier, scriptName):
 		@type scriptName: str
 		@raise LookupError: If there is no script with the provided name.
 		"""
+		scriptAttrName = "script_%s" % scriptName
 		# Don't store the instance method, as this causes a circular reference
 		# and instance methods are meant to be generated on retrieval anyway.
-		func = getattr(self.__class__, "script_%s" % scriptName, None)
+		func = getattr(self.__class__, scriptAttrName, None)
 		if not func:
-			raise LookupError("No such script: %s" % func)
+			raise LookupError("No such script on class {className}. Couldn't find attribute: {scriptAttrName}".format(
+				className=self.__class__.__name__, scriptAttrName=scriptAttrName
+			))
 		# Import late to avoid circular import.
 		import inputCore
 		self._gestureMap[inputCore.normalizeGestureIdentifier(gestureIdentifier)] = func


Test patch: