Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

refactor($compile): remove unnecessary try/catch #13263

Merged
merged 1 commit into from
Aug 30, 2016

Conversation

jbedard
Copy link
Collaborator

@jbedard jbedard commented Nov 6, 2015

I don't see anything in there that could throw...

@gkalpak
Copy link
Member

gkalpak commented Nov 6, 2015

It could throw if directive.restrict does not have an .indexOf method (e.g. it is not a String; nor an array). Funny enough (and although unintented afaict), using an array works 😃

See both in action here.

Since I don't think we want to keep a non-string, user-specified directive.restrict, I suggest keeping this change, but also changing the line that sets directive.restrict from:

directive.restrict = directive.restrict || 'EA';

...to:

directive.restrict = isString(directive.restrict) && directive.restrict || 'EA';
// or
directive.restrict = (directive.restrict && isString(directive.restrict)) ? directive.restrict : 'EA';

@jbedard
Copy link
Collaborator Author

jbedard commented Nov 6, 2015

There are plenty of other directive properties without validation (priority, link, name, require, ...). But I'd be fine doing it if you'd like, better once in validation then a try/catch at runtime...

@gkalpak
Copy link
Member

gkalpak commented Nov 6, 2015

I don't feel strongly either way (as they are both better than a try-catch).

You said you didn't see anything that could throw - just pointing out there is 😛

@jbedard
Copy link
Collaborator Author

jbedard commented Nov 6, 2015

True enough 😄

@Narretz
Copy link
Contributor

Narretz commented Nov 11, 2015

@gkalpak With your proposed change, you wouldn't notice that you have a wrong restrict setting, right? Because it would silently use 'EA' in that case.

@Narretz Narretz added this to the Backlog milestone Nov 11, 2015
@gkalpak
Copy link
Member

gkalpak commented Nov 12, 2015

@Narretz, yes, but that is already the case if you have a falsy value or a wrong value (e.g. 'XYZ'). In both cases your directive won't work as expected, but there won't be an error.

Do you think it's better to log a warning ?
The idea is to be able to safely remove the try-catch, without crashing the whole app if someone mistyped their restrict.

@Narretz
Copy link
Contributor

Narretz commented Nov 13, 2015

@gkalpak I see what you mean now. I guess we could log a debug / warning note. It would be pretty unusual for us, though.

@jbedard
Copy link
Collaborator Author

jbedard commented Nov 14, 2015

I think logging such a thing would be unusual and inconsistent with all the other DDO properties that could be invalid (and are not checked)...

@Narretz
Copy link
Contributor

Narretz commented Feb 29, 2016

I'm still a little uneasy with merging this, as we remove the possibility to handle the exception. I don't think that anyone uses this, but you never know. And in that respect it would be a BC.

@jbedard
Copy link
Collaborator Author

jbedard commented Mar 1, 2016

I still don't see any issue with that, we don't do such validation on any other property. But if we did want validation I'd think it should be done on directive instantiation instead of each and every attempt at using the directive.

@jbedard jbedard force-pushed the compile-remove-trycatch branch from ed6af3e to 7aada46 Compare March 1, 2016 07:12
@jbedard jbedard force-pushed the compile-remove-trycatch branch from 7aada46 to 2ed4849 Compare April 12, 2016 06:05
@jbedard
Copy link
Collaborator Author

jbedard commented Apr 12, 2016

Any thoughts on this? Allow it to throw in addDirective (like this PR currently changes it to do)? If we want to do validation of directive properties I think it should be in registerDirective...

@gkalpak
Copy link
Member

gkalpak commented Apr 12, 2016

I agree with @Narretz that this is a (tiny, highly unlikely to affect anyone) BC.
The problem is that currently, if a directive has a bad restrict value, it will fail, but other directives will work fine. With this change, the problem of one directive, breaks out of the loop and might affect other directives.

So, an app that is working without this change (a simply logs an error for one broken directive), could stop working with this change.

So, I would not merge it into 1.5.
If it offers a non-negligible perf improvement, I would get it into 1.6 (with a BC notice).

("non-negligible" is subjective, so I leave room for interpretation 😛 )

@jbedard
Copy link
Collaborator Author

jbedard commented Apr 13, 2016

If we want to do such validation how about moving it to directive registration instead of on each and every compilation of that directive? Then the user will get one error logged when the directive factory runs instead of 1 per compilation of the directive.

@gkalpak
Copy link
Member

gkalpak commented Apr 13, 2016

Just to be clear, I don't feel strongly about keeping the try-catch block or doing any validation. I just want to avoid breaking a working app.

