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 "InitializeThreading.h"
     29 #include "JSArray.h"
     30 #include "JSFunction.h"
     31 #include "JSLock.h"
     32 #include "JSString.h"
     33 #include "PrototypeFunction.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 JSValue JSC_HOST_CALL functionPrint(ExecState*, JSObject*, JSValue, const ArgList&);
     75 static JSValue JSC_HOST_CALL functionDebug(ExecState*, JSObject*, JSValue, const ArgList&);
     76 static JSValue JSC_HOST_CALL functionGC(ExecState*, JSObject*, JSValue, const ArgList&);
     77 static JSValue JSC_HOST_CALL functionVersion(ExecState*, JSObject*, JSValue, const ArgList&);
     78 static JSValue JSC_HOST_CALL functionRun(ExecState*, JSObject*, JSValue, const ArgList&);
     79 static JSValue JSC_HOST_CALL functionLoad(ExecState*, JSObject*, JSValue, const ArgList&);
     80 static JSValue JSC_HOST_CALL functionCheckSyntax(ExecState*, JSObject*, JSValue, const ArgList&);
     81 static JSValue JSC_HOST_CALL functionReadline(ExecState*, JSObject*, JSValue, const ArgList&);
     82 static NO_RETURN JSValue JSC_HOST_CALL functionQuit(ExecState*, JSObject*, JSValue, const ArgList&);
     83 
     84 #if ENABLE(SAMPLING_FLAGS)
     85 static JSValue JSC_HOST_CALL functionSetSamplingFlags(ExecState*, JSObject*, JSValue, const ArgList&);
     86 static JSValue JSC_HOST_CALL functionClearSamplingFlags(ExecState*, JSObject*, JSValue, const ArgList&);
     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(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(const Vector<UString>& arguments)
    151     : JSGlobalObject()
    152 {
    153     putDirectFunction(globalExec(), new (globalExec()) NativeFunctionWrapper(globalExec(), prototypeFunctionStructure(), 1, Identifier(globalExec(), "debug"), functionDebug));
    154     putDirectFunction(globalExec(), new (globalExec()) NativeFunctionWrapper(globalExec(), prototypeFunctionStructure(), 1, Identifier(globalExec(), "print"), functionPrint));
    155     putDirectFunction(globalExec(), new (globalExec()) NativeFunctionWrapper(globalExec(), prototypeFunctionStructure(), 0, Identifier(globalExec(), "quit"), functionQuit));
    156     putDirectFunction(globalExec(), new (globalExec()) NativeFunctionWrapper(globalExec(), prototypeFunctionStructure(), 0, Identifier(globalExec(), "gc"), functionGC));
    157     putDirectFunction(globalExec(), new (globalExec()) NativeFunctionWrapper(globalExec(), prototypeFunctionStructure(), 1, Identifier(globalExec(), "version"), functionVersion));
    158     putDirectFunction(globalExec(), new (globalExec()) NativeFunctionWrapper(globalExec(), prototypeFunctionStructure(), 1, Identifier(globalExec(), "run"), functionRun));
    159     putDirectFunction(globalExec(), new (globalExec()) NativeFunctionWrapper(globalExec(), prototypeFunctionStructure(), 1, Identifier(globalExec(), "load"), functionLoad));
    160     putDirectFunction(globalExec(), new (globalExec()) NativeFunctionWrapper(globalExec(), prototypeFunctionStructure(), 1, Identifier(globalExec(), "checkSyntax"), functionCheckSyntax));
    161     putDirectFunction(globalExec(), new (globalExec()) NativeFunctionWrapper(globalExec(), prototypeFunctionStructure(), 0, Identifier(globalExec(), "readline"), functionReadline));
    162 
    163 #if ENABLE(SAMPLING_FLAGS)
    164     putDirectFunction(globalExec(), new (globalExec()) NativeFunctionWrapper(globalExec(), prototypeFunctionStructure(), 1, Identifier(globalExec(), "setSamplingFlags"), functionSetSamplingFlags));
    165     putDirectFunction(globalExec(), new (globalExec()) NativeFunctionWrapper(globalExec(), prototypeFunctionStructure(), 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(Identifier(globalExec(), "arguments"), array);
    172 }
    173 
    174 JSValue JSC_HOST_CALL functionPrint(ExecState* exec, JSObject*, JSValue, const ArgList& args)
    175 {
    176     for (unsigned i = 0; i < args.size(); ++i) {
    177         if (i)
    178             putchar(' ');
    179 
    180         printf("%s", args.at(i).toString(exec).UTF8String().c_str());
    181     }
    182 
    183     putchar('\n');
    184     fflush(stdout);
    185     return jsUndefined();
    186 }
    187 
    188 JSValue JSC_HOST_CALL functionDebug(ExecState* exec, JSObject*, JSValue, const ArgList& args)
    189 {
    190     fprintf(stderr, "--> %s\n", args.at(0).toString(exec).UTF8String().c_str());
    191     return jsUndefined();
    192 }
    193 
    194 JSValue JSC_HOST_CALL functionGC(ExecState* exec, JSObject*, JSValue, const ArgList&)
    195 {
    196     JSLock lock(SilenceAssertionsOnly);
    197     exec->heap()->collectAllGarbage();
    198     return jsUndefined();
    199 }
    200 
    201 JSValue JSC_HOST_CALL functionVersion(ExecState*, JSObject*, JSValue, const ArgList&)
    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 jsUndefined();
    206 }
    207 
    208 JSValue JSC_HOST_CALL functionRun(ExecState* exec, JSObject*, JSValue, const ArgList& args)
    209 {
    210     StopWatch stopWatch;
    211     UString fileName = args.at(0).toString(exec);
    212     Vector<char> script;
    213     if (!fillBufferWithContentsOfFile(fileName, script))
    214         return throwError(exec, GeneralError, "Could not open file.");
    215 
    216     JSGlobalObject* globalObject = exec->lexicalGlobalObject();
    217 
    218     stopWatch.start();
    219     evaluate(globalObject->globalExec(), globalObject->globalScopeChain(), makeSource(script.data(), fileName));
    220     stopWatch.stop();
    221 
    222     return jsNumber(globalObject->globalExec(), stopWatch.getElapsedMS());
    223 }
    224 
    225 JSValue JSC_HOST_CALL functionLoad(ExecState* exec, JSObject* o, JSValue v, const ArgList& args)
    226 {
    227     UNUSED_PARAM(o);
    228     UNUSED_PARAM(v);
    229     UString fileName = args.at(0).toString(exec);
    230     Vector<char> script;
    231     if (!fillBufferWithContentsOfFile(fileName, script))
    232         return throwError(exec, GeneralError, "Could not open file.");
    233 
    234     JSGlobalObject* globalObject = exec->lexicalGlobalObject();
    235     Completion result = evaluate(globalObject->globalExec(), globalObject->globalScopeChain(), makeSource(script.data(), fileName));
    236     if (result.complType() == Throw)
    237         exec->setException(result.value());
    238     return result.value();
    239 }
    240 
    241 JSValue JSC_HOST_CALL functionCheckSyntax(ExecState* exec, JSObject* o, JSValue v, const ArgList& args)
    242 {
    243     UNUSED_PARAM(o);
    244     UNUSED_PARAM(v);
    245     UString fileName = args.at(0).toString(exec);
    246     Vector<char> script;
    247     if (!fillBufferWithContentsOfFile(fileName, script))
    248         return throwError(exec, GeneralError, "Could not open file.");
    249 
    250     JSGlobalObject* globalObject = exec->lexicalGlobalObject();
    251     Completion result = checkSyntax(globalObject->globalExec(), makeSource(script.data(), fileName));
    252     if (result.complType() == Throw)
    253         exec->setException(result.value());
    254     return result.value();
    255 }
    256 
    257 #if ENABLE(SAMPLING_FLAGS)
    258 JSValue JSC_HOST_CALL functionSetSamplingFlags(ExecState* exec, JSObject*, JSValue, const ArgList& args)
    259 {
    260     for (unsigned i = 0; i < args.size(); ++i) {
    261         unsigned flag = static_cast<unsigned>(args.at(i).toNumber(exec));
    262         if ((flag >= 1) && (flag <= 32))
    263             SamplingFlags::setFlag(flag);
    264     }
    265     return jsNull();
    266 }
    267 
    268 JSValue JSC_HOST_CALL functionClearSamplingFlags(ExecState* exec, JSObject*, JSValue, const ArgList& args)
    269 {
    270     for (unsigned i = 0; i < args.size(); ++i) {
    271         unsigned flag = static_cast<unsigned>(args.at(i).toNumber(exec));
    272         if ((flag >= 1) && (flag <= 32))
    273             SamplingFlags::clearFlag(flag);
    274     }
    275     return jsNull();
    276 }
    277 #endif
    278 
    279 JSValue JSC_HOST_CALL functionReadline(ExecState* exec, JSObject*, JSValue, const ArgList&)
    280 {
    281     Vector<char, 256> line;
    282     int c;
    283     while ((c = getchar()) != EOF) {
    284         // FIXME: Should we also break on \r?
    285         if (c == '\n')
    286             break;
    287         line.append(c);
    288     }
    289     line.append('\0');
    290     return jsString(exec, line.data());
    291 }
    292 
    293 JSValue JSC_HOST_CALL functionQuit(ExecState* exec, JSObject*, JSValue, const ArgList&)
    294 {
    295     // Technically, destroying the heap in the middle of JS execution is a no-no,
    296     // but we want to maintain compatibility with the Mozilla test suite, so
    297     // we pretend that execution has terminated to avoid ASSERTs, then tear down the heap.
    298     exec->globalData().dynamicGlobalObject = 0;
    299 
    300     cleanupGlobalData(&exec->globalData());
    301     exit(EXIT_SUCCESS);
    302 
    303 #if COMPILER(MSVC) && OS(WINCE)
    304     // Without this, Visual Studio will complain that this method does not return a value.
    305     return jsUndefined();
    306 #endif
    307 }
    308 
    309 // Use SEH for Release builds only to get rid of the crash report dialog
    310 // (luckily the same tests fail in Release and Debug builds so far). Need to
    311 // be in a separate main function because the jscmain function requires object
    312 // unwinding.
    313 
    314 #if COMPILER(MSVC) && !defined(_DEBUG)
    315 #define TRY       __try {
    316 #define EXCEPT(x) } __except (EXCEPTION_EXECUTE_HANDLER) { x; }
    317 #else
    318 #define TRY
    319 #define EXCEPT(x)
    320 #endif
    321 
    322 int jscmain(int argc, char** argv, JSGlobalData*);
    323 
    324 int main(int argc, char** argv)
    325 {
    326 #if defined(_DEBUG) && OS(WINDOWS)
    327     _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR);
    328     _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE);
    329     _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR);
    330     _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE);
    331     _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR);
    332     _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE);
    333 #endif
    334 
    335 #if COMPILER(MSVC) && !OS(WINCE)
    336     timeBeginPeriod(1);
    337 #endif
    338 
    339 #if PLATFORM(QT)
    340     QCoreApplication app(argc, argv);
    341 #endif
    342 
    343     // Initialize JSC before getting JSGlobalData.
    344     JSC::initializeThreading();
    345 
    346     // We can't use destructors in the following code because it uses Windows
    347     // Structured Exception Handling
    348     int res = 0;
    349     JSGlobalData* globalData = JSGlobalData::create().releaseRef();
    350     TRY
    351         res = jscmain(argc, argv, globalData);
    352     EXCEPT(res = 3)
    353 
    354     cleanupGlobalData(globalData);
    355     return res;
    356 }
    357 
    358 static void cleanupGlobalData(JSGlobalData* globalData)
    359 {
    360     JSLock lock(SilenceAssertionsOnly);
    361     globalData->heap.destroy();
    362     globalData->deref();
    363 }
    364 
    365 static bool runWithScripts(GlobalObject* globalObject, const Vector<Script>& scripts, bool dump)
    366 {
    367     UString script;
    368     UString fileName;
    369     Vector<char> scriptBuffer;
    370 
    371     if (dump)
    372         BytecodeGenerator::setDumpsGeneratedCode(true);
    373 
    374     JSGlobalData* globalData = globalObject->globalData();
    375 
    376 #if ENABLE(SAMPLING_FLAGS)
    377     SamplingFlags::start();
    378 #endif
    379 
    380     bool success = true;
    381     for (size_t i = 0; i < scripts.size(); i++) {
    382         if (scripts[i].isFile) {
    383             fileName = scripts[i].argument;
    384             if (!fillBufferWithContentsOfFile(fileName, scriptBuffer))
    385                 return false; // fail early so we can catch missing files
    386             script = scriptBuffer.data();
    387         } else {
    388             script = scripts[i].argument;
    389             fileName = "[Command Line]";
    390         }
    391 
    392         globalData->startSampling();
    393 
    394         Completion completion = evaluate(globalObject->globalExec(), globalObject->globalScopeChain(), makeSource(script, fileName));
    395         success = success && completion.complType() != Throw;
    396         if (dump) {
    397             if (completion.complType() == Throw)
    398                 printf("Exception: %s\n", completion.value().toString(globalObject->globalExec()).ascii());
    399             else
    400                 printf("End: %s\n", completion.value().toString(globalObject->globalExec()).ascii());
    401         }
    402 
    403         globalData->stopSampling();
    404         globalObject->globalExec()->clearException();
    405     }
    406 
    407 #if ENABLE(SAMPLING_FLAGS)
    408     SamplingFlags::stop();
    409 #endif
    410     globalData->dumpSampleData(globalObject->globalExec());
    411 #if ENABLE(SAMPLING_COUNTERS)
    412     AbstractSamplingCounter::dump();
    413 #endif
    414     return success;
    415 }
    416 
    417 #define RUNNING_FROM_XCODE 0
    418 
    419 static void runInteractive(GlobalObject* globalObject)
    420 {
    421     while (true) {
    422 #if HAVE(READLINE) && !RUNNING_FROM_XCODE
    423         char* line = readline(interactivePrompt);
    424         if (!line)
    425             break;
    426         if (line[0])
    427             add_history(line);
    428         Completion completion = evaluate(globalObject->globalExec(), globalObject->globalScopeChain(), makeSource(line, interpreterName));
    429         free(line);
    430 #else
    431         printf("%s", interactivePrompt);
    432         Vector<char, 256> line;
    433         int c;
    434         while ((c = getchar()) != EOF) {
    435             // FIXME: Should we also break on \r?
    436             if (c == '\n')
    437                 break;
    438             line.append(c);
    439         }
    440         if (line.isEmpty())
    441             break;
    442         line.append('\0');
    443         Completion completion = evaluate(globalObject->globalExec(), globalObject->globalScopeChain(), makeSource(line.data(), interpreterName));
    444 #endif
    445         if (completion.complType() == Throw)
    446             printf("Exception: %s\n", completion.value().toString(globalObject->globalExec()).ascii());
    447         else
    448             printf("%s\n", completion.value().toString(globalObject->globalExec()).UTF8String().c_str());
    449 
    450         globalObject->globalExec()->clearException();
    451     }
    452     printf("\n");
    453 }
    454 
    455 static NO_RETURN void printUsageStatement(JSGlobalData* globalData, bool help = false)
    456 {
    457     fprintf(stderr, "Usage: jsc [options] [files] [-- arguments]\n");
    458     fprintf(stderr, "  -d         Dumps bytecode (debug builds only)\n");
    459     fprintf(stderr, "  -e         Evaluate argument as script code\n");
    460     fprintf(stderr, "  -f         Specifies a source file (deprecated)\n");
    461     fprintf(stderr, "  -h|--help  Prints this help message\n");
    462     fprintf(stderr, "  -i         Enables interactive mode (default if no files are specified)\n");
    463 #if HAVE(SIGNAL_H)
    464     fprintf(stderr, "  -s         Installs signal handlers that exit on a crash (Unix platforms only)\n");
    465 #endif
    466 
    467     cleanupGlobalData(globalData);
    468     exit(help ? EXIT_SUCCESS : EXIT_FAILURE);
    469 }
    470 
    471 static void parseArguments(int argc, char** argv, Options& options, JSGlobalData* globalData)
    472 {
    473     int i = 1;
    474     for (; i < argc; ++i) {
    475         const char* arg = argv[i];
    476         if (!strcmp(arg, "-f")) {
    477             if (++i == argc)
    478                 printUsageStatement(globalData);
    479             options.scripts.append(Script(true, argv[i]));
    480             continue;
    481         }
    482         if (!strcmp(arg, "-e")) {
    483             if (++i == argc)
    484                 printUsageStatement(globalData);
    485             options.scripts.append(Script(false, argv[i]));
    486             continue;
    487         }
    488         if (!strcmp(arg, "-i")) {
    489             options.interactive = true;
    490             continue;
    491         }
    492         if (!strcmp(arg, "-d")) {
    493             options.dump = true;
    494             continue;
    495         }
    496         if (!strcmp(arg, "-s")) {
    497 #if HAVE(SIGNAL_H)
    498             signal(SIGILL, _exit);
    499             signal(SIGFPE, _exit);
    500             signal(SIGBUS, _exit);
    501             signal(SIGSEGV, _exit);
    502 #endif
    503             continue;
    504         }
    505         if (!strcmp(arg, "--")) {
    506             ++i;
    507             break;
    508         }
    509         if (!strcmp(arg, "-h") || !strcmp(arg, "--help"))
    510             printUsageStatement(globalData, true);
    511         options.scripts.append(Script(true, argv[i]));
    512     }
    513 
    514     if (options.scripts.isEmpty())
    515         options.interactive = true;
    516 
    517     for (; i < argc; ++i)
    518         options.arguments.append(argv[i]);
    519 }
    520 
    521 int jscmain(int argc, char** argv, JSGlobalData* globalData)
    522 {
    523     JSLock lock(SilenceAssertionsOnly);
    524 
    525     Options options;
    526     parseArguments(argc, argv, options, globalData);
    527 
    528     GlobalObject* globalObject = new (globalData) GlobalObject(options.arguments);
    529     bool success = runWithScripts(globalObject, options.scripts, options.dump);
    530     if (options.interactive && success)
    531         runInteractive(globalObject);
    532 
    533     return success ? 0 : 3;
    534 }
    535 
    536 static bool fillBufferWithContentsOfFile(const UString& fileName, Vector<char>& buffer)
    537 {
    538     FILE* f = fopen(fileName.UTF8String().c_str(), "r");
    539     if (!f) {
    540         fprintf(stderr, "Could not open file: %s\n", fileName.UTF8String().c_str());
    541         return false;
    542     }
    543 
    544     size_t bufferSize = 0;
    545     size_t bufferCapacity = 1024;
    546 
    547     buffer.resize(bufferCapacity);
    548 
    549     while (!feof(f) && !ferror(f)) {
    550         bufferSize += fread(buffer.data() + bufferSize, 1, bufferCapacity - bufferSize, f);
    551         if (bufferSize == bufferCapacity) { // guarantees space for trailing '\0'
    552             bufferCapacity *= 2;
    553             buffer.resize(bufferCapacity);
    554         }
    555     }
    556     fclose(f);
    557     buffer[bufferSize] = '\0';
    558 
    559     return true;
    560 }
    561