Skip to content
This repository was archived by the owner on Jun 18, 2021. It is now read-only.

Commit 10fa1f5

Browse files
committed
Fix version reporting in NodeReport section
Report the versions of Node.js and its components based on the runtime and not compile time constants. Report NodeReport version and the version of Node.js it was built against. Extend the tests to validate the versions reported in the report. Fixes: #29
1 parent 95a046e commit 10fa1f5

13 files changed

+269
-19
lines changed

binding.gyp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
"dll_files": [ "dbghelp.dll", "Netapi32.dll" ],
1515
}],
1616
],
17+
"defines": [
18+
'NODEREPORT_VERSION="<!(node -p \"require(\'./package.json\').version\")"'
19+
],
1720
},
1821
{
1922
"target_name": "install",

src/module.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,7 @@ void Initialize(v8::Local<v8::Object> exports) {
302302
node_isolate = isolate;
303303

304304
SetLoadTime();
305+
SetVersionString(isolate);
305306

306307
const char* verbose_switch = secure_getenv("NODEREPORT_VERBOSE");
307308
if (verbose_switch != nullptr) {

src/node_report.cc

Lines changed: 110 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
#include "node_report.h"
2-
#include "node_version.h"
32
#include "v8.h"
43
#include "time.h"
5-
#include "zlib.h"
6-
#include "ares.h"
74

85
#include <fcntl.h>
6+
#include <map>
97
#include <string.h>
108
#include <stdio.h>
119
#include <stdlib.h>
@@ -35,6 +33,14 @@
3533
#include <sys/utsname.h>
3634
#endif
3735

36+
#if !defined(NODEREPORT_VERSION)
37+
#define NODEREPORT_VERSION "dev"
38+
#endif
39+
40+
#if !defined(UNKNOWN_NODEVERSION_STRING)
41+
#define UNKNOWN_NODEVERSION_STRING "Unable to determine Node.js version\n"
42+
#endif
43+
3844
#ifndef _WIN32
3945
extern char** environ;
4046
#endif
@@ -52,7 +58,7 @@ using v8::String;
5258
using v8::V8;
5359

5460
// Internal/static function declarations
55-
static void PrintVersionInformation(FILE* fp);
61+
static void PrintVersionInformation(FILE* fp, Isolate* isolate);
5662
static void PrintJavaScriptStack(FILE* fp, Isolate* isolate, DumpEvent event, const char* location);
5763
static void PrintStackFromStackTrace(FILE* fp, Isolate* isolate, DumpEvent event);
5864
static void PrintStackFrame(FILE* fp, Isolate* isolate, Local<StackFrame> frame, int index, void* pc);
@@ -70,6 +76,7 @@ const char* v8_states[] = {"JS", "GC", "COMPILER", "OTHER", "EXTERNAL", "IDLE"};
7076
static bool report_active = false; // recursion protection
7177
static char report_filename[NR_MAXNAME + 1] = "";
7278
static char report_directory[NR_MAXPATH + 1] = ""; // defaults to current working directory
79+
static std::string version_string = UNKNOWN_NODEVERSION_STRING;
7380
#ifdef _WIN32
7481
static SYSTEMTIME loadtime_tm_struct; // module load time
7582
#else // UNIX, OSX
@@ -189,6 +196,97 @@ unsigned int ProcessNodeReportVerboseSwitch(const char* args) {
189196
return 0; // Default is verbose mode off
190197
}
191198

199+
void SetVersionString(Isolate* isolate) {
200+
// Catch anything thrown and gracefully return
201+
Nan::TryCatch trycatch;
202+
version_string = UNKNOWN_NODEVERSION_STRING;
203+
204+
// Retrieve the process object
205+
v8::Local<v8::String> process_prop;
206+
if (!Nan::New<v8::String>("process").ToLocal(&process_prop)) return;
207+
v8::Local<v8::Object> global_obj = isolate->GetCurrentContext()->Global();
208+
v8::Local<v8::Value> process_value;
209+
if (!Nan::Get(global_obj, process_prop).ToLocal(&process_value)) return;
210+
if (!process_value->IsObject()) return;
211+
v8::Local<v8::Object> process_obj = process_value.As<v8::Object>();
212+
213+
// Get process.version
214+
v8::Local<v8::String> version_prop;
215+
if (!Nan::New<v8::String>("version").ToLocal(&version_prop)) return;
216+
v8::Local<v8::Value> version;
217+
if (!Nan::Get(process_obj, version_prop).ToLocal(&version)) return;
218+
219+
// e.g. Node.js version: v6.9.1
220+
if (version->IsString()) {
221+
Nan::Utf8String node_version(version);
222+
version_string = "Node.js version: ";
223+
version_string += *node_version;
224+
version_string += "\n";
225+
}
226+
227+
// Get process.versions
228+
v8::Local<v8::String> versions_prop;
229+
if (!Nan::New<v8::String>("versions").ToLocal(&versions_prop)) return;
230+
v8::Local<v8::Value> versions_value;
231+
if (!Nan::Get(process_obj, versions_prop).ToLocal(&versions_value)) return;
232+
if (!versions_value->IsObject()) return;
233+
v8::Local<v8::Object> versions_obj = versions_value.As<v8::Object>();
234+
235+
// Get component names and versions from process.versions
236+
v8::Local<v8::Array> components;
237+
if (!Nan::GetOwnPropertyNames(versions_obj).ToLocal(&components)) return;
238+
v8::Local<v8::Object> components_obj = components.As<v8::Object>();
239+
std::map<std::string, std::string> comp_versions;
240+
uint32_t total_components = (*components)->Length();
241+
for (uint32_t i = 0; i < total_components; i++) {
242+
v8::Local<v8::Value> name_value;
243+
if (!Nan::Get(components_obj, i).ToLocal(&name_value)) continue;
244+
v8::Local<v8::Value> version_value;
245+
if (!Nan::Get(versions_obj, name_value).ToLocal(&version_value)) continue;
246+
247+
Nan::Utf8String component_name(name_value);
248+
Nan::Utf8String component_version(version_value);
249+
if (*component_name == nullptr || *component_version == nullptr) continue;
250+
251+
// Don't duplicate the Node.js version
252+
if (!strcmp("node", *component_name)) {
253+
if (version_string == UNKNOWN_NODEVERSION_STRING) {
254+
version_string = "Node.js version: v";
255+
version_string += *component_version;
256+
version_string += "\n";
257+
}
258+
} else {
259+
comp_versions[*component_name] = *component_version;
260+
}
261+
}
262+
263+
// Format sorted component versions surrounded by (), wrapped
264+
// e.g.
265+
// (ares: 1.10.1-DEV, http_parser: 2.7.0, icu: 57.1, modules: 48,
266+
// openssl: 1.0.2j, uv: 1.9.1, v8: 5.1.281.84, zlib: 1.2.8)
267+
const size_t wrap = 80;
268+
size_t line_length = 1;
269+
version_string += "(";
270+
for (auto it : comp_versions) {
271+
std::string component_name = it.first;
272+
std::string component_version = it.second;
273+
size_t length = component_name.length() + component_version.length() + 2;
274+
if (line_length + length + 1 >= wrap) {
275+
version_string += "\n ";
276+
line_length = 1;
277+
}
278+
version_string += component_name;
279+
version_string += ": ";
280+
version_string += component_version;
281+
line_length += length;
282+
if (it != *std::prev(comp_versions.end())) {
283+
version_string += ", ";
284+
line_length += 2;
285+
}
286+
}
287+
version_string += ")\n";
288+
}
289+
192290
/*******************************************************************************
193291
* Function to save the nodereport module load time value
194292
*******************************************************************************/
@@ -317,7 +415,7 @@ void TriggerNodeReport(Isolate* isolate, DumpEvent event, const char* message, c
317415
fflush(fp);
318416

319417
// Print Node.js and OS version information
320-
PrintVersionInformation(fp);
418+
PrintVersionInformation(fp, isolate);
321419
fflush(fp);
322420

323421
// Print summary JavaScript stack backtrace
@@ -369,12 +467,15 @@ void TriggerNodeReport(Isolate* isolate, DumpEvent event, const char* message, c
369467
* Function to print Node.js version, OS version and machine information
370468
*
371469
******************************************************************************/
372-
static void PrintVersionInformation(FILE* fp) {
470+
static void PrintVersionInformation(FILE* fp, Isolate* isolate) {
373471

374472
// Print Node.js and deps component versions
375-
fprintf(fp, "\nNode.js version: %s\n", NODE_VERSION);
376-
fprintf(fp, "(v8: %s, libuv: %s, zlib: %s, ares: %s)\n",
377-
V8::GetVersion(), uv_version_string(), ZLIB_VERSION, ARES_VERSION_STR);
473+
fprintf(fp, "\n%s", version_string.c_str());
474+
475+
// Print NodeReport version
476+
// e.g. NodeReport version: 1.0.6 (built against Node.js v6.9.1)
477+
fprintf(fp, "\nNodeReport version: %s (built against Node.js v%s)\n",
478+
NODEREPORT_VERSION, NODE_VERSION_STRING);
378479

379480
// Print operating system and machine information (Windows)
380481
#ifdef _WIN32

src/node_report.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ void ProcessNodeReportDirectory(const char* args);
4343
unsigned int ProcessNodeReportVerboseSwitch(const char* args);
4444

4545
void SetLoadTime();
46+
void SetVersionString(Isolate* isolate);
4647

4748
// Local implementation of secure_getenv()
4849
inline const char* secure_getenv(const char* key) {

test/common.js

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ const REPORT_SECTIONS = [
99
'System Information'
1010
];
1111

12+
const reNewline = '(?:\\r*\\n)';
13+
1214
exports.findReports = (pid) => {
1315
// Default filenames are of the form NodeReport.<date>.<time>.<pid>.<seq>.txt
1416
const format = '^NodeReport\\.\\d+\\.\\d+\\.' + pid + '\\.\\d+\\.txt$';
@@ -25,20 +27,60 @@ exports.isWindows = () => {
2527
return process.platform === 'win32';
2628
};
2729

28-
exports.validate = (t, report, pid) => {
30+
exports.validate = (t, report, options) => {
2931
t.test('Validating ' + report, (t) => {
3032
fs.readFile(report, (err, data) => {
33+
const pid = options ? options.pid : process.pid;
3134
const reportContents = data.toString();
32-
const plan = REPORT_SECTIONS.length + (pid ? 1 : 0);
35+
const nodeComponents = Object.keys(process.versions);
36+
const expectedVersions = options ?
37+
options.expectedVersions || nodeComponents :
38+
nodeComponents;
39+
const plan = REPORT_SECTIONS.length + nodeComponents.length + 2;
3340
t.plan(plan);
34-
if (pid) {
35-
t.match(reportContents, new RegExp('Process ID: ' + pid),
36-
'Checking report contains expected process ID ' + pid);
37-
}
41+
42+
// Check all sections are present
3843
REPORT_SECTIONS.forEach((section) => {
3944
t.match(reportContents, new RegExp('==== ' + section),
4045
'Checking report contains ' + section + ' section');
4146
});
47+
48+
// Check NodeReport section
49+
const nodeReportSection = getSection(reportContents, 'NodeReport');
50+
t.match(nodeReportSection, new RegExp('Process ID: ' + pid),
51+
'NodeReport section contains expected process ID');
52+
if (options && options.expectNodeVersion === false) {
53+
t.match(nodeReportSection, /Unable to determine Node.js version/,
54+
'NodeReport section contains expected Node.js version');
55+
} else {
56+
t.match(nodeReportSection,
57+
new RegExp('Node.js version: ' + process.version),
58+
'NodeReport section contains expected Node.js version');
59+
}
60+
nodeComponents.forEach((c) => {
61+
if (c !== 'node') {
62+
if (expectedVersions.indexOf(c) === -1) {
63+
t.notMatch(nodeReportSection,
64+
new RegExp(c + ': ' + process.versions[c]),
65+
'NodeReport section does not contain ' + c + ' version');
66+
} else {
67+
t.match(nodeReportSection,
68+
new RegExp(c + ': ' + process.versions[c]),
69+
'NodeReport section contains expected ' + c + ' version');
70+
}
71+
}
72+
});
73+
const nodereportMetadata = require('../package.json');
74+
t.match(nodeReportSection,
75+
new RegExp('NodeReport version: ' + nodereportMetadata.version),
76+
'NodeReport section contains expected NodeReport version');
4277
});
4378
});
4479
};
80+
81+
const getSection = (report, section) => {
82+
const re = new RegExp('==== ' + section + ' =+' + reNewline + '+([\\S\\s]+?)'
83+
+ reNewline + '+={80}' + reNewline);
84+
const match = re.exec(report);
85+
return match ? match[1] : '';
86+
};

test/test-api-bad-processobj.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
'use strict';
2+
3+
// Testcase to check NodeReport succeeds if process.versions is damaged
4+
if (process.argv[2] === 'child') {
5+
// Tamper with the process object
6+
Object.defineProperty(process, 'versions', {get() { throw 'boom'; }});
7+
const nodereport = require('../');
8+
nodereport.triggerReport();
9+
} else {
10+
const common = require('./common.js');
11+
const spawn = require('child_process').spawn;
12+
const tap = require('tap');
13+
14+
const child = spawn(process.execPath, [__filename, 'child']);
15+
child.on('exit', (code) => {
16+
tap.plan(3);
17+
tap.equal(code, 0, 'Process exited cleanly');
18+
const reports = common.findReports(child.pid);
19+
tap.equal(reports.length, 1, 'Found reports ' + reports);
20+
const report = reports[0];
21+
const validateOpts = { pid: child.pid, expectedVersions: [] };
22+
common.validate(tap, report, validateOpts);
23+
});
24+
}

test/test-api-bad-processversion.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
'use strict';
2+
3+
// Testcase to check NodeReport succeeds if process.version is damaged
4+
if (process.argv[2] === 'child') {
5+
// Tamper with the process object
6+
delete process['version'];
7+
const nodereport = require('../');
8+
nodereport.triggerReport();
9+
} else {
10+
const common = require('./common.js');
11+
const spawn = require('child_process').spawn;
12+
const tap = require('tap');
13+
14+
const child = spawn(process.execPath, [__filename, 'child']);
15+
child.on('exit', (code) => {
16+
tap.plan(3);
17+
tap.equal(code, 0, 'Process exited cleanly');
18+
const reports = common.findReports(child.pid);
19+
tap.equal(reports.length, 1, 'Found reports ' + reports);
20+
const report = reports[0];
21+
const validateOpts = { pid: child.pid, expectNodeVersion: true };
22+
common.validate(tap, report, validateOpts);
23+
});
24+
}

test/test-api-bad-processversions.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
'use strict';
2+
3+
// Testcase to check NodeReport succeeds if part of process.versions is damaged
4+
if (process.argv[2] === 'child') {
5+
// Tamper with the process object
6+
Object.defineProperty(process.versions, 'uv', {get() { throw 'boom'; }});
7+
const nodereport = require('../');
8+
nodereport.triggerReport();
9+
} else {
10+
const common = require('./common.js');
11+
const spawn = require('child_process').spawn;
12+
const tap = require('tap');
13+
14+
const child = spawn(process.execPath, [__filename, 'child']);
15+
child.on('exit', (code) => {
16+
tap.plan(3);
17+
tap.equal(code, 0, 'Process exited cleanly');
18+
const reports = common.findReports(child.pid);
19+
tap.equal(reports.length, 1, 'Found reports ' + reports);
20+
const report = reports[0];
21+
const validateOpts = { pid: child.pid,
22+
expectedVersions: Object.keys(process.versions).filter((c) => c !== 'uv')
23+
};
24+
common.validate(tap, report, validateOpts);
25+
});
26+
}

test/test-api-noversioninfo.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
'use strict';
2+
3+
// Testcase to check NodeReport succeeds if both process.version and
4+
// process.versions are damaged
5+
if (process.argv[2] === 'child') {
6+
// Tamper with the process object
7+
delete process['version'];
8+
delete process['versions'];
9+
10+
const nodereport = require('../');
11+
nodereport.triggerReport();
12+
} else {
13+
const common = require('./common.js');
14+
const spawn = require('child_process').spawn;
15+
const tap = require('tap');
16+
17+
const child = spawn(process.execPath, [__filename, 'child']);
18+
child.on('exit', (code) => {
19+
tap.plan(3);
20+
tap.equal(code, 0, 'Process exited cleanly');
21+
const reports = common.findReports(child.pid);
22+
tap.equal(reports.length, 1, 'Found reports ' + reports);
23+
const report = reports[0];
24+
const validateOpts = { pid: child.pid, expectNodeVersion: false,
25+
expectedVersions: [] };
26+
common.validate(tap, report, validateOpts);
27+
});
28+
}

test/test-api.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,6 @@ if (process.argv[2] === 'child') {
1616
const reports = common.findReports(child.pid);
1717
tap.equal(reports.length, 1, 'Found reports ' + reports);
1818
const report = reports[0];
19-
common.validate(tap, report, child.pid);
19+
common.validate(tap, report, {pid: child.pid});
2020
});
2121
}

test/test-exception.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,6 @@ if (process.argv[2] === 'child') {
3232
const reports = common.findReports(child.pid);
3333
tap.equal(reports.length, 1, 'Found reports ' + reports);
3434
const report = reports[0];
35-
common.validate(tap, report, child.pid);
35+
common.validate(tap, report, {pid: child.pid});
3636
});
3737
}

0 commit comments

Comments
 (0)