text: // Copyright (c) 2014, StrongLoop Inc.
  //
  // Permission to use, copy, modify, and/or distribute this software for any
  // purpose with or without fee is hereby granted, provided that the above
  // copyright notice and this permission notice appear in all copies.
  //
  // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  
  'use strict';
  
  const {
    Array,
    ArrayBuffer,
    ArrayPrototypeForEach,
    ArrayPrototypePush,
    DataView,
    Error,
    Float32Array,
    Float64Array,
    Int16Array,
    Int32Array,
    Int8Array,
    ObjectPrototypeToString,
    SafeMap,
    Uint16Array,
    Uint32Array,
    Uint8Array,
    Uint8ClampedArray,
  } = primordials;
  
  const { Buffer } = require('buffer');
  const { validateString } = require('internal/validators');
  const {
    Serializer,
    Deserializer
  } = internalBinding('serdes');
  
  let profiler = {};
  if (internalBinding('config').hasInspector) {
    profiler = internalBinding('profiler');
  }
  
  const assert = require('internal/assert');
  const { copy } = internalBinding('buffer');
  const { inspect } = require('internal/util/inspect');
  const { FastBuffer } = require('internal/buffer');
  const { getValidatedPath } = require('internal/fs/utils');
  const { toNamespacedPath } = require('path');
  const {
    createHeapSnapshotStream,
    triggerHeapSnapshot
  } = internalBinding('heap_utils');
  const { HeapSnapshotStream } = require('internal/heap_utils');
  
  /**
   * Generates a snapshot of the current V8 heap
   * and writes it to a JSON file.
   * @param {string} [filename]
   * @returns {string}
   */
  function writeHeapSnapshot(filename) {
    if (filename !== undefined) {
      filename = getValidatedPath(filename);
      filename = toNamespacedPath(filename);
    }
    return triggerHeapSnapshot(filename);
  }
  
  /**
   * Generates a snapshot of the current V8 heap
   * and returns a Readable Stream.
   * @returns {import('./stream.js').Readable}
   */
  function getHeapSnapshot() {
    const handle = createHeapSnapshotStream();
    assert(handle);
    return new HeapSnapshotStream(handle);
  }
  
  const {
    cachedDataVersionTag,
    setFlagsFromString: _setFlagsFromString,
    heapStatisticsBuffer,
    heapSpaceStatisticsBuffer,
    heapCodeStatisticsBuffer,
    updateHeapStatisticsBuffer,
    updateHeapSpaceStatisticsBuffer,
    updateHeapCodeStatisticsBuffer,
  
    // Properties for heap statistics buffer extraction.
    kTotalHeapSizeIndex,
    kTotalHeapSizeExecutableIndex,
    kTotalPhysicalSizeIndex,
    kTotalAvailableSize,
    kUsedHeapSizeIndex,
    kHeapSizeLimitIndex,
    kDoesZapGarbageIndex,
    kMallocedMemoryIndex,
    kPeakMallocedMemoryIndex,
    kNumberOfNativeContextsIndex,
    kNumberOfDetachedContextsIndex,
  
    // Properties for heap spaces statistics buffer extraction.
    kHeapSpaces,
    kSpaceSizeIndex,
    kSpaceUsedSizeIndex,
    kSpaceAvailableSizeIndex,
    kPhysicalSpaceSizeIndex,
  
    // Properties for heap code statistics buffer extraction.
    kCodeAndMetadataSizeIndex,
    kBytecodeAndMetadataSizeIndex,
    kExternalScriptSourceSizeIndex
  } = internalBinding('v8');
  
  const kNumberOfHeapSpaces = kHeapSpaces.length;
  
  /**
   * Sets V8 command-line flags.
   * @param {string} flags
   * @returns {void}
   */
  function setFlagsFromString(flags) {
    validateString(flags, 'flags');
    _setFlagsFromString(flags);
  }
  
  /**
   * Gets the current V8 heap statistics.
   * @returns {{
   *   total_heap_size: number;
   *   total_heap_size_executable: number;
   *   total_physical_size: number;
   *   total_available_size: number;
   *   used_heap_size: number;
   *   heap_size_limit: number;
   *   malloced_memory: number;
   *   peak_malloced_memory: number;
   *   does_zap_garbage: number;
   *   number_of_native_contexts: number;
   *   number_of_detached_contexts: number;
   *   }}
   */
  function getHeapStatistics() {
    const buffer = heapStatisticsBuffer;
  
    updateHeapStatisticsBuffer();
  
    return {
      'total_heap_size': buffer[kTotalHeapSizeIndex],
      'total_heap_size_executable': buffer[kTotalHeapSizeExecutableIndex],
      'total_physical_size': buffer[kTotalPhysicalSizeIndex],
      'total_available_size': buffer[kTotalAvailableSize],
      'used_heap_size': buffer[kUsedHeapSizeIndex],
      'heap_size_limit': buffer[kHeapSizeLimitIndex],
      'malloced_memory': buffer[kMallocedMemoryIndex],
      'peak_malloced_memory': buffer[kPeakMallocedMemoryIndex],
      'does_zap_garbage': buffer[kDoesZapGarbageIndex],
      'number_of_native_contexts': buffer[kNumberOfNativeContextsIndex],
      'number_of_detached_contexts': buffer[kNumberOfDetachedContextsIndex]
    };
  }
  
  /**
   * Gets the current V8 heap space statistics.
   * @returns {{
   *   space_name: string;
   *   space_size: number;
   *   space_used_size: number;
   *   space_available_size: number;
   *   physical_space_size: number;
   *   }[]}
   */
  function getHeapSpaceStatistics() {
    const heapSpaceStatistics = new Array(kNumberOfHeapSpaces);
    const buffer = heapSpaceStatisticsBuffer;
  
    for (let i = 0; i < kNumberOfHeapSpaces; i++) {
      updateHeapSpaceStatisticsBuffer(i);
      heapSpaceStatistics[i] = {
        space_name: kHeapSpaces[i],
        space_size: buffer[kSpaceSizeIndex],
        space_used_size: buffer[kSpaceUsedSizeIndex],
        space_available_size: buffer[kSpaceAvailableSizeIndex],
        physical_space_size: buffer[kPhysicalSpaceSizeIndex]
      };
    }
  
    return heapSpaceStatistics;
  }
  
  /**
   * Gets the current V8 heap code statistics.
   * @returns {{
   *   code_and_metadata_size: number;
   *   bytecode_and_metadata_size: number;
   *   external_script_source_size: number;
   *   }}
   */
  function getHeapCodeStatistics() {
    const buffer = heapCodeStatisticsBuffer;
  
    updateHeapCodeStatisticsBuffer();
    return {
      'code_and_metadata_size': buffer[kCodeAndMetadataSizeIndex],
      'bytecode_and_metadata_size': buffer[kBytecodeAndMetadataSizeIndex],
      'external_script_source_size': buffer[kExternalScriptSourceSizeIndex]
    };
  }
  
  /* V8 serialization API */
  
  /* JS methods for the base objects */
  Serializer.prototype._getDataCloneError = Error;
  
  /**
   * Reads raw bytes from the deserializer's internal buffer.
   * @param {number} length
   * @returns {Buffer}
   */
  Deserializer.prototype.readRawBytes = function readRawBytes(length) {
    const offset = this._readRawBytes(length);
    // `this.buffer` can be a Buffer or a plain Uint8Array, so just calling
    // `.slice()` doesn't work.
    return new FastBuffer(this.buffer.buffer,
                          this.buffer.byteOffset + offset,
                          length);
  };
  
  /* Keep track of how to handle different ArrayBufferViews.
   * The default Serializer for Node does not use the V8 methods for serializing
   * those objects because Node's `Buffer` objects use pooled allocation in many
   * cases, and their underlying `ArrayBuffer`s would show up in the
   * serialization. Because a) those may contain sensitive data and the user
   * may not be aware of that and b) they are often much larger than the `Buffer`
   * itself, custom serialization is applied. */
  const arrayBufferViewTypes = [Int8Array, Uint8Array, Uint8ClampedArray,
                                Int16Array, Uint16Array, Int32Array, Uint32Array,
                                Float32Array, Float64Array, DataView];
  
  const arrayBufferViewTypeToIndex = new SafeMap();
  
  {
    const dummy = new ArrayBuffer();
    ArrayPrototypeForEach(arrayBufferViewTypes, (ctor, i) => {
      const tag = ObjectPrototypeToString(new ctor(dummy));
      arrayBufferViewTypeToIndex.set(tag, i);
    });
  }
  
  const bufferConstructorIndex =
    ArrayPrototypePush(arrayBufferViewTypes, FastBuffer) - 1;
  
  class DefaultSerializer extends Serializer {
    constructor() {
      super();
  
      this._setTreatArrayBufferViewsAsHostObjects(true);
    }
  
    /**
     * Used to write some kind of host object, i.e. an
     * object that is created by native C++ bindings.
     * @param {Object} abView
     * @returns {void}
     */
    _writeHostObject(abView) {
      let i = 0;
      if (abView.constructor === Buffer) {
        i = bufferConstructorIndex;
      } else {
        const tag = ObjectPrototypeToString(abView);
        i = arrayBufferViewTypeToIndex.get(tag);
  
        if (i === undefined) {
          throw new this._getDataCloneError(
            `Unserializable host object: ${inspect(abView)}`);
        }
      }
      this.writeUint32(i);
      this.writeUint32(abView.byteLength);
      this.writeRawBytes(new Uint8Array(abView.buffer,
                                        abView.byteOffset,
                                        abView.byteLength));
    }
  }
  
  class DefaultDeserializer extends Deserializer {
    /**
     * Used to read some kind of host object, i.e. an
     * object that is created by native C++ bindings.
     * @returns {any}
     */
    _readHostObject() {
      const typeIndex = this.readUint32();
      const ctor = arrayBufferViewTypes[typeIndex];
      const byteLength = this.readUint32();
      const byteOffset = this._readRawBytes(byteLength);
      const BYTES_PER_ELEMENT = ctor.BYTES_PER_ELEMENT || 1;
  
      const offset = this.buffer.byteOffset + byteOffset;
      if (offset % BYTES_PER_ELEMENT === 0) {
        return new ctor(this.buffer.buffer,
                        offset,
                        byteLength / BYTES_PER_ELEMENT);
      }
      // Copy to an aligned buffer first.
      const buffer_copy = Buffer.allocUnsafe(byteLength);
      copy(this.buffer, buffer_copy, 0, byteOffset, byteOffset + byteLength);
      return new ctor(buffer_copy.buffer,
                      buffer_copy.byteOffset,
                      byteLength / BYTES_PER_ELEMENT);
    }
  }
  
  /**
   * Uses a `DefaultSerializer` to serialize `value`
   * into a buffer.
   * @param {any} value
   * @returns {Buffer}
   */
  function serialize(value) {
    const ser = new DefaultSerializer();
    ser.writeHeader();
    ser.writeValue(value);
    return ser.releaseBuffer();
  }
  
  /**
   * Uses a `DefaultDeserializer` with default options
   * to read a JavaScript value from a buffer.
   * @param {Buffer | TypedArray | DataView} buffer
   * @returns {any}
   */
  function deserialize(buffer) {
    const der = new DefaultDeserializer(buffer);
    der.readHeader();
    return der.readValue();
  }
  
  module.exports = {
    cachedDataVersionTag,
    getHeapSnapshot,
    getHeapStatistics,
    getHeapSpaceStatistics,
    getHeapCodeStatistics,
    setFlagsFromString,
    Serializer,
    Deserializer,
    DefaultSerializer,
    DefaultDeserializer,
    deserialize,
    takeCoverage: profiler.takeCoverage,
    stopCoverage: profiler.stopCoverage,
    serialize,
    writeHeapSnapshot,
  };
  