summaryrefslogtreecommitdiff
path: root/lib/internal/error-serdes.js
blob: 7b4b416b80c67071ec27bd7a94f6b63d797e58d6 (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
124
125
126
127
'use strict';

const Buffer = require('buffer').Buffer;
const { serialize, deserialize } = require('v8');
const {
  SafeSet,
  Object,
  ObjectPrototype,
  FunctionPrototype,
  ArrayPrototype
} = primordials;

const kSerializedError = 0;
const kSerializedObject = 1;
const kInspectedError = 2;

const GetPrototypeOf = Object.getPrototypeOf;
const GetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
const GetOwnPropertyNames = Object.getOwnPropertyNames;
const DefineProperty = Object.defineProperty;
const Assign = Object.assign;
const ObjectPrototypeToString =
    FunctionPrototype.call.bind(ObjectPrototype.toString);
const ForEach = FunctionPrototype.call.bind(ArrayPrototype.forEach);
const Call = FunctionPrototype.call.bind(FunctionPrototype.call);

const errors = {
  Error, TypeError, RangeError, URIError, SyntaxError, ReferenceError, EvalError
};
const errorConstructorNames = new SafeSet(Object.keys(errors));

function TryGetAllProperties(object, target = object) {
  const all = Object.create(null);
  if (object === null)
    return all;
  Assign(all, TryGetAllProperties(GetPrototypeOf(object), target));
  const keys = GetOwnPropertyNames(object);
  ForEach(keys, (key) => {
    const descriptor = GetOwnPropertyDescriptor(object, key);
    const getter = descriptor.get;
    if (getter && key !== '__proto__') {
      try {
        descriptor.value = Call(getter, target);
      } catch {}
    }
    if ('value' in descriptor && typeof descriptor.value !== 'function') {
      delete descriptor.get;
      delete descriptor.set;
      all[key] = descriptor;
    }
  });
  return all;
}

function GetConstructors(object) {
  const constructors = [];

  for (var current = object;
    current !== null;
    current = GetPrototypeOf(current)) {
    const desc = GetOwnPropertyDescriptor(current, 'constructor');
    if (desc && desc.value) {
      DefineProperty(constructors, constructors.length, {
        value: desc.value, enumerable: true
      });
    }
  }

  return constructors;
}

function GetName(object) {
  const desc = GetOwnPropertyDescriptor(object, 'name');
  return desc && desc.value;
}

let util;
function lazyUtil() {
  if (!util)
    util = require('util');
  return util;
}

function serializeError(error) {
  try {
    if (typeof error === 'object' &&
        ObjectPrototypeToString(error) === '[object Error]') {
      const constructors = GetConstructors(error);
      for (var i = 0; i < constructors.length; i++) {
        const name = GetName(constructors[i]);
        if (errorConstructorNames.has(name)) {
          try { error.stack; } catch {}
          const serialized = serialize({
            constructor: name,
            properties: TryGetAllProperties(error)
          });
          return Buffer.concat([Buffer.from([kSerializedError]), serialized]);
        }
      }
    }
  } catch {}
  try {
    const serialized = serialize(error);
    return Buffer.concat([Buffer.from([kSerializedObject]), serialized]);
  } catch {}
  return Buffer.concat([Buffer.from([kInspectedError]),
                        Buffer.from(lazyUtil().inspect(error), 'utf8')]);
}

function deserializeError(error) {
  switch (error[0]) {
    case kSerializedError:
      const { constructor, properties } = deserialize(error.subarray(1));
      const ctor = errors[constructor];
      return Object.create(ctor.prototype, properties);
    case kSerializedObject:
      return deserialize(error.subarray(1));
    case kInspectedError:
      const buf = Buffer.from(error.buffer,
                              error.byteOffset + 1,
                              error.byteLength - 1);
      return buf.toString('utf8');
  }
  require('assert').fail('This should not happen');
}

module.exports = { serializeError, deserializeError };