diff options
Diffstat (limited to 'js/src/vm/ToSource.cpp')
-rw-r--r-- | js/src/vm/ToSource.cpp | 249 |
1 files changed, 249 insertions, 0 deletions
diff --git a/js/src/vm/ToSource.cpp b/js/src/vm/ToSource.cpp new file mode 100644 index 0000000000..50fd8a1037 --- /dev/null +++ b/js/src/vm/ToSource.cpp @@ -0,0 +1,249 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "vm/ToSource.h" + +#include "mozilla/Assertions.h" // MOZ_ASSERT +#include "mozilla/FloatingPoint.h" // mozilla::IsNegativeZero + +#include <iterator> // std::size +#include <stdint.h> // uint32_t + +#include "builtin/Array.h" // ArrayToSource +#include "builtin/Boolean.h" // BooleanToString +#include "builtin/Object.h" // ObjectToSource +#include "gc/GCEnum.h" // CanGC +#include "js/Class.h" // ESClass +#include "js/friend/StackLimits.h" // js::AutoCheckRecursionLimit +#include "js/Object.h" // JS::GetBuiltinClass +#include "js/Printer.h" // QuoteString +#include "js/Symbol.h" // SymbolCode, JS::WellKnownSymbolLimit +#include "js/TypeDecls.h" // Rooted{Function, Object, String, Value}, HandleValue, Latin1Char +#include "js/Utility.h" // UniqueChars +#include "js/Value.h" // JS::Value +#include "util/StringBuffer.h" // JSStringBuilder +#include "vm/ErrorObject.h" // ErrorObject, ErrorToSource +#include "vm/Interpreter.h" // Call +#include "vm/JSContext.h" // JSContext +#include "vm/JSFunction.h" // JSFunction, fun_toStringHelper +#include "vm/SelfHosting.h" // CallSelfHostedFunction +#include "vm/Stack.h" // FixedInvokeArgs +#include "vm/StaticStrings.h" // StaticStrings +#include "vm/StringType.h" // NewStringCopy{N,Z}, ToString +#include "vm/SymbolType.h" // Symbol +#ifdef ENABLE_RECORD_TUPLE +# include "vm/RecordType.h" +# include "vm/TupleType.h" +#endif + +#include "vm/JSContext-inl.h" // JSContext::check +#include "vm/JSObject-inl.h" // IsCallable +#include "vm/ObjectOperations-inl.h" // GetProperty + +using namespace js; + +using mozilla::IsNegativeZero; + +using JS::GetBuiltinClass; + +/* + * Convert a JSString to its source expression; returns null after reporting an + * error, otherwise returns a new string reference. No Handle needed since the + * input is dead after the GC. + */ +static JSString* StringToSource(JSContext* cx, JSString* str) { + UniqueChars chars = QuoteString(cx, str, '"'); + if (!chars) { + return nullptr; + } + return NewStringCopyZ<CanGC>(cx, chars.get()); +} + +static JSString* SymbolToSource(JSContext* cx, JS::Symbol* symbol) { + using JS::SymbolCode; + + RootedString desc(cx, symbol->description()); + SymbolCode code = symbol->code(); + if (symbol->isWellKnownSymbol()) { + // Well-known symbol. + return desc; + } + + if (code == SymbolCode::PrivateNameSymbol) { + MOZ_ASSERT(desc); + return desc; + } + + MOZ_ASSERT(code == SymbolCode::InSymbolRegistry || + code == SymbolCode::UniqueSymbol); + + JSStringBuilder buf(cx); + if (code == SymbolCode::InSymbolRegistry ? !buf.append("Symbol.for(") + : !buf.append("Symbol(")) { + return nullptr; + } + if (desc) { + UniqueChars quoted = QuoteString(cx, desc, '"'); + if (!quoted || !buf.append(quoted.get(), strlen(quoted.get()))) { + return nullptr; + } + } + if (!buf.append(')')) { + return nullptr; + } + return buf.finishString(); +} + +static JSString* BoxedToSource(JSContext* cx, HandleObject obj, + const char* constructor) { + RootedValue value(cx); + if (!Unbox(cx, obj, &value)) { + return nullptr; + } + MOZ_ASSERT(!value.isUndefined()); + + RootedString str(cx, ValueToSource(cx, value)); + if (!str) { + return nullptr; + } + + JSStringBuilder buf(cx); + if (!buf.append("new ") || !buf.append(constructor, strlen(constructor)) || + !buf.append('(') || !buf.append(str) || !buf.append(')')) { + return nullptr; + } + + return buf.finishString(); +} + +JSString* js::ValueToSource(JSContext* cx, HandleValue v) { + AutoCheckRecursionLimit recursion(cx); + if (!recursion.check(cx)) { + return nullptr; + } + cx->check(v); + + switch (v.type()) { + case JS::ValueType::Undefined: + return cx->names().void_0_; + + case JS::ValueType::String: + return StringToSource(cx, v.toString()); + + case JS::ValueType::Symbol: + return SymbolToSource(cx, v.toSymbol()); + + case JS::ValueType::Null: + return cx->names().null; + + case JS::ValueType::Boolean: + return BooleanToString(cx, v.toBoolean()); + + case JS::ValueType::Double: + /* Special case to preserve negative zero, _contra_ toString. */ + if (IsNegativeZero(v.toDouble())) { + static const Latin1Char negativeZero[] = {'-', '0'}; + + return NewStringCopyN<CanGC>(cx, negativeZero, std::size(negativeZero)); + } + [[fallthrough]]; + case JS::ValueType::Int32: + return ToString<CanGC>(cx, v); + + case JS::ValueType::BigInt: { + RootedString str(cx, ToString<CanGC>(cx, v)); + if (!str) { + return nullptr; + } + + RootedString n(cx, cx->staticStrings().getUnit('n')); + + return ConcatStrings<CanGC>(cx, str, n); + } + +#ifdef ENABLE_RECORD_TUPLE + case ValueType::ExtendedPrimitive: { + RootedObject obj(cx, &v.toExtendedPrimitive()); + if (obj->is<TupleType>()) { + Rooted<TupleType*> tup(cx, &obj->as<TupleType>()); + return TupleToSource(cx, tup); + } + if (obj->is<RecordType>()) { + return RecordToSource(cx, obj.as<RecordType>()); + } + MOZ_CRASH("Unsupported ExtendedPrimitive"); + } +#endif + + case JS::ValueType::Object: { + RootedValue fval(cx); + RootedObject obj(cx, &v.toObject()); + if (!GetProperty(cx, obj, obj, cx->names().toSource, &fval)) { + return nullptr; + } + if (IsCallable(fval)) { + RootedValue v(cx); + if (!js::Call(cx, fval, obj, &v)) { + return nullptr; + } + + return ToString<CanGC>(cx, v); + } + + ESClass cls; + if (!GetBuiltinClass(cx, obj, &cls)) { + return nullptr; + } + + // All ToSource functions must be able to handle wrapped objects! + switch (cls) { + case ESClass::Function: + return fun_toStringHelper(cx, obj, true); + + case ESClass::Array: + return ArrayToSource(cx, obj); + + case ESClass::Error: + return ErrorToSource(cx, obj); + + case ESClass::RegExp: { + FixedInvokeArgs<0> args(cx); + RootedValue rval(cx); + if (!CallSelfHostedFunction(cx, cx->names().dollar_RegExpToString_, v, + args, &rval)) { + return nullptr; + } + return ToString<CanGC>(cx, rval); + } + + case ESClass::Boolean: + return BoxedToSource(cx, obj, "Boolean"); + + case ESClass::Number: + return BoxedToSource(cx, obj, "Number"); + + case ESClass::String: + return BoxedToSource(cx, obj, "String"); + + case ESClass::Date: + return BoxedToSource(cx, obj, "Date"); + + default: + return ObjectToSource(cx, obj); + } + } + + case JS::ValueType::PrivateGCThing: + case JS::ValueType::Magic: + MOZ_ASSERT_UNREACHABLE( + "internal value types shouldn't leak into places " + "wanting source representations"); + return nullptr; + } + + MOZ_ASSERT_UNREACHABLE("shouldn't see an unrecognized value type"); + return nullptr; +} |