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     if (!args.isEmpty())
163         printf("%s\n", args[0]->toString(exec).UTF8String().c_str());
164     else
165         putchar('\n');
166     
167     fflush(stdout);
168     return jsUndefined();
169 }
170
171 JSValue* functionDebug(ExecState* exec, JSObject*, const List& args)
172 {
173     fprintf(stderr, "--> %s\n", args[0]->toString(exec).UTF8String().c_str());
174     return jsUndefined();
175 }
176
177 JSValue* functionGC(ExecState*, JSObject*, const List&)
178 {
179     JSLock lock;
180     Collector::collect();
181     return jsUndefined();
182 }
183
184 JSValue* functionVersion(ExecState*, JSObject*, const List&)
185 {
186     // We need this function for compatibility with the Mozilla JS tests but for now
187     // we don't actually do any version-specific handling
188     return jsUndefined();
189 }
190
191 JSValue* functionRun(ExecState* exec, JSObject*, const List& args)
192 {
193     StopWatch stopWatch;
194     UString fileName = args[0]->toString(exec);
195     Vector<char> script;
196     if (!fillBufferWithContentsOfFile(fileName, script))
197         return throwError(exec, GeneralError, "Could not open file.");
198
199     JSGlobalObject* globalObject = exec->dynamicGlobalObject();
200
201     stopWatch.start();
202     Interpreter::evaluate(globalObject->globalExec(), globalObject->globalScopeChain(), fileName, 1, script.data());
203     stopWatch.stop();
204
205     return jsNumber(stopWatch.getElapsedMS());
206 }
207
208 JSValue* functionLoad(ExecState* exec, JSObject*, const List& args)
209 {
210     UString fileName = args[0]->toString(exec);
211     Vector<char> script;
212     if (!fillBufferWithContentsOfFile(fileName, script))
213         return throwError(exec, GeneralError, "Could not open file.");
214
215     JSGlobalObject* globalObject = exec->dynamicGlobalObject();
216     Interpreter::evaluate(globalObject->globalExec(), globalObject->globalScopeChain(), fileName, 1, script.data());
217
218     return jsUndefined();
219 }
220
221 JSValue* functionReadline(ExecState*, JSObject*, const List&)
222 {
223     Vector<char, 256> line;
224     int c;
225     while ((c = getchar()) != EOF) {
226         // FIXME: Should we also break on \r? 
227         if (c == '\n')
228             break;
229         line.append(c);
230     }
231     line.append('\0');
232     return jsString(line.data());
233 }
234
235 JSValue* functionQuit(ExecState*, JSObject*, const List&)
236 {
237     exit(0);
238 #if !COMPILER(MSVC)
239     // MSVC knows that exit(0) never returns, so it flags this return statement as unreachable.
240     return jsUndefined();
241 #endif
242 }
243
244 // Use SEH for Release builds only to get rid of the crash report dialog
245 // (luckily the same tests fail in Release and Debug builds so far). Need to
246 // be in a separate main function because the kjsmain function requires object
247 // unwinding.
248
249 #if PLATFORM(WIN_OS) && !defined(_DEBUG)
250 #define TRY       __try {
251 #define EXCEPT(x) } __except (EXCEPTION_EXECUTE_HANDLER) { x; }
252 #else
253 #define TRY
254 #define EXCEPT(x)
255 #endif
256
257 int kjsmain(int argc, char** argv);
258
259 int main(int argc, char** argv)
260 {
261 #if defined(_DEBUG) && PLATFORM(WIN_OS)
262     _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR);
263     _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE);
264     _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR);
265     _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE);
266     _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR);
267     _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE);
268 #endif
269
270     int res = 0;
271     TRY
272         res = kjsmain(argc, argv);
273     EXCEPT(res = 3)
274     return res;
275 }
276
277 static bool prettyPrintScript(ExecState* exec, const UString& fileName, const Vector<char>& script)
278 {
279     int errLine = 0;
280     UString errMsg;
281     UString scriptUString(script.data());
282     RefPtr<ProgramNode> programNode = exec->parser()->parse<ProgramNode>(exec, fileName, 1, UStringSourceProvider::create(scriptUString), 0, &errLine, &errMsg);
283     if (!programNode) {
284         fprintf(stderr, "%s:%d: %s.\n", fileName.UTF8String().c_str(), errLine, errMsg.UTF8String().c_str());
285         return false;
286     }
287
288     printf("%s\n", programNode->toString().UTF8String().c_str());
289     return true;
290 }
291
292 static bool runWithScripts(const Vector<UString>& fileNames, Vector<UString>& arguments, bool prettyPrint, bool dump)
293 {
294     GlobalObject* globalObject = new GlobalObject(arguments);
295     Vector<char> script;
296
297     if (dump)
298         CodeGenerator::setDumpsGeneratedCode(true);
299
300     bool success = true;
301
302     for (size_t i = 0; i < fileNames.size(); i++) {
303         UString fileName = fileNames[i];
304
305         if (!fillBufferWithContentsOfFile(fileName, script))
306             return false; // fail early so we can catch missing files
307
308         if (prettyPrint)
309             prettyPrintScript(globalObject->globalExec(), fileName, script);
310         else {
311             Completion completion = Interpreter::evaluate(globalObject->globalExec(), globalObject->globalScopeChain(), fileName, 1, script.data());
312             success = success && completion.complType() != Throw;
313             if (dump) {
314                 if (success)
315                     printf("End: %s\n", completion.value()->toString(globalObject->globalExec()).ascii());
316                 else
317                     printf("Exception: %s\n", completion.value()->toString(globalObject->globalExec()).ascii());
318             }
319         }
320     }
321     return success;
322 }
323
324 static void printUsageStatement()
325 {
326     fprintf(stderr, "Usage: testkjs -f file1 [-f file2...][-p][-- arguments...]\n");
327     exit(-1);
328 }
329
330 static void parseArguments(int argc, char** argv, Vector<UString>& fileNames, Vector<UString>& arguments, bool& prettyPrint, bool& dump)
331 {
332     if (argc < 3)
333         printUsageStatement();
334
335     int i = 1;
336     for (; i < argc; ++i) {
337         const char* arg = argv[i];
338         if (strcmp(arg, "-f") == 0) {
339             if (++i == argc)
340                 printUsageStatement();
341             fileNames.append(argv[i]);
342             continue;
343         }
344         if (strcmp(arg, "-p") == 0) {
345             prettyPrint = true;
346             continue;
347         }
348         if (strcmp(arg, "-d") == 0) {
349             dump = true;
350             continue;
351         }
352         if (strcmp(arg, "-s") == 0) {
353 #if PLATFORM(UNIX)
354             signal(SIGILL, _exit);
355             signal(SIGFPE, _exit);
356             signal(SIGBUS, _exit);
357             signal(SIGSEGV, _exit);
358 #endif
359             continue;
360         }
361         if (strcmp(arg, "--") == 0) {
362             ++i;
363             break;
364         }
365         break;
366     }
367
368     for (; i < argc; ++i)
369         arguments.append(argv[i]);
370 }
371
372 int kjsmain(int argc, char** argv)
373 {
374     initializeThreading();
375
376     JSLock lock;
377
378     bool prettyPrint = false;
379     bool dump = false;
380     Vector<UString> fileNames;
381     Vector<UString> arguments;
382     parseArguments(argc, argv, fileNames, arguments, prettyPrint, dump);
383
384     bool success = runWithScripts(fileNames, arguments, prettyPrint, dump);
385
386 #ifndef NDEBUG
387     Collector::collect();
388 #endif
389
390     return success ? 0 : 3;
391 }
392
393 static bool fillBufferWithContentsOfFile(const UString& fileName, Vector<char>& buffer)
394 {
395     FILE* f = fopen(fileName.UTF8String().c_str(), "r");
396     if (!f) {
397         fprintf(stderr, "Could not open file: %s\n", fileName.UTF8String().c_str());
398         return false;
399     }
400
401     size_t buffer_size = 0;
402     size_t buffer_capacity = 1024;
403
404     buffer.resize(buffer_capacity);
405
406     while (!feof(f) && !ferror(f)) {
407         buffer_size += fread(buffer.data() + buffer_size, 1, buffer_capacity - buffer_size, f);
408         if (buffer_size == buffer_capacity) { // guarantees space for trailing '\0'
409             buffer_capacity *= 2;
410             buffer.resize(buffer_capacity);
411         }
412     }
413     fclose(f);
414     buffer[buffer_size] = '\0';
415
416     return true;
417 }