1 // Copyright 2009 the V8 project authors. All rights reserved. 2 // Redistribution and use in source and binary forms, with or without 3 // modification, are permitted provided that the following conditions are 4 // met: 5 // 6 // * Redistributions of source code must retain the above copyright 7 // notice, this list of conditions and the following disclaimer. 8 // * Redistributions in binary form must reproduce the above 9 // copyright notice, this list of conditions and the following 10 // disclaimer in the documentation and/or other materials provided 11 // with the distribution. 12 // * Neither the name of Google Inc. nor the names of its 13 // contributors may be used to endorse or promote products derived 14 // from this software without specific prior written permission. 15 // 16 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 28 #include <v8.h> 29 #include <v8-debug.h> 30 #include <fcntl.h> 31 #include <string.h> 32 #include <stdio.h> 33 #include <stdlib.h> 34 35 /** 36 * This sample program should demonstrate certain aspects of debugging 37 * standalone V8-based application. 38 * 39 * The program reads input stream, processes it line by line and print 40 * the result to output. The actual processing is done by custom JavaScript 41 * script. The script is specified with command line parameters. 42 * 43 * The main cycle of the program will sequentially read lines from standard 44 * input, process them and print to standard output until input closes. 45 * There are 2 possible configuration in regard to main cycle. 46 * 47 * 1. The main cycle is on C++ side. Program should be run with 48 * --main-cycle-in-cpp option. Script must declare a function named 49 * "ProcessLine". The main cycle in C++ reads lines and calls this function 50 * for processing every time. This is a sample script: 51 52 function ProcessLine(input_line) { 53 return ">>>" + input_line + "<<<"; 54 } 55 56 * 57 * 2. The main cycle is in JavaScript. Program should be run with 58 * --main-cycle-in-js option. Script gets run one time at all and gets 59 * API of 2 global functions: "read_line" and "print". It should read input 60 * and print converted lines to output itself. This a sample script: 61 62 while (true) { 63 var line = read_line(); 64 if (!line) { 65 break; 66 } 67 var res = line + " | " + line; 68 print(res); 69 } 70 71 * 72 * When run with "-p" argument, the program starts V8 Debugger Agent and 73 * allows remote debugger to attach and debug JavaScript code. 74 * 75 * Interesting aspects: 76 * 1. Wait for remote debugger to attach 77 * Normally the program compiles custom script and immediately runs it. 78 * If programmer needs to debug script from the very beginning, he should 79 * run this sample program with "--wait-for-connection" command line parameter. 80 * This way V8 will suspend on the first statement and wait for 81 * debugger to attach. 82 * 83 * 2. Unresponsive V8 84 * V8 Debugger Agent holds a connection with remote debugger, but it does 85 * respond only when V8 is running some script. In particular, when this program 86 * is waiting for input, all requests from debugger get deferred until V8 87 * is called again. See how "--callback" command-line parameter in this sample 88 * fixes this issue. 89 */ 90 91 enum MainCycleType { 92 CycleInCpp, 93 CycleInJs 94 }; 95 96 const char* ToCString(const v8::String::Utf8Value& value); 97 void ReportException(v8::TryCatch* handler); 98 v8::Handle<v8::String> ReadFile(const char* name); 99 v8::Handle<v8::String> ReadLine(); 100 101 v8::Handle<v8::Value> Print(const v8::Arguments& args); 102 v8::Handle<v8::Value> ReadLine(const v8::Arguments& args); 103 bool RunCppCycle(v8::Handle<v8::Script> script, v8::Local<v8::Context> context, 104 bool report_exceptions); 105 106 v8::Persistent<v8::Context> debug_message_context; 107 108 109 void DispatchDebugMessages() { 110 // We are in some random thread. We should already have v8::Locker acquired 111 // (we requested this when registered this callback). We was called 112 // because new debug messages arrived; they may have already been processed, 113 // but we shouldn't worry about this. 114 // 115 // All we have to do is to set context and call ProcessDebugMessages. 116 // 117 // We should decide which V8 context to use here. This is important for 118 // "evaluate" command, because it must be executed some context. 119 // In our sample we have only one context, so there is nothing really to 120 // think about. 121 v8::Context::Scope scope(debug_message_context); 122 123 v8::Debug::ProcessDebugMessages(); 124 } 125 126 127 int RunMain(int argc, char* argv[]) { 128 v8::V8::SetFlagsFromCommandLine(&argc, argv, true); 129 v8::HandleScope handle_scope; 130 131 v8::Handle<v8::String> script_source(NULL); 132 v8::Handle<v8::Value> script_name(NULL); 133 int script_param_counter = 0; 134 135 int port_number = -1; 136 bool wait_for_connection = false; 137 bool support_callback = false; 138 MainCycleType cycle_type = CycleInCpp; 139 140 for (int i = 1; i < argc; i++) { 141 const char* str = argv[i]; 142 if (strcmp(str, "-f") == 0) { 143 // Ignore any -f flags for compatibility with the other stand- 144 // alone JavaScript engines. 145 continue; 146 } else if (strcmp(str, "--callback") == 0) { 147 support_callback = true; 148 } else if (strcmp(str, "--wait-for-connection") == 0) { 149 wait_for_connection = true; 150 } else if (strcmp(str, "--main-cycle-in-cpp") == 0) { 151 cycle_type = CycleInCpp; 152 } else if (strcmp(str, "--main-cycle-in-js") == 0) { 153 cycle_type = CycleInJs; 154 } else if (strcmp(str, "-p") == 0 && i + 1 < argc) { 155 port_number = atoi(argv[i + 1]); // NOLINT 156 i++; 157 } else if (strncmp(str, "--", 2) == 0) { 158 printf("Warning: unknown flag %s.\nTry --help for options\n", str); 159 } else if (strcmp(str, "-e") == 0 && i + 1 < argc) { 160 script_source = v8::String::New(argv[i + 1]); 161 script_name = v8::String::New("unnamed"); 162 i++; 163 script_param_counter++; 164 } else { 165 // Use argument as a name of file to load. 166 script_source = ReadFile(str); 167 script_name = v8::String::New(str); 168 if (script_source.IsEmpty()) { 169 printf("Error reading '%s'\n", str); 170 return 1; 171 } 172 script_param_counter++; 173 } 174 } 175 176 if (script_param_counter == 0) { 177 printf("Script is not specified\n"); 178 return 1; 179 } 180 if (script_param_counter != 1) { 181 printf("Only one script may be specified\n"); 182 return 1; 183 } 184 185 // Create a template for the global object. 186 v8::Handle<v8::ObjectTemplate> global = v8::ObjectTemplate::New(); 187 188 // Bind the global 'print' function to the C++ Print callback. 189 global->Set(v8::String::New("print"), v8::FunctionTemplate::New(Print)); 190 191 if (cycle_type == CycleInJs) { 192 // Bind the global 'read_line' function to the C++ Print callback. 193 global->Set(v8::String::New("read_line"), 194 v8::FunctionTemplate::New(ReadLine)); 195 } 196 197 // Create a new execution environment containing the built-in 198 // functions 199 v8::Handle<v8::Context> context = v8::Context::New(NULL, global); 200 debug_message_context = v8::Persistent<v8::Context>::New(context); 201 202 203 // Enter the newly created execution environment. 204 v8::Context::Scope context_scope(context); 205 206 v8::Locker locker; 207 208 if (support_callback) { 209 v8::Debug::SetDebugMessageDispatchHandler(DispatchDebugMessages, true); 210 } 211 212 if (port_number != -1) { 213 const char* auto_break_param = "--debugger_auto_break"; 214 v8::V8::SetFlagsFromString(auto_break_param, strlen(auto_break_param)); 215 v8::Debug::EnableAgent("lineprocessor", port_number, wait_for_connection); 216 } 217 218 bool report_exceptions = true; 219 220 v8::Handle<v8::Script> script; 221 { 222 // Compile script in try/catch context. 223 v8::TryCatch try_catch; 224 script = v8::Script::Compile(script_source, script_name); 225 if (script.IsEmpty()) { 226 // Print errors that happened during compilation. 227 if (report_exceptions) 228 ReportException(&try_catch); 229 return 1; 230 } 231 } 232 233 { 234 v8::TryCatch try_catch; 235 236 script->Run(); 237 if (try_catch.HasCaught()) { 238 if (report_exceptions) 239 ReportException(&try_catch); 240 return 1; 241 } 242 } 243 244 if (cycle_type == CycleInCpp) { 245 bool res = RunCppCycle(script, v8::Context::GetCurrent(), 246 report_exceptions); 247 return !res; 248 } else { 249 // All is already done. 250 } 251 return 0; 252 } 253 254 255 bool RunCppCycle(v8::Handle<v8::Script> script, v8::Local<v8::Context> context, 256 bool report_exceptions) { 257 v8::Locker lock; 258 259 v8::Handle<v8::String> fun_name = v8::String::New("ProcessLine"); 260 v8::Handle<v8::Value> process_val = 261 v8::Context::GetCurrent()->Global()->Get(fun_name); 262 263 // If there is no Process function, or if it is not a function, 264 // bail out 265 if (!process_val->IsFunction()) { 266 printf("Error: Script does not declare 'ProcessLine' global function.\n"); 267 return 1; 268 } 269 270 // It is a function; cast it to a Function 271 v8::Handle<v8::Function> process_fun = 272 v8::Handle<v8::Function>::Cast(process_val); 273 274 275 while (!feof(stdin)) { 276 v8::HandleScope handle_scope; 277 278 v8::Handle<v8::String> input_line = ReadLine(); 279 if (input_line == v8::Undefined()) { 280 continue; 281 } 282 283 const int argc = 1; 284 v8::Handle<v8::Value> argv[argc] = { input_line }; 285 286 v8::Handle<v8::Value> result; 287 { 288 v8::TryCatch try_catch; 289 result = process_fun->Call(v8::Context::GetCurrent()->Global(), 290 argc, argv); 291 if (try_catch.HasCaught()) { 292 if (report_exceptions) 293 ReportException(&try_catch); 294 return false; 295 } 296 } 297 v8::String::Utf8Value str(result); 298 const char* cstr = ToCString(str); 299 printf("%s\n", cstr); 300 } 301 302 return true; 303 } 304 305 int main(int argc, char* argv[]) { 306 int result = RunMain(argc, argv); 307 v8::V8::Dispose(); 308 return result; 309 } 310 311 312 // Extracts a C string from a V8 Utf8Value. 313 const char* ToCString(const v8::String::Utf8Value& value) { 314 return *value ? *value : "<string conversion failed>"; 315 } 316 317 318 // Reads a file into a v8 string. 319 v8::Handle<v8::String> ReadFile(const char* name) { 320 FILE* file = fopen(name, "rb"); 321 if (file == NULL) return v8::Handle<v8::String>(); 322 323 fseek(file, 0, SEEK_END); 324 int size = ftell(file); 325 rewind(file); 326 327 char* chars = new char[size + 1]; 328 chars[size] = '\0'; 329 for (int i = 0; i < size;) { 330 int read = fread(&chars[i], 1, size - i, file); 331 i += read; 332 } 333 fclose(file); 334 v8::Handle<v8::String> result = v8::String::New(chars, size); 335 delete[] chars; 336 return result; 337 } 338 339 340 void ReportException(v8::TryCatch* try_catch) { 341 v8::HandleScope handle_scope; 342 v8::String::Utf8Value exception(try_catch->Exception()); 343 const char* exception_string = ToCString(exception); 344 v8::Handle<v8::Message> message = try_catch->Message(); 345 if (message.IsEmpty()) { 346 // V8 didn't provide any extra information about this error; just 347 // print the exception. 348 printf("%s\n", exception_string); 349 } else { 350 // Print (filename):(line number): (message). 351 v8::String::Utf8Value filename(message->GetScriptResourceName()); 352 const char* filename_string = ToCString(filename); 353 int linenum = message->GetLineNumber(); 354 printf("%s:%i: %s\n", filename_string, linenum, exception_string); 355 // Print line of source code. 356 v8::String::Utf8Value sourceline(message->GetSourceLine()); 357 const char* sourceline_string = ToCString(sourceline); 358 printf("%s\n", sourceline_string); 359 // Print wavy underline (GetUnderline is deprecated). 360 int start = message->GetStartColumn(); 361 for (int i = 0; i < start; i++) { 362 printf(" "); 363 } 364 int end = message->GetEndColumn(); 365 for (int i = start; i < end; i++) { 366 printf("^"); 367 } 368 printf("\n"); 369 } 370 } 371 372 373 // The callback that is invoked by v8 whenever the JavaScript 'print' 374 // function is called. Prints its arguments on stdout separated by 375 // spaces and ending with a newline. 376 v8::Handle<v8::Value> Print(const v8::Arguments& args) { 377 bool first = true; 378 for (int i = 0; i < args.Length(); i++) { 379 v8::HandleScope handle_scope; 380 if (first) { 381 first = false; 382 } else { 383 printf(" "); 384 } 385 v8::String::Utf8Value str(args[i]); 386 const char* cstr = ToCString(str); 387 printf("%s", cstr); 388 } 389 printf("\n"); 390 fflush(stdout); 391 return v8::Undefined(); 392 } 393 394 395 // The callback that is invoked by v8 whenever the JavaScript 'read_line' 396 // function is called. Reads a string from standard input and returns. 397 v8::Handle<v8::Value> ReadLine(const v8::Arguments& args) { 398 if (args.Length() > 0) { 399 return v8::ThrowException(v8::String::New("Unexpected arguments")); 400 } 401 return ReadLine(); 402 } 403 404 v8::Handle<v8::String> ReadLine() { 405 const int kBufferSize = 1024 + 1; 406 char buffer[kBufferSize]; 407 408 char* res; 409 { 410 v8::Unlocker unlocker; 411 res = fgets(buffer, kBufferSize, stdin); 412 } 413 if (res == NULL) { 414 v8::Handle<v8::Primitive> t = v8::Undefined(); 415 return reinterpret_cast<v8::Handle<v8::String>&>(t); 416 } 417 // remove newline char 418 for (char* pos = buffer; *pos != '\0'; pos++) { 419 if (*pos == '\n') { 420 *pos = '\0'; 421 break; 422 } 423 } 424 return v8::String::New(buffer); 425 } 426