"Fossies" - the Fresh Open Source Software Archive

Member "AutoHotkey_L-1.1.33.09/source/Debugger.cpp" (8 May 2021, 81222 Bytes) of package /windows/misc/AutoHotkey_L-1.1.33.09.zip:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "Debugger.cpp" see the Fossies "Dox" file reference documentation.

    1 /*
    2 Debugger.cpp - Main body of AutoHotkey debugger engine.
    3 
    4 Original code by Steve Gray.
    5 
    6 This software is provided 'as-is', without any express or implied
    7 warranty. In no event will the authors be held liable for any damages
    8 arising from the use of this software.
    9 
   10 Permission is granted to anyone to use this software for any purpose,
   11 including commercial applications, and to alter it and redistribute it
   12 freely, without restriction.
   13 */
   14 
   15 #include "stdafx.h"
   16 
   17 #include "defines.h"
   18 #include "globaldata.h" // for access to many global vars
   19 #include "script_object.h"
   20 #include "script_com.h"
   21 #include "TextIO.h"
   22 //#include "Debugger.h" // included by globaldata.h
   23 
   24 #ifdef CONFIG_DEBUGGER
   25 
   26 // helper macro for WriteF()
   27 #define U4T(s) CStringUTF8FromTChar(s).GetString()
   28 
   29 #include <ws2tcpip.h>
   30 #include <wspiapi.h> // for getaddrinfo() on versions of Windows earlier than XP.
   31 #include <stdarg.h>
   32 #include <typeinfo> // for typeid().
   33 
   34 Debugger g_Debugger;
   35 CStringA g_DebuggerHost;
   36 CStringA g_DebuggerPort;
   37 
   38 // The first breakpoint uses sMaxId + 1. Don't change this without also changing breakpoint_remove.
   39 int Breakpoint::sMaxId = 0;
   40 
   41 
   42 Debugger::CommandDef Debugger::sCommands[] =
   43 {
   44     {"run", &run},
   45     {"step_into", &step_into},
   46     {"step_over", &step_over},
   47     {"step_out", &step_out},
   48     {"break", &_break},
   49     {"stop", &stop},
   50     {"detach", &detach},
   51 
   52     {"status", &status},
   53     
   54     {"stack_get", &stack_get},
   55     {"stack_depth", &stack_depth},
   56     {"context_get", &context_get},
   57     {"context_names", &context_names},
   58 
   59     {"property_get", &property_get},
   60     {"property_set", &property_set},
   61     {"property_value", &property_value},
   62     
   63     {"feature_get", &feature_get},
   64     {"feature_set", &feature_set},
   65     
   66     {"breakpoint_set", &breakpoint_set},
   67     {"breakpoint_get", &breakpoint_get},
   68     {"breakpoint_update", &breakpoint_update},
   69     {"breakpoint_remove", &breakpoint_remove},
   70     {"breakpoint_list", &breakpoint_list},
   71 
   72     {"stdout", &redirect_stdout},
   73     {"stderr", &redirect_stderr},
   74 
   75     {"typemap_get", &typemap_get},
   76     
   77     {"source", &source},
   78 };
   79 
   80 
   81 // PreExecLine: aLine is about to execute; handle current line marker, breakpoints and step into/over/out.
   82 int Debugger::PreExecLine(Line *aLine)
   83 {
   84     // Using this->mCurrLine might perform a little better than the alternative, at the expense of a
   85     // small amount of complexity in stack_get (which is only called by request of the debugger client):
   86     //  mStack.mTop->line = aLine;
   87     mCurrLine = aLine;
   88     
   89     // Check for a breakpoint on the current line:
   90     Breakpoint *bp = aLine->mBreakpoint;
   91     if (bp && bp->state == BS_Enabled)
   92     {
   93         if (bp->temporary)
   94         {
   95             aLine->mBreakpoint = NULL;
   96             delete bp;
   97         }
   98         return Break();
   99     }
  100 
  101     if ((mInternalState == DIS_StepInto
  102         || mInternalState == DIS_StepOver && mStack.Depth() <= mContinuationDepth
  103         || mInternalState == DIS_StepOut && mStack.Depth() < mContinuationDepth) // Due to short-circuit boolean evaluation, mStack.Depth() is only evaluated once and only if mInternalState is StepOver or StepOut.
  104         // Although IF/ELSE/LOOP skips its block-begin, standalone/function-body block-begin still gets here; we want to skip it:
  105         && aLine->mActionType != ACT_BLOCK_BEGIN && (aLine->mActionType != ACT_BLOCK_END || aLine->mAttribute) // Ignore { and }; except for function-end, since we want to break there after a "return" to inspect variables while they're still in scope.
  106         && aLine->mLineNumber) // Some scripts (i.e. LowLevel/code.ahk) use mLineNumber==0 to indicate the Line has been generated and injected by the script.
  107     {
  108         return Break();
  109     }
  110     
  111     // Check if a command was sent asynchronously (while the script was running).
  112     // Such commands may also be detected via the AHK_CHECK_DEBUGGER message,
  113     // but if the program is checking for messages infrequently or not at all,
  114     // the check here is needed to ensure the debugger is responsive.
  115     if (HasPendingCommand())
  116     {
  117         // A command was sent asynchronously.
  118         return ProcessCommands();
  119     }
  120     
  121     return DEBUGGER_E_OK;
  122 }
  123 
  124 
  125 bool Debugger::HasPendingCommand()
  126 // Returns true if there is data in the socket's receive buffer.
  127 // This is used for receiving commands asynchronously.
  128 {
  129     u_long dataPending;
  130     if (ioctlsocket(mSocket, FIONREAD, &dataPending) == 0)
  131         return dataPending > 0;
  132     return false;
  133 }
  134 
  135 
  136 int Debugger::EnterBreakState()
  137 {
  138     if (mInternalState != DIS_Break)
  139     {
  140         if (mInternalState != DIS_Starting)
  141             // Send a response for the previous continuation command.
  142             if (int err = SendContinuationResponse())
  143                 return err;
  144         // Remove keyboard/mouse hooks.
  145         if (mDisabledHooks = GetActiveHooks())
  146             AddRemoveHooks(0, true);
  147         // Set break state.
  148         mInternalState = DIS_Break;
  149     }
  150     //else: no continuation command has been received so send no "response".
  151     // Hooks were already removed and mDisabledHooks must not be overwritten.
  152     return DEBUGGER_E_OK;
  153 }
  154 
  155 
  156 void Debugger::ExitBreakState()
  157 {
  158     // Restore keyboard/mouse hooks if they were previously removed.
  159     if (mDisabledHooks)
  160     {
  161         AddRemoveHooks(mDisabledHooks, true);
  162         mDisabledHooks = 0;
  163     }
  164 }
  165 
  166 
  167 int Debugger::Break()
  168 {
  169     if (mInternalState == DIS_Break)
  170         // Already in a break state, so it's likely that we are currently evaluating a
  171         // DBGp command, such as when property_set releases an object which implements
  172         // __delete and this causes a breakpoint to be hit.  In that case we must not
  173         // re-enter the command loop until the current command has completed.
  174         return DEBUGGER_E_OK;
  175     int err = EnterBreakState();
  176     if (!err)
  177         err = ProcessCommands();
  178     return err;
  179 }
  180 
  181 
  182 const int MAX_DBGP_ARGS = 16; // More than currently necessary.
  183 
  184 int Debugger::ProcessCommands()
  185 {
  186     // Disable notification of READ readiness and reset socket to synchronous mode.
  187     u_long zero = 0;
  188     WSAAsyncSelect(mSocket, g_hWnd, 0, 0);
  189     ioctlsocket(mSocket, FIONBIO, &zero);
  190 
  191     int err;
  192 
  193     for (;;)
  194     {
  195         int command_length;
  196 
  197         if (err = ReceiveCommand(&command_length))
  198             break; // Already called FatalError().
  199 
  200         char *command = mCommandBuf.mData;
  201         char *args = strchr(command, ' ');
  202         char *argv[MAX_DBGP_ARGS];
  203         int arg_count = 0;
  204 
  205         // transaction_id is considered optional for the following reasons:
  206         //  - The spec doesn't explicitly say it is mandatory (just "standard").
  207         //  - We don't actually need it for anything.
  208         //  - Rejecting the command doesn't seem useful.
  209         //  - It makes manually typing DBGp commands easier.
  210         char *transaction_id = "";
  211 
  212         if (args)
  213         {
  214             // Split command name and args.
  215             *args++ = '\0';
  216             // Split args into arg vector.
  217             err = ParseArgs(args, argv, arg_count, transaction_id);
  218         }
  219         
  220         if (!err)
  221         {
  222             for (int i = 0; ; ++i)
  223             {
  224                 if (i == _countof(sCommands))
  225                 {
  226                     err = DEBUGGER_E_UNIMPL_COMMAND;
  227                     break;
  228                 }
  229                 if (!strcmp(sCommands[i].mName, command))
  230                 {
  231                     // EXECUTE THE DBGP COMMAND.
  232                     err = (this->*sCommands[i].mFunc)(argv, arg_count, transaction_id);
  233                     break;
  234                 }
  235             }
  236         }
  237 
  238         if (!err)
  239         {
  240             if (mResponseBuf.mDataUsed)
  241                 err = SendResponse();
  242             else
  243                 err = SendStandardResponse(command, transaction_id);
  244             if (err)
  245                 break; // Already called FatalError().
  246         }
  247         else if (err == DEBUGGER_E_CONTINUE)
  248         {
  249             ASSERT(mInternalState != DIS_Break);
  250             // Response will be sent when the debugger breaks again.
  251             err = DEBUGGER_E_OK;
  252         }
  253         else
  254         {
  255             // Clear the response buffer in case a response was partially written
  256             // before the error was encountered (or the command failed because the
  257             // response buffer is full and cannot be expanded).
  258             mResponseBuf.Clear();
  259 
  260             if (mSocket == INVALID_SOCKET) // Already disconnected; see FatalError().
  261                 break;
  262 
  263             if (err = SendErrorResponse(command, transaction_id, err))
  264                 break; // Already called FatalError().
  265         }
  266         
  267         // Remove this command and its args from the buffer.
  268         // (There may be additional commands following it.)
  269         if (mCommandBuf.mDataUsed) // i.e. it hasn't been cleared as a result of disconnecting.
  270             mCommandBuf.Remove(command_length + 1);
  271 
  272         // If a command is received asynchronously, the debugger does not
  273         // enter a break state.  In that case, we need to return after each
  274         // command to avoid blocking in recv().
  275         if (mInternalState != DIS_Break)
  276         {
  277             // As ExitBreakState() can cause re-entry into ReceiveCommand() via the message pump,
  278             // it is safe to call only now that the command has been removed from the buffer.
  279             if (mInternalState != DIS_Starting) // i.e. it hasn't already been called by Disconnect().
  280                 ExitBreakState();
  281             break;
  282         }
  283     }
  284     ASSERT(mInternalState != DIS_Break);
  285     // Register for message-based notification of data arrival.  If a command
  286     // is received asynchronously, control will be passed back to the debugger
  287     // to process it.  This allows the debugger engine to respond even if the
  288     // script is sleeping or waiting for messages.
  289     if (mSocket != INVALID_SOCKET)
  290         WSAAsyncSelect(mSocket, g_hWnd, AHK_CHECK_DEBUGGER, FD_READ | FD_CLOSE);
  291     return err;
  292 }
  293 
  294 int Debugger::ParseArgs(char *aArgs, char **aArgV, int &aArgCount, char *&aTransactionId)
  295 {
  296     aArgCount = 0;
  297 
  298     while (*aArgs)
  299     {
  300         if (aArgCount == MAX_DBGP_ARGS)
  301             return DEBUGGER_E_PARSE_ERROR;
  302 
  303         if (*aArgs != '-')
  304             return DEBUGGER_E_PARSE_ERROR;
  305         ++aArgs;
  306 
  307         char arg_char = *aArgs;
  308         if (!arg_char)
  309             return DEBUGGER_E_PARSE_ERROR;
  310         
  311         if (aArgs[1] == ' ' && aArgs[2] != '-')
  312         {
  313             // Move the arg letter onto the space.
  314             *++aArgs = arg_char;
  315         }
  316         // Store a pointer to the arg letter, followed immediately by its value.
  317         aArgV[aArgCount++] = aArgs;
  318         
  319         if (arg_char == 'i')
  320         {
  321             // Handle transaction_id here to simplify many other sections.
  322             aTransactionId = aArgs + 1;
  323             --aArgCount;
  324         }
  325 
  326         if (arg_char == '-') // -- base64-encoded-data
  327             break;
  328 
  329         char *next_arg;
  330         if (aArgs[1] == '"')
  331         {
  332             char *arg_end;
  333             for (arg_end = aArgs + 1, next_arg = aArgs + 2; *next_arg != '"'; ++arg_end, ++next_arg)
  334             {
  335                 if (*next_arg == '\\')
  336                     ++next_arg; // Currently only \\ and \" are supported; i.e. mark next char as literal.
  337                 if (!*next_arg)
  338                     return DEBUGGER_E_PARSE_ERROR;
  339                 // Copy the value to eliminate the quotes and escape sequences.
  340                 *arg_end = *next_arg;
  341             }
  342             *arg_end = '\0'; // Terminate this arg.
  343             ++next_arg;
  344             if (!*next_arg)
  345                 break;
  346             if (strncmp(next_arg, " -", 2))
  347                 return DEBUGGER_E_PARSE_ERROR;
  348         }
  349         else
  350         {
  351             // Find where this arg's value ends and the next arg begins.
  352             next_arg = strstr(aArgs + 1, " -");
  353             if (!next_arg)
  354                 break;
  355             *next_arg = '\0'; // Terminate this arg.
  356         }
  357         
  358         // Point aArgs to the next arg's hyphen.
  359         aArgs = next_arg + 1;
  360     }
  361 
  362     return DEBUGGER_E_OK;
  363 }
  364 
  365 
  366 //
  367 // DBGP COMMANDS
  368 //
  369 
  370 // Calculate base64-encoded size of data, including NULL terminator. org_size must be > 0 if unsigned.
  371 #define DEBUGGER_BASE64_ENCODED_SIZE(org_size) ((((org_size)-1)/3+1)*4 +1)
  372 
  373 DEBUGGER_COMMAND(Debugger::status)
  374 {
  375     if (aArgCount)
  376         return DEBUGGER_E_INVALID_OPTIONS;
  377 
  378     char *status;
  379     switch (mInternalState)
  380     {
  381     case DIS_Starting:  status = "starting";    break;
  382     case DIS_Break:     status = "break";       break;
  383     default:            status = "running";
  384     }
  385 
  386     return mResponseBuf.WriteF(
  387         "<response command=\"status\" status=\"%s\" reason=\"ok\" transaction_id=\"%e\"/>"
  388         , status, aTransactionId);
  389 }
  390 
  391 DEBUGGER_COMMAND(Debugger::feature_get)
  392 {
  393     // feature_get accepts exactly one arg: -n feature_name.
  394     if (aArgCount != 1 || ArgChar(aArgV, 0) != 'n')
  395         return DEBUGGER_E_INVALID_OPTIONS;
  396 
  397     char *feature_name = ArgValue(aArgV, 0);
  398 
  399     bool supported = false; // Is %feature_name% a supported feature string?
  400     char *setting = "";
  401 
  402     if (!strncmp(feature_name, "language_", 9)) {
  403         if (!strcmp(feature_name + 9, "supports_threads"))
  404             setting = "0";
  405         else if (!strcmp(feature_name + 9, "name"))
  406             setting = DEBUGGER_LANG_NAME;
  407         else if (!strcmp(feature_name + 9, "version"))
  408 #ifdef UNICODE
  409             setting = AHK_VERSION " (Unicode)";
  410 #else
  411             setting = AHK_VERSION;
  412 #endif
  413     } else if (!strcmp(feature_name, "encoding"))
  414         setting = "UTF-8";
  415     else if (!strcmp(feature_name, "protocol_version")
  416             || !strcmp(feature_name, "supports_async"))
  417         setting = "1";
  418     // Not supported: data_encoding - assume base64.
  419     // Not supported: breakpoint_languages - assume only %language_name% is supported.
  420     else if (!strcmp(feature_name, "breakpoint_types"))
  421         setting = "line";
  422     else if (!strcmp(feature_name, "multiple_sessions"))
  423         setting = "0";
  424     else if (!strcmp(feature_name, "max_data"))
  425         setting = _itoa(mMaxPropertyData, (char*)_alloca(MAX_INTEGER_SIZE), 10);
  426     else if (!strcmp(feature_name, "max_children"))
  427         setting = _ultoa(mMaxChildren, (char*)_alloca(MAX_INTEGER_SIZE), 10);
  428     else if (!strcmp(feature_name, "max_depth"))
  429         setting = _ultoa(mMaxDepth, (char*)_alloca(MAX_INTEGER_SIZE), 10);
  430     // TODO: STOPPING state for retrieving variable values, etc. after the script finishes, then implement supports_postmortem feature name. Requires debugger client support.
  431     else
  432     {
  433         for (int i = 0; i < _countof(sCommands); ++i)
  434         {
  435             if (!strcmp(sCommands[i].mName, feature_name))
  436             {
  437                 supported = true;
  438                 break;
  439             }
  440         }
  441     }
  442 
  443     if (*setting)
  444         supported = true;
  445 
  446     return mResponseBuf.WriteF(
  447         "<response command=\"feature_get\" feature_name=\"%e\" supported=\"%i\" transaction_id=\"%e\">%s</response>"
  448         , feature_name, (int)supported, aTransactionId, setting);
  449 }
  450 
  451 DEBUGGER_COMMAND(Debugger::feature_set)
  452 {
  453     char arg, *value;
  454 
  455     char *feature_name = NULL, *feature_value = NULL;
  456 
  457     for (int i = 0; i < aArgCount; ++i)
  458     {
  459         arg = ArgChar(aArgV, i);
  460         value = ArgValue(aArgV, i);
  461         switch (arg)
  462         {
  463         case 'n': feature_name = value; break;
  464         case 'v': feature_value = value; break;
  465         default:
  466             return DEBUGGER_E_INVALID_OPTIONS;
  467         }
  468     }
  469 
  470     if (!feature_name || !feature_value)
  471         return DEBUGGER_E_INVALID_OPTIONS;
  472 
  473     bool success = false;
  474 
  475     // Since all supported features are positive integers:
  476     int ival = atoi(feature_value);
  477     if (ival < 0)
  478     {
  479         // Since this value is invalid, return success="0" to indicate the error.
  480         // Setting the feature to a negative value might cause instability elsewhere.
  481     }
  482     else if (success = !strcmp(feature_name, "max_data"))
  483     {
  484         if (ival == 0) // Although this isn't in the spec, at least one IDE relies on it.
  485             ival = INT_MAX; // Strictly following the spec, we should probably return 0 bytes of data.
  486         mMaxPropertyData = ival;
  487     }
  488     else if (success = !strcmp(feature_name, "max_children"))
  489         mMaxChildren = ival;
  490     else if (success = !strcmp(feature_name, "max_depth"))
  491         mMaxDepth = ival;
  492 
  493     return mResponseBuf.WriteF(
  494         "<response command=\"feature_set\" feature=\"%e\" success=\"%i\" transaction_id=\"%e\"/>"
  495         , feature_name, (int)success, aTransactionId);
  496 }
  497 
  498 DEBUGGER_COMMAND(Debugger::run)
  499 {
  500     return run_step(aArgV, aArgCount, aTransactionId, "run", DIS_Run);
  501 }
  502 
  503 DEBUGGER_COMMAND(Debugger::step_into)
  504 {
  505     return run_step(aArgV, aArgCount, aTransactionId, "step_into", DIS_StepInto);
  506 }
  507 
  508 DEBUGGER_COMMAND(Debugger::step_over)
  509 {
  510     return run_step(aArgV, aArgCount, aTransactionId, "step_over", DIS_StepOver);
  511 }
  512 
  513 DEBUGGER_COMMAND(Debugger::step_out)
  514 {
  515     return run_step(aArgV, aArgCount, aTransactionId, "step_out", DIS_StepOut);
  516 }
  517 
  518 int Debugger::run_step(char **aArgV, int aArgCount, char *aTransactionId, char *aCommandName, DebuggerInternalStateType aNewState)
  519 {
  520     if (aArgCount)
  521         return DEBUGGER_E_INVALID_OPTIONS;
  522     
  523     if (mInternalState != DIS_Break)
  524         return DEBUGGER_E_COMMAND_UNAVAIL;
  525 
  526     mInternalState = aNewState;
  527     mContinuationDepth = mStack.Depth();
  528     mContinuationTransactionId = aTransactionId;
  529     
  530     // Response will be sent when the debugger breaks.
  531     return DEBUGGER_E_CONTINUE;
  532 }
  533 
  534 DEBUGGER_COMMAND(Debugger::_break)
  535 {
  536     if (aArgCount)
  537         return DEBUGGER_E_INVALID_OPTIONS;
  538     if (int err = EnterBreakState())
  539         return err;
  540     return DEBUGGER_E_OK;
  541 }
  542 
  543 DEBUGGER_COMMAND(Debugger::stop)
  544 {
  545     mContinuationTransactionId = aTransactionId;
  546 
  547     // Call g_script.TerminateApp instead of g_script.ExitApp to bypass OnExit subroutine.
  548     g_script.TerminateApp(EXIT_EXIT, 0); // This also causes this->Exit() to be called.
  549     
  550     // Should never be reached, but must be here to avoid a compile error:
  551     return DEBUGGER_E_INTERNAL_ERROR;
  552 }
  553 
  554 DEBUGGER_COMMAND(Debugger::detach)
  555 {
  556     mContinuationTransactionId = aTransactionId; // Seems more appropriate than using the previous ID (if any).
  557     // User wants to stop the debugger but let the script keep running.
  558     Exit(EXIT_NONE, "detach"); // Anything but EXIT_ERROR.  Sends "stopped" response, then disconnects.
  559     return DEBUGGER_E_CONTINUE; // Response already sent.
  560 }
  561 
  562 DEBUGGER_COMMAND(Debugger::breakpoint_set)
  563 {
  564     char arg, *value;
  565     
  566     char *type = NULL, state = BS_Enabled, *filename = NULL;
  567     LineNumberType lineno = 0;
  568     bool temporary = false;
  569 
  570     for (int i = 0; i < aArgCount; ++i)
  571     {
  572         arg = ArgChar(aArgV, i);
  573         value = ArgValue(aArgV, i);
  574         switch (arg)
  575         {
  576         case 't': // type = line | call | return | exception | conditional | watch
  577             type = value;
  578             break;
  579 
  580         case 's': // state = enabled | disabled
  581             if (!strcmp(value, "enabled"))
  582                 state = BS_Enabled;
  583             else if (!strcmp(value, "disabled"))
  584                 state = BS_Disabled;
  585             else
  586                 return DEBUGGER_E_BREAKPOINT_STATE;
  587             break;
  588 
  589         case 'f': // filename
  590             filename = value;
  591             break;
  592 
  593         case 'n': // lineno
  594             lineno = strtoul(value, NULL, 10);
  595             break;
  596 
  597         case 'r': // temporary = 0 | 1
  598             temporary = (*value != '0');
  599             break;
  600             
  601         case 'm': // function
  602         case 'x': // exception
  603         case 'h': // hit_value
  604         case 'o': // hit_condition = >= | == | %
  605         case '-': // expression for conditional breakpoints
  606             // These aren't used/supported, but ignored for now.
  607             break;
  608 
  609         default:
  610             return DEBUGGER_E_INVALID_OPTIONS;
  611         }
  612     }
  613 
  614     if (!type || strcmp(type, "line")) // i.e. type != "line"
  615         return DEBUGGER_E_BREAKPOINT_TYPE;
  616     if (lineno < 1)
  617         return DEBUGGER_E_BREAKPOINT_INVALID;
  618 
  619     int file_index = 0;
  620 
  621     if (filename)
  622     {
  623         // Decode filename URI -> path, in-place.
  624         DecodeURI(filename);
  625         CStringTCharFromUTF8 filename_t(filename);
  626 
  627         // Find the specified source file.
  628         for (file_index = 0; file_index < Line::sSourceFileCount; ++file_index)
  629             if (!_tcsicmp(filename_t, Line::sSourceFile[file_index]))
  630                 break;
  631 
  632         if (file_index >= Line::sSourceFileCount)
  633             return DEBUGGER_E_BREAKPOINT_INVALID;
  634     }
  635 
  636     Line *line = NULL, *found_line = NULL;
  637     // Due to the introduction of expressions in static initializers, lines aren't necessarily in
  638     // line number order.  First determine if any static initializers match the requested lineno.
  639     // If not, use the first non-static line at or following that line number.
  640 
  641     if (g_script.mFirstStaticLine)
  642         for (line = g_script.mFirstStaticLine; ; line = line->mNextLine)
  643         {
  644             if (line->mFileIndex == file_index && line->mLineNumber == lineno) // Exact match, unlike normal lines.
  645             {
  646                 found_line = line;
  647                 break;
  648             }
  649             if (line == g_script.mLastStaticLine)
  650                 break;
  651         }
  652     if (!found_line)
  653         // If line is non-NULL, above has left it set to mLastStaticLine, which we want to exclude:
  654         for (line = line ? line->mNextLine : g_script.mFirstLine; line; line = line->mNextLine)
  655             if (line->mFileIndex == file_index && line->mLineNumber >= lineno)
  656             {
  657                 // ACT_ELSE and ACT_BLOCK_BEGIN generally don't cause PreExecLine() to be called,
  658                 // so any breakpoint set on one of those lines would never be hit.  Attempting to
  659                 // set a breakpoint on one of these should act like setting a breakpoint on a line
  660                 // which contains no code: put the breakpoint at the next line instead.
  661                 // Without this check, setting a breakpoint on a line like "else Exit" would not work.
  662                 // ACT_CASE executes when the *previous* case reaches its end, so for that case
  663                 // we want to shift the breakpoint to the first Line after the ACT_CASE.
  664                 if (line->mActionType == ACT_ELSE || line->mActionType == ACT_BLOCK_BEGIN
  665                     || line->mActionType == ACT_CASE)
  666                     continue;
  667                 // Use the first line of code at or after lineno, like Visual Studio.
  668                 // To display the breakpoint correctly, an IDE should use breakpoint_get.
  669                 if (!found_line || found_line->mLineNumber > line->mLineNumber)
  670                     found_line = line;
  671                 // Must keep searching, since class var initializers can cause lines to be listed out of order.
  672                 //break;
  673             }
  674     if (found_line)
  675     {
  676         if (!found_line->mBreakpoint)
  677             found_line->mBreakpoint = new Breakpoint();
  678         found_line->mBreakpoint->state = state;
  679         found_line->mBreakpoint->temporary = temporary;
  680 
  681         return mResponseBuf.WriteF(
  682             "<response command=\"breakpoint_set\" transaction_id=\"%e\" state=\"%s\" id=\"%i\"/>"
  683             , aTransactionId, state ? "enabled" : "disabled", found_line->mBreakpoint->id);
  684     }
  685     // There are no lines of code beginning at or after lineno.
  686     return DEBUGGER_E_BREAKPOINT_INVALID;
  687 }
  688 
  689 int Debugger::WriteBreakpointXml(Breakpoint *aBreakpoint, Line *aLine)
  690 {
  691     mResponseBuf.WriteF("<breakpoint id=\"%i\" type=\"line\" state=\"%s\" filename=\""
  692                     , aBreakpoint->id, aBreakpoint->state ? "enabled" : "disabled");
  693     mResponseBuf.WriteFileURI(U4T(Line::sSourceFile[aLine->mFileIndex]));
  694     return mResponseBuf.WriteF("\" lineno=\"%u\"/>", aLine->mLineNumber);
  695 }
  696 
  697 DEBUGGER_COMMAND(Debugger::breakpoint_get)
  698 {
  699     // breakpoint_get accepts exactly one arg: -d breakpoint_id.
  700     if (aArgCount != 1 || ArgChar(aArgV, 0) != 'd')
  701         return DEBUGGER_E_INVALID_OPTIONS;
  702 
  703     int breakpoint_id = atoi(ArgValue(aArgV, 0));
  704 
  705     Line *line;
  706     for (line = g_script.mFirstLine; line; line = line->mNextLine)
  707     {
  708         if (line->mBreakpoint && line->mBreakpoint->id == breakpoint_id)
  709         {
  710             mResponseBuf.WriteF("<response command=\"breakpoint_get\" transaction_id=\"%e\">", aTransactionId);
  711             WriteBreakpointXml(line->mBreakpoint, line);
  712             mResponseBuf.Write("</response>");
  713 
  714             return DEBUGGER_E_OK;
  715         }
  716     }
  717 
  718     return DEBUGGER_E_BREAKPOINT_NOT_FOUND;
  719 }
  720 
  721 DEBUGGER_COMMAND(Debugger::breakpoint_update)
  722 {
  723     char arg, *value;
  724     
  725     int breakpoint_id = 0; // Breakpoint IDs begin at 1.
  726     LineNumberType lineno = 0;
  727     char state = -1;
  728 
  729     for (int i = 0; i < aArgCount; ++i)
  730     {
  731         arg = ArgChar(aArgV, i);
  732         value = ArgValue(aArgV, i);
  733         switch (arg)
  734         {
  735         case 'd':
  736             breakpoint_id = atoi(value);
  737             break;
  738 
  739         case 's':
  740             if (!strcmp(value, "enabled"))
  741                 state = BS_Enabled;
  742             else if (!strcmp(value, "disabled"))
  743                 state = BS_Disabled;
  744             else
  745                 return DEBUGGER_E_BREAKPOINT_STATE;
  746             break;
  747 
  748         case 'n':
  749             lineno = strtoul(value, NULL, 10);
  750             break;
  751 
  752         case 'h': // hit_value
  753         case 'o': // hit_condition
  754             // These aren't used/supported, but ignored for now.
  755             break;
  756 
  757         default:
  758             return DEBUGGER_E_INVALID_OPTIONS;
  759         }
  760     }
  761 
  762     if (!breakpoint_id)
  763         return DEBUGGER_E_INVALID_OPTIONS;
  764 
  765     Line *line;
  766     for (line = g_script.mFirstLine; line; line = line->mNextLine)
  767     {
  768         Breakpoint *bp = line->mBreakpoint;
  769 
  770         if (bp && bp->id == breakpoint_id)
  771         {
  772             if (lineno && line->mLineNumber != lineno)
  773             {
  774                 // Move the breakpoint within its current file.
  775                 int file_index = line->mFileIndex;
  776                 Line *old_line = line;
  777 
  778                 for (line = g_script.mFirstLine; line; line = line->mNextLine)
  779                 {
  780                     if (line->mFileIndex == file_index && line->mLineNumber >= lineno)
  781                     {
  782                         line->mBreakpoint = bp;
  783                         break;
  784                     }
  785                 }
  786 
  787                 // If line is NULL, the line was not found.
  788                 if (!line)
  789                     return DEBUGGER_E_BREAKPOINT_INVALID;
  790 
  791                 // Seems best to only remove the breakpoint from its previous line
  792                 // once we know the breakpoint_update has succeeded.
  793                 old_line->mBreakpoint = NULL;
  794             }
  795 
  796             if (state != -1)
  797                 bp->state = state;
  798 
  799             return DEBUGGER_E_OK;
  800         }
  801     }
  802 
  803     return DEBUGGER_E_BREAKPOINT_NOT_FOUND;
  804 }
  805 
  806 DEBUGGER_COMMAND(Debugger::breakpoint_remove)
  807 {
  808     // breakpoint_remove accepts exactly one arg: -d breakpoint_id.
  809     if (aArgCount != 1 || ArgChar(aArgV, 0) != 'd')
  810         return DEBUGGER_E_INVALID_OPTIONS;
  811 
  812     int breakpoint_id = atoi(ArgValue(aArgV, 0));
  813 
  814     Line *line;
  815     for (line = g_script.mFirstLine; line; line = line->mNextLine)
  816     {
  817         if (line->mBreakpoint && line->mBreakpoint->id == breakpoint_id)
  818         {
  819             delete line->mBreakpoint;
  820             line->mBreakpoint = NULL;
  821 
  822             return DEBUGGER_E_OK;
  823         }
  824     }
  825 
  826     return DEBUGGER_E_BREAKPOINT_NOT_FOUND;
  827 }
  828 
  829 DEBUGGER_COMMAND(Debugger::breakpoint_list)
  830 {
  831     if (aArgCount)
  832         return DEBUGGER_E_INVALID_OPTIONS;
  833     
  834     mResponseBuf.WriteF("<response command=\"breakpoint_list\" transaction_id=\"%e\">", aTransactionId);
  835     
  836     Line *line;
  837     for (line = g_script.mFirstLine; line; line = line->mNextLine)
  838     {
  839         if (line->mBreakpoint)
  840         {
  841             WriteBreakpointXml(line->mBreakpoint, line);
  842         }
  843     }
  844 
  845     return mResponseBuf.Write("</response>");
  846 }
  847 
  848 DEBUGGER_COMMAND(Debugger::stack_depth)
  849 {
  850     if (aArgCount)
  851         return DEBUGGER_E_INVALID_OPTIONS;
  852 
  853     return mResponseBuf.WriteF(
  854         "<response command=\"stack_depth\" depth=\"%i\" transaction_id=\"%e\"/>"
  855         , mStack.Depth(), aTransactionId);
  856 }
  857 
  858 DEBUGGER_COMMAND(Debugger::stack_get)
  859 {
  860     int depth = -1;
  861 
  862     if (aArgCount)
  863     {
  864         // stack_get accepts one optional arg: -d depth.
  865         if (aArgCount > 1 || ArgChar(aArgV, 0) != 'd')
  866             return DEBUGGER_E_INVALID_OPTIONS;
  867         depth = atoi(ArgValue(aArgV, 0));
  868         if (depth < 0 || depth >= mStack.Depth())
  869             return DEBUGGER_E_INVALID_STACK_DEPTH;
  870     }
  871 
  872     mResponseBuf.WriteF("<response command=\"stack_get\" transaction_id=\"%e\">", aTransactionId);
  873     
  874     int level = 0;
  875     DbgStack::Entry *se;
  876     for (se = mStack.mTop; se >= mStack.mBottom; --se)
  877     {
  878         if (depth == -1 || depth == level)
  879         {
  880             Line *line;
  881             if (se == mStack.mTop)
  882             {
  883                 ASSERT(mCurrLine); // Should always be valid.
  884                 line = mCurrLine; // See PreExecLine() for comments.
  885             }
  886             else if (se->type == DbgStack::SE_Thread)
  887             {
  888                 // !se->line implies se->type == SE_Thread.
  889                 if (se[1].type == DbgStack::SE_UDF)
  890                     line = se[1].udf->func->mJumpToLine;
  891                 else if (se[1].type == DbgStack::SE_Sub)
  892                     line = se[1].sub->mJumpToLine;
  893                 else
  894                     // The auto-execute thread is probably the only one that can exist without
  895                     // a Sub or Func entry immediately above it.  As se != mStack.mTop, se->line
  896                     // has been set to a non-NULL by DbgStack::Push().
  897                     line = se->line;
  898             }
  899             else
  900             {
  901                 line = se->line;
  902             }
  903             mResponseBuf.WriteF("<stack level=\"%i\" type=\"file\" filename=\"", level);
  904             mResponseBuf.WriteFileURI(U4T(Line::sSourceFile[line->mFileIndex]));
  905             mResponseBuf.WriteF("\" lineno=\"%u\" where=\"", line->mLineNumber);
  906             switch (se->type)
  907             {
  908             case DbgStack::SE_Thread:
  909                 mResponseBuf.WriteF("%e thread", U4T(se->desc)); // %e to escape characters which desc may contain (e.g. "a & b" in hotkey name).
  910                 break;
  911             case DbgStack::SE_UDF:
  912                 mResponseBuf.WriteF("%e()", U4T(se->udf->func->mName));
  913                 break;
  914             case DbgStack::SE_Sub:
  915                 mResponseBuf.WriteF("%e sub", U4T(se->sub->mName)); // %e because label/hotkey names may contain almost anything.
  916                 break;
  917             }
  918             mResponseBuf.Write("\"/>");
  919         }
  920         ++level;
  921     }
  922 
  923     return mResponseBuf.Write("</response>");
  924 }
  925 
  926 DEBUGGER_COMMAND(Debugger::context_names)
  927 {
  928     if (aArgCount)
  929         return DEBUGGER_E_INVALID_OPTIONS;
  930 
  931     return mResponseBuf.WriteF(
  932         "<response command=\"context_names\" transaction_id=\"%e\"><context name=\"Local\" id=\"0\"/><context name=\"Global\" id=\"1\"/></response>"
  933         , aTransactionId);
  934 }
  935 
  936 DEBUGGER_COMMAND(Debugger::context_get)
  937 {
  938     int err = DEBUGGER_E_OK;
  939     char arg, *value;
  940 
  941     int context_id = 0, depth = 0;
  942 
  943     for (int i = 0; i < aArgCount; ++i)
  944     {
  945         arg = ArgChar(aArgV, i);
  946         value = ArgValue(aArgV, i);
  947         switch (arg)
  948         {
  949         case 'c':   context_id = atoi(value);   break;
  950         case 'd':
  951             depth = atoi(value);
  952             if (depth && (depth < 0 || depth >= mStack.Depth())) // Allow depth 0 even when stack is empty.
  953                 return DEBUGGER_E_INVALID_STACK_DEPTH;
  954             break;
  955         default:
  956             return DEBUGGER_E_INVALID_OPTIONS;
  957         }
  958     }
  959 
  960     Var **var = NULL, **var_end = NULL; // An array of pointers-to-var.
  961     VarBkp *bkp = NULL, *bkp_end = NULL;
  962     
  963     // TODO: Include the lazy-var arrays for completeness. Low priority since lazy-var arrays are used only for 10001+ variables, and most conventional debugger interfaces would generally not be useful with that many variables.
  964     if (context_id == PC_Local)
  965     {
  966         mStack.GetLocalVars(depth, var, var_end, bkp, bkp_end);
  967     }
  968     else if (context_id == PC_Global)
  969     {
  970         var = g_script.mVar;
  971         var_end = var + g_script.mVarCount;
  972     }
  973     else
  974         return DEBUGGER_E_INVALID_CONTEXT;
  975 
  976     mResponseBuf.WriteF(
  977         "<response command=\"context_get\" context=\"%i\" transaction_id=\"%e\">"
  978         , context_id, aTransactionId);
  979 
  980     LPTSTR value_buf = NULL;
  981     CStringA name_buf;
  982     PropertyInfo prop(name_buf);
  983     prop.max_data = mMaxPropertyData;
  984     prop.pagesize = mMaxChildren;
  985     prop.max_depth = mMaxDepth;
  986     for ( ; var < var_end; ++var)
  987         if (  (err = GetPropertyInfo(**var, prop, value_buf))
  988             || (err = WritePropertyXml(prop, (*var)->mName))  )
  989             break;
  990     for ( ; bkp < bkp_end; ++bkp)
  991         if (  (err = GetPropertyInfo(*bkp, prop, value_buf))
  992             || (err = WritePropertyXml(prop, bkp->mVar->mName))  )
  993             break;
  994     free(value_buf);
  995     if (err)
  996         return err;
  997 
  998     return mResponseBuf.Write("</response>");
  999 }
 1000 
 1001 DEBUGGER_COMMAND(Debugger::typemap_get)
 1002 {
 1003     if (aArgCount)
 1004         return DEBUGGER_E_INVALID_OPTIONS;
 1005     
 1006     return mResponseBuf.WriteF(
 1007         "<response command=\"typemap_get\" transaction_id=\"%e\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">"
 1008             "<map type=\"string\" name=\"string\" xsi:type=\"xsd:string\"/>"
 1009             "<map type=\"int\" name=\"integer\" xsi:type=\"xsd:long\"/>"
 1010             "<map type=\"float\" name=\"float\" xsi:type=\"xsd:double\"/>"
 1011             "<map type=\"object\" name=\"object\"/>"
 1012         "</response>"
 1013         , aTransactionId);
 1014 }
 1015 
 1016 DEBUGGER_COMMAND(Debugger::property_get)
 1017 {
 1018     return property_get_or_value(aArgV, aArgCount, aTransactionId, true);
 1019 }
 1020 
 1021 DEBUGGER_COMMAND(Debugger::property_value)
 1022 {
 1023     return property_get_or_value(aArgV, aArgCount, aTransactionId, false);
 1024 }
 1025 
 1026 
 1027 int Debugger::GetPropertyInfo(Var &aVar, PropertyInfo &aProp, LPTSTR &aValueBuf)
 1028 {
 1029     aProp.is_alias = aVar.mType == VAR_ALIAS;
 1030     aProp.is_static = aVar.IsStatic();
 1031     return GetPropertyValue(aVar, aProp, aValueBuf);
 1032 }
 1033 
 1034 int Debugger::GetPropertyInfo(VarBkp &aBkp, PropertyInfo &aProp, LPTSTR &aValueBuf)
 1035 {
 1036     aProp.is_static = false;
 1037     aProp.is_builtin = false;
 1038     if (aProp.is_alias = aBkp.mType == VAR_ALIAS)
 1039         return GetPropertyValue(*aBkp.mAliasFor, aProp, aValueBuf);
 1040     aProp.is_binaryclip = aBkp.mAttrib & VAR_ATTRIB_BINARY_CLIP;
 1041     aBkp.ToToken(aProp.value);
 1042     return DEBUGGER_E_OK;
 1043 }
 1044 
 1045 int Debugger::GetPropertyValue(Var &aVar, PropertyInfo &aProp, LPTSTR &aValueBuf)
 1046 {
 1047     aProp.is_binaryclip = aVar.IsBinaryClip();
 1048     if (aProp.is_builtin = aVar.Type() != VAR_NORMAL)
 1049     {
 1050         size_t approx_size = aVar.Get() + 1;
 1051         if (!aValueBuf || _msize(aValueBuf) < _TSIZE(approx_size))
 1052         {
 1053             free(aValueBuf);
 1054             if (approx_size < MAX_PATH)
 1055                 approx_size = MAX_PATH; // Big enough for most built-in vars (avoids repeated reallocation for context_get).
 1056             if (!(aValueBuf = tmalloc(approx_size)))
 1057                 return DEBUGGER_E_INTERNAL_ERROR;
 1058         }
 1059         aVar.Get(aValueBuf);
 1060         aProp.value.SetValue(aValueBuf);
 1061         CLOSE_CLIPBOARD_IF_OPEN; // Above may leave the clipboard open if aVar is Clipboard.
 1062     }
 1063     else
 1064     {
 1065         if (aVar.IsUninitializedNormalVar())
 1066         {
 1067             aProp.value.symbol = SYM_MISSING;
 1068             aProp.value.marker = _T("");
 1069         }
 1070         else
 1071             aVar.ToTokenSkipAddRef(aProp.value);
 1072     }
 1073     return DEBUGGER_E_OK;
 1074 }
 1075 
 1076 int Debugger::GetPropertyInfo(Object::FieldType &aField, PropertyInfo &aProp)
 1077 {
 1078     aField.ToToken(aProp.value);
 1079     return DEBUGGER_E_OK;
 1080 }
 1081 
 1082 
 1083 int Debugger::WritePropertyXml(PropertyInfo &aProp, IObject *aObject)
 1084 {
 1085     PropertyWriter pw(*this, aProp, aObject);
 1086     // Ask the object to write out its properties:
 1087     aObject->DebugWriteProperty(&pw, aProp.page, aProp.pagesize, aProp.max_depth);
 1088     aProp.fullname.Truncate(pw.mNameLength);
 1089     // For simplicity/code size, instead of requiring error handling in aObject,
 1090     // any failure during the above sets pw.mError, which causes it to ignore
 1091     // any further method calls.  Since we're finished now, return the error
 1092     // code (even if it's "OK"):
 1093     return pw.mError;
 1094 }
 1095 
 1096 void Object::DebugWriteProperty(IDebugProperties *aDebugger, int aPage, int aPageSize, int aDepth)
 1097 {
 1098     DebugCookie cookie;
 1099     aDebugger->BeginProperty(NULL, "object", (int)mFieldCount + (mBase != NULL), cookie);
 1100 
 1101     if (aDepth)
 1102     {
 1103         int i = aPageSize * aPage, j = aPageSize * (aPage + 1);
 1104 
 1105         if (mBase)
 1106         {
 1107             // Since this object has a "base", let it count as the first field.
 1108             if (i == 0) // i.e. this is the first page.
 1109             {
 1110                 aDebugger->WriteProperty("<base>", ExprTokenType(mBase));
 1111                 // Now fall through and retrieve field[0] (unless aPageSize == 1).
 1112             }
 1113             // So 20..39 becomes 19..38 when there's a base object:
 1114             else --i; 
 1115             --j;
 1116         }
 1117         if (j > (int)mFieldCount)
 1118             j = (int)mFieldCount;
 1119         // For each field in the requested page...
 1120         for ( ; i < j; ++i)
 1121         {
 1122             Object::FieldType &field = mFields[i];
 1123             
 1124             ExprTokenType key, value;
 1125             if (i >= mKeyOffsetString) // String
 1126                 key.symbol = SYM_STRING, key.marker = field.key.s;
 1127             else if (i >= mKeyOffsetObject)
 1128                 key.symbol = SYM_OBJECT, key.object = field.key.p;
 1129             else
 1130                 key.symbol = SYM_INTEGER, key.value_int64 = field.key.i;
 1131             field.ToToken(value);
 1132 
 1133             aDebugger->WriteProperty(key, value);
 1134         }
 1135     }
 1136 
 1137     aDebugger->EndProperty(cookie);
 1138 }
 1139 
 1140 int Debugger::WritePropertyXml(PropertyInfo &aProp)
 1141 {
 1142     char facetbuf[35]; // Alias Builtin Static ClipboardAll \0
 1143     facetbuf[0] = '\0';
 1144     if (aProp.is_alias)
 1145         strcat(facetbuf, " Alias");
 1146     if (aProp.is_builtin)
 1147         strcat(facetbuf, " Builtin");
 1148     if (aProp.is_static)
 1149         strcat(facetbuf, " Static");
 1150     if (aProp.is_binaryclip)
 1151         strcat(facetbuf, " ClipboardAll");
 1152     aProp.facet = facetbuf + (*facetbuf != '\0'); // Skip the leading space, if non-empty.
 1153 
 1154     char *type;
 1155     switch (aProp.value.symbol)
 1156     {
 1157     case SYM_OPERAND:
 1158     case SYM_STRING: type = "string"; break;
 1159     case SYM_INTEGER: type = "integer"; break;
 1160     case SYM_FLOAT: type = "float"; break;
 1161 
 1162     case SYM_OBJECT:
 1163         // Recursively dump object.
 1164         return WritePropertyXml(aProp, aProp.value.object);
 1165 
 1166     default:
 1167         // Catch SYM_VAR or any invalid symbol in debug mode.  In release mode, treat as undefined
 1168         // (the compiler can omit the SYM_MISSING check because the default branch covers it).
 1169         ASSERT(FALSE);
 1170     case SYM_MISSING:
 1171         type = "undefined";
 1172     }
 1173     // If we fell through, value and type have been set appropriately above.
 1174     mResponseBuf.WriteF("<property name=\"%e\" fullname=\"%e\" type=\"%s\" facet=\"%s\" children=\"0\" encoding=\"base64\" size=\""
 1175         , aProp.name, aProp.fullname.GetString(), type, aProp.facet);
 1176     int err;
 1177     if (err = WritePropertyData(aProp.value, aProp.max_data))
 1178         return err;
 1179     return mResponseBuf.Write("</property>");
 1180 }
 1181 
 1182 int Debugger::WritePropertyXml(PropertyInfo &aProp, LPTSTR aName)
 1183 {
 1184     StringTCharToUTF8(aName, aProp.fullname);
 1185     aProp.name = aProp.fullname;
 1186     return WritePropertyXml(aProp);
 1187 }
 1188 
 1189 void Debugger::AppendKeyName(CStringA &aNameBuf, size_t aParentNameLength, const char *aName)
 1190 {
 1191     const char *ccp;
 1192     for (ccp = aName; cisalnum(*ccp) || *ccp == '_'; ++ccp);
 1193     if (!*ccp && ccp != aName) // If it got to the null-terminator, must be empty or alphanumeric.
 1194     {
 1195         // Since this string is purely composed of alphanumeric characters and/or underscore,
 1196         // it doesn't need any quote marks (imitating expression syntax) or escaped characters.
 1197         aNameBuf.AppendFormat(".%s", aName);
 1198     }
 1199     else
 1200     {
 1201         // " must be replaced with "" as in expressions to remove ambiguity.
 1202         char c;
 1203         int extra = 4; // 4 for [""].  Also count double-quote marks:
 1204         for (ccp = aName; *ccp; ++ccp) extra += *ccp=='"';
 1205         char *cp = aNameBuf.GetBufferSetLength(aParentNameLength + strlen(aName) + extra) + aParentNameLength;
 1206         *cp++ = '[';
 1207         *cp++ = '"';
 1208         for (ccp = aName; c = *ccp; ++ccp)
 1209         {
 1210             *cp++ = c;
 1211             if (c == '"')
 1212                 *cp++ = '"'; // i.e. replace " with ""
 1213         }
 1214         *cp++ = '"';
 1215         *cp++ = ']';
 1216         aNameBuf.ReleaseBuffer();
 1217     }
 1218 }
 1219 
 1220 int Debugger::WritePropertyData(LPCTSTR aData, size_t aDataSize, int aMaxEncodedSize)
 1221 // Accepts a "native" string, converts it to UTF-8, base64-encodes it and writes
 1222 // the end of the property's size attribute followed by the base64-encoded data.
 1223 {
 1224     int err;
 1225     
 1226 #ifdef UNICODE
 1227     LPCWSTR utf16_value = aData;
 1228     size_t total_utf16_size = aDataSize;
 1229 #else
 1230     // ANSI mode.  For simplicity, convert the entire data to UTF-16 rather than attempting to
 1231     // calculate how much ANSI text will produce the right number of UTF-8 bytes (since UTF-16
 1232     // is needed as an intermediate step anyway).  This would fail if the string length exceeds
 1233     // INT_MAX, but that would only matter if we built ANSI for x64 (and we don't).
 1234     CStringWCharFromChar utf16_buf(aData, aDataSize);
 1235     LPCWSTR utf16_value = utf16_buf.GetString();
 1236     size_t total_utf16_size = utf16_buf.GetLength();
 1237     if (!total_utf16_size && aDataSize) // Conversion failed (too large?)
 1238         return DEBUGGER_E_INTERNAL_ERROR;
 1239 #endif
 1240     
 1241     // The spec says: "The IDE should not read more data than the length defined in the packet
 1242     // header.  The IDE can determine if there is more data by using the property data length
 1243     // information."  This has two implications:
 1244     //  1) The size attribute should represent the total size, not the amount of data actually
 1245     //     returned (when limited by aMaxEncodedSize).  This is more useful anyway.
 1246     //  2) Since the data is encoded as UTF-8, the size attribute must be a UTF-8 byte count
 1247     //     for any comparison by the IDE to give the correct result.
 1248 
 1249     // According to the spec, -m 0 should mean "unlimited".
 1250     if (!aMaxEncodedSize)
 1251         aMaxEncodedSize = INT_MAX;
 1252     
 1253     // Calculate:
 1254     //  - the total size in terms of UTF-8 bytes (even if that exceeds INT_MAX).
 1255     size_t total_utf8_size = 0;
 1256     //  - the maximum number of wide chars to convert, taking aMaxEncodedSize into account.
 1257     int utf16_size = (int)total_utf16_size;
 1258     //  - the required buffer size for conversion, in bytes.
 1259     int utf8_size = -1;
 1260 
 1261     for (size_t i = 0; i < total_utf16_size; ++i)
 1262     {
 1263         wchar_t wc = utf16_value[i];
 1264         
 1265         int char_size;
 1266         if (wc <= 0x007F)
 1267             char_size = 1;
 1268         else if (wc <= 0x07FF)
 1269             char_size = 2;
 1270         else if (IS_SURROGATE_PAIR(wc, utf16_value[i+1]))
 1271             char_size = 4;
 1272         else
 1273             char_size = 3;
 1274         
 1275         total_utf8_size += char_size;
 1276 
 1277         if (total_utf8_size > (size_t)aMaxEncodedSize)
 1278         {
 1279             if (utf16_size == total_utf16_size) // i.e. this is the first surplus char.
 1280             {
 1281                 // Truncate the input; utf16_value[i] and beyond will not be converted/sent.
 1282                 utf16_size = (int)i;
 1283                 utf8_size = (int)(total_utf8_size - char_size);
 1284             }
 1285         }
 1286     }
 1287     if (utf8_size == -1) // Data was not limited by aMaxEncodedSize.
 1288         utf8_size = (int)total_utf8_size;
 1289     
 1290     // Calculate maximum length of base64-encoded data.
 1291     int space_needed = DEBUGGER_BASE64_ENCODED_SIZE(utf8_size);
 1292     
 1293     // Reserve enough space for the data's length, "> and encoded data.
 1294     if (err = mResponseBuf.ExpandIfNecessary(mResponseBuf.mDataUsed + space_needed + MAX_INTEGER_LENGTH + 2))
 1295         return err;
 1296     
 1297     // Complete the size attribute by writing the total size, in terms of UTF-8 bytes.
 1298     if (err = mResponseBuf.WriteF("%u\">", total_utf8_size))
 1299         return err;
 1300 
 1301     // Convert to UTF-8, using mResponseBuf temporarily.
 1302     char *utf8_value = mResponseBuf.mData + mResponseBuf.mDataSize - space_needed;
 1303     utf8_size = WideCharToMultiByte(CP_UTF8, 0, utf16_value, utf16_size, utf8_value, utf8_size, NULL, NULL);
 1304     if (!utf8_size && utf16_size) // Conversion failed.
 1305         return DEBUGGER_E_INTERNAL_ERROR;
 1306 
 1307     // Base64-encode and write the var data.
 1308     return mResponseBuf.WriteEncodeBase64(utf8_value, (size_t)utf8_size, true);
 1309 }
 1310 
 1311 int Debugger::WritePropertyData(ExprTokenType &aValue, int aMaxEncodedSize)
 1312 {
 1313     LPTSTR value;
 1314     size_t value_length;
 1315     TCHAR number_buf[MAX_NUMBER_SIZE];
 1316 
 1317     value = TokenToString(aValue, number_buf);
 1318     value_length = _tcslen(value);
 1319     
 1320     return WritePropertyData(value, value_length, aMaxEncodedSize);
 1321 }
 1322 
 1323 int Debugger::ParsePropertyName(LPCSTR aFullName, int aDepth, int aVarScope, bool aVarMustExist
 1324     , PropertySource &aResult)
 1325 {
 1326     CStringTCharFromUTF8 name_buf(aFullName);
 1327     LPTSTR name = name_buf.GetBuffer();
 1328     size_t name_length;
 1329     TCHAR c, *name_end, *src, *dst;
 1330     Var *var = NULL;
 1331     VarBkp *varbkp = NULL;
 1332     SymbolType key_type;
 1333     Object::FieldType *field;
 1334     Object::IndexType insert_pos;
 1335     Object *obj;
 1336 
 1337     aResult.kind = PropNone;
 1338 
 1339     name_end = StrChrAny(name, _T(".["));
 1340     if (name_end)
 1341     {
 1342         c = *name_end;
 1343         *name_end = '\0'; // Temporarily terminate.
 1344     }
 1345     name_length = _tcslen(name);
 1346 
 1347     // Validate name for more accurate error-reporting.
 1348     if (name_length > MAX_VAR_NAME_LENGTH || !Var::ValidateName(name, DISPLAY_NO_ERROR))
 1349         return DEBUGGER_E_INVALID_OPTIONS;
 1350 
 1351     if (aDepth > 0 && aVarScope != FINDVAR_GLOBAL)
 1352     {
 1353         Var **vars = NULL, **vars_end;
 1354         VarBkp *bkps = NULL, *bkps_end;
 1355         mStack.GetLocalVars(aDepth, vars, vars_end, bkps, bkps_end);
 1356         if (bkps)
 1357         {
 1358             for ( ; ; ++bkps)
 1359             {
 1360                 if (bkps == bkps_end)
 1361                 {
 1362                     // No local var at that depth, so make sure to not return the wrong local.
 1363                     aVarScope = FINDVAR_GLOBAL;
 1364                     break;
 1365                 }
 1366                 if (!_tcsicmp(bkps->mVar->mName, name))
 1367                 {
 1368                     varbkp = bkps;
 1369                     break;
 1370                 }
 1371             }
 1372         }
 1373         else if (vars)
 1374         {
 1375             for ( ; ; ++vars)
 1376             {
 1377                 if (vars == vars_end)
 1378                 {
 1379                     // No local var at that depth, so make sure to not return the wrong local.
 1380                     aVarScope = FINDVAR_GLOBAL;
 1381                     break;
 1382                 }
 1383                 if (!_tcsicmp((*vars)->mName, name))
 1384                 {
 1385                     var = *vars;
 1386                     break;
 1387                 }
 1388             }
 1389         }
 1390     }
 1391 
 1392     // If we're allowed to create variables
 1393     if (  !varbkp && !var
 1394         && (!aVarMustExist
 1395         // or this variable doesn't exist
 1396         || !(var = g_script.FindVar(name, name_length, NULL, aVarScope))
 1397             // but it is a built-in variable which hasn't been referenced yet:
 1398             && g_script.GetBuiltInVar(name))  )
 1399         // Find or add the variable.
 1400         var = g_script.FindOrAddVar(name, name_length, aVarScope);
 1401 
 1402     if (!var && !varbkp)
 1403         return DEBUGGER_E_UNKNOWN_PROPERTY;
 1404 
 1405     if (!name_end)
 1406     {
 1407         // Just a variable name.
 1408         if (var)
 1409             aResult.var = var, aResult.kind = PropVar;
 1410         else
 1411             aResult.bkp = varbkp, aResult.kind = PropVarBkp;
 1412         return DEBUGGER_E_OK;
 1413     }
 1414     IObject *iobj;
 1415     if (varbkp && varbkp->mType == VAR_ALIAS)
 1416         var = varbkp->mAliasFor;
 1417     if (var)
 1418         iobj = var->HasObject() ? var->Object() : NULL;
 1419     else
 1420         iobj = (varbkp->mAttrib & VAR_ATTRIB_OBJECT) ? varbkp->mObject : NULL;
 1421 
 1422     if (  !(obj = dynamic_cast<Object *>(iobj))  )
 1423         return DEBUGGER_E_UNKNOWN_PROPERTY;
 1424 
 1425     // aFullName contains a '.' or '['.  Although it looks like an expression, the IDE should
 1426     // only pass a property name which we gave it in response to a previous command, so we
 1427     // only need to support the subset of expression syntax used by WriteObjectPropertyXml().
 1428     for (*name_end = c; ; )
 1429     {
 1430         name = name_end + 1;
 1431         if (c == '[')
 1432         {
 1433             if (*name == '"')
 1434             {
 1435                 // Quoted string which may contain any character.
 1436                 // Replace "" with " in-place and find and of string:
 1437                 for (dst = src = ++name; c = *src; ++src)
 1438                 {
 1439                     if (c == '"')
 1440                     {
 1441                         // Quote mark; but is it a literal quote mark?
 1442                         if (*++src != '"')
 1443                             // Nope.
 1444                             break;
 1445                         //else above skipped the second quote mark, so fall through:
 1446                     }
 1447                     *dst++ = c;
 1448                 }
 1449                 if (*src != ']') return DEBUGGER_E_INVALID_OPTIONS;
 1450                 *dst = '\0'; // Only after the check above, since src might be == dst.
 1451                 name_end = src + 1; // Set it up for the next iteration.
 1452                 key_type = SYM_STRING;
 1453             }
 1454             else if (!_tcsnicmp(name, _T("Object("), 7))
 1455             {
 1456                 // Object(n) where n is the address of a key object, as a literal signed integer.
 1457                 name += 7;
 1458                 name_end = _tcschr(name, ')');
 1459                 if (!name_end || name_end[1] != ']') return DEBUGGER_E_INVALID_OPTIONS;
 1460                 *name_end = '\0';
 1461                 name_end += 2; // Set it up for the next iteration.
 1462                 key_type = SYM_OBJECT;
 1463             }
 1464             else
 1465             {
 1466                 // The only other valid form is a literal signed integer.
 1467                 name_end = _tcschr(name, ']');
 1468                 if (!name_end) return DEBUGGER_E_INVALID_OPTIONS;
 1469                 *name_end = '\0'; // Although not actually necessary for _ttoi(), seems best for maintainability.
 1470                 ++name_end; // Set it up for the next iteration.
 1471                 key_type = SYM_INTEGER;
 1472             }
 1473             c = *name_end; // Set for the next iteration.
 1474         }
 1475         else if (c == '.')
 1476         {
 1477             // For simplicity, let this be any string terminated by '.' or '['.
 1478             // Actual expressions require it to contain only alphanumeric chars and/or '_'.
 1479             name_end = StrChrAny(name, _T(".[")); // This also sets it up for the next iteration.
 1480             if (name_end)
 1481             {
 1482                 c = *name_end; // Save this for the next iteration.
 1483                 *name_end = '\0';
 1484             }
 1485             else
 1486                 c = 0;
 1487             //else there won't be a next iteration.
 1488             key_type = IsPureNumeric(name); // SYM_INTEGER or SYM_STRING.
 1489         }
 1490         else
 1491             return DEBUGGER_E_INVALID_OPTIONS;
 1492         
 1493         if (*name != '<' || name[-1] != '.') // Not a pseudo-property; i.e. ["<base>"] is always a key-value pair.
 1494         {
 1495             Object::KeyType key;
 1496             if (key_type == SYM_STRING)
 1497                 key.s = name;
 1498             else // SYM_INTEGER or SYM_OBJECT
 1499                 key.i = Exp32or64(_ttoi,_ttoi64)(name);
 1500             field = obj->FindField(key_type, key, insert_pos);
 1501         }
 1502         else
 1503             field = NULL;
 1504 
 1505         if (!field)
 1506         {
 1507             // IDE should request .<base> only if it was returned by property_get or context_get,
 1508             // so this always means the object's base (field is always NULL).  By contrast, .base
 1509             // and ["base"] originate either from a key-value pair or the user "inspecting" an
 1510             // expression like `myObj.base`.  Since no field was found, assume it's the latter.
 1511             if (!_tcsicmp(name, _T("base")) || !_tcsicmp(name - 1, _T(".<base>")))
 1512             {
 1513                 if (!c)
 1514                 {
 1515                     // For property_set, this won't allow the base to be set (success="0").
 1516                     // That seems okay since it could only ever be set to NULL anyway.
 1517                     aResult.kind = PropValue;
 1518                     if (obj->mBase)
 1519                         aResult.value.SetValue(obj->mBase);
 1520                     else
 1521                         aResult.value.SetValue(_T(""));
 1522                     return DEBUGGER_E_OK;
 1523                 }
 1524                 if (  !(obj = dynamic_cast<Object *>(obj->mBase))  )
 1525                     return DEBUGGER_E_UNKNOWN_PROPERTY;
 1526                 continue; // Search the base object's fields.
 1527             }
 1528             else
 1529                 return DEBUGGER_E_UNKNOWN_PROPERTY;
 1530         }
 1531 
 1532         if (!c)
 1533         {
 1534             // All done!
 1535             aResult.kind = PropField;
 1536             aResult.field = field;
 1537             return DEBUGGER_E_OK;
 1538         }
 1539 
 1540         if ( field->symbol != SYM_OBJECT || !(obj = dynamic_cast<Object *>(field->object)) )
 1541             // No usable target object for the next iteration, therefore the property mustn't exist.
 1542             return DEBUGGER_E_UNKNOWN_PROPERTY;
 1543 
 1544     } // infinite loop.
 1545 }
 1546 
 1547 
 1548 int Debugger::property_get_or_value(char **aArgV, int aArgCount, char *aTransactionId, bool aIsPropertyGet)
 1549 {
 1550     int err;
 1551     char arg, *value;
 1552 
 1553     char *name = NULL;
 1554     int context_id = 0, depth = 0; // Var context and stack depth.
 1555     CStringA name_buf;
 1556     PropertyInfo prop(name_buf);
 1557     prop.pagesize = mMaxChildren;
 1558     prop.max_data = aIsPropertyGet ? mMaxPropertyData : 1024*1024*1024; // Limit property_value to 1GB by default.
 1559     prop.max_depth = mMaxDepth; // Max property nesting depth.
 1560 
 1561     for (int i = 0; i < aArgCount; ++i)
 1562     {
 1563         arg = ArgChar(aArgV, i);
 1564         value = ArgValue(aArgV, i);
 1565         switch (arg)
 1566         {
 1567         // property long name
 1568         case 'n': name = value; break;
 1569         // context id - optional, default zero. see PropertyContextType enum.
 1570         case 'c': context_id = atoi(value); break;
 1571         // stack depth - optional, default zero
 1572         case 'd':
 1573             depth = atoi(value);
 1574             if (depth && (depth < 0 || depth >= mStack.Depth())) // Allow depth 0 even when stack is empty.
 1575                 return DEBUGGER_E_INVALID_STACK_DEPTH;
 1576             break;
 1577         // max data size - optional
 1578         case 'm': prop.max_data = atoi(value); break;
 1579         // data page - optional
 1580         case 'p': prop.page = atoi(value); break;
 1581 
 1582         default:
 1583             return DEBUGGER_E_INVALID_OPTIONS;
 1584         }
 1585     }
 1586 
 1587     if (!name || prop.max_data < 0)
 1588         return DEBUGGER_E_INVALID_OPTIONS;
 1589 
 1590     int always_use;
 1591     switch (context_id)
 1592     {
 1593     // It seems best to allow context id zero to retrieve either a local or global,
 1594     // rather than requiring the IDE to check each context when looking up a variable.
 1595     //case PC_Local:    always_use = FINDVAR_LOCAL; break;
 1596     case PC_Local:  always_use = FINDVAR_DEFAULT; break;
 1597     case PC_Global: always_use = FINDVAR_GLOBAL; break;
 1598     default:
 1599         return DEBUGGER_E_INVALID_CONTEXT;
 1600     }
 1601 
 1602     if (err = ParsePropertyName(name, depth, always_use, true, prop))
 1603     {
 1604         // Var not found/invalid name.
 1605         if (!aIsPropertyGet)
 1606             return err;
 1607 
 1608         // NOTEPAD++ DBGP PLUGIN:
 1609         // The DBGp plugin for Notepad++ assumes property_get will always succeed.
 1610         // Property retrieval on mouse hover does not choose words intelligently,
 1611         // so it will attempt to retrieve properties like ";" or " r".
 1612         // If we respond with an <error/> instead of a <property/>, Notepad++ will
 1613         // show an error message and then become unstable. Even after the editor
 1614         // window is closed, notepad++.exe must be terminated forcefully.
 1615         //
 1616         // As a work-around (until this is resolved by the plugin's author),
 1617         // we return a property with an empty value and the 'undefined' type.
 1618         
 1619         return mResponseBuf.WriteF(
 1620             "<response command=\"property_get\" transaction_id=\"%e\">"
 1621                 "<property name=\"%e\" fullname=\"%e\" type=\"undefined\" facet=\"\" size=\"0\" children=\"0\"/>"
 1622             "</response>"
 1623             , aTransactionId, name, name);
 1624     }
 1625     //else var and field were set by the called function.
 1626 
 1627     LPTSTR value_buf = NULL;
 1628     switch (prop.kind)
 1629     {
 1630     case PropVar: err = GetPropertyInfo(*prop.var, prop, value_buf); break;
 1631     case PropVarBkp: err = GetPropertyInfo(*prop.bkp, prop, value_buf); break;
 1632     case PropField: err = GetPropertyInfo(*prop.field, prop); break;
 1633     case PropValue: err = DEBUGGER_E_OK; break;
 1634     }
 1635     if (!err)
 1636     {
 1637         if (aIsPropertyGet)
 1638         {
 1639             mResponseBuf.WriteF(
 1640                 "<response command=\"property_get\" transaction_id=\"%e\">"
 1641                 , aTransactionId);
 1642             name_buf.SetString(name); // prop.fullname is an alias of name_buf.
 1643             // For simplicity and code size, we use the full caller-specified name
 1644             // instead of trying to parse out the "short" name or record it during
 1645             // ParsePropertyName (which would have to take into account differences
 1646             // between UTF-8 and LPTSTR):
 1647             prop.name = name;
 1648             err = WritePropertyXml(prop);
 1649         }
 1650         else
 1651         {
 1652             mResponseBuf.WriteF(
 1653                 "<response command=\"property_value\" transaction_id=\"%e\" encoding=\"base64\" size=\""
 1654                 , aTransactionId);
 1655             err = WritePropertyData(prop.value, prop.max_data);
 1656         }
 1657     }
 1658     free(value_buf);
 1659     return err ? err : mResponseBuf.Write("</response>");
 1660 }
 1661 
 1662 DEBUGGER_COMMAND(Debugger::property_set)
 1663 {
 1664     int err;
 1665     char arg, *value;
 1666 
 1667     char *name = NULL, *new_value = NULL, *type = "string";
 1668     int context_id = 0, depth = 0;
 1669 
 1670     for (int i = 0; i < aArgCount; ++i)
 1671     {
 1672         arg = ArgChar(aArgV, i);
 1673         value = ArgValue(aArgV, i);
 1674         switch (arg)
 1675         {
 1676         // property long name
 1677         case 'n': name = value; break;
 1678         // context id - optional, default zero. see PropertyContextType enum.
 1679         case 'c': context_id = atoi(value); break;
 1680         // stack depth - optional, default zero
 1681         case 'd':
 1682             depth = atoi(value);
 1683             if (depth && (depth < 0 || depth >= mStack.Depth())) // Allow depth 0 even when stack is empty.
 1684                 return DEBUGGER_E_INVALID_STACK_DEPTH;
 1685             break;
 1686         // new base64-encoded value
 1687         case '-': new_value = value; break;
 1688         // data type: string, integer or float
 1689         case 't': type = value; break;
 1690         // not sure what the use of the 'length' arg is...
 1691         case 'l': break;
 1692 
 1693         default:
 1694             return DEBUGGER_E_INVALID_OPTIONS;
 1695         }
 1696     }
 1697 
 1698     if (!name || !new_value)
 1699         return DEBUGGER_E_INVALID_OPTIONS;
 1700 
 1701     int always_use;
 1702     switch (context_id)
 1703     {
 1704     // For consistency with property_get, create a local only if no global exists.
 1705     //case PC_Local:    always_use = FINDVAR_LOCAL; break;
 1706     case PC_Local:  always_use = FINDVAR_DEFAULT; break;
 1707     case PC_Global: always_use = FINDVAR_GLOBAL; break;
 1708     default:
 1709         return DEBUGGER_E_INVALID_CONTEXT;
 1710     }
 1711 
 1712     PropertySource target;
 1713     if (err = ParsePropertyName(name, depth, always_use, false, target))
 1714         return err;
 1715 
 1716     // "Data must be encoded using base64." : https://xdebug.org/docs-dbgp.php
 1717     // Fixed in v1.1.24.03 to expect base64 even for integer/float:
 1718     int value_length = (int)Base64Decode(new_value, new_value);
 1719     
 1720     CString val_buf;
 1721     ExprTokenType val;
 1722     if (!strcmp(type, "integer"))
 1723     {
 1724         val.symbol = SYM_INTEGER;
 1725         val.value_int64 = _atoi64(new_value);
 1726     }
 1727     else if (!strcmp(type, "float"))
 1728     {
 1729         val.symbol = SYM_FLOAT;
 1730         val.value_double = atof(new_value);
 1731     }
 1732     else // Assume type is "string", since that's the only other supported type.
 1733     {
 1734         StringUTF8ToTChar(new_value, val_buf, value_length);
 1735         val.symbol = SYM_STRING;
 1736         val.marker = (LPTSTR)val_buf.GetString();
 1737     }
 1738 
 1739     bool success;
 1740     switch (target.kind)
 1741     {
 1742     case PropVarBkp:
 1743         if (target.bkp->mType != VAR_ALIAS)
 1744         {
 1745             VarBkp &bkp = *target.bkp;
 1746             if (bkp.mAttrib & VAR_ATTRIB_OBJECT)
 1747             {
 1748                 bkp.mAttrib &= ~VAR_ATTRIB_OBJECT;
 1749                 bkp.mObject->Release();
 1750             }
 1751             if (val.symbol == SYM_STRING)
 1752             {
 1753                 if ((val.marker_length + 1) * sizeof(TCHAR) > bkp.mByteCapacity && val.marker_length)
 1754                 {
 1755                     if (bkp.mHowAllocated == ALLOC_MALLOC)
 1756                         free(bkp.mCharContents);
 1757                     else
 1758                         bkp.mHowAllocated = ALLOC_MALLOC;
 1759                     bkp.mByteCapacity = (val_buf.GetAllocLength() + 1) * sizeof(TCHAR);
 1760                     bkp.mCharContents = val_buf.DetachBuffer();
 1761                     bkp.mAttrib &= ~VAR_ATTRIB_OFTEN_REMOVED;
 1762                 }
 1763                 else
 1764                     tmemcpy(bkp.mCharContents, val.marker, val.marker_length + 1);
 1765                 bkp.mByteLength = val.marker_length * sizeof(TCHAR);
 1766             }
 1767             else
 1768             {
 1769                 bkp.mContentsInt64 = val.value_int64;
 1770                 bkp.mAttrib = bkp.mAttrib
 1771                     & ~(VAR_ATTRIB_CACHE | VAR_ATTRIB_UNINITIALIZED)
 1772                     | (val.symbol == SYM_INTEGER ? VAR_ATTRIB_HAS_VALID_INT64 : VAR_ATTRIB_HAS_VALID_DOUBLE) | VAR_ATTRIB_CONTENTS_OUT_OF_DATE;
 1773             }
 1774             success = true;
 1775             break;
 1776         }
 1777         target.var = target.bkp->mAliasFor;
 1778         // Fall through:
 1779     case PropVar:
 1780         success = !VAR_IS_READONLY(*target.var) && target.var->Assign(val);
 1781         break;
 1782     case PropField:
 1783         success = target.field->Assign(val);
 1784         break;
 1785     default:
 1786         success = false;
 1787     }
 1788 
 1789     return mResponseBuf.WriteF(
 1790         "<response command=\"property_set\" success=\"%i\" transaction_id=\"%e\"/>"
 1791         , (int)success, aTransactionId);
 1792 }
 1793 
 1794 DEBUGGER_COMMAND(Debugger::source)
 1795 {
 1796     char arg, *value;
 1797 
 1798     char *filename = NULL;
 1799     LineNumberType begin_line = 0, end_line = UINT_MAX;
 1800 
 1801     for (int i = 0; i < aArgCount; ++i)
 1802     {
 1803         arg = ArgChar(aArgV, i);
 1804         value = ArgValue(aArgV, i);
 1805         switch (arg)
 1806         {
 1807         case 'b': begin_line = strtoul(value, NULL, 10); break;
 1808         case 'e': end_line = strtoul(value, NULL, 10); break;
 1809         case 'f': filename = value; break;
 1810         default:
 1811             return DEBUGGER_E_INVALID_OPTIONS;
 1812         }
 1813     }
 1814 
 1815     if (!filename || begin_line > end_line)
 1816         return DEBUGGER_E_INVALID_OPTIONS;
 1817 
 1818     int file_index;
 1819 
 1820     // Decode filename URI -> path, in-place.
 1821     DecodeURI(filename);
 1822 
 1823     CStringTCharFromUTF8 filename_t(filename);
 1824 
 1825     // Ensure the file is actually a source file - i.e. don't let the debugger client retrieve any arbitrary file.
 1826     for (file_index = 0; file_index < Line::sSourceFileCount; ++file_index)
 1827     {
 1828         if (!_tcsicmp(filename_t, Line::sSourceFile[file_index]))
 1829         {
 1830             TextFile tf;
 1831             if (!tf.Open(filename_t, DEFAULT_READ_FLAGS, g_DefaultScriptCodepage))
 1832                 return DEBUGGER_E_CAN_NOT_OPEN_FILE;
 1833             
 1834             mResponseBuf.WriteF("<response command=\"source\" success=\"1\" transaction_id=\"%e\" encoding=\"base64\">"
 1835                                 , aTransactionId);
 1836 
 1837             CStringA utf8_buf;
 1838             TCHAR line_buf[LINE_SIZE + 2]; // May contain up to two characters of the previous line to simplify base64-encoding.
 1839             int line_length;
 1840             int line_remainder = 0;
 1841 
 1842             LineNumberType current_line = 0;
 1843             
 1844             while (-1 != (line_length = tf.ReadLine(line_buf + line_remainder, LINE_SIZE)))
 1845             {
 1846                 if (++current_line >= begin_line)
 1847                 {
 1848                     if (current_line > end_line)
 1849                         break; // done.
 1850                     
 1851                     // Encode in multiples of 3 characters to avoid inserting padding characters.
 1852                     line_length += line_remainder; // Include remainder of previous line.
 1853                     line_remainder = line_length % 3;
 1854                     line_length -= line_remainder;
 1855 
 1856                     if (line_length)
 1857                     {
 1858                         // Convert line to UTF-8.
 1859                         StringTCharToUTF8(line_buf, utf8_buf, line_length);
 1860                         // Base64-encode and write this line and its trailing newline character into the response buffer.
 1861                         if (mResponseBuf.WriteEncodeBase64(utf8_buf.GetString(), utf8_buf.GetLength()) != DEBUGGER_E_OK)
 1862                             goto break_outer_loop; // fail.
 1863                     }
 1864                     //else not enough data to encode in this iteration.
 1865 
 1866                     if (line_remainder) // 0, 1 or 2.
 1867                     {
 1868                         line_buf[0] = line_buf[line_length];
 1869                         if (line_remainder > 1)
 1870                             line_buf[1] = line_buf[line_length + 1];
 1871                     }
 1872                 }
 1873             }
 1874 
 1875             if (line_remainder) // Write any left-over characters.
 1876             {
 1877                 StringTCharToUTF8(line_buf, utf8_buf, line_remainder);
 1878                 if (mResponseBuf.WriteEncodeBase64(utf8_buf.GetString(), utf8_buf.GetLength()) != DEBUGGER_E_OK)
 1879                     break; // fail.
 1880             }
 1881 
 1882             if (!current_line || current_line < begin_line)
 1883                 break; // fail.
 1884             // else if (current_line < end_line) -- just return what we can.
 1885 
 1886             return mResponseBuf.Write("</response>");
 1887         }
 1888     }
 1889 break_outer_loop:
 1890     // If we got here, one of the following is true:
 1891     //  - Something failed and used 'break'.
 1892     //  - The requested file is not a known source file of this script.
 1893     mResponseBuf.Clear();
 1894     return mResponseBuf.WriteF(
 1895         "<response command=\"source\" success=\"0\" transaction_id=\"%e\"/>"
 1896         , aTransactionId);
 1897 }
 1898 
 1899 int Debugger::redirect_std(char **aArgV, int aArgCount, char *aTransactionId, char *aCommandName)
 1900 {
 1901     int new_mode = -1;
 1902     // stdout and stderr accept exactly one arg: -c mode.
 1903     if (aArgCount == 1 && ArgChar(aArgV, 0) == 'c')
 1904         new_mode = atoi(ArgValue(aArgV, 0));
 1905 
 1906     if (new_mode < SR_Disabled || new_mode > SR_Redirect)
 1907         return DEBUGGER_E_INVALID_OPTIONS;
 1908 
 1909     if (!stricmp(aCommandName, "stdout"))
 1910         mStdOutMode = (StreamRedirectType)new_mode;
 1911     else
 1912         mStdErrMode = (StreamRedirectType)new_mode;
 1913 
 1914     return mResponseBuf.WriteF(
 1915         "<response command=\"%s\" success=\"1\" transaction_id=\"%e\"/>"
 1916         , aCommandName, aTransactionId);
 1917 }
 1918 
 1919 DEBUGGER_COMMAND(Debugger::redirect_stdout)
 1920 {
 1921     return redirect_std(aArgV, aArgCount, aTransactionId, "stdout");
 1922 }
 1923 
 1924 DEBUGGER_COMMAND(Debugger::redirect_stderr)
 1925 {
 1926     return redirect_std(aArgV, aArgCount, aTransactionId, "stderr");
 1927 }
 1928 
 1929 //
 1930 // END DBGP COMMANDS
 1931 //
 1932 
 1933 //
 1934 // DBGP STREAMS
 1935 //
 1936 
 1937 int Debugger::WriteStreamPacket(LPCTSTR aText, LPCSTR aType)
 1938 {
 1939     ASSERT(!mResponseBuf.mFailed);
 1940     mResponseBuf.WriteF("<stream type=\"%s\">", aType);
 1941     CStringUTF8FromTChar packet(aText);
 1942     mResponseBuf.WriteEncodeBase64(packet, packet.GetLength() + 1); // Includes the null-terminator.
 1943     mResponseBuf.Write("</stream>");
 1944     return SendResponse();
 1945 }
 1946 
 1947 bool Debugger::OutputStdErr(LPCTSTR aText)
 1948 {
 1949     if (mStdErrMode != SR_Disabled) // i.e. SR_Copy or SR_Redirect
 1950         WriteStreamPacket(aText, "stderr");
 1951     return mStdErrMode == SR_Redirect;
 1952 }
 1953 
 1954 bool Debugger::OutputStdOut(LPCTSTR aText)
 1955 {
 1956     if (mStdOutMode != SR_Disabled) // i.e. SR_Copy or SR_Redirect
 1957         WriteStreamPacket(aText, "stdout");
 1958     return mStdOutMode == SR_Redirect;
 1959 }
 1960 
 1961 int Debugger::SendErrorResponse(char *aCommandName, char *aTransactionId, int aError, char *aExtraAttributes)
 1962 {
 1963     mResponseBuf.WriteF("<response command=\"%s\" transaction_id=\"%e"
 1964         , aCommandName, aTransactionId);
 1965     
 1966     if (aExtraAttributes)
 1967         mResponseBuf.WriteF("\" %s>", aExtraAttributes);
 1968     else
 1969         mResponseBuf.Write("\">");
 1970 
 1971     mResponseBuf.WriteF("<error code=\"%i\"/></response>", aError);
 1972 
 1973     return SendResponse();
 1974 }
 1975 
 1976 int Debugger::SendStandardResponse(char *aCommandName, char *aTransactionId)
 1977 {
 1978     mResponseBuf.WriteF("<response command=\"%s\" transaction_id=\"%e\"/>"
 1979                         , aCommandName, aTransactionId);
 1980 
 1981     return SendResponse();
 1982 }
 1983 
 1984 int Debugger::SendContinuationResponse(char *aCommand, char *aStatus, char *aReason)
 1985 {
 1986     if (!aCommand)
 1987     {
 1988         switch (mInternalState)
 1989         {
 1990         case DIS_StepInto:  aCommand = "step_into"; break;
 1991         case DIS_StepOver:  aCommand = "step_over"; break;
 1992         case DIS_StepOut:   aCommand = "step_out";  break;
 1993         case DIS_Run:       aCommand = "run";       break;
 1994         // Seems more useful then silently failing:
 1995         default:            aCommand = "";
 1996         }
 1997     }
 1998 
 1999     mResponseBuf.WriteF("<response command=\"%s\" status=\"%s\" reason=\"%s\" transaction_id=\"%e\"/>"
 2000                         , aCommand, aStatus, aReason, (LPCSTR)mContinuationTransactionId);
 2001 
 2002     return SendResponse();
 2003 }
 2004 
 2005 // Debugger::ReceiveCommand
 2006 //
 2007 // Receives a full command line into mCommandBuf. If part of the next command is
 2008 // received, it remains in the buffer until the next call to ReceiveCommand.
 2009 //
 2010 int Debugger::ReceiveCommand(int *aCommandLength)
 2011 {
 2012     ASSERT(mSocket != INVALID_SOCKET); // Shouldn't be at this point; will be caught by recv() anyway.
 2013     ASSERT(!mCommandBuf.mFailed); // Should have been previously reset.
 2014 
 2015     DWORD u = 0;
 2016 
 2017     for(;;)
 2018     {
 2019         // Check data received in the previous iteration or by a previous call to ReceiveCommand().
 2020         for ( ; u < mCommandBuf.mDataUsed; ++u)
 2021         {
 2022             if (mCommandBuf.mData[u] == '\0')
 2023             {
 2024                 if (aCommandLength)
 2025                     *aCommandLength = u; // Does not include the null-terminator.
 2026                 return DEBUGGER_E_OK;
 2027             }
 2028         }
 2029 
 2030         // Init or expand the buffer as necessary.
 2031         if (mCommandBuf.mDataUsed == mCommandBuf.mDataSize && mCommandBuf.Expand() != DEBUGGER_E_OK)
 2032             return FatalError(); // This also calls mCommandBuf.Clear() via Disconnect().
 2033 
 2034         // Receive and append data.
 2035         int bytes_received = recv(mSocket, mCommandBuf.mData + mCommandBuf.mDataUsed, (int)(mCommandBuf.mDataSize - mCommandBuf.mDataUsed), 0);
 2036 
 2037         if (bytes_received == SOCKET_ERROR)
 2038             return FatalError();
 2039 
 2040         mCommandBuf.mDataUsed += bytes_received;
 2041     }
 2042 }
 2043 
 2044 // Debugger::SendResponse
 2045 //
 2046 // Sends a response to a command, using mResponseBuf.mData as the message body.
 2047 //
 2048 int Debugger::SendResponse()
 2049 {
 2050     ASSERT(!mResponseBuf.mFailed);
 2051 
 2052     char response_header[DEBUGGER_RESPONSE_OVERHEAD];
 2053     
 2054     // Each message is prepended with a stringified integer representing the length of the XML data packet.
 2055     Exp32or64(_itoa,_i64toa)(mResponseBuf.mDataUsed + DEBUGGER_XML_TAG_SIZE, response_header, 10);
 2056 
 2057     // The length and XML data are separated by a NULL byte.
 2058     char *buf = strchr(response_header, '\0') + 1;
 2059 
 2060     // The XML document tag must always be present to provide XML version and encoding information.
 2061     buf += sprintf(buf, "%s", DEBUGGER_XML_TAG);
 2062 
 2063     // Send the response header.
 2064     if (  SOCKET_ERROR == send(mSocket, response_header, (int)(buf - response_header), 0)
 2065        // Messages sent by the debugger engine must always be NULL terminated.
 2066        // Failure to write the last byte should be extremely rare, so no attempt
 2067        // is made to recover from that condition.
 2068        || DEBUGGER_E_OK != mResponseBuf.Write("\0", 1)
 2069        // Send the message body.
 2070        || SOCKET_ERROR == send(mSocket, mResponseBuf.mData, (int)mResponseBuf.mDataUsed, 0)  )
 2071     {
 2072         // Unrecoverable error: disconnect the debugger.
 2073         return FatalError();
 2074     }
 2075 
 2076     mResponseBuf.Clear();
 2077     return DEBUGGER_E_OK;
 2078 }
 2079 
 2080 // Debugger::Connect
 2081 //
 2082 // Connect to a debugger UI. Returns a Winsock error code on failure, otherwise 0.
 2083 //
 2084 int Debugger::Connect(const char *aAddress, const char *aPort)
 2085 {
 2086     int err;
 2087     WSADATA wsadata;
 2088     SOCKET s;
 2089     
 2090     if (WSAStartup(MAKEWORD(2,2), &wsadata))
 2091         return FatalError();
 2092     
 2093     s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
 2094 
 2095     if (s != INVALID_SOCKET)
 2096     {
 2097         addrinfo hints = {0};
 2098         addrinfo *res;
 2099         
 2100         hints.ai_family = AF_INET;
 2101         hints.ai_socktype = SOCK_STREAM;
 2102         hints.ai_protocol = IPPROTO_TCP;
 2103 
 2104         err = getaddrinfo(aAddress, aPort, &hints, &res);
 2105         
 2106         if (err == 0)
 2107         {
 2108             for (;;)
 2109             {
 2110                 err = connect(s, res->ai_addr, (int)res->ai_addrlen);
 2111                 if (err == 0)
 2112                     break;
 2113                 switch (MessageBox(g_hWnd, DEBUGGER_ERR_FAILEDTOCONNECT, g_script.mFileSpec, MB_ABORTRETRYIGNORE | MB_ICONSTOP | MB_SETFOREGROUND | MB_APPLMODAL))
 2114                 {
 2115                 case IDABORT:
 2116                     g_script.ExitApp(EXIT_CLOSE);
 2117                     // If it didn't exit (due to OnExit), fall through to the next case:
 2118                 case IDIGNORE:
 2119                     closesocket(s);
 2120                     return DEBUGGER_E_INTERNAL_ERROR;
 2121                 }
 2122             }
 2123             
 2124             freeaddrinfo(res);
 2125             
 2126             if (err == 0)
 2127             {
 2128                 mSocket = s;
 2129 
 2130                 CStringUTF8FromTChar ide_key(CString().GetEnvironmentVariable(_T("DBGP_IDEKEY")));
 2131                 CStringUTF8FromTChar session(CString().GetEnvironmentVariable(_T("DBGP_COOKIE")));
 2132 
 2133                 // Clear the buffer in case of a previous failed session.
 2134                 mResponseBuf.Clear();
 2135 
 2136                 // Write init message.
 2137                 mResponseBuf.WriteF("<init appid=\"" AHK_NAME "\" ide_key=\"%e\" session=\"%e\" thread=\"%u\" parent=\"\" language=\"" DEBUGGER_LANG_NAME "\" protocol_version=\"1.0\" fileuri=\""
 2138                     , ide_key.GetString(), session.GetString(), GetCurrentThreadId());
 2139                 mResponseBuf.WriteFileURI(U4T(g_script.mFileSpec));
 2140                 mResponseBuf.Write("\"/>");
 2141 
 2142                 if (SendResponse() == DEBUGGER_E_OK)
 2143                 {
 2144                     // mCurrLine isn't updated unless the debugger is connected, so set it now.
 2145                     // g_script.mCurrLine should always be non-NULL after the script is loaded,
 2146                     // even if no threads are active.
 2147                     mCurrLine = g_script.mCurrLine;
 2148                     return DEBUGGER_E_OK;
 2149                 }
 2150 
 2151                 mSocket = INVALID_SOCKET; // Don't want FatalError() to attempt a second closesocket().
 2152             }
 2153         }
 2154 
 2155         closesocket(s);
 2156     }
 2157 
 2158     WSACleanup();
 2159     return FatalError(DEBUGGER_ERR_FAILEDTOCONNECT DEBUGGER_ERR_DISCONNECT_PROMPT);
 2160 }
 2161 
 2162 // Debugger::Disconnect
 2163 //
 2164 // Disconnect the debugger UI.
 2165 //
 2166 int Debugger::Disconnect()
 2167 {
 2168     if (mSocket != INVALID_SOCKET)
 2169     {
 2170         shutdown(mSocket, 2);
 2171         closesocket(mSocket);
 2172         mSocket = INVALID_SOCKET;
 2173         WSACleanup();
 2174     }
 2175     // These are reset in case we re-attach to the debugger client later:
 2176     mCommandBuf.Clear();
 2177     mResponseBuf.Clear();
 2178     mStdOutMode = SR_Disabled;
 2179     mStdErrMode = SR_Disabled;
 2180     if (mInternalState == DIS_Break)
 2181         ExitBreakState();
 2182     mInternalState = DIS_Starting;
 2183     return DEBUGGER_E_OK;
 2184 }
 2185 
 2186 // Debugger::Exit
 2187 //
 2188 // Gracefully end debug session.  Called on script exit.  Also called by "detach" DBGp command.
 2189 //
 2190 void Debugger::Exit(ExitReasons aExitReason, char *aCommandName)
 2191 {
 2192     if (mSocket == INVALID_SOCKET)
 2193         return;
 2194     // Don't care if it fails as we may be exiting due to a previous failure.
 2195     SendContinuationResponse(aCommandName, "stopped", aExitReason == EXIT_ERROR ? "error" : "ok");
 2196     Disconnect();
 2197 }
 2198 
 2199 int Debugger::FatalError(LPCTSTR aMessage)
 2200 {
 2201     g_Debugger.Disconnect();
 2202 
 2203     if (IDNO == MessageBox(g_hWnd, aMessage, g_script.mFileSpec, MB_YESNO | MB_ICONSTOP | MB_SETFOREGROUND | MB_APPLMODAL))
 2204     {
 2205         // This might not exit, depending on OnExit:
 2206         g_script.ExitApp(EXIT_CLOSE);
 2207     }
 2208     return DEBUGGER_E_INTERNAL_ERROR;
 2209 }
 2210 
 2211 const char *Debugger::sBase64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
 2212 
 2213 #define BINARY_TO_BASE64_CHAR(b) (sBase64Chars[(b) & 63])
 2214 #define BASE64_CHAR_TO_BINARY(q) (strchr(sBase64Chars, q)-sBase64Chars)
 2215 
 2216 // Encode base 64 data.
 2217 size_t Debugger::Base64Encode(char *aBuf, const char *aInput, size_t aInputSize/* = -1*/)
 2218 {
 2219     UINT_PTR buffer;
 2220     size_t i, len = 0;
 2221 
 2222     if (aInputSize == -1) // Direct comparison since aInputSize is unsigned.
 2223         aInputSize = strlen(aInput);
 2224 
 2225     for (i = aInputSize; i > 2; i -= 3)
 2226     {
 2227         buffer = (UCHAR)aInput[0] << 16 | (UCHAR)aInput[1] << 8 | (UCHAR)aInput[2]; // L39: Fixed for chars outside the range 0..127. [thanks jackieku]
 2228         aInput += 3;
 2229 
 2230         aBuf[len + 0] = BINARY_TO_BASE64_CHAR(buffer >> 18);
 2231         aBuf[len + 1] = BINARY_TO_BASE64_CHAR(buffer >> 12);
 2232         aBuf[len + 2] = BINARY_TO_BASE64_CHAR(buffer >> 6);
 2233         aBuf[len + 3] = BINARY_TO_BASE64_CHAR(buffer);
 2234         len += 4;
 2235     }
 2236     if (i > 0)
 2237     {
 2238         buffer = (UCHAR)aInput[0] << 16;
 2239         if (i > 1)
 2240             buffer |= (UCHAR)aInput[1] << 8;
 2241         // aInput not incremented as it is not used below.
 2242 
 2243         aBuf[len + 0] = BINARY_TO_BASE64_CHAR(buffer >> 18);
 2244         aBuf[len + 1] = BINARY_TO_BASE64_CHAR(buffer >> 12);
 2245         aBuf[len + 2] = (i > 1) ? BINARY_TO_BASE64_CHAR(buffer >> 6) : '=';
 2246         aBuf[len + 3] = '=';
 2247         len += 4;
 2248     }
 2249     aBuf[len] = '\0';
 2250     return len;
 2251 }
 2252 
 2253 // Decode base 64 data. aBuf and aInput may point to the same buffer.
 2254 size_t Debugger::Base64Decode(char *aBuf, const char *aInput, size_t aInputSize/* = -1*/)
 2255 {
 2256     UINT_PTR buffer;
 2257     size_t i, len = 0;
 2258 
 2259     if (aInputSize == -1) // Direct comparison since aInputSize is unsigned.
 2260         aInputSize = strlen(aInput);
 2261 
 2262     while (aInputSize > 0 && aInput[aInputSize-1] == '=')
 2263         --aInputSize;
 2264 
 2265     for (i = aInputSize; i > 3; i -= 4)
 2266     {
 2267         buffer  = BASE64_CHAR_TO_BINARY(aInput[0]) << 18 // L39: Fixed bad reliance on order of *side-effects++. [thanks fincs]
 2268                 | BASE64_CHAR_TO_BINARY(aInput[1]) << 12
 2269                 | BASE64_CHAR_TO_BINARY(aInput[2]) << 6
 2270                 | BASE64_CHAR_TO_BINARY(aInput[3]);
 2271         aInput += 4;
 2272 
 2273         aBuf[len + 0] = (buffer >> 16) & 255;
 2274         aBuf[len + 1] = (buffer >> 8) & 255;
 2275         aBuf[len + 2] = buffer & 255;
 2276         len += 3;
 2277     }
 2278 
 2279     if (i > 1)
 2280     {
 2281         buffer  = BASE64_CHAR_TO_BINARY(aInput[0]) << 18
 2282                 | BASE64_CHAR_TO_BINARY(aInput[1]) << 12;
 2283         if (i > 2)
 2284             buffer |= BASE64_CHAR_TO_BINARY(aInput[2]) << 6;
 2285         // aInput not incremented as it is not used below.
 2286 
 2287         aBuf[len++] = (buffer >> 16) & 255;
 2288         if (i > 2)
 2289             aBuf[len++] = (buffer >> 8) & 255;
 2290     }
 2291     aBuf[len] = '\0';
 2292     return len;
 2293 }
 2294 
 2295 //
 2296 // class Debugger::Buffer - simplifies memory management.
 2297 //
 2298 
 2299 // Write data into the buffer, expanding it as necessary.
 2300 int Debugger::Buffer::Write(char *aData, size_t aDataSize)
 2301 {
 2302     if (mFailed) // See WriteF() for comments.
 2303         return DEBUGGER_E_INTERNAL_ERROR;
 2304 
 2305     if (aDataSize == -1)
 2306         aDataSize = strlen(aData);
 2307 
 2308     if (aDataSize == 0)
 2309         return DEBUGGER_E_OK;
 2310 
 2311     if (ExpandIfNecessary(mDataUsed + aDataSize) != DEBUGGER_E_OK)
 2312         return DEBUGGER_E_INTERNAL_ERROR;
 2313 
 2314     memcpy(mData + mDataUsed, aData, aDataSize);
 2315     mDataUsed += aDataSize;
 2316     return DEBUGGER_E_OK;
 2317 }
 2318 
 2319 // Write formatted data into the buffer. Supports %s (char*), %e (char*, "&'<> escaped), %i (int), %u (unsigned int), %p (UINT_PTR).
 2320 int Debugger::Buffer::WriteF(const char *aFormat, ...)
 2321 {
 2322     if (mFailed)
 2323     {
 2324         // A prior Write() failed and the now-invalid data hasn't yet been cleared
 2325         // from the buffer.  Abort.  This allows numerous other parts of the code
 2326         // to omit error-checking.
 2327         return DEBUGGER_E_INTERNAL_ERROR;
 2328     }
 2329 
 2330     int i;
 2331     size_t len;
 2332     char c;
 2333     const char *format_ptr, *s, *param_ptr, *entity;
 2334     char number_buf[MAX_INTEGER_SIZE];
 2335     va_list vl;
 2336     
 2337     for (len = 0, i = 0; i < 2; ++i)
 2338     {
 2339         va_start(vl, aFormat);
 2340 
 2341         // Calculate the required buffer size.
 2342         for (format_ptr = aFormat; c = *format_ptr; ++format_ptr)
 2343         {
 2344             if (c == '%')
 2345             {
 2346                 switch (format_ptr[1])
 2347                 {
 2348                 case 's': s = va_arg(vl, char*); break;
 2349                 case 'i': s = _itoa(va_arg(vl, int), number_buf, 10); break;
 2350                 case 'u': s = _ultoa(va_arg(vl, unsigned long), number_buf, 10); break;
 2351                 case 'p': s = Exp32or64(_ultoa,_ui64toa)(va_arg(vl, UINT_PTR), number_buf, 10); break;
 2352 
 2353                 case 'e': // String, replace "&'<> with appropriate XML entity.
 2354                 {
 2355                     s = va_arg(vl, char*);
 2356                     if (i == 0)
 2357                     {
 2358                         for (param_ptr = s; *param_ptr; ++param_ptr)
 2359                         {
 2360                             switch (*param_ptr)
 2361                             {
 2362                             case '"': case '\'':    len += 6; break; // &quot; or &apos;
 2363                             case '&':               len += 5; break; // &amp;
 2364                             case '<': case '>':     len += 4; break; // &lt; or &gt;
 2365                             default: ++len;
 2366                             }
 2367                         }
 2368                     }
 2369                     else
 2370                     {
 2371                         for (param_ptr = s; c = *param_ptr; ++param_ptr)
 2372                         {
 2373                             switch (c)
 2374                             {
 2375                             case '"': entity = "quot"; break;
 2376                             case '\'': entity = "apos"; break;
 2377                             case '&': entity = "amp"; break;
 2378                             case '<': entity = "lt"; break;
 2379                             case '>': entity = "gt"; break;
 2380                             default:
 2381                                 mData[mDataUsed++] = c;
 2382                                 continue;
 2383                             }
 2384                             // One of: "'&<> - entity is set to the appropriate entity name.
 2385                             mDataUsed += sprintf(mData + mDataUsed, "&%s;", entity);
 2386                         }
 2387                     }
 2388                     ++format_ptr; // Skip %, outer loop will skip format char.
 2389                     //s = NULL; // Skip section below.
 2390                     //break;
 2391                     continue;
 2392                 }
 2393 
 2394                 default:
 2395                     s = NULL; // Skip section below.
 2396                 } // switch (format_ptr[1])
 2397 
 2398                 if (s)
 2399                 {   // %s, %i or %u.
 2400                     if (i == 0)
 2401                     {   // Calculate required buffer space on first iteration.
 2402                         len += strlen(s);
 2403                     }
 2404                     else if (len = strlen(s))
 2405                     {   // Write into buffer on second iteration.
 2406                         // memcpy as we don't want the terminating null character.
 2407                         memcpy(mData + mDataUsed, s, len);
 2408                         mDataUsed += len;
 2409                     }
 2410                     ++format_ptr; // Skip %, outer loop will skip format char.
 2411                     continue;
 2412                 }
 2413             } // if (c == '%')
 2414             // Count or copy character as is.
 2415             if (i == 0)
 2416                 ++len;
 2417             else
 2418                 mData[mDataUsed++] = *format_ptr;
 2419         } // for (format_ptr = aFormat; c = *format_ptr; ++format_ptr)
 2420 
 2421         if (i == 0)
 2422             if (ExpandIfNecessary(mDataUsed + len) != DEBUGGER_E_OK)
 2423                 return DEBUGGER_E_INTERNAL_ERROR;
 2424     } // for (len = 0, i = 0; i < 2; ++i)
 2425 
 2426     return DEBUGGER_E_OK;
 2427 }
 2428 
 2429 // Convert a file path to a URI and write it to the buffer.
 2430 int Debugger::Buffer::WriteFileURI(const char *aPath)
 2431 {
 2432     int c, len = 9; // 8 for "file:///", 1 for '\0' (written by sprintf()).
 2433 
 2434     // Calculate required buffer size for path after encoding.
 2435     for (const char *ptr = aPath; c = *ptr; ++ptr)
 2436     {
 2437         if (cisalnum(c) || strchr("-_.!~*'()/\\", c))
 2438             ++len;
 2439         else
 2440             len += 3;
 2441     }
 2442 
 2443     // Ensure the buffer contains enough space.
 2444     if (ExpandIfNecessary(mDataUsed + len) != DEBUGGER_E_OK)
 2445         return DEBUGGER_E_INTERNAL_ERROR;
 2446 
 2447     Write("file:///", 8);
 2448 
 2449     // Write to the buffer, encoding as we go.
 2450     for (const char *ptr = aPath; c = *ptr; ++ptr)
 2451     {
 2452         if (cisalnum(c) || strchr("-_.!~*()/", c))
 2453         {
 2454             mData[mDataUsed++] = (char)c;
 2455         }
 2456         else if (c == '\\')
 2457         {
 2458             // This could be encoded as %5C, but it's more readable this way:
 2459             mData[mDataUsed++] = '/';
 2460         }
 2461         else
 2462         {
 2463             len = sprintf(mData + mDataUsed, "%%%02X", c & 0xff);
 2464             if (len != -1)
 2465                 mDataUsed += len;
 2466         }
 2467     }
 2468 
 2469     return DEBUGGER_E_OK;
 2470 }
 2471 
 2472 int Debugger::Buffer::WriteEncodeBase64(const char *aInput, size_t aInputSize, bool aSkipBufferSizeCheck/* = false*/)
 2473 {
 2474     if (aInputSize)
 2475     {
 2476         if (!aSkipBufferSizeCheck)
 2477         {
 2478             // Ensure required buffer space is available.
 2479             if (ExpandIfNecessary(mDataUsed + DEBUGGER_BASE64_ENCODED_SIZE(aInputSize)) != DEBUGGER_E_OK)
 2480                 return DEBUGGER_E_INTERNAL_ERROR;
 2481         }
 2482         //else caller has already ensured there is enough space and wants to be absolutely sure mData isn't reallocated.
 2483         ASSERT(mDataUsed + aInputSize < mDataSize);
 2484         
 2485         if (aInput)
 2486             mDataUsed += Debugger::Base64Encode(mData + mDataUsed, aInput, aInputSize);
 2487         //else caller wanted to reserve some buffer space, probably to read the raw data into.
 2488     }
 2489     //else there's nothing to write, just return OK.  Calculations above can't handle (size_t)0
 2490     return DEBUGGER_E_OK;
 2491 }
 2492 
 2493 // Decode a file URI in-place.
 2494 void Debugger::DecodeURI(char *aUri)
 2495 {
 2496     char *end = strchr(aUri, 0);
 2497     char escape[3];
 2498     escape[2] = '\0';
 2499 
 2500     if (!strnicmp(aUri, "file:///", 8))
 2501     {
 2502         // Use memmove since I'm not sure if strcpy can handle overlap.
 2503         end -= 8;
 2504         memmove(aUri, aUri + 8, end - aUri);
 2505     }
 2506     // Some debugger UI's use file://path even though it is invalid (it should be file:///path or file://server/path).
 2507     // For compatibility with these UI's, support file://.
 2508     else if (!strnicmp(aUri, "file://", 7))
 2509     {
 2510         end -= 7;
 2511         memmove(aUri, aUri + 7, end - aUri);
 2512     }
 2513 
 2514     char *dst, *src;
 2515     for (src = dst = aUri; src < end; ++src, ++dst)
 2516     {
 2517         if (*src == '%' && src + 2 < end)
 2518         {
 2519             escape[0] = *++src;
 2520             escape[1] = *++src;
 2521             *dst = (char)strtol(escape, NULL, 16);
 2522         }
 2523         else if (*src == '/')
 2524             *dst = '\\';
 2525         else
 2526             *dst = *src;
 2527     }
 2528     *dst = '\0';
 2529 }
 2530 
 2531 // Initialize or expand the buffer, don't care how much.
 2532 int Debugger::Buffer::Expand()
 2533 {
 2534     return ExpandIfNecessary(mDataSize ? mDataSize * 2 : DEBUGGER_INITIAL_BUFFER_SIZE);
 2535 }
 2536 
 2537 // Expand as necessary to meet a minimum required size.
 2538 int Debugger::Buffer::ExpandIfNecessary(size_t aRequiredSize)
 2539 {
 2540     if (mFailed)
 2541         return DEBUGGER_E_INTERNAL_ERROR;
 2542 
 2543     size_t new_size;
 2544     for (new_size = mDataSize ? mDataSize : DEBUGGER_INITIAL_BUFFER_SIZE
 2545         ; new_size < aRequiredSize
 2546         ; new_size *= 2);
 2547 
 2548     if (new_size > mDataSize)
 2549     {
 2550         // For simplicity, this preserves all of mData not just the first mDataUsed bytes.  Some sections may rely on this.
 2551         char *new_data = (char*)realloc(mData, new_size);
 2552 
 2553         if (new_data == NULL)
 2554         {
 2555             mFailed = TRUE;
 2556             return DEBUGGER_E_INTERNAL_ERROR;
 2557         }
 2558 
 2559         mData = new_data;
 2560         mDataSize = new_size;
 2561     }
 2562     return DEBUGGER_E_OK;
 2563 }
 2564 
 2565 // Remove data from the front of the buffer (i.e. after it is processed).
 2566 void Debugger::Buffer::Remove(size_t aDataSize)
 2567 {
 2568     ASSERT(aDataSize <= mDataUsed);
 2569     // Move remaining data to the front of the buffer.
 2570     if (aDataSize < mDataUsed)
 2571         memmove(mData, mData + aDataSize, mDataUsed - aDataSize);
 2572     mDataUsed -= aDataSize;
 2573 }
 2574 
 2575 void Debugger::Buffer::Clear()
 2576 {
 2577     mDataUsed = 0;
 2578     mFailed = FALSE;
 2579 }
 2580 
 2581 
 2582 
 2583 DbgStack::Entry *DbgStack::Push()
 2584 {
 2585     if (mTop == mTopBound)
 2586         Expand();
 2587     if (mTop >= mBottom)
 2588     {
 2589         // Whenever this entry != mTop, Entry::line is used instead of g_Debugger.mCurrLine,
 2590         // which means that it needs to point to the last executed line at that depth. The
 2591         // following fixes a bug where the line number of this entry appears to revert to
 2592         // the first line of the function or sub whenever the debugger steps into another
 2593         // function or sub.  Entry::line is now also used by BIF_Exception even when the
 2594         // debugger is disconnected, which has two consequences:
 2595         //  - g_script.mCurrLine must be used in place of g_Debugger.mCurrLine.
 2596         //  - Changing PreExecLine() to update mStack.mTop->line won't help.
 2597         mTop->line = g_script.mCurrLine;
 2598     }
 2599     return ++mTop;
 2600 }
 2601 
 2602 void DbgStack::Push(TCHAR *aDesc)
 2603 {
 2604     Entry &s = *Push();
 2605     s.line = NULL;
 2606     s.desc = aDesc;
 2607     s.type = SE_Thread;
 2608 }
 2609     
 2610 void DbgStack::Push(Label *aSub)
 2611 {
 2612     Entry &s = *Push();
 2613     s.line = aSub->mJumpToLine;
 2614     s.sub  = aSub;
 2615     s.type = SE_Sub;
 2616 }
 2617 
 2618 void DbgStack::Push(UDFCallInfo *aUDF)
 2619 {
 2620     Entry &s = *Push();
 2621     s.line = aUDF->func->mJumpToLine;
 2622     s.udf = aUDF;
 2623     s.type = SE_UDF;
 2624 }
 2625 
 2626 
 2627 void DbgStack::GetLocalVars(int aDepth, Var **&aVar, Var **&aVarEnd, VarBkp *&aBkp, VarBkp *&aBkpEnd)
 2628 {
 2629     DbgStack::Entry *se = mTop - aDepth;
 2630     for (;;)
 2631     {
 2632         if (se <= mBottom)
 2633             return;
 2634         if (se->type == DbgStack::SE_UDF)
 2635             break;
 2636         --se;
 2637     }
 2638     Func &func = *se->udf->func;
 2639     if (func.mInstances > 1 && aDepth > 0)
 2640     {
 2641         while (++se <= mTop)
 2642         {
 2643             if (se->type == DbgStack::SE_UDF && se->udf->func == &func)
 2644             {
 2645                 // This instance interrupted the target instance, so its backup
 2646                 // contains the values of the target instance's local variables.
 2647                 aBkp = se->udf->backup;
 2648                 aBkpEnd = aBkp + se->udf->backup_count;
 2649                 return;
 2650             }
 2651         }
 2652     }
 2653     // Since above did not return, this instance wasn't interrupted.
 2654     aVar = func.mVar;
 2655     aVarEnd = aVar + func.mVarCount;
 2656 }
 2657 
 2658 
 2659 void Debugger::PropertyWriter::WriteProperty(LPCSTR aName, ExprTokenType &aValue)
 2660 {
 2661     mProp.fullname.AppendFormat(".%s", aName);
 2662     _WriteProperty(aValue);
 2663 }
 2664 
 2665 
 2666 void Debugger::PropertyWriter::WriteProperty(ExprTokenType &aKey, ExprTokenType &aValue)
 2667 {
 2668     switch (aKey.symbol)
 2669     {
 2670     case SYM_INTEGER: mProp.fullname.AppendFormat("[%Ii]", aKey.value_int64); break;
 2671     case SYM_OBJECT: mProp.fullname.AppendFormat("[Object(%Ii)]", aKey.object); break;
 2672     default:
 2673         ASSERT(aKey.symbol == SYM_STRING);
 2674         mDbg.AppendKeyName(mProp.fullname, mNameLength, CStringUTF8FromTChar(aKey.marker));
 2675     }
 2676     _WriteProperty(aValue);
 2677 }
 2678 
 2679 
 2680 void Debugger::PropertyWriter::_WriteProperty(ExprTokenType &aValue)
 2681 {
 2682     if (mError)
 2683         return;
 2684     PropertyInfo prop(mProp.fullname);
 2685     // Find the property's "relative" name at the end of the buffer:
 2686     prop.name = mProp.fullname.GetString() + mNameLength;
 2687     if (*prop.name == '.')
 2688         prop.name++;
 2689     // Write the property (and if it contains an object, any child properties):
 2690     prop.value.CopyValueFrom(aValue);
 2691     //prop.page = 0; // "the childrens pages are always the first page."
 2692     prop.pagesize = mProp.pagesize;
 2693     prop.max_data = mProp.max_data;
 2694     prop.max_depth = mProp.max_depth - mDepth;
 2695     mError = mDbg.WritePropertyXml(prop);
 2696     // Truncate back to the parent property name:
 2697     mProp.fullname.Truncate(mNameLength);
 2698 }
 2699 
 2700 
 2701 void Debugger::PropertyWriter::BeginProperty(LPCSTR aName, LPCSTR aType, int aNumChildren, DebugCookie &aCookie)
 2702 {
 2703     if (mError)
 2704         return;
 2705 
 2706     mDepth++;
 2707 
 2708     if (mDepth == 1) // Write <property> for the object itself.
 2709     {
 2710         LPTSTR classname = mObject->Type();
 2711         mError = mDbg.mResponseBuf.WriteF("<property name=\"%e\" fullname=\"%e\" type=\"%s\" facet=\"%s\" classname=\"%s\" address=\"%p\" size=\"0\" page=\"%i\" pagesize=\"%i\" children=\"%i\" numchildren=\"%i\">"
 2712                     , mProp.name, mProp.fullname.GetString(), aType, mProp.facet, U4T(classname), mObject, mProp.page, mProp.pagesize, aNumChildren > 0, aNumChildren);
 2713         return;
 2714     }
 2715 
 2716     // aName is the raw name of this property.  Before writing it into the response,
 2717     // convert it to the required expression-like form.  Do this conversion within
 2718     // mProp.fullname so it can be used for the fullname attribute:
 2719     mDbg.AppendKeyName(mProp.fullname, mNameLength, aName);
 2720 
 2721     // Find the property's "relative" name at the end of the buffer:
 2722     LPCSTR name = mProp.fullname.GetString() + mNameLength;
 2723     if (*name == '.')
 2724         name++;
 2725     
 2726     // Save length of outer property name and update mNameLength.
 2727     aCookie = (DebugCookie)mNameLength;
 2728     mNameLength = mProp.fullname.GetLength();
 2729 
 2730     mError = mDbg.mResponseBuf.WriteF("<property name=\"%e\" fullname=\"%e\" type=\"%s\" size=\"0\" page=\"0\" pagesize=\"%i\" children=\"%i\" numchildren=\"%i\">"
 2731                 , name, mProp.fullname.GetString(), aType, mProp.pagesize, aNumChildren > 0, aNumChildren);
 2732 }
 2733 
 2734 
 2735 void Debugger::PropertyWriter::EndProperty(DebugCookie aCookie)
 2736 {
 2737     if (mError)
 2738         return;
 2739 
 2740     mDepth--;
 2741 
 2742     if (mDepth > 0) // If we just ended a child property...
 2743     {
 2744         mNameLength = (size_t)aCookie; // Restore to the value it had before BeginProperty().
 2745         mProp.fullname.Truncate(mNameLength);
 2746     }
 2747     
 2748     mError = mDbg.mResponseBuf.Write("</property>");
 2749 }
 2750 
 2751 
 2752 #endif
 2753 
 2754 
 2755 // Helper for Var::ToText().
 2756 
 2757 LPTSTR Var::ObjectToText(LPTSTR aName, LPTSTR aBuf, int aBufSize)
 2758 {
 2759     LPTSTR aBuf_orig = aBuf;
 2760     aBuf += sntprintf(aBuf, aBufSize, _T("%s: %s object"), aName, mObject->Type());
 2761     if (ComObject *cobj = dynamic_cast<ComObject *>(mObject))
 2762         aBuf += sntprintf(aBuf, BUF_SPACE_REMAINING, _T(" {wrapper: 0x%IX, vt: 0x%04hX, value: 0x%I64X}"), cobj, cobj->mVarType, cobj->mVal64);
 2763     else
 2764         aBuf += sntprintf(aBuf, BUF_SPACE_REMAINING, _T(" {address: 0x%IX}"), mObject);
 2765     return aBuf;
 2766 }