diff --git a/lib/_tls_wrap.js b/lib/_tls_wrap.js index b36b90c54ca233..96099388529c20 100644 --- a/lib/_tls_wrap.js +++ b/lib/_tls_wrap.js @@ -305,14 +305,31 @@ proxiedMethods.forEach(function(name) { }); tls_wrap.TLSWrap.prototype.close = function close(cb) { - if (this.owner) + let ssl; + if (this.owner) { + ssl = this.owner.ssl; this.owner.ssl = null; + } + + // Invoke `destroySSL` on close to clean up possibly pending write requests + // that may self-reference TLSWrap, leading to leak + const done = () => { + if (ssl) { + ssl.destroySSL(); + if (ssl._secureContext.singleUse) { + ssl._secureContext.context.close(); + ssl._secureContext.context = null; + } + } + if (cb) + cb(); + }; if (this._parentWrap && this._parentWrap._handle === this._parent) { - this._parentWrap.once('close', cb); + this._parentWrap.once('close', done); return this._parentWrap.destroy(); } - return this._parent.close(cb); + return this._parent.close(done); }; TLSSocket.prototype._wrapHandle = function(wrap) { diff --git a/src/inspector_agent.cc b/src/inspector_agent.cc index 3f3d548de4cdf6..cb20bd702f37d1 100644 --- a/src/inspector_agent.cc +++ b/src/inspector_agent.cc @@ -170,13 +170,6 @@ std::string GenerateID() { buffer[7]); return uuid; } - -// std::to_string is not available on Smart OS and ARM flavours -const std::string to_string(uint64_t number) { - std::ostringstream result; - result << number; - return result.str(); -} } // namespace diff --git a/src/node_javascript.cc b/src/node_javascript.cc index 6f445f76df2407..3f6d6c82a85269 100644 --- a/src/node_javascript.cc +++ b/src/node_javascript.cc @@ -6,33 +6,46 @@ namespace node { -using v8::HandleScope; using v8::Local; using v8::NewStringType; using v8::Object; using v8::String; +// id##_data is defined in node_natives.h. +#define V(id) \ + static struct : public String::ExternalOneByteStringResource { \ + const char* data() const override { \ + return reinterpret_cast(id##_data); \ + } \ + size_t length() const override { return sizeof(id##_data); } \ + void Dispose() override { /* Default calls `delete this`. */ } \ + } id##_external_data; +NODE_NATIVES_MAP(V) +#undef V + Local MainSource(Environment* env) { - return String::NewFromUtf8( - env->isolate(), - reinterpret_cast(internal_bootstrap_node_native), - NewStringType::kNormal, - sizeof(internal_bootstrap_node_native)).ToLocalChecked(); + auto maybe_string = + String::NewExternalOneByte( + env->isolate(), + &internal_bootstrap_node_external_data); + return maybe_string.ToLocalChecked(); } void DefineJavaScript(Environment* env, Local target) { - HandleScope scope(env->isolate()); - - for (auto native : natives) { - if (native.source != internal_bootstrap_node_native) { - Local name = String::NewFromUtf8(env->isolate(), native.name); - Local source = - String::NewFromUtf8( - env->isolate(), reinterpret_cast(native.source), - NewStringType::kNormal, native.source_len).ToLocalChecked(); - target->Set(name, source); - } - } + auto context = env->context(); +#define V(id) \ + do { \ + auto key = \ + String::NewFromOneByte( \ + env->isolate(), id##_name, NewStringType::kNormal, \ + sizeof(id##_name)).ToLocalChecked(); \ + auto value = \ + String::NewExternalOneByte( \ + env->isolate(), &id##_external_data).ToLocalChecked(); \ + CHECK(target->Set(context, key, value).FromJust()); \ + } while (0); + NODE_NATIVES_MAP(V) +#undef V } } // namespace node diff --git a/test/parallel/test-child-process-exec-cwd.js b/test/parallel/test-child-process-exec-cwd.js index b1a24aca761458..3b6e428a4aca65 100644 --- a/test/parallel/test-child-process-exec-cwd.js +++ b/test/parallel/test-child-process-exec-cwd.js @@ -1,7 +1,7 @@ 'use strict'; const common = require('../common'); -var assert = require('assert'); -var exec = require('child_process').exec; +const assert = require('assert'); +const exec = require('child_process').exec; var pwdcommand, dir; @@ -15,5 +15,5 @@ if (common.isWindows) { exec(pwdcommand, {cwd: dir}, common.mustCall(function(err, stdout, stderr) { assert.ifError(err); - assert.ok(stdout.indexOf(dir) == 0); + assert.strictEqual(stdout.indexOf(dir), 0); })); diff --git a/test/parallel/test-child-process-exec-timeout.js b/test/parallel/test-child-process-exec-timeout.js new file mode 100644 index 00000000000000..25aba6de2780e8 --- /dev/null +++ b/test/parallel/test-child-process-exec-timeout.js @@ -0,0 +1,45 @@ +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const cp = require('child_process'); + +if (process.argv[2] === 'child') { + setTimeout(() => { + // The following console statements are part of the test. + console.log('child stdout'); + console.error('child stderr'); + }, common.platformTimeout(1000)); + return; +} + +const cmd = `${process.execPath} ${__filename} child`; + +// Test the case where a timeout is set, and it expires. +cp.exec(cmd, { timeout: 1 }, common.mustCall((err, stdout, stderr) => { + assert.strictEqual(err.killed, true); + assert.strictEqual(err.code, null); + assert.strictEqual(err.signal, 'SIGTERM'); + assert.strictEqual(err.cmd, cmd); + assert.strictEqual(stdout.trim(), ''); + assert.strictEqual(stderr.trim(), ''); +})); + +// Test with a different kill signal. +cp.exec(cmd, { + timeout: 1, + killSignal: 'SIGKILL' +}, common.mustCall((err, stdout, stderr) => { + assert.strictEqual(err.killed, true); + assert.strictEqual(err.code, null); + assert.strictEqual(err.signal, 'SIGKILL'); + assert.strictEqual(err.cmd, cmd); + assert.strictEqual(stdout.trim(), ''); + assert.strictEqual(stderr.trim(), ''); +})); + +// Test the case where a timeout is set, but not expired. +cp.exec(cmd, { timeout: Math.pow(2, 30) }, common.mustCall((err, stdout, stderr) => { + assert.ifError(err); + assert.strictEqual(stdout.trim(), 'child stdout'); + assert.strictEqual(stderr.trim(), 'child stderr'); +})); diff --git a/test/parallel/test-tls-writewrap-leak.js b/test/parallel/test-tls-writewrap-leak.js new file mode 100644 index 00000000000000..cc55192229531d --- /dev/null +++ b/test/parallel/test-tls-writewrap-leak.js @@ -0,0 +1,26 @@ +'use strict'; +const common = require('../common'); + +if (!common.hasCrypto) { + common.skip('missing crypto'); + return; +} + +const assert = require('assert'); +const net = require('net'); +const tls = require('tls'); + +const server = net.createServer(common.mustCall((c) => { + c.destroy(); +})).listen(0, common.mustCall(() => { + const c = tls.connect({ port: server.address().port }); + c.on('error', () => { + // Otherwise `.write()` callback won't be invoked. + c.destroyed = false; + }); + + c.write('hello', common.mustCall((err) => { + assert.equal(err.code, 'ECANCELED'); + server.close(); + })); +})); diff --git a/tools/js2c.py b/tools/js2c.py index 40c30cc0325f61..4808c56813ce17 100755 --- a/tools/js2c.py +++ b/tools/js2c.py @@ -37,8 +37,11 @@ import string -def ToCArray(filename, lines): - return ','.join(str(ord(c)) for c in lines) +def ToCString(contents): + step = 20 + slices = (contents[i:i+step] for i in xrange(0, len(contents), step)) + slices = map(lambda s: ','.join(str(ord(c)) for c in s), slices) + return ',\n'.join(slices) def ReadFile(filename): @@ -61,21 +64,6 @@ def ReadLines(filename): return result -def LoadConfigFrom(name): - import ConfigParser - config = ConfigParser.ConfigParser() - config.read(name) - return config - - -def ParseValue(string): - string = string.strip() - if string.startswith('[') and string.endswith(']'): - return string.lstrip('[').rstrip(']').split() - else: - return string - - def ExpandConstants(lines, constants): for key, value in constants.items(): lines = lines.replace(key, str(value)) @@ -174,53 +162,37 @@ def ReadMacros(lines): HEADER_TEMPLATE = """\ -#ifndef node_natives_h -#define node_natives_h -namespace node { - -%(source_lines)s\ +#ifndef NODE_NATIVES_H_ +#define NODE_NATIVES_H_ -struct _native { - const char* name; - const unsigned char* source; - size_t source_len; -}; +#include -static const struct _native natives[] = { %(native_lines)s }; +#define NODE_NATIVES_MAP(V) \\ +{node_natives_map} -} -#endif -""" - - -NATIVE_DECLARATION = """\ - { "%(id)s", %(escaped_id)s_native, sizeof(%(escaped_id)s_native) }, -""" +namespace node {{ +{sources} +}} // namespace node -SOURCE_DECLARATION = """\ - const unsigned char %(escaped_id)s_native[] = { %(data)s }; +#endif // NODE_NATIVES_H_ """ -GET_DELAY_INDEX_CASE = """\ - if (strcmp(name, "%(id)s") == 0) return %(i)i; +NODE_NATIVES_MAP = """\ + V({escaped_id}) \\ """ -GET_DELAY_SCRIPT_SOURCE_CASE = """\ - if (index == %(i)i) return Vector(%(id)s, %(length)i); +SOURCES = """\ +static const uint8_t {escaped_id}_name[] = {{ +{name}}}; +static const uint8_t {escaped_id}_data[] = {{ +{data}}}; """ -GET_DELAY_SCRIPT_NAME_CASE = """\ - if (index == %(i)i) return Vector("%(name)s", %(length)i); -""" - def JS2C(source, target): - ids = [] - delay_ids = [] modules = [] - # Locate the macros file name. consts = {} macros = {} macro_lines = [] @@ -235,18 +207,14 @@ def JS2C(source, target): (consts, macros) = ReadMacros(macro_lines) # Build source code lines - source_lines = [ ] - source_lines_empty = [] - - native_lines = [] + node_natives_map = [] + sources = [] for s in modules: - delay = str(s).endswith('-delay.js') lines = ReadFile(str(s)) - lines = ExpandConstants(lines, consts) lines = ExpandMacros(lines, macros) - data = ToCArray(s, lines) + data = ToCString(lines) # On Windows, "./foo.bar" in the .gyp file is passed as "foo.bar" # so don't assume there is always a slash in the file path. @@ -258,89 +226,19 @@ def JS2C(source, target): if '.' in id: id = id.split('.', 1)[0] - if delay: id = id[:-6] - if delay: - delay_ids.append((id, len(lines))) - else: - ids.append((id, len(lines))) - + name = ToCString(id) escaped_id = id.replace('-', '_').replace('/', '_') - source_lines.append(SOURCE_DECLARATION % { - 'id': id, - 'escaped_id': escaped_id, - 'data': data - }) - source_lines_empty.append(SOURCE_DECLARATION % { - 'id': id, - 'escaped_id': escaped_id, - 'data': 0 - }) - native_lines.append(NATIVE_DECLARATION % { - 'id': id, - 'escaped_id': escaped_id - }) - - # Build delay support functions - get_index_cases = [ ] - get_script_source_cases = [ ] - get_script_name_cases = [ ] - - i = 0 - for (id, length) in delay_ids: - native_name = "native %s.js" % id - get_index_cases.append(GET_DELAY_INDEX_CASE % { 'id': id, 'i': i }) - get_script_source_cases.append(GET_DELAY_SCRIPT_SOURCE_CASE % { - 'id': id, - 'length': length, - 'i': i - }) - get_script_name_cases.append(GET_DELAY_SCRIPT_NAME_CASE % { - 'name': native_name, - 'length': len(native_name), - 'i': i - }); - i = i + 1 - - for (id, length) in ids: - native_name = "native %s.js" % id - get_index_cases.append(GET_DELAY_INDEX_CASE % { 'id': id, 'i': i }) - get_script_source_cases.append(GET_DELAY_SCRIPT_SOURCE_CASE % { - 'id': id, - 'length': length, - 'i': i - }) - get_script_name_cases.append(GET_DELAY_SCRIPT_NAME_CASE % { - 'name': native_name, - 'length': len(native_name), - 'i': i - }); - i = i + 1 + node_natives_map.append(NODE_NATIVES_MAP.format(**locals())) + sources.append(SOURCES.format(**locals())) + + node_natives_map = ''.join(node_natives_map) + sources = ''.join(sources) # Emit result output = open(str(target[0]), "w") - output.write(HEADER_TEMPLATE % { - 'builtin_count': len(ids) + len(delay_ids), - 'delay_count': len(delay_ids), - 'source_lines': "\n".join(source_lines), - 'native_lines': "\n".join(native_lines), - 'get_index_cases': "".join(get_index_cases), - 'get_script_source_cases': "".join(get_script_source_cases), - 'get_script_name_cases': "".join(get_script_name_cases) - }) + output.write(HEADER_TEMPLATE.format(**locals())) output.close() - if len(target) > 1: - output = open(str(target[1]), "w") - output.write(HEADER_TEMPLATE % { - 'builtin_count': len(ids) + len(delay_ids), - 'delay_count': len(delay_ids), - 'source_lines': "\n".join(source_lines_empty), - 'get_index_cases': "".join(get_index_cases), - 'get_script_source_cases': "".join(get_script_source_cases), - 'get_script_name_cases': "".join(get_script_name_cases) - }) - output.close() - def main(): natives = sys.argv[1] source_files = sys.argv[2:]