UNPKG

atom-nuclide

Version:

A unified developer experience for web and mobile development, built as a suite of features on top of Atom to provide hackability and the support of an active community.

527 lines (475 loc) 18.6 kB
Object.defineProperty(exports, '__esModule', { value: true }); var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { var callNext = step.bind(null, 'next'); var callThrow = step.bind(null, 'throw'); function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(callNext, callThrow); } } callNext(); }); }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } // Responses to the DBGP 'status' command function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } /* * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ var _utils2; function _utils() { return _utils2 = _interopRequireDefault(require('./utils')); } var _helpers2; function _helpers() { return _helpers2 = require('./helpers'); } var _events2; function _events() { return _events2 = require('events'); } var _DbgpMessageHandler2; function _DbgpMessageHandler() { return _DbgpMessageHandler2 = require('./DbgpMessageHandler'); } var _commonsNodeEvent2; function _commonsNodeEvent() { return _commonsNodeEvent2 = require('../../commons-node/event'); } var STATUS_STARTING = 'starting'; exports.STATUS_STARTING = STATUS_STARTING; var STATUS_STOPPING = 'stopping'; exports.STATUS_STOPPING = STATUS_STOPPING; var STATUS_STOPPED = 'stopped'; exports.STATUS_STOPPED = STATUS_STOPPED; var STATUS_RUNNING = 'running'; exports.STATUS_RUNNING = STATUS_RUNNING; var STATUS_BREAK = 'break'; exports.STATUS_BREAK = STATUS_BREAK; // Error and End are not dbgp status codes, they relate to socket states. var STATUS_ERROR = 'error'; exports.STATUS_ERROR = STATUS_ERROR; var STATUS_END = 'end'; exports.STATUS_END = STATUS_END; // stdout and stderr are emitted when DBGP sends the corresponding message packets. var STATUS_STDOUT = 'stdout'; exports.STATUS_STDOUT = STATUS_STDOUT; var STATUS_STDERR = 'stderr'; exports.STATUS_STDERR = STATUS_STDERR; // Notifications. var BREAKPOINT_RESOLVED_NOTIFICATION = 'breakpoint_resolved'; exports.BREAKPOINT_RESOLVED_NOTIFICATION = BREAKPOINT_RESOLVED_NOTIFICATION; // Valid continuation commands var COMMAND_RUN = 'run'; exports.COMMAND_RUN = COMMAND_RUN; var COMMAND_STEP_INTO = 'step_into'; exports.COMMAND_STEP_INTO = COMMAND_STEP_INTO; var COMMAND_STEP_OVER = 'step_over'; exports.COMMAND_STEP_OVER = COMMAND_STEP_OVER; var COMMAND_STEP_OUT = 'step_out'; exports.COMMAND_STEP_OUT = COMMAND_STEP_OUT; var COMMAND_STOP = 'stop'; exports.COMMAND_STOP = COMMAND_STOP; var COMMAND_DETACH = 'detach'; exports.COMMAND_DETACH = COMMAND_DETACH; var DBGP_SOCKET_STATUS_EVENT = 'dbgp-socket-status'; var DBGP_SOCKET_NOTIFICATION_EVENT = 'dbgp-socket-notification'; var DEFAULT_DBGP_PROPERTY = { $: { type: 'undefined' } }; /** * Handles sending and recieving dbgp messages over a net Socket. * Dbgp documentation can be found at http://xdebug.org/docs-dbgp.php */ var DbgpSocket = (function () { function DbgpSocket(socket) { _classCallCheck(this, DbgpSocket); this._socket = socket; this._transactionId = 0; this._calls = new Map(); this._emitter = new (_events2 || _events()).EventEmitter(); this._isClosed = false; this._messageHandler = (0, (_DbgpMessageHandler2 || _DbgpMessageHandler()).getDbgpMessageHandlerInstance)(); socket.on('end', this._onEnd.bind(this)); socket.on('error', this._onError.bind(this)); socket.on('data', this._onData.bind(this)); } _createClass(DbgpSocket, [{ key: 'onStatus', value: function onStatus(callback) { return (0, (_commonsNodeEvent2 || _commonsNodeEvent()).attachEvent)(this._emitter, DBGP_SOCKET_STATUS_EVENT, callback); } }, { key: 'onNotification', value: function onNotification(callback) { return (0, (_commonsNodeEvent2 || _commonsNodeEvent()).attachEvent)(this._emitter, DBGP_SOCKET_NOTIFICATION_EVENT, callback); } }, { key: '_onError', value: function _onError(error) { // Not sure if hhvm is alive or not // do not set _isClosed flag so that detach will be sent before dispose(). (_utils2 || _utils()).default.logError('socket error ' + error.code); this._emitStatus(STATUS_ERROR, error.code); } }, { key: '_onEnd', value: function _onEnd() { this._isClosed = true; this.dispose(); this._emitStatus(STATUS_END); } }, { key: '_onData', value: function _onData(data) { var _this = this; var message = data.toString(); (_utils2 || _utils()).default.log('Recieved data: ' + message); var responses = []; try { responses = this._messageHandler.parseMessages(message); } catch (e) { // If message parsing fails, then our contract with HHVM is violated and we need to kill the // connection. this._emitStatus(STATUS_ERROR, e.message); return; } responses.forEach(function (r) { var response = r.response; var stream = r.stream; var notify = r.notify; if (response) { _this._handleResponse(response, message); } else if (stream != null) { _this._handleStream(stream); } else if (notify != null) { _this._handleNotification(notify); } else { (_utils2 || _utils()).default.logError('Unexpected socket message: ' + message); } }); } }, { key: '_handleResponse', value: function _handleResponse(response, message) { var responseAttributes = response.$; var command = responseAttributes.command; var transaction_id = responseAttributes.transaction_id; var transactionId = Number(transaction_id); var call = this._calls.get(transactionId); if (!call) { (_utils2 || _utils()).default.logError('Missing call for response: ' + message); return; } this._calls.delete(transactionId); if (call.command !== command) { (_utils2 || _utils()).default.logError('Bad command in response. Found ' + command + '. expected ' + call.command); return; } try { (_utils2 || _utils()).default.log('Completing call: ' + message); call.complete(response); } catch (e) { (_utils2 || _utils()).default.logError('Exception: ' + e.toString() + ' handling call: ' + message); } } }, { key: '_handleStream', value: function _handleStream(stream) { var outputType = stream.$.type; // The body of the `stream` XML can be omitted, e.g. `echo null`, so we defend against this. var outputText = stream._ != null ? (0, (_helpers2 || _helpers()).base64Decode)(stream._) : ''; (_utils2 || _utils()).default.log(outputType + ' message received: ' + outputText); var status = outputType === 'stdout' ? STATUS_STDOUT : STATUS_STDERR; this._emitStatus(status, outputText); } }, { key: '_handleNotification', value: function _handleNotification(notify) { var notifyName = notify.$.name; if (notifyName === 'breakpoint_resolved') { var breakpoint = notify.breakpoint[0].$; if (breakpoint == null) { (_utils2 || _utils()).default.logError('Fail to get breakpoint from \'breakpoint_resolved\' notify: ' + JSON.stringify(notify)); return; } this._emitNotification(BREAKPOINT_RESOLVED_NOTIFICATION, breakpoint); } else { (_utils2 || _utils()).default.logError('Unknown notify: ' + JSON.stringify(notify)); } } }, { key: 'getStackFrames', value: function getStackFrames() { return this._callDebugger('stack_get'); } }, { key: 'getContextsForFrame', value: _asyncToGenerator(function* (frameIndex) { var result = yield this._callDebugger('context_names', '-d ' + frameIndex); return result.context.map(function (context) { return context.$; }); }) }, { key: 'getContextProperties', value: _asyncToGenerator(function* (frameIndex, contextId) { var result = yield this._callDebugger('context_get', '-d ' + frameIndex + ' -c ' + contextId); // 0 results yields missing 'property' member return result.property || []; }) }, { key: 'getPropertiesByFullname', value: _asyncToGenerator(function* (frameIndex, contextId, fullname, page) { // Escape any double quote in the expression. var escapedFullname = fullname.replace(/"/g, '\\"'); var result = yield this._callDebugger('property_value', '-d ' + frameIndex + ' -c ' + contextId + ' -n "' + escapedFullname + '" -p ' + page); // property_value returns the outer property, we want the children ... // 0 results yields missing 'property' member if (result.property == null || result.property[0] == null) { return []; } return result.property[0].property || []; }) }, { key: 'getPropertiesByFullnameAllConexts', value: _asyncToGenerator(function* (frameIndex, fullname, page) { // Pass zero as contextId to search all contexts. return yield this.getPropertiesByFullname(frameIndex, /* contextId */'0', fullname, page); }) }, { key: 'evaluateOnCallFrame', value: _asyncToGenerator(function* (frameIndex, expression) { // Escape any double quote in the expression. var escapedExpression = expression.replace(/"/g, '\\"'); // Quote the input expression so that we can support expression with // space in it(e.g. function evaluation). var result = yield this._callDebugger('property_value', '-d ' + frameIndex + ' -n "' + escapedExpression + '"'); if (result.error && result.error.length > 0) { return { error: result.error[0], wasThrown: true }; } else if (result.property != null) { return { result: result.property[0], wasThrown: false }; } else { (_utils2 || _utils()).default.log('Received non-error evaluateOnCallFrame response with no properties: ' + expression); return { result: DEFAULT_DBGP_PROPERTY, wasThrown: false }; } }) // Returns one of: // starting, stopping, stopped, running, break }, { key: 'getStatus', value: _asyncToGenerator(function* () { var response = yield this._callDebugger('status'); // TODO: Do we ever care about response.$.reason? return response.$.status; }) // Continuation commands get a response, but that response // is a status message which occurs after execution stops. }, { key: 'sendContinuationCommand', value: _asyncToGenerator(function* (command) { this._emitStatus(STATUS_RUNNING); var response = yield this._callDebugger(command); var status = response.$.status; this._emitStatus(status); return status; }) }, { key: 'sendBreakCommand', value: _asyncToGenerator(function* () { var response = yield this._callDebugger('break'); return response.$.success !== '0'; }) }, { key: 'sendStdoutRequest', value: _asyncToGenerator(function* () { // `-c 1` tells HHVM to send stdout to the normal destination, as well as forward it to nuclide. var response = yield this._callDebugger('stdout', '-c 1'); return response.$.success !== '0'; }) /** * Stderr forwarding is not implemented by HHVM yet so this will always return failure. */ }, { key: 'sendStderrRequest', value: _asyncToGenerator(function* () { var response = yield this._callDebugger('stderr', '-c 1'); return response.$.success !== '0'; }) /** * Sets a given config setting in the debugger to a given value. */ }, { key: 'setFeature', value: _asyncToGenerator(function* (name, value) { var response = yield this._callDebugger('feature_set', '-n ' + name + ' -v ' + value); return response.$.success !== '0'; }) /** * Evaluate the expression in the debugger's current context. */ }, { key: 'runtimeEvaluate', value: _asyncToGenerator(function* (expr) { var response = yield this._callDebugger('eval', '-- ' + (0, (_helpers2 || _helpers()).base64Encode)(expr)); if (response.error && response.error.length > 0) { return { error: response.error[0], wasThrown: true }; } else if (response.property != null) { return { result: response.property[0], wasThrown: false }; } else { (_utils2 || _utils()).default.log('Received non-error runtimeEvaluate response with no properties: ' + expr); return { result: DEFAULT_DBGP_PROPERTY, wasThrown: false }; } }) /** * Returns the exception breakpoint id. */ }, { key: 'setExceptionBreakpoint', value: _asyncToGenerator(function* (exceptionName) { var response = yield this._callDebugger('breakpoint_set', '-t exception -x ' + exceptionName); if (response.error) { throw new Error('Error from setPausedOnExceptions: ' + JSON.stringify(response)); } // TODO: Validate that response.$.state === 'enabled' return response.$.id; }) /** * Set breakpoint on a source file line. * Returns a xdebug breakpoint id. */ }, { key: 'setFileLineBreakpoint', value: _asyncToGenerator(function* (breakpointInfo) { var filename = breakpointInfo.filename; var lineNumber = breakpointInfo.lineNumber; var conditionExpression = breakpointInfo.conditionExpression; var params = '-t line -f ' + filename + ' -n ' + lineNumber; if (conditionExpression != null) { params += ' -- ' + (0, (_helpers2 || _helpers()).base64Encode)(conditionExpression); } var response = yield this._callDebugger('breakpoint_set', params); if (response.error) { throw new Error('Error setting breakpoint: ' + JSON.stringify(response)); } // TODO: Validate that response.$.state === 'enabled' return response.$.id; }) /** * Returns requested breakpoint object. */ }, { key: 'getBreakpoint', value: _asyncToGenerator(function* (breakpointId) { var response = yield this._callDebugger('breakpoint_get', '-d ' + breakpointId); if (response.error != null || response.breakpoint == null || response.breakpoint[0] == null || response.breakpoint[0].$ == null) { throw new Error('Error getting breakpoint: ' + JSON.stringify(response)); } return response.breakpoint[0].$; }) }, { key: 'removeBreakpoint', value: _asyncToGenerator(function* (breakpointId) { var response = yield this._callDebugger('breakpoint_remove', '-d ' + breakpointId); if (response.error) { throw new Error('Error removing breakpoint: ' + JSON.stringify(response)); } }) // Sends command to hhvm. // Returns an object containing the resulting attributes. }, { key: '_callDebugger', value: function _callDebugger(command, params) { var _this2 = this; var transactionId = this._sendCommand(command, params); return new Promise(function (resolve, reject) { _this2._calls.set(transactionId, { command: command, complete: function complete(result) { return resolve(result); } }); }); } }, { key: '_sendCommand', value: function _sendCommand(command, params) { var id = ++this._transactionId; var message = command + ' -i ' + id; if (params) { message += ' ' + params; } this._sendMessage(message); return id; } }, { key: '_sendMessage', value: function _sendMessage(message) { var socket = this._socket; if (socket != null) { (_utils2 || _utils()).default.log('Sending message: ' + message); socket.write(message + '\x00'); } else { (_utils2 || _utils()).default.logError('Attempt to send message after dispose: ' + message); } } }, { key: '_emitStatus', value: function _emitStatus(status) { var _emitter; (_utils2 || _utils()).default.log('Emitting status: ' + status); for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { args[_key - 1] = arguments[_key]; } (_emitter = this._emitter).emit.apply(_emitter, [DBGP_SOCKET_STATUS_EVENT, status].concat(args)); } }, { key: '_emitNotification', value: function _emitNotification(notifyName, notify) { (_utils2 || _utils()).default.log('Emitting notification: ' + notifyName); this._emitter.emit(DBGP_SOCKET_NOTIFICATION_EVENT, notifyName, notify); } }, { key: 'dispose', value: function dispose() { if (!this._isClosed) { // TODO[jeffreytan]: workaround a crash(t8181538) in hhvm this.sendContinuationCommand(COMMAND_DETACH); } var socket = this._socket; if (socket) { // end - Sends the FIN packet and closes writing. // destroy - closes for reading and writing. socket.end(); socket.destroy(); this._socket = null; this._isClosed = true; } } }]); return DbgpSocket; })(); exports.DbgpSocket = DbgpSocket; // name and fullname are omitted when we get data back from the `eval` command. // array or object // string // Value if present, subject to encoding if present // array or object members // Maps from transactionId -> call