Restored Objective-C language support
Commit Message
2016-09-13 Giah de Barag <gdb@crelg.com>
Summary
-------
Patches are presented which restore objective-c language support.
These patches are relative to the head of the gdb-7.11-branch.
Impact
------
These changes impact only objective-c language mode.
Description
-----------
These patches enable GDB to (once again) evaluate objective-c
expressions:
(a) [object message:arg] messages,
(b) @"foo" NSStrings, and
(c) @selector(foo:bar:) selectors.
(d) "foo" c strings
These patches complete the previously-unfinished work of merging
objc-exp.y into c-exp.y.
Explanation of Changes
----------------------
Problem 1: Evaluating "foo" c-string literals fails in objective-c
language mode.
Description:
(gdb) set language objective-c
(gdb) p "hello"
$1 = <error reading variable>
Creating a basic C string literal does not work when
language is objective-c.
Solution:
Change objective-c evaluate_subexp_standard to
evaluate_subexp_c.
Test:
The current source language is "auto; currently objective-c".
(gdb) p strlen("test")
$1 = 4
Problem 2: Evaluating objc expressions fails in objective-c language
mode. Examples:
(a) [object message:arg] messages,
(b) @"foo" NSStrings, and
(c) @selector(foo:bar:) selectors.
Description:
(gdb) p [@"hello" performSelector:@selector(length)]
Crashes and burns in many specactular ways, reported in
bugs 20501 and 20503.
/* file: c-exp.y */
exp : NSSTRING
{ write_exp_elt_opcode (pstate, OP_OBJC_NSSTRING);
write_exp_string (pstate, $1);
write_exp_elt_opcode (pstate, OP_OBJC_NSSTRING); } ;
exp : SELECTOR
{ write_exp_elt_opcode (pstate, OP_OBJC_SELECTOR);
write_exp_string (pstate, $1);
write_exp_elt_opcode (pstate, OP_OBJC_SELECTOR); } ;
Communication between scanner and parser is broken; when the
program attempts to reduce these productions, $1 is null.
Solution:
Establish communication between the scanner and the parser by
creating a global variable, last_parsed_token, to hold the last
scanned token (struct typed_stoken) for string and for selector.
The existing @selector() scanner was weak. A new, robust
@selector() scanner was written and inserted to improve quality.
The string scanner puts a pointer to the last scanned token into
last_parsed_token (in its choice of object names, gdb sometimes
conflates the meaning of scanning and parsing, which we went along
with). The newly-created selector scanner also does the same.
Test:
(gdb) p [@"hello" performSelector:@selector(length)]
$2 = 5
(gdb)
Here is another test, more complicated and pedantic, that
invokes all the features that were broken, creating an
NSString on the fly and sending it a a message which turns it
into an NSArray, then sending that NSArray a message which
sends a message to all its objects and creates another NSArray
out of the results.
(gdb) po [[@"(london, paris, rome, berlin)" propertyList] \
performSelector:@selector(mappedArrayUsingSelector:) \
withObject:@selector(capitalizedString)]
(London, Paris, Rome, Berlin)
(gdb)
This single invocation tests:
----------------------------
* the “po” command
* creating a literal NSString @"foo" (which responds to messages)
* sending a message [obj message:arg] to the result of a message
* referencing a selector @selector(foo) (two different ones in
one invocation)
Bugs Fixed
----------
These patches fix all bugs reported in:
https://sourceware.org/bugzilla/show_bug.cgi?id=20501
https://sourceware.org/bugzilla/show_bug.cgi?id=20503
By the way, this bug is obsolete and should be updated:
https://sourceware.org/bugzilla/show_bug.cgi?id=11925
Note to GDB Maintainers
-----------------------
Can you adapt these patches to all relevant branches, or do you need me to
provide patches relative to other branches?
I can supply a recipe to build GNUstep if you want to test this patch
yourself.
Also, this is the first time I am doing something like this, so if I am
neglecting any rule of communication of this list, please inform me, and I
will correct it.
Patches
-------
Here are the patches relative to gdb-7.11-branch. Do you need them
relative to the other branches?
----- CUT HERE -----
Comments
>>>>> "Giah" == Giah de Barag <gdb@crelg.com> writes:
Giah> Patches are presented which restore objective-c language support.
Giah> These patches are relative to the head of the gdb-7.11-branch.
I didn't read the patches really, but I feel a bit responsible since I
merged objc-exp.y into c-exp.y back in the day, and apparently not well.
I think the most important thing to do to avoid future problems is make
sure the objc tests are working. The last time I looked at this, I
think they weren't working properly at all.
Also, it would be good to have new tests for the patches you wrote.
Giah> Also, this is the first time I am doing something like this, so if
Giah> I am neglecting any rule of communication of this list, please
Giah> inform me, and I will correct it.
Nothing wrong with your email but there are some contribution
instructions for gdb:
https://sourceware.org/gdb/wiki/ContributionChecklist
Tom
On Wed, Sep 14, 2016 at 3:54 PM, Tom Tromey <tom@tromey.com> wrote:
>>>>>> "Giah" == Giah de Barag <gdb@crelg.com> writes:
>
> Giah> Patches are presented which restore objective-c language support.
> Giah> These patches are relative to the head of the gdb-7.11-branch.
>
> I didn't read the patches really, but I feel a bit responsible since I
> merged objc-exp.y into c-exp.y back in the day, and apparently not well.
>
> I think the most important thing to do to avoid future problems is make
> sure the objc tests are working. The last time I looked at this, I
> think they weren't working properly at all.
I Agree, part of the problem is that during some "modernization" of
the objective-c runtime, the "Object" class, the root class that is
distributed with the runtime was neutered basically into
non-existence...
I didn't really manage to catch it before release, and gave up afterwords...
https://github.com/gnustep/libobjc2/blob/master/objc/Object.h
https://gcc.gnu.org/git/?p=gcc.git;a=blob;f=libobjc/objc/Object.h;h=f69f8139e8c0b0841e806f72747d09ca641dd2f8;hb=refs/heads/trunk
There used to be... methods, in particular there was a method called 'new'
which allowed one to actually allocate and instantiate an object in a
portable fashion.
given that there are about 50 different objective-c runtimes,
It seemed completely out of scope for the gdb testsuite...
I'm not sure what gcc and clang themselves are using to test i'd
imagine the c/runtime specific interface they provide... but it's a
bit inconvenient for a project like gdb that sits between gcc and
something external like gnustep (which provides its own mechanism for
instantiating objects).
> Also, it would be good to have new tests for the patches you wrote.
>
> Giah> Also, this is the first time I am doing something like this, so if
> Giah> I am neglecting any rule of communication of this list, please
> Giah> inform me, and I will correct it.
>
> Nothing wrong with your email but there are some contribution
> instructions for gdb:
>
> https://sourceware.org/gdb/wiki/ContributionChecklist
>
> Tom
GDB and Objective C
-------------------
1. GDB Objective C Language Support
2. How GDB Creates Objective C NSString Objects
3. How GDB Looks Up Objective C Selectors
4. GNUstep Source & Build Scripts
5. GNUstep and GDB Test
6. Questions Regarding the Patch
1. GDB Objective C Language Support
-----------------------------------
GDB support for Objective C (GCC libobjc and GNUstep) is deep,
powerful, and way beyond mature. The transition from Object to
NSObject was made somewhere around 1990.
GNUstep is *the* (the) GNU library of Objective C high-level types
(string, array, dictionary, set, process, thread, file, etc).
GCC libobjc is one of the two (2) low-level implementations of the
object-oriented runtime design (object, method, selector, etc.) that
work with GCC/GDB.
GNUstep is a part of the GNU software collection, is trivial to build
and package, and is a package on GNU-type system distributions.
GDB’s Objective C language support code has interacted with GNUstep
and the NSObject hierarchy for decades. That code is simple, easy to
understand, and easy to maintain.
2. How GDB Creates Objective C NSString Objects
-----------------------------------------------
Here is a look at part of the GDB/GNUstep debugger API.
In this example, GDB creates a GNUstep NSString when the user types
@"foo". It looks for a convenience function _NSNewStringFromCString,
and if not found, it sends the message stringWithCString to the
GNUstep class NSString.
if (lookup_minimal_symbol("_NSNewStringFromCString", 0, 0).minsym)
{
function = find_function_in_inferior("_NSNewStringFromCString", NULL);
nsstringValue = call_function_by_hand(function, 1, &stringValue[2]);
}
else if (lookup_minimal_symbol("+[NSString stringWithCString:]", 0, 0).minsym)
{
function
= find_function_in_inferior("+[NSString stringWithCString:]", NULL);
type = builtin_type (gdbarch)->builtin_long;
stringValue[0] = value_from_longest
(type, lookup_objc_class (gdbarch, "NSString"));
stringValue[1] = value_from_longest
(type, lookup_child_selector (gdbarch, "stringWithCString:"));
nsstringValue = call_function_by_hand(function, 3, &stringValue[0]);
}
else
error (_("NSString: internal error -- no way to create new NSString"));
In http://svn.gna.org/svn/gnustep/libs/base/trunk/Source/NSDebug.m,
which is in the GNUstep repository, at the very end of the file, you
can see the implementation of _NSNewStringFromCString. Also there is
the function _NSPrintForDebugger which handles the “po” (print object)
command of GDB.
// GNUstep debugger support functions
NSString *_NSNewStringFromCString(const char *cstring);
const char *_NSPrintForDebugger(id object);
3. How GDB Looks Up Objective C Selectors
-----------------------------------------
Here is GDB’s code that looks up a selector.
In the previous example, GDB addressed GNUstep, the high-level type
library, to create an Objective C string (NSString), and here we are
interacting with the low-level runtime library, GCC libobjc, and its
function sel_getUid.
if (lookup_minimal_symbol("sel_getUid", 0, 0).minsym)
function = find_function_in_inferior("sel_getUid", NULL);
else
{
complaint (&symfile_complaints,
_("no way to lookup Objective-C selectors"));
return 0;
}
In https://github.com/gcc-mirror/gcc/blob/master/libobjc/selector.c at the
very end you can see sel_getUid, which takes the string name representing a
method and returns the selector for it, SEL sel_getUid (const char *name).
4. GNUstep Source & Build Scripts
---------------------------------
GNUstep is trivial to install. If it is not a package, you can check
it out and build it.
You only need the “base” library which implements the basic high-level
types like NSString and has the GDB debugger support functions.
Source:
svn co http://svn.gna.org/svn/gnustep/modules/core
(For GDB Objective C language support,
only “make” and “base” are needed.)
Build Scripts:
http://svn.gna.org/svn/gnustep/tools/scripts/trunk/
It would be good for the teams (GCC/GDB/GNUstep) to be more in step
with each other. What do you think?
5. GNUstep and GDB Test
-----------------------
This is a test of:
a) printing an object with po
b) sending a message with [obj msg:arg]
c) creating an NSString with @"foo"
d) looking up a selector with @selector(msg)
GDB console session:
(gdb) po [@"foo" performSelector:@selector(uppercaseString)]
FOO
(gdb)
6. Questions Regarding the Patch
--------------------------------
FAOD:
1. About the Contribution Checklist. Do you need me to update this
patch to conform to the Contribution Checklist?
2. About tests. Are there in fact no automated tests for objective-c
support, and do you need me to create them?
3. Are there any barriers to this patch getting into 7.11, 7.12, and
master (other than the above two questions)?
Thank you.
> On Sep 14, 2016, at 18:54, Tom Tromey <tom@tromey.com> wrote:
>
> I think the most important thing to do to
> avoid future problems is make sure the objc
> tests are working. The last time I looked at
> this, I think they weren't working properly at
> all.
>
> Also, it would be good to have new tests for
> the patches you wrote.
I will work on fixing the broken tests and
writing new tests for the patches I
submitted.
(Ignore the question about tests in my
previous message.)
2016-09-27 Giah de Barag <gdb@crelg.com>
Summary
-------
This new patch restores a newly-discovered removed piece of objective-c
language support.
This piece (like those previously reported) seems to have been
inadvertently removed when objc-eval.y was deleted.
Problem Behavior
----------------
This problem surfaces when typecasting an object pointer to a class type
(in order to dereference it and examine its member variables).
(gdb) p (NSAutoreleasePool *)pool
A syntax error in expression, near `NSAutoreleasePool *)pool'.
(gdb)
Correction
----------
Applying this patch corrects the problem by simply restoring a rule from
objc-eval.y (that was lost when objc-eval.y was deleted).
Corrected Behavior
------------------
After applying the patch, the problem disappears and the missing
language support reappears. Here is a pointer to an object being
typecast in order to be dereferenced.
(gdb) p (NSAutoreleasePool *)pool
$2 = (struct NSAutoreleasePool *) 0x6e0a58
(gdb) p *$
$3 =
{
{
isa = 0x671fee80 <_OBJC_Class_NSAutoreleasePool>
},
_parent = 0x0,
_child = 0x0,
_released = 0x4c15818,
_released_head = 0x6e1178,
_released_count = 8889,
_addImp = 0x670219fa <-[NSAutoreleasePool addObject:]>,
_internal = 0x0
}
(gdb)
Patch
-----
Here is the patch. It restores one rule from objc-eval.y.
----- CUT HERE -----
diff --git a/gdb/c-exp.y b/gdb/c-exp.y
@@ -1216,6 +1221,14 @@ type : ptype
typebase /* Implements (approximately): (type-qualifier)* type-specifier */
: TYPENAME
{ $$ = $1.type; }
+ | CLASSNAME
+ {
+ if ($1.type == NULL)
+ error ("No symbol \"%s\" in current context.",
+ copy_name($1.stoken));
+ else
+ $$ = $1.type;
+ }
| INT_KEYWORD
{ $$ = lookup_signed_typename (parse_language (pstate),
parse_gdbarch (pstate),
----- CUT HERE -----
>>>>> "Giah" == Giah de Barag <gdb@crelg.com> writes:
Giah> This new patch restores a newly-discovered removed piece of
Giah> objective-c language support.
Giah> This piece (like those previously reported) seems to have been
Giah> inadvertently removed when objc-eval.y was deleted.
I think the thing to do is (1) make sure the tests work, and (2) re-send
the patches following the usual contribution instructions, in particular
with a ChangeLog entry.
With that, assuming your copyright assignment is up to date, I wouldn't
expect many problems.
Tom
@@ -10563,6 +10563,7 @@ watchpoint_exp_is_const (const struct expression *exp)
case OP_TYPEID:
case OP_NAME:
case OP_OBJC_NSSTRING:
+ case OP_OBJC_SELECTOR:
case UNOP_NEG:
case UNOP_LOGICAL_NOT:
@@ -129,6 +129,8 @@ void yyerror (char *);
static int type_aggregate_p (struct type *);
+static struct typed_stoken *last_parsed_token;
+
%}
/* Although the yacc "value" of an expression is not used,
@@ -175,6 +177,8 @@ static struct stoken operator_stoken (const char *);
static void check_parameter_typelist (VEC (type_ptr) *);
static void write_destructor_name (struct parser_state *par_state,
struct stoken);
+static void write_exp_nsstring (struct parser_state *par_state, struct typed_stoken *token);
+static void write_exp_selector (struct parser_state *par_state, struct typed_stoken *token);
#ifdef YYBISON
static void c_print_token (FILE *file, int type, YYSTYPE value);
@@ -209,7 +213,7 @@ static void c_print_token (FILE *file, int type, YYSTYPE value);
%token <tsval> STRING
%token <sval> NSSTRING /* ObjC Foundation "NSString" literal */
-%token SELECTOR /* ObjC "@selector" pseudo-operator */
+%token <sval> SELECTOR /* ObjC "@selector" pseudo-operator */
%token <tsval> CHAR
%token <ssym> NAME /* BLOCKNAME defined below to give it higher precedence. */
%token <ssym> UNKNOWN_CPP_NAME
@@ -779,10 +783,11 @@ exp : VARIABLE
}
;
-exp : SELECTOR '(' name ')'
- {
- write_exp_elt_opcode (pstate, OP_OBJC_SELECTOR);
- write_exp_string (pstate, $3);
+exp : SELECTOR /* ObjC selector literal
+ * of the form @selector(foo:bar:)
+ */
+ { write_exp_elt_opcode (pstate, OP_OBJC_SELECTOR);
+ write_exp_selector (pstate, last_parsed_token);
write_exp_elt_opcode (pstate, OP_OBJC_SELECTOR); }
;
@@ -894,11 +899,11 @@ exp : string_exp
}
;
-exp : NSSTRING /* ObjC NextStep NSString constant
- * of the form '@' '"' string '"'.
+exp : NSSTRING /* ObjC GNUstep NSString literal
+ * of the form @"foo"
*/
{ write_exp_elt_opcode (pstate, OP_OBJC_NSSTRING);
- write_exp_string (pstate, $1);
+ write_exp_nsstring (pstate, last_parsed_token);
write_exp_elt_opcode (pstate, OP_OBJC_NSSTRING); }
;
@@ -1659,6 +1664,26 @@ name_not_typename : NAME
%%
+static void
+write_exp_nsstring (struct parser_state *par_state, struct typed_stoken *value)
+/* Like write_exp_string, but for Objective C NSString literal, @"foo" */
+{
+ struct stoken token;
+ token.length = value->length;
+ token.ptr = value->ptr;
+ write_exp_string (par_state, token);
+}
+
+static void
+write_exp_selector (struct parser_state *par_state, struct typed_stoken *value)
+/* Like write_exp_string, but for Objective C selector macro, @selector(foo:bar:) */
+{
+ struct stoken token;
+ token.length = value->length;
+ token.ptr = value->ptr;
+ write_exp_string (par_state, token);
+}
+
/* Like write_exp_string, but prepends a '~'. */
static void
@@ -2250,11 +2275,113 @@ parse_string_or_char (const char *tokptr, const char **outptr,
value->ptr = (char *) obstack_base (&tempbuf);
value->length = obstack_object_size (&tempbuf);
+ last_parsed_token = value;
+
*outptr = tokptr;
return quote == '"' ? (is_objc ? NSSTRING : STRING) : CHAR;
}
+/* Scan an objc selector literal (described by the following regex) from
+ TOKEN_STREAM, place $1 (the value inside the literal parens) into VALUE
+ and return a pointer to the next input in the token_stream.
+
+ @?selector\(([0-9A-Za-z_:-]+)\)
+ */
+static const char *
+parse_objc_selector_literal (const char *tokptr, struct typed_stoken *value)
+{
+ const char *s1 = NULL;
+ const char *s2 = NULL;
+ const char *str = NULL; /* not null-terminated, just a pointer into token_stream */
+ int len = 0;
+ static unsigned sl;
+
+ /* skip any leading whitespace */
+ s1 = tokptr;
+ s1 = skip_spaces_const (s1);
+
+ /* parse the '@' */
+ if (*s1 == '@')
+ s1++;
+ s1 = skip_spaces_const (s1);
+
+ /* parse the word "selector" */
+ if (!sl)
+ sl = strlen("selector");
+ if (strncmp (s1, "selector", sl) != 0)
+ {
+ /* error (_("malformed @selector() macro: expected 'selector'")); */
+ return NULL;
+ }
+ s1 += sl;
+ s1 = skip_spaces_const (s1);
+
+ /* parse the open paren */
+ if (*s1 != '(')
+ {
+ error (_("malformed @selector() macro: expected '('"));
+ return NULL;
+ }
+ s1++;
+ s1 = skip_spaces_const (s1);
+
+ str = s1;
+ s2 = s1;
+
+ /* parse the selector string [0-9A-Za-z_: -]) */
+ for (;;)
+ {
+ if (isalnum (*s2) || (*s2 == '_') || (*s2 == ':') || (*s2 == '-'))
+ ;
+ else if (*s2 == ')' || isspace(*s2))
+ break;
+ else
+ {
+ int buflen = s2 - s1;
+ if (!tempbuf_init)
+ tempbuf_init = 1;
+ else
+ obstack_free (&tempbuf, NULL);
+ obstack_init (&tempbuf);
+ obstack_grow (&tempbuf, str, buflen);
+ obstack_1grow (&tempbuf, '\0');
+ error ("illegal selector: \"%s\"", tempbuf);
+ obstack_free (&tempbuf, NULL);
+ return NULL;
+ }
+ s2++;
+ }
+ len = s2 - s1;
+
+ /* slurp up spaces if terminator was ')' */
+ s2++;
+ s2 = skip_spaces_const (s2);
+
+ /* slurp up ')' if terminator was space(s) */
+ if (*s2 == ')')
+ s2++;
+ s2 = skip_spaces_const (s2);
+
+ /* save the token for reference in the productions */
+ gdb_assert (value != NULL);
+ if (!tempbuf_init)
+ tempbuf_init = 1;
+ else
+ obstack_free (&tempbuf, NULL);
+
+ obstack_init (&tempbuf);
+ obstack_grow (&tempbuf, str, len);
+
+ value->type = SELECTOR;
+ value->ptr = (char *) obstack_base (&tempbuf);
+ value->length = obstack_object_size (&tempbuf);
+
+ last_parsed_token = value;
+
+ return s2;
+}
+
/* This is used to associate some attributes with a token. */
enum token_flag
@@ -2655,14 +2782,14 @@ lex_one_token (struct parser_state *par_state, int *is_quoted_name)
if (parse_language (par_state)->la_language == language_objc)
{
- size_t len = strlen ("selector");
-
- if (strncmp (p, "selector", len) == 0
- && (p[len] == '\0' || isspace (p[len])))
+ /* try to parse a selector literal */
+ const char *newlexptr = parse_objc_selector_literal(p, &yylval.tsval);
+ if (newlexptr != NULL)
{
- lexptr = p + len;
+ lexptr = newlexptr; /* p + yylval.tsval.length */
return SELECTOR;
}
+ /* there was no selector so try to parse a string literal */
else if (*p == '"')
goto parse_string;
}
@@ -3291,6 +3418,7 @@ c_print_token (FILE *file, int type, YYSTYPE value)
break;
case NSSTRING:
+ case SELECTOR:
case VARIABLE:
fprintf (file, "sval<%s>", copy_name (value.sval));
break;
@@ -339,6 +339,7 @@ static const struct op_print objc_op_print_tab[] =
{"/", BINOP_DIV, PREC_MUL, 0},
{"%", BINOP_REM, PREC_MUL, 0},
{"@", BINOP_REPEAT, PREC_REPEAT, 0},
+ {"+", UNOP_PLUS, PREC_PREFIX, 0},
{"-", UNOP_NEG, PREC_PREFIX, 0},
{"!", UNOP_LOGICAL_NOT, PREC_PREFIX, 0},
{"~", UNOP_COMPLEMENT, PREC_PREFIX, 0},
@@ -358,7 +359,7 @@ const struct language_defn objc_language_defn = {
case_sensitive_on,
array_row_major,
macro_expansion_c,
- &exp_descriptor_standard,
+ &exp_descriptor_c,
c_parse,
c_error,
null_post_parser,
@@ -385,7 +386,7 @@ const struct language_defn objc_language_defn = {
c_language_arch_info,
default_print_array_index,
default_pass_by_reference,
- default_get_string,
+ c_get_string,
NULL, /* la_get_symbol_name_cmp */
iterate_over_symbols,
&default_varobj_ops,