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