Type hinting is a nice tool that dynamic typing languages employ to make code more readable. As you can probably imagine, readability is not the goal with obfuscating code, so we ought to get rid of type hints when possible.
Table of Contents
The Benefits of Type Hinting Code
Many programming languages enforce some form of static typing. In those cases, declaring the type of a variable is required. However, many other programming languages have a dynamic typing system where the types are inferred through evaluation.
In the latter case, dynamic type systems are often easier to use because there’s less boilerplate and more flexibility. Of course, the cost associated with the lack of boilerplate is maintenance. How is anyone supposed to know the type of the variable without the type being declared explicitly? The answer: type hints.
One programming language with type hinting that comes to mind for me is Python, as I use it everyday. In Python, variable types are inferred, so there is no need to state the type of a variable. Prior to type hinting, the community often opted for explicit documentation to label the types of their variables. Alternatively, folks might have adopted naming conventions for specific data types to leave out ambiguity.
Eventually, Python began supporting type hints, so you can now specify the types of your variables explicitly. Here’s an example:
def bob_do_something(p: Player): pass
In other words, now parameters (among other data types) can include a type hint alongside the parameter name. These are handy for a variety of reasons, but they help a ton with autocompletion in IDEs. In addition, they show up nicely in documentation, so folks know exactly what your API needs (e.g., my add_block method).
Abandon All Types
Ultimately, if type hints help use make sense of our code, surely removing them will make code harder to read. Without type hints, how do we know what type a method returns? And in fact, languages like Python don’t enforce a particular return type, so a method could return anything. To make matters worse, a method can return nothing (i.e., void), and the lack of type hints removes this clarity from the code. Similarly, without type hints, how do we know what types a method will accept? The result is an incredibly aggravating sequence of trial and error until the method does something of value.
All of that is to say that we can really get some mileage out of our obfuscation by explicitly leaving out type hints. And I doubt you would get called out for it as tons of Python folks don’t use them to begin with.
To start, let’s look at an API I put together for a project; it generates markdown from Python code. For example, here’s a bit of the API showing how to create a document and begin editing it:
import snakemd doc = snakemd.new_doc() doc.add_heading("Why Use SnakeMD?") doc.dump("README")
From this example, there are no type hints. However, if you’re reading the documentation, you’ll know that
new_doc() returns a
Document object. You will also know that the
add_heading() method takes a string and appends it to the document as a heading. And because the API is open source, you can even dig into the source code to see how it’s done:
def add_heading(self, text: str, level: int = 1) -> Heading: """ A convenience method which adds a heading to the document: .. doctest:: document >>> doc = snakemd.new_doc() >>> doc.add_heading("Welcome to SnakeMD!") Heading(text=[Inline(text='Welcome to SnakeMD!',...)], level=1) >>> print(doc) # Welcome to SnakeMD! :param str text: the text for the heading :param int level: the level of the heading from 1 to 6 :return: the :class:`Heading` added to this Document """ heading = Heading(Inline(text), level) self._elements.append(heading) logger.info("Added heading to document: %r", heading) return heading
Now, this particular sample code is fully documented, includes doctests, contains logging, and has type hints. We aren’t really focused on most of those, so I will strip them away and leave the type hints:
def add_heading(self, text: str, level: int = 1) -> Heading: heading = Heading(Inline(text), level) self._elements.append(heading) return heading
With the clutter out of the way, it’s clear that this method takes a string called “text” and an optional “level.” In addition, it returns a “Heading”. We can strip these type hints, so it’s quite a bit more difficult to figure out what the method is doing:
def add_heading(self, text, level = 1): heading = Heading(Inline(text), level) self._elements.append(heading) return heading
It’s subtle, but with the type hints gone, it’s unclear what the parameters are expecting and if the method returns anything at all. Combining this technique with some of the others we’ve included in this series would result in some truly diabolical code.
Layering Obfuscation Techniques
Since we’re already five deep in this series, I figured I’d take a moment to really scramble the example code above using some of the techniques we’ve already discussed. For example, now that the type hints are gone, we can go ahead and start adding some malicious comments:
def add_heading(self, text, level = 1): # adds a heading to the current table heading = Heading(Inline(text), level) self._elements.append(heading) return heading
In this case, heading refers to one of the six main HTML heading tags (e.g., H1, H2, H3, etc.). Instead, we’ve suggested that actually this method adds a “heading” to a table, which I believe are actually called “headers,” so this will just add to the confusion.
While we’re at it, I figure we can take a moment to shadow the built-in string function with the text parameter:
def add_heading(self, str, level = 1): # adds a heading to the current table heading = Heading(Inline(str), level) self._elements.append(heading) return heading
Then, we can stir up the readability by littering the code with visually similar characters:
def adcl_heading(self, str, leve1 = 1): # adds a heading to the current table l1ll = Heading(Inline(str), level) self._elements.append(l1ll) return l1ll
And surely we could take this even further, as I’ve done in the past, but we still have the rest of a series to write. Therefore, if you want to see me massacre some more code, feel free to keep reading any of the following related articles:
- Abusing Python’s Operator Overloading Feature
- 5 Things You Should Know Before You Pick Up Python
- Poetry Is The Best Way to Manage Your Python Projects
Otherwise, you’re welcome to contribute a bit more by visiting my list of ways to grow the site. In either case, take care! I’ll hopefully see you back soon.
Recently, I was giving a lecture about Java's "common" methods (i.e., all of the methods of Object), and I had epiphany about how Java only has toString() while Python has str() and repr(). So, it...
Magic numbers are numerical constants that have no clear meaning in the code and therefore make code harder to read. Anything that makes code harder to read is something we can use to obfuscate our...