So, to that end, since directive.restrict.indexOf is the only part that could (reasonably) throw here, I'm fine replacing the try-catch with a check that directive.restrict.indexOf exists/is a function (either inside addDirectives or during registration if that doesn't break the current behavior of decorated directives).

It's debatable if we want to just skip directives with an invalid restrict property or call the $exceptionHandler().

@jbedard jbedard force-pushed the compile-remove-trycatch branch from 2ed4849 to bfacf9f Compare June 1, 2016 08:17
@jbedard
Copy link
Collaborator Author

jbedard commented Jun 1, 2016

I've added an additional commit that validates the restrict property when the directive factory runs. This way errors are caught and logged but only once per directive instead of once per compilation of the directive like previously. The validation could be a bit stricter, like not allowing "foobar!@-E ", but that would actually be a breaking change so I didn't go that far (the "E" restricts it to elements).

@jbedard jbedard force-pushed the compile-remove-trycatch branch 4 times, most recently from 20f3c34 to 93a4d75 Compare June 3, 2016 06:41

This error occurs when the restrict property of a directive is not valid.

The directive restrict property must be a string with possible character values being:
Copy link
Member

Choose a reason for hiding this comment

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

I would say "...string including one of more of the following characters:".
It might be also worth adding a couple of example: restrict: 'E', restrict: 'EAC'

$compileProvider.directive('invalidRestrictZero', valueFn({restrict: 0}));

// We need to test with the exceptionHandler not rethrowing...
$exceptionHandlerProvider.mode('log');
Copy link
Member

Choose a reason for hiding this comment

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

You can keep the rethrow mode and use the toThrowMinErr helper instead.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

By default in tests $exceptionHandler seems to throw. I had to change that to only log so that we can test that it only invokes $exceptionHandler once (on first usage of the directive) and any further use of the directive does not re-log the same error message.

@jbedard jbedard force-pushed the compile-remove-trycatch branch from 93a4d75 to c985d11 Compare June 3, 2016 15:52
@@ -969,6 +969,17 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
return require;
}

function getDirectiveRestrict(restrict, name) {
if (restrict && !(isString(restrict) && /[EACM]/.test(restrict))) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I've updated this to convert all falsey values to the default like previously.

Cases which previously logged errors per-compilation (of an attribute/element with the same name), but otherwise did nothing:

  • non-string truthy values

Cases which previously silently did nothing:

  • arrays without single-character E/A/C/M
  • strings with only invalid characters

Cases that previously worked unintentionally:

  • arrays with single-character entries of E/A/C/M (such as ['E', 'A'] but not ['EA'])

Now all of these cases get logged as errors the first time an attribute/element of that name gets compiled.

@jbedard
Copy link
Collaborator Author

jbedard commented Jun 4, 2016

I think I've addressed the comments. Let me know if there's anything else, or if you can prove I'm wrong again :)

@gkalpak
Copy link
Member

gkalpak commented Jun 6, 2016

Looking better 😃 We have two possible BCs now:

  1. Directives using an array - either accidentally working (e.g. ['E', 'A']) or not working (e.g. ['EA']).
  2. Directives using string containing non-EACM characters only (e.g. 'X').

I'm OK with the first category, since it's quite far-fetched and only worked accidentally. It's probably a programming error and throwing is a good thing.

What bother's me a little with the second BC, is that (in far-fetched usecase) one could use the restrict property to determine whether on not the directive is active, based on a condition checked at runtime. E.g.:

.directive('superAdminActions', function (User) {
  return {
    restrict: User.isSuperAdmin() ? 'E' : 'X',
    ...
  };
})

It's a little contrieved, but not impossible imo 😃 So, while I don't think we need to support this, I would still land this change in 1.6.x only (with a proper BC notice).

Other than that, LGTM 😃

@jbedard
Copy link
Collaborator Author

jbedard commented Jun 8, 2016

For 1), do we care? Should we support the array case? I would vote no...

For 2) note that this change only logs an error and prevents that directive from being added to the list of directives. The logged error will be new, but it shouldn't actually break anything.

@gkalpak
Copy link
Member

gkalpak commented Jun 10, 2016

For 1), do we care? Should we support the array case? I would vote no...

Me too

For 2) note that this change only logs an error and prevents that directive from being added to the list of directives. The logged error will be new, but it shouldn't actually break anything.

The logged error already is a BC imo. But here is a super contrived example of a usecase that would be totally broken: you broke me 😛

@jbedard
Copy link
Collaborator Author

jbedard commented Jun 10, 2016

Is logging an error really a BC? It just adds information to inform the user of an error that they otherwise might not notice. Unless they purposely crash the app via $exceptionHandler I guess...

