private static Object evalImpl(final Context.MultiGlobalCompiledScript mgcs, final ScriptContext ctxt, final Global ctxtGlobal) throws ScriptException { final Global oldGlobal = Context.getGlobal(); final boolean globalChanged = (oldGlobal != ctxtGlobal); try { if (globalChanged) { Context.setGlobal(ctxtGlobal); } final ScriptFunction script = mgcs.getFunction(ctxtGlobal); ctxtGlobal.setScriptContext(ctxt); return ScriptObjectMirror.translateUndefined(ScriptObjectMirror.wrap(ScriptRuntime.apply(script, ctxtGlobal), ctxtGlobal)); } catch (final Exception e) { throwAsScriptException(e, ctxtGlobal); throw new AssertionError("should not reach here"); } finally { if (globalChanged) { Context.setGlobal(oldGlobal); } } }
private static Object evalImpl(final ScriptFunction script, final ScriptContext ctxt, final Global ctxtGlobal) throws ScriptException { if (script == null) { return null; } final Global oldGlobal = Context.getGlobal(); final boolean globalChanged = (oldGlobal != ctxtGlobal); try { if (globalChanged) { Context.setGlobal(ctxtGlobal); } ctxtGlobal.setScriptContext(ctxt); return ScriptObjectMirror.translateUndefined(ScriptObjectMirror.wrap(ScriptRuntime.apply(script, ctxtGlobal), ctxtGlobal)); } catch (final Exception e) { throwAsScriptException(e, ctxtGlobal); throw new AssertionError("should not reach here"); } finally { if (globalChanged) { Context.setGlobal(oldGlobal); } } }
/** * ECMA 15.5.4.20 String.prototype.trim ( ) * @param self self reference * @return string trimmed from whitespace */ @Function(attributes = Attribute.NOT_ENUMERABLE) public static String trim(final Object self) { final String str = checkObjectToString(self); int start = 0; int end = str.length() - 1; while (start <= end && ScriptRuntime.isJSWhitespace(str.charAt(start))) { start++; } while (end > start && ScriptRuntime.isJSWhitespace(str.charAt(end))) { end--; } return str.substring(start, end + 1); }
/** * ECMA6 23.2.3.6 Set.prototype.forEach ( callbackfn [ , thisArg ] ) * * @param self the self reference * @param callbackFn the callback function * @param thisArg optional this object */ @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1) public static void forEach(final Object self, final Object callbackFn, final Object thisArg) { final NativeSet set = getNativeSet(self); if (!Bootstrap.isCallable(callbackFn)) { throw typeError("not.a.function", ScriptRuntime.safeToString(callbackFn)); } final MethodHandle invoker = Global.instance().getDynamicInvoker(FOREACH_INVOKER_KEY, () -> Bootstrap.createDynamicCallInvoker(Object.class, Object.class, Object.class, Object.class, Object.class, Object.class)); final LinkedMap.LinkedMapIterator iterator = set.getJavaMap().getIterator(); for (;;) { final LinkedMap.Node node = iterator.next(); if (node == null) { break; } try { final Object result = invoker.invokeExact(callbackFn, thisArg, node.getKey(), node.getKey(), self); } catch (final RuntimeException | Error e) { throw e; } catch (final Throwable t) { throw new RuntimeException(t); } } }
/** * ECMA 15.3.4.4 Function.prototype.call (thisArg [ , arg1 [ , arg2, ... ] ] ) * * @param self self reference * @param args arguments for call * @return result of call */ @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1) public static Object call(final Object self, final Object... args) { checkCallable(self); final Object thiz = (args.length == 0) ? UNDEFINED : args[0]; Object[] arguments; if (args.length > 1) { arguments = new Object[args.length - 1]; System.arraycopy(args, 1, arguments, 0, arguments.length); } else { arguments = ScriptRuntime.EMPTY_ARRAY; } if (self instanceof ScriptFunction) { return ScriptRuntime.apply((ScriptFunction)self, thiz, arguments); } else if (self instanceof JSObject) { return ((JSObject)self).call(thiz, arguments); } throw new AssertionError("should not reach here"); }
NativeArray(final Object[] array) { this(ArrayData.allocate(array.length)); ArrayData arrayData = this.getArray(); if (array.length > 0) { arrayData.ensure(array.length - 1); } for (int index = 0; index < array.length; index++) { final Object value = array[index]; if (value == ScriptRuntime.EMPTY) { arrayData = arrayData.delete(index); } else { arrayData = arrayData.set(index, value, false); } } this.setArray(arrayData); }
/** * ECMA 15.4.4.2 Array.prototype.toString ( ) * * @param self self reference * @return string representation of array */ @Function(attributes = Attribute.NOT_ENUMERABLE) public static Object toString(final Object self) { final Object obj = Global.toObject(self); if (obj instanceof ScriptObject) { final InvokeByName joinInvoker = getJOIN(); final ScriptObject sobj = (ScriptObject)obj; try { final Object join = joinInvoker.getGetter().invokeExact(sobj); if (Bootstrap.isCallable(join)) { return joinInvoker.getInvoker().invokeExact(join, sobj); } } catch (final RuntimeException | Error e) { throw e; } catch (final Throwable t) { throw new RuntimeException(t); } } // FIXME: should lookup Object.prototype.toString and call that? return ScriptRuntime.builtinObjectToString(self); }
/** * ECMA 15.4.4.5 Array.prototype.join (separator) * * @param self self reference * @param separator element separator * @return string representation after join */ @Function(attributes = Attribute.NOT_ENUMERABLE) public static String join(final Object self, final Object separator) { final StringBuilder sb = new StringBuilder(); final Iterator<Object> iter = arrayLikeIterator(self, true); final String sep = separator == ScriptRuntime.UNDEFINED ? "," : JSType.toString(separator); while (iter.hasNext()) { final Object obj = iter.next(); if (obj != null && obj != ScriptRuntime.UNDEFINED) { sb.append(JSType.toString(obj)); } if (iter.hasNext()) { sb.append(sep); } } return sb.toString(); }
/** * ECMA 15.4.4.7 Array.prototype.push (args...) * * @param self self reference * @param args arguments to push * @return array length after pushes */ @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1) public static Object push(final Object self, final Object... args) { try { final ScriptObject sobj = (ScriptObject)self; if (bulkable(sobj) && sobj.getArray().length() + args.length <= JSType.MAX_UINT) { final ArrayData newData = sobj.getArray().push(true, args); sobj.setArray(newData); return newData.length(); } long len = JSType.toUint32(sobj.getLength()); for (final Object element : args) { sobj.set(len++, element, CALLSITE_STRICT); } sobj.set("length", len, CALLSITE_STRICT); return len; } catch (final ClassCastException | NullPointerException e) { throw typeError(Context.getGlobal(), e, "not.an.object", ScriptRuntime.safeToString(self)); } }
/** * Test of toString method, of class Runtime. */ @Test public void testToString_Object() { assertEquals(JSType.toString(ScriptRuntime.UNDEFINED), "undefined"); assertEquals(JSType.toString(null), "null"); assertEquals(JSType.toString(Boolean.TRUE), "true"); assertEquals(JSType.toString(Boolean.FALSE), "false"); assertEquals(JSType.toString(""), ""); assertEquals(JSType.toString("nashorn"), "nashorn"); assertEquals(JSType.toString(Double.NaN), "NaN"); assertEquals(JSType.toString(Double.POSITIVE_INFINITY), "Infinity"); assertEquals(JSType.toString(Double.NEGATIVE_INFINITY), "-Infinity"); assertEquals(JSType.toString(0.0), "0"); // FIXME: add more number-to-string test cases // FIXME: add case for Object type (JSObject with getDefaultValue) }
@Override public Object[] asObjectArray() { final int len = (int)Math.min(length(), Integer.MAX_VALUE); final int underlyingLength = (int)Math.min(len, underlying.length()); final Object[] objArray = new Object[len]; for (int i = 0; i < underlyingLength; i++) { objArray[i] = underlying.getObject(i); } Arrays.fill(objArray, underlyingLength, len, ScriptRuntime.UNDEFINED); for (final Map.Entry<Long, Object> entry : sparseMap.entrySet()) { final long key = entry.getKey(); if (key < Integer.MAX_VALUE) { objArray[(int)key] = entry.getValue(); } else { break; // ascending key order } } return objArray; }
@Override public Object pop() { final long len = length(); final long underlyingLen = underlying.length(); if (len == 0) { return ScriptRuntime.UNDEFINED; } if (len == underlyingLen) { final Object result = underlying.pop(); setLength(underlying.length()); return result; } setLength(len - 1); final Long key = len - 1; return sparseMap.containsKey(key) ? sparseMap.remove(key) : ScriptRuntime.UNDEFINED; }
private static GuardedInvocation linkBean(final LinkRequest linkRequest) throws Exception { final CallSiteDescriptor desc = linkRequest.getCallSiteDescriptor(); final Object self = linkRequest.getReceiver(); switch (NashornCallSiteDescriptor.getStandardOperation(desc)) { case NEW: if(BeansLinker.isDynamicConstructor(self)) { throw typeError("no.constructor.matches.args", ScriptRuntime.safeToString(self)); } if(BeansLinker.isDynamicMethod(self)) { throw typeError("method.not.constructor", ScriptRuntime.safeToString(self)); } throw typeError("not.a.function", NashornCallSiteDescriptor.getFunctionErrorMessage(desc, self)); case CALL: if(BeansLinker.isDynamicConstructor(self)) { throw typeError("constructor.requires.new", ScriptRuntime.safeToString(self)); } if(BeansLinker.isDynamicMethod(self)) { throw typeError("no.method.matches.args", ScriptRuntime.safeToString(self)); } throw typeError("not.a.function", NashornCallSiteDescriptor.getFunctionErrorMessage(desc, self)); default: // Everything else is supposed to have been already handled by Bootstrap.beansLinker // delegating to linkNoSuchBeanMember throw new AssertionError("unknown call type " + desc); } }
@Override public Iterator<String> propertyIterator() { // Try __getIds__ first, if not found then try __getKeys__ // In jdk6, we had added "__getIds__" so this is just for compatibility. Object func = adaptee.get(__getIds__); if (!(func instanceof ScriptFunction)) { func = adaptee.get(__getKeys__); } Object obj; if (func instanceof ScriptFunction) { obj = ScriptRuntime.apply((ScriptFunction)func, this); } else { obj = new NativeArray(0); } final List<String> array = new ArrayList<>(); for (final Iterator<Object> iter = ArrayLikeIterator.arrayLikeIterator(obj); iter.hasNext(); ) { array.add((String)iter.next()); } return array.iterator(); }
NativeArray(final Object[] array) { this(ArrayData.allocate(array.length)); ArrayData arrayData = this.getArray(); for (int index = 0; index < array.length; index++) { final Object value = array[index]; if (value == ScriptRuntime.EMPTY) { arrayData = arrayData.delete(index); } else { arrayData = arrayData.set(index, value, false); } } this.setArray(arrayData); }
@Override public String toString() { return inGlobal(new Callable<String>() { @Override public String call() { return ScriptRuntime.safeToString(sobj); } }); }
/** * Converts {@code result} to a printable string. The reason we don't use {@link JSType#toString(Object)} * or {@link ScriptRuntime#safeToString(Object)} is that we want to be able to render Symbol values * even if they occur within an Array, and therefore have to implement our own Array to String * conversion. * * @param result the result * @param global the global object * @return the string representation */ protected static String toString(final Object result, final Global global) { if (result instanceof Symbol) { // Normal implicit conversion of symbol to string would throw TypeError return result.toString(); } if (result instanceof NativeSymbol) { return JSType.toPrimitive(result).toString(); } if (isArrayWithDefaultToString(result, global)) { // This should yield the same string as Array.prototype.toString but // will not throw if the array contents include symbols. final StringBuilder sb = new StringBuilder(); final Iterator<Object> iter = ArrayLikeIterator.arrayLikeIterator(result, true); while (iter.hasNext()) { final Object obj = iter.next(); if (obj != null && obj != ScriptRuntime.UNDEFINED) { sb.append(toString(obj, global)); } if (iter.hasNext()) { sb.append(','); } } return sb.toString(); } return JSType.toString(result); }
/** * Call member function * @param functionName function name * @param args arguments * @return return value of function */ public Object callMember(final String functionName, final Object... args) { Objects.requireNonNull(functionName); final Global oldGlobal = Context.getGlobal(); final boolean globalChanged = (oldGlobal != global); try { if (globalChanged) { Context.setGlobal(global); } final Object val = sobj.get(functionName); if (val instanceof ScriptFunction) { final Object[] modArgs = globalChanged? wrapArrayLikeMe(args, oldGlobal) : args; return wrapLikeMe(ScriptRuntime.apply((ScriptFunction)val, sobj, unwrapArray(modArgs, global))); } else if (val instanceof JSObject && ((JSObject)val).isFunction()) { return ((JSObject)val).call(sobj, args); } throw new NoSuchMethodException("No such function " + functionName); } catch (final NashornException ne) { throw ne.initEcmaError(global); } catch (final RuntimeException | Error e) { throw e; } catch (final Throwable t) { throw new RuntimeException(t); } finally { if (globalChanged) { Context.setGlobal(oldGlobal); } } }
private boolean checkDeleteIndex(final int index, final boolean strict) { if (isValidStringIndex(index)) { if (strict) { throw typeError("cant.delete.property", Integer.toString(index), ScriptRuntime.safeToString(this)); } return true; } return false; }
/** * Nashorn extension: String.prototype.trimRight ( ) * @param self self reference * @return string trimmed right from whitespace */ @Function(attributes = Attribute.NOT_ENUMERABLE) public static String trimRight(final Object self) { final String str = checkObjectToString(self); final int start = 0; int end = str.length() - 1; while (end >= start && ScriptRuntime.isJSWhitespace(str.charAt(end))) { end--; } return str.substring(start, end + 1); }
private Object callAdaptee(final Object retValue, final String name, final Object... args) { final Object func = adaptee.get(name); if (func instanceof ScriptFunction) { return ScriptRuntime.apply((ScriptFunction)func, this, args); } return retValue; }
/** * Test of toNumber method, of class Runtime. */ @Test public void testToNumber_Object() { assertTrue(Double.isNaN(JSType.toNumber(ScriptRuntime.UNDEFINED))); assertEquals(JSType.toNumber((Object)null), 0.0, 0.0); assertEquals(JSType.toNumber(Boolean.TRUE), 1.0, 0.0); assertEquals(JSType.toNumber(Boolean.FALSE), 0.0, 0.0); assertEquals(JSType.toNumber(3.14), 3.14, 0.0); // FIXME: add more assertions for specific String to number cases // FIXME: add case for Object type (JSObject with getDefaultValue) }
/** * ECMA 15.4.4.11 Array.prototype.sort ( comparefn ) * * @param self self reference * @param comparefn element comparison function * @return sorted array */ @Function(attributes = Attribute.NOT_ENUMERABLE) public static ScriptObject sort(final Object self, final Object comparefn) { try { final ScriptObject sobj = (ScriptObject) self; final long len = JSType.toUint32(sobj.getLength()); ArrayData array = sobj.getArray(); if (len > 1) { // Get only non-missing elements. Missing elements go at the end // of the sorted array. So, just don't copy these to sort input. final ArrayList<Object> src = new ArrayList<>(); for (final Iterator<Long> iter = array.indexIterator(); iter.hasNext(); ) { final long index = iter.next(); if (index >= len) { break; } src.add(array.getObject((int)index)); } final Object[] sorted = sort(src.toArray(), comparefn); for (int i = 0; i < sorted.length; i++) { array = array.set(i, sorted[i], true); } // delete missing elements - which are at the end of sorted array if (sorted.length != len) { array = array.delete(sorted.length, len - 1); } sobj.setArray(array); } return sobj; } catch (final ClassCastException | NullPointerException e) { throw typeError("not.an.object", ScriptRuntime.safeToString(self)); } }
private static NativeWeakMap getMap(final Object self) { if (self instanceof NativeWeakMap) { return (NativeWeakMap)self; } else { throw typeError("not.a.weak.map", ScriptRuntime.safeToString(self)); } }
/** * ECMA 15.3.4.3 Function.prototype.apply (thisArg, argArray) * * @param self self reference * @param thiz {@code this} arg for apply * @param array array of argument for apply * @return result of apply */ @Function(attributes = Attribute.NOT_ENUMERABLE) public static Object apply(final Object self, final Object thiz, final Object array) { checkCallable(self); final Object[] args = toApplyArgs(array); if (self instanceof ScriptFunction) { return ScriptRuntime.apply((ScriptFunction)self, thiz, args); } else if (self instanceof JSObject) { return ((JSObject)self).call(thiz, args); } throw new AssertionError("Should not reach here"); }
/** * Nashorn extension: Function.prototype.toSource * * @param self self reference * @return source for function */ @Function(attributes = Attribute.NOT_ENUMERABLE) public static String toSource(final Object self) { if (!(self instanceof ScriptFunction)) { throw typeError("not.a.function", ScriptRuntime.safeToString(self)); } return ((ScriptFunction)self).toSource(); }
/** * ECMA 15.4.5.1 [[DefineOwnProperty]] ( P, Desc, Throw ) as specialized in * ECMA 10.6 for Arguments object. */ @Override public boolean defineOwnProperty(final Object key, final Object propertyDesc, final boolean reject) { final int index = ArrayIndex.getArrayIndex(key); if (index >= 0) { final boolean isMapped = isMapped(index); final Object oldValue = isMapped ? getArray().getObject(index) : null; if (!super.defineOwnProperty(key, propertyDesc, false)) { if (reject) { throw typeError("cant.redefine.property", key.toString(), ScriptRuntime.safeToString(this)); } return false; } if (isMapped) { // When mapped argument is redefined, if new descriptor is accessor property // or data-non-writable property, we have to "unmap" (unlink). final PropertyDescriptor desc = toPropertyDescriptor(Global.instance(), propertyDesc); if (desc.type() == PropertyDescriptor.ACCESSOR) { setDeleted(index, oldValue); } else if (desc.has(PropertyDescriptor.WRITABLE) && !desc.isWritable()) { // delete and set value from new descriptor if it has one, otherwise use old value setDeleted(index, desc.has(PropertyDescriptor.VALUE) ? desc.getValue() : oldValue); } else if (desc.has(PropertyDescriptor.VALUE)) { setArray(getArray().set(index, desc.getValue(), false)); } } return true; } return super.defineOwnProperty(key, propertyDesc, reject); }
/** * ECMA 15.4.5.1 [[DefineOwnProperty]] ( P, Desc, Throw ) as specialized in * ECMA 10.6 for Arguments object. */ @Override public boolean defineOwnProperty(final String key, final Object propertyDesc, final boolean reject) { final int index = ArrayIndex.getArrayIndex(key); if (index >= 0) { final boolean isMapped = isMapped(index); final Object oldValue = isMapped ? getArray().getObject(index) : null; if (!super.defineOwnProperty(key, propertyDesc, false)) { if (reject) { throw typeError("cant.redefine.property", key, ScriptRuntime.safeToString(this)); } return false; } if (isMapped) { // When mapped argument is redefined, if new descriptor is accessor property // or data-non-writable property, we have to "unmap" (unlink). final PropertyDescriptor desc = toPropertyDescriptor(Global.instance(), propertyDesc); if (desc.type() == PropertyDescriptor.ACCESSOR) { setDeleted(index, oldValue); } else if (desc.has(PropertyDescriptor.WRITABLE) && !desc.isWritable()) { // delete and set value from new descriptor if it has one, otherwise use old value setDeleted(index, desc.has(PropertyDescriptor.VALUE) ? desc.getValue() : oldValue); } else if (desc.has(PropertyDescriptor.VALUE)) { setArray(getArray().set(index, desc.getValue(), false)); } } return true; } return super.defineOwnProperty(key, propertyDesc, reject); }
/** * ECMA 9.9 ToObject implementation * * @param obj an item for which to run ToObject * @return ToObject version of given item */ public static Object toObject(final Object obj) { if (obj == null || obj == UNDEFINED) { throw typeError("not.an.object", ScriptRuntime.safeToString(obj)); } if (obj instanceof ScriptObject) { return obj; } return instance().wrapAsObject(obj); }
static long validLength(final Object length) { // ES5 15.4.5.1, steps 3.c and 3.d require two ToNumber conversions here final double doubleLength = JSType.toNumber(length); if (doubleLength != JSType.toUint32(length)) { throw rangeError("inappropriate.array.length", ScriptRuntime.safeToString(length)); } return (long) doubleLength; }
private static double getNumberValue(final Object self) { if (self instanceof Number) { return ((Number)self).doubleValue(); } else if (self instanceof NativeNumber) { return ((NativeNumber)self).getValue(); } else if (self != null && self == Global.instance().getNumberPrototype()) { return 0.0; } else { throw typeError("not.a.number", ScriptRuntime.safeToString(self)); } }
private static Type getPropertyType(final ScriptObject sobj, final String name) { final FindProperty find = sobj.findProperty(name, true); if (find == null) { return null; } final Property property = find.getProperty(); final Class<?> propertyClass = property.getType(); if (propertyClass == null) { // propertyClass == null means its value is Undefined. It is probably not initialized yet, so we won't make // a type assumption yet. return null; } else if (propertyClass.isPrimitive()) { return Type.typeFor(propertyClass); } final ScriptObject owner = find.getOwner(); if (property.hasGetterFunction(owner)) { // Can have side effects, so we can't safely evaluate it; since !propertyClass.isPrimitive(), it's Object. return Type.OBJECT; } // Safely evaluate the property, and return the narrowest type for the actual value (e.g. Type.INT for a boxed // integer). final Object value = property.needsDeclaration() ? ScriptRuntime.UNDEFINED : property.getObjectValue(owner, owner); if (value == ScriptRuntime.UNDEFINED) { return null; } return Type.typeFor(JSType.unboxedFieldType(value)); }
/** * Slice function * @param self native array buffer * @param begin0 start byte index * @param end0 end byte index * @return new array buffer, sliced */ @Function(attributes = Attribute.NOT_ENUMERABLE) public static NativeArrayBuffer slice(final Object self, final Object begin0, final Object end0) { final NativeArrayBuffer arrayBuffer = (NativeArrayBuffer)self; final int byteLength = arrayBuffer.getByteLength(); final int begin = adjustIndex(JSType.toInt32(begin0), byteLength); final int end = adjustIndex(end0 != ScriptRuntime.UNDEFINED ? JSType.toInt32(end0) : byteLength, byteLength); return new NativeArrayBuffer(arrayBuffer, begin, Math.max(end, begin)); }
private static Boolean getBoolean(final Object self) { if (self instanceof Boolean) { return ((Boolean)self); } else if (self instanceof NativeBoolean) { return ((NativeBoolean)self).getValue(); } else if (self != null && self == Global.instance().getBooleanPrototype()) { return false; } else { throw typeError("not.a.boolean", ScriptRuntime.safeToString(self)); } }
static long validLength(final Object length, final boolean reject) { final double doubleLength = JSType.toNumber(length); if (!Double.isNaN(doubleLength) && JSType.isRepresentableAsLong(doubleLength)) { final long len = (long) doubleLength; if (len >= 0 && len <= JSType.MAX_UINT) { return len; } } if (reject) { throw rangeError("inappropriate.array.length", ScriptRuntime.safeToString(length)); } return -1; }
private static String getArgument(final LinkRequest linkRequest) { final Operation op = linkRequest.getCallSiteDescriptor().getOperation(); if (op instanceof NamedOperation) { return ((NamedOperation)op).getName().toString(); } return ScriptRuntime.safeToString(linkRequest.getArguments()[1]); }
/** * Assert that an array is numeric, if not throw type error * @param self self array to check * @return true if numeric */ @Function(attributes = Attribute.NOT_ENUMERABLE) public static Object assertNumeric(final Object self) { if(!(self instanceof NativeArray && ((NativeArray)self).getArray().getOptimisticType().isNumeric())) { throw typeError("not.a.numeric.array", ScriptRuntime.safeToString(self)); } return Boolean.TRUE; }
/** * ECMA 15.4.4.6 Array.prototype.pop () * * @param self self reference * @return array after pop */ @Function(attributes = Attribute.NOT_ENUMERABLE) public static Object pop(final Object self) { try { final ScriptObject sobj = (ScriptObject)self; if (bulkable(sobj)) { return sobj.getArray().pop(); } final long len = JSType.toUint32(sobj.getLength()); if (len == 0) { sobj.set("length", 0, CALLSITE_STRICT); return ScriptRuntime.UNDEFINED; } final long index = len - 1; final Object element = sobj.get(index); sobj.delete(index, true); sobj.set("length", index, CALLSITE_STRICT); return element; } catch (final ClassCastException | NullPointerException e) { throw typeError("not.an.object", ScriptRuntime.safeToString(self)); } }