merge from argo
[tamarin-stm.git] / shell / DebugCLI.cpp
blob8d11460c34531c6ee32b1fda03faa53854761ba8
1 /* -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 4 -*- */
2 /* vi: set ts=4 sw=4 expandtab: (add to ~/.vimrc: set modeline modelines=5) */
3 /* ***** BEGIN LICENSE BLOCK *****
4 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 * The contents of this file are subject to the Mozilla Public License Version
7 * 1.1 (the "License"); you may not use this file except in compliance with
8 * the License. You may obtain a copy of the License at
9 * http://www.mozilla.org/MPL/
11 * Software distributed under the License is distributed on an "AS IS" basis,
12 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 * for the specific language governing rights and limitations under the
14 * License.
16 * The Original Code is [Open Source Virtual Machine.].
18 * The Initial Developer of the Original Code is
19 * Adobe System Incorporated.
20 * Portions created by the Initial Developer are Copyright (C) 2004-2006
21 * the Initial Developer. All Rights Reserved.
23 * Contributor(s):
24 * Adobe AS3 Team
26 * Alternatively, the contents of this file may be used under the terms of
27 * either the GNU General Public License Version 2 or later (the "GPL"), or
28 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 * in which case the provisions of the GPL or the LGPL are applicable instead
30 * of those above. If you wish to allow use of your version of this file only
31 * under the terms of either the GPL or the LGPL, and not to allow others to
32 * use your version of this file under the terms of the MPL, indicate your
33 * decision by deleting the provisions above and replace them with the notice
34 * and other provisions required by the GPL or the LGPL. If you do not delete
35 * the provisions above, a recipient may use your version of this file under
36 * the terms of any one of the MPL, the GPL or the LGPL.
38 * ***** END LICENSE BLOCK ***** */
40 #include "avmshell.h"
42 #ifdef DEBUGGER
43 namespace avmshell
45 /**
46 * The array of top level commands that we support.
47 * They are placed into a Nx2 array, whereby the first component
48 * is a String which is the command and the 2nd component is the
49 * integer identifier for the command.
51 * The StringIntArray object provides a convenient wrapper class
52 * that implements the List interface.
54 * NOTE: order matters! For the case of a single character
55 * match, we let the first hit act like an unambiguous match.
57 DebugCLI::StringIntArray DebugCLI::commandArray[] =
59 { "break", CMD_BREAK },
60 { "bt", INFO_STACK_CMD },
61 { "continue", CMD_CONTINUE },
62 { "delete", CMD_DELETE },
63 { "finish", CMD_FINISH },
64 { "help", CMD_HELP },
65 { "info", CMD_INFO },
66 { "list", CMD_LIST },
67 { "next", CMD_NEXT },
68 { "print", CMD_PRINT },
69 { "pwd", CMD_PWD },
70 { "quit", CMD_QUIT },
71 { "step", CMD_STEP },
72 { "set", CMD_SET },
73 { "show", CMD_SHOW },
74 { "where", INFO_STACK_CMD },
75 { NULL, 0 }
78 /**
79 * Info sub-commands
81 DebugCLI::StringIntArray DebugCLI::infoCommandArray[] =
83 { "arguments", INFO_ARGS_CMD },
84 { "breakpoints", INFO_BREAK_CMD },
85 { "files", INFO_FILES_CMD },
86 { "functions", INFO_FUNCTIONS_CMD },
87 { "locals", INFO_LOCALS_CMD },
88 { "stack", INFO_STACK_CMD },
89 { NULL, 0 }
92 /**
93 * Show sub-commands
95 DebugCLI::StringIntArray DebugCLI::showCommandArray[] =
97 { "break", SHOW_BREAK_CMD },
98 { "functions", SHOW_FUNC_CMD },
99 { "memory", SHOW_MEM_CMD },
100 { "variable", SHOW_VAR_CMD },
101 { NULL, 0 }
104 DebugCLI::DebugCLI(AvmCore *core, Debugger::TraceLevel tracelevel)
105 : Debugger(core, tracelevel)
107 currentSourceLen = 0;
108 warnMissingSource = true;
111 DebugCLI::~DebugCLI()
113 if (currentSource) {
114 delete [] currentSource;
115 currentSource = NULL;
119 char* DebugCLI::nextToken()
121 char *out = currentToken;
122 if (currentToken) {
123 while (*currentToken) {
124 if (*currentToken == ' ' || *currentToken == '\r' || *currentToken == '\n' || *currentToken == '\t') {
125 *currentToken++ = 0;
126 break;
128 currentToken++;
130 currentToken = *currentToken ? currentToken : NULL;
132 return out;
136 * Attempt to match given the given string against our set of commands
137 * @return the command code that was hit.
139 int DebugCLI::determineCommand(DebugCLI::StringIntArray cmdList[],
140 const char *input,
141 int defCmd)
143 if (!input) return INFO_UNKNOWN_CMD;
145 int inputLen = (int)VMPI_strlen(input);
147 // first check for a comment
148 if (input[0] == '#') {
149 return CMD_COMMENT;
152 int match = -1;
153 bool ambiguous = false;
155 for (int i=0; cmdList[i].text; i++) {
156 if (!VMPI_strncmp(input, cmdList[i].text, inputLen)) {
157 if (match != -1) {
158 ambiguous = true;
159 break;
161 match = i;
166 * 3 cases:
167 * - No hits, return unknown and let our caller
168 * dump the error.
169 * - We match unambiguously or we have 1 or more matches
170 * and the input is a single character. We then take the
171 * first hit as our command.
172 * - If we have multiple hits then we dump a 'ambiguous' message
173 * and puke quietly.
175 if (match == -1) {
176 // no command match return unknown
177 return defCmd;
179 // only 1 match or our input is 1 character or first match is exact
180 else if (!ambiguous || inputLen == 1 || !VMPI_strcmp(cmdList[match].text, input)) {
181 return cmdList[match].id;
183 else {
184 // matches more than one command dump message and go
185 core->console << "Ambiguous command '" << input << "': ";
186 bool first = true;
187 for (int i=0; cmdList[i].text; i++) {
188 if (!VMPI_strncmp(cmdList[i].text, input, inputLen)) {
189 if (!first) {
190 core->console << ", ";
191 } else {
192 first = false;
194 core->console << cmdList[i].text;
197 core->console << ".\n";
198 return -1;
202 const char* DebugCLI::commandNumberToCommandName(DebugCLI::StringIntArray cmdList[],
203 int cmdNumber)
205 for (int i = 0; cmdList[i].text; i++)
207 if (cmdList[i].id == cmdNumber)
208 return cmdList[i].text;
211 return "?";
214 bool DebugCLI::printFrame(int k)
216 Atom* ptr;
217 int count, line = -1;
218 SourceInfo* src = NULL;
219 AvmCore* core = AvmCore::getActiveCore();
220 DebugFrame* frame = core->debugger()->frameAt(k);
221 if (frame == NULL) return false;
223 // source information
224 frame->sourceLocation(src, line);
226 core->console << "#" << k << " ";
228 // this
229 Atom a = nullObjectAtom;
230 frame->dhis(a);
231 core->console << core->format(a) << ".";
233 // method
234 MethodInfo* info = functionFor(src, line);
235 if (info) {
236 core->console << info->getMethodName();
237 } else {
238 Stringp name = NULL;
239 if (frame->methodName(name))
240 core->console << name;
241 else
242 core->console << "???";
245 core->console << "(";
247 // dump args
248 frame->arguments(ptr, count);
249 for (int i=0; i<count; i++)
251 // write out the name
252 Stringp nm;
253 if (info && frame->argumentName(i, nm))
254 core->console << nm << "=";
256 core->console << core->format(*ptr++);
257 if (i<count-1)
258 core->console << ",";
260 core->console << ") at ";
261 if (src)
262 core->console << src->name() << ":" << (line) << "\n";
263 else
264 core->console << "???\n";
266 return true;
269 void DebugCLI::bt()
271 AvmCore* core = AvmCore::getActiveCore();
272 //core->stackTrace->dump(core->console);
273 //core->console << '\n';
275 // obtain information about each frame
276 int frameCount = core->debugger()->frameCount();
277 for(int k=0; k<frameCount; k++)
279 printFrame(k);
283 MethodInfo* DebugCLI::functionFor(SourceInfo* src, int line)
285 MethodInfo* info = NULL;
286 if (src)
288 // find the function at this location
289 int size = src->functionCount();
290 for(int i=0; i<size; i++)
292 MethodInfo* m = src->functionAt(i);
293 if (line >= m->firstSourceLine() && line <= m->lastSourceLine())
295 info = m;
296 break;
300 return info;
303 // zero based
304 char* DebugCLI::lineStart(int linenum)
306 if (!currentSource && currentFile)
307 setCurrentSource(currentFile);
309 if (!currentSource) {
310 return NULL;
313 // linenumbers map to zero based array entry
314 char *ptr = currentSource;
315 for (int i=0; i<linenum; i++) {
316 // Scan to end of line
317 while (*ptr != '\n') {
318 if (!*ptr) {
319 return NULL;
321 ptr++;
323 // Advance past newline
324 ptr++;
326 return ptr;
329 void DebugCLI::displayLines(int line, int count)
331 if (!lineStart(0)) {
332 if (currentFile)
333 core->console << currentFile;
334 else
335 core->console << "<unknown>";
336 core->console <<":"<<line<<" ";
337 } else {
338 int lineAt = line;
339 while(count-- > 0)
341 char* ptr = lineStart(lineAt-1);
342 if (!ptr)
344 #if WRAP_AROUND
345 lineAt = 1;
346 count++; // reset line number to beginning skip this iteration
347 #else
348 break;
349 #endif
351 else
353 core->console << (lineAt) << ": ";
354 while (*ptr && *ptr != '\n')
355 core->console << *ptr++;
356 core->console << '\n';
357 lineAt++;
363 void DebugCLI::list(const char* line)
365 int currentLine = (core->callStack) ? core->callStack->linenum() : 0;
366 int linenum = (line) ? VMPI_atoi(line) : currentLine;
367 displayLines(linenum, 10);
370 void DebugCLI::printIP()
372 int line = (core->callStack) ? core->callStack->linenum() : 0;
373 displayLines(line, 1);
376 void DebugCLI::breakpoint(char *location)
378 Stringp filename = currentFile;
379 char *colon = VMPI_strchr(location, ':');
381 if (colon) {
382 *colon = 0;
383 filename = core->internStringLatin1(location);
384 location = colon+1;
387 if (abcCount() == 0) {
388 core->console << "No abc file loaded\n";
389 return;
392 SourceFile* sourceFile = NULL;
393 for (int i = 0, n = abcCount(); i < n; ++i)
395 AbcFile* abcFile = (AbcFile*)abcAt(i);
396 sourceFile = abcFile->sourceNamed(filename);
397 if (sourceFile)
398 break;
401 if (sourceFile == NULL) {
402 core->console << "No source available; can't set breakpoint.\n";
403 return;
406 int targetLine = VMPI_atoi(location);
408 int breakpointId = ++breakpointCount;
410 if (breakpointSet(sourceFile, targetLine)) {
411 core->console << "Breakpoint " << breakpointId << ": file "
412 << filename
413 << ", " << (targetLine) << ".\n";
415 BreakAction *breakAction = new (core->GetGC()) BreakAction(sourceFile,
416 breakpointId,
417 filename,
418 targetLine);
419 breakAction->prev = lastBreakAction;
420 if (lastBreakAction) {
421 lastBreakAction->next = breakAction;
422 } else {
423 firstBreakAction = breakAction;
425 lastBreakAction = breakAction;
426 } else {
427 core->console << "Could not locate specified line.\n";
431 void DebugCLI::showBreakpoints()
433 BreakAction *breakAction = firstBreakAction;
434 while (breakAction) {
435 breakAction->print(core->console);
436 breakAction = breakAction->next;
440 void DebugCLI::deleteBreakpoint(char *idstr)
442 int id = VMPI_atoi(idstr);
444 BreakAction *breakAction = firstBreakAction;
445 while (breakAction) {
446 if (breakAction->id == id) {
447 break;
449 breakAction = breakAction->next;
452 if (breakAction) {
453 if (breakAction->prev) {
454 breakAction->prev->next = breakAction->next;
455 } else {
456 firstBreakAction = breakAction->next;
458 if (breakAction->next) {
459 breakAction->next->prev = breakAction->prev;
460 } else {
461 lastBreakAction = breakAction->prev;
463 if (breakpointClear(breakAction->sourceFile, breakAction->linenum)) {
464 core->console << "Breakpoint " << id << " deleted.\n";
465 } else {
466 core->console << "Internal error; could not delete breakpoint.\n";
468 } else {
469 core->console << "Could not find breakpoint.\n";
473 Atom DebugCLI::autoAtomAt(DebugFrame* frame, int index, AutoVarKind kind) {
474 Atom* arr;
475 int count;
476 bool success;
477 switch (kind) {
478 case AUTO_LOCAL:
479 success = frame->locals(arr, count);
480 break;
482 case AUTO_ARGUMENT:
483 success = frame->arguments(arr, count);
484 break;
485 default:
486 AvmAssert(false);
487 return unreachableAtom;
489 if (success) {
490 if (index >= 0 && index < count) {
491 return arr[index];
494 return unreachableAtom;
497 // interactive
498 Atom DebugCLI::autoAtomKindAt(int frameNumber, int autoIndex, AutoVarKind kind) {
499 AvmCore* core = AvmCore::getActiveCore();
500 DebugFrame* frame = core->debugger()->frameAt(frameNumber);
501 if (!frame) return unreachableAtom;
502 else return atomKind(autoAtomAt(frame, autoIndex, kind));
505 ScriptObject* DebugCLI::autoVarAsObject(int frameNumber, int index, AutoVarKind kind)
507 AvmCore* core = AvmCore::getActiveCore();
508 DebugFrame* frame = core->debugger()->frameAt(frameNumber);
509 if (!frame) return NULL; // should have tested for error earlier
510 return AvmCore::atomToScriptObject(autoAtomAt(frame, index, kind));
513 Stringp DebugCLI::autoVarAsString(int frameNumber, int index, AutoVarKind kind)
515 AvmCore* core = AvmCore::getActiveCore();
516 DebugFrame* frame = core->debugger()->frameAt(frameNumber);
517 if (!frame) return NULL; // should have tested for error earlier
518 return AvmCore::atomToString(autoAtomAt(frame, index, kind));
521 bool DebugCLI::autoVarAsBoolean(int frameNumber, int index, AutoVarKind kind)
523 AvmCore* core = AvmCore::getActiveCore();
524 DebugFrame* frame = core->debugger()->frameAt(frameNumber);
525 if (!frame) return false; // should have tested for error earlier
526 Atom value = autoAtomAt(frame, index, kind);
527 return value == trueAtom ? true : false;
530 double DebugCLI::autoVarAsInteger(int frameNumber, int index, AutoVarKind kind) {
531 AvmCore* core = AvmCore::getActiveCore();
532 DebugFrame* frame = core->debugger()->frameAt(frameNumber);
533 if (!frame) return MathUtils::kNaN; // should have tested for error earlier
534 return AvmCore::number_d(autoAtomAt(frame, index, kind));
537 double DebugCLI::autoVarAsDouble(int frameNumber, int index, AutoVarKind kind) {
538 AvmCore* core = AvmCore::getActiveCore();
539 DebugFrame* frame = core->debugger()->frameAt(frameNumber);
540 if (!frame) return MathUtils::kNaN; // should have tested for error earlier
541 return AvmCore::atomToDouble(autoAtomAt(frame, index, kind));
544 bool DebugCLI::locals(int frameNumber)
546 Atom* ptr;
547 int count, line;
548 SourceInfo* src = NULL;
549 AvmCore* core = AvmCore::getActiveCore();
550 DebugFrame* frame = core->debugger()->frameAt(frameNumber);
551 if (frame == NULL) return false;
553 // source information
554 frame->sourceLocation(src, line);
556 // method
557 MethodInfo* info = functionFor(src, line);
558 if (info) {
559 frame->locals(ptr, count);
560 for(int i=0; i<count; i++) {
561 // write out the name
562 Stringp nm = info->getLocalName(i);
563 if (nm != core->kundefined)
564 core->console << i << ": " << nm;
565 else
566 core->console << "<local_" << i << ">";
567 core->console << " = " << core->format(*ptr++) << "\n";
569 return true;
571 return false;
574 bool DebugCLI::arguments(int frameNumber)
576 int count;
577 Atom* arr;
578 AvmCore* core = AvmCore::getActiveCore();
579 DebugFrame* frame = core->debugger()->frameAt(frameNumber);
580 if (frame && frame->arguments(arr, count)) {
581 for (int i = 0; i < count; i++) {
582 Stringp nm = NULL;
583 frame->argumentName(i, nm);
584 core->console << i << ": " << nm << " = " << core->format(*arr++) << "\n";
586 return true;
588 return false;
591 Atom DebugCLI::ease2Atom(const char* to, Atom baseline)
593 // first make a string out of the value
594 Atom a = core->newStringLatin1(to)->atom();
596 // using the type of baseline try to convert to into an appropriate Atom
597 if (core->isNumber(baseline))
598 return core->numberAtom(a);
599 else if (core->isBoolean(baseline))
600 return AvmCore::booleanAtom(a);
602 return nullStringAtom;
605 void DebugCLI::set()
607 const char* what = nextToken();
608 const char* equ = nextToken();
609 const char* to = nextToken();
610 if (!to || !equ || !what || *equ != '=')
612 core->console << " Bad format, should be: 'set {variable} = {value}' \n";
614 else
616 // look for the varable in our locals or args.
617 Atom* ptr;
618 int count, line;
619 SourceInfo* src;
620 DebugFrame* frame = core->debugger()->frameAt(0);
622 // source information
623 frame->sourceLocation(src, line);
624 if (!src)
626 core->console << "Unable to locate debug information for current source file, so no local or argument names known\n";
627 return;
630 // method
631 MethodInfo* info = functionFor(src, line);
632 if (!info)
634 core->console << "Unable to find method debug information, so no local or argument names known\n";
635 return;
638 frame->arguments(ptr, count);
639 for(int i=0; i<count; i++)
641 Stringp arg = info->getArgName(i);
642 if (arg->equalsLatin1(what))
644 // match!
645 Atom a = ease2Atom(to, ptr[i]);
646 if (a == undefinedAtom)
647 core->console << " Type mismatch : current value is " << core->format(ptr[i]);
648 else
649 frame->setArgument(i, a);
650 return;
654 frame->locals(ptr, count);
655 for(int i=0; i<count; i++)
657 Stringp local = info->getLocalName(i);
658 if ( local->equalsLatin1(what))
660 // match!
661 Atom a = ease2Atom(to, ptr[i]);
662 if (a == undefinedAtom)
663 core->console << " Type mismatch : current value is " << core->format(ptr[i]);
664 else
665 frame->setLocal(i, a);
666 return;
672 void DebugCLI::print(const char *name)
674 if (!name) {
675 core->console << "Must specify a name.\n";
676 return;
679 // todo deal with exceptions
680 Multiname mname(
681 core->getAnyPublicNamespace(),
682 core->internStringLatin1(name)
685 #if 0
686 // rick fixme
687 Atom objAtom = env->findproperty(outerScope, scopes, extraScopes, &mname, false);
688 Atom valueAtom = env->getproperty(objAtom, &mname);
689 core->console << core->string(valueAtom) << '\n';
690 #endif
693 bool DebugCLI::filterException(Exception *exception, bool /*willBeCaught*/)
695 // Filter exceptions when -d switch specified
696 if (activeFlag) {
697 core->console << "Exception has been thrown:\n"
698 << core->string(exception->atom)
699 << '\n';
700 enterDebugger();
701 return true;
703 return false;
706 void DebugCLI::info()
708 char *command = nextToken();
709 int cmd = infoCommandFor(command);
711 switch (cmd) {
712 case -1:
713 // ambiguous, we already printed error message
714 break;
715 case INFO_LOCALS_CMD:
716 locals(0);
717 break;
718 case INFO_BREAK_CMD:
719 showBreakpoints();
720 break;
721 case INFO_UNKNOWN_CMD:
722 core->console << "Unknown command.\n";
723 break;
724 default:
725 core->console << "Command not implemented.\n";
726 break;
730 void DebugCLI::enterDebugger()
732 setCurrentSource( (core->callStack) ? (core->callStack->filename()) : 0 );
734 for (;;) {
735 printIP();
737 core->console << "(asdb) ";
738 Platform::GetInstance()->getUserInput(commandLine, kMaxCommandLine);
740 commandLine[VMPI_strlen(commandLine)-1] = 0;
742 if (!commandLine[0]) {
743 VMPI_strcpy(commandLine, lastCommand);
744 } else {
745 VMPI_strcpy(lastCommand, commandLine);
748 currentToken = commandLine;
750 char *command = nextToken();
751 int cmd = commandFor(command);
753 switch (cmd) {
754 case -1:
755 // ambiguous, we already printed error message
756 break;
757 case CMD_INFO:
758 info();
759 break;
760 case CMD_BREAK:
761 breakpoint(nextToken());
762 break;
763 case CMD_DELETE:
764 deleteBreakpoint(nextToken());
765 break;
766 case CMD_LIST:
767 list(nextToken());
768 break;
769 case CMD_UNKNOWN:
770 core->console << "Unknown command.\n";
771 break;
772 case CMD_QUIT:
773 Platform::GetInstance()->exit(0);
774 break;
775 case CMD_CONTINUE:
776 return;
777 case CMD_PRINT:
778 print(nextToken());
779 break;
780 case CMD_NEXT:
781 stepOver();
782 return;
783 case INFO_STACK_CMD:
784 bt();
785 break;
786 case CMD_FINISH:
787 stepOut();
788 return;
789 case CMD_STEP:
790 stepInto();
791 return;
792 case CMD_SET:
793 set();
794 break;
795 default:
796 core->console << "Command not implemented.\n";
797 break;
802 void DebugCLI::setCurrentSource(Stringp file)
804 if (!file)
805 return;
807 currentFile = file;
809 if (currentSource) {
810 delete [] currentSource;
811 currentSource = NULL;
812 currentSourceLen = 0;
815 // Open this file and suck it into memory
816 StUTF8String currentFileUTF8(currentFile);
817 FileInputStream f(currentFileUTF8.c_str());
818 if (f.valid() && ((uint64_t)file->length() < UINT32_T_MAX)) { //cannot handle files > 4GB
819 currentSourceLen = (uint32_t) f.available();
820 currentSource = new char[currentSourceLen+1];
821 f.read(currentSource, currentSourceLen);
822 currentSource[currentSourceLen] = 0;
824 // whip through converting \r\n to space \n
825 for(int64_t i=0; i<currentSourceLen-1;i++) {
826 if (currentSource[i] == '\r' && currentSource[i+1] == '\n')
827 currentSource[i] = ' ';
829 } else if (warnMissingSource) {
830 core->console << "Could not find '" << currentFile << "'. Try running in the same directory as the .as file.\n";
831 warnMissingSource = false;
836 // BreakAction
839 void BreakAction::print(PrintWriter& out)
841 out << id << " at "
842 << filename
843 << ":" << (linenum) << '\n';
846 #endif