1// Copyright 2008 the V8 project authors. All rights reserved. 2// Redistribution and use in source and binary forms, with or without 3// modification, are permitted provided that the following conditions are 4// met: 5// 6// * Redistributions of source code must retain the above copyright 7// notice, this list of conditions and the following disclaimer. 8// * Redistributions in binary form must reproduce the above 9// copyright notice, this list of conditions and the following 10// disclaimer in the documentation and/or other materials provided 11// with the distribution. 12// * Neither the name of Google Inc. nor the names of its 13// contributors may be used to endorse or promote products derived 14// from this software without specific prior written permission. 15// 16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 28String.prototype.startsWith = function (str) { 29 if (str.length > this.length) 30 return false; 31 return this.substr(0, str.length) == str; 32} 33 34function log10(num) { 35 return Math.log(num)/Math.log(10); 36} 37 38function ToInspectableObject(obj) { 39 if (!obj && typeof obj === 'object') { 40 return void 0; 41 } else { 42 return Object(obj); 43 } 44} 45 46function GetCompletions(global, last, full) { 47 var full_tokens = full.split(); 48 full = full_tokens.pop(); 49 var parts = full.split('.'); 50 parts.pop(); 51 var current = global; 52 for (var i = 0; i < parts.length; i++) { 53 var part = parts[i]; 54 var next = current[part]; 55 if (!next) 56 return []; 57 current = next; 58 } 59 var result = []; 60 current = ToInspectableObject(current); 61 while (typeof current !== 'undefined') { 62 var mirror = new $debug.ObjectMirror(current); 63 var properties = mirror.properties(); 64 for (var i = 0; i < properties.length; i++) { 65 var name = properties[i].name(); 66 if (typeof name === 'string' && name.startsWith(last)) 67 result.push(name); 68 } 69 current = ToInspectableObject(current.__proto__); 70 } 71 return result; 72} 73 74 75// Global object holding debugger related constants and state. 76const Debug = {}; 77 78 79// Debug events which can occour in the V8 JavaScript engine. These originate 80// from the API include file v8-debug.h. 81Debug.DebugEvent = { Break: 1, 82 Exception: 2, 83 NewFunction: 3, 84 BeforeCompile: 4, 85 AfterCompile: 5 }; 86 87 88// The different types of scripts matching enum ScriptType in objects.h. 89Debug.ScriptType = { Native: 0, 90 Extension: 1, 91 Normal: 2 }; 92 93 94// The different types of script compilations matching enum 95// Script::CompilationType in objects.h. 96Debug.ScriptCompilationType = { Host: 0, 97 Eval: 1, 98 JSON: 2 }; 99 100 101// The different types of scopes matching constants runtime.cc. 102Debug.ScopeType = { Global: 0, 103 Local: 1, 104 With: 2, 105 Closure: 3, 106 Catch: 4 }; 107 108 109// Current debug state. 110const kNoFrame = -1; 111Debug.State = { 112 currentFrame: kNoFrame, 113 currentSourceLine: -1 114} 115var trace_compile = false; // Tracing all compile events? 116 117 118// Process a debugger JSON message into a display text and a running status. 119// This function returns an object with properties "text" and "running" holding 120// this information. 121function DebugMessageDetails(message) { 122 // Convert the JSON string to an object. 123 var response = new ProtocolPackage(message); 124 125 if (response.type() == 'event') { 126 return DebugEventDetails(response); 127 } else { 128 return DebugResponseDetails(response); 129 } 130} 131 132function DebugEventDetails(response) { 133 details = {text:'', running:false} 134 135 // Get the running state. 136 details.running = response.running(); 137 138 var body = response.body(); 139 var result = ''; 140 switch (response.event()) { 141 case 'break': 142 if (body.breakpoints) { 143 result += 'breakpoint'; 144 if (body.breakpoints.length > 1) { 145 result += 's'; 146 } 147 result += ' #'; 148 for (var i = 0; i < body.breakpoints.length; i++) { 149 if (i > 0) { 150 result += ', #'; 151 } 152 result += body.breakpoints[i]; 153 } 154 } else { 155 result += 'break'; 156 } 157 result += ' in '; 158 result += body.invocationText; 159 result += ', '; 160 result += SourceInfo(body); 161 result += '\n'; 162 result += SourceUnderline(body.sourceLineText, body.sourceColumn); 163 Debug.State.currentSourceLine = body.sourceLine; 164 Debug.State.currentFrame = 0; 165 details.text = result; 166 break; 167 168 case 'exception': 169 if (body.uncaught) { 170 result += 'Uncaught: '; 171 } else { 172 result += 'Exception: '; 173 } 174 result += '"'; 175 result += body.exception.text; 176 result += '"'; 177 if (body.sourceLine >= 0) { 178 result += ', '; 179 result += SourceInfo(body); 180 result += '\n'; 181 result += SourceUnderline(body.sourceLineText, body.sourceColumn); 182 Debug.State.currentSourceLine = body.sourceLine; 183 Debug.State.currentFrame = 0; 184 } else { 185 result += ' (empty stack)'; 186 Debug.State.currentSourceLine = -1; 187 Debug.State.currentFrame = kNoFrame; 188 } 189 details.text = result; 190 break; 191 192 case 'afterCompile': 193 if (trace_compile) { 194 result = 'Source ' + body.script.name + ' compiled:\n' 195 var source = body.script.source; 196 if (!(source[source.length - 1] == '\n')) { 197 result += source; 198 } else { 199 result += source.substring(0, source.length - 1); 200 } 201 } 202 details.text = result; 203 break; 204 205 default: 206 details.text = 'Unknown debug event ' + response.event(); 207 } 208 209 return details; 210}; 211 212 213function SourceInfo(body) { 214 var result = ''; 215 216 if (body.script) { 217 if (body.script.name) { 218 result += body.script.name; 219 } else { 220 result += '[unnamed]'; 221 } 222 } 223 result += ' line '; 224 result += body.sourceLine + 1; 225 result += ' column '; 226 result += body.sourceColumn + 1; 227 228 return result; 229} 230 231 232function SourceUnderline(source_text, position) { 233 if (!source_text) { 234 return; 235 } 236 237 // Create an underline with a caret pointing to the source position. If the 238 // source contains a tab character the underline will have a tab character in 239 // the same place otherwise the underline will have a space character. 240 var underline = ''; 241 for (var i = 0; i < position; i++) { 242 if (source_text[i] == '\t') { 243 underline += '\t'; 244 } else { 245 underline += ' '; 246 } 247 } 248 underline += '^'; 249 250 // Return the source line text with the underline beneath. 251 return source_text + '\n' + underline; 252}; 253 254 255// Converts a text command to a JSON request. 256function DebugCommandToJSONRequest(cmd_line) { 257 return new DebugRequest(cmd_line).JSONRequest(); 258}; 259 260 261function DebugRequest(cmd_line) { 262 // If the very first character is a { assume that a JSON request have been 263 // entered as a command. Converting that to a JSON request is trivial. 264 if (cmd_line && cmd_line.length > 0 && cmd_line.charAt(0) == '{') { 265 this.request_ = cmd_line; 266 return; 267 } 268 269 // Trim string for leading and trailing whitespace. 270 cmd_line = cmd_line.replace(/^\s+|\s+$/g, ''); 271 272 // Find the command. 273 var pos = cmd_line.indexOf(' '); 274 var cmd; 275 var args; 276 if (pos == -1) { 277 cmd = cmd_line; 278 args = ''; 279 } else { 280 cmd = cmd_line.slice(0, pos); 281 args = cmd_line.slice(pos).replace(/^\s+|\s+$/g, ''); 282 } 283 284 // Switch on command. 285 switch (cmd) { 286 case 'continue': 287 case 'c': 288 this.request_ = this.continueCommandToJSONRequest_(args); 289 break; 290 291 case 'step': 292 case 's': 293 this.request_ = this.stepCommandToJSONRequest_(args); 294 break; 295 296 case 'backtrace': 297 case 'bt': 298 this.request_ = this.backtraceCommandToJSONRequest_(args); 299 break; 300 301 case 'frame': 302 case 'f': 303 this.request_ = this.frameCommandToJSONRequest_(args); 304 break; 305 306 case 'scopes': 307 this.request_ = this.scopesCommandToJSONRequest_(args); 308 break; 309 310 case 'scope': 311 this.request_ = this.scopeCommandToJSONRequest_(args); 312 break; 313 314 case 'print': 315 case 'p': 316 this.request_ = this.printCommandToJSONRequest_(args); 317 break; 318 319 case 'dir': 320 this.request_ = this.dirCommandToJSONRequest_(args); 321 break; 322 323 case 'references': 324 this.request_ = this.referencesCommandToJSONRequest_(args); 325 break; 326 327 case 'instances': 328 this.request_ = this.instancesCommandToJSONRequest_(args); 329 break; 330 331 case 'source': 332 this.request_ = this.sourceCommandToJSONRequest_(args); 333 break; 334 335 case 'scripts': 336 this.request_ = this.scriptsCommandToJSONRequest_(args); 337 break; 338 339 case 'break': 340 case 'b': 341 this.request_ = this.breakCommandToJSONRequest_(args); 342 break; 343 344 case 'clear': 345 this.request_ = this.clearCommandToJSONRequest_(args); 346 break; 347 348 case 'threads': 349 this.request_ = this.threadsCommandToJSONRequest_(args); 350 break; 351 352 case 'trace': 353 // Return undefined to indicate command handled internally (no JSON). 354 this.request_ = void 0; 355 this.traceCommand_(args); 356 break; 357 358 case 'help': 359 case '?': 360 this.helpCommand_(args); 361 // Return undefined to indicate command handled internally (no JSON). 362 this.request_ = void 0; 363 break; 364 365 default: 366 throw new Error('Unknown command "' + cmd + '"'); 367 } 368 369 last_cmd = cmd; 370} 371 372DebugRequest.prototype.JSONRequest = function() { 373 return this.request_; 374} 375 376 377function RequestPacket(command) { 378 this.seq = 0; 379 this.type = 'request'; 380 this.command = command; 381} 382 383 384RequestPacket.prototype.toJSONProtocol = function() { 385 // Encode the protocol header. 386 var json = '{'; 387 json += '"seq":' + this.seq; 388 json += ',"type":"' + this.type + '"'; 389 if (this.command) { 390 json += ',"command":' + StringToJSON_(this.command); 391 } 392 if (this.arguments) { 393 json += ',"arguments":'; 394 // Encode the arguments part. 395 if (this.arguments.toJSONProtocol) { 396 json += this.arguments.toJSONProtocol() 397 } else { 398 json += SimpleObjectToJSON_(this.arguments); 399 } 400 } 401 json += '}'; 402 return json; 403} 404 405 406DebugRequest.prototype.createRequest = function(command) { 407 return new RequestPacket(command); 408}; 409 410 411// Create a JSON request for the evaluation command. 412DebugRequest.prototype.makeEvaluateJSONRequest_ = function(expression) { 413 // Global varaible used to store whether a handle was requested. 414 lookup_handle = null; 415 // Check if the expression is a handle id in the form #<handle>#. 416 var handle_match = expression.match(/^#([0-9]*)#$/); 417 if (handle_match) { 418 // Remember the handle requested in a global variable. 419 lookup_handle = parseInt(handle_match[1]); 420 // Build a lookup request. 421 var request = this.createRequest('lookup'); 422 request.arguments = {}; 423 request.arguments.handles = [ lookup_handle ]; 424 return request.toJSONProtocol(); 425 } else { 426 // Build an evaluate request. 427 var request = this.createRequest('evaluate'); 428 request.arguments = {}; 429 request.arguments.expression = expression; 430 // Request a global evaluation if there is no current frame. 431 if (Debug.State.currentFrame == kNoFrame) { 432 request.arguments.global = true; 433 } 434 return request.toJSONProtocol(); 435 } 436}; 437 438 439// Create a JSON request for the references/instances command. 440DebugRequest.prototype.makeReferencesJSONRequest_ = function(handle, type) { 441 // Build a references request. 442 var handle_match = handle.match(/^#([0-9]*)#$/); 443 if (handle_match) { 444 var request = this.createRequest('references'); 445 request.arguments = {}; 446 request.arguments.type = type; 447 request.arguments.handle = parseInt(handle_match[1]); 448 return request.toJSONProtocol(); 449 } else { 450 throw new Error('Invalid object id.'); 451 } 452}; 453 454 455// Create a JSON request for the continue command. 456DebugRequest.prototype.continueCommandToJSONRequest_ = function(args) { 457 var request = this.createRequest('continue'); 458 return request.toJSONProtocol(); 459}; 460 461 462// Create a JSON request for the step command. 463DebugRequest.prototype.stepCommandToJSONRequest_ = function(args) { 464 // Requesting a step is through the continue command with additional 465 // arguments. 466 var request = this.createRequest('continue'); 467 request.arguments = {}; 468 469 // Process arguments if any. 470 if (args && args.length > 0) { 471 args = args.split(/\s*[ ]+\s*/g); 472 473 if (args.length > 2) { 474 throw new Error('Invalid step arguments.'); 475 } 476 477 if (args.length > 0) { 478 // Get step count argument if any. 479 if (args.length == 2) { 480 var stepcount = parseInt(args[1]); 481 if (isNaN(stepcount) || stepcount <= 0) { 482 throw new Error('Invalid step count argument "' + args[0] + '".'); 483 } 484 request.arguments.stepcount = stepcount; 485 } 486 487 // Get the step action. 488 switch (args[0]) { 489 case 'in': 490 case 'i': 491 request.arguments.stepaction = 'in'; 492 break; 493 494 case 'min': 495 case 'm': 496 request.arguments.stepaction = 'min'; 497 break; 498 499 case 'next': 500 case 'n': 501 request.arguments.stepaction = 'next'; 502 break; 503 504 case 'out': 505 case 'o': 506 request.arguments.stepaction = 'out'; 507 break; 508 509 default: 510 throw new Error('Invalid step argument "' + args[0] + '".'); 511 } 512 } 513 } else { 514 // Default is step next. 515 request.arguments.stepaction = 'next'; 516 } 517 518 return request.toJSONProtocol(); 519}; 520 521 522// Create a JSON request for the backtrace command. 523DebugRequest.prototype.backtraceCommandToJSONRequest_ = function(args) { 524 // Build a backtrace request from the text command. 525 var request = this.createRequest('backtrace'); 526 527 // Default is to show top 10 frames. 528 request.arguments = {}; 529 request.arguments.fromFrame = 0; 530 request.arguments.toFrame = 10; 531 532 args = args.split(/\s*[ ]+\s*/g); 533 if (args.length == 1 && args[0].length > 0) { 534 var frameCount = parseInt(args[0]); 535 if (frameCount > 0) { 536 // Show top frames. 537 request.arguments.fromFrame = 0; 538 request.arguments.toFrame = frameCount; 539 } else { 540 // Show bottom frames. 541 request.arguments.fromFrame = 0; 542 request.arguments.toFrame = -frameCount; 543 request.arguments.bottom = true; 544 } 545 } else if (args.length == 2) { 546 var fromFrame = parseInt(args[0]); 547 var toFrame = parseInt(args[1]); 548 if (isNaN(fromFrame) || fromFrame < 0) { 549 throw new Error('Invalid start frame argument "' + args[0] + '".'); 550 } 551 if (isNaN(toFrame) || toFrame < 0) { 552 throw new Error('Invalid end frame argument "' + args[1] + '".'); 553 } 554 if (fromFrame > toFrame) { 555 throw new Error('Invalid arguments start frame cannot be larger ' + 556 'than end frame.'); 557 } 558 // Show frame range. 559 request.arguments.fromFrame = fromFrame; 560 request.arguments.toFrame = toFrame + 1; 561 } else if (args.length > 2) { 562 throw new Error('Invalid backtrace arguments.'); 563 } 564 565 return request.toJSONProtocol(); 566}; 567 568 569// Create a JSON request for the frame command. 570DebugRequest.prototype.frameCommandToJSONRequest_ = function(args) { 571 // Build a frame request from the text command. 572 var request = this.createRequest('frame'); 573 args = args.split(/\s*[ ]+\s*/g); 574 if (args.length > 0 && args[0].length > 0) { 575 request.arguments = {}; 576 request.arguments.number = args[0]; 577 } 578 return request.toJSONProtocol(); 579}; 580 581 582// Create a JSON request for the scopes command. 583DebugRequest.prototype.scopesCommandToJSONRequest_ = function(args) { 584 // Build a scopes request from the text command. 585 var request = this.createRequest('scopes'); 586 return request.toJSONProtocol(); 587}; 588 589 590// Create a JSON request for the scope command. 591DebugRequest.prototype.scopeCommandToJSONRequest_ = function(args) { 592 // Build a scope request from the text command. 593 var request = this.createRequest('scope'); 594 args = args.split(/\s*[ ]+\s*/g); 595 if (args.length > 0 && args[0].length > 0) { 596 request.arguments = {}; 597 request.arguments.number = args[0]; 598 } 599 return request.toJSONProtocol(); 600}; 601 602 603// Create a JSON request for the print command. 604DebugRequest.prototype.printCommandToJSONRequest_ = function(args) { 605 // Build an evaluate request from the text command. 606 if (args.length == 0) { 607 throw new Error('Missing expression.'); 608 } 609 return this.makeEvaluateJSONRequest_(args); 610}; 611 612 613// Create a JSON request for the dir command. 614DebugRequest.prototype.dirCommandToJSONRequest_ = function(args) { 615 // Build an evaluate request from the text command. 616 if (args.length == 0) { 617 throw new Error('Missing expression.'); 618 } 619 return this.makeEvaluateJSONRequest_(args); 620}; 621 622 623// Create a JSON request for the references command. 624DebugRequest.prototype.referencesCommandToJSONRequest_ = function(args) { 625 // Build an evaluate request from the text command. 626 if (args.length == 0) { 627 throw new Error('Missing object id.'); 628 } 629 630 return this.makeReferencesJSONRequest_(args, 'referencedBy'); 631}; 632 633 634// Create a JSON request for the instances command. 635DebugRequest.prototype.instancesCommandToJSONRequest_ = function(args) { 636 // Build an evaluate request from the text command. 637 if (args.length == 0) { 638 throw new Error('Missing object id.'); 639 } 640 641 // Build a references request. 642 return this.makeReferencesJSONRequest_(args, 'constructedBy'); 643}; 644 645 646// Create a JSON request for the source command. 647DebugRequest.prototype.sourceCommandToJSONRequest_ = function(args) { 648 // Build a evaluate request from the text command. 649 var request = this.createRequest('source'); 650 651 // Default is ten lines starting five lines before the current location. 652 var from = Debug.State.currentSourceLine - 5; 653 var lines = 10; 654 655 // Parse the arguments. 656 args = args.split(/\s*[ ]+\s*/g); 657 if (args.length > 1 && args[0].length > 0 && args[1].length > 0) { 658 from = parseInt(args[0]) - 1; 659 lines = parseInt(args[1]); 660 } else if (args.length > 0 && args[0].length > 0) { 661 from = parseInt(args[0]) - 1; 662 } 663 664 if (from < 0) from = 0; 665 if (lines < 0) lines = 10; 666 667 // Request source arround current source location. 668 request.arguments = {}; 669 request.arguments.fromLine = from; 670 request.arguments.toLine = from + lines; 671 672 return request.toJSONProtocol(); 673}; 674 675 676// Create a JSON request for the scripts command. 677DebugRequest.prototype.scriptsCommandToJSONRequest_ = function(args) { 678 // Build a evaluate request from the text command. 679 var request = this.createRequest('scripts'); 680 681 // Process arguments if any. 682 if (args && args.length > 0) { 683 args = args.split(/\s*[ ]+\s*/g); 684 685 if (args.length > 1) { 686 throw new Error('Invalid scripts arguments.'); 687 } 688 689 request.arguments = {}; 690 switch (args[0]) { 691 case 'natives': 692 request.arguments.types = ScriptTypeFlag(Debug.ScriptType.Native); 693 break; 694 695 case 'extensions': 696 request.arguments.types = ScriptTypeFlag(Debug.ScriptType.Extension); 697 break; 698 699 case 'all': 700 request.arguments.types = 701 ScriptTypeFlag(Debug.ScriptType.Normal) | 702 ScriptTypeFlag(Debug.ScriptType.Native) | 703 ScriptTypeFlag(Debug.ScriptType.Extension); 704 break; 705 706 default: 707 throw new Error('Invalid argument "' + args[0] + '".'); 708 } 709 } 710 711 return request.toJSONProtocol(); 712}; 713 714 715// Create a JSON request for the break command. 716DebugRequest.prototype.breakCommandToJSONRequest_ = function(args) { 717 // Build a evaluate request from the text command. 718 var request = this.createRequest('setbreakpoint'); 719 720 // Process arguments if any. 721 if (args && args.length > 0) { 722 var target = args; 723 var type = 'function'; 724 var line; 725 var column; 726 var condition; 727 var pos; 728 729 // Check for breakpoint condition. 730 pos = args.indexOf(' '); 731 if (pos > 0) { 732 target = args.substring(0, pos); 733 condition = args.substring(pos + 1, args.length); 734 } 735 736 // Check for script breakpoint (name:line[:column]). If no ':' in break 737 // specification it is considered a function break point. 738 pos = target.indexOf(':'); 739 if (pos > 0) { 740 type = 'script'; 741 var tmp = target.substring(pos + 1, target.length); 742 target = target.substring(0, pos); 743 744 // Check for both line and column. 745 pos = tmp.indexOf(':'); 746 if (pos > 0) { 747 column = parseInt(tmp.substring(pos + 1, tmp.length)) - 1; 748 line = parseInt(tmp.substring(0, pos)) - 1; 749 } else { 750 line = parseInt(tmp) - 1; 751 } 752 } else if (target[0] == '#' && target[target.length - 1] == '#') { 753 type = 'handle'; 754 target = target.substring(1, target.length - 1); 755 } else { 756 type = 'function'; 757 } 758 759 request.arguments = {}; 760 request.arguments.type = type; 761 request.arguments.target = target; 762 request.arguments.line = line; 763 request.arguments.column = column; 764 request.arguments.condition = condition; 765 } else { 766 throw new Error('Invalid break arguments.'); 767 } 768 769 return request.toJSONProtocol(); 770}; 771 772 773// Create a JSON request for the clear command. 774DebugRequest.prototype.clearCommandToJSONRequest_ = function(args) { 775 // Build a evaluate request from the text command. 776 var request = this.createRequest('clearbreakpoint'); 777 778 // Process arguments if any. 779 if (args && args.length > 0) { 780 request.arguments = {}; 781 request.arguments.breakpoint = parseInt(args); 782 } else { 783 throw new Error('Invalid break arguments.'); 784 } 785 786 return request.toJSONProtocol(); 787}; 788 789 790// Create a JSON request for the threads command. 791DebugRequest.prototype.threadsCommandToJSONRequest_ = function(args) { 792 // Build a threads request from the text command. 793 var request = this.createRequest('threads'); 794 return request.toJSONProtocol(); 795}; 796 797 798// Handle the trace command. 799DebugRequest.prototype.traceCommand_ = function(args) { 800 // Process arguments. 801 if (args && args.length > 0) { 802 if (args == 'compile') { 803 trace_compile = !trace_compile; 804 print('Tracing of compiled scripts ' + (trace_compile ? 'on' : 'off')); 805 } else { 806 throw new Error('Invalid trace arguments.'); 807 } 808 } else { 809 throw new Error('Invalid trace arguments.'); 810 } 811} 812 813// Handle the help command. 814DebugRequest.prototype.helpCommand_ = function(args) { 815 // Help os quite simple. 816 if (args && args.length > 0) { 817 print('warning: arguments to \'help\' are ignored'); 818 } 819 820 print('break location [condition]'); 821 print(' break on named function: location is a function name'); 822 print(' break on function: location is #<id>#'); 823 print(' break on script position: location is name:line[:column]'); 824 print('clear <breakpoint #>'); 825 print('backtrace [n] | [-n] | [from to]'); 826 print('frame <frame #>'); 827 print('scopes'); 828 print('scope <scope #>'); 829 print('step [in | next | out| min [step count]]'); 830 print('print <expression>'); 831 print('dir <expression>'); 832 print('source [from line [num lines]]'); 833 print('scripts'); 834 print('continue'); 835 print('trace compile'); 836 print('help'); 837} 838 839 840function formatHandleReference_(value) { 841 if (value.handle() >= 0) { 842 return '#' + value.handle() + '#'; 843 } else { 844 return '#Transient#'; 845 } 846} 847 848 849function formatObject_(value, include_properties) { 850 var result = ''; 851 result += formatHandleReference_(value); 852 result += ', type: object' 853 result += ', constructor '; 854 var ctor = value.constructorFunctionValue(); 855 result += formatHandleReference_(ctor); 856 result += ', __proto__ '; 857 var proto = value.protoObjectValue(); 858 result += formatHandleReference_(proto); 859 result += ', '; 860 result += value.propertyCount(); 861 result += ' properties.'; 862 if (include_properties) { 863 result += '\n'; 864 for (var i = 0; i < value.propertyCount(); i++) { 865 result += ' '; 866 result += value.propertyName(i); 867 result += ': '; 868 var property_value = value.propertyValue(i); 869 if (property_value instanceof ProtocolReference) { 870 result += '<no type>'; 871 } else { 872 if (property_value && property_value.type()) { 873 result += property_value.type(); 874 } else { 875 result += '<no type>'; 876 } 877 } 878 result += ' '; 879 result += formatHandleReference_(property_value); 880 result += '\n'; 881 } 882 } 883 return result; 884} 885 886 887function formatScope_(scope) { 888 var result = ''; 889 var index = scope.index; 890 result += '#' + (index <= 9 ? '0' : '') + index; 891 result += ' '; 892 switch (scope.type) { 893 case Debug.ScopeType.Global: 894 result += 'Global, '; 895 result += '#' + scope.object.ref + '#'; 896 break; 897 case Debug.ScopeType.Local: 898 result += 'Local'; 899 break; 900 case Debug.ScopeType.With: 901 result += 'With, '; 902 result += '#' + scope.object.ref + '#'; 903 break; 904 case Debug.ScopeType.Catch: 905 result += 'Catch, '; 906 result += '#' + scope.object.ref + '#'; 907 break; 908 case Debug.ScopeType.Closure: 909 result += 'Closure'; 910 break; 911 default: 912 result += 'UNKNOWN'; 913 } 914 return result; 915} 916 917 918// Convert a JSON response to text for display in a text based debugger. 919function DebugResponseDetails(response) { 920 details = {text:'', running:false} 921 922 try { 923 if (!response.success()) { 924 details.text = response.message(); 925 return details; 926 } 927 928 // Get the running state. 929 details.running = response.running(); 930 931 var body = response.body(); 932 var result = ''; 933 switch (response.command()) { 934 case 'setbreakpoint': 935 result = 'set breakpoint #'; 936 result += body.breakpoint; 937 details.text = result; 938 break; 939 940 case 'clearbreakpoint': 941 result = 'cleared breakpoint #'; 942 result += body.breakpoint; 943 details.text = result; 944 break; 945 946 case 'backtrace': 947 if (body.totalFrames == 0) { 948 result = '(empty stack)'; 949 } else { 950 var result = 'Frames #' + body.fromFrame + ' to #' + 951 (body.toFrame - 1) + ' of ' + body.totalFrames + '\n'; 952 for (i = 0; i < body.frames.length; i++) { 953 if (i != 0) result += '\n'; 954 result += body.frames[i].text; 955 } 956 } 957 details.text = result; 958 break; 959 960 case 'frame': 961 details.text = SourceUnderline(body.sourceLineText, 962 body.column); 963 Debug.State.currentSourceLine = body.line; 964 Debug.State.currentFrame = body.index; 965 break; 966 967 case 'scopes': 968 if (body.totalScopes == 0) { 969 result = '(no scopes)'; 970 } else { 971 result = 'Scopes #' + body.fromScope + ' to #' + 972 (body.toScope - 1) + ' of ' + body.totalScopes + '\n'; 973 for (i = 0; i < body.scopes.length; i++) { 974 if (i != 0) { 975 result += '\n'; 976 } 977 result += formatScope_(body.scopes[i]); 978 } 979 } 980 details.text = result; 981 break; 982 983 case 'scope': 984 result += formatScope_(body); 985 result += '\n'; 986 var scope_object_value = response.lookup(body.object.ref); 987 result += formatObject_(scope_object_value, true); 988 details.text = result; 989 break; 990 991 case 'evaluate': 992 case 'lookup': 993 if (last_cmd == 'p' || last_cmd == 'print') { 994 result = body.text; 995 } else { 996 var value; 997 if (lookup_handle) { 998 value = response.bodyValue(lookup_handle); 999 } else { 1000 value = response.bodyValue(); 1001 } 1002 if (value.isObject()) { 1003 result += formatObject_(value, true); 1004 } else { 1005 result += 'type: '; 1006 result += value.type(); 1007 if (!value.isUndefined() && !value.isNull()) { 1008 result += ', '; 1009 if (value.isString()) { 1010 result += '"'; 1011 } 1012 result += value.value(); 1013 if (value.isString()) { 1014 result += '"'; 1015 } 1016 } 1017 result += '\n'; 1018 } 1019 } 1020 details.text = result; 1021 break; 1022 1023 case 'references': 1024 var count = body.length; 1025 result += 'found ' + count + ' objects'; 1026 result += '\n'; 1027 for (var i = 0; i < count; i++) { 1028 var value = response.bodyValue(i); 1029 result += formatObject_(value, false); 1030 result += '\n'; 1031 } 1032 details.text = result; 1033 break; 1034 1035 case 'source': 1036 // Get the source from the response. 1037 var source = body.source; 1038 var from_line = body.fromLine + 1; 1039 var lines = source.split('\n'); 1040 var maxdigits = 1 + Math.floor(log10(from_line + lines.length)); 1041 if (maxdigits < 3) { 1042 maxdigits = 3; 1043 } 1044 var result = ''; 1045 for (var num = 0; num < lines.length; num++) { 1046 // Check if there's an extra newline at the end. 1047 if (num == (lines.length - 1) && lines[num].length == 0) { 1048 break; 1049 } 1050 1051 var current_line = from_line + num; 1052 spacer = maxdigits - (1 + Math.floor(log10(current_line))); 1053 if (current_line == Debug.State.currentSourceLine + 1) { 1054 for (var i = 0; i < maxdigits; i++) { 1055 result += '>'; 1056 } 1057 result += ' '; 1058 } else { 1059 for (var i = 0; i < spacer; i++) { 1060 result += ' '; 1061 } 1062 result += current_line + ': '; 1063 } 1064 result += lines[num]; 1065 result += '\n'; 1066 } 1067 details.text = result; 1068 break; 1069 1070 case 'scripts': 1071 var result = ''; 1072 for (i = 0; i < body.length; i++) { 1073 if (i != 0) result += '\n'; 1074 if (body[i].id) { 1075 result += body[i].id; 1076 } else { 1077 result += '[no id]'; 1078 } 1079 result += ', '; 1080 if (body[i].name) { 1081 result += body[i].name; 1082 } else { 1083 if (body[i].compilationType == Debug.ScriptCompilationType.Eval) { 1084 result += 'eval from '; 1085 var script_value = response.lookup(body[i].evalFromScript.ref); 1086 result += ' ' + script_value.field('name'); 1087 result += ':' + (body[i].evalFromLocation.line + 1); 1088 result += ':' + body[i].evalFromLocation.column; 1089 } else if (body[i].compilationType == 1090 Debug.ScriptCompilationType.JSON) { 1091 result += 'JSON '; 1092 } else { // body[i].compilation == Debug.ScriptCompilationType.Host 1093 result += '[unnamed] '; 1094 } 1095 } 1096 result += ' (lines: '; 1097 result += body[i].lineCount; 1098 result += ', length: '; 1099 result += body[i].sourceLength; 1100 if (body[i].type == Debug.ScriptType.Native) { 1101 result += ', native'; 1102 } else if (body[i].type == Debug.ScriptType.Extension) { 1103 result += ', extension'; 1104 } 1105 result += '), ['; 1106 var sourceStart = body[i].sourceStart; 1107 if (sourceStart.length > 40) { 1108 sourceStart = sourceStart.substring(0, 37) + '...'; 1109 } 1110 result += sourceStart; 1111 result += ']'; 1112 } 1113 details.text = result; 1114 break; 1115 1116 case 'threads': 1117 var result = 'Active V8 threads: ' + body.totalThreads + '\n'; 1118 body.threads.sort(function(a, b) { return a.id - b.id; }); 1119 for (i = 0; i < body.threads.length; i++) { 1120 result += body.threads[i].current ? '*' : ' '; 1121 result += ' '; 1122 result += body.threads[i].id; 1123 result += '\n'; 1124 } 1125 details.text = result; 1126 break; 1127 1128 case 'continue': 1129 details.text = "(running)"; 1130 break; 1131 1132 default: 1133 details.text = 1134 'Response for unknown command \'' + response.command + '\'' + 1135 ' (' + json_response + ')'; 1136 } 1137 } catch (e) { 1138 details.text = 'Error: "' + e + '" formatting response'; 1139 } 1140 1141 return details; 1142}; 1143 1144 1145/** 1146 * Protocol packages send from the debugger. 1147 * @param {string} json - raw protocol packet as JSON string. 1148 * @constructor 1149 */ 1150function ProtocolPackage(json) { 1151 this.packet_ = JSON.parse(json); 1152 this.refs_ = []; 1153 if (this.packet_.refs) { 1154 for (var i = 0; i < this.packet_.refs.length; i++) { 1155 this.refs_[this.packet_.refs[i].handle] = this.packet_.refs[i]; 1156 } 1157 } 1158} 1159 1160 1161/** 1162 * Get the packet type. 1163 * @return {String} the packet type 1164 */ 1165ProtocolPackage.prototype.type = function() { 1166 return this.packet_.type; 1167} 1168 1169 1170/** 1171 * Get the packet event. 1172 * @return {Object} the packet event 1173 */ 1174ProtocolPackage.prototype.event = function() { 1175 return this.packet_.event; 1176} 1177 1178 1179/** 1180 * Get the packet request sequence. 1181 * @return {number} the packet request sequence 1182 */ 1183ProtocolPackage.prototype.requestSeq = function() { 1184 return this.packet_.request_seq; 1185} 1186 1187 1188/** 1189 * Get the packet request sequence. 1190 * @return {number} the packet request sequence 1191 */ 1192ProtocolPackage.prototype.running = function() { 1193 return this.packet_.running ? true : false; 1194} 1195 1196 1197ProtocolPackage.prototype.success = function() { 1198 return this.packet_.success ? true : false; 1199} 1200 1201 1202ProtocolPackage.prototype.message = function() { 1203 return this.packet_.message; 1204} 1205 1206 1207ProtocolPackage.prototype.command = function() { 1208 return this.packet_.command; 1209} 1210 1211 1212ProtocolPackage.prototype.body = function() { 1213 return this.packet_.body; 1214} 1215 1216 1217ProtocolPackage.prototype.bodyValue = function(index) { 1218 if (index != null) { 1219 return new ProtocolValue(this.packet_.body[index], this); 1220 } else { 1221 return new ProtocolValue(this.packet_.body, this); 1222 } 1223} 1224 1225 1226ProtocolPackage.prototype.body = function() { 1227 return this.packet_.body; 1228} 1229 1230 1231ProtocolPackage.prototype.lookup = function(handle) { 1232 var value = this.refs_[handle]; 1233 if (value) { 1234 return new ProtocolValue(value, this); 1235 } else { 1236 return new ProtocolReference(handle); 1237 } 1238} 1239 1240 1241function ProtocolValue(value, packet) { 1242 this.value_ = value; 1243 this.packet_ = packet; 1244} 1245 1246 1247/** 1248 * Get the value type. 1249 * @return {String} the value type 1250 */ 1251ProtocolValue.prototype.type = function() { 1252 return this.value_.type; 1253} 1254 1255 1256/** 1257 * Get a metadata field from a protocol value. 1258 * @return {Object} the metadata field value 1259 */ 1260ProtocolValue.prototype.field = function(name) { 1261 return this.value_[name]; 1262} 1263 1264 1265/** 1266 * Check is the value is a primitive value. 1267 * @return {boolean} true if the value is primitive 1268 */ 1269ProtocolValue.prototype.isPrimitive = function() { 1270 return this.isUndefined() || this.isNull() || this.isBoolean() || 1271 this.isNumber() || this.isString(); 1272} 1273 1274 1275/** 1276 * Get the object handle. 1277 * @return {number} the value handle 1278 */ 1279ProtocolValue.prototype.handle = function() { 1280 return this.value_.handle; 1281} 1282 1283 1284/** 1285 * Check is the value is undefined. 1286 * @return {boolean} true if the value is undefined 1287 */ 1288ProtocolValue.prototype.isUndefined = function() { 1289 return this.value_.type == 'undefined'; 1290} 1291 1292 1293/** 1294 * Check is the value is null. 1295 * @return {boolean} true if the value is null 1296 */ 1297ProtocolValue.prototype.isNull = function() { 1298 return this.value_.type == 'null'; 1299} 1300 1301 1302/** 1303 * Check is the value is a boolean. 1304 * @return {boolean} true if the value is a boolean 1305 */ 1306ProtocolValue.prototype.isBoolean = function() { 1307 return this.value_.type == 'boolean'; 1308} 1309 1310 1311/** 1312 * Check is the value is a number. 1313 * @return {boolean} true if the value is a number 1314 */ 1315ProtocolValue.prototype.isNumber = function() { 1316 return this.value_.type == 'number'; 1317} 1318 1319 1320/** 1321 * Check is the value is a string. 1322 * @return {boolean} true if the value is a string 1323 */ 1324ProtocolValue.prototype.isString = function() { 1325 return this.value_.type == 'string'; 1326} 1327 1328 1329/** 1330 * Check is the value is an object. 1331 * @return {boolean} true if the value is an object 1332 */ 1333ProtocolValue.prototype.isObject = function() { 1334 return this.value_.type == 'object' || this.value_.type == 'function' || 1335 this.value_.type == 'error' || this.value_.type == 'regexp'; 1336} 1337 1338 1339/** 1340 * Get the constructor function 1341 * @return {ProtocolValue} constructor function 1342 */ 1343ProtocolValue.prototype.constructorFunctionValue = function() { 1344 var ctor = this.value_.constructorFunction; 1345 return this.packet_.lookup(ctor.ref); 1346} 1347 1348 1349/** 1350 * Get the __proto__ value 1351 * @return {ProtocolValue} __proto__ value 1352 */ 1353ProtocolValue.prototype.protoObjectValue = function() { 1354 var proto = this.value_.protoObject; 1355 return this.packet_.lookup(proto.ref); 1356} 1357 1358 1359/** 1360 * Get the number og properties. 1361 * @return {number} the number of properties 1362 */ 1363ProtocolValue.prototype.propertyCount = function() { 1364 return this.value_.properties ? this.value_.properties.length : 0; 1365} 1366 1367 1368/** 1369 * Get the specified property name. 1370 * @return {string} property name 1371 */ 1372ProtocolValue.prototype.propertyName = function(index) { 1373 var property = this.value_.properties[index]; 1374 return property.name; 1375} 1376 1377 1378/** 1379 * Return index for the property name. 1380 * @param name The property name to look for 1381 * @return {number} index for the property name 1382 */ 1383ProtocolValue.prototype.propertyIndex = function(name) { 1384 for (var i = 0; i < this.propertyCount(); i++) { 1385 if (this.value_.properties[i].name == name) { 1386 return i; 1387 } 1388 } 1389 return null; 1390} 1391 1392 1393/** 1394 * Get the specified property value. 1395 * @return {ProtocolValue} property value 1396 */ 1397ProtocolValue.prototype.propertyValue = function(index) { 1398 var property = this.value_.properties[index]; 1399 return this.packet_.lookup(property.ref); 1400} 1401 1402 1403/** 1404 * Check is the value is a string. 1405 * @return {boolean} true if the value is a string 1406 */ 1407ProtocolValue.prototype.value = function() { 1408 return this.value_.value; 1409} 1410 1411 1412function ProtocolReference(handle) { 1413 this.handle_ = handle; 1414} 1415 1416 1417ProtocolReference.prototype.handle = function() { 1418 return this.handle_; 1419} 1420 1421 1422function MakeJSONPair_(name, value) { 1423 return '"' + name + '":' + value; 1424} 1425 1426 1427function ArrayToJSONObject_(content) { 1428 return '{' + content.join(',') + '}'; 1429} 1430 1431 1432function ArrayToJSONArray_(content) { 1433 return '[' + content.join(',') + ']'; 1434} 1435 1436 1437function BooleanToJSON_(value) { 1438 return String(value); 1439} 1440 1441 1442function NumberToJSON_(value) { 1443 return String(value); 1444} 1445 1446 1447// Mapping of some control characters to avoid the \uXXXX syntax for most 1448// commonly used control cahracters. 1449const ctrlCharMap_ = { 1450 '\b': '\\b', 1451 '\t': '\\t', 1452 '\n': '\\n', 1453 '\f': '\\f', 1454 '\r': '\\r', 1455 '"' : '\\"', 1456 '\\': '\\\\' 1457}; 1458 1459 1460// Regular expression testing for ", \ and control characters (0x00 - 0x1F). 1461const ctrlCharTest_ = new RegExp('["\\\\\x00-\x1F]'); 1462 1463 1464// Regular expression matching ", \ and control characters (0x00 - 0x1F) 1465// globally. 1466const ctrlCharMatch_ = new RegExp('["\\\\\x00-\x1F]', 'g'); 1467 1468 1469/** 1470 * Convert a String to its JSON representation (see http://www.json.org/). To 1471 * avoid depending on the String object this method calls the functions in 1472 * string.js directly and not through the value. 1473 * @param {String} value The String value to format as JSON 1474 * @return {string} JSON formatted String value 1475 */ 1476function StringToJSON_(value) { 1477 // Check for" , \ and control characters (0x00 - 0x1F). No need to call 1478 // RegExpTest as ctrlchar is constructed using RegExp. 1479 if (ctrlCharTest_.test(value)) { 1480 // Replace ", \ and control characters (0x00 - 0x1F). 1481 return '"' + 1482 value.replace(ctrlCharMatch_, function (char) { 1483 // Use charmap if possible. 1484 var mapped = ctrlCharMap_[char]; 1485 if (mapped) return mapped; 1486 mapped = char.charCodeAt(); 1487 // Convert control character to unicode escape sequence. 1488 return '\\u00' + 1489 '0' + // TODO %NumberToRadixString(Math.floor(mapped / 16), 16) + 1490 '0' // TODO %NumberToRadixString(mapped % 16, 16); 1491 }) 1492 + '"'; 1493 } 1494 1495 // Simple string with no special characters. 1496 return '"' + value + '"'; 1497} 1498 1499 1500/** 1501 * Convert a Date to ISO 8601 format. To avoid depending on the Date object 1502 * this method calls the functions in date.js directly and not through the 1503 * value. 1504 * @param {Date} value The Date value to format as JSON 1505 * @return {string} JSON formatted Date value 1506 */ 1507function DateToISO8601_(value) { 1508 function f(n) { 1509 return n < 10 ? '0' + n : n; 1510 } 1511 function g(n) { 1512 return n < 10 ? '00' + n : n < 100 ? '0' + n : n; 1513 } 1514 return builtins.GetUTCFullYearFrom(value) + '-' + 1515 f(builtins.GetUTCMonthFrom(value) + 1) + '-' + 1516 f(builtins.GetUTCDateFrom(value)) + 'T' + 1517 f(builtins.GetUTCHoursFrom(value)) + ':' + 1518 f(builtins.GetUTCMinutesFrom(value)) + ':' + 1519 f(builtins.GetUTCSecondsFrom(value)) + '.' + 1520 g(builtins.GetUTCMillisecondsFrom(value)) + 'Z'; 1521} 1522 1523 1524/** 1525 * Convert a Date to ISO 8601 format. To avoid depending on the Date object 1526 * this method calls the functions in date.js directly and not through the 1527 * value. 1528 * @param {Date} value The Date value to format as JSON 1529 * @return {string} JSON formatted Date value 1530 */ 1531function DateToJSON_(value) { 1532 return '"' + DateToISO8601_(value) + '"'; 1533} 1534 1535 1536/** 1537 * Convert an Object to its JSON representation (see http://www.json.org/). 1538 * This implementation simply runs through all string property names and adds 1539 * each property to the JSON representation for some predefined types. For type 1540 * "object" the function calls itself recursively unless the object has the 1541 * function property "toJSONProtocol" in which case that is used. This is not 1542 * a general implementation but sufficient for the debugger. Note that circular 1543 * structures will cause infinite recursion. 1544 * @param {Object} object The object to format as JSON 1545 * @return {string} JSON formatted object value 1546 */ 1547function SimpleObjectToJSON_(object) { 1548 var content = []; 1549 for (var key in object) { 1550 // Only consider string keys. 1551 if (typeof key == 'string') { 1552 var property_value = object[key]; 1553 1554 // Format the value based on its type. 1555 var property_value_json; 1556 switch (typeof property_value) { 1557 case 'object': 1558 if (typeof property_value.toJSONProtocol == 'function') { 1559 property_value_json = property_value.toJSONProtocol(true) 1560 } else if (property_value.constructor.name == 'Array'){ 1561 property_value_json = SimpleArrayToJSON_(property_value); 1562 } else { 1563 property_value_json = SimpleObjectToJSON_(property_value); 1564 } 1565 break; 1566 1567 case 'boolean': 1568 property_value_json = BooleanToJSON_(property_value); 1569 break; 1570 1571 case 'number': 1572 property_value_json = NumberToJSON_(property_value); 1573 break; 1574 1575 case 'string': 1576 property_value_json = StringToJSON_(property_value); 1577 break; 1578 1579 default: 1580 property_value_json = null; 1581 } 1582 1583 // Add the property if relevant. 1584 if (property_value_json) { 1585 content.push(StringToJSON_(key) + ':' + property_value_json); 1586 } 1587 } 1588 } 1589 1590 // Make JSON object representation. 1591 return '{' + content.join(',') + '}'; 1592} 1593 1594 1595/** 1596 * Convert an array to its JSON representation. This is a VERY simple 1597 * implementation just to support what is needed for the debugger. 1598 * @param {Array} arrya The array to format as JSON 1599 * @return {string} JSON formatted array value 1600 */ 1601function SimpleArrayToJSON_(array) { 1602 // Make JSON array representation. 1603 var json = '['; 1604 for (var i = 0; i < array.length; i++) { 1605 if (i != 0) { 1606 json += ','; 1607 } 1608 var elem = array[i]; 1609 if (elem.toJSONProtocol) { 1610 json += elem.toJSONProtocol(true) 1611 } else if (typeof(elem) === 'object') { 1612 json += SimpleObjectToJSON_(elem); 1613 } else if (typeof(elem) === 'boolean') { 1614 json += BooleanToJSON_(elem); 1615 } else if (typeof(elem) === 'number') { 1616 json += NumberToJSON_(elem); 1617 } else if (typeof(elem) === 'string') { 1618 json += StringToJSON_(elem); 1619 } else { 1620 json += elem; 1621 } 1622 } 1623 json += ']'; 1624 return json; 1625} 1626