1/*
2 *  Copyright (C) 1999-2000 Harri Porten (porten@kde.org)
3 *  Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved.
4 *  Copyright (C) 2006 Bjoern Graf (bjoern.graf@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
68using namespace JSC;
69using namespace WTF;
70
71static void cleanupGlobalData(JSGlobalData*);
72static bool fillBufferWithContentsOfFile(const UString& fileName, Vector<char>& buffer);
73
74static JSValue JSC_HOST_CALL functionPrint(ExecState*, JSObject*, JSValue, const ArgList&);
75static JSValue JSC_HOST_CALL functionDebug(ExecState*, JSObject*, JSValue, const ArgList&);
76static JSValue JSC_HOST_CALL functionGC(ExecState*, JSObject*, JSValue, const ArgList&);
77static JSValue JSC_HOST_CALL functionVersion(ExecState*, JSObject*, JSValue, const ArgList&);
78static JSValue JSC_HOST_CALL functionRun(ExecState*, JSObject*, JSValue, const ArgList&);
79static JSValue JSC_HOST_CALL functionLoad(ExecState*, JSObject*, JSValue, const ArgList&);
80static JSValue JSC_HOST_CALL functionCheckSyntax(ExecState*, JSObject*, JSValue, const ArgList&);
81static JSValue JSC_HOST_CALL functionReadline(ExecState*, JSObject*, JSValue, const ArgList&);
82static NO_RETURN JSValue JSC_HOST_CALL functionQuit(ExecState*, JSObject*, JSValue, const ArgList&);
83
84#if ENABLE(SAMPLING_FLAGS)
85static JSValue JSC_HOST_CALL functionSetSamplingFlags(ExecState*, JSObject*, JSValue, const ArgList&);
86static JSValue JSC_HOST_CALL functionClearSamplingFlags(ExecState*, JSObject*, JSValue, const ArgList&);
87#endif
88
89struct Script {
90    bool isFile;
91    char* argument;
92
93    Script(bool isFile, char *argument)
94        : isFile(isFile)
95        , argument(argument)
96    {
97    }
98};
99
100struct 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
113static const char interactivePrompt[] = "> ";
114static const UString interpreterName("Interpreter");
115
116class StopWatch {
117public:
118    void start();
119    void stop();
120    long getElapsedMS(); // call stop() first
121
122private:
123    double m_startTime;
124    double m_stopTime;
125};
126
127void StopWatch::start()
128{
129    m_startTime = currentTime();
130}
131
132void StopWatch::stop()
133{
134    m_stopTime = currentTime();
135}
136
137long StopWatch::getElapsedMS()
138{
139    return static_cast<long>((m_stopTime - m_startTime) * 1000);
140}
141
142class GlobalObject : public JSGlobalObject {
143public:
144    GlobalObject(const Vector<UString>& arguments);
145    virtual UString className() const { return "global"; }
146};
147COMPILE_ASSERT(!IsInteger<GlobalObject>::value, WTF_IsInteger_GlobalObject_false);
148ASSERT_CLASS_FITS_IN_CELL(GlobalObject);
149
150GlobalObject::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
174JSValue 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
188JSValue 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
194JSValue JSC_HOST_CALL functionGC(ExecState* exec, JSObject*, JSValue, const ArgList&)
195{
196    JSLock lock(SilenceAssertionsOnly);
197    exec->heap()->collectAllGarbage();
198    return jsUndefined();
199}
200
201JSValue 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
208JSValue 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
225JSValue 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
241JSValue 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)
258JSValue 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
268JSValue 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
279JSValue 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
293JSValue 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
322int jscmain(int argc, char** argv, JSGlobalData*);
323
324int 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
358static void cleanupGlobalData(JSGlobalData* globalData)
359{
360    JSLock lock(SilenceAssertionsOnly);
361    globalData->heap.destroy();
362    globalData->deref();
363}
364
365static 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
419static 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
455static 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
471static 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
521int 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
536static 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