However your example does show a potential BC, but honestly that seems like something that shouldn't even be supported: changing directive properties after a directive has already been registered. Your example was changing restrict, what about changing link or controller/name, those would already have issues today (I think). I'd say we should go the opposite direction and do a copy(directive) to remove support for modifying any directive properties once the directive factory as run...

@gkalpak
Copy link
Member

gkalpak commented Jun 10, 2016

I'd say we should go the opposite direction and do a copy(directive)

Now that would be a big BC.

I'm not too bothered by any of the two BCs. If others think it is not a BC (i.e. it was never intended to work like this and if you are relying on that specific behavior it's your bad), I'm fine shipping it.
(But if people start complaining and we revert it, I will say "I told you" 😛 )

@jbedard
Copy link
Collaborator Author

jbedard commented Jun 13, 2016

Now that would be a big BC.

Do you really think people abuse this today? I think preventing modifications to the DD would make things more consistent, and allow angular to do things like parsing or validation in the factory function (like this PR) instead of per-compile/link.

@gkalpak
Copy link
Member

gkalpak commented Jun 13, 2016

Now that would be a big BC.

Do you really think people abuse this today?

I do :) I think there are people that try to access stuff off the DDO (e.g. using this inside some of the functions - template/templateUrl/compile/link). Some might define a ddo object and refer to that explicitly, e.g.:

var ddo = {
  ...
  compile: function () {
    ddo.xyz...
  }
};

return ddo;

I'm not saying these are common usecases, but we shouldn't break uncommon usecases either between minor versions. I'm fine with the change if you think it has benefits, but for 1.6.x only (not 1.5.x).

@jbedard
Copy link
Collaborator Author

jbedard commented Aug 27, 2016

Is 1.6 stuff being merged yet?

I do think isolating try/catch like this is worth it, similar to the recent acd4551 and many previous changes that did the same...

@Narretz
Copy link
Contributor

Narretz commented Aug 28, 2016

Current master === 1.6
So there's no BC here anymore? If so I'll merge it once you have squashed the commits. (Please include the comment on the conversion / check in the commit message)

@jbedard jbedard force-pushed the compile-remove-trycatch branch from cbd3b69 to fdac6ee Compare August 29, 2016 00:51
jbedard added a commit to jbedard/angular.js that referenced this pull request Aug 29, 2016
This allows the removal of try/catch from addDirective to avoid V8 deopt.

Previously the directive.restrict property was not validated. This would
potentially cause exceptions on each compilation of the directive
requiring a try/catch and potentially causing repeated errors.

New validation when directive.restrict is specified:
* must be a string
* must contain at least one valid character (E, A, C, M)

Cases which previously silently failed (now throw an error):
* values with an indexOf method (such as strings, arrays) which returned
 returned -1 for all valid restrict characters

Cases which previously worked unintentionally (now throw an error):
* arrays with single-character strings of valid restrict characters

Closes angular#13263
This allows the removal of try/catch from addDirective to avoid V8 deopt.

Previously the directive.restrict property was not validated. This would
potentially cause exceptions on each compilation of the directive
requiring a try/catch and potentially causing repeated errors.

New validation when directive.restrict is specified:
* must be a string
* must contain at least one valid character (E, A, C, M)

Cases which previously silently failed (now throw an error):
* values with an indexOf method (such as strings, arrays) which returned
 returned -1 for all valid restrict characters

Cases which previously worked unintentionally (now throw an error):
* arrays with single-character strings of valid restrict characters

Closes angular#13263
@jbedard jbedard force-pushed the compile-remove-trycatch branch from fdac6ee to 50f9086 Compare August 29, 2016 01:43
@jbedard
Copy link
Collaborator Author

jbedard commented Aug 29, 2016

I've squashed and updated the commit message, and fixed a new eslint ' vs " error.

As for breaking changes, only weird edge cases which were never intentionally supported will throw the error. Let me know if this is not clear from the commit message.

@Narretz Narretz merged commit 11f2731 into angular:master Aug 30, 2016
Narretz pushed a commit that referenced this pull request Aug 30, 2016
This allows the removal of try/catch from addDirective to avoid V8 deopt.

Previously the directive.restrict property was not validated. This would
potentially cause exceptions on each compilation of the directive
requiring a try/catch and potentially causing repeated errors.

New validation when directive.restrict is specified:
* must be a string
* must contain at least one valid character (E, A, C, M)

Cases which previously silently failed (now throw an error):
* values with an indexOf method (such as strings, arrays) which returned
 returned -1 for all valid restrict characters

Cases which previously worked unintentionally (now throw an error):
* arrays with single-character strings of valid restrict characters

