-
Notifications
You must be signed in to change notification settings - Fork 683
Improve error message #1577
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve error message #1577
Conversation
do { \ | ||
const int msg_size = snprintf (NULL, 0, msg_fmt, __VA_ARGS__) + 1; \ | ||
JMEM_DEFINE_LOCAL_ARRAY (msg_p, msg_size, char); \ | ||
snprintf (msg_p, (size_t) msg_size, msg_fmt, __VA_ARGS__); \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does jerry libc has snprintf?
582854e
to
dad2c22
Compare
In general the focus of the JerryScript project is low memory consumption and small binary size, so we don't give too much error context by default. Hence we need a compile time flag to enable this feature. Additional note: we are working on a debugger in #1557, that will hopefully improves debugging experience. |
jerry-core/vm/vm.c
Outdated
@@ -96,7 +96,16 @@ vm_op_get_value (ecma_value_t object, /**< base object */ | |||
|
|||
if (unlikely (ecma_is_value_undefined (object) || ecma_is_value_null (object))) | |||
{ | |||
return ecma_raise_type_error (ECMA_ERR_MSG ("Base object cannot be null or undefined.")); | |||
ecma_string_t *property_name_p = ecma_get_string_from_value (property); | |||
ECMA_STRING_TO_UTF8_STRING (property_name_p, property_name_utf8_p, property_name_size); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Personally, I would prefer if the messages were created using the string helper functions found in ecma-helpers-string.c
, instead of using intermediate buffers and snprintf, if it's possible. Maybe like defining a few magic strings, and using ecma_concat_ecma_strings
to create the final message.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be good to use create a helper function than a helper macro.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed, use ecma-helpers-string
to handle string is better than use plain C functions, but the code might be tedious to compose messages like "Cannot read property '%s' of %s"
. Would you have a suggestion?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, please create a new helper function which can do the concatenation.
The |
1cea6e8
to
7076e72
Compare
Check about the updated patch. The newly introduced helper function is: ecma_string_t *ecma_join_ecma_strings (ecma_string_t **string_array, uint32_t n); I'm not sure whether it is a good idea to let the join function dereference the input ecma-strings. Otherwise, we might need another helper function: void ecma_deref_ecma_strings (ecma_string_t **string_array, uint32_t n); |
do { \ | ||
ecma_object_t *error_obj_p = ecma_new_standard_error_with_message (error_type, error_msg_p); \ | ||
var_name = ecma_make_error_obj_value (error_obj_p); \ | ||
} while (0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you please remove this macro? It doesn't seem to do much anymore, outside of replacing one line for another.
In my opinion it's completely fine for |
I agree, binary size matters. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some comments.
@@ -699,6 +699,31 @@ ecma_concat_ecma_strings (ecma_string_t *string1_p, /**< first ecma-string */ | |||
} /* ecma_concat_ecma_strings */ | |||
|
|||
/** | |||
* Join an array of ecma-strings and release them. | |||
* | |||
* @return a newly joined ecma-string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
newly created
|
||
uint32_t i = 0; | ||
|
||
for (; i < n - 1; ++i) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for (uint32_t i = 0; ...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i++
ecma_join_ecma_strings (ecma_string_t **string_array, uint32_t n) | ||
{ | ||
JERRY_ASSERT (string_array != NULL); | ||
JERRY_ASSERT (n > 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about n > 1 ?
ecma_string_t *string2_p = string_array[i + 1]; | ||
string_array[i + 1] = ecma_concat_ecma_strings (string1_p, string2_p); | ||
ecma_deref_ecma_string (string1_p); | ||
ecma_deref_ecma_string (string2_p); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't need to overwrite the string array, just keep the final string in a local variable.
* @return a newly joined ecma-string | ||
*/ | ||
ecma_string_t * | ||
ecma_join_ecma_strings (ecma_string_t **string_array, uint32_t n) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be const array.
|
||
ecma_string_t *msg_str_p = ecma_join_ecma_strings (string_array, JERRY_NUM_OF_ELEMENTS (string_array)); | ||
ECMA_DEFINE_ERROR_VALUE (error_value, ECMA_ERROR_REFERENCE, msg_str_p); | ||
ecma_deref_ecma_string (msg_str_p); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we use this utility solely for creating errors, we could create the error object in the utility to reduce binary size.
@@ -27,6 +27,7 @@ LIT_MAGIC_STRING_FIRST_STRING_WITH_SIZE (1, LIT_MAGIC_STRING_NEW_LINE_CHAR) | |||
LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_NEW_LINE_CHAR, "\n") | |||
LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_SPACE_CHAR, " ") | |||
LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_DOUBLE_QUOTE_CHAR,"\"") | |||
LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_SINGLE_QUOTE_CHAR,"'") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think it is a good idea to add magic strings for error handling, but I am curious about others opinion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about use JERRY_ENABLE_ERROR_MESSAGES
macro to enable specific magic strings at compile time?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My problem is conceptual, no error messages are magic string at this point. Magic strings cover basic JS functionality. @LaszloLango what do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thinks the concept is that frequently used strings should be magic strings. Increasing the number of magic strings, when the error messages are disabled is not good. I'm OK with using frequent error messages as magic string if JERRY_ENABLE_ERROR_MESSAGES
is enabled.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Errors are usually short living objects. Magic strings are designed for property names which likely present for longer times. But having more magic strings does not hurt either.
Ok, we need a system which allows conditional magic string definition. There is no point adding error message strings when they never used. It would just increase binary size.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Errors are usually short living objects.
Good point. Both solutions are good to me (remove or guard).
There is no point adding error message strings when they never used. It would just increase binary size.
I agree.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Turns out introduce a format function to compose error message can make things easier:
ecma_format_ecma_string ("Cannot read property '%' of %", prop_str_p, name_str_p)
- No need to involve magic string.
- The format string can be easily found with
grep
. - Error handling in parser_parse_script() can be simplified.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Excellent idea.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds great.
cf97620
to
1221039
Compare
Format function: ecma_string_t *ecma_format_ecma_strings (const char *format, ...); Supported placeholders:
Pitfalls:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This patch is much better than the previous ones. Thank you for the hard work. A few comments below.
va_end (args); | ||
|
||
return string_p; | ||
} /* ecma_format_ecma_strings */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should add a JERRY_ENABLE_ERROR_MESSAGES macro around this function.
*/ | ||
ecma_string_t * | ||
ecma_format_ecma_strings (const char * format, /**< format string */ | ||
...) /**< ecma-strings */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I never particularly like ...
, for me it looks like a crude hack. I would prefer a constant array of ecma values. I am curious about others opinion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have no problems with it. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Variadic function is more readable at caller side, and numbers of placeholders/ arguments can be easily checked.
string_p = ecma_concat_ecma_strings (string1_p, string2_p); | ||
ecma_deref_ecma_string (string1_p); | ||
ecma_deref_ecma_string (string2_p); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to support double %
sign? This function is not a general printf, we probably don't need to overcomplicate things.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That would be best.
060a8e5
to
0e3791a
Compare
*/ | ||
ecma_string_t * | ||
ecma_format_ecma_strings (const char *format, /**< format string */ | ||
...) /**< ecma-strings */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ecma values
(lit_utf8_size_t) (end_p - start_p)); | ||
string_p = ecma_concat_ecma_strings (string1_p, string2_p); | ||
ecma_deref_ecma_string (string1_p); | ||
ecma_deref_ecma_string (string2_p); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could skip this if end_p == start_p
jerry-core/vm/vm.c
Outdated
return ecma_raise_type_error (ECMA_ERR_MSG ("Base object cannot be null or undefined.")); | ||
#ifdef JERRY_ENABLE_ERROR_MESSAGES | ||
ecma_value_t obj_to_string_result = ecma_op_to_string (object); | ||
ecma_string_t *obj_name_p = ecma_get_string_from_value (obj_to_string_result); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The ecma_op_to_string can have side effects (can run a JS code) and might not be successful. The side effects worries me most, since we might break ES5 compatibility. What about returning with the class of an object? That is always successful.
jerry-core/vm/vm.c
Outdated
ecma_value_t obj_to_string_result = ecma_op_to_string (object); | ||
ecma_string_t *obj_name_p = ecma_get_string_from_value (obj_to_string_result); | ||
|
||
ecma_value_t prop_to_string_result = ecma_op_to_string (property); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, if property is an object, we should limit this conversion.
Perhaps we could just pass all ecma values to the utility function and let it do the string conversion if needed. Objects could simply be represented with their class. |
0e3791a
to
5d8fdca
Compare
Patch updated. @zherczeg @LaszloLango |
jerry-core/ecma/base/ecma-helpers.h
Outdated
@@ -172,6 +172,7 @@ ecma_string_t *ecma_new_ecma_string_from_magic_string_id (lit_magic_string_id_t | |||
ecma_string_t *ecma_new_ecma_string_from_magic_string_ex_id (lit_magic_string_ex_id_t id); | |||
ecma_string_t *ecma_new_ecma_length_string (void); | |||
ecma_string_t *ecma_concat_ecma_strings (ecma_string_t *string1_p, ecma_string_t *string2_p); | |||
ecma_string_t *ecma_format_ecma_values (const char *format, ...); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#ifdef JERRY_ENABLE_ERROR_MESSAGES
ecma_string_t *ecma_format_ecma_values (const char *format, ...);
#endif /* JERRY_ENABLE_ERROR_MESSAGES */
if (ecma_is_value_string (arg_val)) | ||
{ | ||
arg_string_p = ecma_get_string_from_value (arg_val); | ||
ecma_ref_ecma_string (arg_string_p); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need to increase the reference here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because ecma_get_magic_string
will increase the reference count, while ecma_get_string_from_value
won't. To make algorithm consistent, we increase reference count for the latter case, and dereference them after concatation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see, thanks for the explanation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This patch is getting quite good now. Just a few more minor touches.
ecma_deref_ecma_string (string2_p); | ||
} | ||
|
||
/* Convert an argument to string without side effect. */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
effects.
} | ||
else | ||
{ | ||
if (ecma_is_value_string (arg_val)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can simply use the to_string in all other cases since non-object types can be converted to string without side effects.
jerry-core/vm/vm.c
Outdated
ecma_string_t *error_msg_p = ecma_format_ecma_values ("Cannot set property '%' of %", property, object); | ||
ecma_object_t *error_obj_p = ecma_new_standard_error_with_message (ECMA_ERROR_TYPE, error_msg_p); | ||
ecma_value_t error_value = ecma_make_error_obj_value (error_obj_p); | ||
ecma_deref_ecma_string (error_msg_p); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These four lines are repeated all the time. I would suggest to add the error type as a first argument for ecma_format_ecma_values and create the error there to reduce the binary size. Or introduce another helper. Perhaps the name could be ecma_new_standard_error_with_format.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer the latter option.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
However, if we are going to add a helper function in "ecma-exceptions.c" to raise ecma-error, why not merge ecma_format_ecma_values
into that function too? After all, ecma_format_ecma_values
is guarded by JERRY_ENABLE_ERROR_MESSAGES
, it is only used to compose error message.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was option 1, or I misunderstand something.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see...
5d8fdca
to
577bdc8
Compare
Patch updated. @zherczeg @LaszloLango |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very good patch.
LGTM after the minor code revert.
jerry-core/vm/vm.c
Outdated
ecma_free_value (property); | ||
return to_object; | ||
|
||
return error_value; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see the point of recreating the error when messages are enabled, but not when they are disabled. Please update this ifdef to not change the original behavior.
JerryScript-DCO-1.0-Signed-off-by: Yanhui Shen [email protected]
577bdc8
to
919db82
Compare
Partially reverted. @zherczeg @LaszloLango |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for your hard work. LGTM
Many error messages of JerryScript only tell there is an error but without further context, which is vague and makes it difficult to debug.
This patch helps the situtation, for errors related to unexisting properties or bad references, it can remind the property/variable names at least.
With this patch, for the following testing code (also works for
node
):The expected output would be: