summaryrefslogtreecommitdiff
path: root/test/abort/test-addon-uv-handle-leak.js
blob: 47751954ab5728d3d87716b02c2bd7b327d27d39 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
'use strict';
const common = require('../common');
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const cp = require('child_process');
const { spawnSync } = require('child_process');

// This is a sibling test to test/addons/uv-handle-leak.

const bindingPath = path.resolve(
  __dirname, '..', 'addons', 'uv-handle-leak', 'build',
  `${common.buildType}/binding.node`);

if (!fs.existsSync(bindingPath))
  common.skip('binding not built yet');

if (process.argv[2] === 'child') {

  const { Worker } = require('worker_threads');

  // The worker thread loads and then unloads `bindingPath`. Because of this the
  // symbols in `bindingPath` are lost when the worker thread quits, but the
  // number of open handles in the worker thread's event loop is assessed in the
  // main thread afterwards, and the names of the callbacks associated with the
  // open handles is retrieved at that time as well. Thus, we require
  // `bindingPath` here so that the symbols and their names survive the life
  // cycle of the worker thread.
  require(bindingPath);

  new Worker(`
  const binding = require(${JSON.stringify(bindingPath)});

  binding.leakHandle();
  binding.leakHandle(0);
  binding.leakHandle(0x42);
  `, { eval: true });
} else {
  const child = cp.spawnSync(process.execPath, [__filename, 'child']);
  const stderr = child.stderr.toString();

  assert.strictEqual(child.stdout.toString(), '');

  const lines = stderr.split('\n');

  let state = 'initial';

  // Parse output that is formatted like this:

  // uv loop at [0x559b65ed5770] has active handles
  // [0x7f2de0018430] timer
  //         Close callback: 0x7f2df31de220 CloseCallback(uv_handle_s*) [...]
  //         Data: 0x7f2df33df140 example_instance [...]
  //         (First field): 0x7f2df33dedc0 vtable for ExampleOwnerClass [...]
  // [0x7f2de000b870] timer
  //         Close callback: 0x7f2df31de220 CloseCallback(uv_handle_s*) [...]
  //         Data: (nil)
  // [0x7f2de000b910] timer
  //         Close callback: 0x7f2df31de220 CloseCallback(uv_handle_s*) [...]
  //         Data: 0x42

  function isGlibc() {
    try {
      const lddOut = spawnSync('ldd', [process.execPath]).stdout;
      const libcInfo = lddOut.toString().split('\n').map(
        (line) => line.match(/libc\.so.+=>\s*(\S+)\s/)).filter((info) => info);
      if (libcInfo.length === 0)
        return false;
      const nmOut = spawnSync('nm', ['-D', libcInfo[0][1]]).stdout;
      if (/gnu_get_libc_version/.test(nmOut))
        return true;
    } catch {
      return false;
    }
  }


  if (!(common.isFreeBSD ||
        common.isAIX ||
        (common.isLinux && !isGlibc()) ||
        common.isWindows)) {
    assert(stderr.includes('ExampleOwnerClass'), stderr);
    assert(stderr.includes('CloseCallback'), stderr);
    assert(stderr.includes('example_instance'), stderr);
  }

  while (lines.length > 0) {
    const line = lines.shift().trim();

    switch (state) {
      case 'initial':
        assert(/^uv loop at \[.+\] has \d+ active handles$/.test(line), line);
        state = 'handle-start';
        break;
      case 'handle-start':
        if (/Assertion .+ failed/.test(line)) {
          state = 'done';
          break;
        }
        assert(/^\[.+\] timer$/.test(line), line);
        state = 'close-callback';
        break;
      case 'close-callback':
        assert(/^Close callback:/.test(line), line);
        state = 'data';
        break;
      case 'data':
        assert(/^Data: .+$/.test(line), line);
        state = 'maybe-first-field';
        break;
      case 'maybe-first-field':
        if (/^\(First field\)$/.test(line)) {
          lines.unshift(line);
          state = 'handle-start';
          break;
        }
        state = 'maybe-first-field';
        break;
      case 'done':
        break;
    }
  }
}