diff --git a/lldb/test/API/lang/objcxx/conflicting-names-class-update-utility-expr/Makefile b/lldb/test/API/lang/objcxx/conflicting-names-class-update-utility-expr/Makefile new file mode 100644 --- /dev/null +++ b/lldb/test/API/lang/objcxx/conflicting-names-class-update-utility-expr/Makefile @@ -0,0 +1,4 @@ +OBJCXX_SOURCES := main.mm +LD_EXTRAS := -lobjc -framework Foundation + +include Makefile.rules diff --git a/lldb/test/API/lang/objcxx/conflicting-names-class-update-utility-expr/TestObjCConflictingNamesForClassUpdateExpr.py b/lldb/test/API/lang/objcxx/conflicting-names-class-update-utility-expr/TestObjCConflictingNamesForClassUpdateExpr.py new file mode 100644 --- /dev/null +++ b/lldb/test/API/lang/objcxx/conflicting-names-class-update-utility-expr/TestObjCConflictingNamesForClassUpdateExpr.py @@ -0,0 +1,42 @@ +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + +class TestCase(TestBase): + + mydir = TestBase.compute_mydir(__file__) + + def test(self): + """ + Tests that running the utility expression that retrieves the Objective-C + class list works even when user-code contains functions with apparently + conflicting identifiers (e.g. 'free') but that are not in the global + scope. + + This is *not* supposed to test what happens when there are actual + conflicts such as when a user somehow defined their own '::free' + function. + """ + + self.build() + lldbutil.run_to_source_breakpoint(self, "// break here", lldb.SBFileSpec("main.mm")) + + # First check our side effect variable is in its initial state. + self.expect_expr("called_function", result_summary='"none"') + + # Get the (dynamic) type of our 'id' variable so that our Objective-C + # runtime information is updated. + str_val = self.expect_expr("str") + dyn_val = str_val.GetDynamicValue(lldb.eDynamicCanRunTarget) + dyn_type = dyn_val.GetTypeName() + + # Check our side effect variable which should still be in its initial + # state if none of our trap functions were called. + # If this is failing, then LLDB called one of the trap functions. + self.expect_expr("called_function", result_summary='"none"') + + # Double check that our dynamic type is correct. This is done last + # as the assert message from above is the more descriptive one (it + # contains the unintentionally called function). + self.assertEqual(dyn_type, "__NSCFConstantString *") diff --git a/lldb/test/API/lang/objcxx/conflicting-names-class-update-utility-expr/main.mm b/lldb/test/API/lang/objcxx/conflicting-names-class-update-utility-expr/main.mm new file mode 100644 --- /dev/null +++ b/lldb/test/API/lang/objcxx/conflicting-names-class-update-utility-expr/main.mm @@ -0,0 +1,59 @@ +#import + +// Observable side effect that is changed when one of our trap functions is +// called. This should always retain its initial value in a successful test run. +const char *called_function = "none"; + +// Below several trap functions are declared in different scopes that should +// never be called even though they share the name of some of the utility +// functions that LLDB has to call when updating the Objective-C class list +// (i.e. 'free' and 'objc_copyRealizedClassList_nolock'). +// All functions just indicate that they got called by setting 'called_function' +// to their own name. + +namespace N { +void free(void *) { called_function = "N::free"; } +void objc_copyRealizedClassList_nolock(unsigned int *) { + called_function = "N::objc_copyRealizedClassList_nolock"; +} +} + +struct Context { + void free(void *) { called_function = "Context::free"; } + void objc_copyRealizedClassList_nolock(unsigned int *) { + called_function = "Context::objc_copyRealizedClassList_nolock"; + } +}; + +@interface ObjCContext : NSObject { +} +- (void)free:(void *)p; +- (void)objc_copyRealizedClassList_nolock:(unsigned int *)outCount; +@end + +@implementation ObjCContext +- (void)free:(void *)p { + called_function = "ObjCContext::free"; +} + +- (void)objc_copyRealizedClassList_nolock:(unsigned int *)outCount { + called_function = "ObjCContext::objc_copyRealizedClassList_nolock"; +} +@end + +int main(int argc, char **argv) { + id str = @"str"; + // Make sure all our conflicting functions/methods are emitted. The condition + // is never executed in the test as the process is launched without args. + if (argc == 1234) { + Context o; + o.free(nullptr); + o.objc_copyRealizedClassList_nolock(nullptr); + N::free(nullptr); + N::objc_copyRealizedClassList_nolock(nullptr); + ObjCContext *obj = [[ObjCContext alloc] init]; + [obj free:nullptr]; + [obj objc_copyRealizedClassList_nolock:nullptr]; + } + return 0; // break here +}