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