Bug 999140 - Implement createMappedArrayBuffer for testing, r=Waldo
authorSteve Fink <sfink@mozilla.com>
Fri, 25 Apr 2014 13:46:26 -0700
changeset 198834 b51cc5e640ec40ab19effd112597d5add445be9c
parent 198833 2f0714c1413b30e8b7be060a6bbab47a8a6fe90a
child 198835 024eb3e19b741769861ffac85edb3d624c0c9201
push id3624
push userasasaki@mozilla.com
push dateMon, 09 Jun 2014 21:49:01 +0000
treeherdermozilla-beta@b1a5da15899a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersWaldo
bugs999140
milestone31.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 999140 - Implement createMappedArrayBuffer for testing, r=Waldo
js/src/shell/js.cpp
js/src/tests/js1_8_5/extensions/empty.txt
js/src/tests/js1_8_5/extensions/file-mapped-arraybuffers.js
js/src/tests/js1_8_5/extensions/file-mapped-arraybuffers.txt
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -89,16 +89,21 @@ using mozilla::PodCopy;
 
 enum JSShellExitCode {
     EXITCODE_RUNTIME_ERROR      = 3,
     EXITCODE_FILE_NOT_FOUND     = 4,
     EXITCODE_OUT_OF_MEMORY      = 5,
     EXITCODE_TIMEOUT            = 6
 };
 
+enum PathResolutionMode {
+    RootRelative,
+    ScriptRelative
+};
+
 static size_t gStackChunkSize = 8192;
 
 /*
  * Note: This limit should match the stack limit set by the browser in
  *       js/xpconnect/src/XPCJSRuntime.cpp
  */
 #if defined(MOZ_ASAN) || (defined(DEBUG) && !defined(XP_WIN))
 static size_t gMaxStackSize = 2 * 128 * sizeof(size_t) * 1024;
