Merge remote-tracking branch 'redux/master' into sh4-pool
[tamarin-stm.git] / shell / DebugCLI.cpp
blob13251b8e3da855bb8b0bda0e01bb3e714859061c
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 * NOTE: order matters! For the case of a single character
52 * match, we let the first hit act like an unambiguous match.
54 DebugCLI::StringInt DebugCLI::commandArray[] =
56 { "break", CMD_BREAK },
57 { "bt", INFO_STACK_CMD },
58 { "continue", CMD_CONTINUE },
59 { "delete", CMD_DELETE },
60 { "finish", CMD_FINISH },
61 { "help", CMD_HELP },
62 { "?", CMD_HELP },
63 { "info", CMD_INFO },
64 { "list", CMD_LIST },
65 { "next", CMD_NEXT },
66 { "print", CMD_PRINT },
67 { "quit", CMD_QUIT },
68 { "step", CMD_STEP },
69 { "set", CMD_SET },
70 { "where", INFO_STACK_CMD },
71 { NULL, 0 }
74 /**
75 * Info sub-commands
77 DebugCLI::StringInt DebugCLI::infoCommandArray[] =
79 { "arguments", INFO_ARGS_CMD },
80 { "breakpoints", INFO_BREAK_CMD },
81 { "files", INFO_FILES_CMD },
82 { "functions", INFO_FUNCTIONS_CMD },
83 { "locals", INFO_LOCALS_CMD },
84 { "stack", INFO_STACK_CMD },
85 { NULL, 0 }
88 DebugCLI::DebugCLI(AvmCore *core, Debugger::TraceLevel tracelevel)
89 : Debugger(core, tracelevel)
91 currentSourceLen = 0;
92 warnMissingSource = true;
95 DebugCLI::~DebugCLI()
97 if (currentSource) {
98 delete [] currentSource;
99 currentSource = NULL;
103 char* DebugCLI::nextToken()
105 char *out = currentToken;
106 if (currentToken) {
107 while (*currentToken) {
108 if (*currentToken == ' ' || *currentToken == '\r' || *currentToken == '\n' || *currentToken == '\t') {
109 *currentToken++ = 0;
110 break;
112 currentToken++;
114 currentToken = *currentToken ? currentToken : NULL;
116 return out;
120 * Attempt to match given the given string against our set of commands
121 * @return the command code that was hit.
123 int DebugCLI::determineCommand(DebugCLI::StringInt cmdList[],
124 const char *input,
125 int defCmd)
127 if (!input) return INFO_UNKNOWN_CMD;
129 int inputLen = (int)VMPI_strlen(input);
131 // first check for a comment
132 if (input[0] == '#') {
133 return CMD_COMMENT;
136 int match = -1;
137 bool ambiguous = false;
139 for (int i=0; cmdList[i].text; i++) {
140 if (!VMPI_strncmp(input, cmdList[i].text, inputLen)) {
141 if (match != -1) {
142 ambiguous = true;
143 break;
145 match = i;
150 * 3 cases:
151 * - No hits, return unknown and let our caller
152 * dump the error.
153 * - We match unambiguously or we have 1 or more matches
154 * and the input is a single character. We then take the
155 * first hit as our command.
156 * - If we have multiple hits then we dump a 'ambiguous' message
157 * and puke quietly.
159 if (match == -1) {
160 // no command match return unknown
161 return defCmd;
163 // only 1 match or our input is 1 character or first match is exact
164 else if (!ambiguous || inputLen == 1 || !VMPI_strcmp(cmdList[match].text, input)) {
165 return cmdList[match].id;
167 else {
168 // matches more than one command dump message and go
169 core->console << "Ambiguous command '" << input << "': ";
170 bool first = true;
171 for (int i=0; cmdList[i].text; i++) {
172 if (!VMPI_strncmp(cmdList[i].text, input, inputLen)) {
173 if (!first) {
174 core->console << ", ";
175 } else {
176 first = false;
178 core->console << cmdList[i].text;
181 core->console << ".\n";
182 return -1;
186 const char* DebugCLI::commandNumberToCommandName(DebugCLI::StringInt cmdList[],
187 int cmdNumber)
189 for (int i = 0; cmdList[i].text; i++)
191 if (cmdList[i].id == cmdNumber)
192 return cmdList[i].text;
195 return "?";
198 bool DebugCLI::printFrame(int k)
200 Atom* ptr;
201 int count, line = -1;
202 SourceInfo* src = NULL;
203 AvmCore* core = AvmCore::getActiveCore();
204 DebugFrame* frame = core->debugger()->frameAt(k);
205 if (frame == NULL) return false;
207 // source information
208 frame->sourceLocation(src, line);
210 core->console << "#" << k << " ";
212 // this
213 Atom a = nullObjectAtom;
214 frame->dhis(a);
215 core->console << asAtom(a) << ".";
217 // method
218 MethodInfo* info = functionFor(src, line);
219 if (info) {
220 core->console << info->getMethodName();
221 } else {
222 Stringp name = NULL;
223 if (frame->methodName(name))
224 core->console << name;
225 else
226 core->console << "???";
229 core->console << "(";
231 // dump args
232 frame->arguments(ptr, count);
233 for (int i=0; i<count; i++)
235 // write out the name
236 if (info)
238 Stringp nm = info->getArgName(i);
239 if (nm != core->kundefined)
240 core->console << nm << "=";
243 core->console << asAtom(*ptr++);
244 if (i<count-1)
245 core->console << ",";
247 core->console << ") at ";
248 if (src)
249 core->console << src->name() << ":" << (line) << "\n";
250 else
251 core->console << "???\n";
253 return true;
256 void DebugCLI::bt()
258 AvmCore* core = AvmCore::getActiveCore();
259 //core->stackTrace->dump(core->console);
260 //core->console << '\n';
262 // obtain information about each frame
263 int frameCount = core->debugger()->frameCount();
264 for(int k=0; k<frameCount; k++)
266 printFrame(k);
270 void DebugCLI::help()
272 StringInt* cmd;
274 // "help info" shows sub-commands of the "info" command
275 char* next = nextToken();
276 if (next && commandFor(next) == CMD_INFO)
277 cmd = infoCommandArray;
278 else
279 cmd = commandArray;
281 while (cmd->text)
283 core->console << cmd->text;
284 if (cmd->id == CMD_INFO)
285 core->console << " (see 'help " << cmd->text << "' for sub-commands)";
286 core->console << '\n';
287 cmd++;
291 MethodInfo* DebugCLI::functionFor(SourceInfo* src, int line)
293 MethodInfo* info = NULL;
294 if (src)
296 // find the function at this location
297 int size = src->functionCount();
298 for(int i=0; i<size; i++)
300 MethodInfo* m = src->functionAt(i);
301 if (line >= m->firstSourceLine() && line <= m->lastSourceLine())
303 info = m;
304 break;
308 return info;
311 // zero based
312 char* DebugCLI::lineStart(int linenum)
314 if (!currentSource && currentFile)
315 setCurrentSource(currentFile);
317 if (!currentSource) {
318 return NULL;
321 // linenumbers map to zero based array entry
322 char *ptr = currentSource;
323 for (int i=0; i<linenum; i++) {
324 // Scan to end of line
325 while (*ptr != '\n') {
326 if (!*ptr) {
327 return NULL;
329 ptr++;
331 // Advance past newline
332 ptr++;
334 return ptr;
337 void DebugCLI::displayLines(int line, int count)
339 if (!lineStart(0)) {
340 if (currentFile)
341 core->console << currentFile;
342 else
343 core->console << "<unknown>";
344 core->console <<":"<<line<<" ";
345 } else {
346 int lineAt = line;
347 while(count-- > 0)
349 char* ptr = lineStart(lineAt-1);
350 if (!ptr)
352 #if WRAP_AROUND
353 lineAt = 1;
354 count++; // reset line number to beginning skip this iteration
355 #else
356 break;
357 #endif
359 else
361 core->console << (lineAt) << ": ";
362 while (*ptr && *ptr != '\n')
363 core->console << *ptr++;
364 core->console << '\n';
365 lineAt++;
371 void DebugCLI::list(const char* line)
373 int currentLine = (core->callStack) ? core->callStack->linenum() : 0;
374 int linenum = (line) ? VMPI_atoi(line) : currentLine;
375 displayLines(linenum, 10);
378 void DebugCLI::printIP()
380 int line = (core->callStack) ? core->callStack->linenum() : 0;
381 displayLines(line, 1);
384 void DebugCLI::breakpoint(char *location)
386 if (!location)
388 core->console << "Bad format, should be: 'break [filename:]line'\n";
389 return;
392 Stringp filename = currentFile;
393 char *colon = VMPI_strchr(location, ':');
395 if (colon) {
396 *colon = 0;
397 filename = core->internStringLatin1(location);
398 location = colon+1;
401 if (abcCount() == 0) {
402 core->console << "No abc file loaded\n";
403 return;
406 SourceFile* sourceFile = NULL;
407 for (int i = 0, n = abcCount(); i < n; ++i)
409 AbcFile* abcFile = (AbcFile*)abcAt(i);
410 sourceFile = abcFile->sourceNamed(filename);
411 if (sourceFile)
412 break;
415 if (sourceFile == NULL) {
416 core->console << "No source available; can't set breakpoint.\n";
417 return;
420 int targetLine = VMPI_atoi(location);
422 if (breakpointSet(sourceFile, targetLine)) {
423 int breakpointId = ++breakpointCount;
424 core->console << "Breakpoint " << breakpointId << ": file "
425 << filename
426 << ", " << (targetLine) << ".\n";
428 BreakAction *breakAction = new (core->GetGC()) BreakAction(sourceFile,
429 breakpointId,
430 filename,
431 targetLine);
432 breakAction->prev = lastBreakAction;
433 if (lastBreakAction) {
434 lastBreakAction->next = breakAction;
435 } else {
436 firstBreakAction = breakAction;
438 lastBreakAction = breakAction;
439 } else {
440 core->console << "Could not locate specified line.\n";
444 void DebugCLI::showBreakpoints()
446 BreakAction *breakAction = firstBreakAction;
447 while (breakAction) {
448 breakAction->print(core->console);
449 breakAction = breakAction->next;
453 void DebugCLI::deleteBreakpoint(char *idstr)
455 int id = VMPI_atoi(idstr);
457 BreakAction *breakAction = firstBreakAction;
458 while (breakAction) {
459 if (breakAction->id == id) {
460 break;
462 breakAction = breakAction->next;
465 if (breakAction) {
466 if (breakAction->prev) {
467 breakAction->prev->next = breakAction->next;
468 } else {
469 firstBreakAction = breakAction->next;
471 if (breakAction->next) {
472 breakAction->next->prev = breakAction->prev;
473 } else {
474 lastBreakAction = breakAction->prev;
476 if (breakpointClear(breakAction->sourceFile, breakAction->linenum)) {
477 core->console << "Breakpoint " << id << " deleted.\n";
478 } else {
479 core->console << "Internal error; could not delete breakpoint.\n";
481 } else {
482 core->console << "Could not find breakpoint.\n";
486 void DebugCLI::locals(int frameNumber)
488 Atom* ptr;
489 int count, line;
490 SourceInfo* src = NULL;
491 AvmCore* core = AvmCore::getActiveCore();
492 DebugFrame* frame = core->debugger()->frameAt(frameNumber);
493 if (frame == NULL) return;
495 // source information
496 frame->sourceLocation(src, line);
498 // method
499 MethodInfo* info = functionFor(src, line);
500 if (info) {
501 frame->locals(ptr, count);
502 for(int i=0; i<count; i++) {
503 // write out the name
504 Stringp nm = info->getLocalName(i);
505 core->console << i << ": ";
506 if (nm != core->kundefined)
507 core->console << nm;
508 else
509 core->console << "<local_" << i << ">";
510 core->console << " = " << asAtom(*ptr++) << "\n";
515 void DebugCLI::arguments(int frameNumber)
517 int count;
518 Atom* arr;
519 AvmCore* core = AvmCore::getActiveCore();
520 DebugFrame* frame = core->debugger()->frameAt(frameNumber);
521 if (frame && frame->arguments(arr, count)) {
522 for (int i = 0; i < count; i++) {
523 Stringp nm = NULL;
524 frame->argumentName(i, nm);
525 core->console << i << ": " << nm << " = " << asAtom(*arr++) << "\n";
530 Atom DebugCLI::ease2Atom(const char* to, Atom baseline)
532 // first make a string out of the value
533 Atom a = core->newStringLatin1(to)->atom();
535 // using the type of baseline try to convert to into an appropriate Atom
536 if (core->isNumber(baseline))
537 return core->numberAtom(a);
538 else if (core->isBoolean(baseline))
539 return AvmCore::booleanAtom(a);
541 return nullStringAtom;
545 * Given a pointer, trims leading and trailing whitespace. Note, this will
546 * modify the buffer -- it trims trailing whitespace by writing a null
547 * character.
549 * Returns the first offset past leading whitespace.
551 static char* trim(char* ptr)
553 while (*ptr && (*ptr==' ' || *ptr=='\t'))
554 ++ptr;
555 char* start = ptr;
557 while (*ptr)
558 ++ptr;
560 while (ptr>start && (ptr[-1]==' ' || ptr[-1]=='\t'))
561 --ptr;
562 *ptr = '\0';
564 return start;
567 void DebugCLI::set()
569 char* line = currentToken;
570 char* expr = line;
571 char* equ = VMPI_strchr(currentToken, '=');
572 char* value = NULL;
573 if (equ)
575 *equ = '\0';
576 value = equ+1;
577 expr = trim(expr);
578 value = trim(value);
581 if (!expr || !*expr || !value || !*value)
583 core->console << " Bad format, should be: 'set {variable} = {value}' \n";
585 else
587 bool sawTrailingDot = false;
588 IValue* lvalue = evaluate(expr, &sawTrailingDot);
589 IValue* rvalue = evaluate(value, &sawTrailingDot);
590 AvmAssert(lvalue != NULL);
591 AvmAssert(rvalue != NULL);
592 if (!lvalue->isLValue())
593 core->console << " Can't assign to " << expr << '\n';
594 else
595 lvalue->set(rvalue->get());
599 class ScopeBuilder : public CallStackNode::IScopeChainEnumerator
601 public:
602 ScopeBuilder(MMgc::GC* gc) : scopeChain(gc, kListInitialCapacity) { }
603 /*virtual*/ void addScope(Atom scope)
605 // null/undefined are not legal entries for scope, but
606 // enumerateScopeChainAtoms() can hand them to us, for entries
607 // within the max_scope range that aren't in use. just ignore 'em.
609 if (AvmCore::isNullOrUndefined(scope))
610 return;
612 scopeChain.add(scope);
615 AtomList scopeChain;
618 void DebugCLI::throwUndefinedVarError(const char* name)
620 Stringp errorMessage = core->newStringLatin1("Error: Variable ");
621 errorMessage = errorMessage->appendLatin1(name);
622 errorMessage = errorMessage->appendLatin1(" is not defined.");
623 core->throwAtom(errorMessage->atom());
626 IValue* DebugCLI::getValue(const char *name)
628 DebugStackFrame* frame = (DebugStackFrame*)frameAt(0);
629 Atom thisAtom;
630 frame->dhis(thisAtom);
632 Stringp namestr = core->internStringLatin1(name, -1);
633 if (VMPI_strcmp(name, "this") == 0)
634 return new (core->gc) ConstantValue(thisAtom);
635 if (VMPI_strcmp(name, "NaN") == 0)
636 return new (core->gc) ConstantValue(core->kNaN);
637 if (namestr == core->kfalse)
638 return new (core->gc) ConstantValue(falseAtom);
639 if (namestr == core->ktrue)
640 return new (core->gc) ConstantValue(trueAtom);
641 if (namestr == core->knull)
642 return new (core->gc) ConstantValue(nullObjectAtom);
643 if (namestr == core->kundefined)
644 return new (core->gc) ConstantValue(undefinedAtom);
645 if (name[0] == '-' || VMPI_isdigit(name[0]))
646 return new (core->gc) ConstantValue(core->numberAtom(namestr->atom()));
647 if (name[0] == '\'' || name[0] == '"') // String literal
649 int32_t length = namestr->length();
650 if (length >= 2 && namestr->charAt(length-1) == namestr->charAt(0))
652 // Note, this doesn't do any escaping
653 return new (core->gc) ConstantValue(namestr->substr(1, length-2)->atom());
656 if (name[0] == '<') // XML or XMLList literal
658 if (AvmCore::isObject(thisAtom))
660 Toplevel* toplevel = AvmCore::atomToScriptObject(thisAtom)->toplevel();
661 if (name[1] == '>')
662 return new (core->gc) ConstantValue(toplevel->xmlListClass()->ToXMLList(namestr->atom()));
663 else
664 return new (core->gc) ConstantValue(toplevel->xmlClass()->ToXML(namestr->atom()));
668 Atom* ptr;
669 int count, line;
670 SourceInfo* src;
672 // See if the name matches a function argument or local
673 frame->sourceLocation(src, line);
674 if (src)
676 MethodInfo* info = functionFor(src, line);
677 if (info)
679 // see if the name matches a function argument
680 frame->arguments(ptr, count);
681 for(int i=0; i<count; i++)
683 Stringp arg = info->getArgName(i);
684 if (arg->equalsLatin1(name))
686 // match!
687 return new (core->gc) ArgumentValue(frame, i);
691 // see if the name matches a local
692 frame->locals(ptr, count);
693 for(int i=0; i<count; i++)
695 Stringp local = info->getLocalName(i);
696 if ( local->equalsLatin1(name))
698 // match!
699 return new (core->gc) LocalValue(frame, i);
705 // See if the name matches a property on the scope chain (which includes
706 // the 'this' object)
707 if (frame->trace)
709 MethodEnv* env = frame->trace->env();
710 if (env)
712 ScopeBuilder scopeBuilder(core->GetGC());
713 frame->trace->enumerateScopeChainAtoms(scopeBuilder);
715 for (uint32_t i=0, n=scopeBuilder.scopeChain.length(); i<n; ++i)
717 StPropertyFinder finder(core, scopeBuilder.scopeChain.get(i), name);
718 finder.iterate();
719 if (finder.value)
720 return finder.value;
725 // Look for globals like 'Number'
726 MethodEnv* env = frame->trace->env();
727 if (env)
729 Multiname mn(core->getAnyPublicNamespace(),
730 core->internStringLatin1(name));
731 ScriptEnv* script = core->domainMgr()->findScriptEnvInDomainEnvByMultiname(env->domainEnv(), mn);
732 if (script != (ScriptEnv*)BIND_NONE && script != (ScriptEnv*)BIND_AMBIGUOUS)
734 ScriptObject* global = script->global;
735 if (global)
737 return new (core->gc) PropertyValue(global, mn);
742 throwUndefinedVarError(name);
743 return NULL; // unreachable
747 * Accepts expressions of these forms:
748 * foo
749 * foo.bar
750 * foo.bar.baz
752 * Also allows literals: boolean, string, numeric, XML/XMLList,
753 * null, undefined.
755 * If the expression ends with a dot, e.g. "foo.bar.", then
756 * *outSawTrailingDot is set to true. This is used by the
757 * "print" command to allow "print foo." to print all members
758 * of variable "foo".
760 IValue* DebugCLI::evaluate(char *expr, bool* outSawTrailingDot)
762 const char *name;
763 IValue* value = NULL;
764 bool firstPart = true;
766 *outSawTrailingDot = false;
768 while (expr)
770 name = expr;
771 char *dot = VMPI_strchr(expr, '.');
772 if (dot)
774 *dot = '\0';
775 name = expr;
776 expr = dot+1;
778 else
780 name = expr;
781 expr = NULL;
784 if (firstPart)
786 value = getValue(name);
787 AvmAssert(value != NULL);
789 else
791 if (*name)
793 // "foo.bar" -- find property "bar" of object "foo"
794 Atom parent = value->get();
795 StPropertyFinder finder(core, parent, name);
796 finder.iterate();
797 value = finder.value;
798 if (!value)
800 if (AvmCore::isObject(parent))
802 // Since we didn't find an existing property, we'll just construct
803 // one. If the parent object is dynamic, then calling get() on the
804 // PropertyVaulue we create will return undefined, and calling set()
805 // on it will define a new property. If the parent object is not
806 // dynamic, then get() and set() will throw exceptions. Either way,
807 // that's the correct behavior.
808 Multiname mn(core->getAnyPublicNamespace(),
809 core->internStringLatin1(name));
810 value = new (core->gc) PropertyValue(AvmCore::atomToScriptObject(parent), mn);
812 else
814 if (dot) *dot = '.'; // restore for our caller
815 throwUndefinedVarError(name);
816 return NULL; // unreachable
820 else
822 // "foo."
823 *outSawTrailingDot = true;
827 if (dot) *dot = '.'; // restore for our caller
828 firstPart = false;
831 return value;
835 * Accepts expressions of these forms:
836 * foo
837 * foo.bar
838 * foo.bar.baz
839 * Also, allows a trailing dot, e.g. "foo.bar.", in which case all properties
840 * of foo.bar will be displayed.
842 void DebugCLI::print(char *expr)
844 bool sawTrailingDot = false;
845 IValue* value = evaluate(expr, &sawTrailingDot);
846 AvmAssert(value != NULL);
848 if (sawTrailingDot)
850 // "foo." -- print all of object foo's properties
851 PropertyPrinter printer(core, this, value->get());
852 printer.iterate();
854 else
856 core->console << formatValue(value->get()) << '\n';
860 bool DebugCLI::filterException(Exception *exception, bool /*willBeCaught*/)
862 // Filter exceptions when -d switch specified
863 if (activeFlag) {
864 core->console << "Exception has been thrown:\n"
865 << core->string(exception->atom)
866 << '\n';
867 enterDebugger();
868 return true;
870 return false;
873 void DebugCLI::info()
875 char *command = nextToken();
876 int cmd = infoCommandFor(command);
878 switch (cmd) {
879 case -1:
880 // ambiguous, we already printed error message
881 break;
882 case INFO_ARGS_CMD:
883 arguments(0);
884 break;
885 case INFO_LOCALS_CMD:
886 locals(0);
887 break;
888 case INFO_BREAK_CMD:
889 showBreakpoints();
890 break;
891 case INFO_UNKNOWN_CMD:
892 core->console << "Unknown command.\n";
893 break;
894 default:
895 core->console << "Command not implemented.\n";
896 break;
900 bool DebugCLI::debuggerInterruptOnEnter = false;
901 void DebugCLI::enterDebugger()
903 setCurrentSource( (core->callStack) ? (core->callStack->filename()) : 0 );
904 if (debuggerInterruptOnEnter) {
905 VMPI_debugBreak();
906 return;
909 for (;;) {
910 printIP();
912 core->console << "(asdb) ";
913 if (Platform::GetInstance()->getUserInput(commandLine, kMaxCommandLine) == NULL)
914 Platform::GetInstance()->exit(0);
916 commandLine[VMPI_strlen(commandLine)-1] = 0;
918 if (!commandLine[0]) {
919 VMPI_strcpy(commandLine, lastCommand);
920 } else {
921 VMPI_strcpy(lastCommand, commandLine);
924 currentToken = commandLine;
926 char *command = nextToken();
927 int cmd = commandFor(command);
929 TRY(core, kCatchAction_Ignore)
931 switch (cmd) {
932 case -1:
933 // ambiguous, we already printed error message
934 break;
935 case CMD_COMMENT:
936 break;
937 case CMD_INFO:
938 info();
939 break;
940 case CMD_BREAK:
941 breakpoint(nextToken());
942 break;
943 case CMD_DELETE:
944 deleteBreakpoint(nextToken());
945 break;
946 case CMD_LIST:
947 list(nextToken());
948 break;
949 case CMD_UNKNOWN:
950 core->console << "Unknown command.\n";
951 break;
952 case CMD_QUIT:
953 Platform::GetInstance()->exit(0);
954 break;
955 case CMD_CONTINUE:
956 return;
957 case CMD_PRINT:
958 print(nextToken());
959 break;
960 case CMD_NEXT:
961 stepOver();
962 return;
963 case INFO_STACK_CMD:
964 bt();
965 break;
966 case CMD_FINISH:
967 stepOut();
968 return;
969 case CMD_HELP:
970 help();
971 break;
972 case CMD_STEP:
973 stepInto();
974 return;
975 case CMD_SET:
976 set();
977 break;
978 default:
979 core->console << "Command not implemented.\n";
980 break;
983 CATCH(Exception *exception)
985 core->console << core->string(exception->atom) << '\n';
987 END_CATCH
988 END_TRY
992 void DebugCLI::setCurrentSource(Stringp file)
994 if (!file)
995 return;
997 currentFile = file;
999 if (currentSource) {
1000 delete [] currentSource;
1001 currentSource = NULL;
1002 currentSourceLen = 0;
1005 // Open this file and suck it into memory
1006 StUTF8String currentFileUTF8(currentFile);
1007 FileInputStream f(currentFileUTF8.c_str());
1008 if (f.valid() && ((uint64_t)file->length() < UINT32_T_MAX)) { //cannot handle files > 4GB
1009 currentSourceLen = (uint32_t) f.available();
1010 currentSource = new char[currentSourceLen+1];
1011 f.read(currentSource, currentSourceLen);
1012 currentSource[currentSourceLen] = 0;
1014 // whip through converting \r\n to space \n
1015 for(int64_t i=0; i<currentSourceLen-1;i++) {
1016 if (currentSource[i] == '\r' && currentSource[i+1] == '\n')
1017 currentSource[i] = ' ';
1019 } else if (warnMissingSource) {
1020 core->console << "Could not find '" << currentFile << "'. Try running in the same directory as the .as file.\n";
1021 warnMissingSource = false;
1025 Stringp DebugCLI::formatValue(Atom value)
1027 // An experiment: Printing XML and XMLList variables with their full toXMLString() form.
1028 // if (core->isXMLorXMLList(value))
1029 // {
1030 // ScriptObject* object = AvmCore::atomToScriptObject(value);
1031 // Multiname mn(core->getAnyPublicNamespace(), core->internStringLatin1("toXMLString"));
1032 // Atom thisAtom = undefinedAtom;
1033 // return core->string(object->toplevel()->callproperty(value, &mn, 1, &thisAtom, object->vtable));
1034 // }
1036 StringBuffer sb(core);
1037 sb << asAtom(value);
1038 return sb.toString();
1042 // BreakAction
1045 void BreakAction::print(PrintWriter& out)
1047 out << id << " at "
1048 << filename
1049 << ":" << (linenum) << '\n';
1052 PropertyIterator::PropertyIterator(AvmCore* core, Atom atom)
1053 : core(core)
1055 if (AvmCore::isObject(atom))
1056 object = AvmCore::atomToScriptObject(atom);
1057 else
1058 object = NULL;
1061 void PropertyIterator::iterate()
1063 if (!object)
1064 return;
1066 // Iterate over the object's traits
1067 Traits* t = object->traits();
1068 while (t)
1070 const TraitsBindings* bindings = t->getTraitsBindings();
1071 StTraitsBindingsIterator iter(bindings);
1072 while (iter.next()) {
1073 Stringp key = iter.key();
1074 if (!key)
1075 continue;
1076 Binding b = iter.value();
1077 Multiname mn(iter.ns(), key);
1078 if (!process(&mn, AvmCore::bindingKind(b)))
1079 return;
1081 t = t->base;
1084 // Iterate over the object's dynamic properties
1085 int index = 0;
1086 while ((index = object->nextNameIndex(index)) != 0)
1088 Multiname mn(core->getAnyPublicNamespace(), core->string(object->nextName(index)));
1089 if (!process(&mn, BKIND_VAR))
1090 return;
1094 StPropertyFinder::StPropertyFinder(AvmCore* core, Atom atom, const char* propertyname)
1095 : PropertyIterator(core, atom),
1096 value(NULL),
1097 propertyname(propertyname)
1101 bool StPropertyFinder::process(Multiname* key, BindingKind /*bk*/)
1103 if (key->getName()->equalsLatin1(propertyname))
1105 value = new (core->gc) PropertyValue(object, *key);
1106 return false; // stop iterating
1109 return true;
1112 PropertyPrinter::PropertyPrinter(AvmCore* core, DebugCLI* debugger, Atom atom)
1113 : PropertyIterator(core, atom),
1114 debugger(debugger)
1118 bool PropertyPrinter::process(Multiname* key, BindingKind bk)
1120 if (bk == BKIND_CONST || bk == BKIND_VAR || bk == BKIND_GET || bk == BKIND_GETSET)
1122 Atom value = object->toplevel()->getproperty(object->atom(), key, object->vtable);
1123 switch (bk)
1125 case BKIND_CONST:
1126 core->console << "const ";
1127 break;
1128 case BKIND_VAR:
1129 core->console << "var ";
1130 break;
1131 case BKIND_GET:
1132 case BKIND_GETSET:
1133 core->console << "function get ";
1134 break;
1136 core->console << Multiname::FormatNameOnly(key);
1137 if (bk == BKIND_GET || bk == BKIND_GETSET)
1138 core->console << "()";
1139 core->console << " = " << debugger->formatValue(value) << '\n';
1142 return true;
1145 #endif