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