Merge remote-tracking branch 'redux/master' into sh4-pool
[tamarin-stm.git] / shell / ShellCore.cpp
blob4eacc284f9b3b8dd3baca3e0268231d83158a470
1 /* -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 4 -*- */
2 /* vi: set ts=4 sw=4 expandtab: (add to ~/.vimrc: set modeline modelines=5) */
3 /* ***** BEGIN LICENSE BLOCK *****
4 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 * The contents of this file are subject to the Mozilla Public License Version
7 * 1.1 (the "License"); you may not use this file except in compliance with
8 * the License. You may obtain a copy of the License at
9 * http://www.mozilla.org/MPL/
11 * Software distributed under the License is distributed on an "AS IS" basis,
12 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 * for the specific language governing rights and limitations under the
14 * License.
16 * The Original Code is [Open Source Virtual Machine.].
18 * The Initial Developer of the Original Code is
19 * Adobe System Incorporated.
20 * Portions created by the Initial Developer are Copyright (C) 2004-2006
21 * the Initial Developer. All Rights Reserved.
23 * Contributor(s):
24 * Adobe AS3 Team
26 * Alternatively, the contents of this file may be used under the terms of
27 * either the GNU General Public License Version 2 or later (the "GPL"), or
28 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 * in which case the provisions of the GPL or the LGPL are applicable instead
30 * of those above. If you wish to allow use of your version of this file only
31 * under the terms of either the GPL or the LGPL, and not to allow others to
32 * use your version of this file under the terms of the MPL, indicate your
33 * decision by deleting the provisions above and replace them with the notice
34 * and other provisions required by the GPL or the LGPL. If you do not delete
35 * the provisions above, a recipient may use your version of this file under
36 * the terms of any one of the MPL, the GPL or the LGPL.
38 * ***** END LICENSE BLOCK ***** */
40 #include "avmshell.h"
42 #include "shell_toplevel.cpp"
44 namespace avmshell
46 const int kScriptTimeout = 15;
47 const int kScriptGracePeriod = 5;
49 ShellCoreSettings::ShellCoreSettings()
50 : arguments(NULL)
51 , numargs(-1)
52 , nodebugger(false)
53 , astrace_console(0)
54 , do_verbose(0)
55 , enter_debugger_on_launch(false)
56 , interrupts(AvmCore::interrupts_default)
57 , verifyall(AvmCore::verifyall_default)
58 , verifyonly(AvmCore::verifyonly_default)
59 , greedy(false)
60 , nogc(false)
61 , incremental(true)
62 , exactgc(true)
63 , markstackAllowance(0)
64 , fixedcheck(true)
65 , gcthreshold(0)
66 , langID(-1)
67 , jitordie(AvmCore::jitordie_default)
68 , do_testSWFHasAS3(false)
69 , runmode(AvmCore::runmode_default)
70 #ifdef VMCFG_NANOJIT
71 , njconfig()
72 #endif
73 , st_component(NULL)
74 , st_category(NULL)
75 , st_name(NULL)
76 , apiVersionSeries(kApiVersionSeries_FP)
77 , apiVersion(kApiVersionLatest[kApiVersionSeries_FP])
78 , swfVersion(BugCompatibility::kLatest)
82 ShellToplevel::ShellToplevel(AbcEnv* abcEnv) : Toplevel(abcEnv)
86 ShellCore::ShellCore(MMgc::GC* gc, ApiVersionSeries apiVersionSeries)
87 : AvmCore(gc, apiVersionSeries)
89 systemClass = NULL;
91 gracePeriod = false;
92 inStackOverflow = false;
94 allowDebugger = -1; // aka "not yet set"
96 consoleOutputStream = new (gc) ConsoleOutputStream(gc);
98 setConsoleStream(consoleOutputStream);
101 void ShellCore::stackOverflow(Toplevel* toplevel)
103 if (inStackOverflow)
105 // Already handling a stack overflow, so do not
106 // re-enter handler.
107 return;
110 // Temporarily disable stack overflow checks
111 // so that we can construct an exception object.
112 // There should be plenty of margin before the
113 // actual stack bottom to do this.
114 inStackOverflow = true;
116 Stringp errorMessage = getErrorMessage(kStackOverflowError);
117 GCRef<ErrorObject> error = toplevel->errorClass()->constructObject(errorMessage->atom(), this->intToAtom(0));
118 Exception *exception = new (GetGC()) Exception(this, error->atom());
120 // Restore stack overflow checks
121 inStackOverflow = false;
123 // Throw the stack overflow exception
124 throwException(exception);
127 /* static */
128 void ShellCore::interruptTimerCallback(void* data)
130 ((AvmCore*)data)->raiseInterrupt(ScriptTimeout);
133 void ShellCore::interrupt(Toplevel *toplevel, InterruptReason)
135 if (gracePeriod) {
136 // This script has already had its chance; it violated
137 // the grace period.
138 // Throw an exception it cannot catch.
139 Stringp errorMessage = getErrorMessage(kScriptTerminatedError);
140 GCRef<ErrorObject> error = toplevel->errorClass()->constructObject(errorMessage->atom(), this->intToAtom(0));
141 Exception *exception = new (GetGC()) Exception(this, error->atom());
142 exception->flags |= Exception::EXIT_EXCEPTION;
143 throwException(exception);
146 // Give the script an additional grace period to
147 // clean up, and throw an exception.
148 gracePeriod = true;
150 Platform::GetInstance()->setTimer(kScriptGracePeriod, interruptTimerCallback, this);
152 toplevel->throwError(kScriptTimeoutError);
155 void ShellCore::initShellPool()
157 shell_domain = Domain::newDomain(this, builtinDomain);
158 #ifdef VMCFG_AOT
159 NativeInitializer shellNInit(this,
160 avmplus::NativeID::shell_toplevel_versioned_uris,
161 &shell_toplevel_aotInfo,
162 avmplus::NativeID::shell_toplevel_abc_method_count,
163 avmplus::NativeID::shell_toplevel_abc_class_count);
164 shellNInit.fillInClasses(avmplus::NativeID::shell_toplevel_classEntries);
165 shellNInit.fillInMethods(avmplus::NativeID::shell_toplevel_methodEntries);
166 shellPool = shellNInit.parseBuiltinABC(shell_domain);
167 #else
168 shellPool = AVM_INIT_BUILTIN_ABC_IN_DOMAIN(shell_toplevel, this, shell_domain);
169 #endif
172 ShellToplevel* ShellCore::createShellToplevel()
174 class CodeContextCreator : public AvmCore::ICodeContextCreator
176 public:
177 MMgc::GC* gc;
179 explicit CodeContextCreator(MMgc::GC* _gc) : gc(_gc)
183 virtual CodeContext* create(DomainEnv* domainEnv, const BugCompatibility* bugCompatibility)
185 return new (gc) ShellCodeContext(domainEnv, bugCompatibility);
189 // Initialize a new Toplevel. This will also create a new
190 // DomainEnv based on the builtinDomain.
191 CodeContextCreator ccc(GetGC());
192 ShellToplevel* shell_toplevel = (ShellToplevel*)initToplevel(ccc);
194 DomainEnv* builtinDomainEnv = shell_toplevel->domainEnv();
195 AvmAssert(builtinDomainEnv->domain() == builtinDomain);
196 AvmAssert(builtinDomainEnv->base() == NULL);
198 shell_domainEnv = DomainEnv::newDomainEnv(this, shell_domain, builtinDomainEnv);
200 // Initialize the shell builtins in the new Toplevel
201 // use the same bugCompatibility that the base builtins use
202 const BugCompatibility* shell_bugCompatibility = shell_toplevel->abcEnv()->codeContext()->bugCompatibility();
203 ShellCodeContext* shell_codeContext = new(GetGC()) ShellCodeContext(shell_domainEnv, shell_bugCompatibility);
205 //handleActionPool(shellPool, shell_toplevel, shell_codeContext);
206 shell_toplevel->shellClasses = prepareBuiltinActionPool<shell_toplevelClassManifest>(shellPool, shell_toplevel, shell_codeContext);
208 return shell_toplevel;
211 /*virtual*/ Toplevel* ShellCore::createToplevel(AbcEnv* abcEnv)
213 return ShellToplevel::create(GetGC(), abcEnv);
216 #ifdef VMCFG_EVAL
218 // FIXME, this is currently hokey for several reasons:
220 // - Does not try to determine whether input is Latin1, UTF8, or indeed, already UTF16,
221 // but assumes UTF8, which can be dangerous. Falls back to latin1 if the utf8 conversion
222 // fails, this seems ill-defined in the string layer though so it's just one more hack.
224 // - Does not create an UTF16 string. The string layer is actually broken on this count,
225 // because requesting an empty UTF16 string returns a constant that is a Latin1 string,
226 // and appending to it won't force the representation to UTF16 unless the data require
227 // that to happen. See <URL:https://bugzilla.mozilla.org/show_bug.cgi?id=473995>.
229 // - May incur copying because the terminating NUL is not accounted for in the original
230 // creation
232 // Much useful guidance is found at http://www.unicode.org/faq/utf_bom.html.
234 String* ShellCore::decodeBytesAsUTF16String(uint8_t* bytes, uint32_t nbytes, bool terminate)
236 String* s = newStringUTF8((const char*)bytes, nbytes);
237 if (s == NULL)
238 s = newStringLatin1((const char*)bytes, nbytes);
239 if (terminate)
240 s = s->appendLatin1("\0", 1);
241 return s;
244 String* ShellCore::readFileForEval(String* referencingFile, String* filename)
246 // FIXME, filename sanitazion is more complicated than this
247 if (referencingFile != NULL && filename->charAt(0) != '/' && filename->charAt(0) != '\\') {
248 // find the last slash if any, truncate the string there, append the
249 // new filename
250 int32_t x = referencingFile->lastIndexOf(newStringLatin1("/"));
251 if (x != -1)
252 filename = referencingFile->substring(0,x+1)->append(filename);
254 filename = filename->appendLatin1("\0", 1);
256 // FIXME, not obvious that UTF8 is correct for all operating systems (far from it!)
257 StUTF8String fn(filename);
258 FileInputStream f(fn.c_str());
259 if (!f.valid() || (uint64_t) f.length() >= UINT32_T_MAX)
260 return NULL;
262 uint32_t nbytes = (uint32_t) f.available();
263 uint8_t* bytes = new uint8_t[nbytes];
264 f.read(bytes, nbytes);
265 String* str = decodeBytesAsUTF16String(bytes, nbytes, true);
266 delete [] bytes;
267 return str;
270 // input is always NUL-terminated
271 void ShellCore::evaluateString(String* input, bool record_time)
273 setStackLimit();
275 TRY(this, kCatchAction_ReportAsError)
277 // Always Latin-1 here
278 input = input->appendLatin1("\0", 1);
279 double then = 0, now = 0;
280 if (record_time)
281 then = VMPI_getDate();
282 ApiVersion apiVersion = this->getApiVersionFromCallStack();
283 Atom result = handleActionSource(input, /*filename*/NULL, shell_toplevel, /*ninit*/NULL, user_codeContext, apiVersion);
284 if (record_time)
285 now = VMPI_getDate();
286 if (result != undefinedAtom)
287 console << string(result) << "\n";
288 if (record_time)
289 console << "Elapsed time: " << (now - then)/1000 << "s\n";
291 CATCH(Exception *exception)
293 #ifdef DEBUGGER
294 if (!(exception->flags & Exception::SEEN_BY_DEBUGGER))
296 console << string(exception->atom) << "\n";
298 if (exception->getStackTrace()) {
299 console << exception->getStackTrace()->format(this) << '\n';
301 #else
302 console << string(exception->atom) << "\n";
303 #endif
305 END_CATCH
306 END_TRY
309 #endif // VMCFG_EVAL
311 #ifdef AVMSHELL_PROJECTOR_SUPPORT
313 // Run a known projector file
314 int ShellCore::executeProjector(char *executablePath)
316 AvmAssert(isValidProjectorFile(executablePath));
318 uint8_t header[8];
320 FileInputStream file(executablePath);
322 file.seek(file.length() - 8);
323 file.read(header, 8);
325 int abcLength = (header[4] |
326 header[5]<<8 |
327 header[6]<<16 |
328 header[7]<<24);
330 ScriptBuffer code = newScriptBuffer(abcLength);
331 file.seek(file.length() - 8 - abcLength);
332 file.read(code.getBuffer(), abcLength);
334 ShellCoreSettings settings;
335 return handleArbitraryExecutableContent(settings, code, executablePath);
338 /* static */
339 bool ShellCore::isValidProjectorFile(const char* filename)
341 FileInputStream file(filename);
342 uint8_t header[8];
344 if (!file.valid())
345 return false;
347 file.seek(file.length() - 8);
348 file.read(header, 8);
350 // Check the magic number
351 if (header[0] != 0x56 || header[1] != 0x34 || header[2] != 0x12 || header[3] != 0xFA)
352 return false;
354 return true;
357 #endif // AVMSHELL_PROJECTOR_SUPPORT
359 #ifdef VMCFG_SELFTEST
360 void ShellCore::executeSelftest(ShellCoreSettings& settings)
362 setStackLimit();
363 ::selftests(this, settings.st_component, settings.st_category, settings.st_name);
365 #endif
367 bool ShellCore::setup(const ShellCoreSettings& settings)
369 #ifdef VMCFG_AOT
370 if(nAOTInfos == 0) {
371 console << "To run an AOT enabled avmshell you must link in some AOT compiled ABC blocks.\n";
372 return false;
374 #endif
376 // set the default api version
377 this->defaultAPIVersion = settings.apiVersion;
379 this->defaultBugCompatibilityVersion = settings.swfVersion;
380 this->bugzilla444630 = (this->defaultBugCompatibilityVersion >= BugCompatibility::kSWF10);
382 // This is obscure but well-defined: the clearing of this flag is allowed
383 // at any time, see comment for checkFixedMemory in GCHeap.h.
384 if (!settings.fixedcheck)
385 MMgc::GCHeap::GetGCHeap()->Config().clearCheckFixedMemory();
387 config.interrupts = settings.interrupts;
388 #ifdef VMCFG_VERIFYALL
389 config.verifyall = settings.verifyall;
390 config.verifyonly = settings.verifyonly;
391 #endif
392 config.jitordie = settings.jitordie;
393 #ifdef VMCFG_NANOJIT
394 config.njconfig = settings.njconfig;
395 #endif
397 #ifdef AVMPLUS_VERBOSE
398 if (settings.do_verbose & VB_builtins)
399 config.verbose_vb = settings.do_verbose; // ~builtins then skip verbose settings during setup()
400 #endif
401 config.runmode = settings.runmode;
403 #if VMCFG_METHOD_NAMES
404 // verbose requires methodnames (in avmshell, anyway), before calling initBuiltinPool.
405 if (settings.do_verbose)
406 config.methodNames = true;
407 #ifdef DEBUGGER
408 // debugger in avmshell always enables methodnames.
409 if (allowDebugger)
410 config.methodNames = true;
411 #endif
412 #endif // VMCFG_METHOD_NAMES
414 #ifdef DEBUGGER
415 langID = settings.langID;
416 #endif
418 TRY(this, kCatchAction_ReportAsError)
420 setStackLimit();
422 allowDebugger = !settings.nodebugger;
424 setCacheSizes(settings.cacheSizes);
426 SystemClass::user_argc = settings.numargs;
427 SystemClass::user_argv = settings.arguments;
429 #ifdef DEBUGGER
430 initBuiltinPool((avmplus::Debugger::TraceLevel)settings.astrace_console);
431 #else
432 initBuiltinPool();
433 #endif
434 initShellPool();
436 // init toplevel internally
437 shell_toplevel = createShellToplevel();
439 // Create a new Domain/DomainEnv for the user code
440 Domain* user_domain = Domain::newDomain(this, shell_domain);
441 DomainEnv* user_domainEnv = DomainEnv::newDomainEnv(this, user_domain, shell_domainEnv);
442 const BugCompatibility* user_bugCompatibility = createBugCompatibility(defaultBugCompatibilityVersion);
443 this->user_codeContext = new (GetGC()) ShellCodeContext(user_domainEnv, user_bugCompatibility);
445 #ifdef AVMPLUS_VERBOSE
446 config.verbose_vb = settings.do_verbose; // builtins is done, so propagate verbose
447 #endif
448 return true;
450 CATCH(Exception *exception)
452 #ifdef DEBUGGER
453 if (!(exception->flags & Exception::SEEN_BY_DEBUGGER))
454 console << string(exception->atom) << "\n";
456 if (exception->getStackTrace())
457 console << exception->getStackTrace()->format(this) << '\n';
458 #else
459 // [ed] always show error, even in release mode,
460 // see bug #121382
461 console << string(exception->atom) << "\n";
462 #endif /* DEBUGGER */
463 return false;
465 END_CATCH
466 END_TRY
469 #ifdef AVMPLUS_VERBOSE
470 const char* ShellCore::identifyDomain(Domain* domain) {
471 return domain == builtinDomain ? "builtin" : (domain == shell_domain ? "shell" : NULL);
473 #endif
475 int ShellCore::evaluateFile(ShellCoreSettings& settings, const char* filename)
477 #ifdef VMCFG_AOT
478 ScriptBuffer dummyScriptBuffer;
479 return handleArbitraryExecutableContent(dummyScriptBuffer, NULL);
480 #endif
482 if (config.interrupts)
483 Platform::GetInstance()->setTimer(kScriptTimeout, interruptTimerCallback, this);
485 #ifdef AVMPLUS_VERBOSE
486 if (config.verbose_vb)
487 console << "run " << filename << "\n";
488 #endif
490 FileInputStream f(filename);
491 bool isValid = f.valid() && ((uint64_t)f.length() < UINT32_T_MAX); //currently we cannot read files > 4GB
492 if (!isValid) {
493 console << "cannot open file: " << filename << "\n";
494 return(1);
497 // parse new bytecode
498 ScriptBuffer code = newScriptBuffer((size_t)f.available());
499 f.read(code.getBuffer(), (size_t)f.available());
501 #ifdef DEBUGGER
502 if (settings.enter_debugger_on_launch)
504 // Activate the debug CLI and stop at
505 // start of program
506 debugCLI()->activate();
507 debugCLI()->stepInto();
509 #else
510 // placate MSVC - settings is unreferenced if this is not here
511 (void)settings.enter_debugger_on_launch;
512 #endif
514 return handleArbitraryExecutableContent(settings, code, filename);
517 int ShellCore::handleArbitraryExecutableContent(ShellCoreSettings& settings, ScriptBuffer& code, const char * filename)
519 setStackLimit();
521 TRY(this, kCatchAction_ReportAsError)
523 if (settings.do_testSWFHasAS3 && !isSwf(code))
524 return 1;
525 #ifdef VMCFG_AOT
526 if (filename == NULL) {
527 handleAOT(shell_toplevel, user_codeContext);
528 } else
529 #endif
530 if (AbcParser::canParse(code) == 0) {
531 #ifdef VMCFG_VERIFYALL
532 if (config.verbose_vb & VB_verify)
533 console << "ABC " << filename << "\n";
534 #endif
536 ApiVersion apiVersion = this->getApiVersionFromCallStack();
537 handleActionBlock(code, 0, shell_toplevel, NULL, user_codeContext, apiVersion);
539 else if (isSwf(code)) {
540 #ifdef VMCFG_VERIFYALL
541 if (config.verbose_vb & VB_verify)
542 console << "SWF " << filename << "\n";
543 #endif
544 bool result = handleSwf(filename, code, shell_toplevel, user_codeContext, settings.do_testSWFHasAS3);
545 if (settings.do_testSWFHasAS3)
546 return int(!result); // "no abc" is "false" but translates to "1" for the exit code, and "true" becomes "0"
548 else {
549 #ifdef VMCFG_EVAL
550 AvmCore* core = shell_toplevel->core();
551 if (!core->config.verifyonly) {
552 // FIXME: I'm assuming code is UTF8 - OK for now, but easy to go wrong; it could be 8-bit ASCII
553 String* code_string = decodeBytesAsUTF16String(code.getBuffer(), (uint32_t)code.getSize(), true);
554 String* filename_string = decodeBytesAsUTF16String((uint8_t*)filename, (uint32_t)VMPI_strlen(filename));
555 ScriptBuffer empty; // With luck: allow the
556 code = empty; // buffer to be garbage collected
557 ApiVersion apiVersion = this->getApiVersionFromCallStack();
558 handleActionSource(code_string, filename_string, shell_toplevel, NULL, user_codeContext, apiVersion);
560 #else
561 console << "unknown input format in file: " << filename << "\n";
562 return(1);
563 #endif // VMCFG_EVAL
566 CATCH(Exception *exception)
568 TRY(this, kCatchAction_ReportAsError)
570 #ifdef DEBUGGER
571 if (!(exception->flags & Exception::SEEN_BY_DEBUGGER))
573 console << string(exception->atom) << "\n";
575 if (exception->getStackTrace()) {
576 console << exception->getStackTrace()->format(this) << '\n';
578 #else
579 // [ed] always show error, even in release mode,
580 // see bug #121382
581 console << string(exception->atom) << "\n";
582 #endif /* DEBUGGER */
584 CATCH(Exception * e2)
586 (void)e2;
587 console << "Sorry, an exception occurred but could not be reported\n";
589 END_CATCH
590 END_TRY
592 return 1;
594 END_CATCH
595 END_TRY
597 return 0;