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