summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoan Touzet <wohali@users.noreply.github.com>2020-04-18 01:14:33 +0000
committerGitHub <noreply@github.com>2020-04-18 01:14:33 +0000
commit6474a8e533502845c38a8ac595e602e9801d0b0e (patch)
tree2815e89ff4b28b2e87eda38f984649e8c036eae4
parent1a3316faf2ff1de7bb3863668c64f118d2ba0fcf (diff)
parentf9dc8354ac1401d5e5973b52ed084d0b77028546 (diff)
downloadcouchdb-6474a8e533502845c38a8ac595e602e9801d0b0e.tar.gz
Merge branch 'master' into feat/spidermonkey-68
-rw-r--r--.gitignore1
-rw-r--r--Makefile6
-rw-r--r--Makefile.win6
-rwxr-xr-xbin/warnings_in_scope125
-rwxr-xr-xconfigure13
-rw-r--r--configure.ps114
-rw-r--r--emilio.config20
-rw-r--r--src/couch/priv/couch_js/60/http.cpp214
-rw-r--r--src/couch/priv/couch_js/60/main.cpp69
-rw-r--r--src/couch/priv/couch_js/60/utf8.cpp301
-rw-r--r--src/couch/priv/couch_js/60/utf8.h19
-rw-r--r--src/couch/priv/couch_js/60/util.cpp196
-rw-r--r--src/couch/priv/couch_js/60/util.h4
-rw-r--r--src/couch/test/eunit/couch_js_tests.erl140
14 files changed, 557 insertions, 571 deletions
diff --git a/.gitignore b/.gitignore
index 551a6a61f..8a4a6f08d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -46,6 +46,7 @@ src/couch/priv/couch_js/**/*.d
src/couch/priv/icu_driver/couch_icu_driver.d
src/mango/src/mango_cursor_text.nocompile
src/docs/
+src/emilio/
src/ets_lru/
src/excoveralls/
src/fauxton/
diff --git a/Makefile b/Makefile
index 97fc97c85..fff1df528 100644
--- a/Makefile
+++ b/Makefile
@@ -147,6 +147,7 @@ fauxton: share/www
.PHONY: check
# target: check - Test everything
check: all python-black
+ @$(MAKE) emilio
@$(MAKE) eunit
@$(MAKE) javascript
@$(MAKE) mango-test
@@ -198,6 +199,9 @@ soak-eunit: couch
@$(REBAR) setup_eunit 2> /dev/null
while [ $$? -eq 0 ] ; do $(REBAR) -r eunit $(EUNIT_OPTS) ; done
+emilio:
+ @bin/emilio -c emilio.config src/ | bin/warnings_in_scope -s 3
+
.venv/bin/black:
@python3 -m venv .venv
@.venv/bin/pip3 install black || touch .venv/bin/black
@@ -260,7 +264,7 @@ elixir-credo: elixir-init
.PHONY: javascript
# target: javascript - Run JavaScript test suites or specific ones defined by suites option
javascript: export COUCHDB_TEST_ADMIN_PARTY_OVERRIDE=1
-javascript:
+javascript:
@$(MAKE) devclean
@mkdir -p share/www/script/test
diff --git a/Makefile.win b/Makefile.win
index bdecc7315..0fc4d91c7 100644
--- a/Makefile.win
+++ b/Makefile.win
@@ -134,6 +134,7 @@ fauxton: share\www
.PHONY: check
# target: check - Test everything
check: all python-black
+ @$(MAKE) emilio
@$(MAKE) eunit
@$(MAKE) javascript
@$(MAKE) mango-test
@@ -175,6 +176,9 @@ just-eunit: export ERL_AFLAGS = "-config $(shell echo %cd%)/rel/files/eunit.conf
just-eunit:
@$(REBAR) -r eunit $(EUNIT_OPTS)
+emilio:
+ @bin\emilio -c emilio.config src\ | python.exe bin\warnings_in_scope -s 3
+
.venv/bin/black:
@python.exe -m venv .venv
@.venv\Scripts\pip3.exe install black || copy /b .venv\Scripts\black.exe +,,
@@ -359,7 +363,7 @@ install: release
@echo .
@echo To install CouchDB into your system, copy the rel\couchdb
@echo to your desired installation location. For example:
- @echo xcopy /E rel\couchdb C:\CouchDB\
+ @echo xcopy /E rel\couchdb C:\CouchDB\
@echo .
################################################################################
diff --git a/bin/warnings_in_scope b/bin/warnings_in_scope
new file mode 100755
index 000000000..2a854211a
--- /dev/null
+++ b/bin/warnings_in_scope
@@ -0,0 +1,125 @@
+#!/usr/bin/env python3
+import os
+import subprocess
+from pathlib import Path
+import optparse
+import sys
+import re
+
+def run(command, cwd=None):
+ try:
+ return subprocess.Popen(
+ command, shell=True, cwd=cwd,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ except OSError as err:
+ raise OSError("Error in command '{0}': {1}".format(command, err))
+
+def parse_location(line):
+ # take substring between @@
+ # take second part of it
+ location = line.split(b'@@')[1].strip().split(b' ')[1]
+ tokens = location.split(b',')
+ if len(tokens) == 1:
+ return (int(tokens[0][1:]), 1)
+ elif len(tokens) == 2:
+ return (int(tokens[0][1:]), int(tokens[1]))
+
+def changed_files(directory, scope):
+ result = {}
+ proc = run('git diff --no-prefix --unified={0}'.format(scope), cwd=str(directory))
+ file_path = None
+ for line in iter(proc.stdout.readline, b''):
+ if line.startswith(b'diff --git '):
+ # this would be problematic if directory has space in the name
+ file_name = line.split(b' ')[3].strip()
+ file_path = str(directory.joinpath(str(file_name, 'utf-8')))
+ result[file_path] = set()
+ continue
+ if line.startswith(b'@@'):
+ start_pos, number_of_lines = parse_location(line)
+ for line_number in range(start_pos, start_pos + number_of_lines):
+ result[file_path].add(line_number)
+ return result
+
+def print_changed(file_name, line_number):
+ print('{0}:{1}'.format(str(file_name), str(line_number)))
+
+def changes(dirs, scope):
+ result = {}
+ for directory in dirs:
+ result.update(changed_files(directory, scope))
+ return result
+
+def repositories(root):
+ for directory in Path(root).rglob('.git'):
+ if not directory.is_dir():
+ continue
+ yield directory.parent
+
+def setup_argparse():
+ parser = optparse.OptionParser(description="Filter output to remove unrelated warning")
+ parser.add_option(
+ "-r",
+ "--regexp",
+ dest="regexp",
+ default='(?P<file_name>[^:]+):(?P<line>\d+).*',
+ help="Regexp used to extract file_name and line number",
+ )
+ parser.add_option(
+ "-s",
+ "--scope",
+ dest="scope",
+ default=0,
+ help="Number of lines surrounding the change we consider relevant",
+ )
+ parser.add_option(
+ "-p",
+ "--print-only",
+ action="store_true",
+ dest="print_only",
+ default=False,
+ help="Print changed lines only",
+ )
+ return parser.parse_args()
+
+def filter_stdin(regexp, changes):
+ any_matches = False
+ for line in iter(sys.stdin.readline, ''):
+ matches = re.match(regexp, line)
+ if matches:
+ file_name = matches.group('file_name')
+ line_number = int(matches.group('line'))
+ if file_name in changes and line_number in changes[file_name]:
+ print(line, end='')
+ any_matches = True
+ return any_matches
+
+def validate_regexp(regexp):
+ index = regexp.groupindex
+ if 'file_name' in index and 'line' in index:
+ return True
+ else:
+ raise TypeError("Regexp must define following groups:\n - file_name\n - line")
+
+def main():
+ opts, args = setup_argparse()
+ if opts.print_only:
+ for file_name, changed_lines in changes(repositories('.'), opts.scope).items():
+ for line_number in changed_lines:
+ print_changed(file_name, line_number)
+ return 0
+ else:
+ regexp = re.compile(opts.regexp)
+ validate_regexp(regexp)
+ if filter_stdin(regexp, changes(repositories('.'), opts.scope)):
+ return 1
+ else:
+ return 0
+
+if __name__ == "__main__":
+ try:
+ sys.exit(main())
+ except KeyboardInterrupt:
+ pass
+
diff --git a/configure b/configure
index 38e62e317..854366c8a 100755
--- a/configure
+++ b/configure
@@ -255,12 +255,25 @@ install_local_rebar() {
fi
}
+install_local_emilio() {
+ if [ ! -x "${rootdir}/bin/emilio" ]; then
+ if [ ! -d "${rootdir}/src/emilio" ]; then
+ git clone --depth 1 https://github.com/cloudant-labs/emilio ${rootdir}/src/emilio
+ fi
+ cd ${rootdir}/src/emilio && ${REBAR} compile escriptize; cd ${rootdir}
+ mv ${rootdir}/src/emilio/emilio ${rootdir}/bin/emilio
+ chmod +x ${rootdir}/bin/emilio
+ cd ${rootdir}/src/emilio && ${REBAR} clean; cd ${rootdir}
+ fi
+}
if [ -z "${REBAR}" ]; then
install_local_rebar
REBAR=${rootdir}/bin/rebar
fi
+install_local_emilio
+
# only update dependencies, when we are not in a release tarball
if [ -d .git -a $SKIP_DEPS -ne 1 ]; then
echo "==> updating dependencies"
diff --git a/configure.ps1 b/configure.ps1
index c74fbcf41..65f8517d6 100644
--- a/configure.ps1
+++ b/configure.ps1
@@ -205,6 +205,20 @@ if ((Get-Command "rebar.cmd" -ErrorAction SilentlyContinue) -eq $null)
$env:Path += ";$rootdir\bin"
}
+# check for emilio; if not found, get it and build it
+if ((Get-Command "emilio.cmd" -ErrorAction SilentlyContinue) -eq $null)
+{
+ Write-Verbose "==> emilio.cmd not found; bootstrapping..."
+ if (-Not (Test-Path "src\emilio"))
+ {
+ git clone --depth 1 https://github.com/wohali/emilio $rootdir\src\emilio
+ }
+ cmd /c "cd $rootdir\src\emilio && rebar compile escriptize; cd $rootdir"
+ cp $rootdir\src\emilio\emilio $rootdir\bin\emilio
+ cp $rootdir\src\emilio\bin\emilio.cmd $rootdir\bin\emilio.cmd
+ cmd /c "cd $rootdir\src\emilio && rebar clean; cd $rootdir"
+}
+
# only update dependencies, when we are not in a release tarball
if ( (Test-Path .git -PathType Container) -and (-not $SkipDeps) ) {
Write-Verbose "==> updating dependencies"
diff --git a/emilio.config b/emilio.config
new file mode 100644
index 000000000..0dad93898
--- /dev/null
+++ b/emilio.config
@@ -0,0 +1,20 @@
+{ignore, [
+ "src[\/]bear[\/]*",
+ "src[\/]b64url[\/]*",
+ "src[\/]docs[\/]*",
+ "src[\/]*[\/].eunit[\/]*",
+ "src[\/]fauxton[\/]*",
+ "src[\/]rebar[\/]*",
+ "src[\/]emilio[\/]*",
+ "src[\/]folsom[\/]*",
+ "src[\/]mochiweb[\/]*",
+ "src[\/]snappy[\/]*",
+ "src[\/]ssl_verify_fun[\/]*",
+ "src[\/]ibrowse[\/]*",
+ "src[\/]jiffy[\/]*",
+ "src[\/]meck[\/]*",
+ "src[\/]proper[\/]*",
+ "src[\/]recon[\/]*",
+ "src[\/]hyper[\/]*",
+ "src[\/]triq[\/]*"
+]}.
diff --git a/src/couch/priv/couch_js/60/http.cpp b/src/couch/priv/couch_js/60/http.cpp
index 9ab47b2f0..e1e44d622 100644
--- a/src/couch/priv/couch_js/60/http.cpp
+++ b/src/couch/priv/couch_js/60/http.cpp
@@ -18,7 +18,6 @@
#include <jsapi.h>
#include <js/Initialization.h>
#include "config.h"
-#include "utf8.h"
#include "util.h"
// Soft dependency on cURL bindings because they're
@@ -100,7 +99,6 @@ http_check_enabled()
#ifdef XP_WIN
#define strcasecmp _strcmpi
#define strncasecmp _strnicmp
-#define snprintf _snprintf
#endif
@@ -109,7 +107,7 @@ typedef struct curl_slist CurlHeaders;
typedef struct {
int method;
- char* url;
+ std::string url;
CurlHeaders* req_headers;
int16_t last_status;
} HTTPData;
@@ -127,21 +125,15 @@ const char* METHODS[] = {"GET", "HEAD", "POST", "PUT", "DELETE", "COPY", "OPTION
#define OPTIONS 6
-static bool
-go(JSContext* cx, JSObject* obj, HTTPData* http, char* body, size_t blen);
-
-
-static JSString*
-str_from_binary(JSContext* cx, char* data, size_t length);
+static bool go(JSContext* cx, JSObject* obj, HTTPData* http, std::string& body);
bool
http_ctor(JSContext* cx, JSObject* req)
{
- HTTPData* http = NULL;
+ HTTPData* http = new HTTPData();
bool ret = false;
- http = (HTTPData*) malloc(sizeof(HTTPData));
if(!http)
{
JS_ReportErrorUTF8(cx, "Failed to create CouchHTTP instance.");
@@ -149,7 +141,6 @@ http_ctor(JSContext* cx, JSObject* req)
}
http->method = -1;
- http->url = NULL;
http->req_headers = NULL;
http->last_status = -1;
@@ -159,7 +150,7 @@ http_ctor(JSContext* cx, JSObject* req)
goto success;
error:
- if(http) free(http);
+ if(http) delete http;
success:
return ret;
@@ -171,9 +162,8 @@ http_dtor(JSFreeOp* fop, JSObject* obj)
{
HTTPData* http = (HTTPData*) JS_GetPrivate(obj);
if(http) {
- if(http->url) free(http->url);
if(http->req_headers) curl_slist_free_all(http->req_headers);
- free(http);
+ delete http;
}
}
@@ -182,56 +172,50 @@ bool
http_open(JSContext* cx, JSObject* req, JS::Value mth, JS::Value url, JS::Value snc)
{
HTTPData* http = (HTTPData*) JS_GetPrivate(req);
- char* method = NULL;
int methid;
- bool ret = false;
if(!http) {
JS_ReportErrorUTF8(cx, "Invalid CouchHTTP instance.");
- goto done;
+ return false;
}
- if(mth.isUndefined()) {
- JS_ReportErrorUTF8(cx, "You must specify a method.");
- goto done;
+ if(!mth.isString()) {
+ JS_ReportErrorUTF8(cx, "Method must be a string.");
+ return false;
}
- method = enc_string(cx, mth, NULL);
- if(!method) {
+ std::string method;
+ if(!js_to_string(cx, JS::RootedValue(cx, mth), method)) {
JS_ReportErrorUTF8(cx, "Failed to encode method.");
- goto done;
+ return false;
}
for(methid = 0; METHODS[methid] != NULL; methid++) {
- if(strcasecmp(METHODS[methid], method) == 0) break;
+ if(strcasecmp(METHODS[methid], method.c_str()) == 0) break;
}
if(methid > OPTIONS) {
JS_ReportErrorUTF8(cx, "Invalid method specified.");
- goto done;
+ return false;
}
http->method = methid;
- if(url.isUndefined()) {
- JS_ReportErrorUTF8(cx, "You must specify a URL.");
- goto done;
- }
-
- if(http->url != NULL) {
- free(http->url);
- http->url = NULL;
+ if(!url.isString()) {
+ JS_ReportErrorUTF8(cx, "URL must be a string");
+ return false;
}
- http->url = enc_string(cx, url, NULL);
- if(http->url == NULL) {
+ std::string urlstr;
+ if(!js_to_string(cx, JS::RootedValue(cx, url), urlstr)) {
JS_ReportErrorUTF8(cx, "Failed to encode URL.");
- goto done;
+ return false;
}
+ http->url = urlstr;
if(snc.isBoolean() && snc.isTrue()) {
JS_ReportErrorUTF8(cx, "Synchronous flag must be false.");
- goto done;
+ return false;
}
if(http->req_headers) {
@@ -242,11 +226,7 @@ http_open(JSContext* cx, JSObject* req, JS::Value mth, JS::Value url, JS::Value
// Disable Expect: 100-continue
http->req_headers = curl_slist_append(http->req_headers, "Expect:");
- ret = true;
-
-done:
- if(method) free(method);
- return ret;
+ return true;
}
@@ -254,88 +234,60 @@ bool
http_set_hdr(JSContext* cx, JSObject* req, JS::Value name, JS::Value val)
{
HTTPData* http = (HTTPData*) JS_GetPrivate(req);
- char* keystr = NULL;
- char* valstr = NULL;
- char* hdrbuf = NULL;
- size_t hdrlen = -1;
- bool ret = false;
if(!http) {
JS_ReportErrorUTF8(cx, "Invalid CouchHTTP instance.");
- goto done;
+ return false;
}
- if(name.isUndefined())
+ if(!name.isString())
{
- JS_ReportErrorUTF8(cx, "You must speciy a header name.");
- goto done;
+ JS_ReportErrorUTF8(cx, "Header names must be strings.");
+ return false;
}
- keystr = enc_string(cx, name, NULL);
- if(!keystr)
+ std::string keystr;
+ if(!js_to_string(cx, JS::RootedValue(cx, name), keystr))
{
JS_ReportErrorUTF8(cx, "Failed to encode header name.");
- goto done;
+ return false;
}
- if(val.isUndefined())
+ if(!val.isString())
{
- JS_ReportErrorUTF8(cx, "You must specify a header value.");
- goto done;
+ JS_ReportErrorUTF8(cx, "Header values must be strings.");
+ return false;
}
- valstr = enc_string(cx, val, NULL);
- if(!valstr)
- {
+ std::string valstr;
+ if(!js_to_string(cx, JS::RootedValue(cx, val), valstr)) {
JS_ReportErrorUTF8(cx, "Failed to encode header value.");
- goto done;
- }
-
- hdrlen = strlen(keystr) + strlen(valstr) + 3;
- hdrbuf = (char*) malloc(hdrlen * sizeof(char));
- if(!hdrbuf) {
- JS_ReportErrorUTF8(cx, "Failed to allocate header buffer.");
- goto done;
+ return false;
}
- snprintf(hdrbuf, hdrlen, "%s: %s", keystr, valstr);
- http->req_headers = curl_slist_append(http->req_headers, hdrbuf);
-
- ret = true;
+ std::string header = keystr + ": " + valstr;
+ http->req_headers = curl_slist_append(http->req_headers, header.c_str());
-done:
- if(keystr) free(keystr);
- if(valstr) free(valstr);
- if(hdrbuf) free(hdrbuf);
- return ret;
+ return true;
}
bool
http_send(JSContext* cx, JSObject* req, JS::Value body)
{
HTTPData* http = (HTTPData*) JS_GetPrivate(req);
- char* bodystr = NULL;
- size_t bodylen = 0;
- bool ret = false;
if(!http) {
JS_ReportErrorUTF8(cx, "Invalid CouchHTTP instance.");
- goto done;
+ return false;
}
- if(!body.isUndefined()) {
- bodystr = enc_string(cx, body, &bodylen);
- if(!bodystr) {
- JS_ReportErrorUTF8(cx, "Failed to encode body.");
- goto done;
- }
+ std::string bodystr;
+ if(!js_to_string(cx, JS::RootedValue(cx, body), bodystr)) {
+ JS_ReportErrorUTF8(cx, "Failed to encode body.");
+ return false;
}
- ret = go(cx, req, http, bodystr, bodylen);
-
-done:
- if(bodystr) free(bodystr);
- return ret;
+ return go(cx, req, http, bodystr);
}
int
@@ -395,7 +347,7 @@ typedef struct {
HTTPData* http;
JSContext* cx;
JSObject* resp_headers;
- char* sendbuf;
+ const char* sendbuf;
size_t sendlen;
size_t sent;
int sent_once;
@@ -417,10 +369,9 @@ static size_t recv_body(void *ptr, size_t size, size_t nmem, void *data);
static size_t recv_header(void *ptr, size_t size, size_t nmem, void *data);
static bool
-go(JSContext* cx, JSObject* obj, HTTPData* http, char* body, size_t bodylen)
+go(JSContext* cx, JSObject* obj, HTTPData* http, std::string& body)
{
CurlState state;
- char* referer;
JSString* jsbody;
bool ret = false;
JS::Value tmp;
@@ -431,8 +382,8 @@ go(JSContext* cx, JSObject* obj, HTTPData* http, char* body, size_t bodylen)
state.cx = cx;
state.http = http;
- state.sendbuf = body;
- state.sendlen = bodylen;
+ state.sendbuf = body.c_str();
+ state.sendlen = body.size();
state.sent = 0;
state.sent_once = 0;
@@ -463,13 +414,13 @@ go(JSContext* cx, JSObject* obj, HTTPData* http, char* body, size_t bodylen)
tmp = JS_GetReservedSlot(obj, 0);
- if(!(referer = enc_string(cx, tmp, NULL))) {
+ std::string referer;
+ if(!js_to_string(cx, JS::RootedValue(cx, tmp), referer)) {
JS_ReportErrorUTF8(cx, "Failed to encode referer.");
if(state.recvbuf) JS_free(cx, state.recvbuf);
- return ret;
+ return ret;
}
- curl_easy_setopt(HTTP_HANDLE, CURLOPT_REFERER, referer);
- free(referer);
+ curl_easy_setopt(HTTP_HANDLE, CURLOPT_REFERER, referer.c_str());
if(http->method < 0 || http->method > OPTIONS) {
JS_ReportErrorUTF8(cx, "INTERNAL: Unknown method.");
@@ -490,15 +441,15 @@ go(JSContext* cx, JSObject* obj, HTTPData* http, char* body, size_t bodylen)
curl_easy_setopt(HTTP_HANDLE, CURLOPT_FOLLOWLOCATION, 0);
}
- if(body && bodylen) {
- curl_easy_setopt(HTTP_HANDLE, CURLOPT_INFILESIZE, bodylen);
+ if(body.size() > 0) {
+ curl_easy_setopt(HTTP_HANDLE, CURLOPT_INFILESIZE, body.size());
} else {
curl_easy_setopt(HTTP_HANDLE, CURLOPT_INFILESIZE, 0);
}
// curl_easy_setopt(HTTP_HANDLE, CURLOPT_VERBOSE, 1);
- curl_easy_setopt(HTTP_HANDLE, CURLOPT_URL, http->url);
+ curl_easy_setopt(HTTP_HANDLE, CURLOPT_URL, http->url.c_str());
curl_easy_setopt(HTTP_HANDLE, CURLOPT_HTTPHEADER, http->req_headers);
curl_easy_setopt(HTTP_HANDLE, CURLOPT_READDATA, &state);
curl_easy_setopt(HTTP_HANDLE, CURLOPT_SEEKDATA, &state);
@@ -532,12 +483,13 @@ go(JSContext* cx, JSObject* obj, HTTPData* http, char* body, size_t bodylen)
if(state.recvbuf) {
state.recvbuf[state.read] = '\0';
- jsbody = dec_string(cx, state.recvbuf, state.read+1);
+ std::string bodystr(state.recvbuf, state.read);
+ jsbody = string_to_js(cx, bodystr);
if(!jsbody) {
// If we can't decode the body as UTF-8 we forcefully
// convert it to a string by just forcing each byte
// to a char16_t.
- jsbody = str_from_binary(cx, state.recvbuf, state.read);
+ jsbody = JS_NewStringCopyN(cx, state.recvbuf, state.read);
if(!jsbody) {
if(!JS_IsExceptionPending(cx)) {
JS_ReportErrorUTF8(cx, "INTERNAL: Failed to decode body.");
@@ -572,7 +524,7 @@ go(JSContext* cx, JSObject* obj, HTTPData* http, char* body, size_t bodylen)
static size_t
send_body(void *ptr, size_t size, size_t nmem, void *data)
{
- CurlState* state = (CurlState*) data;
+ CurlState* state = static_cast<CurlState*>(data);
size_t length = size * nmem;
size_t towrite = state->sendlen - state->sent;
@@ -598,19 +550,19 @@ send_body(void *ptr, size_t size, size_t nmem, void *data)
static int
seek_body(void* ptr, curl_off_t offset, int origin)
{
- CurlState* state = (CurlState*) ptr;
+ CurlState* state = static_cast<CurlState*>(ptr);
if(origin != SEEK_SET) return -1;
- state->sent = (size_t) offset;
- return (int) state->sent;
+ state->sent = static_cast<size_t>(offset);
+ return static_cast<int>(state->sent);
}
static size_t
recv_header(void *ptr, size_t size, size_t nmem, void *data)
{
- CurlState* state = (CurlState*) data;
+ CurlState* state = static_cast<CurlState*>(data);
char code[4];
- char* header = (char*) ptr;
+ char* header = static_cast<char*>(ptr);
size_t length = size * nmem;
JSString* hdr = NULL;
uint32_t hdrlen;
@@ -638,7 +590,8 @@ recv_header(void *ptr, size_t size, size_t nmem, void *data)
}
// Append the new header to our array.
- hdr = dec_string(state->cx, header, length);
+ std::string hdrstr(header, length);
+ hdr = string_to_js(state->cx, hdrstr);
if(!hdr) {
return CURLE_WRITE_ERROR;
}
@@ -659,14 +612,17 @@ recv_header(void *ptr, size_t size, size_t nmem, void *data)
static size_t
recv_body(void *ptr, size_t size, size_t nmem, void *data)
{
- CurlState* state = (CurlState*) data;
+ CurlState* state = static_cast<CurlState*>(data);
size_t length = size * nmem;
char* tmp = NULL;
if(!state->recvbuf) {
state->recvlen = 4096;
state->read = 0;
- state->recvbuf = (char *)JS_malloc(state->cx, state->recvlen);
+ state->recvbuf = static_cast<char*>(JS_malloc(
+ state->cx,
+ state->recvlen
+ ));
}
if(!state->recvbuf) {
@@ -676,7 +632,12 @@ recv_body(void *ptr, size_t size, size_t nmem, void *data)
// +1 so we can add '\0' back up in the go function.
size_t oldlen = state->recvlen;
while(length+1 > state->recvlen - state->read) state->recvlen *= 2;
- tmp = (char *) JS_realloc(state->cx, state->recvbuf, oldlen, state->recvlen);
+ tmp = static_cast<char*>(JS_realloc(
+ state->cx,
+ state->recvbuf,
+ oldlen,
+ state->recvlen
+ ));
if(!tmp) return CURLE_WRITE_ERROR;
state->recvbuf = tmp;
@@ -685,23 +646,4 @@ recv_body(void *ptr, size_t size, size_t nmem, void *data)
return length;
}
-JSString*
-str_from_binary(JSContext* cx, char* data, size_t length)
-{
- char16_t* conv = (char16_t*) JS_malloc(cx, length * sizeof(char16_t));
- JSString* ret = NULL;
- size_t i;
-
- if(!conv) return NULL;
-
- for(i = 0; i < length; i++) {
- conv[i] = (char16_t) data[i];
- }
-
- ret = JS_NewUCString(cx, conv, length);
- if(!ret) JS_free(cx, conv);
-
- return ret;
-}
-
#endif /* HAVE_CURL */
diff --git a/src/couch/priv/couch_js/60/main.cpp b/src/couch/priv/couch_js/60/main.cpp
index b6157ed85..828b9dab5 100644
--- a/src/couch/priv/couch_js/60/main.cpp
+++ b/src/couch/priv/couch_js/60/main.cpp
@@ -28,7 +28,6 @@
#include "config.h"
#include "http.h"
-#include "utf8.h"
#include "util.h"
static bool enableSharedMemory = true;
@@ -99,8 +98,9 @@ req_ctor(JSContext* cx, unsigned int argc, JS::Value* vp)
static bool
req_open(JSContext* cx, unsigned int argc, JS::Value* vp)
{
- JSObject* obj = JS_THIS_OBJECT(cx, vp);
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+ JS::Value vobj = args.computeThis(cx);
+ JSObject* obj = vobj.toObjectOrNull();
bool ret = false;
if(argc == 2) {
@@ -119,8 +119,9 @@ req_open(JSContext* cx, unsigned int argc, JS::Value* vp)
static bool
req_set_hdr(JSContext* cx, unsigned int argc, JS::Value* vp)
{
- JSObject* obj = JS_THIS_OBJECT(cx, vp);
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+ JS::Value vobj = args.computeThis(cx);
+ JSObject* obj = vobj.toObjectOrNull();
bool ret = false;
if(argc == 2) {
@@ -137,8 +138,9 @@ req_set_hdr(JSContext* cx, unsigned int argc, JS::Value* vp)
static bool
req_send(JSContext* cx, unsigned int argc, JS::Value* vp)
{
- JSObject* obj = JS_THIS_OBJECT(cx, vp);
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+ JS::Value vobj = args.computeThis(cx);
+ JSObject* obj = vobj.toObjectOrNull();
bool ret = false;
if(argc == 1) {
@@ -155,7 +157,9 @@ static bool
req_status(JSContext* cx, unsigned int argc, JS::Value* vp)
{
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
- JSObject* obj = JS_THIS_OBJECT(cx, vp);
+ JS::Value vobj = args.computeThis(cx);
+ JSObject* obj = vobj.toObjectOrNull();
+
int status = http_status(cx, obj);
if(status < 0)
@@ -169,8 +173,10 @@ static bool
base_url(JSContext *cx, unsigned int argc, JS::Value* vp)
{
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
- JSObject* obj = JS_THIS_OBJECT(cx, vp);
- couch_args *cargs = (couch_args*)JS_GetContextPrivate(cx);
+ JS::Value vobj = args.computeThis(cx);
+ JSObject* obj = vobj.toObjectOrNull();
+
+ couch_args *cargs = static_cast<couch_args*>(JS_GetContextPrivate(cx));
JS::Value uri_val;
bool rc = http_uri(cx, obj, cargs, &uri_val);
args.rval().set(uri_val);
@@ -226,9 +232,15 @@ evalcx(JSContext *cx, unsigned int argc, JS::Value* vp)
if (!sandbox)
return false;
}
- JS_BeginRequest(cx);
+
JSAutoRequest ar(cx);
+ if (!sandbox) {
+ sandbox = NewSandbox(cx, false);
+ if (!sandbox)
+ return false;
+ }
+
js::AutoStableStringChars strChars(cx);
if (!strChars.initTwoByte(cx, str))
return false;
@@ -237,12 +249,6 @@ evalcx(JSContext *cx, unsigned int argc, JS::Value* vp)
size_t srclen = chars.length();
const char16_t* src = chars.begin().get();
- if (!sandbox) {
- sandbox = NewSandbox(cx, false);
- if (!sandbox)
- return false;
- }
-
if(srclen == 0) {
args.rval().setObject(*sandbox);
} else {
@@ -283,7 +289,19 @@ static bool
print(JSContext* cx, unsigned int argc, JS::Value* vp)
{
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
- couch_print(cx, argc, args);
+
+ bool use_stderr = false;
+ if(argc > 1 && args[1].isTrue()) {
+ use_stderr = true;
+ }
+
+ if(!args[0].isString()) {
+ JS_ReportErrorUTF8(cx, "Unable to print non-string value.");
+ return false;
+ }
+
+ couch_print(cx, args[0], use_stderr);
+
args.rval().setUndefined();
return true;
}
@@ -386,7 +404,7 @@ static JSFunctionSpec global_functions[] = {
static bool
csp_allows(JSContext* cx)
{
- couch_args *args = (couch_args*)JS_GetContextPrivate(cx);
+ couch_args* args = static_cast<couch_args*>(JS_GetContextPrivate(cx));
if(args->eval) {
return true;
} else {
@@ -473,10 +491,18 @@ main(int argc, const char* argv[])
// Compile and run
JS::CompileOptions options(cx);
options.setFileAndLine(args->scripts[i], 1);
+ options.setUTF8(true);
JS::RootedScript script(cx);
if(!JS_CompileScript(cx, scriptsrc, slen, options, &script)) {
- fprintf(stderr, "Failed to compile script.\n");
+ JS::RootedValue exc(cx);
+ if(!JS_GetPendingException(cx, &exc)) {
+ fprintf(stderr, "Failed to compile script.\n");
+ } else {
+ JS::RootedObject exc_obj(cx, &exc.toObject());
+ JSErrorReport* report = JS_ErrorFromException(cx, exc_obj);
+ couch_error(cx, report);
+ }
return 1;
}
@@ -484,7 +510,14 @@ main(int argc, const char* argv[])
JS::RootedValue result(cx);
if(JS_ExecuteScript(cx, script, &result) != true) {
- fprintf(stderr, "Failed to execute script.\n");
+ JS::RootedValue exc(cx);
+ if(!JS_GetPendingException(cx, &exc)) {
+ fprintf(stderr, "Failed to execute script.\n");
+ } else {
+ JS::RootedObject exc_obj(cx, &exc.toObject());
+ JSErrorReport* report = JS_ErrorFromException(cx, exc_obj);
+ couch_error(cx, report);
+ }
return 1;
}
diff --git a/src/couch/priv/couch_js/60/utf8.cpp b/src/couch/priv/couch_js/60/utf8.cpp
deleted file mode 100644
index 38dfa6224..000000000
--- a/src/couch/priv/couch_js/60/utf8.cpp
+++ /dev/null
@@ -1,301 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not
-// use this file except in compliance with the License. You may obtain a copy of
-// the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations under
-// the License.
-
-#include <jsapi.h>
-#include <js/Initialization.h>
-#include <js/Conversions.h>
-#include <js/Wrapper.h>
-#include "config.h"
-#include "util.h"
-
-static int
-enc_char(uint8_t *utf8Buffer, uint32_t ucs4Char)
-{
- int utf8Length = 1;
-
- if (ucs4Char < 0x80)
- {
- *utf8Buffer = (uint8_t)ucs4Char;
- }
- else
- {
- int i;
- uint32_t a = ucs4Char >> 11;
- utf8Length = 2;
- while(a)
- {
- a >>= 5;
- utf8Length++;
- }
- i = utf8Length;
- while(--i)
- {
- utf8Buffer[i] = (uint8_t)((ucs4Char & 0x3F) | 0x80);
- ucs4Char >>= 6;
- }
- *utf8Buffer = (uint8_t)(0x100 - (1 << (8-utf8Length)) + ucs4Char);
- }
-
- return utf8Length;
-}
-
-static bool
-enc_charbuf(const char16_t* src, size_t srclen, char* dst, size_t* dstlenp)
-{
- size_t i;
- size_t utf8Len;
- size_t dstlen = *dstlenp;
- size_t origDstlen = dstlen;
- char16_t c;
- char16_t c2;
- uint32_t v;
- uint8_t utf8buf[6];
-
- if(!dst)
- {
- dstlen = origDstlen = (size_t) -1;
- }
-
- while(srclen)
- {
- c = *src++;
- srclen--;
-
- if(c <= 0xD7FF || c >= 0xE000)
- {
- v = (uint32_t) c;
- }
- else if(c >= 0xD800 && c <= 0xDBFF)
- {
- if(srclen < 1) goto buffer_too_small;
- c2 = *src++;
- srclen--;
- if(c2 >= 0xDC00 && c2 <= 0xDFFF)
- {
- v = (uint32_t) (((c - 0xD800) << 10) + (c2 - 0xDC00) + 0x10000);
- }
- else
- {
- // Invalid second half of surrogate pair
- v = (uint32_t) 0xFFFD;
- // Undo our character advancement
- src--;
- srclen++;
- }
- }
- else
- {
- // Invalid first half surrogate pair
- v = (uint32_t) 0xFFFD;
- }
-
- if(v < 0x0080)
- {
- /* no encoding necessary - performance hack */
- if(!dstlen) goto buffer_too_small;
- if(dst) *dst++ = (char) v;
- utf8Len = 1;
- }
- else
- {
- utf8Len = enc_char(utf8buf, v);
- if(utf8Len > dstlen) goto buffer_too_small;
- if(dst)
- {
- for (i = 0; i < utf8Len; i++)
- {
- *dst++ = (char) utf8buf[i];
- }
- }
- }
- dstlen -= utf8Len;
- }
-
- *dstlenp = (origDstlen - dstlen);
- return true;
-
-buffer_too_small:
- *dstlenp = (origDstlen - dstlen);
- return false;
-}
-
-char*
-enc_string(JSContext* cx, JS::Value arg, size_t* buflen)
-{
- JSString* str = NULL;
- const char16_t* src = NULL;
- char* bytes = NULL;
- size_t srclen = 0;
- size_t byteslen = 0;
- js::AutoStableStringChars rawChars(cx);
-
- str = arg.toString();
- if(!str) goto error;
-
- if (!rawChars.initTwoByte(cx, str))
- return NULL;
-
- src = rawChars.twoByteRange().begin().get();
- srclen = JS_GetStringLength(str);
-
- if(!enc_charbuf(src, srclen, NULL, &byteslen)) goto error;
-
- bytes = (char *)JS_malloc(cx, (byteslen) + 1);
- bytes[byteslen] = 0;
-
- if(!enc_charbuf(src, srclen, bytes, &byteslen)) goto error;
-
- if(buflen) *buflen = byteslen;
- goto success;
-
-error:
- if(bytes != NULL) JS_free(cx, bytes);
- bytes = NULL;
-
-success:
- return bytes;
-}
-
-static uint32_t
-dec_char(const uint8_t *utf8Buffer, int utf8Length)
-{
- uint32_t ucs4Char;
- uint32_t minucs4Char;
-
- /* from Unicode 3.1, non-shortest form is illegal */
- static const uint32_t minucs4Table[] = {
- 0x00000080, 0x00000800, 0x0001000, 0x0020000, 0x0400000
- };
-
- if (utf8Length == 1)
- {
- ucs4Char = *utf8Buffer;
- }
- else
- {
- ucs4Char = *utf8Buffer++ & ((1<<(7-utf8Length))-1);
- minucs4Char = minucs4Table[utf8Length-2];
- while(--utf8Length)
- {
- ucs4Char = ucs4Char<<6 | (*utf8Buffer++ & 0x3F);
- }
- if(ucs4Char < minucs4Char || ucs4Char == 0xFFFE || ucs4Char == 0xFFFF)
- {
- ucs4Char = 0xFFFD;
- }
- }
-
- return ucs4Char;
-}
-
-static bool
-dec_charbuf(const char *src, size_t srclen, char16_t *dst, size_t *dstlenp)
-{
- uint32_t v;
- size_t offset = 0;
- size_t j;
- size_t n;
- size_t dstlen = *dstlenp;
- size_t origDstlen = dstlen;
-
- if(!dst) dstlen = origDstlen = (size_t) -1;
-
- while(srclen)
- {
- v = (uint8_t) *src;
- n = 1;
-
- if(v & 0x80)
- {
- while(v & (0x80 >> n))
- {
- n++;
- }
-
- if(n > srclen) goto buffer_too_small;
- if(n == 1 || n > 6) goto bad_character;
-
- for(j = 1; j < n; j++)
- {
- if((src[j] & 0xC0) != 0x80) goto bad_character;
- }
-
- v = dec_char((const uint8_t *) src, n);
- if(v >= 0x10000)
- {
- v -= 0x10000;
-
- if(v > 0xFFFFF || dstlen < 2)
- {
- *dstlenp = (origDstlen - dstlen);
- return false;
- }
-
- if(dstlen < 2) goto buffer_too_small;
-
- if(dst)
- {
- *dst++ = (char16_t)((v >> 10) + 0xD800);
- v = (char16_t)((v & 0x3FF) + 0xDC00);
- }
- dstlen--;
- }
- }
-
- if(!dstlen) goto buffer_too_small;
- if(dst) *dst++ = (char16_t) v;
-
- dstlen--;
- offset += n;
- src += n;
- srclen -= n;
- }
-
- *dstlenp = (origDstlen - dstlen);
- return true;
-
-bad_character:
- *dstlenp = (origDstlen - dstlen);
- return false;
-
-buffer_too_small:
- *dstlenp = (origDstlen - dstlen);
- return false;
-}
-
-JSString*
-dec_string(JSContext* cx, const char* bytes, size_t byteslen)
-{
- JSString* str = NULL;
- char16_t* chars = NULL;
- size_t charslen;
-
- if(!dec_charbuf(bytes, byteslen, NULL, &charslen)) goto error;
-
- chars = (char16_t *)JS_malloc(cx, (charslen + 1) * sizeof(char16_t));
- if(!chars) return NULL;
- chars[charslen] = 0;
-
- if(!dec_charbuf(bytes, byteslen, chars, &charslen)) goto error;
-
- str = JS_NewUCString(cx, chars, charslen - 1);
- if(!str) goto error;
-
- goto success;
-
-error:
- if(chars != NULL) JS_free(cx, chars);
- str = NULL;
-
-success:
- return str;
-}
diff --git a/src/couch/priv/couch_js/60/utf8.h b/src/couch/priv/couch_js/60/utf8.h
deleted file mode 100644
index c8b1f4d82..000000000
--- a/src/couch/priv/couch_js/60/utf8.h
+++ /dev/null
@@ -1,19 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not
-// use this file except in compliance with the License. You may obtain a copy of
-// the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations under
-// the License.
-
-#ifndef COUCH_JS_UTF_8_H
-#define COUCH_JS_UTF_8_H
-
-char* enc_string(JSContext* cx, JS::Value arg, size_t* buflen);
-JSString* dec_string(JSContext* cx, const char* buf, size_t buflen);
-
-#endif
diff --git a/src/couch/priv/couch_js/60/util.cpp b/src/couch/priv/couch_js/60/util.cpp
index 92c6cbf4a..c37c41f2f 100644
--- a/src/couch/priv/couch_js/60/util.cpp
+++ b/src/couch/priv/couch_js/60/util.cpp
@@ -13,53 +13,76 @@
#include <stdlib.h>
#include <string.h>
+#include <sstream>
+
#include <jsapi.h>
#include <js/Initialization.h>
+#include <js/CharacterEncoding.h>
#include <js/Conversions.h>
+#include <mozilla/Unused.h>
#include "help.h"
#include "util.h"
-#include "utf8.h"
std::string
js_to_string(JSContext* cx, JS::HandleValue val)
{
+ JS::AutoSaveExceptionState exc_state(cx);
JS::RootedString sval(cx);
sval = val.toString();
JS::UniqueChars chars(JS_EncodeStringToUTF8(cx, sval));
if(!chars) {
JS_ClearPendingException(cx);
- fprintf(stderr, "Error converting value to string.\n");
- exit(3);
+ return std::string();
}
return chars.get();
}
-std::string
-js_to_string(JSContext* cx, JSString *str)
+bool
+js_to_string(JSContext* cx, JS::HandleValue val, std::string& str)
{
- JS::UniqueChars chars(JS_EncodeString(cx, str));
- if(!chars) {
- JS_ClearPendingException(cx);
- fprintf(stderr, "Error converting to string.\n");
- exit(3);
+ if(!val.isString()) {
+ return false;
}
- return chars.get();
+ if(JS_GetStringLength(val.toString()) == 0) {
+ str = "";
+ return true;
+ }
+
+ std::string conv = js_to_string(cx, val);
+ if(!conv.size()) {
+ return false;
+ }
+
+ str = conv;
+ return true;
}
JSString*
-string_to_js(JSContext* cx, const std::string& s)
+string_to_js(JSContext* cx, const std::string& raw)
{
- JSString* ret = JS_NewStringCopyN(cx, s.c_str(), s.size());
- if(ret != nullptr) {
- return ret;
+ JS::UTF8Chars utf8(raw.c_str(), raw.size());
+ JS::UniqueTwoByteChars utf16;
+ size_t len;
+
+ utf16.reset(JS::UTF8CharsToNewTwoByteCharsZ(cx, utf8, &len).get());
+ if(!utf16) {
+ return nullptr;
+ }
+
+ JSString* ret = JS_NewUCString(cx, utf16.get(), len);
+
+ if(ret) {
+ // JS_NewUCString took ownership on success. We shift
+ // the resulting pointer into Unused to silence the
+ // compiler warning.
+ mozilla::Unused << utf16.release();
}
- fprintf(stderr, "Unable to allocate string object.\n");
- exit(3);
+ return ret;
}
size_t
@@ -84,21 +107,21 @@ couch_readfile(const char* file, char** outbuf_p)
while((nread = fread(fbuf, 1, 16384, fp)) > 0) {
if(buf == NULL) {
- buf = (char*) malloc(nread + 1);
+ buf = new char[nread + 1];
if(buf == NULL) {
fprintf(stderr, "Out of memory.\n");
exit(3);
}
memcpy(buf, fbuf, nread);
} else {
- tmp = (char*) malloc(buflen + nread + 1);
+ tmp = new char[buflen + nread + 1];
if(tmp == NULL) {
fprintf(stderr, "Out of memory.\n");
exit(3);
}
memcpy(tmp, buf, buflen);
memcpy(tmp+buflen, fbuf, nread);
- free(buf);
+ delete buf;
buf = tmp;
}
buflen += nread;
@@ -114,12 +137,17 @@ couch_parse_args(int argc, const char* argv[])
couch_args* args;
int i = 1;
- args = (couch_args*) malloc(sizeof(couch_args));
+ args = new couch_args();
if(args == NULL)
return NULL;
- memset(args, '\0', sizeof(couch_args));
+ args->eval = 0;
+ args->use_http = 0;
+ args->use_test_funs = 0;
args->stack_size = 64L * 1024L * 1024L;
+ args->scripts = nullptr;
+ args->uri_file = nullptr;
+ args->uri = nullptr;
while(i < argc) {
if(strcmp("-h", argv[i]) == 0) {
@@ -193,7 +221,7 @@ couch_readline(JSContext* cx, FILE* fp)
size_t oldbyteslen = 256;
size_t readlen = 0;
- bytes = (char *)JS_malloc(cx, byteslen);
+ bytes = static_cast<char*>(JS_malloc(cx, byteslen));
if(bytes == NULL) return NULL;
while((readlen = couch_fgets(bytes+used, byteslen-used, fp)) > 0) {
@@ -207,7 +235,7 @@ couch_readline(JSContext* cx, FILE* fp)
// Double our buffer and read more.
oldbyteslen = byteslen;
byteslen *= 2;
- tmp = (char *)JS_realloc(cx, bytes, oldbyteslen, byteslen);
+ tmp = static_cast<char*>(JS_realloc(cx, bytes, oldbyteslen, byteslen));
if(!tmp) {
JS_free(cx, bytes);
return NULL;
@@ -222,8 +250,8 @@ couch_readline(JSContext* cx, FILE* fp)
return JS_NewStringCopyZ(cx, nullptr);
}
- // Shring the buffer to the actual data size
- tmp = (char *)JS_realloc(cx, bytes, byteslen, used);
+ // Shrink the buffer to the actual data size
+ tmp = static_cast<char*>(JS_realloc(cx, bytes, byteslen, used));
if(!tmp) {
JS_free(cx, bytes);
return NULL;
@@ -238,22 +266,16 @@ couch_readline(JSContext* cx, FILE* fp)
void
-couch_print(JSContext* cx, unsigned int argc, JS::CallArgs argv)
+couch_print(JSContext* cx, JS::HandleValue obj, bool use_stderr)
{
- uint8_t* bytes = nullptr;
- FILE *stream = stdout;
+ FILE* stream = stdout;
- if (argc) {
- if (argc > 1 && argv[1].isTrue()) {
- stream = stderr;
- }
- JSString* str = JS::ToString(cx, argv.get(0));
- bytes = reinterpret_cast<uint8_t*>(JS_EncodeString(cx, str));
- fprintf(stream, "%s", bytes);
- JS_free(cx, bytes);
+ if(use_stderr) {
+ stream = stderr;
}
- fputc('\n', stream);
+ std::string val = js_to_string(cx, obj);
+ fprintf(stream, "%s\n", val.c_str());
fflush(stream);
}
@@ -261,51 +283,63 @@ couch_print(JSContext* cx, unsigned int argc, JS::CallArgs argv)
void
couch_error(JSContext* cx, JSErrorReport* report)
{
- JS::RootedValue v(cx), stack(cx), replace(cx);
- char* bytes;
- JSObject* regexp;
-
- if(!report || !JSREPORT_IS_WARNING(report->flags))
- {
- fprintf(stderr, "%s\n", report->message().c_str());
-
- // Print a stack trace, if available.
- if (JSREPORT_IS_EXCEPTION(report->flags) &&
- JS_GetPendingException(cx, &v))
- {
- // Clear the exception before an JS method calls or the result is
- // infinite, recursive error report generation.
- JS_ClearPendingException(cx);
-
- // Use JS regexp to indent the stack trace.
- // If the regexp can't be created, don't JS_ReportErrorUTF8 since it is
- // probably not productive to wind up here again.
- JS::RootedObject vobj(cx, v.toObjectOrNull());
-
- if(JS_GetProperty(cx, vobj, "stack", &stack) &&
- (regexp = JS_NewRegExpObject(
- cx, "^(?=.)", 6, JSREG_GLOB | JSREG_MULTILINE)))
- {
- // Set up the arguments to ``String.replace()``
- JS::AutoValueVector re_args(cx);
- JS::RootedValue arg0(cx, JS::ObjectValue(*regexp));
- auto arg1 = JS::StringValue(string_to_js(cx, "\t"));
-
- if (re_args.append(arg0) && re_args.append(arg1)) {
- // Perform the replacement
- JS::RootedObject sobj(cx, stack.toObjectOrNull());
- if(JS_GetProperty(cx, sobj, "replace", &replace) &&
- JS_CallFunctionValue(cx, sobj, replace, re_args, &v))
- {
- // Print the result
- bytes = enc_string(cx, v, NULL);
- fprintf(stderr, "Stacktrace:\n%s", bytes);
- JS_free(cx, bytes);
- }
- }
- }
+ if(!report) {
+ return;
+ }
+
+ if(JSREPORT_IS_WARNING(report->flags)) {
+ return;
+ }
+
+ std::ostringstream msg;
+ msg << "error: " << report->message().c_str();
+
+ mozilla::Maybe<JSAutoCompartment> ac;
+ JS::RootedValue exc(cx);
+ JS::RootedObject exc_obj(cx);
+ JS::RootedObject stack_obj(cx);
+ JS::RootedString stack_str(cx);
+ JS::RootedValue stack_val(cx);
+
+ if(!JS_GetPendingException(cx, &exc)) {
+ goto done;
+ }
+
+ // Clear the exception before an JS method calls or the result is
+ // infinite, recursive error report generation.
+ JS_ClearPendingException(cx);
+
+ exc_obj.set(exc.toObjectOrNull());
+ stack_obj.set(JS::ExceptionStackOrNull(exc_obj));
+
+ if(!stack_obj) {
+ // Compilation errors don't have a stack
+
+ msg << " at ";
+
+ if(report->filename) {
+ msg << report->filename;
+ } else {
+ msg << "<unknown>";
+ }
+
+ if(report->lineno) {
+ msg << ':' << report->lineno << ':' << report->column;
}
+
+ goto done;
+ }
+
+ if(!JS::BuildStackString(cx, stack_obj, &stack_str, 2)) {
+ goto done;
}
+
+ stack_val.set(JS::StringValue(stack_str));
+ msg << std::endl << std::endl << js_to_string(cx, stack_val).c_str();
+
+done:
+ msg << std::endl;
+ fprintf(stderr, "%s", msg.str().c_str());
}
diff --git a/src/couch/priv/couch_js/60/util.h b/src/couch/priv/couch_js/60/util.h
index 407e3e602..4c27f0f66 100644
--- a/src/couch/priv/couch_js/60/util.h
+++ b/src/couch/priv/couch_js/60/util.h
@@ -26,14 +26,14 @@ typedef struct {
} couch_args;
std::string js_to_string(JSContext* cx, JS::HandleValue val);
-std::string js_to_string(JSContext* cx, JSString *str);
+bool js_to_string(JSContext* cx, JS::HandleValue val, std::string& str);
JSString* string_to_js(JSContext* cx, const std::string& s);
couch_args* couch_parse_args(int argc, const char* argv[]);
int couch_fgets(char* buf, int size, FILE* fp);
JSString* couch_readline(JSContext* cx, FILE* fp);
size_t couch_readfile(const char* file, char** outbuf_p);
-void couch_print(JSContext* cx, unsigned int argc, JS::CallArgs argv);
+void couch_print(JSContext* cx, JS::HandleValue str, bool use_stderr);
void couch_error(JSContext* cx, JSErrorReport* report);
void couch_oom(JSContext* cx, void* data);
bool couch_load_funcs(JSContext* cx, JS::HandleObject obj, JSFunctionSpec* funcs);
diff --git a/src/couch/test/eunit/couch_js_tests.erl b/src/couch/test/eunit/couch_js_tests.erl
index cd6452cf9..c2c62463b 100644
--- a/src/couch/test/eunit/couch_js_tests.erl
+++ b/src/couch/test/eunit/couch_js_tests.erl
@@ -14,17 +14,6 @@
-include_lib("eunit/include/eunit.hrl").
--define(FUNC, <<
- "var state = [];\n"
- "function(doc) {\n"
- " var val = \"0123456789ABCDEF\";\n"
- " for(var i = 0; i < 165535; i++) {\n"
- " state.push([val, val]);\n"
- " }\n"
- "}\n"
->>).
-
-
couch_js_test_() ->
{
"Test couchjs",
@@ -33,15 +22,142 @@ couch_js_test_() ->
fun test_util:start_couch/0,
fun test_util:stop_couch/1,
[
+ fun should_create_sandbox/0,
+ fun should_roundtrip_utf8/0,
+ fun should_roundtrip_modified_utf8/0,
+ fun should_replace_broken_utf16/0,
+ fun should_allow_js_string_mutations/0,
{timeout, 60000, fun should_exit_on_oom/0}
]
}
}.
+should_create_sandbox() ->
+ % Try and detect whether we can see out of the
+ % sandbox or not.
+ Src = <<
+ "function(doc) {\n"
+ " try {\n"
+ " emit(false, typeof(Couch.compile_function));\n"
+ " } catch (e) {\n"
+ " emit(true, e.message);\n"
+ " }\n"
+ "}\n"
+ >>,
+ Proc = couch_query_servers:get_os_process(<<"javascript">>),
+ true = couch_query_servers:proc_prompt(Proc, [<<"add_fun">>, Src]),
+ Result = couch_query_servers:proc_prompt(Proc, [<<"map_doc">>, <<"{}">>]),
+ ?assertEqual([[[true, <<"Couch is not defined">>]]], Result).
+
+
+should_roundtrip_utf8() ->
+ % Try round tripping UTF-8 both directions through
+ % couchjs. These tests use hex encoded values of
+ % Ä (C384) and Ü (C39C) so as to avoid odd editor/Erlang encoding
+ % strangeness.
+ Src = <<
+ "function(doc) {\n"
+ " emit(doc.value, \"", 16#C3, 16#9C, "\");\n"
+ "}\n"
+ >>,
+ Proc = couch_query_servers:get_os_process(<<"javascript">>),
+ true = couch_query_servers:proc_prompt(Proc, [<<"add_fun">>, Src]),
+ Doc = {[
+ {<<"value">>, <<16#C3, 16#84>>}
+ ]},
+ Result = couch_query_servers:proc_prompt(Proc, [<<"map_doc">>, Doc]),
+ ?assertEqual([[[<<16#C3, 16#84>>, <<16#C3, 16#9C>>]]], Result).
+
+
+should_roundtrip_modified_utf8() ->
+ % Mimicing the test case from the mailing list
+ Src = <<
+ "function(doc) {\n"
+ " emit(doc.value.toLowerCase(), \"", 16#C3, 16#9C, "\");\n"
+ "}\n"
+ >>,
+ Proc = couch_query_servers:get_os_process(<<"javascript">>),
+ true = couch_query_servers:proc_prompt(Proc, [<<"add_fun">>, Src]),
+ Doc = {[
+ {<<"value">>, <<16#C3, 16#84>>}
+ ]},
+ Result = couch_query_servers:proc_prompt(Proc, [<<"map_doc">>, Doc]),
+ ?assertEqual([[[<<16#C3, 16#A4>>, <<16#C3, 16#9C>>]]], Result).
+
+
+should_replace_broken_utf16() ->
+ % This test reverse the surrogate pair of
+ % the Boom emoji U+1F4A5
+ Src = <<
+ "function(doc) {\n"
+ " emit(doc.value.split(\"\").reverse().join(\"\"), 1);\n"
+ "}\n"
+ >>,
+ Proc = couch_query_servers:get_os_process(<<"javascript">>),
+ true = couch_query_servers:proc_prompt(Proc, [<<"add_fun">>, Src]),
+ Doc = {[
+ {<<"value">>, list_to_binary(xmerl_ucs:to_utf8([16#1F4A5]))}
+ ]},
+ Result = couch_query_servers:proc_prompt(Proc, [<<"map_doc">>, Doc]),
+ % Invalid UTF-8 gets replaced with the 16#FFFD replacement
+ % marker
+ Markers = list_to_binary(xmerl_ucs:to_utf8([16#FFFD, 16#FFFD])),
+ ?assertEqual([[[Markers, 1]]], Result).
+
+
+should_allow_js_string_mutations() ->
+ % This binary corresponds to this string: мама мыла раму
+ % Which I'm told translates to: "mom was washing the frame"
+ MomWashedTheFrame = <<
+ 16#D0, 16#BC, 16#D0, 16#B0, 16#D0, 16#BC, 16#D0, 16#B0, 16#20,
+ 16#D0, 16#BC, 16#D1, 16#8B, 16#D0, 16#BB, 16#D0, 16#B0, 16#20,
+ 16#D1, 16#80, 16#D0, 16#B0, 16#D0, 16#BC, 16#D1, 16#83
+ >>,
+ Mom = <<16#D0, 16#BC, 16#D0, 16#B0, 16#D0, 16#BC, 16#D0, 16#B0>>,
+ Washed = <<16#D0, 16#BC, 16#D1, 16#8B, 16#D0, 16#BB, 16#D0, 16#B0>>,
+ Src1 = <<
+ "function(doc) {\n"
+ " emit(\"length\", doc.value.length);\n"
+ "}\n"
+ >>,
+ Src2 = <<
+ "function(doc) {\n"
+ " emit(\"substring\", doc.value.substring(5, 9));\n"
+ "}\n"
+ >>,
+ Src3 = <<
+ "function(doc) {\n"
+ " emit(\"slice\", doc.value.slice(0, 4));\n"
+ "}\n"
+ >>,
+ Proc = couch_query_servers:get_os_process(<<"javascript">>),
+ true = couch_query_servers:proc_prompt(Proc, [<<"add_fun">>, Src1]),
+ true = couch_query_servers:proc_prompt(Proc, [<<"add_fun">>, Src2]),
+ true = couch_query_servers:proc_prompt(Proc, [<<"add_fun">>, Src3]),
+ Doc = {[{<<"value">>, MomWashedTheFrame}]},
+ Result = couch_query_servers:proc_prompt(Proc, [<<"map_doc">>, Doc]),
+ io:format(standard_error, "~w~n~w~n", [MomWashedTheFrame, Result]),
+ Expect = [
+ [[<<"length">>, 14]],
+ [[<<"substring">>, Washed]],
+ [[<<"slice">>, Mom]]
+ ],
+ ?assertEqual(Expect, Result).
+
+
should_exit_on_oom() ->
+ Src = <<
+ "var state = [];\n"
+ "function(doc) {\n"
+ " var val = \"0123456789ABCDEF\";\n"
+ " for(var i = 0; i < 165535; i++) {\n"
+ " state.push([val, val]);\n"
+ " }\n"
+ "}\n"
+ >>,
Proc = couch_query_servers:get_os_process(<<"javascript">>),
- true = couch_query_servers:proc_prompt(Proc, [<<"add_fun">>, ?FUNC]),
+ true = couch_query_servers:proc_prompt(Proc, [<<"add_fun">>, Src]),
trigger_oom(Proc).
trigger_oom(Proc) ->