merge from argo
[tamarin-stm.git] / shell / ShellCore.cpp
blobe6c216b7099a1ed341d06771950d8a07f81647b1
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 #ifdef VMCFG_TEST_API_VERSIONING
45 #include "api-versions.h"
46 #else
47 #include "noapi-versions.h"
48 #endif
50 namespace avmshell
52 const int kScriptTimeout = 15;
53 const int kScriptGracePeriod = 5;
55 ShellCoreSettings::ShellCoreSettings()
56 : arguments(NULL)
57 , numargs(-1)
58 , nodebugger(false)
59 , astrace_console(0)
60 , do_verbose(0)
61 , enter_debugger_on_launch(false)
62 , interrupts(AvmCore::interrupts_default)
63 , verifyall(AvmCore::verifyall_default)
64 , verifyonly(AvmCore::verifyonly_default)
65 , greedy(false)
66 , nogc(false)
67 , incremental(true)
68 , langID(-1)
69 , jitordie(AvmCore::jitordie_default)
70 , runmode(AvmCore::runmode_default)
71 #ifdef FEATURE_NANOJIT
72 , njconfig()
73 #endif
74 , st_component(NULL)
75 , st_category(NULL)
76 , st_name(NULL)
77 , api(0xffffffff)
81 ShellToplevel::ShellToplevel(AbcEnv* abcEnv) : Toplevel(abcEnv)
83 shellClasses = (ClassClosure**) core()->GetGC()->Calloc(avmplus::NativeID::shell_toplevel_abc_class_count,
84 sizeof(ClassClosure*),
85 MMgc::GC::kZero | MMgc::GC::kContainsPointers);
88 ShellCore::ShellCore(MMgc::GC* gc)
89 : AvmCore(gc)
91 systemClass = NULL;
93 gracePeriod = false;
94 inStackOverflow = false;
96 allowDebugger = -1; // aka "not yet set"
98 consoleOutputStream = new (gc) ConsoleOutputStream();
100 setConsoleStream(consoleOutputStream);
102 setAPIInfo(_min_version_num,
103 _max_version_num-_min_version_num+1,
104 _uris_count,
105 (const char**) _uris,
106 (int32_t*) _api_compat);
109 void ShellCore::stackOverflow(Toplevel* toplevel)
111 if (inStackOverflow)
113 // Already handling a stack overflow, so do not
114 // re-enter handler.
115 return;
118 // Temporarily disable stack overflow checks
119 // so that we can construct an exception object.
120 // There should be plenty of margin before the
121 // actual stack bottom to do this.
122 inStackOverflow = true;
124 Stringp errorMessage = getErrorMessage(kStackOverflowError);
125 Atom args[2] = { nullObjectAtom, errorMessage->atom() };
126 Atom errorAtom = toplevel->errorClass()->construct(1, args);
127 Exception *exception = new (GetGC()) Exception(this, errorAtom);
129 // Restore stack overflow checks
130 inStackOverflow = false;
132 // Throw the stack overflow exception
133 throwException(exception);
136 /* static */
137 void ShellCore::interruptTimerCallback(void* data)
139 ((AvmCore*)data)->raiseInterrupt(ScriptTimeout);
142 void ShellCore::interrupt(Toplevel *toplevel, InterruptReason)
144 if (gracePeriod) {
145 // This script has already had its chance; it violated
146 // the grace period.
147 // Throw an exception it cannot catch.
148 Stringp errorMessage = getErrorMessage(kScriptTerminatedError);
149 Atom args[2] = { nullObjectAtom, errorMessage->atom() };
150 Atom errorAtom = toplevel->errorClass()->construct(1, args);
151 Exception *exception = new (GetGC()) Exception(this, errorAtom);
152 exception->flags |= Exception::EXIT_EXCEPTION;
153 throwException(exception);
156 // Give the script an additional grace period to
157 // clean up, and throw an exception.
158 gracePeriod = true;
160 Platform::GetInstance()->setTimer(kScriptGracePeriod, interruptTimerCallback, this);
162 toplevel->throwError(kScriptTimeoutError);
165 void ShellCore::initShellPool()
167 #ifdef VMCFG_AOT
168 NativeInitializer shellNInit(this,
169 &shell_toplevel_aotInfo,
170 avmplus::NativeID::shell_toplevel_abc_method_count,
171 avmplus::NativeID::shell_toplevel_abc_class_count);
172 shellNInit.fillInClasses(avmplus::NativeID::shell_toplevel_classEntries);
173 shellNInit.fillInMethods(avmplus::NativeID::shell_toplevel_methodEntries);
174 shellPool = shellNInit.parseBuiltinABC(builtinDomain);
175 #else
176 shellPool = AVM_INIT_BUILTIN_ABC(shell_toplevel, this);
177 #endif
180 Toplevel* ShellCore::initShellBuiltins()
182 // Initialize a new Toplevel. This will also create a new
183 // DomainEnv based on the builtinDomain.
184 Toplevel* toplevel = initTopLevel();
186 // Initialize the shell builtins in the new Toplevel
187 handleActionPool(shellPool,
188 toplevel->domainEnv(),
189 toplevel,
190 NULL);
192 return toplevel;
195 Toplevel* ShellCore::createToplevel(AbcEnv* abcEnv)
197 return new (GetGC()) ShellToplevel(abcEnv);
200 #ifdef VMCFG_EVAL
202 // FIXME, this is currently hokey for several reasons:
204 // - Does not try to determine whether input is Latin1, UTF8, or indeed, already UTF16,
205 // but assumes UTF8, which can be dangerous. Falls back to latin1 if the utf8 conversion
206 // fails, this seems ill-defined in the string layer though so it's just one more hack.
208 // - Does not create an UTF16 string. The string layer is actually broken on this count,
209 // because requesting an empty UTF16 string returns a constant that is a Latin1 string,
210 // and appending to it won't force the representation to UTF16 unless the data require
211 // that to happen. See <URL:https://bugzilla.mozilla.org/show_bug.cgi?id=473995>.
213 // - May incur copying because the terminating NUL is not accounted for in the original
214 // creation
216 String* ShellCore::decodeBytesAsUTF16String(uint8_t* bytes, uint32_t nbytes, bool terminate)
218 String* s = newStringUTF8((const char*)bytes, nbytes);
219 if (s == NULL)
220 s = newStringLatin1((const char*)bytes, nbytes);
221 if (terminate)
222 s = s->appendLatin1("\0", 1);
223 return s;
226 String* ShellCore::readFileForEval(String* referencingFile, String* filename)
228 // FIXME, filename sanitazion is more complicated than this
229 if (referencingFile != NULL && filename->charAt(0) != '/' && filename->charAt(0) != '\\') {
230 // find the last slash if any, truncate the string there, append the
231 // new filename
232 int32_t x = referencingFile->lastIndexOf(newStringLatin1("/"));
233 if (x != -1)
234 filename = referencingFile->substring(0,x+1)->append(filename);
236 filename = filename->appendLatin1("\0", 1);
238 // FIXME, not obvious that UTF8 is correct for all operating systems (far from it!)
239 StUTF8String fn(filename);
240 FileInputStream f(fn.c_str());
241 if (!f.valid() || (uint64_t) f.length() >= UINT32_T_MAX)
242 return NULL;
244 uint32_t nbytes = (uint32_t) f.available();
245 uint8_t* bytes = new uint8_t[nbytes];
246 f.read(bytes, nbytes);
247 String* str = decodeBytesAsUTF16String(bytes, nbytes, true);
248 delete [] bytes;
249 return str;
252 // input is always NUL-terminated
253 void ShellCore::evaluateString(String* input, bool record_time)
255 setStackLimit();
257 ShellCodeContext* codeContext = new (GetGC()) ShellCodeContext();
258 codeContext->m_domainEnv = shell_domainEnv;
260 TRY(this, kCatchAction_ReportAsError)
262 // Always Latin-1 here
263 input = input->appendLatin1("\0", 1);
264 double then = 0, now = 0;
265 if (record_time)
266 then = VMPI_getDate();
267 uint32_t api = this->getAPI(NULL);
268 Atom result = handleActionSource(input, NULL, shell_domainEnv, shell_toplevel, NULL, codeContext, api);
269 if (record_time)
270 now = VMPI_getDate();
271 if (result != undefinedAtom)
272 console << string(result) << "\n";
273 if (record_time)
274 console << "Elapsed time: " << (now - then)/1000 << "s\n";
276 CATCH(Exception *exception)
278 #ifdef DEBUGGER
279 if (!(exception->flags & Exception::SEEN_BY_DEBUGGER))
281 console << string(exception->atom) << "\n";
283 if (exception->getStackTrace()) {
284 console << exception->getStackTrace()->format(this) << '\n';
286 #else
287 console << string(exception->atom) << "\n";
288 #endif
290 END_CATCH
291 END_TRY
294 #endif // VMCFG_EVAL
296 #ifdef AVMSHELL_PROJECTOR_SUPPORT
298 // Run a known projector file
299 int ShellCore::executeProjector(char *executablePath)
301 AvmAssert(isValidProjectorFile(executablePath));
303 uint8_t header[8];
305 FileInputStream file(executablePath);
307 file.seek(file.length() - 8);
308 file.read(header, 8);
310 int abcLength = (header[4] |
311 header[5]<<8 |
312 header[6]<<16 |
313 header[7]<<24);
315 ScriptBuffer code = newScriptBuffer(abcLength);
316 file.seek(file.length() - 8 - abcLength);
317 file.read(code.getBuffer(), abcLength);
319 return handleArbitraryExecutableContent(code, executablePath);
322 /* static */
323 bool ShellCore::isValidProjectorFile(const char* filename)
325 FileInputStream file(filename);
326 uint8_t header[8];
328 if (!file.valid())
329 return false;
331 file.seek(file.length() - 8);
332 file.read(header, 8);
334 // Check the magic number
335 if (header[0] != 0x56 || header[1] != 0x34 || header[2] != 0x12 || header[3] != 0xFA)
336 return false;
338 return true;
341 #endif // AVMSHELL_PROJECTOR_SUPPORT
343 #ifdef VMCFG_SELFTEST
344 void ShellCore::executeSelftest(ShellCoreSettings& settings)
346 setStackLimit();
347 ::selftests(this, settings.st_component, settings.st_category, settings.st_name);
349 #endif
351 bool ShellCore::setup(ShellCoreSettings& settings)
353 // set the default api version
354 if (settings.api <= _max_version_num) {
355 this->defaultAPIVersion = settings.api;
357 else {
358 // if there is at least on versioned uri, then there must be a version matrix
359 if (_uris_count > 0) {
360 // last api of any row is largestApiUtils::getLargestVersion(this);
361 this->defaultAPIVersion = ((uint32_t*)_versions)[_versions_count[0]-1];
363 else {
364 this->defaultAPIVersion = 0;
367 //console << "defaultAPIVersion=" << defaultAPIVersion;
368 this->setActiveAPI(ApiUtils::toAPI(this, this->defaultAPIVersion));
370 config.interrupts = settings.interrupts;
371 #ifdef VMCFG_VERIFYALL
372 config.verifyall = settings.verifyall;
373 config.verifyonly = settings.verifyonly;
374 #endif
375 config.jitordie = settings.jitordie;
376 #if defined FEATURE_NANOJIT
377 config.njconfig = settings.njconfig;
378 #endif
380 #ifdef AVMPLUS_VERBOSE
381 if (settings.do_verbose & VB_builtins)
382 config.verbose_vb = settings.do_verbose; // ~builtins then skip verbose settings during setup()
383 #endif
384 config.runmode = settings.runmode;
386 #ifdef VMCFG_METHOD_NAMES
387 // verbose requires methodnames (in avmshell, anyway), before calling initBuiltinPool.
388 if (settings.do_verbose)
389 config.methodNames = true;
390 #ifdef DEBUGGER
391 // debugger in avmshell always enables methodnames.
392 if (allowDebugger)
393 config.methodNames = true;
394 #endif
395 #endif // VMCFG_METHOD_NAMES
397 #ifdef DEBUGGER
398 langID = settings.langID;
399 #endif
401 TRY(this, kCatchAction_ReportAsError)
403 setStackLimit();
405 allowDebugger = !settings.nodebugger;
407 setCacheSizes(settings.cacheSizes);
409 SystemClass::user_argc = settings.numargs;
410 SystemClass::user_argv = settings.arguments;
412 #ifdef DEBUGGER
413 initBuiltinPool((avmplus::Debugger::TraceLevel)settings.astrace_console);
414 #else
415 initBuiltinPool();
416 #endif
417 initShellPool();
419 // init toplevel internally
420 shell_toplevel = initShellBuiltins();
422 // Create a new Domain for the user code
423 shell_domain = new (GetGC()) Domain(this, builtinDomain);
425 // Return a new DomainEnv for the user code
426 shell_domainEnv = new (GetGC()) DomainEnv(this, shell_domain, shell_toplevel->domainEnv());
428 #ifdef AVMPLUS_VERBOSE
429 config.verbose_vb = settings.do_verbose; // builtins is done, so propagate verbose
430 #endif
431 return true;
433 CATCH(Exception *exception)
435 #ifdef DEBUGGER
436 if (!(exception->flags & Exception::SEEN_BY_DEBUGGER))
437 console << string(exception->atom) << "\n";
439 if (exception->getStackTrace())
440 console << exception->getStackTrace()->format(this) << '\n';
441 #else
442 // [ed] always show error, even in release mode,
443 // see bug #121382
444 console << string(exception->atom) << "\n";
445 #endif /* DEBUGGER */
446 return false;
448 END_CATCH
449 END_TRY
452 int ShellCore::evaluateFile(ShellCoreSettings& settings, const char* filename)
454 #ifdef VMCFG_AOT
455 ScriptBuffer dummyScriptBuffer;
456 return handleArbitraryExecutableContent(dummyScriptBuffer, NULL);
457 #endif
459 if (config.interrupts)
460 Platform::GetInstance()->setTimer(kScriptTimeout, interruptTimerCallback, this);
462 #ifdef AVMPLUS_VERBOSE
463 if (config.verbose_vb)
464 console << "run " << filename << "\n";
465 #endif
467 FileInputStream f(filename);
468 bool isValid = f.valid() && ((uint64_t)f.length() < UINT32_T_MAX); //currently we cannot read files > 4GB
469 if (!isValid) {
470 console << "cannot open file: " << filename << "\n";
471 return(1);
474 // parse new bytecode
475 ScriptBuffer code = newScriptBuffer((size_t)f.available());
476 f.read(code.getBuffer(), (size_t)f.available());
478 #ifdef DEBUGGER
479 if (settings.enter_debugger_on_launch)
481 // Activate the debug CLI and stop at
482 // start of program
483 debugCLI()->activate();
484 debugCLI()->stepInto();
486 #else
487 // placate MSVC - settings is unreferenced if this is not here
488 (void)settings.enter_debugger_on_launch;
489 #endif
491 return handleArbitraryExecutableContent(code, filename);
494 int ShellCore::handleArbitraryExecutableContent(ScriptBuffer& code, const char * filename)
496 setStackLimit();
498 TRY(this, kCatchAction_ReportAsError)
500 ShellCodeContext* codeContext = new (GetGC()) ShellCodeContext();
501 codeContext->m_domainEnv = shell_domainEnv;
503 #ifdef VMCFG_AOT
504 if (filename == NULL) {
505 handleAOT(this, shell_domain, shell_domainEnv, shell_toplevel, codeContext);
506 } else
507 #endif
508 if (AbcParser::canParse(code) == 0) {
509 #ifdef VMCFG_VERIFYALL
510 if (config.verbose_vb & VB_verify)
511 console << "ABC " << filename << "\n";
512 #endif
513 uint32_t api = this->getAPI(NULL);
514 handleActionBlock(code, 0, shell_domainEnv, shell_toplevel, NULL, codeContext, api);
516 else if (isSwf(code)) {
517 #ifdef VMCFG_VERIFYALL
518 if (config.verbose_vb & VB_verify)
519 console << "SWF " << filename << "\n";
520 #endif
521 handleSwf(filename, code, shell_domainEnv, shell_toplevel, codeContext);
523 else {
524 #ifdef VMCFG_EVAL
525 AvmCore* core = shell_toplevel->core();
526 if (!core->config.verifyonly) {
527 // FIXME: I'm assuming code is UTF8 - OK for now, but easy to go wrong; it could be 8-bit ASCII
528 String* code_string = decodeBytesAsUTF16String(code.getBuffer(), (uint32_t)code.getSize(), true);
529 String* filename_string = decodeBytesAsUTF16String((uint8_t*)filename, (uint32_t)VMPI_strlen(filename));
530 ScriptBuffer empty; // With luck: allow the
531 code = empty; // buffer to be garbage collected
532 uint32_t api = this->getAPI(NULL);
533 handleActionSource(code_string, filename_string, shell_domainEnv, shell_toplevel, NULL, codeContext, api);
535 #else
536 console << "unknown input format in file: " << filename << "\n";
537 return(1);
538 #endif // VMCFG_EVAL
541 CATCH(Exception *exception)
543 #ifdef DEBUGGER
544 if (!(exception->flags & Exception::SEEN_BY_DEBUGGER))
546 console << string(exception->atom) << "\n";
548 if (exception->getStackTrace()) {
549 console << exception->getStackTrace()->format(this) << '\n';
551 #else
552 // [ed] always show error, even in release mode,
553 // see bug #121382
554 console << string(exception->atom) << "\n";
555 #endif /* DEBUGGER */
557 return 1;
559 END_CATCH
560 END_TRY
562 return 0;