From 53cb351673ba99cbefec968760e9f6f702722c58 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Mon, 16 Apr 2018 21:17:50 +0100 Subject: [PATCH] Add initial work on a script that uses libclang to statically analyse extension code for potential _lua_stackguard usage --- scripts/lua_stackguard_enforcer.py | 115 +++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100755 scripts/lua_stackguard_enforcer.py diff --git a/scripts/lua_stackguard_enforcer.py b/scripts/lua_stackguard_enforcer.py new file mode 100755 index 000000000..684ecde82 --- /dev/null +++ b/scripts/lua_stackguard_enforcer.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python +""" +Enforce the usage of Hammerspoon's Lua stack guarding macros +""" + +from __future__ import print_function +import sys + +from clang.cindex import Config +from clang.cindex import Index +from clang.cindex import CursorKind +from clang.cindex import TypeKind + +FILENAME = "Unknown" + + +def is_function(node): + """Is the given node a C function or an ObjC method?""" + return node.kind in [CursorKind.FUNCTION_DECL, + CursorKind.OBJC_INSTANCE_METHOD_DECL, + CursorKind.OBJC_CLASS_METHOD_DECL] + + +def is_implementation(node): + """Is the given node an ObjC @implementation?""" + return node.kind == CursorKind.OBJC_IMPLEMENTATION_DECL + + +def is_luastate_param(node): + """Is the given node a lua_State parameter?""" + # We need the underlying text tokens to detect the typedef 'lua_State' + tokens = [x.spelling for x in node.get_tokens()] + try: + return node.type.kind == TypeKind.POINTER and \ + tokens[0] == 'lua_State' + except IndexError: + return False + + +def is_lua_call(node): + """Is the given node a Lua->C call?""" + params = [x for x in node.get_children() if x.kind == CursorKind.PARM_DECL] + return node.result_type.kind == TypeKind.INT and len(params) == 1 and \ + is_luastate_param(params[0]) + + +def build_tree(node, recurse=True): + """Build a tree below a node""" + if recurse: + children = [build_tree(c) for c in node.get_children()] + else: + children = ['skipped'] + return {'node': node, + 'kind': node.kind, + 'spelling': node.spelling, + 'tokens': ' '.join([x.spelling for x in node.get_tokens()]), + 'children': children} + + +def contains_lua_calls(item): + """Check if a node contains any Lua API calls""" + if 'lua_' in item['tokens']: + return True + if 'luaL_' in item['tokens']: + return True + if 'LuaSkin' in item['tokens']: + return True + return False + + +def main(filename): + """Main function""" + functions = [] + tree = [] + Config.set_library_file( + "/Library/Developer/CommandLineTools/usr/lib/libclang.dylib") + + index = Index.create() + ast = index.parse(None, [filename]) + if not ast: + print("Unable to parse file") + sys.exit(1) + + # Step 1: Find all the C/ObjC functions + functions = [x for x in ast.cursor.get_children() if is_function(x)] + + # Step 1a: Add in ObjC class methods + for item in ast.cursor.get_children(): + if is_implementation(item): + functions += [x for x in item.get_children() if is_function(x)] + + # Step 2: Filter out all of the functions that are Lua->C calls + functions = [x for x in functions if not is_lua_call(x)] + + # Step 3: Recursively walk the remaining functions + tree = [build_tree(x, False) for x in functions] + + # Step 4: Filter the functions down to those which contain Lua calls + tree = [x for x in tree if contains_lua_calls(x)] + + for thing in tree: + stacktxt = "NO STACKGUARD" + hasstackentry = "_lua_stackguard_entry" in thing['tokens'] + hasstackexit = "_lua_stackguard_exit" in thing['tokens'] + if hasstackentry and hasstackexit: + continue + if hasstackentry and not hasstackexit: + stacktxt = "STACK ENTRY, BUT NO EXIT" + if hasstackexit and not hasstackentry: + stacktxt = "STACK EXIT, BUT NO ENTRY (THIS IS A CRASH)" + print(u"%s :: %s :: %s" % (filename, thing['spelling'], stacktxt)) + + +if __name__ == '__main__': + main(sys.argv[1])