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
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.
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 ***** */
42 #include "shell_toplevel.cpp"
46 const int kScriptTimeout
= 15;
47 const int kScriptGracePeriod
= 5;
49 ShellCoreSettings::ShellCoreSettings()
55 , enter_debugger_on_launch(false)
56 , interrupts(AvmCore::interrupts_default
)
57 , verifyall(AvmCore::verifyall_default
)
58 , verifyonly(AvmCore::verifyonly_default
)
63 , markstackAllowance(0)
67 , jitordie(AvmCore::jitordie_default
)
68 , do_testSWFHasAS3(false)
69 , runmode(AvmCore::runmode_default
)
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
)
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
)
105 // Already handling a stack overflow, so do not
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
);
128 void ShellCore::interruptTimerCallback(void* data
)
130 ((AvmCore
*)data
)->raiseInterrupt(ScriptTimeout
);
133 void ShellCore::interrupt(Toplevel
*toplevel
, InterruptReason
)
136 // This script has already had its chance; it violated
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.
150 Platform::GetInstance()->setTimer(kScriptGracePeriod
, interruptTimerCallback
, this);
152 toplevel
->throwError(kScriptTimeoutError
);
155 void ShellCore::initShellPool()
157 shell_domain
= Domain::newDomain(this, builtinDomain
);
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
);
168 shellPool
= AVM_INIT_BUILTIN_ABC_IN_DOMAIN(shell_toplevel
, this, shell_domain
);
172 ShellToplevel
* ShellCore::createShellToplevel()
174 class CodeContextCreator
: public AvmCore::ICodeContextCreator
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
);
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
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
);
238 s
= newStringLatin1((const char*)bytes
, nbytes
);
240 s
= s
->appendLatin1("\0", 1);
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
250 int32_t x
= referencingFile
->lastIndexOf(newStringLatin1("/"));
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
)
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);
270 // input is always NUL-terminated
271 void ShellCore::evaluateString(String
* input
, bool record_time
)
275 TRY(this, kCatchAction_ReportAsError
)
277 // Always Latin-1 here
278 input
= input
->appendLatin1("\0", 1);
279 double then
= 0, now
= 0;
281 then
= VMPI_getDate();
282 ApiVersion apiVersion
= this->getApiVersionFromCallStack();
283 Atom result
= handleActionSource(input
, /*filename*/NULL
, shell_toplevel
, /*ninit*/NULL
, user_codeContext
, apiVersion
);
285 now
= VMPI_getDate();
286 if (result
!= undefinedAtom
)
287 console
<< string(result
) << "\n";
289 console
<< "Elapsed time: " << (now
- then
)/1000 << "s\n";
291 CATCH(Exception
*exception
)
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';
302 console
<< string(exception
->atom
) << "\n";
311 #ifdef AVMSHELL_PROJECTOR_SUPPORT
313 // Run a known projector file
314 int ShellCore::executeProjector(char *executablePath
)
316 AvmAssert(isValidProjectorFile(executablePath
));
320 FileInputStream
file(executablePath
);
322 file
.seek(file
.length() - 8);
323 file
.read(header
, 8);
325 int abcLength
= (header
[4] |
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
);
339 bool ShellCore::isValidProjectorFile(const char* filename
)
341 FileInputStream
file(filename
);
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)
357 #endif // AVMSHELL_PROJECTOR_SUPPORT
359 #ifdef VMCFG_SELFTEST
360 void ShellCore::executeSelftest(ShellCoreSettings
& settings
)
363 ::selftests(this, settings
.st_component
, settings
.st_category
, settings
.st_name
);
367 bool ShellCore::setup(const ShellCoreSettings
& settings
)
371 console
<< "To run an AOT enabled avmshell you must link in some AOT compiled ABC blocks.\n";
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
;
392 config
.jitordie
= settings
.jitordie
;
394 config
.njconfig
= settings
.njconfig
;
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()
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;
408 // debugger in avmshell always enables methodnames.
410 config
.methodNames
= true;
412 #endif // VMCFG_METHOD_NAMES
415 langID
= settings
.langID
;
418 TRY(this, kCatchAction_ReportAsError
)
422 allowDebugger
= !settings
.nodebugger
;
424 setCacheSizes(settings
.cacheSizes
);
426 SystemClass::user_argc
= settings
.numargs
;
427 SystemClass::user_argv
= settings
.arguments
;
430 initBuiltinPool((avmplus::Debugger::TraceLevel
)settings
.astrace_console
);
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
450 CATCH(Exception
*exception
)
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';
459 // [ed] always show error, even in release mode,
461 console
<< string(exception
->atom
) << "\n";
462 #endif /* DEBUGGER */
469 #ifdef AVMPLUS_VERBOSE
470 const char* ShellCore::identifyDomain(Domain
* domain
) {
471 return domain
== builtinDomain
? "builtin" : (domain
== shell_domain
? "shell" : NULL
);
475 int ShellCore::evaluateFile(ShellCoreSettings
& settings
, const char* filename
)
478 ScriptBuffer dummyScriptBuffer
;
479 return handleArbitraryExecutableContent(dummyScriptBuffer
, NULL
);
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";
490 FileInputStream
f(filename
);
491 bool isValid
= f
.valid() && ((uint64_t)f
.length() < UINT32_T_MAX
); //currently we cannot read files > 4GB
493 console
<< "cannot open file: " << filename
<< "\n";
497 // parse new bytecode
498 ScriptBuffer code
= newScriptBuffer((size_t)f
.available());
499 f
.read(code
.getBuffer(), (size_t)f
.available());
502 if (settings
.enter_debugger_on_launch
)
504 // Activate the debug CLI and stop at
506 debugCLI()->activate();
507 debugCLI()->stepInto();
510 // placate MSVC - settings is unreferenced if this is not here
511 (void)settings
.enter_debugger_on_launch
;
514 return handleArbitraryExecutableContent(settings
, code
, filename
);
517 int ShellCore::handleArbitraryExecutableContent(ShellCoreSettings
& settings
, ScriptBuffer
& code
, const char * filename
)
521 TRY(this, kCatchAction_ReportAsError
)
523 if (settings
.do_testSWFHasAS3
&& !isSwf(code
))
526 if (filename
== NULL
) {
527 handleAOT(shell_toplevel
, user_codeContext
);
530 if (AbcParser::canParse(code
) == 0) {
531 #ifdef VMCFG_VERIFYALL
532 if (config
.verbose_vb
& VB_verify
)
533 console
<< "ABC " << filename
<< "\n";
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";
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"
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
);
561 console
<< "unknown input format in file: " << filename
<< "\n";
566 CATCH(Exception
*exception
)
568 TRY(this, kCatchAction_ReportAsError
)
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';
579 // [ed] always show error, even in release mode,
581 console
<< string(exception
->atom
) << "\n";
582 #endif /* DEBUGGER */
584 CATCH(Exception
* e2
)
587 console
<< "Sorry, an exception occurred but could not be reported\n";