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/Printer.h"
8
9 #include "mozilla/PodOperations.h"
10 #include "mozilla/Printf.h"
11 #include "mozilla/RangedPtr.h"
12
13 #include <stdarg.h>
14 #include <stdio.h>
15
16 #include "ds/LifoAlloc.h"
17 #include "js/CharacterEncoding.h"
18 #include "util/Memory.h"
19 #include "util/Text.h"
20 #include "util/Windows.h"
21 #include "vm/JSContext.h"
22
23 using mozilla::PodCopy;
24
25 namespace {
26
27 class GenericPrinterPrintfTarget : public mozilla::PrintfTarget {
28 public:
GenericPrinterPrintfTarget(js::GenericPrinter & p)29 explicit GenericPrinterPrintfTarget(js::GenericPrinter& p) : printer(p) {}
30
append(const char * sp,size_t len)31 bool append(const char* sp, size_t len) override {
32 return printer.put(sp, len);
33 }
34
35 private:
36 js::GenericPrinter& printer;
37 };
38
39 } // namespace
40
41 namespace js {
42
reportOutOfMemory()43 void GenericPrinter::reportOutOfMemory() {
44 if (hadOOM_) {
45 return;
46 }
47 hadOOM_ = true;
48 }
49
hadOutOfMemory() const50 bool GenericPrinter::hadOutOfMemory() const { return hadOOM_; }
51
printf(const char * fmt,...)52 bool GenericPrinter::printf(const char* fmt, ...) {
53 va_list va;
54 va_start(va, fmt);
55 bool r = vprintf(fmt, va);
56 va_end(va);
57 return r;
58 }
59
vprintf(const char * fmt,va_list ap)60 bool GenericPrinter::vprintf(const char* fmt, va_list ap) {
61 // Simple shortcut to avoid allocating strings.
62 if (strchr(fmt, '%') == nullptr) {
63 return put(fmt);
64 }
65
66 GenericPrinterPrintfTarget printer(*this);
67 if (!printer.vprint(fmt, ap)) {
68 reportOutOfMemory();
69 return false;
70 }
71 return true;
72 }
73
74 const size_t Sprinter::DefaultSize = 64;
75
realloc_(size_t newSize)76 bool Sprinter::realloc_(size_t newSize) {
77 MOZ_ASSERT(newSize > (size_t)offset);
78 char* newBuf = (char*)js_realloc(base, newSize);
79 if (!newBuf) {
80 reportOutOfMemory();
81 return false;
82 }
83 base = newBuf;
84 size = newSize;
85 base[size - 1] = '\0';
86 return true;
87 }
88
Sprinter(JSContext * cx,bool shouldReportOOM)89 Sprinter::Sprinter(JSContext* cx, bool shouldReportOOM)
90 : context(cx),
91 #ifdef DEBUG
92 initialized(false),
93 #endif
94 shouldReportOOM(shouldReportOOM),
95 base(nullptr),
96 size(0),
97 offset(0) {
98 }
99
~Sprinter()100 Sprinter::~Sprinter() {
101 #ifdef DEBUG
102 if (initialized) {
103 checkInvariants();
104 }
105 #endif
106 js_free(base);
107 }
108
init()109 bool Sprinter::init() {
110 MOZ_ASSERT(!initialized);
111 base = js_pod_malloc<char>(DefaultSize);
112 if (!base) {
113 reportOutOfMemory();
114 return false;
115 }
116 #ifdef DEBUG
117 initialized = true;
118 #endif
119 *base = '\0';
120 size = DefaultSize;
121 base[size - 1] = '\0';
122 return true;
123 }
124
checkInvariants() const125 void Sprinter::checkInvariants() const {
126 MOZ_ASSERT(initialized);
127 MOZ_ASSERT((size_t)offset < size);
128 MOZ_ASSERT(base[size - 1] == '\0');
129 }
130
release()131 UniqueChars Sprinter::release() {
132 checkInvariants();
133 if (hadOOM_) {
134 return nullptr;
135 }
136
137 char* str = base;
138 base = nullptr;
139 offset = size = 0;
140 #ifdef DEBUG
141 initialized = false;
142 #endif
143 return UniqueChars(str);
144 }
145
stringAt(ptrdiff_t off) const146 char* Sprinter::stringAt(ptrdiff_t off) const {
147 MOZ_ASSERT(off >= 0 && (size_t)off < size);
148 return base + off;
149 }
150
operator [](size_t off)151 char& Sprinter::operator[](size_t off) {
152 MOZ_ASSERT(off < size);
153 return *(base + off);
154 }
155
reserve(size_t len)156 char* Sprinter::reserve(size_t len) {
157 InvariantChecker ic(this);
158
159 while (len + 1 > size - offset) { /* Include trailing \0 */
160 if (!realloc_(size * 2)) {
161 return nullptr;
162 }
163 }
164
165 char* sb = base + offset;
166 offset += len;
167 return sb;
168 }
169
put(const char * s,size_t len)170 bool Sprinter::put(const char* s, size_t len) {
171 InvariantChecker ic(this);
172
173 const char* oldBase = base;
174 const char* oldEnd = base + size;
175
176 char* bp = reserve(len);
177 if (!bp) {
178 return false;
179 }
180
181 /* s is within the buffer already */
182 if (s >= oldBase && s < oldEnd) {
183 /* buffer was realloc'ed */
184 if (base != oldBase) {
185 s = stringAt(s - oldBase); /* this is where it lives now */
186 }
187 memmove(bp, s, len);
188 } else {
189 js_memcpy(bp, s, len);
190 }
191
192 bp[len] = '\0';
193 return true;
194 }
195
putString(JSString * s)196 bool Sprinter::putString(JSString* s) {
197 InvariantChecker ic(this);
198
199 JSLinearString* linear = s->ensureLinear(context);
200 if (!linear) {
201 return false;
202 }
203
204 size_t length = JS::GetDeflatedUTF8StringLength(linear);
205
206 char* buffer = reserve(length);
207 if (!buffer) {
208 return false;
209 }
210
211 mozilla::DebugOnly<size_t> written =
212 JS::DeflateStringToUTF8Buffer(linear, mozilla::Span(buffer, length));
213 MOZ_ASSERT(written == length);
214
215 buffer[length] = '\0';
216 return true;
217 }
218
getOffset() const219 ptrdiff_t Sprinter::getOffset() const { return offset; }
220
reportOutOfMemory()221 void Sprinter::reportOutOfMemory() {
222 if (hadOOM_) {
223 return;
224 }
225 if (context && shouldReportOOM) {
226 ReportOutOfMemory(context);
227 }
228 hadOOM_ = true;
229 }
230
jsprintf(const char * format,...)231 bool Sprinter::jsprintf(const char* format, ...) {
232 va_list ap;
233 va_start(ap, format);
234
235 bool r = vprintf(format, ap);
236 va_end(ap);
237
238 return r;
239 }
240
241 const char js_EscapeMap[] = {
242 // clang-format off
243 '\b', 'b',
244 '\f', 'f',
245 '\n', 'n',
246 '\r', 'r',
247 '\t', 't',
248 '\v', 'v',
249 '"', '"',
250 '\'', '\'',
251 '\\', '\\',
252 '\0'
253 // clang-format on
254 };
255
256 static const char JSONEscapeMap[] = {
257 // clang-format off
258 '\b', 'b',
259 '\f', 'f',
260 '\n', 'n',
261 '\r', 'r',
262 '\t', 't',
263 '"', '"',
264 '\\', '\\',
265 '\0'
266 // clang-format on
267 };
268
269 template <QuoteTarget target, typename CharT>
QuoteString(Sprinter * sp,const mozilla::Range<const CharT> chars,char quote)270 bool QuoteString(Sprinter* sp, const mozilla::Range<const CharT> chars,
271 char quote) {
272 MOZ_ASSERT_IF(target == QuoteTarget::JSON, quote == '\0');
273
274 using CharPtr = mozilla::RangedPtr<const CharT>;
275
276 const char* escapeMap =
277 (target == QuoteTarget::String) ? js_EscapeMap : JSONEscapeMap;
278
279 if (quote) {
280 if (!sp->putChar(quote)) {
281 return false;
282 }
283 }
284
285 const CharPtr end = chars.end();
286
287 /* Loop control variables: end points at end of string sentinel. */
288 for (CharPtr t = chars.begin(); t < end; ++t) {
289 /* Move t forward from s past un-quote-worthy characters. */
290 const CharPtr s = t;
291 char16_t c = *t;
292 while (c < 127 && c != '\\') {
293 if (target == QuoteTarget::String) {
294 if (!IsAsciiPrintable(c) || c == quote || c == '\t') {
295 break;
296 }
297 } else {
298 if (c < ' ' || c == '"') {
299 break;
300 }
301 }
302
303 ++t;
304 if (t == end) {
305 break;
306 }
307 c = *t;
308 }
309
310 {
311 ptrdiff_t len = t - s;
312 ptrdiff_t base = sp->getOffset();
313 if (!sp->reserve(len)) {
314 return false;
315 }
316
317 for (ptrdiff_t i = 0; i < len; ++i) {
318 (*sp)[base + i] = char(s[i]);
319 }
320 (*sp)[base + len] = '\0';
321 }
322
323 if (t == end) {
324 break;
325 }
326
327 /* Use escapeMap, \u, or \x only if necessary. */
328 const char* escape;
329 if (!(c >> 8) && c != 0 &&
330 (escape = strchr(escapeMap, int(c))) != nullptr) {
331 if (!sp->jsprintf("\\%c", escape[1])) {
332 return false;
333 }
334 } else {
335 /*
336 * Use \x only if the high byte is 0 and we're in a quoted string,
337 * because ECMA-262 allows only \u, not \x, in Unicode identifiers
338 * (see bug 621814).
339 */
340 if (!sp->jsprintf((quote && !(c >> 8)) ? "\\x%02X" : "\\u%04X", c)) {
341 return false;
342 }
343 }
344 }
345
346 /* Sprint the closing quote and return the quoted string. */
347 if (quote) {
348 if (!sp->putChar(quote)) {
349 return false;
350 }
351 }
352
353 return true;
354 }
355
356 template bool QuoteString<QuoteTarget::String, Latin1Char>(
357 Sprinter* sp, const mozilla::Range<const Latin1Char> chars, char quote);
358
359 template bool QuoteString<QuoteTarget::String, char16_t>(
360 Sprinter* sp, const mozilla::Range<const char16_t> chars, char quote);
361
362 template bool QuoteString<QuoteTarget::JSON, Latin1Char>(
363 Sprinter* sp, const mozilla::Range<const Latin1Char> chars, char quote);
364
365 template bool QuoteString<QuoteTarget::JSON, char16_t>(
366 Sprinter* sp, const mozilla::Range<const char16_t> chars, char quote);
367
QuoteString(Sprinter * sp,JSString * str,char quote)368 bool QuoteString(Sprinter* sp, JSString* str, char quote /*= '\0' */) {
369 JSLinearString* linear = str->ensureLinear(sp->context);
370 if (!linear) {
371 return false;
372 }
373
374 JS::AutoCheckCannotGC nogc;
375 return linear->hasLatin1Chars() ? QuoteString<QuoteTarget::String>(
376 sp, linear->latin1Range(nogc), quote)
377 : QuoteString<QuoteTarget::String>(
378 sp, linear->twoByteRange(nogc), quote);
379 }
380
QuoteString(JSContext * cx,JSString * str,char quote)381 UniqueChars QuoteString(JSContext* cx, JSString* str, char quote /* = '\0' */) {
382 Sprinter sprinter(cx);
383 if (!sprinter.init()) {
384 return nullptr;
385 }
386 if (!QuoteString(&sprinter, str, quote)) {
387 return nullptr;
388 }
389 return sprinter.release();
390 }
391
JSONQuoteString(Sprinter * sp,JSString * str)392 bool JSONQuoteString(Sprinter* sp, JSString* str) {
393 JSLinearString* linear = str->ensureLinear(sp->context);
394 if (!linear) {
395 return false;
396 }
397
398 JS::AutoCheckCannotGC nogc;
399 return linear->hasLatin1Chars() ? QuoteString<QuoteTarget::JSON>(
400 sp, linear->latin1Range(nogc), '\0')
401 : QuoteString<QuoteTarget::JSON>(
402 sp, linear->twoByteRange(nogc), '\0');
403 }
404
Fprinter(FILE * fp)405 Fprinter::Fprinter(FILE* fp) : file_(nullptr), init_(false) { init(fp); }
406
407 #ifdef DEBUG
~Fprinter()408 Fprinter::~Fprinter() { MOZ_ASSERT_IF(init_, !file_); }
409 #endif
410
init(const char * path)411 bool Fprinter::init(const char* path) {
412 MOZ_ASSERT(!file_);
413 file_ = fopen(path, "w");
414 if (!file_) {
415 return false;
416 }
417 init_ = true;
418 return true;
419 }
420
init(FILE * fp)421 void Fprinter::init(FILE* fp) {
422 MOZ_ASSERT(!file_);
423 file_ = fp;
424 init_ = false;
425 }
426
flush()427 void Fprinter::flush() {
428 MOZ_ASSERT(file_);
429 fflush(file_);
430 }
431
finish()432 void Fprinter::finish() {
433 MOZ_ASSERT(file_);
434 if (init_) {
435 fclose(file_);
436 }
437 file_ = nullptr;
438 }
439
put(const char * s,size_t len)440 bool Fprinter::put(const char* s, size_t len) {
441 MOZ_ASSERT(file_);
442 int i = fwrite(s, /*size=*/1, /*nitems=*/len, file_);
443 if (size_t(i) != len) {
444 reportOutOfMemory();
445 return false;
446 }
447 #ifdef XP_WIN
448 if ((file_ == stderr) && (IsDebuggerPresent())) {
449 UniqueChars buf = DuplicateString(s, len);
450 if (!buf) {
451 reportOutOfMemory();
452 return false;
453 }
454 OutputDebugStringA(buf.get());
455 }
456 #endif
457 return true;
458 }
459
LSprinter(LifoAlloc * lifoAlloc)460 LSprinter::LSprinter(LifoAlloc* lifoAlloc)
461 : alloc_(lifoAlloc), head_(nullptr), tail_(nullptr), unused_(0) {}
462
~LSprinter()463 LSprinter::~LSprinter() {
464 // This LSprinter might be allocated as part of the same LifoAlloc, so we
465 // should not expect the destructor to be called.
466 }
467
exportInto(GenericPrinter & out) const468 void LSprinter::exportInto(GenericPrinter& out) const {
469 if (!head_) {
470 return;
471 }
472
473 for (Chunk* it = head_; it != tail_; it = it->next) {
474 out.put(it->chars(), it->length);
475 }
476 out.put(tail_->chars(), tail_->length - unused_);
477 }
478
clear()479 void LSprinter::clear() {
480 head_ = nullptr;
481 tail_ = nullptr;
482 unused_ = 0;
483 hadOOM_ = false;
484 }
485
put(const char * s,size_t len)486 bool LSprinter::put(const char* s, size_t len) {
487 // Compute how much data will fit in the current chunk.
488 size_t existingSpaceWrite = 0;
489 size_t overflow = len;
490 if (unused_ > 0 && tail_) {
491 existingSpaceWrite = std::min(unused_, len);
492 overflow = len - existingSpaceWrite;
493 }
494
495 // If necessary, allocate a new chunk for overflow data.
496 size_t allocLength = 0;
497 Chunk* last = nullptr;
498 if (overflow > 0) {
499 allocLength =
500 AlignBytes(sizeof(Chunk) + overflow, js::detail::LIFO_ALLOC_ALIGN);
501
502 LifoAlloc::AutoFallibleScope fallibleAllocator(alloc_);
503 last = reinterpret_cast<Chunk*>(alloc_->alloc(allocLength));
504 if (!last) {
505 reportOutOfMemory();
506 return false;
507 }
508 }
509
510 // All fallible operations complete: now fill up existing space, then
511 // overflow space in any new chunk.
512 MOZ_ASSERT(existingSpaceWrite + overflow == len);
513
514 if (existingSpaceWrite > 0) {
515 PodCopy(tail_->end() - unused_, s, existingSpaceWrite);
516 unused_ -= existingSpaceWrite;
517 s += existingSpaceWrite;
518 }
519
520 if (overflow > 0) {
521 if (tail_ && reinterpret_cast<char*>(last) == tail_->end()) {
522 // tail_ and last are consecutive in memory. LifoAlloc has no
523 // metadata and is just a bump allocator, so we can cheat by
524 // appending the newly-allocated space to tail_.
525 unused_ = allocLength;
526 tail_->length += allocLength;
527 } else {
528 // Remove the size of the header from the allocated length.
529 size_t availableSpace = allocLength - sizeof(Chunk);
530 last->next = nullptr;
531 last->length = availableSpace;
532
533 unused_ = availableSpace;
534 if (!head_) {
535 head_ = last;
536 } else {
537 tail_->next = last;
538 }
539
540 tail_ = last;
541 }
542
543 PodCopy(tail_->end() - unused_, s, overflow);
544
545 MOZ_ASSERT(unused_ >= overflow);
546 unused_ -= overflow;
547 }
548
549 MOZ_ASSERT(len <= INT_MAX);
550 return true;
551 }
552
553 } // namespace js
554