@@ -624,17 +629,17 @@ Version(JSContext *cx, unsigned argc, js
  * Resolve a (possibly) relative filename to an absolute path. If
  * |scriptRelative| is true, then the result will be relative to the directory
  * containing the currently-running script, or the current working directory if
  * the currently-running script is "-e" (namely, you're using it from the
  * command line.) Otherwise, it will be relative to the current working
  * directory.
  */
 static JSString *
-ResolvePath(JSContext *cx, HandleString filenameStr, bool scriptRelative)
+ResolvePath(JSContext *cx, HandleString filenameStr, PathResolutionMode resolveMode)
 {
     JSAutoByteString filename(cx, filenameStr);
     if (!filename)
         return nullptr;
 
     const char *pathname = filename.ptr();
     if (pathname[0] == '/')
         return filenameStr;
@@ -655,20 +660,20 @@ ResolvePath(JSContext *cx, HandleString 
     JS::AutoFilename scriptFilename;
     if (!DescribeScriptedCaller(cx, &scriptFilename))
         return nullptr;
 
     if (!scriptFilename.get())
         return nullptr;
 
     if (strcmp(scriptFilename.get(), "-e") == 0 || strcmp(scriptFilename.get(), "typein") == 0)
-        scriptRelative = false;
+        resolveMode = RootRelative;
 
     static char buffer[PATH_MAX+1];
-    if (scriptRelative) {
+    if (resolveMode == ScriptRelative) {
 #ifdef XP_WIN
         // The docs say it can return EINVAL, but the compiler says it's void
         _splitpath(scriptFilename.get(), nullptr, buffer, nullptr, nullptr);
 #else
         strncpy(buffer, scriptFilename.get(), PATH_MAX+1);
         if (buffer[PATH_MAX] != '\0')
             return nullptr;
 
@@ -687,16 +692,97 @@ ResolvePath(JSContext *cx, HandleString 
     strncpy(buffer + len + 1, pathname, sizeof(buffer) - (len+1));
     if (buffer[PATH_MAX] != '\0')
         return nullptr;
 
     return JS_NewStringCopyZ(cx, buffer);
 }
 
 static bool
+CreateMappedArrayBuffer(JSContext *cx, unsigned argc, Value *vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    if (args.length() < 1 || args.length() > 3) {
+        JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr,
+                             args.length() < 1 ? JSSMSG_NOT_ENOUGH_ARGS : JSSMSG_TOO_MANY_ARGS,
+                             "createMappedArrayBuffer");
+        return false;
+    }
+
+    RootedString rawFilenameStr(cx, JS::ToString(cx, args[0]));
+    if (!rawFilenameStr)
+        return false;
+    // It's a little bizarre to resolve relative to the script, but for testing
+    // I need a file at a known location, and the only good way I know of to do
+    // that right now is to include it in the repo alongside the test script.
+    // Bug 944164 would introduce an alternative.
+    JSString *filenameStr = ResolvePath(cx, rawFilenameStr, ScriptRelative);
+    if (!filenameStr)
+        return false;
+    JSAutoByteString filename(cx, filenameStr);
+    if (!filename)
+        return false;
+
+    uint32_t offset = 0;
+    if (args.length() >= 2) {
+        if (!JS::ToUint32(cx, args[1], &offset))
+            return false;
+    }
+
+    bool sizeGiven = false;
+    uint32_t size;
+    if (args.length() >= 3) {
+        if (!JS::ToUint32(cx, args[2], &size))
+            return false;
+        sizeGiven = true;
+        if (offset > size) {
+            JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
+                                 JSMSG_ARG_INDEX_OUT_OF_RANGE, "2");
+            return false;
+        }
+    }
+
+    FILE *file = fopen(filename.ptr(), "r");
+    if (!file) {
+        JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr,
+                             JSSMSG_CANT_OPEN, filename.ptr(), strerror(errno));
+        return false;
+    }
+    AutoCloseInputFile autoClose(file);
+
+    if (!sizeGiven) {
+        struct stat st;
+        if (fstat(fileno(file), &st) < 0) {
+            JS_ReportError(cx, "Unable to stat file");
+            return false;
+        }
+        if (st.st_size < offset) {
+            JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
+                                 JSMSG_ARG_INDEX_OUT_OF_RANGE, "2");
+            return false;
+        }
+        size = st.st_size - offset;
+    }
+
+    void *contents = JS_CreateMappedArrayBufferContents(fileno(file), offset, size);
+    if (!contents) {
+        JS_ReportError(cx, "failed to allocate mapped array buffer contents (possibly due to bad alignment)");
+        return false;
+    }
+
+    RootedObject obj(cx, JS_NewMappedArrayBufferWithContents(cx, size, contents));
+    if (!obj)
+        return false;
+
+    args.rval().setObject(*obj);
+    return true;
+}
+
+static bool
 Options(JSContext *cx, unsigned argc, jsval *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     JS::ContextOptions oldContextOptions = JS::ContextOptionsRef(cx);
     for (unsigned i = 0; i < args.length(); i++) {
         JSString *str = JS::ToString(cx, args[i]);
         if (!str)
@@ -767,17 +853,17 @@ LoadScript(JSContext *cx, unsigned argc,
 
     RootedString str(cx);
     for (unsigned i = 0; i < args.length(); i++) {
         str = JS::ToString(cx, args[i]);
         if (!str) {
             JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS, "load");
             return false;
         }
-        str = ResolvePath(cx, str, scriptRelative);
+        str = ResolvePath(cx, str, scriptRelative ? ScriptRelative : RootRelative);
         if (!str) {
             JS_ReportError(cx, "unable to resolve path");
             return false;
         }
         JSAutoByteString filename(cx, str);
         if (!filename)
             return false;
         errno = 0;
@@ -3748,17 +3834,17 @@ ReadFile(JSContext *cx, unsigned argc, j
     }
 
     if (!args[0].isString() || (args.length() == 2 && !args[1].isString())) {
         JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS, "snarf");
         return false;
     }
 
     RootedString givenPath(cx, args[0].toString());
-    RootedString str(cx, ResolvePath(cx, givenPath, scriptRelative));
+    RootedString str(cx, ResolvePath(cx, givenPath, scriptRelative ? ScriptRelative : RootRelative));
     if (!str)
         return false;
 
     JSAutoByteString filename(cx, str);
     if (!filename)
         return false;
 
     if (args.length() > 1) {
@@ -3793,17 +3879,17 @@ static bool
 ReadRelativeToScript(JSContext *cx, unsigned argc, jsval *vp)
 {
     return ReadFile(cx, argc, vp, true);
 }
 
 static bool
 redirect(JSContext *cx, FILE* fp, HandleString relFilename)
 {
-    RootedString filename(cx, ResolvePath(cx, relFilename, false));
+    RootedString filename(cx, ResolvePath(cx, relFilename, RootRelative));
     if (!filename)
         return false;
     JSAutoByteString filenameABS(cx, filename);
     if (!filenameABS)
         return false;
     if (freopen(filenameABS.ptr(), "wb", fp) == nullptr) {
         JS_ReportError(cx, "cannot redirect to %s: %s", filenameABS.ptr(), strerror(errno));
         return false;
@@ -4689,16 +4775,20 @@ static const JSFunctionSpecWithHelp shel
 "         integer that fits in 32 bits; use that as the new compartment's\n"
 "         principal. Shell principals are toys, meant only for testing; one\n"
 "         shell principal subsumes another if its set bits are a superset of\n"
 "         the other's. Thus, a principal of 0 subsumes nothing, while a\n"
 "         principals of ~0 subsumes all other principals. The absence of a\n"
 "         principal is treated as if its bits were 0xffff, for subsumption\n"
 "         purposes. If this property is omitted, supply no principal."),
 
+    JS_FN_HELP("createMappedArrayBuffer", CreateMappedArrayBuffer, 1, 0,
+"createMappedArrayBuffer(filename, [offset, [size]])",
+"  Create an array buffer that mmaps the given file."),
+
     JS_FN_HELP("enableStackWalkingAssertion", EnableStackWalkingAssertion, 1, 0,
 "enableStackWalkingAssertion(enabled)",
 "  Enables or disables a particularly expensive assertion in stack-walking\n"
 "  code.  If your test isn't ridiculously thorough, such that performing this\n"
 "  assertion increases test duration by an order of magnitude, you shouldn't\n"
 "  use this."),
 
     JS_FN_HELP("getMaxArgs", GetMaxArgs, 0, 0,
new file mode 100644
new file mode 100644
--- /dev/null
+++ b/js/src/tests/js1_8_5/extensions/file-mapped-arraybuffers.js
@@ -0,0 +1,40 @@
+// |reftest| skip-if(!xulRuntime.shell)
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/licenses/publicdomain/
+
+function viewToString(view)
+{
+  return String.fromCharCode.apply(null, view);
+}
+
+function assertThrows(f, wantException)
+{
+    try {
+      f();
+      assertEq(true, false, "expected " + wantException + " exception");
+    } catch (e) {
+      assertEq(e.name, wantException.name, e.toString());
+    }
+}
+
+function test() {
+    var filename = "file-mapped-arraybuffers.txt";
+    var buffer = createMappedArrayBuffer(filename);
+    var view = new Uint8Array(buffer);
+    assertEq(viewToString(view), "01234567abcdefghijkl");
+
+    var buffer2 = createMappedArrayBuffer(filename, 8);
+    view = new Uint8Array(buffer2);
+    assertEq(viewToString(view), "abcdefghijkl");
+
+    var buffer3 = createMappedArrayBuffer(filename, 0, 8);
+    view = new Uint8Array(buffer3);
+    assertEq(viewToString(view), "01234567");
+
+    // Check that invalid sizes and offsets are caught
+    assertThrows(() => createMappedArrayBuffer("empty.txt", 8), RangeError);
+    assertThrows(() => createMappedArrayBuffer("empty.txt", 0, 8), Error);
+}
+
+test();
+reportCompare(0, 0, 'ok');
new file mode 100644
--- /dev/null
+++ b/js/src/tests/js1_8_5/extensions/file-mapped-arraybuffers.txt
@@ -0,0 +1,1 @@
+01234567abcdefghijkl
\ No newline at end of file