commands/FBFindCommands.py (142 lines of code) (raw):
#!/usr/bin/python
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
import os
import re
import fbchisellldbbase as fb
import fbchisellldbobjcruntimehelpers as objc
import fbchisellldbviewcontrollerhelpers as vcHelpers
import lldb
def lldbcommands():
return [FBFindViewControllerCommand(), FBFindViewCommand(), FBTapLoggerCommand()]
class FBFindViewControllerCommand(fb.FBCommand):
def name(self):
return "fvc"
def description(self):
return "Find the view controllers whose class names match classNameRegex and puts the address of first on the clipboard."
def options(self):
return [
fb.FBCommandArgument(
short="-n",
long="--name",
arg="classNameRegex",
type="string",
help="The view-controller-class regex to search the view controller hierarchy for.",
),
fb.FBCommandArgument(
short="-v",
long="--view",
arg="view",
type="UIView",
help="This function will print the View Controller that owns this view.",
),
]
def run(self, arguments, options):
if options.classNameRegex and options.view:
print("Do not set both the --name and --view flags")
elif options.view:
self.findOwningViewController(options.view)
else:
output = vcHelpers.viewControllerRecursiveDescription(
"(id)[[[UIApplication sharedApplication] keyWindow] rootViewController]"
)
searchString = (
options.classNameRegex if options.classNameRegex else arguments[0]
)
printMatchesInViewOutputStringAndCopyFirstToClipboard(searchString, output)
def findOwningViewController(self, object):
while object:
if self.isViewController(object):
description = fb.evaluateExpressionValue(object).GetObjectDescription()
print("Found the owning view controller.\n{}".format(description))
cmd = 'echo {} | tr -d "\n" | pbcopy'.format(object)
os.system(cmd)
return
else:
object = self.nextResponder(object)
print("Could not find an owning view controller")
@staticmethod
def isViewController(object):
command = "[(id){} isKindOfClass:[UIViewController class]]".format(object)
isVC = fb.evaluateBooleanExpression(command)
return isVC
@staticmethod
def nextResponder(object):
command = "[((id){}) nextResponder]".format(object)
nextResponder = fb.evaluateObjectExpression(command)
try:
if int(nextResponder, 0):
return nextResponder
else:
return None
except Exception:
return None
class FBFindViewCommand(fb.FBCommand):
def name(self):
return "fv"
def description(self):
return "Find the views whose class names match classNameRegex and puts the address of first on the clipboard."
def args(self):
return [
fb.FBCommandArgument(
arg="classNameRegex",
type="string",
help="The view-class regex to search the view hierarchy for.",
)
]
def run(self, arguments, options):
output = fb.evaluateExpressionValue(
"(id)[[[UIApplication sharedApplication] keyWindow] recursiveDescription]"
).GetObjectDescription()
printMatchesInViewOutputStringAndCopyFirstToClipboard(arguments[0], output)
def printMatchesInViewOutputStringAndCopyFirstToClipboard(needle, haystack):
first = None
for match in re.finditer(
".*<.*(" + needle + ")\\S*: (0x[0-9a-fA-F]*);.*", haystack, re.IGNORECASE
):
view = match.groups()[-1]
className = fb.evaluateExpressionValue(
"(id)[(" + view + ") class]"
).GetObjectDescription()
print("{} {}".format(view, className))
if first is None:
first = view
cmd = 'echo %s | tr -d "\n" | pbcopy' % view
os.system(cmd)
class FBTapLoggerCommand(fb.FBCommand):
def name(self):
return "taplog"
def description(self):
return "Log tapped view to the console."
def run(self, arguments, options):
parameterExpr = objc.functionPreambleExpressionForObjectParameterAtIndex(0)
breakpoint = lldb.debugger.GetSelectedTarget().BreakpointCreateByName(
"-[UIApplication sendEvent:]"
)
breakpoint.SetCondition(
"(int)["
+ parameterExpr
+ " type] == 0 && (int)[[["
+ parameterExpr
+ " allTouches] anyObject] phase] == 0"
)
breakpoint.SetOneShot(True)
callback_name = taplog_callback.__qualname__
# Import the callback so LLDB can see it
lldb.debugger.HandleCommand(
"script from %s import %s" % (__name__, callback_name)
)
breakpoint.SetScriptCallbackFunction(callback_name)
lldb.debugger.SetAsync(True)
lldb.debugger.HandleCommand("continue")
def taplog_callback(frame, bp_loc, internal_dict):
parameterExpr = objc.functionPreambleExpressionForObjectParameterAtIndex(0)
print(
"Gesture Recognizers:\n{}".format(
fb.describeObject(
"[[[%s allTouches] anyObject] gestureRecognizers]" % (parameterExpr)
)
)
)
print(
"View:\n{}".format(
fb.describeObject("[[[%s allTouches] anyObject] view]" % (parameterExpr))
)
)
# We don't want to proceed event (click on button for example), so we just skip it
lldb.debugger.HandleCommand("thread return")