PR (#13263)
petebacondarwin pushed a commit to petebacondarwin/angular.js that referenced this pull request Nov 21, 2016
This allows the removal of try/catch from addDirective to avoid V8 deopt.

Previously the directive.restrict property was not validated. This would
potentially cause exceptions on each compilation of the directive
requiring a try/catch and potentially causing repeated errors.

New validation when directive.restrict is specified:
* must be a string
* must contain at least one valid character (E, A, C, M)

Cases which previously silently failed (now throw an error):
* values with an indexOf method (such as strings, arrays) which returned
 returned -1 for all valid restrict characters

Cases which previously worked unintentionally (now throw an error):
* arrays with single-character strings of valid restrict characters

PR (angular#13263)
petebacondarwin pushed a commit to petebacondarwin/angular.js that referenced this pull request Nov 21, 2016
This allows the removal of try/catch from addDirective to avoid V8 deopt.

Previously the directive.restrict property was not validated. This would
potentially cause exceptions on each compilation of the directive
requiring a try/catch and potentially causing repeated errors.

New validation when directive.restrict is specified:
* must be a string
* must contain at least one valid character (E, A, C, M)

Cases which previously silently failed (now throw an error):
* values with an indexOf method (such as strings, arrays) which returned
 returned -1 for all valid restrict characters

Cases which previously worked unintentionally (now throw an error):
* arrays with single-character strings of valid restrict characters

PR (angular#13263)
petebacondarwin pushed a commit to petebacondarwin/angular.js that referenced this pull request Nov 21, 2016
This allows the removal of try/catch from addDirective to avoid V8 deopt.

Previously the directive.restrict property was not validated. This would
potentially cause exceptions on each compilation of the directive
requiring a try/catch and potentially causing repeated errors.

New validation when directive.restrict is specified:
* must be a string
* must contain at least one valid character (E, A, C, M)

Cases which previously silently failed (now throw an error):
* values with an indexOf method (such as strings, arrays) which returned
 returned -1 for all valid restrict characters

Cases which previously worked unintentionally (now throw an error):
* arrays with single-character strings of valid restrict characters

PR (angular#13263)
petebacondarwin pushed a commit to petebacondarwin/angular.js that referenced this pull request Nov 21, 2016
This allows the removal of try/catch from addDirective to avoid V8 deopt.

Previously the directive.restrict property was not validated. This would
potentially cause exceptions on each compilation of the directive
requiring a try/catch and potentially causing repeated errors.

New validation when directive.restrict is specified:
* must be a string
* must contain at least one valid character (E, A, C, M)

Cases which previously silently failed (now throw an error):
* values with an indexOf method (such as strings, arrays) which returned
 returned -1 for all valid restrict characters

Cases which previously worked unintentionally (now throw an error):
* arrays with single-character strings of valid restrict characters

PR (angular#13263)
petebacondarwin pushed a commit to petebacondarwin/angular.js that referenced this pull request Nov 21, 2016
This allows the removal of try/catch from addDirective to avoid V8 deopt.

Previously the directive.restrict property was not validated. This would
potentially cause exceptions on each compilation of the directive
requiring a try/catch and potentially causing repeated errors.

New validation when directive.restrict is specified:
* must be a string
* must contain at least one valid character (E, A, C, M)

Cases which previously silently failed (now throw an error):
* values with an indexOf method (such as strings, arrays) which returned
 returned -1 for all valid restrict characters

Cases which previously worked unintentionally (now throw an error):
* arrays with single-character strings of valid restrict characters

PR (angular#13263)
petebacondarwin pushed a commit that referenced this pull request Nov 23, 2016
This allows the removal of try/catch from addDirective to avoid V8 deopt.

Previously the directive.restrict property was not validated. This would
potentially cause exceptions on each compilation of the directive
requiring a try/catch and potentially causing repeated errors.

New validation when directive.restrict is specified:
* must be a string
* must contain at least one valid character (E, A, C, M)

Cases which previously silently failed (now throw an error):
* values with an indexOf method (such as strings, arrays) which returned
 returned -1 for all valid restrict characters

Cases which previously worked unintentionally (now throw an error):
* arrays with single-character strings of valid restrict characters

PR (#13263)
petebacondarwin pushed a commit that referenced this pull request Nov 24, 2016
This allows the removal of try/catch from addDirective to avoid V8 deopt.

Previously the directive.restrict property was not validated. This would
potentially cause exceptions on each compilation of the directive
requiring a try/catch and potentially causing repeated errors.

New validation when directive.restrict is specified:
* must be a string
* must contain at least one valid character (E, A, C, M)

Cases which previously silently failed (now throw an error):
* values with an indexOf method (such as strings, arrays) which returned
 returned -1 for all valid restrict characters

Cases which previously worked unintentionally (now throw an error):
* arrays with single-character strings of valid restrict characters

PR (#13263)
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants