lineprocessor.cc revision e46be819fca9468a0cd4e74859ce0f778eb8ca60
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 52function 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 62while (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 91enum MainCycleType { 92 CycleInCpp, 93 CycleInJs 94}; 95 96const char* ToCString(const v8::String::Utf8Value& value); 97void ReportException(v8::TryCatch* handler); 98v8::Handle<v8::String> ReadFile(const char* name); 99v8::Handle<v8::String> ReadLine(); 100 101v8::Handle<v8::Value> Print(const v8::Arguments& args); 102v8::Handle<v8::Value> ReadLine(const v8::Arguments& args); 103bool RunCppCycle(v8::Handle<v8::Script> script, v8::Local<v8::Context> context, 104 bool report_exceptions); 105 106v8::Persistent<v8::Context> debug_message_context; 107 108 109void 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 127int 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 = true; 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 // TODO(548): implement this. 148 printf("Error: debugger agent callback is not supported yet.\n"); 149 return 1; 150 } else if (strcmp(str, "--wait-for-connection") == 0) { 151 wait_for_connection = true; 152 } else if (strcmp(str, "--main-cycle-in-cpp") == 0) { 153 cycle_type = CycleInCpp; 154 } else if (strcmp(str, "--main-cycle-in-js") == 0) { 155 cycle_type = CycleInJs; 156 } else if (strcmp(str, "-p") == 0 && i + 1 < argc) { 157 port_number = atoi(argv[i + 1]); 158 i++; 159 } else if (strncmp(str, "--", 2) == 0) { 160 printf("Warning: unknown flag %s.\nTry --help for options\n", str); 161 } else if (strcmp(str, "-e") == 0 && i + 1 < argc) { 162 script_source = v8::String::New(argv[i + 1]); 163 script_name = v8::String::New("unnamed"); 164 i++; 165 script_param_counter++; 166 } else { 167 // Use argument as a name of file to load. 168 script_source = ReadFile(str); 169 script_name = v8::String::New(str); 170 if (script_source.IsEmpty()) { 171 printf("Error reading '%s'\n", str); 172 return 1; 173 } 174 script_param_counter++; 175 } 176 } 177 178 if (script_param_counter == 0) { 179 printf("Script is not specified\n"); 180 return 1; 181 } 182 if (script_param_counter != 1) { 183 printf("Only one script may be specified\n"); 184 return 1; 185 } 186 187 // Create a template for the global object. 188 v8::Handle<v8::ObjectTemplate> global = v8::ObjectTemplate::New(); 189 190 // Bind the global 'print' function to the C++ Print callback. 191 global->Set(v8::String::New("print"), v8::FunctionTemplate::New(Print)); 192 193 if (cycle_type == CycleInJs) { 194 // Bind the global 'read_line' function to the C++ Print callback. 195 global->Set(v8::String::New("read_line"), 196 v8::FunctionTemplate::New(ReadLine)); 197 } 198 199 // Create a new execution environment containing the built-in 200 // functions 201 v8::Handle<v8::Context> context = v8::Context::New(NULL, global); 202 debug_message_context = v8::Persistent<v8::Context>::New(context); 203 204 205 // Enter the newly created execution environment. 206 v8::Context::Scope context_scope(context); 207 208 v8::Locker locker; 209 210 if (support_callback) { 211 v8::Debug::SetDebugMessageDispatchHandler(DispatchDebugMessages, true); 212 } 213 214 if (port_number != -1) { 215 const char* auto_break_param = "--debugger_auto_break"; 216 v8::V8::SetFlagsFromString(auto_break_param, strlen(auto_break_param)); 217 v8::Debug::EnableAgent("lineprocessor", port_number, wait_for_connection); 218 } 219 220 bool report_exceptions = true; 221 222 v8::Handle<v8::Script> script; 223 { 224 // Compile script in try/catch context. 225 v8::TryCatch try_catch; 226 script = v8::Script::Compile(script_source, script_name); 227 if (script.IsEmpty()) { 228 // Print errors that happened during compilation. 229 if (report_exceptions) 230 ReportException(&try_catch); 231 return 1; 232 } 233 } 234 235 { 236 v8::TryCatch try_catch; 237 238 script->Run(); 239 if (try_catch.HasCaught()) { 240 if (report_exceptions) 241 ReportException(&try_catch); 242 return 1; 243 } 244 } 245 246 if (cycle_type == CycleInCpp) { 247 bool res = RunCppCycle(script, v8::Context::GetCurrent(), 248 report_exceptions); 249 return !res; 250 } else { 251 // All is already done. 252 } 253 return 0; 254} 255 256 257bool RunCppCycle(v8::Handle<v8::Script> script, v8::Local<v8::Context> context, 258 bool report_exceptions) { 259 v8::Locker lock; 260 261 v8::Handle<v8::String> fun_name = v8::String::New("ProcessLine"); 262 v8::Handle<v8::Value> process_val = 263 v8::Context::GetCurrent()->Global()->Get(fun_name); 264 265 // If there is no Process function, or if it is not a function, 266 // bail out 267 if (!process_val->IsFunction()) { 268 printf("Error: Script does not declare 'ProcessLine' global function.\n"); 269 return 1; 270 } 271 272 // It is a function; cast it to a Function 273 v8::Handle<v8::Function> process_fun = 274 v8::Handle<v8::Function>::Cast(process_val); 275 276 277 while (!feof(stdin)) { 278 v8::HandleScope handle_scope; 279 280 v8::Handle<v8::String> input_line = ReadLine(); 281 if (input_line == v8::Undefined()) { 282 continue; 283 } 284 285 const int argc = 1; 286 v8::Handle<v8::Value> argv[argc] = { input_line }; 287 288 v8::Handle<v8::Value> result; 289 { 290 v8::TryCatch try_catch; 291 result = process_fun->Call(v8::Context::GetCurrent()->Global(), 292 argc, argv); 293 if (try_catch.HasCaught()) { 294 if (report_exceptions) 295 ReportException(&try_catch); 296 return false; 297 } 298 } 299 v8::String::Utf8Value str(result); 300 const char* cstr = ToCString(str); 301 printf("%s\n", cstr); 302 } 303 304 return true; 305} 306 307int main(int argc, char* argv[]) { 308 int result = RunMain(argc, argv); 309 v8::V8::Dispose(); 310 return result; 311} 312 313 314// Extracts a C string from a V8 Utf8Value. 315const char* ToCString(const v8::String::Utf8Value& value) { 316 return *value ? *value : "<string conversion failed>"; 317} 318 319 320// Reads a file into a v8 string. 321v8::Handle<v8::String> ReadFile(const char* name) { 322 FILE* file = fopen(name, "rb"); 323 if (file == NULL) return v8::Handle<v8::String>(); 324 325 fseek(file, 0, SEEK_END); 326 int size = ftell(file); 327 rewind(file); 328 329 char* chars = new char[size + 1]; 330 chars[size] = '\0'; 331 for (int i = 0; i < size;) { 332 int read = fread(&chars[i], 1, size - i, file); 333 i += read; 334 } 335 fclose(file); 336 v8::Handle<v8::String> result = v8::String::New(chars, size); 337 delete[] chars; 338 return result; 339} 340 341 342void ReportException(v8::TryCatch* try_catch) { 343 v8::HandleScope handle_scope; 344 v8::String::Utf8Value exception(try_catch->Exception()); 345 const char* exception_string = ToCString(exception); 346 v8::Handle<v8::Message> message = try_catch->Message(); 347 if (message.IsEmpty()) { 348 // V8 didn't provide any extra information about this error; just 349 // print the exception. 350 printf("%s\n", exception_string); 351 } else { 352 // Print (filename):(line number): (message). 353 v8::String::Utf8Value filename(message->GetScriptResourceName()); 354 const char* filename_string = ToCString(filename); 355 int linenum = message->GetLineNumber(); 356 printf("%s:%i: %s\n", filename_string, linenum, exception_string); 357 // Print line of source code. 358 v8::String::Utf8Value sourceline(message->GetSourceLine()); 359 const char* sourceline_string = ToCString(sourceline); 360 printf("%s\n", sourceline_string); 361 // Print wavy underline (GetUnderline is deprecated). 362 int start = message->GetStartColumn(); 363 for (int i = 0; i < start; i++) { 364 printf(" "); 365 } 366 int end = message->GetEndColumn(); 367 for (int i = start; i < end; i++) { 368 printf("^"); 369 } 370 printf("\n"); 371 } 372} 373 374 375// The callback that is invoked by v8 whenever the JavaScript 'print' 376// function is called. Prints its arguments on stdout separated by 377// spaces and ending with a newline. 378v8::Handle<v8::Value> Print(const v8::Arguments& args) { 379 bool first = true; 380 for (int i = 0; i < args.Length(); i++) { 381 v8::HandleScope handle_scope; 382 if (first) { 383 first = false; 384 } else { 385 printf(" "); 386 } 387 v8::String::Utf8Value str(args[i]); 388 const char* cstr = ToCString(str); 389 printf("%s", cstr); 390 } 391 printf("\n"); 392 fflush(stdout); 393 return v8::Undefined(); 394} 395 396 397// The callback that is invoked by v8 whenever the JavaScript 'read_line' 398// function is called. Reads a string from standard input and returns. 399v8::Handle<v8::Value> ReadLine(const v8::Arguments& args) { 400 if (args.Length() > 0) { 401 return v8::ThrowException(v8::String::New("Unexpected arguments")); 402 } 403 return ReadLine(); 404} 405 406v8::Handle<v8::String> ReadLine() { 407 const int kBufferSize = 1024 + 1; 408 char buffer[kBufferSize]; 409 410 char* res; 411 { 412 v8::Unlocker unlocker; 413 res = fgets(buffer, buffer_size, stdin); 414 } 415 if (res == NULL) { 416 v8::Handle<v8::Primitive> t = v8::Undefined(); 417 return reinterpret_cast<v8::Handle<v8::String>&>(t); 418 } 419 // remove newline char 420 for (char* pos = buffer; *pos != '\0'; pos++) { 421 if (*pos == '\n') { 422 *pos = '\0'; 423 break; 424 } 425 } 426 return v8::String::New(buffer); 427} 428