Home | History | Annotate | Download | only in JavaScriptCore
      1 /*
      2  *  Copyright (C) 1999-2000 Harri Porten (porten (at) kde.org)
      3  *  Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved.
      4  *  Copyright (C) 2006 Bjoern Graf (bjoern.graf (at) gmail.com)
      5  *
      6  *  This library is free software; you can redistribute it and/or
      7  *  modify it under the terms of the GNU Library General Public
      8  *  License as published by the Free Software Foundation; either
      9  *  version 2 of the License, or (at your option) any later version.
     10  *
     11  *  This library is distributed in the hope that it will be useful,
     12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
     13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     14  *  Library General Public License for more details.
     15  *
     16  *  You should have received a copy of the GNU Library General Public License
     17  *  along with this library; see the file COPYING.LIB.  If not, write to
     18  *  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
     19  *  Boston, MA 02110-1301, USA.
     20  *
     21  */
     22 
     23 #include "config.h"
     24 
     25 #include "BytecodeGenerator.h"
     26 #include "Completion.h"
     27 #include "CurrentTime.h"
     28 #include "ExceptionHelpers.h"
     29 #include "InitializeThreading.h"
     30 #include "JSArray.h"
     31 #include "JSFunction.h"
     32 #include "JSLock.h"
     33 #include "JSString.h"
     34 #include "SamplingTool.h"
     35 #include <math.h>
     36 #include <stdio.h>
     37 #include <stdlib.h>
     38 #include <string.h>
     39 
     40 #if !OS(WINDOWS)
     41 #include <unistd.h>
     42 #endif
     43 
     44 #if HAVE(READLINE)
     45 #include <readline/history.h>
     46 #include <readline/readline.h>
     47 #endif
     48 
     49 #if HAVE(SYS_TIME_H)
     50 #include <sys/time.h>
     51 #endif
     52 
     53 #if HAVE(SIGNAL_H)
     54 #include <signal.h>
     55 #endif
     56 
     57 #if COMPILER(MSVC) && !OS(WINCE)
     58 #include <crtdbg.h>
     59 #include <mmsystem.h>
     60 #include <windows.h>
     61 #endif
     62 
     63 #if PLATFORM(QT)
     64 #include <QCoreApplication>
     65 #include <QDateTime>
     66 #endif
     67 
     68 using namespace JSC;
     69 using namespace WTF;
     70 
     71 static void cleanupGlobalData(JSGlobalData*);
     72 static bool fillBufferWithContentsOfFile(const UString& fileName, Vector<char>& buffer);
     73 
     74 static EncodedJSValue JSC_HOST_CALL functionPrint(ExecState*);
     75 static EncodedJSValue JSC_HOST_CALL functionDebug(ExecState*);
     76 static EncodedJSValue JSC_HOST_CALL functionGC(ExecState*);
     77 static EncodedJSValue JSC_HOST_CALL functionVersion(ExecState*);
     78 static EncodedJSValue JSC_HOST_CALL functionRun(ExecState*);
     79 static EncodedJSValue JSC_HOST_CALL functionLoad(ExecState*);
     80 static EncodedJSValue JSC_HOST_CALL functionCheckSyntax(ExecState*);
     81 static EncodedJSValue JSC_HOST_CALL functionReadline(ExecState*);
     82 static NO_RETURN_WITH_VALUE EncodedJSValue JSC_HOST_CALL functionQuit(ExecState*);
     83 
     84 #if ENABLE(SAMPLING_FLAGS)
     85 static EncodedJSValue JSC_HOST_CALL functionSetSamplingFlags(ExecState*);
     86 static EncodedJSValue JSC_HOST_CALL functionClearSamplingFlags(ExecState*);
     87 #endif
     88 
     89 struct Script {
     90     bool isFile;
     91     char* argument;
     92 
     93     Script(bool isFile, char *argument)
     94         : isFile(isFile)
     95         , argument(argument)
     96     {
     97     }
     98 };
     99 
    100 struct Options {
    101     Options()
    102         : interactive(false)
    103         , dump(false)
    104     {
    105     }
    106 
    107     bool interactive;
    108     bool dump;
    109     Vector<Script> scripts;
    110     Vector<UString> arguments;
    111 };
    112 
    113 static const char interactivePrompt[] = "> ";
    114 static const UString interpreterName("Interpreter");
    115 
    116 class StopWatch {
    117 public:
    118     void start();
    119     void stop();
    120     long getElapsedMS(); // call stop() first
    121 
    122 private:
    123     double m_startTime;
    124     double m_stopTime;
    125 };
    126 
    127 void StopWatch::start()
    128 {
    129     m_startTime = currentTime();
    130 }
    131 
    132 void StopWatch::stop()
    133 {
    134     m_stopTime = currentTime();
    135 }
    136 
    137 long StopWatch::getElapsedMS()
    138 {
    139     return static_cast<long>((m_stopTime - m_startTime) * 1000);
    140 }
    141 
    142 class GlobalObject : public JSGlobalObject {
    143 public:
    144     GlobalObject(JSGlobalData&, const Vector<UString>& arguments);
    145     virtual UString className() const { return "global"; }
    146 };
    147 COMPILE_ASSERT(!IsInteger<GlobalObject>::value, WTF_IsInteger_GlobalObject_false);
    148 ASSERT_CLASS_FITS_IN_CELL(GlobalObject);
    149 
    150 GlobalObject::GlobalObject(JSGlobalData& globalData, const Vector<UString>& arguments)
    151     : JSGlobalObject(globalData)
    152 {
    153     putDirectFunction(globalExec(), new (globalExec()) JSFunction(globalExec(), this, functionStructure(), 1, Identifier(globalExec(), "debug"), functionDebug));
    154     putDirectFunction(globalExec(), new (globalExec()) JSFunction(globalExec(), this, functionStructure(), 1, Identifier(globalExec(), "print"), functionPrint));
    155     putDirectFunction(globalExec(), new (globalExec()) JSFunction(globalExec(), this, functionStructure(), 0, Identifier(globalExec(), "quit"), functionQuit));
    156     putDirectFunction(globalExec(), new (globalExec()) JSFunction(globalExec(), this, functionStructure(), 0, Identifier(globalExec(), "gc"), functionGC));
    157     putDirectFunction(globalExec(), new (globalExec()) JSFunction(globalExec(), this, functionStructure(), 1, Identifier(globalExec(), "version"), functionVersion));
    158     putDirectFunction(globalExec(), new (globalExec()) JSFunction(globalExec(), this, functionStructure(), 1, Identifier(globalExec(), "run"), functionRun));
    159     putDirectFunction(globalExec(), new (globalExec()) JSFunction(globalExec(), this, functionStructure(), 1, Identifier(globalExec(), "load"), functionLoad));
    160     putDirectFunction(globalExec(), new (globalExec()) JSFunction(globalExec(), this, functionStructure(), 1, Identifier(globalExec(), "checkSyntax"), functionCheckSyntax));
    161     putDirectFunction(globalExec(), new (globalExec()) JSFunction(globalExec(), this, functionStructure(), 0, Identifier(globalExec(), "readline"), functionReadline));
    162 
    163 #if ENABLE(SAMPLING_FLAGS)
    164     putDirectFunction(globalExec(), new (globalExec()) JSFunction(globalExec(), this, functionStructure(), 1, Identifier(globalExec(), "setSamplingFlags"), functionSetSamplingFlags));
    165     putDirectFunction(globalExec(), new (globalExec()) JSFunction(globalExec(), this, functionStructure(), 1, Identifier(globalExec(), "clearSamplingFlags"), functionClearSamplingFlags));
    166 #endif
    167 
    168     JSObject* array = constructEmptyArray(globalExec());
    169     for (size_t i = 0; i < arguments.size(); ++i)
    170         array->put(globalExec(), i, jsString(globalExec(), arguments[i]));
    171     putDirect(globalExec()->globalData(), Identifier(globalExec(), "arguments"), array);
    172 }
    173 
    174 EncodedJSValue JSC_HOST_CALL functionPrint(ExecState* exec)
    175 {
    176     for (unsigned i = 0; i < exec->argumentCount(); ++i) {
    177         if (i)
    178             putchar(' ');
    179 
    180         printf("%s", exec->argument(i).toString(exec).utf8().data());
    181     }
    182 
    183     putchar('\n');
    184     fflush(stdout);
    185     return JSValue::encode(jsUndefined());
    186 }
    187 
    188 EncodedJSValue JSC_HOST_CALL functionDebug(ExecState* exec)
    189 {
    190     fprintf(stderr, "--> %s\n", exec->argument(0).toString(exec).utf8().data());
    191     return JSValue::encode(jsUndefined());
    192 }
    193 
    194 EncodedJSValue JSC_HOST_CALL functionGC(ExecState* exec)
    195 {
    196     JSLock lock(SilenceAssertionsOnly);
    197     exec->heap()->collectAllGarbage();
    198     return JSValue::encode(jsUndefined());
    199 }
    200 
    201 EncodedJSValue JSC_HOST_CALL functionVersion(ExecState*)
    202 {
    203     // We need this function for compatibility with the Mozilla JS tests but for now
    204     // we don't actually do any version-specific handling
    205     return JSValue::encode(jsUndefined());
    206 }
    207 
    208 EncodedJSValue JSC_HOST_CALL functionRun(ExecState* exec)
    209 {
    210     UString fileName = exec->argument(0).toString(exec);
    211     Vector<char> script;
    212     if (!fillBufferWithContentsOfFile(fileName, script))
    213         return JSValue::encode(throwError(exec, createError(exec, "Could not open file.")));
    214 
    215     GlobalObject* globalObject = new (&exec->globalData()) GlobalObject(exec->globalData(), Vector<UString>());
    216 
    217     StopWatch stopWatch;
    218     stopWatch.start();
    219     evaluate(globalObject->globalExec(), globalObject->globalScopeChain(), makeSource(script.data(), fileName));
    220     stopWatch.stop();
    221 
    222     return JSValue::encode(jsNumber(stopWatch.getElapsedMS()));
    223 }
    224 
    225 EncodedJSValue JSC_HOST_CALL functionLoad(ExecState* exec)
    226 {
    227     UString fileName = exec->argument(0).toString(exec);
    228     Vector<char> script;
    229     if (!fillBufferWithContentsOfFile(fileName, script))
    230         return JSValue::encode(throwError(exec, createError(exec, "Could not open file.")));
    231 
    232     JSGlobalObject* globalObject = exec->lexicalGlobalObject();
    233     Completion result = evaluate(globalObject->globalExec(), globalObject->globalScopeChain(), makeSource(script.data(), fileName));
    234     if (result.complType() == Throw)
    235         throwError(exec, result.value());
    236     return JSValue::encode(result.value());
    237 }
    238 
    239 EncodedJSValue JSC_HOST_CALL functionCheckSyntax(ExecState* exec)
    240 {
    241     UString fileName = exec->argument(0).toString(exec);
    242     Vector<char> script;
    243     if (!fillBufferWithContentsOfFile(fileName, script))
    244         return JSValue::encode(throwError(exec, createError(exec, "Could not open file.")));
    245 
    246     JSGlobalObject* globalObject = exec->lexicalGlobalObject();
    247 
    248     StopWatch stopWatch;
    249     stopWatch.start();
    250     Completion result = checkSyntax(globalObject->globalExec(), makeSource(script.data(), fileName));
    251     stopWatch.stop();
    252 
    253     if (result.complType() == Throw)
    254         throwError(exec, result.value());
    255     return JSValue::encode(jsNumber(stopWatch.getElapsedMS()));
    256 }
    257 
    258 #if ENABLE(SAMPLING_FLAGS)
    259 EncodedJSValue JSC_HOST_CALL functionSetSamplingFlags(ExecState* exec)
    260 {
    261     for (unsigned i = 0; i < exec->argumentCount(); ++i) {
    262         unsigned flag = static_cast<unsigned>(exec->argument(i).toNumber(exec));
    263         if ((flag >= 1) && (flag <= 32))
    264             SamplingFlags::setFlag(flag);
    265     }
    266     return JSValue::encode(jsNull());
    267 }
    268 
    269 EncodedJSValue JSC_HOST_CALL functionClearSamplingFlags(ExecState* exec)
    270 {
    271     for (unsigned i = 0; i < exec->argumentCount(); ++i) {
    272         unsigned flag = static_cast<unsigned>(exec->argument(i).toNumber(exec));
    273         if ((flag >= 1) && (flag <= 32))
    274             SamplingFlags::clearFlag(flag);
    275     }
    276     return JSValue::encode(jsNull());
    277 }
    278 #endif
    279 
    280 EncodedJSValue JSC_HOST_CALL functionReadline(ExecState* exec)
    281 {
    282     Vector<char, 256> line;
    283     int c;
    284     while ((c = getchar()) != EOF) {
    285         // FIXME: Should we also break on \r?
    286         if (c == '\n')
    287             break;
    288         line.append(c);
    289     }
    290     line.append('\0');
    291     return JSValue::encode(jsString(exec, line.data()));
    292 }
    293 
    294 EncodedJSValue JSC_HOST_CALL functionQuit(ExecState* exec)
    295 {
    296     // Technically, destroying the heap in the middle of JS execution is a no-no,
    297     // but we want to maintain compatibility with the Mozilla test suite, so
    298     // we pretend that execution has terminated to avoid ASSERTs, then tear down the heap.
    299     exec->globalData().dynamicGlobalObject = 0;
    300 
    301     cleanupGlobalData(&exec->globalData());
    302     exit(EXIT_SUCCESS);
    303 
    304 #if COMPILER(MSVC) && OS(WINCE)
    305     // Without this, Visual Studio will complain that this method does not return a value.
    306     return JSValue::encode(jsUndefined());
    307 #endif
    308 }
    309 
    310 // Use SEH for Release builds only to get rid of the crash report dialog
    311 // (luckily the same tests fail in Release and Debug builds so far). Need to
    312 // be in a separate main function because the jscmain function requires object
    313 // unwinding.
    314 
    315 #if COMPILER(MSVC) && !defined(_DEBUG) && !OS(WINCE)
    316 #define TRY       __try {
    317 #define EXCEPT(x) } __except (EXCEPTION_EXECUTE_HANDLER) { x; }
    318 #else
    319 #define TRY
    320 #define EXCEPT(x)
    321 #endif
    322 
    323 int jscmain(int argc, char** argv, JSGlobalData*);
    324 
    325 int main(int argc, char** argv)
    326 {
    327 #if OS(WINDOWS)
    328 #if !OS(WINCE)
    329     // Cygwin calls ::SetErrorMode(SEM_FAILCRITICALERRORS), which we will inherit. This is bad for
    330     // testing/debugging, as it causes the post-mortem debugger not to be invoked. We reset the
    331     // error mode here to work around Cygwin's behavior. See <http://webkit.org/b/55222>.
    332     ::SetErrorMode(0);
    333 #endif
    334 
    335 #if defined(_DEBUG)
    336     _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR);
    337     _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE);
    338     _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR);
    339     _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE);
    340     _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR);
    341     _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE);
    342 #endif
    343 
    344     timeBeginPeriod(1);
    345 #endif
    346 
    347 #if PLATFORM(QT)
    348     QCoreApplication app(argc, argv);
    349 #endif
    350 
    351     // Initialize JSC before getting JSGlobalData.
    352     JSC::initializeThreading();
    353 
    354     // We can't use destructors in the following code because it uses Windows
    355     // Structured Exception Handling
    356     int res = 0;
    357     JSGlobalData* globalData = JSGlobalData::create(ThreadStackTypeLarge).leakRef();
    358     TRY
    359         res = jscmain(argc, argv, globalData);
    360     EXCEPT(res = 3)
    361 
    362     cleanupGlobalData(globalData);
    363     return res;
    364 }
    365 
    366 static void cleanupGlobalData(JSGlobalData* globalData)
    367 {
    368     JSLock lock(SilenceAssertionsOnly);
    369     globalData->clearBuiltinStructures();
    370     globalData->heap.destroy();
    371     globalData->deref();
    372 }
    373 
    374 static bool runWithScripts(GlobalObject* globalObject, const Vector<Script>& scripts, bool dump)
    375 {
    376     UString script;
    377     UString fileName;
    378     Vector<char> scriptBuffer;
    379 
    380     if (dump)
    381         BytecodeGenerator::setDumpsGeneratedCode(true);
    382 
    383     JSGlobalData& globalData = globalObject->globalData();
    384 
    385 #if ENABLE(SAMPLING_FLAGS)
    386     SamplingFlags::start();
    387 #endif
    388 
    389     bool success = true;
    390     for (size_t i = 0; i < scripts.size(); i++) {
    391         if (scripts[i].isFile) {
    392             fileName = scripts[i].argument;
    393             if (!fillBufferWithContentsOfFile(fileName, scriptBuffer))
    394                 return false; // fail early so we can catch missing files
    395             script = scriptBuffer.data();
    396         } else {
    397             script = scripts[i].argument;
    398             fileName = "[Command Line]";
    399         }
    400 
    401         globalData.startSampling();
    402 
    403         Completion completion = evaluate(globalObject->globalExec(), globalObject->globalScopeChain(), makeSource(script, fileName));
    404         success = success && completion.complType() != Throw;
    405         if (dump) {
    406             if (completion.complType() == Throw)
    407                 printf("Exception: %s\n", completion.value().toString(globalObject->globalExec()).utf8().data());
    408             else
    409                 printf("End: %s\n", completion.value().toString(globalObject->globalExec()).utf8().data());
    410         }
    411 
    412         globalData.stopSampling();
    413         globalObject->globalExec()->clearException();
    414     }
    415 
    416 #if ENABLE(SAMPLING_FLAGS)
    417     SamplingFlags::stop();
    418 #endif
    419     globalData.dumpSampleData(globalObject->globalExec());
    420 #if ENABLE(SAMPLING_COUNTERS)
    421     AbstractSamplingCounter::dump();
    422 #endif
    423 #if ENABLE(REGEXP_TRACING)
    424     globalData.dumpRegExpTrace();
    425 #endif
    426     return success;
    427 }
    428 
    429 #define RUNNING_FROM_XCODE 0
    430 
    431 static void runInteractive(GlobalObject* globalObject)
    432 {
    433     while (true) {
    434 #if HAVE(READLINE) && !RUNNING_FROM_XCODE
    435         char* line = readline(interactivePrompt);
    436         if (!line)
    437             break;
    438         if (line[0])
    439             add_history(line);
    440         Completion completion = evaluate(globalObject->globalExec(), globalObject->globalScopeChain(), makeSource(line, interpreterName));
    441         free(line);
    442 #else
    443         printf("%s", interactivePrompt);
    444         Vector<char, 256> line;
    445         int c;
    446         while ((c = getchar()) != EOF) {
    447             // FIXME: Should we also break on \r?
    448             if (c == '\n')
    449                 break;
    450             line.append(c);
    451         }
    452         if (line.isEmpty())
    453             break;
    454         line.append('\0');
    455         Completion completion = evaluate(globalObject->globalExec(), globalObject->globalScopeChain(), makeSource(line.data(), interpreterName));
    456 #endif
    457         if (completion.complType() == Throw)
    458             printf("Exception: %s\n", completion.value().toString(globalObject->globalExec()).utf8().data());
    459         else
    460             printf("%s\n", completion.value().toString(globalObject->globalExec()).utf8().data());
    461 
    462         globalObject->globalExec()->clearException();
    463     }
    464     printf("\n");
    465 }
    466 
    467 static NO_RETURN void printUsageStatement(JSGlobalData* globalData, bool help = false)
    468 {
    469     fprintf(stderr, "Usage: jsc [options] [files] [-- arguments]\n");
    470     fprintf(stderr, "  -d         Dumps bytecode (debug builds only)\n");
    471     fprintf(stderr, "  -e         Evaluate argument as script code\n");
    472     fprintf(stderr, "  -f         Specifies a source file (deprecated)\n");
    473     fprintf(stderr, "  -h|--help  Prints this help message\n");
    474     fprintf(stderr, "  -i         Enables interactive mode (default if no files are specified)\n");
    475 #if HAVE(SIGNAL_H)
    476     fprintf(stderr, "  -s         Installs signal handlers that exit on a crash (Unix platforms only)\n");
    477 #endif
    478 
    479     cleanupGlobalData(globalData);
    480     exit(help ? EXIT_SUCCESS : EXIT_FAILURE);
    481 }
    482 
    483 static void parseArguments(int argc, char** argv, Options& options, JSGlobalData* globalData)
    484 {
    485     int i = 1;
    486     for (; i < argc; ++i) {
    487         const char* arg = argv[i];
    488         if (!strcmp(arg, "-f")) {
    489             if (++i == argc)
    490                 printUsageStatement(globalData);
    491             options.scripts.append(Script(true, argv[i]));
    492             continue;
    493         }
    494         if (!strcmp(arg, "-e")) {
    495             if (++i == argc)
    496                 printUsageStatement(globalData);
    497             options.scripts.append(Script(false, argv[i]));
    498             continue;
    499         }
    500         if (!strcmp(arg, "-i")) {
    501             options.interactive = true;
    502             continue;
    503         }
    504         if (!strcmp(arg, "-d")) {
    505             options.dump = true;
    506             continue;
    507         }
    508         if (!strcmp(arg, "-s")) {
    509 #if HAVE(SIGNAL_H)
    510             signal(SIGILL, _exit);
    511             signal(SIGFPE, _exit);
    512             signal(SIGBUS, _exit);
    513             signal(SIGSEGV, _exit);
    514 #endif
    515             continue;
    516         }
    517         if (!strcmp(arg, "--")) {
    518             ++i;
    519             break;
    520         }
    521         if (!strcmp(arg, "-h") || !strcmp(arg, "--help"))
    522             printUsageStatement(globalData, true);
    523         options.scripts.append(Script(true, argv[i]));
    524     }
    525 
    526     if (options.scripts.isEmpty())
    527         options.interactive = true;
    528 
    529     for (; i < argc; ++i)
    530         options.arguments.append(argv[i]);
    531 }
    532 
    533 int jscmain(int argc, char** argv, JSGlobalData* globalData)
    534 {
    535     JSLock lock(SilenceAssertionsOnly);
    536 
    537     Options options;
    538     parseArguments(argc, argv, options, globalData);
    539 
    540     GlobalObject* globalObject = new (globalData) GlobalObject(*globalData, options.arguments);
    541     bool success = runWithScripts(globalObject, options.scripts, options.dump);
    542     if (options.interactive && success)
    543         runInteractive(globalObject);
    544 
    545     return success ? 0 : 3;
    546 }
    547 
    548 static bool fillBufferWithContentsOfFile(const UString& fileName, Vector<char>& buffer)
    549 {
    550     FILE* f = fopen(fileName.utf8().data(), "r");
    551     if (!f) {
    552         fprintf(stderr, "Could not open file: %s\n", fileName.utf8().data());
    553         return false;
    554     }
    555 
    556     size_t bufferSize = 0;
    557     size_t bufferCapacity = 1024;
    558 
    559     buffer.resize(bufferCapacity);
    560 
    561     while (!feof(f) && !ferror(f)) {
    562         bufferSize += fread(buffer.data() + bufferSize, 1, bufferCapacity - bufferSize, f);
    563         if (bufferSize == bufferCapacity) { // guarantees space for trailing '\0'
    564             bufferCapacity *= 2;
    565             buffer.resize(bufferCapacity);
    566         }
    567     }
    568     fclose(f);
    569     buffer[bufferSize] = '\0';
    570 
    571     if (buffer[0] == '#' && buffer[1] == '!')
    572         buffer[0] = buffer[1] = '/';
    573 
    574     return true;
    575 }
    576