commit f7ba505d3dbc5a4f91240c1fedbf6b245f18d20b
parent b46ba4b78eb9ff83adf03bbf09e8c45f058d55ba
Author: Matthew Gaudet <mgaudet@mozilla.com>
Date: Wed, 12 Nov 2025 22:02:50 +0000
Bug 1989115 - Add FixedBufferPrinter r=mgaudet
Differential Revision: https://phabricator.services.mozilla.com/D269788
Diffstat:
4 files changed, 142 insertions(+), 0 deletions(-)
diff --git a/js/public/Printer.h b/js/public/Printer.h
@@ -323,6 +323,28 @@ class JS_PUBLIC_API JSSprinter : public StringPrinter {
JSString* release(JSContext* cx) { return releaseJS(cx); }
};
+// FixedBufferPrinter, print to a fixed-size buffer. The string in the buffer
+// will always be null-terminated after being passed to the constructor.
+class FixedBufferPrinter final : public GenericPrinter {
+ private:
+ // The first char in the buffer where put will append the next string
+ char* buffer_;
+ // The remaining size available in the buffer
+ size_t size_;
+
+ public:
+ constexpr FixedBufferPrinter(char* buf, size_t size)
+ : buffer_(buf), size_(size) {
+ MOZ_ASSERT(buffer_);
+ memset(buffer_, 0, size_);
+ }
+
+ // Puts |len| characters from |s| at the current position.
+ // If the buffer fills up, this won't do anything.
+ void put(const char* s, size_t len) override;
+ using GenericPrinter::put; // pick up |put(const char* s);|
+};
+
// Fprinter, print a string directly into a file.
class JS_PUBLIC_API Fprinter final : public GenericPrinter {
private:
diff --git a/js/src/jsapi-tests/moz.build b/js/src/jsapi-tests/moz.build
@@ -108,6 +108,7 @@ UNIFIED_SOURCES += [
"testParserAtom.cpp",
"testPersistentRooted.cpp",
"testPreserveJitCode.cpp",
+ "testPrinter.cpp",
"testPrintf.cpp",
"testPrivateGCThingValue.cpp",
"testProfileStrings.cpp",
diff --git a/js/src/jsapi-tests/testPrinter.cpp b/js/src/jsapi-tests/testPrinter.cpp
@@ -0,0 +1,111 @@
+/* -*- 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 "js/Printer.h" // FixedBufferPrinter
+
+#include "jsapi-tests/tests.h"
+
+using namespace js;
+
+struct TestBuffer {
+ const char ASCII_ACK = (char)6;
+
+ char* buffer;
+ size_t size;
+
+ // len is the buffer size, including terminating null
+ explicit TestBuffer(const size_t len) : buffer(new char[len + 3]), size(len) {
+ buffer[size] = ASCII_ACK; // to detect overflow
+ buffer[size + 1] = ASCII_ACK;
+ buffer[size + 2] = ASCII_ACK;
+ }
+
+ ~TestBuffer() { delete[] buffer; }
+
+ // test has written past the end of the buffer
+ bool hasOverflowed() {
+ return buffer[size] != ASCII_ACK || buffer[size + 1] != ASCII_ACK ||
+ buffer[size + 2] != ASCII_ACK;
+ }
+
+ bool matches(const char* expected) { return strcmp(buffer, expected) == 0; }
+};
+
+BEGIN_TEST(testFixedBufferPrinter) {
+ // empty buffer
+ {
+ TestBuffer actual(0);
+ FixedBufferPrinter fbp(actual.buffer, 0);
+ fbp.put("will not fit");
+ CHECK(!actual.hasOverflowed());
+ }
+ // buffer is initially null-terminated
+ {
+ TestBuffer actual(10);
+ // make sure the buffer is not null-terminated
+ memset(actual.buffer, '!', actual.size);
+ FixedBufferPrinter fbp(actual.buffer, 10);
+ CHECK(!actual.hasOverflowed());
+ CHECK(actual.matches(""));
+ }
+ // one put that fits
+ {
+ TestBuffer actual(50);
+ FixedBufferPrinter fbp(actual.buffer, actual.size);
+ const char* expected = "expected string fits";
+ fbp.put(expected);
+ CHECK(!actual.hasOverflowed());
+ CHECK(actual.matches(expected));
+ }
+ // unterminated string in put
+ {
+ TestBuffer actual(50);
+ FixedBufferPrinter fbp(actual.buffer, actual.size);
+ const char* expected = "okBAD";
+ fbp.put(expected, 2);
+ CHECK(!actual.hasOverflowed());
+ CHECK(actual.matches("ok"));
+ }
+ // one put that more than fills the buffer
+ {
+ TestBuffer actual(16);
+ FixedBufferPrinter fbp(actual.buffer, actual.size);
+ const char* expected = "expected string overflow";
+ fbp.put(expected);
+ CHECK(!actual.hasOverflowed());
+ CHECK(actual.matches("expected string"));
+ }
+ // maintains position over multiple puts that fit
+ {
+ TestBuffer actual(16);
+ FixedBufferPrinter fbp(actual.buffer, actual.size);
+ fbp.put("expected ");
+ fbp.put("string");
+ CHECK(actual.matches("expected string"));
+ }
+ // multiple puts, last one more than fills the buffer
+ {
+ TestBuffer actual(9);
+ FixedBufferPrinter fbp(actual.buffer, actual.size);
+ fbp.put("expected");
+ fbp.put("overflow");
+ CHECK(!actual.hasOverflowed());
+ CHECK(actual.matches("expected"));
+ }
+ // put after buffer is full doesn't overflow
+ {
+ TestBuffer actual(2);
+ FixedBufferPrinter fbp(actual.buffer, actual.size);
+ fbp.put("exp");
+ fbp.put("overflow");
+ CHECK(!actual.hasOverflowed());
+ CHECK(actual.matches("e"));
+ }
+
+ return true;
+}
+END_TEST(testFixedBufferPrinter)
diff --git a/js/src/vm/Printer.cpp b/js/src/vm/Printer.cpp
@@ -497,6 +497,14 @@ void LSprinter::clear() {
hadOOM_ = false;
}
+void FixedBufferPrinter::put(const char* s, size_t len) {
+ snprintf(buffer_, size_, "%.*s", int(len), s);
+ size_t written = std::min(len, size_);
+ MOZ_ASSERT(size_ >= written);
+ size_ -= written;
+ buffer_ += written;
+}
+
void LSprinter::put(const char* s, size_t len) {
if (hadOutOfMemory()) {
return;