2008-06-07 Cameron Zwarich <cwzwarich@uwaterloo.ca>
[WebKit-https.git] / JavaScriptCore / kjs / testkjs.cpp
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 "CodeGenerator.h"
26 #include "JSGlobalObject.h"
27 #include "JSLock.h"
28 #include "Parser.h"
29 #include "array_object.h"
30 #include "collector.h"
31 #include "function.h"
32 #include "InitializeThreading.h"
33 #include "interpreter.h"
34 #include "nodes.h"
35 #include "object.h"
36 #include "protect.h"
37 #include <math.h>
38 #include <stdio.h>
39 #include <string.h>
40 #include <wtf/Assertions.h>
41 #include <wtf/HashTraits.h>
42
43 #if !PLATFORM(WIN_OS)
44 #include <unistd.h>
45 #endif
46
47 #if HAVE(SYS_TIME_H)
48 #include <sys/time.h>
49 #endif
50
51 #if PLATFORM(UNIX)
52 #include <signal.h>
53 #endif
54
55 #if PLATFORM(WIN_OS)
56 #include <crtdbg.h>
57 #include <windows.h>
58 #endif
59
60 #if PLATFORM(QT)
61 #include <QDateTime>
62 #endif
63
64 using namespace KJS;
65 using namespace WTF;
66
67 static bool fillBufferWithContentsOfFile(const UString& fileName, Vector<char>& buffer);
68
69 static JSValue* functionPrint(ExecState*, JSObject*, const List&);
70 static JSValue* functionDebug(ExecState*, JSObject*, const List&);
71 static JSValue* functionGC(ExecState*, JSObject*, const List&);
72 static JSValue* functionVersion(ExecState*, JSObject*, const List&);
73 static JSValue* functionRun(ExecState*, JSObject*, const List&);
74 static JSValue* functionLoad(ExecState*, JSObject*, const List&);
75 static JSValue* functionReadline(ExecState*, JSObject*, const List&);
76 static JSValue* functionQuit(ExecState*, JSObject*, const List&);
77
78 class StopWatch {
79 public:
80     void start();
81     void stop();
82     long getElapsedMS(); // call stop() first
83
84 private:
85 #if PLATFORM(QT)
86     uint m_startTime;
87     uint m_stopTime;
88 #elif PLATFORM(WIN_OS)
89     DWORD m_startTime;
90     DWORD m_stopTime;
91 #else
92     // Windows does not have timeval, disabling this class for now (bug 7399)
93     timeval m_startTime;
94     timeval m_stopTime;
95 #endif
96 };
97
98 void StopWatch::start()
99 {
100 #if PLATFORM(QT)
101     QDateTime t = QDateTime::currentDateTime();
102     m_startTime = t.toTime_t() * 1000 + t.time().msec();
103 #elif PLATFORM(WIN_OS)
104     m_startTime = timeGetTime();
105 #else
106     gettimeofday(&m_startTime, 0);
107 #endif
108 }
109
110 void StopWatch::stop()
111 {
112 #if PLATFORM(QT)
113     QDateTime t = QDateTime::currentDateTime();
114     m_stopTime = t.toTime_t() * 1000 + t.time().msec();
115 #elif PLATFORM(WIN_OS)
116     m_stopTime = timeGetTime();
117 #else
118     gettimeofday(&m_stopTime, 0);
119 #endif
120 }
121
122 long StopWatch::getElapsedMS()
123 {
124 #if PLATFORM(WIN_OS) || PLATFORM(QT)
125     return m_stopTime - m_startTime;
126 #else
127     timeval elapsedTime;
128     timersub(&m_stopTime, &m_startTime, &elapsedTime);
129
130     return elapsedTime.tv_sec * 1000 + lroundf(elapsedTime.tv_usec / 1000.0f);
131 #endif
132 }
133
134 class GlobalObject : public JSGlobalObject {
135 public:
136     GlobalObject(Vector<UString>& arguments);
137     virtual UString className() const { return "global"; }
138 };
139 COMPILE_ASSERT(!IsInteger<GlobalObject>::value, WTF_IsInteger_GlobalObject_false);
140
141 GlobalObject::GlobalObject(Vector<UString>& arguments)
142 {
143     putDirectFunction(new PrototypeFunction(globalExec(), functionPrototype(), 1, "debug", functionDebug));
144     putDirectFunction(new PrototypeFunction(globalExec(), functionPrototype(), 1, "print", functionPrint));
145     putDirectFunction(new PrototypeFunction(globalExec(), functionPrototype(), 0, "quit", functionQuit));
146     putDirectFunction(new PrototypeFunction(globalExec(), functionPrototype(), 0, "gc", functionGC));
147     putDirectFunction(new PrototypeFunction(globalExec(), functionPrototype(), 1, "version", functionVersion));
148     putDirectFunction(new PrototypeFunction(globalExec(), functionPrototype(), 1, "run", functionRun));
149     putDirectFunction(new PrototypeFunction(globalExec(), functionPrototype(), 1, "load", functionLoad));
150     putDirectFunction(new PrototypeFunction(globalExec(), functionPrototype(), 0, "readline", functionReadline));
151
152     JSObject* array = arrayConstructor()->construct(globalExec(), globalExec()->emptyList());
153     for (size_t i = 0; i < arguments.size(); ++i)
154         array->put(globalExec(), i, jsString(arguments[i]));
155     putDirect("arguments", array);
156
157     Interpreter::setShouldPrintExceptions(true);
158 }
159
160 JSValue* functionPrint(ExecState* exec, JSObject*, const List& args)
161 {
162     for (unsigned i = 0; i < args.size(); ++i) {
163         if (i != 0)
164             putchar(' ');
165         
166         printf("%s", args[i]->toString(exec).UTF8String().c_str());
167     }
168     
169     putchar('\n');
170     fflush(stdout);
171     return jsUndefined();
172 }
173
174 JSValue* functionDebug(ExecState* exec, JSObject*, const List& args)
175 {
176     fprintf(stderr, "--> %s\n", args[0]->toString(exec).UTF8String().c_str());
177     return jsUndefined();
178 }
179
180 JSValue* functionGC(ExecState*, JSObject*, const List&)
181 {
182     JSLock lock;
183     Collector::collect();
184     return jsUndefined();
185 }
186
187 JSValue* functionVersion(ExecState*, JSObject*, const List&)
188 {
189     // We need this function for compatibility with the Mozilla JS tests but for now
190     // we don't actually do any version-specific handling
191     return jsUndefined();
192 }
193
194 JSValue* functionRun(ExecState* exec, JSObject*, const List& args)
195 {
196     StopWatch stopWatch;
197     UString fileName = args[0]->toString(exec);
198     Vector<char> script;
199     if (!fillBufferWithContentsOfFile(fileName, script))
200         return throwError(exec, GeneralError, "Could not open file.");
201
202     JSGlobalObject* globalObject = exec->dynamicGlobalObject();
203
204     stopWatch.start();
205     Interpreter::evaluate(globalObject->globalExec(), globalObject->globalScopeChain(), fileName, 1, script.data());
206     stopWatch.stop();
207
208     return jsNumber(stopWatch.getElapsedMS());
209 }
210
211 JSValue* functionLoad(ExecState* exec, JSObject*, const List& args)
212 {
213     UString fileName = args[0]->toString(exec);
214     Vector<char> script;
215     if (!fillBufferWithContentsOfFile(fileName, script))
216         return throwError(exec, GeneralError, "Could not open file.");
217
218     JSGlobalObject* globalObject = exec->dynamicGlobalObject();
219     Interpreter::evaluate(globalObject->globalExec(), globalObject->globalScopeChain(), fileName, 1, script.data());
220
221     return jsUndefined();
222 }
223
224 JSValue* functionReadline(ExecState*, JSObject*, const List&)
225 {
226     Vector<char, 256> line;
227     int c;
228     while ((c = getchar()) != EOF) {
229         // FIXME: Should we also break on \r? 
230         if (c == '\n')
231             break;
232         line.append(c);
233     }
234     line.append('\0');
235     return jsString(line.data());
236 }
237
238 JSValue* functionQuit(ExecState*, JSObject*, const List&)
239 {
240     exit(0);
241 #if !COMPILER(MSVC)
242     // MSVC knows that exit(0) never returns, so it flags this return statement as unreachable.
243     return jsUndefined();
244 #endif
245 }
246
247 // Use SEH for Release builds only to get rid of the crash report dialog
248 // (luckily the same tests fail in Release and Debug builds so far). Need to
249 // be in a separate main function because the kjsmain function requires object
250 // unwinding.
251
252 #if PLATFORM(WIN_OS) && !defined(_DEBUG)
253 #define TRY       __try {
254 #define EXCEPT(x) } __except (EXCEPTION_EXECUTE_HANDLER) { x; }
255 #else
256 #define TRY
257 #define EXCEPT(x)
258 #endif
259
260 int kjsmain(int argc, char** argv);
261
262 int main(int argc, char** argv)
263 {
264 #if defined(_DEBUG) && PLATFORM(WIN_OS)
265     _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR);
266     _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE);
267     _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR);
268     _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE);
269     _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR);
270     _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE);
271 #endif
272
273     int res = 0;
274     TRY
275         res = kjsmain(argc, argv);
276     EXCEPT(res = 3)
277     return res;
278 }
279
280 static bool prettyPrintScript(ExecState* exec, const UString& fileName, const Vector<char>& script)
281 {
282     int errLine = 0;
283     UString errMsg;
284     UString scriptUString(script.data());
285     RefPtr<ProgramNode> programNode = exec->parser()->parse<ProgramNode>(exec, fileName, 1, UStringSourceProvider::create(scriptUString), 0, &errLine, &errMsg);
286     if (!programNode) {
287         fprintf(stderr, "%s:%d: %s.\n", fileName.UTF8String().c_str(), errLine, errMsg.UTF8String().c_str());
288         return false;
289     }
290
291     printf("%s\n", programNode->toString().UTF8String().c_str());
292     return true;
293 }
294
295 static bool runWithScripts(const Vector<UString>& fileNames, Vector<UString>& arguments, bool prettyPrint, bool dump)
296 {
297     GlobalObject* globalObject = new GlobalObject(arguments);
298     Vector<char> script;
299
300     if (dump)
301         CodeGenerator::setDumpsGeneratedCode(true);
302
303     bool success = true;
304
305     for (size_t i = 0; i < fileNames.size(); i++) {
306         UString fileName = fileNames[i];
307
308         if (!fillBufferWithContentsOfFile(fileName, script))
309             return false; // fail early so we can catch missing files
310
311         if (prettyPrint)
312             prettyPrintScript(globalObject->globalExec(), fileName, script);
313         else {
314             Completion completion = Interpreter::evaluate(globalObject->globalExec(), globalObject->globalScopeChain(), fileName, 1, script.data());
315             success = success && completion.complType() != Throw;
316             if (dump) {
317                 if (success)
318                     printf("End: %s\n", completion.value()->toString(globalObject->globalExec()).ascii());
319                 else
320                     printf("Exception: %s\n", completion.value()->toString(globalObject->globalExec()).ascii());
321             }
322         }
323     }
324     return success;
325 }
326
327 static void printUsageStatement()
328 {
329     fprintf(stderr, "Usage: testkjs -f file1 [-f file2...][-p][-- arguments...]\n");
330     exit(-1);
331 }
332
333 static void parseArguments(int argc, char** argv, Vector<UString>& fileNames, Vector<UString>& arguments, bool& prettyPrint, bool& dump)
334 {
335     if (argc < 3)
336         printUsageStatement();
337
338     int i = 1;
339     for (; i < argc; ++i) {
340         const char* arg = argv[i];
341         if (strcmp(arg, "-f") == 0) {
342             if (++i == argc)
343                 printUsageStatement();
344             fileNames.append(argv[i]);
345             continue;
346         }
347         if (strcmp(arg, "-p") == 0) {
348             prettyPrint = true;
349             continue;
350         }
351         if (strcmp(arg, "-d") == 0) {
352             dump = true;
353             continue;
354         }
355         if (strcmp(arg, "-s") == 0) {
356 #if PLATFORM(UNIX)
357             signal(SIGILL, _exit);
358             signal(SIGFPE, _exit);
359             signal(SIGBUS, _exit);
360             signal(SIGSEGV, _exit);
361 #endif
362             continue;
363         }
364         if (strcmp(arg, "--") == 0) {
365             ++i;
366             break;
367         }
368         break;
369     }
370
371     for (; i < argc; ++i)
372         arguments.append(argv[i]);
373 }
374
375 int kjsmain(int argc, char** argv)
376 {
377     initializeThreading();
378
379     JSLock lock;
380
381     bool prettyPrint = false;
382     bool dump = false;
383     Vector<UString> fileNames;
384     Vector<UString> arguments;
385     parseArguments(argc, argv, fileNames, arguments, prettyPrint, dump);
386
387     bool success = runWithScripts(fileNames, arguments, prettyPrint, dump);
388
389 #ifndef NDEBUG
390     Collector::collect();
391 #endif
392
393     return success ? 0 : 3;
394 }
395
396 static bool fillBufferWithContentsOfFile(const UString& fileName, Vector<char>& buffer)
397 {
398     FILE* f = fopen(fileName.UTF8String().c_str(), "r");
399     if (!f) {
400         fprintf(stderr, "Could not open file: %s\n", fileName.UTF8String().c_str());
401         return false;
402     }
403
404     size_t buffer_size = 0;
405     size_t buffer_capacity = 1024;
406
407     buffer.resize(buffer_capacity);
408
409     while (!feof(f) && !ferror(f)) {
410         buffer_size += fread(buffer.data() + buffer_size, 1, buffer_capacity - buffer_size, f);
411         if (buffer_size == buffer_capacity) { // guarantees space for trailing '\0'
412             buffer_capacity *= 2;
413             buffer.resize(buffer_capacity);
414         }
415     }
416     fclose(f);
417     buffer[buffer_size] = '\0';
418
419     return true;
420 }