ToSource.cpp (6960B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * vim: set ts=8 sts=2 et sw=2 tw=80: 3 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "vm/ToSource.h" 8 9 #include "mozilla/Assertions.h" // MOZ_ASSERT 10 #include "mozilla/FloatingPoint.h" // mozilla::IsNegativeZero 11 12 #include <stdint.h> // uint32_t 13 14 #include "builtin/Array.h" // ArrayToSource 15 #include "builtin/Boolean.h" // BooleanToString 16 #include "builtin/Object.h" // ObjectToSource 17 #include "gc/GCEnum.h" // CanGC 18 #include "js/Class.h" // ESClass 19 #include "js/friend/StackLimits.h" // js::AutoCheckRecursionLimit 20 #include "js/Object.h" // JS::GetBuiltinClass 21 #include "js/Printer.h" // QuoteString 22 #include "js/Symbol.h" // SymbolCode, JS::WellKnownSymbolLimit 23 #include "js/TypeDecls.h" // Rooted{Object, String, Value}, HandleValue, Latin1Char 24 #include "js/Utility.h" // UniqueChars 25 #include "js/Value.h" // JS::Value 26 #include "util/StringBuilder.h" // JSStringBuilder 27 #include "vm/ErrorObject.h" // ErrorObject, ErrorToSource 28 #include "vm/Interpreter.h" // Call 29 #include "vm/JSContext.h" // JSContext 30 #include "vm/JSFunction.h" // JSFunction, fun_toStringHelper 31 #include "vm/SelfHosting.h" // CallSelfHostedFunction 32 #include "vm/Stack.h" // FixedInvokeArgs 33 #include "vm/StaticStrings.h" // StaticStrings 34 #include "vm/StringType.h" // NewStringCopy{N,Z}, ToString 35 #include "vm/SymbolType.h" // Symbol 36 #include "vm/JSContext-inl.h" // JSContext::check 37 #include "vm/JSObject-inl.h" // IsCallable 38 #include "vm/ObjectOperations-inl.h" // GetProperty 39 40 using namespace js; 41 42 using mozilla::IsNegativeZero; 43 44 using JS::GetBuiltinClass; 45 46 /* 47 * Convert a JSString to its source expression; returns null after reporting an 48 * error, otherwise returns a new string reference. No Handle needed since the 49 * input is dead after the GC. 50 */ 51 static JSString* StringToSource(JSContext* cx, JSString* str) { 52 UniqueChars chars = QuoteString(cx, str, '"'); 53 if (!chars) { 54 return nullptr; 55 } 56 return NewStringCopyZ<CanGC>(cx, chars.get()); 57 } 58 59 static JSString* SymbolToSource(JSContext* cx, JS::Symbol* symbol) { 60 using JS::SymbolCode; 61 62 RootedString desc(cx, symbol->description()); 63 SymbolCode code = symbol->code(); 64 if (symbol->isWellKnownSymbol()) { 65 // Well-known symbol. 66 return desc; 67 } 68 69 if (code == SymbolCode::PrivateNameSymbol) { 70 MOZ_ASSERT(desc); 71 return desc; 72 } 73 74 MOZ_ASSERT(code == SymbolCode::InSymbolRegistry || 75 code == SymbolCode::UniqueSymbol); 76 77 JSStringBuilder buf(cx); 78 if (code == SymbolCode::InSymbolRegistry ? !buf.append("Symbol.for(") 79 : !buf.append("Symbol(")) { 80 return nullptr; 81 } 82 if (desc) { 83 UniqueChars quoted = QuoteString(cx, desc, '"'); 84 if (!quoted || !buf.append(quoted.get(), strlen(quoted.get()))) { 85 return nullptr; 86 } 87 } 88 if (!buf.append(')')) { 89 return nullptr; 90 } 91 return buf.finishString(); 92 } 93 94 static JSString* BoxedToSource(JSContext* cx, HandleObject obj, 95 const char* constructor) { 96 RootedValue value(cx); 97 if (!Unbox(cx, obj, &value)) { 98 return nullptr; 99 } 100 MOZ_ASSERT(!value.isUndefined()); 101 102 RootedString str(cx, ValueToSource(cx, value)); 103 if (!str) { 104 return nullptr; 105 } 106 107 JSStringBuilder buf(cx); 108 if (!buf.append("new ") || !buf.append(constructor, strlen(constructor)) || 109 !buf.append('(') || !buf.append(str) || !buf.append(')')) { 110 return nullptr; 111 } 112 113 return buf.finishString(); 114 } 115 116 JSString* js::ValueToSource(JSContext* cx, HandleValue v) { 117 AutoCheckRecursionLimit recursion(cx); 118 if (!recursion.check(cx)) { 119 return nullptr; 120 } 121 cx->check(v); 122 123 switch (v.type()) { 124 case JS::ValueType::Undefined: 125 return cx->names().void_0_; 126 127 case JS::ValueType::String: 128 return StringToSource(cx, v.toString()); 129 130 case JS::ValueType::Symbol: 131 return SymbolToSource(cx, v.toSymbol()); 132 133 case JS::ValueType::Null: 134 return cx->names().null; 135 136 case JS::ValueType::Boolean: 137 return BooleanToString(cx, v.toBoolean()); 138 139 case JS::ValueType::Double: 140 /* Special case to preserve negative zero, _contra_ toString. */ 141 if (IsNegativeZero(v.toDouble())) { 142 static const Latin1Char negativeZero[] = {'-', '0'}; 143 144 return NewStringCopyN<CanGC>(cx, negativeZero, std::size(negativeZero)); 145 } 146 [[fallthrough]]; 147 case JS::ValueType::Int32: 148 return ToString<CanGC>(cx, v); 149 150 case JS::ValueType::BigInt: { 151 RootedString str(cx, ToString<CanGC>(cx, v)); 152 if (!str) { 153 return nullptr; 154 } 155 156 RootedString n(cx, cx->staticStrings().getUnit('n')); 157 158 return ConcatStrings<CanGC>(cx, str, n); 159 } 160 161 case JS::ValueType::Object: { 162 RootedValue fval(cx); 163 RootedObject obj(cx, &v.toObject()); 164 if (!GetProperty(cx, obj, obj, cx->names().toSource, &fval)) { 165 return nullptr; 166 } 167 if (IsCallable(fval)) { 168 RootedValue v(cx); 169 if (!js::Call(cx, fval, obj, &v)) { 170 return nullptr; 171 } 172 173 return ToString<CanGC>(cx, v); 174 } 175 176 ESClass cls; 177 if (!GetBuiltinClass(cx, obj, &cls)) { 178 return nullptr; 179 } 180 181 // All ToSource functions must be able to handle wrapped objects! 182 switch (cls) { 183 case ESClass::Function: 184 return fun_toStringHelper(cx, obj, true); 185 186 case ESClass::Array: 187 return ArrayToSource(cx, obj); 188 189 case ESClass::Error: 190 return ErrorToSource(cx, obj); 191 192 case ESClass::RegExp: { 193 FixedInvokeArgs<0> args(cx); 194 RootedValue rval(cx); 195 if (!CallSelfHostedFunction(cx, cx->names().dollar_RegExpToString_, v, 196 args, &rval)) { 197 return nullptr; 198 } 199 return ToString<CanGC>(cx, rval); 200 } 201 202 case ESClass::Boolean: 203 return BoxedToSource(cx, obj, "Boolean"); 204 205 case ESClass::Number: 206 return BoxedToSource(cx, obj, "Number"); 207 208 case ESClass::String: 209 return BoxedToSource(cx, obj, "String"); 210 211 case ESClass::Date: 212 return BoxedToSource(cx, obj, "Date"); 213 214 default: 215 return ObjectToSource(cx, obj); 216 } 217 } 218 219 case JS::ValueType::PrivateGCThing: 220 case JS::ValueType::Magic: 221 MOZ_ASSERT_UNREACHABLE( 222 "internal value types shouldn't leak into places " 223 "wanting source representations"); 224 return nullptr; 225 } 226 227 MOZ_ASSERT_UNREACHABLE("shouldn't see an unrecognized value type"); 228 return nullptr; 229 }