Skip to content

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

Merged
merged 1 commit into from
Mar 8, 2017

Conversation

bsdelf
Copy link
Contributor

@bsdelf bsdelf commented Feb 10, 2017

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):

'use strict';

var print = print || console.log;

var printError = (function (i) {
  return function (head, error) {
    print(i + (i <= 9 ? ' ' : ''), head, Array(30 - head.length).join(' '), error);
      print(Array(100).join('-'));
      ++i;
  };
})(1);

[ 'x = 1;',
  'x += 1;',
  'var y = x;',
  'x.y = 1;',
  'var x; x.y;',
  'var x; x[1];',
  'var x; x.y = 1;',
  'var x; x[0] = 1;',
  'var x = null; x[\'y\'];',
  'null.y = 1;'
].forEach(function (src) {
  try {
    eval(src);
  } catch (e) {
    printError(src, e);
  }
});

The expected output would be:

1  x = 1;                         ReferenceError: x is not defined
---------------------------------------------------------------------------------------------------
2  x += 1;                        ReferenceError: x is not defined
---------------------------------------------------------------------------------------------------
3  var y = x;                     ReferenceError: x is not defined
---------------------------------------------------------------------------------------------------
4  x.y = 1;                       ReferenceError: x is not defined
---------------------------------------------------------------------------------------------------
5  var x; x.y;                    TypeError: Cannot read property 'y' of undefined
---------------------------------------------------------------------------------------------------
6  var x; x[1];                   TypeError: Cannot read property '1' of undefined
---------------------------------------------------------------------------------------------------
7  var x; x.y = 1;                TypeError: Cannot set property 'y' of undefined
---------------------------------------------------------------------------------------------------
8  var x; x[0] = 1;               TypeError: Cannot set property '0' of undefined
---------------------------------------------------------------------------------------------------
9  var x = null; x['y'];          TypeError: Cannot read property 'y' of null
---------------------------------------------------------------------------------------------------
10 null.y = 1;                    TypeError: Cannot set property 'y' of null
---------------------------------------------------------------------------------------------------

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__); \
Copy link
Member

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?

@bsdelf bsdelf force-pushed the improve-error-message branch from 582854e to dad2c22 Compare February 10, 2017 14:34
@zherczeg
Copy link
Member

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.

@@ -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);
Copy link
Member

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.

Copy link
Member

@zherczeg zherczeg Feb 13, 2017

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.

Copy link
Contributor Author

@bsdelf bsdelf Feb 14, 2017

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?

Copy link
Member

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.

@dbatyai
Copy link
Member

dbatyai commented Feb 10, 2017

The JERRY_ENABLE_ERROR_MESSAGES guard already exists and would be suitable here as well, so no need for a new one.

@bsdelf bsdelf force-pushed the improve-error-message branch 4 times, most recently from 1cea6e8 to 7076e72 Compare February 18, 2017 13:21
@bsdelf
Copy link
Contributor Author

bsdelf commented Feb 19, 2017

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)
Copy link
Member

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.

@dbatyai
Copy link
Member

dbatyai commented Feb 20, 2017

In my opinion it's completely fine for ecma_join_ecma_strings to dereference the argument strings.

@zherczeg
Copy link
Member

I agree, binary size matters.

Copy link
Member

@zherczeg zherczeg left a 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
Copy link
Member

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)
Copy link
Member

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; ...)

Copy link
Member

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);
Copy link
Member

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);
Copy link
Member

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)
Copy link
Member

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);
Copy link
Member

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,"'")
Copy link
Member

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.

Copy link
Contributor Author

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?

Copy link
Member

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?

Copy link
Contributor

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.

Copy link
Member

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.

Copy link
Contributor

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.

Copy link
Contributor Author

@bsdelf bsdelf Feb 24, 2017

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.

@zherczeg @LaszloLango

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent idea.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds great.

@bsdelf bsdelf force-pushed the improve-error-message branch 2 times, most recently from cf97620 to 1221039 Compare February 25, 2017 17:04
@bsdelf
Copy link
Contributor Author

bsdelf commented Feb 27, 2017

Format function:

ecma_string_t *ecma_format_ecma_strings (const char *format, ...);

Supported placeholders:

  • %: ecma-string.
  • %%: single percent sign.

Pitfalls:

  • ecma_format_ecma_strings ("%%", str_prefix_p, str_p)
  • ecma_format_ecma_strings ("%%%%%", str_surrounded_by_percent_p)

@zherczeg @LaszloLango

Copy link
Member

@zherczeg zherczeg left a 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 */
Copy link
Member

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 */
Copy link
Member

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.

Copy link
Contributor

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. :)

Copy link
Contributor Author

@bsdelf bsdelf Feb 28, 2017

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);
}
Copy link
Member

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would be best.

@bsdelf bsdelf force-pushed the improve-error-message branch 2 times, most recently from 060a8e5 to 0e3791a Compare February 28, 2017 07:13
*/
ecma_string_t *
ecma_format_ecma_strings (const char *format, /**< format string */
...) /**< ecma-strings */
Copy link
Member

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);
Copy link
Member

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

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);
Copy link
Member

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.

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);
Copy link
Member

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.

@zherczeg
Copy link
Member

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.

@bsdelf
Copy link
Contributor Author

bsdelf commented Mar 6, 2017

Patch updated. @zherczeg @LaszloLango

@@ -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, ...);
Copy link
Contributor

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);
Copy link
Contributor

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?

Copy link
Contributor Author

@bsdelf bsdelf Mar 6, 2017

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.

Copy link
Contributor

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.

Copy link
Member

@zherczeg zherczeg left a 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. */
Copy link
Member

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))
Copy link
Member

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.

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);
Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

@bsdelf bsdelf Mar 6, 2017

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.

Copy link
Member

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see...

@bsdelf bsdelf force-pushed the improve-error-message branch from 5d8fdca to 577bdc8 Compare March 7, 2017 14:02
@bsdelf
Copy link
Contributor Author

bsdelf commented Mar 8, 2017

Patch updated. @zherczeg @LaszloLango

Copy link
Member

@zherczeg zherczeg left a 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.

ecma_free_value (property);
return to_object;

return error_value;
Copy link
Member

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]
@bsdelf bsdelf force-pushed the improve-error-message branch from 577bdc8 to 919db82 Compare March 8, 2017 08:20
@bsdelf
Copy link
Contributor Author

bsdelf commented Mar 8, 2017

Partially reverted. @zherczeg @LaszloLango

Copy link
Contributor

@LaszloLango LaszloLango left a 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

@zherczeg zherczeg merged commit a2a160d into jerryscript-project:master Mar 8, 2017
@bsdelf bsdelf deleted the improve-error-message branch March 8, 2017 12:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants