|
40 | 40 | */ |
41 | 41 | package com.oracle.truffle.js.runtime.builtins; |
42 | 42 |
|
43 | | -import java.util.AbstractMap; |
| 43 | +import static com.oracle.truffle.js.runtime.util.DefinePropertyUtil.nonConfigurableMessage; |
| 44 | +import static com.oracle.truffle.js.runtime.util.DefinePropertyUtil.nonWritableMessage; |
| 45 | +import static com.oracle.truffle.js.runtime.util.DefinePropertyUtil.notExtensibleMessage; |
| 46 | +import static com.oracle.truffle.js.runtime.util.DefinePropertyUtil.reject; |
| 47 | + |
44 | 48 | import java.util.ArrayList; |
45 | 49 | import java.util.Collections; |
46 | 50 | import java.util.List; |
47 | | -import java.util.Map; |
48 | 51 |
|
49 | 52 | import org.graalvm.collections.EconomicMap; |
50 | | -import org.graalvm.collections.MapCursor; |
51 | 53 |
|
52 | 54 | import com.oracle.truffle.api.CompilerAsserts; |
53 | 55 | import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; |
|
76 | 78 | import com.oracle.truffle.js.runtime.objects.JSShape; |
77 | 79 | import com.oracle.truffle.js.runtime.objects.Null; |
78 | 80 | import com.oracle.truffle.js.runtime.objects.PropertyDescriptor; |
79 | | -import com.oracle.truffle.js.runtime.objects.PropertyProxy; |
80 | 81 | import com.oracle.truffle.js.runtime.objects.Undefined; |
81 | 82 | import com.oracle.truffle.js.runtime.util.DefinePropertyUtil; |
82 | 83 |
|
@@ -128,7 +129,7 @@ public Object getOwnHelper(JSDynamicObject store, Object thisObj, Object key, No |
128 | 129 |
|
129 | 130 | public static Object getValue(PropertyDescriptor property, Object receiver, Node encapsulatingNode) { |
130 | 131 | if (property.isAccessorDescriptor()) { |
131 | | - JSDynamicObject getter = (JSDynamicObject) property.getGet(); |
| 132 | + Object getter = property.getGet(); |
132 | 133 | if (getter != Undefined.instance) { |
133 | 134 | return JSRuntime.call(getter, receiver, JSArguments.EMPTY_ARGUMENTS_ARRAY, encapsulatingNode); |
134 | 135 | } else { |
@@ -222,7 +223,7 @@ protected static boolean dictionaryObjectSet(JSDynamicObject thisObj, Object key |
222 | 223 |
|
223 | 224 | private static boolean setValue(Object key, PropertyDescriptor property, JSDynamicObject store, Object thisObj, Object value, boolean isStrict, Node encapsulatingNode) { |
224 | 225 | if (property.isAccessorDescriptor()) { |
225 | | - JSDynamicObject setter = (JSDynamicObject) property.getSet(); |
| 226 | + Object setter = property.getSet(); |
226 | 227 | if (setter != Undefined.instance) { |
227 | 228 | JSRuntime.call(setter, thisObj, new Object[]{value}, encapsulatingNode); |
228 | 229 | return true; |
@@ -260,15 +261,122 @@ public PropertyDescriptor getOwnProperty(JSDynamicObject thisObj, Object key) { |
260 | 261 | @Override |
261 | 262 | public boolean defineOwnProperty(JSDynamicObject thisObj, Object key, PropertyDescriptor desc, boolean doThrow) { |
262 | 263 | assert JSRuntime.isPropertyKey(key); |
263 | | - if (!hasOwnProperty(thisObj, key) && JSObject.isExtensible(thisObj)) { |
264 | | - getHashMap(thisObj).put(key, desc); |
| 264 | + PropertyDescriptor current = getHashMap(thisObj).get(key); |
| 265 | + if (current == null) { |
| 266 | + current = super.getOwnProperty(thisObj, key); |
| 267 | + boolean extensible = JSObject.isExtensible(thisObj); |
| 268 | + if (current == null) { |
| 269 | + if (!extensible) { |
| 270 | + return reject(doThrow, notExtensibleMessage(key, doThrow)); |
| 271 | + } |
| 272 | + validateAndPutDesc(thisObj, key, makeFullyPopulatedPropertyDescriptor(desc)); |
| 273 | + return true; |
| 274 | + } else { |
| 275 | + return DefinePropertyUtil.validateAndApplyPropertyDescriptor(thisObj, key, extensible, desc, current, doThrow); |
| 276 | + } |
| 277 | + } else { |
| 278 | + return validateAndApplyPropertyDescriptorExisting(thisObj, key, desc, current, doThrow); |
| 279 | + } |
| 280 | + } |
| 281 | + |
| 282 | + private static PropertyDescriptor validateAndPutDesc(JSDynamicObject thisObj, Object key, PropertyDescriptor newDesc) { |
| 283 | + assert newDesc.isFullyPopulatedPropertyDescriptor(); |
| 284 | + return getHashMap(thisObj).put(key, newDesc); |
| 285 | + } |
| 286 | + |
| 287 | + /** |
| 288 | + * Returns a fully populated property descriptor that is either an accessor property descriptor |
| 289 | + * or a data property descriptor that has all of the corresponding fields. Missing fields are |
| 290 | + * filled with default values. |
| 291 | + */ |
| 292 | + private static PropertyDescriptor makeFullyPopulatedPropertyDescriptor(PropertyDescriptor desc) { |
| 293 | + if (desc.isAccessorDescriptor()) { |
| 294 | + if (desc.hasGet() && desc.hasSet() && desc.hasEnumerable() && desc.hasConfigurable()) { |
| 295 | + return desc; |
| 296 | + } else { |
| 297 | + return PropertyDescriptor.createAccessor(desc.getGet(), desc.getSet(), desc.getEnumerable(), desc.getConfigurable()); |
| 298 | + } |
| 299 | + } else if (desc.isDataDescriptor()) { |
| 300 | + if (desc.hasValue() && desc.hasWritable() && desc.hasEnumerable() && desc.hasConfigurable()) { |
| 301 | + return desc; |
| 302 | + } else { |
| 303 | + Object value = desc.hasValue() ? desc.getValue() : Undefined.instance; |
| 304 | + return PropertyDescriptor.createData(value, desc.getEnumerable(), desc.getWritable(), desc.getConfigurable()); |
| 305 | + } |
| 306 | + } else { |
| 307 | + assert desc.isGenericDescriptor(); |
| 308 | + return PropertyDescriptor.createData(Undefined.instance, desc.getEnumerable(), desc.getWritable(), desc.getConfigurable()); |
| 309 | + } |
| 310 | + } |
| 311 | + |
| 312 | + private static boolean validateAndApplyPropertyDescriptorExisting(JSDynamicObject thisObj, Object key, PropertyDescriptor descriptor, PropertyDescriptor currentDesc, boolean doThrow) { |
| 313 | + CompilerAsserts.neverPartOfCompilation(); |
| 314 | + assert currentDesc.isFullyPopulatedPropertyDescriptor(); |
| 315 | + if (descriptor.hasNoFields()) { |
265 | 316 | return true; |
266 | 317 | } |
267 | 318 |
|
268 | | - // defineProperty is currently not supported on dictionary objects, |
269 | | - // so we need to convert back to a normal shape-based object. |
270 | | - makeOrdinaryObject(thisObj, "defineOwnProperty"); |
271 | | - return super.defineOwnProperty(thisObj, key, desc, doThrow); |
| 319 | + if (!currentDesc.getConfigurable()) { |
| 320 | + if ((descriptor.hasConfigurable() && descriptor.getConfigurable()) || |
| 321 | + (descriptor.hasEnumerable() && (descriptor.getEnumerable() != currentDesc.getEnumerable()))) { |
| 322 | + return reject(doThrow, nonConfigurableMessage(key, doThrow)); |
| 323 | + } |
| 324 | + if (!descriptor.isGenericDescriptor() && descriptor.isAccessorDescriptor() != currentDesc.isAccessorDescriptor()) { |
| 325 | + return reject(doThrow, nonConfigurableMessage(key, doThrow)); |
| 326 | + } |
| 327 | + if (currentDesc.isAccessorDescriptor()) { |
| 328 | + if ((descriptor.hasGet() && !JSRuntime.isSameValue(descriptor.getGet(), currentDesc.getGet())) || |
| 329 | + (descriptor.hasSet() && !JSRuntime.isSameValue(descriptor.getSet(), currentDesc.getSet()))) { |
| 330 | + return reject(doThrow, nonConfigurableMessage(key, doThrow)); |
| 331 | + } |
| 332 | + } else { |
| 333 | + assert currentDesc.isDataDescriptor(); |
| 334 | + if (!currentDesc.getWritable()) { |
| 335 | + if (descriptor.hasWritable() && descriptor.getWritable()) { |
| 336 | + return reject(doThrow, nonConfigurableMessage(key, doThrow)); |
| 337 | + } |
| 338 | + if (descriptor.hasValue() && !JSRuntime.isSameValue(descriptor.getValue(), currentDesc.getValue())) { |
| 339 | + return reject(doThrow, nonWritableMessage(key, doThrow)); |
| 340 | + } |
| 341 | + } |
| 342 | + } |
| 343 | + } |
| 344 | + |
| 345 | + if (currentDesc.isDataDescriptor() && descriptor.isAccessorDescriptor()) { |
| 346 | + PropertyDescriptor newDesc = PropertyDescriptor.createAccessor(descriptor.getGet(), descriptor.getSet(), |
| 347 | + descriptor.getIfHasEnumerable(currentDesc.getEnumerable()), |
| 348 | + descriptor.getIfHasConfigurable(currentDesc.getConfigurable())); |
| 349 | + validateAndPutDesc(thisObj, key, newDesc); |
| 350 | + return true; |
| 351 | + } else if (currentDesc.isAccessorDescriptor() && descriptor.isDataDescriptor()) { |
| 352 | + Object value = descriptor.hasValue() ? descriptor.getValue() : Undefined.instance; |
| 353 | + PropertyDescriptor newDesc = PropertyDescriptor.createData(value, |
| 354 | + descriptor.getIfHasEnumerable(currentDesc.getEnumerable()), |
| 355 | + descriptor.getIfHasConfigurable(currentDesc.getConfigurable()), |
| 356 | + descriptor.getWritable()); |
| 357 | + validateAndPutDesc(thisObj, key, newDesc); |
| 358 | + return true; |
| 359 | + } else { |
| 360 | + if (descriptor.hasConfigurable()) { |
| 361 | + currentDesc.setConfigurable(descriptor.getConfigurable()); |
| 362 | + } |
| 363 | + if (descriptor.hasEnumerable()) { |
| 364 | + currentDesc.setEnumerable(descriptor.getEnumerable()); |
| 365 | + } |
| 366 | + if (descriptor.hasWritable()) { |
| 367 | + currentDesc.setWritable(descriptor.getWritable()); |
| 368 | + } |
| 369 | + if (descriptor.hasValue()) { |
| 370 | + currentDesc.setValue(descriptor.getValue()); |
| 371 | + } |
| 372 | + if (descriptor.hasGet()) { |
| 373 | + currentDesc.setGet(descriptor.getGet()); |
| 374 | + } |
| 375 | + if (descriptor.hasSet()) { |
| 376 | + currentDesc.setSet(descriptor.getSet()); |
| 377 | + } |
| 378 | + return true; |
| 379 | + } |
272 | 380 | } |
273 | 381 |
|
274 | 382 | @SuppressWarnings("unchecked") |
@@ -357,63 +465,6 @@ private static PropertyDescriptor toPropertyDescriptor(Property p, Object value) |
357 | 465 | return desc; |
358 | 466 | } |
359 | 467 |
|
360 | | - private static void makeOrdinaryObject(JSDynamicObject obj, String reason) { |
361 | | - CompilerAsserts.neverPartOfCompilation(); |
362 | | - if (JSConfig.TraceDictionaryObject) { |
363 | | - System.out.printf("transitioning from dictionary object to ordinary object: %s\n", reason); |
364 | | - } |
365 | | - |
366 | | - EconomicMap<Object, PropertyDescriptor> hashMap = getHashMap(obj); |
367 | | - Shape oldShape = obj.getShape(); |
368 | | - JSContext context = JSObject.getJSContext(obj); |
369 | | - Shape newRootShape = makeEmptyShapeForNewType(context, oldShape, JSOrdinary.INSTANCE, obj); |
370 | | - |
371 | | - DynamicObjectLibrary lib = DynamicObjectLibrary.getUncached(); |
372 | | - |
373 | | - List<Property> allProperties = oldShape.getPropertyListInternal(true); |
374 | | - List<Map.Entry<Property, Object>> archive = new ArrayList<>(allProperties.size()); |
375 | | - for (Property prop : allProperties) { |
376 | | - Object key = prop.getKey(); |
377 | | - Object value = Properties.getOrDefault(lib, obj, key, null); |
378 | | - if (HASHMAP_PROPERTY_NAME.equals(key)) { |
379 | | - continue; |
380 | | - } |
381 | | - archive.add(new AbstractMap.SimpleImmutableEntry<>(prop, value)); |
382 | | - } |
383 | | - |
384 | | - lib.resetShape(obj, newRootShape); |
385 | | - |
386 | | - for (int i = 0; i < archive.size(); i++) { |
387 | | - Map.Entry<Property, Object> e = archive.get(i); |
388 | | - Property p = e.getKey(); |
389 | | - Object key = p.getKey(); |
390 | | - if (!newRootShape.hasProperty(key)) { |
391 | | - Object value = e.getValue(); |
392 | | - if (p.getLocation().isConstant()) { |
393 | | - Properties.putConstant(lib, obj, key, value, p.getFlags()); |
394 | | - } else { |
395 | | - Properties.putWithFlags(lib, obj, key, value, p.getFlags()); |
396 | | - } |
397 | | - } |
398 | | - } |
399 | | - |
400 | | - MapCursor<Object, PropertyDescriptor> cursor = hashMap.getEntries(); |
401 | | - while (cursor.advance()) { |
402 | | - Object key = cursor.getKey(); |
403 | | - assert JSRuntime.isPropertyKey(key); |
404 | | - PropertyDescriptor desc = cursor.getValue(); |
405 | | - if (desc.isDataDescriptor()) { |
406 | | - Object value = desc.getValue(); |
407 | | - assert !(value instanceof Accessor || value instanceof PropertyProxy); |
408 | | - JSObjectUtil.defineDataProperty(obj, key, value, desc.getFlags()); |
409 | | - } else { |
410 | | - JSObjectUtil.defineAccessorProperty(obj, key, new Accessor(desc.getGet(), desc.getSet()), desc.getFlags()); |
411 | | - } |
412 | | - } |
413 | | - |
414 | | - assert JSOrdinary.isJSOrdinaryObject(obj) && obj.getShape().getProperty(HASHMAP_PROPERTY_NAME) == null; |
415 | | - } |
416 | | - |
417 | 468 | public static Shape makeDictionaryShape(JSContext context, JSDynamicObject prototype) { |
418 | 469 | assert prototype != Null.instance; |
419 | 470 | return JSObjectUtil.getProtoChildShape(prototype, JSDictionary.INSTANCE, context); |
|
0 commit comments