|
| 1 | +/// Provides detection and patching of the bug described in <https://github.com/dart-lang/sdk/issues/27647>, |
| 2 | +/// in which getters/setters with the identifier `name` don't work for emulated function classes, like [UiProps]. |
| 3 | +@JS() |
| 4 | +library react.ddc_emulated_function_name_bug; |
| 5 | + |
| 6 | +import 'package:js/js.dart'; |
| 7 | + |
| 8 | +/// Create a reduced test case of the issue, using an emulated function pattern that is similar to [UiProps]. |
| 9 | +/// |
| 10 | +/// We can't use [UiProps] itself, since it uses [isBugPresent], and that would cause a cyclic initialization error. |
| 11 | +class _NsmEmulatedFunctionWithNameProperty implements Function { |
| 12 | + void call(); |
| 13 | + |
| 14 | + @override |
| 15 | + noSuchMethod(i) {} |
| 16 | + |
| 17 | + String _name; |
| 18 | + |
| 19 | + // ignore: unnecessary_getters_setters |
| 20 | + String get name => _name; |
| 21 | + // ignore: unnecessary_getters_setters |
| 22 | + set name(String value) => _name = value; |
| 23 | +} |
| 24 | + |
| 25 | +/// Whether this bug, <https://github.com/dart-lang/sdk/issues/27647>, is present in the current runtime. |
| 26 | +/// |
| 27 | +/// This performs functional detection of the bug, and will be `true` |
| 28 | +/// only in the DDC and only in versions of the DDC where this bug is present. |
| 29 | +final bool isBugPresent = (() { |
| 30 | + const testValue = 'test value'; |
| 31 | + |
| 32 | + var testObject = new _NsmEmulatedFunctionWithNameProperty(); |
| 33 | + |
| 34 | + try { |
| 35 | + // In the DDC, this throws: |
| 36 | + // TypeError: Cannot assign to read only property 'name' of function 'function call(...args) { |
| 37 | + // return call.call.apply(call, args); |
| 38 | + // }' |
| 39 | + testObject.name = testValue; |
| 40 | + } catch(_) { |
| 41 | + return true; |
| 42 | + } |
| 43 | + |
| 44 | + try { |
| 45 | + // We don't expect accessing this to throw, but just in case... |
| 46 | + return testObject.name != testValue; |
| 47 | + } catch(_) { |
| 48 | + return true; |
| 49 | + } |
| 50 | +})(); |
| 51 | + |
| 52 | + |
| 53 | +@JS() |
| 54 | +@anonymous |
| 55 | +class _PropertyDescriptor {} |
| 56 | + |
| 57 | +@JS('Object.getPrototypeOf') |
| 58 | +external dynamic _getPrototypeOf(dynamic object); |
| 59 | + |
| 60 | +@JS('Object.getOwnPropertyDescriptor') |
| 61 | +external _PropertyDescriptor _getOwnPropertyDescriptor(dynamic object, String propertyName); |
| 62 | + |
| 63 | +@JS('Object.defineProperty') |
| 64 | +external void _defineProperty(dynamic object, String propertyName, _PropertyDescriptor descriptor); |
| 65 | + |
| 66 | +/// Patches the `name` property on the given [object] to have the expected behavior |
| 67 | +/// by copying the property descriptor for `name` from the appropriate prototype. |
| 68 | +/// |
| 69 | +/// This is a noop if `name` is not a property on the given object. |
| 70 | +/// |
| 71 | +/// __This functionality is unstable, and should not be used when [isBugPresent] is `false`.__ |
| 72 | +/// |
| 73 | +/// This method also had undefined behavior on non-[UiProps] instances. |
| 74 | +void patchName(dynamic object) { |
| 75 | + var current = object; |
| 76 | + while ((current = _getPrototypeOf(current)) != null) { |
| 77 | + var nameDescriptor = _getOwnPropertyDescriptor(current, 'name'); |
| 78 | + |
| 79 | + if (nameDescriptor != null) { |
| 80 | + _defineProperty(object, 'name', nameDescriptor); |
| 81 | + return; |
| 82 | + } |
| 83 | + } |
| 84 | +} |
0 commit comments