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"
44 #ifdef VMCFG_TEST_API_VERSIONING
45 #include "api-versions.h"
47 #include "noapi-versions.h"
52 const int kScriptTimeout
= 15;
53 const int kScriptGracePeriod
= 5;
55 ShellCoreSettings::ShellCoreSettings()
61 , enter_debugger_on_launch(false)
62 , interrupts(AvmCore::interrupts_default
)
63 , verifyall(AvmCore::verifyall_default
)
64 , verifyonly(AvmCore::verifyonly_default
)
69 , jitordie(AvmCore::jitordie_default
)
70 , runmode(AvmCore::runmode_default
)
71 #ifdef FEATURE_NANOJIT
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
)
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,
105 (const char**) _uris
,
106 (int32_t*) _api_compat
);
109 void ShellCore::stackOverflow(Toplevel
* toplevel
)
113 // Already handling a stack overflow, so do not
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
);
137 void ShellCore::interruptTimerCallback(void* data
)
139 ((AvmCore
*)data
)->raiseInterrupt(ScriptTimeout
);
142 void ShellCore::interrupt(Toplevel
*toplevel
, InterruptReason
)
145 // This script has already had its chance; it violated
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.
160 Platform::GetInstance()->setTimer(kScriptGracePeriod
, interruptTimerCallback
, this);
162 toplevel
->throwError(kScriptTimeoutError
);
165 void ShellCore::initShellPool()
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
);
176 shellPool
= AVM_INIT_BUILTIN_ABC(shell_toplevel
, this);
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(),
195 Toplevel
* ShellCore::createToplevel(AbcEnv
* abcEnv
)
197 return new (GetGC()) ShellToplevel(abcEnv
);
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
216 String
* ShellCore::decodeBytesAsUTF16String(uint8_t* bytes
, uint32_t nbytes
, bool terminate
)
218 String
* s
= newStringUTF8((const char*)bytes
, nbytes
);
220 s
= newStringLatin1((const char*)bytes
, nbytes
);
222 s
= s
->appendLatin1("\0", 1);
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
232 int32_t x
= referencingFile
->lastIndexOf(newStringLatin1("/"));
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
)
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);
252 // input is always NUL-terminated
253 void ShellCore::evaluateString(String
* input
, bool record_time
)
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;
266 then
= VMPI_getDate();
267 uint32_t api
= this->getAPI(NULL
);
268 Atom result
= handleActionSource(input
, NULL
, shell_domainEnv
, shell_toplevel
, NULL
, codeContext
, api
);
270 now
= VMPI_getDate();
271 if (result
!= undefinedAtom
)
272 console
<< string(result
) << "\n";
274 console
<< "Elapsed time: " << (now
- then
)/1000 << "s\n";
276 CATCH(Exception
*exception
)
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';
287 console
<< string(exception
->atom
) << "\n";
296 #ifdef AVMSHELL_PROJECTOR_SUPPORT
298 // Run a known projector file
299 int ShellCore::executeProjector(char *executablePath
)
301 AvmAssert(isValidProjectorFile(executablePath
));
305 FileInputStream
file(executablePath
);
307 file
.seek(file
.length() - 8);
308 file
.read(header
, 8);
310 int abcLength
= (header
[4] |
315 ScriptBuffer code
= newScriptBuffer(abcLength
);
316 file
.seek(file
.length() - 8 - abcLength
);
317 file
.read(code
.getBuffer(), abcLength
);
319 return handleArbitraryExecutableContent(code
, executablePath
);
323 bool ShellCore::isValidProjectorFile(const char* filename
)
325 FileInputStream
file(filename
);
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)
341 #endif // AVMSHELL_PROJECTOR_SUPPORT
343 #ifdef VMCFG_SELFTEST
344 void ShellCore::executeSelftest(ShellCoreSettings
& settings
)
347 ::selftests(this, settings
.st_component
, settings
.st_category
, settings
.st_name
);
351 bool ShellCore::setup(ShellCoreSettings
& settings
)
353 // set the default api version
354 if (settings
.api
<= _max_version_num
) {
355 this->defaultAPIVersion
= settings
.api
;
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];
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
;
375 config
.jitordie
= settings
.jitordie
;
376 #if defined FEATURE_NANOJIT
377 config
.njconfig
= settings
.njconfig
;
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()
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;
391 // debugger in avmshell always enables methodnames.
393 config
.methodNames
= true;
395 #endif // VMCFG_METHOD_NAMES
398 langID
= settings
.langID
;
401 TRY(this, kCatchAction_ReportAsError
)
405 allowDebugger
= !settings
.nodebugger
;
407 setCacheSizes(settings
.cacheSizes
);
409 SystemClass::user_argc
= settings
.numargs
;
410 SystemClass::user_argv
= settings
.arguments
;
413 initBuiltinPool((avmplus::Debugger::TraceLevel
)settings
.astrace_console
);
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
433 CATCH(Exception
*exception
)
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';
442 // [ed] always show error, even in release mode,
444 console
<< string(exception
->atom
) << "\n";
445 #endif /* DEBUGGER */
452 int ShellCore::evaluateFile(ShellCoreSettings
& settings
, const char* filename
)
455 ScriptBuffer dummyScriptBuffer
;
456 return handleArbitraryExecutableContent(dummyScriptBuffer
, NULL
);
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";
467 FileInputStream
f(filename
);
468 bool isValid
= f
.valid() && ((uint64_t)f
.length() < UINT32_T_MAX
); //currently we cannot read files > 4GB
470 console
<< "cannot open file: " << filename
<< "\n";
474 // parse new bytecode
475 ScriptBuffer code
= newScriptBuffer((size_t)f
.available());
476 f
.read(code
.getBuffer(), (size_t)f
.available());
479 if (settings
.enter_debugger_on_launch
)
481 // Activate the debug CLI and stop at
483 debugCLI()->activate();
484 debugCLI()->stepInto();
487 // placate MSVC - settings is unreferenced if this is not here
488 (void)settings
.enter_debugger_on_launch
;
491 return handleArbitraryExecutableContent(code
, filename
);
494 int ShellCore::handleArbitraryExecutableContent(ScriptBuffer
& code
, const char * filename
)
498 TRY(this, kCatchAction_ReportAsError
)
500 ShellCodeContext
* codeContext
= new (GetGC()) ShellCodeContext();
501 codeContext
->m_domainEnv
= shell_domainEnv
;
504 if (filename
== NULL
) {
505 handleAOT(this, shell_domain
, shell_domainEnv
, shell_toplevel
, codeContext
);
508 if (AbcParser::canParse(code
) == 0) {
509 #ifdef VMCFG_VERIFYALL
510 if (config
.verbose_vb
& VB_verify
)
511 console
<< "ABC " << filename
<< "\n";
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";
521 handleSwf(filename
, code
, shell_domainEnv
, shell_toplevel
, codeContext
);
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
);
536 console
<< "unknown input format in file: " << filename
<< "\n";
541 CATCH(Exception
*exception
)
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';
552 // [ed] always show error, even in release mode,
554 console
<< string(exception
->atom
) << "\n";
555 #endif /* DEBUGGER */